Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1449 lines (1355 sloc) 57 KB
#include "sounds.h"
#include <cstdlib>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <memory>
#include <ostream>
#include <set>
#include <type_traits>
#include <unordered_map>
#include "avatar.h"
#include "coordinate_conversions.h"
#include "debug.h"
#include "effect.h"
#include "enums.h"
#include "game.h"
#include "item.h"
#include "itype.h"
#include "line.h"
#include "map.h"
#include "map_iterator.h"
#include "messages.h"
#include "monster.h"
#include "npc.h"
#include "overmapbuffer.h"
#include "player.h"
#include "string_formatter.h"
#include "translations.h"
#include "weather.h"
#include "bodypart.h"
#include "calendar.h"
#include "creature.h"
#include "game_constants.h"
#include "optional.h"
#include "player_activity.h"
#include "rng.h"
#include "units.h"
#include "vehicle.h"
#include "vpart_position.h"
#include "veh_type.h"
#include "type_id.h"
#include "point.h"
#include "string_id.h"
#if defined(SDL_SOUND)
# if defined(_MSC_VER) && defined(USE_VCPKG)
# include <SDL2/SDL_mixer.h>
# else
# include <SDL_mixer.h>
# endif
# include <thread>
# if defined(_WIN32) && !defined(_MSC_VER)
# include "mingw.thread.h"
# endif
#endif
#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": "
weather_type previous_weather;
int prev_hostiles = 0;
int previous_speed = 0;
int previous_gear = 0;
bool audio_muted = false;
float g_sfx_volume_multiplier = 1;
auto start_sfx_timestamp = std::chrono::high_resolution_clock::now();
auto end_sfx_timestamp = std::chrono::high_resolution_clock::now();
auto sfx_time = end_sfx_timestamp - start_sfx_timestamp;
activity_id act;
std::pair<std::string, std::string> engine_external_id_and_variant;
const efftype_id effect_alarm_clock( "alarm_clock" );
const efftype_id effect_deaf( "deaf" );
const efftype_id effect_narcosis( "narcosis" );
const efftype_id effect_sleep( "sleep" );
const efftype_id effect_slept_through_alarm( "slept_through_alarm" );
static const trait_id trait_HEAVYSLEEPER2( "HEAVYSLEEPER2" );
static const trait_id trait_HEAVYSLEEPER( "HEAVYSLEEPER" );
static const itype_id fuel_type_muscle( "muscle" );
static const itype_id fuel_type_wind( "wind" );
static const itype_id fuel_type_battery( "battery" );
struct sound_event {
int volume;
sounds::sound_t category;
std::string description;
bool ambient;
bool footstep;
std::string id;
std::string variant;
};
struct centroid {
// Values have to be floats to prevent rounding errors.
float x;
float y;
float z;
float volume;
float weight;
};
// Static globals tracking sounds events of various kinds.
// The sound events since the last monster turn.
static std::vector<std::pair<tripoint, int>> recent_sounds;
// The sound events since the last interactive player turn. (doesn't count sleep etc)
static std::vector<std::pair<tripoint, sound_event>> sounds_since_last_turn;
// The sound events currently displayed to the player.
static std::unordered_map<tripoint, sound_event> sound_markers;
void sounds::ambient_sound( const tripoint &p, int vol, sound_t category,
const std::string &description )
{
sound( p, vol, category, description, true );
}
void sounds::sound( const tripoint &p, int vol, sound_t category, const std::string &description,
bool ambient, const std::string &id, const std::string &variant )
{
if( vol < 0 ) {
// Bail out if no volume.
debugmsg( "negative sound volume %d", vol );
return;
}
// Description is not an optional parameter
if( description.empty() ) {
debugmsg( "Sound at %d:%d has no description!", p.x, p.y );
}
recent_sounds.emplace_back( std::make_pair( p, vol ) );
sounds_since_last_turn.emplace_back( std::make_pair( p,
sound_event {vol, category, description, ambient,
false, id, variant} ) );
}
void sounds::add_footstep( const tripoint &p, int volume, int, monster *,
const std::string &footstep )
{
sounds_since_last_turn.emplace_back( std::make_pair( p, sound_event { volume,
sound_t::movement, footstep, false, true, "", ""} ) );
}
template <typename C>
static void vector_quick_remove( std::vector<C> &source, int index )
{
if( source.size() != 1 ) {
// Swap the target and the last element of the vector.
// This scrambles the vector, but makes removal O(1).
std::iter_swap( source.begin() + index, source.end() - 1 );
}
source.pop_back();
}
static std::vector<centroid> cluster_sounds( std::vector<std::pair<tripoint, int>> recent_sounds )
{
// If there are too many monsters and too many noise sources (which can be monsters, go figure),
// applying sound events to monsters can dominate processing time for the whole game,
// so we cluster sounds and apply the centroids of the sounds to the monster AI
// to fight the combinatorial explosion.
std::vector<centroid> sound_clusters;
const int num_seed_clusters = std::max( std::min( recent_sounds.size(), static_cast<size_t>( 10 ) ),
static_cast<size_t>( log( recent_sounds.size() ) ) );
const size_t stopping_point = recent_sounds.size() - num_seed_clusters;
const size_t max_map_distance = rl_dist( 0, 0, MAPSIZE_X, MAPSIZE_Y );
// Randomly choose cluster seeds.
for( size_t i = recent_sounds.size(); i > stopping_point; i-- ) {
size_t index = rng( 0, i - 1 );
// The volume and cluster weight are the same for the first element.
sound_clusters.push_back(
// Assure the compiler that these int->float conversions are safe.
{
static_cast<float>( recent_sounds[index].first.x ), static_cast<float>( recent_sounds[index].first.y ),
static_cast<float>( recent_sounds[index].first.z ),
static_cast<float>( recent_sounds[index].second ), static_cast<float>( recent_sounds[index].second )
} );
vector_quick_remove( recent_sounds, index );
}
for( const auto &sound_event_pair : recent_sounds ) {
auto found_centroid = sound_clusters.begin();
float dist_factor = max_map_distance;
const auto cluster_end = sound_clusters.end();
for( auto centroid_iter = sound_clusters.begin(); centroid_iter != cluster_end;
++centroid_iter ) {
// Scale the distance between the two by the max possible distance.
tripoint centroid_pos { static_cast<int>( centroid_iter->x ), static_cast<int>( centroid_iter->y ), static_cast<int>( centroid_iter->z ) };
const int dist = rl_dist( sound_event_pair.first, centroid_pos );
if( dist * dist < dist_factor ) {
found_centroid = centroid_iter;
dist_factor = dist * dist;
}
}
const float volume_sum = static_cast<float>( sound_event_pair.second ) + found_centroid->weight;
// Set the centroid location to the average of the two locations, weighted by volume.
found_centroid->x = static_cast<float>( ( sound_event_pair.first.x * sound_event_pair.second ) +
( found_centroid->x * found_centroid->weight ) ) / volume_sum;
found_centroid->y = static_cast<float>( ( sound_event_pair.first.y * sound_event_pair.second ) +
( found_centroid->y * found_centroid->weight ) ) / volume_sum;
found_centroid->z = static_cast<float>( ( sound_event_pair.first.z * sound_event_pair.second ) +
( found_centroid->z * found_centroid->weight ) ) / volume_sum;
// Set the centroid volume to the larger of the volumes.
found_centroid->volume = std::max( found_centroid->volume,
static_cast<float>( sound_event_pair.second ) );
// Set the centroid weight to the sum of the weights.
found_centroid->weight = volume_sum;
}
return sound_clusters;
}
static int get_signal_for_hordes( const centroid &centr )
{
//Volume in tiles. Signal for hordes in submaps
//modify vol using weather vol.Weather can reduce monster hearing
const int vol = centr.volume - weather::sound_attn( g->weather.weather );
const int min_vol_cap = 60; //Hordes can't hear volume lower than this
const int underground_div = 2; //Coefficient for volume reduction underground
const int hordes_sig_div = SEEX; //Divider coefficient for hordes
const int min_sig_cap = 8; //Signal for hordes can't be lower that this if it pass min_vol_cap
const int max_sig_cap = 26; //Signal for hordes can't be higher that this
//Lower the level - lower the sound
int vol_hordes = ( ( centr.z < 0 ) ? vol / ( underground_div * std::abs( centr.z ) ) : vol );
if( vol_hordes > min_vol_cap ) {
//Calculating horde hearing signal
int sig_power = std::ceil( static_cast<float>( vol_hordes ) / hordes_sig_div );
//Capping minimum horde hearing signal
sig_power = std::max( sig_power, min_sig_cap );
//Capping extremely high signal to hordes
sig_power = std::min( sig_power, max_sig_cap );
add_msg( m_debug, "vol %d vol_hordes %d sig_power %d ", vol, vol_hordes, sig_power );
return sig_power;
}
return 0;
}
void sounds::process_sounds()
{
std::vector<centroid> sound_clusters = cluster_sounds( recent_sounds );
const int weather_vol = weather::sound_attn( g->weather.weather );
for( const auto &this_centroid : sound_clusters ) {
// Since monsters don't go deaf ATM we can just use the weather modified volume
// If they later get physical effects from loud noises we'll have to change this
// to use the unmodified volume for those effects.
const int vol = this_centroid.volume - weather_vol;
const tripoint source = tripoint( this_centroid.x, this_centroid.y, this_centroid.z );
// --- Monster sound handling here ---
// Alert all hordes
int sig_power = get_signal_for_hordes( this_centroid );
if( sig_power > 0 ) {
const point abs_ms = g->m.getabs( source.x, source.y );
const point abs_sm = ms_to_sm_copy( abs_ms );
const tripoint target( abs_sm.x, abs_sm.y, source.z );
overmap_buffer.signal_hordes( target, sig_power );
}
// Alert all monsters (that can hear) to the sound.
for( monster &critter : g->all_monsters() ) {
// TODO: Generalize this to Creature::hear_sound
const int dist = rl_dist( source, critter.pos() );
if( vol * 2 > dist ) {
// Exclude monsters that certainly won't hear the sound
critter.hear_sound( source, vol, dist );
}
}
}
recent_sounds.clear();
}
// skip most movement sounds
static bool describe_sound( sounds::sound_t category )
{
if( category == sounds::sound_t::combat || category == sounds::sound_t::speech ||
category == sounds::sound_t::alert ) {
return true;
}
return one_in( 5 );
}
void sounds::process_sound_markers( player *p )
{
bool is_deaf = p->is_deaf();
const float volume_multiplier = p->hearing_ability();
const int weather_vol = weather::sound_attn( g->weather.weather );
for( const auto &sound_event_pair : sounds_since_last_turn ) {
const tripoint &pos = sound_event_pair.first;
const sound_event &sound = sound_event_pair.second;
const int distance_to_sound = rl_dist( p->pos().x, p->pos().y, pos.x, pos.y ) +
abs( p->pos().z - pos.z ) * 10;
const int raw_volume = sound.volume;
// The felt volume of a sound is not affected by negative multipliers, such as already
// deafened players or players with sub-par hearing to begin with.
const int felt_volume = static_cast<int>( raw_volume * std::min( 1.0f,
volume_multiplier ) ) - distance_to_sound;
// Deafening is based on the felt volume, as a player may be too deaf to
// hear the deafening sound but still suffer additional hearing loss.
const bool is_sound_deafening = rng( felt_volume / 2, felt_volume ) >= 150;
// Deaf players hear no sound, but still are at risk of additional hearing loss.
if( is_deaf ) {
if( is_sound_deafening && !p->is_immune_effect( effect_deaf ) ) {
p->add_effect( effect_deaf, std::min( 4_minutes,
time_duration::from_turns( felt_volume - 130 ) / 8 ) );
if( !p->has_trait( trait_id( "NOPAIN" ) ) ) {
p->add_msg_if_player( m_bad, _( "Your eardrums suddenly ache!" ) );
if( p->get_pain() < 10 ) {
p->mod_pain( rng( 0, 2 ) );
}
}
}
continue;
}
if( is_sound_deafening && !p->is_immune_effect( effect_deaf ) ) {
const time_duration deafness_duration = time_duration::from_turns( felt_volume - 130 ) / 4;
p->add_effect( effect_deaf, deafness_duration );
if( p->is_deaf() && !is_deaf ) {
is_deaf = true;
continue;
}
}
// The heard volume of a sound is the player heard volume, regardless of true volume level.
const int heard_volume = static_cast<int>( ( raw_volume - weather_vol ) *
volume_multiplier ) - distance_to_sound;
if( heard_volume <= 0 && pos != p->pos() ) {
continue;
}
// Player volume meter includes all sounds from their tile and adjacent tiles
// TODO: Add noises from vehicle player is in.
if( distance_to_sound <= 1 ) {
p->volume = std::max( p->volume, heard_volume );
}
// Secure the flag before wake_up() clears the effect
bool slept_through = p->has_effect( effect_slept_through_alarm );
// See if we need to wake someone up
if( p->has_effect( effect_sleep ) ) {
if( ( ( !( p->has_trait( trait_HEAVYSLEEPER ) ||
p->has_trait( trait_HEAVYSLEEPER2 ) ) && dice( 2, 15 ) < heard_volume ) ||
( p->has_trait( trait_HEAVYSLEEPER ) && dice( 3, 15 ) < heard_volume ) ||
( p->has_trait( trait_HEAVYSLEEPER2 ) && dice( 6, 15 ) < heard_volume ) ) &&
!p->has_effect( effect_narcosis ) ) {
//Not kidding about sleep-through-firefight
p->wake_up();
add_msg( m_warning, _( "Something is making noise." ) );
} else {
continue;
}
}
const std::string &description = sound.description.empty() ? "a noise" : sound.description;
if( p->is_npc() ) {
if( !sound.ambient ) {
npc *guy = dynamic_cast<npc *>( p );
guy->handle_sound( static_cast<int>( sound.category ), description,
heard_volume, pos );
}
continue;
}
// don't print our own noise or things without descriptions
if( !sound.ambient && ( pos != p->pos() ) && !g->m.pl_sees( pos, distance_to_sound ) ) {
if( !p->activity.is_distraction_ignored( distraction_type::noise ) ) {
const std::string query = string_format( _( "Heard %s!" ), description );
g->cancel_activity_or_ignore_query( distraction_type::noise, query );
}
}
// skip most movement sounds and our own sounds
// unless our own sound is an alarm
if( ( pos != p->pos() || ( pos == p->pos() && sound.category == sound_t::alarm ) ) &&
describe_sound( sound.category ) ) {
game_message_type severity = m_info;
if( sound.category == sound_t::combat || sound.category == sound_t::alarm ) {
severity = m_warning;
}
// if we can see it, don't print a direction
if( pos == p->pos() ) {
add_msg( severity, _( "From your position you hear %1$s." ), description );
} else if( p->sees( pos ) ) {
add_msg( severity, _( "You hear %1$s" ), description );
} else {
std::string direction = direction_name( direction_from( p->pos(), pos ) );
add_msg( severity, _( "From the %1$s you hear %2$s" ), direction, description );
}
}
if( !p->has_effect( effect_sleep ) && p->has_effect( effect_alarm_clock ) &&
!p->has_bionic( bionic_id( "bio_watch" ) ) ) {
// if we don't have effect_sleep but we're in_sleep_state, either
// we were trying to fall asleep for so long our alarm is now going
// off or something disturbed us while trying to sleep
const bool trying_to_sleep = p->in_sleep_state();
if( p->get_effect( effect_alarm_clock ).get_duration() == 1_turns ) {
if( slept_through ) {
add_msg( _( "Your alarm clock finally wakes you up." ) );
} else if( !trying_to_sleep ) {
add_msg( _( "Your alarm clock wakes you up." ) );
} else {
add_msg( _( "Your alarm clock goes off and you haven't slept a wink." ) );
p->activity.set_to_null();
}
add_msg( _( "You turn off your alarm-clock." ) );
p->get_effect( effect_alarm_clock ).set_duration( 0_turns );
}
}
const std::string &sfx_id = sound.id;
const std::string &sfx_variant = sound.variant;
if( !sfx_id.empty() ) {
sfx::play_variant_sound( sfx_id, sfx_variant, sfx::get_heard_volume( pos ) );
}
// Place footstep markers.
if( pos == p->pos() || p->sees( pos ) ) {
// If we are or can see the source, don't draw a marker.
continue;
}
int err_offset;
if( ( heard_volume + distance_to_sound ) / distance_to_sound < 2 ) {
err_offset = 3;
} else if( ( heard_volume + distance_to_sound ) / distance_to_sound < 3 ) {
err_offset = 2;
} else {
err_offset = 1;
}
// If Z-coordinate is different, draw even when you can see the source
const bool diff_z = pos.z != p->posz();
// Enumerate the valid points the player *cannot* see.
// Unless the source is on a different z-level, then any point is fine
std::vector<tripoint> unseen_points;
for( const tripoint &newp : g->m.points_in_radius( pos, err_offset ) ) {
if( diff_z || !p->sees( newp ) ) {
unseen_points.emplace_back( newp );
}
}
// Then place the sound marker in a random one.
if( !unseen_points.empty() ) {
sound_markers.emplace( random_entry( unseen_points ), sound );
}
}
if( p->is_player() ) {
sounds_since_last_turn.clear();
}
}
void sounds::reset_sounds()
{
recent_sounds.clear();
sounds_since_last_turn.clear();
sound_markers.clear();
}
void sounds::reset_markers()
{
sound_markers.clear();
}
std::vector<tripoint> sounds::get_footstep_markers()
{
// Optimization, make this static and clear it in reset_markers?
std::vector<tripoint> footsteps;
footsteps.reserve( sound_markers.size() );
for( const auto &mark : sound_markers ) {
footsteps.push_back( mark.first );
}
return footsteps;
}
std::pair<std::vector<tripoint>, std::vector<tripoint>> sounds::get_monster_sounds()
{
auto sound_clusters = cluster_sounds( recent_sounds );
std::vector<tripoint> sound_locations;
sound_locations.reserve( recent_sounds.size() );
for( const auto &sound : recent_sounds ) {
sound_locations.push_back( sound.first );
}
std::vector<tripoint> cluster_centroids;
cluster_centroids.reserve( sound_clusters.size() );
for( const auto &sound : sound_clusters ) {
cluster_centroids.emplace_back( static_cast<int>( sound.x ), static_cast<int>( sound.y ),
static_cast<int>( sound.z ) );
}
return { sound_locations, cluster_centroids };
}
std::string sounds::sound_at( const tripoint &location )
{
auto this_sound = sound_markers.find( location );
if( this_sound == sound_markers.end() ) {
return std::string();
}
if( !this_sound->second.description.empty() ) {
return this_sound->second.description;
}
return _( "a sound" );
}
#if defined(SDL_SOUND)
void sfx::fade_audio_group( int tag, int duration )
{
Mix_FadeOutGroup( tag, duration );
}
void sfx::fade_audio_channel( int tag, int duration )
{
Mix_FadeOutChannel( tag, duration );
}
bool sfx::is_channel_playing( int channel )
{
return Mix_Playing( channel ) != 0;
}
void sfx::stop_sound_effect_fade( int channel, int duration )
{
if( Mix_FadeOutChannel( channel, duration ) == -1 ) {
dbg( D_ERROR ) << "Failed to stop sound effect: " << Mix_GetError();
}
}
int sfx::set_channel_volume( int channel, int volume )
{
if( !Mix_Playing( channel ) ) {
return -1;
}
if( Mix_FadingChannel( channel ) != MIX_NO_FADING ) {
return -1;
}
return Mix_Volume( channel, volume );
}
void sfx::do_vehicle_engine_sfx()
{
/** Channel Assignments:
23: engine working internal
**/
if( !g->u.in_vehicle ) {
fade_audio_channel( 23, 300 );
add_msg( m_debug, "STOP 23, OUT OF CAR" );
return;
}
if( g->u.in_sleep_state() && !audio_muted ) {
fade_audio_channel( -1, 300 );
audio_muted = true;
return;
} else if( g->u.in_sleep_state() && audio_muted ) {
return;
}
optional_vpart_position vpart_opt = g->m.veh_at( g->u.pos() );
vehicle *veh;
if( vpart_opt.has_value() ) {
veh = &vpart_opt->vehicle();
} else {
return;
}
if( !veh->engine_on ) {
fade_audio_channel( 23, 100 );
add_msg( m_debug, "STOP 23" );
return;
}
std::pair<std::string, std::string> id_and_variant;
for( size_t e = 0; e < veh->engines.size(); ++e ) {
if( veh->is_engine_on( e ) ) {
if( sfx::has_variant_sound( "engine_working_internal",
veh->part_info( veh->engines[ e ] ).get_id().str() ) ) {
id_and_variant = std::make_pair( "engine_working_internal",
veh->part_info( veh->engines[ e ] ).get_id().str() );
} else if( veh->is_engine_type( e, fuel_type_muscle ) ) {
id_and_variant = std::make_pair( "engine_working_internal", "muscle" );
} else if( veh->is_engine_type( e, fuel_type_wind ) ) {
id_and_variant = std::make_pair( "engine_working_internal", "wind" );
} else if( veh->is_engine_type( e, fuel_type_battery ) ) {
id_and_variant = std::make_pair( "engine_working_internal", "electric" );
} else {
id_and_variant = std::make_pair( "engine_working_internal", "combustion" );
}
}
}
if( !is_channel_playing( 23 ) ) {
play_ambient_variant_sound( id_and_variant.first, id_and_variant.second,
sfx::get_heard_volume( g->u.pos() ), 23, 1000 );
add_msg( m_debug, "START %s %s", id_and_variant.first, id_and_variant.second );
} else {
add_msg( m_debug, "PLAYING" );
}
int current_speed = veh->velocity;
bool in_reverse = false;
if( current_speed <= -1 ) {
current_speed = current_speed * -1;
in_reverse = true;
}
float pitch = 1.0f;
int safe_speed = veh->safe_velocity();
int current_gear;
if( in_reverse == true ) {
current_gear = -1;
} else if( current_speed == 0 ) {
current_gear = 0;
} else if( current_speed > 0 && current_speed <= safe_speed / 12 ) {
current_gear = 1;
} else if( current_speed > safe_speed / 12 && current_speed <= safe_speed / 5 ) {
current_gear = 2;
} else if( current_speed > safe_speed / 5 && current_speed <= safe_speed / 4 ) {
current_gear = 3;
} else if( current_speed > safe_speed / 4 && current_speed <= safe_speed / 3 ) {
current_gear = 4;
} else if( current_speed > safe_speed / 3 && current_speed <= safe_speed / 2 ) {
current_gear = 5;
} else {
current_gear = 6;
}
if( veh->has_engine_type( fuel_type_muscle, true ) ||
veh->has_engine_type( fuel_type_wind, true ) ) {
current_gear = previous_gear;
}
if( current_gear > previous_gear ) {
play_variant_sound_pitch( "vehicle", "gear_shift", get_heard_volume( g->u.pos() ), 0, 0.8 );
add_msg( m_debug, "GEAR UP" );
} else if( current_gear < previous_gear ) {
play_variant_sound_pitch( "vehicle", "gear_shift", get_heard_volume( g->u.pos() ), 0, 1.2 );
add_msg( m_debug, "GEAR DOWN" );
}
if( ( safe_speed != 0 ) ) {
if( current_gear == 0 ) {
pitch = 1.0f;
} else if( current_gear == -1 ) {
pitch = 1.2f;
} else {
pitch = 1.0f - static_cast<float>( current_speed ) / static_cast<float>( safe_speed );
}
}
if( pitch <= 0.5f ) {
pitch = 0.5f;
}
if( current_speed != previous_speed ) {
Mix_HaltChannel( 23 );
add_msg( m_debug, "STOP speed %d =/= %d", current_speed, previous_speed );
play_ambient_variant_sound( id_and_variant.first, id_and_variant.second,
sfx::get_heard_volume( g->u.pos() ), 23, 1000, pitch );
add_msg( m_debug, string_format( "PITCH %f", pitch ) );
}
previous_speed = current_speed;
previous_gear = current_gear;
}
void sfx::do_vehicle_exterior_engine_sfx()
{
/** Channel Assignments:
22: engine working external
**/
// early bail-outs for efficiency
if( g->u.in_vehicle ) {
fade_audio_channel( 22, 300 );
add_msg( m_debug, "STOP 22, IN CAR" );
return;
}
if( g->u.in_sleep_state() && !audio_muted ) {
fade_audio_channel( -1, 300 );
audio_muted = true;
return;
} else if( g->u.in_sleep_state() && audio_muted ) {
return;
}
VehicleList vehs = g->m.get_vehicles();
unsigned char noise_factor = 0;
unsigned char vol = 0;
vehicle *veh = nullptr;
for( wrapped_vehicle vehicle : vehs ) {
if( vehicle.v->vehicle_noise > 0 &&
vehicle.v->vehicle_noise - rl_dist( g->u.pos(), vehicle.v->global_pos3() ) > noise_factor ) {
noise_factor = vehicle.v->vehicle_noise - rl_dist( g->u.pos(), vehicle.v->global_pos3() );
veh = vehicle.v;
}
}
if( !noise_factor || !veh ) {
fade_audio_channel( 22, 300 );
add_msg( m_debug, "STOP 22, NO NOISE" );
return;
}
vol = MIX_MAX_VOLUME * noise_factor / veh->vehicle_noise;
std::pair<std::string, std::string> id_and_variant;
for( size_t e = 0; e < veh->engines.size(); ++e ) {
if( veh->is_engine_on( e ) ) {
if( sfx::has_variant_sound( "engine_working_external",
veh->part_info( veh->engines[ e ] ).get_id().str() ) ) {
id_and_variant = std::make_pair( "engine_working_external",
veh->part_info( veh->engines[ e ] ).get_id().str() );
} else if( veh->is_engine_type( e, fuel_type_muscle ) ) {
id_and_variant = std::make_pair( "engine_working_external", "muscle" );
} else if( veh->is_engine_type( e, fuel_type_wind ) ) {
id_and_variant = std::make_pair( "engine_working_external", "wind" );
} else if( veh->is_engine_type( e, fuel_type_battery ) ) {
id_and_variant = std::make_pair( "engine_working_external", "electric" );
} else {
id_and_variant = std::make_pair( "engine_working_external", "combustion" );
}
}
}
if( is_channel_playing( 22 ) ) {
if( engine_external_id_and_variant == id_and_variant ) {
Mix_SetPosition( 22, get_heard_angle( veh->global_pos3() ), 0 );
set_channel_volume( 22, vol );
add_msg( m_debug, "PLAYING, vol: ex:%d true:%d", vol, Mix_Volume( 22, -1 ) );
} else {
engine_external_id_and_variant = id_and_variant;
Mix_HaltChannel( 22 );
add_msg( m_debug, "STOP, change id/var" );
play_ambient_variant_sound( id_and_variant.first, id_and_variant.second, 128, 22, 0 );
Mix_SetPosition( 22, get_heard_angle( veh->global_pos3() ), 0 );
set_channel_volume( 22, vol );
add_msg( m_debug, "START22 %s %s vol: %d", id_and_variant.first, id_and_variant.second,
Mix_Volume( 22, -1 ) );
}
} else {
play_ambient_variant_sound( id_and_variant.first, id_and_variant.second, 128, 22, 0 );
add_msg( m_debug, "Vol: %d %d", vol, Mix_Volume( 22, -1 ) );
Mix_SetPosition( 22, get_heard_angle( veh->global_pos3() ), 0 );
add_msg( m_debug, "Vol: %d %d", vol, Mix_Volume( 22, -1 ) );
set_channel_volume( 22, vol );
add_msg( m_debug, "START22 NEW %s %s vol: ex:%d true:%d", id_and_variant.first,
id_and_variant.second, vol, Mix_Volume( 22, -1 ) );
}
}
void sfx::do_ambient()
{
/* Channel assignments:
0: Daytime outdoors env
1: Nighttime outdoors env
2: Underground env
3: Indoors env
4: Indoors rain env
5: Outdoors snow env
6: Outdoors flurry env
7: Outdoors thunderstorm env
8: Outdoors rain env
9: Outdoors drizzle env
10: Deafness tone
11: Danger high theme
12: Danger medium theme
13: Danger low theme
14: Danger extreme theme
15: Stamina 75%
16: Stamina 50%
17: Stamina 35%
18: Idle chainsaw
19: Chainsaw theme
20: Outdoor blizzard
21: Player activities
22: Exterior engine sound
23: Interior engine sound
Group Assignments:
1: SFX related to weather
2: SFX related to time of day
3: SFX related to context themes
4: SFX related to Fatigue
*/
if( g->u.in_sleep_state() && !audio_muted ) {
fade_audio_channel( -1, 300 );
audio_muted = true;
return;
} else if( g->u.in_sleep_state() && audio_muted ) {
return;
}
audio_muted = false;
const bool is_deaf = g->u.is_deaf();
const int heard_volume = get_heard_volume( g->u.pos() );
const bool is_underground = g->u.pos().z < 0;
const bool is_sheltered = g->is_sheltered( g->u.pos() );
const bool weather_changed = g->weather.weather != previous_weather;
// Step in at night time / we are not indoors
if( calendar::turn.is_night() && !is_sheltered &&
!is_channel_playing( 1 ) && !is_deaf ) {
fade_audio_group( 2, 1000 );
play_ambient_variant_sound( "environment", "nighttime", heard_volume, 1, 1000 );
// Step in at day time / we are not indoors
} else if( !calendar::turn.is_night() && !is_channel_playing( 0 ) &&
!is_sheltered && !is_deaf ) {
fade_audio_group( 2, 1000 );
play_ambient_variant_sound( "environment", "daytime", heard_volume, 0, 1000 );
}
// We are underground
if( ( is_underground && !is_channel_playing( 2 ) &&
!is_deaf ) || ( is_underground &&
weather_changed && !is_deaf ) ) {
fade_audio_group( 1, 1000 );
fade_audio_group( 2, 1000 );
play_ambient_variant_sound( "environment", "underground", heard_volume, 2,
1000 );
// We are indoors
} else if( ( is_sheltered && !is_underground &&
!is_channel_playing( 3 ) && !is_deaf ) ||
( is_sheltered && !is_underground &&
weather_changed && !is_deaf ) ) {
fade_audio_group( 1, 1000 );
fade_audio_group( 2, 1000 );
play_ambient_variant_sound( "environment", "indoors", heard_volume, 3, 1000 );
}
// We are indoors and it is also raining
if( g->weather.weather >= WEATHER_DRIZZLE && g->weather.weather <= WEATHER_ACID_RAIN &&
!is_underground
&& is_sheltered && !is_channel_playing( 4 ) ) {
play_ambient_variant_sound( "environment", "indoors_rain", heard_volume, 4,
1000 );
}
if( ( !is_sheltered && g->weather.weather != WEATHER_CLEAR
&& !is_channel_playing( 5 ) &&
!is_channel_playing( 6 ) && !is_channel_playing( 7 ) && !is_channel_playing( 8 )
&&
!is_channel_playing( 9 ) && !is_deaf )
|| ( !is_sheltered &&
weather_changed && !is_deaf ) ) {
fade_audio_group( 1, 1000 );
// We are outside and there is precipitation
switch( g->weather.weather ) {
case WEATHER_ACID_DRIZZLE:
case WEATHER_DRIZZLE:
play_ambient_variant_sound( "environment", "WEATHER_DRIZZLE", heard_volume, 9,
1000 );
break;
case WEATHER_RAINY:
play_ambient_variant_sound( "environment", "WEATHER_RAINY", heard_volume, 8,
1000 );
break;
case WEATHER_ACID_RAIN:
case WEATHER_THUNDER:
case WEATHER_LIGHTNING:
play_ambient_variant_sound( "environment", "WEATHER_THUNDER", heard_volume, 7,
1000 );
break;
case WEATHER_FLURRIES:
play_ambient_variant_sound( "environment", "WEATHER_FLURRIES", heard_volume, 6,
1000 );
break;
case WEATHER_CLEAR:
case WEATHER_SUNNY:
case WEATHER_CLOUDY:
case WEATHER_SNOWSTORM:
play_ambient_variant_sound( "environment", "WEATHER_SNOWSTORM", heard_volume, 20,
1000 );
break;
case WEATHER_SNOW:
play_ambient_variant_sound( "environment", "WEATHER_SNOW", heard_volume, 5,
1000 );
break;
case WEATHER_NULL:
case NUM_WEATHER_TYPES:
// nothing here, those are pseudo-types, they should not be active at all.
break;
}
}
// Keep track of weather to compare for next iteration
previous_weather = g->weather.weather;
}
// firing is the item that is fired. It may be the wielded gun, but it can also be an attached
// gunmod. p is the character that is firing, this may be a pseudo-character (used by monattack/
// vehicle turrets) or a NPC.
void sfx::generate_gun_sound( const player &source_arg, const item &firing )
{
end_sfx_timestamp = std::chrono::high_resolution_clock::now();
sfx_time = end_sfx_timestamp - start_sfx_timestamp;
if( std::chrono::duration_cast<std::chrono::milliseconds> ( sfx_time ).count() < 80 ) {
return;
}
const tripoint source = source_arg.pos();
int heard_volume = get_heard_volume( source );
if( heard_volume <= 30 ) {
heard_volume = 30;
}
itype_id weapon_id = firing.typeId();
int angle = 0;
int distance = 0;
std::string selected_sound;
// this does not mean p == g->u (it could be a vehicle turret)
if( g->u.pos() == source ) {
selected_sound = "fire_gun";
const auto mods = firing.gunmods();
if( std::any_of( mods.begin(), mods.end(),
[]( const item * e ) {
return e->type->gunmod->loudness < 0;
} ) ) {
weapon_id = "weapon_fire_suppressed";
}
} else {
angle = get_heard_angle( source );
distance = rl_dist( g->u.pos(), source );
if( distance <= 17 ) {
selected_sound = "fire_gun";
} else {
selected_sound = "fire_gun_distant";
}
}
play_variant_sound( selected_sound, weapon_id, heard_volume, angle, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
}
namespace sfx
{
struct sound_thread {
sound_thread( const tripoint &source, const tripoint &target, bool hit, bool targ_mon,
const std::string &material );
bool hit;
bool targ_mon;
std::string material;
skill_id weapon_skill;
int weapon_volume;
// volume and angle for calls to play_variant_sound
int ang_src;
int vol_src;
int vol_targ;
int ang_targ;
// Operator overload required for thread API.
void operator()() const;
};
} // namespace sfx
void sfx::generate_melee_sound( const tripoint &source, const tripoint &target, bool hit,
bool targ_mon,
const std::string &material )
{
// If creating a new thread for each invocation is to much, we have to consider a thread
// pool or maybe a single thread that works continuously, but that requires a queue or similar
// to coordinate its work.
std::thread the_thread( sound_thread( source, target, hit, targ_mon, material ) );
the_thread.detach();
}
sfx::sound_thread::sound_thread( const tripoint &source, const tripoint &target, const bool hit,
const bool targ_mon, const std::string &material )
: hit( hit )
, targ_mon( targ_mon )
, material( material )
{
// This is function is run in the main thread.
const int heard_volume = get_heard_volume( source );
const player *p = g->critter_at<npc>( source );
if( !p ) {
p = &g->u;
// sound comes from the same place as the player is, calculation of angle wouldn't work
ang_src = 0;
vol_src = heard_volume;
vol_targ = heard_volume;
} else {
ang_src = get_heard_angle( source );
vol_src = std::max( heard_volume - 30, 0 );
vol_targ = std::max( heard_volume - 20, 0 );
}
ang_targ = get_heard_angle( target );
weapon_skill = p->weapon.melee_skill();
weapon_volume = p->weapon.volume() / units::legacy_volume_factor;
}
// Operator overload required for thread API.
void sfx::sound_thread::operator()() const
{
// This is function is run in a separate thread. One must be careful and not access game data
// that might change (e.g. g->u.weapon, the character could switch weapons while this thread
// runs).
std::this_thread::sleep_for( std::chrono::milliseconds( rng( 1, 2 ) ) );
std::string variant_used;
static const skill_id skill_bashing( "bashing" );
static const skill_id skill_cutting( "cutting" );
static const skill_id skill_stabbing( "stabbing" );
if( weapon_skill == skill_bashing && weapon_volume <= 8 ) {
variant_used = "small_bash";
play_variant_sound( "melee_swing", "small_bash", vol_src, ang_src, 0.8, 1.2 );
} else if( weapon_skill == skill_bashing && weapon_volume >= 9 ) {
variant_used = "big_bash";
play_variant_sound( "melee_swing", "big_bash", vol_src, ang_src, 0.8, 1.2 );
} else if( ( weapon_skill == skill_cutting || weapon_skill == skill_stabbing ) &&
weapon_volume <= 6 ) {
variant_used = "small_cutting";
play_variant_sound( "melee_swing", "small_cutting", vol_src, ang_src, 0.8, 1.2 );
} else if( ( weapon_skill == skill_cutting || weapon_skill == skill_stabbing ) &&
weapon_volume >= 7 ) {
variant_used = "big_cutting";
play_variant_sound( "melee_swing", "big_cutting", vol_src, ang_src, 0.8, 1.2 );
} else {
variant_used = "default";
play_variant_sound( "melee_swing", "default", vol_src, ang_src, 0.8, 1.2 );
}
if( hit ) {
if( targ_mon ) {
if( material == "steel" ) {
std::this_thread::sleep_for( std::chrono::milliseconds( rng( weapon_volume * 12,
weapon_volume * 16 ) ) );
play_variant_sound( "melee_hit_metal", variant_used, vol_targ, ang_targ, 0.8, 1.2 );
} else {
std::this_thread::sleep_for( std::chrono::milliseconds( rng( weapon_volume * 12,
weapon_volume * 16 ) ) );
play_variant_sound( "melee_hit_flesh", variant_used, vol_targ, ang_targ, 0.8, 1.2 );
}
} else {
std::this_thread::sleep_for( std::chrono::milliseconds( rng( weapon_volume * 9,
weapon_volume * 12 ) ) );
play_variant_sound( "melee_hit_flesh", variant_used, vol_targ, ang_targ, 0.8, 1.2 );
}
}
}
void sfx::do_projectile_hit( const Creature &target )
{
const int heard_volume = sfx::get_heard_volume( target.pos() );
const int angle = get_heard_angle( target.pos() );
if( target.is_monster() ) {
const monster &mon = dynamic_cast<const monster &>( target );
static const std::set<material_id> fleshy = {
material_id( "flesh" ),
material_id( "hflesh" ),
material_id( "iflesh" ),
material_id( "veggy" ),
material_id( "bone" ),
};
const bool is_fleshy = std::any_of( fleshy.begin(), fleshy.end(), [&mon]( const material_id & m ) {
return mon.made_of( m );
} );
if( is_fleshy ) {
play_variant_sound( "bullet_hit", "hit_flesh", heard_volume, angle, 0.8, 1.2 );
return;
} else if( mon.made_of( material_id( "stone" ) ) ) {
play_variant_sound( "bullet_hit", "hit_wall", heard_volume, angle, 0.8, 1.2 );
return;
} else if( mon.made_of( material_id( "steel" ) ) ) {
play_variant_sound( "bullet_hit", "hit_metal", heard_volume, angle, 0.8, 1.2 );
return;
} else {
play_variant_sound( "bullet_hit", "hit_flesh", heard_volume, angle, 0.8, 1.2 );
return;
}
}
play_variant_sound( "bullet_hit", "hit_flesh", heard_volume, angle, 0.8, 1.2 );
}
void sfx::do_player_death_hurt( const player &target, bool death )
{
int heard_volume = get_heard_volume( target.pos() );
const bool male = target.male;
if( !male && !death ) {
play_variant_sound( "deal_damage", "hurt_f", heard_volume );
} else if( male && !death ) {
play_variant_sound( "deal_damage", "hurt_m", heard_volume );
} else if( !male && death ) {
play_variant_sound( "clean_up_at_end", "death_f", heard_volume );
} else if( male && death ) {
play_variant_sound( "clean_up_at_end", "death_m", heard_volume );
}
}
void sfx::do_danger_music()
{
if( g->u.in_sleep_state() && !audio_muted ) {
fade_audio_channel( -1, 100 );
audio_muted = true;
return;
} else if( ( g->u.in_sleep_state() && audio_muted ) || is_channel_playing( 19 ) ) {
fade_audio_group( 3, 1000 );
return;
}
audio_muted = false;
int hostiles = 0;
for( auto &critter : g->u.get_visible_creatures( 40 ) ) {
if( g->u.attitude_to( *critter ) == Creature::A_HOSTILE ) {
hostiles++;
}
}
if( hostiles == prev_hostiles ) {
return;
}
if( hostiles <= 4 ) {
fade_audio_group( 3, 1000 );
prev_hostiles = hostiles;
return;
} else if( hostiles >= 5 && hostiles <= 9 && !is_channel_playing( 13 ) ) {
fade_audio_group( 3, 1000 );
play_ambient_variant_sound( "danger_low", "default", 100, 13, 1000 );
prev_hostiles = hostiles;
return;
} else if( hostiles >= 10 && hostiles <= 14 && !is_channel_playing( 12 ) ) {
fade_audio_group( 3, 1000 );
play_ambient_variant_sound( "danger_medium", "default", 100, 12, 1000 );
prev_hostiles = hostiles;
return;
} else if( hostiles >= 15 && hostiles <= 19 && !is_channel_playing( 11 ) ) {
fade_audio_group( 3, 1000 );
play_ambient_variant_sound( "danger_high", "default", 100, 11, 1000 );
prev_hostiles = hostiles;
return;
} else if( hostiles >= 20 && !is_channel_playing( 14 ) ) {
fade_audio_group( 3, 1000 );
play_ambient_variant_sound( "danger_extreme", "default", 100, 14, 1000 );
prev_hostiles = hostiles;
return;
}
prev_hostiles = hostiles;
}
void sfx::do_fatigue()
{
/*15: Stamina 75%
16: Stamina 50%
17: Stamina 25%*/
if( g->u.stamina >= g->u.get_stamina_max() * .75 ) {
fade_audio_group( 4, 2000 );
return;
} else if( g->u.stamina <= g->u.get_stamina_max() * .74
&& g->u.stamina >= g->u.get_stamina_max() * .5 &&
g->u.male && !is_channel_playing( 15 ) ) {
fade_audio_group( 4, 1000 );
play_ambient_variant_sound( "plmove", "fatigue_m_low", 100, 15, 1000 );
return;
} else if( g->u.stamina <= g->u.get_stamina_max() * .49
&& g->u.stamina >= g->u.get_stamina_max() * .25 &&
g->u.male && !is_channel_playing( 16 ) ) {
fade_audio_group( 4, 1000 );
play_ambient_variant_sound( "plmove", "fatigue_m_med", 100, 16, 1000 );
return;
} else if( g->u.stamina <= g->u.get_stamina_max() * .24 && g->u.stamina >= 0 &&
g->u.male && !is_channel_playing( 17 ) ) {
fade_audio_group( 4, 1000 );
play_ambient_variant_sound( "plmove", "fatigue_m_high", 100, 17, 1000 );
return;
} else if( g->u.stamina <= g->u.get_stamina_max() * .74
&& g->u.stamina >= g->u.get_stamina_max() * .5 &&
!g->u.male && !is_channel_playing( 15 ) ) {
fade_audio_group( 4, 1000 );
play_ambient_variant_sound( "plmove", "fatigue_f_low", 100, 15, 1000 );
return;
} else if( g->u.stamina <= g->u.get_stamina_max() * .49
&& g->u.stamina >= g->u.get_stamina_max() * .25 &&
!g->u.male && !is_channel_playing( 16 ) ) {
fade_audio_group( 4, 1000 );
play_ambient_variant_sound( "plmove", "fatigue_f_med", 100, 16, 1000 );
return;
} else if( g->u.stamina <= g->u.get_stamina_max() * .24 && g->u.stamina >= 0 &&
!g->u.male && !is_channel_playing( 17 ) ) {
fade_audio_group( 4, 1000 );
play_ambient_variant_sound( "plmove", "fatigue_f_high", 100, 17, 1000 );
return;
}
}
void sfx::do_hearing_loss( int turns )
{
g_sfx_volume_multiplier = .1;
fade_audio_group( 1, 50 );
fade_audio_group( 2, 50 );
// Negative duration is just insuring we stay in sync with player condition,
// don't play any of the sound effects for going deaf.
if( turns == -1 ) {
return;
}
play_variant_sound( "environment", "deafness_shock", 100 );
play_variant_sound( "environment", "deafness_tone_start", 100 );
if( turns <= 35 ) {
play_ambient_variant_sound( "environment", "deafness_tone_light", 90, 10, 100 );
} else if( turns <= 90 ) {
play_ambient_variant_sound( "environment", "deafness_tone_medium", 90, 10, 100 );
} else if( turns >= 91 ) {
play_ambient_variant_sound( "environment", "deafness_tone_heavy", 90, 10, 100 );
}
}
void sfx::remove_hearing_loss()
{
stop_sound_effect_fade( 10, 300 );
g_sfx_volume_multiplier = 1;
do_ambient();
}
void sfx::do_footstep()
{
end_sfx_timestamp = std::chrono::high_resolution_clock::now();
sfx_time = end_sfx_timestamp - start_sfx_timestamp;
if( std::chrono::duration_cast<std::chrono::milliseconds> ( sfx_time ).count() > 400 ) {
int heard_volume = sfx::get_heard_volume( g->u.pos() );
const auto terrain = g->m.ter( g->u.pos() ).id();
static const std::set<ter_str_id> grass = {
ter_str_id( "t_grass" ),
ter_str_id( "t_shrub" ),
ter_str_id( "t_shrub_peanut" ),
ter_str_id( "t_shrub_peanut_harvested" ),
ter_str_id( "t_shrub_blueberry" ),
ter_str_id( "t_shrub_blueberry_harvested" ),
ter_str_id( "t_shrub_strawberry" ),
ter_str_id( "t_shrub_strawberry_harvested" ),
ter_str_id( "t_shrub_blackberry" ),
ter_str_id( "t_shrub_blackberry_harvested" ),
ter_str_id( "t_shrub_huckleberry" ),
ter_str_id( "t_shrub_huckleberry_harvested" ),
ter_str_id( "t_shrub_raspberry" ),
ter_str_id( "t_shrub_raspberry_harvested" ),
ter_str_id( "t_shrub_grape" ),
ter_str_id( "t_shrub_grape_harvested" ),
ter_str_id( "t_shrub_rose" ),
ter_str_id( "t_shrub_rose_harvested" ),
ter_str_id( "t_shrub_hydrangea" ),
ter_str_id( "t_shrub_hydrangea_harvested" ),
ter_str_id( "t_shrub_lilac" ),
ter_str_id( "t_shrub_lilac_harvested" ),
ter_str_id( "t_underbrush" ),
ter_str_id( "t_underbrush_harvested_spring" ),
ter_str_id( "t_underbrush_harvested_summer" ),
ter_str_id( "t_underbrush_harvested_autumn" ),
ter_str_id( "t_underbrush_harvested_winter" ),
ter_str_id( "t_moss" ),
ter_str_id( "t_grass_white" ),
ter_str_id( "t_grass_long" ),
ter_str_id( "t_grass_tall" ),
ter_str_id( "t_grass_dead" ),
ter_str_id( "t_grass_golf" ),
ter_str_id( "t_golf_hole" ),
ter_str_id( "t_trunk" ),
ter_str_id( "t_stump" ),
};
static const std::set<ter_str_id> dirt = {
ter_str_id( "t_dirt" ),
ter_str_id( "t_dirtmound" ),
ter_str_id( "t_dirtmoundfloor" ),
ter_str_id( "t_sand" ),
ter_str_id( "t_clay" ),
ter_str_id( "t_dirtfloor" ),
ter_str_id( "t_palisade_gate_o" ),
ter_str_id( "t_sandbox" ),
ter_str_id( "t_claymound" ),
ter_str_id( "t_sandmound" ),
ter_str_id( "t_rootcellar" ),
ter_str_id( "t_railroad_rubble" ),
ter_str_id( "t_railroad_track" ),
ter_str_id( "t_railroad_track_h" ),
ter_str_id( "t_railroad_track_v" ),
ter_str_id( "t_railroad_track_d" ),
ter_str_id( "t_railroad_track_d1" ),
ter_str_id( "t_railroad_track_d2" ),
ter_str_id( "t_railroad_tie" ),
ter_str_id( "t_railroad_tie_d" ),
ter_str_id( "t_railroad_tie_d" ),
ter_str_id( "t_railroad_tie_h" ),
ter_str_id( "t_railroad_tie_v" ),
ter_str_id( "t_railroad_tie_d" ),
ter_str_id( "t_railroad_track_on_tie" ),
ter_str_id( "t_railroad_track_h_on_tie" ),
ter_str_id( "t_railroad_track_v_on_tie" ),
ter_str_id( "t_railroad_track_d_on_tie" ),
ter_str_id( "t_railroad_tie" ),
ter_str_id( "t_railroad_tie_h" ),
ter_str_id( "t_railroad_tie_v" ),
ter_str_id( "t_railroad_tie_d1" ),
ter_str_id( "t_railroad_tie_d2" ),
};
static const std::set<ter_str_id> metal = {
ter_str_id( "t_ov_smreb_cage" ),
ter_str_id( "t_metal_floor" ),
ter_str_id( "t_grate" ),
ter_str_id( "t_bridge" ),
ter_str_id( "t_elevator" ),
ter_str_id( "t_guardrail_bg_dp" ),
ter_str_id( "t_slide" ),
ter_str_id( "t_conveyor" ),
ter_str_id( "t_machinery_light" ),
ter_str_id( "t_machinery_heavy" ),
ter_str_id( "t_machinery_old" ),
ter_str_id( "t_machinery_electronic" ),
};
static const std::set<ter_str_id> water = {
ter_str_id( "t_water_moving_sh" ),
ter_str_id( "t_water_moving_dp" ),
ter_str_id( "t_water_sh" ),
ter_str_id( "t_water_dp" ),
ter_str_id( "t_swater_sh" ),
ter_str_id( "t_swater_dp" ),
ter_str_id( "t_water_pool" ),
ter_str_id( "t_sewage" ),
};
static const std::set<ter_str_id> chain_fence = {
ter_str_id( "t_chainfence" ),
};
if( !g->u.wearing_something_on( bp_foot_l ) ) {
play_variant_sound( "plmove", "walk_barefoot", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else if( sfx::has_variant_sound( "plmove", terrain.str() ) ) {
play_variant_sound( "plmove", terrain.str(), heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else if( grass.count( terrain ) > 0 ) {
play_variant_sound( "plmove", "walk_grass", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else if( dirt.count( terrain ) > 0 ) {
play_variant_sound( "plmove", "walk_dirt", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else if( metal.count( terrain ) > 0 ) {
play_variant_sound( "plmove", "walk_metal", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else if( water.count( terrain ) > 0 ) {
play_variant_sound( "plmove", "walk_water", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else if( chain_fence.count( terrain ) > 0 ) {
play_variant_sound( "plmove", "clear_obstacle", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
} else {
play_variant_sound( "plmove", "walk_tarmac", heard_volume, 0, 0.8, 1.2 );
start_sfx_timestamp = std::chrono::high_resolution_clock::now();
return;
}
}
}
void sfx::do_obstacle( const std::string &obst )
{
int heard_volume = sfx::get_heard_volume( g->u.pos() );
//const auto terrain = g->m.ter( g->u.pos() ).id();
static const std::set<std::string> water = {
"t_water_sh",
"t_water_dp",
"t_water_moving_sh",
"t_water_moving_dp",
"t_swater_sh",
"t_swater_dp",
"t_water_pool",
"t_sewage",
};
if( sfx::has_variant_sound( "plmove", obst ) ) {
play_variant_sound( "plmove", obst, heard_volume, 0, 0.8, 1.2 );
} else if( water.count( obst ) > 0 ) {
play_variant_sound( "plmove", "walk_water", heard_volume, 0, 0.8, 1.2 );
} else {
play_variant_sound( "plmove", "clear_obstacle", heard_volume, 0, 0.8, 1.2 );
}
}
void sfx::play_activity_sound( const std::string &id, const std::string &variant, int volume )
{
if( act != g->u.activity.id() ) {
act = g->u.activity.id();
play_ambient_variant_sound( id, variant, volume, 21, 0 );
}
}
void sfx::end_activity_sounds()
{
act = activity_id::NULL_ID();
fade_audio_channel( 21, 2000 );
}
#else // if defined(SDL_SOUND)
/** Dummy implementations for builds without sound */
/*@{*/
void sfx::load_sound_effects( JsonObject & ) { }
void sfx::load_sound_effect_preload( JsonObject & ) { }
void sfx::load_playlist( JsonObject & ) { }
void sfx::play_variant_sound( const std::string &, const std::string &, int, int, float, float ) { }
void sfx::play_variant_sound( const std::string &, const std::string &, int ) { }
void sfx::play_ambient_variant_sound( const std::string &, const std::string &, int, int, int,
float ) { }
void sfx::play_activity_sound( const std::string &, const std::string &, int ) { }
void sfx::end_activity_sounds() { }
void sfx::generate_gun_sound( const player &, const item & ) { }
void sfx::generate_melee_sound( const tripoint &, const tripoint &, bool, bool,
const std::string & ) { }
void sfx::do_hearing_loss( int ) { }
void sfx::remove_hearing_loss() { }
void sfx::do_projectile_hit( const Creature & ) { }
void sfx::do_footstep() { }
void sfx::do_danger_music() { }
void sfx::do_vehicle_engine_sfx() { }
void sfx::do_vehicle_exterior_engine_sfx() { }
void sfx::do_ambient() { }
void sfx::fade_audio_group( int, int ) { }
void sfx::fade_audio_channel( int, int ) { }
bool sfx::is_channel_playing( int )
{
return false;
}
int sfx::set_channel_volume( int, int )
{
return 0;
}
bool sfx::has_variant_sound( const std::string &, const std::string & )
{
return false;
}
void sfx::stop_sound_effect_fade( int, int ) { }
void sfx::do_player_death_hurt( const player &, bool ) { }
void sfx::do_fatigue() { }
void sfx::do_obstacle( const std::string & ) { }
/*@}*/
#endif // if defined(SDL_SOUND)
/** Functions from sfx that do not use the SDL_mixer API at all. They can be used in builds
* without sound support. */
/*@{*/
int sfx::get_heard_volume( const tripoint &source )
{
int distance = rl_dist( g->u.pos(), source );
// fract = -100 / 24
const float fract = -4.166666;
int heard_volume = fract * distance - 1 + 100;
if( heard_volume <= 0 ) {
heard_volume = 0;
}
heard_volume *= g_sfx_volume_multiplier;
return ( heard_volume );
}
int sfx::get_heard_angle( const tripoint &source )
{
int angle = coord_to_angle( g->u.pos(), source ) + 90;
//add_msg(m_warning, "angle: %i", angle);
return ( angle );
}
/*@}*/
You can’t perform that action at this time.