Files
GTASource/game/Vehicles/Planes.cpp

7196 lines
251 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
// Title : Planes.cpp
// Author : Alexander Roger
// Started : 10/04/2003
//
//
//
//
// C headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Rage headers
#include "audiosoundtypes/soundcontrol.h"
#include "audioengine/widgets.h"
#include "crskeleton/skeleton.h"
#include "pheffects/wind.h"
#include "pharticulated/joint.h"
#include "pharticulated/joint1dof.h"
#include "pharticulated/articulatedbody.h"
#include "pharticulated/articulatedcollider.h"
#include "phbound/boundcomposite.h"
// Framework headers
#include "grcore/debugdraw.h"
#include "fwmaths/angle.h"
#include "fwmaths/random.h"
#include "fwmaths/vector.h"
#if !__NO_OUTPUT
#include "fwnet/netchannel.h"
#endif
// Game headers
#include "audio/ambience/ambientaudioentity.h"
#include "audio/environment/environmentgroup.h"
#include "audio/northaudioengine.h"
#include "audio/planeaudioentity.h"
#include "camera/CamInterface.h"
#include "camera/cinematic/CinematicDirector.h"
#include "control/gamelogic.h"
#include "control/replay/Replay.h"
#include "camera/gameplay/gameplaydirector.h"
#include "camera/gameplay/follow/FollowVehicleCamera.h"
#include "vehicleAi/vehicleintelligence.h"
#include "vehicleAi/task/TaskVehicleMissionBase.h"
#include "vehicleAi/task/TaskVehicleFlying.h"
#include "vehicleAi/task/TaskVehiclePlayer.h"
#include "vehicleAi/FlyingVehicleAvoidance.h"
#include "debug/debugglobals.h"
#include "debug/debugscene.h"
#include "event/EventShocking.h"
#include "event/ShockingEvents.h"
#include "game/modelIndices.h"
#include "Stats/StatsMgr.h"
#include "game/wind.h"
#include "game/weather.h"
#include "modelInfo/vehicleModelInfo.h"
#include "network/Events/NetworkEventTypes.h"
#include "network/NetworkInterface.h"
#include "pedgroup/pedGroup.h"
#include "peds/ped.h"
#include "peds/pedIntelligence.h"
#include "peds/Ped.h"
#include "peds/PopCycle.h"
#include "physics/physics.h"
#include "physics/gtaArchetype.h"
#include "physics/WorldProbe/worldprobe.h"
#include "physics/gtaInst.h"
#include "physics/PhysicsHelpers.h"
#include "renderer/lights/AsyncLightOcclusionMgr.h"
#include "renderer/ApplyDamage.h"
#include "scene/world/GameWorld.h"
#include "scene/world/GameWorldHeightMap.h"
#include "script/script.h"
#include "Stats/StatsInterface.h"
#include "streaming/streaming.h"
#include "system/controlMgr.h"
#include "system/pad.h"
#include "task/Physics/TaskNMRelax.h"
#include "timecycle/TimeCycleConfig.h"
#include "peds/PlayerInfo.h"
#include "vehicles/heli.h"
#include "vehicles/planes.h"
#include "vehicles/train.h"
#include "vehicles/metadata/VehicleEntryPointInfo.h"
#include "vehicles/metadata/VehicleLayoutInfo.h"
#include "vehicles/vehicle_channel.h"
#include "vehicles/vehicleFactory.h"
#include "vehicles/vehiclepopulation.h"
#include "vehicles/VehicleGadgets.h"
#include "Vfx/Decals/DecalManager.h"
#include "Vfx/Misc/Fire.h"
#include "Vfx/Systems/VfxVehicle.h"
#include "vfx/Systems/VfxBlood.h"
#include "vfx/Systems/VfxWeapon.h"
#include "weapons/explosion.h"
#include "Task/Vehicle/TaskInVehicle.h"
ENTITY_OPTIMISATIONS()
VEHICLE_OPTIMISATIONS()
AUDIO_VEHICLES_OPTIMISATIONS()
#if !__NO_OUTPUT
RAGE_DEFINE_SUBCHANNEL(net, damage_plane, DIAG_SEVERITY_DEBUG3)
#undef __net_channel
#define __net_channel net_damage_plane
#endif
//s32 CPlane::GenPlane_Status = PGEN_NOPLANE;
//s32 CPlane::GenPlane_ModelIndex;
//u32 CPlane::GenPlane_LastTimeGenerated = 0;
//bool CPlane::GenPlane_Active = true;
bank_float CPlane::ms_fMinEngineSpeed = 0.4f;
bank_float CPlane::ms_fStdEngineSpeed = 0.5f;
bank_float CPlane::ms_fMaxEngineSpeed = 1.0f;
bank_float bfMinJetOnGroundEngineSpeed = 0.20f;
bank_float bfStdJetOnGroundEngineSpeed = 0.25f;
bank_float bfMaxJetOnGroundEngineSpeed = 1.0f;
bank_float CPlane::ms_fEngineSpeedChangeRate = 0.08f; // rate of change
dev_float dfEngineSpeedPassiveChangeRate = 0.005f; // rate of change
bank_float CPlane::ms_fEngineSpeedDropRateInWater = 0.24f;
bank_float CPlane::ms_fEngineSpeedDropRateWhenMissFiring = 1.0f;
dev_float CPlane::ms_fControlSmoothSpeed = 5.0f;
dev_float dfControlSmoothSpeedForAI = 1.0f;
bank_float CPlane::ms_fCeilingLiftCutoffRange = 60.0f;
bank_float CPlane::ms_fPropRenderSpeed= 55.0f;
//bank_float CPlane::ms_fEngineSpeedPropMult = 5.0f; //brings the propellors up to full speed faster
bool CPlane::ms_ReduceDragWithGroundEffect = false;
bank_float CPlane::ms_fTopSpeedDampRate = 0.35f;
bank_float CPlane::ms_fTopSpeedDampMaxHeight = 270.0f;
bank_float CPlane::ms_fTopSpeedDampMinHeight = 230.0f;
#if RSG_PC
bank_float sfPlaneRandomNoiseIdleMultForPlayer = 0.1f;
bank_float sfPlaneRandomNoiseThrottleMultForPlayer = 0.075f;
#else
bank_float sfPlaneRandomNoiseIdleMultForPlayer = 0.3f;
bank_float sfPlaneRandomNoiseThrottleMultForPlayer = 0.2f;
#endif
static dev_float sfPlaneRandomNoiseIdleMultForAI = 0.3f;
static dev_float sfPlaneRandomNoiseThrottleMultForAI = 0.2f;
#if USE_SIXAXIS_GESTURES
bank_float CPlane::MOTION_CONTROL_PITCH_MIN = -1.0f;
bank_float CPlane::MOTION_CONTROL_PITCH_MAX = 1.0f;
bank_float CPlane::MOTION_CONTROL_ROLL_MIN = -1.75f;
bank_float CPlane::MOTION_CONTROL_ROLL_MAX = 1.75f;
bank_float CPlane::MOTION_CONTROL_YAW_MULT = 2.5f;
#endif
//////////////////////////////////////////////////////////////////////////
// Class CAircraftDamageBase
//////////////////////////////////////////////////////////////////////////
CAircraftDamageBase::CAircraftDamageBase()
: m_pLastDamageInflictor(NULL)
{
}
CAircraftDamageBase::~CAircraftDamageBase()
{
}
static dev_float sfPlaneCollisionDamageMult = 200.0f;
bank_float bfPlaneLandingDamageMult = 0.75f;
void CAircraftDamageBase::ApplyDamageToPlane(CPlane* pParent, CEntity* pInflictor, eDamageType nDamageType, u32 nWeaponHash, float fDamage, const Vector3& vecPos, const Vector3& vecNorm, const Vector3& UNUSED_PARAM(vecDirn), int nComponent, phMaterialMgr::Id UNUSED_PARAM(nMaterialId), int UNUSED_PARAM(nPieceIndex), const bool UNUSED_PARAM(bFireDriveby), const bool bLandingDamage, const float fDamageRadius)
{
// Figure out which section we hit
// This is all hardcoded as a link between components and sections
// If we want crazy plane variations this will have to modernise
// The damage for collisions is the impulse mag sum applied
// Want to divide by plane mass or else collisions against static objects
// can be a bit too large
if(nWeaponHash == WEAPONTYPE_RAMMEDBYVEHICLE)
{
Assert(pParent->GetMass() > 0.0f);
fDamage *= sfPlaneCollisionDamageMult/pParent->GetMass();
}
if(bLandingDamage)
{
// if landing gear is unbreakable, quick return here
if(pParent->m_nVehicleFlags.bUnbreakableLandingGear)
{
return;
}
fDamage *= bfPlaneLandingDamageMult;
}
int nNumHitSections = 0;
int pHitSectionIndices[CAircraftDamage::NUM_DAMAGE_SECTIONS];
FindAllHitSections(nNumHitSections, pHitSectionIndices, pParent, nDamageType, vecPos, nComponent, fDamageRadius);
if(nNumHitSections > 1)
{
fDamage /= nNumHitSections;
}
bool canPartsBreakOff = pParent->CarPartsCanBreakOff();
for(int iHitSectionIdx = 0; iHitSectionIdx < nNumHitSections; iHitSectionIdx++)
{
if(pHitSectionIndices[iHitSectionIdx] != INVALID_SECTION && AssertVerify(!pParent->IsNetworkClone()))
{
// Hit a damagable part of the plane
ApplyDamageToSection(pParent, pHitSectionIndices[iHitSectionIdx], nComponent, fDamage, nDamageType, vecPos, vecNorm);
if( GetSectionHealth(pHitSectionIndices[iHitSectionIdx]) <= 0.0f &&
!HasSectionBrokenOff(pParent, iHitSectionIdx) &&
canPartsBreakOff &&
!pParent->GetVehicleFragInst()->IsBreakingDisabled() )
{
// Just killed this part, so make sure its allowed to be broken
// Look up the group
fragInstGta* pInst = pParent->GetVehicleFragInst();
if(physicsVerifyf(pInst,"All vehicles need a frag inst"))
{
if(fDamage > GetInstantBreakOffDamageThreshold(pHitSectionIndices[iHitSectionIdx]) || nDamageType == DAMAGE_TYPE_EXPLOSIVE)
{
int nGroup = -1;
fragTypeGroup* pGroup = NULL;
eHierarchyId nId = GetHierarchyIdFromSection(pHitSectionIndices[iHitSectionIdx]);
if((nId >= PLANE_FIRST_BREAKABLE_PART && nId < PLANE_FIRST_BREAKABLE_PART + PLANE_NUM_BREAKABLES)
|| ( nId >= FIRST_LANDING_GEAR && nId < FIRST_LANDING_GEAR + NUM_LANDING_GEARS && pParent->GetModelIndex() != MI_PLANE_MICROLIGHT ))
{
// Lookup the frag group from the hierarchy id and see if it has broken off
// First we need a bone index
int iBoneIndex = pParent->GetBoneIndex(nId);
if(iBoneIndex > -1)
{
nGroup = pInst->GetGroupFromBoneIndex(iBoneIndex);
if(nGroup > -1 && pInst->GetTypePhysics())
{
pGroup = pInst->GetTypePhysics()->GetAllGroups()[nGroup];
}
}
}
if(nGroup != -1 && pGroup != NULL)
{
// This manually breaks off group
// but this is not allowed if group has child groups
//pInst->BreakOffAboveGroup(nGroup);
BreakOffAboveGroupRecursive(nGroup,pInst);
// Stop all children in this group breaking
// Don't use the clear all children function because this clears child groups as well
// which we don't want to do
// e.g. if wing can fall off don't want to allow engines to fall off if wing if their health is still OK
int iChild = pGroup->GetChildFragmentIndex();
for(int i = 0; i < pGroup->GetNumChildren(); i++)
{
pInst->ClearDontBreakFlag(BIT(iChild + i));
}
PostBreakOffSection(pParent, pHitSectionIndices[iHitSectionIdx], nDamageType);
}
}
}
}
}
else if(nComponent == 0 && AssertVerify(!pParent->IsNetworkClone()))
{
ApplyDamageToChassis(pParent, fDamage, nDamageType, nComponent, vecPos);
}
}
//don't override with a null inflictor
if(pInflictor)
{
m_pLastDamageInflictor = pInflictor;
}
}
void CAircraftDamageBase::InitCompositeBound(CPlane* pParent)
{
// Find the breakable components and make sure they are flagged not to break off
// This is really general initphys stuff but don't want to have to do this iteration twice
// So combined InitCompositeBound and InitPhys
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(pParent->GetVehicleFragInst()->GetArchetype()->GetBound());
Assert(pBoundComp);
Assert(pParent->GetVehicleFragInst()->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE);
// Stop the frag system breaking off the breakable components
// Find the breakable groups
int nStartId = GetFirstBreakablePart();
fragInstGta* pFragInst = pParent->GetVehicleFragInst();
FastAssert(pFragInst); // All vehicles must have a frag inst
for(int i = 0; i < GetNumOfBreakablePart(); i++)
{
eHierarchyId nId = (eHierarchyId)(nStartId + i);
int iBoneIndex = pParent->GetBoneIndex(nId);
if(iBoneIndex > -1)
{
int nGroup = pFragInst->GetGroupFromBoneIndex(iBoneIndex);
if(nGroup > -1)
{
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetAllGroups()[nGroup];
int iChild = pGroup->GetChildFragmentIndex();
// Make sure no children in this group will break off
for(int k = 0; k < pGroup->GetNumChildren(); k++)
{
pFragInst->SetDontBreakFlag(BIT(iChild + k));
pBoundComp->SetIncludeFlags(iChild+k,ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
}
}
if( pParent->GetModelIndex() == MI_PLANE_MOGUL ||
pParent->GetModelIndex() == MI_PLANE_NOKOTA ||
pParent->GetModelIndex() == MI_PLANE_ROGUE ||
pParent->GetModelIndex() == MI_PLANE_MOLOTOK ||
pParent->GetModelIndex() == MI_PLANE_PYRO ||
pParent->GetModelIndex() == MI_PLANE_BOMBUSHKA ||
pParent->GetModelIndex() == MI_PLANE_AVENGER ||
pParent->GetModelIndex() == MI_PLANE_VOLATOL )
{
pParent->m_nVehicleFlags.bUseDeformation = false;
}
}
void CAircraftDamageBase::BreakOffSection(CPlane* pParent, int nHitSection, bool bDisappear, bool bNetworkAllowed)
{
if (pParent && pParent->IsNetworkClone() && !bNetworkAllowed)
return;
if (pParent && !pParent->CarPartsCanBreakOff())
{
bDisappear = true;
}
fragInstGta* pInst = pParent->GetVehicleFragInst();
if(physicsVerifyf(pInst,"All vehicles need a frag inst"))
{
int nGroup = -1;
fragTypeGroup* pGroup = NULL;
eHierarchyId nId = GetHierarchyIdFromSection(nHitSection);
bool canBreakOffLandingGear = pParent->GetModelIndex() != MI_PLANE_AVENGER;
if((nId >= PLANE_FIRST_BREAKABLE_PART && nId < PLANE_FIRST_BREAKABLE_PART + PLANE_NUM_BREAKABLES)
|| ( canBreakOffLandingGear && nId >= FIRST_LANDING_GEAR && nId < FIRST_LANDING_GEAR + NUM_LANDING_GEARS ))
{
// Lookup the frag group from the hierarchy id and see if it has broken off
// First we need a bone index
int iBoneIndex = pParent->GetBoneIndex(nId);
if(iBoneIndex > -1)
{
nGroup = pParent->GetFragInst()->GetGroupFromBoneIndex(iBoneIndex);
pGroup = pInst->GetTypePhysics()->GetAllGroups()[nGroup];
}
}
if(nGroup != -1 && pGroup != NULL)
{
if(bDisappear)
{
pInst->DeleteAboveGroup(nGroup);
}
else
{
BreakOffAboveGroupRecursive(nGroup,pInst);
int iChild = pGroup->GetChildFragmentIndex();
// Stop all children in this group breaking
// Don't use the clear all children function because this clears child groups as well
// which we don't want to do
// e.g. if wing can fall off don't want to allow engines to fall off if wing if their health is still OK
for(int i = 0; i < pGroup->GetNumChildren(); i++)
{
pInst->ClearDontBreakFlag(BIT(iChild + i));
}
}
PostBreakOffSection(pParent, nHitSection, DAMAGE_TYPE_NONE);
}
}
}
float fBrokenOffWheelAngularDampingC = 0.35f;
float fBrokenOffWheelAngularDampingV = 0.25f;
float fBrokenOffWheelAngularDampingV2 = 0.35f;
void CAircraftDamageBase::BreakOffAboveGroupRecursive(int nGroup, fragInst* pFragInst)
{
Assert(nGroup > -1 && nGroup < pFragInst->GetTypePhysics()->GetNumChildGroups());
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetAllGroups()[nGroup];
// Break off all child groups as well
for(int i = 0; i < pGroup->GetNumChildGroups(); i++)
{
int iChildGroup = pGroup->GetChildGroupsPointersIndex() + i;
BreakOffAboveGroupRecursive(iChildGroup, pFragInst);
}
if (fragCacheEntry* entry = pFragInst->GetCacheEntry())
{
if(entry->GetHierInst() && entry->GetHierInst()->groupBroken->IsClear(nGroup))//make sure the part isn't already broken off
{
fragInst *pBrokenOffInst = pFragInst->BreakOffAboveGroup(nGroup);
if(CEntity* pEntity = CPhysics::GetEntityFromInst(pBrokenOffInst))
{
if(pEntity->GetIsTypeObject() && ((CObject *)pEntity)->m_nObjectFlags.bCarWheel)
{
phArchetypeDamp* pGtaArchetype = (phArchetypeDamp*)pBrokenOffInst->GetArchetype();
pGtaArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_C, Vector3(fBrokenOffWheelAngularDampingC, fBrokenOffWheelAngularDampingC, fBrokenOffWheelAngularDampingC));
pGtaArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V, Vector3(fBrokenOffWheelAngularDampingV, fBrokenOffWheelAngularDampingV, fBrokenOffWheelAngularDampingV));
pGtaArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V2, Vector3(fBrokenOffWheelAngularDampingV2, fBrokenOffWheelAngularDampingV2, fBrokenOffWheelAngularDampingV2));
}
}
}
}
}
bool CAircraftDamageBase::HasSectionBrokenOff(const CPlane* pParent, int iSection) const
{
Assert(pParent);
Assert(pParent->GetFragInst());
Assert(pParent->GetFragInst()->GetType());
eHierarchyId nId = GetHierarchyIdFromSection(iSection);
if(nId >= GetFirstBreakablePart() && nId < GetFirstBreakablePart() + GetNumOfBreakablePart())
{
// Lookup the frag group from the hierarchy id and see if it has broken off
// First we need a bone index
int iBoneIndex = pParent->GetBoneIndex(nId);
if(iBoneIndex > -1)
{
int iGroup = pParent->GetFragInst()->GetGroupFromBoneIndex(iBoneIndex);
if(iGroup > -1)
{
Assert(pParent->GetFragInst()->GetCacheEntry());
Assert(pParent->GetFragInst()->GetTypePhysics()->GetNumChildGroups() > iGroup);
Assert(pParent->GetFragInst()->GetCacheEntry()->GetHierInst());
return pParent->GetFragInst()->GetCacheEntry()->GetHierInst()->groupBroken->IsSet(iGroup);
}
}
}
return false;
}
void CAircraftDamageBase::PostBreakOffSection(CPlane* UNUSED_PARAM(pParent), int UNUSED_PARAM(nSection), eDamageType UNUSED_PARAM(nDamageType))
{
}
void CAircraftDamageBase::ApplyDamageToChassis(CPlane* UNUSED_PARAM(pParent), float UNUSED_PARAM(fDamage), eDamageType UNUSED_PARAM(nDamageType), int UNUSED_PARAM(nComponent), const Vector3 &UNUSED_PARAM(vHitPos))
{
}
//////////////////////////////////////////////////////////////////////////
// Class CAircraftDamage
//////////////////////////////////////////////////////////////////////////
#if __BANK
bool CAircraftDamage::ms_bDrawFlamePosition = false;
#endif
float CAircraftDamage::ms_fMinCausingFlameDamage = 250.0f;
float CAircraftDamage::ms_fMaxCausingFlameDamage = 1000.0f;
float CAircraftDamage::ms_fMinFlameLifeSpan = 3.0f;
float CAircraftDamage::ms_fMaxFlameLifeSpan = 4.0f;
float CAircraftDamage::ms_fFlameDamageMultiplier = 1.1f;
float CAircraftDamage::ms_fFlameMergeableDistance = 5.0f;
const CAircraftDamage::eAircraftSection CAircraftDamage::sm_iHierarchyIdToSection[PLANE_NUM_DAMAGE_PARTS] =
{
CAircraftDamage::RUDDER, // PLANE_RUDDER,
CAircraftDamage::RUDDER_2, // PLANE_RUDDER,
CAircraftDamage::ELEVATOR_L, // PLANE_ELEVATOR_L,
CAircraftDamage::ELEVATOR_R, // PLANE_ELEVATOR_R,
CAircraftDamage::AILERON_L, // PLANE_AILERON_L,
CAircraftDamage::AILERON_R, // PLANE_AILERON_R,
CAircraftDamage::AIRBRAKE_L, // PLANE_AIRBRAKE_L
CAircraftDamage::AIRBRAKE_R, // PLANE_AIRBRAKE_R
CAircraftDamage::WING_L, // PLANE_WING_L,
CAircraftDamage::WING_R, // PLANE_WING_R,
CAircraftDamage::TAIL, // PLANE_TAIL,
CAircraftDamage::ENGINE_L, // PLANE_ENGINE_L,
CAircraftDamage::ENGINE_R, // PLANE_ENGINE_R
};
CompileTimeAssert(PLANE_NUM_DAMAGE_PARTS == 13);
const float CAircraftDamage::sm_fInitialComponentHealth[CAircraftDamage::NUM_DAMAGE_SECTIONS] =
{
5000.0f, // WING_L,
5000.0f, // WING_R,
2500.0f, // TAIL,
1000.0f, // ENGINE_L,
1000.0f, // ENGINE_R,
300.0f, // ELEVATOR_L,
300.0f, // ELEVATOR_R,
300.0f, // AILERON_L,
300.0f, // AILERON_R,
300.0f, // RUDDER,
300.0f, // RUDDER_2,
300.0f, // AIRBRAKE_L,
300.0f // AIRBRAKE_R,
};
const float CAircraftDamage::sm_fInstantBreakOffDamageThreshold[CAircraftDamage::NUM_DAMAGE_SECTIONS] =
{
200.0f, // WING_L
200.0f, // WING_R
100.0f, // TAIL
50.0f, // ENGINE_L
50.0f, // ENGINE_R
5.0f, // ELEVATOR_L
5.0f, // ELEVATOR_R
5.0f, // AILERON_L
5.0f, // AILERON_R
15.0f, // RUDDER
15.0f, // RUDDER_2
15.0f, // AIRBRAKE_L,
15.0f // AIRBRAKE_R,
};
CompileTimeAssert(CAircraftDamage::NUM_DAMAGE_SECTIONS == 13);
#define AIRCRAFT_DAMAGE_FLAME_POOL_SIZE 34
FW_INSTANTIATE_CLASS_POOL(CAircraftDamage::DamageFlame, AIRCRAFT_DAMAGE_FLAME_POOL_SIZE, atHashString("AircraftFlames",0x998f00ac));
CAircraftDamage::CAircraftDamage()
: CAircraftDamageBase()
, m_bDestroyedByPed(false)
, m_bIgnoreBrokenOffPartForAIHandling(false)
, m_bControlPartsBreakOffInstantly(true)
, m_bHasCustomSectionDamageScale(false)
{
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fHealth[i] = sm_fInitialComponentHealth[i];
m_fHealthDamageScale[i] = 1.0f;
m_fBrokenOffSmokeLifeTime[i] = 0.0f;
}
m_pDamageFlames.Reset();
m_fEngineDegradeTimer = 0.0f;
}
CAircraftDamage::~CAircraftDamage()
{
for(int index = 0; index < m_pDamageFlames.GetCount(); index++)
{
delete m_pDamageFlames[index];
}
m_pDamageFlames.Reset();
}
int CAircraftDamage::GetSectionFromHierarchyId(eHierarchyId iComponent)
{
int i = ((int) iComponent) - PLANE_FIRST_DAMAGE_PART;
if(i > -1 && i < PLANE_NUM_DAMAGE_PARTS)
{
return sm_iHierarchyIdToSection[i];
}
return INVALID_SECTION;
}
void CAircraftDamage::CheckDestroyedByPed(CEntity* inflictor, CPlane* pParent, const int sectionBroken)
{
if (!pParent)
return;
if (!inflictor)
return;
if (pParent->IsNetworkClone())
return;
if (pParent->GetStatus() == STATUS_WRECKED)
return;
switch (sectionBroken)
{
case ENGINE_L:
case ENGINE_R:
case WING_L:
case WING_R:
case TAIL:
{
if (inflictor->GetIsTypePed())
{
m_bDestroyedByPed = true;
}
else if (inflictor->GetIsTypeVehicle())
{
CVehicle* vehicle = static_cast<CVehicle* >(inflictor);
if (vehicle->GetDriver())
{
m_bDestroyedByPed = true;
}
}
}
break;
default:
break;
}
}
bool CAircraftDamage::BreakOffComponent(CEntity* inflictor, CPlane* pParent, int iComponent, bool bNetworkAllowed)
{
int iSectionToBreakOff = GetSectionFromChildIndex(pParent, iComponent);
if(iSectionToBreakOff != INVALID_SECTION)
{
SetSectionHealth(iSectionToBreakOff, Min(0.0f, GetSectionHealth(iSectionToBreakOff)));
BreakOffSection(pParent, iSectionToBreakOff, bNetworkAllowed);
//Check if the breakable component is critical to maintain the plane in the Aircraft flying
CheckDestroyedByPed(inflictor, pParent, iSectionToBreakOff);
return true;
}
return false;
}
int CAircraftDamage::GetSectionFromChildIndex(const CPlane* pParent, int iChildIndex)
{
// Lookup bone index from child
Assert(pParent);
Assert(pParent->GetVehicleFragInst());
Assert(pParent->GetVehicleFragInst()->GetTypePhysics()->GetNumChildren() > iChildIndex);
int iBoneIndex = pParent->GetVehicleFragInst()->GetType()->GetBoneIndexFromID(
pParent->GetVehicleFragInst()->GetTypePhysics()->GetAllChildren()[iChildIndex]->GetBoneID());
if(iBoneIndex > -1)
{
for(int i =0; i < PLANE_NUM_DAMAGE_PARTS; i++)
{
eHierarchyId nId = (eHierarchyId)(i + PLANE_FIRST_DAMAGE_PART);
if(pParent->GetBoneIndex(nId) == iBoneIndex)
{
return GetSectionFromHierarchyId(nId);
}
}
}
return INVALID_SECTION;
}
// We store a map of hierarchyIds to damage sections
// so multiple hierarchy ids map to a single section
// This does the reverse, finding the first hierarchy id associated with a section
eHierarchyId CAircraftDamage::GetHierarchyIdFromSection(int iSection) const
{
for(int i = 0; i < PLANE_NUM_DAMAGE_PARTS; i++)
{
if(sm_iHierarchyIdToSection[i] == iSection)
{
return (eHierarchyId)(i + PLANE_FIRST_DAMAGE_PART);
}
}
return VEH_INVALID_ID;
}
float CAircraftDamage::GetSectionHealthAsFractionActual(eAircraftSection iSection) const
{
Assert(iSection < NUM_DAMAGE_SECTIONS);
Assert(iSection > -1);
return GetSectionHealth(iSection) / sm_fInitialComponentHealth[iSection];
}
float CAircraftDamage::GetSectionHealthAsFraction(eAircraftSection iSection) const
{
Assert(iSection < NUM_DAMAGE_SECTIONS);
Assert(iSection > -1);
return Max(GetSectionHealth(iSection), 0.0f) / sm_fInitialComponentHealth[iSection];
}
void CAircraftDamage::SetSectionHealthAsFraction(eAircraftSection iSection, float fHealth)
{
Assert(iSection < NUM_DAMAGE_SECTIONS);
Assert(iSection > -1);
SetSectionHealth(iSection, fHealth * sm_fInitialComponentHealth[iSection]);
}
extern float sfEngineMinDistance;
float sfPlaneEngineBreakDownHealth = 200.0f; // Also referenced in CVehicleDamage::ProcessPetrolTankDamage
bank_float dfEngineDegradeStartingHealth = ENGINE_DAMAGE_PLANE_DAMAGE_START;
bank_float dfEngineDegradeMinDamage = 4.0f;
bank_float dfEngineDegradeMaxDamage = 8.0f;
float dfPlaneEngineMissFireStartingHealth = 600.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfPlaneEngineMissFireMinTime = 0.5f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfPlaneEngineMissFireMaxTime = 1.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfPlaneEngineMissFireMinRecoverTime = 10.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfPlaneEngineMissFireMaxRecoverTime = 20.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
void CAircraftDamage::ProcessPhysics(CPlane* pParent, float fTimeStep)
{
// Process damage fire
for(int i = 0; i < m_pDamageFlames.GetCount(); i++)
{
m_pDamageFlames[i]->fLifeSpan -= fTimeStep;
if(m_pDamageFlames[i]->fLifeSpan<=0.0f)
{
delete m_pDamageFlames[i];
m_pDamageFlames.Delete(i);
i--;
#if __ASSERT
for(int debugId = 1; debugId < m_pDamageFlames.size(); debugId++)
{
Assertf(m_pDamageFlames[debugId-1]->iEffectIndex < m_pDamageFlames[debugId]->iEffectIndex,
"CAircraftDamage::ProcessPhysics Effect IDs are not sorted properly, the id at slot %d is %d and at slot %d is %d",
debugId-1, m_pDamageFlames[debugId-1]->iEffectIndex, debugId, m_pDamageFlames[debugId]->iEffectIndex);
Assertf(debugId <= m_pDamageFlames [debugId]->iEffectIndex,
"CAircraftDamage::ProcessPhysics Effect IDs are not sorted properly, the id at slot %d is %d",
debugId, m_pDamageFlames[debugId]->iEffectIndex);
}
#endif
}
}
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fBrokenOffSmokeLifeTime[i] -= fTimeStep;
m_fBrokenOffSmokeLifeTime[i] = Max(m_fBrokenOffSmokeLifeTime[i], 0.0f);
}
if (pParent && pParent->IsNetworkClone())
return;
// Process engine degrade
float planeDamageThreshold = pParent->m_Transmission.GetPlaneDamageThresholdOverride();
if (planeDamageThreshold==0.0f)
{
planeDamageThreshold = dfEngineDegradeStartingHealth;
}
if(pParent->CanEngineDegrade() && pParent->m_nVehicleFlags.bEngineOn
&& pParent->GetVehicleDamage()->GetEngineHealth() > sfPlaneEngineBreakDownHealth
&& pParent->GetVehicleDamage()->GetEngineHealth() < planeDamageThreshold)
{
if(m_fEngineDegradeTimer > 1.0f)
{
float fEngineDegradeDamage = dfEngineDegradeMinDamage + (dfEngineDegradeMaxDamage - dfEngineDegradeMinDamage)
* ((pParent->GetVehicleDamage()->GetEngineHealth() - sfPlaneEngineBreakDownHealth) / (planeDamageThreshold - sfPlaneEngineBreakDownHealth));
fEngineDegradeDamage = Clamp(fEngineDegradeDamage, dfEngineDegradeMinDamage, dfEngineDegradeMaxDamage);
Vector3 vEnginePosLocal = VEC3_ZERO;
if(pParent->GetBoneIndex(VEH_ENGINE) >= 0)
{
Matrix34 matEngineLocal = pParent->GetLocalMtx(pParent->GetBoneIndex(VEH_ENGINE));
vEnginePosLocal = matEngineLocal.d;
vEnginePosLocal.x = 0.0f;
}
if(GetSectionHealth(ENGINE_R) > 0.0f)
{
Vector3 vRightEnginePos = vEnginePosLocal + XAXIS * (sfEngineMinDistance - SMALL_FLOAT);
float fRightEngineDamageMult = 1.0f - GetSectionHealthAsFraction(ENGINE_R);
pParent->GetVehicleDamage()->ApplyDamageToEngine(pParent, DAMAGE_TYPE_COLLISION, fEngineDegradeDamage * fRightEngineDamageMult, vRightEnginePos, -ZAXIS, ZAXIS, false, true, 0.0f);
}
if(GetSectionHealth(ENGINE_L) > 0.0f)
{
Vector3 vLeftEnginePos = vEnginePosLocal - XAXIS * (sfEngineMinDistance - SMALL_FLOAT);
float fLeftEngineDamageMult = 1.0f - GetSectionHealthAsFraction(ENGINE_L);
pParent->GetVehicleDamage()->ApplyDamageToEngine(pParent, DAMAGE_TYPE_COLLISION, fEngineDegradeDamage * fLeftEngineDamageMult, vLeftEnginePos, -ZAXIS, ZAXIS, false, true, 0.0f);
}
m_fEngineDegradeTimer = 0.0f;
}
m_fEngineDegradeTimer += fTimeStep;
}
if( (pParent->m_nVehicleFlags.bCanEngineMissFire || pParent->GetVehicleDamage()->GetEngineHealth() <= 0.0f )
&& pParent->m_nVehicleFlags.bEngineOn
&& pParent->GetVehicleDamage()->GetEngineHealth() < sfPlaneEngineBreakDownHealth
&& !pParent->IsNetworkClone())
{
pParent->SwitchEngineOff();
static_cast<audPlaneAudioEntity*>(pParent->m_VehicleAudioEntity)->TriggerBackFire();
}
}
void CAircraftDamage::ProcessVfx(CPlane* pParent)
{
for (int i=0; i<m_pDamageFlames.GetCount(); i++)
{
Assert(m_pDamageFlames[i]->fLifeSpan>0.0f);
CAircraftDamage::DamageFlame* pDamageFlame = m_pDamageFlames[i];
g_vfxVehicle.UpdatePtFxPlaneDamageFire(pParent, RCC_VEC3V(pDamageFlame->vLocalOffset), pDamageFlame->fLifeSpan, ms_fMaxFlameLifeSpan, pDamageFlame->iEffectIndex);
}
for (int i=0; i<NUM_DAMAGE_SECTIONS; i++)
{
if(m_fBrokenOffSmokeLifeTime[i] > 0.0f && HasSectionBrokenOff(pParent, i))
{
eHierarchyId nId = GetHierarchyIdFromSection(i);
int nBoneIndex = pParent->GetBoneIndex(nId);
if(nBoneIndex > -1)
{
const crBoneData* pParentBoneData = pParent->GetVehicleFragInst()->GetType()->GetSkeletonData().GetBoneData(nBoneIndex)->GetParent();
int nParentIndex = pParent->GetVehicleFragInst()->GetComponentFromBoneIndex(pParentBoneData->GetIndex());
if(nParentIndex > -1 && !pParent->GetVehicleFragInst()->GetChildBroken(nParentIndex))
{
Vector3 vPos;
if(pParent->GetDefaultBonePositionForSetup(nId, vPos))
{
g_vfxVehicle.UpdatePtFxAircraftSectionDamageSmoke(pParent, RCC_VEC3V(vPos), i);
}
}
}
}
}
}
float CAircraftDamage::ApplyDamageToEngine(CEntity* inflictor, CPlane *pParent, float fDamage, eDamageType nDamageType, const Vector3 &vecPosLocal, const Vector3 &vecNormLocal)
{
if (pParent && pParent->IsNetworkClone())
return 0.f;
float fOriginalDamage = fDamage;
eAircraftSection nEngineSection = ENGINE_L;
eHierarchyId nLeftEngineId = GetHierarchyIdFromSection(ENGINE_L);
int iLeftEngineBoneIndex = pParent->GetBoneIndex(nLeftEngineId);
eHierarchyId nRightEngineId = GetHierarchyIdFromSection(ENGINE_R);
int iRightEngineBoneIndex = pParent->GetBoneIndex(nRightEngineId);
if(iLeftEngineBoneIndex >= 0 && iRightEngineBoneIndex >= 0)
{
Matrix34 matLeftEngineLocal = pParent->GetLocalMtx(iLeftEngineBoneIndex);
Matrix34 matRightEngineLocal = pParent->GetLocalMtx(iRightEngineBoneIndex);
if(matLeftEngineLocal.d.Dist2(vecPosLocal) < matRightEngineLocal.d.Dist2(vecPosLocal))
{
nEngineSection = ENGINE_L;
}
else
{
nEngineSection = ENGINE_R;
}
}
else if(iLeftEngineBoneIndex >= 0)
{
nEngineSection = ENGINE_L;
}
else if(iRightEngineBoneIndex >= 0)
{
nEngineSection = ENGINE_R;
}
else
{
if(vecPosLocal.x < 0.0f)
{
nEngineSection = ENGINE_L;
}
else
{
nEngineSection = ENGINE_R;
}
}
// Scale the damage by the engine health and engine section ratio
float fDamageMult = (sm_fInitialComponentHealth[nEngineSection] * 2.0f) / ENGINE_HEALTH_MAX;
fDamage *= fDamageMult;
float fOriginalHealth = GetSectionHealth(nEngineSection);
Vector3 vecPosGlobal, vecNormGlobal;
const Matrix34& matParent = RCC_MATRIX34(pParent->GetMatrixRef());
matParent.Transform(vecPosLocal, vecPosGlobal);
matParent.Transform3x3(vecNormLocal, vecNormGlobal);
ApplyDamageToSection(pParent, nEngineSection, 0, fDamage, nDamageType, vecPosGlobal, vecNormGlobal);
// inflict unconsumed damage to the other engine
if(nDamageType == DAMAGE_TYPE_EXPLOSIVE)
{
float fInflictedDamage = fDamage - Max(fOriginalHealth, 0.0f);
if(fInflictedDamage > 0.0f)
{
eAircraftSection nOtherEngineSection = nEngineSection == ENGINE_L ? ENGINE_R : ENGINE_L;
ApplyDamageToSection(pParent, nOtherEngineSection, 0, fInflictedDamage, nDamageType, vecPosGlobal, vecNormGlobal);
}
}
// Try to cut out one engine before all engines cut out
if(pParent->GetVehicleDamage()->GetEngineHealth() > sfPlaneEngineBreakDownHealth && GetSectionHealth(ENGINE_L) > 0.0f && GetSectionHealth(ENGINE_R) > 0.0f)
{
float fEngineBreakDownHealthAsFraction = sfPlaneEngineBreakDownHealth / ENGINE_HEALTH_MAX;
if(GetSectionHealthAsFraction(ENGINE_L) < GetSectionHealthAsFraction(ENGINE_R) && GetSectionHealthAsFraction(ENGINE_L) < fEngineBreakDownHealthAsFraction)
{
SetSectionHealth(ENGINE_L, 0.0f);
}
else if(GetSectionHealthAsFraction(ENGINE_R) < GetSectionHealthAsFraction(ENGINE_L) && GetSectionHealthAsFraction(ENGINE_R) < fEngineBreakDownHealthAsFraction)
{
SetSectionHealth(ENGINE_R, 0.0f);
}
}
//Check if any of the engines were destroyed.
if (GetSectionHealth(ENGINE_L) <= 0.0f)
{
CheckDestroyedByPed(inflictor, pParent, ENGINE_L);
}
else if(GetSectionHealth(ENGINE_R) <= 0.0f)
{
CheckDestroyedByPed(inflictor, pParent, ENGINE_R);
}
// Find out the amount of damage applies to main engine
if(pParent->GetVehicleDamage()->GetEngineHealth() > sfPlaneEngineBreakDownHealth)
{
float fExpectedMainEngineHealth;
if(GetSectionHealth(ENGINE_L) > 0.0f && GetSectionHealth(ENGINE_R) > 0.0f)
{
fExpectedMainEngineHealth = (GetSectionHealthAsFraction(ENGINE_L) + GetSectionHealthAsFraction(ENGINE_R)) * 0.5f * ENGINE_HEALTH_MAX;
}
else if(fOriginalHealth > 0.0f && GetSectionHealth(nEngineSection) <= 0.0f)
{
fExpectedMainEngineHealth = (GetSectionHealthAsFraction(ENGINE_L) + GetSectionHealthAsFraction(ENGINE_R)) * 0.5f * ENGINE_HEALTH_MAX;
}
else
{
fExpectedMainEngineHealth = Max(GetSectionHealthAsFraction(ENGINE_L), GetSectionHealthAsFraction(ENGINE_R)) * ENGINE_HEALTH_MAX;
}
return Max(pParent->GetVehicleDamage()->GetEngineHealth() - fExpectedMainEngineHealth, 0.0f);
}
else
{
return fOriginalDamage;
}
}
#if __BANK && DEBUG_DRAW
void CAircraftDamage::DebugDraw(CPlane* pParent)
{
if(ms_bDrawFlamePosition)
{
for (int i=0; i<m_pDamageFlames.GetCount(); i++)
{
CAircraftDamage::DamageFlame* pDamageFlame = m_pDamageFlames[i];
Assert(pDamageFlame->iSectionIndex < NUM_DAMAGE_SECTIONS);
Assert(pDamageFlame->iSectionIndex >= -1); // VEH_INVALID_ID (-1) indicates for chassis
Vector3 vPos;
Matrix34 matParent;
pParent->GetMatrixCopy(matParent);
matParent.Transform(pDamageFlame->vLocalOffset, vPos);
float t = pDamageFlame->fLifeSpan/ms_fMaxFlameLifeSpan;
Vector3 vGreen = VEC3V_TO_VECTOR3(Color_green.GetRGB());
Vector3 vRed = VEC3V_TO_VECTOR3(Color_red.GetRGB());
Color32 color (vGreen + (vRed - vGreen) * t);
grcDebugDraw::Sphere(vPos, 0.1f, color, false);
}
}
}
#endif
void CAircraftDamage::UpdateBuoyancyOnPartBreakingOff(CPlane* pParent, int nComponentID)
{
// Here we can scale the buoyancy of any breakable plane parts to alter how they float once broken off as a separate
// inst. We couldn't do this in GenerateWaterSamplesForPlane() because we didn't have a mapping between bound
// components and bones / hierarchy IDs at that point.
CBuoyancyInfo* pBuoyancyInfo = pParent->GetBaseModelInfo()->GetBuoyancyInfo();
Assert(pBuoyancyInfo);
// Search for the water sample index associated with this part.
int nSectionID = GetSectionFromChildIndex(pParent, nComponentID);
for(int i = 0; i < pBuoyancyInfo->m_nNumWaterSamples; ++i)
{
CWaterSample& sample = pBuoyancyInfo->m_WaterSamples[i];
if(nSectionID == CAircraftDamage::GetSectionFromChildIndex(pParent, sample.m_nComponent))
{
switch(nSectionID)
{
case WING_L:
case WING_R:
case TAIL:
case ENGINE_L:
case ENGINE_R:
case ELEVATOR_L:
case ELEVATOR_R:
case AILERON_L:
case AILERON_R:
case RUDDER:
case RUDDER_2:
case AIRBRAKE_L:
case AIRBRAKE_R:
// All these parts get their buoyancy scaled right down so that they sink when they break off.
sample.m_fBuoyancyMult = 0.0f;
break;
default:
// Not all components have a damage section ID. They come through here.
break;
}
}
}
}
static dev_float sfDamageModifier = 0.2f; // this is how much the damage affects handling BEFORE a part is totally destroyed
static dev_float sfLiftDamageModifier = 0.1f;
static dev_float sfAileronDisabledMult = 0.7f;
// So you get handling modifier in range 1 -> (1-sfDamageModifier) before anything has fallen off
float CAircraftDamage::GetRollMult(CPlane* pParent) const
{
bool bIgnoreBrokenOffParts = GetShouldIgnoreBrokenParts( pParent );
float fLeftMult = bIgnoreBrokenOffParts ? 1.0f : GetSectionHealthAsFraction(AILERON_L);
if(fLeftMult > 0.0f || !HasSectionBrokenOff(pParent,AILERON_L))
{
fLeftMult = 1.0f - (sfDamageModifier*(1.0f - fLeftMult ));
}
float fRightMult = bIgnoreBrokenOffParts ? 1.0f : GetSectionHealthAsFraction(AILERON_R);
if(fRightMult > 0.0f || !HasSectionBrokenOff(pParent,AILERON_R))
{
fRightMult = 1.0f - (sfDamageModifier*(1.0f - fRightMult ));
}
//Ailerons can be disable from script, so change the handling due to that
if(pParent->GetLeftAileronDisabled())
{
fLeftMult *= sfAileronDisabledMult;
}
if(pParent->GetRightAileronDisabled())
{
fRightMult *= sfAileronDisabledMult;
}
return Clamp((fLeftMult + fRightMult) / 2.0f, 0.0f, 1.0f);
}
float CAircraftDamage::GetPitchMult(CPlane* pParent) const
{
bool bIgnoreBrokenOffParts = GetShouldIgnoreBrokenParts( pParent );
if( !bIgnoreBrokenOffParts &&
HasSectionBrokenOff(pParent, ELEVATOR_L) && HasSectionBrokenOff(pParent, ELEVATOR_R))
{
return 0.0f;
}
float fLeftMult = 1.0f;
float fRightMult = 1.0f;
if( !bIgnoreBrokenOffParts )
{
fLeftMult -= (sfDamageModifier*(1.0f - GetSectionHealthAsFraction(ELEVATOR_L)));
fRightMult -= (sfDamageModifier*(1.0f - GetSectionHealthAsFraction(ELEVATOR_R)));
}
return Clamp((fLeftMult + fRightMult) / 2.0f, 0.0f, 1.0f);
}
float CAircraftDamage::GetYawMult(CPlane* pParent) const
{
bool bIgnoreBrokenOffParts = GetShouldIgnoreBrokenParts( pParent );
float fMult = bIgnoreBrokenOffParts ? 1.0f : GetSectionHealthAsFraction(RUDDER);
if(fMult > 0.0f || !HasSectionBrokenOff(pParent, RUDDER))
{
fMult = 1.0f - (sfDamageModifier*(1.0f - fMult));
}
return Clamp(fMult, 0.0f, 1.0f);
}
float CAircraftDamage::GetLiftMult(CPlane* pParent) const
{
bool bIgnoreBrokenOffParts = GetShouldIgnoreBrokenParts( pParent );
if( !bIgnoreBrokenOffParts &&
( HasSectionBrokenOff(pParent,WING_L) || GetHierarchyIdFromSection(WING_L) == VEH_INVALID_ID ) &&
( HasSectionBrokenOff(pParent, WING_R) || GetHierarchyIdFromSection(WING_R) == VEH_INVALID_ID ) )
{
return 0.0f;
}
float fEngineMult = GetThrustMult(pParent);
float fLeftMult = bIgnoreBrokenOffParts ? 1.0f : GetSectionHealthAsFraction(WING_L);
if(fLeftMult > 0.0f || !HasSectionBrokenOff(pParent,WING_L))
{
fLeftMult = 1.0f - (sfLiftDamageModifier*(1.0f - fLeftMult));
}
float fRightMult = bIgnoreBrokenOffParts ? 1.0f : GetSectionHealthAsFraction(WING_R);
if(fRightMult > 0.0f || !HasSectionBrokenOff(pParent, WING_R))
{
fRightMult = 1.0f - (sfLiftDamageModifier*(1.0f - fRightMult));
}
float fGlideMulti = 1.0f;
if(fEngineMult == 0.0f && pParent->pHandling && pParent->pHandling->GetFlyingHandlingData())
{
fGlideMulti = pParent->pHandling->GetFlyingHandlingData()->m_fEngineOffGlideMulti;
}
return Clamp((fLeftMult + fRightMult + fEngineMult) * fGlideMulti / 3.0f, 0.0f, 1.0f);
}
dev_float dfEngineWarmUpMaxTime = 5.0f;
float CAircraftDamage::GetThrustMult(CPlane* pParent) const
{
if(pParent->m_Transmission.GetCurrentlyMissFiring())
{
return 0.0f;
}
if(!pParent->IsEngineOn())
{
return 0.0f;
}
if(pParent->GetStatus() == STATUS_WRECKED || (HasSectionBrokenOff(pParent,CAircraftDamage::WING_L) && HasSectionBrokenOff(pParent, CAircraftDamage::WING_R)))
{
return 0.0f;
}
float fLeftEngineMult = GetSectionHealthAsFraction(ENGINE_L);
if(fLeftEngineMult > 0.0f || !HasSectionBrokenOff(pParent,ENGINE_L))
{
fLeftEngineMult = 1.0f - (sfDamageModifier*(1.0f - fLeftEngineMult));
}
float fRightEngineMult = GetSectionHealthAsFraction(ENGINE_R);
if(fRightEngineMult > 0.0f || !HasSectionBrokenOff(pParent, ENGINE_R))
{
fRightEngineMult = 1.0f - (sfDamageModifier*(1.0f - fRightEngineMult));
}
float fMainEngineMult = (pParent->GetVehicleDamage()->GetEngineHealth() - sfPlaneEngineBreakDownHealth) / (ENGINE_HEALTH_MAX - sfPlaneEngineBreakDownHealth);
fMainEngineMult = Clamp(fMainEngineMult, 0.0f, 1.0f);
fMainEngineMult = 1.0f - (sfDamageModifier*(1.0f - fMainEngineMult));
float fEngineHealthMult = Min((fLeftEngineMult + fRightEngineMult) / 2.0f, fMainEngineMult);
float fMarmUpMult = Clamp(pParent->GetWarmUpTime() / dfEngineWarmUpMaxTime, 0.0f, 1.0f);
return Clamp(fEngineHealthMult * fMarmUpMult, 0.0f, 1.0f);
}
void CAircraftDamage::Fix()
{
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fHealth[i] = sm_fInitialComponentHealth[i];
m_fBrokenOffSmokeLifeTime[i] = 0.0f;
}
// clear out all the damage fire
for(int index = 0; index < m_pDamageFlames.GetCount(); index++)
{
delete m_pDamageFlames[index];
}
m_pDamageFlames.Reset();
m_fEngineDegradeTimer = 0.0f;
}
void CAircraftDamage::BlowingUpVehicle(CPlane *UNUSED_PARAM(pParent))
{
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fBrokenOffSmokeLifeTime[i] = 0.0f;
}
// clear out all the damage fire
for(int index = 0; index < m_pDamageFlames.GetCount(); index++)
{
delete m_pDamageFlames[index];
}
m_pDamageFlames.Reset();
}
void CAircraftDamage::ApplyDamageToSection(CPlane* pParent, int iSection, int iComponent, float fDamage, eDamageType nDamageType, const Vector3 &vHitPos, const Vector3 &vHitNorm)
{
FastAssert(iSection < NUM_DAMAGE_SECTIONS);
if(nDamageType == DAMAGE_TYPE_COLLISION)
{
ComputeFlameDamageAndCreatNewFire(pParent, iSection, iComponent, fDamage, vHitPos);
}
else if(nDamageType == DAMAGE_TYPE_EXPLOSIVE)
{
// If any controlling parts gets hit by explosion, they should break off instantly
if( m_bControlPartsBreakOffInstantly &&
( iSection == ELEVATOR_L || iSection == ELEVATOR_R
|| iSection == AILERON_L || iSection == AILERON_R
|| iSection == RUDDER || iSection == RUDDER_2
|| iSection == AIRBRAKE_L || iSection == AIRBRAKE_R) )
{
fDamage = Max(fDamage, m_fHealth[iSection]);
}
//if (iSection==WING_L || iSection==WING_R)
{
VfxCollInfo_s vfxCollInfo;
vfxCollInfo.regdEnt = pParent;
vfxCollInfo.vPositionWld = VECTOR3_TO_VEC3V(vHitPos);
vfxCollInfo.vNormalWld = VECTOR3_TO_VEC3V(vHitNorm);
vfxCollInfo.vDirectionWld = -VECTOR3_TO_VEC3V(vHitNorm);
vfxCollInfo.materialId = PGTAMATERIALMGR->g_idCarMetal;
vfxCollInfo.roomId = -1;
vfxCollInfo.componentId = iComponent;
vfxCollInfo.weaponGroup = WEAPON_EFFECT_GROUP_EXPLOSION;
vfxCollInfo.force = 100.0f;
vfxCollInfo.isBloody = false;
vfxCollInfo.isWater = false;
vfxCollInfo.isExitFx = false;
vfxCollInfo.noPtFx = false;
vfxCollInfo.noPedDamage = false;
vfxCollInfo.noDecal = false;
vfxCollInfo.isSnowball = false;
vfxCollInfo.isFMJAmmo = false;
// play any weapon impact effects
g_vfxWeapon.DoWeaponImpactVfx(vfxCollInfo, DAMAGE_TYPE_EXPLOSIVE, NULL);
}
}
m_fHealth[iSection] -= fDamage * m_fHealthDamageScale[iSection];
if(iSection == ENGINE_L || iSection == ENGINE_R)
{
m_fHealth[iSection] = rage::Max(ENGINE_DAMAGE_FINSHED,m_fHealth[iSection]);
}
else
{
m_fHealth[iSection] = rage::Max(0.0f,m_fHealth[iSection]);
}
}
void CAircraftDamage::ApplyDamageToChassis(CPlane* pParent, float fDamage, eDamageType nDamageType, int nComponent, const Vector3 &vHitPos)
{
if(nDamageType == DAMAGE_TYPE_COLLISION)
{
ComputeFlameDamageAndCreatNewFire(pParent, INVALID_SECTION, nComponent, fDamage, vHitPos);
}
}
CAircraftDamage::DamageFlame *CAircraftDamage::NewFlame()
{
if(m_pDamageFlames.IsFull())
{
Assertf(false, "CAircraftDamage::m_pDamageFlames is full, the collection size is %d", m_pDamageFlames.GetMaxCount());
return NULL;
}
DamageFlame *pNewFlame = rage_new DamageFlame();
if(pNewFlame == NULL)
{
Assertf(false, "CAircraftDamage::DamageFlame pool is full, the pool size is %d", DamageFlame::GetPool()->GetSize());
return NULL;
}
// Find unique effect id
int id = 0;
for(int i = 0; i < m_pDamageFlames.size(); i++)
{
if(i == m_pDamageFlames[i]->iEffectIndex)
{
id++;
continue;
}
break;
}
pNewFlame->iEffectIndex = id;
DamageFlame *&prNewFlame = m_pDamageFlames.Insert(id);
prNewFlame = pNewFlame;
#if __ASSERT
for(int debugId = 1; debugId < m_pDamageFlames.size(); debugId++)
{
Assertf(m_pDamageFlames[debugId-1]->iEffectIndex < m_pDamageFlames[debugId]->iEffectIndex,
"CAircraftDamage::NewFlame Effect IDs are not sorted properly, the id at slot %d is %d and at slot %d is %d",
debugId-1, m_pDamageFlames[debugId-1]->iEffectIndex, debugId, m_pDamageFlames[debugId]->iEffectIndex);
Assertf(debugId <= m_pDamageFlames [debugId]->iEffectIndex,
"CAircraftDamage::NewFlame Effect IDs are not sorted properly, the id at slot %d is %d",
debugId, m_pDamageFlames[debugId]->iEffectIndex);
}
#endif
return pNewFlame;
}
CAircraftDamage::DamageFlame *CAircraftDamage::FindCloseFlame(int iSection, const Vector3 &vHitPosLocal)
{
for(int index = 0; index < m_pDamageFlames.GetCount(); index++)
{
if(m_pDamageFlames[index]->iSectionIndex == iSection
&& m_pDamageFlames[index]->vLocalOffset.Dist2(vHitPosLocal) < (ms_fFlameMergeableDistance * ms_fFlameMergeableDistance))
{
return m_pDamageFlames[index];
}
}
return NULL;
}
void CAircraftDamage::ComputeFlameDamageAndCreatNewFire(CPlane* pParent, int iSection, int nComponent, float &fDamage, const Vector3 &vHitPos)
{
FastAssert(iSection < NUM_DAMAGE_SECTIONS);
if(iSection != INVALID_SECTION && HasSectionBrokenOff(pParent, iSection))
{
return;
}
for (int index = 0; index < m_pDamageFlames.GetCount(); index++)
{
if(m_pDamageFlames[index]->iSectionIndex == iSection)
{
fDamage *= ms_fFlameDamageMultiplier;
}
}
if(fDamage > ms_fMinCausingFlameDamage)
{
Vector3 vPositionLocal;
const Matrix34& matParent = RCC_MATRIX34(pParent->GetMatrixRef());
matParent.UnTransform(vHitPos, vPositionLocal);
// Move the fire position towards to the center of hit component
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(pParent->GetVehicleFragInst()->GetArchetype()->GetBound());
Vector3 vComponentCenterLocal = VEC3V_TO_VECTOR3(pBoundComp->GetCurrentMatrix(nComponent).GetCol3());
vPositionLocal += (vComponentCenterLocal - vPositionLocal) * 0.1f;
if(DamageFlame *pCloseFlame = FindCloseFlame(iSection, vPositionLocal))
{
float t = (fDamage - ms_fMinCausingFlameDamage) / (ms_fMaxCausingFlameDamage - ms_fMinCausingFlameDamage);
t = Min(t, 1.0f);
float fNewLifeSpan = ms_fMinFlameLifeSpan + t * (ms_fMaxFlameLifeSpan - ms_fMinFlameLifeSpan);
pCloseFlame->fLifeSpan = Max(pCloseFlame->fLifeSpan, fNewLifeSpan);
}
else if(DamageFlame *pNewFlame = NewFlame())
{
pNewFlame->vLocalOffset = vPositionLocal;
#if __ASSERT
spdAABB tempBox;
spdAABB boundBox = pParent->GetLocalSpaceBoundBox(tempBox);
boundBox.GrowUniform(ScalarV(0.1f));
Assertf(boundBox.ContainsPoint(VECTOR3_TO_VEC3V(pNewFlame->vLocalOffset)),
"The damage flame offset is not within the bounding box, model %s, box [%3.2f, %3.2f, %3.2f][%3.2f, %3.2f, %3.2f], hitPos[%3.2f, %3.2f, %3.2f], hitPosLocal[%3.2f, %3.2f, %3.2f], section %d, comp %d, damage %3.2f",
pParent->GetModelName(),
boundBox.GetMin().GetXf(), boundBox.GetMin().GetYf(), boundBox.GetMin().GetZf(),
boundBox.GetMax().GetXf(), boundBox.GetMax().GetYf(), boundBox.GetMax().GetZf(),
vHitPos.x, vHitPos.y, vHitPos.z,
vPositionLocal.x, vPositionLocal.y, vPositionLocal.z,
iSection, nComponent, fDamage);
#endif
pNewFlame->iSectionIndex = iSection;
float t = (fDamage - ms_fMinCausingFlameDamage) / (ms_fMaxCausingFlameDamage - ms_fMinCausingFlameDamage);
t = Min(t, 1.0f);
pNewFlame->fLifeSpan = ms_fMinFlameLifeSpan + t * (ms_fMaxFlameLifeSpan - ms_fMinFlameLifeSpan);
}
}
}
dev_float dfPlaneSectionBrokenOffSmokeMaxLifeTime = 3.0f;
dev_float dfPlaneSectionBrokenOffSmokeMinLifeTime = 1.5f;
dev_float dfPlaneSectionBrokenOffEngineHealth = 500.0f;
void CAircraftDamage::PostBreakOffSection(CPlane* pParent, int nHitSection, eDamageType nDamageType)
{
FastAssert(nHitSection < NUM_DAMAGE_SECTIONS);
for(int i = 0; i < m_pDamageFlames.GetCount(); i++)
{
DamageFlame *pFlame = m_pDamageFlames[i];
if(pFlame->iSectionIndex == nHitSection)
{
delete pFlame;
m_pDamageFlames.Delete(i);
i--;
#if __ASSERT
for(int debugId = 1; debugId < m_pDamageFlames.size(); debugId++)
{
Assertf(m_pDamageFlames[debugId-1]->iEffectIndex < m_pDamageFlames[debugId]->iEffectIndex,
"CAircraftDamage::ProcessPhysics Effect IDs are not sorted properly, the id at slot %d is %d and at slot %d is %d",
debugId-1, m_pDamageFlames[debugId-1]->iEffectIndex, debugId, m_pDamageFlames[debugId]->iEffectIndex);
Assertf(debugId <= m_pDamageFlames [debugId]->iEffectIndex,
"CAircraftDamage::ProcessPhysics Effect IDs are not sorted properly, the id at slot %d is %d",
debugId, m_pDamageFlames[debugId]->iEffectIndex);
}
#endif
}
}
if(nDamageType != DAMAGE_TYPE_WATER_CANNON)
{
m_fBrokenOffSmokeLifeTime[nHitSection] = fwRandom::GetRandomNumberInRange(dfPlaneSectionBrokenOffSmokeMinLifeTime, dfPlaneSectionBrokenOffSmokeMaxLifeTime);
}
switch(nHitSection)
{
case WING_L:
{
if(HasSectionBrokenOff(pParent, AILERON_L))
{
SetSectionHealth(AILERON_L, Min(0.0f, GetSectionHealth(AILERON_L)));
PostBreakOffSection(pParent, AILERON_L, nDamageType);
}
if(HasSectionBrokenOff(pParent, AIRBRAKE_L))
{
SetSectionHealth(AIRBRAKE_L, Min(0.0f, GetSectionHealth(AIRBRAKE_L)));
PostBreakOffSection(pParent, AIRBRAKE_L, nDamageType);
}
if( MI_PLANE_TULA.IsValid() &&
pParent->GetModelIndex() == MI_PLANE_TULA )
{
pParent->BreakOffRotor(0);
pParent->BreakOffRotor(1);
pParent->BreakOffRotor(2);
pParent->BreakOffRotor(3);
}
break;
}
case WING_R:
{
if(HasSectionBrokenOff(pParent, AILERON_R))
{
SetSectionHealth(AILERON_R, Min(0.0f, GetSectionHealth(AILERON_R)));
PostBreakOffSection(pParent, AILERON_R, nDamageType);
}
if(HasSectionBrokenOff(pParent, AIRBRAKE_R))
{
SetSectionHealth(AIRBRAKE_R, Min(0.0f, GetSectionHealth(AIRBRAKE_R)));
PostBreakOffSection(pParent, AIRBRAKE_R, nDamageType);
}
break;
}
case TAIL:
{
if(HasSectionBrokenOff(pParent, ELEVATOR_L))
{
SetSectionHealth(ELEVATOR_L, Min(0.0f, GetSectionHealth(ELEVATOR_L)));
PostBreakOffSection(pParent, ELEVATOR_L, nDamageType);
}
if(HasSectionBrokenOff(pParent, ELEVATOR_R))
{
SetSectionHealth(ELEVATOR_R, Min(0.0f, GetSectionHealth(ELEVATOR_R)));
PostBreakOffSection(pParent, ELEVATOR_R, nDamageType);
}
if(HasSectionBrokenOff(pParent, RUDDER))
{
SetSectionHealth(RUDDER, Min(0.0f, GetSectionHealth(RUDDER)));
PostBreakOffSection(pParent, RUDDER, nDamageType);
}
if(HasSectionBrokenOff(pParent, RUDDER_2))
{
SetSectionHealth(RUDDER_2, Min(0.0f, GetSectionHealth(RUDDER_2)));
PostBreakOffSection(pParent, RUDDER_2, nDamageType);
}
break;
}
case ELEVATOR_L:
case AILERON_L:
{
float fDamageToEngine = Max(GetSectionHealth(ENGINE_L) - dfPlaneSectionBrokenOffEngineHealth, 0.0f);
if(fDamageToEngine > 0.0f && nDamageType != DAMAGE_TYPE_EXPLOSIVE)
{
Matrix34 matBreakOffSectionLocal = pParent->GetLocalMtx(pParent->GetBoneIndex(GetHierarchyIdFromSection(nHitSection)));
float fDamageMult = ENGINE_HEALTH_MAX / (sm_fInitialComponentHealth[ENGINE_L] * 2.0f);
pParent->GetVehicleDamage()->ApplyDamageToEngine(pParent, DAMAGE_TYPE_COLLISION, fDamageToEngine * fDamageMult, matBreakOffSectionLocal.d, -ZAXIS, ZAXIS, false, true, 0.0f);
SetSectionHealth(ENGINE_L, Min(GetSectionHealth(ENGINE_L), dfPlaneSectionBrokenOffEngineHealth));
}
break;
}
case ELEVATOR_R:
case AILERON_R:
{
float fDamageToEngine = Max(GetSectionHealth(ENGINE_R) - dfPlaneSectionBrokenOffEngineHealth, 0.0f);
if(fDamageToEngine > 0.0f && nDamageType != DAMAGE_TYPE_EXPLOSIVE)
{
Matrix34 matBreakOffSectionLocal = pParent->GetLocalMtx(pParent->GetBoneIndex(GetHierarchyIdFromSection(nHitSection)));
float fDamageMult = ENGINE_HEALTH_MAX / (sm_fInitialComponentHealth[ENGINE_R] * 2.0f);
pParent->GetVehicleDamage()->ApplyDamageToEngine(pParent, DAMAGE_TYPE_COLLISION, fDamageToEngine * fDamageMult, matBreakOffSectionLocal.d, -ZAXIS, ZAXIS, false, true, 0.0f);
SetSectionHealth(ENGINE_R, Min(GetSectionHealth(ENGINE_R), dfPlaneSectionBrokenOffEngineHealth));
}
break;
}
}
if(pParent)
{
static_cast<audVehicleAudioEntity*>(pParent->GetAudioEntity())->BreakOffSection(nHitSection);
}
}
void CAircraftDamage::FindAllHitSections(int &nNumHitSections, int *pHitSectionIndices, const CPlane *pParent, eDamageType nDamageType, const Vector3& vecPos, int nComponent, float fDamageRadius)
{
nNumHitSections = 0;
if(nDamageType == DAMAGE_TYPE_EXPLOSIVE && fDamageRadius > 0.0f)
{
// Make at least half of the plane getting affected by explosion
fDamageRadius = Max(fDamageRadius, vecPos.Dist(VEC3V_TO_VECTOR3(pParent->GetMatrix().d())));
float fShortestDistanceToExplosionSquared = LARGE_FLOAT;
int nSmallComponentIndex = -1;
for(int iSectionIdx = 0; iSectionIdx < NUM_DAMAGE_SECTIONS; iSectionIdx++)
{
eHierarchyId iHierId = GetHierarchyIdFromSection(iSectionIdx);
int iBoneIdx = pParent->GetBoneIndex(iHierId);
if(iBoneIdx >= 0)
{
Matrix34 matSection;
pParent->GetGlobalMtx(iBoneIdx, matSection);
float fDistanceToExplosionSquared = matSection.d.Dist2(vecPos);
if(fDistanceToExplosionSquared < (fDamageRadius * fDamageRadius))
{
if(NetworkInterface::IsGameInProgress() && !pParent->m_nVehicleFlags.bPlaneResistToExplosion)
{
pHitSectionIndices[nNumHitSections] = iSectionIdx;
nNumHitSections++;
}
else
{
switch(iSectionIdx)
{
// apply the damage to large component all the time
case WING_L:
case WING_R:
case TAIL:
case ENGINE_L:
case ENGINE_R:
// break all air brakes if hit
case AIRBRAKE_L:
case AIRBRAKE_R:
{
pHitSectionIndices[nNumHitSections] = iSectionIdx;
nNumHitSections++;
break;
}
// Only blow off one small component at a time
default:
{
if(fDistanceToExplosionSquared < fShortestDistanceToExplosionSquared)
{
if(nSmallComponentIndex < 0)
{
nSmallComponentIndex = nNumHitSections;
nNumHitSections++;
}
pHitSectionIndices[nSmallComponentIndex] = iSectionIdx;
fShortestDistanceToExplosionSquared = fDistanceToExplosionSquared;
}
break;
}
}
}
}
}
}
}
else
{
pHitSectionIndices[0] = GetSectionFromChildIndex(pParent,nComponent);
nNumHitSections = 1;
}
}
bool CAircraftDamage::GetShouldIgnoreBrokenParts( CPlane* pParent ) const
{
return ( m_bIgnoreBrokenOffPartForAIHandling &&
pParent->GetDriver() &&
!pParent->IsDriverAPlayer() );
}
#if __BANK
const char* strSectionNames[] =
{
"WING_L",
"WING_R",
"TAIL",
"ENGINE_L",
"ENGINE_R",
"ELEVATOR_L",
"ELEVATOR_R",
"AILERON_L",
"AILERON_R",
"RUDDER",
"RUDDER_2",
"AIRBRAKE_L",
"AIRBRAKE_R",
"LANDING_GEAR"
};
void CAircraftDamage::DisplayDebugOutput(CPlane* UNUSED_PARAM(pParent)) const
{
char vehDebugString[512];
static dev_s32 StartX = 20;
static dev_s32 StartY = 10;
int y = StartY;
for(int i =0; i < NUM_DAMAGE_SECTIONS; i++)
{
formatf(vehDebugString,512,"%s %.2f\n",strSectionNames[i],GetSectionHealth((eAircraftSection)i));
grcDebugDraw::PrintToScreenCoors(vehDebugString,StartX,y);
y++;
}
}
#endif
//////////////////////////////////////////////////////////////////////////
// Class CLandingGearDamage
//////////////////////////////////////////////////////////////////////////
const float CLandingGearDamage::sm_fInitialComponentHealth[CLandingGearDamage::NUM_DAMAGE_SECTIONS] =
{
2600.0f, // GEAR_F,
1300.0f, // GEAR_RL,
1300.0f, // GEAR_LM1,
1300.0f, // GEAR_RM,
1300.0f, // GEAR_RM1,
1300.0f // GEAR_RR,
};
const CLandingGearDamage::eLandingGearSection CLandingGearDamage::sm_iHierarchyIdToSection[NUM_LANDING_GEARS] =
{
CLandingGearDamage::GEAR_F,
CLandingGearDamage::GEAR_RL,
CLandingGearDamage::GEAR_LM1,
CLandingGearDamage::GEAR_RM,
CLandingGearDamage::GEAR_RM1,
CLandingGearDamage::GEAR_RR
};
CompileTimeAssert(NUM_LANDING_GEARS == 6);
const float CLandingGearDamage::sm_fInstantBreakOffDamageThreshold[NUM_DAMAGE_SECTIONS] =
{
2400.0f, // GEAR_F,
900.0f, // GEAR_RL,
900.0f, // GEAR_LM1,
900.0f, // GEAR_RM,
900.0f, // GEAR_RM1,
900.0f // GEAR_RR,
};
CLandingGearDamage::CLandingGearDamage()
{
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fHealth[i] = sm_fInitialComponentHealth[i];
m_fHealthDamageScale[i] = 1.0f;
}
m_bHasCustomLandingGearSectionDamageScale = false;
}
// Query damage on sections
float CLandingGearDamage::GetSectionHealthAsFraction(eLandingGearSection iSection) const
{
Assert(iSection < NUM_DAMAGE_SECTIONS);
Assert(iSection > -1);
return Max(GetSectionHealth(iSection), 0.0f) / sm_fInitialComponentHealth[iSection];
}
bool CLandingGearDamage::HasSectionBroken(const CPlane* UNUSED_PARAM(pParent), eLandingGearSection UNUSED_PARAM(iSection)) const
{
// TODO: Check if only the joint is broken
return false;
}
// Specifically damage a section
void CLandingGearDamage::SetSectionHealthAsFraction(eLandingGearSection iSection, float fHealth)
{
Assert(iSection < NUM_DAMAGE_SECTIONS);
Assert(iSection > -1);
SetSectionHealth(iSection, fHealth * sm_fInitialComponentHealth[iSection]);
}
dev_float dfLandingGearOffMissionDamageMulti = 2.0f;
void CLandingGearDamage::ApplyDamageToSection(CPlane* pParent, int iSection, int UNUSED_PARAM(iComponent), float fDamage, eDamageType UNUSED_PARAM(nDamageType), const Vector3 &UNUSED_PARAM(vHitPos), const Vector3 &UNUSED_PARAM(vHitNorm))
{
if(pParent->m_nVehicleFlags.bUnbreakableLandingGear)
{
return;
}
FastAssert(iSection < NUM_DAMAGE_SECTIONS);
if(!CTheScripts::GetPlayerIsOnAMission())
{
fDamage *= dfLandingGearOffMissionDamageMulti;
}
m_fHealth[iSection] -= fDamage * m_fHealthDamageScale[iSection];
m_fHealth[iSection] = rage::Max(0.0f,m_fHealth[iSection]);
}
void CLandingGearDamage::BreakSection(CPlane* UNUSED_PARAM(pParent), eLandingGearSection UNUSED_PARAM(nHitSection))
{
// TODO: Only break the joint and let landing gear swing freely
}
void CLandingGearDamage::Fix()
{
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fHealth[i] = sm_fInitialComponentHealth[i];
}
}
void CLandingGearDamage::BreakAll(CPlane* pParent)
{
for(int i=0; i<pParent->GetNumWheels(); i++)
{
CWheel *pWheel = pParent->GetWheel(i);
if(pWheel)
{
BreakOffComponent(NULL, pParent, pWheel->GetFragChild(), true);
}
}
}
bool CLandingGearDamage::BreakOffComponent(CEntity* UNUSED_PARAM(inflictor), CPlane* pParent, int iComponent, bool bNetworkAllowed)
{
if( pParent->GetModelIndex() != MI_PLANE_MICROLIGHT )
{
int iSectionToBreakOff = GetSectionFromChildIndex(pParent, iComponent);
if(iSectionToBreakOff != INVALID_SECTION)
{
SetSectionHealth(iSectionToBreakOff, Min(0.0f, GetSectionHealth(iSectionToBreakOff)));
BreakOffSection(pParent, iSectionToBreakOff, false, bNetworkAllowed);
return true;
}
}
return false;
}
void CLandingGearDamage::PostBreakOffSection(CPlane* UNUSED_PARAM(pParent), int nSection, eDamageType UNUSED_PARAM(nDamageType))
{
// Request by B* 1060213, break all landing gear on next impact when the front landing gear breaks off
if(nSection == GEAR_F)
{
for(int i = 0 ; i < NUM_DAMAGE_SECTIONS; i++)
{
m_fHealth[i] = rage::Min(m_fHealth[i], 0.0f);
}
}
}
void CLandingGearDamage::ProcessPhysics(CPlane* pParent)
{
// Request by B* 1060213, break all landing gear on next impact when the front landing gear breaks off
if(HasSectionBrokenOff(pParent, GEAR_F))
{
pParent->SetBrake(1.0f);
for(int i=0; i<pParent->GetNumWheels(); i++)
{
CWheel *pWheel = pParent->GetWheel(i);
if(pWheel && pWheel->GetIsTouching())
{
BreakOffComponent(NULL, pParent, pWheel->GetFragChild());
}
}
}
}
bool CLandingGearDamage::IsComponentBreakable(const CPlane *pParent, int iComponent)
{
return GetSectionFromChildIndex(pParent, iComponent) != INVALID_SECTION;
}
// Child index is 1:1 match to composite bound component
int CLandingGearDamage::GetSectionFromChildIndex(const CPlane* pParent, int iChildIndex)
{
// Lookup bone index from child
Assert(pParent);
Assert(pParent->GetVehicleFragInst());
Assert(pParent->GetVehicleFragInst()->GetTypePhysics()->GetNumChildren() > iChildIndex);
int iBoneIndex = pParent->GetVehicleFragInst()->GetType()->GetBoneIndexFromID(
pParent->GetVehicleFragInst()->GetTypePhysics()->GetAllChildren()[iChildIndex]->GetBoneID());
if(iBoneIndex > -1)
{
for(int i =0; i < NUM_LANDING_GEARS; i++)
{
eHierarchyId nId = (eHierarchyId)(i + FIRST_LANDING_GEAR);
if(pParent->GetBoneIndex(nId) == iBoneIndex)
{
return GetSectionFromHierarchyId(nId);
}
}
}
// The impact might be applied on wheels, which should be child of the landing gear. Check parent bone as well.
const crBoneData* pParentBoneData = pParent->GetVehicleFragInst()->GetType()->GetSkeletonData().GetBoneData(iBoneIndex)->GetParent();
if(pParentBoneData)
{
for(int iSectionIdx = 0; iSectionIdx < NUM_DAMAGE_SECTIONS; iSectionIdx++)
{
eHierarchyId iHierId = GetHierarchyIdFromSection(iSectionIdx);
int iSectionBoneIdx = pParent->GetBoneIndex(iHierId);
if(iSectionBoneIdx == pParentBoneData->GetIndex())
{
return iSectionIdx;
break;
}
}
}
return INVALID_SECTION;
}
int CLandingGearDamage::GetSectionFromHierarchyId(eHierarchyId iComponent)
{
int i = ((int) iComponent) - FIRST_LANDING_GEAR;
if(i > -1 && i < NUM_LANDING_GEARS)
{
return sm_iHierarchyIdToSection [i];
}
return INVALID_SECTION;
}
// This will give the first hierarchy id associated with a damage section
// In theory multiple components could be hooked up to one damage section so be warned
eHierarchyId CLandingGearDamage::GetHierarchyIdFromSection(int nLandingGearSection) const
{
for(int i = 0; i < NUM_LANDING_GEARS; i++)
{
if(sm_iHierarchyIdToSection[i] == nLandingGearSection)
{
return (eHierarchyId)(i + FIRST_LANDING_GEAR);
}
}
return VEH_INVALID_ID;
}
void CLandingGearDamage::FindAllHitSections(int &nNumHitSections, int *pHitSectionIndices, const CPlane *pParent, eDamageType nDamageType, const Vector3& vecPos, int nComponent, float fDamageRadius)
{
nNumHitSections = 0;
if(nDamageType == DAMAGE_TYPE_EXPLOSIVE && fDamageRadius > 0.0f)
{
// Don't apply any explosion damage to landing gears if they are locked up
if(pParent->GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_UP)
{
return;
}
// Make at least half of the plane getting affected by explosion
fDamageRadius = Max(fDamageRadius, vecPos.Dist(VEC3V_TO_VECTOR3(pParent->GetMatrix().d())));
for(int iSectionIdx = 0; iSectionIdx < NUM_DAMAGE_SECTIONS; iSectionIdx++)
{
eHierarchyId iHierId = GetHierarchyIdFromSection(iSectionIdx);
int iBoneIdx = pParent->GetBoneIndex(iHierId);
if(iBoneIdx >= 0)
{
Matrix34 matSection;
pParent->GetGlobalMtx(iBoneIdx, matSection);
if(matSection.d.Dist2(vecPos) < (fDamageRadius * fDamageRadius))
{
pHitSectionIndices[nNumHitSections] = iSectionIdx;
nNumHitSections++;
}
}
}
}
else
{
pHitSectionIndices[0] = GetSectionFromChildIndex(pParent,nComponent);
nNumHitSections = 1;
}
}
//////////////////////////////////////////////////////////////////////////
// Class CPlane
//
CPlane::CPlane(const eEntityOwnedBy ownedBy, u32 popType) : CAutomobile(ownedBy, popType, VEHICLE_TYPE_PLANE)
{
m_fYawControl = 0.0f;
m_fPitchControl = 0.0f;
m_fRollControl = 0.0f;
m_fThrottleControl = 0.0f;
m_fAirBrakeControl = 0.0f;
m_fScriptThrottleControl = 1.0f;
m_fVirtualSpeedControl = 1.0f;
m_fScriptTurbulenceMult = 1.0f;
m_fScriptPilotSkillNoiseScalar = 1.0f;
m_fYaw = 0.0f;
m_fRoll = 0.0f;
m_fPitch = 0.0f;
m_fBrake = 0.0f;
m_fVisualRoll = 0.0f;
m_fVisualPitch = 0.0f;
m_fCurrentFlapAngle = 0.0f;
m_fJoystickPitch = 0.0f;
m_fJoystickRoll = 0.0f;
m_fEngineSpeed = 0.0f;
m_TakeOffDirection = 0.0f;
m_FlightDirection = 0.0f;
m_FlightDirectionAvoidingTerrain = 0.0f;
m_LowestFlightHeight = 15.0f;
m_DesiredHeight = 25.0f;
m_MinHeightAboveTerrain = 20.0f;
m_OldTilt = 0.0f;
m_OnGroundTimer = 0;
m_fPreviousRoll = 0.0f;
m_OldOrientation = 0.0f;
m_bVtolRollVariablesInitialised = false;
m_VtolRollIntegral = 0;
m_VtolRollPreviousError = 0;
m_nPhysicalFlags.bFlyer = true;
m_ExtendedRemovalRange = (s16)(2.5f * CVehiclePopulation::GetCullRange(1.0f, 1.0f)); // planes get removed further from the camera
m_nVehicleFlags.bNeverUseSmallerRemovalRange = true;
m_nVehicleFlags.bCanMakeIntoDummyVehicle = true;
m_nVehicleFlags.bIsBig=true;
m_iNumPropellers = 0;
m_bDisableRightAileron = false;
m_bDisableLeftAileron = false;
m_bIsPropellerDamagedThisFrame = false;
m_bIsEngineTurnedOffByPlayer = false;
m_bIsEngineTurnedOffByScript = false;
m_bEnableThrustFallOffThisFrame = true;
m_bDisableAutomaticCrashTask = false;
m_bBreakOffPropellerOnContact = false;
m_bDisableVerticalFlightModeTransition = false;
m_bDisableExplodeFromBodyDamageOnCollision = false;
m_bDisableExplodeFromBodyDamage = false;
m_fTurbulenceMagnitude = 0.0f;
m_fTurbulenceNoise = 0.0f;
m_fTurbulenceTimeSinceLastCycle = 0.0f;
m_fTurbulenceRecoverTimer = 0.0f;
m_vTurbulenceAirflow = VEC3_ZERO;
m_fTurnEngineOffTimer = 0.0f;
m_fEngineWarmUpTimer = 0.0f;
m_fLeftLightRotation = 0.0f;
m_fRightLightRotation = 0.0f;
m_fCurrentVerticalFlightRatio = 1.0f;
m_fDesiredVerticalFlightRatio = 1.0f;
m_fRollInputBias = 0.0f;
m_fPitchInputBias = 0.0f;
m_nVehicleFlags.bExplodesOnHighExplosionDamage = false;
m_nVehicleFlags.bInteriorLightOn = true;
m_IndividualPropellerFlags = m_IndividualPropellerFlags | 0xff;
m_windowBoneCached = false;
m_allowRollAndYawWhenCrashing = true;
m_dipStraightDownWhenCrashing = false;
for(int i = 0; i < 16; i++)
{
m_windowBoneIndices[i] = -1;
}
//m_prevTorque = Vector3( 0.0f, 0.0f, 0.0f );
CFlyingVehicleAvoidanceManager::AddVehicle(RegdVeh(this));
}
//
//
//
CPlane::~CPlane()
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnDestroyOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfDestroyCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
CFlyingVehicleAvoidanceManager::RemoveVehicle(RegdVeh(this));
}
CPlaneIntelligence *CPlane::GetPlaneIntelligence() const
{
Assert(dynamic_cast<CPlaneIntelligence*>(m_pIntelligence));
return static_cast<CPlaneIntelligence*>(m_pIntelligence);
}
static bool sbEnablePropellerMods = false;
void CPlane::SetModelId(fwModelId modelId)
{
CAutomobile::SetModelId(modelId);
eRotationAxis rotationAxis = !GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) ? ROT_AXIS_LOCAL_Y : ROT_AXIS_LOCAL_Z;
// Figure out which propellers are valid, and set them up
for(int nPropIndex = 0 ; nPropIndex < PLANE_NUM_PROPELLERS; nPropIndex++ )
{
eHierarchyId nId = (eHierarchyId)(PLANE_PROP_1 + nPropIndex);
if(GetBoneIndex(nId) > -1 && physicsVerifyf(m_iNumPropellers < PLANE_NUM_PROPELLERS,"Out of room for plane propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId, rotationAxis, this);
m_propellerCollision[m_iNumPropellers].Init(nId,this);
m_fPropellerHealth[m_iNumPropellers] = VEH_DAMAGE_HEALTH_STD;
m_iNumPropellers ++;
}
}
m_fCurrentVerticalFlightRatio = GetVerticalFlightModeAvaliable() ? 1.0f: 0.0f;
// If this is a seaplane, set up the anchor helper and the extension class for instance variables.
if(pHandling->GetSeaPlaneHandlingData())
{
CSeaPlaneExtension* pExtension = GetExtension<CSeaPlaneExtension>();
if(!pExtension)
{
pExtension = rage_new CSeaPlaneExtension();
Assert(pExtension);
GetExtensionList().Add(*pExtension);
}
if(pExtension)
{
pExtension->GetAnchorHelper().SetParent(this);
}
// for the VTOL seaplane we want the wings to default to the normal position
m_fCurrentVerticalFlightRatio = 0.0f;
}
m_fDesiredVerticalFlightRatio = m_fCurrentVerticalFlightRatio;
// Call InitCompositeBound again, as it has dependency with propellers
InitCompositeBound();
}
Vec3V_Out CPlane::ComputeNosePosition() const
{
Vec3V vStartCoords = GetVehiclePosition();
ScalarV fOffsetScalar = ScalarV( GetVehicleModelInfo()->GetBoundingBoxMax().y);
const Vec3V vNoseOffset = GetVehicleForwardDirection() * fOffsetScalar;
return vStartCoords + vNoseOffset;
}
dev_float dfLargePlaneMass = 30000.0f;
bool CPlane::IsLargePlane() const
{
// This mass limit passes for the Cargoplane, Jet and Titan
return pHandling->m_fMass >= dfLargePlaneMass;
}
dev_float dfFastPlaneThrust = 1.0f;
bool CPlane::IsFastPlane() const
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if (pFlyingHandling)
{
// This thrust limit passes for the Besra, Hydra, Lazer and Stunt
return pFlyingHandling->m_fThrust >= dfFastPlaneThrust;
}
return false;
}
bool CPlane::AnyPlaneEngineOnFire() const
{
return GetVehicleDamage()->GetEngineOnFire()
|| (GetAircraftDamage().GetSectionHealth(CAircraftDamage::ENGINE_L) < ENGINE_DAMAGE_ONFIRE && GetAircraftDamage().GetSectionHealth(CAircraftDamage::ENGINE_L) > ENGINE_DAMAGE_FINSHED)
|| (GetAircraftDamage().GetSectionHealth(CAircraftDamage::ENGINE_R) < ENGINE_DAMAGE_ONFIRE && GetAircraftDamage().GetSectionHealth(CAircraftDamage::ENGINE_R) > ENGINE_DAMAGE_FINSHED);
}
//
//
// CPlane::ProcessControl()
//
// not decided what we want to do in here yet:
// probably not too much, unless we moving flyingControls calls
// here. want to use specific vars for player/AI inputs so
// have to organise something
//
//
void CPlane::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
BANK_ONLY(const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessControlOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfProcessControlCallingEntity(), vThisPosition );
#if __BANK
// draw a speedo for player car
if(CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING && (GetStatus()==STATUS_PLAYER))
{
float fSpeed = DotProduct(GetVelocity(), VEC3V_TO_VECTOR3(GetTransform().GetB()));///GAME_VELOCITY_CONST;
float fRoll = ( RtoD * GetTransform().GetRoll());
char debugText[100];
formatf(debugText,100, "Speed: %g, Height: %g, RollAngle: %g\n%s", fSpeed, vThisPosition.z, fRoll,m_landingGear.GetDebugStateName());
grcDebugDraw::PrintToScreenCoors(debugText, 40,25);
CFlightModelHelper tempHelper;
tempHelper.DebugDrawControlInputs(this,m_fEngineSpeed,m_fPitchControl,m_fRollControl,m_fYawControl);
}
if(CVehicle::ms_nVehicleDebug==VEH_DEBUG_DAMAGE)
{
m_aircraftDamage.DisplayDebugOutput(this);
}
#endif
if( GetDriver() && GetDriver()->IsLocalPlayer() && IsEngineOn() && IsInAir() && ShouldGenerateEngineShockingEvents() )
{
if ( !CPopCycle::IsCurrentZoneAirport() )
{
CEventShockingPlaneFlyby ev(*this);
CShockingEventsManager::Add(ev);
}
}
else if( IsPlayerDrivingOnGround() )
{
CEventShockingMadDriver madDriverEvent(*GetDriver());
CShockingEventsManager::Add(madDriverEvent);
}
UpdatePropellers();
SmoothInputs();
m_landingGear.ProcessControl(this);
// Then go ahead and call the parent version of process control.
CAutomobile::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
}
static dev_float ms_fUpdateRotorBoundSpeedRatioThreshold = 0.1f;
bool CPlane::NeedUpdatePropellerBound(int iPropellerIndex) const
{
float fSpeedRatio = m_propellers[iPropellerIndex].GetSpeed()/ms_fPropRenderSpeed;
return fSpeedRatio <= ms_fUpdateRotorBoundSpeedRatioThreshold;
}
void CPlane::UpdatePropellers()
{
float fDesEngineSpeed = ms_fStdEngineSpeed;
bool keepEngineOnInWater = pHandling->GetSeaPlaneHandlingData() ||
!( m_iNumPropellers == 0 || m_fCurrentVerticalFlightRatio < 1.0f );
if(GetDriver() && !GetDriver()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE))
{
if(!m_nVehicleFlags.bEngineOn || (GetIsInWater() && HasContactWheels() == false && !keepEngineOnInWater )
|| m_Transmission.GetCurrentlyMissFiring())
{
if(IsInAir())
{
fDesEngineSpeed = GetWindBlowPropellorSpeed();
}
else
{
fDesEngineSpeed = 0.0f;
}
}
else
{
if(!IsInAir() && GetNumPropellors() == 0)
{
fDesEngineSpeed = bfStdJetOnGroundEngineSpeed;
if(m_fThrottleControl > 0.0f)
fDesEngineSpeed = bfStdJetOnGroundEngineSpeed + m_fThrottleControl*(bfMaxJetOnGroundEngineSpeed - bfStdJetOnGroundEngineSpeed);
else if(m_fThrottleControl < 0.0f)
fDesEngineSpeed = bfStdJetOnGroundEngineSpeed + m_fThrottleControl*(bfStdJetOnGroundEngineSpeed - bfMinJetOnGroundEngineSpeed);
}
else
{
if(m_fThrottleControl > 0.0f)
fDesEngineSpeed = ms_fStdEngineSpeed + m_fThrottleControl*(ms_fMaxEngineSpeed - ms_fStdEngineSpeed);
else if(m_fThrottleControl < 0.0f)
fDesEngineSpeed = ms_fStdEngineSpeed + m_fThrottleControl*(ms_fStdEngineSpeed - ms_fMinEngineSpeed);
}
}
}
else if(!GetIsUsingScriptAutoPilot() && !IsFullThrottleActive())
{
fDesEngineSpeed = 0.0f;
if(m_fEngineSpeed == 0.0f && !IsNetworkClone() && !m_nVehicleFlags.bKeepEngineOnWhenAbandoned && !IsRunningCarRecording())
{
SwitchEngineOff();
}
}
float fEngineSpeedDropRate = ms_fEngineSpeedChangeRate;
float fEngineSpeedIncreaseRate = ms_fEngineSpeedChangeRate;
if( GetIsInWater() &&
!keepEngineOnInWater )
{
fEngineSpeedDropRate = Max(fEngineSpeedDropRate, ms_fEngineSpeedDropRateInWater);
}
if(m_Transmission.GetCurrentlyMissFiring() && GetVehicleDamage()->GetEngineHealth() > (sfPlaneEngineBreakDownHealth + dfEngineDegradeMaxDamage))
{
fEngineSpeedDropRate = Max(fEngineSpeedDropRate, ms_fEngineSpeedDropRateWhenMissFiring);
}
if(!m_nVehicleFlags.bEngineOn && IsInAir() && fDesEngineSpeed > 0.0f)
{
fEngineSpeedDropRate = Min(fEngineSpeedDropRate, dfEngineSpeedPassiveChangeRate);
}
if( GetModelIndex() == MI_PLANE_AVENGER )
{
fEngineSpeedIncreaseRate *= 2.0f;
}
m_fEngineSpeed += rage::Clamp(fDesEngineSpeed - m_fEngineSpeed,-fEngineSpeedDropRate*fwTimer::GetTimeStep(),fEngineSpeedIncreaseRate*fwTimer::GetTimeStep());
if(m_fEngineSpeed == 0.0f)
{
m_fEngineWarmUpTimer = 0.0f;
}
else if(m_fEngineSpeed >= ms_fMinEngineSpeed || m_iNumPropellers == 0 /*Jet engine*/)
{
m_fEngineWarmUpTimer += fwTimer::GetTimeStep();
}
m_fEngineWarmUpTimer = Clamp(m_fEngineWarmUpTimer, 0.0f, dfEngineWarmUpMaxTime);
int iPropsLeft = m_iNumPropellers;
for(int i =0; i< m_iNumPropellers; i++)
{
if(m_fPropellerHealth[i] <= 0.0f)
{
iPropsLeft--;
}
if(IsIndividualPropellerOn(i))
{
f32 propSpeedMult = GetPropellorSpeedMult(i);
// we push this here to prevent an issue from the plane audio entity grabbing the propSpeedMult from the
// audio update thread, as it can block in the anim
static_cast<audPlaneAudioEntity*>(m_VehicleAudioEntity)->SetPropellorSpeedMult(i, propSpeedMult);
m_propellers[i].UpdatePropeller(propSpeedMult * ms_fPropRenderSpeed, fwTimer::GetTimeStep());
}
else
{
f32 propSpeed = m_propellers[i].GetSpeed();
// stop propeller in short time
propSpeed *= 1.0f - fwTimer::GetTimeStep();
f32 propSpeedMult = propSpeed / ms_fPropRenderSpeed;
static_cast<audPlaneAudioEntity*>(m_VehicleAudioEntity)->SetPropellorSpeedMult(i, propSpeedMult);
m_propellers[i].UpdatePropeller(propSpeed, fwTimer::GetTimeStep());
}
// B*1954149: Modify the propeller disc bound include flags based on the propeller speed.
// Disc bound should not collide with anything if the propeller isn't moving.
if(GetVehicleFragInst() && GetVehicleFragInst()->GetArchetype()->GetBound())
{
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
if(pBoundComp->GetTypeAndIncludeFlags() && m_propellerCollision[i].GetFragDisc() > -1)
{
u32 nRotorIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES;
nRotorIncludeFlags &= ~(ArchetypeFlags::GTA_AI_TEST | ArchetypeFlags::GTA_SCRIPT_TEST | ArchetypeFlags::GTA_WHEEL_TEST);
if( GetModelIndex() == MI_PLANE_MICROLIGHT &&
( !IsIndividualPropellerOn( i ) ||
( !sbEnablePropellerMods && i > 0 ) ) )
{
nRotorIncludeFlags = 0;
//m_propellers[ i ].SetPropellerVisible( false );
}
pBoundComp->SetIncludeFlags(m_propellerCollision[i].GetFragDisc(), nRotorIncludeFlags);
}
}
}
//if we're out of props then turn the engine off
if(m_iNumPropellers > 0 && iPropsLeft == 0 && !IsNetworkClone())
{
SwitchEngineOff();
//also kill the engine if it isn't already
if(m_Transmission.GetEngineHealth() > ENGINE_DAMAGE_ONFIRE)
{
m_Transmission.SetEngineHealth(ENGINE_DAMAGE_ONFIRE);
}
}
}
dev_float fWindBlowingPropellorMaxSpeed = 0.025f;
dev_float dfWindBlowingPropellorTopWindSpeed = 10.0f;
dev_float dfWindBlowingPropellorMinAirSpeedRatio = 0.05f;
dev_float dfWindBlowingPropellorMaxAirSpeedRatio = 0.25f;
float CPlane::GetWindBlowPropellorSpeed()
{
float fWindBlowingPropellorSpeed = 0.0f;
if(m_fEngineSpeed < fWindBlowingPropellorMaxSpeed)
{
Vector3 vVelocity = GetVelocity();
Vector3 vecWindSpeed(0.0f, 0.0f, 0.0f);
WIND.GetLocalVelocity(GetTransform().GetPosition(), RC_VEC3V(vecWindSpeed), false, false);
Vector3 vHeading = VEC3V_TO_VECTOR3(GetMatrix().GetCol1());
float fForwardSpeed = Abs(vVelocity.Dot(vHeading));
float fForwardWindSpeed = Abs(vecWindSpeed.Dot(vHeading));
if(fForwardSpeed > pHandling->m_fEstimatedMaxFlatVel * dfWindBlowingPropellorMinAirSpeedRatio)
{
fWindBlowingPropellorSpeed = Clamp(fForwardSpeed / (pHandling->m_fEstimatedMaxFlatVel * dfWindBlowingPropellorMaxAirSpeedRatio) + fForwardWindSpeed / dfWindBlowingPropellorTopWindSpeed, dfWindBlowingPropellorMinAirSpeedRatio, 1.0f) * fWindBlowingPropellorMaxSpeed;
}
}
return fWindBlowingPropellorSpeed;
}
float CPlane::GetPropellorSpeedMult(int i) const
{
float OneMinusEngineSpeedCubed = 1.0f - m_fEngineSpeed;
OneMinusEngineSpeedCubed *= OneMinusEngineSpeedCubed * OneMinusEngineSpeedCubed;
float fSectionHealthMult = 1.0f;
bool bEngineSectionDead = false;
if(m_iNumPropellers > 1)
{
if(m_propellers[i].GetBoneIndex() >= 0)
{
Matrix34 propellerMat = GetObjectMtx(m_propellers[i].GetBoneIndex());
if(propellerMat.d.x < 0.0f)
{
fSectionHealthMult = GetAircraftDamage().GetSectionHealthAsFraction(CAircraftDamage::ENGINE_L) * 2.0f;
}
else
{
fSectionHealthMult = GetAircraftDamage().GetSectionHealthAsFraction(CAircraftDamage::ENGINE_R) * 2.0f;
}
if(fSectionHealthMult <= 0.0f || GetVehicleDamage()->GetEngineHealth() < sfPlaneEngineBreakDownHealth)
{
bEngineSectionDead = true;
}
}
}
float OneMinusEngineHealthCubed = 1.0f - Clamp((GetVehicleDamage()->GetEngineHealth() * fSectionHealthMult) / ENGINE_HEALTH_MAX, 0.0f, 1.0f);
OneMinusEngineHealthCubed *= OneMinusEngineHealthCubed * OneMinusEngineHealthCubed;
static float fMinEngineHealthMult = 0.5f;
if(bEngineSectionDead)
{
OneMinusEngineHealthCubed = 1.0;
}
else
{
OneMinusEngineHealthCubed = Clamp(OneMinusEngineHealthCubed, 0.0f, fMinEngineHealthMult);
}
float fPropSpeedMult = (1.0f - OneMinusEngineSpeedCubed) * (1.0f - OneMinusEngineHealthCubed);
static float fFourBladesPropellerSpeedMulti = 31.2f / ms_fPropRenderSpeed;
if(GetModelIndex() == MI_PLANE_TITAN || (MI_PLANE_VELUM2.IsValid() && GetModelIndex() == MI_PLANE_VELUM2.GetModelIndex()))
{
fPropSpeedMult *= fFourBladesPropellerSpeedMulti;
}
if( GetModelIndex() == MI_PLANE_STARLING )
{
fPropSpeedMult = Min( 1.0f, Dot( GetTransform().GetForward(), VECTOR3_TO_VEC3V( GetVelocity() ) ).Getf() / 20.0f );
fPropSpeedMult *= fPropSpeedMult;
if( GetStatus() == STATUS_WRECKED )
{
fPropSpeedMult = 0.0f;
}
float currentPropSpeed = m_propellers[0].GetSpeed() / ms_fPropRenderSpeed;
fPropSpeedMult = currentPropSpeed + ( ( fPropSpeedMult - currentPropSpeed ) * 0.2f );
}
return fPropSpeedMult;
}
static dev_float dfPlaneJoystickSmoothSpeed = 5.0f;
void CPlane::SmoothInputs()
{
float fRandomControlYaw = 0.0f;
float fRandomControlPitch = 0.0f;
float fRandomControlRoll = 0.0f;
bool isPlayerInControl = false;
static dev_float sfMinVelocityForRandomControl = 10.0f;
if(GetDriver() && GetDriver()->IsPlayer())
{
if(((GetIntelligence()->GetActiveTask() &&
GetIntelligence()->GetActiveTask()->GetTaskType() == CTaskTypes::TASK_VEHICLE_PLAYER_DRIVE_PLANE) || IsNetworkClone()) &&
GetVelocity().Mag2() > sfMinVelocityForRandomControl )
{
isPlayerInControl = true;
CPlayerInfo* pPlayerInfo = GetDriver()->GetPlayerInfo();
physicsFatalAssertf( pPlayerInfo, "Expected a player info!" ); // Assumed to be a player
fRandomControlYaw = pPlayerInfo->GetRandomControlYaw();
fRandomControlPitch = pPlayerInfo->GetRandomControlPitch();
fRandomControlRoll = pPlayerInfo->GetRandomControlRoll();
}
}
float fMaxDelta = fwTimer::GetTimeStep() * (isPlayerInControl ? ms_fControlSmoothSpeed : dfControlSmoothSpeedForAI);
m_fYaw += rage::Clamp(m_fYawControl + fRandomControlYaw - m_fYaw, -fMaxDelta, fMaxDelta);
m_fRoll += rage::Clamp(m_fRollControl + fRandomControlRoll - m_fRoll, -fMaxDelta, fMaxDelta);
m_fPitch += rage::Clamp(m_fPitchControl + fRandomControlPitch - m_fPitch, -fMaxDelta, fMaxDelta);
if( IsDriverAPlayer() )
{
if( isPlayerInControl )
{
m_fBrake += rage::Clamp(-m_fThrottleControl - m_fBrake, -fMaxDelta, fMaxDelta);
}
else
{
// GTAV - B*1771418 - air brakes flicker too much on vehicle select pre race screen
fMaxDelta *= 0.5f;
m_fBrake += rage::Clamp(-m_fThrottleControl - m_fBrake, -fMaxDelta, fMaxDelta);
}
}
else
{
m_fBrake += rage::Clamp(Max(-m_fThrottleControl, m_fAirBrakeControl) - m_fBrake, -fMaxDelta, fMaxDelta);
}
m_fBrake = rage::Clamp(m_fBrake, 0.0f, 1.0f);
// smooth out the joystick control
if(GetDriver() && GetDriver()->IsInFirstPersonVehicleCamera())
{
TUNE_GROUP_FLOAT(JOYSTICK_IK, PlaneJoyStickLerp, 0.1f, 0.0f, 1.0f, 0.001f);
m_fJoystickPitch = rage::Lerp(PlaneJoyStickLerp, m_fJoystickPitch, m_fPitchControl);
m_fJoystickRoll = rage::Lerp(PlaneJoyStickLerp, m_fJoystickRoll, m_fRollControl);
CTaskMotionBase *pCurrentMotionTask = GetDriver()->GetCurrentMotionTask();
if (pCurrentMotionTask && pCurrentMotionTask->GetTaskType() == CTaskTypes::TASK_MOTION_IN_AUTOMOBILE)
{
const CTaskMotionInAutomobile* pAutoMobileTask = static_cast<const CTaskMotionInAutomobile*>(pCurrentMotionTask);
float fAnimWeight = pAutoMobileTask->GetSteeringWheelWeight();
m_fJoystickPitch *= fAnimWeight;
m_fJoystickRoll *= fAnimWeight;
}
}
else
{
// smooth out the joystick control
fMaxDelta = fwTimer::GetTimeStep() * dfPlaneJoystickSmoothSpeed;
m_fJoystickPitch += rage::Clamp(m_fPitchControl + fRandomControlPitch - m_fJoystickPitch, -fMaxDelta, fMaxDelta);
m_fJoystickRoll += rage::Clamp(m_fRollControl + fRandomControlRoll - m_fJoystickRoll, -fMaxDelta, fMaxDelta);
}
}
//
//
//
void CPlane::DisableAileron(bool bLeftAileron, bool bDisableAileron)
{
if(bLeftAileron)
{
m_bDisableLeftAileron = bDisableAileron;
}
else
{
m_bDisableRightAileron = bDisableAileron;
}
}
//
//
//
bool CPlane::IsPropeller (int nComponent) const
{
for(int i = 0; i < m_iNumPropellers; i++)
{
if(m_propellerCollision[i].IsPropellerComponent(this, nComponent))
{
// Allow collision with blade bound when its stationary
if(m_propellerCollision[i].GetFragDisc() != nComponent && m_propellers[i].GetSpeed() == 0.0f)
{
return false;
}
return true;
}
}
return false;
}
bool CPlane::ShouldKeepRotorInCenter(CPropeller *pPropeller) const
{
for(int i = 0; i < m_iNumPropellers; i++)
{
if(&m_propellers[i] == pPropeller)
{
if(m_propellerCollision[i].GetFragChild() >= 0)
{
int iParentGroupIndex = GetFragInst()->GetTypePhysics()->GetChild(m_propellerCollision[i].GetFragChild())->GetOwnerGroupPointerIndex();
if(iParentGroupIndex != 0xFF)
{
fragTypeGroup* pParentGroup = GetFragInst()->GetTypePhysics()->GetGroup(iParentGroupIndex);
// Keep the rotor in center when it's attached to the chassis
if(pParentGroup && pParentGroup->GetParentGroupPointerIndex() == 0)
{
return true;
}
}
}
break;
}
}
return false;
}
bool CPlane::IsStalling() const
{
if(m_VehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_PLANE)
{
return static_cast<audPlaneAudioEntity*>(m_VehicleAudioEntity)->GetIsStallWarningOn();
}
return false;
}
//
//
//
void CPlane::ProcessFlightHandling(float fTimeStep)
{
if(fTimeStep<=0.0f)
return;
if (IsFullThrottleActive())
{
SetThrottleControl(10);
if (!GetDriver() || !GetDriver()->IsPlayer())
{
SetYawControl(0);
SetPitchControl(0);
SetRollControl(0);
}
}
if (m_nVehicleFlags.bIsDrowning && !IsNetworkClone())
{
m_fEngineSpeed = 0.0f;
m_fThrottleControl = 0.0f;
SwitchEngineOff();
}
SetDampingForFlight(m_fCurrentVerticalFlightRatio);
if(GetStatus() != STATUS_WRECKED && !(m_aircraftDamage.HasSectionBrokenOff(this,CAircraftDamage::WING_L) && m_aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R)))
{
//if we can go into vertical flight mode then process both flight models
if(GetVerticalFlightModeAvaliable())
{
if(m_fCurrentVerticalFlightRatio > 0.0f)//no point processing a flight model if it's completely turned off
{
ProcessVerticalFlightModel(fTimeStep, m_fCurrentVerticalFlightRatio);
}
if(m_fCurrentVerticalFlightRatio < 1.0f)//no point processing a flight model if it's completely turned off
{
ProcessFlightModel(fTimeStep, 1.0f - m_fCurrentVerticalFlightRatio);
}
}
else
{
//normal plane just process normal flight model.
ProcessFlightModel(fTimeStep);
}
if(GetDriver() && GetDriver()->IsLocalPlayer())
{
CControl* pControl = GetDriver()->GetControlFromPlayer();
if(pControl)
{
// If hold L2 long enough, turn the engine off - B*931846
float fPlayerThrottleControl = pControl->GetVehicleFlyThrottleUp().GetNorm01() - pControl->GetVehicleFlyThrottleDown().GetNorm01();
if(fPlayerThrottleControl < -0.9f && IsInAir())
{
m_fTurnEngineOffTimer += fTimeStep;
}
else
{
if(fPlayerThrottleControl > 0.9f && IsInAir())
{
ResetEngineTurnedOffByPlayer();
}
else if(Abs(fPlayerThrottleControl) > 0.9f && !IsInAir())
{
ResetEngineTurnedOffByPlayer();
}
m_fTurnEngineOffTimer = 0.0f;
}
if(m_fTurnEngineOffTimer > 1.0f && IsEngineOn() && IsInAir() &&
( !GetVerticalFlightModeAvaliable() || m_fCurrentVerticalFlightRatio < 1.0f ) )
{
SwitchEngineOff();
m_bIsEngineTurnedOffByPlayer = true;
}
}
}
}
Assert(GetTransform().IsOrthonormal());
}
const int diAfterburnerEffectProbeResults = 10;
void CPlane::ProcessPrePhysics()
{
CAutomobile::ProcessPrePhysics();
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
for (int i = 0; i < PLANE_NUM_AFTERBURNERS; ++i)
{
// Blow away everything behind the jet
if (sm_bJetWashEnabled &&
m_nVehicleFlags.bEngineOn && GetVehicleModelInfo() && GetVehicleModelInfo()->GetBoneIndex(PLANE_AFTERBURNER + i) != -1 && pFlyingHandling)
{
//Find out the thrust force
Vector3 vBlowDirection = VEC3V_TO_VECTOR3(GetTransform().GetB());
vBlowDirection.Negate();
float fBlowForce = m_fGravityForWheelIntegrator*GetEngineSpeed()*GetMass()*m_aircraftDamage.GetThrustMult(this)*pFlyingHandling->m_fAfterburnerEffectForceMulti;
s32 afterburnerBoneId = GetVehicleModelInfo()->GetBoneIndex(PLANE_AFTERBURNER + i);
Matrix34 matGlobal;
GetGlobalMtx(afterburnerBoneId, matGlobal);
//setup the capsule test.
WorldProbe::CShapeTestHitPoint testHitPoints[diAfterburnerEffectProbeResults];
WorldProbe::CShapeTestResults capsuleResults(testHitPoints, diAfterburnerEffectProbeResults);
const u32 iTestFlags = (ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE);
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
capsuleDesc.SetResultsStructure(&capsuleResults);
capsuleDesc.SetIncludeFlags(iTestFlags);
capsuleDesc.SetExcludeEntity(this);
capsuleDesc.SetIsDirected(true);
capsuleDesc.SetDoInitialSphereCheck(true);
capsuleDesc.SetCapsule(matGlobal.d, matGlobal.d + vBlowDirection * pFlyingHandling->m_fAfterburnerEffectDistance, pFlyingHandling->m_fAfterburnerEffectRadius);
WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc);
for (WorldProbe::ResultIterator it = capsuleResults.begin(); it < capsuleResults.last_result(); ++it)
{
if (it->IsAHit())
{
CEntity* pHitEntity = CPhysics::GetEntityFromInst(it->GetHitInst());
bool bProcessHit = (pHitEntity && pHitEntity->GetIsPhysical() && pHitEntity->IsCollisionEnabled());
if (NetworkInterface::IsGameInProgress() && bProcessHit)
{
if (pHitEntity->GetIsTypePed())
{
CPed* pVictim = static_cast<CPed*>(pHitEntity);
bProcessHit = NetworkInterface::IsFriendlyFireAllowed(pVictim, GetDriver());
}
else if (pHitEntity->GetIsTypeVehicle())
{
CVehicle* pVictimVehicle = static_cast<CVehicle*>(pHitEntity);
bProcessHit = NetworkInterface::IsFriendlyFireAllowed(pVictimVehicle, GetDriver());
}
}
if (bProcessHit)
{
CPhysical *pHitPhysical = (CPhysical *)pHitEntity;
Vector3 vWorldCentroid;
float fCentroidRadius = 0.0f;
if (pHitEntity->GetCurrentPhysicsInst() && pHitEntity->GetCurrentPhysicsInst()->GetArchetype())
{
vWorldCentroid = VEC3V_TO_VECTOR3(pHitEntity->GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetWorldCentroid(pHitEntity->GetCurrentPhysicsInst()->GetMatrix()));
fCentroidRadius = pHitEntity->GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid();
}
else
{
fCentroidRadius = pHitEntity->GetBoundCentreAndRadius(vWorldCentroid);
}
Vector3 vOffset = it->GetHitPosition() - VEC3V_TO_VECTOR3(pHitPhysical->GetMatrix().GetCol3());
if (it->GetHitPosition().Dist2(vWorldCentroid) > square(fCentroidRadius))
{
Vector3 vOffsetToCentroid = it->GetHitPosition() - vWorldCentroid;
vOffsetToCentroid *= (fCentroidRadius - 0.1f) / it->GetHitPosition().Dist(vWorldCentroid);
Vector3 vHitPositionAdjusted = vWorldCentroid + vOffsetToCentroid;
vOffset = vHitPositionAdjusted - VEC3V_TO_VECTOR3(pHitPhysical->GetMatrix().GetCol3());
}
if (pHitEntity->GetIsTypePed())
{
CPed* pHitPed = static_cast<CPed*>(pHitEntity);
if (pHitPed && !pHitPed->GetUsingRagdoll() && !pHitPed->GetIsParachuting() && !pHitPed->IsNetworkClone())
{
CEventSwitch2NM event(10000, rage_new CTaskNMRelax(1000, 10000));
pHitPed->SwitchToRagdoll(event);
//Calculate and apply damage from jetwash
{
u32 weaponHash = WEAPONTYPE_FALL;
int nComponent = RAGDOLL_SPINE0;
float fDamage = 50.f;
//Apply damage.
CEventDamage tempDamageEvent(GetDriver(), fwTimer::GetTimeInMilliseconds(), weaponHash);
CPedDamageCalculator damageCalculator(NULL, fDamage, weaponHash, nComponent, false);
damageCalculator.ApplyDamageAndComputeResponse(pHitPed, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
}
}
}
float fForceMulti = 1.0f - Clamp(it->GetHitPosition().Dist(matGlobal.d) / (pFlyingHandling->m_fAfterburnerEffectDistance + pFlyingHandling->m_fAfterburnerEffectRadius), 0.1f, 1.0f);
float fAcceleration = fBlowForce * fForceMulti / pHitPhysical->GetMass();
fAcceleration = Min(fAcceleration, DEFAULT_ACCEL_LIMIT - 0.1f);
Vector3 vBlowForce = vBlowDirection * (pHitPhysical->GetMass() * fAcceleration);
pHitPhysical->ApplyForce(vBlowForce, vOffset, it->GetHitComponent());
}
}
}
#if __BANK
if (ms_bDebugAfterburnerEffect)
{
grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(matGlobal.d), VECTOR3_TO_VEC3V(matGlobal.d + vBlowDirection * pFlyingHandling->m_fAfterburnerEffectDistance), pFlyingHandling->m_fAfterburnerEffectRadius, Color_red, false);
}
#endif
}
}
}
static dev_float sfNozzleAdjustAngle = -( DtoR * 90.0f);
static dev_float sfRudderAdjustAngleForVTOL = ( DtoR * 30.0f);
static dev_float sfRudderAdjustAngleForTULA = ( DtoR * 25.0f);
static dev_float sf_VerticalFlightTransitionSpeed = 1.0f;
static dev_float sf_VerticalFlightTransitionSpeedTULA = 0.25f;
dev_float dfPlaneLandingPadShakingIntensityMult = 0.1f;
ePhysicsResult CPlane::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
TUNE_GROUP_BOOL( VEHICLE_TURBULENCE, DISABLE_PLANE_TURBULENCE, false );
m_landingGear.ProcessPhysics(this);
m_aircraftDamage.ProcessPhysics(this, fTimeStep);
m_landingGearDamage.ProcessPhysics(this);
if( !DISABLE_PLANE_TURBULENCE )
{
ProcessTurbulence(fTimeStep);
}
//Work out the position of the nozzles for vertical flight mode use
if(GetVerticalFlightModeAvaliable())
{
float verticalFlightModeTransitionSpeed = sf_VerticalFlightTransitionSpeed;
if( ( MI_PLANE_TULA.IsValid() &&
GetModelIndex() == MI_PLANE_TULA ) ||
GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) )
{
verticalFlightModeTransitionSpeed = sf_VerticalFlightTransitionSpeedTULA;
}
if( !m_bDisableVerticalFlightModeTransition )
{
if(m_fCurrentVerticalFlightRatio < m_fDesiredVerticalFlightRatio)
{
m_fCurrentVerticalFlightRatio += verticalFlightModeTransitionSpeed * fTimeStep;
m_fCurrentVerticalFlightRatio = rage::Clamp(m_fCurrentVerticalFlightRatio, 0.0f, m_fDesiredVerticalFlightRatio);
}
else if(m_fCurrentVerticalFlightRatio > m_fDesiredVerticalFlightRatio)
{
m_fCurrentVerticalFlightRatio -= verticalFlightModeTransitionSpeed * fTimeStep;
m_fCurrentVerticalFlightRatio = rage::Clamp(m_fCurrentVerticalFlightRatio, m_fDesiredVerticalFlightRatio, 1.0f);
}
}
}
// Seaplanes need to be forced to sink if required when destroyed.
if(pHandling->GetSeaPlaneHandlingData())
{
CSeaPlaneExtension* pSeaPlaneExtension = this->GetExtension<CSeaPlaneExtension>();
Assert(pSeaPlaneExtension);
if(pSeaPlaneExtension && m_nVehicleFlags.bBlownUp && pSeaPlaneExtension->m_nFlags.bSinksWhenDestroyed)
{
// If we are anchored then release anchor.
if(pSeaPlaneExtension->GetAnchorHelper().IsAnchored())
{
pSeaPlaneExtension->GetAnchorHelper().Anchor(false);
}
if(m_Buoyancy.m_fForceMult > 0.0f)
{
const float kfSinkForceMultStep = 0.002f;
m_Buoyancy.m_fForceMult -= kfSinkForceMultStep;
}
else
{
m_Buoyancy.m_fForceMult = 0.0f;
}
}
}
ePhysicsResult eResult = CAutomobile::ProcessPhysics(fTimeStep,bCanPostpone,nTimeSlice);
m_bIsPropellerDamagedThisFrame = false;
m_bEnableThrustFallOffThisFrame = true;
if(GetNumWheels() > 0 && GetCollider())
{
#if APPLY_GRAVITY_IN_INTEGRATOR
// Turn the gravity off completely for articulated body, this fix the issue that plane tends to fall down after switching to articulated.
if(GetCollider()->IsArticulated())
{
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, true);
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, true);
}
else
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, true);
#else
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, false);
GetCollider()->GetInstance()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false);
#endif
}
if(GetDriver() && GetDriver()->IsLocalPlayer())
{
int nNumberWheelsJustTouchGround = 0;
int nNumberWheelsTouchGround = 0;
for(int i=0; i<GetNumWheels(); i++)
{
CWheel *pWheel = GetWheel(i);
if(pWheel && pWheel->GetIsTouching())
{
nNumberWheelsTouchGround++;
if(!pWheel->GetWasTouching())
{
nNumberWheelsJustTouchGround++;
}
}
}
if(nNumberWheelsJustTouchGround > 0 && nNumberWheelsTouchGround < 2)
{
CControlMgr::StartPlayerPadShakeByIntensity(400, float(nNumberWheelsJustTouchGround) * 0.5f * dfPlaneLandingPadShakingIntensityMult);
}
}
// Start the crash task if a AI plane lost one or both wings, or engine will never start again, or we're already below min height map somehow
if(GetDriver() && !GetDriver()->IsPlayer() && !m_bDisableAutomaticCrashTask)
{
bool bEngineWillNeverStart = !IsEngineOn() && GetVehicleDamage()->GetEngineHealth() <= sfPlaneEngineBreakDownHealth;
Vec3V vVehiclePos = GetTransform().GetPosition();
float fMinHeightAtPos = CGameWorldHeightMap::GetMinHeightFromWorldHeightMap(vVehiclePos.GetXf(), vVehiclePos.GetYf());
bool bBelowMinHeight = vVehiclePos.GetZf() < fMinHeightAtPos;
if(m_aircraftDamage.HasSectionBrokenOff(this,CAircraftDamage::WING_L) || m_aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R) || bEngineWillNeverStart || bBelowMinHeight)
{
// Create a crash task if one doesn't already exist
if (!IsNetworkClone())
{
aiTask *pActiveTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH);
if(!pActiveTask)
{
CEntity* pLastDamageInflictor = m_aircraftDamage.GetLastDamageInflictor();
CTaskVehicleCrash *pCarTask = rage_new CTaskVehicleCrash(pLastDamageInflictor);
pCarTask->SetCrashFlag(CTaskVehicleCrash::CF_BlowUpInstantly, false);
pCarTask->SetCrashFlag(CTaskVehicleCrash::CF_InACutscene, false);
pCarTask->SetCrashFlag(CTaskVehicleCrash::CF_AddExplosion, true);
GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pCarTask, VEHICLE_TASK_PRIORITY_CRASH);
}
// Do not automatically crash a second time!
m_bDisableAutomaticCrashTask = true;
}
}
}
// Tilt-able wings
if(GetVerticalFlightModeAvaliable())
{
if( ( MI_PLANE_TULA.IsValid() &&
GetModelIndex() == MI_PLANE_TULA ) ||
GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) )
{
fragInst *pVehicleFragInst = GetVehicleFragInst();
float currentAngle = GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) ? ( 1.0f - m_fCurrentVerticalFlightRatio ) * sfNozzleAdjustAngle : -m_fCurrentVerticalFlightRatio * sfNozzleAdjustAngle;
for( int i = 0; i < 2; i++ )
{
int iBoneIndex = GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) ? GetBoneIndex( (eHierarchyId)( (int)PLANE_ENGINE_L + i ) ) : GetBoneIndex( (eHierarchyId)( (int)PLANE_WING_L + i ) );
if( iBoneIndex > -1 )
{
int iFragGroup = (s8)pVehicleFragInst->GetGroupFromBoneIndex(iBoneIndex);
int iFragChild = -1;
if(iFragGroup > -1)
{
fragTypeGroup* pGroup = pVehicleFragInst->GetTypePhysics()->GetAllGroups()[iFragGroup];
iFragChild = (s8)pGroup->GetChildFragmentIndex();
}
else
{
iFragChild = (s8)pVehicleFragInst->GetComponentFromBoneIndex(iBoneIndex);
}
SetBoneRotation( iBoneIndex, ROT_AXIS_LOCAL_X, currentAngle, true, NULL, NULL );
GetSkeleton()->PartialUpdate(iBoneIndex);
if(iBoneIndex > -1 && iFragGroup > -1 && iFragChild > -1)
{
if( pVehicleFragInst->GetArchetype() &&
pVehicleFragInst->GetArchetype()->GetBound() &&
phBound::IsTypeComposite(pVehicleFragInst->GetArchetype()->GetBound()->GetType()))
{
phBoundComposite *pBound = (phBoundComposite *)pVehicleFragInst->GetArchetype()->GetBound();
fragTypeGroup* pGroup = pVehicleFragInst->GetTypePhysics()->GetAllGroups()[iFragGroup];
UpdateLinkAttachmentMatricesRecursive(pBound, pGroup );
}
}
}
}
}
}
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellerCollision[i].UpdateBound(this, m_propellers[i].GetBoneIndex(), m_propellers[i].GetAngle(), m_propellers[i].GetAxis(), NeedUpdatePropellerBound(i));
}
#if __BANK
if(GetDriver() && GetDriver()->IsPlayer() && NetworkInterface::IsGameInProgress() && GetPhysArch() && GetPhysArch()->GetBound() && GetPhysArch()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite *pCompositeBound = static_cast<phBoundComposite *>(GetPhysArch()->GetBound());
pCompositeBound->CalculateCompositeExtents();
bool bBadBoundingBox = false;
if(pCompositeBound->GetRadiusAroundCentroid() > 5.0f * GetBaseModelInfo()->GetBoundingSphereRadius())
{
bBadBoundingBox = true;
}
if(VEC3V_TO_VECTOR3(pCompositeBound->GetBoundingBoxMax() - pCompositeBound->GetBoundingBoxMin()).Mag2() >
17.0f * (GetBaseModelInfo()->GetBoundingBoxMax() - GetBaseModelInfo()->GetBoundingBoxMin()).Mag2())
{
bBadBoundingBox = true;
}
Assertf(!bBadBoundingBox, "[B*2075251] %s: Physics calculated a bounding box bigger than expected (Radius around centroid: %f expected: %f) (Bound box size: %f expected: %f).",
GetDebugName(),
pCompositeBound->GetRadiusAroundCentroid(),
GetBaseModelInfo()->GetBoundingSphereRadius(),
VEC3V_TO_VECTOR3(pCompositeBound->GetBoundingBoxMax() - pCompositeBound->GetBoundingBoxMin()).Mag2(),
(GetBaseModelInfo()->GetBoundingBoxMax() - GetBaseModelInfo()->GetBoundingBoxMin()).Mag2()
);
if(bBadBoundingBox)
{
if(GetNetworkObject())
{
Displayf("[B*2075251] Vehicle (%s Net: %s)", GetDebugName(), GetNetworkObject()->GetLogName());
}
for(int nBound = 0; nBound < pCompositeBound->GetNumBounds(); nBound++)
{
Mat34V currMat = pCompositeBound->GetCurrentMatrix(nBound);
Mat34V lastMat = pCompositeBound->GetLastMatrix(nBound);
Displayf("\ncomponent %i bound matrices set:\n\nCurrent:\n%f, %f, %f, %f\n%f, %f, %f, %f\n%f, %f, %f, %f\nLast:\n%f, %f, %f, %f\n%f, %f, %f, %f\n%f, %f, %f, %f\n",
nBound,
currMat.GetCol0().GetXf(), currMat.GetCol1().GetXf(), currMat.GetCol2().GetXf(), currMat.GetCol3().GetXf(),
currMat.GetCol0().GetYf(), currMat.GetCol1().GetYf(), currMat.GetCol2().GetYf(), currMat.GetCol3().GetYf(),
currMat.GetCol0().GetZf(), currMat.GetCol1().GetZf(), currMat.GetCol2().GetZf(), currMat.GetCol3().GetZf(),
lastMat.GetCol0().GetXf(), lastMat.GetCol1().GetXf(), lastMat.GetCol2().GetXf(), lastMat.GetCol3().GetXf(),
lastMat.GetCol0().GetYf(), lastMat.GetCol1().GetYf(), lastMat.GetCol2().GetYf(), lastMat.GetCol3().GetYf(),
lastMat.GetCol0().GetZf(), lastMat.GetCol1().GetZf(), lastMat.GetCol2().GetZf(), lastMat.GetCol3().GetZf()
);
}
}
}
#endif
if( !IsInAir() &&
MI_PLANE_STARLING.IsValid() &&
GetModelIndex() == MI_PLANE_STARLING )
{
Vec3V groundNormal = VECTOR3_TO_VEC3V( GetWheel(0)->GetHitNormal() + GetWheel(1)->GetHitNormal() ) * ScalarV( 0.5f );
if( Dot( GetTransform().GetC(), groundNormal ).Getf() > 0.3f )
{
static dev_float sfTorqueScale = 50.0f;
Vec3V rightVector = Cross( GetTransform().GetB(), groundNormal );
float torque = sfTorqueScale * GetAngInertia().y * ( GetTransform().GetA().GetZf() - rightVector.GetZf() );
Vector3 torqueVector = VEC3V_TO_VECTOR3( GetTransform().GetB() ) * torque;
ApplyInternalTorque( torqueVector );
}
}
UpdateMeredithVent();
int handlebarsBone = GetBoneIndex( PLANE_HANDLEBARS );
if( GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE &&
handlebarsBone != -1 )
{
static dev_float maxRollScale = 0.1f;
static dev_float maxPitchScale = 0.1f;
static dev_float maxVisualRateOfChange = 1.0f;
m_fVisualRoll = m_fVisualRoll + ( Clamp( m_fRoll - m_fVisualRoll, -maxVisualRateOfChange * fTimeStep, maxVisualRateOfChange * fTimeStep ) );
m_fVisualPitch = m_fVisualPitch + ( Clamp( m_fPitch - m_fVisualPitch, -maxVisualRateOfChange * fTimeStep, maxVisualRateOfChange * fTimeStep ) );
SetComponentRotation( PLANE_HANDLEBARS, ROT_AXIS_LOCAL_Y, m_fVisualRoll * maxRollScale, true );
SetComponentRotation( PLANE_HANDLEBARS, ROT_AXIS_LOCAL_X, m_fVisualPitch * maxPitchScale, false );
fragInst *pVehicleFragInst = GetVehicleFragInst();
int iFragGroup = (s8)pVehicleFragInst->GetGroupFromBoneIndex(handlebarsBone);
int iFragChild = -1;
if(iFragGroup > -1)
{
fragTypeGroup* pGroup = pVehicleFragInst->GetTypePhysics()->GetAllGroups()[iFragGroup];
iFragChild = (s8)pGroup->GetChildFragmentIndex();
}
else
{
iFragChild = (s8)pVehicleFragInst->GetComponentFromBoneIndex(handlebarsBone);
}
GetSkeleton()->PartialUpdate(handlebarsBone);
if(iFragGroup > -1 && iFragChild > -1)
{
if( pVehicleFragInst->GetArchetype() &&
pVehicleFragInst->GetArchetype()->GetBound() &&
phBound::IsTypeComposite(pVehicleFragInst->GetArchetype()->GetBound()->GetType()))
{
phBoundComposite *pBound = (phBoundComposite *)pVehicleFragInst->GetArchetype()->GetBound();
fragTypeGroup* pGroup = pVehicleFragInst->GetTypePhysics()->GetAllGroups()[iFragGroup];
UpdateLinkAttachmentMatricesRecursive(pBound, pGroup );
}
}
}
static dev_s32 maxNumberOfWingFlaps = 2;
for( int i = 0; i < maxNumberOfWingFlaps; i++ )
{
eHierarchyId flapBoneId = (eHierarchyId)( (int)PLANE_WINGFLAP_L + i );
int flapBone = GetBoneIndex( flapBoneId );
if( flapBone != -1 )
{
static dev_float sf_maxRotationAngle = DtoR * 25.0f;
static dev_float maxFlapRateOfChange = 0.1f;
const CLandingGear& gear = GetLandingGear();
float targetFlapAngle = gear.GetGearDeployRatio();
targetFlapAngle = Max( targetFlapAngle, -m_fThrottleControl );
targetFlapAngle *= sf_maxRotationAngle;
m_fCurrentFlapAngle = m_fCurrentFlapAngle + ( Clamp( targetFlapAngle - m_fCurrentFlapAngle, -maxFlapRateOfChange * fTimeStep, maxFlapRateOfChange * fTimeStep ) );
SetComponentRotation( flapBoneId, ROT_AXIS_LOCAL_X, m_fCurrentFlapAngle, true );
}
}
return eResult;
}
bool CPlane::WantsToBeAwake()
{
// TODO - doesn't this run the risk of not being awake if two of the four controls
// have opposite nonzero values? Not sure how likely that would be in practice,
// but fabsf of each element would fix that.
bool bRet = (m_fThrottleControl + m_fYawControl + m_fPitchControl + m_fRollControl + fabs(m_fCurrentVerticalFlightRatio - m_fDesiredVerticalFlightRatio))!=0.0f
|| m_landingGear.WantsToBeAwake(this) || m_bIsPropellerDamagedThisFrame;
if( !bRet )
{
bRet = m_fDesiredVerticalFlightRatio != m_fCurrentVerticalFlightRatio;
}
if(!bRet)
{
return CAutomobile::WantsToBeAwake();
}
return bRet;
}
dev_float dfLandingGearFriction = 1.0f;
dev_float dfGroundFrictionWithoutLandingGear = 0.6f;
void CPlane::ProcessPreComputeImpacts(phContactIterator impacts)
{
CAutomobile::ProcessPreComputeImpacts(impacts);
// Only do propeller processing if engine is on
impacts.Reset();
const int numWheels = GetNumWheels();
Vector3 vCgPosition = RCC_VECTOR3(pHandling->m_vecCentreOfMassOffset);
MAT34V_TO_MATRIX34(GetMatrix()).Transform(vCgPosition);
bool bLandingGearIntact = GetIsLandingGearintact();
bool bLandingGearLockedUp = GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_UP;
bool bIsInAir = IsInAir();
while(!impacts.AtEnd())
{
CEntity *pOtherEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
for(int i = 0; i < m_iNumPropellers; i++)
{
if(impacts.IsDisabled())
{
// B*1954149: Allow impacts with projectiles to get be processed by the propellers.
if(!pOtherEnt || (pOtherEnt && !(pOtherEnt->GetIsTypeObject() && pOtherEnt->GetBaseModelInfo() && pOtherEnt->GetBaseModelInfo()->GetModelType() == MI_TYPE_WEAPON)))
break;
}
m_propellerCollision[i].ProcessPreComputeImpacts(this,impacts,Min(m_propellers->GetSpeed(), m_fEngineSpeed));
}
GetLandingGear().ProcessPreComputeImpacts(this, impacts);
if(!impacts.IsDisabled())
{
// Those impacts are generate when the contact wheel is fully compressed. We try to reduce amount of bounce up or rotation due to those impacts, also plane should loss some speed.
for(int i = 0; i < numWheels; ++i)
{
CWheel *wheel = m_ppWheels[i];
for(int k = 0; k < MAX_WHEEL_BOUNDS_PER_WHEEL; k++)
{
if(wheel->GetFragChild(k) == impacts.GetMyComponent())
{
impacts.SetElasticity(0.0f);
impacts.SetFriction(Max(dfLandingGearFriction, impacts.GetFriction()));
}
}
}
phInst* otherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL;
// increase the body vs ground friction when landing gear is broken off
if(pOtherEntity && pOtherEntity->GetIsTypeBuilding())
{
if( !bLandingGearIntact && !bIsInAir )
{
impacts.SetElasticity(0.0f);
impacts.SetFriction(Max(dfGroundFrictionWithoutLandingGear, impacts.GetFriction()));
}
if( bLandingGearLockedUp )
{
impacts.SetElasticity(0.0f);
impacts.SetDepth(0.0f);
}
if(pOtherEntity->GetArchetype() && (pOtherEntity->GetModelId() == MI_GROUND_LIGHT_RED || pOtherEntity->GetModelId() == MI_GROUND_LIGHT_GREEN || pOtherEntity->GetModelId() == MI_GROUND_LIGHT_YELLOW))
{
impacts.DisableImpact();
}
}
}
impacts++;
}
if(IsLargePlane() && (GetStatus() == STATUS_WRECKED || (m_aircraftDamage.HasSectionBrokenOff(this,CAircraftDamage::WING_L) && m_aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R))))
{
impacts.Reset();
while(!impacts.AtEnd())
{
if(!impacts.IsDisabled())
{
phInst* otherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL;
// Disable push for large wreck plane colliding against static bounds
if(pOtherEntity && pOtherEntity->GetIsTypeBuilding())
{
impacts.SetDepth(0.0f);
}
}
impacts++;
}
}
}
//
//
//
//
static dev_float sfRudderAdjustAngle = ( DtoR * 10.0f);
static dev_float sfAileronAdjustAngle = ( DtoR * 30.0f);
static dev_float sfElevatorAdjustAngle = ( DtoR * 20.0f);
static dev_float sfElevatorAdjustAngleForVTOL = ( DtoR * 10.0f);
static dev_float sfAirbrakeAdjustAngle = ( DtoR * 30.0f);
ePrerenderStatus CPlane::PreRender(const bool bIsVisibleInMainViewport)
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
// Spin the rotors
for(int i =0; i < m_iNumPropellers; i++)
{
m_propellers[i].PreRender(this);
}
if(GetBoneIndex(VEH_HBGRIP_L) >= 0.0f || GetBoneIndex(VEH_HBGRIP_R) >= 0.0f)
{
TUNE_GROUP_FLOAT(JOYSTICK_IK, PlaneJoyStickRotX, 10.0f, 0.0f, 20.0f, 0.001f);
TUNE_GROUP_FLOAT(JOYSTICK_IK, PlaneJoyStickRotY, 6.5f, 0.0f, 20.0f, 0.001f);
float fJoystickAdjustAngleX = ( DtoR * PlaneJoyStickRotX);
float fJoystickAdjustAngleY = ( DtoR * PlaneJoyStickRotY);
SetComponentRotation(VEH_CAR_STEERING_WHEEL, ROT_AXIS_LOCAL_X, m_fJoystickPitch * fJoystickAdjustAngleX, true);
SetComponentRotation(VEH_CAR_STEERING_WHEEL, ROT_AXIS_LOCAL_Y, m_fJoystickRoll * fJoystickAdjustAngleY, false);
}
#if 0
static dev_bool bRenderWheelMatrices = false;
if(bRenderWheelMatrices)
{
for(int i = 0 ; i < GetNumWheels() ; i++)
{
if(GetWheel(i)->GetHierarchyId() > VEH_INVALID_ID)
{
int iBoneIndex = GetBoneIndex(GetWheel(i)->GetHierarchyId());
if(iBoneIndex > -1)
{
Matrix34 matDraw = GetLocalMtx(iBoneIndex);
const crBoneData* pParent = GetSkeletonData().GetBoneData(iBoneIndex)->GetParent();
while(pParent)
{
int iParentBoneIndex = pParent->GetIndex();
matDraw.Dot(GetLocalMtx(iParentBoneIndex));
pParent = GetSkeletonData().GetBoneData(iParentBoneIndex)->GetParent();
}
matDraw.Dot(GetMatrix());
grcDebugDraw::Axis(matDraw,0.3f);
// Also draw global matrix
static dev_bool bDrawGlobal = false;
if(bDrawGlobal)
{
grcDebugDraw::Sphere(GetGlobalMtx(iBoneIndex).d,0.3f,Color_green,false);
}
}
}
}
}
#endif
#if GTA_REPLAY
if (!CReplayMgr::IsEditModeActive())
#endif
{
if( !IsAnimated() )
{
// Elevators (pitch)
SetComponentRotation(PLANE_ELEVATOR_L,ROT_AXIS_LOCAL_X,-m_fPitch*sfElevatorAdjustAngle,true);
SetComponentRotation(PLANE_ELEVATOR_R,ROT_AXIS_LOCAL_X,-m_fPitch*sfElevatorAdjustAngle,true);
// Nozzle
if(GetVerticalFlightModeAvaliable())
{
if( ( !MI_PLANE_TULA.IsValid() ||
GetModelIndex() != MI_PLANE_TULA ) &&
!GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) )
{
SetComponentRotation(PLANE_NOZZLE_F,ROT_AXIS_LOCAL_X,(1.0f - m_fCurrentVerticalFlightRatio)*sfNozzleAdjustAngle,true);
SetComponentRotation(PLANE_NOZZLE_R,ROT_AXIS_LOCAL_X,(1.0f - m_fCurrentVerticalFlightRatio)*sfNozzleAdjustAngle,true);
// Adjust rudder (yaw)
SetComponentRotation(PLANE_RUDDER,ROT_AXIS_LOCAL_Z,m_fYaw*sfRudderAdjustAngleForVTOL,true);
SetComponentRotation(PLANE_RUDDER_2,ROT_AXIS_LOCAL_Z,m_fYaw*sfRudderAdjustAngleForVTOL,true);
}
else
{
// Adjust rudder (yaw)
SetComponentRotation(PLANE_RUDDER,ROT_AXIS_LOCAL_Z,m_fYaw*sfRudderAdjustAngleForTULA,true);
SetComponentRotation(PLANE_RUDDER_2,ROT_AXIS_LOCAL_Z,m_fYaw*sfRudderAdjustAngleForTULA,true);
}
// Elevators (pitch)
SetComponentRotation(PLANE_ELEVATOR_L,ROT_AXIS_LOCAL_X,-m_fPitch*sfElevatorAdjustAngleForVTOL,true);
SetComponentRotation(PLANE_ELEVATOR_R,ROT_AXIS_LOCAL_X,-m_fPitch*sfElevatorAdjustAngleForVTOL,true);
}
else
{
// Elevators (pitch)
SetComponentRotation(PLANE_ELEVATOR_L,ROT_AXIS_LOCAL_X,-m_fPitch*sfElevatorAdjustAngle,true);
SetComponentRotation(PLANE_ELEVATOR_R,ROT_AXIS_LOCAL_X,-m_fPitch*sfElevatorAdjustAngle,true);
// Adjust rudder (yaw)
SetComponentRotation(PLANE_RUDDER,ROT_AXIS_LOCAL_Z,m_fYaw*sfRudderAdjustAngle,true);
SetComponentRotation(PLANE_RUDDER_2,ROT_AXIS_LOCAL_Z,m_fYaw*sfRudderAdjustAngle,true);
}
// Ailerons (roll)
if(m_bDisableLeftAileron)
{
SetComponentRotation(PLANE_AILERON_L,ROT_AXIS_LOCAL_X,0.0f*sfAileronAdjustAngle,true);
}
else
{
SetComponentRotation(PLANE_AILERON_L,ROT_AXIS_LOCAL_X,m_fRoll*sfAileronAdjustAngle,true);
}
if(m_bDisableRightAileron)
{
SetComponentRotation(PLANE_AILERON_R,ROT_AXIS_LOCAL_X,0.0f*sfAileronAdjustAngle,true);
}
else
{
SetComponentRotation(PLANE_AILERON_R,ROT_AXIS_LOCAL_X,-m_fRoll*sfAileronAdjustAngle,true);
}
// Air brakes
SetComponentRotation(PLANE_AIRBRAKE_L,ROT_AXIS_LOCAL_X,-m_fBrake*sfAirbrakeAdjustAngle,true);
SetComponentRotation(PLANE_AIRBRAKE_R,ROT_AXIS_LOCAL_X,-m_fBrake*sfAirbrakeAdjustAngle,true);
}
}
// vfx
if (!IsDummy())
{
if ((m_nVehicleFlags.bEngineOn || IsRunningCarRecording()) &&
!m_nVehicleFlags.bIsDrowning &&
!m_nVehicleFlags.bDisableParticles)
{
// GTAV - FIX B*1739284 - Allow script to force the afterburner on
if( m_nVehicleFlags.bForceAfterburnerVFX )
{
float prevThrottle = GetThrottle();
SetThrottle( 1.0f );
for (int i = 0; i < PLANE_NUM_AFTERBURNERS; ++i)
{
g_vfxVehicle.UpdatePtFxPlaneAfterburner(this, eHierarchyId(PLANE_AFTERBURNER + i), i);
}
SetThrottle( prevThrottle );
}
else
{
for (int i = 0; i < PLANE_NUM_AFTERBURNERS; ++i)
{
g_vfxVehicle.UpdatePtFxPlaneAfterburner(this, eHierarchyId(PLANE_AFTERBURNER + i), i);
}
}
g_vfxVehicle.UpdatePtFxPlaneWingTips(this);
m_aircraftDamage.ProcessVfx(this);
}
#if __BANK && DEBUG_DRAW
m_aircraftDamage.DebugDraw(this);
#endif
}
ePrerenderStatus result = CAutomobile::PreRender(bIsVisibleInMainViewport);
return result;
}
void CPlane::DoEmergencyLightEffects(crSkeleton *pSkeleton, s32 boneId, const ConfigPlaneEmergencyLightsSettings &lightParam, float distFade, float rotAmount)
{
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
const s32 boneIdx = pModelInfo->GetBoneIndex(boneId);
if (boneIdx > -1)
{
Matrix34 &localMtx = GetLocalMtxNonConst(boneIdx);
localMtx.MakeRotateZ(rotAmount);
Matrix34 worldMtx;
pSkeleton->PartialUpdate(boneIdx);
pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(worldMtx));
fwInteriorLocation interiorLocation = this->GetInteriorLocation();
CLightSource* pLightSource = CAsyncLightOcclusionMgr::AddLight();
if (pLightSource)
{
pLightSource->Reset();
pLightSource->SetCommon(
LIGHT_TYPE_SPOT,
LIGHTFLAG_VEHICLE | LIGHTFLAG_CAST_DYNAMIC_GEOM_SHADOWS,
worldMtx.d,
VEC3V_TO_VECTOR3(lightParam.color),
lightParam.intensity * distFade,
LIGHT_ALWAYS_ON);
pLightSource->SetShadowFadeDistance(20);
pLightSource->SetDirTangent(worldMtx.b, worldMtx.a);
pLightSource->SetRadius(lightParam.falloff);
pLightSource->SetFalloffExponent(lightParam.falloffExponent);
pLightSource->SetSpotlight(lightParam.innerAngle, lightParam.outerAngle);
pLightSource->SetInInterior(interiorLocation);
pLightSource->SetShadowTrackingId(fwIdKeyGenerator::Get(this) + boneIdx);
// NOTE: we don't want to call Lights::AddSceneLight directly - the AsyncLightOcclusionMgr will handle that for us
}
}
}
void CPlane::DoControlPanelLightEffects(s32 boneId, const ConfigPlaneControlPanelSettings &lightParam, float distFade)
{
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
const s32 boneIdx = pModelInfo->GetBoneIndex(boneId);
if (boneIdx > -1 && (boneIdx < GetSkeleton()->GetBoneCount()))
{
Matrix34 worldMtx;
GetGlobalMtx(boneIdx, worldMtx);
fwInteriorLocation interiorLocation = this->GetInteriorLocation();
CLightSource light(
LIGHT_TYPE_POINT,
LIGHTFLAG_VEHICLE | LIGHTFLAG_NO_SPECULAR,
worldMtx.d,
VEC3V_TO_VECTOR3(lightParam.color),
lightParam.intensity * distFade,
LIGHT_ALWAYS_ON);
light.SetDirTangent(worldMtx.b, worldMtx.a);
light.SetRadius(lightParam.falloff);
light.SetInInterior(interiorLocation);
light.SetFalloffExponent(lightParam.falloffExponent);
Lights::AddSceneLight(light);
}
}
void CPlane::DoInsideHullLightEffects(s32 boneId, const ConfigPlaneInsideHullSettings &lightParam, float distFade)
{
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
const s32 boneIdx = pModelInfo->GetBoneIndex(boneId);
if (boneIdx > -1)
{
Matrix34 worldMtx;
GetGlobalMtx(boneIdx, worldMtx);
fwInteriorLocation interiorLocation = this->GetInteriorLocation();
CLightSource light(
LIGHT_TYPE_POINT,
LIGHTFLAG_VEHICLE | LIGHTFLAG_NO_SPECULAR,
worldMtx.d,
VEC3V_TO_VECTOR3(lightParam.color),
lightParam.intensity * distFade,
LIGHT_ALWAYS_ON);
light.SetDirTangent(worldMtx.b, worldMtx.a);
light.SetRadius(lightParam.falloff);
light.SetFalloffExponent(lightParam.falloffExponent);
light.SetSpotlight(lightParam.innerAngle, lightParam.outerAngle);
light.SetInInterior(interiorLocation);
Lights::AddSceneLight(light);
}
}
void CPlane::CacheWindowBones()
{
const crSkeletonData* pSkelData = GetVehicleModelInfo()->GetFragType()->GetCommonDrawable()->GetSkeletonData();
if (pSkelData)
{
u32 index = 0;
const char * boneNames[] = {
"window_lf",
"window_lf1",
"window_lf2",
"window_lf3",
"window_rf",
"window_rf1",
"window_rf2",
"window_rf3",
"window_lr",
"window_lr1",
"window_lr2",
"window_lr3",
"window_rr",
"window_rr1",
"window_rr2",
"window_rr3"
};
for (u32 i = 0; i < NELEM(boneNames); i++)
{
const crBoneData* boneData = pSkelData->FindBoneData(boneNames[i]);
if (boneData)
{
m_windowBoneIndices[index] = (s16)boneData->GetIndex();
index++;
}
}
}
m_windowBoneCached = true;
}
static dev_float PlaneLights_ExteriorFadeStart = 0.0f;
static dev_float PlaneLights_ExteriorFadeEnd = 1000.0f;
static dev_float PlaneLights_InteriorFadeDistance = 5.0f;
static dev_float PlaneLights_InteriorFadeCutoff = 50.0f;
BANK_ONLY(bank_float bfPlaneTurbulenceOn = true;)
void CPlane::PreRender2(const bool bIsVisibleInMainViewport)
{
#if GTA_REPLAY
if (!CReplayMgr::IsEditModeActive())
#endif
{
if( !IsAnimated() )
{
// Update the langing gear aux door after CVehicle::PreRender(), so the bone matrix won't be overwritten
// by syncing to articulated body. We'll do this in PreRender2(), to give the animation the most time
// to be done, as the landing gear PreRender() will access bones causing waits on the animation.
m_landingGear.PreRenderDoors(this);
}
}
// The engine state seems to be dependent on the button (holding L2 will switch engine off even in air)
// So employ similar trick used by trains. If plane is occupied and light status is true (dependent on TOD)
// then force lights on
m_OverrideLights = GetVehicleLightsStatus() && GetDriver() ? FORCE_CAR_LIGHTS_ON : NO_CAR_LIGHT_OVERRIDE;
CAutomobile::PreRender2(bIsVisibleInMainViewport);
if (m_nVehicleFlags.bEngineOn || IsRunningCarRecording())
{
// downwash vfx
if (GetVerticalFlightModeAvaliable())
{
g_vfxVehicle.ProcessPlaneDownwash(this);
}
// ground disturbance vfx
g_vfxVehicle.ProcessGroundDisturb(this);
}
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
const Vector3& camPos = camInterface::GetPos();
const float dist = camPos.Dist(vThisPosition);
float exteriorFadeDist = Saturate((dist - PlaneLights_ExteriorFadeStart) / (PlaneLights_ExteriorFadeEnd - PlaneLights_ExteriorFadeStart));
float interiorFadeDist = 1.0f - rage::Saturate((dist - PlaneLights_InteriorFadeCutoff) / PlaneLights_InteriorFadeDistance);
const bool bVehicleIsLuxe2 = ( GetVehicleModelInfo()->GetModelNameHash() == MI_PLANE_LUXURY_JET3.GetName().GetHash()
|| GetVehicleModelInfo()->GetModelNameHash() == MI_PLANE_NIMBUS.GetName().GetHash());
// Disable emissives when wrecked
if (GetStatus() == STATUS_WRECKED && bVehicleIsLuxe2)
{
if(GetBoneIndex(VEH_EMISSIVES) != -1)
{
GetLocalMtxNonConst(GetBoneIndex(VEH_EMISSIVES)).Zero3x3();
}
}
// Special luxor lights
if ((GetStatus() != STATUS_WRECKED) && bVehicleIsLuxe2)
{
extern ConfigLightSettings g_PlaneLuxe2Cabin;
extern ConfigLightSettings g_PlaneLuxe2CabinStrip;
extern ConfigLightSettings g_PlaneLuxe2CabinTV;
extern ConfigLightSettings g_PlaneLuxe2CabinLOD;
extern ConfigLightSettings g_PlaneLuxe2CabinWindow;
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
bool enteringInsideExitingAVehicle = IsEnteringInsideOrExiting();
const bool enableHD = (enteringInsideExitingAVehicle && camInterface::IsRenderingFirstPersonCamera());
if ((camInterface::IsRenderedCameraInsideVehicle() || enableHD) BANK_ONLY( || camInterface::GetDebugDirector().IsFreeCamActive()))
{
for (u32 l = VEH_SIREN_7; l <= VEH_SIREN_9; l++ )
{
AddLight(LIGHT_TYPE_SPOT, pModelInfo->GetBoneIndex(l), g_PlaneLuxe2Cabin, interiorFadeDist);
}
for (u32 l = VEH_SIREN_10; l <= VEH_SIREN_11; l++ )
{
AddLight(LIGHT_TYPE_CAPSULE, pModelInfo->GetBoneIndex(l), g_PlaneLuxe2CabinStrip, interiorFadeDist);
}
for (u32 l = VEH_SIREN_13; l <= VEH_SIREN_15; l++ )
{
AddLight(LIGHT_TYPE_SPOT, pModelInfo->GetBoneIndex(l), g_PlaneLuxe2CabinTV, interiorFadeDist);
}
if (!m_windowBoneCached)
{
CacheWindowBones();
}
for (u32 l = 0; l < NELEM(m_windowBoneIndices); l++ )
{
AddLight(LIGHT_TYPE_SPOT, m_windowBoneIndices[l], g_PlaneLuxe2CabinWindow, interiorFadeDist);
}
}
else
{
AddLight(LIGHT_TYPE_CAPSULE, pModelInfo->GetBoneIndex(VEH_SIREN_12), g_PlaneLuxe2CabinLOD, interiorFadeDist);
AddLight(LIGHT_TYPE_SPOT, pModelInfo->GetBoneIndex(VEH_SIREN_9), g_PlaneLuxe2Cabin, interiorFadeDist);
}
}
// lights
if( (GetStatus() != STATUS_WRECKED) && (m_nVehicleFlags.bEngineOn || (GetDriver() && (m_bIsEngineTurnedOffByPlayer || m_bIsEngineTurnedOffByScript))) )
{
#if RSG_PC
int frameCount = (int)((float)fwTimer::GetTimeInMilliseconds_ScaledNonClipped()/(1000.0f/30.0f));
#else
int frameCount = fwTimer::GetFrameCount_ScaledNonClipped();
#endif
bool blinkLight = ((frameCount + GetRandomSeed()) & 31) <= 0;
bool blinkLight2 = ((frameCount + GetRandomSeed() + 12) & 31) <= 0;
bool blinkLight3 = ((frameCount + GetRandomSeed() + 15) & 31) <= 0;
bool blinkLight4 = ((frameCount + GetRandomSeed() + 24) & 31) <= 0;
extern ConfigVehiclePositionLightSettings g_PlanePosLights;
extern ConfigVehicleWhiteLightSettings g_PlaneWhiteHeadLights;
extern ConfigVehicleWhiteLightSettings g_PlaneWhiteTailLights;
const bool bVehicleIsSwift2 = (GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_SWIFT2.GetName().GetHash()
|| GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_SUPER_VOLITO.GetName().GetHash()
|| GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_SUPER_VOLITO2.GetName().GetHash()
|| GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_VOLATUS.GetName().GetHash());
const bool bVehicleIsCargoPlane = (GetVehicleModelInfo()->GetModelNameHash() == MI_PLANE_CARGOPLANE.GetName().GetHash());
bool addBlinkLights = true;
if (bVehicleIsSwift2) { addBlinkLights = !IsInsideVehicleModeEnabled(); }
if (addBlinkLights)
{
if (blinkLight) DoPosLightEffects (VEH_SIREN_1, g_PlanePosLights, true,false,exteriorFadeDist);
if (blinkLight2) DoPosLightEffects (VEH_SIREN_2, g_PlanePosLights,false,false,exteriorFadeDist);
if (blinkLight3) DoWhiteLightEffects (VEH_SIREN_3, g_PlaneWhiteTailLights,false,exteriorFadeDist);
if (blinkLight4) DoWhiteLightEffects (VEH_SIREN_4, g_PlaneWhiteHeadLights, true,exteriorFadeDist);
}
crSkeleton *pSkeleton = GetSkeleton();
if (m_nVehicleFlags.bInteriorLightOn)
{
extern ConfigPlaneControlPanelSettings g_PlaneControlPanelLights;
if (g_PlaneControlPanelLights.intensity > 0.0f)
{
for (u32 l = VEH_SIREN_5; l <= VEH_SIREN_6; l++)
{
DoControlPanelLightEffects(l, g_PlaneControlPanelLights, interiorFadeDist);
}
}
if (bVehicleIsCargoPlane)
{
extern ConfigPlaneEmergencyLightsSettings g_PlaneLeftEmergencyLights;
extern ConfigPlaneEmergencyLightsSettings g_PlaneRightEmergencyLights;
extern ConfigPlaneInsideHullSettings g_PlaneInsideHullLights;
if(!fwTimer::IsGamePaused())
{
m_fLeftLightRotation = fmod(m_fLeftLightRotation + g_PlaneLeftEmergencyLights.rotation * fwTimer::GetTimeStep(), TWO_PI);
m_fRightLightRotation = fmod(m_fRightLightRotation + g_PlaneRightEmergencyLights.rotation * fwTimer::GetTimeStep(), TWO_PI);
}
if (g_PlaneLeftEmergencyLights.intensity > 0.0f || g_PlaneRightEmergencyLights.intensity > 0.0f)
{
for (u32 l = VEH_SIREN_7; l <= VEH_SIREN_12; l++ )
{
const bool evenSiren = ((l - VEH_SIREN_7) % 2 == 0);
DoEmergencyLightEffects(
pSkeleton,
l,
(evenSiren) ? g_PlaneLeftEmergencyLights : g_PlaneRightEmergencyLights,
interiorFadeDist,
(evenSiren) ? m_fLeftLightRotation : m_fRightLightRotation);
}
}
if (g_PlaneInsideHullLights.intensity > 0.0f)
{
for (u32 l = VEH_SIREN_13; l <= VEH_SIREN_18; l++)
{
DoInsideHullLightEffects(l, g_PlaneInsideHullLights, interiorFadeDist);
}
}
}
}
}
// GTAV - B*1983577 - If any of the landing gear covers have bones but no collision hide them if the plane has been blown up
if( m_nVehicleFlags.bBlownUp )
{
for( int i = 0; i < NUM_LANDING_GEAR_DOORS; i++ )
{
int nBoneIndex = GetBoneIndex( (eHierarchyId)( LANDING_GEAR_DOOR_FL + i ) );
int nComponent = GetFragInst()->GetComponentFromBoneIndex(nBoneIndex);
if( nComponent == -1 &&
nBoneIndex > -1 )
{
crSkeleton *pSkeleton = GetSkeleton();
Matrix34 boneMat;
Matrix34 mat;
mat.Zero();
pSkeleton->GetGlobalMtx( nBoneIndex, RC_MAT34V(boneMat) );
mat.d = boneMat.d;
pSkeleton->SetGlobalMtx( nBoneIndex, RCC_MAT34V(mat) );
}
}
}
}
bool CPlane::IsTurbulenceOn()
{
return IsInAir() && m_fTurbulenceRecoverTimer <= 0.0f && m_fTurbulenceTimer > 0.0f;
}
bool CPlane::GetCanMakeIntoDummy(eVehicleDummyMode dummyMode)
{
if ( dummyMode != VDM_SUPERDUMMY )
{
return CAutomobile::GetCanMakeIntoDummy(dummyMode);
}
return false;
}
bool CPlane::TryToMakeIntoDummy(eVehicleDummyMode dummyMode, bool bSkipClearanceTest)
{
if ( dummyMode != VDM_SUPERDUMMY )
{
return CAutomobile::TryToMakeIntoDummy(dummyMode, bSkipClearanceTest);
}
return false;
}
static dev_float TurbulenceMaxFrequency = 2.0f;
static dev_float TurbulenceMinFrequency = 0.5f;
static dev_float TurbulenceBlendingRate = 2.0f;
static dev_float TurbulenceNoiseManitude = 0.9f;
float TurbulenceSideWindThreshold = 3.0f;
static float sfBuffetingBodyHealthThreshold = 950.0f;
bank_float bfTurbulenceTimeMin = 5.0f;
bank_float bfTurbulenceTimeMax = 10.0f;
bank_float bfTurbulenceRecoverTimeMin = 10.0f;
bank_float bfTurbulenceRecoverTimeMax = 20.0f;
bank_float sfPlaneTurbulencePedVibMult = 0.025f;
bank_float sfPlaneTurbulencePedVibMin = 0.005f;
dev_float dfTurbulenceRollFrequencyMulti = 2.0f;
void CPlane::ProcessTurbulence(float fTimeStep)
{
bool bResetAndQuit = false;
float fWeatherMult = WeatherTurbulenceMult();
CControl *pControl = NULL;
if(GetStatus()==STATUS_PLAYER && GetDriver() && GetDriver()->IsPlayer())
{
pControl = GetDriver()->GetControlFromPlayer();
}
bool bControlInactive = pControl && CTaskVehiclePlayerDrive::IsThePlayerControlInactive(pControl);
if(GetDriver() && GetDriver()->IsPlayer() && !bControlInactive BANK_ONLY(&& bfPlaneTurbulenceOn) && GetAircraftDamage().GetLiftMult(this) > 0.0f)
{
if(m_fTurbulenceRecoverTimer <= 0.0f)
{
if(m_fTurbulenceTimer > 0.0f)
{
m_fTurbulenceTimer -= fTimeStep;
}
// Recompute turbulence time
else
{
// Recompute next turbulence values
m_fTurbulenceTimer = fwRandom::GetRandomNumberInRange(bfTurbulenceTimeMin, bfTurbulenceTimeMax);
m_fTurbulenceTimer *= fWeatherMult;
m_fTurbulenceRecoverTimer = fwRandom::GetRandomNumberInRange(bfTurbulenceRecoverTimeMin, bfTurbulenceRecoverTimeMax);
m_fTurbulenceRecoverTimer *= WeatherTurbulenceMultInv();
}
}
else
{
m_fTurbulenceRecoverTimer -= fTimeStep;
}
}
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
CFlyingHandlingData* pVerticleHandlingData = NULL;
if( GetVerticalFlightModeAvaliable() && m_fCurrentVerticalFlightRatio > 0.0f )
{
pVerticleHandlingData = pHandling->GetVerticalFlyingHandlingData();
}
if(pFlyingHandling == NULL)
{
bResetAndQuit = true;
}
if(!IsInAir() || GetFrameCollisionHistory()->GetMaxCollisionImpulseMagLastFrame() > 0.0f)
{
bResetAndQuit = true;
}
if(GetStatus() == STATUS_WRECKED || (m_aircraftDamage.HasSectionBrokenOff(this,CAircraftDamage::WING_L) && m_aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R)))
{
bResetAndQuit = true;
}
if(bResetAndQuit)
{
m_fTurbulenceMagnitude = 0.0f;
m_fTurbulenceNoise = 0.0f;
m_fTurbulenceTimeSinceLastCycle = 0.0f;
m_vTurbulenceAirflow = VEC3_ZERO;
m_fTurbulenceRecoverTimer = 0.0f;
m_fTurbulenceTimer = 0.0f;
return;
}
float fTurbulenceMagnitudeMax = pFlyingHandling->m_fTurublenceMagnitudeMax;
if( pVerticleHandlingData )
{
fTurbulenceMagnitudeMax *= ( 1.0f - m_fCurrentVerticalFlightRatio );
fTurbulenceMagnitudeMax += ( pVerticleHandlingData->m_fTurublenceMagnitudeMax * m_fCurrentVerticalFlightRatio );
}
float fTurbulenceT = (m_fTurbulenceMagnitude + m_fTurbulenceNoise * m_fTurbulenceMagnitude * TurbulenceNoiseManitude) / fTurbulenceMagnitudeMax;
fTurbulenceT = Clamp(fTurbulenceT, 0.0f, 1.0f);
float fFrequency = TurbulenceMinFrequency + fTurbulenceT * (TurbulenceMaxFrequency - TurbulenceMinFrequency);
float fHalfCycle = 0.5f / fFrequency;
float fFullCycle = fHalfCycle * 2.0f;
m_fTurbulenceTimeSinceLastCycle += fTimeStep;
bool bHalfCycleJustCompleted = (m_fTurbulenceTimeSinceLastCycle - fTimeStep) < fHalfCycle && m_fTurbulenceTimeSinceLastCycle >= fHalfCycle;
bool bFullCycleJustCOmpleted = m_fTurbulenceTimeSinceLastCycle > fFullCycle;
// If turbulence time just starts and the old turbulence had blended out, then restart new turbulence right away
if(IsTurbulenceOn() && m_fTurbulenceMagnitude == 0.0f)
{
bFullCycleJustCOmpleted = true;
}
if(bFullCycleJustCOmpleted)
{
Vector3 vecWindSpeed(0.0f, 0.0f, 0.0f);
WIND.GetLocalVelocity(GetTransform().GetPosition(), RC_VEC3V(vecWindSpeed), false, false);
vecWindSpeed *= ComputeWindMult(m_fCurrentVerticalFlightRatio);
vecWindSpeed += m_vTurbulenceAirflow;
if(IsTurbulenceOn())
{
m_fTurbulenceMagnitude = fTurbulenceMagnitudeMax * fWeatherMult * m_fScriptTurbulenceMult;
}
else // Fade out the turbulence
{
float fTurbulenceMagDelta = -m_fTurbulenceMagnitude;
fTurbulenceMagDelta = Clamp(fTurbulenceMagDelta, -TurbulenceBlendingRate * m_fTurbulenceTimeSinceLastCycle, TurbulenceBlendingRate * m_fTurbulenceTimeSinceLastCycle);
m_fTurbulenceMagnitude += fTurbulenceMagDelta;
}
m_fTurbulenceNoise = fwRandom::GetRandomNumberInRange(-1.0f, 0.0f);
fTurbulenceT = (m_fTurbulenceMagnitude + m_fTurbulenceNoise * m_fTurbulenceMagnitude * TurbulenceNoiseManitude) / fTurbulenceMagnitudeMax;
fTurbulenceT = Clamp(fTurbulenceT, 0.0f, 1.0f);
fFrequency = TurbulenceMinFrequency + fTurbulenceT * (TurbulenceMaxFrequency - TurbulenceMinFrequency);
m_fTurbulenceTimeSinceLastCycle = 0.0f;
if(GetStatus()==STATUS_PLAYER && GetDriver() && GetDriver()->IsLocalPlayer())
{
// Shake the controller pad for first half cycle
float fPedShakeIntensity = fTurbulenceT * sfPlaneTurbulencePedVibMult;
if(fPedShakeIntensity >= sfPlaneTurbulencePedVibMin)
{
float fHalfCycle = 0.5f / fFrequency;
u32 fPedShakeTime = (u32)(fHalfCycle * 1000.0f);
CControlMgr::StartPlayerPadShakeByIntensity(fPedShakeTime, fPedShakeIntensity);
}
}
}
else if(bHalfCycleJustCompleted)
{
m_fTurbulenceNoise = fwRandom::GetRandomNumberInRange(-1.0f, 0.0f);
fTurbulenceT = (m_fTurbulenceMagnitude + m_fTurbulenceNoise * m_fTurbulenceMagnitude * TurbulenceNoiseManitude) / fTurbulenceMagnitudeMax;
fTurbulenceT = Clamp(fTurbulenceT, 0.0f, 1.0f);
fFrequency = TurbulenceMinFrequency + fTurbulenceT * (TurbulenceMaxFrequency - TurbulenceMinFrequency);
float timeElapsedSinceHalfCycle = m_fTurbulenceTimeSinceLastCycle - fHalfCycle;
m_fTurbulenceTimeSinceLastCycle = 0.5f / fFrequency + timeElapsedSinceHalfCycle;
if(GetStatus()==STATUS_PLAYER && GetDriver() && GetDriver()->IsLocalPlayer())
{
// Shake the controller pad for second half cycle
float fPedShakeIntensity = fTurbulenceT * sfPlaneTurbulencePedVibMult;
if(fPedShakeIntensity >= sfPlaneTurbulencePedVibMin)
{
float fHalfCycle = 0.5f / fFrequency;
u32 fPedShakeTime = (u32)(fHalfCycle * 1000.0f);
CControlMgr::StartPlayerPadShakeByIntensity(fPedShakeTime, fPedShakeIntensity);
}
}
}
// Quit if the turbulence is completely blended out
if(m_fTurbulenceMagnitude == 0.0f)
{
m_vTurbulenceAirflow = VEC3_ZERO;
return;
}
float fSin = rage::Sinf(fFrequency * m_fTurbulenceTimeSinceLastCycle * 2.0f * PI);
Matrix34 matPlane;
GetMatrixCopy(matPlane);
matPlane.RotateLocalY(m_fTurbulenceNoise * HALF_PI + HALF_PI * 0.5f);
m_vTurbulenceAirflow = matPlane.c * (fTurbulenceT * fTurbulenceMagnitudeMax * fSin);
// Apply turbulence
float fTimeStepInv = 1.0f / fTimeStep;
Vector3 vTurbulenceAcceleration = m_vTurbulenceAirflow * fTimeStepInv;
float fTurbulenceForceMulti = pFlyingHandling->m_fTurublenceForceMulti;
if( pVerticleHandlingData )
{
fTurbulenceForceMulti *= ( 1.0f - m_fCurrentVerticalFlightRatio );
fTurbulenceForceMulti += ( pVerticleHandlingData->m_fTurublenceForceMulti * m_fCurrentVerticalFlightRatio );
}
ApplyInternalForceCg( vTurbulenceAcceleration * GetMass() * fTurbulenceForceMulti );
float fSinRolling = rage::Sinf(fFrequency * m_fTurbulenceTimeSinceLastCycle * 2.0f * dfTurbulenceRollFrequencyMulti * PI);
Vector3 vecAngInertia = GetAngInertia();
float fTurbulenceImpulseDistributeBias = fwRandom::GetRandomNumberInRange(0.0f, 0.5f) * fSinRolling;
float fTurbulenceRollTorqueMulti = pFlyingHandling->m_fTurublenceRollTorqueMulti;
if( pVerticleHandlingData )
{
fTurbulenceRollTorqueMulti *= ( 1.0f - m_fCurrentVerticalFlightRatio );
fTurbulenceRollTorqueMulti += ( m_fCurrentVerticalFlightRatio * pVerticleHandlingData->m_fTurublenceRollTorqueMulti );
}
ApplyInternalTorque(vTurbulenceAcceleration * (0.5f + fTurbulenceImpulseDistributeBias) * vecAngInertia.y * fTurbulenceRollTorqueMulti, VEC3V_TO_VECTOR3(GetTransform().GetA()));
ApplyInternalTorque(vTurbulenceAcceleration * (0.5f - fTurbulenceImpulseDistributeBias) * vecAngInertia.y * fTurbulenceRollTorqueMulti, VEC3V_TO_VECTOR3(GetTransform().GetA()) * -1.0f);
if(vTurbulenceAcceleration.z > 0.0f)
{
float fTurbulencePitchTorqueMulti = pFlyingHandling->m_fTurublencePitchTorqueMulti;
if( pVerticleHandlingData )
{
fTurbulencePitchTorqueMulti *= ( 1.0f - m_fCurrentVerticalFlightRatio );
fTurbulencePitchTorqueMulti += ( m_fCurrentVerticalFlightRatio * pVerticleHandlingData->m_fTurublencePitchTorqueMulti );
}
ApplyInternalTorque(vTurbulenceAcceleration * fTurbulencePitchTorqueMulti * vecAngInertia.x, VEC3V_TO_VECTOR3(GetTransform().GetB()));
}
#if 0
{
Vector3 vecVelocityToDraw = GetVelocity();
if(vecVelocityToDraw.Mag2() > 25.0f)
{
vecVelocityToDraw.Normalize();
vecVelocityToDraw *= 5.0f;
}
grcDebugDraw::Arrow(GetMatrix().d() + GetMatrix().c(), GetMatrix().d() + GetMatrix().c() + VECTOR3_TO_VEC3V(vecVelocityToDraw), 0.1f, Color_green);
Vector3 vTurbulenceToDraw = m_vTurbulenceAirflow;
grcDebugDraw::Arrow(GetMatrix().d() + GetMatrix().a(), GetMatrix().d() + GetMatrix().a() + VECTOR3_TO_VEC3V(vTurbulenceToDraw * (0.5f + fTurbulenceImpulseDistributeBias) ), 0.1f, Color_red);
grcDebugDraw::Arrow(GetMatrix().d() - GetMatrix().a(), GetMatrix().d() - GetMatrix().a() + VECTOR3_TO_VEC3V(vTurbulenceToDraw * (0.5f - fTurbulenceImpulseDistributeBias) ), 0.1f, Color_red);
if(vTurbulenceForce.z > 0.0f)
{
grcDebugDraw::Arrow(GetMatrix().d() + GetMatrix().b(), GetMatrix().d() + GetMatrix().b() + VECTOR3_TO_VEC3V(vTurbulenceToDraw), 0.1f, Color_red);
}
}
#endif
}
int CPlane::InitPhys()
{
CAutomobile::InitPhys();
m_landingGear.InitPhys(this, LANDING_GEAR_F, LANDING_GEAR_RM1, LANDING_GEAR_LM1, LANDING_GEAR_RR, LANDING_GEAR_RL, LANDING_GEAR_RM);
#if __WIN32PC
m_fWingForce = 0.0f;
#endif // __WIN32PC
return INIT_OK;
}
void CPlane::InitWheels()
{
CAutomobile::InitWheels();
float fTotalStaticForce = 0.0f;
for(int i = 0; i < GetNumWheels(); i++)
{
Assert(GetWheel(i));
GetWheel(i)->GetConfigFlags().SetFlag(WCF_UPDATE_SUSPENSION);
fTotalStaticForce += GetWheel(i)->GetStaticForce();
}
// Scale the wheels' static forces to match the gravity (1.0f)
if(Abs(fTotalStaticForce - 1.0f) > SMALL_FLOAT)
{
float fStaticForceScale = 1.0f / fTotalStaticForce;
for(int i = 0; i < GetNumWheels(); i++)
{
GetWheel(i)->SetStaticForce(GetWheel(i)->GetStaticForce() * fStaticForceScale);
}
}
m_landingGear.InitWheels(this);
}
void CPlane::InitCompositeBound()
{
CAutomobile::InitCompositeBound();
// Turn collision on for landing gear
m_landingGear.InitCompositeBound(this);
// Turn on collision for all breakable part children
m_aircraftDamage.InitCompositeBound(this);
m_landingGearDamage.InitCompositeBound(this);
if( sbEnablePropellerMods &&
GetModelIndex() == MI_PLANE_MICROLIGHT )
{
for( int i = 1; i < m_iNumPropellers; i++ )
{
EnableIndividualPropeller( i, false );
}
}
UpdatePropellerCollision();
for(int i = 0; i < GetNumDoors(); i++)
{
CCarDoor* pDoor = GetDoor(i);
eHierarchyId nDoorId = pDoor->GetHierarchyId();
eHierarchyId nWindowId = GetWindowIdFromDoor(nDoorId);
UpdateWindowBound(nWindowId);
}
if( GetModelIndex() == MI_PLANE_STRIKEFORCE )
{
m_nVehicleFlags.bUseDeformation = false;
}
}
void CPlane::UpdateWindowBound(eHierarchyId eWindowId)
{
if(eWindowId < 0 || eWindowId >= VEH_NUM_NODES)
{
return;
}
s32 nWindowBone = GetBoneIndex(eWindowId);
if(nWindowBone > -1)
{
int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(nWindowBone);
if(nGroup > -1)
{
const fragPhysicsLOD* physicsLOD = GetVehicleFragInst()->GetTypePhysics();
fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup);
int iChild = pGroup->GetChildFragmentIndex();
u32 nIncludeFlags = IsBrokenFlagSet(iChild)
? (ArchetypeFlags::GTA_CAR_DOOR_LATCHED_INCLUDE_TYPES | ArchetypeFlags::GTA_CAMERA_TEST) // turn off the collision if the window is smashed
: ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES;
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
Assert(pBoundComp);
for(int k = 0; k < pGroup->GetNumChildren(); k++)
{
pBoundComp->SetIncludeFlags(iChild+k, nIncludeFlags);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : BlowUpCar
// PURPOSE : Does everything needed to destroy a car
///////////////////////////////////////////////////////////////////////////////////
void CPlane::BlowUpCar( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion )
{
//Use previous damage entity that has actually destroyed the engine or the wings.
if (GetStatus() != STATUS_WRECKED && !IsNetworkClone() && GetDestroyedByPed())
{
if (pCulprit && !pCulprit->GetIsTypePed() && !pCulprit->GetIsTypeVehicle())
{
pCulprit = GetWeaponDamageEntity();
}
}
CVehicle::BlowUpCar(pCulprit);
#if __DEV
if (gbStopVehiclesExploding)
{
return;
}
#endif
if (GetStatus() == STATUS_WRECKED)
{
return; // Don't blow cars up a 2nd time
}
/// don't damage if this flag is set (usually during a cutscene)
if (m_nPhysicalFlags.bNotDamagedByAnything)
{
return;
}
bool blowUpInstantly = true;
// set the plane/heli to go out of control if not already
if (GetStatus()!=STATUS_PLAYER)
{
blowUpInstantly = false;//let the plane crash before blowing up except for the player
}
else
{
if(GetDriver() && GetDriver()->IsLocalPlayer())
{
audNorthAudioEngine::NotifyLocalPlayerPlaneCrashed();
}
}
// set the engine temp super high so we get nice sounds as the chassis cools down
m_EngineTemperature = MAX_ENGINE_TEMPERATURE;
// If this is a seaplane, it looks better if it sinks after being blown up.
if(CSeaPlaneExtension* pSeaPlaneExtension = GetExtension<CSeaPlaneExtension>())
{
pSeaPlaneExtension->m_nFlags.bSinksWhenDestroyed = 1;
}
#if !__NO_OUTPUT
if (NetworkInterface::IsGameInProgress() && !IsNetworkClone() && GetNetworkObject())
{
netDebug1("**************************************************************************************************");
netDebug1("PLANE BLOWN UP: %s", GetNetworkObject()->GetLogName());
sysStack::PrintStackTrace();
netDebug1("**************************************************************************************************");
}
#endif // !__NO_OUTPUT
// Everything now goes through a crash task
// Create a crash task if one doesn't already exist
if (!IsNetworkClone() && !(blowUpInstantly && bAddExplosion))
{
aiTask *pActiveTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH);
if(pActiveTask)
{
// Blow up the plane if it receives consecutive explosion impacts
if(weaponHash == WEAPONTYPE_EXPLOSION)
{
CTaskVehicleCrash *pCrashTask = static_cast<CTaskVehicleCrash *>(pActiveTask);
pCrashTask->SetCrashFlag(CTaskVehicleCrash::CF_HitByConsecutiveExplosion, true);
}
}
else
{
CTaskVehicleCrash *pCarTask = rage_new CTaskVehicleCrash( pCulprit, 0, weaponHash);
if(pHandling->GetSeaPlaneHandlingData() && GetIsInWater())
{
blowUpInstantly = true;
}
pCarTask->SetCrashFlag(CTaskVehicleCrash::CF_BlowUpInstantly, blowUpInstantly);
pCarTask->SetCrashFlag(CTaskVehicleCrash::CF_InACutscene, bInACutscene);
pCarTask->SetCrashFlag(CTaskVehicleCrash::CF_AddExplosion, bAddExplosion);
GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pCarTask, VEHICLE_TASK_PRIORITY_CRASH);
return;
}
}
//Because this task is not run in Clones we need to Finish Blowing Up the Vehicle.
else
{
FinishBlowingUpVehicle(pCulprit, bInACutscene, bAddExplosion, bNetCall, weaponHash, bDelayedExplosion);
}
}
//////////////////////////////////////////////////////////////////////////
// Damage the vehicle even more, close to these bones during BlowUpCar
//////////////////////////////////////////////////////////////////////////
const int PLANE_BONE_COUNT_TO_DEFORM = 30;
const eHierarchyId ExtraPlaneBones[PLANE_BONE_COUNT_TO_DEFORM] = {
VEH_CHASSIS, VEH_CHASSIS, PLANE_WING_L, PLANE_WING_R, //default CPU code path has 4 max impacts, do the most important ones first
VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS,
VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS,
PLANE_WING_L, PLANE_WING_L, PLANE_WING_L, PLANE_WING_L,
PLANE_WING_R, PLANE_WING_R, PLANE_WING_R, PLANE_WING_R,
PLANE_ELEVATOR_L, PLANE_ELEVATOR_R, PLANE_TAIL,
PLANE_PROP_1, PLANE_PROP_2, PLANE_PROP_3, PLANE_PROP_4,
PLANE_WINGTIP_1, PLANE_WINGTIP_2, PLANE_WING_RL, PLANE_WING_RR };
const eHierarchyId* CPlane::GetExtraBonesToDeform(int& extraBoneCount)
{
extraBoneCount = PLANE_BONE_COUNT_TO_DEFORM;
return ExtraPlaneBones;
}
//
//
//This should only be called from the crash task
const float PLANE_BLOW_OFF_WING_PROBABILITY = 0.75;
void CPlane::FinishBlowingUpVehicle( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool ASSERT_ONLY(bNetCall), u32 weaponHash, bool bDelayedExplosion )
{
#if __DEV
if (gbStopVehiclesExploding)
{
return;
}
#endif
if (GetStatus() == STATUS_WRECKED)
{
return; // Don't blow cars up a 2nd time
}
/// don't damage if this flag is set (usually during a cutscene)
if (m_nPhysicalFlags.bNotDamagedByAnything)
{
return;
}
// we can't blow up cars controlled by another machine
// but we still have to change their status to wrecked
// so the car 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);
SetIsWrecked();
//Check to see that it is the player
if (pCulprit && pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer())
{
CStatsMgr::RegisterVehicleBlownUpByPlayer(this);
CCrime::ReportDestroyVehicle(this, static_cast<CPed*>(pCulprit));
}
return;
}
if (NetworkUtils::IsNetworkCloneOrMigrating(this))
{
// the vehicle is migrating. Create a weapon damage event to blow up the vehicle, which will be sent to the new owner. If the migration fails
// then the vehicle will be blown up a little later.
CBlowUpVehicleEvent::Trigger(*this, pCulprit, bAddExplosion, weaponHash, bDelayedExplosion);
return;
}
//Total damage done for the damage trackers
float totalDamage = GetHealth() + m_VehicleDamage.GetEngineHealth() + m_VehicleDamage.GetPetrolTankHealth();
for(s32 i=0; i<GetNumWheels(); i++)
{
totalDamage += m_VehicleDamage.GetTyreHealth(i);
totalDamage += m_VehicleDamage.GetSuspensionHealth(i);
}
totalDamage = totalDamage > 0.0f ? totalDamage : 1000.0f;
SetIsWrecked();
// increment player stats
if (( pCulprit && pCulprit->GetIsTypePed() && ((CPed*)pCulprit)->IsLocalPlayer() ) || pCulprit == FindPlayerVehicle())
{
CGameWorld::FindLocalPlayer()->GetPlayerInfo()->HavocCaused += HAVOC_BLOWUPCAR;
}
// remove the plane
if (GetStatus() == STATUS_PLAYER)
{
for (s32 i=0; i<GetSeatManager()->GetMaxSeats(); i++)
{
if (GetSeatManager()->GetPedInSeat(i))
{
GetSeatManager()->GetPedInSeat(i)->SetPedResetFlag( CPED_RESET_FLAG_DontRenderThisFrame, true );
}
}
SetIsVisibleForModule( SETISVISIBLE_MODULE_GAMEPLAY, false, true );
DisableCollision();
SetVelocity(Vector3(0,0,0));
SetAngVelocity(Vector3(0,0,0));
}
SetWeaponDamageInfo(pCulprit, weaponHash, fwTimer::GetTimeInMilliseconds());
//Set the destruction information.
SetDestructionInfo(pCulprit, weaponHash);
if(GetModelIndex() != MI_PLANE_JET)
{
m_nPhysicalFlags.bRenderScorched = TRUE; // need to make Scorched BEFORE components blow off
}
SetHealth(0.0f); // Make sure this happens before AddExplosion or it will blow up twice
// Vector3 Temp = vThisPosition;
KillPedsInVehicle(pCulprit, weaponHash);
// Switch off the engine. (For sound purposes)
this->SwitchEngineOff(false);
this->m_OverrideLights = NO_CAR_LIGHT_OVERRIDE;
this->m_nVehicleFlags.bLightsOn = FALSE;
this->TurnSirenOn(FALSE);
this->m_nAutomobileFlags.bTaxiLight = FALSE;
//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);
CPed* pInflictorPed = pCulprit->GetIsTypeVehicle() ? static_cast<CVehicle*>(pCulprit)->GetDriver() : static_cast<CPed*>(pCulprit);
CCrime::ReportDestroyVehicle(this, pInflictorPed);
}
if( bAddExplosion )
{
AddVehicleExplosion(pCulprit, bInACutscene, bDelayedExplosion);
}
// knock bits off the car
GetVehicleDamage()->BlowUpCarParts(pCulprit, CVehicleDamage::Break_Off_Car_Parts_Pending_Bound_Update);
// Break lights, windows and sirens
GetVehicleDamage()->BlowUpVehicleParts(pCulprit);
if(!GetVehicleDamage()->IsBlowUpCarPartsPending())
{
// if we are not postpone the parts breaking, then do it now
BlowUpPlaneParts();
}
g_decalMan.Remove(this);
CPed* fireCulprit = NULL;
if (pCulprit && pCulprit->GetIsTypePed())
{
fireCulprit = static_cast<CPed*>(pCulprit);
}
g_vfxVehicle.ProcessWreckedFires(this, fireCulprit, FIRE_DEFAULT_NUM_GENERATIONS);
m_aircraftDamage.BlowingUpVehicle(this);
}
void CPlane::PostBoundDeformationUpdate()
{
if(GetVehicleDamage() && GetVehicleDamage()->IsBlowUpCarPartsPending() && CApplyDamage::GetNumDamagePending(this) == 0)
{
BlowUpPlaneParts();
}
CVehicle::PostBoundDeformationUpdate();
}
void CPlane::BlowUpPlaneParts()
{
//Destroy all propellers
fragInstGta* pFragInst = GetVehicleFragInst();
vehicleAssert(pFragInst);
for(int i = 0; i < m_iNumPropellers; i++)
{
int nRotorChild = m_propellerCollision[i].GetFragChild();
if(nRotorChild != -1 && !pFragInst->GetChildBroken(nRotorChild))
{
BreakOffRotor(i);
}
}
//Damage and break off all the breakable parts of the plane and landing gear
bool bWingDestructionSkipped = false;
for(int i = 0; i < CAircraftDamage::NUM_DAMAGE_SECTIONS; i++)
{
// Wings don't blow off under certain chances
if(NetworkInterface::IsGameInProgress() && !bWingDestructionSkipped && (i == CAircraftDamage::WING_L || i == CAircraftDamage::WING_R))
{
if(fwRandom::GetRandomNumberInRange(0.0f, 1.0f) > PLANE_BLOW_OFF_WING_PROBABILITY)
{
bWingDestructionSkipped = true;
continue;
}
}
int nBoneIndex = GetBoneIndex(GetAircraftDamage().GetHierarchyIdFromSection(i));
int nComponent = GetFragInst()->GetComponentFromBoneIndex(nBoneIndex);
if(nComponent > -1)
{
GetAircraftDamage().BreakOffSection(this, i);
}
}
if( GetModelIndex() != MI_PLANE_MICROLIGHT &&
GetModelIndex() != MI_PLANE_AVENGER )
{
for(int i = 0; i < CLandingGearDamage::NUM_DAMAGE_SECTIONS; i++)
{
int nBoneIndex = GetBoneIndex(GetLandingGearDamage().GetHierarchyIdFromSection(i));
int nComponent = GetFragInst()->GetComponentFromBoneIndex(nBoneIndex);
if(nComponent > -1)
{
GetLandingGearDamage().BreakOffSection(this, i);
}
}
}
// Break all screens and emissives in the plane
if(GetBoneIndex(VEH_EMISSIVES) != -1)
{
GetLocalMtxNonConst(GetBoneIndex(VEH_EMISSIVES)).Zero3x3();
}
}
// Copy and pasted parts from above - [HACK_GTAV] B* 1673245
void CPlane::DamageForWorldExtents()
{
fragInstGta* pFragInst = GetVehicleFragInst();
if (Verifyf(pFragInst, "NULL frag inst for vehicle!"))
{
// Make sure the plane can be broken up.
pFragInst->SetDisableBreakable(false);
// Cache off the plane's velocity.
Vector3 vVel = GetVelocity();
// Zero out the plane's velocity to give the broken parts the appearance of having been ripped backwards.
SetVelocity(Vector3(0.0, 0.0, 0.0));
// Break off one of the wings.
for(int iSection = 0; iSection < CAircraftDamage::NUM_DAMAGE_SECTIONS; iSection++)
{
if (iSection == CAircraftDamage::AILERON_L || iSection == CAircraftDamage::ELEVATOR_L || iSection == CAircraftDamage::AIRBRAKE_L || iSection == CAircraftDamage::WING_L)
{
int nBoneIndex = GetBoneIndex(GetAircraftDamage().GetHierarchyIdFromSection(iSection));
int nComponent = GetFragInst()->GetComponentFromBoneIndex(nBoneIndex);
if(nComponent > -1)
{
GetAircraftDamage().SetSectionHealth(iSection, 0.0f);
GetAircraftDamage().BreakOffSection(this, iSection);
}
}
}
// Restore the plane's velocity.
SetVelocity(vVel);
}
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DragPlaneToNewCoordinates
// PURPOSE : Should be used for planes taxiing on the runway. The point along the path
// is specified and the plane is dragged to these coordinates.
///////////////////////////////////////////////////////////////////////////////////
void CPlane::DragPlaneToNewCoordinates(CEntity *pEntity, Vector3 &pos)
{
#define FRONTWHEEL_Y (20.0f)
#define REARWHEEL_Y (-5.0f)
//Vector3 front = pEntity->TransformIntoWorldSpace(Vector3(0.0f, FRONTWHEEL_Y, 0.0f));
Vector3 rear = pEntity->TransformIntoWorldSpace(Vector3(0.0f, REARWHEEL_Y, 0.0f));
Vector3 newBVec = pos - rear;
newBVec.z = 0.0f;
newBVec.Normalize();
Matrix34 newMat;
newMat.b = -newBVec;
newMat.c = Vector3(0.0f, 0.0f, 1.0f);
newMat.a.Cross(newMat.b, newMat.c);
newMat.d = pos - (FRONTWHEEL_Y * newBVec);
static float hardCodedZ = 15.85f;
newMat.d.z = hardCodedZ;
pEntity->SetMatrix(newMat);
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FlyPlaneToNewCoordinates
// PURPOSE : Should be used for planes flying around. Some points along the path
// are specified and the planes matrix is constructed accordingly.
///////////////////////////////////////////////////////////////////////////////////
void CPlane::FlyPlaneToNewCoordinates(CEntity *pEntity, Vector3 &pos, Vector3 &posAhead, Vector3 &posWayAhead)
{
Matrix34 newMat;
newMat.d = pos;
newMat.b = pos - posAhead;
newMat.b.z = 0.0f;
newMat.b.Normalize();
Vector3 dirAlongSpline = posWayAhead - pos;
dirAlongSpline.Normalize();
static float rollAmount = 2.0f;
Vector3 wayAheadNorm = posWayAhead-pos;
wayAheadNorm.Normalize();
float roll = rollAmount * wayAheadNorm.CrossZ(newMat.b);
newMat.c = Vector3(roll * newMat.b.y, -roll * newMat.b.x, 1.0f);
newMat.c.Normalize();
newMat.a.Cross(newMat.b, newMat.c);
pEntity->SetMatrix(newMat);
}
bank_float bfWindyWindMultForPlane = 1.5f;
bank_float bfRainingWindMultForPlane = 1.25f;
bank_float bfSnowingWindMultForPlane = 1.25f;
bank_float bfSandStormWindMultForPlane = 1.25f;
bank_float bfTurbulenceWindMultForPlane = 0.0f; // Turn off the wind effects completely when turbulence kicks in
float CPlane::ComputeWindMult(float fRatioOfVerticalFlightToUse)
{
// Turn off wind effect when plane is landed
if(!IsInAir())
{
return 0.0f;
}
// Turn off wind effect when plane is wrecked or lost all its wings
if(GetStatus() == STATUS_WRECKED || (m_aircraftDamage.HasSectionBrokenOff(this,CAircraftDamage::WING_L) && m_aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R)))
{
return 0.0f;
}
float fMult = 1.0f;
if(pHandling && pHandling->GetFlyingHandlingData())
{
if(GetVerticalFlightModeAvaliable() && pHandling->GetVerticalFlyingHandlingData() && fRatioOfVerticalFlightToUse > 0.0f)
{
float fWindMultFromHandling = (pHandling->GetFlyingHandlingData()->m_fWindMult * (1.0f - fRatioOfVerticalFlightToUse)) + (pHandling->GetVerticalFlyingHandlingData()->m_fWindMult * fRatioOfVerticalFlightToUse);
fMult *= fWindMultFromHandling * m_fScriptTurbulenceMult;
}
else
{
fMult *= pHandling->GetFlyingHandlingData()->m_fWindMult * m_fScriptTurbulenceMult;
}
}
if(g_weather.IsWindy())
{
fMult *= bfWindyWindMultForPlane;
}
if(g_weather.IsRaining())
{
fMult *= bfRainingWindMultForPlane;
}
if(g_weather.IsSnowing())
{
fMult *= bfSnowingWindMultForPlane;
}
if(g_weather.GetSandstorm() > 0.2f)
{
fMult *= bfSandStormWindMultForPlane;
}
if(IsTurbulenceOn())
{
fMult *= bfTurbulenceWindMultForPlane;
}
return fMult;
}
bank_float bfCalmWeatherTurbulenceMultForPlane = 0.5f;
bank_float bfWindyTurbulenceMultForPlane = 0.05f;
bank_float bfRainingTurbulenceMultForPlane = 0.5f;
bank_float bfSnowingTurbulenceMultForPlane = 0.4f;
bank_float bfSandStormTurbulenceMultForPlane = 0.5f;
float CPlane::WeatherTurbulenceMult()
{
float fMult = bfCalmWeatherTurbulenceMultForPlane;
if(g_weather.IsWindy())
{
fMult += bfWindyTurbulenceMultForPlane;
}
if(g_weather.IsRaining())
{
fMult += bfRainingTurbulenceMultForPlane;
}
if(g_weather.IsSnowing())
{
fMult += bfSnowingTurbulenceMultForPlane;
}
if(g_weather.GetSandstorm() > 0.2f)
{
fMult += bfSandStormTurbulenceMultForPlane;
}
return Min(fMult, 1.0f);
}
float CPlane::WeatherTurbulenceMultInv()
{
float fMult = 1.0f;
if(g_weather.IsWindy())
{
fMult -= bfWindyTurbulenceMultForPlane;
}
if(g_weather.IsRaining())
{
fMult -= bfRainingTurbulenceMultForPlane;
}
if(g_weather.IsSnowing())
{
fMult -= bfSnowingTurbulenceMultForPlane;
}
if(g_weather.GetSandstorm() > 0.2f)
{
fMult -= bfSandStormTurbulenceMultForPlane;
}
return Max(fMult, 0.0f);
}
void CPlane::EnableIndividualPropeller(int iPropellerIndex, bool bEnable)
{
if(iPropellerIndex >= 0 && iPropellerIndex < PLANE_NUM_PROPELLERS)
{
int iPropellerMask = 1 << iPropellerIndex;
if(bEnable)
{
m_IndividualPropellerFlags = m_IndividualPropellerFlags | iPropellerMask;
}
else
{
m_IndividualPropellerFlags = m_IndividualPropellerFlags & (~iPropellerMask);
}
}
}
bool CPlane::IsIndividualPropellerOn(int iPropellerIndex) const
{
if(iPropellerIndex >= 0 && iPropellerIndex < PLANE_NUM_PROPELLERS)
{
int iPropellerMask = 1 << iPropellerIndex;
return (m_IndividualPropellerFlags & iPropellerMask) != 0;
}
return false;
}
// These DoesBullet/ProjectileHit... methods were copied directly from CHeli so that plane propellers exhibit the same behavior as helis.
// If we ever get around to componentising the vehicles, it'd be really nice to have this behaviour in its own module and shared by planes and helis.
bool CPlane::DoesBulletHitPropellerBound(int iComponent) const
{
bool bIsDiscBound;
int iPropellerIndex;
return DoesBulletHitPropellerBound(iComponent, bIsDiscBound, iPropellerIndex);
}
bool CPlane::DoesBulletHitPropellerBound(int iComponent, bool &bHitDiscBound, int &iPropellerIndex) const
{
for(int i =0; i < m_iNumPropellers; i++)
{
if(m_propellerCollision[i].GetFragDisc() == iComponent)
{
bHitDiscBound = true;
iPropellerIndex = i;
return true;
}
else if(m_propellerCollision[i].GetFragGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[m_propellerCollision[i].GetFragGroup()];
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
if(iComponent == m_propellerCollision[i].GetFragChild() + iChild)
{
bHitDiscBound = false;
iPropellerIndex = i;
return true;
}
}
}
}
return false;
}
static dev_float ms_fPlaneBlockBulletSpeedRatioThreshold = 0.1f;
bool CPlane::DoesBulletHitPropeller(int iEntryComponent, const Vector3 &UNUSED_PARAM(vEntryPos), int iExitComponent, const Vector3 &UNUSED_PARAM(vExitPos)) const
{
if(iEntryComponent != iExitComponent)
{
return false;
}
bool bHitDiscBound;
int iPropellerIndex;
if(DoesBulletHitPropellerBound(iEntryComponent, bHitDiscBound, iPropellerIndex))
{
float fSpeedRatio = m_propellers[iPropellerIndex].GetSpeed();
if(bHitDiscBound)
{
return fSpeedRatio > ms_fPlaneBlockBulletSpeedRatioThreshold
&& fSpeedRatio > fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
}
else
{
return fSpeedRatio <= ms_fPlaneBlockBulletSpeedRatioThreshold;
}
}
return false;
}
bool CPlane::DoesProjectileHitPropeller(int iComponent) const
{
bool bHitDiscBound;
int iPropellerIndex;
if(DoesBulletHitPropellerBound(iComponent, bHitDiscBound, iPropellerIndex))
{
float fSpeedRatio = m_propellers[iPropellerIndex].GetSpeed();
if(bHitDiscBound)
{
return fSpeedRatio > ms_fPlaneBlockBulletSpeedRatioThreshold
&& fSpeedRatio > fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
}
else
{
return fSpeedRatio <= ms_fPlaneBlockBulletSpeedRatioThreshold;
}
}
return false;
}
float CPlane::ms_fPlaneControlLaggingMinBlendingRate = 1.0f;
float CPlane::ms_fPlaneControlLaggingMaxBlendingRate = 10.0f;
float CPlane::ms_fPlaneControlAbilityControlDampMin = 0.7f;
float CPlane::ms_fPlaneControlAbilityControlDampMax = 1.0f;
#if RSG_PC
float CPlane::ms_fPlaneControlAbilityControlRandomMin = 0.0f;
float CPlane::ms_fPlaneControlAbilityControlRandomMax = 1.0f;
float CPlane::ms_fPlaneControlAbilityControlRandomRollMult = 0.32f;
float CPlane::ms_fPlaneControlAbilityControlRandomPitchMult = 0.16f;
float CPlane::ms_fPlaneControlAbilityControlRandomYawMult = 0.16f;
float CPlane::ms_fPlaneControlAbilityControlRandomThrottleMult = 0.32f;
#else
float CPlane::ms_fPlaneControlAbilityControlRandomMin = 0.0f;
float CPlane::ms_fPlaneControlAbilityControlRandomMax = 1.0f;
float CPlane::ms_fPlaneControlAbilityControlRandomRollMult = 0.64f;
float CPlane::ms_fPlaneControlAbilityControlRandomPitchMult = 0.32f;
float CPlane::ms_fPlaneControlAbilityControlRandomYawMult = 0.32f;
float CPlane::ms_fPlaneControlAbilityControlRandomThrottleMult = 0.64f;
#endif
float CPlane::ms_fTimeBetweenRandomControlInputsMin = 3.0f;
float CPlane::ms_fTimeBetweenRandomControlInputsMax = 6.0f;
float CPlane::ms_fRandomControlLerpDown = 0.98f;
float CPlane::ms_fMaxAbilityToAdjustDifficulty = 0.6f;
bank_bool CPlane::ms_bDebugAfterburnerEffect = false;
#if __BANK
void CPlane::InitWidgets(bkBank& bank)
{
bank.PushGroup("Planes");
bank.AddSlider("Ceiling cut off range", &ms_fCeilingLiftCutoffRange, 0.0f, 100.0f,0.01f);
bank.AddSlider("Lagging control min blending rate", &ms_fPlaneControlLaggingMinBlendingRate, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Lagging control max blending rate", &ms_fPlaneControlLaggingMaxBlendingRate, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlDampMin", &ms_fPlaneControlAbilityControlDampMin, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlDampMax", &ms_fPlaneControlAbilityControlDampMax, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlRandomMin", &ms_fPlaneControlAbilityControlRandomMin, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlRandomMax", &ms_fPlaneControlAbilityControlRandomMax, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlRandomRollMult", &ms_fPlaneControlAbilityControlRandomRollMult, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlRandomPitchMult", &ms_fPlaneControlAbilityControlRandomPitchMult, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlRandomYawMult", &ms_fPlaneControlAbilityControlRandomYawMult, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fPlaneControlAbilityControlRandomThrottleMult", &ms_fPlaneControlAbilityControlRandomThrottleMult, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fTimeBetweenRandomControlInputsMin", &ms_fTimeBetweenRandomControlInputsMin, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fTimeBetweenRandomControlInputsMax", &ms_fTimeBetweenRandomControlInputsMax, 0.0f, 100.0f, 0.1f);
bank.AddSlider("ms_fRandomControlLerpDown", &ms_fRandomControlLerpDown, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine idling speed", &ms_fStdEngineSpeed, 0.0f,1.0f,0.001f);
bank.AddSlider("Full reverse engine speed", &ms_fMinEngineSpeed, 0.0f,1.0f,0.001f);
bank.AddSlider("Full forward engine speed", &ms_fMaxEngineSpeed, 0.0f,1.0f,0.001f);
bank.AddSlider("Jet on ground idle engine speed", &bfStdJetOnGroundEngineSpeed, 0.0f,1.0f,0.001f);
bank.AddSlider("Jet on ground full reverse engine speed", &bfMinJetOnGroundEngineSpeed, 0.0f,1.0f,0.001f);
bank.AddSlider("Jet on ground full forward engine speed", &bfMaxJetOnGroundEngineSpeed, 0.0f,1.0f,0.001f);
bank.AddSlider("Propeller speed mult", &ms_fPropRenderSpeed, 0.0f,100.0f,0.01f);
bank.AddToggle("Debug afterburner effect", &ms_bDebugAfterburnerEffect);
bank.PushGroup("Speed Damping");
bank.AddSlider("ms_fTopSpeedDampRate", &ms_fTopSpeedDampRate, -100.0f, 1000.0f,0.1f);
bank.AddSlider("ms_fTopSpeedDampMaxHeight", &ms_fTopSpeedDampMaxHeight, -100.0f, 1000.0f,0.1f);
bank.AddSlider("ms_fTopSpeedDampMinHeight", &ms_fTopSpeedDampMinHeight, -100.0f, 1000.0f,0.1f);
bank.PopGroup();
bank.PushGroup("Damage");
bank.AddSlider("Min damage to cause fire", &CAircraftDamage::ms_fMinCausingFlameDamage, 0.0f, 1000.0f, 1.0f);
bank.AddSlider("Max damage to cause fire", &CAircraftDamage::ms_fMaxCausingFlameDamage, 0.0f, 1000.0f, 1.0f);
bank.AddSlider("Min damage flame life span", &CAircraftDamage::ms_fMinFlameLifeSpan, 0.0f, 100.0f, 1.0f);
bank.AddSlider("Max damage flame life span", &CAircraftDamage::ms_fMaxFlameLifeSpan, 0.0f, 100.0f, 1.0f);
bank.AddSlider("flame damage multiplier", &CAircraftDamage::ms_fFlameDamageMultiplier, 1.0f, 10.0f, 0.01f);
bank.AddSlider("flame mergeable distance", &CAircraftDamage::ms_fFlameMergeableDistance, 0.0f, 10.0f, 0.01f);
bank.AddToggle("Draw flame positions", &CAircraftDamage::ms_bDrawFlamePosition);
bank.AddSlider("Turbulence body health threshold", &sfBuffetingBodyHealthThreshold, 0.0f, 1000.0f, 1.0f);
bank.AddSlider("Engine degrade min damage", &dfEngineDegradeMinDamage, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine degrade max damage", &dfEngineDegradeMaxDamage, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine miss fire min time", &bfPlaneEngineMissFireMinTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine miss fire max time", &bfPlaneEngineMissFireMaxTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine miss fire min recover time", &bfPlaneEngineMissFireMinRecoverTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine miss fire max recover time", &bfPlaneEngineMissFireMaxRecoverTime, 0.0f, 100.0f, 0.1f);
bank.AddSlider("Engine speed drop rate when miss firing", &ms_fEngineSpeedDropRateWhenMissFiring, 0.0f, 10.0f, 0.01f);
bank.AddSlider("Landing impact damage multiplier", &bfPlaneLandingDamageMult, 0.0f, 10.0f, 0.01f);
bank.PopGroup();
bank.PushGroup("Turbulence");
bank.AddToggle("Turbulence on", &bfPlaneTurbulenceOn);
bank.AddSlider("Turbulence min during",&bfTurbulenceTimeMin,0.0f,20.0f,0.01f);
bank.AddSlider("Turbulence max during",&bfTurbulenceTimeMax,0.0f,20.0f,0.01f);
bank.AddSlider("Turbulence min recover time",&bfTurbulenceRecoverTimeMin,0.0f,20.0f,0.01f);
bank.AddSlider("Turbulence max recover time",&bfTurbulenceRecoverTimeMax,0.0f,20.0f,0.01f);
bank.AddSlider("Wind multiplier in windy condition",&bfWindyWindMultForPlane,0.0f,10.0f,0.01f);
bank.AddSlider("Wind multiplier in raining condition",&bfRainingWindMultForPlane,0.0f,10.0f,0.01f);
bank.AddSlider("Wind multiplier in snow condition",&bfSnowingWindMultForPlane,0.0f,10.0f,0.01f);
bank.AddSlider("Wind multiplier in sand storm condition",&bfSandStormWindMultForPlane,0.0f,10.0f,0.01f);
bank.AddSlider("Turbulence multiplier in windy condition",&bfWindyTurbulenceMultForPlane,0.0f,1.0f,0.01f);
bank.AddSlider("Turbulence multiplier in raining condition",&bfRainingTurbulenceMultForPlane,0.0f,1.0f,0.01f);
bank.AddSlider("Turbulence multiplier in snow condition",&bfSnowingTurbulenceMultForPlane,0.0f,1.0f,0.01f);
bank.AddSlider("Turbulence multiplier in sand storm condition",&bfSandStormTurbulenceMultForPlane,0.0f,1.0f,0.01f);
bank.AddSlider("Controller vibration scale when turbulence",&sfPlaneTurbulencePedVibMult,0.0f,1.0f,0.01f);
bank.PopGroup();
bank.PopGroup();
CLandingGear::InitWidgets(bank);
}
#endif
dev_float PLANE_STALL_ANGLE = ( DtoR * 20.0f);
dev_float PLANE_STALL_MIN_VERTICAL_SPEED = -9.8f;
dev_float PLANE_WING_MAX_ANGLE_OF_ATTACK = 15.0f;
dev_float PLANE_WING_MAX_SIDE_SLIP = 15.0f;
dev_float PLANE_WING_MAX_FORCE = 70.0f;
dev_float fNozeDownAngSpeed = 180.0f;
dev_float sfPlaneControlPilotSchoolMulti = 1.0f;
float FullRumbleSideWindSpeed = 100.0f;
dev_float sfPlaneStallBiasSpeed = 1.0f;
dev_float sfPlaneVirtualLiftSpeedLimit = 2.5f;
dev_float sfReverseThrustMult = 0.25f;
dev_float sfReverseThrustMultInReverse = 0.45f;
dev_float sfForwardThrustMultOnGround = 0.75f;
dev_float sfPlaneTakeOffPitchMulti = 1.0f;
dev_float sfPlaneThrustNoseDivingMulti = 0.5f;
dev_float dfPlaneThrottleControlLimit = 10.0f;
dev_float dfPlaneBrakeControlLimit = 5.0f;
void CPlane::CalculateYawAndPitchcontrol( CControl* pControl, float& fYawControl, float& fPitchControl )
{
// CONTROLS
if(fYawControl==FLY_INPUT_NULL)
{
fYawControl = 0.0f;
if(pControl)
{
fYawControl = pControl->GetVehicleFlyRollLeftRight().GetNorm(ioValue::ALWAYS_DEAD_ZONE);
}
}
if(fPitchControl==FLY_INPUT_NULL)
{
fPitchControl = 0.0f;
if(pControl)
{
fPitchControl = pControl->GetVehicleFlyPitchUpDown().GetNorm(ioValue::ALWAYS_DEAD_ZONE);
// Comment it out to make PC have the same control as in PS2 in JoyPad
}
}
// stuff to convert from a nice circular joystick input, into a nice square representation
float fJoyTheta = rage::Atan2f(fPitchControl, fYawControl);
float fMaxMag = 1.0f;
if( fJoyTheta > -PI/4.0f && fJoyTheta <= PI/4.0f )
{
fMaxMag = 1.0f / rage::Cosf(fJoyTheta);
}
else if( fJoyTheta > PI/4.0f && fJoyTheta <= 0.75f * PI)
{
fMaxMag = 1.0f / rage::Cosf(fJoyTheta - PI/2.0f);
}
else if( fJoyTheta > 0.75f * PI )
{
fMaxMag = 1.0f / rage::Cosf(fJoyTheta - PI);
}
else if( fJoyTheta <= -0.75f*PI )
{
fMaxMag = 1.0f / rage::Cosf(fJoyTheta + PI);
}
else if( fJoyTheta > -0.75f*PI && fJoyTheta < -PI/4.0f)
{
fMaxMag = 1.0f / rage::Cosf(fJoyTheta + PI/2.0f);
}
fYawControl *= fMaxMag;
fPitchControl *= -fMaxMag;
if(!IsInAir())
{
fPitchControl *= sfPlaneTakeOffPitchMulti;
}
}
void CPlane::CalculateDamageEffects( float& fPitchFromDamageEffect, float& fYawFromDamageEffect, float& fRollFromDamageEffect, float fPitchControl, float fYawControl, float fRollControl )
{
static dev_float sfRollControlLostWingEffect = 1.5f;
static dev_float sfRollControlFlapDamageEffect = 5.0f;
static dev_float sfPitchControlAileronDamageEffect = 3.0f;
static dev_float sfYawControlRudderDamageEffect = 3.0f;
static dev_float sfRollControlLostEngineEffect = 0.25f;
static dev_float sfYawControlLostEngineEffect = 0.1f;
if(IsInAir())
{
bool applyRollFromDamage = true;
if( GetVerticalFlightModeAvaliable() &&
m_fDesiredVerticalFlightRatio == 1.0f )
{
// don't apply roll effect if we're in vertical flight mode and we have no wings
if( ( GetAircraftDamage().HasSectionBrokenOff( this, CAircraftDamage::WING_L ) || GetAircraftDamage().GetHierarchyIdFromSection( CAircraftDamage::WING_L ) == VEH_INVALID_ID ) &&
( GetAircraftDamage().HasSectionBrokenOff( this, CAircraftDamage::WING_R ) || GetAircraftDamage().GetHierarchyIdFromSection( CAircraftDamage::WING_R ) == VEH_INVALID_ID ) )
{
applyRollFromDamage = false;
}
}
// Wing Damage
//if we're in the air and we've lost a wing make the plane spin.
if( applyRollFromDamage &&
GetAircraftDamage().HasSectionBrokenOff(this, CAircraftDamage::WING_L))
{
fRollFromDamageEffect -= sfRollControlLostWingEffect;
}
else if( applyRollFromDamage &&
GetAircraftDamage().HasSectionBrokenOff(this, CAircraftDamage::WING_R))
{
fRollFromDamageEffect += sfRollControlLostWingEffect;
}
if(m_iNumPropellers > 1 && GetEngineSpeed() > 0.0f)
{
float fRollControlLostPowerEffect = 0.0f;
float fYawControlLostPowerEffect = 0.0f;
// Engine Damage
//if we lost an engine, also make the plane spin.
if(GetVehicleDamage()->GetEngineHealth() > sfPlaneEngineBreakDownHealth)
{
if(GetAircraftDamage().GetSectionHealth(CAircraftDamage::ENGINE_L) <= 0.0f)
{
fRollControlLostPowerEffect -= sfRollControlLostEngineEffect;
fYawControlLostPowerEffect -= sfYawControlLostEngineEffect;
}
else if(GetAircraftDamage().GetSectionHealth(CAircraftDamage::ENGINE_R) <= 0.0f)
{
fRollControlLostPowerEffect += sfRollControlLostEngineEffect;
fYawControlLostPowerEffect += sfYawControlLostEngineEffect;
}
}
// Propeller Damage
//if we lost any propeller, also make the plane spin.
{
int nPropellersLostOnRightSide = 0;
int nPropellersLostOnLeftSide = 0;
for(int i = 0; i < m_iNumPropellers; i++)
{
if(m_fPropellerHealth[i] <= 0.0f)
{
if(i < m_iNumPropellers / 2)
{
nPropellersLostOnLeftSide++;
}
else
{
nPropellersLostOnRightSide++;
}
}
}
if(nPropellersLostOnRightSide + nPropellersLostOnLeftSide < m_iNumPropellers
&& nPropellersLostOnRightSide != nPropellersLostOnLeftSide)
{
if(nPropellersLostOnLeftSide > nPropellersLostOnRightSide)
{
fRollControlLostPowerEffect -= sfRollControlLostEngineEffect;
fYawControlLostPowerEffect -= sfYawControlLostEngineEffect;
}
else
{
fRollControlLostPowerEffect += sfRollControlLostEngineEffect;
fYawControlLostPowerEffect += sfYawControlLostEngineEffect;
}
}
}
fRollFromDamageEffect += Clamp(fRollControlLostPowerEffect, -sfRollControlLostEngineEffect, sfRollControlLostEngineEffect);
fYawFromDamageEffect += Clamp(fYawControlLostPowerEffect, -sfYawControlLostEngineEffect, sfYawControlLostEngineEffect);
}
float fBodyHealthMult = 0.0f;
if(GetHealth() < sfBuffetingBodyHealthThreshold)
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
fBodyHealthMult = 1.0f - GetHealth() / GetMaxHealth();
fBodyHealthMult = Clamp(fBodyHealthMult, 0.0f, 1.0f);
fBodyHealthMult *= pFlyingHandling->m_fBodyDamageControlEffectMult;
}
// Flap (pitch) Damage causes rolling noise
float fHealthMult = Max(1.0f - m_aircraftDamage.GetPitchMult(this), fBodyHealthMult);
fRollFromDamageEffect += fwRandom::GetRandomNumberInRange(-1.0f, 1.0f) * sfRollControlFlapDamageEffect * fHealthMult * (0.5f * fPitchControl + 0.5f);
// Aileron (roll) Damage causes pitching noise
fHealthMult = Max(1.0f - m_aircraftDamage.GetRollMult(this), fBodyHealthMult);
fPitchFromDamageEffect += fwRandom::GetRandomNumberInRange(-1.0f, 1.0f) * sfPitchControlAileronDamageEffect * fHealthMult * (0.5f * fRollControl + 0.5f);
// Rudder (yaw) Damage causes yaw noise
fHealthMult = Max(1.0f - m_aircraftDamage.GetYawMult(this), fBodyHealthMult);
fYawFromDamageEffect += fwRandom::GetRandomNumberInRange(-1.0f, 1.0f) * sfYawControlRudderDamageEffect * fHealthMult * (0.5f * fYawControl + 0.5f);
// B*3843387 - make damage not affect the handling of the schnuppe as much
if( GetModelIndex() == MI_PLANE_STARLING )
{
fPitchFromDamageEffect *= 0.2f;
fYawFromDamageEffect *= 0.5f;
fRollFromDamageEffect *= 0.5f;
}
}
}
void CPlane::CalculateThrust( float fThrottleControl, float fYawControl, float fForwardSpeed, float fThrustHealthMult, float fOverallMultiplier, bool bPlayerDriver )
{
// THRUST
Vector3 vecTransformB = VEC3V_TO_VECTOR3(GetTransform().GetB());
Vector3 vecThrust = vecTransformB;
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
// only let pilot backwards thrust when on the ground
if( pFlyingHandling->m_fThrust == 0.0f &&
!IsInAir() )
{
if( fThrottleControl >= 0.0f )
{
fThrottleControl = Abs( fYawControl );
}
static dev_float zeroThrustOnGroundForce = 1.25f;
fThrottleControl *= zeroThrustOnGroundForce;
float fFallOff = pFlyingHandling->m_fThrustFallOff * fForwardSpeed * fForwardSpeed;
fThrottleControl *= Max( 0.0f, ( 1.0f -fFallOff ) );
}
else if(IsInAir() || fThrottleControl > 0.0f)
{
if(fForwardSpeed > 0.0f)
{
if( fThrottleControl > 0.0f ||
GetModelIndex() != MI_PLANE_SEABREEZE ||
GetFrameCollisionHistory()->GetMaxCollisionImpulseMagLastFrame() == 0.0f )
{
// Rescales throttle so full brake = 0.0f throttle
fThrottleControl = pFlyingHandling->m_fThrust*0.5f*(fThrottleControl + 1.0f);
}
if( m_bEnableThrustFallOffThisFrame )
{
// let player driven planes
// use old code path
if( bPlayerDriver )
{
float fFallOff = pFlyingHandling->m_fThrustFallOff*fForwardSpeed*fForwardSpeed;
fThrottleControl *= Max( 0.0f, (1.0f -fFallOff) );
}
else
{
// if we're trying to accelerate then falloff
// allow deceleration
if( DotProduct(vecThrust, GetVelocity()) * fThrottleControl > 0.0f )
{
// Make throttle fall off at high speed
// clamp to max of 1.0f
float fFallOff = Min(pFlyingHandling->m_fThrustFallOff*fForwardSpeed*fForwardSpeed, 1.0f);
fThrottleControl *= (1.0f -fFallOff);
}
}
}
}
}
// need to do another dotProduct here rather than use fForwardSpd because we don't want the influence of the windspeed
else if(fThrottleControl < 0.0f && DotProduct(vecThrust, GetVelocity()) < 0.02f && IsInAir())
{
fThrottleControl = pFlyingHandling->m_fThrust*MIN(0.0f, (fThrottleControl - 8.0f*0.97f*fForwardSpeed));
}
else if(fForwardSpeed < 0.0f)
{
fThrottleControl = pFlyingHandling->m_fThrust*fThrottleControl;
// Make throttle fall off at high speed
float fFallOff = pFlyingHandling->m_fThrustFallOff*fForwardSpeed*fForwardSpeed;
fThrottleControl *= (1.0f -fFallOff);
}
//Reduce throttle when using reverse, so we can't stop as quick on a runway.
if(fThrottleControl >= 0.0f)
{
if(!IsInAir())
{
fThrottleControl *= sfForwardThrustMultOnGround;
}
}
else
{
if(fForwardSpeed > 0.0f)
{
fThrottleControl *= sfReverseThrustMult;
}
else
{
fThrottleControl *= sfReverseThrustMultInReverse;
}
}
// limit height that this plane can fly
Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
if( HeightAboveCeiling(vThisPosition.z) > 0.0f )
{
fThrottleControl = Min( fThrottleControl, 0.0f );
SetRocketBoosting(false);
}
//Reduce throttle when upside down
if((GetTransform().GetC().GetZf() < 0.0f) && GetFrameCollisionHistory()->GetMaxCollisionImpulseMagLastFrame() > 0.0f)
{
fThrottleControl = 0.0f;
}
//Increase throttle when nose diving
if(IsInAir() && fThrottleControl > 0.0 && fThrottleControl < 2.0 && vecTransformB.z < 0.0f && IsEngineOn())
{
fThrottleControl += fThrottleControl * Abs(vecTransformB.z) * sfPlaneThrustNoseDivingMulti;
}
//Remove thrust based on how many propellers are alive
float fPropMult = 1.0f;
for(int i = 0; i< m_iNumPropellers; i++)
{
if( m_fPropellerHealth[i] <= 0.0f )
{
fPropMult -= (1.0f/m_iNumPropellers);
if( GetModelIndex() == MI_PLANE_MICROLIGHT )
{
fPropMult = 0.0f;
}
}
}
Vector3 vThrust( vecThrust*m_fGravityForWheelIntegrator*fThrottleControl*GetMass()*fThrustHealthMult*fPropMult*fOverallMultiplier );
#if __ASSERT
Assertf(vThrust.Mag2() < square(150.0f * rage::Max(1.0f, GetMass())),
"ProcessFlightModel force over limit, force [%3.2f, %3.2f, %3.2f], thrust[%3.2f, %3.2f, %3.2f], throttle %3.2f, healthMult %3.2f, propMult %3.2f, overall %3.2f",
vThrust.x, vThrust.y, vThrust.z, vecThrust.x, vecThrust.y, vecThrust.z, fThrottleControl, fThrustHealthMult, fPropMult, fOverallMultiplier);
#endif
ApplyInternalForceCg(vThrust);
}
void CPlane::CalculateSideSlip( Vector3 vecAirSpeed, float fOverallMultiplier )
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
// SIDEFORCES
Vector3 vecSlideSlipForce = VEC3V_TO_VECTOR3(GetTransform().GetA());
// first do sideslip angle of attack
float fSideSlipAngle = -1.0f*DotProduct(vecSlideSlipForce, vecAirSpeed);
fSideSlipAngle = rage::Clamp(fSideSlipAngle, -PLANE_WING_MAX_SIDE_SLIP, PLANE_WING_MAX_SIDE_SLIP);//limit the slip angle so we dont apply massive forces when hitting objects
// doing sideways stuff
// sideways force from sidesliping
vecSlideSlipForce *= pFlyingHandling->m_fSideSlipMult * fSideSlipAngle * rage::Abs( fSideSlipAngle );
#if __ASSERT
{
Vector3 vForce = vecSlideSlipForce * GetMass() * m_fGravityForWheelIntegrator * fOverallMultiplier;
Assertf(vForce.Mag2() < square(150.0f * rage::Max(1.0f, GetMass())),
"ProcessFlightModel force over limit, force [%3.2f, %3.2f, %3.2f], SlideSlipForce [%3.2f, %3.2f, %3.2f], mult %3.2f, angle %3.2f, overall %3.2f",
vForce.x, vForce.y, vForce.z, vecSlideSlipForce.x, vecSlideSlipForce.y, vecSlideSlipForce.z, pFlyingHandling->m_fSideSlipMult, fSideSlipAngle, fOverallMultiplier);
}
#endif
ApplyInternalForceCg(vecSlideSlipForce*GetMass()*m_fGravityForWheelIntegrator*fOverallMultiplier);
}
Vector3 CPlane::CalculateYaw( Vector3 vecTailOffset, Vector3 vecWindSpeed, float fOverallMultiplier, float fYawControl, float fVirtualFwdSpd, float fYawFromDamageEffect )
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
Vector3 vecAngInertia = GetAngInertia();
// control force from steering and stabilising force from rudder
Vector3 vecRudderForce = VEC3V_TO_VECTOR3( GetTransform().GetA() );
float fSideSlipAngle = -1.0f*DotProduct(vecRudderForce, GetLocalSpeed(vecTailOffset) - ComputeWindMult(0.0f)*vecWindSpeed - m_vTurbulenceAirflow);
fSideSlipAngle = rage::Clamp(fSideSlipAngle, -PLANE_WING_MAX_SIDE_SLIP, PLANE_WING_MAX_SIDE_SLIP);//limit the slip angle so we dont apply massive forces when hitting objects
float fHealthMult = m_aircraftDamage.GetYawMult(this);
float virtualSpeedForYaw = fVirtualFwdSpd;
const float sfOnGroundYawLowSpeedThresh = pFlyingHandling->m_fOnGroundYawBoostSpeedCap;
const float sfOnGroundYawLowSpeedPeak = pFlyingHandling->m_fOnGroundYawBoostSpeedPeak;
// If we are on the ground increase the yaw force, so we can turn more easily
if(!IsInAir() && fVirtualFwdSpd <= sfOnGroundYawLowSpeedThresh)
{
// This ends up being a piecemeal linear function that goes from 0 at 0 m/s to 4 times the normal yaw force of 20 m/s at 5 m/s and
// then falls back down to the normal yaw force of 20 m/s as we approach 20 m/s
// The result is a dramatic increase in turning power on the runway for all planes that fades in and out nicely around a low speed threshold (Since the overall speed value remains continuous at all points)
if(fVirtualFwdSpd <= sfOnGroundYawLowSpeedPeak)
{
virtualSpeedForYaw = (fVirtualFwdSpd / sfOnGroundYawLowSpeedPeak) * sfOnGroundYawLowSpeedThresh * (sfOnGroundYawLowSpeedThresh / sfOnGroundYawLowSpeedPeak);
}
else
{
virtualSpeedForYaw = sfOnGroundYawLowSpeedThresh * (sfOnGroundYawLowSpeedThresh / fVirtualFwdSpd);
}
}
float fRudderForceFactor = pFlyingHandling->m_fYawMult*fYawControl*virtualSpeedForYaw + pFlyingHandling->m_fYawStabilise*fSideSlipAngle*rage::Abs(fSideSlipAngle);
fRudderForceFactor += fYawFromDamageEffect * pFlyingHandling->m_fYawMult * fVirtualFwdSpd; // Intentionally not using virtualSpeedForYaw here
fRudderForceFactor *= fHealthMult;
vecRudderForce *= fRudderForceFactor*vecAngInertia.z*m_fGravityForWheelIntegrator;
Vector3 vecTorque = vecTailOffset;
vecTorque.Cross(vecRudderForce*fOverallMultiplier);
#if __ASSERT
Vector3 vecAngAccel = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecTorque)));
vecAngAccel.Multiply(GetInvAngInertia());
Assertf(vecAngAccel.Mag2() < square(150.0f), "vecAngAccel.Mag2() < square(150.0f), A=%.2f,%.2f,%.2f, %.2f YawMult, %.2f YawControl, %.2f ForwardSpd, %.2f YawStab, %.2f fSideSlipAngle, %.2f AngInertia.z %.2f overall",
GetTransform().GetA().GetXf(), GetTransform().GetA().GetYf(), GetTransform().GetA().GetZf(),
pFlyingHandling->m_fYawMult, fYawControl, fVirtualFwdSpd, pFlyingHandling->m_fYawStabilise, fSideSlipAngle, vecAngInertia.z, fOverallMultiplier);
#endif
return vecTorque;
}
Vector3 CPlane::CalculateRoll( float fOverallMultiplier, float fRollControl, float fRollFromDamageEffect, float fVirtualFwdSpd )
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
Vector3 vecAngInertia = GetAngInertia();
// ROLL
Vector3 vecRollForce = VEC3V_TO_VECTOR3( GetTransform().GetA() );
float fHealthMult = m_aircraftDamage.GetRollMult(this);
float fRollForceFactor = pFlyingHandling->m_fRollMult * fRollControl * fVirtualFwdSpd;
fRollForceFactor *= fHealthMult;
fRollForceFactor += fRollFromDamageEffect * pFlyingHandling->m_fRollMult * fVirtualFwdSpd;
if(!IsInAir())// if we are on the ground reduce the roll force, so we don't tip over the plane as easily.
{
static dev_float sfOnGroundRollReductionFactor = 0.2f;
fRollForceFactor *= sfOnGroundRollReductionFactor;
}
vecRollForce *= fRollForceFactor * vecAngInertia.y * m_fGravityForWheelIntegrator;
Vector3 vecTorque = VEC3V_TO_VECTOR3( GetTransform().GetC() );
vecTorque.Cross( vecRollForce * fOverallMultiplier );
#if __ASSERT
Vector3 vecAngAccel = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecTorque)));
vecAngAccel.Multiply(GetInvAngInertia());
Assertf(vecAngAccel.Mag2() < square(150.0f), "vecAngAccel.Mag2() < square(150.0f), A=%.2f,%.2f,%.2f C=%.2f,%.2f,%.2f, %.2f RollMult, %.2f RollControl, %.2f ForwardSpd, %.2f AngInertia.y %.2f overall",
GetTransform().GetA().GetXf(), GetTransform().GetA().GetYf(), GetTransform().GetA().GetZf(),
GetTransform().GetC().GetXf(), GetTransform().GetC().GetYf(), GetTransform().GetC().GetZf(),
pFlyingHandling->m_fRollMult, fRollControl, fVirtualFwdSpd, vecAngInertia.y, fOverallMultiplier);
#endif
return vecTorque;
}
Vector3 CPlane::CalculateStuntRoll( float UNUSED_PARAM( fOverallMultiplier ), float fRollControl, float fTimeStep )
{
TUNE_GROUP_FLOAT( STUNT_PLANE, STUNT_ROLL_MAX_SPEED, 50.0f, 0.0f, 100.0f, 0.1f );
TUNE_GROUP_FLOAT( STUNT_PLANE, STUNT_ROLL_CONTROL_DEAD_ZONE, 0.0f, 0.0f, 10.0f, 0.1f );
TUNE_GROUP_FLOAT( STUNT_PLANE, STUNT_ROLL_TORQUE_SCALE, 1.0f, 0.0f, 10.0f, 0.1f );
TUNE_GROUP_BOOL( STUNT_PLANE, USE_ROLL_VELOCITY, true );
TUNE_GROUP_BOOL( STUNT_PLANE, USE_STUNT_ROLL_LOCK, false );
TUNE_GROUP_INT( STUNT_PLANE, STICK_HELD_DURATION, 250, 0, 10000, 0 );
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
static float targetRollAngle = 0.0f;
if( fRollControl > 0.0f )
{
fRollControl = Max( fRollControl - STUNT_ROLL_CONTROL_DEAD_ZONE, 0.0f );
}
else
{
fRollControl = Min( fRollControl + STUNT_ROLL_CONTROL_DEAD_ZONE, 0.0f );
}
fRollControl /= 1.0f - STUNT_ROLL_CONTROL_DEAD_ZONE;
fRollControl *= -1.0f;
fRollControl *= Abs( fRollControl );
float currentRoll = GetTransform().GetRoll();
static u32 anglePressedDurationTime = 0;
static bool useTargetRoll = false;
float rollLockControl = fRollControl;
if( fRollControl != 0.0f &&
( Abs( fRollControl * PI * 0.5f ) > Abs( targetRollAngle ) ||
targetRollAngle * fRollControl < 0.0f ) )
{
useTargetRoll = false;
anglePressedDurationTime = fwTimer::GetTimeInMilliseconds();
rollLockControl *= 100.0f;
rollLockControl = Clamp( rollLockControl, -1.0f, 1.0f );
targetRollAngle = rollLockControl * PI * 0.5f;
if( Abs( currentRoll ) > Abs( targetRollAngle ) ||
currentRoll * targetRollAngle < 0.0f )
{
targetRollAngle = 0.0f;
}
}
if( fRollControl == 0 &&
fwTimer::GetTimeInMilliseconds() - anglePressedDurationTime < 250 )
{
if( Abs( currentRoll ) < Abs( targetRollAngle ) )
{
useTargetRoll = true;
}
anglePressedDurationTime = 0;
}
float torque = 0.0f;
if( useTargetRoll &&
USE_STUNT_ROLL_LOCK )
{
currentRoll += PI;
torque = -( ( targetRollAngle + PI ) - currentRoll );
}
else
{
torque = pFlyingHandling->m_fRollMult * -fRollControl;
}
torque /= fTimeStep;
Vector3 angularVelocity = GetAngVelocity();
float rollVelocity = angularVelocity.Dot( VEC3V_TO_VECTOR3( GetTransform().GetB() ) );
float angularMomentum = angularVelocity.y * Abs( angularVelocity.y ) * GetAngInertia().y;
if( USE_ROLL_VELOCITY )
{
static bool useSqrVel = true;
if( useSqrVel )
{
angularMomentum = rollVelocity * Abs( rollVelocity ) * ( GetAngInertia().y * 0.5f );
}
else
{
angularMomentum = rollVelocity * GetAngInertia().y;
}
}
//torque -= GetAngVelocity().y * STUNT_ROLL_TORQUE_SCALE;
torque = Clamp( torque, -STUNT_ROLL_MAX_SPEED, STUNT_ROLL_MAX_SPEED );
torque *= GetAngInertia().y;
torque -= angularMomentum * STUNT_ROLL_TORQUE_SCALE;
Vector3 vecTorque( 0.0f, torque, 0.0f );
vecTorque = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( vecTorque ) ) );
return vecTorque;
}
Vector3 CPlane::ApplyRollStabilisation( float fOverallMultiplier )
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
// also need to add some stabilisation for roll (so plane naturally levels out)
Vector3 vecForward = VEC3V_TO_VECTOR3( GetTransform().GetB() );
Vector3 vecRightVectorWithoutRoll;
vecRightVectorWithoutRoll.Cross(vecForward, Vector3(0.0f,0.0f,1.0f));
float fRollOffset = 1.0f;
if(GetTransform().GetC().GetZf() > 0.0f)
{
if(GetTransform().GetA().GetZf() > 0.0f)
{
fRollOffset = -1.0f;
}
}
else
{
vecRightVectorWithoutRoll *= -1.0f;
// Is it right that the following code is done if C.z > 0.0f or not?
if(GetTransform().GetA().GetZf() > 0.0f)
{
fRollOffset = -1.0f;
}
}
const Vector3 vecRight = VEC3V_TO_VECTOR3( GetTransform().GetA() );;
fRollOffset *= 1.0f - DotProduct(vecRightVectorWithoutRoll, vecRight);
// roll stabilisation recedes as we go vertical
fRollOffset *= 1.0f - rage::Abs(vecForward.z);
Vector3 vecTorque = VEC3V_TO_VECTOR3( GetTransform().GetC() );
vecTorque.Cross(pFlyingHandling->m_fRollStabilise*fRollOffset*GetAngInertia().y*0.5f*m_fGravityForWheelIntegrator*vecRight*fOverallMultiplier);
#if __ASSERT
Vector3 vecAngAccel = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecTorque)));
vecAngAccel.Multiply(GetInvAngInertia());
Assertf(vecAngAccel.Mag2() < square(150.0f), "vecAngAccel.Mag2() < square(150.0f), vecTorque.Cross(pFlyingHandling->m_fRollStabilise*fRollOffset*vecAngInertia.y*0.5f*-GRAVITY*vecRight*fOverallMultiplier)");
#endif
return vecTorque;
}
Vector3 CPlane::CalculatePitch( Vector3 vecWindSpeed, Vector3 vecTailOffset, Vector3 vecAirSpeed, float fOverallMultiplier, float fPitchControl, float fPitchFromDamageEffect, float fVirtualFwdSpd, bool bClampAngleOfAttack )
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
// PITCH
// calculate tailplane angle of attack
Vector3 vecPitchTorque = VEC3V_TO_VECTOR3( GetTransform().GetC() );
Vector3 vecTransformB = VEC3V_TO_VECTOR3( GetTransform().GetB() );
// Add bias speed to help plane nose down easily during the stall
Vector3 sfBiasVelocity = vecPitchTorque;
sfBiasVelocity.z = 0.0f;
sfBiasVelocity *= sfPlaneStallBiasSpeed;
// the bias velocity is aligned with forward direction
if( DotProduct( sfBiasVelocity, vecTransformB ) < 0.0f )
{
sfBiasVelocity *= -1.0f;
}
if( fPitchControl > 0.0f &&
HasContactWheels() &&
( GetModelIndex() == MI_PLANE_HOWARD ||
GetModelIndex() == MI_PLANE_NOKOTA ) )
{
static dev_float sfScaleNegPitchWhenOnGround = 0.2f;
fPitchControl *= sfScaleNegPitchWhenOnGround;
}
float fAngleOfAttack = -1.0f*DotProduct(vecPitchTorque, GetLocalSpeed(vecTailOffset) + sfBiasVelocity - ComputeWindMult(0.0f)*vecWindSpeed - m_vTurbulenceAirflow);
if(bClampAngleOfAttack)
{
fAngleOfAttack = rage::Clamp(fAngleOfAttack, -PLANE_WING_MAX_ANGLE_OF_ATTACK, PLANE_WING_MAX_ANGLE_OF_ATTACK);//limit the angle of attack so we dont apply massive forces when hitting objects
}
float fPitchTorqueFactor = pFlyingHandling->m_fPitchMult*fPitchControl*fVirtualFwdSpd + pFlyingHandling->m_fPitchStabilise*fAngleOfAttack*rage::Abs(fAngleOfAttack);
// As elevators take damage apply less pitch
float fHealthMult = m_aircraftDamage.GetPitchMult(this);
fPitchTorqueFactor *= fHealthMult;
fPitchTorqueFactor += fPitchFromDamageEffect * pFlyingHandling->m_fPitchMult * fVirtualFwdSpd;
vecPitchTorque *= fPitchTorqueFactor * GetAngInertia().x * m_fGravityForWheelIntegrator;
// Prevent plane take off backwards
if( DotProduct( vecTransformB, vecAirSpeed ) < 0.0f )
{
vecPitchTorque.z = Min( 0.0f, vecPitchTorque.z );
}
Vector3 vecTorque = vecTailOffset;
vecTorque.Cross( vecPitchTorque * fOverallMultiplier );
#if __ASSERT
Vector3 vecAngAccel = VEC3V_TO_VECTOR3( GetTransform().UnTransform3x3( VECTOR3_TO_VEC3V( vecTorque ) ) );
vecAngAccel.Multiply( GetInvAngInertia() );
Assertf( vecAngAccel.Mag2() < square( 150.0f ), "vecAngAccel.Mag2() < square(150.0f), C=%.2f,%.2f,%.2f, TF=%.2f,%.2f,%.2f, %.2f RudderForceFactor, %.2f PitchMult, %.2f PitchControl, %.2f fVirtualFwdSpd, %.2f PitchStab, %.2f AngleOfAttack, %.2f vecAngInertia.x",
GetTransform().GetC().GetXf(), GetTransform().GetC().GetYf(), GetTransform().GetC().GetZf(),
vecTailOffset.x, vecTailOffset.y, vecTailOffset.z,
fPitchTorqueFactor, pFlyingHandling->m_fPitchMult, fPitchControl, fVirtualFwdSpd, pFlyingHandling->m_fPitchStabilise, fAngleOfAttack, GetAngInertia().x );
#endif
return vecTorque;
}
void CPlane::CalculateLandingGearDrag( float fForwardSpd, float fOverallMultiplier )
{
Vector3 drag = m_landingGear.GetDragMult(this) * -fForwardSpd * VEC3V_TO_VECTOR3( GetTransform().GetB() ) * GetMass();
#if __ASSERT
{
Vector3 vForce = drag*fOverallMultiplier;
Assertf(vForce.Mag2() < square(150.0f * rage::Max(1.0f, GetMass())),
"ProcessFlightModel force over limit, force [%3.2f, %3.2f, %3.2f], dragMult %3.2f, spd %3.2f, overall %3.2f",
vForce.x, vForce.y, vForce.z, m_landingGear.GetDragMult(this), fForwardSpd, fOverallMultiplier);
}
#endif
ApplyInternalForceCg( drag * fOverallMultiplier );
}
void CPlane::CalculateLift( Vector3 vecAirSpeed, float fOverallMultiplier, float fPitchControl, float fVirtualFwdSpd, bool bHadCollision )
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
Vector3 vecTransformA = VEC3V_TO_VECTOR3( GetTransform().GetA() );
Vector3 vecTransformB = VEC3V_TO_VECTOR3( GetTransform().GetB() );
Vector3 vecTransformC = VEC3V_TO_VECTOR3( GetTransform().GetC() );
// LIFT
// calculate main wing angle of attack
float fAngleOfAttack = DotProduct( vecTransformC, vecAirSpeed ) / rage::Max( 0.01f, vecAirSpeed.Mag() );
fAngleOfAttack = -1.0f * rage::Asinf( Clamp( fAngleOfAttack, -1.0f, 1.0f ) );
fAngleOfAttack *= 1.0f + ( Max( 0.0f, ( Abs( vecTransformA.GetZ() ) - Abs( fPitchControl ) ) ) * pFlyingHandling->m_fExtraLiftWithRoll );
float fHealthMult = m_aircraftDamage.GetLiftMult(this);
// Also take into account landing gear
fHealthMult *= m_landingGear.GetLiftMult(this);
float fFormLift = pFlyingHandling->m_fFormLiftMult;
float fAttackMult = pFlyingHandling->m_fAttackLiftMult;
if(vecTransformC.z < 0.0f) // If strictly upside down (IsUpsideDown function is too fuzzy)
{
fAttackMult = fAngleOfAttack >= 0.0f ? pFlyingHandling->m_fAttackLiftMult : pFlyingHandling->m_fAttackDiveMult;
}
float fVirtualForwardSpdSqr = fVirtualFwdSpd * fVirtualFwdSpd;
if( !bHadCollision )
{
// If the lift force magnitude going to be less than the gravity, we virtually boost up the forward speed to generate the lift
// force that its magnitude is closer to the gravity. This prevents the unexpected dropping during the flight
float fSpeedSqrRequiredForLift = 1.0f / Max(Abs(fFormLift + fAttackMult*fAngleOfAttack), SMALL_FLOAT);
if(IsInAir() && fSpeedSqrRequiredForLift > fVirtualForwardSpdSqr && vecTransformC.z > 0.0f)
{
if(square(fVirtualFwdSpd + sfPlaneVirtualLiftSpeedLimit) >= fSpeedSqrRequiredForLift)
{
fVirtualForwardSpdSqr = fSpeedSqrRequiredForLift;
}
}
}
float fMass = GetMass();
float fWingForce = ( fFormLift + fAttackMult * fAngleOfAttack ) * fVirtualForwardSpdSqr * fMass * m_fGravityForWheelIntegrator;
fWingForce *= fHealthMult;
fWingForce = rage::Clamp( fWingForce, -PLANE_WING_MAX_FORCE * GetMass(), PLANE_WING_MAX_FORCE * GetMass() );
Vector3 vecLiftForce = fWingForce*vecTransformC;
// Cut the lift force if plane flies over the height limit
const float fThisPositionZ = GetTransform().GetPosition().GetZf();
float fHeightAboveCeiling = HeightAboveCeiling( fThisPositionZ );
if( fHeightAboveCeiling > 0.0f )
{
vecLiftForce.z = Min( 0.0f, vecLiftForce.z );
SetRocketBoosting( false );
// More gravity need to be applied if plane further approaches to the world ceiling
if( GetVelocity().z > 0.0f )
{
float fCeilingApproachingRate = fHeightAboveCeiling / ( WORLDLIMITS_ZMAX - fThisPositionZ + fHeightAboveCeiling );
fCeilingApproachingRate = Clamp( fCeilingApproachingRate, 0.0f, 1.0f );
#if __ASSERT
{
Vector3 vForce = fMass * -m_fGravityForWheelIntegrator * 2.0f * fCeilingApproachingRate * ZAXIS;
Assertf( vForce.Mag2() < square( 150.0f * rage::Max( 1.0f, GetMass() ) ),
"ProcessFlightModel force over limit, force [%3.2f, %3.2f, %3.2f], rate %3.2f",
vForce.x, vForce.y, vForce.z, fCeilingApproachingRate );
}
#endif
ApplyInternalForceCg( fMass * -m_fGravityForWheelIntegrator * 2.0f * fCeilingApproachingRate * ZAXIS );
}
}
// Cut the lift force if plane is flying backwards
if( DotProduct( vecTransformB, vecAirSpeed ) < 0.0f )
{
vecLiftForce.z = Min( 0.0f, vecLiftForce.z );
}
// Cut the lift force if attack angle pass over the stall angle
// Only stall the plane if its being flown by a player.
if( GetDriver() && GetDriver()->IsAPlayerPed() )
{
if( fAngleOfAttack > PLANE_STALL_ANGLE && vecAirSpeed.z > PLANE_STALL_MIN_VERTICAL_SPEED )
{
vecLiftForce.z = Min( 0.0f, fWingForce, vecLiftForce.z );
}
}
#if __ASSERT
{
Vector3 vForce = vecLiftForce * fOverallMultiplier;
Assertf( vForce.Mag2() < square(150.0f * rage::Max(1.0f, GetMass())),
"ProcessFlightModel force over limit, force [%3.2f, %3.2f, %3.2f], wingForce %3.2f, lift %3.2f, attactMult %3.2f, angle %3.2f, spdSqr %3.2f, overall %3.2f",
vForce.x, vForce.y, vForce.z, fWingForce, fFormLift, fAttackMult, fAngleOfAttack, fVirtualForwardSpdSqr, fOverallMultiplier );
}
#endif
ApplyInternalForceCg( vecLiftForce * fOverallMultiplier );
#if __WIN32PC
m_fWingForce = fWingForce;
#endif // __WIN32PC
}
float CPlane::GetGroundEffectOnDrag()
{
float dragReduction = 0.0f;
if( ms_ReduceDragWithGroundEffect &&
IsInAir() )
{
float x = GetVehiclePosition().GetXf();
float y = GetVehiclePosition().GetYf();
float fMinHeightAtPos = CGameWorldHeightMap::GetMinHeightFromWorldHeightMap(x, y);
float fMaxHeightAtPos = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(x, y);
float fAvgHeightAtPos = 0.5f * ( fMinHeightAtPos + fMaxHeightAtPos );
TUNE_GROUP_FLOAT( VEHICLE_PLANE_GROUND_EFFECT, HEIGHT_ABOVE_GROUND_SCALE, 1.25f, 0.0f, 10.0f, 0.1f );
float boundSize = GetBoundRadius() * 2.0f;
float distanceFromGround = GetVehiclePosition().GetZf() - ( boundSize * HEIGHT_ABOVE_GROUND_SCALE ) - fAvgHeightAtPos;
if( distanceFromGround < 0.0f )
{
dragReduction = Max( 0.0f, 1.0f + ( distanceFromGround / boundSize ) );
}
}
return dragReduction;
}
void CPlane::ProcessFlightModel(float fTimeStep, float fOverallMultiplier)
{
// better make sure we've got some handling data, otherwise we're screwed for flying
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if(pFlyingHandling == NULL)
{
return;
}
if(fTimeStep <= 0.0f)
{
return;
}
CControl *pControl = NULL;
if(GetStatus()==STATUS_PLAYER && GetDriver() && GetDriver()->IsPlayer())
{
pControl = GetDriver()->GetControlFromPlayer();
}
bool bControlInactive = pControl && CTaskVehiclePlayerDrive::IsThePlayerControlInactive(pControl);
bool bPlayerDriver = GetDriver() && GetDriver()->IsPlayer();
Vector3 vecAirSpeed(0.0f, 0.0f, 0.0f);
Vector3 vecWindSpeed(0.0f, 0.0f, 0.0f);
if (!PopTypeIsMission())
{
WIND.GetLocalVelocity(GetTransform().GetPosition(), RC_VEC3V(vecWindSpeed), false, false);
vecAirSpeed -= ComputeWindMult(0.0f)*vecWindSpeed + m_vTurbulenceAirflow;
}
vecAirSpeed += GetVelocity();
float fMass = GetMass();
#if __BANK
if(GetStatus()==STATUS_PLAYER && CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
{
Vector3 vecWindSocPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) + VEC3V_TO_VECTOR3(GetTransform().GetC()) * GetBaseModelInfo()->GetBoundingBoxMax().z;
grcDebugDraw::Line(vecWindSocPos, vecWindSocPos + 10.0f*vecWindSpeed, Color32(0,0,255));
grcDebugDraw::Line(vecWindSocPos, vecWindSocPos - 10.0f*vecAirSpeed, Color32(0,255,0));
}
#endif
// Get local copies so we can mess about with them
float fPitchControl = m_fPitchControl;
float fYawControl = m_fYawControl;
float fRollControl = m_fRollControl;
float fThrottleControl = Clamp(m_fThrottleControl, -dfPlaneThrottleControlLimit, dfPlaneThrottleControlLimit);
float fVirtualSpeedControl = m_fVirtualSpeedControl;
float fBrakeControl = Clamp(m_fAirBrakeControl, -dfPlaneBrakeControlLimit, dfPlaneBrakeControlLimit);
Vector3 vecTransformB = VEC3V_TO_VECTOR3(GetTransform().GetB());
if(fThrottleControl==FLY_INPUT_NULL)
{
fThrottleControl = 0.0f;
if(pControl)
{
fThrottleControl = pControl->GetVehicleFlyThrottleUp().GetNorm01() - pControl->GetVehicleFlyThrottleDown().GetNorm01();
}
}
if( fRollControl == FLY_INPUT_NULL )
{
fRollControl = fYawControl;
}
// reset this
m_fVirtualSpeedControl = 1.0f;
// MISC STUFF
Vector3 vecRudderArm = vecTransformB;
Vector3 vecTailOffset = vecRudderArm;
float rudderOffset = GetBaseModelInfo()->GetBoundingBoxMin().y;
if( GetModelIndex() == MI_PLANE_VOLATOL )
{
static dev_float sfMaxRudderOffset = 18.0f;
rudderOffset = Clamp( rudderOffset, -sfMaxRudderOffset, sfMaxRudderOffset );
}
vecTailOffset *= rudderOffset;
float fForwardSpd = vecAirSpeed.Dot(vecRudderArm);
if ( bPlayerDriver )
{
fForwardSpd = Clamp(fForwardSpd, -pHandling->m_fEstimatedMaxFlatVel, pHandling->m_fEstimatedMaxFlatVel); // Clamp the forward speed to prevent unexpected controlling force
}
// virtual speed is our speed scaled by our Virtual speed control
// in general the Virtual speed should be a multiplier to scale the speed
// to some value large enough that the plane would have enough lift to fly
// I use the virtual speed for all torques and for the lift force calculation
// the basic idea is the plane moves at correct rate but handles as if moving at
// the min speed to stay in the air
float fThrustHealthMult = m_aircraftDamage.GetThrustMult(this);
float fVirtualFwdSpd = fForwardSpd * fVirtualSpeedControl;
fVirtualFwdSpd = Clamp(fVirtualFwdSpd, -pHandling->m_fEstimatedMaxFlatVel, pHandling->m_fEstimatedMaxFlatVel); // Clamp the forward speed to prevent unexpected controlling force
float fTopSpeed = pHandling->m_fEstimatedMaxFlatVel;
float fCurrentSpeed = vecAirSpeed.Mag();
CalculateYawAndPitchcontrol( pControl, fYawControl, fPitchControl );
// Apply the driver influence to controls
if(bPlayerDriver && IsInAir(false) && !bControlInactive && !m_nVehicleFlags.bUsedForPilotSchool BANK_ONLY(&& !CVehicleFactory::ms_bflyingAce))
{
ModifyControlsBasedOnFlyingStats(GetDriver(), pFlyingHandling, fYawControl, fPitchControl, fRollControl, fThrottleControl, fTimeStep);
}
// For player, the braking is done by applying negative throttle. No brake control needed for player driven plane.
if(bPlayerDriver && !bControlInactive)
{
fBrakeControl = 0.0f;
}
// DAMAGE EFFECT
float fPitchFromDamageEffect = 0.0f;
float fYawFromDamageEffect = 0.0f;
float fRollFromDamageEffect = 0.0f;
CalculateDamageEffects( fPitchFromDamageEffect, fYawFromDamageEffect, fRollFromDamageEffect, fPitchControl, fYawControl, fRollControl );
CalculateThrust( fThrottleControl, fYawControl, fForwardSpd, fThrustHealthMult, fOverallMultiplier, bPlayerDriver );
static float s_fAirBrakeMult = 0.01f;
Vector3 vAirBrake( -vecTransformB * m_fGravityForWheelIntegrator * fBrakeControl * fMass * fThrustHealthMult * fOverallMultiplier * Max(fForwardSpd, 0.0f) * s_fAirBrakeMult);
ApplyInternalForceCg(vAirBrake);
TUNE_GROUP_BOOL( STUNT_PLANE, REDUCE_ROLL_WITH_PITCH_INPUT, true );
TUNE_GROUP_BOOL( STUNT_PLANE, USE_STUNT_PLANE_ROLL, false );
TUNE_GROUP_FLOAT( STUNT_PLANE, REDUCE_ROLL_WITH_PITCH_INPUT_AMOUNT, 0.3f, 0.0f, 2.0f, 0.1f );
if( REDUCE_ROLL_WITH_PITCH_INPUT &&
pFlyingHandling->m_fExtraLiftWithRoll )
{
fRollControl *= Max( 0.0f, Abs( fRollControl ) - Abs( fPitchControl * REDUCE_ROLL_WITH_PITCH_INPUT_AMOUNT ) );
}
CalculateSideSlip( vecAirSpeed, fOverallMultiplier );
Vector3 vecTorque = CalculateYaw( vecTailOffset, vecWindSpeed, fOverallMultiplier, fYawControl, fVirtualFwdSpd, fYawFromDamageEffect );
if( !USE_STUNT_PLANE_ROLL )
{
vecTorque += CalculateRoll( fOverallMultiplier, fRollControl, fRollFromDamageEffect, fVirtualFwdSpd );
}
else
{
vecTorque += CalculateStuntRoll( fOverallMultiplier, fRollControl, fTimeStep );
}
bool bHadCollision = GetFrameCollisionHistory() && GetFrameCollisionHistory()->GetCollisionImpulseMagSum() > 0.0f;
bool bClampAngleOfAttack = true;
if(!NetworkInterface::IsGameInProgress() && GetDriver() && GetDriver()->IsLocalPlayer())
{
bClampAngleOfAttack = bHadCollision || fCurrentSpeed <= fTopSpeed;
}
if( !bHadCollision )
{
vecTorque += ApplyRollStabilisation( fOverallMultiplier );
}
vecTorque += CalculatePitch( vecWindSpeed, vecTailOffset, vecAirSpeed, fOverallMultiplier, fPitchControl, fPitchFromDamageEffect, fVirtualFwdSpd, bClampAngleOfAttack );
// static dev_bool restrictMaxTorqueDelta = true;
//
// if( restrictMaxTorqueDelta &&
// GetModelIndex() == MI_PLANE_VOLATOL )
// {
// static Vector3 maxTorque( 10.0, 10.0f, 10.0f );
// Vector3 maxTorqueDelta = GetAngInertia() * maxTorque;
//
// for( int i = 0; i < 3; i++ )
// {
// //vecTorque[ i ] = Clamp( vecTorque[ i ], m_prevTorque[ i ] - maxTorqueDelta[ i ], m_prevTorque[ i ] + maxTorqueDelta[ i ] );
// vecTorque[ i ] = Clamp( vecTorque[ i ], -maxTorqueDelta[ i ], maxTorqueDelta[ i ] );
// }
//
//// m_prevTorque = vecTorque;
// }
ApplyInternalTorque(vecTorque);
CalculateLift( vecAirSpeed, fOverallMultiplier, fPitchControl, fVirtualFwdSpd, bHadCollision );
CalculateLandingGearDrag( fForwardSpd, fOverallMultiplier );
}
void CPlane::DriveDesiredVerticalFlightModeRatio( float driveToVerticalFlightMode )
{
//Work out the position of the nozzles for vertical flight mode use
m_fDesiredVerticalFlightRatio += driveToVerticalFlightMode * sf_VerticalFlightTransitionSpeed * fwTimer::GetTimeStep();
m_fDesiredVerticalFlightRatio = rage::Clamp(m_fDesiredVerticalFlightRatio, 0.0f, 1.0f);
}
void CPlane::SetDesiredVerticalFlightModeRatio(float verticalFlightModeRatio)
{
Assert( verticalFlightModeRatio >= 0.0f && verticalFlightModeRatio <= 1.0f);
#if !__NO_OUTPUT
if( NetworkInterface::IsGameInProgress() && !IsNetworkClone() && GetNetworkObject() &&
verticalFlightModeRatio != verticalFlightModeRatio)
{
Displayf("[CPlane::SetDesiredVerticalFlightModeRatio] new: %.2f old: %.2f", verticalFlightModeRatio, m_fDesiredVerticalFlightRatio );
sysStack::PrintStackTrace();
}
#endif // !__NO_OUTPUT
m_fDesiredVerticalFlightRatio = verticalFlightModeRatio;
}
void CPlane::SetVerticalFlightModeRatio(float verticalFlightModeRatio)
{
Assert( verticalFlightModeRatio >= 0.0f && verticalFlightModeRatio <= 1.0f);
m_fDesiredVerticalFlightRatio = verticalFlightModeRatio;
m_fCurrentVerticalFlightRatio = verticalFlightModeRatio;
}
bool CPlane::GetVerticalFlightModeAvaliable() const
{
if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_FLIGHT_MODE ) )
{
return true;
}
s32 nBoneIndex = GetBoneIndex((eHierarchyId)PLANE_NOZZLE_F);
if( nBoneIndex < 0 &&
( !MI_PLANE_TULA.IsValid() ||
GetModelIndex() != MI_PLANE_TULA ) )
{
return false;
}
return true;
}
const bool CPlane::IsInVerticalFlightMode() const
{
if (GetVerticalFlightModeAvaliable())
{
return (GetVerticalFlightModeRatio() >= 1.0f);
}
return false;
}
dev_float fDeepWaterResistanceV = 2.0f;
dev_float fDeepWaterResistanceV2 = 1.0f;
dev_float fDeepWaterResistanceDepth = -100.0f;
dev_float fShallowWaterResistanceV = 1.0f;
dev_float fShallowWaterResistanceV2 = 0.1f;
dev_float fShallowWaterResistanceDepth = -20.0f;
void CPlane::SetDampingForFlight(float fRatioOfVerticalFlightToUse)
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if(pFlyingHandling == NULL)
{
return;
}
bool bIsArticulated = false;
if(GetCollider())
{
bIsArticulated = GetCollider()->IsArticulated();
}
float fDampingMultiplier = 1.0f;
//Increase damping for certain characters
//static dev_float sfDampingPilotSchool = 1.0f;
if(GetDriver())
{
StatId stat = STAT_FLYING_ABILITY.GetStatId();
float fFlyingStatValue = rage::Clamp(static_cast<float>(StatsInterface::GetIntStat(stat)) / 100.0f, 0.0f, 1.0f);
float fMinPlaneDamping = CPlayerInfo::GetPlayerStatInfoForPed(*GetDriver()).m_MinPlaneDamping;
float fMaxPlaneDamping = CPlayerInfo::GetPlayerStatInfoForPed(*GetDriver()).m_MaxPlaneDamping;
// Swapped the max damping and min damping, as better skills should drive faster, which means less damping
fDampingMultiplier = ((1.0f - fFlyingStatValue) * fMaxPlaneDamping + fFlyingStatValue * fMinPlaneDamping)/100.0f;
}
float fLV = 0.0f;
Vector3 vecAV(0.0f, 0.0f, 0.0f);
Vector3 vecSpeedRes(0.0f, 0.0f, 0.0f);
float fRatioOfNormalFlightToUse = 1.0f;
// drop the top speed of the fast planes by 15% when close to the ground - B*655365
float fTopSpeed = pHandling->m_fEstimatedMaxFlatVel;
float fCurrentSpeed = GetVelocity().Mag();
float fSpeedRate = fCurrentSpeed / fTopSpeed;
float fCurrentHeight = GetTransform().GetPosition().GetZf();
if(fSpeedRate > (1.0f - ms_fTopSpeedDampRate) && fCurrentHeight < ms_fTopSpeedDampMaxHeight)
{
float fHeightMultiplier = (ms_fTopSpeedDampMaxHeight - fCurrentHeight) / (ms_fTopSpeedDampMaxHeight - ms_fTopSpeedDampMinHeight);
fHeightMultiplier = Clamp(fHeightMultiplier, 0.0f, 1.0f);
float fSpeedMultiplier = (fSpeedRate - (1.0f - ms_fTopSpeedDampRate)) / ms_fTopSpeedDampRate;
fSpeedMultiplier = Clamp(fSpeedMultiplier, 0.0f, 1.0f);
fLV += ms_fTopSpeedDampRate * fHeightMultiplier * fSpeedMultiplier;
}
//Lerp between damping settings when changing between vertical and normal flight mode
if(GetVerticalFlightModeAvaliable())
{
CFlyingHandlingData* pVerticalFlyingHandling = pHandling->GetVerticalFlyingHandlingData();
if(pVerticalFlyingHandling == NULL)
{
return;
}
fRatioOfNormalFlightToUse = 1.0f - fRatioOfVerticalFlightToUse;
float fLVNormal = bIsArticulated ? 0.5f*pFlyingHandling->m_fMoveRes : pFlyingHandling->m_fMoveRes;
float fLVVertical = bIsArticulated ? 0.5f*pVerticalFlyingHandling->m_fMoveRes : pVerticalFlyingHandling->m_fMoveRes;
fLV += ((fLVNormal*fRatioOfNormalFlightToUse) + (fLVVertical*fRatioOfVerticalFlightToUse)) * fDampingMultiplier;
if(MagSquared(pFlyingHandling->m_vecTurnRes).Getf() > 0.0f || MagSquared(pVerticalFlyingHandling->m_vecTurnRes).Getf() > 0.0f)
{
Vector3 vecAVNormal = bIsArticulated ? 0.5f*RCC_VECTOR3(pFlyingHandling->m_vecTurnRes) : RCC_VECTOR3(pFlyingHandling->m_vecTurnRes);
Vector3 vecAVVertical = bIsArticulated ? 0.5f*RCC_VECTOR3(pVerticalFlyingHandling->m_vecTurnRes) : RCC_VECTOR3(pVerticalFlyingHandling->m_vecTurnRes);
vecAV = ((vecAVNormal*fRatioOfNormalFlightToUse) + (vecAVVertical*fRatioOfVerticalFlightToUse)) * fDampingMultiplier;
}
if(MagSquared(pFlyingHandling->m_vecSpeedRes).Getf() > 0.0f || MagSquared(pVerticalFlyingHandling->m_vecSpeedRes).Getf() > 0.0f)
{
Vector3 vecSpeedResNormal = RCC_VECTOR3(pFlyingHandling->m_vecSpeedRes);
Vector3 vecSpeedResVertical = RCC_VECTOR3(pVerticalFlyingHandling->m_vecSpeedRes);
vecSpeedRes = ((vecSpeedResNormal*fRatioOfNormalFlightToUse) + (vecSpeedResVertical*fRatioOfVerticalFlightToUse)) * fDampingMultiplier;
}
}
else
{
fLV += (bIsArticulated ? 0.5f*pFlyingHandling->m_fMoveRes : pFlyingHandling->m_fMoveRes) * fDampingMultiplier;
if(MagSquared(pFlyingHandling->m_vecTurnRes).Getf() > 0.0f )
{
vecAV = (bIsArticulated ? 0.5f*RCC_VECTOR3(pFlyingHandling->m_vecTurnRes) : RCC_VECTOR3(pFlyingHandling->m_vecTurnRes)) * fDampingMultiplier;
}
if(MagSquared(pFlyingHandling->m_vecSpeedRes).Getf() > 0.0f)
{
vecSpeedRes = RCC_VECTOR3(pFlyingHandling->m_vecSpeedRes) * fDampingMultiplier;
}
}
//Damp roll if we aren't trying to roll
static dev_float sfRollNoInput = 0.01f;
if(rage::Abs(GetRollControl()) < sfRollNoInput)
{
static dev_float sfExtraRollDamp = 4.0f;
vecAV.y *= 1.0f + sfExtraRollDamp * fRatioOfNormalFlightToUse;
vecSpeedRes.y *= 1.0f + sfExtraRollDamp * fRatioOfNormalFlightToUse;
}
if(GetIsInWater())
{
if(GetVehiclePosition().GetZf() < fShallowWaterResistanceDepth)
{
fLV = Max(fLV, fShallowWaterResistanceV);
}
if(GetVehiclePosition().GetZf() < fDeepWaterResistanceDepth)
{
fLV = Max(fLV, fDeepWaterResistanceV);
}
}
Vector3 vLV = Vector3(fLV, fLV, fLV);
// reset the damping if plane is wrecked or lost all its wings, so it can fall like rock
if(!GetIsInWater() && (GetStatus() == STATUS_WRECKED || (m_aircraftDamage.HasSectionBrokenOff(this,CAircraftDamage::WING_L) && m_aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R))))
{
vLV.z = 0.0f;
vecAV = Vector3(CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF, CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF, CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF);
vecSpeedRes = Vector3(CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF, CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF, CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF);
}
phArchetypeDamp* pArch = (phArchetypeDamp*)GetVehicleFragInst()->GetArchetype();
// Now setup the damping
pArch->ActivateDamping(phArchetypeDamp::LINEAR_V, vLV);
CPlayerInfo *pPlayer = GetDriver() ? GetDriver()->GetPlayerInfo() : NULL;
float fLV2 = 0.0f;
if(GetIsInWater())
{
if(GetVehiclePosition().GetZf() < fShallowWaterResistanceDepth)
{
fLV2 = Max(fLV2, fShallowWaterResistanceV2);
}
if(GetVehiclePosition().GetZf() < fDeepWaterResistanceDepth)
{
fLV2 = Max(fLV2, fDeepWaterResistanceV2);
}
}
else
{
fLV2 = m_fDragCoeff;
if (pPlayer && pPlayer->m_fForceAirDragMult > 0.0f)
{
fLV2 = m_fDragCoeff * pPlayer->m_fForceAirDragMult;
}
}
Vector3 vecLinearV2Damping;
vecLinearV2Damping.Set(fLV2, fLV2, fLV2);
TUNE_GROUP_FLOAT(VEHICLE_DAMPING, PLANE_V2_DAMP_MULT, 1.0f, 0.0f, 5.0f, 0.01f);
if( GetDriver() &&
GetDriver()->IsAPlayerPed() )
{
float fSlipStreamEffect = GetSlipStreamEffect();
float fGroundEffect = GetGroundEffectOnDrag();
vecLinearV2Damping -= (vecLinearV2Damping * Min( 1.0f, fSlipStreamEffect + fGroundEffect ) );
}
pArch->ActivateDamping(phArchetypeDamp::LINEAR_V2, vecLinearV2Damping * PLANE_V2_DAMP_MULT);
if(vecAV.Mag2() > 0.0f)
{
pArch->ActivateDamping(phArchetypeDamp::ANGULAR_V, vecAV);
}
if(vecSpeedRes.Mag2() > 0.0f )
{
pArch->ActivateDamping(phArchetypeDamp::ANGULAR_V2, vecSpeedRes);
}
}
dev_float PLANE_VERTICAL_MINIMUM_CONTROL_SPEED = (1.0f);
dev_float PLANE_VERTICAL_RUDDER_MAX_ANGLE_OF_ATTACK = ( DtoR * 30.0f);
void CPlane::ProcessVerticalFlightModel(float fTimeStep, float fOverallMultiplier)
{
int numberOfContactWheels = GetNumContactWheels();
int minNumberOfContactWheels = (int)( (float)GetNumWheels() * 0.5f ) + 1;
const Vec3V velocity = VECTOR3_TO_VEC3V(GetVelocity());
if( numberOfContactWheels >= minNumberOfContactWheels && m_fThrottleControl == 0.0f &&
IsLessThanOrEqualAll( Abs( velocity ), Vec3VFromF32( PLANE_VERTICAL_MINIMUM_CONTROL_SPEED ) ) )
{
return;
}
if(!IsEngineOn())
{
return;
}
// better make sure we've got some handling data, otherwise we're screwed for flying
CFlyingHandlingData* pFlyingHandling = pHandling->GetVerticalFlyingHandlingData();
if(pFlyingHandling == NULL)
return;
if(fTimeStep <= 0.0f)
return;
CControl *pControl = NULL;
if(GetStatus()==STATUS_PLAYER && GetDriver() && GetDriver()->IsPlayer())
pControl = GetDriver()->GetControlFromPlayer();
Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 vecAirSpeed(0.0f, 0.0f, 0.0f);
Vector3 vecWindSpeed(0.0f, 0.0f, 0.0f);
if (!PopTypeIsMission())
{
WIND.GetLocalVelocity(RCC_VEC3V(vThisPosition), RC_VEC3V(vecWindSpeed), false, false);
vecAirSpeed -= ComputeWindMult(1.0f)*vecWindSpeed + m_vTurbulenceAirflow;
}
vecAirSpeed += GetVelocity();
float fMass = GetMass();
Vector3 vecAngInertia = GetAngInertia();
#if __BANK
if(GetStatus()==STATUS_PLAYER && CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
{
Vector3 vecWindSocPos = vThisPosition + VEC3V_TO_VECTOR3(GetTransform().GetC()) * GetBaseModelInfo()->GetBoundingBoxMax().z;
grcDebugDraw::Line(vecWindSocPos, vecWindSocPos + 10.0f*vecWindSpeed, Color32(0,0,255));
grcDebugDraw::Line(vecWindSocPos, vecWindSocPos - 10.0f*vecAirSpeed, Color32(0,255,0));
}
#endif
// Get local copies so we can mess about with them
float fPitchControl = GetPitchControl();
float fYawControl = GetYawControl();
float fRollControl = -GetRollControl();
float fThrottleControl = GetThrottleControl();
{
// CONTROLS
if(fPitchControl==FLY_INPUT_NULL)
{
fPitchControl = 0.0f;
if(pControl)
fPitchControl = pControl->GetVehicleFlyPitchUpDown().GetNorm(ioValue::ALWAYS_DEAD_ZONE);
}
if(fRollControl==FLY_INPUT_NULL)
{
fRollControl = 0.0f;
if(pControl)
fRollControl = pControl->GetVehicleFlyRollLeftRight().GetNorm(ioValue::ALWAYS_DEAD_ZONE);
}
if(fYawControl==FLY_INPUT_NULL)
{
fYawControl = 0.0f;
if(pControl)
{
// commented out as there are no mappings to these functions but I have left them here so they are easy to add again.
// if(pControl->GetVehicleLookRight().IsDown() && !pControl->GetVehicleLookLeft().IsDown())
// fYawControl = 1.0f;
// if(pControl->GetVehicleLookLeft().IsDown() && !pControl->GetVehicleLookRight().IsDown())
// fYawControl = -1.0f;
// 2nd stick controls option for chris
if(ABS(pControl->GetVehicleGunLeftRight().GetNorm()) > 0.008f)
fYawControl = pControl->GetVehicleGunLeftRight().GetNorm();
}
}
if(fThrottleControl==FLY_INPUT_NULL)
{
fThrottleControl = 0.0f;
if(pControl)
fThrottleControl = pControl->GetVehicleFlyThrottleUp().GetNorm01() - pControl->GetVehicleFlyThrottleDown().GetNorm01();
}
// Apply the driver influence to controls
if(GetDriver() && GetDriver()->IsPlayer() && !m_nVehicleFlags.bUsedForPilotSchool BANK_ONLY(&& !CVehicleFactory::ms_bflyingAce))
{
ModifyControlsBasedOnFlyingStats(GetDriver(), pFlyingHandling, fYawControl, fPitchControl, fRollControl, fThrottleControl, fTimeStep);
}
////////////////////////
// ADDED NOISE
if(IsInAir())
{
static dev_float sfRandomRollNoiseLower = -1.5f;//prefer the random noise moving the plane forward then back
static dev_float sfRandomRollNoiseUpper = 1.0f;
float fRandomNoiseIdleMult = sfPlaneRandomNoiseIdleMultForAI;
float fRandomNoiseThrottleMult = sfPlaneRandomNoiseThrottleMultForAI;
if(GetDriver() && GetDriver()->IsPlayer())
{
fRandomNoiseIdleMult = sfPlaneRandomNoiseIdleMultForPlayer;
fRandomNoiseThrottleMult = sfPlaneRandomNoiseThrottleMultForPlayer;
}
float fDamageRate = 1.0f - GetHealth()/GetMaxHealth();
fDamageRate = Clamp(fDamageRate, 0.0f, 1.0f);
float fRandomNoiseMult = fRandomNoiseIdleMult + fThrottleControl * fRandomNoiseThrottleMult + fDamageRate * pFlyingHandling->m_fBodyDamageControlEffectMult;
float fRandomRollNoise = fwRandom::GetRandomNumberInRange( sfRandomRollNoiseLower*fRandomNoiseMult, sfRandomRollNoiseUpper*fRandomNoiseMult);
ApplyInternalTorque(fRandomRollNoise*ZAXIS*vecAngInertia.x, VEC3V_TO_VECTOR3(GetTransform().GetB()));
float fRandomPitchNoise = fwRandom::GetRandomNumberInRange( sfRandomRollNoiseLower*fRandomNoiseMult, sfRandomRollNoiseUpper*fRandomNoiseMult);
ApplyInternalTorque(fRandomPitchNoise*ZAXIS*vecAngInertia.y, VEC3V_TO_VECTOR3(GetTransform().GetA()));
}
float fPitchFromDamageEffect = 0.0f;
float fYawFromDamageEffect = 0.0f;
float fRollFromDamageEffect = 0.0f;
static dev_float sfRollControlLostWingEffect = 1.5f;
static dev_float sfRollControlFlapDamageEffect = 15.0f;
static dev_float sfPitchControlAileronDamageEffect = 0.0f;
static dev_float sfYawControlRudderDamageEffect = 3.0f;
static dev_float sfControlBodyDamageEffectMulti = 0.1f;
static dev_float sfHoverThrottleWhenOnGroundMulti = 0.5f;
if(IsInAir())
{
// Wing Damage
//if we're in the air and we've lost a wing make the plane spin.
if(GetAircraftDamage().HasSectionBrokenOff(this, CAircraftDamage::WING_L))
{
fRollFromDamageEffect -= sfRollControlLostWingEffect;
}
else if(GetAircraftDamage().HasSectionBrokenOff(this, CAircraftDamage::WING_R))
{
fRollFromDamageEffect += sfRollControlLostWingEffect;
}
float fBodyHealthMult = 0.0f;
if(GetHealth() < sfBuffetingBodyHealthThreshold)
{
fBodyHealthMult = 1.0f - GetHealth() / GetMaxHealth();
fBodyHealthMult = Clamp(fBodyHealthMult, 0.0f, 1.0f);
fBodyHealthMult *= sfControlBodyDamageEffectMulti;
}
// Flap (pitch) Damage causes rolling noise
float fHealthMult = Max(1.0f - m_aircraftDamage.GetPitchMult(this), fBodyHealthMult);
fRollFromDamageEffect += fwRandom::GetRandomNumberInRange(-1.0f, 1.0f) * sfRollControlFlapDamageEffect * fHealthMult * (0.5f * fPitchControl + 0.5f);
// Aileron (roll) Damage causes pitching noise
fHealthMult = Max(1.0f - m_aircraftDamage.GetRollMult(this), fBodyHealthMult);
fPitchFromDamageEffect += fwRandom::GetRandomNumberInRange(-1.0f, 1.0f) * sfPitchControlAileronDamageEffect * fHealthMult * (0.5f * fRollControl + 0.5f);
// Rudder (yaw) Damage causes yaw noise
fHealthMult = Max(1.0f - m_aircraftDamage.GetYawMult(this), fBodyHealthMult);
fYawFromDamageEffect += fwRandom::GetRandomNumberInRange(-1.0f, 1.0f) * sfYawControlRudderDamageEffect * fHealthMult * (0.5f * fYawControl + 0.5f);
}
///////////////////////
// LIFT&THRUST
Vector3 vecLift(VEC3V_TO_VECTOR3(GetTransform().GetC()));
float fSpeedThroRotor = vecAirSpeed.Dot(vecLift);
if(fSpeedThroRotor < 0.0f)
fSpeedThroRotor *= 2.0f;
// copy input argument to local var
float fThrottleMult = fThrottleControl;
if(fThrottleMult > 1.0f)
fThrottleMult = 1.0f + (fThrottleMult - 1.0f)*pFlyingHandling->m_fThrust;
// limit height that this plane can fly
vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); // Just in case the position has changed since the start of the function
float fHealthMult = m_aircraftDamage.GetThrustMult(this);
//Remove thrust based on how many propellers are alive
float fPropMult = 1.0f;
for(int i = 0; i< m_iNumPropellers; i++)
{
if( m_fPropellerHealth[i] <= 0.0f )
{
fPropMult -= (1.0f/m_iNumPropellers);
}
}
fThrottleMult *= fHealthMult * fPropMult;
fThrottleMult -= (pFlyingHandling->m_fThrustFallOff*fSpeedThroRotor);
fThrottleMult = Clamp( fThrottleMult, -2.0f, 2.0f );
//make the plane auto hover
float hoverThrottle = 1.0f - fThrottleMult;
hoverThrottle = Max( 0.0f, hoverThrottle );
float propellerSpeed = 0.0f;
for(int nPropIndex = 0 ; nPropIndex < PLANE_NUM_PROPELLERS; nPropIndex++ )
{
eHierarchyId nId = (eHierarchyId)(PLANE_PROP_1 + nPropIndex);
if( GetBoneIndex(nId) > -1 )
{
// Found a valid propeller
propellerSpeed += m_propellers[ nPropIndex ].GetSpeed();
}
}
if( GetNumContactWheels() )
{
hoverThrottle *= sfHoverThrottleWhenOnGroundMulti;
}
if( m_nFlags.bPossiblyTouchesWater )
{
hoverThrottle = 0.0f;
}
hoverThrottle = Clamp(hoverThrottle, 0.0f, 1.0f);
hoverThrottle *= fHealthMult;
if( m_iNumPropellers > 0 &&
propellerSpeed == 0.0f )
{
hoverThrottle = 0.0f;
}
Vector3 hoverForce = vecLift*m_fGravityForWheelIntegrator*hoverThrottle*fHealthMult*fMass*fOverallMultiplier;
ApplyInternalForceCg( hoverForce );
if( fThrottleMult > 0.0f && HeightAboveCeiling( vThisPosition.z ) > 0.0f )
{
fThrottleMult *= 10.0f / ( HeightAboveCeiling( vThisPosition.z ) + 10.0f );
}
//Normal lift force
if( m_iNumPropellers > 0 &&
propellerSpeed == 0.0f )
{
fThrottleMult = 0.0f;
}
Vector3 liftForce = vecLift*m_fGravityForWheelIntegrator*pFlyingHandling->m_fThrust*fThrottleMult*fMass*fOverallMultiplier;
ApplyInternalForceCg( liftForce );
if( fPropMult != 1.0f )
{
Vector3 averageActivePropOffset( 0.0f, 0.0f, 0.0f );
int numActiveProps = 0;
for(int i = 0; i< m_iNumPropellers; i++)
{
if( m_fPropellerHealth[i] > 0.0f )
{
averageActivePropOffset += GetObjectMtx( m_propellers[ i ].GetBoneIndex() ).d;
numActiveProps++;
}
}
if( numActiveProps > 0 )
{
averageActivePropOffset /= (float)numActiveProps;
ApplyInternalTorque( liftForce, averageActivePropOffset );
}
}
// Additional force when pitching or rolling
vecLift = -fPitchControl * VEC3V_TO_VECTOR3(GetTransform().GetB());
vecLift.z = 0.0f;
ApplyInternalForceCg(vecLift*m_fGravityForWheelIntegrator*fThrottleMult*fMass*fOverallMultiplier);
vecLift = -fRollControl * VEC3V_TO_VECTOR3(GetTransform().GetA());
vecLift.z = 0.0f;
ApplyInternalForceCg(vecLift*m_fGravityForWheelIntegrator*fThrottleMult*fMass*fOverallMultiplier);
// only apply stabilising force when plane is right way up and not losing any wings
float fPitchFromVelocityDamping = 0.0f;
float fRollFromVelocityDamping = 0.0f;
float fForceOffset = 0.0f;
if(GetTransform().GetC().GetZf() > 0.0f && !GetAircraftDamage().HasSectionBrokenOff(this, CAircraftDamage::WING_L) && !GetAircraftDamage().HasSectionBrokenOff(this, CAircraftDamage::WING_R))
{
Vector3 vecTempUp(0.0f,0.0f,1.0f);
vecTempUp += ComputeWindMult(1.0f)*vecWindSpeed + m_vTurbulenceAirflow;
vecTempUp.Normalize();
Vector3 vecRight(VEC3V_TO_VECTOR3(GetTransform().GetA()));
Vector3 vecUp(VEC3V_TO_VECTOR3(GetTransform().GetC()));
fForceOffset = -Clamp(vecRight.Dot(vecTempUp), -pFlyingHandling->m_fFormLiftMult, pFlyingHandling->m_fFormLiftMult);
ApplyInternalTorque(pFlyingHandling->m_fAttackLiftMult*fForceOffset*vecAngInertia.y*vecUp*fOverallMultiplier, vecRight);
vecUp = VEC3V_TO_VECTOR3(GetTransform().GetC()); // not sure if ApplyInternalTorque could have changed GetC()
Vector3 vecForward(VEC3V_TO_VECTOR3(GetTransform().GetB()));
fForceOffset = -Clamp(vecForward.Dot(vecTempUp), -pFlyingHandling->m_fFormLiftMult, pFlyingHandling->m_fFormLiftMult);
ApplyInternalTorque(pFlyingHandling->m_fAttackLiftMult*fForceOffset*vecAngInertia.x*vecUp*fOverallMultiplier, vecForward);
// Add some tilt to help damping velocity when no control is given
static float sfVelocityDampingPitchMult = 1.0f;
static float sfVelocityDampingRollMult = 1.0f;
static float sfVelocityDampingTiltDampingSpeed = 10.0f;
if(fPitchControl == 0.0f && fRollControl == 0.0f)
{
float fSideSpeed = DotProduct(vecAirSpeed, VEC3V_TO_VECTOR3(GetTransform().GetA()));
float fFwdSpeed = DotProduct(vecAirSpeed, VEC3V_TO_VECTOR3(GetTransform().GetB()));
fPitchFromVelocityDamping = Sign(fFwdSpeed) * sfVelocityDampingPitchMult * Min(Abs(fFwdSpeed) / sfVelocityDampingTiltDampingSpeed, 1.0f);
fRollFromVelocityDamping = Sign(fSideSpeed) * sfVelocityDampingRollMult * Min(Abs(fSideSpeed) / sfVelocityDampingTiltDampingSpeed, 1.0f);
}
}
Vector3 vecUp(VEC3V_TO_VECTOR3(GetTransform().GetC()));
Vector3 vecForward(VEC3V_TO_VECTOR3(GetTransform().GetB()));
ApplyInternalTorque(vecUp*(fPitchControl + fPitchFromDamageEffect + fPitchFromVelocityDamping)*pFlyingHandling->m_fPitchMult*vecAngInertia.x*fOverallMultiplier, vecForward);
vecUp = VEC3V_TO_VECTOR3(GetTransform().GetC()); // not sure if ApplyInternalTorque could have changed GetC()
Vector3 vecRight(VEC3V_TO_VECTOR3(GetTransform().GetA()));
ApplyInternalTorque(vecUp*(fRollControl + fRollFromDamageEffect + fRollFromVelocityDamping)*pFlyingHandling->m_fRollMult*vecAngInertia.y*fOverallMultiplier, vecRight);
{
Vector3 vecTailAirSpeed(VEC3_ZERO);
// get tail position and add contribution from rotational velocity
{
vecTailAirSpeed.z = GetBaseModelInfo()->GetBoundingBoxMin().z;
vecTailAirSpeed = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(VECTOR3_TO_VEC3V(vecTailAirSpeed)));
vecTailAirSpeed.CrossNegate(GetAngVelocity());
}
vecTailAirSpeed.Add(vecAirSpeed);
float fSideSpeed = DotProduct(vecTailAirSpeed, VEC3V_TO_VECTOR3(GetTransform().GetA()));
float fFwdSpeed = DotProduct(vecTailAirSpeed, VEC3V_TO_VECTOR3(GetTransform().GetB()));
float fAirSpeedSqr = vecAirSpeed.Mag2();
float fSideSlipAngle = -rage::Atan2f(fSideSpeed, fFwdSpeed);
fSideSlipAngle = rage::Clamp(fSideSlipAngle, -PLANE_VERTICAL_RUDDER_MAX_ANGLE_OF_ATTACK, PLANE_VERTICAL_RUDDER_MAX_ANGLE_OF_ATTACK);
// control force from steering and stabilising force from rudder
Vector3 vecRudderForce = VEC3V_TO_VECTOR3(GetTransform().GetA());
vecRudderForce *= (pFlyingHandling->m_fYawMult*(fYawControl+fYawFromDamageEffect) + pFlyingHandling->m_fYawStabilise*fSideSlipAngle*fAirSpeedSqr)*vecAngInertia.z;
ApplyInternalTorque(vecRudderForce*fOverallMultiplier, -VEC3V_TO_VECTOR3(GetTransform().GetB()));
}
}
}
void CPlane::ResetVtolRollVariables()
{
m_bVtolRollVariablesInitialised = false;
m_VtolRollIntegral = 0;
m_VtolRollPreviousError = 0;
}
bool CPlane::GetDestroyedByPed( )
{
if (m_aircraftDamage.GetDestroyedByPed())
return true;
static const u32 LAST_DAMAGE_TRESHOLD = 3*1000;
const u32 timeSinceLastDamage = fwTimer::GetTimeInMilliseconds() - GetWeaponDamagedTime();
CEntity* lastdamager = GetWeaponDamageEntity();
if (lastdamager && timeSinceLastDamage <= LAST_DAMAGE_TRESHOLD)
{
if (lastdamager->GetIsTypePed())
return true;
if (lastdamager->GetIsTypeVehicle() && static_cast<CVehicle*>(lastdamager)->GetDriver())
return true;
}
return false;
}
void CPlane::ApplyDeformationToBones(const void* basePtr)
{
CAutomobile::ApplyDeformationToBones(basePtr);
// Apply deformation to after burner
CVehicleModelInfo* pModelInfo = GetVehicleModelInfo();
for (int i = 0; i < PLANE_NUM_AFTERBURNERS; ++i)
{
s32 afterburnerBoneId = pModelInfo->GetBoneIndex(PLANE_AFTERBURNER + i);
if (afterburnerBoneId != -1)
{
Matrix34 &mtxLocal = GetLocalMtxNonConst(afterburnerBoneId);
const Vec3V basePos = GetSkeletonData().GetBoneData(afterburnerBoneId)->GetDefaultTranslation();
const Vec3V damage = GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(basePtr, basePos);
mtxLocal.d = VEC3V_TO_VECTOR3(basePos + damage);
}
}
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellers[i].ApplyDeformation(this,basePtr);
}
m_landingGear.ApplyDeformation(this, basePtr);
}
void CPlane::Fix(bool resetFrag, bool allowNetwork)
{
CAutomobile::Fix(resetFrag, allowNetwork);
m_landingGear.Fix(this);
m_aircraftDamage.Fix();
m_landingGearDamage.Fix();
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellers[i].Fix();
m_fPropellerHealth[i] = VEH_DAMAGE_HEALTH_STD;
}
if(GetVerticalFlightModeAvaliable())
{
SetDesiredVerticalFlightModeRatio( 1.0f );
}
ResetEngineTurnedOffByPlayer();
}
bool CPlane::GetWheelDeformation(const CWheel *pWheel, Vector3 &vDeformation) const
{
return m_landingGear.GetWheelDeformation(this, pWheel, vDeformation);
}
bool CPlane::IsEntryPointUseable(s32 iEntryPointIndex) const
{
if (!IsEntryIndexValid(iEntryPointIndex))
{
return false;
}
const CVehicleEntryPointAnimInfo* pAnimInfo = GetEntryAnimInfo(iEntryPointIndex);
const CModelSeatInfo* pModelSeatinfo = GetVehicleModelInfo()->GetModelSeatInfo();
if (pModelSeatinfo && pAnimInfo && pAnimInfo->GetUseNewPlaneSystem())
{
const CAircraftDamage& aircraftDamage = GetAircraftDamage();
const CVehicleEntryPointInfo* pEntryPointInfo = pModelSeatinfo->GetLayoutInfo()->GetEntryPointInfo(iEntryPointIndex);
taskFatalAssertf(pEntryPointInfo, "CPlane::IsEntryPointUseable() : Plane doesn't have valid entry point info");
if (pEntryPointInfo->GetVehicleSide() == CVehicleEntryPointInfo::SIDE_LEFT)
{
if (aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_L))
{
return false;
}
}
else if (pEntryPointInfo->GetVehicleSide() == CVehicleEntryPointInfo::SIDE_RIGHT)
{
if (aircraftDamage.HasSectionBrokenOff(this, CAircraftDamage::WING_R))
{
return false;
}
}
}
return true;
}
bool CPlane::IsPlayerDrivingOnGround() const
{
// Check for local player driver
if( GetDriver() && GetDriver()->IsLocalPlayer() )
{
// Check for valid driving on the ground status
const float fNegligibleVelSquared = 0.005f;
if( IsEngineOn() && !GetIsAnyFixedFlagSet() && !GetIsInWater() && HasContactWheels() && GetVelocity().Mag2() > fNegligibleVelSquared )
{
return true;
}
}
return false;
}
bool CPlane::GetIsLandingGearintact() const
{
for(int i = 0; i < NUM_LANDING_GEARS; i++)
{
if(GetLandingGearDamage().HasSectionBrokenOff(this, i))
{
return false;
}
}
return true;
}
bool CPlane::AreControlPanelsIntact(bool bCheckForZeroHealth) const
{
const CAircraftDamage& aircraftDamage = GetAircraftDamage();
for(int iSection = CAircraftDamage::ENGINE_L; iSection < CAircraftDamage::NUM_DAMAGE_SECTIONS; iSection++)
{
if(aircraftDamage.HasSectionBrokenOff(this, iSection) || ( bCheckForZeroHealth && aircraftDamage.GetSectionHealth(iSection) <= 0.0f))
{
return false;
}
}
return true;
}
dev_float sfDamageStrongMult = 0.4f;
dev_float sfCrashingMult = 10.0f;
dev_float sfDamageNoDriverMult = 3.0f;
dev_float sfRotorDamagePerSec = 100.0f; // rotor breaks out in 10s
dev_float sfRotorDamageFromImpactMag = 15.0f;
dev_float sfRotorEngineDamageMult = 0.1f;
dev_float sfUpsideDownRotorDamageMult = 5.0f;
// Use collision information to damage rotors
void CPlane::ProcessCollision(phInst const * myInst, CEntity* pHitEnt, phInst const * hitInst, const Vector3& vMyHitPos, const Vector3& vOtherHitPos,
float fImpulseMag, const Vector3& vMyNormal, int iMyComponent, int iOtherComponent,
phMaterialMgr::Id iOtherMaterial, bool bIsPositiveDepth, bool bIsNewContact)
{
fragInstGta* pFragInst = GetVehicleFragInst();
vehicleAssert(pFragInst);
float fDamageMult = 1.0f;
if(m_nPhysicalFlags.bNotDamagedByCollisions)
{
fDamageMult = 0.0f;
}
else if(m_nVehicleFlags.bTakeLessDamage)
{
fDamageMult = sfDamageStrongMult;
}
else if(!GetDriver())
{
fDamageMult = sfDamageNoDriverMult;
}
fDamageMult *= pHandling->m_fEngineDamageMult;
float armourMultiplier = GetVariationInstance().GetArmourDamageMultiplier();
fDamageMult *= armourMultiplier;
// Increase the damage done to the engine when upside down
if(IsUpsideDown())
{
fDamageMult *= sfUpsideDownRotorDamageMult;
}
bool bDamageDone = false;
bool bNetworkClone = IsNetworkClone();
bool bToggleFlightModeOnContact = GetVerticalFlightModeAvaliable() && !IsInVerticalFlightMode() && HasContactWheels();
for(int i = 0; i < m_iNumPropellers; i++)
{
// check for main rotor impacts
int nRotorChild = m_propellerCollision[i].GetFragChild();
if(GetIsVisible() && iMyComponent==nRotorChild && !pFragInst->GetChildBroken(nRotorChild))
{
if( bToggleFlightModeOnContact &&
( !pHitEnt ||
pHitEnt->GetIsTypeBuilding() ) )
{
SetDesiredVerticalFlightModeRatio( 1.0f );
bToggleFlightModeOnContact = false;
}
if(m_bBreakOffPropellerOnContact && pHitEnt && pHitEnt->GetIsTypeBuilding())
{
BreakOffRotor(i);
}
if(m_propellers[i].GetSpeed() > 0.0f)
{
if (pHitEnt)// && CPhysics::GetIsLastTimeSlice(CPhysics::GetCurrentTimeSlice()))// && (fwTimer::GetSystemFrameCount()%4)==0)
{
// do collision effects
Vector3 collPos = vMyHitPos;
Vector3 collNormal = vMyNormal;
//Vector3 collNormalNeg = -vMyNormal;
Vector3 collDir(0.0f, 0.0f, 0.0f);
Vector3 collPtToVehCentre = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) - collPos;
collPtToVehCentre.Normalize();
Vector3 collVel = CrossProduct(collPtToVehCentre, VEC3V_TO_VECTOR3(GetTransform().GetC()));
collVel.Normalize();
float scrapeMag = 100000.0f;
float accumImpulse = 100000.0f;
g_vfxMaterial.DoMtlScrapeFx(this, 0, pHitEnt, 0, RCC_VEC3V(collPos), RCC_VEC3V(collNormal), RCC_VEC3V(collVel), PGTAMATERIALMGR->g_idCarMetal, iOtherMaterial, RCC_VEC3V(collDir), scrapeMag, accumImpulse, VFXMATERIAL_LOD_RANGE_SCALE_HELI, 1.0f, 1.0);
g_vfxMaterial.DoMtlScrapeFx(pHitEnt, 0, this, 0, RCC_VEC3V(collPos), RCC_VEC3V(collNormal), RCC_VEC3V(collVel), iOtherMaterial, PGTAMATERIALMGR->g_idCarMetal, RCC_VEC3V(collDir), scrapeMag, accumImpulse, VFXMATERIAL_LOD_RANGE_SCALE_HELI, 1.0f, 1.0);
if (pHitEnt->GetIsTypePed())
{
g_vfxBlood.UpdatePtFxBloodMist(static_cast<CPed*>(pHitEnt));
}
pHitEnt->ProcessFxEntityCollision(collPos, collNormal, 0, accumImpulse);
}
// rotor take damage
if(m_nVehicleFlags.bCanBeVisiblyDamaged && !bNetworkClone)
{
float fDamage = sfRotorDamagePerSec * fwTimer::GetTimeStep();
fDamage += fImpulseMag * GetInvMass() * sfRotorDamageFromImpactMag;
fDamage *= fDamageMult;
// !!! GTA V HACK !!!
if(GetModelId() == MI_PLANE_VELUM || GetModelId() == MI_PLANE_VELUM2)
{
fDamage *= 0.01f;
}
m_fPropellerHealth[i] -= fDamage;
bDamageDone = true;
if(m_fPropellerHealth[i] <= 0.0f)
{
BreakOffRotor(i);
}
const float damage = fDamage * sfRotorEngineDamageMult;
m_Transmission.ApplyEngineDamage(this, this, DAMAGE_TYPE_COLLISION, damage);
m_bIsPropellerDamagedThisFrame = true;
}
}
}
}
// make sure overall health is less than full if any plane components are damaged
if(bDamageDone && GetHealth() >= CREATED_VEHICLE_HEALTH)
{
if (!IsNetworkClone())
{
ChangeHealth(-1.0f);
}
}
return CAutomobile::ProcessCollision(myInst, pHitEnt, hitInst, vMyHitPos,vOtherHitPos,fImpulseMag,vMyNormal,iMyComponent,iOtherComponent,
iOtherMaterial, bIsPositiveDepth, bIsNewContact);
}
void CPlane::BreakOffRotor(int nRotor)
{
int iChild = m_propellerCollision[nRotor].GetFragChild();
if (GetVehicleFragInst() && iChild > -1 )
{
m_propellers[nRotor].SetSpeed(0.0f);
m_fPropellerHealth[nRotor] = 0.0f;
if( !GetVehicleFragInst()->GetChildBroken(iChild) )
{
// rotor destruction effect
g_vfxVehicle.TriggerPtFxPropellerDestroy(this, m_propellers[nRotor].GetBoneIndex());
//GetVehicleFragInst()->BreakOffAbove(iChild);
GetVehicleFragInst()->DeleteAbove(iChild);
if(m_VehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_PLANE)
{
static_cast<audPlaneAudioEntity*>(m_VehicleAudioEntity)->TriggerPropBreak();
}
}
}
}
const bool CPlane::IsRotorBroken(int nRotor)
{
int iChild = m_propellerCollision[nRotor].GetFragChild();
if (GetVehicleFragInst() && iChild > -1)
return GetVehicleFragInst()->GetChildBroken(iChild);
return false;
}
dev_float dfWaterImpactDamageMult = 50.0f;
dev_float dfWaterImpactDamageMultSeaPlane = 25.0f;
dev_u32 snRumbleDuration = 100;
dev_float sfRumbleIntensityScale = 0.3f;
extern float sfVehAircraftDamImpactThreshold;
dev_float sfSeaPlaneWaterDamImpactThreshold = 15.0f;
dev_float sfSeabreazeWaterDamImpactThreshold = 170.0f;
void CPlane::NotifyWaterImpact(const Vector3& vecForce, const Vector3& vecTorque, int nComponent, float fTimeStep)
{
if (IsNetworkClone())
return;
const Matrix34& matPlane = RCC_MATRIX34(GetMatrixRef());
Vector3 vecForceLocal;
matPlane.UnTransform3x3(vecForce, vecForceLocal);
Vector3 vecTorqueLocal;
matPlane.UnTransform3x3(vecTorque, vecTorqueLocal);
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
Vector3 vComponentCenterLocal = VEC3V_TO_VECTOR3(pBoundComp->GetCurrentMatrix(nComponent).GetCol3());
Vector3 vComponentCenterGlobal;
matPlane.Transform(vComponentCenterLocal, vComponentCenterGlobal);
Vector3 vForceLocalFromTorque;
vForceLocalFromTorque.Cross(vecTorqueLocal, vComponentCenterLocal);
float fWaterImpactDamageMult = pHandling->GetSeaPlaneHandlingData() ? dfWaterImpactDamageMultSeaPlane : dfWaterImpactDamageMult;
float fDamage = (vecForceLocal.Mag() + vForceLocalFromTorque.Mag()) * fTimeStep * GetInvMass() * fWaterImpactDamageMult;
float fWaterImpactDamageThreshold = pHandling->GetSeaPlaneHandlingData() ? sfSeaPlaneWaterDamImpactThreshold : sfVehAircraftDamImpactThreshold;
if( GetModelIndex() == MI_PLANE_SEABREEZE )
{
fWaterImpactDamageThreshold = sfSeabreazeWaterDamImpactThreshold;
}
if(fDamage >= fWaterImpactDamageThreshold)
{
// the water impact type is equivalent to water cannon impact
GetAircraftDamage().ApplyDamageToPlane(this, NULL, DAMAGE_TYPE_WATER_CANNON, 0, fDamage, vComponentCenterGlobal, VEC3_ZERO, VEC3_ZERO, nComponent);
GetLandingGearDamage().ApplyDamageToPlane(this, NULL, DAMAGE_TYPE_WATER_CANNON, 0, fDamage, vComponentCenterGlobal, VEC3_ZERO, VEC3_ZERO, nComponent);
}
// If there is a player in this plane, give their control pad a shake to let them know they've hit water.
if(ContainsLocalPlayer() && !GetWasInWater())
{
CControlMgr::StartPlayerPadShakeByIntensity(snRumbleDuration, fDamage*sfRumbleIntensityScale);
}
}
void CPlane::SwitchEngineOn(bool bNoDelay )
{
if(bNoDelay)
{
m_fEngineSpeed = ms_fStdEngineSpeed;
}
CVehicle::SwitchEngineOn(bNoDelay);
}
void CPlane::ModifyControlsBasedOnFlyingStats( CPed* pPilot, CFlyingHandlingData* pFlyingHandling, float &fYawControl, float &fPitchControl, float &fRollControl, float& fThrottleControl, float fTimeStep )
{
Assert( pFlyingHandling );
Assert( pPilot );
StatId stat = STAT_FLYING_ABILITY.GetStatId();
float fFlyingStatValue = rage::Clamp(static_cast<float>(StatsInterface::GetIntStat(stat)) / 100.0f, 0.0f, 1.0f);
float fMinPlaneControlAbility = CPlayerInfo::GetPlayerStatInfoForPed(*pPilot).m_MinPlaneControlAbility;
float fMaxPlaneControlAbility = CPlayerInfo::GetPlayerStatInfoForPed(*pPilot).m_MaxPlaneControlAbility;
float fPlaneControlAbilityMult = ((1.0f - fFlyingStatValue) * fMinPlaneControlAbility + fFlyingStatValue * fMaxPlaneControlAbility)/100.0f;
fPlaneControlAbilityMult = Clamp(fPlaneControlAbilityMult/ms_fMaxAbilityToAdjustDifficulty, 0.0f, 1.0f);
const float fPlaneDifficulty = pFlyingHandling->m_fInputSensitivityForDifficulty * fTimeStep;
if( fPlaneControlAbilityMult < 1.0f )
{
CPlayerInfo* pPlayerInfo = pPilot->GetPlayerInfo();
physicsFatalAssertf( pPlayerInfo, "Expected a player info!" ); // Assumed to be a player
float fYawControlModified = fYawControl;
float fPitchControlModified = fPitchControl;
float fRollControlModified = fRollControl;
float fThrottleControlModified = fThrottleControl;
// Limited controls for inexperienced pilots
float fAbilityMult = Clamp(ms_fPlaneControlAbilityControlDampMin + ((ms_fPlaneControlAbilityControlDampMax - ms_fPlaneControlAbilityControlDampMin) * fPlaneControlAbilityMult), 0.0f, 1.0f);
fYawControlModified *= fAbilityMult;
fPitchControlModified *= fAbilityMult;
fRollControlModified *= fAbilityMult;
// Input lagging for inexperienced pilots
float fControlLaggingBlendingRate = (1.0f - fPlaneControlAbilityMult) * ms_fPlaneControlLaggingMinBlendingRate + fPlaneControlAbilityMult * ms_fPlaneControlLaggingMaxBlendingRate;
const float fLaggedYawControl = pPlayerInfo->GetLaggedYawControl();
const float fLaggedPitchControl = pPlayerInfo->GetLaggedPitchControl();
const float fLaggedRollControl = pPlayerInfo->GetLaggedRollControl();
fYawControlModified = Clamp(fYawControlModified, fLaggedYawControl - fControlLaggingBlendingRate * fTimeStep, fLaggedYawControl + fControlLaggingBlendingRate * fTimeStep);
fPitchControlModified = Clamp(fPitchControlModified, fLaggedPitchControl - fControlLaggingBlendingRate * fTimeStep, fLaggedPitchControl + fControlLaggingBlendingRate * fTimeStep);
fRollControlModified = Clamp(fRollControlModified, fLaggedRollControl - fControlLaggingBlendingRate * fTimeStep, fLaggedRollControl + fControlLaggingBlendingRate * fTimeStep);
pPlayerInfo->SetLaggedYawControl(fYawControlModified);
pPlayerInfo->SetLaggedPitchControl(fPitchControlModified);
pPlayerInfo->SetLaggedRollControl(fRollControlModified);
// Random control inputs for inexperienced pilots
float fTimeBetweenRandomControlInputs = pPlayerInfo->GetTimeBetweenRandomControlInputs();
float fRandomControlYaw = pPlayerInfo->GetRandomControlYaw();
float fRandomControlPitch = pPlayerInfo->GetRandomControlPitch();
float fRandomControlRoll = pPlayerInfo->GetRandomControlRoll();
float fRandomControlThrottle = pPlayerInfo->GetRandomControlThrottle();
fTimeBetweenRandomControlInputs -= fTimeStep;
// Select a new set of randomised controls
if( fTimeBetweenRandomControlInputs <= 0.0f )
{
float fAbilityRandomMult = Clamp(ms_fPlaneControlAbilityControlRandomMin + ((ms_fPlaneControlAbilityControlRandomMax - ms_fPlaneControlAbilityControlRandomMin) * (1.0f - fPlaneControlAbilityMult)), 0.0f, 1.0f);
fRandomControlYaw = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomYawMult, fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomYawMult);
fRandomControlPitch = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomPitchMult, fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomPitchMult);
fRandomControlRoll = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomRollMult, fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomRollMult);
fRandomControlThrottle = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult*ms_fPlaneControlAbilityControlRandomThrottleMult, 0.0f);
fTimeBetweenRandomControlInputs = Clamp(ms_fTimeBetweenRandomControlInputsMin + ((ms_fTimeBetweenRandomControlInputsMax - ms_fTimeBetweenRandomControlInputsMin) * (1.0f - fPlaneControlAbilityMult)), 0.0f, 1.0f);
}
// Modify the controls and update the lerp speed
fYawControlModified += fRandomControlYaw;
fPitchControlModified += fRandomControlPitch;
fRollControlModified += fRandomControlRoll;
fThrottleControlModified += fRandomControlThrottle;
fYawControl += (fYawControlModified - fYawControl) * m_fScriptPilotSkillNoiseScalar;
fPitchControl += (fPitchControlModified - fPitchControl) * m_fScriptPilotSkillNoiseScalar;
fRollControl += (fRollControlModified - fRollControl) * m_fScriptPilotSkillNoiseScalar;
fThrottleControl += (fThrottleControlModified - fThrottleControl) * m_fScriptPilotSkillNoiseScalar;
fRandomControlYaw = Lerp(fPlaneDifficulty, fRandomControlYaw, 0.0f);
fRandomControlPitch = Lerp(fPlaneDifficulty, fRandomControlPitch, 0.0f);
fRandomControlRoll = Lerp(fPlaneDifficulty, fRandomControlRoll, 0.0f);
fRandomControlThrottle = Lerp(fPlaneDifficulty, fRandomControlThrottle, 0.0f);
// Store the values back on the player info
pPlayerInfo->SetTimeBetweenRandomControlInputs(fTimeBetweenRandomControlInputs);
pPlayerInfo->SetRandomControlYaw(fRandomControlYaw);
pPlayerInfo->SetRandomControlPitch(fRandomControlPitch);
pPlayerInfo->SetRandomControlRoll(fRandomControlRoll);
pPlayerInfo->SetRandomControlThrottle(fRandomControlThrottle);
}
}
void CPlane::UpdateMeredithVent()
{
fragInst* pFragInst = GetVehicleFragInst();
fragHierarchyInst* pHierInst = pFragInst->GetCacheEntry()->GetHierInst();
phArticulatedCollider* pArticulatedCollider = pHierInst->articulatedCollider;
if(pArticulatedCollider == NULL)
{
return;
}
int iBoneIndex = GetBoneIndex( PLANE_EXHAUST_VENT );
if( iBoneIndex == -1 )
{
return;
}
//pFragInst->GetCacheEntry()->LazyArticulatedInit();
int fragChild = GetVehicleFragInst()->GetComponentFromBoneIndex( iBoneIndex );
int linkIndex = pArticulatedCollider->GetLinkFromComponent( fragChild );
if( linkIndex > 0 )
{
phJoint1Dof* p1DofJoint = NULL;
phJoint* pJoint = &pHierInst->body->GetJoint(linkIndex - 1);
if (pJoint && pJoint->GetJointType() == phJoint::JNT_1DOF)
{
p1DofJoint = static_cast<phJoint1Dof*>(pJoint);
}
if(p1DofJoint)
{
float speedPercentage = Clamp( GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ) / pHandling->m_fEstimatedMaxFlatVel, 0.0f, 0.7f );
// First figure out what our closed angle is
// Assume open angle is 0
float fMin, fMax;
p1DofJoint->GetAngleLimits( fMin,fMax );
float fClosedAngle = 0.0f;
float fOpenAngle = 0.0f;
if( -fMin > fMax )
{
fClosedAngle = fMin;
fOpenAngle = fMax;
}
else
{
fClosedAngle = fMax;
fOpenAngle = fMin;
}
TUNE_GROUP_BOOL( STUNT_PLANE, OVERRIDE_MEREDITH_VENT, false );
TUNE_GROUP_FLOAT( STUNT_PLANE, OVERRIDE_MEREDITH_VENT_SPEED, 0.0f, 0.0f, 0.7f, 0.1f );
if( OVERRIDE_MEREDITH_VENT )
{
speedPercentage = OVERRIDE_MEREDITH_VENT_SPEED;
}
float fTargetAngle = fClosedAngle + ( ( speedPercentage * 1.42857f ) * ( fOpenAngle - fClosedAngle ) );
//float fTargetSpeed = 1.0f;
p1DofJoint->SetMuscleTargetAngle( fTargetAngle );
// Set the strength and stiffness
// Done here for tweaking
// Todo: move to Init()
// Need to scale some things by inertia
const float fInvAngInertia = p1DofJoint->ResponseToUnitTorque( pHierInst->body );
float fMinMaxTorque = 100.0f / fInvAngInertia;
// is it possible (or better) to get the inertia from the FragType?
p1DofJoint->SetStiffness( 0.75f ); // If i set this to >= 1.0f the game crashes with a NAN root link position
p1DofJoint->SetMinAndMaxMuscleTorque( fMinMaxTorque );
p1DofJoint->SetMuscleAngleStrength( 1000.0f );
p1DofJoint->SetMuscleSpeedStrength( 1500.0f );
p1DofJoint->SetDriveState( phJoint::DRIVE_STATE_ANGLE );
//float fCurrentAngle = p1DofJoint->GetAngle( pHierInst->body );
//// Smooth the desired speed out close to the target
//float fAngleDiff = fTargetAngle - fCurrentAngle;
//fAngleDiff = fwAngle::LimitRadianAngle(fAngleDiff);
//float fJointSpeedTarget = 0.0f;
//fJointSpeedTarget = rage::Clamp(fTargetSpeed*fAngleDiff/sfSpeedStrengthFadeOutAngle,-fTargetSpeed,fTargetSpeed);
//// If this is set to 0 then we get damping
//// this gives us damping
//p1DofJoint->SetMuscleTargetSpeed(fJointSpeedTarget);
//p1DofJoint->SetDriveState(phJoint::DRIVE_STATE_ANGLE_AND_SPEED);
//if(fabs(fCurrentAngle) < fabs(fOpenAngle))// if the joint is past its lower limit clamp to 0 so we get a slightly more accurate ratio.
//{
// fCurrentAngle = 0.0f;
//}
}
}
}
void CPlane::UpdatePropellerCollision()
{
u32 nRotorIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES;
// don't let rotors hit ped bounds, only ragdoll bounds, since it's a non-physical collision anyway and we don't want peds to stand on rotors
// Remove this to fix the ped not hit by rotors. The issue that ped able to stand on rotors has been fixed in the other place.
//nRotorIncludeFlags &= ~ArchetypeFlags::GTA_PED_TYPE;
nRotorIncludeFlags &= ~(ArchetypeFlags::GTA_AI_TEST | ArchetypeFlags::GTA_SCRIPT_TEST | ArchetypeFlags::GTA_WHEEL_TEST);
u32 nBladeIncludeFlags = ArchetypeFlags::GTA_WEAPON_AND_PROJECTILE_INCLUDE_TYPES | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_RAGDOLL_TYPE;
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>( GetVehicleFragInst()->GetArchetype()->GetBound() );
for( int i = 0; i < m_iNumPropellers; i++ )
{
if( m_propellerCollision[ i ].GetFragGroup() > -1 )
{
int includeFlags = nBladeIncludeFlags;
if( GetModelIndex() == MI_PLANE_MICROLIGHT &&
( !IsIndividualPropellerOn( i ) ||
( !sbEnablePropellerMods && i > 0 ) ) )
{
includeFlags = 0;
//m_propellers[ i ].SetPropellerVisible( false );
}
//else
//{
// m_propellers[ i ].SetPropellerVisible( true );
//}
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[ m_propellerCollision[ i ].GetFragGroup() ];
if( pGroup->GetNumChildren() > 1 )
{
for( int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++ )
{
pBoundComp->SetIncludeFlags( m_propellerCollision[ i ].GetFragChild() + iChild, includeFlags );
}
}
}
if( m_propellerCollision[i].GetFragDisc() > 0 ) // don't set GTA_ALL_SHAPETEST_TYPES so probes won't hit rotors
{
int includeFlags = nRotorIncludeFlags;
if( GetModelIndex() == MI_PLANE_MICROLIGHT &&
( !IsIndividualPropellerOn( i ) ||
( !sbEnablePropellerMods && i > 0 ) ) )
{
includeFlags = 0;
}
pBoundComp->SetIncludeFlags( m_propellerCollision[i].GetFragDisc(), includeFlags );
}
}
}
#if __WIN32PC
void CPlane::ProcessForceFeedback()
{
// Used to tune the plane force to range more appropriate for the device input.
TUNE_FLOAT(PLANE_FORCE_FEEDBACK_SCALER, 0.0000625f, 0.0f, 1.0f, 0.0000001f);
// Used to tune how quick the force feedback response is.
TUNE_FLOAT(PLANE_FORCE_FEEDBACK_RESPONSE, 1.0f, 0.01f, 5.0f, 0.01f);
// Used to tune the velocity effect on the force feedback.
TUNE_FLOAT(PLANE_VELOCITY_FORCE_FEEDBACK_SCALER, 0.0067f, 0.0000f, 1.0f, 0.0001f);
// Used to tune the resistance in the device's resistance force.
TUNE_FLOAT(PLANE_VELOCITY_FORCE_FEEDBACK_RESISTANCE_SCALER, 0.06f, 0.01f, 1.0f, 0.01f);
const ioValue& leftRightValue = CControlMgr::GetMainCameraControl(false).GetVehicleFlyRollLeftRight();
const ioValue& upDownValue = CControlMgr::GetMainCameraControl(false).GetVehicleFlyPitchUpDown();
float force = m_fWingForce * PLANE_FORCE_FEEDBACK_SCALER;
// Whilst we are only applying force feedback to the y axis of the device, we want to add resistance to the x axis of the device.
Vector2 inputVelocity = Vector2( leftRightValue.GetNorm(ioValue::NO_DEAD_ZONE) - leftRightValue.GetLastNorm(ioValue::NO_DEAD_ZONE),
upDownValue.GetNorm(ioValue::NO_DEAD_ZONE) - upDownValue.GetLastNorm(ioValue::NO_DEAD_ZONE) );
inputVelocity /= fwTimer::GetTimeStep();
inputVelocity *= PLANE_VELOCITY_FORCE_FEEDBACK_RESISTANCE_SCALER;
force *= Clamp(GetVelocity().Mag() / PLANE_VELOCITY_FORCE_FEEDBACK_SCALER, 0.0f, 1.0f);
float xResistance = (1.0f/(1.0f + exp(-inputVelocity.x/PLANE_FORCE_FEEDBACK_RESPONSE))-0.5f) * 2.0f;
float forceCompensated = (1.0f/(1.0f + exp((-force - inputVelocity.y)/PLANE_FORCE_FEEDBACK_RESPONSE))-0.5f) * 2.0f;
CControlMgr::ApplyDirectionalForce(xResistance, forceCompensated, 100);
}
#endif // __WIN32PC