// Title : Heli.cpp // Author : Alexander Roger // Started : 10/04/2003 // // // // // C headers #include #include #include // 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(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(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(pTask)); pCarTask = static_cast(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; iGetFragChild(); 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(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(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(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(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(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(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(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(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(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(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 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(pCulprit)->GetDriver() : static_cast(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(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(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(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(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(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(m_Ropes[i]->GetUniqueID()), CTrackedEventInfo((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(m_pIntelligence)); return static_cast(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(); 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; CGetOccupierOfSeat((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(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(pNewRope->GetUniqueID(), VECTOR3_TO_VEC3V(vAttachPosition), rot, fLength, fMinLength, fMaxLength, fLengthChangeRate, ropeType, numSections, true, lockFromFront, ROPE_WEIGHT_SCALE, false, true), CTrackedEventInfo((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(pNewRope->GetUniqueID(), VECTOR3_TO_VEC3V(vAttachPosition), 0, eSeatRopeID, pNewRope->GetLocalOffset() ), CTrackedEventInfo((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((int)pRope->GetUniqueID(), VECTOR3_TO_VEC3V(vAttachPosition), pAttachToEntity->GetTransform().GetPosition(), fAttachDist, fEntityMoveSpeed), CTrackedEventInfo((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(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(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(r->GetLengthChangeRate(), r->GetIsWindingFront(), r->GetIsUnwindingFront(), r->GetIsUnwindingBack()), CTrackedEventInfo((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(r->GetLengthChangeRate(), r->GetIsWindingFront(), r->GetIsUnwindingFront(), r->GetIsUnwindingBack()), CTrackedEventInfo((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(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(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( 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(); 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; nChildGetTypePhysics()->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; } } } } }