Files
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2325 lines
74 KiB
C++

#include "vehicleengine.h"
#include "audioeffecttypes/biquadfiltereffect.h"
#include "audioeffecttypes/waveshapereffect.h"
#include "audioengine/category.h"
#include "audioengine/engine.h"
#include "audiohardware/submix.h"
#include "audio/caraudioentity.h"
#include "audio/boataudioentity.h"
#include "audio/vehicleaudioentity.h"
#include "audiohardware/granularmix.h"
#include "Task/Vehicle/TaskInVehicle.h"
#include "camera/CamInterface.h"
#include "peds/PlayerInfo.h"
#include "peds/PedIntelligence.h"
#include "game/weather.h"
#include "timecycle/TimeCycle.h"
#include "vehicles/vehicle.h"
#include "vehicles/Submarine.h"
AUDIO_VEHICLES_OPTIMISATIONS()
audCurve audVehicleEngine::sm_RevsOffVolCurve;
audCurve audVehicleEngine::sm_ExhaustRattleVol;
audCurve audVehicleEngine::sm_StartLoopVolCurve;
f32 audVehicleEngine::sm_AutoShutOffCutoffTime = 3.0f;
u32 audVehicleEngine::sm_AutoShutOffIgnitionTime = 200;
f32 audVehicleEngine::sm_AutoShutOffRestartDelay = 0.1f;
u32 g_ScreenFadedEngineSwitchTime = 200;
u32 g_SportsCarRevsEngineSwitchTime = 200;
u32 g_RocketBoostCooldownTime = 10000;
extern u32 g_EngineStopDuration;
extern f32 g_RevMaxIncreaseRate;
extern f32 g_RevMaxDecreaseRate;
extern f32 g_ExhaustPopRateThreshold;
extern f32 g_audEngineSteamSoundVolRange;
extern f32 g_EngineVolumeTrim;
extern u32 g_EngineSwitchFadeInTime;
extern u32 g_EngineSwitchFadeOutTime;
extern audCategory* g_EngineCategory;
extern audCategory* g_EngineLoudCategory;
extern const u32 g_TemperatureVariableHash;
extern const u32 g_VariationVariableHash;
extern audCurve g_EnginePowerToDistortion;
extern bool g_GranularEnabled;
extern bool g_GranularEnableNPCGranularEngines;
extern bool g_GranularForceLowQuality;
extern bool g_GranularForceHighQuality;
#if __BANK
extern bool g_GranularForceRevLimiter;
extern bool g_MuteFanbeltDamage;
extern bool g_ForceDamage;
extern f32 g_ForcedDamageFactor;
#endif
// -------------------------------------------------------------------------------
// audVehicleKersSystem constructor
// -------------------------------------------------------------------------------
audVehicleKersSystem::audVehicleKersSystem() :
m_Parent(NULL)
, m_KERSBoostLoop(NULL)
, m_KERSRechargeLoop(NULL)
, m_JetWhineSound(NULL)
, m_KersState(AUD_KERS_STATE_OFF)
, m_KersRechargeRate(0.0f)
, m_LastKersBoostTime(0u)
, m_LastKersBoostFailTime(0u)
{
}
// -------------------------------------------------------------------------------
// audVehicleKersSystem Init
// -------------------------------------------------------------------------------
void audVehicleKersSystem::Init(audVehicleAudioEntity* parent)
{
m_Parent = parent;
}
// -------------------------------------------------------------------------------
// audVehicleKersSystem StopAllSounds
// -------------------------------------------------------------------------------
void audVehicleKersSystem::StopAllSounds()
{
m_Parent->StopAndForgetSoundsContinueUpdating(m_KERSBoostLoop, m_KERSRechargeLoop, m_JetWhineSound);
m_KersState = AUD_KERS_STATE_OFF;
}
// -------------------------------------------------------------------------------
// audVehicleKersSystem StopJetWhineSound
// -------------------------------------------------------------------------------
void audVehicleKersSystem::StopJetWhineSound()
{
if(m_JetWhineSound)
{
m_JetWhineSound->StopAndForget(true);
}
}
// -------------------------------------------------------------------------------
// audVehicleKersSystem Update
// -------------------------------------------------------------------------------
void audVehicleKersSystem::Update()
{
if(m_Parent)
{
bool boostBoneAboveWater = true;
if (m_Parent->GetVehicleModelNameHash() == ATSTRINGHASH("Toreador", 0x56C8A5EF))
{
boostBoneAboveWater = false;
for (int i = 0; i < VFXVEHICLE_NUM_ROCKET_BOOSTS; i++)
{
Mat34V boneMtx;
CVehicle* pVehicle = m_Parent->GetVehicle();
CVehicleModelInfo* pModelInfo = pVehicle->GetVehicleModelInfo();
s32 rocketBoostBoneId = pModelInfo->GetBoneIndex(VEH_ROCKET_BOOST + i);
if (i == 0 && rocketBoostBoneId == -1)
{
rocketBoostBoneId = pModelInfo->GetBoneIndex(VEH_EXHAUST);
}
if (rocketBoostBoneId > -1)
{
CVfxHelper::GetMatrixFromBoneIndex(boneMtx, pVehicle, rocketBoostBoneId);
if (!IsZeroAll(boneMtx.GetCol0()))
{
f32 waterDepth;
CVfxHelper::GetWaterDepth(boneMtx.GetCol3(), waterDepth);
if (waterDepth <= 0.0f)
{
boostBoneAboveWater = true;
}
}
}
}
}
if(!m_Parent->IsDisabled())
{
const u32 kersSoundsetName = GetKersSoundSetName();
switch(m_KersState)
{
case AUD_KERS_STATE_BOOSTING:
{
if(!m_KERSBoostLoop)
{
audSoundSet kersSoundset;
if(kersSoundset.Init(kersSoundsetName))
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
m_Parent->CreateAndPlaySound_Persistent(kersSoundset.Find(ATSTRINGHASH("activate", 0xE64E0807)), &m_KERSBoostLoop, &initParams);
if(m_KERSBoostLoop)
{
REPLAY_ONLY(CReplayMgr::ReplayRecordSoundPersistant(kersSoundsetName, ATSTRINGHASH("activate", 0xE64E0807), &initParams, m_KERSBoostLoop, m_Parent->GetVehicle());)
}
}
}
if(m_KERSBoostLoop)
{
m_KERSBoostLoop->FindAndSetVariableValue(ATSTRINGHASH("chargeRemaining", 0x44E9F02C), GetKersBoostRemaining());
m_KERSBoostLoop->FindAndSetVariableValue(ATSTRINGHASH("underwater", 0xA2663418), boostBoneAboveWater ? 0.f : 1.f);
if(m_Parent->GetVehicle()->InheritsFromSubmarineCar())
{
m_KERSBoostLoop->FindAndSetVariableValue(ATSTRINGHASH("submarinemode", 0x331C9916), static_cast<CSubmarineCar*>(m_Parent->GetVehicle())->IsInSubmarineMode() ? 1.f : 0.f);
}
m_LastKersBoostTime = fwTimer::GetTimeInMilliseconds();
}
if(m_KERSRechargeLoop)
{
m_KERSRechargeLoop->StopAndForget(true);
}
}
break;
case AUD_KERS_STATE_RECHARGING:
{
if(m_KersRechargeRate > 0.0f)
{
if(!m_KERSRechargeLoop)
{
audSoundSet kersSoundset;
if(kersSoundset.Init(kersSoundsetName))
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
m_Parent->CreateAndPlaySound_Persistent(kersSoundset.Find(ATSTRINGHASH("recharge", 0xFB025813)), &m_KERSRechargeLoop, &initParams);
if(m_KERSRechargeLoop)
{
REPLAY_ONLY(CReplayMgr::ReplayRecordSoundPersistant(kersSoundsetName, ATSTRINGHASH("recharge", 0xFB025813), &initParams, m_KERSRechargeLoop, m_Parent->GetVehicle());)
}
}
}
if(m_KERSRechargeLoop)
{
m_KERSRechargeLoop->FindAndSetVariableValue(ATSTRINGHASH("rate", 0x7E68C088), m_KersRechargeRate * 30);
m_KERSRechargeLoop->FindAndSetVariableValue(ATSTRINGHASH("chargeRemaining", 0x44E9F02C), GetKersBoostRemaining());
}
}
else if(m_KERSRechargeLoop)
{
m_KERSRechargeLoop->StopAndForget(true);
}
if(m_KERSBoostLoop)
{
m_KERSBoostLoop->StopAndForget(true);
}
}
break;
default:
m_Parent->StopAndForgetSoundsContinueUpdating(m_KERSRechargeLoop, m_KERSBoostLoop);
break;
}
}
else
{
m_Parent->StopAndForgetSoundsContinueUpdating(m_KERSRechargeLoop, m_KERSBoostLoop);
}
}
}
// -------------------------------------------------------------------------------
// audVehicleKersSystem GetKersSoundSetName
// -------------------------------------------------------------------------------
u32 audVehicleKersSystem::GetKersSoundSetName() const
{
// Temp fix for V DLC - should be moved into audio metadata for future projects
const u32 modelNameHash = m_Parent->GetVehicleModelNameHash();
if(modelNameHash == ATSTRINGHASH("LECTRO", 0x26321E67))
{
return ATSTRINGHASH("LECTRO_KERS_SOUNDSET", 0x9FDAAA0C);
}
else if(modelNameHash == ATSTRINGHASH("VINDICATOR", 0xAF599F01))
{
return ATSTRINGHASH("VINDICATOR_KERS_SOUNDSET", 0xD46FFCB6);
}
else if (modelNameHash == ATSTRINGHASH("Toreador", 0x56C8A5EF))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("dlc_h4_toreador_boost_sounds", 0xE5D0ADAF);
}
else
{
return ATSTRINGHASH("dlc_h4_toreador_boost_npc_sounds", 0x7DBB48FC);
}
}
else if (modelNameHash == ATSTRINGHASH("FORMULA", 0x1446590A))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("dlc_ch_formula_kers_sounds", 0x44072C9D);
}
else
{
return ATSTRINGHASH("dlc_ch_formula_kers_npc_sounds", 0x2DA99AD9);
}
}
else if (modelNameHash == ATSTRINGHASH("FORMULA2", 0x8B213907))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("dlc_ch_formula2_kers_sounds", 0x695BBFA8);
}
else
{
return ATSTRINGHASH("dlc_ch_formula2_kers_npc_sounds", 0xFE208B94);
}
}
else if (modelNameHash == ATSTRINGHASH("OPENWHEEL1", 0x58F77553))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("dlc_sum20_openwheel1_kers_sounds", 0x893FE1E0);
}
else
{
return ATSTRINGHASH("dlc_sum20_openwheel1_kers_npc_sounds", 0x2D7A59BF);
}
}
else if (modelNameHash == ATSTRINGHASH("OPENWHEEL2", 0x4669D038))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("dlc_sum20_openwheel2_kers_sounds", 0x4A06F8CC);
}
else
{
return ATSTRINGHASH("dlc_sum20_openwheel2_kers_npc_sounds", 0x9D80AEDC);
}
}
else if(modelNameHash == ATSTRINGHASH("STARLING", 0x9A9EB7DE))
{
if(m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_Smuggler_Starling_Sounds", 0xC99D5122);
}
else
{
return ATSTRINGHASH("DLC_Smuggler_Starling_NPC_Sounds", 0x1C2ADE23);
}
}
else if(modelNameHash == ATSTRINGHASH("MOGUL", 0xD35698EF) ||
modelNameHash == ATSTRINGHASH("TULA", 0x3E2E4F8A) ||
modelNameHash == ATSTRINGHASH("BOMBUSHKA", 0xFE0A508C))
{
if(m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_SM_JATO_Sounds", 0x721906BF);
}
else
{
return ATSTRINGHASH("DLC_SM_JATO_NPC_Sounds", 0xD8F86B5F);
}
}
else if(modelNameHash == ATSTRINGHASH("VOLTIC2", 0x3AF76F4A))
{
if(m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_ImportExport_Voltic2_Sounds", 0x243BA50);
}
else
{
return ATSTRINGHASH("DLC_ImportExport_Voltic2_NPC_Sounds", 0xE2D93E04);
}
}
else if(modelNameHash == ATSTRINGHASH("OPPRESSOR", 0x34B82784))
{
if(m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_Gunrunning_Oppressor_Sounds", 0xC98DA953);
}
else
{
return ATSTRINGHASH("DLC_Gunrunning_Oppressor_NPC_Sounds", 0x23FDA269);
}
}
else if(modelNameHash == ATSTRINGHASH("VIGILANTE", 0xB5EF4C33))
{
if(m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_SM_Vigilante_Sounds", 0x8DA9F2D3);
}
else
{
return ATSTRINGHASH("DLC_SM_Vigilante_NPC_Sounds", 0x85F7F382);
}
}
else if(modelNameHash == ATSTRINGHASH("THRUSTER", 0x58CDAF30))
{
if(m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_XM_JATO_Sounds", 0x1F0EE445);
}
else
{
return ATSTRINGHASH("DLC_XM_JATO_NPC_Sounds", 0x45580054);
}
}
else if (modelNameHash == ATSTRINGHASH("SCRAMJET", 0xD9F0503D))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_BTL_scramjet_Sounds", 0x527F2941);
}
else
{
return ATSTRINGHASH("DLC_BTL_scramjet_NPC_Sounds", 0xD9AB36B8);
}
}
else if (modelNameHash == ATSTRINGHASH("OPPRESSOR2", 0x7B54A9D3))
{
if (m_Parent->IsFocusVehicle())
{
return ATSTRINGHASH("DLC_BTL_oppressor2_Rocket_Player_Sounds", 0x5E5366A6);
}
else
{
return ATSTRINGHASH("DLC_BTL_oppressor2_Rocket_NPC_Sounds", 0x6CEEDD6F);
}
}
return 0u;
}
// -------------------------------------------------------------------------------
// Set KERS system active (or not)
// -------------------------------------------------------------------------------
void audVehicleKersSystem::SetKERSState(audKersState state)
{
m_KersState = state;
if(m_KersState != AUD_KERS_STATE_RECHARGING)
{
m_KersRechargeRate = 0.0f;
}
}
// -------------------------------------------------------------------------------
// Called when the KERS system is recharging
// -------------------------------------------------------------------------------
void audVehicleKersSystem::SetKersRechargeRate(f32 rechargeRate)
{
m_KersRechargeRate = rechargeRate;
}
// -------------------------------------------------------------------------------
// Trigger KERS boost activation sound
// -------------------------------------------------------------------------------
void audVehicleKersSystem::TriggerKersBoostActivate()
{
if(m_Parent && !m_Parent->IsDisabled())
{
audSoundSet kersSoundset;
const u32 soundsetName = GetKersSoundSetName();
if(kersSoundset.Init(soundsetName))
{
audMetadataRef soundRef = kersSoundset.Find(ATSTRINGHASH("maxboost", 0x79B9A1AC));
if(soundRef.IsValid())
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
m_Parent->CreateDeferredSound(kersSoundset.Find(ATSTRINGHASH("maxboost", 0x79B9A1AC)), m_Parent->GetVehicle(), &initParams, true, true);
REPLAY_ONLY(CReplayMgr::ReplayRecordSound(soundsetName, ATSTRINGHASH("maxboost", 0x79B9A1AC), &initParams, m_Parent->GetVehicle()));
}
}
}
}
// -------------------------------------------------------------------------------
// Trigger KERS boost activation fail sound
// -------------------------------------------------------------------------------
void audVehicleKersSystem::TriggerKersBoostActivateFail()
{
if(m_Parent && !m_Parent->IsDisabled())
{
const u32 time = fwTimer::GetTimeInMilliseconds();
if(time - m_LastKersBoostFailTime > 250)
{
audSoundSet kersSoundset;
const u32 soundsetName = GetKersSoundSetName();
if(kersSoundset.Init(soundsetName))
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
m_Parent->CreateDeferredSound(kersSoundset.Find(ATSTRINGHASH("activate_fail", 0x2A684B3A)), m_Parent->GetVehicle(), &initParams, true, true);
REPLAY_ONLY(CReplayMgr::ReplayRecordSound(soundsetName, ATSTRINGHASH("activate_fail", 0x2A684B3A), &initParams, m_Parent->GetVehicle()));
m_LastKersBoostFailTime = time;
}
}
}
}
// -------------------------------------------------------------------------------
// audVehicleKersSystem GetKersBoostRemaining
// -------------------------------------------------------------------------------
f32 audVehicleKersSystem::GetKersBoostRemaining() const
{
if(m_Parent->GetVehicle()->pHandling->hFlags & HF_HAS_KERS)
{
return m_Parent->GetVehicle()->m_Transmission.GetKERSRemaining()/CTransmission::ms_KERSDurationMax;
}
else if(m_Parent->GetVehicle()->HasRocketBoost())
{
return m_Parent->GetVehicle()->GetRocketBoostRemaining()/m_Parent->GetVehicle()->GetRocketBoostCapacity();
}
return 0.f;
}
// -------------------------------------------------------------------------------
// Update jet whine
// -------------------------------------------------------------------------------
void audVehicleKersSystem::UpdateJetWhine(audVehicleVariables* UNUSED_PARAM(state))
{
if(!m_JetWhineSound)
{
const u32 modelNameHash = m_Parent->GetVehicleModelNameHash();
if(modelNameHash == ATSTRINGHASH("VOLTIC2", 0x3AF76F4A) ||
modelNameHash == ATSTRINGHASH("OPPRESSOR", 0x34B82784) ||
modelNameHash == ATSTRINGHASH("VIGILANTE", 0xB5EF4C33) ||
modelNameHash == ATSTRINGHASH("SCRAMJET", 0xD9F0503D))
{
audSoundSet rocketWhineSoundSet;
if(rocketWhineSoundSet.Init(GetKersSoundSetName()))
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
m_Parent->CreateAndPlaySound_Persistent(rocketWhineSoundSet.Find(ATSTRINGHASH("jet_whine", 0x118AFEFA)), &m_JetWhineSound, &initParams);
}
}
}
if(m_JetWhineSound)
{
m_JetWhineSound->FindAndSetVariableValue(ATSTRINGHASH("chargeRemaining", 0x44E9F02C), GetKersBoostRemaining());
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine constructor
// -------------------------------------------------------------------------------
audVehicleEngine::audVehicleEngine() :
m_Parent(NULL)
, m_VehicleEngineAudioSettings(NULL)
, m_GranularEngineAudioSettings(NULL)
, m_EngineState(AUD_ENGINE_OFF)
, m_AutoShutOffSystemState(AUD_AUTOSHUTOFF_INACTIVE)
, m_WasEngineOnLastFrame(false)
, m_WasEngineStartingLastFrame(false)
, m_EngineAutoShutOffRestartTime(0u)
, m_EngineStopTime(0u)
, m_EngineLastRevs(0.0f)
, m_IgnitionVariation(0.0f)
, m_IgnitionPitch(1.0f)
, m_ForceGranularLowQuality(false)
, m_IsMisfiring(false)
, m_HasTriggeredBackfire(false)
, m_LastMisfireTime(0u)
, m_ElectricToCombustionFade(1.0f)
, m_ConsecutiveExhaustPops(0)
REPLAY_ONLY(, m_LastReplayUpdateTime(0u))
, m_HasBreakableFanbelt(false)
, m_HasDraggableExhaust(false)
, m_RevsOffLoop(NULL)
, m_UnderLoadLoop(NULL)
, m_InductionLoop(NULL)
, m_IgnitionSound(NULL)
, m_StartupSound(NULL)
, m_ExhaustRattleLoop(NULL)
, m_ExhaustDragSound(NULL)
, m_BlownExhaustLow(NULL)
, m_BlownExhaustHigh(NULL)
, m_StartLoop(NULL)
, m_EngineSteamSound(NULL)
, m_EngineCoolingSound(NULL)
, m_EngineCoolingFan(NULL)
, m_ExhaustPopSound(NULL)
, m_RevLimiterPopSound(NULL)
, m_ShutdownSound(NULL)
, m_VehicleShutdownSound(NULL)
, m_HotwireSound(NULL)
, m_FanbeltDamageSound(NULL)
, m_EngineMode(COMBUSTION)
, m_RevsOffActive(false)
, m_GranularEngineActive(false)
, m_GranularEngineQuality(audGranularMix::GrainPlayerQualityMax)
, m_HasBeenInitialised(false)
, m_QualitySettingChanged(false)
, m_NumUpdates(0)
, m_HasTriggeredLimiterPop(false)
#if GTA_REPLAY
, m_ReplayIsHotwiring(false)
#endif
{
}
// -------------------------------------------------------------------------------
// Initialise all the static data members
// -------------------------------------------------------------------------------
void audVehicleEngine::InitClass()
{
VerifyExists(sm_RevsOffVolCurve.Init(ATSTRINGHASH("ENGINE_REVS_OFF_VOL", 0xF1078E66)), "Invalid RevsOffVolCurve");
VerifyExists(sm_ExhaustRattleVol.Init(ATSTRINGHASH("DAMAGE_CURVES_EXHAUST_RATTLE_VOL", 0x864CBCE)), "invalid damage to exhaust rattle curve");
VerifyExists(sm_StartLoopVolCurve.Init(ATSTRINGHASH("START_LOOP_VOL_CURVE", 0x99B00627)), "Invalid StartLoopVolCurve");
}
// -------------------------------------------------------------------------------
// audVehicleEngine Init
// -------------------------------------------------------------------------------
void audVehicleEngine::Init(audCarAudioEntity* parent)
{
naAssertf(parent, "Initialising engine without a valid vehicle");
if(parent)
{
m_Parent = parent;
m_VehicleEngineAudioSettings = parent->GetVehicleEngineAudioSettings();
m_GranularEngineAudioSettings = parent->GetGranularEngineAudioSettings();
}
InitCommon();
m_HasBreakableFanbelt = ENTITY_SEED_PROB(m_Parent->GetVehicle()->GetRandomSeed(), 0.1f);
m_HasDraggableExhaust = ENTITY_SEED_PROB(m_Parent->GetVehicle()->GetRandomSeed(), 0.1f);
m_engine.Init(parent, audVehicleEngineComponent::ENGINE_SOUND_TYPE_ENGINE);
m_exhaust.Init(parent, audVehicleEngineComponent::ENGINE_SOUND_TYPE_EXHAUST);
audGranularMix::audGrainPlayerQuality engineQuality = GetDesiredEngineQuality();
if(g_GranularEnabled && m_GranularEngineAudioSettings)
{
if((engineQuality == audGranularMix::GrainPlayerQualityHigh && m_GranularEngineAudioSettings->EngineAccel != g_NullSoundHash) ||
(engineQuality == audGranularMix::GrainPlayerQualityLow && m_GranularEngineAudioSettings->NPCEngineAccel != g_NullSoundHash && g_GranularEnableNPCGranularEngines))
{
m_granularEngine.Init(parent, audGranularEngineComponent::ENGINE_SOUND_TYPE_ENGINE, engineQuality);
m_granularExhaust.Init(parent, audGranularEngineComponent::ENGINE_SOUND_TYPE_EXHAUST, engineQuality);
m_GranularEngineActive = true;
m_GranularEngineQuality = engineQuality;
}
}
m_ElectricEngine.Init(parent);
m_Turbo.Init(parent);
m_Transmission.Init(parent);
m_HasBeenInitialised = true;
}
// -------------------------------------------------------------------------------
// audVehicleEngine Init
// -------------------------------------------------------------------------------
void audVehicleEngine::Init(audBoatAudioEntity* parent)
{
naAssertf(parent, "Initialising engine without a valid vehicle");
if(parent)
{
m_Parent = parent;
m_GranularEngineAudioSettings = m_Parent->GetGranularEngineAudioSettings();
}
InitCommon();
m_HasBreakableFanbelt = false;
m_HasDraggableExhaust = false;
audGranularMix::audGrainPlayerQuality engineQuality = GetDesiredEngineQuality();
if(g_GranularEnabled && m_GranularEngineAudioSettings)
{
if((engineQuality == audGranularMix::GrainPlayerQualityHigh && m_GranularEngineAudioSettings->EngineAccel != g_NullSoundHash) ||
(engineQuality == audGranularMix::GrainPlayerQualityLow && m_GranularEngineAudioSettings->NPCEngineAccel != g_NullSoundHash && g_GranularEnableNPCGranularEngines))
{
m_granularEngine.Init(parent, audGranularEngineComponent::ENGINE_SOUND_TYPE_ENGINE, engineQuality);
m_granularExhaust.Init(parent, audGranularEngineComponent::ENGINE_SOUND_TYPE_EXHAUST, engineQuality);
m_GranularEngineActive = true;
m_GranularEngineQuality = engineQuality;
m_HasBeenInitialised = true;
}
}
}
// -------------------------------------------------------------------------------
// Get desired engine quality
// -------------------------------------------------------------------------------
audGranularMix::audGrainPlayerQuality audVehicleEngine::GetDesiredEngineQuality() const
{
if(g_GranularForceLowQuality)
{
return audGranularMix::GrainPlayerQualityLow;
}
else if(g_GranularForceHighQuality)
{
return audGranularMix::GrainPlayerQualityHigh;
}
else
{
if(m_ForceGranularLowQuality)
{
return audGranularMix::GrainPlayerQualityLow;
}
else
{
return m_Parent->IsFocusVehicle() ? audGranularMix::GrainPlayerQualityHigh : audGranularMix::GrainPlayerQualityLow;
}
}
}
// -------------------------------------------------------------------------------
// Initialise anything vehicle-independent
// -------------------------------------------------------------------------------
void audVehicleEngine::InitCommon()
{
m_GranularEngineActive = false;
m_AutoShutOffSystemState = AUD_AUTOSHUTOFF_INACTIVE;
m_RevsOffLoopVolSmoother.Init(0.002f,0.001f,true);
m_UnderLoadRatioSmoother.Init(0.004f,0.001f,true);
m_ExhaustRattleSmoother.Init(0.0005f, true);
m_StartLoopVolSmoother.Init(0.0004f,0.00007f);
m_FanBeltVolumeSmoother.Init(0.0005f, 0.0015f, true);
m_ExhaustPopSmoother.Init(0.00005f, 0.001f, true);
m_EngineSteamFluctuator.Init(0.01f, 0.01f, 0.8f,0.95f,1.0f,1.1f,0.7f, 0.4f, 1.f);
if( m_Parent->GetVehicle() )
{
m_IgnitionPitch = (f32)(150 - (m_Parent->GetVehicle()->GetRandomSeed() % 300));
}
m_IgnitionVariation = audEngineUtil::GetRandomNumberInRange(0.0f,1.0f);
m_NumUpdates = 0;
m_KersSystem.Init(m_Parent);
}
// -------------------------------------------------------------------------------
// audVehicleEngine Update
// -------------------------------------------------------------------------------
void audVehicleEngine::Update(audVehicleVariables *state)
{
#if GTA_REPLAY
u32 currentTime = fwTimer::GetTimeInMilliseconds();
if(CReplayMgr::IsEditModeActive())
{
if(currentTime < m_LastReplayUpdateTime)
{
m_WasEngineOnLastFrame = false;
}
}
#endif
CVehicle* vehicle = m_Parent->GetVehicle();
const bool isEngineOn = !m_Parent->ShouldForceEngineOff() && vehicle && vehicle->m_nVehicleFlags.bEngineOn;
if(m_Parent->HasAutoShutOffEngine())
{
UpdateAutoShutOffSystem(state, isEngineOn);
}
else
{
CheckForEngineStartStop(state, isEngineOn);
}
UpdateIgnition(state);
if(m_Parent->GetDynamicWaveSlot())
{
if(m_EngineState != AUD_ENGINE_OFF)
{
if(!m_GranularEngineActive && m_VehicleEngineAudioSettings)
{
if(!m_engine.AreSoundsValid())
{
m_engine.Start();
}
else
{
if(!m_engine.HasPrepared())
{
if(m_engine.Prepare())
{
m_engine.Play();
}
}
}
if(!m_exhaust.AreSoundsValid())
{
m_exhaust.Start();
}
else
{
if(!m_exhaust.HasPrepared())
{
if(m_exhaust.Prepare())
{
m_exhaust.Play();
}
}
}
if(m_engine.HasPrepared() && m_engine.HasPrepared())
{
if(!m_engine.AreSoundsValid())
{
m_engine.Stop(-1);
m_engine.Start();
}
else
{
m_engine.Update(state);
}
if(!m_exhaust.AreSoundsValid())
{
m_exhaust.Stop(-1);
m_exhaust.Start();
}
else
{
m_exhaust.Update(state);
}
}
}
else if(m_GranularEngineActive)
{
if(!m_granularEngine.IsSoundValid(m_GranularEngineQuality))
{
m_granularEngine.Start();
}
else
{
if(!m_granularEngine.HasPrepared())
{
if( m_granularEngine.Prepare() )
{
m_granularEngine.Play();
}
}
}
if(!m_granularExhaust.IsSoundValid(m_GranularEngineQuality))
{
m_granularExhaust.Start();
}
else
{
if(!m_granularExhaust.HasPrepared())
{
if( m_granularExhaust.Prepare() )
{
m_granularExhaust.Play();
}
}
}
if(m_granularEngine.HasPrepared() && m_granularExhaust.HasPrepared())
{
if(!m_granularEngine.IsSoundValid(m_GranularEngineQuality))
{
m_granularEngine.Stop(-1);
m_granularEngine.Start();
}
if( !m_granularExhaust.IsSoundValid(m_GranularEngineQuality) )
{
m_granularExhaust.Stop(-1);
m_granularExhaust.Start();
}
}
// Updating channel volumes can potentially trigger a lot of simultaneous decodes if a new channel is turned on, so only do both engine & exhaust on the same frame for high quality cars, otherwise stagger the updates
bool updateChannelVolumes = m_GranularEngineQuality == audGranularMix::GrainPlayerQualityHigh || m_granularEngine.HasQualitySettingChanged() || m_NumUpdates % audGranularEngineComponent::ENGINE_SOUND_TYPE_MAX == audGranularEngineComponent::ENGINE_SOUND_TYPE_ENGINE;
m_granularEngine.Update(state, updateChannelVolumes);
updateChannelVolumes = m_GranularEngineQuality == audGranularMix::GrainPlayerQualityHigh || m_granularExhaust.HasQualitySettingChanged() || m_NumUpdates % audGranularEngineComponent::ENGINE_SOUND_TYPE_MAX == audGranularEngineComponent::ENGINE_SOUND_TYPE_EXHAUST;
m_granularExhaust.Update(state, updateChannelVolumes);
}
}
}
if( m_EngineState != AUD_ENGINE_OFF )
{
m_ElectricEngine.Update(state);
m_Turbo.Update(state);
m_Transmission.Update(state);
if(m_Parent->AreExhaustPopsEnabled())
{
UpdateExhaustPops(state);
}
if( m_exhaust.HasPlayed() &&
m_engine.HasPlayed() )
{
UpdateStartLoop(state);
}
if(m_Parent->GetVehicle()->HasRocketBoost())
{
m_KersSystem.UpdateJetWhine(state);
}
UpdateRevsOff(state);
UpdateEngineLoad(state);
UpdateInduction(state);
UpdateDamage(state);
UpdateHybridEngine(state);
UpdateExhaustRattle(state);
m_KersSystem.Update();
}
else if (m_Parent->GetVehicleModelNameHash() == ATSTRINGHASH("Toreador", 0x56C8A5EF))
{
if (m_Parent->GetVehicle()->m_nVehicleFlags.bEngineOn)
{
m_KersSystem.Update();
}
else
{
m_KersSystem.StopAllSounds();
}
}
bool hotwireActive = false;
if(vehicle)
{
m_WasEngineOnLastFrame = isEngineOn;
m_WasEngineStartingLastFrame = m_Parent->GetVehicle()->m_nVehicleFlags.bEngineStarting || m_Parent->ShouldForceEngineOff();
if(IsHotwiringVehicle())
{
//@@: location AUDVEHICLEENGINE_UPDATE_SET_HOTWIRING_ACTIVE
m_WasEngineStartingLastFrame = true;
hotwireActive = true;
}
}
if(hotwireActive && m_EngineState == AUD_ENGINE_OFF)
{
if(!m_HotwireSound)
{
audSoundInitParams initParams;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
m_Parent->CreateAndPlaySound_Persistent(m_Parent->GetExtrasSoundSet()->Find(ATSTRINGHASH("VehicleHotwire", 0xE16CA9BF)), &m_HotwireSound, &initParams);
}
}
else if(m_HotwireSound)
{
m_HotwireSound->StopAndForget();
}
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
m_LastReplayUpdateTime = currentTime;
}
#endif
m_EngineLastRevs = state->revs;
m_NumUpdates++;
}
bool audVehicleEngine::IsHotwiringVehicle() const
{
if(m_Parent && m_Parent->IsPlayerVehicle())
{
CPed* playerPed = FindPlayerPed();
if(playerPed)
{
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
return m_ReplayIsHotwiring;
}
#endif
const CTaskMotionInAutomobile* pTask = static_cast<const CTaskMotionInAutomobile*>(playerPed->GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_MOTION_IN_AUTOMOBILE));
if(pTask)
{
return pTask->IsHotwiringVehicle();
}
}
}
return false;
}
// -------------------------------------------------------------------------------
// Update the automatic engine shutoff system
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateAutoShutOffSystem(audVehicleVariables* state, bool engineOn)
{
if(m_AutoShutOffSystemState == AUD_AUTOSHUTOFF_ACTIVE && m_Parent->GetAutoShutOffTimer() == 0.0f)
{
if(m_Parent->GetVehicle()->m_nVehicleFlags.bEngineOn)
{
// AI cars must actually start moving before the engine comes back on. Prevents cars that are
// eg. blipping their throttle from constantly restarting the engine
if(m_Parent->GetVehicle()->m_Transmission.GetThrottle() > 0.1f &&
(m_Parent->IsPlayerDrivingVehicle() || state->fwdSpeedAbs > 0.1f))
{
m_AutoShutOffSystemState = AUD_AUTOSHUTOFF_RESTARTING;
m_EngineAutoShutOffRestartTime = fwTimer::GetTimeInMilliseconds();
m_AutoShutOffRestartTimer = 0.0f;
}
}
else
{
m_AutoShutOffSystemState = AUD_AUTOSHUTOFF_INACTIVE;
}
}
else if(m_AutoShutOffSystemState == AUD_AUTOSHUTOFF_RESTARTING)
{
m_AutoShutOffRestartTimer += fwTimer::GetTimeStep();
if(m_AutoShutOffRestartTimer > sm_AutoShutOffRestartDelay)
{
m_WasEngineOnLastFrame = false;
m_WasEngineStartingLastFrame = true;
CheckForEngineStartStop(state, engineOn);
m_AutoShutOffSystemState = AUD_AUTOSHUTOFF_INACTIVE;
}
}
else
{
if(m_Parent->GetAutoShutOffTimer() > sm_AutoShutOffCutoffTime)
{
if(m_AutoShutOffSystemState == AUD_AUTOSHUTOFF_INACTIVE)
{
if(m_EngineState == AUD_ENGINE_ON || m_EngineState == AUD_ENGINE_STARTING)
{
m_AutoShutOffSystemState = AUD_AUTOSHUTOFF_ACTIVE;
m_EngineState = AUD_ENGINE_STOPPING;
m_EngineStopTime = fwTimer::GetTimeInMilliseconds();
m_Parent->StopAndForgetSounds(m_RevLimiterPopSound);
StopEngine(true);
}
}
}
else
{
CheckForEngineStartStop(state, engineOn);
}
}
}
// -------------------------------------------------------------------------------
// Check if the engine has been started or stopped in the last frame
// -------------------------------------------------------------------------------
void audVehicleEngine::CheckForEngineStartStop(audVehicleVariables* state, bool engineOn)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle)
{
// If the vehicle's engine is on
if(engineOn)
{
// If it wasn't on last frame then we need to start the audio engine
if(!m_WasEngineOnLastFrame)
{
// Randomise our next misfire time each time the engine starts
m_LastMisfireTime = state->timeInMs + (u32)audEngineUtil::GetRandomNumberInRange(7000,20000);
if(m_EngineState == AUD_ENGINE_OFF )
{
// Don't fade in if the engine is starting, but do fade in if its not (ie if script started it immediately)
bool fadeIn = !m_WasEngineStartingLastFrame;
if(fadeIn)
{
m_EngineState = AUD_ENGINE_ON;
}
else
{
// Hybrid cars start in electric mode
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
CarAudioSettings* carSettings = ((audCarAudioEntity*)m_Parent)->GetCarAudioSettings();
if(carSettings && (carSettings->EngineType == HYBRID || carSettings->EngineType == ELECTRIC))
{
m_EngineMode = ELECTRIC;
m_ElectricToCombustionFade = 0.0f;
}
}
bool isSubmarine = m_Parent->GetVehicle()->InheritsFromSubmarine();
// Submarine startup has a bubble-y element to it, which we only want to play when submerged
if(!m_StartupSound)
{
if(!isSubmarine || m_Parent->GetVehicle()->GetIsInWater())
{
audSoundInitParams initParams;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.TrackEntityPosition = true;
if(IsGranularEngineActive())
{
initParams.Volume = m_GranularEngineAudioSettings->MasterVolume/100.f;
}
else if(m_VehicleEngineAudioSettings)
{
initParams.Volume = m_VehicleEngineAudioSettings->MasterVolume/100.f;
}
m_Parent->CreateAndPlaySound_Persistent(m_Parent->GetEngineStartupSound(), &m_StartupSound, &initParams);
}
}
m_EngineState = AUD_ENGINE_STARTING;
}
if(m_GranularEngineActive)
{
if(fadeIn)
{
if(m_QualitySettingChanged)
{
m_granularEngine.SetFadeInRequired(400);
m_granularExhaust.SetFadeInRequired(400);
}
else
{
u32 fadeTime = GetEngineSwitchFadeTime();
m_granularEngine.SetFadeInRequired(fadeTime);
m_granularExhaust.SetFadeInRequired(fadeTime);
}
}
m_granularEngine.Start();
m_granularExhaust.Start();
}
else if(m_VehicleEngineAudioSettings)
{
if(fadeIn)
{
if(m_QualitySettingChanged)
{
m_engine.SetFadeInRequired(400);
m_exhaust.SetFadeInRequired(400);
}
else
{
u32 fadeTime = GetEngineSwitchFadeTime();
m_engine.SetFadeInRequired(fadeTime);
m_exhaust.SetFadeInRequired(fadeTime);
}
}
m_engine.Start();
m_exhaust.Start();
}
m_EngineOffTemperature = 100.0f;
m_Parent->EngineStarted();
}
else if(m_EngineState == AUD_ENGINE_STOPPING)
{
// Engine got restarted before we actually called StopEngine, so just flick back to on
m_EngineState = AUD_ENGINE_ON;
}
}
else
{
if(m_EngineState == AUD_ENGINE_STARTING)
{
// If we're not granular, or we are and have started playback, allow the player to interrupt the
// starting behaviour if they start accelerating
if(!IsGranularEngineActive() || GetGranularEngine()->AreAnySoundsPlaying())
{
if(vehicle->GetThrottle()>0.1f || state->fwdSpeed<-0.1f)
{
m_EngineState = AUD_ENGINE_ON;
}
}
}
}
}
// If the vehicle's engine is turned off
else
{
if(m_WasEngineOnLastFrame)
{
m_EngineState = AUD_ENGINE_STOPPING;
m_EngineStopTime = fwTimer::GetTimeInMilliseconds();
m_Parent->StopAndForgetSounds(m_RevLimiterPopSound);
}
else if( m_EngineState == AUD_ENGINE_STOPPING )
{
if(!m_Parent->IsStartupSoundPlaying())
{
if(fwTimer::GetTimeInMilliseconds() - m_EngineStopTime > g_EngineStopDuration)
{
if(state->revs <= 0.2f)
{
StopEngine(true);
}
}
}
}
}
}
}
// -------------------------------------------------------------------------------
// Stop the engine
// -------------------------------------------------------------------------------
void audVehicleEngine::StopEngine(bool playStopSound)
{
if(m_Parent)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(m_EngineState == AUD_ENGINE_STOPPING && playStopSound)
{
m_engine.Stop(400);
m_exhaust.Stop(400);
m_granularEngine.Stop(400);
m_granularExhaust.Stop(400);
if(vehicle)
{
// Play a sound depending on the manner in which the vehicle stopped
u32 engineShutdownHash = g_NullSoundHash;
u32 vehicleShutdownHash = m_Parent->GetVehicleShutdownSound();
const f32 engineHealth = m_Parent->ComputeEffectiveEngineHealth();
f32 engineDamageFactor = 1.0f - (engineHealth / CTransmission::GetEngineHealthMax());
if((vehicle->m_nVehicleFlags.bIsDrowning && m_Parent->GetAudioVehicleType() != AUD_VEHICLE_BOAT) || m_Parent->IsDamageModel() || engineDamageFactor >= 0.8f)
{
if(m_EngineMode != ELECTRIC)
{
engineShutdownHash = ATSTRINGHASH("ENGINE_BREAKDOWN", 0xF9192597);
}
}
else
{
engineShutdownHash = m_Parent->GetEngineShutdownSound();
}
bool isSubmarine = m_Parent->GetVehicle()->InheritsFromSubmarine();
// Submarine shutdown has a bubble-y element to it, which we only want to play when submerged
if(!isSubmarine || m_Parent->GetVehicle()->GetIsInWater())
{
audSoundInitParams initParams;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
// Don't update the entity for submarines - we want the sound audible above and below water
if(!isSubmarine)
{
initParams.UpdateEntity = true;
}
initParams.Tracker = vehicle->GetPlaceableTracker();
if(IsGranularEngineActive())
{
initParams.Volume = m_GranularEngineAudioSettings->MasterVolume/100.f;
}
else if(m_VehicleEngineAudioSettings)
{
initParams.Volume = m_VehicleEngineAudioSettings->MasterVolume/100.f;
}
if(!m_ShutdownSound && engineShutdownHash != g_NullSoundHash)
{
m_Parent->CreateAndPlaySound_Persistent(engineShutdownHash, &m_ShutdownSound, &initParams);
}
// Only play this if the vehicle has really, truly, properly shutdown (rather than breaking or going into energy saving mode)
if(!vehicle->m_nVehicleFlags.bEngineOn && engineShutdownHash != ATSTRINGHASH("ENGINE_BREAKDOWN", 0xF9192597))
{
if(!m_VehicleShutdownSound && vehicleShutdownHash != g_NullSoundHash)
{
m_Parent->CreateAndPlaySound_Persistent(vehicleShutdownHash, &m_VehicleShutdownSound, &initParams);
}
}
}
}
}
else if(vehicle && vehicle->GetStatus() == STATUS_WRECKED)
{
// disable release if we're stopping engine sounds because the vehicle is wrecked
m_engine.Stop(-1);
m_exhaust.Stop(-1);
m_granularEngine.Stop(-1);
m_granularExhaust.Stop(-1);
}
else
{
// Streamed out/converting to dummy - fade out the engine. Fade time now reduced, as the dynamic activation range will mean that you shouldn't
// hear the change in most normal situations. In cases where you do, there must be loads of vehicles in the immediate vicinity so the swap won't
// be noticable anyway. Having shorter duration also aids with the vehicle bank swapping, as the waveslot will become will be free much sooner.
u32 releaseTime = (u32)(g_EngineSwitchFadeOutTime * m_Parent->GetEngineSwitchFadeTimeScalar());
if(audVehicleAudioEntity::IsPlayerVehicleStarving())
{
releaseTime = 200;
}
else if(m_Parent->IsForcedGameObjectResetRequired())
{
releaseTime = 400;
}
m_engine.Stop(releaseTime);
m_exhaust.Stop(releaseTime);
m_granularEngine.Stop(releaseTime);
m_granularExhaust.Stop(releaseTime);
}
// If we're wrecked, stop the steam sound, otherwise let it fade out gradually as the engine cools
if(vehicle && vehicle->GetStatus() == STATUS_WRECKED)
{
m_Parent->StopAndForgetSounds(m_EngineSteamSound);
}
m_Transmission.Stop();
m_Turbo.Stop();
m_ElectricEngine.Stop();
m_Parent->SetBoostActive(false);
// Toreador boost can remain on even if the audio engine is technically off
if (m_Parent->GetVehicleModelNameHash() != ATSTRINGHASH("Toreador", 0x56C8A5EF) || !m_Parent->GetVehicle()->m_nVehicleFlags.bEngineOn)
{
m_KersSystem.StopAllSounds();
}
m_Parent->StopAndForgetSounds(m_ExhaustRattleLoop, m_RevsOffLoop, m_UnderLoadLoop, m_InductionLoop, m_BlownExhaustLow, m_BlownExhaustHigh, m_StartLoop, m_StartupSound, m_IgnitionSound, m_RevLimiterPopSound, m_FanbeltDamageSound, m_ExhaustDragSound);
}
if(m_EngineState != AUD_ENGINE_OFF)
{
m_EngineOffTemperature = m_Parent->GetVehicle()->m_EngineTemperature;
}
m_QualitySettingChanged = false;
if (m_EngineState == AUD_ENGINE_STOPPING)
{
m_Parent->FreeWaveSlot(false);
}
m_EngineState = AUD_ENGINE_OFF;
}
// -------------------------------------------------------------------------------
// Update induction
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateInduction(audVehicleVariables *state)
{
if (!m_VehicleEngineAudioSettings)
return;
// Kill the induction sound when we enter the mod shop and are idling, otherwise it won't
// switch when we upgrade the engine.
if((m_Parent->IsInCarModShop() && state->revs <= 0.2f) || m_Parent->ShouldForceEngineOff())
{
if(m_InductionLoop)
{
m_InductionLoop->StopAndForget();
}
return;
}
if(!m_InductionLoop)
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.u32ClientVar = (u32)AUD_VEHICLE_SOUND_ENGINE;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
u32 inductionLoopHash = m_VehicleEngineAudioSettings->InductionLoop;
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
if(((audCarAudioEntity*)m_Parent)->IsEngineUpgraded() && m_VehicleEngineAudioSettings->UpgradedInductionLoop != g_NullSoundHash)
{
inductionLoopHash = m_VehicleEngineAudioSettings->UpgradedInductionLoop;
}
}
if(inductionLoopHash != g_NullSoundHash)
{
m_Parent->CreateAndPlaySound_Persistent(inductionLoopHash, &m_InductionLoop, &initParams);
}
}
if(m_InductionLoop)
{
m_InductionLoop->FindAndSetVariableValue(ATSTRINGHASH("revs", 0x44D3B231), state->revs);
m_InductionLoop->FindAndSetVariableValue(ATSTRINGHASH("throttle", 0xEA0151DC), state->throttle);
}
}
// -------------------------------------------------------------------------------
// Update exhaust rattle
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateExhaustRattle(audVehicleVariables *state)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle && m_VehicleEngineAudioSettings)
{
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
if(((audCarAudioEntity*)m_Parent)->GetCarAudioSettings()->EngineType != ELECTRIC)
{
f32 revsOffVolLin = m_RevsOffLoopVolSmoother.GetLastValue();
revsOffVolLin *= audDriverUtil::ComputeLinearVolumeFromDb(state->exhaustPostSubmixVol + g_EngineVolumeTrim);
f32 bodyDamageFactor = m_Parent->ComputeEffectiveBodyDamageFactor();
f32 revsFactor = (state->revs - 0.2f) * 1.25f;
if(m_EngineState == AUD_ENGINE_STARTING)
{
revsOffVolLin = 1.0f;
}
else if(m_ShutdownSound)
{
revsOffVolLin = 1.0f;
revsFactor = 0.25f;
}
f32 exhaustRattleVolLin = (revsFactor * 0.6f) * revsOffVolLin * sm_ExhaustRattleVol.CalculateValue(bodyDamageFactor);
f32 exhaustRattleVolLinSmoothed = m_ExhaustRattleSmoother.CalculateValue(exhaustRattleVolLin, g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0));
m_Parent->UpdateLoopWithLinearVolumeAndPitch(&m_ExhaustRattleLoop, ATSTRINGHASH("DAMAGE_EXHAUST_RATTLE", 0x367D8F), exhaustRattleVolLinSmoothed, 0, m_Parent->GetEnvironmentGroup(), NULL, true, false);
if(m_HasDraggableExhaust BANK_ONLY( || g_ForceDamage))
{
f32 volumeLin = 0.0f;
u32 timeSinceStationary = state->timeInMs - ((audCarAudioEntity*)m_Parent)->GetLastTimeStationary();
const u32 maxExhaustDragTime = 2500;
const u32 exhaustDragFadeTime = 1500;
if(timeSinceStationary < maxExhaustDragTime)
{
if(state->numWheelsTouchingFactor == 1.0f && bodyDamageFactor > 0.5f)
{
volumeLin = 1.0f - Min(state->fwdSpeedAbs/30.0f, 1.0f);
volumeLin *= Min(state->fwdSpeedAbs/3.0f, 1.0f);
volumeLin *= Min(bodyDamageFactor/0.7f, 1.0f);
if(timeSinceStationary > exhaustDragFadeTime)
{
volumeLin *= 1.0f - Min((timeSinceStationary - exhaustDragFadeTime)/(f32)(maxExhaustDragTime - exhaustDragFadeTime), 1.0f);
}
}
}
m_Parent->UpdateLoopWithLinearVolumeAndPitch(&m_ExhaustDragSound, ATSTRINGHASH("DAMAGE_EXHAUST_DRAG_PULL_AWAY", 0x4B0B6D81), volumeLin, state->roadNoisePitch, m_Parent->GetEnvironmentGroup(), NULL, true, true);
}
else if(m_ExhaustDragSound)
{
m_ExhaustDragSound->StopAndForget();
}
}
}
}
}
// -------------------------------------------------------------------------------
// Update engine load
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateEngineLoad(audVehicleVariables *state)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle)
{
f32 revRatio = (state->revs - 0.2f) * 1.25f;
f32 desiredUnderLoadRatio = vehicle->m_Transmission.IsEngineUnderLoad() && !vehicle->GetHandBrake()? (1.0f - Min(((state->gear - 1) + revRatio) / 3.0f, 1.0f)) : 0.0f;
if(m_Parent->HasHeavyRoadNoise())
{
desiredUnderLoadRatio = Max(desiredUnderLoadRatio, Min(m_Parent->GetVehicle()->GetTransform().GetB().GetZf()/0.15f, 0.25f) * Min(state->fwdSpeedRatio/0.3f, 1.0f));
}
f32 underLoadVolLin = m_UnderLoadRatioSmoother.CalculateValue(desiredUnderLoadRatio, state->timeInMs) * Min(state->fwdSpeedAbs/3.0f, 1.0f);
if(!IsGranularEngineActive() && !m_UnderLoadLoop && underLoadVolLin > g_SilenceVolumeLin)
{
audSoundInitParams initParams;
initParams.Category = g_EngineLoudCategory;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.UpdateEntity = true;
Assign(initParams.EffectRoute, m_Parent->GetExhaustEffectRoute());
m_Parent->CreateAndPlaySound_Persistent(m_Parent->GetExtrasSoundSet()->Find(ATSTRINGHASH("Vehicle_UnderLoadLoop", 0xA52BD7E7)), &m_UnderLoadLoop, &initParams);
}
if(m_UnderLoadLoop)
{
if(underLoadVolLin > g_SilenceVolumeLin)
{
s32 pitch = audDriverUtil::ConvertRatioToPitch(state->pitchRatio);
m_UnderLoadLoop->SetClientVariable((u32)AUD_VEHICLE_SOUND_EXHAUST);
m_UnderLoadLoop->SetRequestedPitch(pitch);
m_UnderLoadLoop->SetRequestedVolume(audDriverUtil::ComputeDbVolumeFromLinear(underLoadVolLin));
m_UnderLoadLoop->SetRequestedEnvironmentalLoudnessFloat(state->environmentalLoudness);
m_UnderLoadLoop->SetRequestedFrequencySmoothingRate(m_Parent->GetFrequencySmoothingRate());
}
else
{
m_UnderLoadLoop->StopAndForget();
}
}
}
}
// -------------------------------------------------------------------------------
// Update revs off
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateRevsOff(audVehicleVariables *state)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle && m_VehicleEngineAudioSettings)
{
m_RevsOffActive = false;
if(m_EngineState == AUD_ENGINE_STARTING && state->revs > CTransmission::ms_fIdleRevs)
{
m_RevsOffActive = true;
}
else if(m_IsMisfiring || (state->revs < m_EngineLastRevs && state->throttle <= 0.1f && m_EngineState == AUD_ENGINE_ON && state->gear != 0 && state->timeInMs > m_Transmission.GetLastGearChangeTimeMs()+500))
{
m_RevsOffActive = true;
}
f32 revRatio = (state->revs - 0.2f) * 1.25f;
f32 revsOffVolLin = m_RevsOffLoopVolSmoother.CalculateValue(m_RevsOffActive ? revRatio : 0.0f, state->timeInMs);
if(!IsGranularEngineActive() && !m_RevsOffLoop && revsOffVolLin > g_SilenceVolumeLin)
{
audSoundInitParams initParams;
initParams.Category = g_EngineLoudCategory;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.UpdateEntity = true;
Assign(initParams.EffectRoute, m_Parent->GetExhaustEffectRoute());
m_Parent->CreateAndPlaySound_Persistent(m_VehicleEngineAudioSettings->RevsOffLoop, &m_RevsOffLoop, &initParams);
}
if(m_RevsOffLoop)
{
if(revsOffVolLin > g_SilenceVolumeLin)
{
s32 pitch = audDriverUtil::ConvertRatioToPitch(state->pitchRatio);
m_RevsOffLoop->SetClientVariable((u32)AUD_VEHICLE_SOUND_EXHAUST);
m_RevsOffLoop->SetRequestedPitch(pitch);
m_RevsOffLoop->SetRequestedVolume(audDriverUtil::ComputeDbVolumeFromLinear(revsOffVolLin));
m_RevsOffLoop->SetRequestedEnvironmentalLoudnessFloat(state->environmentalLoudness);
m_RevsOffLoop->SetRequestedFrequencySmoothingRate(m_Parent->GetFrequencySmoothingRate());
}
else
{
m_RevsOffLoop->StopAndForget();
}
}
}
}
// -------------------------------------------------------------------------------
// Update hybrid engine
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateHybridEngine(audVehicleVariables * state)
{
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
CarAudioSettings* carSettings = ((audCarAudioEntity*)m_Parent)->GetCarAudioSettings();
if(carSettings->EngineType == HYBRID)
{
if(m_EngineMode == ELECTRIC && state->fwdSpeedRatio > 0.4f && state->gear > 0)
{
m_EngineMode = COMBUSTION;
}
// Testing against m_Transmission.GetRevRatio() here as state->revs is faked based on the fwd speed for hybrid vehicles
else if(m_EngineMode == COMBUSTION && state->throttle < 0.1f && m_Parent->GetVehicle()->m_Transmission.GetRevRatio() == CTransmission::ms_fIdleRevs && state->fwdSpeedRatio < 0.15f)
{
m_EngineMode = ELECTRIC;
}
if(m_EngineMode == COMBUSTION)
{
m_ElectricToCombustionFade += (fwTimer::GetTimeStep() * 2.0f);
}
else
{
m_ElectricToCombustionFade -= (fwTimer::GetTimeStep() * 2.0f);
}
m_ElectricToCombustionFade = Clamp(m_ElectricToCombustionFade, 0.0f, 1.0f);
f32 combustionVolumeScale = m_ElectricToCombustionFade;
f32 electricVolumeScale = 1.0f - m_ElectricToCombustionFade;
m_granularEngine.SetVolumeScale(combustionVolumeScale);
m_granularExhaust.SetVolumeScale(combustionVolumeScale);
m_engine.SetVolumeScale(combustionVolumeScale);
m_exhaust.SetVolumeScale(combustionVolumeScale);
m_ElectricEngine.SetVolumeScale(electricVolumeScale);
}
}
}
// -------------------------------------------------------------------------------
// Update engine damage stuff
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateDamage(audVehicleVariables *state)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle && m_Parent->GetAudioVehicleType() != AUD_VEHICLE_BOAT)
{
// Update misfires - this should only occur on vehicles that are actually damaged (not 'fake' audio damaged)
if(!(m_IsMisfiring && m_HasTriggeredBackfire) &&
state->gear > 0 &&
!vehicle->m_nVehicleFlags.bIsDrowning &&
state->clutchRatio > 0.1f &&
m_LastMisfireTime + 3000 < state->timeInMs &&
m_VehicleEngineAudioSettings &&
m_Parent->GetVehicle()->GetVehicleDamage()->GetEngineHealth() < 300.f)
{
if(state->gasPedal > 0.8f &&
state->fwdSpeedRatio > 0.1f &&
m_Parent->GetRevsSmoother()->GetLastValue() < 0.7f &&
m_Parent->GetRevsSmoother()->GetLastValue() > 0.5f)
{
m_LastMisfireTime = state->timeInMs;
m_HasTriggeredBackfire = false;
m_IsMisfiring = true;
}
}
f32 radBurstDamage = vehicle->GetVehicleDamage()->GetEngineHealth();
#if __BANK
if(g_ForceDamage && m_Parent->IsPlayerVehicle())
{
radBurstDamage = ((1.0f - g_ForcedDamageFactor) * 1000.0f);
}
#endif
// Update radiator damage
if(m_EngineSteamSound)
{
if (radBurstDamage > ENGINE_DAMAGE_RADBURST)
{
m_EngineSteamSound->StopAndForget();
}
}
else
{
// ignore always damaged flag for radburst SFX
if (radBurstDamage < ENGINE_DAMAGE_RADBURST && vehicle->m_nVehicleFlags.bEngineOn)
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
// If we're getting into a pre-damaged vehicle then ramp up the steam volume slowly so it doesn't just come in at full
// volume as soon as the engine starts
if(m_EngineState == AUD_ENGINE_STARTING)
{
initParams.AttackTime = 15000;
}
m_Parent->CreateSound_PersistentReference(ATSTRINGHASH("DAMAGE_RADIATOR_HISS", 0xD536089C), &m_EngineSteamSound, &initParams);
if(m_EngineSteamSound)
{
m_EngineSteamSound->SetUpdateEntity(true);
m_EngineSteamSound->SetClientVariable((u32)AUD_VEHICLE_SOUND_ENGINE);
m_EngineSteamSound->PrepareAndPlay();
}
}
}
f32 desiredFanbeltVolLin = m_Parent->GetDrowningFactor() == 0.0f ? (0.05f + (0.45f * state->throttle)) + (0.5f * state->fwdSpeedRatio) : 0.0f;
f32 fanbeltVolumeLin = m_FanBeltVolumeSmoother.CalculateValue(desiredFanbeltVolLin, state->timeInMs);
f32 engineDamageFactor = state->engineDamageFactor;
if(engineDamageFactor > 0.5f && fanbeltVolumeLin > g_SilenceVolumeLin && (m_HasBreakableFanbelt BANK_ONLY( || g_ForceDamage)))
{
if(!m_FanbeltDamageSound BANK_ONLY( &&!g_MuteFanbeltDamage))
{
const audMetadataRef fanbeltSoundRef = audCarAudioEntity::GetDamageSoundSet()->Find("Fanbelt_Damage");
audSoundInitParams initParams;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.TrackEntityPosition = true;
initParams.UpdateEntity = true;
m_Parent->CreateAndPlaySound_Persistent(fanbeltSoundRef, &m_FanbeltDamageSound, &initParams);
}
if(m_FanbeltDamageSound)
{
f32 revRatio = (state->revs - 0.2f) * 1.25f;
m_FanbeltDamageSound->FindAndSetVariableValue(ATSTRINGHASH("revs", 0x44D3B231), revRatio);
m_FanbeltDamageSound->FindAndSetVariableValue(ATSTRINGHASH("damageFactor", 0xAD8C0EF4), engineDamageFactor);
m_FanbeltDamageSound->SetRequestedVolume(audDriverUtil::ComputeDbVolumeFromLinear(fanbeltVolumeLin));
#if __BANK
if(g_MuteFanbeltDamage)
{
m_FanbeltDamageSound->StopAndForget();
}
#endif
}
}
else if(m_FanbeltDamageSound)
{
m_FanbeltDamageSound->StopAndForget();
}
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine UpdateExhaustPops
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateExhaustPops(audVehicleVariables* state)
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle && m_VehicleEngineAudioSettings)
{
bool revLimiterPopsEnabled = false;
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle->m_nFlags.bPossiblyTouchesWater)
{
f32 waterZ = 0.f;
const Vector3 exhaustPos = VEC3V_TO_VECTOR3(vehicle->TransformIntoWorldSpace(m_Parent->GetExhaustOffsetPos()));
vehicle->m_Buoyancy.GetWaterLevelIncludingRivers(exhaustPos, &waterZ, false, POOL_DEPTH, REJECTIONABOVEWATER, NULL);
// Exhaust is underwater, so don't do any pops
if(waterZ > exhaustPos.z)
{
if(m_RevLimiterPopSound)
{
m_Parent->StopAndForgetSounds(m_RevLimiterPopSound);
}
return;
}
}
if(vehicle->InheritsFromSubmarineCar())
{
// Don't let sub cars exhaust pop while transforming
CSubmarineCar* pSubCar = static_cast<CSubmarineCar*>(vehicle);
if(pSubCar->GetAnimationState() != CSubmarineCar::State_Finished || pSubCar->GetAnimationPhase() > 0.f)
{
if(m_RevLimiterPopSound)
{
m_Parent->StopAndForgetSounds(m_RevLimiterPopSound);
}
return;
}
}
bool exhaustBroken = state->engineDamageFactor >= 0.7f;
if(!exhaustBroken)
{
if(m_GranularEngineActive && m_GranularEngineAudioSettings)
{
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
if(AUD_GET_TRISTATE_VALUE(m_GranularEngineAudioSettings->Flags, FLAG_ID_GRANULARENGINEAUDIOSETTINGS_HASREVLIMITER) == AUD_TRISTATE_TRUE || ((audCarAudioEntity*)m_Parent)->IsEngineUpgraded())
{
u32 revLimiterPopHash = m_GranularEngineAudioSettings->RevLimiterPopSound;
if(((audCarAudioEntity*)m_Parent)->IsExhaustUpgraded() && m_GranularEngineAudioSettings->UpgradedRevLimiterPop != g_NullSoundHash)
{
revLimiterPopHash = m_GranularEngineAudioSettings->UpgradedRevLimiterPop;
}
if((state->onRevLimiter BANK_ONLY(|| g_GranularForceRevLimiter)) && revLimiterPopHash != g_NullSoundHash)
{
revLimiterPopsEnabled = true;
if(!m_RevLimiterPopSound && !m_HasTriggeredLimiterPop)
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.u32ClientVar = AUD_VEHICLE_SOUND_EXHAUST;
m_Parent->CreateAndPlaySound_Persistent(revLimiterPopHash, &m_RevLimiterPopSound, &initParams);
m_HasTriggeredLimiterPop = true;
if(m_RevLimiterPopSound)
{
bool hasVisualPops = (AUD_GET_TRISTATE_VALUE(m_GranularEngineAudioSettings->Flags, FLAG_ID_GRANULARENGINEAUDIOSETTINGS_VISUALFXONLIMITER) == AUD_TRISTATE_TRUE);
hasVisualPops |= ((audCarAudioEntity*)m_Parent)->GetExhaustUpgradeLevel() == 1.0f;
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
vehicle->m_nVehicleFlags.bAudioBackfired = hasVisualPops;
}
}
}
}
}
}
}
}
if(!revLimiterPopsEnabled)
{
m_HasTriggeredLimiterPop = false;
}
if(m_RevLimiterPopSound)
{
m_RevLimiterPopSound->SetRequestedVolume(state->exhaustConeAtten);
}
u32 exhaustPopHash = m_VehicleEngineAudioSettings->ExhaustPops;
bool exhaustUpgraded = false;
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
if(((audCarAudioEntity*)m_Parent)->IsExhaustUpgraded())
{
exhaustUpgraded = true;
if(m_VehicleEngineAudioSettings->UpgradedExhaustPops != g_NullSoundHash)
{
exhaustPopHash = m_VehicleEngineAudioSettings->UpgradedExhaustPops;
}
}
}
if(exhaustPopHash != g_NullSoundHash)
{
f32 exhaustSmootherValue = m_ExhaustPopSmoother.CalculateValue(state->revs * Max(state->fwdSpeedRatio * 1.2f, 1.0f), g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0));
if(state->throttleInput == 0.0f &&
exhaustSmootherValue > 0.5f &&
fwTimer::GetTimeInMilliseconds() - m_Parent->GetVehicleEngine()->GetTurbo()->GetLastDumpValveExhaustPopTime() > 2000 &&
!vehicle->m_nVehicleFlags.bIsDrowning &&
state->rawRevs < m_EngineLastRevs)
{
if(!m_ExhaustPopSound && m_ConsecutiveExhaustPops == 0)
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.u32ClientVar = AUD_VEHICLE_SOUND_EXHAUST;
m_Parent->CreateAndPlaySound_Persistent(exhaustPopHash, &m_ExhaustPopSound, &initParams);
m_ExhaustPopSmoother.Reset();
m_ExhaustPopSmoother.CalculateValue(exhaustSmootherValue - 0.1f);
if(m_ExhaustPopSound)
{
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
vehicle->m_nVehicleFlags.bAudioBackfired = true;
REPLAY_ONLY(CReplayMgr::ReplayRecordSoundPersistant(exhaustPopHash, &initParams, m_ExhaustPopSound, m_Parent->GetVehicle()));
}
}
m_ConsecutiveExhaustPops++;
}
}
else if(state->throttleInput > 0.1f)
{
m_ConsecutiveExhaustPops = 0;
}
}
if(m_ExhaustPopSound)
{
if(vehicle->m_nVehicleFlags.bIsDrowning)
{
m_ExhaustPopSound->StopAndForget();
}
else
{
m_ExhaustPopSound->SetRequestedVolume(state->exhaustConeAtten);
}
}
if(m_IsMisfiring && state->timeInMs - m_LastMisfireTime < 550 && state->timeInMs - m_LastMisfireTime > 400 && !m_HasTriggeredBackfire)
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.Volume = state->exhaustConeAtten;
initParams.Position = VEC3V_TO_VECTOR3(vehicle->TransformIntoWorldSpace(m_Parent->GetExhaustOffsetPos()));
initParams.u32ClientVar = AUD_VEHICLE_SOUND_EXHAUST;
if(m_Parent->CreateAndPlaySound(ATSTRINGHASH("EX_POPS_BACKFIRE", 0x38BEF758), &initParams))
{
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
vehicle->m_nVehicleFlags.bAudioBackfiredBanger = true;
REPLAY_ONLY(CReplayMgr::ReplayRecordSound(ATSTRINGHASH("EX_POPS_BACKFIRE", 0x38BEF758), &initParams, m_Parent->GetVehicle()));
}
}
m_HasTriggeredBackfire = true;
}
else if(m_IsMisfiring && state->timeInMs - m_LastMisfireTime >= 550)
{
m_IsMisfiring = false;
m_HasTriggeredBackfire = false;
if(m_Parent->IsPlayerVehicle())
{
m_LastMisfireTime = state->timeInMs + (u32)audEngineUtil::GetRandomNumberInRange(7000,20000);
}
else
{
m_LastMisfireTime = state->timeInMs + (u32)audEngineUtil::GetRandomNumberInRange(15000,30000);
}
}
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine UpdateIgnition
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateIgnition(audVehicleVariables* state)
{
CVehicle* vehicle = m_Parent->GetVehicle();
bool performingAutoShutOffRestart = false;
if(m_Parent->HasAutoShutOffEngine())
{
if(fwTimer::GetTimeInMilliseconds() - m_EngineAutoShutOffRestartTime < sm_AutoShutOffIgnitionTime)
{
performingAutoShutOffRestart = true;
}
}
if(vehicle)
{
if((vehicle->m_nVehicleFlags.bEngineStarting || performingAutoShutOffRestart) && m_Parent->GetVehicle()->GetDriver() && !m_IgnitionSound)
{
if(!vehicle->m_nVehicleFlags.bIsDrowning)
{
audSoundInitParams initParams;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
s32 predelay = 0;
if(!m_Parent->IsKickstarted())
{
if(!performingAutoShutOffRestart && vehicle->m_failedEngineStartAttempts>0)
{
// delay the retry
predelay = audEngineUtil::GetRandomNumberInRange(300,650);
}
}
initParams.Predelay = predelay;
initParams.Pitch = (s16) m_IgnitionPitch;
initParams.UpdateEntity = true;
initParams.VolumeCurveScale = state->rollOffScale;
m_Parent->CreateSound_PersistentReference(m_Parent->GetEngineIgnitionLoopSound(), &m_IgnitionSound, &initParams);
if(m_IgnitionSound)
{
m_IgnitionSound->FindAndSetVariableValue(g_VariationVariableHash, m_IgnitionVariation);
m_IgnitionSound->SetClientVariable((u32)AUD_VEHICLE_SOUND_ENGINE);
m_IgnitionSound->PrepareAndPlay();
}
}
}
else if(m_IgnitionSound && ((!performingAutoShutOffRestart && !vehicle->m_nVehicleFlags.bEngineStarting) || m_Parent->GetVehicle()->GetDriver() == NULL))
{
// Kickstarts are one shots, so let them play out fully
if(!m_Parent->IsKickstarted())
{
m_IgnitionSound->StopAndForget();
}
}
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine UpdateStartLoop
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateStartLoop(audVehicleVariables* state)
{
if(m_VehicleEngineAudioSettings)
{
f32 volLin=0.0f;
if(m_VehicleEngineAudioSettings->StartLoop > 0)
{
volLin = state->throttle * sm_StartLoopVolCurve.CalculateValue(state->revs) * audDriverUtil::ComputeLinearVolumeFromDb(state->exhaustPostSubmixVol + g_EngineVolumeTrim);
}
volLin = m_StartLoopVolSmoother.CalculateValue(volLin,state->timeInMs);
// dont want to ever stop this loop since pitch smoothing will be out of sync with the main engine
m_Parent->UpdateLoopWithLinearVolumeAndPitchRatio(&m_StartLoop, m_VehicleEngineAudioSettings->StartLoop, volLin, state->pitchRatio, m_Parent->GetEnvironmentGroup(), g_EngineCategory, false, true);
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine SetGranularPitch
// -------------------------------------------------------------------------------
void audVehicleEngine::SetGranularPitch(s32 pitch)
{
for(u32 loop = 0; loop < audGranularMix::GrainPlayerQualityMax; loop++)
{
audSound* engineSound = GetGranularEngine()->GetGranularSound((audGranularMix::audGrainPlayerQuality)loop);
audSound* exhaustSound = GetGranularExhaust()->GetGranularSound((audGranularMix::audGrainPlayerQuality)loop);
if(engineSound)
{
engineSound->SetRequestedPitch(pitch);
}
if(exhaustSound)
{
exhaustSound->SetRequestedPitch(pitch);
}
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine CalculateDialRPMRatio
// -------------------------------------------------------------------------------
f32 audVehicleEngine::CalculateDialRPMRatio() const
{
if(m_GranularEngineActive)
{
return m_granularEngine.CalculateDialRPMRatio();
}
else
{
return m_EngineLastRevs;
}
}
// -------------------------------------------------------------------------------
// audVehicleEngine UpdateEngineCooling
// -------------------------------------------------------------------------------
void audVehicleEngine::UpdateEngineCooling(audVehicleVariables* UNUSED_PARAM(state))
{
CVehicle* vehicle = m_Parent->GetVehicle();
if(vehicle && m_Parent->GetAudioVehicleType() != AUD_VEHICLE_BOAT)
{
static const f32 engineCooldownTemperatureRange = 20.0f;
const f32 baseTemperature = g_weather.GetTemperature(m_Parent->GetVehicle()->GetTransform().GetPosition());
const bool isRocketCoolingActive = vehicle->HasRocketBoost() && (m_KersSystem.GetKersState() == audVehicleKersSystem::AUD_KERS_STATE_BOOSTING || (fwTimer::GetTimeInMilliseconds() - m_KersSystem.GetLastKersBoostTime()) < g_RocketBoostCooldownTime);
const bool isCoolingFanOn = vehicle->m_nVehicleFlags.bIsCoolingFanOn || isRocketCoolingActive;
const bool shouldEngineCoolingStop = (vehicle->m_nVehicleFlags.bIsDrowning || m_Parent->IsDisabled() || m_Parent->IsInAmphibiousBoatMode() || (!isCoolingFanOn && (vehicle->m_nVehicleFlags.bEngineOn || vehicle->m_EngineTemperature <= m_EngineOffTemperature - engineCooldownTemperatureRange)));
const bool shouldEngineCoolingStart = (!vehicle->m_nVehicleFlags.bIsDrowning && !m_Parent->IsDisabled() && (isCoolingFanOn || (m_EngineState == AUD_ENGINE_OFF && vehicle->m_EngineTemperature > baseTemperature + 50.f)));
f32 engineCoolingProgress = Clamp((engineCooldownTemperatureRange - (m_EngineOffTemperature - vehicle->m_EngineTemperature))/engineCooldownTemperatureRange, 0.0f, 1.0f);
if(isRocketCoolingActive)
{
const f32 rocketCoolingProgress = 1.f - Clamp((fwTimer::GetTimeInMilliseconds() - m_KersSystem.GetLastKersBoostTime()) / (f32)g_RocketBoostCooldownTime, 0.0f, 1.0f);
engineCoolingProgress = Max(engineCoolingProgress, rocketCoolingProgress);
}
if(m_EngineSteamSound)
{
if(m_EngineState == AUD_ENGINE_OFF && engineCoolingProgress == 0.0f)
{
m_Parent->StopAndForgetSounds(m_EngineSteamSound);
}
else
{
f32 freqRatio = m_EngineSteamFluctuator.CalculateValue();
// Computing this manually rather than using the audVehicleVariables value as the steam can persist once the vehicle becomes
// super dummy, at which point we stop calculating the vehicle variables
const f32 engineHealth = m_Parent->ComputeEffectiveEngineHealth();
f32 engineHealthFactor = Clamp(engineHealth / CTransmission::GetEngineHealthMax(), 0.0f, 1.0f);
#if __BANK
if(g_ForceDamage && m_Parent->IsPlayerVehicle())
{
engineHealthFactor = (1.0f - g_ForcedDamageFactor);
}
#endif
f32 engineOffVol = m_EngineState == AUD_ENGINE_ON ? 0.0f : audDriverUtil::ComputeDbVolumeFromLinear(engineCoolingProgress);
f32 drowningVol = audDriverUtil::ComputeDbVolumeFromLinear(1.0f - m_Parent->GetDrowningFactor());
m_EngineSteamSound->SetRequestedVolume((engineHealthFactor * g_audEngineSteamSoundVolRange) + engineOffVol + drowningVol);
m_EngineSteamSound->SetRequestedFrequencyRatio(freqRatio);
}
}
if(m_EngineCoolingSound)
{
if(shouldEngineCoolingStop)
{
m_EngineCoolingSound->StopAndForget();
}
else
{
((audSound*)m_EngineCoolingSound)->FindAndSetVariableValue(g_TemperatureVariableHash, engineCoolingProgress);
}
}
else
{
if(shouldEngineCoolingStart && !shouldEngineCoolingStop)
{
audSoundInitParams initParams;
if(vehicle->m_nVehicleFlags.bIsCoolingFanOn)
{
initParams.AttackTime = 15500;
}
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
m_Parent->CreateSound_PersistentReference(ATSTRINGHASH("HEAT_STRESS_HEAT_TICK_LOOP", 0x578B87D4), &m_EngineCoolingSound, &initParams);
if(m_EngineCoolingSound)
{
m_EngineCoolingSound->SetUpdateEntity(true);
m_EngineCoolingSound->SetClientVariable((u32)AUD_VEHICLE_SOUND_ENGINE);
((audSound*)m_EngineCoolingSound)->FindAndSetVariableValue(g_TemperatureVariableHash, engineCoolingProgress);
m_EngineCoolingSound->PrepareAndPlay();
}
}
}
if(isCoolingFanOn && !m_EngineCoolingFan && !vehicle->m_nVehicleFlags.bIsDrowning && !m_Parent->IsDisabled() && m_VehicleEngineAudioSettings)
{
audSoundInitParams initParams;
initParams.EnvironmentGroup = m_Parent->GetEnvironmentGroup();
initParams.RemoveHierarchy = false;
m_Parent->CreateSound_PersistentReference(m_VehicleEngineAudioSettings->CoolingFan, &m_EngineCoolingFan, &initParams);
if(m_EngineCoolingFan)
{
m_EngineCoolingFan->SetUpdateEntity(true);
m_EngineCoolingFan->SetClientVariable((u32)AUD_VEHICLE_SOUND_ENGINE);
m_EngineCoolingFan->PrepareAndPlay();
}
}
else if((!isCoolingFanOn||vehicle->m_nVehicleFlags.bIsDrowning||m_Parent->IsDisabled()) && m_EngineCoolingFan)
{
m_EngineCoolingFan->StopAndForget();
}
}
}
// -------------------------------------------------------------------------------
// Convert to a dummy vehicle
// -------------------------------------------------------------------------------
void audVehicleEngine::ConvertToDummy()
{
StopEngine(false);
m_KersSystem.StopAllSounds();
}
// -------------------------------------------------------------------------------
// Convert to a disabled vehicle
// -------------------------------------------------------------------------------
void audVehicleEngine::ConvertToDisabled()
{
m_Parent->StopAndForgetSounds(m_EngineCoolingFan, m_EngineSteamSound, m_EngineCoolingSound);
}
// -------------------------------------------------------------------------------
// Called when the engine quality setting changes
// -------------------------------------------------------------------------------
void audVehicleEngine::QualitySettingChanged()
{
bool wasGranularEngineActive = m_GranularEngineActive;
m_QualitySettingChanged = true;
m_GranularEngineActive = false;
audGranularMix::audGrainPlayerQuality engineQuality = GetDesiredEngineQuality();
if(g_GranularEnabled && m_GranularEngineAudioSettings)
{
if((engineQuality == audGranularMix::GrainPlayerQualityHigh && m_GranularEngineAudioSettings->EngineAccel != g_NullSoundHash) ||
(engineQuality == audGranularMix::GrainPlayerQualityLow && m_GranularEngineAudioSettings->NPCEngineAccel != g_NullSoundHash && g_GranularEnableNPCGranularEngines))
{
m_granularEngine.Init(m_Parent, audGranularEngineComponent::ENGINE_SOUND_TYPE_ENGINE, engineQuality);
m_granularExhaust.Init(m_Parent, audGranularEngineComponent::ENGINE_SOUND_TYPE_EXHAUST, engineQuality);
m_GranularEngineActive = true;
m_GranularEngineQuality = engineQuality;
}
}
bool freeWaveSlot = true;
// Switched from non-granular to granular? Just fade out and start over. Shouldn't ever happen in the final game.
if(wasGranularEngineActive && !m_GranularEngineActive)
{
m_engine.Stop(400);
m_exhaust.Stop(400);
m_granularEngine.Stop(400);
m_granularExhaust.Stop(400);
m_EngineState = AUD_ENGINE_OFF;
}
// Switched from granular high quality to granular low quality - trigger the crossfade
else if(wasGranularEngineActive && m_GranularEngineActive)
{
m_granularEngine.QualitySettingChanged();
m_granularExhaust.QualitySettingChanged();
}
// Non-granular to non-granular. No change - just leave everything as-is
else if(!wasGranularEngineActive && !m_GranularEngineActive)
{
m_QualitySettingChanged = false;
freeWaveSlot = false;
}
if(freeWaveSlot)
{
m_Parent->ReapplyDSPEffects();
m_Parent->FreeWaveSlot(false);
}
// Jet whine uses player bank sounds, so need to stop it to switch
m_KersSystem.StopJetWhineSound();
}
// -------------------------------------------------------------------------------
// Get the engine switch fade time
// -------------------------------------------------------------------------------
u32 audVehicleEngine::GetEngineSwitchFadeTime() const
{
if(m_Parent->GetAudioVehicleType() == AUD_VEHICLE_CAR && ((audCarAudioEntity*)m_Parent)->IsSportsCarRevsSoundPlayingOrAboutToPlay())
{
return g_SportsCarRevsEngineSwitchTime;
}
else if(camInterface::IsFadingIn() || camInterface::IsFadedOut())
{
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive() && camInterface::IsFadedOut())
{
return 0;
}
else
#endif
{
return g_ScreenFadedEngineSwitchTime;
}
}
else
{
return (u32)(g_EngineSwitchFadeInTime * m_Parent->GetEngineSwitchFadeTimeScalar());
}
}
// -------------------------------------------------------------------------------
// Convert from a dummy vehicle
// -------------------------------------------------------------------------------
void audVehicleEngine::ConvertFromDummy()
{
CVehicle* vehicle = m_Parent->GetVehicle();
m_ExhaustRattleSmoother.Reset();
m_ExhaustPopSmoother.Reset();
m_FanBeltVolumeSmoother.Reset();
m_ExhaustRattleSmoother.CalculateValue(0.0f);
m_ExhaustPopSmoother.CalculateValue(0.0f);
m_FanBeltVolumeSmoother.CalculateValue(0.0f);
if(vehicle)
{
// If the vehicle's engine is on and we're out of sync
if(vehicle->m_nVehicleFlags.bEngineOn && !m_WasEngineStartingLastFrame && m_EngineState == AUD_ENGINE_OFF && !m_Parent->ShouldForceEngineOff())
{
m_EngineState = AUD_ENGINE_ON;
if(m_GranularEngineActive)
{
if(m_QualitySettingChanged)
{
u32 fadeTime = 400;
REPLAY_ONLY(fadeTime = camInterface::IsFadedOut()? 0 : fadeTime;)
m_granularEngine.SetFadeInRequired(fadeTime);
m_granularExhaust.SetFadeInRequired(fadeTime);
}
else
{
u32 fadeTime = GetEngineSwitchFadeTime();
m_granularEngine.SetFadeInRequired(fadeTime);
m_granularExhaust.SetFadeInRequired(fadeTime);
}
m_granularEngine.Start();
m_granularExhaust.Start();
}
else if(m_VehicleEngineAudioSettings)
{
if(m_QualitySettingChanged)
{
u32 fadeTime = 400;
REPLAY_ONLY(fadeTime = camInterface::IsFadedOut()? 0 : fadeTime;)
m_engine.SetFadeInRequired(fadeTime);
m_exhaust.SetFadeInRequired(fadeTime);
}
else
{
u32 fadeTime = GetEngineSwitchFadeTime();
m_engine.SetFadeInRequired(fadeTime);
m_exhaust.SetFadeInRequired(fadeTime);
}
m_engine.Start();
m_exhaust.Start();
}
}
}
}