diff --git a/resource/dist/ModelExtras.ini b/resource/dist/ModelExtras.ini index b3e28c80..86b2d448 100755 --- a/resource/dist/ModelExtras.ini +++ b/resource/dist/ModelExtras.ini @@ -74,11 +74,13 @@ TailLightShadowIntensity = 80 # Opacity of projected groun [SOUND] SoundEffects = 1 # Master toggle for all ModelExtras sound effects SoundMult = 0.6 # Multiplier for ModelExtras audio effects (0.0 to 1.0) -SoundEffects_GlobalAirbreakSound = 1 # Plays engine air brake sound for big vehicles (check [TABLE]) -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 +GlobalAirbreakSound = 1 # Plays engine air brake sound for big vehicles (check [TABLE]) +GlobalEngineSound = 1 # Plays engine on/off sound +GlobalIndicatorSound = 1 # Plays indicator on/off sound +GlobalReverseSound = 1 # Plays reverse sound for big vehicles (check [TABLE]) +DoorChimeSound = 1 # Plays open door warning chime sound for vehicles (check [TABLE]) +DoorChimeOnlySelected = 1 # Open door warning chime for selected vehicles only (check [TABLE]) +NonPlayerVehicles = 1 # Plays sound effects for traffic vehicles too [CONFIG] ModLoaderData = 1 # Loads per-vehicle data configs (.jsonc/.json/.eml/.ivfc) directly from ModLoader subfolders @@ -101,5 +103,6 @@ SirenLightKey = 0x4C # Toggle emergency siren lig SpotLightKey = 0x42 # Toggle police searchlight / spotlight on / off (Default: B [0x42]) [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 +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 +DoorChime_VehicleModels = "400, 401, 402, 405, 409, 410, 411, 413, 415, 416, 418, 420, 421, 422, 426, 429, 436, 440, 445, 451, 458, 459, 470, 477, 480, 482, 489, 490, 492, 496, 500, 505, 506, 507, 516, 517, 526, 527, 529, 533, 540, 541, 546, 547, 550, 551, 554, 558, 559, 560, 561, 562, 565, 579, 582, 585, 587, 589, 596, 597, 598, 599, 602, 603" # Allowed vehicle model IDs for open door warning chime sound diff --git a/resource/dist/ModelExtras/audio/door_chime.mp3 b/resource/dist/ModelExtras/audio/door_chime.mp3 new file mode 100644 index 00000000..5ce7e1a0 Binary files /dev/null and b/resource/dist/ModelExtras/audio/door_chime.mp3 differ diff --git a/src/ModelExtrasAPI.cpp b/src/ModelExtrasAPI.cpp index d5a9449f..d3e42a4d 100755 --- a/src/ModelExtrasAPI.cpp +++ b/src/ModelExtrasAPI.cpp @@ -900,6 +900,16 @@ bool ME_PlaySound(CVehicle *pVeh, const char *soundPath, float volume) { void ME_StopVehicleSounds(CVehicle *pVeh) { if (!pVeh) return; auto &sData = SoundEffects::m_VehData.Get(pVeh); + if (sData.m_hDoorChimeStream) { + AudioMgr::StopLoopStream(sData.m_hDoorChimeStream); + sData.m_hDoorChimeStream = 0; + } +} + +bool ME_IsDoorChimePlaying(CVehicle *pVeh) { + if (!pVeh) return false; + auto &sData = SoundEffects::m_VehData.Get(pVeh); + return sData.m_hDoorChimeStream != 0; } diff --git a/src/ModelExtrasAPI.h b/src/ModelExtrasAPI.h index cca228d2..d6b56ba8 100755 --- a/src/ModelExtrasAPI.h +++ b/src/ModelExtrasAPI.h @@ -288,6 +288,7 @@ extern "C" // Audio & Sounds ME_WRAPPER bool ME_PlaySound(CVehicle *pVeh, const char *soundPath, float volume); ME_WRAPPER void ME_StopVehicleSounds(CVehicle *pVeh); + ME_WRAPPER bool ME_IsDoorChimePlaying(CVehicle *pVeh); ME_WRAPPER float ME_GetBrakePressure(CVehicle *pVeh); ME_WRAPPER bool ME_PlayFileSound(const char *soundPath, float volume); ME_WRAPPER void ME_PlaySwitchSound(CVehicle *pVeh); diff --git a/src/features/soundeffects.cpp b/src/features/soundeffects.cpp index e24e795c..dfe0227a 100755 --- a/src/features/soundeffects.cpp +++ b/src/features/soundeffects.cpp @@ -3,10 +3,12 @@ #include "soundeffects.h" #include "lights/lights.h" #include "eVehicleClass.h" +#include "utils/datamgr.h" using namespace plugin; std::vector ValidForReverseSound; +static std::vector ValidForDoorChime; #define ANIMGROUP_TRUCK 2 #define ANIMGROUP_BUS 15 @@ -17,24 +19,82 @@ static bool bEngineSounds = false; static bool bIndicatorSounds = false; static bool bAirbreakSounds = false; static bool bOnlyPlayerVehicle = true; +static bool bDoorChimeSounds = true; +static bool bDoorChimeOnlySelected = true; +static float fDoorChimeVolume = 0.7f; + +static std::string GetAudioPath(const char *name) +{ + const char *extensions[] = {".mp3", ".wav", ".ogg"}; + for (const char *ext : extensions) + { + std::string relPath = std::string("ModelExtras/audio/") + name + ext; + std::string fullPath = PLUGIN_PATH((char *)relPath.c_str()); + DWORD attr = GetFileAttributesA(fullPath.c_str()); + if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) + { + return fullPath; + } + } + return MOD_DATA_PATH("audio/") + std::string(name) + ".mp3"; +} + +static bool IsVehicleEligible(int model, const char *jsonKey, bool onlySelected, const std::vector &modelList) +{ + const auto &jsonData = DataMgr::Get(model); + if (jsonData.is_object() && jsonData.contains("sound") && jsonData["sound"].is_object() && + jsonData["sound"].contains(jsonKey) && jsonData["sound"][jsonKey].is_boolean()) + { + return jsonData["sound"][jsonKey].get(); + } + + if (!onlySelected) + { + return true; + } + + return !modelList.empty() && std::find(modelList.begin(), modelList.end(), model) != modelList.end(); +} void SoundEffects::ReloadConfig() { CBaseFeature::ReloadConfig(); - std::string line = gConfig.ReadString("TABLE", "SoundEffects_BigVehicleModels", ""); + std::string line = gConfig.ReadString("TABLE", "BigVehicleModels", ""); ValidForReverseSound.clear(); Util::GetModelsFromIni(line, ValidForReverseSound); - bReverseSounds = gConfig.ReadBoolean("SOUND", "SoundEffects_GlobalReverseSound", gConfig.ReadBoolean("FEATURES", "SoundEffects_GlobalReverseSound", false)); - bEngineSounds = gConfig.ReadBoolean("SOUND", "SoundEffects_GlobalEngineSound", gConfig.ReadBoolean("FEATURES", "SoundEffects_GlobalEngineSound", false)); - 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)); + std::string doorChimeModelsLine = gConfig.ReadString("TABLE", "DoorChime_VehicleModels", "400, 401, 402, 405, 409, 410, 411, 413, 415, 416, 418, 420, 421, 422, 426, 429, 436, 440, 445, 451, 458, 459, 470, 477, 480, 482, 489, 490, 492, 496, 500, 505, 506, 507, 516, 517, 526, 527, 529, 533, 540, 541, 546, 547, 550, 551, 554, 558, 559, 560, 561, 562, 565, 579, 582, 585, 587, 589, 596, 597, 598, 599, 602, 603"); + ValidForDoorChime.clear(); + Util::GetModelsFromIni(doorChimeModelsLine, ValidForDoorChime); + + bReverseSounds = gConfig.ReadBoolean("SOUND", "GlobalReverseSound", false); + bEngineSounds = gConfig.ReadBoolean("SOUND", "GlobalEngineSound", false); + bIndicatorSounds = gConfig.ReadBoolean("SOUND", "GlobalIndicatorSound", false); + bAirbreakSounds = gConfig.ReadBoolean("SOUND", "GlobalAirbreakSound", false); + bOnlyPlayerVehicle = !gConfig.ReadBoolean("SOUND", "NonPlayerVehicles", false); + bDoorChimeSounds = gConfig.ReadBoolean("SOUND", "DoorChimeSound", true); + bDoorChimeOnlySelected = gConfig.ReadBoolean("SOUND", "DoorChimeOnlySelected", true); + fDoorChimeVolume = std::clamp(gConfig.ReadFloat("SOUND", "DoorChimeVolume", 0.7f), 0.0f, 1.0f); + if (CPools::ms_pVehiclePool) for (auto *vehicle : CPools::ms_pVehiclePool) if (vehicle) { + auto &data = m_VehData.Get(vehicle); + auto *player = FindPlayerPed(); + bool permitted = m_bActive && (!bOnlyPlayerVehicle || (player && vehicle->IsDriver(player))); + if (!permitted || !bDoorChimeSounds || !IsVehicleEligible(vehicle->m_nModelIndex, "door_chime", bDoorChimeOnlySelected, ValidForDoorChime)) + AudioMgr::StopLoopStream(data.m_hDoorChimeStream); + } } void SoundEffects::Reload(CVehicle *pVeh) { ReloadConfig(); + if (pVeh) + { + auto &data = m_VehData.Get(pVeh); + if (data.m_hDoorChimeStream) + { + AudioMgr::StopLoopStream(data.m_hDoorChimeStream); + } + } } void SoundEffects::Init() @@ -45,134 +105,182 @@ void SoundEffects::Init() { ReloadConfig(); }; - } void SoundEffects::ProcessVehicle(CVehicle *pVeh) { - if (!CBaseFeature::IsEnabled(eFeatureMatrix::SoundEffects)) { + if (!CBaseFeature::IsEnabled(eFeatureMatrix::SoundEffects)) + { return; } CPed *pPlayer = FindPlayerPed(); - if (!pPlayer) { + auto &data = m_VehData.Get(pVeh); + + if (!pPlayer || pVeh->m_fHealth <= 0.0f || pVeh->bEngineBroken || pVeh->bIsDrowning || MathUtil::DistanceSquared(pVeh->GetPosition(), pPlayer->GetPosition()) > (75.0f * 75.0f)) + { + if (data.m_hDoorChimeStream) + { + AudioMgr::StopLoopStream(data.m_hDoorChimeStream); + } return; } - if (MathUtil::DistanceSquared(pVeh->GetPosition(), pPlayer->GetPosition()) > (75.0f * 75.0f)) { + + bool isPlayerDriver = (pVeh->m_pDriver == pPlayer); + bool isPlayerNear = (MathUtil::DistanceSquared(pVeh->GetPosition(), pPlayer->GetPosition()) < (12.0f * 12.0f)); + + if (bOnlyPlayerVehicle && !isPlayerDriver && !isPlayerNear) + { + if (data.m_hDoorChimeStream) + { + AudioMgr::StopLoopStream(data.m_hDoorChimeStream); + } return; } - if (bOnlyPlayerVehicle && pVeh->m_pDriver != pPlayer) { - return; - } - - auto &data = m_VehData.Get(pVeh); - float speed = Util::GetVehicleSpeed(pVeh); - int model = pVeh->m_nModelIndex; - bool isPlayerDriver = (pVeh->m_pDriver == pPlayer); + float speed = Util::GetVehicleSpeed(pVeh); + int model = pVeh->m_nModelIndex; - // Initialize previous state on first detection so newly seen running vehicles don't trigger sound - if (!data.m_bInitialized) - { - data.m_bEngineState = pVeh->bEngineOn; - data.m_bIndicatorState = Lights::IsIndicatorOn(pVeh); - data.m_bInitialized = true; - return; - } - - int animGroup = pVeh->m_pHandlingData ? pVeh->m_pHandlingData->m_nAnimGroup : 0; - bool isAllowed = (pVeh->m_nVehicleSubClass == VEHICLE_AUTOMOBILE || pVeh->m_nVehicleSubClass == VEHICLE_MTRUCK) && - (animGroup == ANIMGROUP_TRUCK || animGroup == ANIMGROUP_BUS || animGroup == ANIMGROUP_COACH || - pVeh->bIsBig || pVeh->bIsBus || (pVeh->bIsVan && pVeh->m_pHandlingData && pVeh->m_pHandlingData->m_fMass >= 2500.0f) || - (pVeh->m_pHandlingData && pVeh->m_pHandlingData->m_fMass >= 3500.0f)); - bool isBigVeh = isAllowed || std::find(ValidForReverseSound.begin(), ValidForReverseSound.end(), pVeh->m_nModelIndex) != ValidForReverseSound.end(); + // Initialize previous state on first detection so newly seen running vehicles don't trigger sound + if (!data.m_bInitialized) + { + data.m_bEngineState = pVeh->bEngineOn; + data.m_bIndicatorState = Lights::IsIndicatorOn(pVeh); + data.m_bInitialized = true; + return; + } - if (bEngineSounds) - { - bool isValid = !CModelInfo::IsPlaneModel(model) && !CModelInfo::IsBmxModel(model) && !CModelInfo::IsHeliModel(model) && !CModelInfo::IsBoatModel(model); - bool isEligible = isPlayerDriver || (!bOnlyPlayerVehicle && pVeh->m_pDriver != nullptr); + int animGroup = pVeh->m_pHandlingData ? pVeh->m_pHandlingData->m_nAnimGroup : 0; + bool isAllowed = (pVeh->m_nVehicleSubClass == VEHICLE_AUTOMOBILE || pVeh->m_nVehicleSubClass == VEHICLE_MTRUCK) && + (animGroup == ANIMGROUP_TRUCK || animGroup == ANIMGROUP_BUS || animGroup == ANIMGROUP_COACH || + pVeh->bIsBig || pVeh->bIsBus || (pVeh->bIsVan && pVeh->m_pHandlingData && pVeh->m_pHandlingData->m_fMass >= 2500.0f) || + (pVeh->m_pHandlingData && pVeh->m_pHandlingData->m_fMass >= 3500.0f)); + bool isBigVeh = isAllowed || std::find(ValidForReverseSound.begin(), ValidForReverseSound.end(), pVeh->m_nModelIndex) != ValidForReverseSound.end(); - if (isValid && isEligible) - { - // Engine transitioned from OFF (false) to ON (true) - if (!data.m_bEngineState && pVeh->bEngineOn) - { - unsigned int curTime = CTimer::m_snTimeInMilliseconds; - if (curTime - data.m_nLastEngineSoundTime > 2000) - { - static std::string carPath = MOD_DATA_PATH("audio/engine_start.wav"); - static std::string bikePath = MOD_DATA_PATH("audio/bike_engine_start.wav"); - if (CModelInfo::IsBikeModel(model) || CModelInfo::IsQuadBikeModel(model)) - { - AudioMgr::Play3DSound(bikePath, pVeh->GetPosition(), pVeh, 1.0f, 65.0f); - } - else - { - AudioMgr::Play3DSound(carPath, pVeh->GetPosition(), pVeh, 1.0f, 65.0f); - } - data.m_nLastEngineSoundTime = curTime; - } - } - } - - data.m_bEngineState = pVeh->bEngineOn; - } + if (bEngineSounds) + { + bool isValid = !CModelInfo::IsPlaneModel(model) && !CModelInfo::IsBmxModel(model) && !CModelInfo::IsHeliModel(model) && !CModelInfo::IsBoatModel(model); + bool isEligible = isPlayerDriver || (!bOnlyPlayerVehicle && pVeh->m_pDriver != nullptr); - if (bIndicatorSounds && isPlayerDriver) + if (isValid && isEligible) + { + if (!data.m_bEngineState && pVeh->bEngineOn) { - bool state = Lights::IsIndicatorOn(pVeh); - if (state != data.m_bIndicatorState) + unsigned int curTime = CTimer::m_snTimeInMilliseconds; + if (curTime - data.m_nLastEngineSoundTime > 2000) { - static std::string onpath = MOD_DATA_PATH("audio/indicator_on.wav"); - static std::string offpath = MOD_DATA_PATH("audio/indicator_off.wav"); - - if (state) + static std::string carPath = MOD_DATA_PATH("audio/engine_start.wav"); + static std::string bikePath = MOD_DATA_PATH("audio/bike_engine_start.wav"); + if (CModelInfo::IsBikeModel(model) || CModelInfo::IsQuadBikeModel(model)) { - AudioMgr::PlayFileSound(onpath, 0.6f); + AudioMgr::Play3DSound(bikePath, pVeh->GetPosition(), pVeh, 1.0f, 65.0f); } else { - AudioMgr::PlayFileSound(offpath, 0.6f); + AudioMgr::Play3DSound(carPath, pVeh->GetPosition(), pVeh, 1.0f, 65.0f); } - data.m_bIndicatorState = state; + data.m_nLastEngineSoundTime = curTime; } } + } + + data.m_bEngineState = pVeh->bEngineOn; + } + + if (bIndicatorSounds && isPlayerDriver) + { + bool state = Lights::IsIndicatorOn(pVeh); + if (state != data.m_bIndicatorState) + { + static std::string onpath = MOD_DATA_PATH("audio/indicator_on.wav"); + static std::string offpath = MOD_DATA_PATH("audio/indicator_off.wav"); - if (bAirbreakSounds && isBigVeh) + if (state) + { + AudioMgr::PlayFileSound(onpath, 0.6f); + } + else { - float pedal = pVeh->m_fBreakPedal; + AudioMgr::PlayFileSound(offpath, 0.6f); + } + data.m_bIndicatorState = state; + } + } - if (speed > 10.0f) - { - data.m_fMaxPedal = std::max(data.m_fMaxPedal, pedal); + if (bAirbreakSounds && isBigVeh && (!bOnlyPlayerVehicle || isPlayerDriver)) + { + float pedal = pVeh->m_fBreakPedal; - if (pedal >= 0.5f) - { - data.m_fBrakePressure += pedal * 0.02f; - } - } + if (speed > 10.0f) + { + data.m_fMaxPedal = std::max(data.m_fMaxPedal, pedal); - if (pedal <= 0.05f && data.m_fMaxPedal > 0.0f) - { - static std::string path = MOD_DATA_PATH("audio/airbreak.wav"); - AudioMgr::Play3DSound(path, pVeh->GetPosition(), pVeh, data.m_fBrakePressure, 60.0f); - data.m_fMaxPedal = 0.0f; - data.m_fBrakePressure = 0.0f; - } + if (pedal >= 0.5f) + { + data.m_fBrakePressure += pedal * 0.02f; } + } + + if (pedal <= 0.05f && data.m_fMaxPedal > 0.0f) + { + static std::string path = MOD_DATA_PATH("audio/airbreak.wav"); + AudioMgr::Play3DSound(path, pVeh->GetPosition(), pVeh, data.m_fBrakePressure, 60.0f); + data.m_fMaxPedal = 0.0f; + data.m_fBrakePressure = 0.0f; + } + } + + if (bReverseSounds && isBigVeh && (!bOnlyPlayerVehicle || isPlayerDriver)) + { + static std::string path = MOD_DATA_PATH("audio/reverse.wav"); - if (bReverseSounds) + if (pVeh->m_nCurrentGear == 0 && pVeh->bEngineOn && !pVeh->bEngineBroken && speed >= 3.0f) + { + unsigned int curTime = CTimer::m_snTimeInMilliseconds; + if (curTime - data.m_nLastReverseSoundTime >= 1000) { - static std::string path = MOD_DATA_PATH("audio/reverse.wav"); + AudioMgr::Play3DSound(path, pVeh->GetPosition(), pVeh, 0.7f, 50.0f); + data.m_nLastReverseSoundTime = curTime; + } + } + } + + if (bDoorChimeSounds && pVeh->m_nVehicleSubClass == VEHICLE_AUTOMOBILE) + { + CAutomobile *pAuto = static_cast(pVeh); + bool isEligible = IsVehicleEligible(model, "door_chime", bDoorChimeOnlySelected, ValidForDoorChime); + bool isFrontDoorOpen = false; - if (isBigVeh && pVeh->m_nCurrentGear == 0 && pVeh->bEngineOn && !pVeh->bEngineBroken && speed >= 3.0f) + if (isEligible && !pVeh->bEngineBroken && !pVeh->bIsDrowning) + { + bool leftOpen = (!pAuto->m_doors[eDoors::DOOR_FRONT_LEFT].IsClosed() && + std::abs(pAuto->m_doors[eDoors::DOOR_FRONT_LEFT].m_fAngle) > 0.15f); + bool rightOpen = (!pAuto->m_doors[eDoors::DOOR_FRONT_RIGHT].IsClosed() && + std::abs(pAuto->m_doors[eDoors::DOOR_FRONT_RIGHT].m_fAngle) > 0.15f); + + if (leftOpen || rightOpen) + { + if (isPlayerDriver || pVeh->bEngineOn || isPlayerNear) { - unsigned int curTime = CTimer::m_snTimeInMilliseconds; - if (curTime - data.m_nLastReverseSoundTime >= 1000) - { - AudioMgr::Play3DSound(path, pVeh->GetPosition(), pVeh, 0.7f, 50.0f); - data.m_nLastReverseSoundTime = curTime; - } + isFrontDoorOpen = true; } } + } + if (isFrontDoorOpen) + { + static std::string chimePath = GetAudioPath("door_chime"); + if (!data.m_hDoorChimeStream) + { + data.m_hDoorChimeStream = AudioMgr::PlayLoopStream(chimePath, pVeh->GetPosition(), fDoorChimeVolume, 25.0f); + } + else + { + AudioMgr::UpdateLoopStream(data.m_hDoorChimeStream, pVeh->GetPosition(), fDoorChimeVolume, 25.0f); + } + } + else if (data.m_hDoorChimeStream) + { + AudioMgr::StopLoopStream(data.m_hDoorChimeStream); + } + } } \ No newline at end of file diff --git a/src/features/soundeffects.h b/src/features/soundeffects.h index 5067db3a..02bfc8b1 100755 --- a/src/features/soundeffects.h +++ b/src/features/soundeffects.h @@ -13,8 +13,15 @@ struct SoundEffectsData unsigned int m_nLastReverseSoundTime = 0; float m_fBrakePressure = 0.0f; float m_fMaxPedal = 0.0f; + StreamHandle m_hDoorChimeStream = 0; SoundEffectsData(CVehicle *pVeh) {} - ~SoundEffectsData() {} + ~SoundEffectsData() + { + if (m_hDoorChimeStream) + { + AudioMgr::StopLoopStream(m_hDoorChimeStream); + } + } }; class SoundEffects : public CVehFeature diff --git a/src/loader.cpp b/src/loader.cpp index 7732043d..2272dff3 100755 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -167,7 +167,7 @@ void ModelExtras::Init() for (CVehicle *pVeh : CPools::ms_pVehiclePool) { - if (!pVeh || pVeh->m_fHealth <= 0.0f) continue; + if (!pVeh) continue; for (auto *pFeature : s_ActiveVehicleFeatures) { diff --git a/src/utils/audiomgr.cpp b/src/utils/audiomgr.cpp index 00a99205..26d064ae 100755 --- a/src/utils/audiomgr.cpp +++ b/src/utils/audiomgr.cpp @@ -20,6 +20,8 @@ namespace BassAPI using tBASS_ChannelSetAttribute = BOOL(WINAPI *)(DWORD, DWORD, float); using tBASS_ChannelIsActive = DWORD(WINAPI *)(DWORD); using tBASS_StreamFree = BOOL(WINAPI *)(HSTREAM); + using tBASS_Pause = BOOL(WINAPI *)(); + using tBASS_Start = BOOL(WINAPI *)(); static tBASS_Init fnInit = nullptr; static tBASS_StreamCreateFile fnStreamCreate = nullptr; @@ -28,6 +30,8 @@ namespace BassAPI static tBASS_ChannelSetAttribute fnChannelSetAttr = nullptr; static tBASS_ChannelIsActive fnChannelIsActive = nullptr; static tBASS_StreamFree fnStreamFree = nullptr; + static tBASS_Pause fnPause = nullptr; + static tBASS_Start fnStart = nullptr; static bool bReady = false; @@ -53,6 +57,8 @@ namespace BassAPI fnChannelSetAttr = (tBASS_ChannelSetAttribute)GetProcAddress(hBass, "BASS_ChannelSetAttribute"); fnChannelIsActive = (tBASS_ChannelIsActive)GetProcAddress(hBass, "BASS_ChannelIsActive"); fnStreamFree = (tBASS_StreamFree)GetProcAddress(hBass, "BASS_StreamFree"); + fnPause = (tBASS_Pause)GetProcAddress(hBass, "BASS_Pause"); + fnStart = (tBASS_Start)GetProcAddress(hBass, "BASS_Start"); if (fnInit && fnStreamCreate && fnChannelPlay && fnChannelPause && fnChannelSetAttr && fnChannelIsActive && fnStreamFree) { @@ -70,8 +76,8 @@ static float gfSoundMult = 1.0f; void AudioMgr::ReloadConfig() { - gbSoundEffectsEnabled = gConfig.ReadBoolean("SOUND", "SoundEffects", gConfig.ReadBoolean("FEATURES", "SoundEffects", false)); - gfSoundMult = gConfig.ReadFloat("SOUND", "SoundMult", gConfig.ReadFloat("TWEAKS", "SoundMult", 0.6f)); + gbSoundEffectsEnabled = gConfig.ReadBoolean("SOUND", "SoundEffects", false); + gfSoundMult = gConfig.ReadFloat("SOUND", "SoundMult", 0.6f); } void AudioMgr::Init() @@ -94,39 +100,36 @@ void AudioMgr::Init() needToFree.clear(); }; - Events::processScriptsEvent += [] + Events::drawingEvent += [] { static bool bWasPaused = false; - bool bIsPaused = CTimer::m_UserPause || CTimer::m_CodePause; + bool bIsPaused = !Util::IsWindowFocused() || *(bool *)0xBA67A4 || CTimer::m_UserPause || CTimer::m_CodePause; if (bIsPaused != bWasPaused) { bWasPaused = bIsPaused; - if (BassAPI::bReady && BassAPI::fnChannelPause && BassAPI::fnChannelPlay && BassAPI::fnChannelIsActive) + if (BassAPI::bReady) { - for (auto stream : needToFree) + if (bIsPaused) { - if (stream) + if (BassAPI::fnPause) { - if (bIsPaused) - { - if (BassAPI::fnChannelIsActive(stream) == 1 /* BASS_ACTIVE_PLAYING */) - { - BassAPI::fnChannelPause(stream); - } - } - else - { - if (BassAPI::fnChannelIsActive(stream) == 3 /* BASS_ACTIVE_PAUSED */) - { - BassAPI::fnChannelPlay(stream, FALSE); - } - } + BassAPI::fnPause(); + } + } + else + { + if (BassAPI::fnStart) + { + BassAPI::fnStart(); } } } } + }; + Events::processScriptsEvent += [] + { static size_t prev = 0; size_t cur = CTimer::m_snTimeInMilliseconds; @@ -169,7 +172,15 @@ void AudioMgr::PlaySwitchSound(CEntity *pEntity) bool AudioMgr::ShouldPlaySound() { - return gbSoundEffectsEnabled; + if (!gbSoundEffectsEnabled) + { + return false; + } + if (!Util::IsWindowFocused() || *(bool *)0xBA67A4 || 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) @@ -203,7 +214,7 @@ void AudioMgr::Play3DSound(const std::string &path, const CVector &worldPos, CEn CVector toSound = worldPos - TheCamera.GetPosition(); CVector camRight = TheCamera.m_mCameraMatrix.right; float rightDot = (toSound.x * camRight.x + toSound.y * camRight.y + toSound.z * camRight.z) / dist; - pan = std::clamp(rightDot, -1.0f, 1.0f); + pan = std::clamp(-rightDot, -1.0f, 1.0f); } // Calibrated volume scaling with in-game SFX master volume (0xBA6797) @@ -293,7 +304,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) { @@ -321,7 +332,7 @@ StreamHandle AudioMgr::PlaySirenStream(const std::string &path, const CVector &w CVector toSound = worldPos - TheCamera.GetPosition(); CVector camRight = TheCamera.m_mCameraMatrix.right; float rightDot = (toSound.x * camRight.x + toSound.y * camRight.y + toSound.z * camRight.z) / dist; - pan = std::clamp(rightDot, -1.0f, 1.0f); + pan = std::clamp(-rightDot, -1.0f, 1.0f); } constexpr float INV_64 = 1.0f / 64.0f; @@ -349,7 +360,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 +372,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; @@ -377,7 +394,7 @@ void AudioMgr::UpdateSirenStream(StreamHandle stream, const CVector &worldPos, f CVector toSound = worldPos - TheCamera.GetPosition(); CVector camRight = TheCamera.m_mCameraMatrix.right; float rightDot = (toSound.x * camRight.x + toSound.y * camRight.y + toSound.z * camRight.z) / dist; - pan = std::clamp(rightDot, -1.0f, 1.0f); + pan = std::clamp(-rightDot, -1.0f, 1.0f); } constexpr float INV_64 = 1.0f / 64.0f; @@ -388,7 +405,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 +429,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);