1157 lines
37 KiB
C++
1157 lines
37 KiB
C++
![]() |
#include "vehicles/AmphibiousAutomobile.h"
|
||
|
|
||
|
#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 "Vfx/Systems/VfxWater.h"
|
||
|
#include "debug/DebugScene.h"
|
||
|
#include "camera/CamInterface.h"
|
||
|
#include "Stats/StatsMgr.h"
|
||
|
#include "Vfx/Decals/DecalManager.h"
|
||
|
#include "scene/world/GameWorldHeightMap.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 "frontend/NewHud.h"
|
||
|
#include "vehicleAi/task/TaskVehicleAnimation.h"
|
||
|
#include "vehicles/Boat.h"
|
||
|
|
||
|
ENTITY_OPTIMISATIONS()
|
||
|
VEHICLE_OPTIMISATIONS()
|
||
|
|
||
|
extern float g_Boat_InteriorLight_ShutDownDistance;
|
||
|
|
||
|
dev_float CAmphibiousAutomobile::sm_fJetskiRiderResistanceMultMax = 1.0f;
|
||
|
dev_float CAmphibiousAutomobile::sm_fJetskiRiderResistanceMultMin = 0.5f;
|
||
|
|
||
|
dev_float CAmphibiousAutomobile::sm_fPropellerTimeOutOfWaterTolerance = 50.0f;
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
// Class CAmphibiousAutomobile
|
||
|
//
|
||
|
CAmphibiousAutomobile::CAmphibiousAutomobile(const eEntityOwnedBy ownedBy, u32 popType) : CAutomobile(ownedBy, popType, VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE)
|
||
|
{
|
||
|
InitAmphibiousAutomobile();
|
||
|
}
|
||
|
|
||
|
CAmphibiousAutomobile::CAmphibiousAutomobile(const eEntityOwnedBy ownedBy, u32 popType, VehicleType vehType) : CAutomobile(ownedBy, popType, vehType)
|
||
|
{
|
||
|
InitAmphibiousAutomobile();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
CAmphibiousAutomobile::~CAmphibiousAutomobile()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool CAmphibiousAutomobile::PlaceOnRoadAdjustInternal(float HeightSampleRangeUp, float HeightSampleRangeDown, bool bJustSetCompression)
|
||
|
{
|
||
|
// Make this boat track the water surface by forcing the low LOD buoyancy processing.
|
||
|
Vector3 vPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
||
|
m_Buoyancy.ForceUpdateRiverProbeResultSynchronously(vPos);
|
||
|
Assert(pHandling);
|
||
|
Assert(pHandling->GetBoatHandlingData());
|
||
|
m_Buoyancy.SetLowLodDraughtOffset(pHandling->GetBoatHandlingData()->m_fLowLodDraughtOffset);
|
||
|
|
||
|
Matrix34 originalMat = MAT34V_TO_MATRIX34(GetMatrix());
|
||
|
|
||
|
bool bPlaceOnGroundResult = false;
|
||
|
|
||
|
bool bOriginalAnchorState = GetAnchorHelper().UsingLowLodMode();
|
||
|
GetAnchorHelper().SetLowLodMode(true);
|
||
|
Assert(pHandling);
|
||
|
Assert(pHandling->GetBoatHandlingData());
|
||
|
bool bFoundWater = ProcessLowLodBuoyancy();
|
||
|
GetAnchorHelper().SetLowLodMode(bOriginalAnchorState);
|
||
|
|
||
|
// Check if vehicle was placed on water so that we can do place on water specific stuff
|
||
|
bool bPlacedOnWater = false;
|
||
|
|
||
|
Matrix34 placeOnWaterMat = MAT34V_TO_MATRIX34(GetMatrix());
|
||
|
|
||
|
SetMatrix(originalMat);
|
||
|
|
||
|
bool bFoundGround = CAutomobile::PlaceOnRoadAdjustInternal(HeightSampleRangeUp, HeightSampleRangeDown, bJustSetCompression);
|
||
|
|
||
|
Matrix34 placeOnGroundMat = MAT34V_TO_MATRIX34(GetMatrix());
|
||
|
|
||
|
bPlaceOnGroundResult = bFoundGround || bFoundWater;
|
||
|
|
||
|
if(bFoundGround && bFoundWater)
|
||
|
{
|
||
|
// If placing on water would place this higher, place on water
|
||
|
if(placeOnGroundMat.d.GetZ() < placeOnWaterMat.d.GetZ() )
|
||
|
{
|
||
|
SetMatrix(placeOnWaterMat);
|
||
|
bPlacedOnWater = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetMatrix(placeOnGroundMat);
|
||
|
}
|
||
|
}
|
||
|
else if(bFoundGround)
|
||
|
{
|
||
|
SetMatrix(placeOnGroundMat);
|
||
|
}
|
||
|
else if(bFoundWater)
|
||
|
{
|
||
|
SetMatrix(placeOnWaterMat);
|
||
|
bPlacedOnWater = true;
|
||
|
}
|
||
|
// No ground and no water
|
||
|
else if( !bFoundWater && !bFoundGround )
|
||
|
{
|
||
|
SetMatrix(originalMat);
|
||
|
|
||
|
if( m_BoatHandling.GetPlaceOnWaterTimeout() == 0 )
|
||
|
{
|
||
|
m_BoatHandling.SetPlaceOnWaterTimeout( fwTimer::GetTimeInMilliseconds() );
|
||
|
}
|
||
|
|
||
|
u32 nTimeWaiting = fwTimer::GetTimeInMilliseconds() - m_BoatHandling.GetPlaceOnWaterTimeout();
|
||
|
|
||
|
// We don't want to get stuck continually trying to place this boat if there is no water in this region. If we have
|
||
|
// been waiting for longer than 60s to place this boat, remove it from the place on road queue.
|
||
|
const u32 knPlaceOnWaterTimeout = 60000;
|
||
|
|
||
|
if( !Verifyf( nTimeWaiting < knPlaceOnWaterTimeout,
|
||
|
"A boat(%s) at (%5.3f, %5.3f, %5.3f) has been waiting %5.3f seconds to be placed on water.",
|
||
|
GetModelName(), vPos.x, vPos.y, vPos.z, nTimeWaiting / 1000.0f ) )
|
||
|
{
|
||
|
m_nVehicleFlags.bPlaceOnRoadQueued = false;
|
||
|
bPlaceOnGroundResult = true;
|
||
|
}
|
||
|
|
||
|
if(!bPlaceOnGroundResult)
|
||
|
{
|
||
|
m_nVehicleFlags.bPlaceOnRoadQueued = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(bPlacedOnWater)
|
||
|
{
|
||
|
ActivatePhysics();
|
||
|
m_BoatHandling.SetPlaceOnWaterTimeout( 0 );
|
||
|
m_nVehicleFlags.bPlaceOnRoadQueued = false;
|
||
|
}
|
||
|
|
||
|
if( bPlaceOnGroundResult )
|
||
|
{
|
||
|
m_BoatHandling.SetPlaceOnWaterTimeout( 0 );
|
||
|
}
|
||
|
|
||
|
return bPlaceOnGroundResult;
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::InitAmphibiousAutomobile()
|
||
|
{
|
||
|
m_LastEngineModeSwitchTime = 0u;
|
||
|
m_bReverseBrakeAndThrottle = false;
|
||
|
|
||
|
m_AnchorHelper.SetParent(this);
|
||
|
|
||
|
m_bUseBoatNetworkBlend = false;
|
||
|
}
|
||
|
|
||
|
bool CAmphibiousAutomobile::IsPropellerSubmerged()
|
||
|
{
|
||
|
// Lower framerates would cause this to fail since the check is done less frequently. Scale the threshold up if the framerate is lower
|
||
|
float fTimeStepMult = rage::Max( fwTimer::GetTimeStep() / 0.033f, 1.0f );
|
||
|
|
||
|
float fPropOutOfWaterTime = ( float ) ( fwTimer::GetTimeInMilliseconds() - m_BoatHandling.GetLastTimePropInWater() );
|
||
|
|
||
|
return fPropOutOfWaterTime < ( sm_fPropellerTimeOutOfWaterTolerance * fTimeStepMult );
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::AddPhysics()
|
||
|
{
|
||
|
CAutomobile::AddPhysics();
|
||
|
|
||
|
// if we're adding physics and the anchor flag was set, we actually need to do the anchoring now
|
||
|
if(m_AnchorHelper.IsAnchored())
|
||
|
{
|
||
|
m_AnchorHelper.Anchor(true, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CAmphibiousAutomobile::InitPhys()
|
||
|
{
|
||
|
CAutomobile::InitPhys();
|
||
|
|
||
|
if( m_BoatHandling.GetWreckedAction() == BWA_UNDECIDED &&
|
||
|
fwRandom::GetRandomNumberInRange(0.0f,1.0f) <= 0.5f)
|
||
|
{
|
||
|
m_BoatHandling.SetWreckedAction( BWA_SINK );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_BoatHandling.SetWreckedAction( BWA_FLOAT );
|
||
|
}
|
||
|
|
||
|
if( GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE )
|
||
|
{
|
||
|
static dev_bool sbOverrideAngInertia = true;
|
||
|
static dev_float sfSolverInvAngularMultX = 1.0f;
|
||
|
|
||
|
if(sbOverrideAngInertia && GetCollider())
|
||
|
{
|
||
|
Vector3 vecSolverAngularInertia = VEC3V_TO_VECTOR3(GetCollider()->GetInvAngInertia());
|
||
|
vecSolverAngularInertia.x *= sfSolverInvAngularMultX; //allow some pitch
|
||
|
vecSolverAngularInertia.y = 0.0f;//prevent roll
|
||
|
vecSolverAngularInertia.z = 0.0f;//prevent yaw
|
||
|
|
||
|
GetCollider()->SetSolverInvAngInertiaResetOverride(vecSolverAngularInertia);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return INIT_OK;
|
||
|
}
|
||
|
|
||
|
ePrerenderStatus CAmphibiousAutomobile::PreRender( const bool bIsVisibleInMainViewport )
|
||
|
{
|
||
|
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
|
||
|
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(GetTransform().GetPosition()) );
|
||
|
|
||
|
m_BoatHandling.UpdatePropeller( this );
|
||
|
|
||
|
return CAutomobile::PreRender( bIsVisibleInMainViewport );
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::PreRender2(const bool bIsVisibleInMainViewport)
|
||
|
{
|
||
|
return CAutomobile::PreRender2(bIsVisibleInMainViewport);
|
||
|
|
||
|
// check and update overrides to light status
|
||
|
//bool bForcedOn = UpdateVehicleLightOverrideStatus();
|
||
|
|
||
|
//bool bRemoteOn = IsNetworkClone() ? m_nVehicleFlags.bLightsOn : true;
|
||
|
|
||
|
// TODO: SOMETHING WITH BOAT LIGHTS
|
||
|
//if( bRemoteOn && (true == m_nVehicleFlags.bEngineOn || (true == bForcedOn) ) && (GetVehicleLightsStatus()) )
|
||
|
//{
|
||
|
// CVehicleModelInfo* pModelInfo=GetVehicleModelInfo();
|
||
|
|
||
|
// float cutoffDistanceTweak = CVehicle::GetLightsCutoffDistanceTweak();
|
||
|
// float camDist = rage::Sqrtf(CVfxHelper::GetDistSqrToCamera(GetTransform().GetPosition()));
|
||
|
// const float interiorLightsBrightness = rage::Clamp(((g_Boat_InteriorLight_ShutDownDistance + cutoffDistanceTweak) - camDist)*oo_BoatLights_FadeLength,0.0f,1.0f);
|
||
|
|
||
|
// if( interiorLightsBrightness )
|
||
|
// {
|
||
|
// extern ConfigBoatLightSettings g_BoatLights;
|
||
|
|
||
|
// DoLight(VEH_SIREN_1, g_BoatLights, interiorLightsBrightness, pModelInfo, false, 0.0f);
|
||
|
// DoLight(VEH_SIREN_2, g_BoatLights, interiorLightsBrightness, pModelInfo, false, 0.0f);
|
||
|
|
||
|
// if (m_nVehicleFlags.bInteriorLightOn)
|
||
|
// {
|
||
|
// float coronaFade = 1.0f - rage::Clamp(((g_Boat_corona_ShutDownDistance ) - camDist)*oo_Corona_FadeLength,0.0f,1.0f);
|
||
|
// DoLight(VEH_INTERIORLIGHT, g_BoatLights, interiorLightsBrightness, pModelInfo, cutoffDistanceTweak > 0.0f, coronaFade);
|
||
|
// }
|
||
|
// }
|
||
|
//}
|
||
|
|
||
|
// //g_vfxVehicle.ProcessLowLodBoatWake(this);
|
||
|
// fragInst* pInst = GetFragInst();
|
||
|
|
||
|
// if( pInst &&
|
||
|
// GetDriver() )
|
||
|
// {
|
||
|
// pInst->PoseBoundsFromSkeleton( true, true, false );
|
||
|
// }
|
||
|
//}
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::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 = IsPropellerSubmerged() 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 CAmphibiousAutomobile::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
|
||
|
{
|
||
|
if(GetCollider())
|
||
|
{
|
||
|
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, false);
|
||
|
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false);
|
||
|
}
|
||
|
|
||
|
// Update the wheel in water flag based on whether the propeller is submerged
|
||
|
for(int i = 0; i < GetNumWheels(); i++)
|
||
|
{
|
||
|
GetWheel(i)->SetWheelInWater(IsPropellerSubmerged());
|
||
|
}
|
||
|
|
||
|
if(IsNetworkClone())
|
||
|
{
|
||
|
if( IsPropellerSubmerged() && GetNumContactWheels() == 0 )
|
||
|
{
|
||
|
m_bUseBoatNetworkBlend = true;
|
||
|
}
|
||
|
else if( m_bUseBoatNetworkBlend && GetNumContactWheels() != 0 )
|
||
|
{
|
||
|
m_bUseBoatNetworkBlend = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool bSkipPhysicsUpdate = false;
|
||
|
|
||
|
if(m_AnchorHelper.IsAnchored())
|
||
|
{
|
||
|
// Don't reactivate if we are in low LOD anchor mode (this would happen if we call ProcessIsAsleep() while in the water).
|
||
|
CBoat::UpdateBuoyancyLodMode( this, m_AnchorHelper );
|
||
|
|
||
|
if( m_AnchorHelper.UsingLowLodMode() )
|
||
|
{
|
||
|
return PHYSICS_DONE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( m_vehicleAiLod.IsLodFlagSet( CVehicleAILod::AL_LodSuperDummy ) )
|
||
|
{
|
||
|
bSkipPhysicsUpdate = true;
|
||
|
}
|
||
|
|
||
|
if( GetCurrentPhysicsInst()==NULL || !GetCurrentPhysicsInst()->IsInLevel() )
|
||
|
{
|
||
|
bSkipPhysicsUpdate = true;
|
||
|
}
|
||
|
|
||
|
if(!bSkipPhysicsUpdate)
|
||
|
{
|
||
|
SetDamping();
|
||
|
|
||
|
bool bSkipBuoyancy = false;
|
||
|
|
||
|
// if network has re-spotted this car and we don't want it to collide with other network vehicles for a while
|
||
|
ProcessRespotCounter( fTimeStep );
|
||
|
|
||
|
// If boat is wrecked then we might want to sink it
|
||
|
if( GetStatus()==STATUS_WRECKED && m_BoatHandling.GetWreckedAction() == BWA_SINK )
|
||
|
{
|
||
|
// If we are anchored then release anchor
|
||
|
if( m_AnchorHelper.IsAnchored() )
|
||
|
{
|
||
|
m_AnchorHelper.Anchor( false );
|
||
|
}
|
||
|
|
||
|
dev_float AMPHIBIOUS_AUTOMOBILE_SINK_FORCEMULT_STEP = 0.002f;
|
||
|
|
||
|
if(m_Buoyancy.m_fForceMult > AMPHIBIOUS_AUTOMOBILE_SINK_FORCEMULT_STEP)
|
||
|
{
|
||
|
m_Buoyancy.m_fForceMult -= AMPHIBIOUS_AUTOMOBILE_SINK_FORCEMULT_STEP;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Buoyancy.m_fForceMult = 0.0f;
|
||
|
}
|
||
|
|
||
|
// Check for boat well below water surface and then fix if below 20m
|
||
|
dev_float AMPHIBIOUS_AUTOMOBILE_SUBMERGE_SLEEP_GLOBAL_Z = -20.0f;
|
||
|
|
||
|
if( GetTransform().GetPosition().GetZf() < AMPHIBIOUS_AUTOMOBILE_SUBMERGE_SLEEP_GLOBAL_Z )
|
||
|
{
|
||
|
DeActivatePhysics();
|
||
|
bSkipBuoyancy = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// do buoyancy all the time for boats, will cause them to wake up
|
||
|
// but allow to go to sleep if stranded on a beach
|
||
|
if(!bSkipBuoyancy)
|
||
|
{
|
||
|
if(InheritsFromAmphibiousQuadBike())
|
||
|
{
|
||
|
m_Buoyancy.SetShouldStickToWaterSurface(true);
|
||
|
}
|
||
|
|
||
|
m_BoatHandling.SetInWater( m_Buoyancy.Process(this, fTimeStep, false, CPhysics::GetIsLastTimeSlice(nTimeSlice)) || m_AnchorHelper.UsingLowLodMode() );
|
||
|
SetIsInWater(m_BoatHandling.IsInWater());
|
||
|
}
|
||
|
|
||
|
m_nVehicleFlags.bIsDrowning = false;
|
||
|
|
||
|
m_BoatHandling.ProcessPhysics( this, fTimeStep );
|
||
|
|
||
|
} // End if(!bSkipPhysicsUpdate)
|
||
|
|
||
|
return CAutomobile::ProcessPhysics(fTimeStep, bCanPostpone, nTimeSlice);
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::UpdatePhysicsFromEntity(bool bWarp)
|
||
|
{
|
||
|
bool bWasAnchored = GetAnchorHelper().IsAnchored();
|
||
|
if(bWasAnchored && !GetAnchorHelper().UsingLowLodMode())
|
||
|
{
|
||
|
GetAnchorHelper().Anchor(false);
|
||
|
}
|
||
|
|
||
|
CAutomobile::UpdatePhysicsFromEntity(bWarp);
|
||
|
|
||
|
if(bWasAnchored && !GetAnchorHelper().UsingLowLodMode())
|
||
|
{
|
||
|
GetAnchorHelper().Anchor(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
|
||
|
{
|
||
|
if(m_AnchorHelper.IsAnchored())
|
||
|
{
|
||
|
ProcessLowLodBuoyancy();
|
||
|
}
|
||
|
|
||
|
CAutomobile::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
|
||
|
|
||
|
m_BoatHandling.DoProcessControl( this );
|
||
|
|
||
|
CPlayerInfo *pPlayer = GetDriver() ? GetDriver()->GetPlayerInfo() : NULL;
|
||
|
|
||
|
float fAirResistance = m_fDragCoeff;
|
||
|
if( pPlayer && pPlayer->m_fForceAirDragMult > 0.0f )
|
||
|
{
|
||
|
fAirResistance *= pPlayer->m_fForceAirDragMult;
|
||
|
}
|
||
|
|
||
|
phArchetypeDamp* pArch = (phArchetypeDamp*)GetVehicleFragInst()->GetArchetype();
|
||
|
|
||
|
Vector3 vecLinearV2Damping;
|
||
|
vecLinearV2Damping.Set( fAirResistance, fAirResistance, 0.0f );
|
||
|
|
||
|
if(InheritsFromAmphibiousQuadBike() && static_cast<CAmphibiousQuadBike*>(this)->IsWheelsFullyOut() && IsPropellerSubmerged())
|
||
|
{
|
||
|
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_OUT_DRAG_MULT, 37.0f, -10.0f, 100.0f, 0.1f);
|
||
|
vecLinearV2Damping *= WHEEL_OUT_DRAG_MULT;
|
||
|
}
|
||
|
|
||
|
if( pPlayer )
|
||
|
{
|
||
|
float fSlipStreamEffect = GetSlipStreamEffect();
|
||
|
vecLinearV2Damping -= (vecLinearV2Damping * fSlipStreamEffect);
|
||
|
}
|
||
|
|
||
|
pArch->ActivateDamping( phArchetypeDamp::LINEAR_V2, vecLinearV2Damping );
|
||
|
|
||
|
// Note: for the dummy instance, changing these values is a bit dubious since
|
||
|
// multiple instances actually point to the same phArchetypeDamp object. In practice,
|
||
|
// there are no known problems. We might want to not even do it at all, but now
|
||
|
// when we don't have to do this on each frame for all AI vehicles, it shouldn't matter much.
|
||
|
phInstGta* pDummyInst = GetDummyInst();
|
||
|
if(pDummyInst)
|
||
|
{
|
||
|
phArchetypeDamp* pDummyArch = ((phArchetypeDamp*)pDummyInst->GetArchetype());
|
||
|
pDummyArch->ActivateDamping( phArchetypeDamp::LINEAR_V2, vecLinearV2Damping );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool CAmphibiousAutomobile::ProcessLowLodBuoyancy()
|
||
|
{
|
||
|
// If a boat is in low LOD anchor mode, it is physically inactive and we place it based on the water level sampled at
|
||
|
// points around the edges of the bounding box.
|
||
|
|
||
|
bool bFoundWater = false;
|
||
|
|
||
|
if( m_AnchorHelper.UsingLowLodMode() )
|
||
|
{
|
||
|
const Vector3 vOriginalPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
|
||
|
|
||
|
// Indicate this boat is in low LOD anchor mode for debugging.
|
||
|
#if __BANK
|
||
|
if( CBuoyancy::ms_bIndicateLowLODBuoyancy )
|
||
|
{
|
||
|
grcDebugDraw::Sphere( vOriginalPosition + Vector3(0.0f, 0.0f, 2.0f), 1.0f, Color_yellow );
|
||
|
}
|
||
|
#endif // __BANK
|
||
|
|
||
|
float fWaterZAtEntPos;
|
||
|
WaterTestResultType waterTestResult = m_Buoyancy.GetWaterLevelIncludingRivers( vOriginalPosition, &fWaterZAtEntPos, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL, this );
|
||
|
bFoundWater = waterTestResult > WATERTEST_TYPE_NONE;
|
||
|
|
||
|
if( bFoundWater )
|
||
|
{
|
||
|
// We know the optimum stable height offset for boats so if this boat isn't at that offset yet, LERP it
|
||
|
// there over a few frames.
|
||
|
float fLowLodDraughtOffset = m_Buoyancy.GetLowLodDraughtOffset();
|
||
|
float fDifferenceToIdeal = pHandling->GetBoatHandlingData()->m_fLowLodDraughtOffset - fLowLodDraughtOffset;
|
||
|
if(fabs(fDifferenceToIdeal) > 0.001f)
|
||
|
{
|
||
|
dev_float sfStabiliseFactor = 0.1f;
|
||
|
fLowLodDraughtOffset += sfStabiliseFactor * fDifferenceToIdeal;
|
||
|
m_Buoyancy.SetLowLodDraughtOffset( fLowLodDraughtOffset );
|
||
|
}
|
||
|
|
||
|
// Now we actually move the boat to simulate buoyancy physics.
|
||
|
Matrix34 boatMat = MAT34V_TO_MATRIX34(GetMatrix());
|
||
|
Vector3 vPlaneNormal;
|
||
|
ComputeLowLodBuoyancyPlaneNormal( boatMat, vPlaneNormal );
|
||
|
|
||
|
// Apply correction for the orientation of the boat when in equilibrium.
|
||
|
float fCorrectionRotAngle = pHandling->GetBoatHandlingData()->m_fLowLodAngOffset;
|
||
|
if( fabs( fCorrectionRotAngle ) > SMALL_FLOAT )
|
||
|
{
|
||
|
boatMat.UnTransform3x3( vPlaneNormal );
|
||
|
vPlaneNormal.RotateX( fCorrectionRotAngle );
|
||
|
boatMat.Transform3x3( vPlaneNormal );
|
||
|
}
|
||
|
|
||
|
// Figure out how much to rotate the boat's matrix by to match the plane's orientation.
|
||
|
Vector3 vRotationAxis;
|
||
|
vRotationAxis.Cross( boatMat.c, vPlaneNormal );
|
||
|
vRotationAxis.NormalizeFast();
|
||
|
float fRotationAngle = boatMat.c.Angle( vPlaneNormal );
|
||
|
|
||
|
// If we are far away from the equilibrium orientation for the boat (because of big waves or other external
|
||
|
// forces on the boat) we limit the amount we move towards the ideal position each frame. Only do this for
|
||
|
// the first few seconds after switching to low LOD mode or it damps the motion when following the waves.
|
||
|
const u32 knTimeToLerpAfterSwitchToLowLod = 2500;
|
||
|
u32 nTimeSpentLerping = fwTimer::GetTimeInMilliseconds() - m_BoatHandling.GetLowLodBuoyancyModeTimer();
|
||
|
if( nTimeSpentLerping < knTimeToLerpAfterSwitchToLowLod )
|
||
|
{
|
||
|
float fX = float( nTimeSpentLerping ) / float( knTimeToLerpAfterSwitchToLowLod );
|
||
|
float kfStabiliseFactor = rage::Min( fX * fX, 1.0f );
|
||
|
fRotationAngle *= kfStabiliseFactor;
|
||
|
}
|
||
|
if( fabs( fRotationAngle ) > SMALL_FLOAT && vRotationAxis.IsNonZero() && vRotationAxis.FiniteElements() )
|
||
|
{
|
||
|
boatMat.RotateUnitAxis( vRotationAxis, fRotationAngle );
|
||
|
}
|
||
|
|
||
|
// If the boat rotates about its up-axis too much it can end up violating the physical constraints when
|
||
|
// switching back to full physical buoyancy mode. We know the positions of the prow and stern constraints
|
||
|
// so apply a LERP back towards this orientation if we start to deviate too much.
|
||
|
float fConstraintYawDiff = m_AnchorHelper.GetYawOffsetFromConstrainedOrientation();
|
||
|
const float fLerpBackTowardsConstraintYawFactor = 1.0f;
|
||
|
fConstraintYawDiff *= fLerpBackTowardsConstraintYawFactor;
|
||
|
if( fabs( fConstraintYawDiff ) > SMALL_FLOAT )
|
||
|
{
|
||
|
boatMat.RotateZ( fConstraintYawDiff );
|
||
|
}
|
||
|
|
||
|
CBoat::RejuvenateMatrixIfNecessary( boatMat );
|
||
|
SetMatrix( boatMat );
|
||
|
|
||
|
SetPosition( Vector3( vOriginalPosition.x, vOriginalPosition.y, fWaterZAtEntPos+fLowLodDraughtOffset ) );
|
||
|
|
||
|
// Update the "is in water" flag as would be done in CBuoyancy::Process().
|
||
|
SetIsInWater( true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bFoundWater;
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::ComputeLowLodBuoyancyPlaneNormal(const Matrix34& boatMat, Vector3& vPlaneNormal)
|
||
|
{
|
||
|
// Work out the position of the back bottom corners and the prow.
|
||
|
Vector3 vBoundBoxMin = GetBoundingBoxMin();
|
||
|
Vector3 vBoundBoxMax = GetBoundingBoxMax();
|
||
|
|
||
|
const int nNumVerts = 3;
|
||
|
Vector3 vBoundBoxBottomVerts[nNumVerts];
|
||
|
vBoundBoxBottomVerts[0] = Vector3(vBoundBoxMin.x, vBoundBoxMin.y, vBoundBoxMin.z);
|
||
|
vBoundBoxBottomVerts[1] = Vector3(vBoundBoxMax.x, vBoundBoxMin.y, vBoundBoxMin.z);
|
||
|
vBoundBoxBottomVerts[2] = Vector3(0.5f*(vBoundBoxMin.x+vBoundBoxMax.x), vBoundBoxMax.y, vBoundBoxMin.z);
|
||
|
|
||
|
for(int nVert = 0; nVert < nNumVerts; ++nVert)
|
||
|
{
|
||
|
boatMat.Transform(vBoundBoxBottomVerts[nVert]);
|
||
|
float fWaterZ;
|
||
|
CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(vBoundBoxBottomVerts[nVert], &fWaterZ);
|
||
|
vBoundBoxBottomVerts[nVert].z = fWaterZ;
|
||
|
|
||
|
#if __BANK
|
||
|
if(CBuoyancy::ms_bIndicateLowLODBuoyancy)
|
||
|
{
|
||
|
grcDebugDraw::Sphere(vBoundBoxBottomVerts[nVert], 0.1f, Color_red);
|
||
|
}
|
||
|
#endif // __BANK
|
||
|
}
|
||
|
|
||
|
// Use the verts above to define a plane which can be used to set the orientation of the boat.
|
||
|
vPlaneNormal.Cross(vBoundBoxBottomVerts[0]-vBoundBoxBottomVerts[1], vBoundBoxBottomVerts[0]-vBoundBoxBottomVerts[2]);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
bool CAmphibiousAutomobile::WantsToBeAwake()
|
||
|
{
|
||
|
int fNumContactWheels = GetNumContactWheels();
|
||
|
int fNumWheels = GetNumWheels();
|
||
|
bool bIsInWater = m_BoatHandling.IsInWater();
|
||
|
bool bThrottle = ( GetThrottle() != 0.0f );
|
||
|
bool bAutomobileWantsToBeAwake = CAutomobile::WantsToBeAwake();
|
||
|
|
||
|
return ( ( fNumContactWheels != fNumWheels && bIsInWater && !m_AnchorHelper.IsAnchored() ) || bThrottle ) || ( bAutomobileWantsToBeAwake && !m_AnchorHelper.IsAnchored() );
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::Fix( bool resetFrag, bool allowNetwork )
|
||
|
{
|
||
|
m_BoatHandling.Fix();
|
||
|
CAutomobile::Fix( resetFrag, allowNetwork );
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::SetDamping()
|
||
|
{
|
||
|
CBoatHandlingData* pBoatHandling = pHandling->GetBoatHandlingData();
|
||
|
|
||
|
if( pBoatHandling == NULL )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool bIsArticulated = false;
|
||
|
if( GetCollider() )
|
||
|
{
|
||
|
bIsArticulated = GetCollider()->IsArticulated();
|
||
|
}
|
||
|
|
||
|
float fDampingMultiplier = 1.0f;
|
||
|
|
||
|
if( GetDriver() )
|
||
|
{
|
||
|
if( GetDriver()->IsPlayer() && GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE )
|
||
|
{
|
||
|
CControl *pControl = GetDriver()->GetControlFromPlayer();
|
||
|
if(pControl)
|
||
|
{
|
||
|
float fStickY = pControl->GetVehicleSteeringUpDown().GetNorm();
|
||
|
fStickY = fStickY + 1.0f;
|
||
|
fStickY = rage::Clamp(fStickY, 0.0f, 1.0f);
|
||
|
fDampingMultiplier *= sm_fJetskiRiderResistanceMultMin + fStickY * ( sm_fJetskiRiderResistanceMultMax - sm_fJetskiRiderResistanceMultMin );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float fDV = bIsArticulated ? CVehicleModelInfo::STD_BOAT_LINEAR_V_COEFF : 2.0f * CVehicleModelInfo::STD_BOAT_LINEAR_V_COEFF;
|
||
|
fDV *= fDampingMultiplier;
|
||
|
((phArchetypeDamp*)GetVehicleFragInst()->GetArchetype())->ActivateDamping( phArchetypeDamp::LINEAR_V, Vector3( fDV, fDV, fDV ) );
|
||
|
|
||
|
fDV = bIsArticulated ? 1.0f : 2.0f;
|
||
|
fDV *= fDampingMultiplier;
|
||
|
( (phArchetypeDamp*)GetVehicleFragInst()->GetArchetype() )->ActivateDamping( phArchetypeDamp::ANGULAR_V, fDV * RCC_VECTOR3( pBoatHandling->m_vecTurnResistance ) );
|
||
|
( (phArchetypeDamp*)GetVehicleFragInst()->GetArchetype() )->ActivateDamping( phArchetypeDamp::ANGULAR_V2, fDV * Vector3( 0.02f, CVehicleModelInfo::STD_BOAT_ANGULAR_V2_COEFF_Y, 0.02f ) );
|
||
|
}
|
||
|
|
||
|
void CAmphibiousAutomobile::ApplyDeformationToBones(const void* basePtr)
|
||
|
{
|
||
|
CAutomobile::ApplyDeformationToBones(basePtr);
|
||
|
|
||
|
if(basePtr)
|
||
|
{
|
||
|
CBoatHandling* pBoatHandling = GetBoatHandling();
|
||
|
|
||
|
Vector3 vecPos;
|
||
|
if( GetBoneIndex( AMPHIBIOUS_AUTOMOBILE_STATIC_PROP ) != -1 )
|
||
|
{
|
||
|
// get the original position of the prop
|
||
|
GetDefaultBonePositionForSetup( AMPHIBIOUS_AUTOMOBILE_STATIC_PROP, vecPos );
|
||
|
|
||
|
pBoatHandling->SetPropDeformOffset1( VEC3V_TO_VECTOR3( GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset( basePtr, VECTOR3_TO_VEC3V( vecPos ) ) ) );
|
||
|
}
|
||
|
if( GetBoneIndex( AMPHIBIOUS_AUTOMOBILE_STATIC_PROP2 ) != -1 )
|
||
|
{
|
||
|
// get the original position of the prop
|
||
|
GetDefaultBonePositionForSetup( AMPHIBIOUS_AUTOMOBILE_STATIC_PROP2, vecPos );
|
||
|
|
||
|
pBoatHandling->SetPropDeformOffset2( VEC3V_TO_VECTOR3( GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset( basePtr, VECTOR3_TO_VEC3V( vecPos ) ) ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float CAmphibiousAutomobile::GetBoatEngineThrottle()
|
||
|
{
|
||
|
float fThrottle = GetThrottle() - GetBrake();
|
||
|
|
||
|
// Cars brake when going backwards fast and pressing accelerate, boats don't
|
||
|
// Reverse throttle if this is the case so that accelerating doesn't make you go backwards
|
||
|
if(GetShouldReverseBrakeAndThrottle())
|
||
|
{
|
||
|
fThrottle *= -1.0f;
|
||
|
}
|
||
|
|
||
|
if( !m_nVehicleFlags.bEngineOn && m_nVehicleFlags.bIsDrowning )
|
||
|
{
|
||
|
fThrottle = 0.0f;
|
||
|
}
|
||
|
|
||
|
return fThrottle;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
CAmphibiousQuadBike::CAmphibiousQuadBike(const eEntityOwnedBy ownedBy, u32 popType) : CAmphibiousAutomobile(ownedBy, popType, VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE)
|
||
|
{
|
||
|
m_fSuspensionRaiseAmount = 0.0f;
|
||
|
m_bShouldTuckInWheels = false;
|
||
|
m_fWheelShiftAmount = 0.0f;
|
||
|
m_fWheelTiltAmount = 0.0f;
|
||
|
m_bRetractButtonHasBeenUp = true;
|
||
|
m_bToggleTuckInWheelsFromNetwork = false;
|
||
|
|
||
|
m_fWheelsFullyRetractedTime = 0.0f;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
CAmphibiousQuadBike::~CAmphibiousQuadBike()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
bank_float sfQuadBikeSelfRightingTorqueScale = 15.0f;
|
||
|
bank_float sfQuadBikeAntiRollOverTorqueScale = -35.0f;
|
||
|
|
||
|
dev_float CAmphibiousQuadBike::sm_fWheelRaiseForTiltAndShift = 0.2f;
|
||
|
dev_float CAmphibiousQuadBike::sm_fWheelRetractRaiseAmount = 0.44f;
|
||
|
dev_float CAmphibiousQuadBike::sm_fWheelRetractShiftAmount = 0.1f;
|
||
|
dev_float CAmphibiousQuadBike::sm_fWheelRetractTiltAmount = 60.0f;
|
||
|
//
|
||
|
//
|
||
|
// physics
|
||
|
ePhysicsResult CAmphibiousQuadBike::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
|
||
|
{
|
||
|
//Use the fallen collider flag on quads all the time as they are open wheeled and not constrained
|
||
|
if(!GetDriver())
|
||
|
{
|
||
|
for(int i=0; i<GetNumWheels(); i++)
|
||
|
{
|
||
|
GetWheel(i)->GetConfigFlags().SetFlag(WCF_BIKE_FALLEN_COLLIDER);
|
||
|
}
|
||
|
|
||
|
// Allow peds to flip overturned bikes by walking into the side
|
||
|
if(const CCollisionRecord* pedCollisionRecord = GetFrameCollisionHistory()->GetMostSignificantCollisionRecordOfType(ENTITY_TYPE_PED))
|
||
|
{
|
||
|
const Mat34V quadBikeMatrix = GetMatrix();
|
||
|
const Vec3V quadBikeUp = quadBikeMatrix.GetCol2();
|
||
|
const Vec3V quadBikeForward = quadBikeMatrix.GetCol1();
|
||
|
const Vec3V collisionNormal = RCC_VEC3V(pedCollisionRecord->m_MyCollisionNormal);
|
||
|
|
||
|
// Only apply torques if the impact isn't with the front or bottom of the bike
|
||
|
const ScalarV frontImpactAngleCosine = ScalarVFromF32(0.766f); // 40 degrees
|
||
|
const ScalarV bottomImpactAngleCosine = ScalarV(V_HALF); // 60 degrees
|
||
|
if(And(IsLessThan(Abs(Dot(quadBikeForward,collisionNormal)),frontImpactAngleCosine),IsLessThan(Dot(quadBikeUp,collisionNormal),bottomImpactAngleCosine)).Getb())
|
||
|
{
|
||
|
// Only bother applying torque if we're offset to begin with
|
||
|
const float minRollForSelfRighting = 20.0f*DtoR;
|
||
|
float currentRoll = GetTransform().GetRoll();
|
||
|
if(abs(currentRoll) > minRollForSelfRighting)
|
||
|
{
|
||
|
// The roll angle favors whichever direction gets us to the z-axis quicker but we always want to rotate
|
||
|
// so that the top of the bike goes towards the ped.
|
||
|
Vec3V torque = Scale(quadBikeForward,ScalarVFromF32(currentRoll));
|
||
|
if(Cross(collisionNormal,torque).GetZf() < 0)
|
||
|
{
|
||
|
// Negate the rotation axis and increase how much roll we need to cover since we're going the long way.
|
||
|
torque = Scale(Negate(quadBikeForward), ScalarVFromF32(currentRoll > 0 ? 2.0f*PI - currentRoll : -2.0f*PI - currentRoll));
|
||
|
}
|
||
|
|
||
|
// Apply a torque around the forward axis, relative to the roll error and angular inertia around the forward axis.
|
||
|
ApplyInternalTorque(RCC_VECTOR3(torque) * sfQuadBikeSelfRightingTorqueScale * GetAngInertia().y);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(GetDriver() && !IsPropellerSubmerged())// Do some autoleveling if in the air.
|
||
|
{
|
||
|
if( IsInAir() && pHandling->GetBikeHandlingData() && !GetFrameCollisionHistory()->GetMostSignificantCollisionRecord() && GetNumWheels() != 3 )
|
||
|
{
|
||
|
bool bDoAutoLeveling = true;
|
||
|
if(GetDriver()->IsAPlayerPed())
|
||
|
{
|
||
|
Vec3V vPosition = GetTransform().GetPosition();
|
||
|
float fMapMaxZ = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(vPosition.GetXf(), vPosition.GetYf());
|
||
|
|
||
|
//Let the player perform rolls when really high in the air
|
||
|
Vec3V vBBMinZGlobal = GetTransform().Transform(Vec3V(0.0f, 0.0f, GetBoundingBoxMin().z));
|
||
|
if(vBBMinZGlobal.GetZf() > fMapMaxZ)
|
||
|
{
|
||
|
bDoAutoLeveling = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(bDoAutoLeveling)
|
||
|
{
|
||
|
AutoLevelAndHeading( pHandling->GetBikeHandlingData()->m_fInAirSteerMult );
|
||
|
}
|
||
|
}
|
||
|
else if(GetNumContactWheels() != GetNumWheels() && GetCollider()) // Roll over protection
|
||
|
{
|
||
|
static dev_float sfRollOverProtectionVelocity = 1.5f;
|
||
|
|
||
|
Vec3V vCurrentAngVelocity = GetCollider()->GetAngVelocity();
|
||
|
|
||
|
vCurrentAngVelocity = UnTransform3x3Full(GetCollider()->GetMatrix(), vCurrentAngVelocity);
|
||
|
|
||
|
if(rage::Abs(vCurrentAngVelocity.GetYf()) > sfRollOverProtectionVelocity)
|
||
|
{
|
||
|
float fAngVelocityY = rage::Clamp(vCurrentAngVelocity.GetYf(), -3.0f, 3.0f);
|
||
|
const Mat34V quadBikeMatrix = GetMatrix();
|
||
|
const Vec3V quadBikeForward = quadBikeMatrix.GetCol1();
|
||
|
|
||
|
ApplyInternalTorque(RCC_VECTOR3(quadBikeForward) * fAngVelocityY * sfQuadBikeAntiRollOverTorqueScale * GetAngInertia().y);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(ContainsLocalPlayer())
|
||
|
{
|
||
|
if( !IsWheelsFullyIn() && IsPropellerSubmerged() && GetBoatEngineThrottle() )
|
||
|
{
|
||
|
static dev_u32 duration = 80;
|
||
|
static dev_s32 freq0 = 0;
|
||
|
static dev_s32 freq1 = 80;
|
||
|
CControlMgr::StartPlayerPadShake(duration, freq0, duration, freq1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( IsWheelsFullyIn() )
|
||
|
{
|
||
|
m_fWheelsFullyRetractedTime += fTimeStep;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_fWheelsFullyRetractedTime = 0.0f;
|
||
|
}
|
||
|
|
||
|
return CAmphibiousAutomobile::ProcessPhysics(fTimeStep, bCanPostpone, nTimeSlice);
|
||
|
}
|
||
|
|
||
|
void CAmphibiousQuadBike::ProcessPostPhysics()
|
||
|
{
|
||
|
CAmphibiousAutomobile::ProcessPostPhysics();
|
||
|
|
||
|
m_fVerticalVelocityToKnockOffThisBike = CVehicle::ms_fVerticalVelocityToKnockOffVehicle;
|
||
|
}
|
||
|
|
||
|
void CAmphibiousQuadBike::ToggleTuckInWheels()
|
||
|
{
|
||
|
m_bShouldTuckInWheels = !m_bShouldTuckInWheels;
|
||
|
m_VehicleAudioEntity->SetLandingGearRetracting(m_bShouldTuckInWheels);
|
||
|
|
||
|
CNewHud::ForceSpecialVehicleStatusUpdate();
|
||
|
}
|
||
|
|
||
|
void CAmphibiousQuadBike::PreRender2( const bool bIsVisibleInMainViewport )
|
||
|
{
|
||
|
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_RAISE_SPEED, 0.4f, -10.0f, 10.0f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_TILT_SPEED, 100.0f, -10.0f, 200.0f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_SHIFT_SPEED, 0.5f, -100.0f, 100.0f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_TILT_FOR_SHIFT, 30.0f, -100.0f, 100.0f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(AMPHIBIOUS_VEHICLE_TUNE, WHEEL_RAISE_FOR_SHIFT, 0.35f, -100.0f, 100.0f, 0.1f);
|
||
|
|
||
|
float fWheelMoveDirection = -1.0f;
|
||
|
bool bUpdateWheelRaise = false;
|
||
|
bool bUpdateWheelTilt = false;
|
||
|
bool bUpdateWheelShift = false;
|
||
|
|
||
|
if(m_bShouldTuckInWheels)
|
||
|
{
|
||
|
// Reverse the direction in which the wheels move
|
||
|
fWheelMoveDirection = 1.0f;
|
||
|
|
||
|
bUpdateWheelRaise = true;
|
||
|
|
||
|
// Only allow the wheels to rotate and shift inwards after they've been raised a bit
|
||
|
if(m_fSuspensionRaiseAmount >= sm_fWheelRaiseForTiltAndShift)
|
||
|
{
|
||
|
bUpdateWheelTilt = true;
|
||
|
|
||
|
// To avoid clipping, only shift the wheels when they're tilted and raised a certain amount
|
||
|
if( m_fWheelTiltAmount >= WHEEL_TILT_FOR_SHIFT && m_fSuspensionRaiseAmount >= WHEEL_RAISE_FOR_SHIFT )
|
||
|
{
|
||
|
bUpdateWheelShift = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bUpdateWheelShift = true;
|
||
|
bUpdateWheelTilt = true;
|
||
|
bUpdateWheelRaise = true;
|
||
|
}
|
||
|
|
||
|
// Update the suspension raise, shift inwards and tilt inwards
|
||
|
if(bUpdateWheelRaise)
|
||
|
{
|
||
|
m_fSuspensionRaiseAmount = rage::Clamp(m_fSuspensionRaiseAmount + fwTimer::GetTimeStep() * WHEEL_RAISE_SPEED * fWheelMoveDirection, 0.0f, sm_fWheelRetractRaiseAmount);
|
||
|
}
|
||
|
|
||
|
if(bUpdateWheelTilt)
|
||
|
{
|
||
|
m_fWheelTiltAmount = rage::Clamp(m_fWheelTiltAmount + fwTimer::GetTimeStep() * WHEEL_TILT_SPEED * fWheelMoveDirection, 0.0f, sm_fWheelRetractTiltAmount);
|
||
|
}
|
||
|
|
||
|
if(bUpdateWheelShift)
|
||
|
{
|
||
|
m_fWheelShiftAmount = rage::Clamp(m_fWheelShiftAmount + fwTimer::GetTimeStep() * WHEEL_SHIFT_SPEED * fWheelMoveDirection, 0.0f, sm_fWheelRetractShiftAmount);
|
||
|
}
|
||
|
|
||
|
for( int i = 0; i < GetNumWheels(); i++ )
|
||
|
{
|
||
|
CWheel* wheel = GetWheel( i );
|
||
|
|
||
|
// Make sure the front wheels are straight
|
||
|
if( wheel->GetConfigFlags().IsFlagSet( WCF_STEER ) && m_fSuspensionRaiseAmount != 0.0f )
|
||
|
{
|
||
|
float steeringAngle = wheel->GetSteeringAngle();
|
||
|
|
||
|
if( steeringAngle != 0.0f )
|
||
|
{
|
||
|
static dev_float removeSteeringRate = 10.0f;
|
||
|
|
||
|
if( steeringAngle > 0.0f )
|
||
|
{
|
||
|
steeringAngle = Max( 0.0f, steeringAngle - ( removeSteeringRate * fwTimer::GetTimeStep() ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
steeringAngle = Min( 0.0f, steeringAngle + ( removeSteeringRate * fwTimer::GetTimeStep() ) );
|
||
|
}
|
||
|
|
||
|
wheel->SetSteerAngle( steeringAngle );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update the suspension and set the wheel raise if wheels not at end positions
|
||
|
if(m_fSuspensionRaiseAmount != sm_fWheelRetractRaiseAmount && m_fSuspensionRaiseAmount != 0.0f)
|
||
|
{
|
||
|
wheel->SetSuspensionRaiseAmount( -m_fSuspensionRaiseAmount );
|
||
|
wheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION );
|
||
|
wheel->UpdateSuspension(NULL, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CAmphibiousAutomobile::PreRender2(bIsVisibleInMainViewport);
|
||
|
}
|
||
|
|
||
|
bool CAmphibiousQuadBike::IsWheelTransitionFinished() const
|
||
|
{
|
||
|
// If tucking in, check that wheels are fully tucked in
|
||
|
if(m_bShouldTuckInWheels)
|
||
|
{
|
||
|
return m_fSuspensionRaiseAmount == sm_fWheelRetractRaiseAmount &&
|
||
|
m_fWheelShiftAmount == sm_fWheelRetractShiftAmount &&
|
||
|
m_fWheelTiltAmount == sm_fWheelRetractTiltAmount;
|
||
|
}
|
||
|
// If not tucking in, check that wheels are fully out
|
||
|
else
|
||
|
{
|
||
|
return m_fSuspensionRaiseAmount == 0.0f &&
|
||
|
m_fWheelShiftAmount == 0.0f &&
|
||
|
m_fWheelTiltAmount == 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CAmphibiousQuadBike::WantsToBeAwake()
|
||
|
{
|
||
|
return (!IsWheelTransitionFinished() || CAmphibiousAutomobile::WantsToBeAwake() );
|
||
|
}
|
||
|
|
||
|
void CAmphibiousQuadBike::ProcessPreComputeImpacts(phContactIterator impacts)
|
||
|
{
|
||
|
if(IsWheelsFullyIn())
|
||
|
{
|
||
|
impacts.Reset();
|
||
|
|
||
|
bool processingAudioImpacts = false;
|
||
|
if(!impacts.AtEnd())
|
||
|
{
|
||
|
processingAudioImpacts = m_VehicleAudioEntity->GetCollisionAudio().StartProcessImpacts();
|
||
|
}
|
||
|
|
||
|
const Mat34V& boatMatrix = GetMatrixRef();
|
||
|
const Vec3V boatUp = boatMatrix.GetCol2();
|
||
|
|
||
|
static dev_float sfOutOfWaterTime = 10.0f;
|
||
|
static dev_float sfInWaterFrictionMultiplier = 0.1f;
|
||
|
static dev_float sfOutOfWaterFrictionMultiplier = 3.0f;
|
||
|
|
||
|
bool resetSpeedBoostObjectID = true;
|
||
|
|
||
|
// Compute a friction multiplier based on how long we've been out of the water.
|
||
|
float outOfWaterScale = Min( GetBoatHandling()->GetOutOfWaterTime(), m_fWheelsFullyRetractedTime );
|
||
|
outOfWaterScale = Min( outOfWaterScale / sfOutOfWaterTime, 1.0f );
|
||
|
|
||
|
float frictionMultiplier = Lerp( outOfWaterScale, sfInWaterFrictionMultiplier, sfOutOfWaterFrictionMultiplier );
|
||
|
|
||
|
Vector3 lastSideImpact = VEC3_ZERO;
|
||
|
while(!impacts.AtEnd())
|
||
|
{
|
||
|
// Process audio for one impact
|
||
|
if(processingAudioImpacts)
|
||
|
{
|
||
|
m_VehicleAudioEntity->GetCollisionAudio().ProcessImpactBoat(impacts);
|
||
|
}
|
||
|
|
||
|
phInst* otherInstance = impacts.GetOtherInstance();
|
||
|
|
||
|
// This is where we can hook up event generation for vehicles colliding against foliage bounds.
|
||
|
if(otherInstance && otherInstance->GetArchetype()->GetTypeFlags()&ArchetypeFlags::GTA_FOLIAGE_TYPE)
|
||
|
{
|
||
|
// If the bound hit was actually a composite, check that the child bound that we hit is really a foliage bound.
|
||
|
bool bReallyFoliageBound = false;
|
||
|
if(otherInstance->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE)
|
||
|
{
|
||
|
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(otherInstance->GetArchetype()->GetBound());
|
||
|
if(pBoundComposite->GetTypeFlags(impacts.GetOtherComponent())&ArchetypeFlags::GTA_FOLIAGE_TYPE)
|
||
|
bReallyFoliageBound = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bReallyFoliageBound = true;
|
||
|
}
|
||
|
|
||
|
if(bReallyFoliageBound)
|
||
|
{
|
||
|
#if DEBUG_DRAW
|
||
|
if(CPhysics::ms_bShowFoliageImpactsVehicles)
|
||
|
{
|
||
|
grcDebugDraw::Sphere(impacts.GetMyPosition(), 0.2f, Color_yellow);
|
||
|
grcDebugDraw::Sphere(impacts.GetOtherPosition(), 0.05f, Color_grey, false);
|
||
|
grcDebugDraw::Line(impacts.GetMyPosition(), impacts.GetOtherPosition(), Color_grey);
|
||
|
}
|
||
|
#endif // DEBUG_DRAW
|
||
|
|
||
|
// Now that we've noted the collision, disable the impact.
|
||
|
impacts.DisableImpact();
|
||
|
++impacts;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vector3 vecNormal;
|
||
|
impacts.GetMyNormal(vecNormal);
|
||
|
|
||
|
// Only modify the friction if the bottom of the boat is being hit
|
||
|
if(IsGreaterThanAll(Dot(RCC_VEC3V(vecNormal),boatUp),ScalarV(V_HALF)))
|
||
|
{
|
||
|
// Contacts don't get their friction recomputed each frame so we need to do that before multiplying it
|
||
|
float materialFriction,elasticity;
|
||
|
PGTAMATERIALMGR->FindCombinedFrictionAndElasticity(impacts.GetMyMaterialId(),impacts.GetOtherMaterialId(),materialFriction,elasticity);
|
||
|
impacts.SetFriction(materialFriction*frictionMultiplier);
|
||
|
}
|
||
|
|
||
|
impacts++;
|
||
|
}
|
||
|
|
||
|
int count = impacts.CountElements();
|
||
|
|
||
|
phCachedContactIteratorElement *buffer = Alloca(phCachedContactIteratorElement, count);
|
||
|
|
||
|
impacts.Reset();
|
||
|
|
||
|
phCachedContactIterator cachedImpacts;
|
||
|
cachedImpacts.InitFromIterator( impacts, buffer, count );
|
||
|
|
||
|
while(!cachedImpacts.AtEnd())
|
||
|
{
|
||
|
phInst* otherInstance = cachedImpacts.GetOtherInstance();
|
||
|
CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL;
|
||
|
|
||
|
if( ProcessBoostPadImpact( cachedImpacts, pOtherEntity ) )
|
||
|
{
|
||
|
cachedImpacts.DisableImpact();
|
||
|
resetSpeedBoostObjectID = false;
|
||
|
}
|
||
|
++cachedImpacts;
|
||
|
}
|
||
|
|
||
|
if(processingAudioImpacts)
|
||
|
{
|
||
|
m_VehicleAudioEntity->GetCollisionAudio().EndProcessImpacts();
|
||
|
}
|
||
|
|
||
|
// if we're not touching any boost pads reset the current object
|
||
|
if( resetSpeedBoostObjectID )
|
||
|
{
|
||
|
m_iCurrentSpeedBoostObjectID = 0;
|
||
|
m_iPrevSpeedBoostObjectID = 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CAmphibiousAutomobile::ProcessPreComputeImpacts(impacts);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAmphibiousQuadBike::ForceWheelsOut()
|
||
|
{
|
||
|
m_bShouldTuckInWheels = false;
|
||
|
m_fSuspensionRaiseAmount = 0.0f;
|
||
|
m_fWheelShiftAmount = 0.0f;
|
||
|
m_fWheelTiltAmount = 0.0f;
|
||
|
|
||
|
for( int i = 0; i < GetNumWheels(); i++ )
|
||
|
{
|
||
|
CWheel* wheel = GetWheel( i );
|
||
|
|
||
|
wheel->SetSuspensionRaiseAmount( -m_fSuspensionRaiseAmount );
|
||
|
wheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION );
|
||
|
wheel->UpdateSuspension(NULL, false);
|
||
|
}
|
||
|
|
||
|
ActivatePhysics();
|
||
|
}
|
||
|
|
||
|
void CAmphibiousQuadBike::ForceWheelsIn()
|
||
|
{
|
||
|
m_bShouldTuckInWheels = true;
|
||
|
m_fSuspensionRaiseAmount = sm_fWheelRetractRaiseAmount;
|
||
|
m_fWheelShiftAmount = sm_fWheelRetractShiftAmount;
|
||
|
m_fWheelTiltAmount = sm_fWheelRetractTiltAmount;
|
||
|
|
||
|
for( int i = 0; i < GetNumWheels(); i++ )
|
||
|
{
|
||
|
CWheel* wheel = GetWheel( i );
|
||
|
|
||
|
wheel->SetSuspensionRaiseAmount( -m_fSuspensionRaiseAmount );
|
||
|
wheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION );
|
||
|
wheel->UpdateSuspension(NULL, false);
|
||
|
}
|
||
|
|
||
|
ActivatePhysics();
|
||
|
}
|