Files
GTASource/game/Vehicles/Submarine.cpp

2290 lines
72 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
#include "vehicles/Submarine.h"
#include "Peds/Ped.h"
#include "Physics/Floater.h"
#include "Physics/physics.h"
#include "Physics/PhysicsHelpers.h"
#include "Renderer/Water.h"
#include "renderer/River.h"
#include "system/control.h"
#include "System/controlMgr.h"
#include "Task/Motion/TaskMotionBase.h"
#include "Task/Vehicle/TaskInVehicle.h"
#include "Vfx/Systems/VfxWater.h"
#include "debug/DebugScene.h"
#include "camera/CamInterface.h"
#include "Stats/StatsMgr.h"
#include "Vfx/Decals/DecalManager.h"
#include "scene/world/gameWorld.h"
#include "Vfx/Misc/Fire.h"
#include "vfx/Systems/VfxVehicle.h"
#include "debug/debugglobals.h"
#include "network/Events/NetworkEventTypes.h"
#include "audio/boataudioentity.h"
#include "audio/caraudioentity.h"
#include "audio/radioaudioentity.h"
#include "audio/radioslot.h"
#include "vehicleAi/task/TaskVehicleAnimation.h"
ENTITY_OPTIMISATIONS()
VEHICLE_OPTIMISATIONS()
dev_float CSubmarineHandling::ms_fControlSmoothSpeed = 2.0f;
dev_float CSubmarineHandling::ms_fSubSinkForceMultStep = 0.002f;
// Below "design depth" we start applying damage to the sub. If the player doesn't heed this warning and head back towards the surface
// we then start to deplete oxygen halfway between "design depth" and "crush depth".
// At "crush depth" we instantly kill the occupants.
const float kfGlobalDesignDepth = -150.0f;
const float kfGlobalAirLeakDepth = -170.0f;
const float kfGlobalCrushDepth = -185.0f;
dev_float CSubmarineHandling::ms_fCrushStepIncrementSize = 1.0f;
dev_float CSubmarineHandling::ms_fImplosionDamageAmount = 1000000.0f; // 100000 N/M^2 = 14.5 psi
dev_u32 CSubmarineHandling::ms_nNumberOfImplosionVectors = 10;
dev_u32 CSubmarineHandling::ms_nMinIntervalBeforeNextImplisionEvent = 100;
dev_u32 CSubmarineHandling::ms_nMaxIntervalBeforeNextImplisionEvent = 2500;
dev_float CSubmarineHandling::ms_fSubCarPerImplosionDamageAmount = ( ms_fImplosionDamageAmount / (float)ms_nNumberOfImplosionVectors ) / 20.0f;
float CSubmarineCar::ms_wheelUpOffset = 0.126f;
float CSubmarineCar::ms_wheelInset = 0.2772f;
float CSubmarineCar::ms_wheelCoverInset = -0.32f;
float CSubmarineCar::ms_wheelCoverZOffset = -0.3f;
float CSubmarineCar::ms_wheelCoverRotation = 0.3f;
float CSubmarineCar::ms_wheelSuspensionOffset = 0.35f;
float CSubmarineCar::ms_rearWheelUpFactor = 1.2f;
float CSubmarineCar::ms_animationSpeed = 1.5f;
//////////////////////////////////////////////////////////////////////////
// Class CSubmarineHandling
CSubmarineHandling::CSubmarineHandling()
{
m_fPitchControl = 0.0f;
m_fYawControl = 0.0f;
m_fDiveControl = 0.0f;
m_fMaxDepthReached = 0.0f;
m_nAirLeaks = 0;
m_fRudderAngle = 0.0f;
m_fElevatorAngle = 0.0f;
m_fMinWreckDepth = 0.8f;
m_iNumPropellers = 0;
m_pBuoyancyOverride = NULL;
m_bEnableCrushDamage = true;
m_bCrushDepthsModifiedThisFrame = false;
m_nSubmarineFlags.bSinkWhenWrecked = 1;
m_nSubmarineFlags.bForceSurfaceMode = 0;
m_nSubmarineFlags.bDisplayingCrushWarning = 0;
m_nSubmarineFlags.bPropellerSubmarged = 0;
m_iForcingNeutralBuoyancyTime = 0;
m_fForceThrottleValue = 0.0f;
m_fForceThrottleSeconds = 0.0f;
ComputeTimeForNextImplosionEvent();
}
CSubmarineHandling::~CSubmarineHandling()
{
if(m_pBuoyancyOverride)
{
delete m_pBuoyancyOverride;
}
}
void CSubmarineHandling::SetModelId(CVehicle *pVehicle, fwModelId UNUSED_PARAM(modelId))
{
// Copy the buoyancy info for this submarine class so that we can fiddle with it later.
m_pBuoyancyOverride = pVehicle->m_Buoyancy.GetBuoyancyInfo(pVehicle)->Clone();
pVehicle->m_Buoyancy.SetBuoyancyInfoOverride(m_pBuoyancyOverride);
// Figure out which propellers are valid, and set them up
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
for(int nPropIndex = 0 ; nPropIndex < SUBMARINECAR_NUM_PROPELLERS; nPropIndex++ )
{
eHierarchyId nId = (eHierarchyId)(SUBMARINECAR_PROPELLER_1 + nPropIndex);
if(pVehicle->GetBoneIndex(nId) > -1 && physicsVerifyf(m_iNumPropellers < SUB_NUM_PROPELLERS,"Out of room for sub propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId,ROT_AXIS_LOCAL_Y,pVehicle);
m_iNumPropellers ++;
}
}
}
else
{
for(int nPropIndex = 0 ; nPropIndex < SUB_NUM_PROPELLERS; nPropIndex++ )
{
eHierarchyId nId = (eHierarchyId)(SUB_PROPELLER_1 + nPropIndex);
if(pVehicle->GetBoneIndex(nId) > -1 && physicsVerifyf(m_iNumPropellers < SUB_NUM_PROPELLERS,"Out of room for sub propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId,ROT_AXIS_LOCAL_Y,pVehicle);
m_iNumPropellers ++;
}
}
}
m_AnchorHelper.SetParent(pVehicle);
}
//////////////////////////////////////////////////////////////////////////
// Damage the vehicle even more, close to these bones during BlowUpCar
//////////////////////////////////////////////////////////////////////////
const int SUB_BONE_COUNT_TO_DEFORM = 15;
const eHierarchyId ExtraSubBones[SUB_BONE_COUNT_TO_DEFORM] =
{
VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS,
SUB_PROPELLER_1, SUB_PROPELLER_2, SUB_PROPELLER_3, SUB_PROPELLER_4, SUB_PROPELLER_LEFT, SUB_PROPELLER_RIGHT, SUB_RUDDER, SUB_RUDDER2, SUB_ELEVATOR_L, SUB_ELEVATOR_R };
const eHierarchyId* CSubmarineHandling::GetExtraBonesToDeform(int& extraBoneCount)
{
extraBoneCount = SUB_BONE_COUNT_TO_DEFORM;
return ExtraSubBones;
}
void CSubmarineHandling::ForceThrottleForTime( float in_fThrottle, float in_fSeconds )
{
m_fForceThrottleValue = in_fThrottle;
m_fForceThrottleSeconds = in_fSeconds;
}
float CSubmarineHandling::GetCurrentDepth(CVehicle* pVehicle) const
{
Vec3V vSubPos = pVehicle->GetTransform().GetPosition();
// Subtract the submarine's Z coord from the water height at its location to get the depth.
float fWaterLevel = 0.0f;
pVehicle->m_Buoyancy.GetWaterLevelIncludingRiversNoWaves(RC_VECTOR3(vSubPos), &fWaterLevel, POOL_DEPTH, REJECTIONABOVEWATER, NULL);
return vSubPos.GetZf() - fWaterLevel;
}
void CSubmarineHandling::Implode(CVehicle *pVehicle, bool fullyImplode)
{
if(IsUnderAirLeakDepth(pVehicle))
{
m_nAirLeaks++;
}
float fDamagePerImplosion = 0.0f;
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
fDamagePerImplosion = ms_fSubCarPerImplosionDamageAmount;
}
else
{
fDamagePerImplosion = ms_fImplosionDamageAmount / (float)ms_nNumberOfImplosionVectors;
}
for (int i = 0; i < ms_nNumberOfImplosionVectors; ++i)
{
float randomAlpha = DtoR * fwRandom::GetRandomNumberInRange(0.0f, 180.0f);
float randomTheta = DtoR * fwRandom::GetRandomNumberInRange(0.0f, 360.0f);
Vector3 damagePosLocal = Vector3(cos(randomAlpha) * 2.0f, sin(randomAlpha) * 2.0f - 1.5f, cos(randomTheta) * 2.0f);
Vector3 impulseLocal = -damagePosLocal;
CVehicleDamage::DamageVehicleByDriver(impulseLocal, damagePosLocal, fDamagePerImplosion, DAMAGE_TYPE_WATER_CANNON, pVehicle, fullyImplode);
}
if (fullyImplode)
{
// B*1929776: Don't modify the health for clones.
if(!pVehicle->IsNetworkClone())
{
// It's game over.
pVehicle->SetHealth(0.0f);
pVehicle->BlowUpCar(NULL, false, false, false, WEAPONTYPE_DROWNINGINVEHICLE, false);
pVehicle->m_nVehicleFlags.bIsDrowning = true;
}
g_vfxWater.TriggerPtFxSubmarineCrush(static_cast<CSubmarine*>(pVehicle));
// More pad shake if we are destroying the submarine.
dev_u32 snRumbleDuration = 750;
dev_float sfRumbleIntensity = 1.0f; // Max intensity.
CControlMgr::StartPlayerPadShakeByIntensity(snRumbleDuration, sfRumbleIntensity);
}
else
{
dev_u32 snRumbleDuration = 100;
dev_float sfRumbleIntensityScale = 0.3f;
CControlMgr::StartPlayerPadShakeByIntensity(snRumbleDuration, fDamagePerImplosion*sfRumbleIntensityScale);
}
if(pVehicle->GetVehicleAudioEntity())
{
if(IsUnderAirLeakDepth(pVehicle))
{
pVehicle->GetVehicleAudioEntity()->TriggerSubmarineImplodeWarning(fullyImplode);
}
pVehicle->GetVehicleAudioEntity()->TriggerSubmarineCrushSound();
pVehicle->GetVehicleAudioEntity()->TriggerDynamicExplosionSounds(true);
}
}
// Needs to be called every frame or it will reset.
void CSubmarineHandling::SetCrushDepths(bool bEnableCrushDamage, float fDesignDamageDepth, float fAirLeakDepth, float fCrushDepth)
{
m_bEnableCrushDamage = bEnableCrushDamage;
m_fDesignDepth = fDesignDamageDepth;
m_fAirLeakDepth = fAirLeakDepth;
m_fCrushDepth = fCrushDepth;
m_bCrushDepthsModifiedThisFrame = true;
}
void CSubmarineHandling::ProcessCrushDepth(CVehicle *pVehicle)
{
float fCurrentDepth = GetCurrentDepth(pVehicle);
if(fCurrentDepth < m_fMaxDepthReached - ms_fCrushStepIncrementSize)
{
m_fMaxDepthReached = fCurrentDepth;
}
// Has someone modified the crush depths for this frame?
if(!m_bCrushDepthsModifiedThisFrame)
{
m_bEnableCrushDamage = true;
m_fDesignDepth = kfGlobalDesignDepth;
m_fAirLeakDepth = kfGlobalAirLeakDepth;
m_fCrushDepth = kfGlobalCrushDepth;
}
#if DISPLAY_CRUSH_DEPTH_HELP_TEXT
if(m_bEnableCrushDamage && pVehicle->ContainsLocalPlayer())
{
DisplayCrushDepthMessages(pVehicle);
}
#endif // DISPLAY_CRUSH_DEPTH_HELP_TEXT
// Handle applying damage from being too deep.
if(m_bEnableCrushDamage && IsUnderDesignDepth(pVehicle) && pVehicle->GetStatus()!=STATUS_WRECKED)
{
if(IsUnderCrushDepth(pVehicle))
{
// If we are at the absolute limit we wish players to explore, kill them instantly.
Implode(pVehicle, true);
}
else if(fwTimer::GetTimeInMilliseconds() > GetTimeForNextImplosionEvent())
{
// While below the design depth we continually apply implosion damage after random intervals of time.
Implode(pVehicle, false);
ComputeTimeForNextImplosionEvent();
}
if(IsUnderAirLeakDepth(pVehicle))
{
if(pVehicle && pVehicle->GetDriver() && pVehicle->GetDriver()->IsLocalPlayer())
{
// Add a constant low-intensity pad shake when sub is permanently broken and leaking air.
dev_u32 snRumbleDuration = 1000;
dev_float sfRumbleIntensity = 0.1f;
CControlMgr::StartPlayerPadShakeByIntensity(snRumbleDuration, sfRumbleIntensity);
}
}
}
if(CPhysics::GetIsLastTimeSlice(CPhysics::GetCurrentTimeSlice()))
{
m_bCrushDepthsModifiedThisFrame = false;
}
}
ePhysicsResult CSubmarineHandling::ProcessPhysics(CVehicle *pVehicle, float fTimeStep, bool UNUSED_PARAM(bCanPostpone), int nTimeSlice)
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessPhysicsOfFocusEntity(), pVehicle );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfProcessPhysicsCallingEntity(), VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition()) );
// No point processing submarine physics if we have a basic attachment to something.
if(pVehicle->GetIsAttached() && !pVehicle->GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION)
&& (pVehicle->GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_BASIC||pVehicle->GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD))
{
return PHYSICS_DONE;
}
// process doors (doors can keep us awake?)
for(int i=0; i<pVehicle->GetNumDoors(); i++)
{
pVehicle->GetDoor(i)->ProcessPhysics(pVehicle, fTimeStep, nTimeSlice);
}
ProcessCrushDepth(pVehicle);
// see if the prop / rudder is actually in the water
float fDriveInWater = 0.0f;
// Find propeller position
for(int i = 0; i < m_iNumPropellers; i++)
{
fDriveInWater += m_propellers[i].GetSubmergeFraction(pVehicle);
}
if(m_iNumPropellers > 0)
{
fDriveInWater /= (float) m_iNumPropellers;
}
m_nSubmarineFlags.bPropellerSubmarged = fDriveInWater > 0.0f ? 1 : 0;
// Do any processing necessary when asleep here, then quit.
if(pVehicle->ProcessIsAsleep())
{
// Need to always keep the transmission updated, else the sub can become asleep whilst the engine revs are still high, leading them to get audibly stuck on
ProcessProps(pVehicle, fTimeStep, fDriveInWater);
return PHYSICS_DONE;
}
ProcessSubmarineHandling(pVehicle, fTimeStep, fDriveInWater);
ProcessDepthLimit(pVehicle);
// Make sure the possbilyTouchesWater flag is up to date before acting on it...
if(pVehicle->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate)
{
// If bPossiblyTouchesWater is false we are either well above the surface of the water or in a tunnel.
if (!pVehicle->m_nFlags.bPossiblyTouchesWater)
{
// we can't set these flags for network clones of script objects directly as they are synced
if(!pVehicle->IsNetworkClone() || !(pVehicle->GetNetworkObject() && pVehicle->GetNetworkObject()->IsScriptObject(true)))
{
pVehicle->SetIsInWater( false );
}
pVehicle->m_Buoyancy.ResetBuoyancy();
}
// else process the buoyancy class
else
{
float fBuoyancyAccel = 0.0f;
pVehicle->m_Buoyancy.Process(pVehicle, fTimeStep, false, CPhysics::GetIsLastTimeSlice(nTimeSlice), &fBuoyancyAccel);
pVehicle->SetIsInWater(pVehicle->m_Buoyancy.GetStatus() > NOT_IN_WATER);
}
}
// if it's a submarine car apply some lateral damping to make it easier to control.
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
Matrix34 vehicleMat = MAT34V_TO_MATRIX34( pVehicle->GetMatrix() );
Vector3 rightVector = vehicleMat.GetVector( 0 );
float sideVelocity = rightVector.Dot( pVehicle->GetVelocity() );
static float sideVelocityDamping = -0.25f;
sideVelocity *= pVehicle->GetMass() * sideVelocityDamping;
rightVector.Scale( sideVelocity );
pVehicle->ApplyForceCg( rightVector );
}
for( int i = 0; i < pVehicle->GetNumberOfVehicleGadgets(); i++)
{
pVehicle->GetVehicleGadget(i)->ProcessPhysics(pVehicle,fTimeStep,nTimeSlice);
}
return PHYSICS_DONE;
}
void CSubmarineHandling::ProcessDepthLimit(CVehicle *pVehicle)
{
// Apply extra floating force if submarine approaches the world depth limit.
dev_float dfMaxWorldDepth = -250.0f;
if(pVehicle->GetTransform().GetPosition().GetZf() < dfMaxWorldDepth)
{
float fDesiredFloatingSpeed = dfMaxWorldDepth - pVehicle->GetTransform().GetPosition().GetZf();
float fDesiredFloatingAcceleration = Max(fDesiredFloatingSpeed - pVehicle->GetVelocity().z, 0.0f);
Vector3 vTorque = fDesiredFloatingAcceleration*pVehicle->GetMass() * ZAXIS;
pVehicle->ApplyForceCg(vTorque);
}
}
void CSubmarineHandling::ComputeTimeForNextImplosionEvent()
{
u32 nCurrentTime = fwTimer::GetTimeInMilliseconds();
u32 nRandInterval = fwRandom::GetRandomNumberInRange((int)ms_nMinIntervalBeforeNextImplisionEvent, (int)ms_nMaxIntervalBeforeNextImplisionEvent);
m_nNextImplosionForceTime = nCurrentTime + nRandInterval;
}
void CSubmarineHandling::DoProcessControl(CVehicle *pVehicle, bool fullUpdate, float fFullUpdateTimeStep)
{
physicsAssertf(pVehicle->GetVehicleType()==VEHICLE_TYPE_SUBMARINE || pVehicle->GetVehicleType()==VEHICLE_TYPE_SUBMARINECAR, "submarine doesn't have correct vehicle type (%d)", pVehicle->GetVehicleType());
pVehicle->ProcessIntelligence(fullUpdate, fFullUpdateTimeStep);
if(pVehicle->IsInDriveableState())
{
if ( m_fForceThrottleSeconds > 0.0f )
{
pVehicle->SetThrottle(m_fForceThrottleValue);
m_fForceThrottleSeconds -= fFullUpdateTimeStep;
}
//update the rudder angle
UpdateRudder(pVehicle);
//update the elevator angle
UpdateElevators(pVehicle);
}
if(pVehicle->GetIsInWater())
{
ProcessBuoyancy(pVehicle);
}
//check for and apply vehicle damage
pVehicle->GetVehicleDamage()->Process(fwTimer::GetTimeStep());
}
void CSubmarineHandling::ProcessBuoyancy(CVehicle *pVehicle)
{
const CSubmarineHandlingData* pSubHandling = pVehicle->pHandling->GetSubmarineHandlingData();
if(physicsVerifyf(pSubHandling,"Can't find submarine handling"))
{
physicsAssertf(pSubHandling->GetDiveSpeed() <= 1.0f, "Submarine dive speed should be between 0 and 1");
Assert(pVehicle->GetDoor(0));
CCarDoor* pHatch = pVehicle->GetDoor(0);
// If the sub is missing its hatch and is submerged enough, mark it as wrecked so that peds within it can drown.
if( pHatch->GetFragChild() != -1 &&
pVehicle->GetFragInst()->GetChildBroken(pHatch->GetFragChild()) &&
pVehicle->m_Buoyancy.GetSubmergedLevel() > m_fMinWreckDepth )
{
pVehicle->SetIsWrecked();
}
// B*1974048 - Spy car is no longer wrecked if the door is opened in water.
//if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR &&
// pVehicle->m_Buoyancy.GetSubmergedLevel() > m_fMinWreckDepth )
//{
// int numDoors = pVehicle->GetNumDoors();
// for( int i = 0; i < numDoors; i++ )
// {
// if( pVehicle->GetDoor( i )->GetDoorRatio() > 0.01f )
// {
// pVehicle->SetIsWrecked();
// pVehicle->SwitchEngineOff();
// break;
// }
// }
//}
// Handle sinking when wrecked.
if(pVehicle->GetStatus()==STATUS_WRECKED && GetSinksWhenWrecked())
{
// If we are anchored then release anchor.
if(GetAnchorHelper().IsAnchored())
{
GetAnchorHelper().Anchor(false);
}
pVehicle->m_Buoyancy.m_fForceMult = rage::Max(0.0f, pVehicle->m_Buoyancy.m_fForceMult - ms_fSubSinkForceMultStep);
return;
}
else if( pVehicle->GetVehicleType() != VEHICLE_TYPE_SUBMARINECAR )
{
pVehicle->m_Buoyancy.m_fForceMult = 1.0f + (pSubHandling->GetDiveSpeed() * m_fDiveControl);
}
// While the sub has no driver we want it to surface and have greater buoyancy than when being driven so that it
// doesn't look weird that peds can enter it.
bool bForcingNeutralBuoyancy = fwTimer::GetTimeInMilliseconds() < m_iForcingNeutralBuoyancyTime;
bool bSurfaceMode = !(!GetForceSurfaceMode() && (bForcingNeutralBuoyancy || ((pVehicle->GetDriver() || pVehicle->m_nVehicleFlags.bUsingPretendOccupants) && pHatch->GetIsClosed())));
TUNE_GROUP_FLOAT( SUBMARINE_TUNE, BIG_SUB_BUOYANCY_MULT, 2.25f, 0.0f, 10.0f, 0.1f );
const float fSubmergeLevel = pVehicle->m_Buoyancy.GetSubmergedLevel();
if(!bSurfaceMode)
{
if(pVehicle->GetBaseModelInfo() && pVehicle->GetBaseModelInfo()->GetBuoyancyInfo())
{
// The sub has neutral buoyancy while there is a driver at the controls.
// Work out fSizeTotal from the current buoyancy data and use that to compute what the new buoyancy constant should be.
pVehicle->m_Buoyancy.GetBuoyancyInfo(pVehicle)->m_fBuoyancyConstant =
pVehicle->GetVehicleModelInfo()->ComputeBuoyancyConstantFromSubmergeValue(1.0f, 0.0f, true);
if( MI_SUB_KOSATKA.IsValid() &&
pVehicle->GetModelIndex() == MI_SUB_KOSATKA &&
fSubmergeLevel < 0.9f &&
m_fDiveControl >= 0.0f )
{
pVehicle->m_Buoyancy.GetBuoyancyInfo( pVehicle )->m_fBuoyancyConstant *= BIG_SUB_BUOYANCY_MULT;
}
}
}
else
{
if(pVehicle->GetBaseModelInfo() && pVehicle->GetBaseModelInfo()->GetBuoyancyInfo())
{
pVehicle->m_Buoyancy.GetBuoyancyInfo(pVehicle)->m_fBuoyancyConstant =
pVehicle->GetVehicleModelInfo()->ComputeBuoyancyConstantFromSubmergeValue(pVehicle->pHandling->m_fBuoyancyConstant, 0.0f, true);
}
}
TUNE_GROUP_FLOAT( SUBMARINE_TUNE, BIG_SUB_BUOYANCY_EXTRA_MULT, 1.15f, 0.0f, 10.0f, 0.1f );
TUNE_GROUP_FLOAT( SUBMARINE_TUNE, BIG_SUB_SUBMERGE_LEVEL_FOR_EXTRA_MULT, 0.6f, 0.0f, 10.0f, 0.1f );
if( MI_SUB_KOSATKA.IsValid() &&
pVehicle->GetModelIndex() == MI_SUB_KOSATKA &&
fSubmergeLevel < BIG_SUB_SUBMERGE_LEVEL_FOR_EXTRA_MULT &&
m_fDiveControl >= 0.0f )
{
pVehicle->m_Buoyancy.GetBuoyancyInfo( pVehicle )->m_fBuoyancyConstant *= BIG_SUB_BUOYANCY_EXTRA_MULT;
}
if( MI_SUB_KOSATKA.IsValid() &&
pVehicle->GetModelIndex() == MI_SUB_KOSATKA )
{
TUNE_GROUP_FLOAT( SUBMARINE_TUNE, BIG_SUB_SUBMERGE_LEVEL_FOR_PITCH, 0.3f, 0.0f, 1.0f, 0.1f );
m_fPitchControl *= Max( 0.0f, ( fSubmergeLevel - BIG_SUB_SUBMERGE_LEVEL_FOR_PITCH ) / ( 1.0f - BIG_SUB_SUBMERGE_LEVEL_FOR_PITCH ) );
}
// If we are breaking the surface, we apply some forces here to avoid the bounce back from the sudden drop in
// buoyancy. This makes it easier to pilot the sub up to the surface and skim along it.
TUNE_FLOAT(sfSubStickToSurfaceAtSubmergeFactor, 0.5f, 0.1f, 1.0f, 0.1f);
TUNE_FLOAT(sfSubStickToSurfaceAtSubmergeFactorSurfaceMode, 0.2f, 0.1f, 1.0f, 0.1f);
if(fSubmergeLevel < 1.0f)
{
// Whether we apply forces or not depends on the player control inputs.
dev_float sfPitchThreshold = -0.1f;
if(!(GetDiveControl() < 0.0f || GetPitchControl() < 0.0f || pVehicle->GetVehicleForwardDirection().GetZf() < sfPitchThreshold))
{
Vector3 vSubVelocity = pVehicle->GetVelocity();
float fSubStickToSurfaceAtSubmergeFactor = bSurfaceMode ? sfSubStickToSurfaceAtSubmergeFactorSurfaceMode : sfSubStickToSurfaceAtSubmergeFactor;
if(vSubVelocity.z < 0.0f && fSubmergeLevel > fSubStickToSurfaceAtSubmergeFactor)
{
vSubVelocity.x = vSubVelocity.y = 0.0f;
vSubVelocity.z *= -1.0f*pVehicle->GetMass();
// Scale quadratically based on submerge level to avoid sudden bump with stick to surface force.
float temp = 1.0f/sfSubStickToSurfaceAtSubmergeFactor;
float x = rage::Clamp(fSubmergeLevel*temp - 0.5f*temp, 0.0f, 1.0f);
vSubVelocity.z *= x*x;
pVehicle->ApplyImpulseCg(vSubVelocity);
}
}
TUNE_GROUP_FLOAT( SUBMARINE_TUNE, AUTO_ANCHOR_DEPTH, 0.5f, 0.0f, 1.0f, 0.1f );
if( MI_SUB_KOSATKA.IsValid() &&
pVehicle->GetModelIndex() == MI_SUB_KOSATKA &&
!pVehicle->GetDriver() &&
fSubmergeLevel < AUTO_ANCHOR_DEPTH &&
!GetAnchorHelper().IsAnchored() )
{
if( GetAnchorHelper().CanAnchorHere( true ) )
{
GetAnchorHelper().Anchor( true );
}
}
}
}
}
float CSubmarineHandling::ProcessProps(CVehicle *pVehicle, float fTimeStep, float fDriveInWater)
{
bool isSubmarineCar = ( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR );
if( isSubmarineCar )
{
if( !static_cast< CSubmarineCar* >( pVehicle )->AreWheelsActive() )
{
pVehicle->m_Transmission.SelectAppropriateGearForSpeed();
}
}
// Thrust
// Want smooth engine changes
float fDriveForce = pVehicle->m_Transmission.ProcessSubmarine(pVehicle, fDriveInWater, fTimeStep);
// Now update propellers with drive force (for rotation)
// Todo: transmission should handle idle revs
// Clutch should be down at idle?
float fPropSpeed = rage::Clamp(pVehicle->m_Transmission.GetRevRatio() - CTransmission::ms_fIdleRevs / (1.0f - CTransmission::ms_fIdleRevs),0.0f,1.0f);
TUNE_FLOAT(sfDefaultPropSpeed, 24.0f, 0.0f, 100.0f, 0.1f);
float fMainPropellerSpeed = sfDefaultPropSpeed;
float fTurnPropellerSpeed = sfDefaultPropSpeed;
if( isSubmarineCar )
{
fPropSpeed *= 10.0f;
}
if( MI_SUB_KOSATKA.IsValid() && pVehicle->GetModelIndex() == MI_SUB_KOSATKA )
{
TUNE_FLOAT(sfKosatkaPropSpeed, 10.0f, 0.0f, 100.0f, 0.1f);
fMainPropellerSpeed = sfKosatkaPropSpeed;
fTurnPropellerSpeed = sfKosatkaPropSpeed;
}
fPropSpeed *= fMainPropellerSpeed / pVehicle->m_Transmission.GetGearRatio(pVehicle->m_Transmission.GetGear());
if( MI_SUB_AVISA.IsValid() && pVehicle->GetModelIndex() == MI_SUB_AVISA )
{
for(int i = 0; i < m_iNumPropellers; i++)
{
// Left and right props help turn us
float fFinalPropSpeed = 0.0f;
int iLeftBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_LEFT);
int iRightBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_RIGHT);
if(iLeftBoneIndex > -1 && iLeftBoneIndex == m_propellers[i].GetBoneIndex())
{
fFinalPropSpeed = (fTurnPropellerSpeed*(-m_fYawControl));
}
else if(iRightBoneIndex > -1 && iRightBoneIndex == m_propellers[i].GetBoneIndex())
{
fFinalPropSpeed = (fTurnPropellerSpeed*(-m_fYawControl));
}
else if(m_propellers[i].GetBoneIndex() == pVehicle->GetBoneIndex(SUB_PROPELLER_3) || m_propellers[i].GetBoneIndex() == pVehicle->GetBoneIndex(SUB_PROPELLER_4))
{
fFinalPropSpeed = (fTurnPropellerSpeed*(-m_fDiveControl));
}
else
{
fFinalPropSpeed = fPropSpeed;
}
fFinalPropSpeed = rage::Clamp(fFinalPropSpeed,-fMainPropellerSpeed,fMainPropellerSpeed);
m_propellers[i].UpdatePropeller(fFinalPropSpeed,fTimeStep);
fDriveInWater += m_propellers[i].GetSubmergeFraction(pVehicle);
}
}
else if( MI_SUB_KOSATKA.IsValid() && pVehicle->GetModelIndex() == MI_SUB_KOSATKA )
{
for(int i = 0; i < m_iNumPropellers; i++)
{
// Left and right props help turn us
float fTurnMult = 0.0f;
int iLeftBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_1);
int iRightBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_2);
if(iLeftBoneIndex > -1 && iLeftBoneIndex == m_propellers[i].GetBoneIndex())
{
fTurnMult = -1.0f;
}
else if(iRightBoneIndex > -1 && iRightBoneIndex == m_propellers[i].GetBoneIndex())
{
fTurnMult = 1.0f;
}
// Can't go faster than main propeller speed
float fDesiredPropSpeed = rage::Clamp(fPropSpeed + (fTurnMult*fTurnPropellerSpeed*m_fYawControl),-fMainPropellerSpeed,fMainPropellerSpeed);
fDesiredPropSpeed *= -1.0f; // Flip the rotation direction as the propeller is modeled backwards.
float fCurrentPropSpeed = m_propellers[i].GetSpeed();
TUNE_FLOAT(sfPropChangeTimeFactor, 3.0f, -2.0f, 10.0f, 0.1f);
//Lerp towards new prop speed.
float fNewPropSpeed = fCurrentPropSpeed + (Clamp(fDesiredPropSpeed - fCurrentPropSpeed, -1.0f, 1.0f) * sfPropChangeTimeFactor * fTimeStep);
m_propellers[i].UpdatePropeller(fNewPropSpeed,fTimeStep);
fDriveInWater += m_propellers[i].GetSubmergeFraction(pVehicle);
}
}
else
{
for(int i = 0; i < m_iNumPropellers; i++)
{
// Left and right props help turn us
float fTurnMult = 0.0f;
int iLeftBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_LEFT);
if( isSubmarineCar )
{
iLeftBoneIndex = pVehicle->GetBoneIndex( SUBMARINECAR_PROPELLER_1 );
}
int iRightBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_RIGHT);
if( isSubmarineCar )
{
iRightBoneIndex = pVehicle->GetBoneIndex( SUBMARINECAR_PROPELLER_2 );
}
if(iRightBoneIndex < 0)
{
iRightBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_2);
}
// Only do this for subs with two main propellers like "submersible2" so as not to turn the main prop on "submersible" when
// just under yaw control.
if(iRightBoneIndex > -1 && iLeftBoneIndex < 0)
{
iLeftBoneIndex = pVehicle->GetBoneIndex(SUB_PROPELLER_1);
}
if(iLeftBoneIndex > -1 && iLeftBoneIndex == m_propellers[i].GetBoneIndex())
{
fTurnMult = -1.0f;
}
else if(iRightBoneIndex > -1 && iRightBoneIndex == m_propellers[i].GetBoneIndex())
{
fTurnMult = 1.0f;
}
// Can't go faster than main propeller speed
fTurnMult = rage::Clamp(fPropSpeed + (fTurnMult*fTurnPropellerSpeed*m_fYawControl),-fMainPropellerSpeed,fMainPropellerSpeed);
fTurnMult *= -1.0f; // Flip the rotation direction as the propeller is modeled backwards.
m_propellers[i].UpdatePropeller(fTurnMult,fTimeStep);
fDriveInWater += m_propellers[i].GetSubmergeFraction(pVehicle);
}
}
return fDriveForce;
}
void CSubmarineHandling::ProcessSubmarineHandling(CVehicle *pVehicle, float fTimeStep, float fDriveInWater)
{
const CSubmarineHandlingData* pSubHandling = pVehicle->pHandling->GetSubmarineHandlingData();
if(!physicsVerifyf(pSubHandling,"Can't find submarine handling"))
{
return;
}
// Submarine physics sim
// pitch and yaw
Vector3 vTorque;
if(fDriveInWater > 0.0f && pVehicle->IsInDriveableState())
{
if( (pVehicle->GetBoneIndex(SUB_RUDDER) > -1 && pVehicle->GetBoneIndex(SUB_ELEVATOR_L) > -1 ) ||
(pVehicle->GetBoneIndex(SUBMARINECAR_RUDDER_1) > -1 && pVehicle->GetBoneIndex(SUBMARINECAR_ELEVATOR_L) > -1 ) )
{
// New sub handling
dev_float SUB_FORCE_CLAMP = 20.0f;
// get propeller position (will get from bone)
Vector3 vecThrustPos = pVehicle->GetBoundingBoxMin();
vecThrustPos.x = 0.0f;
vecThrustPos.y = 0.0f;
vecThrustPos.z *= 0.9f;
vecThrustPos = pVehicle->TransformIntoWorldSpace(vecThrustPos);
Vector3 vecLocalSpeed;
vecLocalSpeed = pVehicle->GetLocalSpeed(vecThrustPos, true);
float fFwdDot = vecLocalSpeed.Dot( VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()) );
//float fFwdDotSq = fFwdDot * fFwdDot;
//fFwdDot = Min(fFwdDot, fFwdDotSq);
//fFwdDot = Min(fFwdDot, SUB_FORCE_CLAMP);
fFwdDot = Clamp( fFwdDot, -SUB_FORCE_CLAMP, SUB_FORCE_CLAMP );
float increasePitchUpForce = 1.0f;
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
// even if the submarine car is stationary apply a little bit of torque as the props still spin and it looks a
// bit weird that it doesn't turn.
dev_float SUB_CAR_FORCE_CLAMP_MIN = 1.0f;
if( Abs( fFwdDot ) < SUB_CAR_FORCE_CLAMP_MIN )
{
static dev_float SUB_CAR_INCREASE_STATIONARY_PITCH_UP_FORCE = 3.0f;
increasePitchUpForce = SUB_CAR_INCREASE_STATIONARY_PITCH_UP_FORCE;
if( fFwdDot < 0.0f )
{
fFwdDot = -SUB_CAR_FORCE_CLAMP_MIN;
}
else
{
fFwdDot = SUB_CAR_FORCE_CLAMP_MIN;
}
}
}
// Calculate rudder force
float fRudderForce = fFwdDot * pSubHandling->GetYawMult() * -m_fRudderAngle;
bool bGoingForward = fFwdDot > 0.0f;
if( bGoingForward )
{
fRudderForce *= -1.0f;
}
vTorque = fRudderForce * pVehicle->GetAngInertia().z * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC()) * fDriveInWater;
pVehicle->ApplyTorque(vTorque);
Vector3 angularInertia = pVehicle->GetAngInertia();
const Vector3 vecRight = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetA());
const Vector3 vecUp = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC());
if(MI_SUB_KOSATKA.IsValid() && pVehicle->GetModelIndex()==MI_SUB_KOSATKA)
{
// Yaw from bow thruster
Vector3 vBowThrusterTorque = m_fYawControl * pVehicle->GetAngInertia().z * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC()) * pSubHandling->GetYawMult() * fDriveInWater;
pVehicle->ApplyTorque(vBowThrusterTorque);
// pitch between some min and max pitch angle, we'll base this on the elevator angle
float fPitchAngle = bGoingForward ? m_fElevatorAngle : -m_fElevatorAngle;
float fPitch = bGoingForward ? pVehicle->GetTransform().GetPitch() : -pVehicle->GetTransform().GetPitch();
float fPitchDiff = fPitchAngle - fPitch;
fPitchDiff = fwAngle::LimitRadianAngleForPitch(fPitchDiff);
// Give us a pitch input from -1 -> 1. We want to scale the upward pitch such that we don't pitch up at all when on the surface.
Vector3 vObjectCentre;
pVehicle->GetBoundCentre(vObjectCentre);
float fDepth = pVehicle->m_Buoyancy.GetAvgAbsWaterLevel()-vObjectCentre.z;
fPitchDiff = rage::Clamp(fPitchDiff, -1.0f, 1.0f);
TUNE_FLOAT(sfDepthAboveWhichNoPitch, 1.2f, -2.0f, 10.0f, 0.1f);
TUNE_FLOAT(sfDepthBelowWhichFullPitch, 2.5f, -2.0f, 10.0f, 0.1f);
float fLerpRange = sfDepthBelowWhichFullPitch - sfDepthAboveWhichNoPitch;
float fLerpFraction = rage::Clamp(fDepth, sfDepthAboveWhichNoPitch, sfDepthBelowWhichFullPitch);
fLerpFraction = (fLerpFraction-sfDepthAboveWhichNoPitch)/fLerpRange;
fPitchDiff *= fLerpFraction;
//if any of the sub is above the surface reduce the force from the elevator even further
if(pVehicle->m_Buoyancy.GetStatus() == PARTIALLY_IN_WATER)
{
dev_float PARTIALLY_SURFACED_MULT = 0.6f;
if(fPitchDiff > 0.0f)
{
fPitchDiff *= PARTIALLY_SURFACED_MULT;
}
}
// Apply pitch.
vTorque = fFwdDot * vecRight * angularInertia.x * fPitchDiff * pSubHandling->GetPitchMult() * fDriveInWater;
pVehicle->ApplyTorque(vTorque);
}
else
{
// Calculate elevator force
float fElevatorForce = fFwdDot *pSubHandling->GetPitchMult() * -m_fElevatorAngle;
if(bGoingForward)
{
fElevatorForce *= -1.0f;
}
if( m_fElevatorAngle > 0.0f )
{
fElevatorForce *= increasePitchUpForce;
}
// If we're coming out of the water massively reduce the amount of torque we can apply
if(pVehicle->m_Buoyancy.GetStatus() == PARTIALLY_IN_WATER)
{
dev_float PARTIALLY_SURFACED_MULT = 0.6f;
if(fElevatorForce > 0.0f)//if we're trying to rise above the surface reduce the force from the elevator significantly
{
fElevatorForce *= PARTIALLY_SURFACED_MULT;
}
}
vTorque = fElevatorForce * angularInertia.x * vecRight * fDriveInWater;
pVehicle->ApplyTorque(vTorque);
}
// Roll slightly when yawing as it looks nice.
Vector3 vecRudderForce = vecRight;
vecRudderForce *= pSubHandling->GetRollMult() * -m_fRudderAngle * fFwdDot * angularInertia.y * -GRAVITY;
pVehicle->ApplyTorque( vecRudderForce, vecUp );
// also need to add some stabilization for roll (so sub naturally levels out)
//Vector3 torque = vecRight;
//torque *= pSubHandling->GetRollStab() * angularInertia.y * 0.5f * -GRAVITY;
//pVehicle->ApplyTorque( torque, vecUp );
float torqueMag = pSubHandling->GetRollStab() * angularInertia.y * 0.5f * -GRAVITY * vecRight.GetZ();
Vector3 torque = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()) * torqueMag;
pVehicle->ApplyTorque( torque );
}
else //Old sub steering still used for submersible
{
// pitch between some min and max pitch angle
float fPitchAngle = PI * pSubHandling->GetPitchAngle() / 180.0f;
float fPitchDiff = (m_fPitchControl * fPitchAngle) - pVehicle->GetTransform().GetPitch();
fPitchDiff = fwAngle::LimitRadianAngleForPitch(fPitchDiff);
// How fast does input saturate?
// Shouldn't need to tweak per vehicle
static dev_float fPitchSensitivity = 4.0f;
fPitchDiff *= fPitchSensitivity;
// Give us a pitch input from -1 -> 1. We want to scale the upward pitch such that we don't pitch up at all when on the surface.
Vector3 vObjectCentre;
pVehicle->GetBoundCentre(vObjectCentre);
float fDepth = pVehicle->m_Buoyancy.GetAvgAbsWaterLevel()-vObjectCentre.z;
#if __BANK
TUNE_BOOL(DEBUG_SUBMARINE_DEPTH, false);
if(DEBUG_SUBMARINE_DEPTH)
{
char zText[100];
sprintf(zText, "Depth: %5.3f", fDepth);
grcDebugDraw::Text(VEC3V_TO_VECTOR3(pVehicle->GetVehiclePosition()), Color_white, zText);
}
#endif // __BANK
fPitchDiff = rage::Clamp(fPitchDiff, -1.0f, 1.0f);
if(fPitchDiff > 0.0f)
{
TUNE_FLOAT(sfDepthAboveWhichNoPitch, 1.2f, -2.0f, 10.0f, 0.1f);
TUNE_FLOAT(sfDepthBelowWhichFullPitch, 2.5f, -2.0f, 10.0f, 0.1f);
float fLerpRange = sfDepthBelowWhichFullPitch - sfDepthAboveWhichNoPitch;
float fLerpFraction = rage::Clamp(fDepth, sfDepthAboveWhichNoPitch, sfDepthBelowWhichFullPitch);
fLerpFraction = (fLerpFraction-sfDepthAboveWhichNoPitch)/fLerpRange;
fPitchDiff *= fLerpFraction;
}
// Apply pitch.
vTorque = VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetA()) * pVehicle->GetAngInertia().x * fPitchDiff * pSubHandling->GetPitchMult() * fDriveInWater;
pVehicle->ApplyTorque(vTorque);
// Yaw
vTorque = m_fYawControl * pVehicle->GetAngInertia().z * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC()) * pSubHandling->GetYawMult() * fDriveInWater;
pVehicle->ApplyTorque(vTorque);
}
}
// Thrust
// Want smooth engine changes
float fDriveForce = ProcessProps(pVehicle, fTimeStep, fDriveInWater);
if(fDriveForce != 0.0f)
{
vTorque = fDriveForce*pVehicle->GetMass()*VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
pVehicle->ApplyForceCg(vTorque);
}
}
bool CSubmarineHandling::WantsToBeAwake(CVehicle *pVehicle)
{
if(pVehicle->m_Buoyancy.GetBuoyancyInfoUpdatedThisFrame())
{
return pVehicle->m_Buoyancy.GetSubmergedLevel() > 0.0f;
}
else
{
if(pVehicle->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate)
{
return pVehicle->m_nFlags.bPossiblyTouchesWater;
}
else
{
return pVehicle->GetIsInWater();
}
}
}
void CSubmarineHandling::PreRender(CVehicle *pVehicle, const bool UNUSED_PARAM(bIsVisibleInMainViewport))
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), pVehicle );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition()) );
if(!pVehicle->IsDummy())
{
// vehicle damage fx
pVehicle->GetVehicleDamage()->PreRender();
}
pVehicle->SetComponentRotation(SUB_RUDDER, ROT_AXIS_Z, -m_fRudderAngle, true);
pVehicle->SetComponentRotation(SUB_RUDDER2, ROT_AXIS_Z, -m_fRudderAngle, true);
pVehicle->SetComponentRotation(SUB_ELEVATOR_L, ROT_AXIS_X, -m_fElevatorAngle, true);
pVehicle->SetComponentRotation(SUB_ELEVATOR_R, ROT_AXIS_X, -m_fElevatorAngle, true);
for(int i=0; i<pVehicle->GetNumDoors(); i++)
{
pVehicle->GetDoor(i)->ProcessPreRender(pVehicle);
}
PreRenderPropellers( pVehicle );
if (IsSubLeakingAir())
{
float damageEvo = Min(1.0f, GetNumberOfAirLeaks()/20.0f);
g_vfxWater.UpdatePtFxSubmarineAirLeak(static_cast<CSubmarine*>(pVehicle), damageEvo);
}
}
void CSubmarineHandling::PreRender2(CVehicle *pVehicle, const bool UNUSED_PARAM(bIsVisibleInMainViewport))
{
//Check to see if light and camera are underwater together or not.
//If not then we reduce the intensity and size of headlight Corona
CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)pVehicle->GetBaseModelInfo();
Vector3 leftBonePos;
Vector3 rightBonePos;
s32 boneIdLeft = VEH_HEADLIGHT_L;
s32 boneIdRight = VEH_HEADLIGHT_R;
s32 boneIdxLeft = pModelInfo->GetBoneIndex(boneIdLeft);
s32 boneIdxRight = pModelInfo->GetBoneIndex(boneIdRight);
pVehicle->GetDefaultBonePositionSimple(boneIdxLeft,leftBonePos);
pVehicle->GetDefaultBonePositionSimple(boneIdxRight,rightBonePos);
//Using only mid position between lights as its cheaper
Vector3 lightPos = (leftBonePos + rightBonePos) *0.5f;
lightPos = pVehicle->TransformIntoWorldSpace(lightPos);
float oceanWaterZ = 0.0f;
bool isLightUnderwater = false;
bool foundOceanWater = CVfxHelper::GetOceanWaterZ(RCC_VEC3V(lightPos), oceanWaterZ);
if (foundOceanWater)
{
float oceanWaterDepth = Max(0.0f, oceanWaterZ-lightPos.z);
isLightUnderwater = oceanWaterDepth>0.0f;
}
// Kosatka: keep external lights always on (when above water surface): B*6786266 - Kosatka - Headlight on top of the tower to be on all the time during the night
if(pVehicle->GetModelNameHash() == MID_KOSATKA)
{
pVehicle->m_OverrideLights = isLightUnderwater? NO_CAR_LIGHT_OVERRIDE : FORCE_CAR_LIGHTS_ON;
}
// don't need to update the lights here for submarine car as the automobile will do them
if( pVehicle->GetVehicleType() != VEHICLE_TYPE_SUBMARINECAR )
{
u32 nLightFlags = LIGHTS_USE_EXTRA_AS_HEADLIGHTS;
if( camInterface::IsDominantRenderedCameraAnyFirstPersonCamera() && pVehicle->ContainsLocalPlayer() )
{
nLightFlags |= LIGHTS_IGNORE_INTERIOR_LIGHT;
}
else
{
nLightFlags |= LIGHTS_FORCE_INTERIOR_LIGHT;
}
if(Water::IsCameraUnderwater())
{
nLightFlags |= (LIGHTS_USE_VOLUMETRIC_LIGHTS | LIGHTS_FORCE_LOWRES_VOLUME);
}
if(Water::IsCameraUnderwater() != isLightUnderwater)
{
nLightFlags |= LIGHTS_UNDERWATER_CORONA_FADE;
}
pVehicle->DoVehicleLights(nLightFlags);
}
// vfx
if( m_fDiveControl<0.0f &&
pVehicle->GetVehicleType() != VEHICLE_TYPE_SUBMARINECAR )
{
g_vfxWater.UpdatePtFxSubmarineDive(static_cast<CSubmarine*>(pVehicle), -m_fDiveControl);
}
}
void CSubmarineHandling::PreRenderPropellers( CVehicle* pVehicle )
{
for( int i = 0; i < m_iNumPropellers; i++ )
{
m_propellers[i].PreRender( pVehicle );
// process propeller vfx
s32 propBoneIndex = m_propellers[i].GetBoneIndex();
g_vfxWater.UpdatePtFxPropeller(pVehicle, i, propBoneIndex, m_propellers[i].GetSpeed());
}
}
//
//
//
static float s_subCarSteerModMult = 1.75f;
void CSubmarineHandling::UpdateRudder(CVehicle *pVehicle)
{
float fSpeedSteerMod = 1.0f;
float fSpeeedSteerModMult = 0.5f*pVehicle->pHandling->m_fTractionBiasFront;
float fSteerAngle = m_fYawControl * pVehicle->pHandling->m_fSteeringLock;
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
fSteerAngle = Clamp( fSteerAngle, -pVehicle->pHandling->m_fSteeringLock, pVehicle->pHandling->m_fSteeringLock );
fSpeeedSteerModMult *= s_subCarSteerModMult;
}
Assert(fSpeeedSteerModMult < 1.0f && fSpeeedSteerModMult > 0.0f);
fSpeedSteerMod = DotProduct(pVehicle->GetVelocity(), VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()));
if(fSpeedSteerMod > 1.0f)
{
fSpeedSteerMod = rage::Powf(fSpeeedSteerModMult, fSpeedSteerMod);
}
else
{
fSpeedSteerMod = 1.0f;
}
// rudder angle
float fDesiredRudderAngle = fSteerAngle * fSpeedSteerMod;
m_fRudderAngle += rage::Clamp(fDesiredRudderAngle - m_fRudderAngle, -fwTimer::GetTimeStep() * ms_fControlSmoothSpeed, fwTimer::GetTimeStep() * ms_fControlSmoothSpeed);
}
//
//
//
void CSubmarineHandling::UpdateElevators(CVehicle *pVehicle)
{
float fSpeedSteerMod = 1.0f;
float fSpeeedSteerModMult = 0.5f*pVehicle->pHandling->m_fTractionBiasFront;
float fSteerAngle = m_fPitchControl * pVehicle->pHandling->m_fSteeringLock;
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR )
{
fSpeeedSteerModMult *= s_subCarSteerModMult;
}
Assert(fSpeeedSteerModMult < 1.0f && fSpeeedSteerModMult > 0.0f);
fSpeedSteerMod = DotProduct(pVehicle->GetVelocity(), VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()));
if(fSpeedSteerMod > 1.0f)
{
fSpeedSteerMod = rage::Powf(fSpeeedSteerModMult, fSpeedSteerMod);
}
else
{
fSpeedSteerMod = 1.0f;
}
// elevator angle
float fDesiredElevatorAngle = fSteerAngle * fSpeedSteerMod;
m_fElevatorAngle += rage::Clamp(fDesiredElevatorAngle - m_fElevatorAngle, -fwTimer::GetTimeStep() * ms_fControlSmoothSpeed, fwTimer::GetTimeStep() * ms_fControlSmoothSpeed);
}
void CSubmarineHandling::ApplyDeformationToBones(CVehicle *pVehicle, const void* basePtr)
{
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellers[i].ApplyDeformation(pVehicle ,basePtr);
}
}
void CSubmarineHandling::Fix(CVehicle *pVehicle, bool UNUSED_PARAM(resetFrag))
{
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellers[i].Fix();
}
m_nAirLeaks = 0;
pVehicle->m_nVehicleFlags.bIsDrowning = false;
m_fMaxDepthReached = 0.0f;
}
#if DISPLAY_CRUSH_DEPTH_HELP_TEXT
void CSubmarineHandling::DisplayCrushDepthMessages(CVehicle *pVehicle)
{
if(IsUnderCrushDepth(pVehicle))
{
CHelpMessage::SetMessageTextAndAddToBrief(HELP_TEXT_SLOT_STANDARD, "SUB_CRUSH_WARN_3");
SetDisplayingCrushWarningFlag(true);
}
else if(IsUnderAirLeakDepth(pVehicle))
{
CHelpMessage::SetMessageTextAndAddToBrief(HELP_TEXT_SLOT_STANDARD, "SUB_CRUSH_WARN_2");
SetDisplayingCrushWarningFlag(true);
}
else if(IsUnderDesignDepth(pVehicle))
{
CHelpMessage::SetMessageTextAndAddToBrief(HELP_TEXT_SLOT_STANDARD, "SUB_CRUSH_WARN_1");
SetDisplayingCrushWarningFlag(true);
}
else
{
if(GetIsDisplayingCrushWarning())
{
CHelpMessage::Clear(HELP_TEXT_SLOT_STANDARD, true);
SetDisplayingCrushWarningFlag(false);
}
}
}
#endif // DISPLAY_CRUSH_DEPTH_HELP_TEXT
//////////////////////////////////////////////////////////////////////////
// Class CSubmarine
//
CSubmarine::CSubmarine(const eEntityOwnedBy ownedBy, u32 popType) : CVehicle(ownedBy, popType, VEHICLE_TYPE_SUBMARINE)
{
m_nVehicleFlags.bCanMakeIntoDummyVehicle = false;
m_nVehicleFlags.bUnbreakableLights = true;
m_nVehicleFlags.bUseDeformation = true;
m_SubHandling.SetMinDepthToWreck( 0.8f );
}
//
//
//
void CSubmarine::InitCompositeBound()
{
CVehicle::InitCompositeBound();
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
// B*2009160: Allow the windscreen to have normal vehicle collision on subs.
// submersible2 has a glass bubble that is used as a windscreen and it sticks out from the main chassis quite a lot.
if(GetBoneIndex(VEH_WINDSCREEN) > -1)
{
int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex(VEH_WINDSCREEN));
if(nChildIndex != -1)
{
pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
//Make sure the exposed rudder on the Kosatka collides against stuff.
if(GetBoneIndex(SUB_RUDDER2) > -1)
{
int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex(SUB_RUDDER2));
if(nChildIndex != -1)
{
pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
}
//
//
//
void CSubmarine::InitDoors()
{
CVehicle::InitDoors();
Assert(GetNumDoors()==0);
m_pDoors = m_aSubDoors;
m_nNumDoors = 0;
float fOpenDoorAngle = 1.0f*PI;
u32 uInitFlags = CCarDoor::AXIS_Y|CCarDoor::WILL_LOCK_SWINGING;
//Apparently we don't want to break off doors on this massive sub url:bugstar:6715420
if( MI_SUB_KOSATKA.IsValid() && GetModelIndex() == MI_SUB_KOSATKA)
{
uInitFlags |= CCarDoor::DONT_BREAK;
}
if(GetBoneIndex(VEH_DOOR_DSIDE_F) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_DSIDE_F, 0.0f, fOpenDoorAngle, uInitFlags);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_PSIDE_F) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_PSIDE_F, 0.0f, -fOpenDoorAngle, uInitFlags);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_DSIDE_R) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_DSIDE_R, 0.0f, fOpenDoorAngle, uInitFlags);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_PSIDE_R) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_PSIDE_R, 0.0f, -fOpenDoorAngle, uInitFlags);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_BOOT) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_BOOT, 0.0f, fOpenDoorAngle, uInitFlags);
m_nNumDoors++;
}
}
//
//
//
void CSubmarine::BlowUpCar(CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool ASSERT_ONLY(bNetCall), u32 weaponHash, bool bDelayedExplosion)
{
CVehicle::BlowUpCar(pCulprit);
#if __DEV
if (gbStopVehiclesExploding)
{
return;
}
#endif
if (GetStatus() == STATUS_WRECKED)
{
return; // Don't blow cars up a 2nd time
}
// we can't blow up boats controlled by another machine
// but we still have to change their status to wrecked
// so the boat doesn't blow up if we take control of an
// already blown up car
if (IsNetworkClone())
{
Assertf(bNetCall, "Trying to blow up clone %s", GetNetworkObject()->GetLogName());
KillPedsInVehicle(pCulprit, weaponHash);
KillPedsGettingInVehicle(pCulprit);
m_nPhysicalFlags.bRenderScorched = TRUE;
SetTimeOfDestruction();
SetIsWrecked();
// Switch off the engine. (For sound purposes)
this->SwitchEngineOff(false);
this->m_nVehicleFlags.bLightsOn = FALSE;
g_decalMan.Remove(this);
//Check to see that it is the player
if (pCulprit && pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer())
{
CStatsMgr::RegisterVehicleBlownUpByPlayer(this);
}
return;
}
if (NetworkUtils::IsNetworkCloneOrMigrating(this))
{
// the vehicle is migrating. Create a weapon damage event to blow up the vehicle, which will be sent to the new owner. If the migration fails
// then the vehicle will be blown up a little later.
CBlowUpVehicleEvent::Trigger(*this, pCulprit, bAddExplosion, weaponHash, bDelayedExplosion);
return;
}
//Total damage done for the damage trackers
float totalDamage = GetHealth() + m_VehicleDamage.GetEngineHealth() + m_VehicleDamage.GetPetrolTankHealth();
for(s32 i=0; i<GetNumWheels(); i++)
{
totalDamage += m_VehicleDamage.GetTyreHealth(i);
totalDamage += m_VehicleDamage.GetSuspensionHealth(i);
}
totalDamage = totalDamage > 0.0f ? totalDamage : 1000.0f;
// AddToMoveSpeedZ(0.13f);
ApplyImpulseCg(Vector3(0.0f, 0.0f, 6.5f));
SetWeaponDamageInfo(pCulprit, weaponHash, fwTimer::GetTimeInMilliseconds());
//Set the destruction information.
SetDestructionInfo(pCulprit, weaponHash);
SetIsWrecked();
this->m_nPhysicalFlags.bRenderScorched = TRUE; // need to make Scorched BEFORE components blow off
// do it before SpawnFlyingComponent() so this bit is propagated to all vehicle parts before they go flying:
// let know pipeline, that we don't want any extra passes visible for this clump anymore:
// CVisibilityPlugins::SetClumpForAllAtomicsFlag((RpClump*)this->m_pRwObject, VEHICLE_ATOMIC_PIPE_NO_EXTRA_PASSES); rage
SetHealth(0.0f); // Make sure this happens before AddExplosion or it will blow up twice
KillPedsInVehicle(pCulprit, weaponHash);
// Switch off the engine. (For sound purposes)
this->SwitchEngineOff(false);
this->m_nVehicleFlags.bLightsOn = FALSE;
if( bAddExplosion )
{
AddVehicleExplosion(pCulprit, bInACutscene, bDelayedExplosion);
}
//Update Damage Trackers
GetVehicleDamage()->UpdateDamageTrackers(pCulprit, weaponHash, DAMAGE_TYPE_EXPLOSIVE, totalDamage, false);
//Check to see that it is the player
if (pCulprit && ((pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer()) || pCulprit == CGameWorld::FindLocalPlayerVehicle()))
{
CStatsMgr::RegisterVehicleBlownUpByPlayer(this);
}
g_decalMan.Remove(this);
CPed* fireCulprit = NULL;
if (pCulprit && pCulprit->GetIsTypePed())
{
fireCulprit = static_cast<CPed*>(pCulprit);
}
g_vfxVehicle.ProcessWreckedFires(this, fireCulprit, FIRE_DEFAULT_NUM_GENERATIONS);
}
//
//
//
bool CSubmarine::PlaceOnRoadAdjustInternal(float HeightSampleRangeUp, float HeightSampleRangeDown, bool UNUSED_PARAM(bJustSetCompression))
{
Matrix34 matrix = MAT34V_TO_MATRIX34(GetMatrix());
const int nInitialNumResults = 8;
WorldProbe::CShapeTestProbeDesc probeDesc;
WorldProbe::CShapeTestFixedResults<nInitialNumResults> probeResults;
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetStartAndEnd(matrix.d + HeightSampleRangeUp*ZAXIS, matrix.d - HeightSampleRangeDown*ZAXIS);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
int nBestHit = -1;
float fBestDeltaZ = LARGE_FLOAT;
WorldProbe::ResultIterator it;
int i = 0;
for(it = probeResults.begin(); it < probeResults.end(); ++it, ++i)
{
if(it->GetHitDetected() && rage::Abs((it->GetHitPosition() - matrix.d).z) < fBestDeltaZ)
{
nBestHit = i;
fBestDeltaZ = rage::Abs((it->GetHitPosition() - matrix.d).z);
}
}
if(nBestHit > -1)
{
float fHeightAboveRoad = -GetBaseModelInfo()->GetBoundingBoxMin().z;
if(GetVehicleFragInst() && GetFragmentComponentIndex(VEH_CHASSIS) > -1)
{
const phBoundComposite* pBoundComp = GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds();
fHeightAboveRoad = -pBoundComp->GetBound(GetFragmentComponentIndex(VEH_CHASSIS))->GetBoundingBoxMax().GetZf();
}
matrix.d.z = probeResults[nBestHit].GetHitPosition().z + CONCAVE_DISTANCE_MARGIN + GetHeightAboveRoad();
SetMatrix(matrix, true, true, true);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
// Class CSubmarineCar
//
CSubmarineCar::CSubmarineCar(const eEntityOwnedBy ownedBy, u32 popType) : CAutomobile(ownedBy, popType, VEHICLE_TYPE_SUBMARINECAR)
{
m_InSubmarineMode = false;
m_AnimationPhase = 0.0f;
m_AnimationState = State_Finished;
m_PrevElevatorRotation = 0.0f;
m_PrevYawControl = 0.0f;
m_SubHandling.SetMinDepthToWreck( 0.8f );
m_LastEngineModeSwitchTime = 0u;
m_RudderAngleClunkRetriggerValid = true;
m_SetWheelsDown = true;
m_SetWheelsUp = false;
m_UpdateCollision = false;
}
//
//
//
CSubmarineCar::~CSubmarineCar()
{
}
ePrerenderStatus CSubmarineCar::PreRender( const bool bIsVisibleInMainViewport )
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(GetTransform().GetPosition()) );
if( m_SetWheelsDown )
{
m_AnimationPhase = 0.0f;
UpdateWheels( 0.0f );
UpdateWheelCovers( 1.0f );
UpdateSuspensionPositions();
m_UpdateCollision = true;
m_SetWheelsDown = false;
}
else if( m_SetWheelsUp )
{
m_AnimationPhase = 1.0f;
UpdateWheels( 1.0f );
UpdateWheelCovers( 0.0f );
UpdateSuspensionPositions();
m_UpdateCollision = true;
m_SetWheelsUp = false;
}
if(!IsDummy())
{
// vehicle damage fx
GetVehicleDamage()->PreRender();
}
float rudderAngle = m_SubHandling.GetRudderAngle();
float elevatorAngle = m_SubHandling.GetElevatorAngle();
SetComponentRotation( SUBMARINECAR_RUDDER_1, ROT_AXIS_Z, rudderAngle, true );
SetComponentRotation( SUBMARINECAR_RUDDER_2, ROT_AXIS_Z, rudderAngle, true );
f32 yawControl = m_SubHandling.GetYawControl();
// If we're applying directional control and a) we weren't before or b) we changed direction, allow for a thunk sound
if((Abs(yawControl) > 0.f && (m_PrevYawControl == 0 || (m_PrevYawControl != 0 && Sign(yawControl) != Sign(m_PrevYawControl)))))
{
m_RudderAngleClunkRetriggerValid = true;
}
// If we're waiting on a thunk and the yaw control is being applied, or we've released the yaw control after having it heavily applied, actually play the thunk
if((yawControl == 0 && Abs(m_PrevYawControl) > 0.f && Abs(rudderAngle) > 0.4f) || (Abs(yawControl) > 0.01f && m_RudderAngleClunkRetriggerValid))
{
if(m_VehicleAudioEntity)
{
m_VehicleAudioEntity->TriggerSubmarineCarRudderTurnStart();
}
m_RudderAngleClunkRetriggerValid = false;
}
m_PrevYawControl = yawControl;
float finalElevatorAngle = elevatorAngle - ( rudderAngle * 0.3f );
const float maxElevatorAngle = pHandling->m_fSteeringLock;
finalElevatorAngle = Clamp( finalElevatorAngle, -maxElevatorAngle, maxElevatorAngle );
SetComponentRotation( SUBMARINECAR_ELEVATOR_L, ROT_AXIS_X, finalElevatorAngle, true );
finalElevatorAngle = elevatorAngle + ( rudderAngle * 0.3f );
finalElevatorAngle = Clamp( finalElevatorAngle, -maxElevatorAngle, maxElevatorAngle );
SetComponentRotation( SUBMARINECAR_ELEVATOR_R, ROT_AXIS_X, finalElevatorAngle, true );
if(Abs(m_PrevElevatorRotation) < (maxElevatorAngle * 0.95f) && Abs(finalElevatorAngle) >= (maxElevatorAngle * 0.95f))
{
if(m_VehicleAudioEntity)
{
m_VehicleAudioEntity->TriggerSubmarineCarWingFlapSound();
}
}
m_PrevElevatorRotation = finalElevatorAngle;
if( m_InSubmarineMode )
{
m_SubHandling.PreRenderPropellers( this );
}
switch( m_AnimationState )
{
// retract / extend the suspension if we're transforming
case State_MoveWheelsUp:
case State_MoveWheelsDown:
{
UpdateSuspensionPositions();
// fall through to also update the steering angle
}
case State_MoveWheelCoversIn:
case State_MoveWheelCoversOut:
{
UpdateSteeringAngle();
break;
}
default:
break;
}
return CAutomobile::PreRender( bIsVisibleInMainViewport );
}
void CSubmarineCar::PreRender2(const bool bIsVisibleInMainViewport)
{
// if we're animating the wheels we need to update their positions after the automobile prerender to made sure they're where we want them
//if( ( m_wheelOffset > 0.0f && m_wheelOffset < ms_wheelUpOffset ) ||
// ( !m_InSubmarineMode && m_wheelOffset != 0.0f ) )
Vector3 bonePosition = VEC3_ZERO;
float animationPhase = m_AnimationPhase;
switch( m_AnimationState )
{
// retract / extend the suspension if we're transforming
case State_MoveWheelCoversOut:
{
UpdateWheelCovers( 1.0f - animationPhase );
// fall through so that the wheels are kept in the correct position
animationPhase = 1.0f;
}
case State_MoveWheelsUp:
{
UpdateWheels( animationPhase );
break;
}
case State_MoveWheelCoversIn:
{
UpdateWheelCovers( 1.0f - animationPhase );
// fall through so that the wheels are kept in the correct position
animationPhase = 1.0f;
}
case State_MoveWheelsDown:
{
UpdateWheels( animationPhase );
break;
}
case State_Finished:
{
if( m_InSubmarineMode )
{
UpdateWheels( animationPhase );
break;
}
}
default:
break;
}
m_SubHandling.PreRender2(this, bIsVisibleInMainViewport);
CAutomobile::PreRender2(bIsVisibleInMainViewport);
}
void CSubmarineCar::CheckForAudioModeSwitch(bool UNUSED_PARAM(isFocusVehicle) BANK_ONLY(, bool forceBoat))
{
audVehicleAudioEntity* vehicleAudioEntity = m_VehicleAudioEntity;
if(vehicleAudioEntity && vehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_CAR)
{
audCarAudioEntity* carAudioEntity = (audCarAudioEntity*)vehicleAudioEntity;
const bool inBoatMode = carAudioEntity->IsInAmphibiousBoatMode();
const bool shouldBeInBoatMode = m_InSubmarineMode BANK_ONLY(|| forceBoat);
bool modeSwitchValid = true;
if(inBoatMode && !shouldBeInBoatMode)
{
modeSwitchValid = !IsInAir(true);
}
if(modeSwitchValid && inBoatMode != shouldBeInBoatMode)
{
if(fwTimer::GetTimeInMilliseconds() - m_LastEngineModeSwitchTime > 1000)
{
m_LastEngineModeSwitchTime = fwTimer::GetTimeInMilliseconds();
carAudioEntity->SetInAmphibiousBoatMode(shouldBeInBoatMode);
}
}
}
}
//
//
// physics
ePhysicsResult CSubmarineCar::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
if( CPhysics::GetIsLastTimeSlice( nTimeSlice ) )
{
UpdateAnimationPhase();
}
ePhysicsResult result = PHYSICS_DONE;
if( !m_InSubmarineMode )
{
if( GetCollider() )
{
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, true);
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, true);
}
result = CAutomobile::ProcessPhysics(fTimeStep, bCanPostpone, nTimeSlice);
}
else
{
if( GetCollider() )
{
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, false);
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false);
}
if( CPhysics::GetIsLastTimeSlice( nTimeSlice ) )
{
if( GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE && (m_nVehicleFlags.bAnimateWheels==0) )
{
float fAnimWeight = 1.0f;
float fSteerAngle = m_SubHandling.GetYawControl();
if( GetDriver() )
{
CTaskMotionBase *pCurrentMotionTask = GetDriver()->GetCurrentMotionTask();
if (pCurrentMotionTask && pCurrentMotionTask->GetTaskType() == CTaskTypes::TASK_MOTION_IN_AUTOMOBILE)
{
const CTaskMotionInAutomobile* pAutoMobileTask = static_cast<const CTaskMotionInAutomobile*>(pCurrentMotionTask);
fAnimWeight = pAutoMobileTask->GetSteeringWheelWeight();
fSteerAngle = pAutoMobileTask->GetSteeringWheelAngle();
}
}
SetComponentRotation( VEH_CAR_STEERING_WHEEL, ROT_AXIS_LOCAL_Y, -fSteerAngle * (GetVehicleModelInfo()->GetMaxSteeringWheelAngle() * DtoR) * fAnimWeight, true );
}
}
if( HasRocketBoost() )
{
ProcessRocketBoost();
}
ProcessBoostPhysics( fTimeStep );
result = m_SubHandling.ProcessPhysics(this, fTimeStep, bCanPostpone, nTimeSlice);
//For the Toreador we need extra stabilization even after the rocket has ended to make sure the vehicle doesnt pitch up
TUNE_INT(siMinTimeToKeepStabilisingPostRocketBoost, 1250, 0, 200000, 1);
bool bPostBoostStabilisation = IsRocketBoosting() || (fwTimer::GetTimeInMilliseconds() < ( GetBoostToggleTime() + siMinTimeToKeepStabilisingPostRocketBoost ));
if( GetIsInWater() && (CPhysics::ms_bInStuntMode || bPostBoostStabilisation) )
{
static dev_float sfExtraStablisationTorque = 2.0f;
static dev_float sfMinVelocityForExtraStablisation = 100.0f;
static dev_float sfMaxVelocityForExtraStablisation = 1600.0f;
float velocity2 = GetVelocity().Mag2();
if( velocity2 > sfMinVelocityForExtraStablisation )
{
float scaleForInput = 1.0f - Abs( m_SubHandling.GetPitchControl() );
float extraTorque = sfExtraStablisationTorque * ( ( velocity2 - sfMinVelocityForExtraStablisation ) / ( sfMaxVelocityForExtraStablisation - sfMinVelocityForExtraStablisation ) );
float pitchAmount = -GetTransform().GetB().GetZf();
if( pitchAmount * m_SubHandling.GetPitchControl() < 0.0f )
{
extraTorque *= scaleForInput;
}
extraTorque *= GetAngInertia().x;
extraTorque *= pitchAmount;
extraTorque /= fTimeStep;
float currentMomentum = GetAngInertia().x * GetAngVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetRight() ) );
extraTorque -= currentMomentum;
Vector3 torque = VEC3V_TO_VECTOR3( GetTransform().GetA() ) * extraTorque;
ApplyTorque( torque );
}
}
}
if( m_AnimationState != State_Finished ||
m_UpdateCollision )
{
UpdateExtraCollision();
}
return result;
}
//
//
//
void CSubmarineCar::ProcessPostPhysics()
{
CAutomobile::ProcessPostPhysics();
}
void CSubmarineCar::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
m_SubHandling.DoProcessControl(this, fullUpdate, fFullUpdateTimeStep);
CAutomobile::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
if( m_AnimationState != State_Finished )
{
float throttleRatio = 1.0f;
if( m_AnimationState == State_MoveWheelsDown ||
m_AnimationState == State_MoveWheelsUp )
{
throttleRatio = 1.0f - ( m_AnimationPhase );
}
float throttle = GetThrottle();
throttle *= ( throttleRatio * throttleRatio );
SetThrottle( throttle );
}
}
//
//
//
bool CSubmarineCar::WantsToBeAwake()
{
if( m_AnimationPhase > 0.0f )
{
return true;
}
if(GetIsInWater() || m_nFlags.bPossiblyTouchesWater)
{
return m_SubHandling.WantsToBeAwake(this);
}
else
{
return CAutomobile::WantsToBeAwake();
}
}
void CSubmarineCar::Fix( bool resetFrag, bool allowNetwork )
{
m_SubHandling.Fix(this, resetFrag);
CAutomobile::Fix(resetFrag,allowNetwork);
m_SetWheelsUp = m_InSubmarineMode;
m_SetWheelsDown = !m_SetWheelsUp;
}
bool CSubmarineCar::AreWheelsActive()
{
// the wheels aren't active if we're in submarine mode
if( m_InSubmarineMode )
{
return false;
}
// if we're not in submarine mode but the animation is complete
// then we must be fully in car mode and the wheels are active
if( m_AnimationState == State_Finished )
{
return true;
}
// if we're moving the wheel covers in or out then the wheels can't be active.
if( m_AnimationState == State_MoveWheelCoversIn )
{
return false;
}
if( m_AnimationState == State_MoveWheelCoversOut )
{
return false;
}
// if we're raising / lowering the wheels and they're nearly all the way up
// then they can't be active
if( m_AnimationPhase > 0.9f )
{
return false;
}
return true;
}
void CSubmarineCar::SetSubmarineMode( bool submarine, bool forceWheelPosition )
{
if( forceWheelPosition )
{
m_SetWheelsDown = !submarine;
m_SetWheelsUp = submarine;
SetAnimationState(State_Finished);
}
m_InSubmarineMode = submarine;
}
void CSubmarineCar::SetTransformingToSubmarine(bool instantTransform)
{
if( m_AnimationState == State_MoveWheelCoversIn )
{
SetAnimationState(State_MoveWheelCoversOut, instantTransform);
}
else if( m_AnimationState == State_MoveWheelsDown )
{
SetAnimationState(State_MoveWheelsUp, instantTransform);
}
else
{
m_AnimationPhase = 0.0f;
SetAnimationState(State_StartToSubmarine, instantTransform);
}
}
void CSubmarineCar::SetTransformingToCar(bool instantTransform)
{
if( m_AnimationState == State_MoveWheelCoversOut )
{
SetAnimationState(State_MoveWheelCoversIn, instantTransform);
}
else if( m_AnimationState == State_MoveWheelsUp )
{
SetAnimationState(State_MoveWheelsDown, instantTransform);
}
else
{
m_AnimationPhase = 1.0f;
SetAnimationState(State_StartToCar, instantTransform);
}
}
void CSubmarineCar::SetAnimationState(AnimationState newState, bool instantTransform)
{
if(m_AnimationState != newState)
{
m_AnimationState = newState;
if(m_VehicleAudioEntity && !instantTransform)
{
m_VehicleAudioEntity->OnSubmarineCarAnimationStateChanged(newState, m_AnimationPhase);
}
}
}
static float s_moveCoverAnimSpeedModifier = 1.5f;
void CSubmarineCar::UpdateAnimationPhase()
{
float maximumChange = fwTimer::GetTimeStep() * ms_animationSpeed;
static dev_float wheelMovementScale = 1.0f;
switch( m_AnimationState )
{
case State_MoveWheelsUp:
{
if( m_AnimationPhase >= 1.0f )
{
m_AnimationPhase -= 1.0f;
SetAnimationState(State_MoveWheelCoversOut);
}
m_AnimationPhase += maximumChange * wheelMovementScale;
break;
}
case State_MoveWheelCoversOut:
{
m_AnimationPhase += ( maximumChange * s_moveCoverAnimSpeedModifier );
break;
}
case State_MoveWheelCoversIn:
{
if( m_AnimationPhase <= 0.0f )
{
m_AnimationPhase += 1.0f;
SetAnimationState(State_MoveWheelsDown);
}
m_AnimationPhase -= ( maximumChange * s_moveCoverAnimSpeedModifier );
break;
}
case State_MoveWheelsDown:
{
m_AnimationPhase -= maximumChange * wheelMovementScale;
break;
}
default:
{
SetAnimationState(State_Finished);
}
}
m_AnimationPhase = rage::Clamp( m_AnimationPhase, 0.0f, 1.0f );
}
void CSubmarineCar::UpdateSuspensionPositions()
{
int numberOfWheels = GetNumWheels();
for( int i = 0; i < numberOfWheels; i++ )
{
CWheel* wheel = GetWheel( i );
float offset = m_AnimationPhase * ms_wheelSuspensionOffset;
if( wheel->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
offset *= ms_rearWheelUpFactor;
}
wheel->SetSuspensionRaiseAmount( -offset );
wheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION );
if( m_AnimationPhase > 0.0f )
{
wheel->SetDisableBottomingOut( true );
}
else
{
wheel->SetDisableBottomingOut( false );
}
}
}
void CSubmarineCar::UpdateSteeringAngle()
{
// if we're in submarine mode, or transforming to it, we need to reduce the steering angle
// so that in submarine mode the wheels are straight and don't clip through the car
if( m_AnimationPhase != 0.0f )
{
int numberOfWheels = GetNumWheels();
float steeringFactor = 1.0f - m_AnimationPhase;
if( m_AnimationState == State_MoveWheelCoversOut ||
m_AnimationState == State_MoveWheelCoversIn )
{
steeringFactor = 0.0f;
}
for( int i = 0; i < numberOfWheels; i++ )
{
CWheel* wheel = GetWheel( i );
if( wheel->GetConfigFlags().IsFlagSet(WCF_STEER) )
{
float steeringAngle = wheel->GetSteeringAngle();
if( steeringAngle != 0.0f )
{
steeringAngle *= steeringFactor;
wheel->SetSteerAngle( steeringAngle );
}
}
}
}
}
static float s_animStageForCoverZOffset = 0.3f;
void CSubmarineCar::UpdateWheelCovers( float animationPhase )
{
Vector3 bonePosition = VEC3_ZERO;
float wheelInset = animationPhase * ms_wheelCoverInset;
float zOffset = 0.0f;
float coverAngle = ( animationPhase * 3.0f ) * ms_wheelCoverRotation;
coverAngle = Min( coverAngle, ms_wheelCoverRotation );
if( animationPhase > s_animStageForCoverZOffset )
{
zOffset = ( ( animationPhase - s_animStageForCoverZOffset ) / ( 1.0f - s_animStageForCoverZOffset ) ) * ms_wheelCoverZOffset;
}
for( int i = 0; i < 2; i++ )
{
eHierarchyId compId = (eHierarchyId)( (int)SUBMARINECAR_WHEELCOVER_L + i );
int nBoneIndex = GetBoneIndex((eHierarchyId)compId);
Matrix34& boneMat = GetLocalMtxNonConst( nBoneIndex );
const crBoneData* pBoneData = GetSkeletonData().GetBoneData(nBoneIndex);
Vec3V defaultTranslation = pBoneData->GetDefaultTranslation();
bonePosition = VEC3V_TO_VECTOR3( defaultTranslation );
if( i == 1 )
{
bonePosition.SetX( bonePosition.GetX() + wheelInset );
}
else
{
bonePosition.SetX( bonePosition.GetX() - wheelInset );
}
bonePosition.SetZ( bonePosition.GetZ() - zOffset );//( animationPhase * ms_wheelCoverZOffset ) );
boneMat.FromQuaternion( RCC_QUATERNION( pBoneData->GetDefaultRotation() ) );
boneMat.d.Set( bonePosition );
if( i == 1 )
{
boneMat.RotateY( coverAngle );
}
else
{
boneMat.RotateY( -coverAngle );
}
GetSkeleton()->PartialUpdate( nBoneIndex );
}
}
void CSubmarineCar::UpdateWheels( float animationPhase )
{
Matrix34 newBoneMatrix;
for( int i = 0; i < GetNumWheels(); i++ )
{
eHierarchyId compId = (eHierarchyId)( (int)VEH_WHEEL_LF + i );
CWheel* wheel = GetWheel( i );
int nBoneIndex = GetBoneIndex((eHierarchyId)compId);
Matrix34& boneMat = GetLocalMtxNonConst( nBoneIndex );
const crBoneData* pBoneData = GetSkeletonData().GetBoneData(nBoneIndex);
// Get default matrix
newBoneMatrix.FromQuaternion( RCC_QUATERNION( pBoneData->GetDefaultRotation() ) );
Vec3V defaultTranslation = pBoneData->GetDefaultTranslation();
newBoneMatrix.d = VEC3V_TO_VECTOR3(defaultTranslation);
if( wheel->GetConfigFlags().IsFlagSet(WCF_LEFTWHEEL) )
{
newBoneMatrix.d.SetX( newBoneMatrix.d.GetX() + ( animationPhase * ms_wheelInset ) );
}
else
{
newBoneMatrix.d.SetX( newBoneMatrix.d.GetX() + ( animationPhase * -ms_wheelInset ) );
}
float minOffset = animationPhase * ms_wheelUpOffset;
float maxOffset = ms_wheelUpOffset;
if( m_SetWheelsDown )
{
maxOffset *= animationPhase;
}
if( wheel->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) )
{
maxOffset *= ms_rearWheelUpFactor;
minOffset *= ms_rearWheelUpFactor;
}
float currentOffset = boneMat.d.GetZ() - defaultTranslation.GetZf();
if( minOffset > currentOffset )
{
minOffset -= ( minOffset - currentOffset ) * ( 1.0f - animationPhase );
}
currentOffset = Clamp( currentOffset, minOffset, maxOffset );
newBoneMatrix.d.SetZ( defaultTranslation.GetZf() + currentOffset );
if(MI_CAR_TOREADOR.IsValid() && GetModelIndex() == MI_CAR_TOREADOR)
{
TUNE_FLOAT(sfToreadorSubmarineCarWheelRotation, 90.0f, 0.0f, 100.0f,0.1f)
float fWheelTiltAmount = animationPhase * sfToreadorSubmarineCarWheelRotation;
float fTransformDirection = ( wheel->GetConfigFlags().IsFlagSet(WCF_LEFTWHEEL) ? 1.0f : -1.0f );
newBoneMatrix.RotateLocalY(fWheelTiltAmount * fTransformDirection * DtoR);
}
boneMat.Set(newBoneMatrix);
GetSkeleton()->PartialUpdate( nBoneIndex );
//Just hide the hub when transforming, otherwise it clips.
TUNE_FLOAT(sfSubmarineCarAnimationPhaseToHideHub, 0.05f, 0.0f, 1.0f,0.01f)
if(animationPhase <= sfSubmarineCarAnimationPhaseToHideHub)
{
wheel->GetConfigFlags().ClearFlag(WCF_DONT_RENDER_HUB);
}
else
{
wheel->GetConfigFlags().SetFlag(WCF_DONT_RENDER_HUB);
}
}
}
const int SUBMARINECAR_BONE_COUNT_TO_DEFORM = 8;
const eHierarchyId ExtraSubmarineCarBones[SUBMARINECAR_BONE_COUNT_TO_DEFORM] = { SUBMARINECAR_PROPELLER_1, SUBMARINECAR_PROPELLER_2, SUBMARINECAR_RUDDER_1, SUBMARINECAR_RUDDER_2, SUBMARINECAR_ELEVATOR_L, SUBMARINECAR_ELEVATOR_R, };
const eHierarchyId* CSubmarineCar::GetExtraBonesToDeform(int& extraBoneCount)
{
extraBoneCount = SUBMARINECAR_BONE_COUNT_TO_DEFORM;
return ExtraSubmarineCarBones;
}
void CSubmarineCar::ProcessPreComputeImpacts(phContactIterator impacts)
{
if( GetIsInWater() &&
m_InSubmarineMode )
{
impacts.Reset();
while(!impacts.AtEnd())
{
phInst* pOtherInst = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst( pOtherInst );
if( !pOtherEntity ||
pOtherEntity->GetIsTypeBuilding() )
{
static dev_float sfFrictionMultiplier = 0.1f;
float friction = impacts.GetFriction();
impacts.SetFriction( friction * sfFrictionMultiplier );
}
impacts++;
}
}
impacts.Reset();
CVehicle::ProcessPreComputeImpacts(impacts);
}
void CSubmarineCar::UpdateExtraCollision()
{
const s32 EXTRA_SUBMARINE_CAR_BONES = 3;
static eHierarchyId aSubmarineCarBones[ EXTRA_SUBMARINE_CAR_BONES ] =
{
SUBMARINECAR_FIN_MOUNT,
SUBMARINECAR_DRIVE_PLANE_L,
SUBMARINECAR_DRIVE_PLANE_R
};
if( fragInstGta* pFragInst = GetVehicleFragInst() )
{
m_UpdateCollision = false;
if( m_AnimationState == State_StartToSubmarine &&
m_AnimationPhase == 0.0f )
{
pFragInst->ForceArticulatedColliderMode();
}
if( fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics() )
{
phArticulatedCollider* pCollider = nullptr;
if( pFragInst->GetCacheEntry() &&
pFragInst->GetCacheEntry()->GetHierInst() )
{
pCollider = pFragInst->GetCacheEntry()->GetHierInst()->articulatedCollider;
}
if( pCollider )
{
for( int i = 0; i < EXTRA_SUBMARINE_CAR_BONES; i++ )
{
int boneIndex = GetBoneIndex( aSubmarineCarBones[ i ] );
if( physicsLOD &&
boneIndex != -1 )
{
int group = pFragInst->GetGroupFromBoneIndex( boneIndex );
if( group > -1 )
{
fragTypeGroup* pGroup = physicsLOD->GetGroup( group );
phBoundComposite* pBound = (phBoundComposite*)pFragInst->GetArchetype()->GetBound();
int childFragmentIndex = pGroup->GetChildFragmentIndex();
const Matrix34 &matrixParentBone = GetObjectMtx( boneIndex );
Matrix34 matLink = matrixParentBone;
matLink.d -= VEC3V_TO_VECTOR3( pBound->GetCGOffset() );
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
pBound->SetCurrentMatrix( childFragmentIndex + iChild, MATRIX34_TO_MAT34V( matrixParentBone ) );
// pBound->SetLastMatrix( childFragmentIndex + iChild, MATRIX34_TO_MAT34V( matrixParentBone ) );
// Keep articulated collider link attachments in sync.
if( pCollider )
{
if( Matrix34 *pMat = (Matrix34 *)pCollider->GetLinkAttachmentMatrices() )
{
pMat[ childFragmentIndex + iChild ] = matLink;
}
}
}
}
}
}
}
}
}
}