1 #include "sounds.h"
2
3 #include <algorithm>
4 #include <chrono>
5 #include <cmath>
6 #include <cstdlib>
7 #include <memory>
8 #include <type_traits>
9 #include <unordered_map>
10
11 #include "activity_type.h"
12 #include "cached_options.h" // IWYU pragma: keep
13 #include "calendar.h"
14 #include "character.h"
15 #include "coordinate_conversions.h"
16 #include "coordinates.h"
17 #include "debug.h"
18 #include "effect.h"
19 #include "enums.h"
20 #include "game.h"
21 #include "game_constants.h"
22 #include "itype.h" // IWYU pragma: keep
23 #include "line.h"
24 #include "make_static.h"
25 #include "map.h"
26 #include "map_iterator.h"
27 #include "messages.h"
28 #include "monster.h"
29 #include "npc.h"
30 #include "overmapbuffer.h"
31 #include "player.h"
32 #include "player_activity.h"
33 #include "point.h"
34 #include "rng.h"
35 #include "safemode_ui.h"
36 #include "string_formatter.h"
37 #include "translations.h"
38 #include "type_id.h"
39 #include "units.h"
40 #include "veh_type.h" // IWYU pragma: keep
41 #include "vehicle.h"
42 #include "vpart_position.h"
43 #include "weather.h"
44 #include "weather_type.h"
45
46 #if defined(SDL_SOUND)
47 # if defined(_MSC_VER) && defined(USE_VCPKG)
48 # include <SDL2/SDL_mixer.h>
49 # else
50 # include <SDL_mixer.h>
51 # endif
52 # include <thread>
53 # if defined(_WIN32) && !defined(_MSC_VER)
54 # include "mingw.thread.h"
55 # endif
56
57 # define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": "
58
59 static int prev_hostiles = 0;
60 static int previous_speed = 0;
61 static int previous_gear = 0;
62 static bool audio_muted = false;
63 #endif
64
65 static weather_type_id previous_weather;
66 static float g_sfx_volume_multiplier = 1.0f;
67 static auto start_sfx_timestamp = std::chrono::high_resolution_clock::now();
68 static auto end_sfx_timestamp = std::chrono::high_resolution_clock::now();
69 static auto sfx_time = end_sfx_timestamp - start_sfx_timestamp;
70 static activity_id act;
71 static std::pair<std::string, std::string> engine_external_id_and_variant;
72
73 static const efftype_id effect_alarm_clock( "alarm_clock" );
74 static const efftype_id effect_deaf( "deaf" );
75 static const efftype_id effect_narcosis( "narcosis" );
76 static const efftype_id effect_sleep( "sleep" );
77 static const efftype_id effect_slept_through_alarm( "slept_through_alarm" );
78
79 static const trait_id trait_HEAVYSLEEPER2( "HEAVYSLEEPER2" );
80 static const trait_id trait_HEAVYSLEEPER( "HEAVYSLEEPER" );
81
82 static const itype_id fuel_type_muscle( "muscle" );
83 static const itype_id fuel_type_wind( "wind" );
84 static const itype_id fuel_type_battery( "battery" );
85
86 static const itype_id itype_weapon_fire_suppressed( "weapon_fire_suppressed" );
87
88 struct sound_event {
89 int volume;
90 sounds::sound_t category;
91 std::string description;
92 bool ambient;
93 bool footstep;
94 std::string id;
95 std::string variant;
96 };
97
98 struct centroid {
99 // Values have to be floats to prevent rounding errors.
100 float x;
101 float y;
102 float z;
103 float volume;
104 float weight;
105 };
106
107 namespace io
108 {
109 // *INDENT-OFF*
110 template<>
enum_to_string(sounds::sound_t data)111 std::string enum_to_string<sounds::sound_t>( sounds::sound_t data )
112 {
113 switch ( data ) {
114 case sounds::sound_t::background: return "background";
115 case sounds::sound_t::weather: return "weather";
116 case sounds::sound_t::music: return "music";
117 case sounds::sound_t::movement: return "movement";
118 case sounds::sound_t::speech: return "speech";
119 case sounds::sound_t::electronic_speech: return "electronic_speech";
120 case sounds::sound_t::activity: return "activity";
121 case sounds::sound_t::destructive_activity: return "destructive_activity";
122 case sounds::sound_t::alarm: return "alarm";
123 case sounds::sound_t::combat: return "combat";
124 case sounds::sound_t::alert: return "alert";
125 case sounds::sound_t::order: return "order";
126 case sounds::sound_t::_LAST: break;
127 }
128 debugmsg( "Invalid valid_target" );
129 abort();
130 }
131 // *INDENT-ON*
132 } // namespace io
133
134 // Static globals tracking sounds events of various kinds.
135 // The sound events since the last monster turn.
136 static std::vector<std::pair<tripoint, int>> recent_sounds;
137 // The sound events since the last interactive player turn. (doesn't count sleep etc)
138 static std::vector<std::pair<tripoint, sound_event>> sounds_since_last_turn;
139 // The sound events currently displayed to the player.
140 static std::unordered_map<tripoint, sound_event> sound_markers;
141
142 // This is an attempt to handle attenuation of sound for underground areas.
143 // The main issue it adresses is that you can hear activity
144 // relatively deep underground while on the surface.
145 // My research indicates that attenuation through soil-like materials is as
146 // high as 100x the attenuation through air, plus vertical distances are
147 // roughly five times as large as horizontal ones.
sound_distance(const tripoint & source,const tripoint & sink)148 static int sound_distance( const tripoint &source, const tripoint &sink )
149 {
150 const int lower_z = std::min( source.z, sink.z );
151 const int upper_z = std::max( source.z, sink.z );
152 const int vertical_displacement = upper_z - lower_z;
153 int vertical_attenuation = vertical_displacement;
154 if( lower_z < 0 && vertical_displacement > 0 ) {
155 // Apply a moderate bonus attenuation (5x) for the first level of vertical displacement.
156 vertical_attenuation += 4;
157 // At displacements greater than one, apply a large additional attenuation (100x) per level.
158 const int underground_displacement = std::min( -lower_z, vertical_displacement );
159 vertical_attenuation += ( underground_displacement - 1 ) * 20;
160 }
161 // Regardless of underground effects, scale the vertical distance by 5x.
162 vertical_attenuation *= 5;
163 return rl_dist( source.xy(), sink.xy() ) + vertical_attenuation;
164 }
165
ambient_sound(const tripoint & p,int vol,sound_t category,const std::string & description)166 void sounds::ambient_sound( const tripoint &p, int vol, sound_t category,
167 const std::string &description )
168 {
169 sound( p, vol, category, description, true );
170 }
171
sound(const tripoint & p,int vol,sound_t category,const std::string & description,bool ambient,const std::string & id,const std::string & variant)172 void sounds::sound( const tripoint &p, int vol, sound_t category, const std::string &description,
173 bool ambient, const std::string &id, const std::string &variant )
174 {
175 if( vol < 0 ) {
176 // Bail out if no volume.
177 debugmsg( "negative sound volume %d", vol );
178 return;
179 }
180 // Description is not an optional parameter
181 if( description.empty() ) {
182 debugmsg( "Sound at %d:%d has no description!", p.x, p.y );
183 }
184 recent_sounds.emplace_back( std::make_pair( p, vol ) );
185 sounds_since_last_turn.emplace_back( std::make_pair( p,
186 sound_event {vol, category, description, ambient,
187 false, id, variant} ) );
188 }
189
sound(const tripoint & p,int vol,sound_t category,const translation & description,bool ambient,const std::string & id,const std::string & variant)190 void sounds::sound( const tripoint &p, int vol, sound_t category, const translation &description,
191 bool ambient, const std::string &id, const std::string &variant )
192 {
193 sounds::sound( p, vol, category, description.translated(), ambient, id, variant );
194 }
195
add_footstep(const tripoint & p,int volume,int,monster *,const std::string & footstep)196 void sounds::add_footstep( const tripoint &p, int volume, int, monster *,
197 const std::string &footstep )
198 {
199 sounds_since_last_turn.emplace_back( std::make_pair( p, sound_event { volume,
200 sound_t::movement, footstep, false, true, "", ""} ) );
201 }
202
203 template <typename C>
vector_quick_remove(std::vector<C> & source,int index)204 static void vector_quick_remove( std::vector<C> &source, int index )
205 {
206 if( source.size() != 1 ) {
207 // Swap the target and the last element of the vector.
208 // This scrambles the vector, but makes removal O(1).
209 std::iter_swap( source.begin() + index, source.end() - 1 );
210 }
211 source.pop_back();
212 }
213
cluster_sounds(std::vector<std::pair<tripoint,int>> input_sounds)214 static std::vector<centroid> cluster_sounds( std::vector<std::pair<tripoint, int>> input_sounds )
215 {
216 // If there are too many monsters and too many noise sources (which can be monsters, go figure),
217 // applying sound events to monsters can dominate processing time for the whole game,
218 // so we cluster sounds and apply the centroids of the sounds to the monster AI
219 // to fight the combinatorial explosion.
220 std::vector<centroid> sound_clusters;
221 if( input_sounds.empty() ) {
222 return sound_clusters;
223 }
224 const int num_seed_clusters =
225 std::max( std::min( input_sounds.size(), static_cast<size_t>( 10 ) ),
226 static_cast<size_t>( std::log( input_sounds.size() ) ) );
227 const size_t stopping_point = input_sounds.size() - num_seed_clusters;
228 const size_t max_map_distance = sound_distance( tripoint( point_zero, OVERMAP_DEPTH ),
229 tripoint( MAPSIZE_X, MAPSIZE_Y, OVERMAP_HEIGHT ) );
230 // Randomly choose cluster seeds.
231 for( size_t i = input_sounds.size(); i > stopping_point; i-- ) {
232 size_t index = rng( 0, i - 1 );
233 // The volume and cluster weight are the same for the first element.
234 sound_clusters.push_back(
235 // Assure the compiler that these int->float conversions are safe.
236 {
237 static_cast<float>( input_sounds[index].first.x ), static_cast<float>( input_sounds[index].first.y ),
238 static_cast<float>( input_sounds[index].first.z ),
239 static_cast<float>( input_sounds[index].second ), static_cast<float>( input_sounds[index].second )
240 } );
241 vector_quick_remove( input_sounds, index );
242 }
243 for( const auto &sound_event_pair : input_sounds ) {
244 auto found_centroid = sound_clusters.begin();
245 float dist_factor = max_map_distance;
246 const auto cluster_end = sound_clusters.end();
247 for( auto centroid_iter = sound_clusters.begin(); centroid_iter != cluster_end;
248 ++centroid_iter ) {
249 // Scale the distance between the two by the max possible distance.
250 tripoint centroid_pos { static_cast<int>( centroid_iter->x ), static_cast<int>( centroid_iter->y ), static_cast<int>( centroid_iter->z ) };
251 const int dist = sound_distance( sound_event_pair.first, centroid_pos );
252 if( dist * dist < dist_factor ) {
253 found_centroid = centroid_iter;
254 dist_factor = dist * dist;
255 }
256 }
257 const float volume_sum = static_cast<float>( sound_event_pair.second ) + found_centroid->weight;
258 // Set the centroid location to the average of the two locations, weighted by volume.
259 found_centroid->x = static_cast<float>( ( sound_event_pair.first.x * sound_event_pair.second ) +
260 ( found_centroid->x * found_centroid->weight ) ) / volume_sum;
261 found_centroid->y = static_cast<float>( ( sound_event_pair.first.y * sound_event_pair.second ) +
262 ( found_centroid->y * found_centroid->weight ) ) / volume_sum;
263 found_centroid->z = static_cast<float>( ( sound_event_pair.first.z * sound_event_pair.second ) +
264 ( found_centroid->z * found_centroid->weight ) ) / volume_sum;
265 // Set the centroid volume to the larger of the volumes.
266 found_centroid->volume = std::max( found_centroid->volume,
267 static_cast<float>( sound_event_pair.second ) );
268 // Set the centroid weight to the sum of the weights.
269 found_centroid->weight = volume_sum;
270 }
271 return sound_clusters;
272 }
273
get_signal_for_hordes(const centroid & centr)274 static int get_signal_for_hordes( const centroid ¢r )
275 {
276 //Volume in tiles. Signal for hordes in submaps
277 //modify vol using weather vol.Weather can reduce monster hearing
278 const int vol = centr.volume - get_weather().weather_id->sound_attn;
279 const int min_vol_cap = 60; //Hordes can't hear volume lower than this
280 const int underground_div = 2; //Coefficient for volume reduction underground
281 const int hordes_sig_div = SEEX; //Divider coefficient for hordes
282 const int min_sig_cap = 8; //Signal for hordes can't be lower that this if it pass min_vol_cap
283 const int max_sig_cap = 26; //Signal for hordes can't be higher that this
284 //Lower the level - lower the sound
285 int vol_hordes = ( ( centr.z < 0 ) ? vol / ( underground_div * std::abs( centr.z ) ) : vol );
286 if( vol_hordes > min_vol_cap ) {
287 //Calculating horde hearing signal
288 int sig_power = std::ceil( static_cast<float>( vol_hordes ) / hordes_sig_div );
289 //Capping minimum horde hearing signal
290 sig_power = std::max( sig_power, min_sig_cap );
291 //Capping extremely high signal to hordes
292 sig_power = std::min( sig_power, max_sig_cap );
293 add_msg_debug( "vol %d vol_hordes %d sig_power %d ", vol, vol_hordes, sig_power );
294 return sig_power;
295 }
296 return 0;
297 }
298
process_sounds()299 void sounds::process_sounds()
300 {
301 std::vector<centroid> sound_clusters = cluster_sounds( recent_sounds );
302 const int weather_vol = get_weather().weather_id->sound_attn;
303 for( const auto &this_centroid : sound_clusters ) {
304 // Since monsters don't go deaf ATM we can just use the weather modified volume
305 // If they later get physical effects from loud noises we'll have to change this
306 // to use the unmodified volume for those effects.
307 const int vol = this_centroid.volume - weather_vol;
308 const tripoint source = tripoint( this_centroid.x, this_centroid.y, this_centroid.z );
309 // --- Monster sound handling here ---
310 // Alert all hordes
311 int sig_power = get_signal_for_hordes( this_centroid );
312 if( sig_power > 0 ) {
313
314 const point abs_ms = get_map().getabs( source.xy() );
315 // TODO: fix point types
316 const point_abs_sm abs_sm( ms_to_sm_copy( abs_ms ) );
317 const tripoint_abs_sm target( abs_sm, source.z );
318 overmap_buffer.signal_hordes( target, sig_power );
319 }
320 // Alert all monsters (that can hear) to the sound.
321 for( monster &critter : g->all_monsters() ) {
322 // TODO: Generalize this to Creature::hear_sound
323 const int dist = sound_distance( source, critter.pos() );
324 if( vol * 2 > dist ) {
325 // Exclude monsters that certainly won't hear the sound
326 critter.hear_sound( source, vol, dist );
327 }
328 }
329 }
330 recent_sounds.clear();
331 }
332
333 // skip some sounds to avoid message spam
describe_sound(sounds::sound_t category,bool from_player_position)334 static bool describe_sound( sounds::sound_t category, bool from_player_position )
335 {
336 if( from_player_position ) {
337 switch( category ) {
338 case sounds::sound_t::_LAST:
339 debugmsg( "ERROR: Incorrect sound category" );
340 return false;
341 case sounds::sound_t::background:
342 case sounds::sound_t::weather:
343 case sounds::sound_t::music:
344 // detailed music descriptions are printed in iuse::play_music
345 case sounds::sound_t::movement:
346 case sounds::sound_t::activity:
347 case sounds::sound_t::destructive_activity:
348 case sounds::sound_t::combat:
349 case sounds::sound_t::alert:
350 case sounds::sound_t::order:
351 case sounds::sound_t::speech:
352 return false;
353 case sounds::sound_t::electronic_speech:
354 case sounds::sound_t::alarm:
355 return true;
356 }
357 } else {
358 switch( category ) {
359 case sounds::sound_t::background:
360 case sounds::sound_t::weather:
361 case sounds::sound_t::music:
362 case sounds::sound_t::movement:
363 case sounds::sound_t::activity:
364 case sounds::sound_t::destructive_activity:
365 return one_in( 100 );
366 case sounds::sound_t::speech:
367 case sounds::sound_t::electronic_speech:
368 case sounds::sound_t::alarm:
369 case sounds::sound_t::combat:
370 case sounds::sound_t::alert:
371 case sounds::sound_t::order:
372 return true;
373 case sounds::sound_t::_LAST:
374 debugmsg( "ERROR: Incorrect sound category" );
375 return false;
376 }
377 }
378 return true;
379 }
380
process_sound_markers(player * p)381 void sounds::process_sound_markers( player *p )
382 {
383 bool is_deaf = p->is_deaf();
384 const float volume_multiplier = p->hearing_ability();
385 const int weather_vol = get_weather().weather_id->sound_attn;
386 // NOLINTNEXTLINE(modernize-loop-convert)
387 for( std::size_t i = 0; i < sounds_since_last_turn.size(); i++ ) {
388 // copy values instead of making references here to fix use-after-free error
389 // sounds_since_last_turn may be inserted with new elements inside the loop
390 // so the references may become invalid after the vector enlarged its internal buffer
391 const tripoint pos = sounds_since_last_turn[i].first;
392 const sound_event sound = sounds_since_last_turn[i].second;
393 const int distance_to_sound = sound_distance( p->pos(), pos );
394 const int raw_volume = sound.volume;
395
396 // The felt volume of a sound is not affected by negative multipliers, such as already
397 // deafened players or players with sub-par hearing to begin with.
398 const int felt_volume = static_cast<int>( raw_volume * std::min( 1.0f,
399 volume_multiplier ) ) - distance_to_sound;
400
401 // Deafening is based on the felt volume, as a player may be too deaf to
402 // hear the deafening sound but still suffer additional hearing loss.
403 const bool is_sound_deafening = rng( felt_volume / 2, felt_volume ) >= 150;
404
405 // Deaf players hear no sound, but still are at risk of additional hearing loss.
406 if( is_deaf ) {
407 if( is_sound_deafening && !p->is_immune_effect( effect_deaf ) ) {
408 p->add_effect( effect_deaf, std::min( 4_minutes,
409 time_duration::from_turns( felt_volume - 130 ) / 8 ) );
410 if( !p->has_trait( trait_id( "NOPAIN" ) ) ) {
411 p->add_msg_if_player( m_bad, _( "Your eardrums suddenly ache!" ) );
412 if( p->get_pain() < 10 ) {
413 p->mod_pain( rng( 0, 2 ) );
414 }
415 }
416 }
417 continue;
418 }
419
420 if( is_sound_deafening && !p->is_immune_effect( effect_deaf ) ) {
421 const time_duration deafness_duration = time_duration::from_turns( felt_volume - 130 ) / 4;
422 p->add_effect( effect_deaf, deafness_duration );
423 if( p->is_deaf() && !is_deaf ) {
424 is_deaf = true;
425 continue;
426 }
427 }
428
429 // The heard volume of a sound is the player heard volume, regardless of true volume level.
430 const int heard_volume = static_cast<int>( ( raw_volume - weather_vol ) *
431 volume_multiplier ) - distance_to_sound;
432
433 if( heard_volume <= 0 && pos != p->pos() ) {
434 continue;
435 }
436
437 // Player volume meter includes all sounds from their tile and adjacent tiles
438 if( distance_to_sound <= 1 ) {
439 p->volume = std::max( p->volume, heard_volume );
440 }
441
442 // Noises from vehicle player is in.
443 if( p->controlling_vehicle ) {
444 vehicle *veh = veh_pointer_or_null( get_map().veh_at( p->pos() ) );
445 const int noise = veh ? static_cast<int>( veh->vehicle_noise ) : 0;
446
447 p->volume = std::max( p->volume, noise );
448 }
449
450 // Secure the flag before wake_up() clears the effect
451 bool slept_through = p->has_effect( effect_slept_through_alarm );
452 // See if we need to wake someone up
453 if( p->has_effect( effect_sleep ) ) {
454 if( ( ( !( p->has_trait( trait_HEAVYSLEEPER ) ||
455 p->has_trait( trait_HEAVYSLEEPER2 ) ) && dice( 2, 15 ) < heard_volume ) ||
456 ( p->has_trait( trait_HEAVYSLEEPER ) && dice( 3, 15 ) < heard_volume ) ||
457 ( p->has_trait( trait_HEAVYSLEEPER2 ) && dice( 6, 15 ) < heard_volume ) ) &&
458 !p->has_effect( effect_narcosis ) ) {
459 //Not kidding about sleep-through-firefight
460 p->wake_up();
461 add_msg( m_warning, _( "Something is making noise." ) );
462 } else {
463 continue;
464 }
465 }
466 const std::string &description = sound.description.empty() ? _( "a noise" ) : sound.description;
467 if( p->is_npc() ) {
468 if( !sound.ambient ) {
469 npc *guy = dynamic_cast<npc *>( p );
470 guy->handle_sound( sound.category, description, heard_volume, pos );
471 }
472 continue;
473 }
474
475 // don't print our own noise or things without descriptions
476 if( !sound.ambient && ( pos != p->pos() ) && !get_map().pl_sees( pos, distance_to_sound ) ) {
477 if( !p->activity.is_distraction_ignored( distraction_type::noise ) &&
478 !get_safemode().is_sound_safe( sound.description, distance_to_sound ) ) {
479 const std::string query = string_format( _( "Heard %s!" ), description );
480 g->cancel_activity_or_ignore_query( distraction_type::noise, query );
481 }
482 }
483
484 // skip some sounds to avoid message spam
485 if( describe_sound( sound.category, pos == p->pos() ) ) {
486 game_message_type severity = m_info;
487 if( sound.category == sound_t::combat || sound.category == sound_t::alarm ) {
488 severity = m_warning;
489 }
490 // if we can see it, don't print a direction
491 if( pos == p->pos() ) {
492 add_msg( severity, _( "From your position you hear %1$s" ), description );
493 } else if( p->sees( pos ) ) {
494 add_msg( severity, _( "You hear %1$s" ), description );
495 } else {
496 std::string direction = direction_name( direction_from( p->pos(), pos ) );
497 add_msg( severity, _( "From the %1$s you hear %2$s" ), direction, description );
498 }
499 }
500
501 if( !p->has_effect( effect_sleep ) && p->has_effect( effect_alarm_clock ) &&
502 !p->has_flag( STATIC( json_character_flag( "ALARMCLOCK" ) ) ) ) {
503 // if we don't have effect_sleep but we're in_sleep_state, either
504 // we were trying to fall asleep for so long our alarm is now going
505 // off or something disturbed us while trying to sleep
506 const bool trying_to_sleep = p->in_sleep_state();
507 if( p->get_effect( effect_alarm_clock ).get_duration() == 1_turns ) {
508 if( slept_through ) {
509 add_msg( _( "Your alarm clock finally wakes you up." ) );
510 } else if( !trying_to_sleep ) {
511 add_msg( _( "Your alarm clock wakes you up." ) );
512 } else {
513 add_msg( _( "Your alarm clock goes off and you haven't slept a wink." ) );
514 p->activity.set_to_null();
515 }
516 add_msg( _( "You turn off your alarm-clock." ) );
517 p->get_effect( effect_alarm_clock ).set_duration( 0_turns );
518 }
519 }
520
521 const std::string &sfx_id = sound.id;
522 const std::string &sfx_variant = sound.variant;
523 if( !sfx_id.empty() ) {
524 sfx::play_variant_sound( sfx_id, sfx_variant, sfx::get_heard_volume( pos ) );
525 }
526
527 // Place footstep markers.
528 if( pos == p->pos() || p->sees( pos ) ) {
529 // If we are or can see the source, don't draw a marker.
530 continue;
531 }
532
533 int err_offset;
534 if( ( heard_volume + distance_to_sound ) / distance_to_sound < 2 ) {
535 err_offset = 3;
536 } else if( ( heard_volume + distance_to_sound ) / distance_to_sound < 3 ) {
537 err_offset = 2;
538 } else {
539 err_offset = 1;
540 }
541
542 // If Z-coordinate is different, draw even when you can see the source
543 const bool diff_z = pos.z != p->posz();
544
545 // Enumerate the valid points the player *cannot* see.
546 // Unless the source is on a different z-level, then any point is fine
547 std::vector<tripoint> unseen_points;
548 for( const tripoint &newp : get_map().points_in_radius( pos, err_offset ) ) {
549 if( diff_z || !p->sees( newp ) ) {
550 unseen_points.emplace_back( newp );
551 }
552 }
553
554 // Then place the sound marker in a random one.
555 if( !unseen_points.empty() ) {
556 sound_markers.emplace( random_entry( unseen_points ), sound );
557 }
558 }
559 if( p->is_player() ) {
560 sounds_since_last_turn.clear();
561 }
562 }
563
reset_sounds()564 void sounds::reset_sounds()
565 {
566 recent_sounds.clear();
567 sounds_since_last_turn.clear();
568 sound_markers.clear();
569 }
570
reset_markers()571 void sounds::reset_markers()
572 {
573 sound_markers.clear();
574 }
575
get_footstep_markers()576 std::vector<tripoint> sounds::get_footstep_markers()
577 {
578 // Optimization, make this static and clear it in reset_markers?
579 std::vector<tripoint> footsteps;
580 footsteps.reserve( sound_markers.size() );
581 for( const auto &mark : sound_markers ) {
582 footsteps.push_back( mark.first );
583 }
584 return footsteps;
585 }
586
get_monster_sounds()587 std::pair<std::vector<tripoint>, std::vector<tripoint>> sounds::get_monster_sounds()
588 {
589 auto sound_clusters = cluster_sounds( recent_sounds );
590 std::vector<tripoint> sound_locations;
591 sound_locations.reserve( recent_sounds.size() );
592 for( const auto &sound : recent_sounds ) {
593 sound_locations.push_back( sound.first );
594 }
595 std::vector<tripoint> cluster_centroids;
596 cluster_centroids.reserve( sound_clusters.size() );
597 for( const auto &sound : sound_clusters ) {
598 cluster_centroids.emplace_back( static_cast<int>( sound.x ), static_cast<int>( sound.y ),
599 static_cast<int>( sound.z ) );
600 }
601 return { sound_locations, cluster_centroids };
602 }
603
sound_at(const tripoint & location)604 std::string sounds::sound_at( const tripoint &location )
605 {
606 auto this_sound = sound_markers.find( location );
607 if( this_sound == sound_markers.end() ) {
608 return std::string();
609 }
610 if( !this_sound->second.description.empty() ) {
611 return this_sound->second.description;
612 }
613 return _( "a sound" );
614 }
615
616 #if defined(SDL_SOUND)
fade_audio_group(group group,int duration)617 void sfx::fade_audio_group( group group, int duration )
618 {
619 if( test_mode ) {
620 return;
621 }
622 Mix_FadeOutGroup( static_cast<int>( group ), duration );
623 }
624
fade_audio_channel(channel channel,int duration)625 void sfx::fade_audio_channel( channel channel, int duration )
626 {
627 if( test_mode ) {
628 return;
629 }
630 Mix_FadeOutChannel( static_cast<int>( channel ), duration );
631 }
632
is_channel_playing(channel channel)633 bool sfx::is_channel_playing( channel channel )
634 {
635 if( test_mode ) {
636 return false;
637 }
638 return Mix_Playing( static_cast<int>( channel ) ) != 0;
639 }
640
stop_sound_effect_fade(channel channel,int duration)641 void sfx::stop_sound_effect_fade( channel channel, int duration )
642 {
643 if( test_mode ) {
644 return;
645 }
646 if( Mix_FadeOutChannel( static_cast<int>( channel ), duration ) == -1 ) {
647 dbg( D_ERROR ) << "Failed to stop sound effect: " << Mix_GetError();
648 }
649 }
650
stop_sound_effect_timed(channel channel,int time)651 void sfx::stop_sound_effect_timed( channel channel, int time )
652 {
653 if( test_mode ) {
654 return;
655 }
656 Mix_ExpireChannel( static_cast<int>( channel ), time );
657 }
658
set_channel_volume(channel channel,int volume)659 int sfx::set_channel_volume( channel channel, int volume )
660 {
661 if( test_mode ) {
662 return 0;
663 }
664 int ch = static_cast<int>( channel );
665 if( !Mix_Playing( ch ) ) {
666 return -1;
667 }
668 if( Mix_FadingChannel( ch ) != MIX_NO_FADING ) {
669 return -1;
670 }
671 return Mix_Volume( ch, volume );
672 }
673
do_vehicle_engine_sfx()674 void sfx::do_vehicle_engine_sfx()
675 {
676 if( test_mode ) {
677 return;
678 }
679
680 static const channel ch = channel::interior_engine_sound;
681 const Character &player_character = get_player_character();
682 if( !player_character.in_vehicle ) {
683 fade_audio_channel( ch, 300 );
684 add_msg_debug( "STOP interior_engine_sound, OUT OF CAR" );
685 return;
686 }
687 if( player_character.in_sleep_state() && !audio_muted ) {
688 fade_audio_channel( channel::any, 300 );
689 audio_muted = true;
690 return;
691 } else if( player_character.in_sleep_state() && audio_muted ) {
692 return;
693 }
694 optional_vpart_position vpart_opt = get_map().veh_at( player_character.pos() );
695 vehicle *veh;
696 if( vpart_opt.has_value() ) {
697 veh = &vpart_opt->vehicle();
698 } else {
699 return;
700 }
701 if( !veh->engine_on ) {
702 fade_audio_channel( ch, 100 );
703 add_msg_debug( "STOP interior_engine_sound" );
704 return;
705 }
706
707 std::pair<std::string, std::string> id_and_variant;
708
709 for( size_t e = 0; e < veh->engines.size(); ++e ) {
710 if( veh->is_engine_on( e ) ) {
711 if( sfx::has_variant_sound( "engine_working_internal",
712 veh->part_info( veh->engines[ e ] ).get_id().str() ) ) {
713 id_and_variant = std::make_pair( "engine_working_internal",
714 veh->part_info( veh->engines[ e ] ).get_id().str() );
715 } else if( veh->is_engine_type( e, fuel_type_muscle ) ) {
716 id_and_variant = std::make_pair( "engine_working_internal", "muscle" );
717 } else if( veh->is_engine_type( e, fuel_type_wind ) ) {
718 id_and_variant = std::make_pair( "engine_working_internal", "wind" );
719 } else if( veh->is_engine_type( e, fuel_type_battery ) ) {
720 id_and_variant = std::make_pair( "engine_working_internal", "electric" );
721 } else {
722 id_and_variant = std::make_pair( "engine_working_internal", "combustion" );
723 }
724 }
725 }
726
727 if( !is_channel_playing( ch ) ) {
728 play_ambient_variant_sound( id_and_variant.first, id_and_variant.second,
729 sfx::get_heard_volume( player_character.pos() ), ch, 1000 );
730 add_msg_debug( "START %s %s", id_and_variant.first, id_and_variant.second );
731 } else {
732 add_msg_debug( "PLAYING" );
733 }
734 int current_speed = veh->velocity;
735 bool in_reverse = false;
736 if( current_speed <= -1 ) {
737 current_speed = current_speed * -1;
738 in_reverse = true;
739 }
740 double pitch = 1.0;
741 int safe_speed = veh->safe_velocity();
742 int current_gear;
743 if( in_reverse ) {
744 current_gear = -1;
745 } else if( current_speed == 0 ) {
746 current_gear = 0;
747 } else if( current_speed > 0 && current_speed <= safe_speed / 12 ) {
748 current_gear = 1;
749 } else if( current_speed > safe_speed / 12 && current_speed <= safe_speed / 5 ) {
750 current_gear = 2;
751 } else if( current_speed > safe_speed / 5 && current_speed <= safe_speed / 4 ) {
752 current_gear = 3;
753 } else if( current_speed > safe_speed / 4 && current_speed <= safe_speed / 3 ) {
754 current_gear = 4;
755 } else if( current_speed > safe_speed / 3 && current_speed <= safe_speed / 2 ) {
756 current_gear = 5;
757 } else {
758 current_gear = 6;
759 }
760 if( veh->has_engine_type( fuel_type_muscle, true ) ||
761 veh->has_engine_type( fuel_type_wind, true ) ) {
762 current_gear = previous_gear;
763 }
764
765 if( current_gear > previous_gear ) {
766 play_variant_sound( "vehicle", "gear_shift", get_heard_volume( player_character.pos() ),
767 0_degrees, 0.8, 0.8 );
768 add_msg_debug( "GEAR UP" );
769 } else if( current_gear < previous_gear ) {
770 play_variant_sound( "vehicle", "gear_shift", get_heard_volume( player_character.pos() ),
771 0_degrees, 1.2, 1.2 );
772 add_msg_debug( "GEAR DOWN" );
773 }
774 if( ( safe_speed != 0 ) ) {
775 if( current_gear == 0 ) {
776 pitch = 1.0;
777 } else if( current_gear == -1 ) {
778 pitch = 1.2;
779 } else {
780 pitch = 1.0 - static_cast<double>( current_speed ) / static_cast<double>( safe_speed );
781 }
782 }
783 if( pitch <= 0.5 ) {
784 pitch = 0.5;
785 }
786
787 if( current_speed != previous_speed ) {
788 Mix_HaltChannel( static_cast<int>( ch ) );
789 add_msg_debug( "STOP speed %d =/= %d", current_speed, previous_speed );
790 play_ambient_variant_sound( id_and_variant.first, id_and_variant.second,
791 sfx::get_heard_volume( player_character.pos() ), ch, 1000, pitch );
792 add_msg_debug( "PITCH %f", pitch );
793 }
794 previous_speed = current_speed;
795 previous_gear = current_gear;
796 }
797
do_vehicle_exterior_engine_sfx()798 void sfx::do_vehicle_exterior_engine_sfx()
799 {
800 if( test_mode ) {
801 return;
802 }
803
804 static const channel ch = channel::exterior_engine_sound;
805 static const int ch_int = static_cast<int>( ch );
806 const Character &player_character = get_player_character();
807 // early bail-outs for efficiency
808 if( player_character.in_vehicle ) {
809 fade_audio_channel( ch, 300 );
810 add_msg_debug( "STOP exterior_engine_sound, IN CAR" );
811 return;
812 }
813 if( player_character.in_sleep_state() && !audio_muted ) {
814 fade_audio_channel( channel::any, 300 );
815 audio_muted = true;
816 return;
817 } else if( player_character.in_sleep_state() && audio_muted ) {
818 return;
819 }
820
821 VehicleList vehs = get_map().get_vehicles();
822 unsigned char noise_factor = 0;
823 unsigned char vol = 0;
824 vehicle *veh = nullptr;
825
826 for( wrapped_vehicle vehicle : vehs ) {
827 if( vehicle.v->vehicle_noise > 0 &&
828 vehicle.v->vehicle_noise -
829 sound_distance( player_character.pos(), vehicle.v->global_pos3() ) > noise_factor ) {
830
831 noise_factor = vehicle.v->vehicle_noise - sound_distance( player_character.pos(),
832 vehicle.v->global_pos3() );
833 veh = vehicle.v;
834 }
835 }
836 if( !noise_factor || !veh ) {
837 fade_audio_channel( ch, 300 );
838 add_msg_debug( "STOP exterior_engine_sound, NO NOISE" );
839 return;
840 }
841
842 vol = MIX_MAX_VOLUME * noise_factor / veh->vehicle_noise;
843 std::pair<std::string, std::string> id_and_variant;
844
845 for( size_t e = 0; e < veh->engines.size(); ++e ) {
846 if( veh->is_engine_on( e ) ) {
847 if( sfx::has_variant_sound( "engine_working_external",
848 veh->part_info( veh->engines[ e ] ).get_id().str() ) ) {
849 id_and_variant = std::make_pair( "engine_working_external",
850 veh->part_info( veh->engines[ e ] ).get_id().str() );
851 } else if( veh->is_engine_type( e, fuel_type_muscle ) ) {
852 id_and_variant = std::make_pair( "engine_working_external", "muscle" );
853 } else if( veh->is_engine_type( e, fuel_type_wind ) ) {
854 id_and_variant = std::make_pair( "engine_working_external", "wind" );
855 } else if( veh->is_engine_type( e, fuel_type_battery ) ) {
856 id_and_variant = std::make_pair( "engine_working_external", "electric" );
857 } else {
858 id_and_variant = std::make_pair( "engine_working_external", "combustion" );
859 }
860 }
861 }
862
863 if( is_channel_playing( ch ) ) {
864 if( engine_external_id_and_variant == id_and_variant ) {
865 Mix_SetPosition( ch_int, to_degrees( get_heard_angle( veh->global_pos3() ) ), 0 );
866 set_channel_volume( ch, vol );
867 add_msg_debug( "PLAYING exterior_engine_sound, vol: ex:%d true:%d", vol, Mix_Volume( ch_int,
868 -1 ) );
869 } else {
870 engine_external_id_and_variant = id_and_variant;
871 Mix_HaltChannel( ch_int );
872 add_msg_debug( "STOP exterior_engine_sound, change id/var" );
873 play_ambient_variant_sound( id_and_variant.first, id_and_variant.second, 128, ch, 0 );
874 Mix_SetPosition( ch_int, to_degrees( get_heard_angle( veh->global_pos3() ) ), 0 );
875 set_channel_volume( ch, vol );
876 add_msg_debug( "START exterior_engine_sound %s %s vol: %d", id_and_variant.first,
877 id_and_variant.second,
878 Mix_Volume( ch_int, -1 ) );
879 }
880 } else {
881 play_ambient_variant_sound( id_and_variant.first, id_and_variant.second, 128, ch, 0 );
882 add_msg_debug( "Vol: %d %d", vol, Mix_Volume( ch_int, -1 ) );
883 Mix_SetPosition( ch_int, to_degrees( get_heard_angle( veh->global_pos3() ) ), 0 );
884 add_msg_debug( "Vol: %d %d", vol, Mix_Volume( ch_int, -1 ) );
885 set_channel_volume( ch, vol );
886 add_msg_debug( "START exterior_engine_sound NEW %s %s vol: ex:%d true:%d", id_and_variant.first,
887 id_and_variant.second, vol, Mix_Volume( ch_int, -1 ) );
888 }
889 }
890
do_ambient()891 void sfx::do_ambient()
892 {
893 if( test_mode ) {
894 return;
895 }
896
897 const Character &player_character = get_player_character();
898 if( player_character.in_sleep_state() && !audio_muted ) {
899 fade_audio_channel( channel::any, 300 );
900 audio_muted = true;
901 return;
902 } else if( player_character.in_sleep_state() && audio_muted ) {
903 return;
904 }
905 audio_muted = false;
906 const bool is_deaf = player_character.is_deaf();
907 const int heard_volume = get_heard_volume( player_character.pos() );
908 const bool is_underground = player_character.pos().z < 0;
909 const bool is_sheltered = g->is_sheltered( player_character.pos() );
910 const bool weather_changed = get_weather().weather_id != previous_weather;
911 // Step in at night time / we are not indoors
912 if( is_night( calendar::turn ) && !is_sheltered &&
913 !is_channel_playing( channel::nighttime_outdoors_env ) && !is_deaf ) {
914 fade_audio_group( group::time_of_day, 1000 );
915 play_ambient_variant_sound( "environment", "nighttime", heard_volume,
916 channel::nighttime_outdoors_env, 1000 );
917 // Step in at day time / we are not indoors
918 } else if( !is_night( calendar::turn ) && !is_channel_playing( channel::daytime_outdoors_env ) &&
919 !is_sheltered && !is_deaf ) {
920 fade_audio_group( group::time_of_day, 1000 );
921 play_ambient_variant_sound( "environment", "daytime", heard_volume, channel::daytime_outdoors_env,
922 1000 );
923 }
924 // We are underground
925 if( ( is_underground && !is_channel_playing( channel::underground_env ) &&
926 !is_deaf ) || ( is_underground &&
927 weather_changed && !is_deaf ) ) {
928 fade_audio_group( group::weather, 1000 );
929 fade_audio_group( group::time_of_day, 1000 );
930 play_ambient_variant_sound( "environment", "underground", heard_volume, channel::underground_env,
931 1000 );
932 // We are indoors
933 } else if( ( is_sheltered && !is_underground &&
934 !is_channel_playing( channel::indoors_env ) && !is_deaf ) ||
935 ( is_sheltered && !is_underground &&
936 weather_changed && !is_deaf ) ) {
937 fade_audio_group( group::weather, 1000 );
938 fade_audio_group( group::time_of_day, 1000 );
939 play_ambient_variant_sound( "environment", "indoors", heard_volume, channel::indoors_env, 1000 );
940 }
941
942 // We are indoors and it is also raining
943 if( get_weather().weather_id->rains &&
944 get_weather().weather_id->precip != precip_class::very_light &&
945 !is_underground && is_sheltered && !is_channel_playing( channel::indoors_rain_env ) ) {
946 play_ambient_variant_sound( "environment", "indoors_rain", heard_volume, channel::indoors_rain_env,
947 1000 );
948 }
949 if( ( !is_sheltered &&
950 get_weather().weather_id->sound_category != weather_sound_category::silent && !is_deaf &&
951 !is_channel_playing( channel::outdoors_snow_env ) &&
952 !is_channel_playing( channel::outdoors_flurry_env ) &&
953 !is_channel_playing( channel::outdoors_thunderstorm_env ) &&
954 !is_channel_playing( channel::outdoors_rain_env ) &&
955 !is_channel_playing( channel::outdoors_drizzle_env ) &&
956 !is_channel_playing( channel::outdoor_blizzard ) )
957 || ( !is_sheltered &&
958 weather_changed && !is_deaf ) ) {
959 fade_audio_group( group::weather, 1000 );
960 // We are outside and there is precipitation
961 switch( get_weather().weather_id->sound_category ) {
962 case weather_sound_category::drizzle:
963 play_ambient_variant_sound( "environment", "WEATHER_DRIZZLE", heard_volume,
964 channel::outdoors_drizzle_env,
965 1000 );
966 break;
967 case weather_sound_category::rainy:
968 play_ambient_variant_sound( "environment", "WEATHER_RAINY", heard_volume,
969 channel::outdoors_rain_env,
970 1000 );
971 break;
972 case weather_sound_category::thunder:
973 play_ambient_variant_sound( "environment", "WEATHER_THUNDER", heard_volume,
974 channel::outdoors_thunderstorm_env,
975 1000 );
976 break;
977 case weather_sound_category::flurries:
978 play_ambient_variant_sound( "environment", "WEATHER_FLURRIES", heard_volume,
979 channel::outdoors_flurry_env,
980 1000 );
981 break;
982 case weather_sound_category::snowstorm:
983 play_ambient_variant_sound( "environment", "WEATHER_SNOWSTORM", heard_volume,
984 channel::outdoor_blizzard,
985 1000 );
986 break;
987 case weather_sound_category::snow:
988 play_ambient_variant_sound( "environment", "WEATHER_SNOW", heard_volume, channel::outdoors_snow_env,
989 1000 );
990 break;
991 case weather_sound_category::silent:
992 break;
993 case weather_sound_category::last:
994 debugmsg( "Invalid weather sound category." );
995 break;
996 }
997 }
998 // Keep track of weather to compare for next iteration
999 previous_weather = get_weather().weather_id;
1000 }
1001
1002 // firing is the item that is fired. It may be the wielded gun, but it can also be an attached
1003 // gunmod. p is the character that is firing, this may be a pseudo-character (used by monattack/
1004 // vehicle turrets) or a NPC.
generate_gun_sound(const player & source_arg,const item & firing)1005 void sfx::generate_gun_sound( const player &source_arg, const item &firing )
1006 {
1007 if( test_mode ) {
1008 return;
1009 }
1010
1011 end_sfx_timestamp = std::chrono::high_resolution_clock::now();
1012 sfx_time = end_sfx_timestamp - start_sfx_timestamp;
1013 if( std::chrono::duration_cast<std::chrono::milliseconds> ( sfx_time ).count() < 80 ) {
1014 return;
1015 }
1016 const tripoint source = source_arg.pos();
1017 int heard_volume = get_heard_volume( source );
1018 if( heard_volume <= 30 ) {
1019 heard_volume = 30;
1020 }
1021
1022 itype_id weapon_id = firing.typeId();
1023 units::angle angle = 0_degrees;
1024 int distance = 0;
1025 std::string selected_sound;
1026 const Character &player_character = get_player_character();
1027 // this does not mean p == avatar (it could be a vehicle turret)
1028 if( player_character.pos() == source ) {
1029 selected_sound = "fire_gun";
1030
1031 const auto mods = firing.gunmods();
1032 if( std::any_of( mods.begin(), mods.end(),
1033 []( const item * e ) {
1034 return e->type->gunmod->loudness < 0;
1035 } ) ) {
1036 weapon_id = itype_weapon_fire_suppressed;
1037 }
1038
1039 } else {
1040 angle = get_heard_angle( source );
1041 distance = sound_distance( player_character.pos(), source );
1042 if( distance <= 17 ) {
1043 selected_sound = "fire_gun";
1044 } else {
1045 selected_sound = "fire_gun_distant";
1046 }
1047 }
1048
1049 play_variant_sound( selected_sound, weapon_id.str(), heard_volume, angle, 0.8, 1.2 );
1050 start_sfx_timestamp = std::chrono::high_resolution_clock::now();
1051 }
1052
1053 namespace sfx
1054 {
1055 struct sound_thread {
1056 sound_thread( const tripoint &source, const tripoint &target, bool hit, bool targ_mon,
1057 const std::string &material );
1058
1059 bool hit;
1060 bool targ_mon;
1061 std::string material;
1062
1063 skill_id weapon_skill;
1064 int weapon_volume;
1065 // volume and angle for calls to play_variant_sound
1066 units::angle ang_src;
1067 int vol_src;
1068 int vol_targ;
1069 units::angle ang_targ;
1070
1071 // Operator overload required for thread API.
1072 void operator()() const;
1073 };
1074 } // namespace sfx
1075
generate_melee_sound(const tripoint & source,const tripoint & target,bool hit,bool targ_mon,const std::string & material)1076 void sfx::generate_melee_sound( const tripoint &source, const tripoint &target, bool hit,
1077 bool targ_mon,
1078 const std::string &material )
1079 {
1080 if( test_mode ) {
1081 return;
1082 }
1083 // If creating a new thread for each invocation is to much, we have to consider a thread
1084 // pool or maybe a single thread that works continuously, but that requires a queue or similar
1085 // to coordinate its work.
1086 try {
1087 std::thread the_thread( sound_thread( source, target, hit, targ_mon, material ) );
1088 try {
1089 if( the_thread.joinable() ) {
1090 the_thread.detach();
1091 }
1092 } catch( std::system_error &err ) {
1093 dbg( D_ERROR ) << "Failed to detach melee sound thread: std::system_error: " << err.what();
1094 }
1095 } catch( std::system_error &err ) {
1096 // not a big deal, just skip playing the sound.
1097 dbg( D_ERROR ) << "Failed to create melee sound thread: std::system_error: " << err.what();
1098 }
1099 }
1100
sound_thread(const tripoint & source,const tripoint & target,const bool hit,const bool targ_mon,const std::string & material)1101 sfx::sound_thread::sound_thread( const tripoint &source, const tripoint &target, const bool hit,
1102 const bool targ_mon, const std::string &material )
1103 : hit( hit )
1104 , targ_mon( targ_mon )
1105 , material( material )
1106 {
1107 // This is function is run in the main thread.
1108 const int heard_volume = get_heard_volume( source );
1109 npc *np = g->critter_at<npc>( source );
1110 const player &p = np ? static_cast<player &>( *np ) :
1111 dynamic_cast<player &>( get_player_character() );
1112 if( !p.is_npc() ) {
1113 // sound comes from the same place as the player is, calculation of angle wouldn't work
1114 ang_src = 0_degrees;
1115 vol_src = heard_volume;
1116 vol_targ = heard_volume;
1117 } else {
1118 ang_src = get_heard_angle( source );
1119 vol_src = std::max( heard_volume - 30, 0 );
1120 vol_targ = std::max( heard_volume - 20, 0 );
1121 }
1122 ang_targ = get_heard_angle( target );
1123 weapon_skill = p.weapon.melee_skill();
1124 weapon_volume = p.weapon.volume() / units::legacy_volume_factor;
1125 }
1126
1127 // Operator overload required for thread API.
operator ()() const1128 void sfx::sound_thread::operator()() const
1129 {
1130 // This is function is run in a separate thread. One must be careful and not access game data
1131 // that might change (e.g. g->u.weapon, the character could switch weapons while this thread
1132 // runs).
1133 std::this_thread::sleep_for( std::chrono::milliseconds( rng( 1, 2 ) ) );
1134 std::string variant_used;
1135
1136 static const skill_id skill_bashing( "bashing" );
1137 static const skill_id skill_cutting( "cutting" );
1138 static const skill_id skill_stabbing( "stabbing" );
1139
1140 if( weapon_skill == skill_bashing && weapon_volume <= 8 ) {
1141 variant_used = "small_bash";
1142 play_variant_sound( "melee_swing", "small_bash", vol_src, ang_src, 0.8, 1.2 );
1143 } else if( weapon_skill == skill_bashing && weapon_volume >= 9 ) {
1144 variant_used = "big_bash";
1145 play_variant_sound( "melee_swing", "big_bash", vol_src, ang_src, 0.8, 1.2 );
1146 } else if( ( weapon_skill == skill_cutting || weapon_skill == skill_stabbing ) &&
1147 weapon_volume <= 6 ) {
1148 variant_used = "small_cutting";
1149 play_variant_sound( "melee_swing", "small_cutting", vol_src, ang_src, 0.8, 1.2 );
1150 } else if( ( weapon_skill == skill_cutting || weapon_skill == skill_stabbing ) &&
1151 weapon_volume >= 7 ) {
1152 variant_used = "big_cutting";
1153 play_variant_sound( "melee_swing", "big_cutting", vol_src, ang_src, 0.8, 1.2 );
1154 } else {
1155 variant_used = "default";
1156 play_variant_sound( "melee_swing", "default", vol_src, ang_src, 0.8, 1.2 );
1157 }
1158 if( hit ) {
1159 if( targ_mon ) {
1160 if( material == "steel" ) {
1161 std::this_thread::sleep_for( std::chrono::milliseconds( rng( weapon_volume * 12,
1162 weapon_volume * 16 ) ) );
1163 play_variant_sound( "melee_hit_metal", variant_used, vol_targ, ang_targ, 0.8, 1.2 );
1164 } else {
1165 std::this_thread::sleep_for( std::chrono::milliseconds( rng( weapon_volume * 12,
1166 weapon_volume * 16 ) ) );
1167 play_variant_sound( "melee_hit_flesh", variant_used, vol_targ, ang_targ, 0.8, 1.2 );
1168 }
1169 } else {
1170 std::this_thread::sleep_for( std::chrono::milliseconds( rng( weapon_volume * 9,
1171 weapon_volume * 12 ) ) );
1172 play_variant_sound( "melee_hit_flesh", variant_used, vol_targ, ang_targ, 0.8, 1.2 );
1173 }
1174 }
1175 }
1176
do_projectile_hit(const Creature & target)1177 void sfx::do_projectile_hit( const Creature &target )
1178 {
1179 if( test_mode ) {
1180 return;
1181 }
1182
1183 const int heard_volume = sfx::get_heard_volume( target.pos() );
1184 const units::angle angle = get_heard_angle( target.pos() );
1185 if( target.is_monster() ) {
1186 const monster &mon = dynamic_cast<const monster &>( target );
1187 static const std::set<material_id> fleshy = {
1188 material_id( "flesh" ),
1189 material_id( "hflesh" ),
1190 material_id( "iflesh" ),
1191 material_id( "veggy" ),
1192 material_id( "bone" ),
1193 };
1194 const bool is_fleshy = std::any_of( fleshy.begin(), fleshy.end(), [&mon]( const material_id & m ) {
1195 return mon.made_of( m );
1196 } );
1197
1198 if( is_fleshy ) {
1199 play_variant_sound( "bullet_hit", "hit_flesh", heard_volume, angle, 0.8, 1.2 );
1200 return;
1201 } else if( mon.made_of( material_id( "stone" ) ) ) {
1202 play_variant_sound( "bullet_hit", "hit_wall", heard_volume, angle, 0.8, 1.2 );
1203 return;
1204 } else if( mon.made_of( material_id( "steel" ) ) ) {
1205 play_variant_sound( "bullet_hit", "hit_metal", heard_volume, angle, 0.8, 1.2 );
1206 return;
1207 } else {
1208 play_variant_sound( "bullet_hit", "hit_flesh", heard_volume, angle, 0.8, 1.2 );
1209 return;
1210 }
1211 }
1212 play_variant_sound( "bullet_hit", "hit_flesh", heard_volume, angle, 0.8, 1.2 );
1213 }
1214
do_player_death_hurt(const Character & target,bool death)1215 void sfx::do_player_death_hurt( const Character &target, bool death )
1216 {
1217 if( test_mode ) {
1218 return;
1219 }
1220
1221 int heard_volume = get_heard_volume( target.pos() );
1222 const bool male = target.male;
1223 if( !male && !death ) {
1224 play_variant_sound( "deal_damage", "hurt_f", heard_volume );
1225 } else if( male && !death ) {
1226 play_variant_sound( "deal_damage", "hurt_m", heard_volume );
1227 } else if( !male && death ) {
1228 play_variant_sound( "clean_up_at_end", "death_f", heard_volume );
1229 } else if( male && death ) {
1230 play_variant_sound( "clean_up_at_end", "death_m", heard_volume );
1231 }
1232 }
1233
do_danger_music()1234 void sfx::do_danger_music()
1235 {
1236 if( test_mode ) {
1237 return;
1238 }
1239
1240 Character &player_character = get_player_character();
1241 if( player_character.in_sleep_state() && !audio_muted ) {
1242 fade_audio_channel( channel::any, 100 );
1243 audio_muted = true;
1244 return;
1245 } else if( ( player_character.in_sleep_state() && audio_muted ) ||
1246 is_channel_playing( channel::chainsaw_theme ) ) {
1247 fade_audio_group( group::context_themes, 1000 );
1248 return;
1249 }
1250 audio_muted = false;
1251 int hostiles = 0;
1252 for( auto &critter : player_character.get_visible_creatures( 40 ) ) {
1253 if( player_character.attitude_to( *critter ) == Creature::Attitude::HOSTILE ) {
1254 hostiles++;
1255 }
1256 }
1257 if( hostiles == prev_hostiles ) {
1258 return;
1259 }
1260 if( hostiles <= 4 ) {
1261 fade_audio_group( group::context_themes, 1000 );
1262 prev_hostiles = hostiles;
1263 return;
1264 } else if( hostiles >= 5 && hostiles <= 9 && !is_channel_playing( channel::danger_low_theme ) ) {
1265 fade_audio_group( group::context_themes, 1000 );
1266 play_ambient_variant_sound( "danger_low", "default", 100, channel::danger_low_theme, 1000 );
1267 prev_hostiles = hostiles;
1268 return;
1269 } else if( hostiles >= 10 && hostiles <= 14 &&
1270 !is_channel_playing( channel::danger_medium_theme ) ) {
1271 fade_audio_group( group::context_themes, 1000 );
1272 play_ambient_variant_sound( "danger_medium", "default", 100, channel::danger_medium_theme, 1000 );
1273 prev_hostiles = hostiles;
1274 return;
1275 } else if( hostiles >= 15 && hostiles <= 19 && !is_channel_playing( channel::danger_high_theme ) ) {
1276 fade_audio_group( group::context_themes, 1000 );
1277 play_ambient_variant_sound( "danger_high", "default", 100, channel::danger_high_theme, 1000 );
1278 prev_hostiles = hostiles;
1279 return;
1280 } else if( hostiles >= 20 && !is_channel_playing( channel::danger_extreme_theme ) ) {
1281 fade_audio_group( group::context_themes, 1000 );
1282 play_ambient_variant_sound( "danger_extreme", "default", 100, channel::danger_extreme_theme, 1000 );
1283 prev_hostiles = hostiles;
1284 return;
1285 }
1286 prev_hostiles = hostiles;
1287 }
1288
do_fatigue()1289 void sfx::do_fatigue()
1290 {
1291 if( test_mode ) {
1292 return;
1293 }
1294
1295 Character &player_character = get_player_character();
1296 /*15: Stamina 75%
1297 16: Stamina 50%
1298 17: Stamina 25%*/
1299 if( player_character.get_stamina() >= player_character.get_stamina_max() * .75 ) {
1300 fade_audio_group( group::fatigue, 2000 );
1301 return;
1302 } else if( player_character.get_stamina() <= player_character.get_stamina_max() * .74 &&
1303 player_character.get_stamina() >= player_character.get_stamina_max() * .5 &&
1304 player_character.male && !is_channel_playing( channel::stamina_75 ) ) {
1305 fade_audio_group( group::fatigue, 1000 );
1306 play_ambient_variant_sound( "plmove", "fatigue_m_low", 100, channel::stamina_75, 1000 );
1307 return;
1308 } else if( player_character.get_stamina() <= player_character.get_stamina_max() * .49 &&
1309 player_character.get_stamina() >= player_character.get_stamina_max() * .25 &&
1310 player_character.male && !is_channel_playing( channel::stamina_50 ) ) {
1311 fade_audio_group( group::fatigue, 1000 );
1312 play_ambient_variant_sound( "plmove", "fatigue_m_med", 100, channel::stamina_50, 1000 );
1313 return;
1314 } else if( player_character.get_stamina() <= player_character.get_stamina_max() * .24 &&
1315 player_character.get_stamina() >= 0 && player_character.male &&
1316 !is_channel_playing( channel::stamina_35 ) ) {
1317 fade_audio_group( group::fatigue, 1000 );
1318 play_ambient_variant_sound( "plmove", "fatigue_m_high", 100, channel::stamina_35, 1000 );
1319 return;
1320 } else if( player_character.get_stamina() <= player_character.get_stamina_max() * .74 &&
1321 player_character.get_stamina() >= player_character.get_stamina_max() * .5 &&
1322 !player_character.male && !is_channel_playing( channel::stamina_75 ) ) {
1323 fade_audio_group( group::fatigue, 1000 );
1324 play_ambient_variant_sound( "plmove", "fatigue_f_low", 100, channel::stamina_75, 1000 );
1325 return;
1326 } else if( player_character.get_stamina() <= player_character.get_stamina_max() * .49 &&
1327 player_character.get_stamina() >= player_character.get_stamina_max() * .25 &&
1328 !player_character.male && !is_channel_playing( channel::stamina_50 ) ) {
1329 fade_audio_group( group::fatigue, 1000 );
1330 play_ambient_variant_sound( "plmove", "fatigue_f_med", 100, channel::stamina_50, 1000 );
1331 return;
1332 } else if( player_character.get_stamina() <= player_character.get_stamina_max() * .24 &&
1333 player_character.get_stamina() >= 0 && !player_character.male &&
1334 !is_channel_playing( channel::stamina_35 ) ) {
1335 fade_audio_group( group::fatigue, 1000 );
1336 play_ambient_variant_sound( "plmove", "fatigue_f_high", 100, channel::stamina_35, 1000 );
1337 return;
1338 }
1339 }
1340
do_hearing_loss(int turns)1341 void sfx::do_hearing_loss( int turns )
1342 {
1343 if( test_mode ) {
1344 return;
1345 }
1346
1347 g_sfx_volume_multiplier = .1;
1348 fade_audio_group( group::weather, 50 );
1349 fade_audio_group( group::time_of_day, 50 );
1350 // Negative duration is just insuring we stay in sync with player condition,
1351 // don't play any of the sound effects for going deaf.
1352 if( turns == -1 ) {
1353 return;
1354 }
1355 play_variant_sound( "environment", "deafness_shock", 100 );
1356 play_variant_sound( "environment", "deafness_tone_start", 100 );
1357 if( turns <= 35 ) {
1358 play_ambient_variant_sound( "environment", "deafness_tone_light", 90, channel::deafness_tone, 100 );
1359 } else if( turns <= 90 ) {
1360 play_ambient_variant_sound( "environment", "deafness_tone_medium", 90, channel::deafness_tone,
1361 100 );
1362 } else if( turns >= 91 ) {
1363 play_ambient_variant_sound( "environment", "deafness_tone_heavy", 90, channel::deafness_tone, 100 );
1364 }
1365 }
1366
remove_hearing_loss()1367 void sfx::remove_hearing_loss()
1368 {
1369 if( test_mode ) {
1370 return;
1371 }
1372 stop_sound_effect_fade( channel::deafness_tone, 300 );
1373 g_sfx_volume_multiplier = 1;
1374 do_ambient();
1375 }
1376
do_footstep()1377 void sfx::do_footstep()
1378 {
1379 if( test_mode ) {
1380 return;
1381 }
1382
1383 end_sfx_timestamp = std::chrono::high_resolution_clock::now();
1384 sfx_time = end_sfx_timestamp - start_sfx_timestamp;
1385 if( std::chrono::duration_cast<std::chrono::milliseconds> ( sfx_time ).count() > 400 ) {
1386 const Character &player_character = get_player_character();
1387 int heard_volume = sfx::get_heard_volume( player_character.pos() );
1388 const auto terrain = get_map().ter( player_character.pos() ).id();
1389 static const std::set<ter_str_id> grass = {
1390 ter_str_id( "t_grass" ),
1391 ter_str_id( "t_shrub" ),
1392 ter_str_id( "t_shrub_peanut" ),
1393 ter_str_id( "t_shrub_peanut_harvested" ),
1394 ter_str_id( "t_shrub_blueberry" ),
1395 ter_str_id( "t_shrub_blueberry_harvested" ),
1396 ter_str_id( "t_shrub_strawberry" ),
1397 ter_str_id( "t_shrub_strawberry_harvested" ),
1398 ter_str_id( "t_shrub_blackberry" ),
1399 ter_str_id( "t_shrub_blackberry_harvested" ),
1400 ter_str_id( "t_shrub_huckleberry" ),
1401 ter_str_id( "t_shrub_huckleberry_harvested" ),
1402 ter_str_id( "t_shrub_raspberry" ),
1403 ter_str_id( "t_shrub_raspberry_harvested" ),
1404 ter_str_id( "t_shrub_grape" ),
1405 ter_str_id( "t_shrub_grape_harvested" ),
1406 ter_str_id( "t_shrub_rose" ),
1407 ter_str_id( "t_shrub_rose_harvested" ),
1408 ter_str_id( "t_shrub_hydrangea" ),
1409 ter_str_id( "t_shrub_hydrangea_harvested" ),
1410 ter_str_id( "t_shrub_lilac" ),
1411 ter_str_id( "t_shrub_lilac_harvested" ),
1412 ter_str_id( "t_underbrush" ),
1413 ter_str_id( "t_underbrush_harvested_spring" ),
1414 ter_str_id( "t_underbrush_harvested_summer" ),
1415 ter_str_id( "t_underbrush_harvested_autumn" ),
1416 ter_str_id( "t_underbrush_harvested_winter" ),
1417 ter_str_id( "t_moss" ),
1418 ter_str_id( "t_grass_white" ),
1419 ter_str_id( "t_grass_long" ),
1420 ter_str_id( "t_grass_tall" ),
1421 ter_str_id( "t_grass_dead" ),
1422 ter_str_id( "t_grass_golf" ),
1423 ter_str_id( "t_golf_hole" ),
1424 ter_str_id( "t_trunk" ),
1425 ter_str_id( "t_stump" ),
1426 };
1427 static const std::set<ter_str_id> dirt = {
1428 ter_str_id( "t_dirt" ),
1429 ter_str_id( "t_dirtmound" ),
1430 ter_str_id( "t_dirtmoundfloor" ),
1431 ter_str_id( "t_sand" ),
1432 ter_str_id( "t_clay" ),
1433 ter_str_id( "t_dirtfloor" ),
1434 ter_str_id( "t_palisade_gate_o" ),
1435 ter_str_id( "t_sandbox" ),
1436 ter_str_id( "t_claymound" ),
1437 ter_str_id( "t_sandmound" ),
1438 ter_str_id( "t_rootcellar" ),
1439 ter_str_id( "t_railroad_rubble" ),
1440 ter_str_id( "t_railroad_track" ),
1441 ter_str_id( "t_railroad_track_h" ),
1442 ter_str_id( "t_railroad_track_v" ),
1443 ter_str_id( "t_railroad_track_d" ),
1444 ter_str_id( "t_railroad_track_d1" ),
1445 ter_str_id( "t_railroad_track_d2" ),
1446 ter_str_id( "t_railroad_tie" ),
1447 ter_str_id( "t_railroad_tie_d" ),
1448 ter_str_id( "t_railroad_tie_d" ),
1449 ter_str_id( "t_railroad_tie_h" ),
1450 ter_str_id( "t_railroad_tie_v" ),
1451 ter_str_id( "t_railroad_tie_d" ),
1452 ter_str_id( "t_railroad_track_on_tie" ),
1453 ter_str_id( "t_railroad_track_h_on_tie" ),
1454 ter_str_id( "t_railroad_track_v_on_tie" ),
1455 ter_str_id( "t_railroad_track_d_on_tie" ),
1456 ter_str_id( "t_railroad_tie" ),
1457 ter_str_id( "t_railroad_tie_h" ),
1458 ter_str_id( "t_railroad_tie_v" ),
1459 ter_str_id( "t_railroad_tie_d1" ),
1460 ter_str_id( "t_railroad_tie_d2" ),
1461 };
1462 static const std::set<ter_str_id> metal = {
1463 ter_str_id( "t_ov_smreb_cage" ),
1464 ter_str_id( "t_metal_floor" ),
1465 ter_str_id( "t_grate" ),
1466 ter_str_id( "t_bridge" ),
1467 ter_str_id( "t_elevator" ),
1468 ter_str_id( "t_guardrail_bg_dp" ),
1469 ter_str_id( "t_slide" ),
1470 ter_str_id( "t_conveyor" ),
1471 ter_str_id( "t_machinery_light" ),
1472 ter_str_id( "t_machinery_heavy" ),
1473 ter_str_id( "t_machinery_old" ),
1474 ter_str_id( "t_machinery_electronic" ),
1475 };
1476 static const std::set<ter_str_id> chain_fence = {
1477 ter_str_id( "t_chainfence" ),
1478 };
1479
1480 const auto play_plmove_sound_variant = [&]( const std::string & variant ) {
1481 play_variant_sound( "plmove", variant, heard_volume, 0_degrees, 0.8, 1.2 );
1482 start_sfx_timestamp = std::chrono::high_resolution_clock::now();
1483 };
1484
1485 auto veh_displayed_part = get_map().veh_at( player_character.pos() ).part_displayed();
1486
1487 if( !veh_displayed_part && ( terrain->has_flag( TFLAG_DEEP_WATER ) ||
1488 terrain->has_flag( TFLAG_SHALLOW_WATER ) ) ) {
1489 play_plmove_sound_variant( "walk_water" );
1490 return;
1491 }
1492 if( !player_character.wearing_something_on( bodypart_id( "foot_l" ) ) ) {
1493 play_plmove_sound_variant( "walk_barefoot" );
1494 return;
1495 }
1496 if( veh_displayed_part ) {
1497 const std::string &part_id = veh_displayed_part->part().info().get_id().str();
1498 if( has_variant_sound( "plmove", part_id ) ) {
1499 play_plmove_sound_variant( part_id );
1500 } else if( veh_displayed_part->has_feature( VPFLAG_AISLE ) ) {
1501 play_plmove_sound_variant( "walk_tarmac" );
1502 } else {
1503 play_plmove_sound_variant( "clear_obstacle" );
1504 }
1505 return;
1506 }
1507 if( sfx::has_variant_sound( "plmove", terrain.str() ) ) {
1508 play_plmove_sound_variant( terrain.str() );
1509 return;
1510 }
1511 if( grass.count( terrain ) > 0 ) {
1512 play_plmove_sound_variant( "walk_grass" );
1513 return;
1514 }
1515 if( dirt.count( terrain ) > 0 ) {
1516 play_plmove_sound_variant( "walk_dirt" );
1517 return;
1518 }
1519 if( metal.count( terrain ) > 0 ) {
1520 play_plmove_sound_variant( "walk_metal" );
1521 return;
1522 }
1523 if( chain_fence.count( terrain ) > 0 ) {
1524 play_plmove_sound_variant( "clear_obstacle" );
1525 return;
1526 }
1527
1528 play_plmove_sound_variant( "walk_tarmac" );
1529 }
1530 }
1531
do_obstacle(const std::string & obst)1532 void sfx::do_obstacle( const std::string &obst )
1533 {
1534 if( test_mode ) {
1535 return;
1536 }
1537
1538 int heard_volume = sfx::get_heard_volume( get_player_character().pos() );
1539 if( sfx::has_variant_sound( "plmove", obst ) ) {
1540 play_variant_sound( "plmove", obst, heard_volume, 0_degrees, 0.8, 1.2 );
1541 } else if( ter_str_id( obst ).is_valid() &&
1542 ( ter_id( obst )->has_flag( TFLAG_SHALLOW_WATER ) ||
1543 ter_id( obst )->has_flag( TFLAG_DEEP_WATER ) ) ) {
1544 play_variant_sound( "plmove", "walk_water", heard_volume, 0_degrees, 0.8, 1.2 );
1545 } else {
1546 play_variant_sound( "plmove", "clear_obstacle", heard_volume, 0_degrees, 0.8, 1.2 );
1547 }
1548 // prevent footsteps from triggering
1549 start_sfx_timestamp = std::chrono::high_resolution_clock::now();
1550 }
1551
play_activity_sound(const std::string & id,const std::string & variant,int volume)1552 void sfx::play_activity_sound( const std::string &id, const std::string &variant, int volume )
1553 {
1554 if( test_mode ) {
1555 return;
1556 }
1557 Character &player_character = get_player_character();
1558 if( act != player_character.activity.id() ) {
1559 act = player_character.activity.id();
1560 play_ambient_variant_sound( id, variant, volume, channel::player_activities, 0 );
1561 }
1562 }
1563
end_activity_sounds()1564 void sfx::end_activity_sounds()
1565 {
1566 if( test_mode ) {
1567 return;
1568 }
1569 act = activity_id::NULL_ID();
1570 fade_audio_channel( channel::player_activities, 2000 );
1571 }
1572
1573 #else // if defined(SDL_SOUND)
1574
1575 /** Dummy implementations for builds without sound */
1576 /*@{*/
load_sound_effects(const JsonObject &)1577 void sfx::load_sound_effects( const JsonObject & ) { }
load_sound_effect_preload(const JsonObject &)1578 void sfx::load_sound_effect_preload( const JsonObject & ) { }
load_playlist(const JsonObject &)1579 void sfx::load_playlist( const JsonObject & ) { }
play_variant_sound(const std::string &,const std::string &,int,units::angle,double,double)1580 void sfx::play_variant_sound( const std::string &, const std::string &, int, units::angle, double,
1581 double ) { }
play_variant_sound(const std::string &,const std::string &,int)1582 void sfx::play_variant_sound( const std::string &, const std::string &, int ) { }
play_ambient_variant_sound(const std::string &,const std::string &,int,channel,int,double,int)1583 void sfx::play_ambient_variant_sound( const std::string &, const std::string &, int, channel, int,
1584 double, int ) { }
play_activity_sound(const std::string &,const std::string &,int)1585 void sfx::play_activity_sound( const std::string &, const std::string &, int ) { }
end_activity_sounds()1586 void sfx::end_activity_sounds() { }
generate_gun_sound(const player &,const item &)1587 void sfx::generate_gun_sound( const player &, const item & ) { }
generate_melee_sound(const tripoint &,const tripoint &,bool,bool,const std::string &)1588 void sfx::generate_melee_sound( const tripoint &, const tripoint &, bool, bool,
1589 const std::string & ) { }
do_hearing_loss(int)1590 void sfx::do_hearing_loss( int ) { }
remove_hearing_loss()1591 void sfx::remove_hearing_loss() { }
do_projectile_hit(const Creature &)1592 void sfx::do_projectile_hit( const Creature & ) { }
do_footstep()1593 void sfx::do_footstep() { }
do_danger_music()1594 void sfx::do_danger_music() { }
do_vehicle_engine_sfx()1595 void sfx::do_vehicle_engine_sfx() { }
do_vehicle_exterior_engine_sfx()1596 void sfx::do_vehicle_exterior_engine_sfx() { }
do_ambient()1597 void sfx::do_ambient() { }
fade_audio_group(group,int)1598 void sfx::fade_audio_group( group, int ) { }
fade_audio_channel(channel,int)1599 void sfx::fade_audio_channel( channel, int ) { }
is_channel_playing(channel)1600 bool sfx::is_channel_playing( channel )
1601 {
1602 return false;
1603 }
set_channel_volume(channel,int)1604 int sfx::set_channel_volume( channel, int )
1605 {
1606 return 0;
1607 }
has_variant_sound(const std::string &,const std::string &)1608 bool sfx::has_variant_sound( const std::string &, const std::string & )
1609 {
1610 return false;
1611 }
stop_sound_effect_fade(channel,int)1612 void sfx::stop_sound_effect_fade( channel, int ) { }
stop_sound_effect_timed(channel,int)1613 void sfx::stop_sound_effect_timed( channel, int ) {}
do_player_death_hurt(const Character &,bool)1614 void sfx::do_player_death_hurt( const Character &, bool ) { }
do_fatigue()1615 void sfx::do_fatigue() { }
do_obstacle(const std::string &)1616 void sfx::do_obstacle( const std::string & ) { }
1617 /*@}*/
1618
1619 #endif // if defined(SDL_SOUND)
1620
1621 /** Functions from sfx that do not use the SDL_mixer API at all. They can be used in builds
1622 * without sound support. */
1623 /*@{*/
get_heard_volume(const tripoint & source)1624 int sfx::get_heard_volume( const tripoint &source )
1625 {
1626 int distance = sound_distance( get_player_character().pos(), source );
1627 // fract = -100 / 24
1628 const float fract = -4.166666f;
1629 int heard_volume = fract * distance - 1 + 100;
1630 if( heard_volume <= 0 ) {
1631 heard_volume = 0;
1632 }
1633 heard_volume *= g_sfx_volume_multiplier;
1634 return ( heard_volume );
1635 }
1636
get_heard_angle(const tripoint & source)1637 units::angle sfx::get_heard_angle( const tripoint &source )
1638 {
1639 units::angle angle = coord_to_angle( get_player_character().pos(), source ) + 90_degrees;
1640 //add_msg(m_warning, "angle: %i", angle);
1641 return angle;
1642 }
1643 /*@}*/
1644