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

6735 lines
230 KiB
C++

// Title : Heli.cpp
// Author : Alexander Roger
// Started : 10/04/2003
//
//
//
//
// C headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Rage headers
#include "crskeleton/skeleton.h"
#include "math/vecmath.h"
#include "phbound/boundcomposite.h"
#include "pheffects/wind.h"
#include "grcore/debugdraw.h"
#include "fwmaths/angle.h"
#include "fwmaths/vector.h"
#include "fwpheffects/ropedatamanager.h"
#include "fwpheffects/ropemanager.h"
#include "fwscene/world/WorldLimits.h"
#if !__NO_OUTPUT
#include "fwnet/netchannel.h"
#endif
// Game headers
#include "audio/collisionaudioentity.h"
#include "audio/northaudioengine.h"
#include "audio/policescanner.h"
#include "camera/CamInterface.h"
#include "control/gamelogic.h"
#include "control/replay/Misc/RopePacket.h"
#include "vehicleAi/vehicleintelligence.h"
#include "vehicleAi/Task/TaskVehicleMissionBase.h"
#include "vehicleAi/Task/TaskVehicleFlying.h"
#include "vehicleAi/Task/TaskVehicleGotoHelicopter.h"
#include "vehicleAi/FlyingVehicleAvoidance.h"
#include "Vehicles/VehicleGadgets.h"
#include "debug/debugglobals.h"
#include "debug/debugscene.h"
#include "event/ShockingEvents.h"
#include "event/EventDamage.h"
#include "event/EventShocking.h"
#include "game/clock.h"
#include "game/modelIndices.h"
#include "game/weather.h"
#include "Stats/StatsMgr.h"
#include "modelInfo/vehicleModelInfo.h"
#include "network/Events/NetworkEventTypes.h"
#include "Network/Live/NetworkTelemetry.h"
#include "network/NetworkInterface.h"
#include "network/players/NetGamePlayer.h"
#include "peds/pedIntelligence.h"
#include "peds/ped.h"
#include "physics/gtaArchetype.h"
#include "physics/gtaInst.h"
#include "physics/physics.h"
#include "physics/WorldProbe/worldprobe.h"
#include "renderer/ApplyDamage.h"
#include "renderer/lights/lights.h"
#include "renderer/PostScan.h"
#include "renderer/water.h"
#include "renderer/zoneCull.h"
#include "scene/world/gameWorld.h"
#include "scene/world/GameWorldHeightMap.h"
#include "Scene/portals/Portal.h"
#include "script/script_hud.h"
#include "Stats/StatsInterface.h"
#include "streaming/streaming.h"
#include "streaming/populationstreaming.h"
#include "system/pad.h"
#include "peds/PlayerInfo.h"
#include "Task/General/TaskBasic.h"
#include "Task/Movement/TaskGoto.h"
#include "Task/Combat/TaskCombat.h"
#include "TimeCycle/TimeCycle.h"
#include "VehicleAI/Task/TaskVehiclePlayer.h"
#include "vehicleAi/task/TaskVehicleAnimation.h"
#include "Vehicles/Boat.h"
#include "vehicles/heli.h"
#include "vehicles/vehicleFactory.h"
#include "vehicles/vehiclepopulation.h"
#include "vehicles/vehicle_channel.h"
#include "Vfx/Decals/DecalManager.h"
#include "Vfx/Misc/Coronas.h"
#include "Vfx/Misc/Fire.h"
#include "vfx/Systems/VfxBlood.h"
#include "vfx/Systems/VfxMaterial.h"
#include "vfx/Systems/VfxVehicle.h"
#include "vfx/Systems/VfxWater.h"
#include "weapons/explosion.h"
#include "weapons/Projectiles/Projectile.h"
#include "game/wind.h"
#include "audio/heliaudioentity.h"
#include "Task/Vehicle/TaskInVehicle.h"
#if __PPU
#include "system/controlMgr.h"
#endif
ENTITY_OPTIMISATIONS()
VEHICLE_OPTIMISATIONS()
AUDIO_VEHICLES_OPTIMISATIONS()
#if !__NO_OUTPUT
RAGE_DEFINE_SUBCHANNEL(net, damage_heli, DIAG_SEVERITY_DEBUG3)
#undef __net_channel
#define __net_channel net_damage_heli
#endif
#define HELI_ROTOR_ANGULAR_ACCELERATION (0.2f) //(0.001f) // GTA_SA units
#define HELI_ROTOR_ANGULAR_DECCELERATION (0.1f) //(0.00055f) // GTA_SA units
bool CHeli::bPoliceHelisAllowed = true;
bool CHeli::bHeliControlsCheat = false;
#define HELI_MINIMUM_CONTROL_SPEED (1.0f)
//
#define HELI_ROTOR_DOTPROD_LIMIT (0.95f)
dev_float sfHeliRearRotorLostYaw = 5.0f;
dev_float sfHeliRearRotorLostRoll = 0.35f;
dev_float sfHeliRearRotorDamageYaw = 0.0f;
dev_float sfHeliRearRotorDamageRoll = 0.01f;
dev_float sfHeliMainRotorLostYaw = 0.15f;
dev_float sfHeliMainRotorLostRoll = 0.01f;
dev_float sfHeliMainRotorDamageYaw = 0.0f;
dev_float sfHeliMainRotorDamageRoll = 0.01f;
//Out of control tuning variables, could do with tidying up.
dev_float sfHeliLostRearRotorThrottle = 0.8f;
dev_float sfHeliOutOfControlMinThrottle = 0.0f;
dev_float sfHeliOutOfControlMaxThrottle = 2.0f;
dev_float sfHeliTailLostYaw = 1.25f;
dev_float sfHeliOutOfControlMinYaw = 0.5f;
dev_float sfHeliTailLostRoll = -1.2f;
dev_float sfHeliOutOfControlMaxRoll = 1.2f;
dev_float sfHeliOutOfControlPitch = 0.9f;
dev_float sfHeliOutOfControlRollAfterRecoveryMult = 2.0f;
dev_float sfHeliOutOfControlThrottleDropAfterRecoveryMin = 0.0f;
dev_float sfHeliOutOfControlThrottleDropAfterRecoveryMax = 1.4f;
dev_float sfHeliOutOfControlMaxYawControl = 8.0f;
dev_float sfHeliOutOfControlYawControlAdjustmentNeg = -1.25f;
dev_float sfHeliOutOfControlYawControlAdjustmentPos = 1.25f;
dev_float sfHeliOutOfControlMinThrottleControl = 0.0f;
dev_float sfHeliOutOfControlThrottleControlAdjustmentNeg = -0.8f;
dev_float sfHeliOutOfControlThrottleControlAdjustmentPos = 0.3f;
int siHeliOutOfControlRecoveryPeriodsMax = 3;
int siHeliOutOfControlInitialFailPeriodsMin = 100;
int siHeliOutOfControlInitialFailPeriodsMax = 400;
int siHeliOutOfControlFailPeriodsMin = 1200;
int siHeliOutOfControlFailPeriodsMax = 3500;
int siHeliOutOfControlRecoverPeriodsMin = 600;
int siHeliOutOfControlRecoverPeriodsMax = 1200;
dev_float sfHeliDrownPull = -10.0f;
dev_float sfInWaterRotorDamage = 300.0f;
dev_float sfMainRotorInWaterSplash = 1.0f;
dev_float sfRollYawSpeedThreshold = 5.0f;
dev_float sfRollYawSpeedBlendRate = 0.5f;
dev_float sfRollYawMult = -1.1f;
dev_float sfAutogyroRotorAccel = 0.04f;
dev_float sfAutogyroRotorDamping = 0.1f;
dev_float sfAutogyroRotorDampingC = 0.01f;
dev_float sfHoverModeYawMult = 10.0f;
dev_float sfHoverModePitchMult = 4.0f;
dev_float sfWantedTargetInHeliOrPlaneSpawnDistance = 400.0f;
dev_float sfMinWantedTargetInHeliOrPlaneVelocity = 25.0f;
dev_float sfMinSpawnOffsetAngleForWantedTarget = EIGHTH_PI;
dev_float sfMaxSpawnOffsetAngleForWantedTarget = EIGHTH_PI * 3.0f;
#if __BANK
static bool sbVisualiseHeliControls = false;
#endif
#if USE_SIXAXIS_GESTURES
bank_float CRotaryWingAircraft::MOTION_CONTROL_PITCH_MIN = -0.5f;
bank_float CRotaryWingAircraft::MOTION_CONTROL_PITCH_MAX = 0.5f;
bank_float CRotaryWingAircraft::MOTION_CONTROL_ROLL_MIN = -0.5f;
bank_float CRotaryWingAircraft::MOTION_CONTROL_ROLL_MAX = 0.5f;
bank_float CRotaryWingAircraft::MOTION_CONTROL_YAW_MULT = 2.5f;
#endif
dev_float HELI_RUDDER_MAX_ANGLE_OF_ATTACK = ( DtoR * 30.0f);
dev_float HeliLight_FadeDistance = 250.0f;
//////////////////////////////////////////////////////////////////////////
// CRotaryWingAircraft
CRotaryWingAircraft::CRotaryWingAircraft(const eEntityOwnedBy ownedBy, u32 popType, VehicleType veh) : CAutomobile(ownedBy, popType, veh)
{
m_fYawControl = 0.0f;
m_fPitchControl = 0.0f;
m_fRollControl = 0.0f;
m_fThrottleControl = 0.0f;
m_fCollectiveControl = 1.0f;
m_fJoystickPitch = 0.0f;
m_fJoystickRoll = 0.0f;
m_fMainRotorSpeed = 0.0f;
m_fPrevMainRotorSpeed = 0.0f;
m_vecRearRotorPosition.Zero();
m_nTailBoomGroup = -1;
m_fMainRotorHealth = VEH_DAMAGE_HEALTH_STD;
m_fRearRotorHealth = VEH_DAMAGE_HEALTH_STD;
m_fTailBoomHealth = VEH_DAMAGE_HEALTH_STD;
m_fMainRotorHealthDamageScale = 1.0f;
m_fRearRotorHealthDamageScale = 1.0f;
m_fTailBoomHealthDamageScale = 1.0f;
m_fMainRotorBrokenOffSmokeLifeTime = 0.0f;
m_fRearRotorBrokenOffSmokeLifeTime = 0.0f;
m_bCanBreakOffTailBoom = true;
m_nVehicleFlags.bCanMakeIntoDummyVehicle = false;
m_nPhysicalFlags.bFlyer = true;
m_nVehicleFlags.bNeverUseSmallerRemovalRange = true;
m_iNumPropellers = 0;
SetAllowFreezeWaitingOnCollision(false);
m_bHoverMode = false;
m_bStrafeMode = false;
m_fStrafeModeTargetHeight = 0.0f;
m_bEnableThrustVectoring = true;
m_vHoverModeDesiredTarget = Vector3(0.0f, 0.0f, 0.0f);
m_fSearchLightScore = 1.0f;
m_bHadSearchLightLastFrame = false;
m_fLastWheelContactTime = 0.0f;
m_fHeliOutOfControlYaw = sfHeliTailLostYaw;
m_fHeliOutOfControlPitch = sfHeliOutOfControlPitch;
m_fHeliOutOfControlRoll = sfHeliTailLostRoll;
m_fHeliOutOfControlThrottle = sfHeliLostRearRotorThrottle;
m_uHeliOutOfControlRecoverStart = 0;
m_iHeliOutOfControlRecoverPeriods = 0;
m_bHeliOutOfControlRecovering = false;
m_bHeliRotorDestroyedByPed = false;
m_bDoBlowUpVehicle = false;
m_bDisableTurbulanceThisFrame = false;
m_bIsInAir = false;
m_pilotSkillNoiseScalar = 1.0f;
m_fControlLaggingRateMulti = 1.0f;
m_fJetpackStrafeForceScale = 0.0f;
m_fJetPackThrusterThrotle = 0.0f;
m_fWingAngle[ 0 ] = 0.0f;
m_fWingAngle[ 1 ] = 0.0f;
m_uBreakOffTailBoomPending = Break_Off_Tail_Boom_Immediately;
m_windowBoneCached = false;
m_windowBoneIndices[0] = 0;
m_windowBoneIndices[1] = 0;
m_windowBoneIndices[2] = 0;
m_windowBoneIndices[3] = 0;
m_bDisableAutomaticCrashTask = false;
CFlyingVehicleAvoidanceManager::AddVehicle(RegdVeh(this));
}
CRotaryWingAircraft::~CRotaryWingAircraft()
{
CFlyingVehicleAvoidanceManager::RemoveVehicle(RegdVeh(this));
}
bool CRotaryWingAircraft::GetSearchLightOn()
{
CSearchLight *pSearchLight = GetSearchLight();
return (pSearchLight && pSearchLight->GetLightOn());
}
void CRotaryWingAircraft::SetSearchLightOn(bool bOn)
{
CSearchLight *pSearchLight = GetSearchLight();
if(pSearchLight)
{
pSearchLight->SetLightOn(bOn);
}
}
int CRotaryWingAircraft::InitPhys()
{
CAutomobile::InitPhys();
fragInstGta* pFragInst = GetVehicleFragInst();
Assert(pFragInst);
if(!InheritsFromBlimp() && GetBoneIndex(HELI_TAIL) > -1)
{
s16 nGroup = (s16)pFragInst->GetGroupFromBoneIndex(GetBoneIndex(HELI_TAIL));
if(nGroup > -1)
{
m_nTailBoomGroup = (s8)nGroup;
fragTypeGroup* pTailGroup = pFragInst->GetTypePhysics()->GetAllGroups()[nGroup];
int nFirstChild = pTailGroup->GetChildFragmentIndex();
int nNumChildren = pTailGroup->GetNumChildren();
// make sure this component doesn't break off until we want it to
for(int nChild=nFirstChild; nChild < nFirstChild + nNumChildren; nChild++)
{
((fragInstGta*)pFragInst)->SetDontBreakFlag(BIT(nChild));
}
}
}
return INIT_OK;
}
void CRotaryWingAircraft::InitDoors()
{
CVehicle::InitDoors();
Assert(GetNumDoors()==0);
m_pDoors = GetCarDoorPointer();
m_nNumDoors = 0;
if(GetBoneIndex(VEH_DOOR_DSIDE_F) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_DSIDE_F, -0.4f*PI, 0.0f, CCarDoor::AXIS_Z|CCarDoor::WILL_LOCK_SWINGING);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_PSIDE_F) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_PSIDE_F, 0.4f*PI, 0.0f, CCarDoor::AXIS_Z|CCarDoor::WILL_LOCK_SWINGING);
m_nNumDoors++;
}
// heli's have sliding doors on the rear
if(GetBoneIndex(VEH_DOOR_DSIDE_R) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_DSIDE_R, -1.0f, 0.0f, CCarDoor::AXIS_SLIDE_Y);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_DOOR_PSIDE_R) > -1)
{
m_pDoors[m_nNumDoors].Init(this, VEH_DOOR_PSIDE_R, -1.0f, 0.0f, CCarDoor::AXIS_SLIDE_Y);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_FOLDING_WING_L) > -1)
{
u32 uInitFlags = CCarDoor::AXIS_Y|CCarDoor::DONT_BREAK;
m_pDoors[m_nNumDoors].Init(this, VEH_FOLDING_WING_L, 0.1f*PI, 0.0f, uInitFlags);
m_nNumDoors++;
}
if(GetBoneIndex(VEH_FOLDING_WING_R) > -1)
{
u32 uInitFlags = CCarDoor::AXIS_Y|CCarDoor::DONT_BREAK;
m_pDoors[m_nNumDoors].Init(this, VEH_FOLDING_WING_R, 0.1f*PI, 0.0f, uInitFlags);
m_nNumDoors++;
}
}
void CRotaryWingAircraft::UpdateRotorBounds()
{
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetVehicleFragInst()->GetArchetype()->GetBound());
Assert(pBoundComp);
if(pBoundComp->GetTypeAndIncludeFlags())
{
// turn collision on for rear boom on helicopter
if(GetBoneIndex(HELI_TAIL) > -1)
{
int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex(HELI_TAIL));
if(nChildIndex != -1)
{
pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
// Treat the rotor like wheels
// GTAV - B*1954524 - if the rotor disc is stationary it shouldn't collide with anything.
u32 nRotorIncludeFlags = 0;
// 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;
u32 nCameraTestFlag = m_fMainRotorSpeed > 0.0f ? 0 : ArchetypeFlags::GTA_CAMERA_TEST;
if( m_fMainRotorSpeed > 0.0f )
{
nRotorIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES;
nRotorIncludeFlags &= ~(ArchetypeFlags::GTA_AI_TEST | ArchetypeFlags::GTA_SCRIPT_TEST | ArchetypeFlags::GTA_WHEEL_TEST | nCameraTestFlag);
}
u32 nBladeIncludeFlags = ArchetypeFlags::GTA_WEAPON_AND_PROJECTILE_INCLUDE_TYPES | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_RAGDOLL_TYPE | nCameraTestFlag;
if(GetMainRotorGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[GetMainRotorGroup()];
if(pGroup->GetNumChildren() > 1)
{
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
pBoundComp->SetIncludeFlags(GetMainRotorChild() + iChild, nBladeIncludeFlags);
}
}
}
if(GetMainRotorDisc() > 0) // don't set GTA_ALL_SHAPETEST_TYPES so probes won't hit rotors
{
pBoundComp->SetIncludeFlags(GetMainRotorDisc(), nRotorIncludeFlags);
}
if(GetRearRotorGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[GetRearRotorGroup()];
if(pGroup->GetNumChildren() > 1)
{
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
pBoundComp->SetIncludeFlags(GetRearRotorChild() + iChild, nBladeIncludeFlags);
}
}
}
if(GetRearRotorDisc() > 0)
{
pBoundComp->SetIncludeFlags(GetRearRotorDisc(), nRotorIncludeFlags);
}
if( m_iNumPropellers == Num_Drone_Rotors )
{
// the drone has 4 rotors so check for those
if(GetMain2RotorGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[GetMain2RotorGroup()];
if(pGroup->GetNumChildren() > 1)
{
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
pBoundComp->SetIncludeFlags(GetMain2RotorChild() + iChild, nBladeIncludeFlags);
}
}
}
if(GetRear2RotorGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[GetRear2RotorGroup()];
if(pGroup->GetNumChildren() > 1)
{
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
pBoundComp->SetIncludeFlags(GetRear2RotorChild() + iChild, nBladeIncludeFlags);
}
}
}
if(GetMain2RotorDisc() > 0) // don't set GTA_ALL_SHAPETEST_TYPES so probes won't hit rotors
{
pBoundComp->SetIncludeFlags(GetMain2RotorDisc(), nRotorIncludeFlags);
}
if(GetRear2RotorDisc() > 0) // don't set GTA_ALL_SHAPETEST_TYPES so probes won't hit rotors
{
pBoundComp->SetIncludeFlags(GetRear2RotorDisc(), nRotorIncludeFlags);
}
}
// B*1928675: Allow peds to collide with the skids.
for(int boneId = VEH_SUSPENSION_LF; boneId <= VEH_SUSPENSION_RF; boneId++)
{
int boneIdx = GetBoneIndex((eHierarchyId)boneId);
if(boneIdx > -1)
{
int nGroupIndex = GetVehicleFragInst()->GetGroupFromBoneIndex(boneIdx);
if(nGroupIndex != -1)
{
fragTypeGroup *pGroup = GetVehicleFragInst()->GetTypePhysics()->GetGroup(nGroupIndex);
int nChildIndex = pGroup->GetChildFragmentIndex();
for(int nChild = 0; nChild < pGroup->GetNumChildren(); ++nChild)
{
pBoundComp->SetIncludeFlags(nChildIndex + nChild, pBoundComp->GetIncludeFlags(nChildIndex + nChild) | ArchetypeFlags::GTA_PED_TYPE);
}
}
}
}
}
}
void CRotaryWingAircraft::InitCompositeBound()
{
CAutomobile::InitCompositeBound();
UpdateRotorBounds();
if( GetModelIndex() == MI_HELI_AKULA )
{
m_nVehicleFlags.bUseDeformation = false;
}
}
bool CRotaryWingAircraft::WantsToBeAwake()
{
if(GetIsAttached() && !GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION)
&& GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_BASIC)
{
return false;
}
if(GetStatus()==STATUS_WRECKED)
return false;
if(m_nVehicleFlags.bEngineOn && (Abs(m_fThrottleControl) + Abs(m_fYawControl) + Abs(m_fPitchControl) + Abs(m_fRollControl)) > 0.001f)
{
bool bAllowActivation = !GetIsAttached() || GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION)
|| GetAttachmentExtension()->GetAttachState()!=ATTACH_STATE_BASIC;
return bAllowActivation && !(IsRunningCarRecording() && CanBeInactiveDuringRecording());
}
if( HasContactWheels() == false )
{
return true;
}
if( IsWheelContactPhysicalMoving() )
{
return true;
}
return false;
}
float CRotaryWingAircraft::ComputeAdditionalYawFromTransform(float fYaw, const fwTransform& transform, float fFlatMoveSpeed)
{
//add some extra yaw when flying forward and rolling
if(fFlatMoveSpeed > sfRollYawSpeedThreshold)
{
fFlatMoveSpeed -= sfRollYawSpeedThreshold;
return (fYaw + transform.GetRoll() * rage::Min(1.0f, fFlatMoveSpeed * sfRollYawSpeedBlendRate) * sfRollYawMult);
}
return fYaw;
}
CSearchLight* CRotaryWingAircraft::GetSearchLight()
{
CVehicleWeaponMgr* pWeaponMgr = GetVehicleWeaponMgr();
if(pWeaponMgr)
{
for(int i = 0; i < pWeaponMgr->GetNumVehicleWeapons(); i++)
{
CVehicleWeapon* pWeapon = pWeaponMgr->GetVehicleWeapon(i);
if(pWeapon && pWeapon->GetType() == VGT_SEARCHLIGHT)
{
return static_cast<CSearchLight*>(pWeapon);
}
}
}
return NULL;
}
bool CRotaryWingAircraft::GetIsDrone() const
{
return (GetModelIndex() == MI_HELI_DRONE || GetModelIndex() == MI_HELI_DRONE_2);
}
bool CRotaryWingAircraft::GetIsJetPack() const
{
return (GetModelIndex() == MI_JETPACK_THRUSTER);
}
//
//
// CRotaryWingAircraft::ProcessControl()
//
//
static dev_float dfJoystickSmoothSpeed = 5.0f;
void CRotaryWingAircraft::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessControlOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfProcessControlCallingEntity(), vThisPosition );
// DEBUG!! -AC, This call to process control is either at the wrong place (it should be moved to the bottom of this function)
// or it is at the right place (and all other vehicle::ProcessControl functions should emulate it).
// then can go ahead and call the parent version of process control
CAutomobile::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
// END DEBUG!!
bool bDoSearchLight = false;
bool bFireSearchLightGun = false;
CSearchLight* pSearchLight = GetSearchLight();
if(pSearchLight && !IsNetworkClone()) //Making sure game does not crash if there is no searchlight
{
#if GTA_REPLAY
if(!CReplayMgr::IsEditModeActive())
#endif
{
// Deal with the searchlight
if( (GetStatus() != STATUS_WRECKED) &&
(true == m_nVehicleFlags.bEngineOn) &&
!CCullZones::PlayerNoRain() &&
(GetDriver() || IsUsingPretendOccupants()) &&
!pSearchLight->GetIsDamaged() )
{
//if player is controlling the chopper then don't change the state
if(GetDriver() && GetDriver()->IsPlayer())
{
//Skip everything
}
else
{
CVehicleModelInfo* vmi = GetVehicleModelInfo();
Assert(vmi);
CPed* pPlayerPed = NULL;
// Do some checks to see if we should be a wanted dispatched heli that uses searchlight
bool bUseWantedSearchLight = false;
if( !this->m_nVehicleFlags.bMoveAwayFromPlayer &&
vmi->GetVehicleType() == VEHICLE_TYPE_HELI && vmi->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_LAW_ENFORCEMENT) )
{
pPlayerPed = !NetworkInterface::IsGameInProgress() ? CGameWorld::FindLocalPlayer() : NetworkInterface::FindClosestWantedPlayer(VEC3V_TO_VECTOR3(GetTransform().GetPosition()),WANTED_LEVEL_HELI_1_DISPATCHED);
CWanted* pPlayerWanted = pPlayerPed ? pPlayerPed->GetPlayerWanted() : NULL;
//Checking both real and fake wanted level as scripters might set one of these
if( (pPlayerWanted && pPlayerWanted->GetWantedLevel() >= WANTED_LEVEL_HELI_1_DISPATCHED) ||
CScriptHud::iFakeWantedLevel >= WANTED_LEVEL_HELI_1_DISPATCHED )
{
bUseWantedSearchLight = true;
if(pPlayerPed)
{
// If the target is in a vehicle we need to make sure it's a valid type
CVehicle* pPlayerVehicle = pPlayerPed->GetIsInVehicle() ? pPlayerPed->GetMyVehicle() : NULL;
if(pPlayerVehicle)
{
VehicleType vehicleType = pPlayerVehicle->GetVehicleType();
if( vehicleType == VEHICLE_TYPE_HELI ||
vehicleType == VEHICLE_TYPE_PLANE ||
(vehicleType == VEHICLE_TYPE_SUBMARINE && pPlayerVehicle->m_Buoyancy.GetStatus() == FULLY_IN_WATER) )
{
bUseWantedSearchLight = false;
}
}
}
}
}
if (bUseWantedSearchLight)
{
bDoSearchLight = true;
bFireSearchLightGun = true;
SetSearchLightTarget((CPhysical *)pPlayerPed);
}
else
{
aiTask *pTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority( VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_FOLLOW, VEHICLE_TASK_PRIORITY_PRIMARY);
CTaskVehicleMissionBase *pCarTask = NULL;
if(pTask)
{
Assert(dynamic_cast<CTaskVehicleMissionBase*>(pTask));
pCarTask = static_cast<CTaskVehicleMissionBase*>(pTask);
}
if(pCarTask && pCarTask->GetTargetEntity())
{
bDoSearchLight = true;
bFireSearchLightGun = false;
SetSearchLightTarget((CPhysical *)pCarTask->GetTargetEntity());
}
else
{
SetSearchLightTarget(NULL);
}
}
//This check is here so that the checks below still has a chance to turn the light off
//GetForceLightOn is set by script, but we would still like it to perform the conditions below
if(pSearchLight->GetForceLightOn())
{
bDoSearchLight = true;
}
if (this->GetIsInWater()
|| (pSearchLight->GetSearchLightTarget() == NULL && !pSearchLight->GetForceLightOn())
|| (CClock::GetHour() < 19 && CClock::GetHour() >= 7)
|| CPortal::IsInteriorScene()
)
{
bDoSearchLight = false;
bFireSearchLightGun = false;
}
// If we picked a target that is actually above us we don't render the search light.
if (pSearchLight->GetSearchLightTarget() && pSearchLight->GetSearchLightTarget()->GetTransform().GetPosition().GetZf() > vThisPosition.z && !pSearchLight->GetForceLightOn())
{
bDoSearchLight = false;
bFireSearchLightGun = false;
}
// if our driver is dead
if (!GetDriver() || GetDriver()->IsDead())
{
bDoSearchLight = false;
bFireSearchLightGun = false;
}
pSearchLight->SetLightOn(bDoSearchLight);
}
}
else
{
pSearchLight->SetLightOn(false);
}
}
pSearchLight->ProcessSearchLight(this);
ProcessSearchLightScoring();
}
if(g_InterestingEvents.IsActive())
{
// chance is once in every 10 secs
float fTimeStep = fwTimer::GetTimeStep();
float fChance = fTimeStep / 10.0f;
if(bFireSearchLightGun)
fChance *= 2.0f;
float fRandVal = fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
if(fRandVal < fChance)
{
//Register this as an 'interesting event'
g_InterestingEvents.Add(CInterestingEvents::EHelicopterOverhead, this);
}
}
// Add in a shocking event if the helicopter is flying
if (GetDriver() && GetDriver()->IsPlayer())
{
if( m_fMainRotorSpeed > 0.1f && ShouldGenerateEngineShockingEvents() )
{
CEventShockingHelicopterOverhead ev(*this);
CShockingEventsManager::Add(ev);
}
}
// check if tail has broken off, and delete any wheels that may have been attached to it
if(m_nTailBoomGroup > -1 && GetVehicleFragInst()->GetGroupBroken(m_nTailBoomGroup))
{
for(int i=0; i<GetNumWheels(); i++)
{
CWheel* pWheel = GetWheel(i);
int nFragChild = pWheel->GetFragChild();
if(nFragChild > -1)
{
fragPhysicsLOD* pTypePhysics = GetVehicleFragInst()->GetTypePhysics();
fragTypeChild* pChild = pTypePhysics->GetAllChildren()[nFragChild];
if(pWheel && pWheel->GetConfigFlags().IsFlagSet(WCF_REARWHEEL) && pChild->GetOwnerGroupPointerIndex() > 0)
{
if(!GetVehicleFragInst()->GetChildBroken(nFragChild))
GetVehicleFragInst()->DeleteAbove(nFragChild);
}
}
}
}
// smooth out the joystick control
if(GetDriver() && GetDriver()->IsInFirstPersonVehicleCamera())
{
TUNE_GROUP_FLOAT(JOYSTICK_IK, HeliJoyStickLerp, 0.1f, 0.0f, 1.0f, 0.001f);
m_fJoystickPitch = rage::Lerp(HeliJoyStickLerp, m_fJoystickPitch, m_fPitchControl);
m_fJoystickRoll = rage::Lerp(HeliJoyStickLerp, 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
{
float fMaxDelta = fwTimer::GetTimeStep() * dfJoystickSmoothSpeed;
m_fJoystickPitch += rage::Clamp(m_fPitchControl - m_fJoystickPitch, -fMaxDelta, fMaxDelta);
m_fJoystickRoll += rage::Clamp(m_fRollControl - m_fJoystickRoll, -fMaxDelta, fMaxDelta);
}
#if __BANK
if((CVehicle::ms_nVehicleDebug==VEH_DEBUG_PERFORMANCE || CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
&& (GetStatus()==STATUS_PLAYER))
{
static int x = 4;
static int y = 20;
char debugText[100];
float fSpeed = GetVelocity().Mag() * 3.6f;
float fSpeedHoriz = GetVelocity().XYMag() * 3.6f;
float fSpeedVert = GetVelocity().z * 3.6f;
sprintf(debugText, "Spd:%3.1f km/h (Horiz:%3.1f, Vert:%3.1f)", fSpeed, fSpeedHoriz, fSpeedVert);
grcDebugDraw::PrintToScreenCoors(debugText, x,y);
sprintf(debugText, "Throttle:%1.2f, Engine:%1.2f", m_fThrottleControl, m_fMainRotorSpeed);
grcDebugDraw::PrintToScreenCoors(debugText, x,y+1);
}
if(CVehicle::ms_nVehicleDebug == VEH_DEBUG_DAMAGE && (GetStatus()==STATUS_PLAYER))
{
static int x = 75;
static int y = 22;
// Print rotor healths
char debugText[100];
formatf(debugText,"Main rotor health: %f", GetMainRotorHealth());
grcDebugDraw::PrintToScreenCoors(debugText, x,y);
formatf(debugText,"Rear rotor health: %f", GetRearRotorHealth());
grcDebugDraw::PrintToScreenCoors(debugText, x,y+1);
formatf(debugText,"Tail boom health: %f", GetTailBoomHealth());
grcDebugDraw::PrintToScreenCoors(debugText, x,y+2);
}
#endif
#if __BANK
// Do some heli control debugging
if(CDebugScene::FocusEntities_Get(0) == this)
{
VisualisePitchRoll();
}
#endif // __BANK
}
float sfHeliAbandonedYawMult = 0.5f;
float sfHeliAbandonedPitchMult = 1.0f;
float HELI_CRASH_THROTTLE = -0.1f;
float HELI_CRASH_PITCH = 0.4f;
float HELI_CRASH_YAW = 0.4f;
float HELI_CRASH_ROLL = 0.4f;
float HELI_ABANDONED_THROTTLE = 0.5f;
dev_float HELI_THROTTLE_CONTROL_DAMPING = 0.1f;
dev_float HELI_AUTO_THROTTLE_FALLOFF = 0.2f;
dev_float CRotaryWingAircraft::ms_fPreviousSearchLightOwnerBonus = 0.7f;
dev_float CRotaryWingAircraft::ms_fUnblockedSearchLightBonus = 0.5f;
float CRotaryWingAircraft::ms_fBestSearchLightScore = 1.0f;
bool CRotaryWingAircraft::ms_bNeedToResetSearchLightScoring = true;
dev_float dfHeliPushByWindMillSpeed = 5.0f;
dev_float dfCrushPedMinSpeed = 0.2f;
void CRotaryWingAircraft::ProcessPreComputeImpacts(phContactIterator impacts)
{
static dev_float fMinRotorSpeedForDamage = 0.1f;
float fSpeedForDamage = m_fMainRotorSpeed < fMinRotorSpeedForDamage ? 0.0f : m_fMainRotorSpeed;
impacts.Reset();
while(!impacts.AtEnd())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
if( ProcessFoliageImpact( pOtherInstance, impacts.GetMyPosition(), impacts.GetMyComponent(), impacts.GetOtherComponent(), impacts.GetOtherElement() ) )
{
// Now that we've noted the collision, disable the impact.
impacts.DisableImpact();
++impacts;
continue;
}
for(int i = 0; i < m_iNumPropellers; i++)
{
m_propellerCollisions[i].ProcessPreComputeImpacts(this,impacts,fSpeedForDamage);
}
if(!impacts.IsDisabled())
{
if(pOtherEntity)
{
if(pOtherEntity->GetArchetype() && pOtherEntity->GetModelIndex() == MI_WINDMILL)
{
impacts.SetDepth(Min(dfHeliPushByWindMillSpeed * fwTimer::GetTimeStep(), impacts.GetDepth()));
}
// Blow up as early as possible when hitting the map while crashing.
if(pOtherEntity->GetIsTypeBuilding() && !IsPropeller(impacts.GetMyComponent()))
{
if(NULL != GetIntelligence()->GetTaskManager()->FindTaskByTypeActive(
VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH))
{
m_bDoBlowUpVehicle = true;
}
}
if(NetworkInterface::IsGameInProgress() && pOtherEntity->GetIsTypePed())
{
Vec3V vPedNormal;
impacts.GetOtherNormal(vPedNormal);
if(vPedNormal.GetZf() < 0.0f)
{
Vec3V vVehicleVelocity(VECTOR3_TO_VEC3V(GetLocalSpeed(VEC3V_TO_VECTOR3(impacts.GetMyPosition()), true, impacts.GetMyComponent())));
if(vVehicleVelocity.GetZf() * vPedNormal.GetZf() > dfCrushPedMinSpeed
&& impacts.GetDepth() > CTaskNMBehaviour::sm_Tunables.m_MinContactDepthForContinuousPushActivation)
{
static_cast<CPed *>(pOtherEntity)->SetPedResetFlag(CPED_RESET_FLAG_CapsuleBeingPushedByVehicle, true);
}
}
}
}
if( !m_bIsInAir )
{
if( pOtherEntity && pOtherEntity->GetIsTypeVehicle() )
{
// GTAV - B*1724742 - cars can pick up helicopters
// if the helicopter is on the ground, upright, and is colliding with a vehicle
// test the normal.
// if the normal is trying to go under the vehicle flatten it to only be on the x y axis
Vector3 myNormal;// = VECTOR3_ZERO;
impacts.GetMyNormal( myNormal );
if( GetTransform().GetUp().GetZf() > 0.6f &&
myNormal.GetZ() < 0.95f &&
myNormal.GetZ() > 0.3f )
{
myNormal.SetZ( 0.0f );
myNormal.Normalize();
impacts.SetMyNormal( myNormal );
}
}
}
}
impacts++;
}
impacts.Reset();
CVehicle::ProcessPreComputeImpacts(impacts);
}
void CRotaryWingAircraft::ProcessPostPhysics()
{
if (m_bDoBlowUpVehicle)
{
m_bDoBlowUpVehicle = false;
if(CTaskVehicleCrash* pTask = static_cast<CTaskVehicleCrash*>(GetIntelligence()->GetTaskManager()->FindTaskByTypeActive(
VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH)))
{
pTask->DoBlowUpVehicle(this);
}
}
//crash the helicopter if it's below the minimum height
if(GetStatus() != STATUS_WRECKED && !m_bDisableAutomaticCrashTask)
{
Vec3V vVehiclePos = GetTransform().GetPosition();
float fMinHeightAtPos = CGameWorldHeightMap::GetMinHeightFromWorldHeightMap(vVehiclePos.GetXf(), vVehiclePos.GetYf());
bool bBelowMinHeight = vVehiclePos.GetZf() < fMinHeightAtPos;
if(bBelowMinHeight)
{
StartCrashingBehavior();
m_bDisableAutomaticCrashTask = true;
}
}
CAutomobile::ProcessPostPhysics();
}
float CRotaryWingAircraft::GetHoverModeYawMult()
{
return (m_bHoverMode ? sfHoverModeYawMult : 1.0f);
}
float CRotaryWingAircraft::GetHoverModePitchMult()
{
return (m_bHoverMode ? sfHoverModePitchMult : 1.0f);
}
dev_float dfHeliRotorBrokenOffSmokeLifeTime = 8.0f;
void CRotaryWingAircraft::BreakOffMainRotor()
{
fragInstGta* fragInstVehicle = GetVehicleFragInst();
int iChild = m_propellerCollisions[Rotor_Main].GetFragChild();
if (fragInstVehicle && (iChild > -1) && !fragInstVehicle->GetChildBroken(iChild))
{
if (CarPartsCanBreakOff())
{
// rotor destruction effect
g_vfxVehicle.TriggerPtFxHeliRotorDestroy(this, false);
if(GetVehicleAudioEntity()->GetAudioVehicleType() == AUD_VEHICLE_HELI)
{
static_cast<audHeliAudioEntity*>(GetVehicleAudioEntity())->PlayRotorBreakSound();
}
}
GetVehicleFragInst()->DeleteAbove(iChild);
m_fMainRotorHealth = 0.0f;
m_fMainRotorSpeed = 0.0f;
m_fMainRotorBrokenOffSmokeLifeTime = dfHeliRotorBrokenOffSmokeLifeTime;
if (!IsNetworkClone() && GetWeaponDamageEntity() && GetWeaponDamageEntity()->GetIsTypePed())
{
m_bHeliRotorDestroyedByPed = true;
}
}
if( GetIsDrone() )
{
int iChild = m_propellerCollisions[Rotor_Main2].GetFragChild();
if (fragInstVehicle && (iChild > -1) && !fragInstVehicle->GetChildBroken(iChild))
{
GetVehicleFragInst()->DeleteAbove(iChild);
}
}
}
void CRotaryWingAircraft::BreakOffRearRotor()
{
fragInstGta* fragInstVehicle = GetVehicleFragInst();
int iChild = m_propellerCollisions[Rotor_Rear].GetFragChild();
if (fragInstVehicle && (iChild > -1) && !GetVehicleFragInst()->GetChildBroken(iChild))
{
if (CarPartsCanBreakOff())
{
// rotor destruction effect
g_vfxVehicle.TriggerPtFxHeliRotorDestroy(this, true);
if(GetVehicleAudioEntity()->GetAudioVehicleType() == AUD_VEHICLE_HELI)
{
static_cast<audHeliAudioEntity*>(GetVehicleAudioEntity())->PlayRearRotorBreakSound();
}
}
GetVehicleFragInst()->DeleteAbove(iChild);
m_fRearRotorHealth = 0.0f;
m_fRearRotorBrokenOffSmokeLifeTime = dfHeliRotorBrokenOffSmokeLifeTime;
}
if( GetIsDrone() )
{
int iChild = m_propellerCollisions[Rotor_Rear2].GetFragChild();
if (fragInstVehicle && (iChild > -1) && !GetVehicleFragInst()->GetChildBroken(iChild))
{
GetVehicleFragInst()->DeleteAbove(iChild);
}
}
}
dev_float dfHeliBrokenOffTailBoomMinMass = 50.0f;
dev_float dfHeliBrokenOffTailBoomMinHeliMass = 1000.0f;
void CRotaryWingAircraft::BreakOffTailBoom(int eBreakingState)
{
if(eBreakingState != Break_Off_Tail_Boom_Immediately && CApplyDamage::GetNumDamagePending(this) > 0)
{
m_uBreakOffTailBoomPending = (u8)eBreakingState;
return;
}
CPropeller& rearPropeller = GetRearPropeller();
rearPropeller.SetPropellerSpeed(0.0f);
rearPropeller.PreRender(this); //To set the fast bone to the slow bone
fragInstGta* fragInstVehicle = GetVehicleFragInst();
if (fragInstVehicle && (m_nTailBoomGroup > -1) && !fragInstVehicle->GetGroupBroken(m_nTailBoomGroup) && GetCanBreakOffTailBoom())
{
if (CarPartsCanBreakOff())
{
fragInst* pNewFragInst = fragInstVehicle->BreakOffAboveGroup(m_nTailBoomGroup);
if (pNewFragInst)
{
fragCacheEntry* pCacheEntry = pNewFragInst->GetCacheEntry();
if( pCacheEntry &&
pCacheEntry->GetHierInst() &&
GetMass() > dfHeliBrokenOffTailBoomMinHeliMass &&
pCacheEntry->GetMass( pCacheEntry->GetHierInst() ) <= dfHeliBrokenOffTailBoomMinMass )
{
pCacheEntry->SetMass( dfHeliBrokenOffTailBoomMinMass );
}
CEntity* pNewEntity = CPhysics::GetEntityFromInst(pNewFragInst);
if (pNewEntity)
{
g_vfxVehicle.TriggerPtFxVehicleDebris(pNewEntity);
if(GetVehicleAudioEntity()->GetAudioVehicleType() == AUD_VEHICLE_HELI)
{
static_cast<audHeliAudioEntity*>(GetVehicleAudioEntity())->PlayTailBreakSound();
}
}
}
}
else
{
fragInstVehicle->DeleteAboveGroup(m_nTailBoomGroup);
}
m_fTailBoomHealth = 0.0f;
m_fRearRotorBrokenOffSmokeLifeTime = 0.0f; // Stop playing rear rotor broken off smoke
}
m_uBreakOffTailBoomPending = Break_Off_Tail_Boom_Immediately;
}
void CRotaryWingAircraft::PostBoundDeformationUpdate()
{
if(m_uBreakOffTailBoomPending == Break_Off_Tail_Boom_Pending_Bound_Update && CApplyDamage::GetNumDamagePending(this) == 0)
{
BreakOffTailBoom();
}
CVehicle::PostBoundDeformationUpdate();
}
bool CRotaryWingAircraft:: HasBoundUpdatePending() const
{
return (m_uBreakOffTailBoomPending == Break_Off_Tail_Boom_Pending_Bound_Update) || CVehicle::HasBoundUpdatePending();
}
bool CRotaryWingAircraft::GetIsTailBoomBroken() const
{
return GetVehicleFragInst() && m_nTailBoomGroup > -1 && GetVehicleFragInst()->GetGroupBroken(m_nTailBoomGroup);
}
bool CRotaryWingAircraft::GetIsMainRotorBroken() const
{
int iChild = m_propellerCollisions[Rotor_Main].GetFragChild();
return GetVehicleFragInst() && iChild > -1 && GetVehicleFragInst()->GetChildBroken(iChild);
}
bool CRotaryWingAircraft::GetIsRearRotorBroken() const
{
int iChild = m_propellerCollisions[Rotor_Rear].GetFragChild();
return GetVehicleFragInst() && iChild > -1 && GetVehicleFragInst()->GetChildBroken(iChild);
}
bool CRotaryWingAircraft::IsPropeller (int nComponent) const
{
bool rotorStopped = ( GetMainRotorSpeed() == 0.0f );
for(int i = 0; i < m_iNumPropellers; i++)
{
if( m_propellerCollisions[i].IsPropellerComponent( this, nComponent ) )
{
// Allow collision with blade bound when its stationary
if( !rotorStopped || nComponent == m_propellerCollisions[ i ].GetFragDisc() )
{
return true;
}
}
}
return false;
}
ePrerenderStatus CRotaryWingAircraft::PreRender(const bool bIsVisibleInMainViewport)
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
for(int i= 0; i < m_iNumPropellers; i++)
{
m_propellers[i].PreRender(this);
}
if(GetStatus() == STATUS_PLAYER)
{
if( !GetIsJetPack() )
{
TUNE_GROUP_FLOAT(JOYSTICK_IK, HeliJoyStickRotX, 4.0f, 0.0f, 20.0f, 0.001f);
TUNE_GROUP_FLOAT(JOYSTICK_IK, HeliJoyStickRotY, 6.5f, 0.0f, 20.0f, 0.001f);
float fJoystickAdjustAngleX = ( DtoR * HeliJoyStickRotX);
float fJoystickAdjustAngleY = ( DtoR * HeliJoyStickRotY);
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);
}
else
{
TUNE_GROUP_FLOAT(JOYSTICK_JETPACK_IK, JetPackJoyStickRotX, 6.5f, 0.0f, 20.0f, 0.001f);
TUNE_GROUP_FLOAT(JOYSTICK_JETPACK_IK, JetPackJoyStickRotY, 6.5f, 0.0f, 20.0f, 0.001f);
float fJoystickAdjustAngleX = ( DtoR * JetPackJoyStickRotX);
float fJoystickAdjustAngleY = ( DtoR * JetPackJoyStickRotY);
SetComponentRotation(HELI_HBGRIP_LOW_R, ROT_AXIS_LOCAL_X, m_fJoystickPitch * fJoystickAdjustAngleX, true);
SetComponentRotation(HELI_HBGRIP_LOW_R, ROT_AXIS_LOCAL_Y, -m_fJoystickRoll * fJoystickAdjustAngleY, false);
SetComponentRotation(HELI_HBGRIP_LOW_L, ROT_AXIS_LOCAL_X, m_fThrottleControl * fJoystickAdjustAngleX, true);
SetComponentRotation(HELI_HBGRIP_LOW_L, ROT_AXIS_LOCAL_Y, m_fYawControl * fJoystickAdjustAngleY, false);
GetSkeleton()->PartialUpdate( GetBoneIndex( HELI_HBGRIP_LOW_R ) );
GetSkeleton()->PartialUpdate( GetBoneIndex( HELI_HBGRIP_LOW_L ) );
}
}
// vfx
if (!IsDummy() && !m_nVehicleFlags.bIsDrowning &&
!m_nVehicleFlags.bDisableParticles)
{
if(m_fMainRotorBrokenOffSmokeLifeTime > 0.0f && GetIsMainRotorBroken())
{
if(GetBoneIndex(HELI_ROTOR_MAIN) > -1)
{
Vector3 vPos;
GetDefaultBonePositionForSetup(HELI_ROTOR_MAIN, vPos);
g_vfxVehicle.UpdatePtFxAircraftSectionDamageSmoke(this, RCC_VEC3V(vPos), 0);
}
}
if(m_fRearRotorBrokenOffSmokeLifeTime > 0.0f && GetIsRearRotorBroken() && !GetIsTailBoomBroken())
{
if(GetBoneIndex(HELI_ROTOR_REAR) > -1)
{
Vector3 vPos;
GetDefaultBonePositionForSetup(HELI_ROTOR_REAR, vPos);
g_vfxVehicle.UpdatePtFxAircraftSectionDamageSmoke(this, RCC_VEC3V(vPos), 1);
}
}
if ((m_nVehicleFlags.bEngineOn || IsRunningCarRecording()))
{
g_vfxVehicle.UpdatePtFxPlaneAfterburner(this, HELI_AFTERBURNER, 0);
g_vfxVehicle.UpdatePtFxPlaneAfterburner(this, HELI_AFTERBURNER_2, 1);
}
}
return CAutomobile::PreRender(bIsVisibleInMainViewport);
}
void CRotaryWingAircraft::CacheWindowBones()
{
const crSkeletonData* pSkelData = GetVehicleModelInfo()->GetFragType()->GetCommonDrawable()->GetSkeletonData();
if (pSkelData)
{
u32 index = 0;
const char * boneNames[] = {
"window_lm",
"window_lr",
"window_rr",
"window_rm",
};
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;
}
void CRotaryWingAircraft::PreRender2(const bool bIsVisibleInMainViewport)
{
// deal with searchlights before CAutomobile::PreRender2() call so that we can decide
// if the light should be added or not. The light is finally added in CSearchLight:PostPreRender call
if( (GetStatus() != STATUS_WRECKED) && (true == m_nVehicleFlags.bEngineOn) && GetSearchLight() REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive()))
{
bool bHasPriority = HasSearchLightPriority() || (GetDriver() && GetDriver()->IsPlayer());
GetSearchLight()->SetHasPriority(bHasPriority);
if (bHasPriority)
{
// Make sure no one gets priority after we have taken it.
ms_fBestSearchLightScore = 1.0f;
// Note that we had control of the searchlight last frame to help deal with rapid switching.
m_bHadSearchLightLastFrame = true;
}
}
CAutomobile::PreRender2(bIsVisibleInMainViewport);
const bool bVehicleSuperVolito = (GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_SUPER_VOLITO.GetName().GetHash()
|| GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_SUPER_VOLITO2.GetName().GetHash());
const bool bVehicleIsSwift2 = ( GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_SWIFT2.GetName().GetHash()
|| bVehicleSuperVolito
|| GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_VOLATUS.GetName().GetHash());
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
const Vector3& camPos = camInterface::GetPos();
const float dist = camPos.Dist(vThisPosition);
float fadeDist = rage::Clamp((dist - GetLightsCutoffDistanceTweak())/HeliLight_FadeDistance,0.0f,1.0f);
if( (GetStatus() != STATUS_WRECKED) && (true == m_nVehicleFlags.bEngineOn) )
{
#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;
const bool isAnnihilator = (GetModelIndex()==MI_HELI_POLICE_2);
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
const float fade = (0.5f - fadeDist) * 2.0f;
const int vehTimeFlags = 0xF8003F;
const float dayLightFade = Lights::CalculateTimeFade(vehTimeFlags);
#if ENABLE_FRAG_OPTIMIZATION
DoInteriorLightEffect(VEH_INTERIORLIGHT, fade*dayLightFade, pModelInfo, GetInteriorLocation());
DoInteriorLightEffect(VEH_ENGINE, fade*dayLightFade, pModelInfo, GetInteriorLocation(), true);
#else
DoInteriorLightEffect(VEH_INTERIORLIGHT, fade*dayLightFade, pModelInfo, GetSkeleton(), GetInteriorLocation());
DoInteriorLightEffect(VEH_ENGINE, fade*dayLightFade, pModelInfo, GetSkeleton(), GetInteriorLocation(), true);
#endif
extern ConfigVehiclePositionLightSettings g_HeliPosLights;
extern ConfigVehicleWhiteLightSettings g_HeliWhiteHeadLights;
extern ConfigVehicleWhiteLightSettings g_HeliWhiteTailLights;
bool addBlinkLights = true;
if (bVehicleIsSwift2) { addBlinkLights = !IsInsideVehicleModeEnabled(); }
if(bVehicleSuperVolito && (camInterface::IsRenderedCameraInsideVehicle() || (IsEnteringInsideOrExiting() && camInterface::IsRenderingFirstPersonCamera())))
{
blinkLight = blinkLight2 = false;
}
if (addBlinkLights)
{
if( blinkLight ) DoPosLightEffects (VEH_SIREN_1, g_HeliPosLights, true,isAnnihilator,fadeDist);
if( blinkLight2 ) DoPosLightEffects (VEH_SIREN_2, g_HeliPosLights,false,isAnnihilator,fadeDist);
if( blinkLight3 && ( (m_nTailBoomGroup == -1) || (m_nTailBoomGroup != -1 && false == GetVehicleFragInst()->GetGroupBroken(m_nTailBoomGroup) ) ) )
{
DoWhiteLightEffects (VEH_SIREN_3, g_HeliWhiteTailLights, false,fadeDist);
}
if (blinkLight4 ) DoWhiteLightEffects (VEH_SIREN_4, g_HeliWhiteHeadLights, true,fadeDist);
}
}
// Special swift2 lights
if ((GetStatus() != STATUS_WRECKED) && bVehicleIsSwift2)
{
extern ConfigLightSettings g_HeliSwift2Cabin;
extern ConfigLightSettings g_PlaneLuxe2CabinTV;
extern ConfigLightSettings g_PlaneLuxe2CabinWindow;
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
bool enteringInsideExitingAVehicle = IsEnteringInsideOrExiting();
const bool enableHD = (enteringInsideExitingAVehicle && camInterface::IsRenderingFirstPersonCamera());
const float distFade = 1.0f - fadeDist;
if ((camInterface::IsRenderedCameraInsideVehicle() || enableHD) BANK_ONLY( || camInterface::GetDebugDirector().IsFreeCamActive()))
{
for (u32 l = VEH_SIREN_6; l <= VEH_SIREN_9; l++ )
{
AddLight(LIGHT_TYPE_SPOT, pModelInfo->GetBoneIndex(l), g_HeliSwift2Cabin, distFade);
}
AddLight(LIGHT_TYPE_SPOT, pModelInfo->GetBoneIndex(VEH_SIREN_12), g_PlaneLuxe2CabinTV, distFade);
if (!m_windowBoneCached)
{
CacheWindowBones();
}
for (u32 l = 0; l < NELEM(m_windowBoneIndices); l++ )
{
AddLight(LIGHT_TYPE_SPOT, m_windowBoneIndices[l], g_PlaneLuxe2CabinWindow, distFade);
}
}
else
{
for (u32 l = VEH_SIREN_6; l <= VEH_SIREN_9; l++ )
{
AddLight(LIGHT_TYPE_SPOT, pModelInfo->GetBoneIndex(l), g_HeliSwift2Cabin, distFade);
}
}
}
}
// Return true if we have the best (valid) helicopter searchlight priority score.
bool CRotaryWingAircraft::HasSearchLightPriority()
{
//Returning last state if game is paused as no state changes when game is paused
if(fwTimer::IsUserPaused() || fwTimer::IsGamePaused())
{
return m_bHadSearchLightLastFrame;
}
else
{
ms_bNeedToResetSearchLightScoring = true;
if (m_fSearchLightScore > 0.0f)
{
// Our score was positive, so invalid.
return false;
}
else
{
// If we have the best search light score, then we won.
return abs(ms_fBestSearchLightScore - m_fSearchLightScore) < VERY_SMALL_FLOAT;
}
}
}
// Use a set of heuristics to determine the helicopter searchlight priority score.
void CRotaryWingAircraft::ProcessSearchLightScoring()
{
// Reset our search light score. Positive scores are invalid.
m_fSearchLightScore = 1.0f;
//If we are the first to ProcessSearchLightScoring, then reset the global score too.
if (ms_bNeedToResetSearchLightScoring)
{
ResetSearchLightScores();
ms_bNeedToResetSearchLightScoring = false;
}
CSearchLight* pSearchLight = GetSearchLight();
if (pSearchLight && GetSearchLightTarget())
{
// Higher negative scores are better.
float fBaseScore = pSearchLight->GetLightOn() && pSearchLight->GetLightBrightness() > 0.0f ? -1.0f : 1.0f;
// Give a slight preference to heli's who had search light priority last time.
if (m_bHadSearchLightLastFrame)
{
fBaseScore *= ms_fPreviousSearchLightOwnerBonus;
}
// Give a slight preference to heli's whose search light is not blocked by anything
if(!pSearchLight->GetSearchLightBlocked())
{
fBaseScore *= ms_fUnblockedSearchLightBonus;
}
// Scale the base score with distance.
float fDist = Dist(GetSearchLightTarget()->GetTransform().GetPosition(), GetTransform().GetPosition()).Getf();
m_fSearchLightScore = fBaseScore * fDist;
if (m_fSearchLightScore < 0.0f && (m_fSearchLightScore >= ms_fBestSearchLightScore || ms_fBestSearchLightScore > 0.0f))
{
// We have the best score so far.
ms_fBestSearchLightScore = m_fSearchLightScore;
}
}
m_bHadSearchLightLastFrame = false;
}
void CRotaryWingAircraft::ApplyDeformationToBones(const void* basePtr)
{
CAutomobile::ApplyDeformationToBones(basePtr);
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellers[i].ApplyDeformation(this,basePtr);
}
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : Fix
// PURPOSE : Revert all damage back to OK
///////////////////////////////////////////////////////////////////////////////////
void CRotaryWingAircraft::Fix(bool resetFrag, bool allowNetwork)
{
CAutomobile::Fix(resetFrag, allowNetwork);
m_fMainRotorHealth = VEH_DAMAGE_HEALTH_STD;
m_fRearRotorHealth = VEH_DAMAGE_HEALTH_STD;
m_fTailBoomHealth = VEH_DAMAGE_HEALTH_STD;
m_fMainRotorBrokenOffSmokeLifeTime = 0.0f;
m_fRearRotorBrokenOffSmokeLifeTime = 0.0f;
fragInst* pFragInst = GetVehicleFragInst();
if(m_nTailBoomGroup > -1 && pFragInst)
{
fragTypeGroup* pTailGroup = pFragInst->GetTypePhysics()->GetAllGroups()[m_nTailBoomGroup];
int nFirstChild = pTailGroup->GetChildFragmentIndex();
int nNumChildren = pTailGroup->GetNumChildren();
// make sure this component doesn't break off until we want it to
for(int nChild=nFirstChild; nChild < nFirstChild + nNumChildren; nChild++)
{
((fragInstGta*)pFragInst)->SetDontBreakFlag(BIT(nChild));
}
}
for(int i =0; i< m_iNumPropellers; i++)
{
m_propellers[i].Fix();
}
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : BlowUpCar
// PURPOSE : Does everything needed to destroy a heli
///////////////////////////////////////////////////////////////////////////////////
void CRotaryWingAircraft::BlowUpCar( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion )
{
#if __DEV
if (gbStopVehiclesExploding)
{
return;
}
#endif
/// don't damage if this flag is set (usually during a cutscene)
if (m_nPhysicalFlags.bNotDamagedByAnything)
{
return;
}
if (GetStatus() == STATUS_WRECKED)
{
return; // Don't blow cars up a 2nd time
}
bool blowUpInstantly = true;
bool blowUpInstantlyOverride = false;
// Check if the caller of this function wants the vehicle to instantly blow up, no matter what (for example, network vehicle blow up script)
if (m_nPhysicalFlags.bExplodeInstantlyWhenChecked == true)
{
blowUpInstantlyOverride = true;
}
// set the plane/heli to go out of control if not already
if (GetStatus()==STATUS_ABANDONED && !blowUpInstantlyOverride)
{
blowUpInstantly = false;
}
// if heli is in the air, crash and burn, otherwise just blow up now
else if(HasContactWheels()==false && fwRandom::GetRandomNumberInRange(0.0f, 100.0f)<50.0f && !blowUpInstantlyOverride)
{
blowUpInstantly = false;
}
if(blowUpInstantly && GetStatus() == STATUS_PLAYER && GetDriver() && GetDriver()->IsLocalPlayer())
{
audNorthAudioEngine::NotifyLocalPlayerPlaneCrashed();
}
if ( m_fRearRotorHealth <= 0.0f && m_fTailBoomHealth <= 0.0f )// If the rear rotors are broken off then blow up instantly
{
blowUpInstantly = true;
}
if(weaponHash == WEAPONTYPE_EXPLOSION)
{
blowUpInstantly = true;
}
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(weaponHash);
if(pWeaponInfo && pWeaponInfo->GetDamageType() == DAMAGE_TYPE_EXPLOSIVE)
{
blowUpInstantly = true;
}
if(!bAddExplosion && IsInAir() && !blowUpInstantly)
{
audHeliAudioEntity * audio ( (audHeliAudioEntity*)GetVehicleAudioEntity());
if(audio)
{
audio->TriggerGoingDownSound();
}
}
else if(blowUpInstantly)
{
// Heli is completely blown up, stop the going down sound
audHeliAudioEntity * audio ( (audHeliAudioEntity*)GetVehicleAudioEntity());
if(audio)
{
audio->StopGoingDownSound();
}
}
// set the engine temp super high so we get nice sounds as the chassis cools down
m_EngineTemperature = MAX_ENGINE_TEMPERATURE;
// Everything now goes through a crash task
// Create a crash task if one doesn't already exist
if (!IsNetworkClone())
{
#if !__NO_OUTPUT
if (NetworkInterface::IsGameInProgress() && GetNetworkObject())
{
netDebug1("**************************************************************************************************");
netDebug1("HELI BLOWN UP: %s", GetNetworkObject()->GetLogName());
sysStack::PrintStackTrace();
netDebug1("**************************************************************************************************");
}
#endif // !__NO_OUTPUT
KillPedsInVehicle(pCulprit, weaponHash);
KillPedsGettingInVehicle(pCulprit);
aiTask *pActiveTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH);
if(!pActiveTask)
{
CTaskVehicleCrash *pCarTask = rage_new CTaskVehicleCrash( pCulprit, 0, weaponHash );
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;
}
else if(blowUpInstantly)
{
// Blow up heli if we've been told to blow up again instantly
CTaskVehicleCrash *pCrashTask = static_cast<CTaskVehicleCrash *>(pActiveTask);
pCrashTask->SetCrashFlag(CTaskVehicleCrash::CF_HitByConsecutiveExplosion, true);
}
}
//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 HELI_BONE_COUNT_TO_DEFORM = 30;
const eHierarchyId ExtraHeliBones[HELI_BONE_COUNT_TO_DEFORM] =
{
VEH_CHASSIS, VEH_CHASSIS, HELI_TAIL, HELI_TAIL, //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, VEH_CHASSIS, VEH_CHASSIS,
VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, VEH_CHASSIS, HELI_TAIL, HELI_TAIL, HELI_TAIL, HELI_TAIL, HELI_TAIL, HELI_TAIL,
HELI_ROTOR_MAIN, HELI_ROTOR_MAIN, HELI_ROTOR_MAIN, HELI_ROTOR_REAR, HELI_RUDDER, VEH_CHASSIS, HELI_ELEVATORS
};
const eHierarchyId* CRotaryWingAircraft::GetExtraBonesToDeform(int& extraBoneCount)
{
extraBoneCount = HELI_BONE_COUNT_TO_DEFORM;
return ExtraHeliBones;
}
//////////////////////////////////////////////////////////////////////////
// Return true if the props are hit
//////////////////////////////////////////////////////////////////////////
bool CRotaryWingAircraft::ApplyDamageToPropellers( CEntity* inflictor, float fApplyDamage, int nComponent )
{
// See if the main or rear rotors are damaged.
if(GetVehicleFragInst() && m_nVehicleFlags.bCanBeVisiblyDamaged)
{
bool bHitPropeller = false;;
if(m_propellerCollisions[Rotor_Rear].GetFragChild() == nComponent)
{
bHitPropeller = true;
}
else if(m_propellerCollisions[Rotor_Rear].GetFragGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[m_propellerCollisions[Rotor_Rear].GetFragGroup()];
if(pGroup->GetNumChildren() > 1)
{
for(int iChild = 1; iChild < pGroup->GetNumChildren(); iChild++)
{
if(m_propellerCollisions[Rotor_Rear].GetFragChild() + iChild == nComponent)
{
bHitPropeller = true;
break;
}
}
}
}
if(bHitPropeller)
{
static dev_float sfRearRotorDamageMult = 7.5f;
m_fRearRotorHealth -= (fApplyDamage * sfRearRotorDamageMult * m_fRearRotorHealthDamageScale);
if(m_fRearRotorHealth <= 0.0f)
{
m_fRearRotorHealth = 0.0f;
BreakOffRearRotor();
StartCrashingBehavior(inflictor);
if (inflictor && inflictor->GetIsTypePed())
{
m_bHeliRotorDestroyedByPed = true;
}
}
return TRUE;
}
if(m_propellerCollisions[Rotor_Main].GetFragChild() == nComponent)
{
bHitPropeller = true;
}
else if(m_propellerCollisions[Rotor_Main].GetFragGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[m_propellerCollisions[Rotor_Main].GetFragGroup()];
if(pGroup->GetNumChildren() > 1)
{
for(int iChild = 1; iChild < pGroup->GetNumChildren(); iChild++)
{
if(m_propellerCollisions[Rotor_Main].GetFragChild() + iChild == nComponent)
{
bHitPropeller = true;
break;
}
}
}
}
if(bHitPropeller)
{
m_fMainRotorHealth -= fApplyDamage * m_fMainRotorHealthDamageScale;
if(m_fMainRotorHealth <= 0.0f)
{
m_fMainRotorHealth = 0.0f;
BreakOffMainRotor();
if (inflictor && inflictor->GetIsTypePed())
{
m_bHeliRotorDestroyedByPed = true;
}
}
return TRUE;
}
}
return FALSE;
}
bool CRotaryWingAircraft::GetHeliRotorDestroyedByPed( )
{
if (m_bHeliRotorDestroyedByPed)
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;
}
//////////////////////////////////////////////////////////////////////////
//This is called by the crash task
//////////////////////////////////////////////////////////////////////////
void CRotaryWingAircraft::FinishBlowingUpVehicle(CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool ASSERT_ONLY(bNetCall), u32 weaponHash, bool bDelayedExplosion)
{
//This should only be called from the crash task
Assert(IsNetworkClone() || GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH));
CVehicle::BlowUpCar(pCulprit);
#if __DEV
if (gbStopVehiclesExploding)
{
return;
}
#endif
/// don't damage if this flag is set (usually during a cutscene)
if (m_nPhysicalFlags.bNotDamagedByAnything)
{
return;
}
if (GetStatus() == STATUS_WRECKED)
{
return; // Don't blow cars up a 2nd time
}
audVehicleCollisionAudio * collisionAudio = GetAudioEntity() ? &(((audVehicleAudioEntity*)GetAudioEntity())->GetCollisionAudio()) : NULL;
if(collisionAudio)
{
collisionAudio->WreckVehicle();
}
// we can't blow up helis 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);
// break the tail and main rotor off heli's (do this before BlowUpCarParts because we want the main rotor to disapear, not break off)
if (m_fRearRotorHealth > 0.0f)
{
BreakOffRearRotor();
}
BreakOffTailBoom();
if(m_fMainRotorHealth > 0.0f)
{
BreakOffMainRotor();
}
m_nPhysicalFlags.bRenderScorched = TRUE;
SetTimeOfDestruction();
SetIsWrecked();
// knock bits off the car
GetVehicleDamage()->BlowUpCarParts(pCulprit);
// Break lights, windows and sirens
GetVehicleDamage()->BlowUpVehicleParts(pCulprit);
// Switch off the engine. (For sound purposes)
SwitchEngineOff(false);
m_OverrideLights = NO_CAR_LIGHT_OVERRIDE;
m_nVehicleFlags.bLightsOn = FALSE;
TurnSirenOn(FALSE);
m_nAutomobileFlags.bTaxiLight = FALSE;
g_decalMan.Remove(this);
//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;
}
//Set the destruction information.
SetDestructionInfo(pCulprit, weaponHash);
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 = GetPosition();
KillPedsInVehicle(pCulprit, weaponHash);
KillPedsGettingInVehicle(pCulprit);
// break the tail and main rotor off heli's (do this before BlowUpCarParts because we want the main rotor to disapear, not break off)
if(m_fRearRotorHealth > 0.0f)
{
BreakOffRearRotor();
}
BreakOffTailBoom(GPU_VEHICLE_DAMAGE_ONLY(Break_Off_Tail_Boom_Pending_Bound_Update));
if(m_fMainRotorHealth > 0.0f)
{
BreakOffMainRotor();
}
// knock bits off the car
GetVehicleDamage()->BlowUpCarParts(pCulprit, CVehicleDamage::Break_Off_Car_Parts_Pending_Bound_Update);
// Break lights, windows and sirens
GetVehicleDamage()->BlowUpVehicleParts(pCulprit);
// 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);
}
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);
}
void CRotaryWingAircraft::Teleport(const Vector3& vecSetCoors, float fSetHeading/* =-10.0f */, bool bCalledByPedTask/* =false */, bool bTriggerPortalRescan, bool bCalledByPedTask2/* =false */, bool bWarp/* =true */, bool UNUSED_PARAM(bKeepRagdoll), bool UNUSED_PARAM(bResetPlants))
{
// Not sure why we need to reset the rotor blades but this might need to be re-added.
// m_fMainRotorSpeed = 0.0f;
CAutomobile::Teleport(vecSetCoors,fSetHeading,bCalledByPedTask,bTriggerPortalRescan,bCalledByPedTask2, bWarp);
}
void CRotaryWingAircraft::SetDampingForFlight(float fLinearDampingMult)
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if(pFlyingHandling == NULL)
return;
phArchetypeDamp* pArchetypeDamp = static_cast<phArchetypeDamp*>(GetVehicleFragInst()->GetArchetype());
ScalarV dampingMultiplier = ScalarV(V_ONE);
if(const CPed* pDriver = GetDriver())
{
if(pDriver->IsPlayer())
{
StatId stat = STAT_FLYING_ABILITY.GetStatId();
float fFlyingStatValue = rage::Clamp(static_cast<float>(StatsInterface::GetIntStat(stat)) / 100.0f, 0.0f, 1.0f);
const CPlayerInfo::sPlayerStatInfo& driverStatInfo = CPlayerInfo::GetPlayerStatInfoForPed(*pDriver);
float fMinHeliDamping = driverStatInfo.m_MinHeliDamping;
float fMaxHeliDamping = driverStatInfo.m_MaxHeliDamping;
// Swapped the max damping and min damping, as better skills should drive faster, which means less damping
dampingMultiplier = ScalarVFromF32(((1.0f - fFlyingStatValue) * fMaxHeliDamping + fFlyingStatValue * fMinHeliDamping)/100.0f);
float airDragMult = pDriver->GetPlayerInfo()->m_fForceAirDragMult;
if( airDragMult > 0.0f)
{
dampingMultiplier *= ScalarV( airDragMult );
}
}
}
ScalarV linearDampingMultiplier = Scale(dampingMultiplier,ScalarVFromF32(fLinearDampingMult));
if(const phCollider* pCollider = GetCollider())
{
if(pCollider->IsArticulated())
{
linearDampingMultiplier = Scale(linearDampingMultiplier,ScalarV(V_HALF));
}
}
const Vec3V handlingLinearVelocityDamping = Vec3VFromF32(pFlyingHandling->m_fMoveRes);
Vec3V linearVelocityDamping = Scale(handlingLinearVelocityDamping,linearDampingMultiplier);
if( GetDriver() &&
GetDriver()->IsAPlayerPed() )
{
float fSlipStreamEffect = GetSlipStreamEffect();
linearVelocityDamping -= (linearVelocityDamping * ScalarV( fSlipStreamEffect ) );
}
pArchetypeDamp->ActivateDamping(phArchetypeDamp::LINEAR_V, RCC_VECTOR3(linearVelocityDamping));
// Only modify the angular tuning if it's non-zero
const Vec3V handlingLinearAngularVelocityDamping = pFlyingHandling->m_vecTurnRes;
const Vec3V linearAngularVelocityDamping = SelectFT(IsZero(handlingLinearAngularVelocityDamping), Scale(handlingLinearAngularVelocityDamping,linearDampingMultiplier), RCC_VEC3V(pArchetypeDamp->GetDampingConstant(phArchetypeDamp::ANGULAR_V)));
pArchetypeDamp->ActivateDamping(phArchetypeDamp::ANGULAR_V, RCC_VECTOR3(linearAngularVelocityDamping));
const Vec3V handlingQuadraticAngularVelocityDamping = pFlyingHandling->m_vecSpeedRes;
const Vec3V quadraticAngularVelocityDamping = SelectFT(IsZero(handlingQuadraticAngularVelocityDamping), Scale(handlingQuadraticAngularVelocityDamping,dampingMultiplier), RCC_VEC3V(pArchetypeDamp->GetDampingConstant(phArchetypeDamp::ANGULAR_V2)));
pArchetypeDamp->ActivateDamping(phArchetypeDamp::ANGULAR_V2, RCC_VECTOR3(quadraticAngularVelocityDamping));
}
dev_float dfOutOfControlRearRotorHealthRatio = 0.5f;
void CRotaryWingAircraft::ProcessFlightHandling(float fTimeStep)
{
if (IsFullThrottleActive())
{
SetThrottleControl(5);
SetMainRotorSpeed(MAX_ROT_SPEED_HELI_BLADES);
if (!GetDriver() || !GetDriver()->IsPlayer())
{
SetYawControl(0);
SetPitchControl(0);
SetRollControl(0);
}
}
m_bIsInAir = IsInAir(false);
SetDampingForFlight();
if(!m_bIsInAir && HasContactWheels())
{
m_fLastWheelContactTime = fwTimer::GetTimeInMilliseconds() * 0.0001f;
}
if(m_fMainRotorSpeed > 0.0f && m_fMainRotorHealth > 0.0f)
{
float fOldYawControl = m_fYawControl;
float fOldPitchControl = m_fPitchControl;
float fOldRollControl = m_fRollControl;
float fOldThrottleControl = m_fThrottleControl;
// Make sure the possbilyTouchesWater flag is up to date before acting on it...
if( m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate &&
m_nFlags.bPossiblyTouchesWater &&
(GetTransform().GetPosition().GetZf() < 10.0f || GetIsInWater()))
{
// test centre of heli, and pull down into water
float fWaterLevel;
const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 vecTestPoint = vThisPosition;
Assert(pHandling);
Assert(pHandling->GetFlyingHandlingData());
CFlyingHandlingData* pFlightHandling = pHandling->GetFlyingHandlingData();
if(m_Buoyancy.GetWaterLevelIncludingRivers(vecTestPoint, &fWaterLevel, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL)
&& m_Buoyancy.GetSubmergedLevel()>pFlightHandling->m_fSubmergeLevelToPullHeliUnderwater)
{
// pull heli down into the water
float fPullDown = fWaterLevel - (vThisPosition.z + GetBoundingBoxMin().z);
fPullDown = rage::Min(fPullDown / rage::Abs(GetBoundingBoxMin().z), 1.5f);
ApplyInternalForceCg(fPullDown * sfHeliDrownPull * GetMass() * ZAXIS);
}
// test if the main rotor is hitting the water
if(m_propellerCollisions[Rotor_Main].GetFragChild() > -1)
{
phBound* pBoundRotor = ((phBoundComposite*)GetVehicleFragInst()->GetArchetype()->GetBound())->GetBound(m_propellerCollisions[Rotor_Main].GetFragChild());
if(pBoundRotor)
{
Matrix34 matRotor = RCC_MATRIX34(((phBoundComposite*)GetVehicleFragInst()->GetArchetype()->GetBound())->GetCurrentMatrix(m_propellerCollisions[Rotor_Main].GetFragChild()));
matRotor.RotateLocalZ(fwRandom::GetRandomNumberInRange(0.0f, HALF_PI));
Matrix34 m = MAT34V_TO_MATRIX34(GetMatrix());
matRotor.Dot(m);
Vector3 vecSamplePoints[4];
Vector3 vecSplashDirn[4];
vecSamplePoints[0].Set(0.0f, pBoundRotor->GetBoundingBoxMax().GetYf(), 0.0f);
vecSamplePoints[1].Set(-pBoundRotor->GetBoundingBoxMax().GetXf(), 0.0f, 0.0f);
vecSamplePoints[2].Set(0.0f, -pBoundRotor->GetBoundingBoxMax().GetYf(), 0.0f);
vecSamplePoints[3].Set(pBoundRotor->GetBoundingBoxMax().GetXf(), 0.0f, 0.0f);
Vector3 vecRight(VEC3V_TO_VECTOR3(GetTransform().GetA()));
Vector3 vecForward(VEC3V_TO_VECTOR3(GetTransform().GetB()));
vecSplashDirn[0].Set(-vecRight + ZAXIS);
vecSplashDirn[1].Set(-vecForward + ZAXIS);
vecSplashDirn[2].Set(vecRight + ZAXIS);
vecSplashDirn[3].Set(vecForward + ZAXIS);
for(int nSample=0; nSample < 4; nSample++)
{
matRotor.Transform(vecSamplePoints[nSample], vecTestPoint);
if(m_Buoyancy.GetWaterLevelIncludingRivers(vecTestPoint, &fWaterLevel, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL) && fWaterLevel > vecTestPoint.z)
{
vecTestPoint.z = fWaterLevel;
if ((fwTimer::GetSystemFrameCount()&3)==0)
{
g_vfxWater.TriggerPtFxSplashHeliBlade(this, RCC_VEC3V(vecTestPoint));
}
GetVehicleAudioEntity()->TriggerHeliRotorSplash();
// rotor take damage
if(m_fMainRotorHealth > 0.0f && m_nVehicleFlags.bCanBeVisiblyDamaged)
{
if (!IsNetworkClone())
{
float fDamage = sfInWaterRotorDamage * fwTimer::GetTimeStep();
m_fMainRotorHealth -= fDamage * m_fMainRotorHealthDamageScale;
if(m_fMainRotorHealth <= 0.0f)
{
BreakOffMainRotor();
}
}
if(m_fMainRotorHealth > 0.0f)
m_fMainRotorSpeed = rage::Min(MIN_ROT_SPEED_HELI_CONTROL + 0.01f, m_fMainRotorSpeed);
}
}
}
}
}
if(m_propellerCollisions[Rotor_Rear].GetFragChild() > -1 && m_fRearRotorHealth > 0.0f)
{
Matrix34 matRearRotor = RCC_MATRIX34(((phBoundComposite*)GetVehicleFragInst()->GetArchetype()->GetBound())->GetCurrentMatrix(m_propellerCollisions[Rotor_Rear].GetFragChild()));
if(matRearRotor.a.IsNonZero())
{
vecTestPoint = matRearRotor.d;
vecTestPoint = TransformIntoWorldSpace(vecTestPoint);
if(m_Buoyancy.GetWaterLevelIncludingRivers(vecTestPoint, &fWaterLevel, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL) && fWaterLevel > vecTestPoint.z)
{
// rotor take damage
float fDamage = sfInWaterRotorDamage * fwTimer::GetTimeStep();
m_fRearRotorHealth -= fDamage * m_fRearRotorHealthDamageScale;
if(m_fRearRotorHealth <= 0.0f)
{
// rotor destruction effect
BreakOffRearRotor();
StartCrashingBehavior();
}
}
}
}
}
static dev_float sfLeaveGroundMaxTime = 5.0f;
float fCurrentTime = fwTimer::GetTimeInMilliseconds() * 0.0001f;
float fLeaveGroundTimeRatio = (fCurrentTime - m_fLastWheelContactTime) / sfLeaveGroundMaxTime;
fLeaveGroundTimeRatio = Clamp(fLeaveGroundTimeRatio, 0.0f, 1.0f);
bool bProcessDamageControl = true;
// If we've lost the backend start spinning round
if((m_nTailBoomGroup > -1 && GetVehicleFragInst()->GetGroupBroken(m_nTailBoomGroup)) || GetStatus() == STATUS_OUT_OF_CONTROL)
{
bProcessDamageControl = false;
if(fwTimer::GetTimeInMilliseconds() > m_uHeliOutOfControlRecoverStart)
{
if(m_bHeliOutOfControlRecovering)
{
m_bHeliOutOfControlRecovering = false;
if(m_iHeliOutOfControlRecoverPeriods < siHeliOutOfControlRecoveryPeriodsMax-1)
{
m_fHeliOutOfControlRoll = fwRandom::GetRandomNumberInRange(sfHeliTailLostRoll, sfHeliOutOfControlMaxRoll) * sfHeliOutOfControlRollAfterRecoveryMult;
m_fHeliOutOfControlThrottle = fwRandom::GetRandomNumberInRange(sfHeliOutOfControlThrottleDropAfterRecoveryMin, sfHeliOutOfControlThrottleDropAfterRecoveryMax);
m_uHeliOutOfControlRecoverStart = fwTimer::GetTimeInMilliseconds() + fwRandom::GetRandomNumberInRange(siHeliOutOfControlFailPeriodsMin, siHeliOutOfControlFailPeriodsMax);
}
}
else
{
if(m_iHeliOutOfControlRecoverPeriods > 0)
{
m_uHeliOutOfControlRecoverStart = fwTimer::GetTimeInMilliseconds() + fwRandom::GetRandomNumberInRange(siHeliOutOfControlRecoverPeriodsMin, siHeliOutOfControlRecoverPeriodsMax);
m_bHeliOutOfControlRecovering = true;
m_iHeliOutOfControlRecoverPeriods--;
if(m_iHeliOutOfControlRecoverPeriods < 0)
m_iHeliOutOfControlRecoverPeriods = 0;
}
}
}
if(m_iHeliOutOfControlRecoverPeriods < siHeliOutOfControlRecoveryPeriodsMax && !m_bHeliOutOfControlRecovering)// first recovering period do nothing.
{
m_fHeliOutOfControlYaw += Sign(m_fHeliOutOfControlYaw) * fwRandom::GetRandomNumberInRange(sfHeliOutOfControlYawControlAdjustmentNeg, sfHeliOutOfControlYawControlAdjustmentPos) * fTimeStep;
m_fHeliOutOfControlYaw = Clamp(m_fHeliOutOfControlYaw, -sfHeliOutOfControlMaxYawControl, sfHeliOutOfControlMaxYawControl);
if(m_bIsInAir)
{
m_fYawControl = m_fHeliOutOfControlYaw * fLeaveGroundTimeRatio;
m_fPitchControl = m_fHeliOutOfControlPitch * fLeaveGroundTimeRatio;
m_fRollControl = m_fHeliOutOfControlRoll * fLeaveGroundTimeRatio;
bProcessDamageControl = true;
}
// Make it spin by a little amount
m_fRearRotorHealth = Min(m_fRearRotorHealth, VEH_DAMAGE_HEALTH_STD * dfOutOfControlRearRotorHealthRatio);
m_fHeliOutOfControlThrottle += fwRandom::GetRandomNumberInRange(sfHeliOutOfControlThrottleControlAdjustmentNeg, sfHeliOutOfControlThrottleControlAdjustmentPos) * fTimeStep;
m_fHeliOutOfControlThrottle = Clamp(m_fHeliOutOfControlThrottle, sfHeliOutOfControlMinThrottleControl, 2.0f);
SetThrottleControl(m_fHeliOutOfControlThrottle);
}
}
if(m_bIsInAir && bProcessDamageControl) // helicopter is under control, but still might buffeting due to the damage
{
// Early out in the common case where the engine has taken no damage
if(m_fRearRotorHealth < VEH_DAMAGE_HEALTH_STD)
{
float fHeliRearRotorYaw = m_fRearRotorHealth <= 0.0f ? sfHeliRearRotorLostYaw : sfHeliRearRotorDamageYaw;
float fHeliRearRotorRoll = m_fRearRotorHealth <= 0.0f ? sfHeliRearRotorLostRoll : sfHeliRearRotorDamageRoll;
float fHeliRearRotorDamageRate = 1.0f - m_fRearRotorHealth / VEH_DAMAGE_HEALTH_STD;
fHeliRearRotorDamageRate = Clamp(fHeliRearRotorDamageRate, 0.0f, 1.0f);
if(m_fRollControl < 2.0f)
m_fRollControl += m_fMainRotorSpeed*fHeliRearRotorRoll*fHeliRearRotorDamageRate * fLeaveGroundTimeRatio;
m_fYawControl += m_fMainRotorSpeed*fHeliRearRotorYaw*fHeliRearRotorDamageRate * fLeaveGroundTimeRatio;
if(m_fRearRotorHealth <= 0.0f)
{
if(GetStatus() != STATUS_OUT_OF_CONTROL)
{
SetThrottleControl(sfHeliLostRearRotorThrottle);
}
audHeliAudioEntity * audio ( (audHeliAudioEntity*)GetVehicleAudioEntity());
if(audio)
{
audio->TriggerGoingDownSound();
}
}
}
if(m_fMainRotorHealth < VEH_DAMAGE_HEALTH_STD)
{
float fHeliMainRotorYaw = m_fMainRotorHealth <= 0.0f ? sfHeliMainRotorLostYaw : sfHeliMainRotorDamageYaw;
float fHeliMainRotorRoll = m_fMainRotorHealth <= 0.0f ? sfHeliMainRotorLostRoll : sfHeliMainRotorDamageRoll;
float fHeliMainRotorDamageRate = 1.0f - m_fMainRotorHealth / VEH_DAMAGE_HEALTH_STD;
fHeliMainRotorDamageRate = Clamp(fHeliMainRotorDamageRate, 0.0f, 1.0f);
if(m_fRollControl < 2.0f)
m_fRollControl += m_fMainRotorSpeed*fHeliMainRotorRoll*fHeliMainRotorDamageRate * fLeaveGroundTimeRatio;
m_fYawControl += m_fMainRotorSpeed*fHeliMainRotorYaw*fHeliMainRotorDamageRate * fLeaveGroundTimeRatio;
}
}
// reset control variables for clones - they should use values synced over the network
if(IsNetworkClone())
{
m_fYawControl = fOldYawControl;
m_fPitchControl = fOldPitchControl;
m_fRollControl = fOldRollControl;
m_fThrottleControl = fOldThrottleControl;
}
ProcessFlightModel(fTimeStep);
}
m_bDisableTurbulanceThisFrame = false;
}
void CRotaryWingAircraft::ModifyControlsBasedOnFlyingStats( CPed* pPilot, CFlyingHandlingData* pFlyingHandling, float &fYawControl, float &fPitchControl, float &fRollControl, float &fThrottleControl, float fTimeStep )
{
Assert( pFlyingHandling );
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 fHeliControlAbilityMult = ((1.0f - fFlyingStatValue) * fMinPlaneControlAbility + fFlyingStatValue * fMaxPlaneControlAbility)/100.0f;
fHeliControlAbilityMult = Clamp(fHeliControlAbilityMult/ms_fMaxAbilityToAdjustDifficulty, 0.0f, 1.0f);
// Scale by the vehicle modifier
float fHeliDifficulty = pFlyingHandling->m_fInputSensitivityForDifficulty * fTimeStep;
if( fHeliControlAbilityMult < 1.0f || m_fControlLaggingRateMulti < 1.0f )
{
CPlayerInfo* pPlayerInfo = pPilot->GetPlayerInfo();
physicsFatalAssertf( pPlayerInfo, "Expected a player info!" ); // Assumed to be a player
// Limited controls for inexperienced pilots
float fAbilityMult = Clamp(ms_fHeliControlAbilityControlDampMin + ((ms_fHeliControlAbilityControlDampMax - ms_fHeliControlAbilityControlDampMin) * fHeliControlAbilityMult), 0.0f, 1.0f);
float fYawControlModified = fYawControl;
float fPitchControlModified = fPitchControl;
float fRollControlModified = fRollControl;
float fThrottleControlModified = fThrottleControl;
fYawControlModified *= fAbilityMult;
fPitchControlModified *= fAbilityMult;
fRollControlModified *= fAbilityMult;
// Input lagging for inexperienced pilots
const float fLaggedYawControl = pPlayerInfo->GetLaggedYawControl();
const float fLaggedPitchControl = pPlayerInfo->GetLaggedPitchControl();
const float fLaggedRollControl = pPlayerInfo->GetLaggedRollControl();
float fControlLaggingBlendingRate = (1.0f - fHeliControlAbilityMult) * ms_fHeliControlLaggingMinBlendingRate + fHeliControlAbilityMult * ms_fHeliControlLaggingMaxBlendingRate;
fControlLaggingBlendingRate *= m_fControlLaggingRateMulti;
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;
if( fTimeBetweenRandomControlInputs <= 0.0f )
{
float fAbilityRandomMult = Clamp(ms_fHeliControlAbilityControlRandomMin + ((ms_fHeliControlAbilityControlRandomMax - ms_fHeliControlAbilityControlRandomMin) * (1.0f - fHeliControlAbilityMult)), 0.0f, 1.0f);
fRandomControlYaw = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult, fAbilityRandomMult);
fRandomControlPitch = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult, fAbilityRandomMult);
fRandomControlRoll = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult, fAbilityRandomMult);
fRandomControlThrottle = fwRandom::GetRandomNumberInRange(-fAbilityRandomMult, 0.0f);
fTimeBetweenRandomControlInputs = Clamp(ms_fTimeBetweenRandomControlInputsMin + ((ms_fTimeBetweenRandomControlInputsMax - ms_fTimeBetweenRandomControlInputsMin) * (1.0f - fHeliControlAbilityMult)), 0.0f, 1.0f);
}
fYawControlModified += fRandomControlYaw;
fPitchControlModified += fRandomControlPitch;
fRollControlModified += fRandomControlRoll;
fThrottleControlModified += fRandomControlThrottle;
fYawControl += (fYawControlModified - fYawControl) * m_pilotSkillNoiseScalar;
fPitchControl += (fPitchControlModified - fPitchControl) * m_pilotSkillNoiseScalar;
fRollControl += (fRollControlModified - fRollControl) * m_pilotSkillNoiseScalar;
fThrottleControl += (fThrottleControlModified - fThrottleControl) * m_pilotSkillNoiseScalar;
fRandomControlYaw = Lerp(fHeliDifficulty, fRandomControlYaw, 0.0f);
fRandomControlPitch = Lerp(fHeliDifficulty, fRandomControlPitch, 0.0f);
fRandomControlRoll = Lerp(fHeliDifficulty, fRandomControlRoll, 0.0f);
fRandomControlThrottle = Lerp(fHeliDifficulty, 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);
}
}
u32 uAbandonedTimeForCrashing = (u32)(fwTimer::GetMaximumFrameTime() * 2000.0f); // Two frames
float fDeadDriverCrashingHeight = 3.0f;
float fAbandonedCrashingHeight = 5.0f;
void CRotaryWingAircraft::SetIsAbandoned(const CPed *pOriginalDriver)
{
if(GetStatus()==STATUS_WRECKED)
return;
if(GetStatus() != STATUS_ABANDONED)
{
if (!IsNetworkClone())
{
bool bStartCrashing = false;
if( GetSeatManager()->GetNumPlayers() <= 1 && // the SetIsAbandoned is called before removing the driver from the vehicle
pOriginalDriver &&
pOriginalDriver->GetPedIntelligence()->GetTaskManager()->FindTaskByTypeActive(PED_TASK_TREE_PRIMARY, CTaskTypes::TASK_EXIT_VEHICLE_SEAT))
{
CEntity* lastDriver = const_cast<CPed*>(pOriginalDriver);
bStartCrashing = StartCrashingBehavior(lastDriver, true);
}
if(!bStartCrashing)
{
aiTask *pActiveTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_NO_DRIVER, VEHICLE_TASK_PRIORITY_PRIMARY);
if(!pActiveTask)
{
GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY,
rage_new CTaskVehicleNoDriver(CTaskVehicleNoDriver::NO_DRIVER_TYPE_ABANDONED),
VEHICLE_TASK_PRIORITY_PRIMARY,
false);
}
}
}
}
SetStatus(STATUS_ABANDONED);
}
void CRotaryWingAircraft::SetIsOutOfControl()
{
if(GetStatus()==STATUS_WRECKED)
return;
// Use some randomness if the vehicle is set out of control, used when the driver is killed, etc.
if(GetStatus() != STATUS_OUT_OF_CONTROL)
{
m_fHeliOutOfControlYaw = fwRandom::GetRandomNumberInRange(-sfHeliTailLostYaw, sfHeliTailLostYaw);
m_fHeliOutOfControlRoll = fwRandom::GetRandomNumberInRange(sfHeliTailLostRoll, sfHeliOutOfControlMaxRoll);
m_fHeliOutOfControlPitch = fwRandom::GetRandomNumberInRange(-sfHeliOutOfControlPitch, sfHeliOutOfControlPitch);
static dev_float sfRandomMinThrottleWeighting = 0.2f;
m_fHeliOutOfControlThrottle = fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < sfRandomMinThrottleWeighting ? sfHeliOutOfControlMinThrottle : sfHeliOutOfControlMaxThrottle;
m_iHeliOutOfControlRecoverPeriods = siHeliOutOfControlRecoveryPeriodsMax;
m_uHeliOutOfControlRecoverStart = fwTimer::GetTimeInMilliseconds() + fwRandom::GetRandomNumberInRange(siHeliOutOfControlInitialFailPeriodsMin, siHeliOutOfControlInitialFailPeriodsMax);
m_bHeliOutOfControlRecovering = false;
if(m_fRearRotorHealth <= 0.0f) // If the rear rotor broken off, vehicle will be out of control right away
{
m_iHeliOutOfControlRecoverPeriods--;
}
//don't override crash task if we're already crashing
aiTask* pCrashTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH);
if (!IsNetworkClone() && !pCrashTask)
{
sVehicleMissionParams params;
// Just fly to a far away position because we are trying to get away. We should stream out before reaching it
Vector3 vTargetPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
static dev_float sfTargetOffset = 100.0f;
vTargetPos.x += fwRandom::GetRandomNumberInRange(-sfTargetOffset, sfTargetOffset);
vTargetPos.y += fwRandom::GetRandomNumberInRange(-sfTargetOffset, sfTargetOffset);
params.SetTargetPosition(vTargetPos);
params.m_iDrivingFlags = DF_DontTerminateTaskWhenAchieved;
CTaskVehicleGoToHelicopter *pHeliTask = rage_new CTaskVehicleGoToHelicopter(params, 0, -1.0f, 40);
GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pHeliTask, VEHICLE_TASK_PRIORITY_PRIMARY, false );
}
}
SetStatus(STATUS_OUT_OF_CONTROL);
if(IsInAir())
{
audHeliAudioEntity * audio ( (audHeliAudioEntity*)GetVehicleAudioEntity());
if(audio)
{
audio->TriggerGoingDownSound();
}
}
}
bool CRotaryWingAircraft::StartCrashingBehavior(CEntity *pCulprit, bool bIsAbandend)
{
if (!IsNetworkClone())
{
//already crashing
aiTask *pActiveTask = GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH);
if(pActiveTask)
{
return true;
}
bool bShouldCrash = true;
//always crash if no collision, otherwise we'll fall through world
//or no tail boom as then we can't fly anyway
if(IsCollisionLoadedAroundPosition() && !GetIsTailBoomBroken())
{
if(!IsInAir() ||
( pHandling->GetSeaPlaneHandlingData() &&
m_nFlags.bPossiblyTouchesWater ) )
{
bShouldCrash = false;
}
if(GetMainRotorSpeed() < MIN_ROT_SPEED_HELI_CONTROL)
{
bShouldCrash = false;
}
if(bShouldCrash)
{
float fCrashingHeightThreshold = bIsAbandend? fAbandonedCrashingHeight : fDeadDriverCrashingHeight;
Vector3 vStart = VEC3V_TO_VECTOR3(GetVehiclePosition());
Vector3 vEnd = vStart;
vEnd.z += GetBoundingBoxMin().z - fCrashingHeightThreshold;
WorldProbe::CShapeTestHitPoint probeHitPoint;
WorldProbe::CShapeTestResults probeResults(probeHitPoint);
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vStart, vEnd);
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetExcludeEntity(this);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
probeDesc.SetIsDirected(true);
bShouldCrash = !WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
}
}
if(bShouldCrash)
{
CTaskVehicleCrash *pCarTask = rage_new CTaskVehicleCrash(pCulprit);
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);
return true;
}
}
return false;
}
float CRotaryWingAircraft::ms_fHeliControlLaggingMinBlendingRate = 1.0f;
float CRotaryWingAircraft::ms_fHeliControlLaggingMaxBlendingRate = 10.0f;
float CRotaryWingAircraft::ms_fHeliControlAbilityControlDampMin = 0.7f;
float CRotaryWingAircraft::ms_fHeliControlAbilityControlDampMax = 1.0f;
float CRotaryWingAircraft::ms_fHeliControlAbilityControlRandomMin = 0.0f;
#if RSG_PC
float CRotaryWingAircraft::ms_fHeliControlAbilityControlRandomMax = 0.28f;
#else
float CRotaryWingAircraft::ms_fHeliControlAbilityControlRandomMax = 0.56f;
#endif
float CRotaryWingAircraft::ms_fTimeBetweenRandomControlInputsMin = 3.0f;
float CRotaryWingAircraft::ms_fTimeBetweenRandomControlInputsMax = 6.0f;
float CRotaryWingAircraft::ms_fRandomControlLerpDown = 0.995f;
float CRotaryWingAircraft::ms_fMaxAbilityToAdjustDifficulty = 0.6f;
SearchLightInfo CRotaryWingAircraft::ms_SearchLightInfo;
#if __BANK
void CRotaryWingAircraft::InitWidgets()
{
rage::bkBank* bank = BANKMGR.FindBank("Input");
if(AssertVerify(bank))
{
bank->PushGroup("Heli controls");
bank->AddToggle("Visualise Heli Controls",&sbVisualiseHeliControls);
bank->AddSlider("Lagging control min blending rate", &CRotaryWingAircraft::ms_fHeliControlLaggingMinBlendingRate, 0.0f, 100.0f, 0.1f);
bank->AddSlider("Lagging control max blending rate", &CRotaryWingAircraft::ms_fHeliControlLaggingMaxBlendingRate, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fHeliControlAbilityControlDampMin", &CRotaryWingAircraft::ms_fHeliControlAbilityControlDampMin, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fHeliControlAbilityControlDampMax", &CRotaryWingAircraft::ms_fHeliControlAbilityControlDampMax, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fHeliControlAbilityControlRandomMin", &CRotaryWingAircraft::ms_fHeliControlAbilityControlRandomMin, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fHeliControlAbilityControlRandomMax", &CRotaryWingAircraft::ms_fHeliControlAbilityControlRandomMax, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fTimeBetweenRandomControlInputsMin", &CRotaryWingAircraft::ms_fTimeBetweenRandomControlInputsMin, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fTimeBetweenRandomControlInputsMax", &CRotaryWingAircraft::ms_fTimeBetweenRandomControlInputsMax, 0.0f, 100.0f, 0.1f);
bank->AddSlider("fRandomControlLerpDown", &CRotaryWingAircraft::ms_fRandomControlLerpDown, 0.0f, 100.0f, 0.1f);
bank->PopGroup();
}
}
void CRotaryWingAircraft::VisualisePitchRoll()
{
if(sbVisualiseHeliControls)
{
static Vector2 vRollDebugPos(0.65f,0.25f);
static Vector2 vPitchDebugPos(0.75f,0.15f);
static Vector2 vYawDebugPos(0.65f,0.5f);
static Vector2 vThrottleDebugPos(0.5f,0.15f);
float fScale = 0.2f;
float fEndWidth = 0.01f;
grcDebugDraw::Meter(vPitchDebugPos,Vector2(0.0f,1.0f),fScale,fEndWidth,Color32(255,255,255),"Pitch");
grcDebugDraw::MeterValue(vPitchDebugPos, Vector2(0.0f,1.0f), fScale, m_fPitchControl, fEndWidth, Color32(255,0,0));
grcDebugDraw::Meter(vRollDebugPos,Vector2(1.0f,0.0f),fScale,fEndWidth,Color32(255,255,255),"Roll");
grcDebugDraw::MeterValue(vRollDebugPos, Vector2(1.0f,0.0f), fScale, m_fRollControl, fEndWidth, Color32(255,0,0));
grcDebugDraw::Meter(vYawDebugPos,Vector2(1.0f,0.0f),fScale,fEndWidth,Color32(255,255,255),"Yaw");
grcDebugDraw::MeterValue(vYawDebugPos, Vector2(1.0f,0.0f), fScale, m_fYawControl, fEndWidth, Color32(255,0,0));
grcDebugDraw::Meter(vThrottleDebugPos,Vector2(0.0f,1.0f),fScale,fEndWidth,Color32(255,255,255),"Throttle");
grcDebugDraw::MeterValue(vThrottleDebugPos, Vector2(0.0f,1.0f), fScale, 2.0f*(0.5f-m_fThrottleControl), fEndWidth, Color32(255,0,0));
/*
static const float fWidth = 1.0f;
static const float fHeight = 1.0f;
static const float fDebugAxisLength = 100.0f/320.0f;
// Visualise roll
Vector2 vMin, vMax, vValue;
vMin.x = (fWidth / 2.0f) - (fDebugAxisLength/2.0f);
vMin.y = (fHeight / 3.0f) - 20.0f/240.0f;
vMax.x = vMin.x + fDebugAxisLength;
vMax.y = vMin.y;
vValue.y = vMin.y;
vValue.x = (vMax.x-vMin.x) *(0.5f*-m_fRollControl+0.5f) + vMin.x;
grcDebugDraw::Line(vMin,vMax,Color32(255,255,255));
grcDebugDraw::Line(vMin - Vector2(0.0f,5.0f/240.0f),vMin+ Vector2(0.0f,5.0f/240.0f),Color32(255,255,255));
grcDebugDraw::Line(vMax - Vector2(0.0f,5.0f/240.0f),vMax+ Vector2(0.0f,5.0f/240.0f),Color32(255,255,255));
grcDebugDraw::Line(vValue - Vector2(0.0f,5.0f/240.0f),vValue+ Vector2(0.0f,5.0f/240.0f),Color32(255,0,0));
// Visualise pitch
vMin.x = (4.0f*fWidth / 5.0f) + 20.0f/320.0f;
vMin.y = (fHeight / 3.0f);
vMax.x = vMin.x;
vMax.y = vMin.y + fDebugAxisLength;
// Y axis goes from top to bottom so need to multiply by -ve 1
vValue.y = (vMax.y - vMin.y) *(-0.5f*m_fPitchControl+0.5f) + vMin.y;
vValue.x = vMin.x;
grcDebugDraw::Line(vMin,vMax,Color32(255,255,255));
grcDebugDraw::Line(vMin - Vector2(5.0f/320.0f,0.0f),vMin+ Vector2(5.0f/320.0f,0.0f),Color32(255,255,255));
grcDebugDraw::Line(vMax - Vector2(5.0f/320.0f,0.0f),vMax+ Vector2(5.0f/320.0f,0.0f),Color32(255,255,255));
grcDebugDraw::Line(vValue - Vector2(5.0f/320.0f,0.0f),vValue+ Vector2(5.0f/320.0f,0.0f),Color32(255,0,0));
*/
}
}
#endif
//////////////////////////////////////////////////////////////////////////
// CHeli
//
//
//
bank_float CHeli::ms_fRotorSpeedMults[] =
{
25.2f, // Main
59.6f, // Rear
25.2f, // Main_2
45.0f // Rear_2 - used for the drone rotor speed
};
CompileTimeAssert(CRotaryWingAircraft::Num_Heli_Rotors == 2);
dev_float s_minTurbulenceScalar = 0.1f;
dev_float s_maxTurbulenceScalar = 1.0f;
dev_float s_massForMaxTurbulenceScalar = 4000.0f;
dev_float s_massForMinTurbulenceScalar = 20000.0f;
CHeli::CHeli(const eEntityOwnedBy ownedBy, u32 popType, VehicleType veh)
: CRotaryWingAircraft(ownedBy, popType, veh)
, m_InitialRopeLength(0.0f)
, m_pAntiSwayEntity(NULL)
, m_vLastFramesAntiSwayEntityDistance(0.0f, 0.0f, 0.0f)
, m_AntiSwaySpringDistance(-8.0f)
, m_fEngineDegradeTimer(0.0f)
, m_turbulenceTimer(0.0f)
, m_turbulenceRecoverTimer(0.0f)
, m_turbulenceRoll(0.0f)
, m_turbulencePitch(0.0f)
, m_turbulenceDrop(0.0f)
, m_turbulenceScalar(1.0f)
, m_fTimeSpentLanded(0.0f)
, m_fTimeSpentCollidingNotLanded(0.0f)
, m_fJetPackGroundHeight(0.0f)
, m_vHeliSteeringBias(VEC3_ZERO)
, m_bPoliceDispatched(false)
, m_bSwatDispatched(false)
, m_nPickupRopeType(PICKUP_HOOK)
{
#if REGREF_VALIDATE_CDCDCDCD
fwRefAwareBaseImpl_AddInterestingObject(this);
#endif
m_pOwnerPlayer = NULL;
m_bHasLandingGear = false;
m_bDisableExplodeFromBodyDamage = false;
m_bCanPickupEntitiesThatHavePickupDisabled = false;
m_bDisableSelfRighting = false;
if(veh == VEHICLE_TYPE_HELI)
{
gPostScan.AddToAlwaysPreRenderList(this);
}
for(int i = 0; i < eNumRopeIds; ++i)
{
m_Ropes[i]=NULL;
}
}
//
//
//
CHeli::~CHeli()
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnDestroyOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfDestroyCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
#if GTA_REPLAY
//Don't allow heli to delete rope that it does not own. Let CPacketDeleteRope handle it during replay.
if(!CReplayMgr::IsEditModeActive())
#endif
{
for(int i = 0; i < eNumRopeIds; ++i)
{
// Remove any existing ropes
if(m_Ropes[i])
{
#if GTA_REPLAY
if(CReplayMgr::ShouldRecord())
{
CEntity* pAttachedA = CReplayRopeManager::GetAttachedEntityA(m_Ropes[i]->GetUniqueID());
CEntity* pAttachedB = CReplayRopeManager::GetAttachedEntityB(m_Ropes[i]->GetUniqueID());
CReplayMgr::RecordPersistantFx<CPacketDeleteRope>( CPacketDeleteRope(m_Ropes[i]->GetUniqueID()), CTrackedEventInfo<ptxEffectRef>((ptxEffectRef)m_Ropes[i]), pAttachedA, pAttachedB, false);
}
#endif //GTA_REPLAY
CPhysics::GetRopeManager()->RemoveRope(m_Ropes[i]);
m_Ropes[i]=NULL;
}
}
}
}
bool CHeli::ShouldCreateLandingGear()
{
CVehicleModelInfo* pModelInfo = GetVehicleModelInfo();
return (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HELICOPTER_WITH_LANDING_GEAR) && GetBoneIndex(FIRST_LANDING_GEAR) > -1);
}
/////////////////////////////////////////////////////////////////////////////////////////
int CHeli::InitPhys()
{
int result = CRotaryWingAircraft::InitPhys();
if( ShouldCreateLandingGear() )
{
m_landingGear.InitPhys( this, LANDING_GEAR_F, LANDING_GEAR_RM1, LANDING_GEAR_LM1, LANDING_GEAR_RR, LANDING_GEAR_RL, LANDING_GEAR_RM );
m_bHasLandingGear = true;
}
float mass = Clamp( GetMass(), s_massForMaxTurbulenceScalar, s_massForMinTurbulenceScalar );
mass -= s_massForMaxTurbulenceScalar;
if( mass > 0.0f )
{
mass /= ( s_massForMinTurbulenceScalar - s_massForMaxTurbulenceScalar );
}
m_turbulenceScalar = s_maxTurbulenceScalar - ( mass * ( s_maxTurbulenceScalar - s_minTurbulenceScalar ) );
return result;
}
void CHeli::InitWheels()
{
CAutomobile::InitWheels();
for(int i = 0; i < GetNumWheels(); i++)
{
Assert(GetWheel(i));
GetWheel(i)->GetConfigFlags().SetFlag(WCF_UPDATE_SUSPENSION);
}
if( m_bHasLandingGear )
{
m_landingGear.InitWheels(this);
}
}
CHeliIntelligence *CHeli::GetHeliIntelligence() const
{
Assert(dynamic_cast<CHeliIntelligence*>(m_pIntelligence));
return static_cast<CHeliIntelligence*>(m_pIntelligence);
}
void CHeli::SetModelId(fwModelId modelId)
{
CRotaryWingAircraft::SetModelId(modelId);
#if __ASSERT
const char* strDebugPropNames[] =
{
"Main",
"Rear",
"Main_2",
"Rear_2"
};
#endif
// Figure out which propellers are valid, and set them up
for(int nPropIndex = 0 ; nPropIndex < HELI_NUM_ROTORS; nPropIndex++ )
{
CVehicleModelInfo *pModelInfo = GetVehicleModelInfo();
eRotationAxis nAxis = ROT_AXIS_LOCAL_Z;
if( (nPropIndex != Rotor_Main) && (!pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_DONT_ROTATE_TAIL_ROTOR)) )//if were not the main rotor and we don't have "dont rotate" flag set, rotate on the x axis
nAxis = ROT_AXIS_LOCAL_X;
if( GetIsDrone() )
{
nAxis = ROT_AXIS_LOCAL_Z;
}
if( GetModelIndex() == MI_JETPACK_THRUSTER )
{
nAxis = ROT_AXIS_LOCAL_Z;
}
eHierarchyId nId = (eHierarchyId)(HELI_ROTOR_MAIN + nPropIndex);
if(vehicleVerifyf(GetBoneIndex(nId) > -1,"Vehicle %s is missing a propeller: %s",GetModelName(),strDebugPropNames[nPropIndex])
&& vehicleVerifyf(m_iNumPropellers < Num_Heli_Rotors,"Out of room for plane propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId,nAxis, this);
m_propellerCollisions[m_iNumPropellers].Init(nId,this);
m_iNumPropellers++;
}
}
if( GetIsDrone() )
{
for(int nPropIndex = HELI_NUM_ROTORS ; nPropIndex < HELI_NUM_DRONE_ROTORS; nPropIndex++ )
{
eRotationAxis nAxis = ROT_AXIS_LOCAL_Z;
eHierarchyId nId = (eHierarchyId)(HELI_ROTOR_MAIN + nPropIndex);
if(vehicleVerifyf(GetBoneIndex(nId) > -1,"Vehicle %s is missing a propeller: %s",GetModelName(),strDebugPropNames[nPropIndex])
&& vehicleVerifyf(m_iNumPropellers < Num_Drone_Rotors,"Out of room for plane propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId,nAxis, this);
m_propellerCollisions[m_iNumPropellers].Init(nId,this);
m_iNumPropellers++;
}
}
}
if( m_bHasLandingGear )
{
// Turn collision on for landing gear
m_landingGear.InitCompositeBound( this );
}
// Call InitCompositeBound again, as it has dependency with propellers
InitCompositeBound();
// cargobob can survive from on rocket explosion
if(GetIsCargobob())
{
m_nVehicleFlags.bExplodesOnHighExplosionDamage = false;
}
// Cache off the initial rotor position so we don't need to compute it at runtime
if(GetSkeleton() && GetBoneIndex(HELI_ROTOR_REAR) > -1 && !GetIsJetPack())
{
GetDefaultBonePositionForSetup(HELI_ROTOR_REAR,m_vecRearRotorPosition);
}
else
{
m_vecRearRotorPosition = Vector3(0.0f, 0.0f, GetBoundingBoxMax().z);
}
// 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 );
}
}
}
void CHeli::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
// DEBUG!! -AC, This call to process control is either at the wrong place (it should be moved to the bottom of this function)
// or it is at the right place (and all other vehicle::ProcessControl functions should emulate it).
// then can go ahead and call the parent version of process control
CRotaryWingAircraft::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
// END DEBUG!!
if (!m_nVehicleFlags.bAnimatePropellers)
{
UpdateRotorSpeed();
}
UpdateRopes();
ProcessLanded(fFullUpdateTimeStep);
if( HasLandingGear() )
{
m_landingGear.ProcessControl( this );
}
ProcessWings();
}
// void CHeli::ProcessDriverInputsForPlayer(CPed *pPlayerPed)
// {
// Assert(pPlayerPed && (pPlayerPed->IsControlledByLocalPlayer()));
// CControl *pControl = pPlayerPed->GetControlFromPlayer();
//
// // auto throttle will keep us hovering, but won't stop us stopping from moving up, if that makes any sense?
// float fDesiredThrottleControl = pControl->GetVehicleFlyThrottleUp().GetNorm01() - pControl->GetVehicleFlyThrottleDown().GetNorm01();
//
// float fAutoThrottle = 0.0f;
// {
// if(!GetNumContactWheels())
// fAutoThrottle = 0.98f * rage::Clamp(1.0f - GetVelocity().z * HELI_AUTO_THROTTLE_FALLOFF, 0.0f, 1.0f);
//
// // need to combine desired throttle and auto throttle, somehow
// if(fDesiredThrottleControl > 0.01f)
// {
// fDesiredThrottleControl += 1.0f;
// }
// else if(fDesiredThrottleControl < -0.01f)
// {
// fDesiredThrottleControl = rage::Max(-0.1f, fDesiredThrottleControl + 0.5f);
// }
// else
// {
// fDesiredThrottleControl = fAutoThrottle;
// }
// }
//
//
// float fThrottleChangeMult = rage::Powf(HELI_THROTTLE_CONTROL_DAMPING, fwTimer::GetTimeStep());
//
// // Store this here since it will be overridden by superclass process control inputs
// fDesiredThrottleControl = fThrottleChangeMult*m_fThrottleControl + (1.0f - fThrottleChangeMult)*fDesiredThrottleControl;
//
// CRotaryWingAircraft::ProcessDriverInputsForPlayer(pPlayerPed);
//
// SetThrottleControl(fDesiredThrottleControl);
//
//
// }
//////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////
void CHeli::BlowUpCar( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion )
{
CRotaryWingAircraft::BlowUpCar(pCulprit,bInACutscene,bAddExplosion,bNetCall,weaponHash,bDelayedExplosion);
}
//////////////////////////////////////////////////////////////////////////
//This should only be called from the crash task
//////////////////////////////////////////////////////////////////////////
void CHeli::FinishBlowingUpVehicle( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion )
{
//This should only be called from the crash task
Assert(IsNetworkClone() || GetIntelligence()->GetTaskManager()->FindTaskByTypeWithPriority(VEHICLE_TASK_TREE_PRIMARY, CTaskTypes::TASK_VEHICLE_CRASH, VEHICLE_TASK_PRIORITY_CRASH));
// If this heli was chasing a player we will make it so that the player doesn't get any new helis created for a while.
if (GetOwnerPlayer())
{
((CNetGamePlayer*)GetOwnerPlayer())->GetPlayerPed()->GetPlayerInfo()->m_LastTimeHeliWasDestroyed = fwTimer::GetTimeInMilliseconds();
SetOwnerPlayer(NULL);
}
CRotaryWingAircraft::FinishBlowingUpVehicle(pCulprit,bInACutscene,bAddExplosion, bNetCall, weaponHash, bDelayedExplosion);
if( GetIsCargobob() )
{
RemoveRopesAndHook();
}
}
void CHeli::ProcessControlInputsInactive(CPed *)
{
if(HasContactWheels() && GetVelocity().Mag2() < 16.0f)
{
m_fThrottleControl = 0.0f;
m_fYawControl = 0.0f;
m_fPitchControl = 0.0f;
m_fRollControl = 0.0f;
}
}
static dev_float sfSpringConstant = -3.3f;
static dev_float sfDampingConstant = 1.7f;
/*
static dev_float sfHeliControlMichealMulti = 0.7f;
static dev_float sfHeliControlFrankMulti = 0.5f;
static dev_float sfHeliControlTrevorMulti = 0.9f;
*/
extern float TurbulenceSideWindThreshold; // from Planes.cp
extern float FullRumbleSideWindSpeed; // from Planes.cpp
static dev_float sfLiftForceMaxMultiWhenMissingFiring = 0.1f;
// Turbulence related tunings
bank_float sfRandomRollNoiseLower = -1.5f;//prefer the random noise moving the plane forward then back
bank_float sfRandomRollNoiseUpper = 1.5f;
bank_float sfRandomPitchNoiseLower = -1.0f;
bank_float sfRandomPitchNoiseUpper = 1.0f;
bank_float sfRandomDropNoiseLower = -1.0f;
bank_float sfRandomDropNoiseUpper = 0.2f;
bank_float sfRandomNoiseSpeedMax = 30.0f;
#if RSG_PC
bank_float sfRandomNoiseIdleMultForPlayer = 0.05f;
bank_float sfRandomNoiseThrottleMultForPlayer = 0.025f;
bank_float sfRandomNoiseSpeedMultForPlayer = 0.125f;
#else
bank_float sfRandomNoiseIdleMultForPlayer = 0.1f;
bank_float sfRandomNoiseThrottleMultForPlayer = 0.05f;
bank_float sfRandomNoiseSpeedMultForPlayer = 0.25f;
#endif
bank_float sfRandomNoiseIdleMultForAI = 0.2f;
bank_float sfRandomNoiseThrottleMultForAI = 0.15f;
bank_float sfRandomNoiseSpeedMultForAI = 0.25f;
bank_float sfTurbulenceTimeMin = 0.5f;
bank_float sfTurbulenceTimeMax = 1.0f;
bank_float sfTurbulenceRecoverTimeMin = 1.5f;
bank_float sfTurbulenceRecoverTimeMax = 2.5f;
bank_float sfHeliPhysicalTurbulencePedVibMult = 0.0f;
dev_float sfHeliSteeringBiasFadeOutRate = 1.0f;
dev_float sfHeliSteeringBiasMag = 1.0f;
void CHeli::ProcessAntiSway(float fTimeStep)
{
// If we have an entity that we don't want to sway too much, add a spring and damper
if(m_pAntiSwayEntity && m_pAntiSwayEntity->GetCurrentPhysicsInst() && m_pAntiSwayEntity->GetCurrentPhysicsInst()->IsInLevel())
{
if( !m_pAntiSwayEntity->GetFrameCollisionHistory()->GetNumCollidedEntities() )
{
Vector3 vPosition = VEC3V_TO_VECTOR3(GetVehiclePosition());
vPosition.z += m_AntiSwaySpringDistance;
Vector3 vEntityDistance(VEC3V_TO_VECTOR3(m_pAntiSwayEntity->GetMatrix().d()) - vPosition);
static dev_float sfMaxDistanceToDampSq = 100.0f*100.0f;
if(vEntityDistance.Mag2() < sfMaxDistanceToDampSq)
{
Vector3 dampingToApply(VEC3_ZERO);
Vector3 vDampingDistance = vEntityDistance-m_vLastFramesAntiSwayEntityDistance;
if(fTimeStep > SMALL_FLOAT && vDampingDistance.Mag2() < sfMaxDistanceToDampSq)
{
dampingToApply = ((vDampingDistance) / fTimeStep);
}
Vector3 forceToApply = ((vEntityDistance * sfSpringConstant) - (dampingToApply * sfDampingConstant)) * m_pAntiSwayEntity->GetMass();// scale the torque by mass
float fForceLimit = DEFAULT_ACCEL_LIMIT * Max(1.0f, m_pAntiSwayEntity->GetMass());
float fForceMag = forceToApply.Mag();
if(fForceMag > fForceLimit)
{
forceToApply *= (fForceLimit - 0.1f)/fForceMag;
}
m_pAntiSwayEntity->ApplyForceCg(forceToApply);
}
m_vLastFramesAntiSwayEntityDistance = vEntityDistance;
}
}
}
void CHeli::ProcessTurbulence(float fThrottleControl, float fTimeStep)
{
const Mat34V& matrix = GetMatrixRef();
const Vec3V vecRight = matrix.GetCol0();
const Vec3V vecForward = matrix.GetCol1();
Vector3 vecAngInertia = GetAngInertia();
float fMass = GetMass();
CPed* pDriver = GetDriver();
bool bDrivenByPlayer = pDriver && pDriver->IsPlayer();
CControl *pControl = NULL;
if(bDrivenByPlayer && GetStatus()==STATUS_PLAYER)
{
pControl = pDriver->GetControlFromPlayer();
}
bool bControlInactive = pControl && CTaskVehiclePlayerDrive::IsThePlayerControlInactive(pControl);
const Vec3V velocity = VECTOR3_TO_VEC3V(GetVelocity());
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if(m_turbulenceRecoverTimer <= 0.0f)
{
if(m_turbulenceTimer > 0.0f)
{
// Apply pitch noise
ApplyInternalTorque(m_turbulencePitch*ZAXIS*vecAngInertia.x, RCC_VECTOR3(vecForward));
// Apply roll noise
ApplyInternalTorque(m_turbulenceRoll*ZAXIS*vecAngInertia.y, RCC_VECTOR3(vecRight));
// Apply drop noise
ApplyInternalForceCg(m_turbulenceDrop*ZAXIS*fMass);
m_turbulenceTimer -= fTimeStep;
}
else
{
// Recompute next turbulence values
float fRandomNoiseIdleMult = sfRandomNoiseIdleMultForAI;
float fRandomNoiseThrottleMult = sfRandomNoiseThrottleMultForAI;
float fRandomNoiseSpeedMult = sfRandomNoiseSpeedMultForAI;
if(bDrivenByPlayer)
{
fRandomNoiseIdleMult = sfRandomNoiseIdleMultForPlayer;
fRandomNoiseThrottleMult = sfRandomNoiseThrottleMultForPlayer;
fRandomNoiseSpeedMult = sfRandomNoiseSpeedMultForPlayer;
}
float fDamageRate = 1.0f - Min(GetHealth()/GetMaxHealth(), GetVehicleDamage()->GetEngineHealth()/ENGINE_HEALTH_MAX);
fDamageRate = Clamp(fDamageRate, 0.0f, 1.0f);
float fSpeedRate = RCC_VECTOR3(velocity).XYMag() / sfRandomNoiseSpeedMax;
fSpeedRate = Clamp(fSpeedRate, 0.0f, 1.0f);
float fRandomNoiseMult = fRandomNoiseIdleMult + fThrottleControl * fRandomNoiseThrottleMult + fDamageRate * pFlyingHandling->m_fBodyDamageControlEffectMult + fSpeedRate * fRandomNoiseSpeedMult + WeatherTurbulenceMult();
m_turbulenceRoll = fwRandom::GetRandomNumberInRange( sfRandomRollNoiseLower*fRandomNoiseMult, sfRandomRollNoiseUpper*fRandomNoiseMult) * m_turbulenceScalar;
m_turbulencePitch = fwRandom::GetRandomNumberInRange( sfRandomPitchNoiseLower*fRandomNoiseMult, sfRandomPitchNoiseUpper*fRandomNoiseMult) * m_turbulenceScalar;
m_turbulenceDrop = fwRandom::GetRandomNumberInRange( sfRandomDropNoiseLower*fRandomNoiseMult, sfRandomDropNoiseUpper*fRandomNoiseMult) * m_turbulenceScalar;
m_turbulenceTimer = fwRandom::GetRandomNumberInRange(sfTurbulenceTimeMin, sfTurbulenceTimeMax);
m_turbulenceRecoverTimer = fwRandom::GetRandomNumberInRange(sfTurbulenceRecoverTimeMin, sfTurbulenceRecoverTimeMax);
}
}
else
{
m_turbulenceRecoverTimer -= fTimeStep;
if(bDrivenByPlayer && sfHeliPhysicalTurbulencePedVibMult > 0.0f && m_turbulenceRecoverTimer <= 0.0f && !bControlInactive)
{
u32 uTurbulenceTime = (u32)(m_turbulenceTimer * 1000.0f);
CControlMgr::StartPlayerPadShakeByIntensity(uTurbulenceTime, sfHeliPhysicalTurbulencePedVibMult);
}
}
}
void CHeli::ProcessSteeringBias(float fTimeStep, float &fRollControl, float &fPitchControl)
{
const Mat34V& matrix = GetMatrixRef();
// Fades out the steering bias
m_vHeliSteeringBias *= Max(1.0f - sfHeliSteeringBiasFadeOutRate * fTimeStep, 0.0f);
// Update the steering bias
if(GetMainRotorSpeed() > 0.0f)
{
if(const CCollisionHistory* pCollisionHistory = GetFrameCollisionHistory())
{
if(const CCollisionRecord* pCollisionRecord = pCollisionHistory->GetMostSignificantCollisionRecordOfType(ENTITY_TYPE_BUILDING))
{
if(pCollisionRecord->m_fCollisionImpulseMag > 0.0f)
{
Vector3 vAccumulatedContactToCenter = VEC3_ZERO;
for(const CCollisionRecord* pColRecord = pCollisionHistory->GetFirstBuildingCollisionRecord();
pColRecord != NULL ; pColRecord = pColRecord->GetNext())
{
if(pColRecord->m_MyCollisionComponent == GetMainRotorDisc() && pColRecord->m_fCollisionImpulseMag > 0.0f)
{
vAccumulatedContactToCenter += VEC3V_TO_VECTOR3(GetVehiclePosition()) - pColRecord->m_MyCollisionPos;
}
}
if(!vAccumulatedContactToCenter.IsZero())
{
vAccumulatedContactToCenter.z = 0.0f;
vAccumulatedContactToCenter.NormalizeSafe();
vAccumulatedContactToCenter *= sfHeliSteeringBiasMag;
m_vHeliSteeringBias += vAccumulatedContactToCenter;
m_vHeliSteeringBias.NormalizeSafe();
m_vHeliSteeringBias *= sfHeliSteeringBiasMag;
}
}
}
}
}
// Decompose the steering bias
Vector3 vStteringBiasLocal = VEC3V_TO_VECTOR3(UnTransform3x3Ortho(matrix,RCC_VEC3V(m_vHeliSteeringBias)));
fRollControl -= vStteringBiasLocal.x;
fPitchControl -= vStteringBiasLocal.y;
}
void CHeli::ProcessFlightModel(float fTimeStep)
{
const Vec3V velocity = VECTOR3_TO_VEC3V(GetVelocity());
if(!m_bIsInAir && GetNumContactWheels() == 4 && m_fThrottleControl == 0.0f
&& IsLessThanOrEqualAll(Abs(velocity), Vec3VFromF32(HELI_MINIMUM_CONTROL_SPEED)))
{
return;
}
// better make sure we've got some handling data, otherwise we're screwed for flying
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if(pFlyingHandling == NULL)
return;
Assert(fTimeStep > 0.0f);
const Mat34V& matrix = GetMatrixRef();
const Vec3V vecRight = matrix.GetCol0();
const Vec3V vecForward = matrix.GetCol1();
const Vec3V vecUp = matrix.GetCol2();
const Vec3V vecPosition = matrix.GetCol3();
CControl *pControl = NULL;
CPed* pDriver = GetDriver();
bool bDrivenByPlayer = pDriver && pDriver->IsPlayer();
if(bDrivenByPlayer && GetStatus()==STATUS_PLAYER)
pControl = pDriver->GetControlFromPlayer();
bool bControlInactive = pControl && CTaskVehiclePlayerDrive::IsThePlayerControlInactive(pControl);
Vector3 vecAirSpeed = RCC_VECTOR3(velocity);
Vector3 vecWindSpeed(0.0f, 0.0f, 0.0f);
// Exclude non-players and blimps from wind purely for optimization purposes
if (bDrivenByPlayer && !InheritsFromBlimp() && !PopTypeIsMission() && !GetIsDrone() )
{
WIND.GetLocalVelocity(vecPosition, RC_VEC3V(vecWindSpeed), false, false);
vecWindSpeed *= ComputeWindMult();
vecAirSpeed -= vecWindSpeed;
}
float fMass = GetMass();
Vector3 vecAngInertia = GetAngInertia();
#if __BANK
if(CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING && GetStatus()==STATUS_PLAYER)
{
Vector3 vecWindSocPos = VEC3V_TO_VECTOR3(AddScaled(vecPosition,vecUp,ScalarVFromF32(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();
float fEngineSpeed = GetMainRotorSpeed();
float fCollectiveControl = GetCollectiveControl();
SetCollectiveControl(1.0f);
Assert(FPIsFinite(fPitchControl));
Assert(FPIsFinite(fYawControl));
Assert(FPIsFinite(fRollControl));
Assert(FPIsFinite(fThrottleControl));
Assert(FPIsFinite(fEngineSpeed));
Assert(FPIsFinite(fCollectiveControl));
{
// 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();
}
static bool disableHeliTurbulence = false;
if( CGameLogic::GetCurrentLevelIndex() == 2 )
{
TUNE_GROUP_BOOL( VEHICLE_TURBULENCE, DISABLE_HELI_TURBULENCE, true );
disableHeliTurbulence = DISABLE_HELI_TURBULENCE;
}
else
{
TUNE_GROUP_BOOL( VEHICLE_TURBULENCE, DISABLE_HELI_TURBULENCE, false );
disableHeliTurbulence = DISABLE_HELI_TURBULENCE;
}
// Apply the driver influence to controls
if( !GetIsDrone() && bDrivenByPlayer && m_bIsInAir && !bControlInactive && !m_nVehicleFlags.bUsedForPilotSchool BANK_ONLY(&& !CVehicleFactory::ms_bflyingAce) && !disableHeliTurbulence )
{
ModifyControlsBasedOnFlyingStats(pDriver, pFlyingHandling, fYawControl, fPitchControl, fRollControl, fThrottleControl, fTimeStep);
}
if(GetMainRotorDisc() > -1 && m_bIsInAir && pDriver && !bDrivenByPlayer)
{
ProcessSteeringBias(fTimeStep, fRollControl, fPitchControl);
}
////////////////////////
// ADDED NOISE
if(m_bIsInAir && !m_bDisableTurbulanceThisFrame && !bControlInactive && !InheritsFromBlimp() && !GetIsDrone() && !disableHeliTurbulence )
{
ProcessTurbulence(fThrottleControl, fTimeStep);
}
///////////////////////
// LIFT&THRUST
Vector3 vecLift(RCC_VECTOR3(vecUp));
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;
Assert(IsEqualAll(GetTransform().GetPosition(),vecPosition));
if(fThrottleMult > 0.0f)
{
float heightAboveCeiling = HeightAboveCeiling(vecPosition.GetZf());
if(heightAboveCeiling > 0.0f)
{
fThrottleMult *= 10.0f/(heightAboveCeiling + 10.0f);
}
}
if(m_bHoverMode)
{
vecLift = Vector3(0.0f, 0.0f, 1.0f);
static dev_float sfLinearDampingForHoverMult = 10.0f;
SetDampingForFlight(sfLinearDampingForHoverMult);
}
fThrottleMult -= pFlyingHandling->m_fThrustFallOff*fSpeedThroRotor;
// Cut out the lift force if the engine is missing firing
if(IsEngineOn() && m_Transmission.GetCurrentlyMissFiring())
{
fThrottleMult = Min(fThrottleMult, sfLiftForceMaxMultiWhenMissingFiring);
}
//Normal lift force
Vector3 liftForce = vecLift;
if(m_bEnableThrustVectoring)
{
// Additional force when pitching or rolling
Vector3 negAdditionalThrustForce = pFlyingHandling->m_fThrustVectoring * (fPitchControl*RCC_VECTOR3(vecForward) + fRollControl*RCC_VECTOR3(vecRight));
negAdditionalThrustForce.z = 0.0f;
liftForce -= negAdditionalThrustForce;
}
if( GetIsJetPack() &&
pControl &&
m_nVehicleFlags.bEngineOn )
{
liftForce *= Max( pControl->GetVehicleFlyThrottleUp().GetNorm01(), liftForce.Dot( Vector3( 0.0f, 0.0f, 1.0f ) ) );
}
ApplyInternalForceCg(liftForce*(-GRAVITY*fThrottleMult*fEngineSpeed*fMass*fCollectiveControl));
// only apply stabilising force when heli is right way up (to enable recovery from upside down)
if(vecUp.GetZf() > 0.0f)
{
Vector3 vecTempUp(0.0f,0.0f,1.0f);
vecTempUp += vecWindSpeed;
vecTempUp.Normalize();
float fForceOffset = -Clamp(RCC_VECTOR3(vecRight).Dot(vecTempUp), -pFlyingHandling->m_fFormLiftMult, pFlyingHandling->m_fFormLiftMult);
ApplyInternalTorque((fEngineSpeed*pFlyingHandling->m_fAttackLiftMult*fForceOffset*vecAngInertia.y)*RCC_VECTOR3(vecUp), RCC_VECTOR3(vecRight));
fForceOffset = -Clamp(RCC_VECTOR3(vecForward).Dot(vecTempUp), -pFlyingHandling->m_fFormLiftMult, pFlyingHandling->m_fFormLiftMult);
ApplyInternalTorque((fEngineSpeed*pFlyingHandling->m_fAttackLiftMult*fForceOffset*vecAngInertia.x)*RCC_VECTOR3(vecUp), RCC_VECTOR3(vecForward));
}
else
{
float fForceOffset;
if(vecRight.GetZf() < 0.0f) fForceOffset = pFlyingHandling->m_fFormLiftMult;
else fForceOffset = -pFlyingHandling->m_fFormLiftMult;
ApplyInternalTorque((fEngineSpeed*pFlyingHandling->m_fAttackLiftMult*fForceOffset*vecAngInertia.y)*RCC_VECTOR3(vecUp), RCC_VECTOR3(vecRight));
if(vecForward.GetZf() < 0.0f) fForceOffset = pFlyingHandling->m_fFormLiftMult;
else fForceOffset = -pFlyingHandling->m_fFormLiftMult;
ApplyInternalTorque((fEngineSpeed*pFlyingHandling->m_fAttackLiftMult*fForceOffset*vecAngInertia.x)*RCC_VECTOR3(vecUp), RCC_VECTOR3(vecForward));
}
static dev_float sfOnGroundMult = 0.1f;
float fOnGroundMult = m_bIsInAir ? 1.0f : sfOnGroundMult;
if (!IsNetworkClone())
{
m_fJetpackStrafeForceScale = 0.0f;
}
static dev_float strafeModeStrafeVelocityAccelerationMin = 10.0f;
static dev_float strafeModeStrafeVelocityAccelerationMax = 15.0f;
static dev_float sfJetpackMaxLateralAccelerationInv = 1.0f / ( strafeModeStrafeVelocityAccelerationMin + strafeModeStrafeVelocityAccelerationMax );
if( !m_bStrafeMode )
{
ApplyInternalTorque(RCC_VECTOR3(vecUp)*(fPitchControl*fOnGroundMult*pFlyingHandling->m_fPitchMult*GetHoverModePitchMult()*vecAngInertia.x*fEngineSpeed), RCC_VECTOR3(vecForward));
ApplyInternalTorque(RCC_VECTOR3(vecUp)*(fRollControl*fOnGroundMult*pFlyingHandling->m_fRollMult*vecAngInertia.y*fEngineSpeed), RCC_VECTOR3(vecRight));
ProcessTail(vecAirSpeed, pFlyingHandling, fYawControl, fEngineSpeed);
m_fStrafeModeTargetHeight = GetTransform().GetPosition().GetZf();
if( GetIsJetPack() &&
m_nVehicleFlags.bEngineOn )
{
// apply extra lateral drag if no stick input
float invTimeStep = 1.0f / fTimeStep;
static dev_float sfJetpackExtraLateralDragAcceleration = 5.0f;
static dev_float sfExtraDragMaxRoll = 0.3f;
static dev_float sfMaxLateralDragRollScale = 0.015f;
static dev_float sfLaterDragTorqueScale = -1.5f;
if( fRollControl == 0.0f )
{
float currentSpeed = GetVelocity().Dot( VEC3V_TO_VECTOR3( vecRight ) );
float maxAcceleration = sfJetpackExtraLateralDragAcceleration * fEngineSpeed;
float acceleration = Clamp( -currentSpeed * invTimeStep, -maxAcceleration, maxAcceleration );
if (!IsNetworkClone())
{
m_fJetpackStrafeForceScale += acceleration * sfJetpackMaxLateralAccelerationInv;
}
acceleration *= fMass;
ApplyInternalForceCg( RCC_VECTOR3( vecRight ) * acceleration );
float targetRoll = Clamp( currentSpeed * sfMaxLateralDragRollScale, -sfExtraDragMaxRoll, sfExtraDragMaxRoll );
float torque = targetRoll - vecRight.GetZf();
torque *= sfLaterDragTorqueScale * GetAngInertia().GetY();
Vector3 vecTorque = VEC3V_TO_VECTOR3( vecForward ) * torque;
ApplyInternalTorque( vecTorque );
}
if( fPitchControl == 0.0f )
{
float currentSpeed = GetVelocity().Dot( VEC3V_TO_VECTOR3( vecForward ) );
float maxAcceleration = sfJetpackExtraLateralDragAcceleration * fEngineSpeed;
float acceleration = Clamp( -currentSpeed * invTimeStep, -maxAcceleration, maxAcceleration );
acceleration *= fMass;
ApplyInternalForceCg( RCC_VECTOR3( vecForward ) * acceleration );
float targetPitch = Clamp( -currentSpeed * sfMaxLateralDragRollScale, -sfExtraDragMaxRoll, sfExtraDragMaxRoll );
float torque = targetPitch - vecForward.GetZf();
torque *= sfLaterDragTorqueScale * GetAngInertia().GetX();
Vector3 vecTorque = VEC3V_TO_VECTOR3( vecRight ) * torque;
ApplyInternalTorque( vecTorque );
}
ApplyInternalForceCg( CalculateHoverForce( invTimeStep ) );
}
}
else
{
float invTimeStep = 1.0f / fTimeStep;
static dev_float strafeModeStrafeVelocityScale = 0.25f;
float targetSpeed = pHandling->m_fEstimatedMaxFlatVel * strafeModeStrafeVelocityScale * -fRollControl;
float currentSpeed = GetVelocity().Dot( VEC3V_TO_VECTOR3( vecRight ) );
float maxAcceleration = strafeModeStrafeVelocityAccelerationMin + ( strafeModeStrafeVelocityAccelerationMax * fEngineSpeed * Abs( fRollControl ) );
float acceleration = Clamp( ( targetSpeed - currentSpeed ) * invTimeStep, -maxAcceleration, maxAcceleration );
if (!IsNetworkClone())
{
m_fJetpackStrafeForceScale += ( -fRollControl * 0.5f ) + ( 0.5f * acceleration * sfJetpackMaxLateralAccelerationInv );
}
Vector3 cumulativeForce = RCC_VECTOR3( vecRight ) * acceleration;
static dev_float strafeModeMaxRoll = 0.3f;
static dev_float strafeModeMaxTorque = -2.0f;
static dev_float strafeModeScaleOpposingTorque = 1.5f;
float targetRoll = fRollControl * strafeModeMaxRoll;
float torque = targetRoll - vecRight.GetZf();
torque *= strafeModeMaxTorque * GetAngInertia().GetY();
if( targetRoll == 0.0f )
{
torque *= strafeModeScaleOpposingTorque;
}
Vector3 vecTorque = VEC3V_TO_VECTOR3( vecForward ) * torque;
ApplyInternalTorque( vecTorque );
static dev_float strafeModeMaxPitch = 0.3f;
static dev_float strafeModeMaxPitchTorque = 2.0f;
float targetPitch = fPitchControl * strafeModeMaxPitch;
torque = targetPitch - vecForward.GetZf();
torque *= strafeModeMaxPitchTorque * GetAngInertia().GetX();
if( targetRoll == 0.0f )
{
torque *= strafeModeScaleOpposingTorque;
}
vecTorque = VEC3V_TO_VECTOR3( vecRight ) * torque;
ApplyInternalTorque( vecTorque );
const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame();
float dirToCam = aimFrame.GetFront().Dot( VEC3V_TO_VECTOR3( vecForward ) );
// only rotate towards the camera position if we are reasonable close to it
if( dirToCam > 0.0f &&
vecUp.GetZf() > 0.75f )
{
Vector3 aimPos = aimFrame.GetPosition() + ( aimFrame.GetFront() * 30.0f );
aimPos = VEC3V_TO_VECTOR3( GetTransform().UnTransform( VECTOR3_TO_VEC3V( aimPos ) ) );
aimPos.Normalize();
static float sfYawTorqueScale = 15.0f;
float targetYaw = -aimPos.x * Abs( aimPos.x );
torque = targetYaw;
torque *= sfYawTorqueScale;
torque -= GetAngVelocity().GetZ();
torque *= GetAngInertia().GetZ();
vecTorque = Vector3( 0.0f, 0.0f, 1.0f ) * torque;
ApplyInternalTorque( vecTorque );
}
static dev_float forwardSpeedScale = 1.25f;
targetSpeed = pHandling->m_fEstimatedMaxFlatVel * strafeModeStrafeVelocityScale * -fPitchControl;
if( targetSpeed > 0.0f )
{
targetSpeed *= forwardSpeedScale;
}
currentSpeed = GetVelocity().Dot( VEC3V_TO_VECTOR3( vecForward ) );
maxAcceleration = strafeModeStrafeVelocityAccelerationMin + ( strafeModeStrafeVelocityAccelerationMax * fEngineSpeed * Abs( fPitchControl ) );
acceleration = Clamp( ( targetSpeed - currentSpeed ) * invTimeStep, -maxAcceleration, maxAcceleration );
cumulativeForce += RCC_VECTOR3( vecForward ) * acceleration;
maxAcceleration = strafeModeStrafeVelocityAccelerationMin + ( strafeModeStrafeVelocityAccelerationMax * fEngineSpeed );
float throttleControl = pControl ? pControl->GetVehicleFlyThrottleUp().GetNorm01() - pControl->GetVehicleFlyThrottleDown().GetNorm01() : 0.0f;
static dev_float sfVerticalAccelerationScale = 0.75f;
static dev_float sfVerticalMaxSpeedScale = 0.5f;
if( vecUp.GetZf() > 0.5f )
{
if( throttleControl != 0.0f )
{
targetSpeed = pHandling->m_fEstimatedMaxFlatVel * strafeModeStrafeVelocityScale * throttleControl * sfVerticalMaxSpeedScale;
currentSpeed = GetVelocity().Dot( VEC3V_TO_VECTOR3( vecUp ) );
maxAcceleration = strafeModeStrafeVelocityAccelerationMin + ( strafeModeStrafeVelocityAccelerationMax * fEngineSpeed * Abs( throttleControl ) ) * sfVerticalAccelerationScale;
acceleration = Clamp( ( targetSpeed - currentSpeed ) * invTimeStep, -maxAcceleration, maxAcceleration );
cumulativeForce += RCC_VECTOR3( vecUp ) * acceleration;
}
else
{
static dev_float sfStrafeModeHeightForce = 3.0f;
float maxAcceleration = sfStrafeModeHeightForce * fEngineSpeed;
float acceleration = Clamp( ( m_fStrafeModeTargetHeight - GetTransform().GetPosition().GetZf() ) * invTimeStep, -maxAcceleration, maxAcceleration );
cumulativeForce += Vector3( 0.0f, 0.0f, 1.0f ) * acceleration;
}
cumulativeForce += CalculateHoverForce( invTimeStep );
}
if( cumulativeForce.Mag2() > maxAcceleration * maxAcceleration )
{
cumulativeForce.Normalize();
cumulativeForce *= maxAcceleration;
}
cumulativeForce *= fMass;
ApplyInternalForceCg( cumulativeForce );
if( throttleControl != 0.0f )
{
m_fStrafeModeTargetHeight = GetTransform().GetPosition().GetZf();
}
}
}
// If the wind is strong enough, shake the control pad
if(bDrivenByPlayer && m_bIsInAir)
{
float fSideWindSpeed = DotProduct(RCC_VECTOR3(vecRight), vecWindSpeed);
if(Abs(fSideWindSpeed) > TurbulenceSideWindThreshold)
{
CControlMgr::StartPlayerPadShakeByIntensity(400,Abs(fSideWindSpeed) / FullRumbleSideWindSpeed);
}
}
}
void CHeli::ProcessTail(Vector3& vecAirSpeed, CFlyingHandlingData* pFlyingHandling, float fYawControl, float fEngineSpeed)
{
const Mat34V& matrix = GetMatrixRef();
const Vec3V vecRight = matrix.GetCol0();
const Vec3V vecForward = matrix.GetCol1();
const Vec3V vecUp = matrix.GetCol2();
const Vec3V velocity = VECTOR3_TO_VEC3V(GetVelocity());
// get tail position and add contribution from rotational velocity
Vector3 vecTailAirSpeed = VEC3V_TO_VECTOR3(Transform3x3(matrix,RCC_VEC3V(m_vecRearRotorPosition)));
vecTailAirSpeed.CrossNegate(GetAngVelocity());
vecTailAirSpeed.Add(vecAirSpeed);
float fSideSpeed = DotProduct(vecTailAirSpeed, RCC_VECTOR3(vecRight));
float fFwdSpeed = DotProduct(vecTailAirSpeed, RCC_VECTOR3(vecForward));
float fAirSpeedSqr = vecAirSpeed.Mag2();
float fSideSlipAngle = -rage::Atan2f(fSideSpeed, fFwdSpeed);
fSideSlipAngle = rage::Clamp(fSideSlipAngle, -HELI_RUDDER_MAX_ANGLE_OF_ATTACK, HELI_RUDDER_MAX_ANGLE_OF_ATTACK);
float fMass = GetMass();
Vector3 vecAngInertia = GetAngInertia();
// doing sideways stuff
Vector3 vecRudderForce(RCC_VECTOR3(vecRight));
// sideways force from sidesliping
vecRudderForce *= rage::Clamp(pFlyingHandling->m_fSideSlipMult * fSideSlipAngle * fAirSpeedSqr, -100.0f, 100.0f);
ApplyInternalForceCg(vecRudderForce*fMass);
// control force from steering and stabilising force from rudder
vecRudderForce = RCC_VECTOR3(vecRight);
if(m_bEnableThrustVectoring)
{
fYawControl = ComputeAdditionalYawFromTransform(fYawControl, GetTransform(), RCC_VECTOR3(velocity).XYMag());
}
vecRudderForce *= (pFlyingHandling->m_fYawMult*GetHoverModeYawMult()*fYawControl*fEngineSpeed + pFlyingHandling->m_fYawStabilise*fSideSlipAngle*fAirSpeedSqr)*vecAngInertia.z;
ApplyInternalTorque(vecRudderForce, -RCC_VECTOR3(vecForward));
// PITCH STABILISATION
fSideSpeed = DotProduct(vecTailAirSpeed, RCC_VECTOR3(vecUp));
fSideSlipAngle = -rage::Atan2f(fSideSpeed, fFwdSpeed);
fSideSlipAngle = rage::Clamp(fSideSlipAngle, -HELI_RUDDER_MAX_ANGLE_OF_ATTACK, HELI_RUDDER_MAX_ANGLE_OF_ATTACK);
vecRudderForce = RCC_VECTOR3(vecUp);
//vecRudderForce *= pFlyingHandling->m_fPitchStabilise*fSideSlipAngle*rage::Abs(fSideSlipAngle)*vecAngInertia.x;
vecRudderForce *= pFlyingHandling->m_fPitchStabilise*fSideSlipAngle*fAirSpeedSqr*vecAngInertia.x;
ApplyInternalTorque(vecRudderForce, -RCC_VECTOR3(vecForward));
}
///////////////////////////////////////////////////////////////////////
//
// FUNCTION: DoHeliGenerationAndRemoval
// PURPOSE: Possibly trigger helis
//
///////////////////////////////////////////////////////////////////////
//float fRotRamp = 1.03f;
#define MAX_NUM_ACTIVE_HELIS_ON_ONE_MACHINE (3)
void CHeli::DoHeliGenerationAndRemoval()
{
// This doesn't need to be done every frame.
if ((fwTimer::GetSystemFrameCount() & 15) != 5)
{
return;
}
CVehicle::Pool *VehiclePool = CVehicle::GetPool();
CVehicle* pVehicle;
s32 i = (s32) VehiclePool->GetSize();
while(i--)
{
bool bHeliHasOrders = false;
pVehicle = VehiclePool->GetSlot(i);
// bMoveAwayFromPlayer is synced, don't set for clones otherwise helicopters will fly off when they migrate...
if(pVehicle && !pVehicle->IsNetworkClone() && pVehicle->GetVehicleType() == VEHICLE_TYPE_HELI && pVehicle->PopTypeIsRandom() && pVehicle->IsLawEnforcementVehicle())
{
for (s32 iSeat = 0; iSeat < pVehicle->GetSeatManager()->GetMaxSeats(); iSeat++)
{
CPed* pPedInSeat = pVehicle->GetSeatManager()->GetPedInSeat(iSeat);
if (pPedInSeat && pPedInSeat->GetPedIntelligence()->GetOrder())
{
bHeliHasOrders = true;
}
}
if(!bHeliHasOrders)
{
pVehicle->m_nVehicleFlags.bMoveAwayFromPlayer = true;
}
}
}
// s32 ReqNumHelis, /*Allowed,*/ NumRandomHelis = 0;
//
// ReqNumHelis = CGameWorld::FindLocalPlayerWanted()->NumOfHelisRequired(CGameWorld::FindLocalPlayerWanted()->GetWantedLevel());
//
// #define MAXNUMHELISFOR1PLAYER (10) // The number of police helis we can have chasing one player. (Should never reach this)
//
// CHeli *apHelis[MAXNUMHELISFOR1PLAYER];
// CHeli *apHelis_Network[NUM_RAISED_HEIGHT_LEVELS_NETWORK]; // 1 for the each of the different heights
// s32 numHelisFound = 0;
// s32 totalNumActiveHelis = 0;
//
// for (s32 n = 0; n < NUM_RAISED_HEIGHT_LEVELS_NETWORK; n++)
// {
// apHelis_Network[n] = NULL;
//}
//for (s32 n = 0; n < MAXNUMHELISFOR1PLAYER; n++)
//{
// apHelis[n] = NULL;
//}
//
//
// CVehicle::Pool *VehiclePool = CVehicle::GetPool();
// CVehicle* pVehicle;
// s32 i=VehiclePool->GetSize();
//
// while(i--)
// {
// pVehicle = VehiclePool->GetSlot(i);
// if(pVehicle && pVehicle->GetVehicleType() == VEHICLE_TYPE_HELI)
// {
// CHeli *pHeli = (CHeli *)pVehicle;
//
// if ( pVehicle->GetStatus() != STATUS_WRECKED &&
// (pVehicle->IsUsingPretendOccupants() || (pVehicle->GetDriver() && !pVehicle->GetDriver()->IsPlayer())))
// {
// totalNumActiveHelis++;
// }
//
//
// if (pHeli->GetOwnerPlayer() && pHeli->GetOwnerPlayer()->IsMyPlayer())
// {
// // If we have already found a heli we will avoid that one (don't get too close to it)
// if (!NetworkInterface::IsGameInProgress())
// {
// if (numHelisFound >= 1)
// {
// if (pHeli->GetHeliIntelligence()->GetHeliToAvoid() != apHelis[0])
// {
// pHeli->GetHeliIntelligence()->SetHeliToAvoid(apHelis[0]);
// }
// }
// }
// else
// {
// if (pHeli->GetOwnerPlayer())
// {
// Assert(pHeli->GetHeliIntelligence()->GetRaisedHeight() >= 0 && pHeli->GetHeliIntelligence()->GetRaisedHeight() < NUM_RAISED_HEIGHT_LEVELS_NETWORK);
//
// if (apHelis_Network[pHeli->GetHeliIntelligence()->GetRaisedHeight()] == NULL)
// {
// apHelis_Network[pHeli->GetHeliIntelligence()->GetRaisedHeight()] = pHeli;
// }
// else
// {
// CHeli *pHeli1 = apHelis_Network[pHeli->GetHeliIntelligence()->GetRaisedHeight()];
// CHeli *pHeli2 = pHeli;
//
// if (pHeli2->GetOwnerPlayer() && pHeli1->GetOwnerPlayer()->GetRlGamerId() < pHeli2->GetOwnerPlayer()->GetRlGamerId()) // Make sure it's always the same heli avoiding to other.
// {
// CHeli *pTemp = pHeli1;
// pHeli1 = pHeli2;
// pHeli2 = pTemp;
// }
// pHeli1->GetHeliIntelligence()->SetHeliToAvoid(pHeli2);
// }
// }
// }
//
// Assert(numHelisFound < MAXNUMHELISFOR1PLAYER);
// if (numHelisFound < MAXNUMHELISFOR1PLAYER)
// {
// apHelis[numHelisFound] = pHeli;
// numHelisFound++;
// }
// }
// }
// }
//
// // The script can switch off the police helis
// if (!bPoliceHelisAllowed) ReqNumHelis = 0;
//
// if (CGameWorld::FindLocalPlayerWanted()->m_DontDispatchCopsForThisPlayer) ReqNumHelis = 0;
// if (!NetworkInterface::IsGameInProgress()) numHelisFound = totalNumActiveHelis;
// CHeli *pNewHeli = NULL;
//
// if (ReqNumHelis > numHelisFound && CVehicle::GetPool()->GetNoOfFreeSpaces() > 4 && CPed::GetPool()->GetNoOfFreeSpaces() > 4 &&
// (!NetworkInterface::IsGameInProgress() || totalNumActiveHelis < MAX_NUM_ACTIVE_HELIS_ON_ONE_MACHINE) )
// {
// // Make sure we don't create a fresh heli right after one was destroyed.
// if ( (!CGameWorld::GetMainPlayerInfo()) || (CGameWorld::GetMainPlayerInfo()->m_LastTimeHeliWasDestroyed==0) || (fwTimer::GetTimeInMilliseconds() > CGameWorld::GetMainPlayerInfo()->m_LastTimeHeliWasDestroyed + 30000) )
// {
// if (CGameWorld::FindLocalPlayerWanted()->GetWantedLevel() >= WANTED_LEVEL_HELI_2_DISPATCHED || NetworkInterface::IsGameInProgress())
// {
// if (CStreaming::HasObjectLoaded(MI_HELI_POLICE_2, CModelInfo::GetStreaming ModuleId()))
// {
// pNewHeli = CHeli::GenerateHeli(FindPlayerPed(), MI_HELI_POLICE_2);
// }
// }
// else
// {
// if (CStreaming::HasObjectLoaded(MI_HELI_POLICE_1, CModelInfo::GetStreaming ModuleId()))
// {
// pNewHeli = CHeli::GenerateHeli(FindPlayerPed(), MI_HELI_POLICE_1);
// }
// }
//
// if (pNewHeli)
// {
// // TODO: ** THIS CODE WILL NOT WORK ANYMORE AS PLAYER INDICIES ARE NOT CONSISTENT ACROSS THE NETWORK **
//
// pNewHeli->SetOwnerPlayer(NULL);
//
// if (NetworkInterface::IsGameInProgress())
// { // Network games: Half the helis fly at elevated height (based on player index)
// pNewHeli->GetHeliIntelligence()->SetRaisedHeight(0);//(s8)(localPlayer % NUM_RAISED_HEIGHT_LEVELS_NETWORK);
// }
// else
// { // In single player: If there are 2 helis one of them should fly higher.
// if (numHelisFound >= 1 && apHelis[0] && apHelis[0]->GetHeliIntelligence()->GetRaisedHeight() == 0)
// {
// pNewHeli->GetHeliIntelligence()->SetRaisedHeight(1);
// }
//
// if (pNewHeli->GetModelIndex() == MI_HELI_POLICE_1)
// {
// CNetworkTelemetry::EmergencySvcsCalled(MI_HELI_POLICE_1.ConvertToStreamingIndex(), CGameWorld::FindLocalPlayerCoors());
// }
// else if (pNewHeli->GetModelIndex() == MI_HELI_POLICE_2)
// {
// CNetworkTelemetry::EmergencySvcsCalled(MI_HELI_POLICE_2.ConvertToStreamingIndex(), CGameWorld::FindLocalPlayerCoors());
// }
// }
//
// Assert(numHelisFound < MAXNUMHELISFOR1PLAYER);
// if (numHelisFound < MAXNUMHELISFOR1PLAYER)
// {
// apHelis[numHelisFound] = pNewHeli;
// numHelisFound++;
// }
//
// NumRandomHelis++;
// g_PoliceScanner.ReportDispatch(1, AUD_UNIT_AIR, VEC3V_TO_VECTOR3(pNewHeli->GetTransform().GetPosition()));
// }
// }
// }
//
//
//
//
// // If we have more random helis than we need we tell them to go away
// /*Allowed = ReqNumHelis;
// for(s32 C=0; C<numHelisFound; C++)
// {
// if (apHelis[C])
// {
// // Make sure at least one passenger is alive
// bool bPassengerAlive = false;
// for( s32 i = Seat_backLeft; i <= Seat_backRight; i++ )
// {
// if( apHelis[C]->GetOccupierOfSeat((Seat) i) && !apHelis[C]->GetOccupierOfSeat((Seat) i)->IsInjured() )
// {
// bPassengerAlive = true;
// }
// }
// if(Allowed > 0 && bPassengerAlive)
// {
// Allowed--;
// }
// else
// { // This one needs to go away
// Assert(apHelis[C]->GetVehicleType() == VEHICLE_TYPE_HELI);
// CVehMission *pMission = CCarIntelligence::FindUberMissionForCar(apHelis[C]);
// Vector3 targetCoors;
//
// if (apHelis[C]->GetPosition().x > 0.0f)
// {
// targetCoors.x = WORLDLIMITS_REP_XMIN;
// }
// else
// {
// targetCoors.x = WORLDLIMITS_REP_XMAX;
// }
//
// if (apHelis[C]->GetPosition().y > 0.0f)
// {
// targetCoors.y = WORLDLIMITS_REP_YMIN;
// }
// else
// {
// targetCoors.y = WORLDLIMITS_REP_YMAX;
// }
// targetCoors.z = 400.0f;
//
// if (pMission)
// {
// pMission->m_missionType = MISSION_GOTO;
//
// pMission->m_TargetCoors = targetCoors;
// pMission->m_FlightHeight = pMission->m_MinHeightAboveTerrain = 400;
// }
// apHelis[C]->GetHeliIntelligence()->GetMission()->m_missionType = MISSION_FLEE;
// apHelis[C]->GetIntelligence()->GetMission()->m_FlightHeight = apHelis[C]->GetIntelligence()->GetMission()->m_MinHeightAboveTerrain = 400;
// apHelis[C]->GetIntelligence()->GetMission()->m_TargetCoors = targetCoors;
//
// apHelis[C]->m_nVehicleFlags.bMoveAwayFromPlayer = true;
// apHelis[C]->PopTypeSet(POPTYPE_RANDOM_AMBIENT); // Heli can now be removed by population code.
//
// apHelis[C]->m_ExtendedRemovalRange = 0;
//
// CGameWorld::GetMainPlayerInfo()->m_LastTimeHeliWasDestroyed = fwTimer::GetTimeInMilliseconds();
// }
// }
// }*/
}
////////////////////////////////////////////////////////////////////////////////
// FUNCTION: RemovePoliceHelisUponDeathArrest
// FUNCTION: When player dies or gets arrested this function is called to remove active helis.
// As you can hear helis miles away it is odd for the helis to still be there
////////////////////////////////////////////////////////////////////////////////
void CHeli::RemovePoliceHelisUponDeathArrest()
{
// Also remove the ones that are in the pool that we don't have pointers to.
CVehicle::Pool *VehiclePool = CVehicle::GetPool();
CVehicle* pVehicle;
s32 i = (s32) VehiclePool->GetSize();
while(i--)
{
pVehicle = VehiclePool->GetSlot(i);
if(pVehicle)
{
CVehicleModelInfo* vmi = pVehicle->GetVehicleModelInfo();
Assert(vmi);
if(vmi->GetVehicleType() == VEHICLE_TYPE_HELI && vmi->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_LAW_ENFORCEMENT))
{
if (!pVehicle->PopTypeIsMission())
{
CVehicleFactory::GetFactory()->Destroy(pVehicle);
}
}
}
}
}
///////////////////////////////////////////////////////////////////////
//
// FUNCTION: GenerateHeli
// PURPOSE: Generates one heli
//
///////////////////////////////////////////////////////////////////////
CHeli* CHeli::GenerateHeli(CPed* pTargetPed, u32 mi, u32 driverPedMi, bool bFadeIn)
{
Assert(pTargetPed);
Vector3 vTargetPedPosition;
bool bShouldSpawnWithinLastSpottedLocation = pTargetPed->GetPlayerWanted() && pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwatHeliSpawnWithinLastSpottedLocation);
if (bShouldSpawnWithinLastSpottedLocation)
{
vTargetPedPosition = pTargetPed->GetPlayerWanted()->m_LastSpottedByPolice;
}
else
{
vTargetPedPosition = VEC3V_TO_VECTOR3(pTargetPed->GetTransform().GetPosition());
}
Vector3 Coors = CHeli::FindSpawnCoordinatesForHeli(pTargetPed);
bool canCreateHeli = true;
CHeli *pNewHeli = 0;
if(NetworkInterface::IsGameInProgress())
{
const u32 numRequiredPeds = 3;
const u32 numRequiredCars = 1;
const u32 numRequiredObjects = 0;
canCreateHeli = NetworkInterface::CanRegisterObjects(numRequiredPeds, numRequiredCars, numRequiredObjects, 0, 0, false);
}
if(canCreateHeli)
{
Matrix34 mat;
mat.c = Vector3(0.0f, 0.0f, 1.0f);
mat.a = vTargetPedPosition - Coors;
mat.a.z = 0.0f;
mat.a.Normalize();
mat.b.Cross(mat.c, mat.a);
mat.d = Coors;
fwModelId modelId((strLocalIndex(mi)));
pNewHeli = (CHeli *)CVehicleFactory::GetFactory()->Create(modelId, ENTITY_OWNEDBY_POPULATION, POPTYPE_RANDOM_AMBIENT, &mat); // Was permanent vehicle but now allow helis to be removed.
}
if(pNewHeli)
{
CGameWorld::Add(pNewHeli, CGameWorld::OUTSIDE );
pNewHeli->SetHeading(fwAngle::GetRadianAngleBetweenPoints(vTargetPedPosition.x, vTargetPedPosition.y, Coors.x, Coors.y));
pNewHeli->SetUpDriver(false, false, driverPedMi);
// Don't create Passengers here, schedule them after the helicopter is created - they need access to ped group info
// Don't create cops in the front passenger seat.
//pNewHeli->SetupPassenger(2, false);
//pNewHeli->SetupPassenger(3, false);
// pNewHeli->m_nHeliFlags.bUseSearchLightOnTarget = true;
//
// CVehMission *pMission = CCarIntelligence::FindUberMissionForCar(pNewHeli);
//
// pMission->m_missionType = MISSION_POLICE_BEHAVIOUR;
// pMission->m_pTargetEntity = pTargetPed;
// pMission->m_CruiseSpeed = 70;
// pMission->m_FlightHeight = 40;
// pMission->m_MinHeightAboveTerrain = 20;
//
// make the audio skip the startup sequence
if(pNewHeli->GetVehicleAudioEntity()->GetAudioVehicleType() == AUD_VEHICLE_HELI)
{
static_cast<audHeliAudioEntity*>(pNewHeli->GetVehicleAudioEntity())->SetHeliShouldSkipStartup(true);
}
pNewHeli->SwitchEngineOn(true);
pNewHeli->m_fMainRotorSpeed = MIN_ROT_SPEED_HELI_CONTROL * 1.25f; // Make sure we're off to a flying start.
pNewHeli->DelayedRemovalTimeReset(30000);
if(bFadeIn)
{
pNewHeli->GetLodData().SetResetDisabled(false);
pNewHeli->GetLodData().SetResetAlpha(true);
}
}
return (pNewHeli);
}
Vector3 CHeli::FindSpawnCoordinatesForHeli(const CPed* pTargetPed, bool bReturnFirstValid)
{
float fAngle = 0.0f;
Vector3 vTargetPedPosition;
bool bShouldSpawnWithinLastSpottedLocation = pTargetPed->GetPlayerWanted() && pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_SwatHeliSpawnWithinLastSpottedLocation);
if (bShouldSpawnWithinLastSpottedLocation)
{
vTargetPedPosition = pTargetPed->GetPlayerWanted()->m_LastSpottedByPolice;
}
else
{
vTargetPedPosition = VEC3V_TO_VECTOR3(pTargetPed->GetTransform().GetPosition());
}
// Check if our target is wanted and in a plane or heli
bool bWantedTargetInHeliOrPlane = false;
const CVehicle* pTargetPedVehicle = pTargetPed->GetVehiclePedInside();
if( pTargetPedVehicle && (pTargetPedVehicle->InheritsFromHeli() || pTargetPedVehicle->InheritsFromPlane()) &&
pTargetPed->GetPlayerWanted() && pTargetPed->GetPlayerWanted()->GetWantedLevel() > WANTED_CLEAN )
{
// See how fast their plane or heli is moving
ScalarV scVehicleVelocitySq = ScalarVFromF32(pTargetPedVehicle->GetVelocity().Mag2());
ScalarV scMinVehicleVelocitySq = ScalarVFromF32(sfMinWantedTargetInHeliOrPlaneVelocity * sfMinWantedTargetInHeliOrPlaneVelocity);
if(IsGreaterThanAll(scVehicleVelocitySq, scMinVehicleVelocitySq))
{
// If moving fast enough we want to specify a range for the angle that they spawn at to be in front but off to the side a bit
bWantedTargetInHeliOrPlane = true;
}
}
// Find start coordinates for heli.
float fRange = bWantedTargetInHeliOrPlane ? sfWantedTargetInHeliOrPlaneSpawnDistance : 250.0f;
Vector3 Coors;
static dev_s32 s_iMaxTries = 5;
for(int i = 0; i < s_iMaxTries; ++i)
{
Coors = vTargetPedPosition;
if(!bWantedTargetInHeliOrPlane)
{
fAngle = fwRandom::GetRandomNumberInRange(0.0f, TWO_PI);
}
else
{
float fOffsetAngle = fwRandom::GetRandomNumberInRange(sfMinSpawnOffsetAngleForWantedTarget, sfMaxSpawnOffsetAngleForWantedTarget);
fOffsetAngle = fwRandom::GetRandomTrueFalse() ? fOffsetAngle : -fOffsetAngle;
fAngle = fwAngle::LimitRadianAngleSafe(pTargetPedVehicle->GetTransform().GetHeading() + fOffsetAngle);
}
Coors.x -= fRange * rage::Sinf(fAngle);
Coors.y += fRange * rage::Cosf(fAngle);
// Off the map. We're going to have to pick a different coordinate
if (Coors.x < WORLDLIMITS_REP_XMIN || Coors.x > WORLDLIMITS_REP_XMAX ||
Coors.y < WORLDLIMITS_REP_YMIN || Coors.y > WORLDLIMITS_REP_YMAX )
{
fAngle += PI;
Coors = vTargetPedPosition;
Coors.x += fRange * rage::Cosf(fAngle);
Coors.y += fRange * rage::Sinf(fAngle);
}
float fMinZ = (Coors.z + 40.0f);
#if __BANK
TUNE_BOOL(IGNORE_HEIGHT_MAP_FOR_HELI_SPAWN, false);
if (IGNORE_HEIGHT_MAP_FOR_HELI_SPAWN)
{
// Heli should be quite high
Coors.z = fMinZ;
// Do a test to make sure the heli is above the map collision.
// This might not work as the collision might not be streamed in.
Coors.z = rage::Max(Coors.z, WorldProbe::FindGroundZForCoord(BOTTOM_SURFACE,Coors.x, Coors.y) + 20.0f);
}
else
#endif
{
// Heli should be quite high (above everything else)
Coors.z = Max(fMinZ, CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(Coors.x, Coors.y));
}
if (camInterface::IsSphereVisibleInGameViewport(Coors, 7.5f))
{
continue;
}
else if(NetworkInterface::IsGameInProgress() && NetworkInterface::IsVisibleToAnyRemotePlayer(Coors, 7.5f, fRange))
{
continue;
}
if(!bWantedTargetInHeliOrPlane)
{
static dev_float s_fCloserRange = 75.0f;
// Store off this position and calculate a closer one
Vector3 vFarCoords = Coors;
Coors.x -= s_fCloserRange * rage::Cosf(fAngle);
Coors.y -= s_fCloserRange * rage::Sinf(fAngle);
#if __BANK
if(!IGNORE_HEIGHT_MAP_FOR_HELI_SPAWN)
#endif
{
Coors.z = Max(fMinZ, CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(Coors.x, Coors.y));
}
// Check if the closer position is visible. If it is revert to the further position
if(camInterface::IsSphereVisibleInGameViewport(Coors, 7.5f))
{
Coors = vFarCoords;
}
else if(NetworkInterface::IsGameInProgress() && NetworkInterface::IsVisibleToAnyRemotePlayer(Coors, 7.5f, s_fCloserRange))
{
Coors = vFarCoords;
}
}
if (bReturnFirstValid)
break;
}
return Coors;
}
// static bank_float s_fRopeAdjustmentZ = 0.85f;
// static bank_float s_fRopeAdjustmentX = 1.2f;
// static bank_float s_fRopeAdjustmentY = 0.85f;
//////////////////////////////////////////////////////////////////////////
// Get the position we want to attach to the rope to on the heli
//////////////////////////////////////////////////////////////////////////
void CHeli::GetRopeAttachPosition(Vector3& vAttachPos, bool bDriverSide, s32 iSeatId)
{
// Just some sensible initial position in case bone is not found
vAttachPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
float fAttachOffsetX = 0.0f;
float fAttachOffsetY = 0.0f;
float fAttachOffsetZ = 0.0f;
eHierarchyId leftAttachBone = HELI_ROPE_ATTACH_A;
eHierarchyId rightAttachBone = HELI_ROPE_ATTACH_B;
Matrix34 matRopeAttach;
bool bIsAnnihilator = MI_HELI_POLICE_2.IsValid() && GetModelIndex() == MI_HELI_POLICE_2;
bool bIsAnnihilator2 = MI_HELI_ANNIHILATOR2.IsValid() && GetModelIndex() == MI_HELI_ANNIHILATOR2;
// Provide some bone overrides for certain helicopters, as we can't add new bones
if (bIsAnnihilator || bIsAnnihilator2)
{
Matrix34 matLocalRopeAttach;
leftAttachBone = VEH_SIREN_2;
rightAttachBone = VEH_SIREN_1;
fAttachOffsetZ = -0.8f;
if (bDriverSide)
{
// Rear Left
fAttachOffsetX = -0.5f;
if (!HasComponent(leftAttachBone))
{
aiAssertf(0, "Trying to create rope for the vehicle: %s when it doesn't have bone to attach it to!", GetVehicleModelInfo()->GetModelName());
return;
}
GetGlobalMtx(GetBoneIndex(leftAttachBone), matRopeAttach);
if (iSeatId == 2)
{
fAttachOffsetY = 0.5f;
}
else if (iSeatId == 4)
{
fAttachOffsetY = -0.3f;
}
}
else
{
// Rear Right
fAttachOffsetX = 0.5f;
if (!HasComponent(rightAttachBone))
{
aiAssertf(0, "Trying to create rope for the vehicle: %s when it doesn't have bone to attach it to!", GetVehicleModelInfo()->GetModelName());
return;
}
GetGlobalMtx(GetBoneIndex(rightAttachBone), matRopeAttach);
if (iSeatId == 3)
{
fAttachOffsetY = 0.5f;
}
else if (iSeatId == 5)
{
fAttachOffsetY = -0.3f;
}
}
Vector3 vLocalRopeAttachPosition = VEC3_ZERO;
if (bIsAnnihilator)
vLocalRopeAttachPosition = Vector3(0.0f, fAttachOffsetY, fAttachOffsetZ);
else
vLocalRopeAttachPosition = Vector3(fAttachOffsetX, fAttachOffsetY, fAttachOffsetZ);
matRopeAttach.Transform(vLocalRopeAttachPosition, vAttachPos);
}
//Find rappel attach location for the Buzzards
else if ((MI_HELI_BUZZARD.IsValid() && GetModelIndex() == MI_HELI_BUZZARD) || (MI_HELI_BUZZARD2.IsValid() && GetModelIndex() == MI_HELI_BUZZARD2))
{
Matrix34 matLocalRopeAttach;
if (bDriverSide)
{
// BUZZARD LEFT
leftAttachBone = VEH_ENGINE;
fAttachOffsetX = -0.5f;
fAttachOffsetZ = -0.2f;
if (!HasComponent(leftAttachBone))
{
aiAssertf(0, "Trying to create rope for the vehicle: %s when it doesn't have bone to attach it to!", GetVehicleModelInfo()->GetModelName());
return;
}
GetGlobalMtx(GetBoneIndex(leftAttachBone), matRopeAttach);
}
else
{
// BUZZARD RIGHT
rightAttachBone = VEH_ENGINE;
fAttachOffsetX = 0.5f;
fAttachOffsetZ = -0.2f;
if (!HasComponent(rightAttachBone))
{
aiAssertf(0, "Trying to create rope for the vehicle: %s when it doesn't have bone to attach it to!", GetVehicleModelInfo()->GetModelName());
return;
}
GetGlobalMtx(GetBoneIndex(rightAttachBone), matRopeAttach);
}
Vector3 vLocalRopeAttachPosition = Vector3(fAttachOffsetX, 0.0f, fAttachOffsetZ);
matRopeAttach.Transform(vLocalRopeAttachPosition, vAttachPos);
}
else
{
if (bDriverSide)
{
if (!HasComponent(leftAttachBone))
{
aiAssertf(0, "Trying to create rope for the vehicle: %s when it doesn't have bone to attach it to!", GetVehicleModelInfo()->GetModelName());
return;
}
GetGlobalMtx(GetBoneIndex(leftAttachBone), matRopeAttach);
}
else
{
if (!HasComponent(rightAttachBone))
{
aiAssertf(0, "Trying to create rope for the vehicle: %s when it doesn't have bone to attach it to!", GetVehicleModelInfo()->GetModelName());
return;
}
GetGlobalMtx(GetBoneIndex(rightAttachBone), matRopeAttach);
}
vAttachPos = matRopeAttach.d;
vAttachPos.z += fAttachOffsetZ;
}
}
#define ROPE_WEIGHT_SCALE 8.0f // technically is time scale , not weight
//////////////////////////////////////////////////////////////////////////
// PURPOSE : Creates a rope for the desired side (if it doesn't exist already)
//////////////////////////////////////////////////////////////////////////
ropeInstance* CHeli::CreateAndAttachRope(bool bDriverSide, s32 iSeatId, float fLength, float fMinLength, float fMaxLength, float fLengthChangeRate, int ropeType, int numSections, bool lockFromFront)
{
// Get the matrix of the proper bone.
Vector3 vAttachPosition;
GetRopeAttachPosition(vAttachPosition, bDriverSide, iSeatId);
eRopeID eSeatRopeID = GetRopeIdFromSeat(bDriverSide, iSeatId);
ropeInstance* pRopeForSeat = GetRope(eSeatRopeID);
// Either get the existing rope or create a new one
ropeInstance* pNewRope = NULL;
if(pRopeForSeat)
{
pNewRope = pRopeForSeat;
}
else
{
Vec3V rot(0.0f, 0.0f, -1.0f);
ropeManager* pRopeManager = CPhysics::GetRopeManager();
Assert( pRopeManager );
pNewRope = pRopeManager->AddRope(VECTOR3_TO_VEC3V(vAttachPosition), rot, fLength, fMinLength, fMaxLength, fLengthChangeRate, ropeType, numSections, true, lockFromFront, ROPE_WEIGHT_SCALE, false, true );
Assert( pNewRope );
if( !pNewRope )
return NULL;
pNewRope->SetNewUniqueId();
pNewRope->SetPhysInstFlags( ArchetypeFlags::GTA_ENVCLOTH_OBJECT_TYPE, ArchetypeFlags::GTA_ENVCLOTH_OBJECT_INCLUDE_TYPES & (~ArchetypeFlags::GTA_VEHICLE_TYPE) );
// Set the proper rope
Assertf( !m_Ropes[eSeatRopeID], "Stomping memory! There is already rope created for seat id %d", (int)eSeatRopeID);
m_Ropes[eSeatRopeID]=pNewRope;
#if GTA_REPLAY
CReplayMgr::RecordPersistantFx<CPacketAddRope>(
CPacketAddRope(pNewRope->GetUniqueID(), VECTOR3_TO_VEC3V(vAttachPosition), rot, fLength, fMinLength, fMaxLength, fLengthChangeRate, ropeType, numSections, true, lockFromFront, ROPE_WEIGHT_SCALE, false, true),
CTrackedEventInfo<ptxEffectRef>((ptxEffectRef)pNewRope),
NULL,
true);
#endif
}
// Attach to the heli if not already attached
if(!pNewRope->IsAttached(GetCurrentPhysicsInst()))
{
pNewRope->AttachToObject(VECTOR3_TO_VEC3V(vAttachPosition), GetCurrentPhysicsInst(), 0, NULL, NULL);
#if GTA_REPLAY
CReplayMgr::RecordPersistantFx<CPacketAttachRopeToEntity>(
CPacketAttachRopeToEntity(pNewRope->GetUniqueID(), VECTOR3_TO_VEC3V(vAttachPosition), 0, eSeatRopeID, pNewRope->GetLocalOffset() ),
CTrackedEventInfo<ptxEffectRef>((ptxEffectRef)pNewRope),
this,
false);
#endif
}
#if __ASSERT
strLocalIndex txdSlot = g_TxdStore.FindSlot(ropeDataManager::GetRopeTxdName());
if (txdSlot != -1)
{
Assertf(CStreaming::HasObjectLoaded(txdSlot, g_TxdStore.GetStreamingModuleId()), "Rope txd not streamed in for vehicle %s", GetModelName());
}
#endif // __ASSERT
return pNewRope;
}
ropeAttachment* CHeli::AttachObjectToRope(bool bDriverSide, s32 iSeatId, CEntity* pAttachToEntity, float fEntityMoveSpeed)
{
eRopeID eSeatRopeID = GetRopeIdFromSeat(bDriverSide, iSeatId);
ropeInstance* pRope = GetRope(eSeatRopeID);
if(!pRope)
{
return NULL;
}
Vector3 vAttachPosition;
GetRopeAttachPosition(vAttachPosition, bDriverSide, iSeatId);
static bank_float fAttachDist = 1.5f;
#if GTA_REPLAY
CReplayMgr::RecordPersistantFx<CPacketAttachObjectsToRopeArray>(
CPacketAttachObjectsToRopeArray((int)pRope->GetUniqueID(), VECTOR3_TO_VEC3V(vAttachPosition), pAttachToEntity->GetTransform().GetPosition(), fAttachDist, fEntityMoveSpeed),
CTrackedEventInfo<ptxEffectRef>((ptxEffectRef)pRope),
this,
pAttachToEntity,
false);
#endif
return &pRope->AttachObjectsToConstraintArray(VECTOR3_TO_VEC3V(vAttachPosition), pAttachToEntity->GetTransform().GetPosition(), GetCurrentPhysicsInst(), pAttachToEntity->GetCurrentPhysicsInst(), 0, 0, fAttachDist, fEntityMoveSpeed);
}
bool CHeli::IsRopeAttachedToNonHeliEntity(const ropeInstance* pRopeInstance) const
{
if(pRopeInstance)
{
phInst* pHeliInst = GetCurrentPhysicsInst();
if(pRopeInstance->HasAttachmentsInArray())
{
return true;
}
else if(pRopeInstance->GetInstanceA() && pRopeInstance->GetInstanceB())
{
return (pRopeInstance->GetInstanceA() != pHeliInst || pRopeInstance->GetInstanceB() != pHeliInst);
}
else if(pRopeInstance->GetAttachedTo())
{
return pRopeInstance->GetAttachedTo() != pHeliInst;
}
}
return false;
}
bool CHeli::AreRopesAttachedToNonHeliEntity() const
{
for(int i = 0; i < eNumRopeIds; ++i)
{
if(IsRopeAttachedToNonHeliEntity(m_Ropes[i]))
{
return true;
}
}
return false;
}
bool CHeli::CanEntitiesAttachToRope(bool bDriverSide, s32 iSeatId)
{
eRopeID eSeatRopeID = GetRopeIdFromSeat(bDriverSide, iSeatId);
ropeInstance* pRopeForSeat = GetRope(eSeatRopeID);
return pRopeForSeat && !pRopeForSeat->GetIsUnwindingFront();
}
void CHeli::AttachAntiSwayToEntity(CPhysical* pEntity, float fSpringDistance)
{
m_pAntiSwayEntity = pEntity;
m_AntiSwaySpringDistance = fSpringDistance;
}
CVehicleGadgetPickUpRope *CHeli::AddPickupRope()
{
s32 iMountAAttachHelperBoneIndex = GetBoneIndex(VEH_TOW_MOUNT_A);
s32 iMountBAttachHelperBoneIndex = GetBoneIndex(VEH_TOW_MOUNT_B);
if(iMountAAttachHelperBoneIndex > -1 && iMountBAttachHelperBoneIndex > -1)
{
//Make sure the bones are valid
Assert(GetSkeleton() && iMountAAttachHelperBoneIndex < GetSkeleton()->GetBoneCount() && iMountBAttachHelperBoneIndex < GetSkeleton()->GetBoneCount());
CVehicleGadgetPickUpRope* pPickUpRope = NULL;
switch(m_nPickupRopeType)
{
case PICKUP_HOOK:
pPickUpRope = rage_new CVehicleGadgetPickUpRopeWithHook(iMountAAttachHelperBoneIndex, iMountBAttachHelperBoneIndex);
break;
case PICKUP_MAGNET:
pPickUpRope = rage_new CVehicleGadgetPickUpRopeWithMagnet(iMountAAttachHelperBoneIndex, iMountBAttachHelperBoneIndex);
((CVehicleGadgetPickUpRopeWithMagnet*)pPickUpRope)->InitAudio(this);
break;
}
pPickUpRope->Init(this);
AddVehicleGadget(pPickUpRope);
static_cast<audHeliAudioEntity*>(GetVehicleAudioEntity())->PlayHookDeploySound();
if( m_InitialRopeLength > 0.0f )
{
pPickUpRope->SetRopeLength(m_InitialRopeLength, m_InitialRopeLength, false);
}
return pPickUpRope;
}
return NULL;
}
void CHeli::RemoveRopesAndHook()
{
for(int i = 0; i < GetNumberOfVehicleGadgets(); i++)
{
CVehicleGadget *pVehicleGadget = GetVehicleGadget(i);
if(pVehicleGadget && (pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET))
{
CVehicleGadgetPickUpRope *pPickUpRope = static_cast<CVehicleGadgetPickUpRope*>(pVehicleGadget);
pPickUpRope->DeleteRopesPropAndConstraints(this);
return;
}
}
}
bool CHeli::GetIsCargobob() const
{
if(GetModelIndex() == MI_HELI_CARGOBOB || GetModelIndex() == MI_HELI_CARGOBOB2 || GetModelIndex() == MI_HELI_CARGOBOB3 || GetModelIndex() == MI_HELI_CARGOBOB4 || GetModelIndex() == MI_HELI_CARGOBOB5)
{
return true;
}
return false;
}
bool CHeli::GetWheelDeformation(const CWheel *pWheel, Vector3 &vDeformation) const
{
return m_bHasLandingGear && m_landingGear.GetWheelDeformation( this, pWheel, vDeformation );
}
bool CHeli::DoesBulletHitPropellerBound(int iComponent) const
{
bool bIsDiscBound;
int iPropellerIndex;
return DoesBulletHitPropellerBound(iComponent, bIsDiscBound, iPropellerIndex);
}
bool CHeli::DoesBulletHitPropellerBound(int iComponent, bool &bHitDiscBound, int &iPropellerIndex) const
{
for(int i =0; i < m_iNumPropellers; i++)
{
if(m_propellerCollisions[i].GetFragDisc() == iComponent)
{
bHitDiscBound = true;
iPropellerIndex = i;
return true;
}
else if(m_propellerCollisions[i].GetFragGroup() > -1)
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[m_propellerCollisions[i].GetFragGroup()];
for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++)
{
if(iComponent == m_propellerCollisions[i].GetFragChild() + iChild)
{
bHitDiscBound = false;
iPropellerIndex = i;
return true;
}
}
}
}
return false;
}
static dev_float ms_fBlockBulletSpeedRatioThreshold = 0.1f;
bool CHeli::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()/ms_fRotorSpeedMults[iPropellerIndex];
if(bHitDiscBound)
{
return iPropellerIndex != Rotor_Main // Don't let bullet hit the main rotor disc bound, B*1450124
&& fSpeedRatio > ms_fBlockBulletSpeedRatioThreshold
&& fSpeedRatio > fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
}
else
{
return fSpeedRatio <= ms_fBlockBulletSpeedRatioThreshold;
}
}
return false;
}
bool CHeli::DoesProjectileHitPropeller(int iComponent) const
{
bool bHitDiscBound;
int iPropellerIndex;
if(DoesBulletHitPropellerBound(iComponent, bHitDiscBound, iPropellerIndex))
{
float fSpeedRatio = m_propellers[iPropellerIndex].GetSpeed()/ms_fRotorSpeedMults[iPropellerIndex];
if(bHitDiscBound)
{
return fSpeedRatio > ms_fBlockBulletSpeedRatioThreshold
&& fSpeedRatio > fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
}
else
{
return fSpeedRatio <= ms_fBlockBulletSpeedRatioThreshold;
}
}
return false;
}
bool CHeli::NeedUpdatePropellerBound(int iPropellerIndex) const
{
float fSpeedRatio = m_propellers[iPropellerIndex].GetSpeed()/ms_fRotorSpeedMults[iPropellerIndex];
return fSpeedRatio <= ms_fBlockBulletSpeedRatioThreshold;
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SwitchPoliceHelis
// PURPOSE : For the script to decide whether the police helis are allowed.
///////////////////////////////////////////////////////////////////////////////////
void CHeli::SwitchPoliceHelis(bool bSwitch)
{
bPoliceHelisAllowed = bSwitch;
}
bank_float bfWindyWindMultForHeli = 2.0f;
bank_float bfRainingWindMultForHeli = 1.5f;
bank_float bfSnowingWindMultForHeli = 1.5f;
bank_float bfSandStormWindMultForHeli = 1.5f;
float CHeli::ComputeWindMult()
{
// Disable wind effect when heli is landed
if(!m_bIsInAir)
{
return 0.0f;
}
float fMult = 1.0f;
if(pHandling && pHandling->GetFlyingHandlingData())
{
fMult *= pHandling->GetFlyingHandlingData()->m_fWindMult;
}
if(g_weather.IsWindy())
{
fMult *= bfWindyWindMultForHeli;
}
if(g_weather.IsRaining())
{
fMult *= bfRainingWindMultForHeli;
}
if(g_weather.IsSnowing())
{
fMult *= bfSnowingWindMultForHeli;
}
if(g_weather.GetSandstorm() > 0.2f)
{
fMult *= bfSandStormWindMultForHeli;
}
return fMult;
}
bank_float bfWindyTurbulenceMultForHeli = 0.2f;
bank_float bfRainingTurbulenceMultForHeli = 0.1f;
bank_float bfSnowingTurbulenceMultForHeli = 0.1f;
bank_float bfSandStormTurbulenceMultForHeli = 0.1f;
float CHeli::WeatherTurbulenceMult()
{
float fMult = 0.0f;
if(g_weather.IsWindy())
{
fMult += bfWindyTurbulenceMultForHeli;
}
if(g_weather.IsRaining())
{
fMult += bfRainingTurbulenceMultForHeli;
}
if(g_weather.IsSnowing())
{
fMult += bfSnowingTurbulenceMultForHeli;
}
if(g_weather.GetSandstorm() > 0.2f)
{
fMult += bfSandStormTurbulenceMultForHeli;
}
return fMult;
}
#if __BANK
void CHeli::InitRotorWidgets(bkBank& bank)
{
const char* strPropNames[CRotaryWingAircraft::Num_Heli_Rotors] =
{
"Main",
"Rear"
};
CompileTimeAssert(CRotaryWingAircraft::Num_Heli_Rotors == 2);
bank.PushGroup("Heli rotors");
for(int i =0; i < Num_Heli_Rotors; i++)
{
bank.AddSlider(strPropNames[i],&ms_fRotorSpeedMults[i],0.0f,100.0f,0.01f);
}
bank.PopGroup();
}
void CHeli::InitTurbulenceWidgets(bkBank& bank)
{
bank.PushGroup("Heli turbulence");
bank.AddSlider("Turbulence roll noise lower limit",&sfRandomRollNoiseLower,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence roll noise upper limit",&sfRandomRollNoiseUpper,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence pitch noise lower limit",&sfRandomPitchNoiseLower,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence pitch noise upper limit",&sfRandomPitchNoiseUpper,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence drop noise lower limit",&sfRandomDropNoiseLower,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence drop noise upper limit",&sfRandomDropNoiseUpper,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence noise speed limit",&sfRandomNoiseSpeedMax,0.0f,100.0f,1.0f);
bank.AddSlider("Turbulence idle multiplier for player",&sfRandomNoiseIdleMultForPlayer,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence throttle multiplier for player",&sfRandomNoiseThrottleMultForPlayer,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence speed multiplier for player",&sfRandomNoiseSpeedMultForPlayer,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence idle multiplier for AI",&sfRandomNoiseIdleMultForAI,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence throttle multiplier for AI",&sfRandomNoiseThrottleMultForAI,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence speed multiplier for AI",&sfRandomNoiseSpeedMultForAI,-10.0f,10.0f,0.01f);
bank.AddSlider("Turbulence min during",&sfTurbulenceTimeMin,0.0f,20.0f,0.01f);
bank.AddSlider("Turbulence max during",&sfTurbulenceTimeMax,0.0f,20.0f,0.01f);
bank.AddSlider("Turbulence min recover time",&sfTurbulenceRecoverTimeMin,0.0f,20.0f,0.01f);
bank.AddSlider("Turbulence max recover time",&sfTurbulenceRecoverTimeMax,0.0f,20.0f,0.01f);
bank.AddSlider("Wind multiplier in windy condition",&bfWindyWindMultForHeli,0.0f,10.0f,0.01f);
bank.AddSlider("Wind multiplier in raining condition",&bfRainingWindMultForHeli,0.0f,10.0f,0.01f);
bank.AddSlider("Wind multiplier in snow condition",&bfSnowingWindMultForHeli,0.0f,10.0f,0.01f);
bank.AddSlider("Wind multiplier in sand storm condition",&bfSandStormWindMultForHeli,0.0f,10.0f,0.01f);
bank.AddSlider("Turbulence multiplier in windy condition",&bfWindyTurbulenceMultForHeli,0.0f,1.0f,0.01f);
bank.AddSlider("Turbulence multiplier in raining condition",&bfRainingTurbulenceMultForHeli,0.0f,1.0f,0.01f);
bank.AddSlider("Turbulence multiplier in snow condition",&bfSnowingTurbulenceMultForHeli,0.0f,1.0f,0.01f);
bank.AddSlider("Turbulence multiplier in sand storm condition",&bfSandStormTurbulenceMultForHeli,0.0f,1.0f,0.01f);
bank.AddSlider("Controller vibration scale when turbulence",&sfHeliPhysicalTurbulencePedVibMult,0.0f,1.0f,0.01f);
bank.PopGroup();
}
#endif
///////////////////////////////////////////////////////////////////////////////
// UpdateEngineSpeed
///////////////////////////////////////////////////////////////////////////////
float ms_fHeliEngineSpeedDropRateWhenMissFiring = 1.0f;
float dfHeliEngineMissFireStartingHealth = 600.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float sfHeliEngineBreakDownHealth = 200.0f; // Also referenced in CVehicleDamage::ProcessPetrolTankDamage;
float bfHeliEngineMissFireMinTime = 0.5f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfHeliEngineMissFireMaxTime = 1.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfHeliEngineMissFireMinRecoverTime = 10.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float bfHeliEngineMissFireMaxRecoverTime = 20.0f; // Referenced in CVehicleDamage::ProcessPetrolTankDamage
float dfHeliEngineDegradeStartingHealth = ENGINE_DAMAGE_PLANE_DAMAGE_START;
float dfHeliEngineDegradeMinDamage = 2.0f;
float dfHeliEngineDegradeMaxDamage = 5.0f;
void CHeli::UpdateRotorSpeed()
{
int iDriverSeatIndex = GetVehicleModelInfo()->GetModelSeatInfo()->GetDriverSeat();
int iDriverSeatBoneIndex = GetVehicleModelInfo()->GetModelSeatInfo()->GetBoneIndexFromSeat(iDriverSeatIndex);
// Standard heli
float fOldRotorSpeed = m_fPrevMainRotorSpeed;
if(m_nVehicleFlags.bEngineOn && (GetStatus()==STATUS_PLAYER || GetStatus()==STATUS_PHYSICS || GetStatus()==STATUS_OUT_OF_CONTROL) && !m_Transmission.GetCurrentlyMissFiring())
{
m_fMainRotorSpeed += HELI_ROTOR_ANGULAR_ACCELERATION * fwTimer::GetTimeStep();
if(m_fMainRotorSpeed > MAX_ROT_SPEED_HELI_BLADES)
m_fMainRotorSpeed = MAX_ROT_SPEED_HELI_BLADES;
}
else
{
if(!m_nVehicleFlags.bEngineStarting)
{
bool bAnyAlivePassenger = false;
for(s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); ++iSeat)
{
CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat);
if(pPassenger && !pPassenger->IsInjured())
{
bAnyAlivePassenger = true;
break;
}
}
// only switch the engine off is no-one is on board and no-one else is about to enter the seat
CComponentReservation* pComponentReservation = m_ComponentReservationMgr.FindComponentReservation(iDriverSeatBoneIndex, false);
if (pComponentReservation && pComponentReservation->GetPedUsingComponent() == NULL && !IsNetworkClone() && !bAnyAlivePassenger && !IsFullThrottleActive())
{
SwitchEngineOff();
}
}
// stop engine suddenly if fallen in water
if(GetIsInWater() && m_fTimeInWater > 0.0f)
{
if(m_Buoyancy.GetSubmergedLevel() > 0.99f)
{
m_fMainRotorSpeed = 0.0f;
}
else if (!IsNetworkClone())
{
SwitchEngineOff();
}
}
if(m_Transmission.GetCurrentlyMissFiring() && GetVehicleDamage()->GetEngineHealth() > (sfHeliEngineBreakDownHealth + dfHeliEngineDegradeMaxDamage))
{
m_fMainRotorSpeed -= ms_fHeliEngineSpeedDropRateWhenMissFiring * fwTimer::GetTimeStep();
}
else
{
m_fMainRotorSpeed -= HELI_ROTOR_ANGULAR_DECCELERATION * fwTimer::GetTimeStep();
}
if(m_fMainRotorSpeed < 0.0f)
m_fMainRotorSpeed = 0.0f;
float fFallSpeed = DotProduct(VEC3V_TO_VECTOR3(GetTransform().GetC()), GetVelocity());
if(fFallSpeed < -2.0f && m_fMainRotorSpeed > 0.0f)
{
if(m_fMainRotorSpeed < 0.5f)
{
m_fMainRotorSpeed += 20.f*HELI_ROTOR_ANGULAR_DECCELERATION * fwTimer::GetTimeStep();
if(m_fMainRotorSpeed > 0.5f)
m_fMainRotorSpeed = 0.5f;
}
}
}
if(fOldRotorSpeed != m_fMainRotorSpeed && (fOldRotorSpeed == 0.0f || m_fMainRotorSpeed == 0.0f))
{
UpdateRotorBounds();
}
vehicleAssert(m_iNumPropellers <= Max_Num_Rotors);
// Update the propellers from the rotor speed variables
if(GetIsCargobob() || GetIsDrone() || GetIsJetPack() )
{
// The rotor speeds are the same for cargobob
for(int i =0; i < m_iNumPropellers; i++)
{
float fRotorDirection = (i == Rotor_Rear || i == Rotor_Main2) ? -1.0f : 1.0f;
float fRotorSpeedMult = ( GetIsDrone() ) ? ms_fRotorSpeedMults[ Rotor_Rear2 ] : ms_fRotorSpeedMults[ Rotor_Main ];
m_propellers[i].UpdatePropeller(fRotorDirection * m_fMainRotorSpeed * fRotorSpeedMult,fwTimer::GetTimeStep());
}
}
else
{
for(int i = 0; i < m_iNumPropellers; i++)
{
float fRotorSpeed = m_fMainRotorSpeed;
if ((i == Rotor_Rear) && GetIsTailBoomBroken())
{
fRotorSpeed = 0.0f;
}
m_propellers[i].UpdatePropeller(fRotorSpeed*ms_fRotorSpeedMults[i],fwTimer::GetTimeStep());
}
}
// calc the ground position intersection of the heli (directly down in the -z direction)
if (m_fMainRotorSpeed>0.3f && m_fMainRotorHealth>0.0f)
{
g_vfxVehicle.ProcessHeliDownwash(this);
}
// B*1991050: Now storing the last known rotor speed explcitly so we can detect changes to rotor speed across complete frames rather than just changes that were done inside this method.
// This ensures the heli rotor disc bounds are correctly updated even if the rotor speed is changed directly outside of this method (e.g. Police heli generation or via script).
m_fPrevMainRotorSpeed = m_fMainRotorSpeed;
}
void CHeli::SetMainRotorSpeed(float fMainRotorSpeed)
{
CRotaryWingAircraft::SetMainRotorSpeed(fMainRotorSpeed);
// Update the propellers from the rotor speed variables
if(GetIsCargobob() || GetIsDrone() || GetIsJetPack() )
{
// The rotor speeds are the same for cargobob
for(int i =0; i < m_iNumPropellers; i++)
{
float fRotorDirection = (i == Rotor_Rear || i == Rotor_Main2) ? -1.0f : 1.0f;
m_propellers[i].SetPropellerSpeed(fRotorDirection * m_fMainRotorSpeed * ms_fRotorSpeedMults[0]);
}
}
else
{
for(int i =0; i < m_iNumPropellers; i++)
{
m_propellers[i].SetPropellerSpeed(m_fMainRotorSpeed*ms_fRotorSpeedMults[i]);
}
}
}
void CHeli::PreRender2( const bool bIsVisibleInMainViewport )
{
#if GTA_REPLAY
if (!CReplayMgr::IsEditModeActive())
#endif
{
if( m_bHasLandingGear )
{
m_landingGear.PreRenderDoors( this );
}
}
CRotaryWingAircraft::PreRender2( bIsVisibleInMainViewport );
// jetpack vfx
if (GetIsJetPack())
{
if (!IsDummy() && !m_nVehicleFlags.bIsDrowning &&
!m_nVehicleFlags.bDisableParticles)
{
if ((m_nVehicleFlags.bEngineOn || IsRunningCarRecording()))
{
s32 airBrakeBoneIndex = GetBoneIndex(HELI_AIRBRAKE_L);
if (airBrakeBoneIndex>-1)
{
float vfxScale = Clamp( m_fJetpackStrafeForceScale, 0.0f, 1.0f );
g_vfxVehicle.UpdatePtFxThrusterJet(this, airBrakeBoneIndex, 0, vfxScale);
}
airBrakeBoneIndex = GetBoneIndex(HELI_AIRBRAKE_R);
if (airBrakeBoneIndex>-1)
{
float vfxScale = Clamp( -m_fJetpackStrafeForceScale, 0.0f, 1.0f );
g_vfxVehicle.UpdatePtFxThrusterJet(this, airBrakeBoneIndex, 1, vfxScale);
}
}
}
if( GetIsJetPack() &&
GetSkeleton() &&
!IsAnimated() )
{
if( !GetDriver() )
{
GetLandingGear().ControlLandingGear( this, CLandingGear::COMMAND_DEPLOY );
}
static dev_float sfMaxSecondaryGearRotationAmount = DtoR * -95.0f;
float rotationAmount = ( 1.0f - GetLandingGear().GetGearDeployRatio() ) * sfMaxSecondaryGearRotationAmount;
int boneIndex = GetBoneIndex( VEH_MISC_B );
SetBoneRotation( boneIndex, ROT_AXIS_LOCAL_X, rotationAmount );
GetSkeleton()->PartialUpdate( boneIndex );
}
}
}
void CHeli::ApplyDeformationToBones( const void* basePtr )
{
CRotaryWingAircraft::ApplyDeformationToBones( basePtr );
if( m_bHasLandingGear )
{
m_landingGear.ApplyDeformation( this, basePtr );
}
}
void CHeli::Fix( bool resetFrag, bool allowNetwork )
{
CRotaryWingAircraft::Fix( resetFrag, allowNetwork );
if( m_bHasLandingGear )
{
m_landingGear.Fix( this );
}
}
//////////////////////////////////////////////
// Update our ropes and wind them if needed //
//////////////////////////////////////////////
void CHeli::UpdateRopes()
{
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
return;
}
#endif
const CSeatManager* pSeatManager = GetSeatManager();
const bool bIsAnnihilator = (MI_HELI_POLICE_2.IsValid() && GetModelIndex() == MI_HELI_POLICE_2) || (MI_HELI_ANNIHILATOR2.IsValid() && GetModelIndex() == MI_HELI_ANNIHILATOR2);
bool bAllRearSeatsFree = !pSeatManager->GetPedInSeat(Seat_backLeft) && !pSeatManager->GetPedInSeat(Seat_backRight);
if (bIsAnnihilator)
{
bAllRearSeatsFree &= pSeatManager->GetPedInSeat(4) == nullptr && pSeatManager->GetPedInSeat(5) == nullptr;
}
// If we don't have any peds in our heli anymore (excluding the driver) and no-one attached to our ropes then wind them
if(bAllRearSeatsFree)
{
// Don't start winding the ropes if we still have peds attached to the heli directly
CPed* pDriver = GetDriver();
if(pDriver)
{
CPedGroup* pMyGroup = pDriver->GetPedsGroup();
const CPedGroupMembership* pGroupMembership = pMyGroup ? pMyGroup->GetGroupMembership() : NULL;
if(pGroupMembership)
{
for (int i = 0; i < CPedGroupMembership::MAX_NUM_MEMBERS; i++)
{
const CPed* pPedMember = pGroupMembership->GetMember(i);
if (pPedMember && pPedMember != pDriver && pPedMember->GetAttachParent() == this)
{
return;
}
}
}
}
for(int i = 0; i < eNumRopeIds; ++i)
{
if(m_Ropes[i] && !IsRopeAttachedToNonHeliEntity(m_Ropes[i]) && !HasScheduledOccupants())
{
s32 sSeatId = i+2; //skip front seats - we start from 1st passenger.
CPed* pLastPedInSeat = pSeatManager->GetLastPedInSeat(sSeatId);
bool bBackSeatFree = !pLastPedInSeat || !pLastPedInSeat->GetPedResetFlag(CPED_RESET_FLAG_IsRappelling) || pLastPedInSeat->GetMyVehicle() != this;
bool bSeatFree = bBackSeatFree;
if (bIsAnnihilator)
{
CPed* pPedInExtraRearSeat = pSeatManager->GetLastPedInSeat(sSeatId+2);
bool bExtraBackSeatFree = !pPedInExtraRearSeat || !pPedInExtraRearSeat->GetPedResetFlag(CPED_RESET_FLAG_IsRappelling) || pPedInExtraRearSeat->GetMyVehicle() != this;
bSeatFree &= bExtraBackSeatFree;
}
if(bSeatFree)
{
m_Ropes[i]->StopUnwindingFront();
m_Ropes[i]->StartWindingFront(7.0f);
#if GTA_REPLAY
if(CReplayMgr::ShouldRecord())
{
ropeInstance* r = m_Ropes[i];
CReplayMgr::RecordPersistantFx<CPacketRopeWinding>(
CPacketRopeWinding(r->GetLengthChangeRate(), r->GetIsWindingFront(), r->GetIsUnwindingFront(), r->GetIsUnwindingBack()),
CTrackedEventInfo<ptxEffectRef>((ptxEffectRef)r),
NULL,
false);
}
#endif
}
}
}
}
else
{
for(int i = 0; i < eNumRopeIds; ++i)
{
if(m_Ropes[i] && m_Ropes[i]->GetIsUnwindingFront() && m_Ropes[i]->GetLength() >= m_Ropes[i]->GetMaxLength())
{
m_Ropes[i]->StopUnwindingFront();
#if GTA_REPLAY
if(CReplayMgr::ShouldRecord())
{
ropeInstance* r = m_Ropes[i];
CReplayMgr::RecordPersistantFx<CPacketRopeWinding>(
CPacketRopeWinding(r->GetLengthChangeRate(), r->GetIsWindingFront(), r->GetIsUnwindingFront(), r->GetIsUnwindingBack()),
CTrackedEventInfo<ptxEffectRef>((ptxEffectRef)r),
NULL,
false);
}
#endif
}
}
}
}
void CHeli::ProcessLanded(float fTimeStep)
{
//Ensure the heli is not in the air.
if(!HasContactWheels())
{
//Clear the time the heli has spent landed.
m_fTimeSpentLanded = 0.0f;
if( GetFrameCollisionHistory()->GetMostRecentCollisionTime() <= fwTimer::GetTimeInMilliseconds() &&
GetFrameCollisionHistory()->GetMostRecentCollisionTime() > (fwTimer::GetTimeInMilliseconds() - 100) )
{
m_fTimeSpentCollidingNotLanded += fTimeStep;
}
else
{
m_fTimeSpentCollidingNotLanded = 0.0f;
}
}
else
{
//Increase the time the heli has spent landed.
m_fTimeSpentLanded += fTimeStep;
}
}
dev_float sfHeliDamageStrongMult = 0.4f;
dev_float sfHeliCrashingMult = 10.0f;
dev_float sfHeliDamageNoDriverMult = 3.0f;
dev_float dfHeliDamageUpsideDownMult = 10.0f;
dev_float sfMainRotorDamagePerSec = 10.0f;
dev_float sfMainRotorDamageFromImpactMag = 10.0f;
dev_float sfRearRotorDamagePerSec = 60.0f;
dev_float sfRearRotorDamageFromImpactMag = 60.0f;
dev_float sfTailDamageFromImpact = 12.0f;
// Use collision information to damage rotors
void CHeli::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_fMainRotorHealth <= 0.0f || m_fRearRotorHealth <= 0.0f || (m_nTailBoomGroup > -1 && pFragInst->GetGroupBroken(m_nTailBoomGroup)))
fDamageMult = sfHeliCrashingMult;
else if(m_nPhysicalFlags.bNotDamagedByCollisions)
fDamageMult = 0.0f;
else if(m_nVehicleFlags.bTakeLessDamage)
fDamageMult = sfHeliDamageStrongMult;
else if(!GetDriver())
fDamageMult = sfHeliDamageNoDriverMult;
else if(IsUpsideDown())
fDamageMult = dfHeliDamageUpsideDownMult;
bool bPassDamageToTail = false;
bool bDamageDone = false;
bool bNetworkClone = IsNetworkClone();
// check for main rotor impacts
int nMainRotorChild = m_propellerCollisions[Rotor_Main].GetFragChild();
int nRearRotorChild = m_propellerCollisions[Rotor_Rear].GetFragChild();
if(GetIsVisible() && iMyComponent==nMainRotorChild && m_fMainRotorSpeed > 0.0f && !pFragInst->GetChildBroken(nMainRotorChild))
{
if (pHitEnt)// && CPhysics::GetIsLastTimeSlice(CPhysics::GetCurrentTimeSlice()))// && (fwTimer::GetSystemFrameCount()%4)==0)
{
// do collision effects
Vector3 collPos = vMyHitPos;
Vector3 collNormal = vMyNormal;
//Vector3 collNormalNeg = -vMyNormal;
// Vector3 collVel = vecImpulse;
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 = sfMainRotorDamagePerSec * fwTimer::GetTimeStep();
fDamage += fImpulseMag * GetInvMass() * sfMainRotorDamageFromImpactMag;
fDamage *= fDamageMult;
m_fMainRotorHealth -= fDamage * m_fMainRotorHealthDamageScale;
bDamageDone = true;
if(m_fMainRotorHealth <= 0.0f)
{
BreakOffMainRotor();
}
}
physicsAssert(hitInst);
u16 nGenerationId = 0;
#if LEVELNEW_GENERATION_IDS
if(hitInst && hitInst->IsInLevel())
{
nGenerationId = PHLEVEL->GetGenerationID(hitInst->GetLevelIndex());
}
#endif // LEVELNEW_GENERATION_IDS
WorldProbe::CShapeTestFixedResults<> tempResults;
tempResults[0].SetHitComponent((u16)iOtherComponent);
tempResults[0].SetHitInst(
hitInst->GetLevelIndex(),
nGenerationId
);
tempResults[0].SetHitMaterialId(iOtherMaterial);
tempResults[0].SetHitPosition(vMyHitPos);
g_CollisionAudioEntity.ReportHeliBladeCollision(&tempResults[0], (CVehicle*)this);
}
// check for tail rotor impacts
else if(iMyComponent==nRearRotorChild && m_fMainRotorSpeed > 0.0f && !pFragInst->GetChildBroken(nRearRotorChild))
{
// pass impacts to the rear rotor on to the tail
bPassDamageToTail = true;
// do collision effects
// rear rotor take damage
if(m_nVehicleFlags.bCanBeVisiblyDamaged && !bNetworkClone)
{
float fDamage = sfRearRotorDamagePerSec * fwTimer::GetTimeStep();
fDamage += fImpulseMag * GetInvMass() * sfRearRotorDamageFromImpactMag;
fDamage *= fDamageMult;
m_fRearRotorHealth -= fDamage * m_fRearRotorHealthDamageScale;
bDamageDone = true;
if(m_fRearRotorHealth <= 0.0f)
{
BreakOffRearRotor();
StartCrashingBehavior(pHitEnt);
}
}
}
// if parent of our collision is the tail rotor then apply damage to it too
int nParentGroup = pFragInst->GetTypePhysics()->GetAllChildren()[iMyComponent]->GetOwnerGroupPointerIndex();
if(m_nTailBoomGroup > -1 && (nParentGroup==m_nTailBoomGroup || bPassDamageToTail) && !pFragInst->GetGroupBroken(m_nTailBoomGroup) && m_nVehicleFlags.bCanBeVisiblyDamaged && !bNetworkClone)
{
float fDamage = fImpulseMag * GetInvMass() * sfTailDamageFromImpact;
fDamage *= fDamageMult;
m_fTailBoomHealth -= fDamage * m_fTailBoomHealthDamageScale;
bDamageDone = true;
// once the tail's health reaches zero, let it break off if hit hard enough again
if(m_fTailBoomHealth <= 0.0)
{
fragTypeGroup* pTailGroup = pFragInst->GetTypePhysics()->GetAllGroups()[m_nTailBoomGroup];
int nFirstChild = pTailGroup->GetChildFragmentIndex();
int nNumChildren = pTailGroup->GetNumChildren();
// make sure this component doesn't break off until we want it to
for(int nChild=nFirstChild; nChild < nFirstChild + nNumChildren; nChild++)
{
((fragInstGta*)pFragInst)->ClearDontBreakFlag(BIT(nChild));
}
}
}
// make sure overall health is less than full if any helicopter components are damaged
if(bDamageDone && GetHealth() >= CREATED_VEHICLE_HEALTH)
{
if (!IsNetworkClone())
{
ChangeHealth(-1.0f);
}
}
return CRotaryWingAircraft::ProcessCollision(myInst, pHitEnt, hitInst, vMyHitPos,vOtherHitPos,fImpulseMag,vMyNormal,iMyComponent,iOtherComponent,
iOtherMaterial,bIsPositiveDepth,bIsNewContact);
}
dev_float dfHeliPushAttachedEntitySpeed = 1.0f;
dev_float dfHeliContactDepthToDetachCargo = 1.0f;
void CHeli::ProcessPreComputeImpacts(phContactIterator impacts)
{
// Disable push between cargobob and its attached entity
if(GetIsCargobob() && GetNumberOfVehicleGadgets() > 0)
{
const CPhysical *pAttachedEntity = NULL;
CVehicleGadgetPickUpRope *pPickUpRope = NULL;
for(int i = 0; i < GetNumberOfVehicleGadgets(); i++)
{
CVehicleGadget *pVehicleGadget = GetVehicleGadget(i);
if(pVehicleGadget && (pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET))
{
pPickUpRope = static_cast<CVehicleGadgetPickUpRope*>(pVehicleGadget);
pAttachedEntity = pPickUpRope->GetAttachedEntity();
break;
}
}
if(pPickUpRope && pAttachedEntity)
{
impacts.Reset();
while(!impacts.AtEnd())
{
if(!impacts.IsDisabled())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
if(pOtherEntity == pAttachedEntity)
{
if(!impacts.IsConstraint())
{
if(impacts.GetDepth() > dfHeliContactDepthToDetachCargo)
{
if(NetworkInterface::IsGameInProgress())
{
if(!NetworkUtils::IsNetworkCloneOrMigrating(pAttachedEntity))
{
pPickUpRope->DetachEntity(this);
}
else if(!impacts.IsConstraint())
{
// send an event to the owner of the attached entity requesting they detach
//CRequestDetachmentEvent::Trigger(*pAttachedEntity);
impacts.DisableImpact();
}
}
else if(!impacts.IsConstraint())
{
pPickUpRope->DetachEntity(this);
}
}
}
if(IsNetworkClone() && !impacts.IsConstraint())
{
impacts.SetDepth(Min(dfHeliPushAttachedEntitySpeed * fwTimer::GetTimeStep(), impacts.GetDepth()));
}
}
}
impacts++;
}
}
}
if( HasLandingGear() )
{
impacts.Reset();
while(!impacts.AtEnd())
{
GetLandingGear().ProcessPreComputeImpacts( this, impacts );
impacts++;
}
}
static dev_float sfThresholdForPontoonImpacts = 5.0f;
CSeaPlaneHandlingData* pSeaPlaneHandling = pHandling->GetSeaPlaneHandlingData();
if( pSeaPlaneHandling &&
GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetUp() ) ) < sfThresholdForPontoonImpacts )
{
impacts.Reset();
while( !impacts.AtEnd() )
{
if( !impacts.IsDisabled() )
{
if( (u32)impacts.GetMyComponent() == pSeaPlaneHandling->m_fLeftPontoonComponentId ||
(u32)impacts.GetMyComponent() == pSeaPlaneHandling->m_fRightPontoonComponentId )
{
Vec3V vecNorm;
impacts.GetMyNormal( vecNorm );
float normalDotUp = Dot( vecNorm, GetTransform().GetC() ).Getf();
if( normalDotUp > 0.6f )
{
impacts.DisableImpact();
impacts++;
continue;
}
}
}
impacts++;
}
}
bool isValkyrie = ( MI_HELI_VALKYRIE.IsValid() &&
( GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_VALKYRIE.GetName().GetHash() ) ) ||
( MI_HELI_VALKYRIE2.IsValid() &&
( GetVehicleModelInfo()->GetModelNameHash() == MI_HELI_VALKYRIE2.GetName().GetHash() ) );
if( IsNetworkClone() &&
isValkyrie )
{
impacts.Reset();
while(!impacts.AtEnd())
{
if(!impacts.IsDisabled())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst( pOtherInstance );
if( pOtherEntity &&
pOtherEntity->GetIsTypePed() )
{
int myComponent = impacts.GetMyComponent();
// Hack to fix GTAV - B*1978367 - If a ped hits one of the side weapons on a network clone
// Valkyrie don't move the weapons as they get forced back into position and cause the ped to be
// launched.
int valkyrieWeapon2Component = GetBoneIndex( VEH_WEAPON_2A );
int valkyrieWeapon3Component = GetBoneIndex( VEH_WEAPON_3A );
if( valkyrieWeapon2Component > -1 )
{
valkyrieWeapon2Component = GetVehicleFragInst()->GetComponentFromBoneIndex( valkyrieWeapon2Component );
}
if( valkyrieWeapon3Component > -1 )
{
valkyrieWeapon3Component = GetVehicleFragInst()->GetComponentFromBoneIndex( valkyrieWeapon3Component );
}
if( myComponent == valkyrieWeapon2Component ||
myComponent == valkyrieWeapon3Component )
{
impacts.SetMassInvScales( 0.0f, 1.0f );
}
}
}
impacts++;
}
}
impacts.Reset();
CRotaryWingAircraft::ProcessPreComputeImpacts(impacts);
// for the jet packs we want to apply self righting forces but we don't want to do that if
// there are any contacts that would oppose this
if( GetIsJetPack() )
{
impacts.Reset();
while(!impacts.AtEnd())
{
if(!impacts.IsDisabled())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst( pOtherInstance );
if( !pOtherEntity ||
!pOtherEntity->GetIsTypePed() )
{
Vec3V myNormal;
impacts.GetMyNormal( myNormal );
static ScalarV minNormalToDisableSelfRighting( -0.2f );
if( IsLessThan( myNormal.GetZ(), minNormalToDisableSelfRighting ).Getb() )
{
m_bDisableSelfRighting = true;
}
}
}
impacts++;
}
}
}
void CHeli::ProcessPrePhysics()
{
CAutomobile::ProcessPrePhysics();
static dev_bool sbForceMagicForce = false;
m_fJetPackGroundHeight = -FLT_MAX;
m_bDisableSelfRighting = GetVehicleDamage()->GetEngineHealth() <= 0.0f;
if( GetIsJetPack() &&
m_nVehicleFlags.bEngineOn )
{
bool disableWheelForces = GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_UP;
for( int i = 0; i < m_nNumWheels; i++ )
{
CWheel* pWheel = GetWheel( i );
if( pWheel )
{
pWheel->SetDisableWheelForces( disableWheelForces );
}
}
if( m_nVehicleFlags.bEngineOn &&
( GetLandingGear().GetPublicState() != CLandingGear::STATE_LOCKED_DOWN ||
sbForceMagicForce ) )
{
// when the landing gear is retracted check to see if we are close to the ground so we can apply a force to keep us above it
Vector3 vProbeDirection( 0.0f, 0.0f, -4.0f );
Matrix34 matGlobal = MAT34V_TO_MATRIX34( GetTransform().GetMatrix() );
// add the distance we expect to travel in a frame to the position
matGlobal.d += GetVelocity() * fwTimer::GetTimeStep() * 2.0f;
matGlobal.d.z += CPhysics::ms_bInStuntMode ? 0.0f : 0.5f;
//setup the capsule test.
WorldProbe::CShapeTestHitPoint testHitPoints[ 10 ];
WorldProbe::CShapeTestResults capsuleResults( testHitPoints, 10 );
const u32 iTestFlags = (ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
capsuleDesc.SetResultsStructure(&capsuleResults);
capsuleDesc.SetIncludeFlags(iTestFlags);
capsuleDesc.SetExcludeEntity(this);
capsuleDesc.SetIsDirected(true);
capsuleDesc.SetDoInitialSphereCheck(true);
capsuleDesc.SetCapsule( matGlobal.d, matGlobal.d + vProbeDirection, 0.75f );
WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc);
for(WorldProbe::ResultIterator it = capsuleResults.begin(); it < capsuleResults.last_result(); ++it)
{
if(it->IsAHit())
{
bool inGhostCollision = false;
bool beingRespotted = false;
bool useThisHit = true;
CEntity* hitEntity = it->GetHitEntity();
if(hitEntity && hitEntity->GetIsPhysical())
{
CPhysical* hitPhysical = SafeCast(CPhysical, hitEntity);
if(hitPhysical->GetIsTypeVehicle())
{
CVehicle* hitVehicle = SafeCast(CVehicle, hitEntity);
if(hitVehicle->IsBeingRespotted() ||
IsBeingRespotted() )
{
useThisHit = false;
beingRespotted = true;
}
}
if(useThisHit)
{
netObject* netEntity = hitPhysical->GetNetworkObject();
if(netEntity)
{
CNetObjPhysical* netPhysicalEntity = SafeCast(CNetObjPhysical, netEntity);
if( netPhysicalEntity->IsInGhostCollision() ||
( GetNetworkObject() &&
static_cast<CNetObjPhysical*>( GetNetworkObject() )->IsInGhostCollision() ) )
{
useThisHit = false;
inGhostCollision = true;
}
}
}
}
if( useThisHit && it->GetHitPosition().z > m_fJetPackGroundHeight &&
it->GetHitNormal().z > 0.5f )
{
if( NetworkInterface::IsGameInProgress() &&
hitEntity &&
GetNetworkObject() &&
hitEntity->GetIsPhysical() )
{
CNetObjPhysical* netPhysicalEntity = SafeCast( CNetObjPhysical, GetNetworkObject() );
if( netPhysicalEntity->IsInGhostCollision() ||
IsBeingRespotted() )
{
vehicleDebugf1( "CHeli::ProcessPrePhysics: jetpack: %s: hitting: %s with hover probe, being respotted: %d, In ghost collision: %d", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "", static_cast< CDynamicEntity* >( hitEntity )->GetNetworkObject() ? static_cast< CDynamicEntity* >( hitEntity )->GetNetworkObject()->GetLogName() : "", (int)beingRespotted, (int)inGhostCollision );
}
}
m_fJetPackGroundHeight = it->GetHitPosition().z;
}
}
}
}
}
}
ePhysicsResult CHeli::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
float planeDamageThreshold = m_Transmission.GetPlaneDamageThresholdOverride();
if (planeDamageThreshold==0.0f)
{
planeDamageThreshold = dfHeliEngineDegradeStartingHealth;
}
// Process engine degrade
if(CanEngineDegrade() && m_nVehicleFlags.bEngineOn
&& GetVehicleDamage()->GetEngineHealth() > sfHeliEngineBreakDownHealth
&& GetVehicleDamage()->GetEngineHealth() < planeDamageThreshold)
{
if(m_fEngineDegradeTimer > 1.0f)
{
Vector3 vEnginePosLocal = VEC3_ZERO;
if(GetBoneIndex(VEH_ENGINE) >= 0)
{
Matrix34 matEngineLocal = GetLocalMtx(GetBoneIndex(VEH_ENGINE));
vEnginePosLocal = matEngineLocal.d;
}
float fEngineDegradeDamage = dfHeliEngineDegradeMinDamage + (dfHeliEngineDegradeMaxDamage - dfHeliEngineDegradeMinDamage)
* ((GetVehicleDamage()->GetEngineHealth() - sfHeliEngineBreakDownHealth) / (planeDamageThreshold - sfHeliEngineBreakDownHealth));
fEngineDegradeDamage = Clamp(fEngineDegradeDamage, dfHeliEngineDegradeMinDamage, dfHeliEngineDegradeMaxDamage);
GetVehicleDamage()->ApplyDamageToEngine(this, DAMAGE_TYPE_COLLISION, fEngineDegradeDamage, vEnginePosLocal, -ZAXIS, ZAXIS, false, true, 0.0f);
m_fEngineDegradeTimer = 0.0f;
}
m_fEngineDegradeTimer += fTimeStep;
}
// 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;
}
}
if( pSeaPlaneExtension->GetAnchorHelper().IsAnchored() )
{
CBoat::UpdateBuoyancyLodMode( this, pSeaPlaneExtension->GetAnchorHelper() );
if( pSeaPlaneExtension->GetAnchorHelper().UsingLowLodMode() )
{
return PHYSICS_DONE;
}
}
}
// Script sometimes want the helicopter to keep flying until it explodes from damage
// so if they're blocking missfiring, block switching the engine off, unless the engine is dead
if( ( m_nVehicleFlags.bCanEngineMissFire || GetVehicleDamage()->GetEngineHealth() <= 0.0f )
&& m_nVehicleFlags.bEngineOn
&& GetVehicleDamage()->GetEngineHealth() < sfHeliEngineBreakDownHealth
&& !IsNetworkClone())
{
SwitchEngineOff();
}
m_fMainRotorBrokenOffSmokeLifeTime -= fTimeStep;
m_fMainRotorBrokenOffSmokeLifeTime = Max(m_fMainRotorBrokenOffSmokeLifeTime, 0.0f);
m_fRearRotorBrokenOffSmokeLifeTime -= fTimeStep;
m_fRearRotorBrokenOffSmokeLifeTime = Max(m_fRearRotorBrokenOffSmokeLifeTime, 0.0f);
if( m_bHasLandingGear )
{
m_landingGear.ProcessPhysics( this );
}
static dev_float sfVelocityThresholdForSelfLeveling = 1.0f;
static dev_float sfTimeThresholdForSelfLeveling = 0.25f;
static dev_float sfUprightThreshold = 0.97f;
Vec3V groundNormal( 0.0f, 0.0f, 1.0f );
bool inAir = IsInAir( false );
if( !inAir &&
GetWheel(0) &&
GetWheel(2) )
{
groundNormal = VECTOR3_TO_VEC3V( GetWheel(0)->GetHitNormal() + GetWheel(2)->GetHitNormal() ) * ScalarV( 0.5f );
}
float fCurrentTime = fwTimer::GetTimeInMilliseconds() * 0.0001f;
float fLeaveGroundTimeRatio = (fCurrentTime - m_fLastWheelContactTime) / sfTimeThresholdForSelfLeveling;
float fAngle = Dot( GetTransform().GetUp(), groundNormal ).Getf();
bool upRight = fAngle > sfUprightThreshold;
bool aboveVelocityThreshold = GetVelocity().Mag2() > sfVelocityThresholdForSelfLeveling;
bool hasDriver = GetDriver() != NULL;
if( !m_bDisableSelfRighting &&
GetIsJetPack() &&
( !hasDriver ||
fLeaveGroundTimeRatio < 1.0f ) &&
( m_nVehicleFlags.bEngineOn ||
aboveVelocityThreshold ||
inAir ||
!upRight ) )
{
fAngle = AcosfSafe( fAngle );
if( Abs( fAngle ) > 0.001f )
{
static dev_float selfLevelingPitchTorqueScale = 30.0f;
static dev_float selfLevelingRollTorqueScale = 20.0f;
// Calculate the rotation axis by crossing the current and desired up vectors
Vec3V vAxisToRotateOn = Cross( GetTransform().GetUp(), groundNormal );
// Apply a pitch stabilisation torque so that we roll towards the desired rotation
Vec3V vRightAxis = GetTransform().GetA();
Vec3V vRightTorque = Dot( vAxisToRotateOn, vRightAxis ) * vRightAxis * ScalarV( fAngle * selfLevelingPitchTorqueScale );
// Apply a roll stabilisation force so that we roll towards the desired rotation
Vec3V vFwdAxis = GetTransform().GetB();
Vec3V vFwdTorque = Dot( vAxisToRotateOn, vFwdAxis ) * vFwdAxis * ScalarV( fAngle * selfLevelingRollTorqueScale );
Vector3 vecTorque = VEC3V_TO_VECTOR3( vRightTorque + vFwdTorque );
vecTorque *= GetAngInertia();
if( hasDriver )
{
vecTorque *= 1.0f - fLeaveGroundTimeRatio;
}
ApplyInternalTorque( vecTorque );
}
}
if( GetIsJetPack() &&
CPhysics::GetIsLastTimeSlice( nTimeSlice ) &&
m_nVehicleFlags.bEngineOn )
{
for( int i = 0; i < NUM_HELI_WINGS_MAX; i++ )
{
int iBoneIndex = GetBoneIndex( (eHierarchyId)( (int)HELI_WING_L + i ) );
if(iBoneIndex > -1)
{
static dev_float sfMaxRotationAngle = DtoR * 25.0f;
static dev_float sfMaxRotationAngleInStrafeMode = 0.7f;
static dev_float sfMaxRotationRate = 1.0f;
static dev_float sfMaxRotationRateInStrafeMode = sfMaxRotationRate * 2.0f;
static dev_float sfMaxSpeedForTargetAngle = 20.0f;
static dev_float sfMaxWingAngleDeltaBrakingScale = 1.5f;
float maxWingAngleDelta = ( m_bStrafeMode ? sfMaxRotationRateInStrafeMode : sfMaxRotationRate ) * fTimeStep;
float targetAngle = m_fPitchControl;
float forwardVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ) / sfMaxSpeedForTargetAngle;
forwardVelocity = Clamp( forwardVelocity, -1.0f, 1.0f );
if( forwardVelocity * targetAngle >= 0.0f )
{
maxWingAngleDelta *= sfMaxWingAngleDeltaBrakingScale;
targetAngle += forwardVelocity;
}
if( !m_bStrafeMode )
{
targetAngle += ( m_fYawControl * ( (float)i - 0.5f ) );
}
else
{
targetAngle *= sfMaxRotationAngleInStrafeMode;
}
if( m_fWingAngle[ i ] != targetAngle )
{
m_fWingAngle[ i ] += Clamp( targetAngle - m_fWingAngle[ i ], -maxWingAngleDelta, maxWingAngleDelta );
m_fWingAngle[ i ] = Clamp( m_fWingAngle[ i ], -1.0f, 1.0f );
SetBoneRotation( iBoneIndex, ROT_AXIS_LOCAL_X, m_fWingAngle[ i ] * sfMaxRotationAngle, true, NULL, NULL );
GetSkeleton()->PartialUpdate( iBoneIndex );
int fragGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( iBoneIndex );
if( fragGroup > -1 &&
GetVehicleFragInst()->GetArchetype() &&
GetVehicleFragInst()->GetArchetype()->GetBound() &&
phBound::IsTypeComposite( GetVehicleFragInst()->GetArchetype()->GetBound()->GetType() ) )
{
fragTypeGroup* pGroup = GetVehicleFragInst()->GetTypePhysics()->GetAllGroups()[ fragGroup ];
phBoundComposite *pBound = (phBoundComposite *)GetVehicleFragInst()->GetArchetype()->GetBound();
UpdateLinkAttachmentMatricesRecursive( pBound, pGroup );
}
}
}
}
}
ePhysicsResult result = CAutomobile::ProcessPhysics(fTimeStep,bCanPostpone,nTimeSlice);
for(int i = 0; i < m_iNumPropellers; i++)
{
// B*1855069: This is now called every frame to keep the rotor current and last matrices in sync and to update articulated link attachment matrices.
// Not having the current and last matrices being equal allows the solver to see the large rotation due to very high rotor speed.
// Not updating the link attachment matrices causes a discrepancy between the bounds and the articulated body.
// Both of these things can cause the vehicle's swept bounding box to become very large.
// The existing NeedUpdatePropellerBound() call is now used to determine when the composite extents and BVH are updated, so that isn't being done any more often than it was previously.
// The same is done for all vehicles that use this propeller collision class.
m_propellerCollisions[i].UpdateBound(this, m_propellers[i].GetBoneIndex(), m_propellers[i].GetAngle(), m_propellers[i].GetAxis(), NeedUpdatePropellerBound(i));
}
//if( GetIsJetPack() )
//{
// if( !IsInAir() )
// {
// Vec3V groundNormal = VECTOR3_TO_VEC3V( GetWheel(0)->GetHitNormal() + GetWheel(2)->GetHitNormal() ) * ScalarV( 0.5f );
// if( Dot( GetTransform().GetC(), groundNormal ).Getf() > 0.3f )
// {
// static dev_float sfTorqueScale = 50.0f;
// Vec3V forwardVector = Cross( GetTransform().GetA(), groundNormal );
// float torque = sfTorqueScale * GetAngInertia().y * ( GetTransform().GetB().GetZf() - forwardVector.GetZf() );
// Vector3 torqueVector = VEC3V_TO_VECTOR3( GetTransform().GetA() ) * torque;
// ApplyInternalTorque( torqueVector );
// }
// }
//}
return result;
}
void CHeli::ProcessPostPhysics()
{
CRotaryWingAircraft::ProcessPostPhysics();
ProcessAntiSway(fwTimer::GetTimeStep());
}
void CHeli::ProcessWings()
{
CPed* pDriver = GetDriver();
if( pDriver )
{
if( pDriver && pDriver->IsLocalPlayer() )
{
CControl *pControl = pDriver->GetControlFromPlayer();
static bool sbHasButtonBeenUp = true;
sbHasButtonBeenUp |= pControl->GetVehicleRoof().IsUp();
bool bIsAkula = MI_HELI_AKULA.IsValid() && GetModelIndex() == MI_HELI_AKULA;
bool bIsAnnihilator2 = MI_HELI_ANNIHILATOR2.IsValid() && GetModelIndex() == MI_HELI_ANNIHILATOR2;
bool bInputHeld = pControl->GetVehicleRoof().HistoryHeldDown( CTaskVehicleConvertibleRoof::ms_uTimeToHoldButtonDown );
bool bInputTapped = pControl->GetVehicleRoof().IsReleased() && !pControl->GetVehicleRoof().IsReleasedAfterHistoryHeldDown(ms_uTimeToIgnoreButtonHeldDown);
bool bInputActivated = (bIsAkula || bIsAnnihilator2) ? bInputTapped : bInputHeld;
if( sbHasButtonBeenUp &&
bInputActivated &&
( GetAreWingsFullyDeployed() || // DON'T TOGGLE THE MODE IF IT IS STILL TRANSITIONING
GetAreWingsFullyRetracted() ) )
{
ToggleWings( !GetAreWingsDeployed() );
sbHasButtonBeenUp = false;
}
}
}
}
void CHeli::ToggleWings( bool deploy )
{
for( int i = (int)VEH_FOLDING_WING_L; i <= (int)VEH_FOLDING_WING_R; i++ )
{
eHierarchyId nDoorId = (eHierarchyId)i;
CCarDoor* pDoor = GetDoorFromId( nDoorId );
if( pDoor )
{
// Clear flags before setting them, avoid any flag conflicts
pDoor->ClearFlag(CCarDoor::DRIVEN_AUTORESET|CCarDoor::WILL_LOCK_SWINGING|CCarDoor::WILL_LOCK_DRIVEN|CCarDoor::DRIVEN_NORESET|CCarDoor::DRIVEN_SPECIAL);
pDoor->SetTargetDoorOpenRatio( deploy ? 1.0f : 0.0f, deploy ? (CCarDoor::DRIVEN_NORESET|CCarDoor::DRIVEN_SPECIAL) : (CCarDoor::DRIVEN_NORESET|CCarDoor::DRIVEN_SPECIAL|CCarDoor::WILL_LOCK_DRIVEN), this );
}
}
audVehicleAudioEntity* vehicleAudioEntity = GetVehicleAudioEntity();
if(vehicleAudioEntity)
{
vehicleAudioEntity->ToggleHeliWings(deploy);
}
}
bool CHeli::GetAreWingsDeployed()
{
for( int i = (int)VEH_FOLDING_WING_L; i <= (int)VEH_FOLDING_WING_R; i++ )
{
eHierarchyId nDoorId = (eHierarchyId)i;
CCarDoor* pDoor = GetDoorFromId( nDoorId );
if( pDoor )
{
if( pDoor->GetDoorRatio() < 0.7f )
{
return false;
}
}
}
return true;
}
bool CHeli::GetAreWingsFullyDeployed()
{
for( int i = (int)VEH_FOLDING_WING_L; i <= (int)VEH_FOLDING_WING_R; i++ )
{
eHierarchyId nDoorId = (eHierarchyId)i;
CCarDoor* pDoor = GetDoorFromId( nDoorId );
if( pDoor )
{
if( pDoor->GetDoorRatio() < 0.9f )
{
return false;
}
}
}
return true;
}
bool CHeli::GetAreWingsFullyRetracted()
{
for( int i = (int)VEH_FOLDING_WING_L; i <= (int)VEH_FOLDING_WING_R; i++ )
{
eHierarchyId nDoorId = (eHierarchyId)i;
CCarDoor* pDoor = GetDoorFromId( nDoorId );
if( pDoor )
{
if( !pDoor->GetIsLatched( this ) )
{
return false;
}
}
}
return true;
}
Vector3 CHeli::CalculateHoverForce( float invTimeStep )
{
static dev_float sfJetpackMinHoverHeight = 1.1f;
static dev_float sfJetpackMaxHoverHeight = 1.5f;
static dev_float sfJetpackDistanceFromTargetToReduceAccelerationInv = 1.0f / 0.5f;
static dev_float sfJetpackHoverAccelerationMax = 15.0f;
static dev_float sfJetpackHoverAccelerationWhenStationary = 10.0f;
static dev_float sfJetpackMaxSpeedForFullAcceleration = 10.0f;
Vector3 result( 0.0f, 0.0f, 0.0f );
float gearRetractRatio = 1.0f - GetLandingGear().GetGearDeployRatio();
float targetHeight = sfJetpackMinHoverHeight + ( ( sfJetpackMaxHoverHeight - sfJetpackMinHoverHeight ) * gearRetractRatio );
if( //m_fJetPackGroundHeight < GetTransform().GetPosition().GetZf() &&
m_fJetPackGroundHeight + targetHeight > GetTransform().GetPosition().GetZf() )
{
float targetDistance = ( m_fJetPackGroundHeight + targetHeight - GetTransform().GetPosition().GetZf() );
float currentSpeed = GetVelocity().Dot( Vector3( 0.0f, 0.0f, 1.0f ) );
float acceleration = 2.0f * ( ( ( targetDistance * invTimeStep ) - currentSpeed ) * invTimeStep );
float maxAcceleration = sfJetpackHoverAccelerationWhenStationary + ( sfJetpackHoverAccelerationMax * Min( 1.0f, GetVelocity().Mag2() / sfJetpackMaxSpeedForFullAcceleration ) );
maxAcceleration = Max( maxAcceleration, Abs( Min( 0.0f, currentSpeed * invTimeStep ) ) );
maxAcceleration = Min( maxAcceleration, 149.9f );
acceleration *= Min( 1.0f, targetDistance * sfJetpackDistanceFromTargetToReduceAccelerationInv );
acceleration = Clamp( acceleration, 0.0f, maxAcceleration );
acceleration *= GetMass();
result = Vector3( 0.0f, 0.0f, 1.0f ) * acceleration;
m_fStrafeModeTargetHeight = GetTransform().GetPosition().GetZf() + targetHeight;
}
return result;
}
//////////////////////////////////////////////////////////////////////////
// Class CAutogyro
bank_float CAutogyro::ms_fRotorSpeedMults[] =
{
21.0f, // Main
25.0f // Rear
};
CompileTimeAssert(CRotaryWingAircraft::Num_Heli_Rotors == 2);
void CAutogyro::SetModelId(fwModelId modelId)
{
CRotaryWingAircraft::SetModelId(modelId);
#if __ASSERT
const char* strDebugPropNames[] =
{
"Main",
"Rear"
};
#endif
// Figure out which propellers are valid, and set them up
for(int nPropIndex = 0 ; nPropIndex < HELI_NUM_ROTORS; nPropIndex++ )
{
eRotationAxis nAxis = nPropIndex == Rotor_Main ? ROT_AXIS_LOCAL_Z : ROT_AXIS_LOCAL_Y;
eHierarchyId nId = (eHierarchyId)(HELI_ROTOR_MAIN + nPropIndex);
if(vehicleVerifyf(GetBoneIndex(nId) > -1,"Vehicle %s is missing a propeller: %s",GetModelName(),strDebugPropNames[nPropIndex])
&& vehicleVerifyf(m_iNumPropellers < Num_Heli_Rotors,"Out of room for plane propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId,nAxis, this);
m_propellerCollisions[m_iNumPropellers].Init(nId,this);
m_iNumPropellers++;
}
}
}
static dev_float fAngleOfAttackDropOffMult = 20.0f; // Roughly related to rotor span... converts rpm into velocity at wing
bank_float CAutogyro::ms_fCeilingLiftCutoffRange = 25.0f; // Over what range do we scale down lift?
void CAutogyro::ProcessFlightHandling(float fTimeStep)
{
Assert(pHandling);
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
Assert(pFlyingHandling);
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 -= pFlyingHandling->m_fWindMult*vecWindSpeed;
}
vecAirSpeed += GetVelocity();
// need to see if rotors are angled away from vertical (probably the case with a gyro)
// effective airspeed at wing
// this changes with the speed the rotors spin at
// effectively increasing wind speed in plane of rotors
Matrix34 gyroRotorMat;
gyroRotorMat.Identity();
// Hijack m_fGearDownDragV for this
gyroRotorMat.RotateLocalX(pFlyingHandling->m_fGearDownDragV);
Matrix34 m = MAT34V_TO_MATRIX34(GetMatrix());
gyroRotorMat.Dot3x3(m);
Vector3 vecLift = gyroRotorMat.c;
vecAirSpeed += (gyroRotorMat.b + gyroRotorMat.a) * m_fMainRotorSpeed * fAngleOfAttackDropOffMult;
float fSpeedThroughRotor = -vecLift.Dot(vecAirSpeed);
// Don't allow speed through rotor to reverse rotor direction
if(fSpeedThroughRotor < 0.0f)
{
fSpeedThroughRotor = 0.0f;
}
else
{
// Check for stall
static dev_bool bStall = false;
if(bStall)
{
static dev_float fSinStallAngleStart = rage::Sinf(PI / 12.0f); // 15 deg ~ critical angle
Vector3 vecWindDir = -vecAirSpeed;
vecWindDir.Normalize();
float fSinStallAngle = vecWindDir.Dot(vecLift);
if(fSinStallAngle > fSinStallAngleStart)
{
float fAmountOverStallAngle = fSinStallAngle - fSinStallAngleStart;
static dev_float fRotorAccelStallMult = 2.0f; // How quickly does accel drop off over stall angle?
float fStallMult = rage::Max(0.0f, 1.0f - fAmountOverStallAngle*fRotorAccelStallMult);
fSpeedThroughRotor *= fStallMult;
#if __BANK
if(GetStatus()==STATUS_PLAYER && CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
{
grcDebugDraw::AddDebugOutput("STALLED!\n");
}
#endif
}
}
}
#if __BANK
if(GetStatus()==STATUS_PLAYER && CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
{
grcDebugDraw::AddDebugOutput("Speed through rotor %.2f\n",fSpeedThroughRotor);
}
#endif
m_fSpeedThroughMainRotor = fSpeedThroughRotor;
CRotaryWingAircraft::ProcessFlightHandling(fTimeStep);
}
void CAutogyro::ProcessFlightModel(float fTimeStep)
{
// 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();
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 -= pFlyingHandling->m_fWindMult*vecWindSpeed;
}
vecAirSpeed += GetVelocity();
float fMass = GetMass();
Vector3 vecAngInertia = GetAngInertia();
#if __BANK
if(GetStatus()==STATUS_PLAYER && CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
{
Vector3 vecWindSocPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) + VEC3V_TO_VECTOR3(GetTransform().GetC()) * 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();
float fEngineSpeed = GetMainRotorSpeed();
{
// For gyro throttle variable gives us the rear engine speed
// and 'engineSpeed' gives us the current speed of the main rotor
// 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();
}
// MISC STUFF
Vector3 vecRudderArm(VEC3V_TO_VECTOR3(GetTransform().GetB()));
Vector3 vecTailOffset = vecRudderArm * GetBoundingBoxMin().y;
float fForwardSpd = vecAirSpeed.Dot(vecRudderArm);
float fForwardSpdSqr = fForwardSpd*fForwardSpd;
Matrix34 gyroRotorMat;
gyroRotorMat.Identity();
gyroRotorMat.RotateLocalX(pFlyingHandling->m_fGearDownDragV);
Matrix34 m = MAT34V_TO_MATRIX34(GetMatrix());
gyroRotorMat.Dot3x3(m);
// Pitch and roll
Vector3 vecUp(VEC3V_TO_VECTOR3(GetTransform().GetC()));
Vector3 vecForward(VEC3V_TO_VECTOR3(GetTransform().GetB()));
ApplyInternalTorque(vecUp*fPitchControl*pFlyingHandling->m_fPitchMult*GetHoverModePitchMult()*vecAngInertia.x*fEngineSpeed, 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*pFlyingHandling->m_fRollMult*vecAngInertia.y*fEngineSpeed, vecRight);
{
Vector3 vecTailAirSpeed(VEC3_ZERO);
// get tail position and add contribution from rotational velocity
{
vecTailAirSpeed = m_vecRearRotorPosition;
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, -HELI_RUDDER_MAX_ANGLE_OF_ATTACK, HELI_RUDDER_MAX_ANGLE_OF_ATTACK);
// doing sideways stuff
Vector3 vecRudderForce(VEC3V_TO_VECTOR3(GetTransform().GetA()));
// sideways force from sidesliping
vecRudderForce *= rage::Clamp(pFlyingHandling->m_fSideSlipMult * fSideSlipAngle * fAirSpeedSqr, -100.0f, 100.0f);
ApplyInternalForceCg(vecRudderForce*fMass);
// YAW
// This is proportional to forwards speed (like in plane) since rudder is fixed
vecRudderForce = VEC3V_TO_VECTOR3(GetTransform().GetA());
fSideSlipAngle = -1.0f*GetLocalSpeed(vecTailOffset).Dot(VEC3V_TO_VECTOR3(GetTransform().GetA()));
// Add some extra yaw when moving and rolling
if(m_bEnableThrustVectoring)
{
Vector3 vFlatVelocity = GetVelocity();
vFlatVelocity.z = 0.0f;
fYawControl = ComputeAdditionalYawFromTransform(fYawControl, GetTransform(), vFlatVelocity.Mag());
}
// control force from steering and stabilising force from rudder
vecRudderForce *= (pFlyingHandling->m_fYawMult*GetHoverModeYawMult()*fYawControl*fForwardSpd + pFlyingHandling->m_fYawStabilise*fSideSlipAngle*fAirSpeedSqr)*vecAngInertia.z;
ApplyInternalTorque(vecRudderForce, -VEC3V_TO_VECTOR3(GetTransform().GetB()));
// PITCH STABILISATION
// calculate tailplane angle of attack
// want autogyro blades to remain flat relative to velocity
vecRudderForce = VEC3V_TO_VECTOR3(GetTransform().GetC());
Matrix34 matPitchStable = gyroRotorMat;
// Again, hijack this param cos its not needed for autogyros
matPitchStable.RotateLocalX(pFlyingHandling->m_fGearDownLiftMult);
float fAngleOfAttackForPitch = -1.0f*GetLocalSpeed(vecTailOffset).Dot(matPitchStable.c);
vecRudderForce *= (pFlyingHandling->m_fPitchStabilise*fAngleOfAttackForPitch*rage::Abs(fAngleOfAttackForPitch))*vecAngInertia.x*-GRAVITY;
ApplyInternalTorque(vecRudderForce, vecTailOffset);
// ROLL
vecRudderForce = VEC3V_TO_VECTOR3(GetTransform().GetA());
// also need to add some stabilisation for roll (so plane naturally levels out)
vecRudderForce.Cross(VEC3V_TO_VECTOR3(GetTransform().GetB()), 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
{
vecRudderForce *= -1.0f;
if(GetTransform().GetA().GetZf() > 0.0f)
fRollOffset = -1.0f;
}
fRollOffset *= 1.0f - DotProduct(VEC3V_TO_VECTOR3(GetTransform().GetA()), vecRudderForce);
// roll stabilisation recedes as we go vertical
fRollOffset *= 1.0f - rage::Abs(GetTransform().GetB().GetZf());
ApplyInternalTorque(pFlyingHandling->m_fRollStabilise*fRollOffset*vecAngInertia.y*0.5f*-GRAVITY*VEC3V_TO_VECTOR3(GetTransform().GetA()), VEC3V_TO_VECTOR3(GetTransform().GetC()));
}
///////////////////////
// LIFT&THRUST
static dev_bool bMethod1 = true;
if(bMethod1)
{
// effective airspeed at wing
// airspeed in plane of rotors does not do anything
// since receding blade forces cancel out proceding blade
// so airspeed only acts along .c
// The .b and .a forces come from the rotor rpm
// The components in the direction of the plane of rotation don't really mean anything geometrically
Vector3 vVelAtWing = vecAirSpeed.Dot(gyroRotorMat.c)*gyroRotorMat.c;
vVelAtWing += (gyroRotorMat.b + gyroRotorMat.a) * fEngineSpeed * fAngleOfAttackDropOffMult;
float fAngleOfAttack = vVelAtWing.Dot(gyroRotorMat.c) / rage::Max(0.01f, vVelAtWing.Mag());
fAngleOfAttack = -1.0f*rage::Asinf(Clamp(fAngleOfAttack, -1.0f, 1.0f));
// Lift....
// 1: From gyro blades spinning
// 2: From gyro forwards speed
// Note attackLift is being used for something different here
// Past a certain angle we get no more lift
static dev_float fCrititicalAngleOfAttack = PI/2.0f;
if(fAngleOfAttack > fCrititicalAngleOfAttack)
{
fAngleOfAttack = 2.0f*fCrititicalAngleOfAttack - fAngleOfAttack;
fAngleOfAttack = rage::Max(0.0f,fAngleOfAttack);
}
// adjust fForwardsSpeed as well
float fForwardsSpdForLift = vVelAtWing.Dot(gyroRotorMat.b);
float fForwardsSpdForLiftSq = fForwardsSpdForLift*fForwardsSpdForLift;
float fRotorLift = pFlyingHandling->m_fFormLiftMult;
float fAttackLift = pFlyingHandling->m_fAttackLiftMult*fAngleOfAttack;
Vector3 vecLift = gyroRotorMat.c;
float fTotalLiftForce = (fRotorLift+fAttackLift)*-GRAVITY*fMass*fForwardsSpdForLiftSq;
// Lift needs to drop above pre determined flight ceiling
const float ThisZ = GetTransform().GetPosition().GetZf();
if(fTotalLiftForce > fMass*-GRAVITY
&& HeightAboveCeiling(ThisZ) > 0.0f)
{
fTotalLiftForce = MAX(0.0f, 1.0f - HeightAboveCeiling(ThisZ)/ms_fCeilingLiftCutoffRange)*fMass*-GRAVITY;
}
vecLift *= fTotalLiftForce;
ApplyInternalForceCg(vecLift);
#if __BANK
if(GetStatus()==STATUS_PLAYER && CVehicle::ms_nVehicleDebug==VEH_DEBUG_HANDLING)
{
// Would be useful to see the wind speed and propeller matrix
static dev_bool bDebugDraw = false;
if(bDebugDraw)
{
static dev_float fAxisScale = 2.0f;
Matrix34 debugMat = gyroRotorMat;
debugMat.d = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
static dev_float fLiftScale = 0.005f;
static dev_float fVelScale = 1.0f;
grcDebugDraw::Line(debugMat.d,(vVelAtWing*fVelScale)+debugMat.d,Color_green,Color_blue);
grcDebugDraw::Line(debugMat.d,((vecLift*fLiftScale/(-GRAVITY*fMass))+debugMat.d),Color_red);
grcDebugDraw::Axis(debugMat,fAxisScale);
}
grcDebugDraw::AddDebugOutput("Angle of attack %.3f\n",fAngleOfAttack);
}
#endif
}
else
{
Vector3 vLift = ZAXIS;
vLift.RotateX(pFlyingHandling->m_fGearDownDragV);
vLift = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(VECTOR3_TO_VEC3V(vLift)));
//float fAngleOfAttack = vecAirSpeed.Dot(vLift) / rage::Max(0.01f, vecAirSpeed.Mag());
//fAngleOfAttack = -1.0f*rage::Asinf(Clamp(fAngleOfAttack, -1.0f, 1.0f));
float fSpeedThroughRotor = m_fSpeedThroughMainRotor;
static dev_bool bClampRotorSpeed = true;
if(bClampRotorSpeed)
{
static dev_float fMaxSpeedThroughRotor = 3.0f;
fSpeedThroughRotor = rage::Clamp(m_fSpeedThroughMainRotor,0.0f,fMaxSpeedThroughRotor);
}
float fAircraftFormLift = pFlyingHandling->m_fFormLiftMult * fForwardSpdSqr;
// If we mult by fForward speed then this will never push autogyro backwards
float fAutogyroLift = pFlyingHandling->m_fAttackLiftMult* fSpeedThroughRotor;
vLift *= (fAircraftFormLift+fAutogyroLift)*-GRAVITY*fMass;
ApplyInternalForceCg(vLift);
}
// Thrust
// only let pilot backwards thrust when on the ground
if(IsInAir() || fThrottleControl > 0.01f)
{
// Rescales throttle so full brake = 0.0f throttle
fThrottleControl = pFlyingHandling->m_fThrust*0.5f*(fThrottleControl + 1.0f);
// Make throttle fall off at high speed
float fFallOff = pFlyingHandling->m_fThrustFallOff*fForwardSpdSqr;
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(VEC3V_TO_VECTOR3(GetTransform().GetB()), GetVelocity()) < 0.02f)
fThrottleControl = pFlyingHandling->m_fThrust*MIN(0.0f, (fThrottleControl - 8.0f*0.97f*fForwardSpd));
else
fThrottleControl = 0.0f;
//grcDebugDraw::AddDebugOutput("Thrust: %.2f\n",fThrottleControl);
ApplyInternalForceCg(vecRudderArm*-GRAVITY*fThrottleControl*fMass);
}
}
// void CAutogyro::ProcessDriverInputsForPlayer(CPed *pPlayerPed)
// {
// CRotaryWingAircraft::ProcessDriverInputsForPlayer(pPlayerPed);
//
// ProcessPlayerControlInputsForBrakeAndGasPedal(pPlayerPed->GetControlFromPlayer(),false);
//
// if(GetNumContactWheels() > 0)
// {
// SetSteerAngle(-m_fYawControl*pHandling->m_fSteeringLock);
// }
// }
void CAutogyro::Teleport(const Vector3& vecSetCoors, float fSetHeading , bool bCalledByPedTask, bool bTriggerPortalRescan, bool bCalledByPedTask2, bool bWarp, bool UNUSED_PARAM(bKeepRagdoll), bool UNUSED_PARAM(bResetPlants))
{
// Not sure why we need to reset the rotor blades but this might need to be re-added.
//m_fRearPropellerSpeed = 0.0f;
//m_fSpeedThroughMainRotor = 0.0f;
CRotaryWingAircraft::Teleport(vecSetCoors,fSetHeading,bCalledByPedTask,bTriggerPortalRescan,bCalledByPedTask2,bWarp);
}
CAutogyro::CAutogyro(const eEntityOwnedBy ownedBy, const u32 popType) : CRotaryWingAircraft(ownedBy, popType, VEHICLE_TYPE_AUTOGYRO)
{
m_fRearPropellerSpeed = 0.0f;
m_fSpeedThroughMainRotor = 0.0f;
}
static dev_float sfAutogyroPropellerAccel = 0.1f;
static dev_float sfAutogyroPropellerDeccel = -0.05f;
static dev_float sfAutogyroPropellerDeccelWater = -0.5f;
static dev_float sfMaxPropellerSpeed = 1.0f;
void CAutogyro::UpdatePropellerSpeed()
{
if(m_nVehicleFlags.bEngineOn && (GetStatus()==STATUS_PLAYER || GetStatus()==STATUS_PHYSICS))
{
m_fRearPropellerSpeed += sfAutogyroPropellerAccel * fwTimer::GetTimeStep();
if(m_fRearPropellerSpeed > sfMaxPropellerSpeed)
m_fRearPropellerSpeed = sfMaxPropellerSpeed;
}
else
{
// stop engine suddenly if fallen in water
if(GetIsInWater() && m_fTimeInWater > 0.0f)
{
m_fRearPropellerSpeed += sfAutogyroPropellerDeccelWater * fwTimer::GetTimeStep();
}
else
{
m_fRearPropellerSpeed += sfAutogyroPropellerDeccel * fwTimer::GetTimeStep();
}
if(m_fRearPropellerSpeed < 0.0f)
m_fRearPropellerSpeed = 0.0f;
}
// Rotor speed should be driven by wind speed through rotors
float fRotorAccel = m_fSpeedThroughMainRotor* sfAutogyroRotorAccel - m_fMainRotorSpeed*sfAutogyroRotorDamping - sfAutogyroRotorDampingC;
m_fMainRotorSpeed += fRotorAccel * fwTimer::GetTimeStep();
static dev_float sfMinRotorSpeedEngineOn = 0.1f;
static dev_float sfMaxRotorSpeed = 1.0f;
float fMinRotorSpeed = m_nVehicleFlags.bEngineOn ? sfMinRotorSpeedEngineOn : 0.0f;
static dev_bool bClamp = true;
if(bClamp)
m_fMainRotorSpeed = rage::Clamp(m_fMainRotorSpeed,fMinRotorSpeed,sfMaxRotorSpeed);
GetMainPropeller().UpdatePropeller(m_fMainRotorSpeed*ms_fRotorSpeedMults[Rotor_Main],fwTimer::GetTimeStep());
GetRearPropeller().UpdatePropeller(m_fRearPropellerSpeed*ms_fRotorSpeedMults[Rotor_Rear] ,fwTimer::GetTimeStep());
}
void CAutogyro::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
UpdatePropellerSpeed();
CRotaryWingAircraft::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
}
#if __BANK
void CAutogyro::InitRotorWidgets(bkBank& bank)
{
const char* strPropNames[CRotaryWingAircraft::Num_Heli_Rotors] =
{
"Main",
"Rear"
};
CompileTimeAssert(Num_Heli_Rotors == 2);
bank.PushGroup("Autogyro rotors");
for(int i =0; i < Num_Heli_Rotors; i++)
{
bank.AddSlider(strPropNames[i],&ms_fRotorSpeedMults[i],0.0f,100.0f,0.01f);
}
bank.PopGroup();
}
#endif
CBlimp::CBlimp(const eEntityOwnedBy ownedBy, const u32 popType)
: CHeli(ownedBy, popType, VEHICLE_TYPE_BLIMP)
, m_isBlimpBroken(false)
{
m_nVehicleFlags.bCanEngineDegrade = false;
m_nVehicleFlags.bCanPlayerAircraftEngineDegrade = false;
}
void CBlimp::SetModelId(fwModelId modelId)
{
CRotaryWingAircraft::SetModelId(modelId);
// Figure out which propellers are valid, and set them up
for(int nPropIndex = 0 ; nPropIndex < Num_Heli_Rotors; nPropIndex++ )// use num heli rotors as we are reusing the heli rotor array
{
eHierarchyId nId = (eHierarchyId)(BLIMP_PROP_1 + nPropIndex);
if(GetBoneIndex(nId) > -1 && physicsVerifyf(m_iNumPropellers < Num_Heli_Rotors,"Out of room for propellers"))
{
// Found a valid propeller
m_propellers[m_iNumPropellers].Init(nId,ROT_AXIS_LOCAL_Y, this);
m_propellerCollisions[m_iNumPropellers].Init(nId,this);
m_iNumPropellers++;
}
}
// Call InitCompositeBound again, as it has dependency with propellers
InitCompositeBound();
}
static dev_float sfBlimpRudderAdjustAngle = ( DtoR * 10.0f);
static dev_float sfBlimpElevatorAdjustAngle = ( DtoR * 20.0f);
ePrerenderStatus CBlimp::PreRender(const bool bIsVisibleInMainViewport)
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
for(int i= 0; i < m_iNumPropellers; i++)
{
m_propellers[i].PreRender(this);
}
// Adjust rudder (yaw)
SetComponentRotation(BLIMP_RUDDER_UPPER,ROT_AXIS_LOCAL_Z,m_fYawControl*sfBlimpRudderAdjustAngle,true);
SetComponentRotation(BLIMP_RUDDER_LOWER,ROT_AXIS_LOCAL_Z,m_fYawControl*sfBlimpRudderAdjustAngle,true);
// Elevators (pitch)
SetComponentRotation(BLIMP_ELEVATOR_L,ROT_AXIS_LOCAL_X,-m_fPitchControl*sfBlimpElevatorAdjustAngle,true);
SetComponentRotation(BLIMP_ELEVATOR_R,ROT_AXIS_LOCAL_X,-m_fPitchControl*sfBlimpElevatorAdjustAngle,true);
return CAutomobile::PreRender(bIsVisibleInMainViewport);
}
//
void CBlimp::UpdateRotorSpeed()
{
// Standard heli
float fOldRotorSpeed = m_fPrevMainRotorSpeed;
if(m_nVehicleFlags.bEngineOn && (GetStatus()==STATUS_PLAYER || GetStatus()==STATUS_PHYSICS || GetStatus()==STATUS_OUT_OF_CONTROL) && !m_Transmission.GetCurrentlyMissFiring())
{
m_fMainRotorSpeed += HELI_ROTOR_ANGULAR_ACCELERATION * fwTimer::GetTimeStep();
if(m_fMainRotorSpeed > MAX_ROT_SPEED_HELI_BLADES)
m_fMainRotorSpeed = MAX_ROT_SPEED_HELI_BLADES;
}
else
{
if(!m_nVehicleFlags.bEngineStarting)
{
int iDriverSeatIndex = GetVehicleModelInfo()->GetModelSeatInfo()->GetDriverSeat();
int iDriverSeatBoneIndex = GetVehicleModelInfo()->GetModelSeatInfo()->GetBoneIndexFromSeat(iDriverSeatIndex);
bool bAnyAlivePassenger = false;
for(s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); ++iSeat)
{
CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat);
if(pPassenger && !pPassenger->IsInjured())
{
bAnyAlivePassenger = true;
break;
}
}
// only switch the engine off is no-one is on board and no-one else is about to enter the seat
CComponentReservation* pComponentReservation = m_ComponentReservationMgr.FindComponentReservation(iDriverSeatBoneIndex, false);
if (pComponentReservation && pComponentReservation->GetPedUsingComponent() == NULL && !IsNetworkClone() && !bAnyAlivePassenger)
{
SwitchEngineOff();
}
}
m_fMainRotorSpeed -= HELI_ROTOR_ANGULAR_ACCELERATION * fwTimer::GetTimeStep();
if(m_fMainRotorSpeed < 0.0f)
m_fMainRotorSpeed = 0.0f;
}
if(fOldRotorSpeed != m_fMainRotorSpeed && (fOldRotorSpeed == 0.0f || m_fMainRotorSpeed == 0.0f))
{
UpdateRotorBounds();
}
//
for(int i =0; i < m_iNumPropellers; i++)
{
float fRotorDirection = (i == Rotor_Rear) ? -1.0f : 1.0f;
m_propellers[i].UpdatePropeller(fRotorDirection * m_fMainRotorSpeed * ms_fRotorSpeedMults[0],fwTimer::GetTimeStep());
}
m_fPrevMainRotorSpeed = m_fMainRotorSpeed;
}
void CBlimp::FinishBlowingUpVehicle( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion)
{
// go thru all the fragment children (there's more of them)
BreakBlimp();
CHeli::FinishBlowingUpVehicle(pCulprit, bInACutscene, bAddExplosion, bNetCall, weaponHash, bDelayedExplosion);
if (m_fMainRotorHealth > 0.0f)
{
m_fMainRotorHealth = 0.0f;
m_fMainRotorSpeed = 0.0f;
}
if(m_fRearRotorHealth > 0.0f)
{
m_fRearRotorHealth = 0.0f;
}
}
void CBlimp::BreakBlimp()
{
m_isBlimpBroken = true;
fragInst* pFragInst = GetVehicleFragInst();
for(int nChild=0; nChild<pFragInst->GetTypePhysics()->GetNumChildren(); nChild++)
{
if(pFragInst->GetChildBroken(nChild))
continue;
int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID());
// go through each of the possible extras
if(!GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_STRONG))
{
for(int nPart=BLIMP_PROP_1; nPart<=BLIMP_PROP_1 + BLIMP_NUM_OVERRIDDEN_NODES; nPart++)
{
// if the bone index of this child matches the bone index of the extra that's turned off then delete this component
if(GetBoneIndex((eHierarchyId)nPart) == boneIndex)
{
PartHasBrokenOff((eHierarchyId)nPart);
// depending on distance, just delete component instead of having it come flying off
if(nPart == BLIMP_SHELL)
pFragInst->DeleteAbove(nChild);
else
{
pFragInst->BreakOffAbove(nChild);
}
CVehicle::ClearLastBrokenOffPart();
break;
}
}
}
}
}