#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(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; iGetNumDoors(); 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; iGetNumDoors(); i++) { pVehicle->GetDoor(i)->ProcessPreRender(pVehicle); } PreRenderPropellers( pVehicle ); if (IsSubLeakingAir()) { float damageEvo = Min(1.0f, GetNumberOfAirLeaks()/20.0f); g_vfxWater.UpdatePtFxSubmarineAirLeak(static_cast(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(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(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 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(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 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(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; } } } } } } } } } }