Skip to content
Permalink
Tree: e79ded4f8e
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1018 lines (941 sloc) 39.7 KB
#include "sounds.h"
#include "coordinate_conversions.h"
#include "game.h"
#include "map.h"
#include "debug.h"
#include "enums.h"
#include "overmapbuffer.h"
#include "translations.h"
#include "messages.h"
#include "monster.h"
#include "line.h"
#include "mtype.h"
#include "weather.h"
#include "npc.h"
#include "item.h"
#include "player.h"
#include "path_info.h"
#include "options.h"
#include "time.h"
#include "mapdata.h"
#include "itype.h"
#include <chrono>
#include <algorithm>
#include <cmath>
#ifdef SDL_SOUND
# include <SDL_mixer.h>
# include <thread>
# if ((defined _WIN32 || defined WINDOWS) && !defined _MSC_VER)
# include "mingw.thread.h"
# endif
#endif
#define dbg(x) DebugLog((DebugLevel)(x),D_SDL) << __FILE__ << ":" << __LINE__ << ": "
weather_type previous_weather;
int prev_hostiles = 0;
int deafness_turns = 0;
int current_deafness_turns = 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;
const efftype_id effect_deaf( "deaf" );
const efftype_id effect_sleep( "sleep" );
struct sound_event {
int volume;
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, std::string description )
{
sound( p, vol, description, true );
}
void sounds::sound( const tripoint &p, int vol, 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;
}
recent_sounds.emplace_back( std::make_pair( p, vol ) );
sounds_since_last_turn.emplace_back(
std::make_pair( p, sound_event {vol, description, ambient, false, id, variant} ) );
}
void sounds::add_footstep( const tripoint &p, int volume, int, monster * )
{
sounds_since_last_turn.emplace_back(
std::make_pair( p, sound_event {volume, "", 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 combanatorial explosion.
std::vector<centroid> sound_clusters;
const int num_seed_clusters = std::max( std::min( recent_sounds.size(), ( size_t ) 10 ),
( 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 * SEEX, MAPSIZE * SEEY );
// 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.
{
( float ) recent_sounds[index].first.x, ( float ) recent_sounds[index].first.y,
( float ) recent_sounds[index].first.z,
( float ) recent_sounds[index].second, ( 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 { ( int ) centroid_iter->x, ( int ) centroid_iter->y, ( 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 = ( 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 = ( float )( ( sound_event_pair.first.x * sound_event_pair.second ) +
( found_centroid->x * found_centroid->weight ) ) / volume_sum;
found_centroid->y = ( float )( ( sound_event_pair.first.y * sound_event_pair.second ) +
( found_centroid->y * found_centroid->weight ) ) / volume_sum;
found_centroid->z = ( 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, ( float ) sound_event_pair.second );
// Set the centroid weight to the sum of the weights.
found_centroid->weight = volume_sum;
}
return sound_clusters;
}
int get_signal_for_hordes( const centroid &centr )
{
//Volume in tiles. Signal fo hordes in submaps
const int vol = centr.volume - weather_data( g->weather ).sound_attn; //modify vol using weather vol.Weather can reduce monster hearing
const int min_vol_cap = 60;//Hordes can't hear volume lower than this
const int undeground_div = 2;//Coeffficient for volume reduction undeground
const int hordes_sig_div = SEEX;//Divider coefficent 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 / ( undeground_div * std::abs( centr.z ) ) : vol );
if( vol_hordes > min_vol_cap ) {
//Calculating horde hearing signal
int sig_power = std::ceil( ( 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_data( g->weather ).sound_attn;
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 (int i = 0, numz = g->num_zombies(); i < numz; i++) {
monster &critter = g->zombie(i);
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();
}
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_data( g->weather ).sound_attn;
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(), pos );
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 = ( 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( 40, ( felt_volume - 130 ) / 8 ) );
if( !p->has_trait( "DEADENED" ) ) {
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 int deafness_duration = ( felt_volume - 130 ) / 4;
p->add_effect( effect_deaf, deafness_duration );
if( p->is_deaf() ) {
is_deaf = true;
sfx::do_hearing_loss( deafness_duration );
continue;
}
}
// The heard volume of a sound is the player heard volume, regardless of true volume level.
const int heard_volume = ( int )( ( raw_volume - weather_vol ) * volume_multiplier ) - distance_to_sound;
if( heard_volume <= 0 ) {
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 );
}
// See if we need to wake someone up
if( p->has_effect( effect_sleep ) ) {
if( ( !( p->has_trait( "HEAVYSLEEPER" ) ||
p->has_trait( "HEAVYSLEEPER2" ) ) && dice( 2, 15 ) < heard_volume ) ||
( p->has_trait( "HEAVYSLEEPER" ) && dice( 3, 15 ) < heard_volume ) ||
( p->has_trait( "HEAVYSLEEPER2" ) && dice( 6, 15 ) < heard_volume ) ) {
//Not kidding about sleep-thru-firefight
p->wake_up();
add_msg( m_warning, _( "Something is making noise." ) );
} else {
continue;
}
}
const std::string &description = sound.description;
if( !sound.ambient && ( pos != p->pos() ) && !g->m.pl_sees( pos, distance_to_sound ) ) {
if( !p->activity.ignore_trivial ) {
const std::string query = description.empty()
? _( "Heard a noise!" )
: string_format( _( "Heard %s!" ), description.c_str() );
if( g->cancel_activity_or_ignore_query( query.c_str() ) ) {
p->activity.ignore_trivial = true;
for( auto activity : p->backlog ) {
activity.ignore_trivial = true;
}
}
}
}
if( !description.empty() ) {
// If it came from us, don't print a direction
if( pos == p->pos() ) {
add_msg( _( "You hear %s" ), description.c_str() );
} else {
// Else print a direction as well
std::string direction = direction_name( direction_from( p->pos(), pos ) );
add_msg( m_warning, _( "From the %s you hear %s" ), direction.c_str(), description.c_str() );
}
}
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 coord 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;
tripoint newp = pos;
int &newx = newp.x;
int &newy = newp.y;
for( newx = pos.x - err_offset; newx <= pos.x + err_offset; newx++ ) {
for( newy = pos.y - err_offset; newy <= pos.y + err_offset; newy++ ) {
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 );
}
}
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( (int)sound.x, (int)sound.y, (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" );
}
#ifdef SDL_SOUND
bool is_underground( const tripoint &p )
{
return p.z < 0;
}
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();
}
}
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
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.get_effect_int( effect_deaf ) > 0;
const int heard_volume = get_heard_volume( g->u.pos() );
const bool is_underground = ::is_underground( g->u.pos() );
const bool is_sheltered = g->is_sheltered( g->u.pos() );
const bool weather_changed = g->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_DRIZZLE && g->weather <= WEATHER_ACID_RAIN && !is_underground
&& !is_channel_playing( 4 ) ) {
play_ambient_variant_sound( "environment", "indoors_rain", heard_volume, 4,
1000 );
}
if( ( !is_sheltered && g->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 ) {
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:
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;
}
// 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 &p, 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 = p.pos();
int heard_volume = get_heard_volume( source );
if( heard_volume <= 30 ) {
heard_volume = 30;
}
itype_id weapon_id = firing.typeId();
int angle;
int distance;
std::string selected_sound;
// this does not mean p == g->u (it could be a vehicle turret)
if( g->u.pos() == source ) {
angle = 0;
distance = 0;
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( tripoint source, tripoint target, bool hit, bool targ_mon,
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;
int npc_index = g->npc_at( source );
if( npc_index == -1 ) {
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 {
p = g->active_npc[npc_index];
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 std::set<material_id> const 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 ) {
if( deafness_turns == 0 ) {
deafness_turns = turns;
g_sfx_volume_multiplier = .1;
fade_audio_group( 1, 50 );
fade_audio_group( 2, 50 );
play_variant_sound( "environment", "deafness_shock", 100 );
play_variant_sound( "environment", "deafness_tone_start", 100 );
if( deafness_turns <= 35 ) {
play_ambient_variant_sound( "environment", "deafness_tone_light", 90, 10, 100 );
} else if( deafness_turns <= 90 ) {
play_ambient_variant_sound( "environment", "deafness_tone_medium", 90, 10, 100 );
} else if( deafness_turns >= 91 ) {
play_ambient_variant_sound( "environment", "deafness_tone_heavy", 90, 10, 100 );
}
} else {
deafness_turns += turns;
}
}
void sfx::remove_hearing_loss() {
if( current_deafness_turns >= deafness_turns ) {
stop_sound_effect_fade( 10, 300 );
g_sfx_volume_multiplier = 1;
deafness_turns = 0;
current_deafness_turns = 0;
do_ambient();
}
current_deafness_turns++;
}
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 std::set<ter_str_id> const grass = {
ter_str_id( "t_grass" ),
ter_str_id( "t_shrub" ),
ter_str_id( "t_underbrush" ),
};
static std::set<ter_str_id> const dirt = {
ter_str_id( "t_dirt" ),
ter_str_id( "t_sand" ),
ter_str_id( "t_dirtfloor" ),
ter_str_id( "t_palisade_gate_o" ),
ter_str_id( "t_sandbox" ),
};
static std::set<ter_str_id> const 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" ),
};
static std::set<ter_str_id> const water = {
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 std::set<ter_str_id> const chain_fence = {
ter_str_id( "t_chainfence_h" ),
ter_str_id( "t_chainfence_v" ),
};
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( 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() {
int heard_volume = sfx::get_heard_volume( g->u.pos() );
const auto terrain = g->m.ter( g->u.pos() ).id();
static std::set<ter_str_id> const water = {
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" ),
};
if( water.count( terrain ) > 0 ) {
return;
} else {
play_variant_sound( "plmove", "clear_obstacle", heard_volume, 0, 0.8, 1.2 );
}
}
#else // ifdef SDL_SOUND
/** Dummy implementations for builds without sound */
/*@{*/
void sfx::load_sound_effects( JsonObject & ) { }
void sfx::load_playlist( JsonObject & ) { }
void sfx::play_variant_sound( std::string, std::string, int, int, float, float ) { }
void sfx::play_variant_sound( std::string, std::string, int ) { }
void sfx::play_ambient_variant_sound( std::string, std::string, int, int, int ) { }
void sfx::generate_gun_sound( const player&, const item& ) { }
void sfx::generate_melee_sound( const tripoint, const tripoint, bool, bool, 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_ambient() { }
void sfx::fade_audio_group( int, int ) { }
void sfx::fade_audio_channel( int, int ) { }
bool is_channel_playing( int ) { 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() { }
/*@}*/
#endif // ifdef 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 = g->m.coord_to_angle( g->u.posx(), g->u.posy(), source.x, source.y ) + 90;
//add_msg(m_warning, "angle: %i", angle);
return ( angle );
}
/*@}*/
You can’t perform that action at this time.