diff --git a/resource/dist/ModelExtras.ini b/resource/dist/ModelExtras.ini index 5f68a6ef..8f767f27 100755 --- a/resource/dist/ModelExtras.ini +++ b/resource/dist/ModelExtras.ini @@ -72,6 +72,8 @@ SoundEffects_GlobalEngineSound = 1 # Plays engine on/off sound SoundEffects_GlobalIndicatorSound = 1 # Plays indicator on/off sound SoundEffects_GlobalReverseSound = 1 # Plays reverse sound for big vehicles (check [TABLE]) SoundEffects_NonPlayerVehicles = 1 # Plays sound effects for traffic vehicles too +BrakePadSound = 0 # Realistic 3D brake pad friction squeak sound when braking (0 = Off, 1 = On) +BrakePadOnlySelected = 1 # Limit brake squeak sound only to specific vehicles in [TABLE] (0 = all vehicles, 1 = table only) [CONFIG] EnableLiveReload = 1 # Enables the 'MERELOAD' cheat to live-reload vehicle configs and INI settings without restarting @@ -95,3 +97,4 @@ SpotLightKey = 0x42 # Toggle police searchlight [TABLE] SoundEffects_BigVehicleModels = "403, 406, 407, 408, 414, 416, 427, 428, 431, 433, 437, 443, 455, 456, 486, 498, 499, 508, 514, 515, 524, 525, 528, 530, 532, 544, 552, 573, 574, 578, 588, 601, 609" # Vehicle model IDs for air brake and reverse warning sounds BackFireEffect_VehicleModels = "402, 411, 415, 424, 429, 434, 439, 451, 461, 468, 475, 477, 480, 494, 496, 502, 503, 504, 506, 521, 522, 535, 541, 558, 559, 560, 561, 562, 565, 581, 587, 589, 603, 604" # Vehicle model IDs for sports exhaust backfire flames +BrakePad_VehicleModels = "401, 404, 410, 412, 413, 418, 419, 420, 421, 422, 423, 426, 434, 436, 438, 439, 440, 442, 458, 459, 466, 467, 474, 475, 478, 479, 482, 483, 489, 491, 492, 496, 500, 504, 505, 507, 508, 516, 517, 518, 526, 527, 529, 534, 535, 536, 540, 542, 543, 545, 546, 547, 549, 550, 551, 554, 555, 565, 566, 567, 575, 576, 580, 582, 585, 589, 600, 604, 605" # Vehicle model IDs for brake pad squeak sound (used when BrakePadOnlySelected = 1) diff --git a/resource/dist/ModelExtras/audio/brake_pad.mp3 b/resource/dist/ModelExtras/audio/brake_pad.mp3 new file mode 100644 index 00000000..d8ab95d0 Binary files /dev/null and b/resource/dist/ModelExtras/audio/brake_pad.mp3 differ diff --git a/src/features/soundeffects.cpp b/src/features/soundeffects.cpp index e8042726..3b70c18f 100755 --- a/src/features/soundeffects.cpp +++ b/src/features/soundeffects.cpp @@ -7,6 +7,7 @@ using namespace plugin; std::vector ValidForReverseSound; +static std::vector ValidForBrakePadSound; #define ANIMGROUP_TRUCK 2 #define ANIMGROUP_BUS 15 @@ -17,6 +18,8 @@ static bool bEngineSounds = false; static bool bIndicatorSounds = false; static bool bAirbreakSounds = false; static bool bOnlyPlayerVehicle = true; +static bool bBrakePadSounds = false; +static bool bBrakePadOnlySelected = true; void SoundEffects::ReloadConfig() { @@ -30,11 +33,27 @@ void SoundEffects::ReloadConfig() bIndicatorSounds = gConfig.ReadBoolean("SOUND", "SoundEffects_GlobalIndicatorSound", gConfig.ReadBoolean("FEATURES", "SoundEffects_GlobalIndicatorSound", false)); bAirbreakSounds = gConfig.ReadBoolean("SOUND", "SoundEffects_GlobalAirbreakSound", gConfig.ReadBoolean("FEATURES", "SoundEffects_GlobalAirbreakSound", false)); bOnlyPlayerVehicle = !gConfig.ReadBoolean("SOUND", "SoundEffects_NonPlayerVehicles", gConfig.ReadBoolean("FEATURES", "SoundEffects_NonPlayerVehicles", false)); + + bBrakePadSounds = gConfig.ReadBoolean("SOUND", "BrakePadSound", gConfig.ReadBoolean("FEATURES", "BrakePadSound", false)); + bBrakePadOnlySelected = gConfig.ReadBoolean("SOUND", "BrakePadOnlySelected", gConfig.ReadBoolean("FEATURES", "BrakePadOnlySelected", true)); + + std::string brakeLine = gConfig.ReadString("TABLE", "BrakePad_VehicleModels", ""); + ValidForBrakePadSound.clear(); + Util::GetModelsFromIni(brakeLine, ValidForBrakePadSound); } void SoundEffects::Reload(CVehicle *pVeh) { ReloadConfig(); + if (pVeh) + { + auto &data = m_VehData.Get(pVeh); + if (data.m_hBrakePadStream) + { + AudioMgr::StopLoopStream(data.m_hBrakePadStream); + } + data.m_fBrakePadVol = 0.0f; + } } void SoundEffects::Init() @@ -175,4 +194,56 @@ void SoundEffects::ProcessVehicle(CVehicle *pVeh) } } + if (bBrakePadSounds && pVeh->m_nVehicleSubClass == VEHICLE_AUTOMOBILE) + { + CAutomobile *pAuto = static_cast(pVeh); + bool isEligible = !bBrakePadOnlySelected || (std::find(ValidForBrakePadSound.begin(), ValidForBrakePadSound.end(), model) != ValidForBrakePadSound.end()); + float targetVol = 0.0f; + + // Wheels must touch ground (not airborne) + bool bOnGround = (pAuto->m_nWheelsOnGround > 0); + + // Wheel lockup check: if front wheels are locked up / stopped spinning while car is moving at speed, don't squeak + bool bWheelLocked = (speed > 5.0f && std::abs(pAuto->m_fWheelSpeed[0]) < 0.01f && std::abs(pAuto->m_fWheelSpeed[1]) < 0.01f); + + if (isEligible && bOnGround && !bWheelLocked && !pVeh->bEngineBroken && !pVeh->bIsDrowning && !isBigVeh && !pVeh->bIsBig && !pVeh->bIsBus) + { + float brakeInput = pVeh->m_fBreakPedal; + if (brakeInput > 0.05f && speed > 0.2f) + { + float speedFactor = std::clamp((speed - 0.2f) / 4.0f, 0.0f, 1.0f); + targetVol = std::clamp(brakeInput, 0.35f, 1.0f) * speedFactor * 0.7f; + } + } + + if (targetVol > data.m_fBrakePadVol) + { + data.m_fBrakePadVol = std::min(targetVol, data.m_fBrakePadVol + 0.1f); + } + else if (targetVol < data.m_fBrakePadVol) + { + data.m_fBrakePadVol = std::max(targetVol, data.m_fBrakePadVol - 0.06f); + } + + if (data.m_fBrakePadVol > 0.01f) + { + static std::string path = MOD_DATA_PATH("audio/brake_pad.mp3"); + if (!data.m_hBrakePadStream) + { + data.m_hBrakePadStream = AudioMgr::PlayLoopStream(path, pVeh->GetPosition(), data.m_fBrakePadVol, 25.0f); + } + else + { + AudioMgr::UpdateLoopStream(data.m_hBrakePadStream, pVeh->GetPosition(), data.m_fBrakePadVol, 25.0f); + } + } + else + { + if (data.m_hBrakePadStream) + { + AudioMgr::StopLoopStream(data.m_hBrakePadStream); + } + data.m_fBrakePadVol = 0.0f; + } + } } \ No newline at end of file diff --git a/src/features/soundeffects.h b/src/features/soundeffects.h index 5067db3a..5b4b31fe 100755 --- a/src/features/soundeffects.h +++ b/src/features/soundeffects.h @@ -13,8 +13,16 @@ struct SoundEffectsData unsigned int m_nLastReverseSoundTime = 0; float m_fBrakePressure = 0.0f; float m_fMaxPedal = 0.0f; + StreamHandle m_hBrakePadStream = 0; + float m_fBrakePadVol = 0.0f; SoundEffectsData(CVehicle *pVeh) {} - ~SoundEffectsData() {} + ~SoundEffectsData() + { + if (m_hBrakePadStream) + { + AudioMgr::StopLoopStream(m_hBrakePadStream); + } + } }; class SoundEffects : public CVehFeature diff --git a/src/utils/audiomgr.cpp b/src/utils/audiomgr.cpp index 00a99205..09912f5f 100755 --- a/src/utils/audiomgr.cpp +++ b/src/utils/audiomgr.cpp @@ -97,7 +97,7 @@ void AudioMgr::Init() Events::processScriptsEvent += [] { static bool bWasPaused = false; - bool bIsPaused = CTimer::m_UserPause || CTimer::m_CodePause; + bool bIsPaused = !Util::IsWindowFocused() || CTimer::m_UserPause || CTimer::m_CodePause; if (bIsPaused != bWasPaused) { @@ -169,7 +169,19 @@ void AudioMgr::PlaySwitchSound(CEntity *pEntity) bool AudioMgr::ShouldPlaySound() { - return gbSoundEffectsEnabled; + if (!gbSoundEffectsEnabled) + { + return false; + } + if (!Util::IsWindowFocused()) + { + return false; + } + if (CTimer::m_UserPause || CTimer::m_CodePause) + { + return false; + } + return true; } void AudioMgr::Play3DSound(const std::string &path, const CVector &worldPos, CEntity *pEntity, float baseVolume, float maxDistance) @@ -293,7 +305,7 @@ std::string AudioMgr::GetSirenAudioPath(int modeIndex) return ""; } -StreamHandle AudioMgr::PlaySirenStream(const std::string &path, const CVector &worldPos, float baseVolume, float maxDistance) +StreamHandle AudioMgr::PlayLoopStream(const std::string &path, const CVector &worldPos, float baseVolume, float maxDistance) { if (path.empty() || !BassAPI::bReady || !BassAPI::fnStreamCreate || !BassAPI::fnChannelPlay) { @@ -349,7 +361,7 @@ StreamHandle AudioMgr::PlaySirenStream(const std::string &path, const CVector &w return 0; } -void AudioMgr::UpdateSirenStream(StreamHandle stream, const CVector &worldPos, float baseVolume, float maxDistance) +void AudioMgr::UpdateLoopStream(StreamHandle stream, const CVector &worldPos, float baseVolume, float maxDistance) { if (!stream || !BassAPI::bReady || !BassAPI::fnChannelSetAttr || !BassAPI::fnChannelIsActive) { @@ -361,6 +373,12 @@ void AudioMgr::UpdateSirenStream(StreamHandle stream, const CVector &worldPos, f return; } + if (!ShouldPlaySound()) + { + BassAPI::fnChannelSetAttr(stream, 2 /* BASS_ATTRIB_VOL */, 0.0f); + return; + } + float distSq = MathUtil::DistanceSquared(worldPos, TheCamera.GetPosition()); float dist = std::sqrt(distSq); const float nearDist = 5.0f; @@ -388,7 +406,7 @@ void AudioMgr::UpdateSirenStream(StreamHandle stream, const CVector &worldPos, f BassAPI::fnChannelSetAttr(stream, 3 /* BASS_ATTRIB_PAN */, pan); } -void AudioMgr::StopSirenStream(StreamHandle &stream) +void AudioMgr::StopLoopStream(StreamHandle &stream) { if (!stream) { @@ -412,4 +430,19 @@ void AudioMgr::StopSirenStream(StreamHandle &stream) needToFree.erase(it); } stream = 0; +} + +StreamHandle AudioMgr::PlaySirenStream(const std::string &path, const CVector &worldPos, float baseVolume, float maxDistance) +{ + return PlayLoopStream(path, worldPos, baseVolume, maxDistance); +} + +void AudioMgr::UpdateSirenStream(StreamHandle stream, const CVector &worldPos, float baseVolume, float maxDistance) +{ + UpdateLoopStream(stream, worldPos, baseVolume, maxDistance); +} + +void AudioMgr::StopSirenStream(StreamHandle &stream) +{ + StopLoopStream(stream); } \ No newline at end of file diff --git a/src/utils/audiomgr.h b/src/utils/audiomgr.h index 517affc3..8d6e32b9 100755 --- a/src/utils/audiomgr.h +++ b/src/utils/audiomgr.h @@ -20,6 +20,9 @@ class AudioMgr static void PlayFileSound(const std::string &path, float volume = 1.0f); static void PlayClickSound(); static void PlaySwitchSound(CEntity *pEntity = nullptr); + static StreamHandle PlayLoopStream(const std::string &path, const CVector &worldPos, float baseVolume = 1.0f, float maxDistance = 40.0f); + static void UpdateLoopStream(StreamHandle stream, const CVector &worldPos, float baseVolume = 1.0f, float maxDistance = 40.0f); + static void StopLoopStream(StreamHandle &stream); static StreamHandle PlaySirenStream(const std::string &path, const CVector &worldPos, float baseVolume = 1.0f, float maxDistance = 120.0f); static void UpdateSirenStream(StreamHandle stream, const CVector &worldPos, float baseVolume = 1.0f, float maxDistance = 120.0f); static void StopSirenStream(StreamHandle &stream);