Files
GTASource/game/audio/boataudioentity.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

1834 lines
61 KiB
C++

//
// audio/boataudioentity.h
//
// Copyright (C) 1999-2006 Rockstar Games. All Rights Reserved.
//
#include "boataudioentity.h"
#include "frontendaudioentity.h"
#include "grcore/debugdraw.h"
#include "northaudioengine.h"
#include "vehicleaudioentity.h"
#include "peds/ped.h"
#include "vehicles/boat.h"
#include "vehicles/vehicle.h"
#include "vfx/Systems/VfxWater.h"
#include "weapons/Weapon.h"
#include "control/replay/replay.h"
#include "camera/cinematic/CinematicDirector.h"
#include "audiohardware/submix.h"
#include "audiohardware/driverutil.h"
#include "audioengine/engine.h"
#include "audioengine/soundfactory.h"
#include "audiosoundtypes/simplesound.h"
#include "audioeffecttypes/waveshapereffect.h"
#include "Vehicles/Submarine.h"
#include "scriptaudioentity.h"
#include "script/script_hud.h"
#include "debugaudio.h"
AUDIO_VEHICLES_OPTIMISATIONS()
EXT_PF_COUNTER(NumCarEngines);
EXT_PF_VALUE_FLOAT(Revs);
EXT_PF_VALUE_FLOAT(RawRevs);
EXT_PF_VALUE_FLOAT(Throttle);
EXT_PF_VALUE_INT(Gear);
EXT_PF_COUNTER(NumCarEngines);
EXT_PF_COUNTER(NumRealVehicles);
EXT_PF_COUNTER(NumDisabledVehicles);
EXT_PF_COUNTER(NumDummyVehicles);
extern u32 g_MaxGranularEngines;
extern u32 g_MaxVehicleSubmixes;
extern u32 g_MaxDummyVehicles;
extern audCategory *g_IgnitionCategory;
extern audCurve g_RevsSmoothingCurve;
extern bool g_GranularEnableNPCGranularEngines;
extern bool g_ForceGranularNPCEngines;
extern bool g_GranularEnabled;
extern f32 g_RevMaxIncreaseRate;
extern f32 g_RevMaxDecreaseRate;
extern u32 g_EngineStartDuration;
extern u32 g_EngineStopDuration;
extern f32 g_EngineVolumeTrim;
extern f32 g_ExhaustVolumeTrim;
extern u32 g_SuperDummyRadiusSq;
extern f32 g_BaseRollOffScalePlayer;
extern f32 g_BaseRollOffScaleNPC;
extern f32 g_MaxRealRangeScale;
extern f32 g_MaxGranularRangeScale;
extern audCategory *g_PlayerVolumeCategories[5];
extern audCategory *g_PedVolumeCategories[5];
bool g_TreatAllBoatsAsNPCs = false;
bank_float g_FakeRevsOffsetBelow = 0.1f;
bank_float g_FakeRevsOffsetAbove = 0.1f;
bank_float g_FakeRevsMaxHoldTimeMin = 0.0f;
bank_float g_FakeRevsMaxHoldTimeMax = 1.0f;
bank_float g_FakeRevsMinHoldTimeMin = 0.0f;
bank_float g_FakeRevsMinHoldTimeMax = 0.1f;
bank_float g_FakeRevsIncreaseRateBase = 20000;
bank_float g_FakeRevsIncreaseRateScale = 2.0f;
bank_float g_FakeRevsDecreaseRateBase = 20000;
bank_float g_FakeRevsDecreaseRateScale = 2.0f;
bank_float g_FakeOutOfWaterMinTime = 0.2f;
bank_float g_FakeOutOfWaterMaxTime = 1.3f;
bank_float g_FakeInWaterMinTime = 1.5f;
bank_float g_FakeInWaterMaxTime = 6.0f;
float g_SubmersibleUnderwaterCutoff = 300.0f;
f32 g_LowerBoatDynamicImpactThreshold = -10.f;
f32 g_UpperBoatDynamicImpactThreshold = -5.f;
f32 g_BoatActivationDistSq = (90.0f * 90.0f);
u32 g_FakeWaveHitDistanceSq = 900;
f32 g_BoatOffscreenVisiblityScalar = 0.3f;
f32 g_BankSprayPanDistanceScalar = 10.0f;
f32 g_BankSprayForwardDistanceScalar = -4.0f;
#if __BANK
f32 g_VolumeTrims[5];
f32 g_WaterTurbulenceVolumeTrim = 0.0f;
f32 g_BankSprayVolumeTrim = 0.0f;
bool g_DebugDraw = false;
extern bool g_GranularDebugRenderingEnabled;
extern bool g_SimulatePlayerEnteringVehicle;
extern f32 g_OverriddenThrottle;
extern f32 g_OverriddenRevs;
extern bool g_OverrideSim;
#endif
audCurve audBoatAudioEntity::sm_BoatAirTimeToPitch;
audCurve audBoatAudioEntity::sm_BoatAirTimeToVolume;
audCurve audBoatAudioEntity::sm_BoatAirTimeToRevsMultiplier;
audCurve audBoatAudioEntity::sm_BoatAirTimeToResonance;
// ----------------------------------------------------------------
// audBoatAudioEntity constructor
// ----------------------------------------------------------------
audBoatAudioEntity::audBoatAudioEntity() : audVehicleAudioEntity()
{
m_VehicleType = AUD_VEHICLE_BOAT;
m_BankSpray = NULL;
m_WaterDiveSound = NULL;
#if NON_GRANULAR_BOATS_ENABLED
for(u32 loop = 0; loop < BOAT_ENGINE_SOUND_TYPE_MAX; loop++)
{
m_EngineSounds[loop] = NULL;
#if __BANK
g_VolumeTrims[loop] = 0.0f;
#endif
}
#endif
}
// ----------------------------------------------------------------
// audBoatAudioEntity destructor
// ----------------------------------------------------------------
audBoatAudioEntity::~audBoatAudioEntity()
{
ShutdownPrivate();
}
// ----------------------------------------------------------------
// audBoatAudioEntity Shutdown
// ----------------------------------------------------------------
void audBoatAudioEntity::Shutdown()
{
ShutdownPrivate();
audVehicleAudioEntity::Shutdown();
}
// ----------------------------------------------------------------
// audBoatAudioEntity ShutdownPrivate
// ----------------------------------------------------------------
void audBoatAudioEntity::ShutdownPrivate()
{
StopEngineSounds();
}
// ----------------------------------------------------------------
// audBoatAudioEntity InitClass
// ----------------------------------------------------------------
void audBoatAudioEntity::InitClass()
{
sm_BoatAirTimeToPitch.Init(ATSTRINGHASH("BOAT_AIRTIME_TO_PITCH", 0xEB2A07E3));
sm_BoatAirTimeToVolume.Init(ATSTRINGHASH("BOAT_AIRTIME_TO_VOLUME", 0x8475723E));
sm_BoatAirTimeToRevsMultiplier.Init(ATSTRINGHASH("BOAT_AIRTIME_TO_REVS_MULTIPLIER", 0x9D096A5));
sm_BoatAirTimeToResonance.Init(ATSTRINGHASH("BOAT_AIRTIME_TO_RESONANCE", 0x6F5CC0EE));
}
// ----------------------------------------------------------------
// audBoatAudioEntity Reset
// ----------------------------------------------------------------
void audBoatAudioEntity::Reset()
{
audVehicleAudioEntity::Reset();
m_BoatAudioSettings = NULL;
m_GranularEngineSettings = NULL;
m_WasEngineOnLastFrame = false;
m_RollOffSmoother.Init(1.0f, 0.2f);
m_RevsSmoother.Init(0.003f,0.001f,true);
m_ThrottleSmoother.Init(0.005f,0.005f,-1.0f,1.0f);
m_BankSpraySmoother.Init(0.0015f, 0.002f);
m_KeelForce = 0.f;
m_SpeedEvo = 0.f;
m_OutOfWaterRevsMultiplier = 1.0f;
m_ManufacturerHash = m_ModelHash = m_CategoryHash = g_NullSoundHash;
m_ScannerVehicleSettingsHash = 0;
m_WaterTurbulenceOutOfWaterTimer = 0.0f;
m_WaterTurbulenceInWaterTimer = 0.0f;
m_EngineOn = false;
m_WaterSamples.Reset();
m_FakeRevsHoldTime = 0.0f;
m_WasHighFakeRevs = false;
m_FakeRevsSmoother.Init(0.0001f, 0.0001f, 0.0f, 1.0f);
m_FakeOutOfWater = false;
m_FakeOutOfWaterTimer = 0.0f;
}
// ----------------------------------------------------------------
// Init anything boat related
// ----------------------------------------------------------------
bool audBoatAudioEntity::InitVehicleSpecific()
{
m_BoatAudioSettings = GetBoatAudioSettings();
if(m_BoatAudioSettings)
{
#if NON_GRANULAR_BOATS_ENABLED
m_EngineCurves[BOAT_ENGINE_SOUND_TYPE_ENGINE_1].Init(m_BoatAudioSettings->Engine1Vol);
m_EngineCurves[BOAT_ENGINE_SOUND_TYPE_ENGINE_2].Init(m_BoatAudioSettings->Engine2Vol);
m_EngineCurves[BOAT_ENGINE_SOUND_TYPE_LOW_RESO].Init(m_BoatAudioSettings->LowResoLoopVol);
m_EngineCurves[BOAT_ENGINE_SOUND_TYPE_RESO].Init(m_BoatAudioSettings->ResoLoopVol);
m_EnginePitchCurves[BOAT_ENGINE_SOUND_TYPE_ENGINE_1].Init(m_BoatAudioSettings->Engine1Pitch);
m_EnginePitchCurves[BOAT_ENGINE_SOUND_TYPE_ENGINE_2].Init(m_BoatAudioSettings->Engine2Pitch);
m_EnginePitchCurves[BOAT_ENGINE_SOUND_TYPE_LOW_RESO].Init(m_BoatAudioSettings->LowResoPitch);
m_EnginePitchCurves[BOAT_ENGINE_SOUND_TYPE_RESO].Init(m_BoatAudioSettings->ResoPitch);
#endif
m_WaterTurbulenceVolumeCurve.Init(m_BoatAudioSettings->WaterTurbulanceVol);
m_VehicleSpeedToHullSlapVol.Init(m_BoatAudioSettings->IdleHullSlapSpeedToVol);
m_WaterTurbulencePitchCurve.Init(m_BoatAudioSettings->WaterTurbulancePitch);
if(AUD_GET_TRISTATE_VALUE(m_BoatAudioSettings->Flags, FLAG_ID_BOATAUDIOSETTINGS_ISSUBMARINE) == AUD_TRISTATE_TRUE)
{
InitSubmersible(m_BoatAudioSettings);
}
const u32 modelNameHash = GetVehicleModelNameHash();
if (modelNameHash == ATSTRINGHASH("Kosatka", 0x4FAF0D70))
{
m_HasMissileLockWarningSystem = true;
}
m_GranularEngineSettings = audNorthAudioEngine::GetObject<GranularEngineAudioSettings>(m_BoatAudioSettings->GranularEngine);
#if !NON_GRANULAR_BOATS_ENABLED
if (modelNameHash != ATSTRINGHASH("Kosatka", 0x4FAF0D70))
{
audAssertf(m_GranularEngineSettings, "Non granular boats have been disabled, but boat %s does not have a granular engine!", GetVehicleModelName());
}
#endif
#if NA_RADIO_ENABLED
m_AmbientRadioDisabled = (AUD_GET_TRISTATE_VALUE(m_BoatAudioSettings->Flags, FLAG_ID_BOATAUDIOSETTINGS_DISABLEAMBIENTRADIO) == AUD_TRISTATE_TRUE);
m_RadioType = (RadioType)m_BoatAudioSettings->RadioType;
m_RadioGenre = (RadioGenre)m_BoatAudioSettings->RadioGenre;
#endif
m_VehicleEngine.Init(this);
SetEnvironmentGroupSettings(true, audNorthAudioEngine::GetOcclusionManager()->GetMaxOcclusionFullPathDepth());
SetupVolumeCones();
return true;
}
return false;
}
// ----------------------------------------------------------------
// Check if this boat is using a granular engine
// ----------------------------------------------------------------
bool audBoatAudioEntity::IsUsingGranularEngine()
{
return m_VehicleEngine.IsGranularEngineActive();
}
// ----------------------------------------------------------------
// Get the idle hull slap volume
// ----------------------------------------------------------------
f32 audBoatAudioEntity::GetIdleHullSlapVolumeLinear(f32 speed) const
{
return m_VehicleSpeedToHullSlapVol.CalculateValue(speed);
}
// ----------------------------------------------------------------
// Get the boat horn sound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetVehicleHornSoundHash(bool UNUSED_PARAM(ignoreMods))
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->HornLoop;
}
else
{
m_BoatAudioSettings = GetBoatAudioSettings();
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->HornLoop;
}
}
return 0;
}
// ----------------------------------------------------------------
// Get the granular engine settings
// ----------------------------------------------------------------
GranularEngineAudioSettings* audBoatAudioEntity::GetGranularEngineAudioSettings() const
{
return m_GranularEngineSettings;
}
// ----------------------------------------------------------------
// Get the switch time scalar
// ----------------------------------------------------------------
f32 audBoatAudioEntity::GetEngineSwitchFadeTimeScalar() const
{
if(!m_IsPlayerVehicle)
{
// If we're low on vehicles, we can be a bit more lazy with our fade time - the lack of vehicles makes the sharper
// cuts in/out more obvious, so this improves the sound of the transitions
if(m_Vehicle->GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE &&
m_Vehicle->GetOwnedBy() != ENTITY_OWNEDBY_SCRIPT)
{
if(sm_ActivationRangeScale == g_MaxRealRangeScale &&
(!m_VehicleEngine.IsGranularEngineActive() || sm_GranularActivationRangeScale == g_MaxGranularRangeScale))
{
return 2.0f;
}
}
}
return 1.0f;
}
// ----------------------------------------------------------------
// Populate boat variables
// ----------------------------------------------------------------
void audBoatAudioEntity::PopulateBoatAudioVariables(audVehicleVariables* state)
{
naAssertf(m_Vehicle, "m_vehicle must be valid, about to access a null ptr...");
state->gear = m_Vehicle->m_Transmission.GetGear();
state->gasPedal = (m_Vehicle->m_nVehicleFlags.bEngineOn || m_Vehicle->m_nVehicleFlags.bEngineStarting) ? Abs(m_Vehicle->m_Transmission.GetThrottle()) : 0.0f;
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
state->rawRevs = m_ReplayRevs;
}
else
#endif
{
if(m_Vehicle->GetDriver() && (!m_Vehicle->GetDriver()->IsPlayer() || g_TreatAllBoatsAsNPCs) && !IsSubmersible() && m_Vehicle->m_nVehicleFlags.bEngineOn)
{
// AI controlled boat - fake the revs based on fwd speed
if(!m_Vehicle->GetIsJetSki() || state->fwdSpeed < 2.5f || m_Vehicle->m_nVehicleFlags.bIsRacing)
{
state->rawRevs = Clamp(state->fwdSpeed * 0.1f, 0.2f, 0.85f);
m_FakeOutOfWaterTimer = 0.0f;
m_FakeOutOfWater = false;
}
else
{
f32 fakeRevs = Clamp(state->fwdSpeed * 0.14f, 0.2f, 0.7f);
f32 baseFakeRevs = Max(fakeRevs - audEngineUtil::GetRandomNumberInRange(g_FakeRevsOffsetBelow * 0.5f, g_FakeRevsOffsetBelow), 0.2f);
f32 maxFakeRevs = Min(fakeRevs + audEngineUtil::GetRandomNumberInRange(g_FakeRevsOffsetAbove * 0.5f, g_FakeRevsOffsetAbove), 0.8f);
f32 fakeRevsTarget = m_WasHighFakeRevs? maxFakeRevs : baseFakeRevs;
m_FakeRevsSmoother.SetBounds(baseFakeRevs, maxFakeRevs);
state->rawRevs = m_FakeRevsSmoother.CalculateValue(fakeRevsTarget, state->timeInMs);
if(m_WasHighFakeRevs)
{
state->gasPedal = 1.0f;
}
else
{
state->gasPedal = 0.0f;
}
if(state->rawRevs == fakeRevsTarget)
{
m_FakeRevsHoldTime -= fwTimer::GetTimeStep();
if(m_FakeRevsHoldTime < 0.0f)
{
if(m_WasHighFakeRevs)
{
m_WasHighFakeRevs = false;
m_FakeRevsHoldTime = audEngineUtil::GetRandomNumberInRange(g_FakeRevsMinHoldTimeMin, g_FakeRevsMinHoldTimeMax);
}
else
{
m_WasHighFakeRevs = true;
m_FakeRevsHoldTime = audEngineUtil::GetRandomNumberInRange(g_FakeRevsMaxHoldTimeMin, g_FakeRevsMaxHoldTimeMax);
}
m_FakeRevsSmoother.SetRates((1.0f/g_FakeRevsIncreaseRateBase) * audEngineUtil::GetRandomNumberInRange(1.0f, g_FakeRevsIncreaseRateScale), (1.0f/g_FakeRevsDecreaseRateBase) * audEngineUtil::GetRandomNumberInRange(1.0f, g_FakeRevsDecreaseRateScale));
}
}
if(state->fwdSpeed > 5.0f && m_DistanceFromListener2LastFrame > g_FakeWaveHitDistanceSq)
{
m_FakeOutOfWaterTimer -= fwTimer::GetTimeStep();
if(m_FakeOutOfWaterTimer < 0.0f)
{
m_FakeOutOfWater = !m_FakeOutOfWater;
if(m_FakeOutOfWater)
{
m_FakeOutOfWaterTimer = audEngineUtil::GetRandomNumberInRange(g_FakeOutOfWaterMinTime, g_FakeOutOfWaterMaxTime);
}
else
{
m_FakeOutOfWaterTimer = audEngineUtil::GetRandomNumberInRange(g_FakeInWaterMinTime, g_FakeInWaterMaxTime);
}
}
}
else
{
m_FakeOutOfWaterTimer = 0.0f;
m_FakeOutOfWater = false;
}
}
}
else
{
state->rawRevs = F32_VerifyFinite(m_Vehicle->m_Transmission.GetRevRatio());
m_FakeOutOfWaterTimer = 0.0f;
m_FakeOutOfWater = false;
}
if(m_Vehicle->GetDriver())
{
f32 filterFactor = 0.2f;
f32 desiredRevsMultiplier = 1.0f;
if(m_OutOfWaterTimer > 0.0f)
{
desiredRevsMultiplier = sm_BoatAirTimeToRevsMultiplier.CalculateValue(m_OutOfWaterTimer);
desiredRevsMultiplier *= Min(state->fwdSpeed * 0.5f, 1.0f);
}
m_OutOfWaterRevsMultiplier = (desiredRevsMultiplier * filterFactor) + (m_OutOfWaterRevsMultiplier * (1.0f - filterFactor));
state->rawRevs *= m_OutOfWaterRevsMultiplier;
}
REPLAY_ONLY(m_ReplayRevs = state->rawRevs;)
}
state->granularEnginePitchOffset = static_cast<s32>(sm_BoatAirTimeToPitch.CalculateValue(m_OutOfWaterTimer));
state->engineConeAtten = m_GranularEngineSettings ? m_EngineVolumeCone.ComputeAttenuation(m_Vehicle->GetMatrix()) : 0.f;
state->exhaustConeAtten = m_GranularEngineSettings ? m_ExhaustVolumeCone.ComputeAttenuation(m_Vehicle->GetMatrix()) : 0.f;
f32 entityVariableThrottle = 0.0f;
f32 entityVariableRevs = 0.0f;
f32 fakeEngineFactor = 0.0f;
if(HasEntityVariableBlock())
{
entityVariableThrottle = GetEntityVariableValue(ATSTRINGHASH("fakethrottle", 0xEB27990));
entityVariableRevs = GetEntityVariableValue(ATSTRINGHASH("fakerevs", 0xCEB98BEB));
// Reduce this each frame so that that the behaviour gets canceled even if a sound forgets to do so
fakeEngineFactor = GetEntityVariableValue(ATSTRINGHASH("usefakeengine", 0x91DF7F97));
SetEntityVariable(ATSTRINGHASH("usefakeengine", 0x91DF7F97), Clamp(fakeEngineFactor - 0.1f, 0.0f, 1.0f));
}
if(fakeEngineFactor > 0.1f)
{
state->gasPedal = entityVariableThrottle;
state->rawRevs = entityVariableRevs;
}
// Reversing flag (not the same as being in reverse gear) can be used to determine whether the vehicle is
// intentionally moving backwards, as opposed to going backwards because it did a big drift/spin
if(state->gear == 0 && state->fwdSpeed < -0.1f)
{
m_IsReversing = true;
}
else if(state->fwdSpeed >= -0.1f)
{
m_IsReversing = false;
}
if(m_IsReversing)
{
state->rawRevs = Clamp(state->rawRevs, 0.0f, 0.8f);
}
#if __BANK
if(g_OverrideSim && ((m_Vehicle == g_AudioDebugEntity) || (!g_AudioDebugEntity && m_IsPlayerVehicle)))
{
state->gasPedal = g_OverriddenThrottle;
state->rawRevs = g_OverriddenRevs;
}
#endif
if(m_IsPlayerVehicle)
{
m_ThrottleSmoother.SetRates(0.005f,0.005f);
}
else
{
m_ThrottleSmoother.SetRates(0.0025f,0.0025f);
}
if(m_VehicleEngine.GetState() == audVehicleEngine::AUD_ENGINE_STARTING)
{
// GTA 5 DLC fix - fake startup revs don't sound great on certain boats. Should be made
// into a gameobject PTS/fakeThrottle sound (as with cars) on future projects.
bool skipStartupRevs = false;
const u32 modelNameHash = GetVehicleModelNameHash();
if(modelNameHash == ATSTRINGHASH("TORO", 0x3FD5AA2F) || modelNameHash == ATSTRINGHASH("TORO2",0x362CAC6D))
{
skipStartupRevs = true;
}
if(!skipStartupRevs)
{
// if the player starts acelerating then interrupt starting behaviour
if((m_Vehicle->GetThrottle()>0.1f || state->fwdSpeed<-0.1f) && m_IsPlayerVehicle)
{
// force the throttle to be 0 so it'll smooth up
m_ThrottleSmoother.Reset();
m_ThrottleSmoother.CalculateValue(0.0f, state->timeInMs);
}
else
{
// seriously smooth revs if we're starting the engine
m_RevsSmoother.SetRates(1.5f / 1000.f,0.45f / 1000.f);
u32 engineOnDuration = fwTimer::GetTimeInMilliseconds() - m_EngineStartTime;
if(engineOnDuration < 5)
{
state->rawRevs = 0.0f;
}
else if(engineOnDuration > (u32)(g_EngineStartDuration/3.0f))
{
state->rawRevs = CTransmission::ms_fIdleRevs;
}
else
{
state->rawRevs = 0.75f;
}
}
}
}
else if(m_VehicleEngine.GetState() == audVehicleEngine::AUD_ENGINE_STOPPING)
{
// seriously smooth revs if we're stopping the engine
m_RevsSmoother.SetRates(0.3f / 1000.f, 0.3f / 1000.f);
state->rawRevs = 0.0f;
}
else
{
f32 revsSmoothScalar = g_RevsSmoothingCurve.CalculateValue(state->rawRevs);
// Prevent divide by zero
if(revsSmoothScalar < 1.0f)
{
revsSmoothScalar = 1.0f;
}
if(state->gear == 0 && IsReversing())
{
m_RevsSmoother.SetRates(0.0002f, g_RevMaxDecreaseRate / (1000.f * revsSmoothScalar));
}
else
{
m_RevsSmoother.SetRates(g_RevMaxIncreaseRate / 1000.f, g_RevMaxDecreaseRate / (1000.f * revsSmoothScalar));
}
}
state->clutchRatio = m_Vehicle->m_Transmission.GetClutchRatio();
// If the engine is off, revert to clutch pressed
if(!m_Vehicle->IsEngineOn())
{
state->clutchRatio = 0.1f;
}
const f32 engineHealth = ComputeEffectiveEngineHealth();
state->engineDamageFactor = 1.0f - engineHealth / CTransmission::GetEngineHealthMax();
state->throttle = m_ThrottleSmoother.CalculateValue(state->gasPedal, state->timeInMs);
state->throttleInput = m_Vehicle->GetThrottle();
state->onRevLimiter = false;
state->revs = F32_VerifyFinite(m_RevsSmoother.CalculateValue(state->rawRevs, state->timeInMs));
if(m_VehicleEngine.GetState() != audVehicleEngine::AUD_ENGINE_STARTING && m_VehicleEngine.GetState() != audVehicleEngine::AUD_ENGINE_STOPPING)
{
// clamp revs to idle minimum
state->revs = Max(CTransmission::ms_fIdleRevs,state->revs);
}
if(m_VehicleEngine.IsGranularEngineActive())
{
state->carVolOffset = (m_GranularEngineSettings->MasterVolume/100.f);
}
else
{
state->carVolOffset = 0.0f;
}
f32 enginePostSubmixVol = state->carVolOffset;
f32 exhaustPostSubmixVol = state->carVolOffset;
// In practice rev ratio always lies between 0.2 and 1.0, so scale up to a 0-1 range
f32 revRatio = ((state->revs - 0.2f) * 1.25f);
// Similarly, clutch ratio actually goes from 0.1 to 1.0, so scale up to a 0-1 range
f32 clutchRatio = ((state->clutchRatio - 0.1f) * 1.11f);
if(m_VehicleEngine.IsGranularEngineActive())
{
enginePostSubmixVol += (m_GranularEngineSettings->EngineVolume_PostSubmix * 0.01f);
exhaustPostSubmixVol += (m_GranularEngineSettings->ExhaustVolume_PostSubmix * 0.01f);
enginePostSubmixVol += revRatio * (m_GranularEngineSettings->EngineRevsVolume_PostSubmix * 0.01f);
enginePostSubmixVol += state->throttle * (m_GranularEngineSettings->EngineThrottleVolume_PostSubmix * 0.01f);
enginePostSubmixVol += (m_GranularEngineSettings->EngineClutchAttenuation_PostSubmix * 0.01f) * (1.0f - clutchRatio);
exhaustPostSubmixVol += revRatio * (m_GranularEngineSettings->ExhaustRevsVolume_PostSubmix * 0.01f );
exhaustPostSubmixVol += state->throttle * (m_GranularEngineSettings->ExhaustThrottleVolume_PostSubmix * 0.01f);
exhaustPostSubmixVol += (m_GranularEngineSettings->ExhaustClutchAttenuation_PostSubmix * 0.01f) * (1.0f - clutchRatio);
f32 engineIdleBoostLin = Lerp(m_VehicleEngine.GetGranularEngine()->GetIdleVolumeScale(), 1.0f, audDriverUtil::ComputeLinearVolumeFromDb((m_GranularEngineSettings->EngineIdleVolume_PostSubmix/100.0f)));
f32 exhaustIdleBoostLin = Lerp(m_VehicleEngine.GetGranularEngine()->GetIdleVolumeScale(), 1.0f, audDriverUtil::ComputeLinearVolumeFromDb((m_GranularEngineSettings->ExhaustIdleVolume_PostSubmix/100.0f)));
enginePostSubmixVol += audDriverUtil::ComputeDbVolumeFromLinear(engineIdleBoostLin);
exhaustPostSubmixVol += audDriverUtil::ComputeDbVolumeFromLinear(exhaustIdleBoostLin);
}
else
{
enginePostSubmixVol = m_BoatAudioSettings->EngineVolPostSubmix;
exhaustPostSubmixVol = m_BoatAudioSettings->ExhaustVolPostSubmix;
}
if(m_IsPlayerVehicle)
{
const f32 airtimeVolumeBoost = sm_BoatAirTimeToVolume.CalculateValue(m_OutOfWaterTimer);
enginePostSubmixVol += airtimeVolumeBoost;
exhaustPostSubmixVol += airtimeVolumeBoost;
}
state->granularEnginePitchOffset = static_cast<s32>(sm_BoatAirTimeToPitch.CalculateValue(m_OutOfWaterTimer));
state->enginePostSubmixVol = enginePostSubmixVol + state->engineConeAtten;
state->exhaustPostSubmixVol = exhaustPostSubmixVol + state->exhaustConeAtten;
#if __ASSERT
audAssertf(state->enginePostSubmixVol <= 24.0f, "Vehicle %s has an invalid engine post submix volume (%f)", GetVehicleModelName(), state->enginePostSubmixVol);
audAssertf(state->exhaustPostSubmixVol <= 24.0f, "Vehicle %s has an invalid exhaust post submix volume (%f)", GetVehicleModelName(), state->exhaustPostSubmixVol);
#endif
state->enginePostSubmixVol += g_EngineVolumeTrim;
state->exhaustPostSubmixVol += g_ExhaustVolumeTrim;
f32 rollOff = m_IsPlayerVehicle? m_BoatAudioSettings->MaxRollOffScalePlayer : m_BoatAudioSettings->MaxRollOffScaleNPC;
f32 desiredRollOff = m_IsPlayerVehicle? g_BaseRollOffScalePlayer : g_BaseRollOffScaleNPC + (rollOff * Clamp(revRatio * revRatio * revRatio, 0.0f, 1.0f));
state->rollOffScale = m_RollOffSmoother.CalculateValue(desiredRollOff);
#if __BANK
if(m_Vehicle->GetStatus() == STATUS_PLAYER)
{
PF_SET(Revs, state->revs);
PF_SET(RawRevs, state->rawRevs);
PF_SET(Throttle, state->throttle);
PF_SET(Gear, state->gear);
}
#endif
}
// ----------------------------------------------------------------
// Engine Started
// ----------------------------------------------------------------
void audBoatAudioEntity::EngineStarted()
{
m_EngineStartTime = fwTimer::GetTimeInMilliseconds();
m_RevsSmoother.CalculateValue(CTransmission::ms_fIdleRevs,m_EngineStartTime);// init revs to idle
m_EngineEffectWetSmoother.Reset();
m_EngineEffectWetSmoother.CalculateValue(0.0f, fwTimer::GetTimeStep());
}
// ----------------------------------------------------------------
// Player entered/exited the vehicles
// ----------------------------------------------------------------
void audBoatAudioEntity::OnFocusVehicleChanged()
{
if(g_GranularEnabled &&
m_VehicleEngine.HasBeenInitialised() &&
m_VehicleEngine.GetState() != audVehicleEngine::AUD_ENGINE_STOPPING)
{
m_VehicleEngine.QualitySettingChanged();
}
}
// -------------------------------------------------------------------------------
// audBoatAudioEntity::AcquireWaveSlot
// -------------------------------------------------------------------------------
bool audBoatAudioEntity::AcquireWaveSlot()
{
// We want to load sfx slot on Kosatka even if not the focus vehicle, see url:bugstar:6767607
if(IsSubmersible() && (m_IsFocusVehicle || GetVehicleModelNameHash() == ATSTRINGHASH("KOSATKA", 0x4FAF0D70)) && m_BoatAudioSettings->SFXBankSound != 0u && m_BoatAudioSettings->SFXBankSound != g_NullSoundHash)
{
m_RequiresSFXBank = true;
RequestSFXWaveSlot(m_BoatAudioSettings->SFXBankSound);
if(!m_SFXWaveSlot)
{
return false;
}
}
else
{
m_RequiresSFXBank = false;
}
if(m_VehicleEngine.IsGranularEngineActive())
{
if(m_VehicleEngine.IsLowQualityGranular())
{
RequestWaveSlot(&sm_StandardWaveSlotManager, m_GranularEngineSettings->NPCEngineAccel);
}
else
{
RequestWaveSlot(&sm_HighQualityWaveSlotManager, m_GranularEngineSettings->EngineAccel);
}
}
else
{
RequestWaveSlot(&sm_StandardWaveSlotManager, m_BoatAudioSettings->Engine1Loop);
}
if(m_RequiresSFXBank)
{
// If we didn't manage to acquire a primary wave slot, release the SFX slot so we don't hog it
if(m_SFXWaveSlot && !m_EngineWaveSlot)
{
audWaveSlotManager::FreeWaveSlot(m_SFXWaveSlot, this);
}
return m_SFXWaveSlot != NULL && m_EngineWaveSlot != NULL;
}
else
{
return m_EngineWaveSlot != NULL;
}
}
// -------------------------------------------------------------------------------
// Convert the boat from dummy
// -------------------------------------------------------------------------------
void audBoatAudioEntity::ConvertFromDummy()
{
if(m_VehicleEngine.IsGranularEngineActive())
{
m_VehicleEngine.ConvertFromDummy();
}
}
// ----------------------------------------------------------------
// Convert to super dummy
// ----------------------------------------------------------------
void audBoatAudioEntity::ConvertToSuperDummy()
{
m_VehicleEngine.ConvertToDummy();
StopEngineSounds(true);
}
// ----------------------------------------------------------------
// Convert to disabled
// ----------------------------------------------------------------
void audBoatAudioEntity::ConvertToDisabled()
{
ConvertToSuperDummy();
StopAndForgetSounds(m_BankSpray, m_SubTurningSweetener, m_SubExtrasSound, m_SubmersibleCreakSound);
}
// ----------------------------------------------------------------
// Get the desired boat LOD
// ----------------------------------------------------------------
audVehicleLOD audBoatAudioEntity::GetDesiredLOD(f32 fwdSpeedRatio, u32 originalDistFromListenerSq, bool visibleBySniper)
{
// No driver, no revs, disabled? Probably just sat stationary so keep it disabled
// Kosatka is an exception, see url:bugstar:6767607
if(!m_Vehicle->GetDriver() && (!m_Vehicle->m_nVehicleFlags.bEngineOn && m_VehicleEngine.GetState() == audVehicleEngine::AUD_ENGINE_OFF) && !m_IsFocusVehicle && GetVehicleModelNameHash() != ATSTRINGHASH("KOSATKA", 0x4FAF0D70))
{
return AUD_VEHICLE_LOD_DISABLED;
}
else if(m_IsFocusVehicle)
{
return AUD_VEHICLE_LOD_REAL;
}
else
{
f32 desiredVisibilityScalar = 1.0f;
spdSphere boundSphere;
m_Vehicle->GetBoundSphere(boundSphere);
if(!camInterface::IsSphereVisibleInGameViewport(boundSphere.GetV4()))
{
// We're not maxing out the submixes, allow vehicles to stay audible even after going off screen
if(sm_ActivationRangeScale == g_MaxRealRangeScale &&
(!m_VehicleEngine.IsGranularEngineActive() || sm_GranularActivationRangeScale == g_MaxGranularRangeScale))
{
desiredVisibilityScalar = Max(g_BoatOffscreenVisiblityScalar, m_VisibilitySmoother.GetLastValue());
}
else
{
desiredVisibilityScalar = g_BoatOffscreenVisiblityScalar;
}
}
f32 visibilityScalar = m_VisibilitySmoother.CalculateValue(desiredVisibilityScalar, fwTimer::GetTimeStep());
Vec3V vehiclePosition = m_Vehicle->GetTransform().GetPosition();
Vec3V listenerPosition = g_AudioEngine.GetEnvironment().GetVolumeListenerPosition(audNorthAudioEngine::GetMicrophones().IsTinyRacersMicrophoneActive() ? 1 : 0);
f32 vehicleActivationRangeScale = 1.f;
if (GetVehicleModelNameHash() == ATSTRINGHASH("Kosatka", 0x4FAF0D70))
{
vehiclePosition = ComputeClosestPositionOnVehicleYAxis();
vehicleActivationRangeScale = 2.f;
}
Vector3 distToListener = VEC3V_TO_VECTOR3(vehiclePosition - listenerPosition);
// Artificially increase height offset to give preference to vehicles on the same plane as the listener
distToListener.SetZ(distToListener.GetZ() * 2.0f);
f32 mag2 = visibleBySniper? originalDistFromListenerSq : distToListener.Mag2();
f32 speedRatioMult = Clamp(fwdSpeedRatio/0.5f, 0.0f, 1.0f);
f32 granularActivationRangeScale = m_VehicleEngine.IsGranularEngineActive() ? sm_GranularActivationRangeScale : 1.0f;
// Active vehicles primarily take speed into account, but also a bit of distance - we still want to hear the idle on very close stationary ones
if(mag2 < (g_BoatActivationDistSq * sm_ActivationRangeScale * vehicleActivationRangeScale * granularActivationRangeScale * visibilityScalar * (0.6f + (0.4f * speedRatioMult))))
{
return AUD_VEHICLE_LOD_REAL;
}
else
{
return AUD_VEHICLE_LOD_DISABLED;
}
}
}
// ----------------------------------------------------------------
// Set up boat volume cones
// ----------------------------------------------------------------
void audBoatAudioEntity::SetupVolumeCones()
{
Vec3V engineConeDir = Vec3V(0.0f, 1.0f, 0.0f);
Vec3V exhaustConeDir = Vec3V(0.0f, -1.0f, 0.0f);
bool useExhaustCone = true;
const Vec3V exPos = m_Vehicle->TransformIntoWorldSpace(m_ExhaustOffsetPos);
const Vec3V vVehiclePosition = m_Vehicle->GetTransform().GetPosition();
Vec3V ex2centre = vVehiclePosition - exPos;
ScalarV dp = Dot(ex2centre, m_Vehicle->GetTransform().GetB());
if(IsTrue(IsLessThanOrEqual(dp, ScalarV(V_ZERO))))
{
useExhaustCone = false;
}
Vec3V temp = vVehiclePosition - m_Vehicle->TransformIntoWorldSpace(m_EngineOffsetPos);
dp = Dot(temp, m_Vehicle->GetTransform().GetB());
if(IsTrue(IsGreaterThan(dp, ScalarV(V_ZERO))))
{
// rear
engineConeDir = exhaustConeDir;
}
if(m_GranularEngineSettings)
{
m_EngineVolumeCone.Init(engineConeDir, m_GranularEngineSettings->EngineMaxConeAttenuation/100.f, 60.f,165.f);
m_ExhaustVolumeCone.Init(exhaustConeDir, (useExhaustCone?m_GranularEngineSettings->ExhaustMaxConeAttenuation/100.f:0.f), 45.f, 160.f);
}
}
// ----------------------------------------------------------------
// Is this boat a submersible
// ----------------------------------------------------------------
bool audBoatAudioEntity::IsSubmersible() const
{
if(m_BoatAudioSettings)
{
return AUD_GET_TRISTATE_VALUE(m_BoatAudioSettings->Flags, FLAG_ID_BOATAUDIOSETTINGS_ISSUBMARINE) == AUD_TRISTATE_TRUE;
}
else
{
return false;
}
}
// ----------------------------------------------------------------
// Update anything boat related
// ----------------------------------------------------------------
void audBoatAudioEntity::UpdateVehicleSpecific(audVehicleVariables& vehicleVariables, audVehicleDSPSettings& dspSettings)
{
f32 boatSprayAngle = 0.0f;
const u32 timeInMs = fwTimer::GetTimeInMilliseconds();
if(m_Vehicle->GetAudioEnvironmentGroup())
{
SetEnvironmentGroupSettings(true, audNorthAudioEngine::GetOcclusionManager()->GetMaxOcclusionFullPathDepth());
if(m_Vehicle->PopTypeIsMission())
{
m_Vehicle->GetAudioEnvironmentGroup()->GetEnvironmentGameMetric().SetRolloffFactor(3.f);
}
else
{
m_Vehicle->GetAudioEnvironmentGroup()->GetEnvironmentGameMetric().SetRolloffFactor(1.f);
}
}
if(m_BoatAudioSettings)
{
if(!IsDisabled())
{
bool isInWater = CalculateIsInWater();
if(m_FakeOutOfWater)
{
isInWater = false;
}
if(IsReal())
{
PopulateBoatAudioVariables(&vehicleVariables);
if(m_VehicleEngine.IsGranularEngineActive())
{
UpdateEngineGranular(vehicleVariables, timeInMs);
}
#if NON_GRANULAR_BOATS_ENABLED
else if(m_EngineWaveSlot)
{
UpdateEngine(vehicleVariables, timeInMs);
}
#endif
dspSettings.rolloffScale = vehicleVariables.rollOffScale;
dspSettings.enginePostSubmixAttenuation = vehicleVariables.enginePostSubmixVol;
dspSettings.exhaustPostSubmixAttenuation = vehicleVariables.exhaustPostSubmixVol;
dspSettings.AddDSPParameter(atHashString("RPM", 0x5B924509), vehicleVariables.revs);
dspSettings.AddDSPParameter(atHashString("Throttle", 0xEA0151DC), vehicleVariables.throttle);
dspSettings.AddDSPParameter(atHashString("ThrottleInput", 0x918028C4), vehicleVariables.throttleInput);
AddCommonDSPParameters(dspSettings);
if(sm_BoatAirTimeToResonance.IsValid())
{
dspSettings.AddDSPParameter(atHashString("AirtimeResonance", 0x6CECA0B1), sm_BoatAirTimeToResonance.CalculateValue(m_OutOfWaterTimer));
}
}
if(IsReal() || IsDummy())
{
UpdateNonEngine(vehicleVariables, timeInMs);
}
f32 speedFactor = 0.0f;
f32 inWaterFactor = 0.0f;
if(IsSubmersible())
{
UpdateSubmersible(vehicleVariables, m_BoatAudioSettings);
}
speedFactor = CalculateWaterSpeedFactor();
if(m_Vehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR)
{
inWaterFactor = !m_Vehicle->GetIsInWater()?0.f:1.f;
}
else
{
inWaterFactor = (!((CBoat*)m_Vehicle)->m_BoatHandling.IsInWater()?0.f:1.f);
}
inWaterFactor = m_Vehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR ? 1.0f : (!((CBoat*)m_Vehicle)->m_BoatHandling.IsInWater()?0.f:1.f);
if(m_IsFocusVehicle)
{
const f32 bankVol = m_BankSpraySmoother.CalculateValue(inWaterFactor, fwTimer::GetTimeInMilliseconds());
if(bankVol > g_SilenceVolumeLin)
{
if(!m_BankSpray)
{
audSoundInitParams initParams;
initParams.UpdateEntity = true;
initParams.EnvironmentGroup = m_Vehicle->GetAudioEnvironmentGroup();
CreateSound_PersistentReference(m_BoatAudioSettings->BankSpraySound, &m_BankSpray, &initParams);
if(m_BankSpray)
{
m_BankSpray->PrepareAndPlay();
}
}
if(m_BankSpray)
{
m_BankSpray->SetRequestedVolume(audDriverUtil::ComputeDbVolumeFromLinear(bankVol) BANK_ONLY(+ g_BankSprayVolumeTrim));
Vector3 boatRight = VEC3V_TO_VECTOR3(m_Vehicle->GetTransform().GetA());
Vector3 boatForward = VEC3V_TO_VECTOR3(m_Vehicle->GetTransform().GetB());
boatSprayAngle = DotProduct(boatRight, g_UnitUp);
m_BankSpray->FindAndSetVariableValue(ATSTRINGHASH("angle", 0xF3805B5), Abs(boatSprayAngle));
boatRight.SetZ(0.0f);
boatForward.SetZ(0.0f);
// Pan the sound left and right as the angle changes
m_BankSpray->SetRequestedPosition(VEC3V_TO_VECTOR3(m_Vehicle->GetTransform().GetPosition()) + (boatForward * g_BankSprayForwardDistanceScalar * ((vehicleVariables.isInFirstPersonCam || vehicleVariables.isInHoodMountedCam) ? 0.0f : 1.0f)) + (boatRight * g_BankSprayPanDistanceScalar * Clamp(boatSprayAngle * 2, -1.0f, 1.0f)));
}
}
else if(m_BankSpray)
{
m_BankSpray->StopAndForget();
}
}
else if(m_BankSpray)
{
m_BankSpray->StopAndForget();
}
#if __BANK
if(m_IsPlayerVehicle)
{
if(g_SimulatePlayerEnteringVehicle)
{
OnFocusVehicleChanged();
g_SimulatePlayerEnteringVehicle = false;
}
if(g_DebugDraw)
{
char tempString[128];
sprintf(tempString, "In Water Factor: %.02f", inWaterFactor);
grcDebugDraw::Text(Vector2(0.1f, 0.11f), Color32(0,0,255), tempString );
sprintf(tempString, "Speed Factor: %.02f", speedFactor);
grcDebugDraw::Text(Vector2(0.1f, 0.13f), Color32(0,0,255), tempString );
sprintf(tempString, "Spray Angle: %.02f", Abs(boatSprayAngle));
grcDebugDraw::Text(Vector2(0.1f, 0.15f), Color32(0,0,255), tempString );
sprintf(tempString, "Out of Water Timer: %.02f", m_OutOfWaterTimer);
grcDebugDraw::Text(Vector2(0.1f, 0.17f), Color32(0,0,255), tempString );
sprintf(tempString, "In Water Timer: %.02f", m_InWaterTimer);
grcDebugDraw::Text(Vector2(0.1f, 0.19f), Color32(0,0,255), tempString );
sprintf(tempString, "Raw Revs: %.02f", vehicleVariables.rawRevs);
grcDebugDraw::Text(Vector2(0.1f, 0.23f), Color32(0,0,255), tempString );
sprintf(tempString, "Smoothed Revs: %.02f", vehicleVariables.revs);
grcDebugDraw::Text(Vector2(0.1f, 0.25f), Color32(0,0,255), tempString );
sprintf(tempString, "Velocity Sq: %.02f", m_CachedVehicleVelocity.Mag2());
grcDebugDraw::Text(Vector2(0.1f, 0.27f), Color32(0,0,255), tempString );
if(m_BankSpray)
{
grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(m_BankSpray->GetRequestedPosition()), 1.f, Color32(255, 0, 0, (int)(255 * audDriverUtil::ComputeLinearVolumeFromDb(m_BankSpray->GetRequestedVolume()))));
}
if(IsSubmersible())
{
sprintf(tempString, "Angular Velocity: %.02f", m_SubAngularVelocityMag);
grcDebugDraw::Text(Vector2(0.1f, 0.29f), Color32(0,0,255), tempString );
sprintf(tempString, "In Water: %s", m_Vehicle->GetIsInWater() ? "true" : "false");
grcDebugDraw::Text(Vector2(0.1f, 0.31f), Color32(0, 0, 255), tempString);
}
}
}
#endif
}
else
{
m_WaterTurbulenceOutOfWaterTimer = 0.0f;
m_WaterTurbulenceInWaterTimer = 0.0f;
}
}
m_WasEngineOnLastFrame = m_Vehicle->m_nVehicleFlags.bEngineOn;
}
// ----------------------------------------------------------------
// audBoatAudioEntity CalculateIsInWater
// ----------------------------------------------------------------
bool audBoatAudioEntity::CalculateIsInWater() const
{
#if GTA_REPLAY // For replay we set this from CPacketBoatUpdate
if(CReplayMgr::IsEditModeActive())
{
return m_ReplayIsInWater;
}
#endif
if(IsSubmersible() || m_Vehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINE)
{
return m_Vehicle->GetIsInWater();
}
else
{
if(m_Vehicle->GetVehicleType() != VEHICLE_TYPE_SUBMARINECAR)
{
CBoat* boat = (CBoat*)m_Vehicle;
return m_WaterSamples.IsSet(CVfxWater::GetEntryWaterSampleIndex(boat));
}
else
{
return false;
}
}
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetVehicleRainSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetVehicleRainSound(bool interiorView) const
{
if(m_BoatAudioSettings)
{
if(interiorView)
{
return m_BoatAudioSettings->VehicleRainSoundInterior;
}
else
{
return m_BoatAudioSettings->VehicleRainSound;
}
}
return audVehicleAudioEntity::GetVehicleRainSound(interiorView);
}
// ----------------------------------------------------------------
// audBoatAudioEntity TriggerDoorOpenSound
// ----------------------------------------------------------------
void audBoatAudioEntity::TriggerDoorOpenSound(eHierarchyId doorIndex)
{
if(IsDisabled())
{
return;
}
if(m_BoatAudioSettings)
{
u32 hash = m_BoatAudioSettings->DoorOpen;
TriggerDoorSound(hash, doorIndex);
}
}
// ----------------------------------------------------------------
// audBoatAudioEntity TriggerDoorCloseSound
// ----------------------------------------------------------------
void audBoatAudioEntity::TriggerDoorCloseSound(eHierarchyId doorIndex, const bool UNUSED_PARAM(isBroken))
{
if(IsDisabled())
{
return;
}
if(m_BoatAudioSettings)
{
u32 hash = m_BoatAudioSettings->DoorClose;
TriggerDoorSound(hash, doorIndex);
}
}
// ----------------------------------------------------------------
// audBoatAudioEntity TriggerDoorFullyOpenSound
// ----------------------------------------------------------------
void audBoatAudioEntity::TriggerDoorFullyOpenSound(eHierarchyId doorIndex)
{
if(IsDisabled())
{
return;
}
if(m_BoatAudioSettings)
{
u32 hash = m_BoatAudioSettings->DoorLimit;
TriggerDoorSound(hash, doorIndex);
}
}
// ----------------------------------------------------------------
// audBoatAudioEntity TriggerDoorStartCloseSound
// ----------------------------------------------------------------
void audBoatAudioEntity::TriggerDoorStartCloseSound(eHierarchyId doorIndex)
{
if(IsDisabled())
{
return;
}
if(m_BoatAudioSettings)
{
u32 hash = m_BoatAudioSettings->DoorStartClose;
TriggerDoorSound(hash, doorIndex);
}
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetEngineIgnitionSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetEngineIgnitionLoopSound() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->IgnitionLoop;
}
return audVehicleAudioEntity::GetEngineIgnitionLoopSound();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetWaveHitBigAirTime
// ----------------------------------------------------------------
f32 audBoatAudioEntity::GetWaveHitBigAirTime() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->BigAirMinTime;
}
return audVehicleAudioEntity::GetWaveHitBigAirTime();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetTurretSoundSet
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetTurretSoundSet() const
{
if (GetVehicleModelNameHash() == ATSTRINGHASH("PATROLBOAT", 0xEF813606))
{
return ATSTRINGHASH("PATROLBOAT_TURRET_SOUNDS", 0x92EBBEC5);
}
return audVehicleAudioEntity::GetTurretSoundSet();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetEngineShutdownSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetEngineShutdownSound() const
{
if(m_BoatAudioSettings)
{
if(IsSubmersible() && !m_WasInWaterLastFrame)
{
// GTAV fix - subs are using a bubble-y underwater sound here, which makes no sense on dry land
return g_NullSoundHash;
}
else
{
return m_BoatAudioSettings->ShutdownOneShot;
}
}
return audVehicleAudioEntity::GetEngineShutdownSound();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetEngineShutdownSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetEngineStartupSound() const
{
if(m_BoatAudioSettings)
{
if(IsSubmersible() && !m_WasInWaterLastFrame)
{
// GTAV fix - subs are using a bubble-y underwater sound here, which makes no sense on dry land
return g_NullSoundHash;
}
else
{
return m_BoatAudioSettings->EngineStartUp;
}
}
return audVehicleAudioEntity::GetEngineStartupSound();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetIdleHullSlapSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetIdleHullSlapSound() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->IdleHullSlapLoop;
}
return audVehicleAudioEntity::GetIdleHullSlapSound();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetLeftWaterSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetLeftWaterSound() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->LeftWaterSound;
}
return audVehicleAudioEntity::GetLeftWaterSound();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetWaveHitSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetWaveHitSound() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->WaveHitSound;
}
return audVehicleAudioEntity::GetWaveHitSound();
}
// ----------------------------------------------------------------
// audBoatAudioEntity GetWaveHitBigSound
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetWaveHitBigSound() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->WaveHitBigAirSound;
}
return audVehicleAudioEntity::GetWaveHitBigSound();
}
// ----------------------------------------------------------------
// Compute the ignition hold time
// ----------------------------------------------------------------
f32 audBoatAudioEntity::ComputeIgnitionHoldTime(bool isStartingThisTime)
{
// Maintaining behaviour of old code - non-granular boats just have a one shot ignition rather than a proper hold time
if(IsUsingGranularEngine())
{
bool isStartingFirstTime = (isStartingThisTime && m_Vehicle->m_failedEngineStartAttempts == 0);
const f32 maxHold = (isStartingFirstTime?sm_StartingIgnitionMaxHold:sm_IgnitionMaxHold);
const f32 minHold = (isStartingFirstTime?sm_StartingIgnitionMinHold:sm_IgnitionMinHold);
f32 timeFactor = 1.f;
if(!isStartingFirstTime && (m_Vehicle->GetStatus() == STATUS_WRECKED || m_Vehicle->m_Transmission.GetEngineHealth() <= ENGINE_DAMAGE_ONFIRE))
{
// its not going to start so try for longer
timeFactor = 2.5f;
}
m_IgnitionHoldTime = audEngineUtil::GetRandomNumberInRange(minHold * timeFactor, maxHold * timeFactor);
}
else
{
m_IgnitionHoldTime = 0.0f;
}
return m_IgnitionHoldTime;
}
// ----------------------------------------------------------------
// boat min openness
// ----------------------------------------------------------------
f32 audBoatAudioEntity::GetMinOpenness() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->Openness;
}
return 1.f;
}
// ----------------------------------------------------------------
// audBoatAudioEntity UpdateNonEngine
// ----------------------------------------------------------------
void audBoatAudioEntity::UpdateNonEngine(audVehicleVariables& vehicleVariables, const u32 UNUSED_PARAM(timeInMs))
{
if(m_Vehicle->m_nVehicleFlags.bEngineOn)
{
Vec3V pos;
const s32 engineBoneId = m_Vehicle->GetVehicleModelInfo()->GetBoneIndex(VEH_ENGINE);
f32 waterDepth = m_EngineWaterDepth;
// Using separate water timers for turbulence sounds - the default timers only monitor the big bow VFX
// at the front of the vehicle, so can report that the boat is not in water even when water VFX is playing visually.
bool isAnyWaterVFXActive = IsAnyWaterVFXActive();
if(isAnyWaterVFXActive)
{
m_WaterTurbulenceInWaterTimer += fwTimer::GetTimeStep();
m_WaterTurbulenceOutOfWaterTimer = 0.f;
}
else
{
m_WaterTurbulenceOutOfWaterTimer += fwTimer::GetTimeStep();
m_WaterTurbulenceInWaterTimer = 0.f;
}
f32 volLin = m_WaterTurbulenceVolumeCurve.CalculateValue((vehicleVariables.revs - 0.2f) * 1.25f);
volLin *= isAnyWaterVFXActive? Clamp(m_WaterTurbulenceInWaterTimer * 3.0f, 0.0f, 1.0f) : 1.0f - Clamp(m_WaterTurbulenceOutOfWaterTimer * 3.0f, 0.0f, 1.0f);
// Submersibles use the centre of the sub as the turbulence sound position
if(volLin > g_SilenceVolumeLin)
{
if(IsSubmersible())
{
if (GetVehicleModelNameHash() == ATSTRINGHASH("Kosatka", 0x4FAF0D70))
{
pos = ComputeClosestPositionOnVehicleYAxis();
}
else
{
f32 waterZ = 0.f;
Vector3 vRiverHitPos, vRiverHitNormal;
Vector3 buoyancyProbePos = VEC3V_TO_VECTOR3(m_Vehicle->GetTransform().GetPosition());
bool inRiver = m_Vehicle->m_Buoyancy.GetCachedRiverBoundProbeResult(vRiverHitPos, vRiverHitNormal) && (vRiverHitPos.z + REJECTIONABOVEWATER) > buoyancyProbePos.z;
waterZ = inRiver ? vRiverHitPos.z : Water::GetWaterLevel(buoyancyProbePos, &waterZ, false, POOL_DEPTH, REJECTIONABOVEWATER, NULL);
waterDepth = buoyancyProbePos.z - waterZ;
pos = RCC_VEC3V(buoyancyProbePos);
}
}
else if(engineBoneId !=-1 )
{
pos = m_Vehicle->TransformIntoWorldSpace(m_ExhaustOffsetPos);
}
else
{
pos = m_Vehicle->GetTransform().GetPosition();
}
// For submersibles, fade out the turbulence as we get below water
if(IsSubmersible())
{
volLin *= 1.0f - Min(abs(Min((waterDepth + 1.0f), 0.0f))/2.0f, 1.0f);
}
if(!m_WaterTurbulenceSound)
{
audSoundInitParams initParams;
initParams.AttackTime = m_WasEngineOnLastFrame? sm_LodSwitchFadeTime : 300;
initParams.EnvironmentGroup = m_Vehicle->GetAudioEnvironmentGroup();
CreateAndPlaySound_Persistent(m_BoatAudioSettings->WaterTurbulance, &m_WaterTurbulenceSound, &initParams);
}
if(m_WaterTurbulenceSound)
{
f32 vol = audDriverUtil::ComputeDbVolumeFromLinear(volLin);
f32 pitch = m_WaterTurbulencePitchCurve.CalculateValue(vehicleVariables.revs);
m_WaterTurbulenceSound->SetRequestedPitch(static_cast<s32>(pitch));
m_WaterTurbulenceSound->SetRequestedVolume(vol BANK_ONLY(+ g_WaterTurbulenceVolumeTrim));
m_WaterTurbulenceSound->SetRequestedPosition(pos);
}
}
else if(m_WaterTurbulenceSound)
{
m_WaterTurbulenceSound->StopAndForget();
}
}
else if(m_WaterTurbulenceSound)
{
m_WaterTurbulenceSound->SetReleaseTime(800);
m_WaterTurbulenceSound->StopAndForget();
}
}
#if NON_GRANULAR_BOATS_ENABLED
// ----------------------------------------------------------------
// audBoatAudioEntity UpdateEngine
// ----------------------------------------------------------------
void audBoatAudioEntity::UpdateEngine(audVehicleVariables& vehicleVariables, const u32 UNUSED_PARAM(timeInMs))
{
naAssertf(m_Vehicle, "In UpdateEngine audBoatAudioentity as no vehicle!");
naAssertf(m_BoatAudioSettings, "In UpdateEngine audBoatAudioEntity has no boat audio settings");
if(m_EngineWaveSlot)
{
if(m_WasEngineOnLastFrame && !m_Vehicle->m_nVehicleFlags.bEngineOn)
{
audSoundInitParams ignitionInitParams;
ignitionInitParams.EnvironmentGroup = m_Vehicle->GetAudioEnvironmentGroup();
ignitionInitParams.Tracker = m_Vehicle->GetPlaceableTracker();
ignitionInitParams.UpdateEntity = true;
CreateAndPlaySound(m_BoatAudioSettings->ShutdownOneShot,&ignitionInitParams);
}
}
if(!m_Vehicle->m_nVehicleFlags.bEngineOn)
{
StopEngineSounds();
}
else if(!m_EngineOn)
{
StartEngineSounds();
}
if(m_Vehicle->m_nVehicleFlags.bEngineOn)
{
for(u32 i = 0 ; i < BOAT_ENGINE_SOUND_TYPE_MAX; i++)
{
if(m_EngineSounds[i])
{
f32 volLin = m_EngineCurves[i].CalculateValue(vehicleVariables.revs);
f32 vol = audDriverUtil::ComputeDbVolumeFromLinear(volLin);
f32 pitch = m_EnginePitchCurves[i].CalculateValue(vehicleVariables.revs);
if(IsSubmersible() &&
(i == BOAT_ENGINE_SOUND_TYPE_ENGINE_1 || i == BOAT_ENGINE_SOUND_TYPE_ENGINE_2))
{
pitch += m_SubTurningEnginePitchModifier.CalculateValue(m_SubAngularVelocityMag);
}
m_EngineSounds[i]->SetRequestedPitch(static_cast<s32>(pitch));
m_EngineSounds[i]->SetRequestedVolume(vol BANK_ONLY(+ g_VolumeTrims[i]));
if(i == 0)
{
m_EngineSounds[i]->SetRequestedPostSubmixVolumeAttenuation(m_BoatAudioSettings->ExhaustVolPostSubmix);
}
else if(i == 1)
{
m_EngineSounds[i]->SetRequestedPostSubmixVolumeAttenuation(m_BoatAudioSettings->EngineVolPostSubmix);
}
}
}
}
}
#endif
// ----------------------------------------------------------------
// audBoatAudioEntity UpdateEngineGranular
// ----------------------------------------------------------------
void audBoatAudioEntity::UpdateEngineGranular(audVehicleVariables& vehicleVariables, const u32 UNUSED_PARAM(timeInMs))
{
naAssertf(m_Vehicle, "In UpdateEngine audBoatAudioentity as no vehicle!");
naAssertf(m_BoatAudioSettings, "In UpdateEngine audBoatAudioEntity has no boat audio settings");
if(IsSubmersible())
{
m_VehicleEngine.SetGranularPitch(static_cast<s32>(m_SubTurningEnginePitchModifier.CalculateValue(m_SubAngularVelocityMag)));
}
else
{
m_VehicleEngine.SetGranularPitch(vehicleVariables.granularEnginePitchOffset);
}
m_VehicleEngine.Update(&vehicleVariables);
}
// -------------------------------------------------------------------------------
// Get the engine submix synth def
// -------------------------------------------------------------------------------
u32 audBoatAudioEntity::GetEngineSubmixSynth() const
{
if(ShouldUseDSPEffects() && m_BoatAudioSettings)
{
return m_BoatAudioSettings->EngineSynthDef;
}
else
{
return audVehicleAudioEntity::GetEngineSubmixSynth();
}
}
// -------------------------------------------------------------------------------
// Get the engine submix synth preset
// -------------------------------------------------------------------------------
u32 audBoatAudioEntity::GetEngineSubmixSynthPreset() const
{
if(ShouldUseDSPEffects() && m_BoatAudioSettings)
{
return m_BoatAudioSettings->EngineSynthPreset;
}
else
{
return audVehicleAudioEntity::GetEngineSubmixSynthPreset();
}
}
// -------------------------------------------------------------------------------
// Get the exhaust submix synth def
// -------------------------------------------------------------------------------
u32 audBoatAudioEntity::GetExhaustSubmixSynth() const
{
if(ShouldUseDSPEffects() && m_BoatAudioSettings)
{
return m_BoatAudioSettings->ExhaustSynthDef;
}
else
{
return audVehicleAudioEntity::GetExhaustSubmixSynth();
}
}
// -------------------------------------------------------------------------------
// Get the exhaust submix synth preset
// -------------------------------------------------------------------------------
u32 audBoatAudioEntity::GetExhaustSubmixSynthPreset() const
{
if(ShouldUseDSPEffects() && m_BoatAudioSettings)
{
return m_BoatAudioSettings->ExhaustSynthPreset;
}
else
{
return audVehicleAudioEntity::GetExhaustSubmixSynthPreset();
}
}
// -------------------------------------------------------------------------------
// Get the engine submix voice
// -------------------------------------------------------------------------------
u32 audBoatAudioEntity::GetEngineSubmixVoice() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->EngineSubmixVoice;
}
else
{
return audVehicleAudioEntity::GetEngineSubmixVoice();
}
}
// -------------------------------------------------------------------------------
// Get the exhaust submix voice
// -------------------------------------------------------------------------------
u32 audBoatAudioEntity::GetExhaustSubmixVoice() const
{
if(m_BoatAudioSettings)
{
return m_BoatAudioSettings->ExhaustSubmixVoice;
}
else
{
return audVehicleAudioEntity::GetExhaustSubmixVoice();
}
}
#if NON_GRANULAR_BOATS_ENABLED
// ----------------------------------------------------------------
// Start engine sounds
// ----------------------------------------------------------------
void audBoatAudioEntity::StartEngineSounds()
{
naAssertf(m_BoatAudioSettings, "No boat audio setting in StartEngineSounds");
naAssertf(m_EngineWaveSlot, "Invalid engine slot index in StartengineSounds");
audSoundInitParams initParams;
if(!m_Vehicle->m_nVehicleFlags.bSkipEngineStartup && !m_WasEngineOnLastFrame)
{
audSoundInitParams ignitionInitParams;
ignitionInitParams.EnvironmentGroup = m_Vehicle->GetAudioEnvironmentGroup();
ignitionInitParams.Tracker = m_Vehicle->GetPlaceableTracker();
ignitionInitParams.UpdateEntity = true;
CreateAndPlaySound(m_BoatAudioSettings->IgnitionOneShot,&ignitionInitParams);
initParams.Predelay = 700;
}
else
{
initParams.AttackTime = m_WasEngineOnLastFrame? sm_LodSwitchFadeTime : 300;
}
initParams.UpdateEntity = true;
initParams.u32ClientVar = AUD_VEHICLE_SOUND_ENGINE;
initParams.EnvironmentGroup = m_Vehicle->GetAudioEnvironmentGroup();
initParams.Category = g_AudioEngine.GetCategoryManager().GetCategoryPtr(ATSTRINGHASH("VEHICLES_BOATS_ENGINES", 0x6CB697B1));
Assign(initParams.EffectRoute, GetExhaustEffectRoute());
if(!m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_ENGINE_1])
{
CreateSound_PersistentReference(m_BoatAudioSettings->Engine1Loop, &m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_ENGINE_1], &initParams);
}
Assign(initParams.EffectRoute, GetEngineEffectRoute());
if(!m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_ENGINE_2])
{
CreateSound_PersistentReference(m_BoatAudioSettings->Engine2Loop, &m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_ENGINE_2], &initParams);
}
initParams.EffectRoute = 0;
if(!m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_LOW_RESO])
{
CreateSound_PersistentReference(m_BoatAudioSettings->LowResoLoop, &m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_LOW_RESO], &initParams);
}
if(!m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_RESO])
{
CreateSound_PersistentReference(m_BoatAudioSettings->ResoLoop, &m_EngineSounds[BOAT_ENGINE_SOUND_TYPE_RESO], &initParams);
}
for(u32 i = 0 ; i < BOAT_ENGINE_SOUND_TYPE_MAX; i++)
{
if(m_EngineSounds[i])
{
m_EngineSounds[i]->PrepareAndPlay(m_EngineWaveSlot->waveSlot,false,15000);
}
}
m_EngineOn = true;
}
#endif
// ----------------------------------------------------------------
// Stop engine sounds
// ----------------------------------------------------------------
void audBoatAudioEntity::StopEngineSounds(bool convertedToDummy)
{
#if NON_GRANULAR_BOATS_ENABLED
for(u32 i = 0 ; i < BOAT_ENGINE_SOUND_TYPE_MAX; i++)
{
if(m_EngineSounds[i])
{
m_EngineSounds[i]->SetReleaseTime(convertedToDummy ? sm_LodSwitchFadeTime : 800);
m_EngineSounds[i]->StopAndForget();
}
}
#else
UNUSED_VAR(convertedToDummy);
#endif
m_EngineOn = false;
}
// ----------------------------------------------------------------
// Get boat audio settings
// ----------------------------------------------------------------
BoatAudioSettings *audBoatAudioEntity::GetBoatAudioSettings()
{
BoatAudioSettings *settings = NULL;
if(g_AudioEngine.IsAudioEnabled())
{
if(m_ForcedGameObject != 0u)
{
settings = audNorthAudioEngine::GetObject<BoatAudioSettings>(m_ForcedGameObject);
m_ForcedGameObject = 0u;
}
if(!settings)
{
if(m_Vehicle->GetVehicleModelInfo()->GetAudioNameHash() != 0)
{
settings = audNorthAudioEngine::GetObject<BoatAudioSettings>(m_Vehicle->GetVehicleModelInfo()->GetAudioNameHash());
}
if(!settings)
{
settings = audNorthAudioEngine::GetObject<BoatAudioSettings>(GetVehicleModelNameHash());
}
}
if(!audVerifyf(settings, "Couldn't find boat audio settings for %s", GetVehicleModelName()))
{
// fall back to default for now
settings = audNorthAudioEngine::GetObject<BoatAudioSettings>(ATSTRINGHASH("JETMAX", 0x33581161));
}
else
{
m_ManufacturerHash = settings->ScannerMake;
m_ModelHash = settings->ScannerModel;
m_CategoryHash = settings->ScannerCategory;
m_ScannerVehicleSettingsHash = settings->ScannerVehicleSettings;
}
}
return settings;
}
// ----------------------------------------------------------------
// Get a sound from the object data
// ----------------------------------------------------------------
u32 audBoatAudioEntity::GetSoundFromObjectData(const u32 fieldHash) const
{
if(m_BoatAudioSettings)
{
u32 *ret =(u32 *)(m_BoatAudioSettings->GetFieldPtr(fieldHash));
return (ret) ? *ret : 0;
}
return 0;
}
VehicleCollisionSettings* audBoatAudioEntity::GetVehicleCollisionSettings() const
{
if(m_BoatAudioSettings)
{
return audNorthAudioEngine::GetObject<VehicleCollisionSettings>(m_BoatAudioSettings->VehicleCollisions);
}
return audVehicleAudioEntity::GetVehicleCollisionSettings();
}
#if __BANK
// ----------------------------------------------------------------
// Add widgets to RAG tool
// ----------------------------------------------------------------
void audBoatAudioEntity::AddWidgets(bkBank &bank)
{
bank.PushGroup("Boats", false);
bank.AddToggle("Enable Granular Debug Rendering", &g_GranularDebugRenderingEnabled);
bank.AddToggle("Debug Draw", &g_DebugDraw);
bank.AddSlider("Engine 1 Volume Trim", &g_VolumeTrims[0], -100.0f, 100.f, 0.1f);
bank.AddSlider("Engine 2 Volume Trim", &g_VolumeTrims[1], -100.0f, 100.f, 0.1f);
bank.AddSlider("Low Reso Trim", &g_VolumeTrims[2], -100.0f, 100.f, 0.1f);
bank.AddSlider("Reso Volume Trim", &g_VolumeTrims[3], -100.0f, 100.f, 0.1f);
bank.AddSlider("Water Turbulance Volume Trim", &g_WaterTurbulenceVolumeTrim, -100.0f, 100.f, 0.1f);
bank.AddSlider("Bank Spray Volume Trim", &g_BankSprayVolumeTrim, -100.0f, 100.f, 0.1f);
bank.AddSlider("Bank Spray Panning Distance Scalar", &g_BankSprayPanDistanceScalar, 0.0f, 10.f, 0.1f);
bank.AddSlider("Bank Spray Forward Distance Scalar", &g_BankSprayForwardDistanceScalar, -50.0f, 50.f, 0.1f);
bank.AddSlider("Submersible Underwater Cutoff", &g_SubmersibleUnderwaterCutoff, 0.0f, 24000.f, 1.0f);
bank.PushGroup("NPC Fake Revs");
bank.AddToggle("Treat All Boats as NPCs", &g_TreatAllBoatsAsNPCs);
bank.AddSlider("Fake Revs Offset Above", &g_FakeRevsOffsetAbove, 0.0f, 1.0f, 0.001f);
bank.AddSlider("Fake Revs Offset Below", &g_FakeRevsOffsetBelow, 0.0f, 1.0f, 0.001f);
bank.AddSlider("Fake Revs Min Hold Time Min", &g_FakeRevsMinHoldTimeMin, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Fake Revs Min Hold Time Max", &g_FakeRevsMinHoldTimeMax, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Fake Revs Max Hold Time Min", &g_FakeRevsMaxHoldTimeMin, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Fake Revs Max Hold Time Max", &g_FakeRevsMaxHoldTimeMax, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Fake Revs Increase Rate Base", &g_FakeRevsIncreaseRateBase, 0.0f, 200000.0f, 1000.0f);
bank.AddSlider("Fake Revs Increase Rate Scale", &g_FakeRevsIncreaseRateScale, 0.0f, 100.f, 0.1f);
bank.AddSlider("Fake Revs Decrease Rate Base", &g_FakeRevsDecreaseRateBase, 0.0f, 200000.0f, 1000.0f);
bank.AddSlider("Fake Revs Decrease Rate Scale", &g_FakeRevsDecreaseRateScale, 0.0f, 100.f, 0.1f);
bank.AddSlider("Fake Out of Water Min Time", &g_FakeOutOfWaterMinTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Fake Out of Water Max Time", &g_FakeOutOfWaterMaxTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Fake In Water Min Time", &g_FakeInWaterMinTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Fake In Water Max Time", &g_FakeInWaterMaxTime, 0.0f, 100.0f, 0.1f);
bank.PopGroup();
bank.PopGroup();
}
#endif