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 &centr )
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