// Title : Vehicle.cpp // Author : Richard Jobling/William Henderson // Started : 25/08/99 // // // #if !__SPU #include "vehicle.h" //Rage headers #include "audioengine/engineutil.h" #include "bank/bank.h" #include "bank/bkmgr.h" #include "creature/componentskeleton.h" #include "creature/creature.h" #include "crskeleton/skeleton.h" #include "crskeleton/skeletonData.h" #include "Fragment/TypeGroup.h" #include "Fragment/Manager.h" #include "Fragment/CacheManager.h" #include "grcore/device.h" #include "grcore/image.h" #include "math/angmath.h" #include "pharticulated/articulatedcollider.h" #include "phbound/boundcomposite.h" #include "phbound/boundcapsule.h" #include "phbound/support.h" #include "physics/debugcontacts.h" #include "physics/sleep.h" #include "physics/debugcontacts.h" #include "rmptfx/ptxmanager.h" #include "fwscene/scan/scan.h" #include "fwscene/scan/scanDebug.h" #include "scene/ContinuityMgr.h" #include "script/thread.h" #include "softrasterizer/scansetup.h" #include "system/param.h" #include "vector/vector3.h" // Framework headers #include "fwanimation/animdirector.h" #include "fwanimation/animmanager.h" #include "fwanimation/directorcomponentexpressions.h" #include "fwdecorator/decoratorExtension.h" #include "fwmaths/random.h" #include "fwmaths/vector.h" #include "fwmaths/angle.h" #include "fwscene/stores/txdstore.h" #include "fwscene/stores/drawablestore.h" #include "fwscene/stores/staticboundsstore.h" #include "fwscript/scriptguid.h" #include "fwutil/PtrList.h" #if __BANK #include "fwdebug/picker.h" #endif // #if __BANK // Game headers #include "ai/debug/system/AIDebugLogManager.h" #include "animation/GestureManager.h" #include "Animation/MoveVehicle.h" #include "audio/caraudioentity.h" #include "audio/boataudioentity.h" #include "audio/frontendaudioentity.h" #include "audio/northaudioengine.h" #include "audio/scriptaudioentity.h" #include "Camera/CamInterface.h" #include "Camera/Cinematic/CinematicDirector.h" #include "Camera/Viewports/ViewportManager.h" #include "Cutscene/CutSceneManagerNew.h" #include "Debug/DebugScene.h" #include "Debug/Rendering/DebugLights.h" #include "Debug/VectorMap.h" #include "Control/Record.h" #include "Control/Garages.h" #include "Control/replay/Replay.h" #include "Event/EventDamage.h" #include "Event/EventShocking.h" #include "Event/ShockingEvents.h" #include "Frontend/MobilePhone.h" #include "Frontend/minimap.h" #include "Frontend/NewHud.h" #include "game/clock.h" #include "game/Crime.h" #include "game/modelIndices.h" #include "game/weather.h" #include "game/wind.h" #include "modelInfo/vehicleModelInfo.h" #include "modelinfo/VehicleModelInfoExtensions.h" #include "modelInfo/VehicleModelInfoLights.h" #include "modelInfo/VehicleModelInfoSirens.h" #include "network/Debug/NetworkDebug.h" #include "network/General/NetworkUtil.h" #include "network/NetworkInterface.h" #include "network/events/NetworkEventTypes.h" #include "network/Objects/Entities/NetObjPlayer.h" #include "network/Objects/NetworkObjectPopulationMgr.h" #include "network/objects/prediction/NetBlenderPed.h" #include "network/objects/prediction/NetBlenderPhysical.h" #include "network/players/NetGamePlayer.h" #include "objects/object.h" #include "pathserver/pathServer.h" #include "peds/PedCapsule.h" #include "peds/pedIntelligence.h" #include "peds/Ped.h" #include "Peds/PedIKSettings.h" #include "Peds/HealthConfig.h" #include "PedGroup/PedGroup.h" #include "Peds/PedHelmetComponent.h" #include "peds/pedplacement.h" #include "peds/pedpopulation.h" #include "peds/popcycle.h" #include "performance/clearinghouse.h" #include "physics/gtaInst.h" #include "physics/gtaMaterialManager.h" #include "physics/physics.h" #include "physics/WorldProbe/worldprobe.h" #include "Renderer/Renderer.h" #include "renderer/Deferred/DeferredLighting.h" #include "renderer/lights/AsyncLightOcclusionMgr.h" #include "renderer/lights/LightCulling.h" #include "renderer/lights/lights.h" #include "renderer/PostProcessFX.h" #include "renderer/PostProcessFXHelper.h" #include "renderer/PostScan.h" #include "renderer/Util/Util.h" #include "renderer/occlusion.h" #include "renderer/water.h" #include "renderer/zonecull.h" #include "renderer/DrawLists/drawListMgr.h" #include "scene/DynamicEntity.h" #include "Scene/Physical.h" #include "scene/texLod.h" #include "scene/world/gameWorld.h" #include "scene/world/GameWorldHeightMap.h" #include "script/Handlers/GameScriptEntity.h" #include "script/script.h" #include "script/script_channel.h" #include "shaders/CustomShaderEffectVehicle.h" #include "Stats/StatsMgr.h" #include "streaming/populationstreaming.h" #include "streaming/streaming.h" #include "streaming/streamingvisualize.h" #include "system/controlMgr.h" #include "system/dependencyscheduler.h" #include "system/pad.h" #include "task/animation/taskanims.h" #include "task/Default/TaskUnalerted.h" #include "Task/Response/TaskFlee.h" #include "Task/Scenario/ScenarioManager.h" #include "Task/Service/Police/TaskPolicePatrol.h" #include "Task/System/TaskTree.h" #include "Task/Vehicle/TaskCarUtils.h" #include "Task/Vehicle/TaskCar.h" #include "Task/Vehicle/TaskCarCollisionResponse.h" #include "Task/Vehicle/TaskEnterVehicle.h" #include "Task/Vehicle/TaskExitVehicle.h" #include "Task/Vehicle/TaskInVehicle.h" #include "Task/Vehicle/TaskVehicleBase.h" #include "Task/Vehicle/VehicleHullAI.h" #include "Task/Combat/TaskDamageDeath.h" #include "task/Movement/TaskParachute.h" #include "TimeCycle/TimeCycle.h" #include "TimeCycle/TimeCycleConfig.h" #include "vehicleAi/task/TaskVehicleGoToPlane.h" #include "vehicles/AmphibiousAutomobile.h" #include "vehicles/automobile.h" #include "vehicles/boat.h" // sizeof(CBoat) #include "Vehicles/Door.h" #include "Vehicles/DoVehicleLightsAsync.h" #include "vehicles/handlingMgr.h" #include "vehicles/planes.h" #include "vehicles/heli.h" #include "vehicles/bike.h" #include "vehicles/vehicle.h" #include "Vehicles/Submarine.h" #include "vehicles/trailer.h" #include "Vehicles/train.h" #include "vehicles/vehiclefactory.h" #include "Vehicles/VehicleLightAsyncMgr.h" #include "vehicles/vehiclepopulation.h" #include "vehicles/VehicleGadgets.h" #include "vehicles/virtualroad.h" #include "vehicles/Metadata/AIHandlingInfo.h" #include "vehicles/Metadata/VehicleSeatInfo.h" #include "vehicles/Metadata/VehicleEntryPointInfo.h" #include "vehicles/Metadata/VehicleExplosionInfo.h" #include "vehicles/Metadata/VehicleLayoutInfo.h" #include "Vfx/Decals/DecalManager.h" #include "Vfx/Misc/Coronas.h" #include "Vfx/Misc/Fire.h" #include "vfx/particles/PtFxManager.h" #include "vfx/Systems/VfxVehicle.h" #include "vfx/vehicleglass/VehicleGlassManager.h" #include "vfx/vfxhelper.h" #include "vehicleAI/driverpersonality.h" #include "vehicleAI/vehicleintelligence.h" #include "vehicleAI/vehicleintelligenceFactory.h" #include "vehicleAi/task/DeferredTasks.h" #include "VehicleAI/Task/TaskVehicleCruise.h" #include "vehicleAi/task/TaskVehicleGoToHelicopter.h" #include "VehicleAI/Task/TaskVehiclePlayer.h" #include "vehicleAI/Task/TaskVehicleAnimation.h" #include "vehicleAI/Task/TaskVehicleGoToLongRange.h" #include "control/trafficlights.h" #include "Weapons/AirDefence.h" #include "weapons/inventory/PedInventoryLoadOut.h" #include "weapons/Weapon.h" #include "weapons/weapondamage.h" #include "fwscene/world/WorldRepMulti.h" #include "renderer/OcclusionQueries.h" #include "renderer/ApplyDamage.h" #include "control/replay/ReplayExtensions.h" #if NAVMESH_EXPORT #include "pathserver/ExportCollision.h" #endif // NAVMESH_EXPORT /* #include "system/findsize.h" FindSize(CVehicle); FindSize(CAutomobile); FindSize(CBike); FindSize(CSubmarine); FindSize(CTrain); FindSize(CBoat); FindSize(CPlane); */ PARAM(NoHDGarage, "Dont Allow extra vehicle HD in MP garages"); PARAM(lognetpretendoccupantfailures, "Log reasons we failed to convert pretend occupants to peds in network games"); // Used to just be sizeof(CPlane), but when running on PC, it appears that CBoat is larger. static const int kSizeOfLargestVehicleClass = (int) Max(sizeof(CBoat), sizeof(CPlane)); #define HELI_FRICTION_SCALE 0.5f INSTANTIATE_RTTI_CLASS(CVehicle,0xF820AAA9); INSTANTIATE_POOL_ALLOCATOR(CVehicle); #define PARKING_POPULATION (0.0f) #define ANIMUPDATE_IF_ANIMATED (1) AI_OPTIMISATIONS() AI_VEHICLE_OPTIMISATIONS() ENTITY_OPTIMISATIONS() VEHICLE_OPTIMISATIONS() #define FORCELIGHTSON 0 RAGE_DEFINE_CHANNEL(vehicle) bank_bool CVehicle::ms_bForceBikeLean = false; bank_bool CVehicle::ms_bUseAutomobileBVHupdate = true; bank_bool CVehicle::ms_bAlwaysUpdateCompositeBound = false; bank_float CVehicle::ms_fAOHeightCutoff = 50.0f; #if __BANK int CVehicle::ms_nVehicleDebug = VEH_DEBUG_OFF; bool CVehicle::ms_bVehicleDownforceDebug = false; bool CVehicle::ms_bVehicleQADebug = false; float CVehicle::ms_fHydraulicSuspension = 0.0f; float CVehicle::ms_fForcedGroundClearance = 0.0f; bool CVehicle::ms_bForceSlowZone = false; bool CVehicle::ms_bPrecomputeExtraWheelBoundImpacts = true; float CVehicle::ms_fVehicleDebugScale = 1.0f; bool CVehicle::ms_bDebugDrawRevRatio = true; bool CVehicle::ms_bDebugDrawSpeedRatio = true; bool CVehicle::ms_bDebugDrawDriveForce = true; bool CVehicle::ms_bDebugDrawClutchRatio = true; bool CVehicle::ms_bDebugDrawWheelSpeed = true; bool CVehicle::ms_bDebugDrawManifoldPressure = true; bool CVehicle::ms_bDebugDrawLargeText = false; #if STENCIL_VEHICLE_INTERIOR bool CVehicle::ms_StencilOutInterior = true; #endif // STENCIL_VEHICLE_INTERIOR #endif dev_bool CVehicle::ms_forceVehicleLightsOff = false; bool CVehicle::ms_forceActualVehicleLightsOff = false; atQueue CVehicle::ms_RecentVehicleBreakTimes; float CVehicle::ms_VehicleBreaksPerSecond = 0.0f; bank_float CVehicle::ms_MaxVehicleBreaksPerSecond = 1.0f; bank_u32 CVehicle::ms_VehicleBreakWindowSize = 10; bank_u32 CVehicle::ms_uTimeToIgnoreButtonHeldDown = 250; bank_float CVehicle::sm_WheelDamageRadiusThreshold = (0.04f); bank_float CVehicle::sm_WheelDamageRadiusThreshWheelDamageMax = (0.05f); bank_float CVehicle::sm_WheelDamageRadiusThreshWheelDamageScale = (0.15f); bank_float CVehicle::sm_WheelDamageRadiusScale = (1.3f); bank_float CVehicle::sm_WheelSelfRadiusScale = (1.4f); bank_float CVehicle::sm_WheelHeavyDamageRadiusScale = (2.3f); bank_float CVehicle::sm_WheelSelfHeavyWheelRadiusScale = (0.4f); bool CVehicle::ms_CanEnterLockedForSpecialEditionVehicles = false; bool CVehicle::ms_DisableHDVehicles; bool CVehicle::ms_bDialsScriptOveridden = false; const float CVehicle::ROCKET_BOOST_RECHARGE_RATE = 0.5f; const float CVehicle::ROCKET_BOOST_RECHARGE_RATE_PLANE = 0.1f; const float CVehicle::SIDE_SHUNT_DURATION = 0.75f; #if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE CPhysical::CSecondSurfaceConfig CVehicle::ms_vehicleSecondSurfaceConfig( 0.2f, // Sink lift factor 1.0f, // TangV2 lift factor 1.5f, // Max TangV2 lift factor 0.5f // Drag factor ); CPhysical::CSecondSurfaceConfig CVehicle::ms_vehicleSecondSurfaceConfigLessSink( 0.2f, // Sink lift factor 1.0f, // TangV2 lift factor 1.5f, // Max TangV2 lift factor 0.1f // Drag factor ); #endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE AUTOID_IMPL(CSeaPlaneExtension); CSeaPlaneExtension::CSeaPlaneExtension() { m_nFlags.bSinksWhenDestroyed = 0; m_fTimeOnWater = 0.0f; } #define VEHICLE_VISUALISE_RANGE 50.0f dev_float CVehicle::ms_fVehicleSubmergeSleepGlobalZ = -8.0f; bank_float CVehicle::ms_fFoliageVehicleDragCoeff = 7.5f; bank_float CVehicle::ms_fFoliageDragDistanceScaleCoeff = 0.5f; bank_float CVehicle::ms_fFoliageBoundRadiusScaleCoeff = 0.4f; bank_float CVehicle::ms_fMinVehicleSpeedForFoliageDrag = 0.0f; bank_float CVehicle::ms_fFoliageMaxDoorAccel = 50.0f; bank_float CVehicle::ms_fFoliageDoorForceScaleCoeff = 2.5f; bank_float CVehicle::ms_fMaxAngAccel = 15.0f; dev_float CVehicle::ms_fVerticalVelocityToKnockOffVehicle = 25.0f; dev_float CVehicle::ms_fVerticalVelocityToKnockOffHoverBike = 40.0f; #if __BANK bool CVehicle::m_bDebugVisPetrolLevel = false; bool CVehicle::m_bDebugEngineTemperature=false; bool CVehicle::ms_bDebugWheelTemperatures = false; bool CVehicle::ms_bDebugWheelFlags = false; bool CVehicle::ms_bDebugIgnoreHoldGearWithWheelspinFlag = false; bool CVehicle::ms_bDebugVehicleHealth=false; bool CVehicle::ms_bDebugVehicleWeapons=false; bool CVehicle::ms_bDebugVehicleStatus=false; bool CVehicle::ms_bDebugVehicleIsDriveableFail=false; #endif #if __BANK u32 CVehicle::ms_uZeroToSixtyStartTime = 0; u32 CVehicle::ms_uZeroToSixtyEndTime = 0; float CVehicle::ms_fBestZeroToSixtyTime = 0.0f; float CVehicle::ms_fBestSixtyToZeroTime = 0.0f; float CVehicle::ms_fHighestGForce = 0.0f; #endif ConfigVehicleEmissiveSettings g_LightSwitchSettings; static dev_float BrakeLightScaler = 1.0f; static dev_float TailLightScaler = 1.75f; float CHeadlightTuningData::ms_globalHeadlightIntensityMult = 1.0f; float CHeadlightTuningData::ms_globalHeadlightDistMult = 1.0f; float CHeadlightTuningData::ms_globalConeInnerAngleMod = 1.0f; float CHeadlightTuningData::ms_globalConeOuterAngleMod = 1.0f; float CHeadlightTuningData::ms_globalOnlyOneLightMod = 0.8f; float CHeadlightTuningData::ms_globalFake2LightAngleMod = 0.95f; float CHeadlightTuningData::ms_globalFake2LightsDisplacementMod = 0.82f; float CHeadlightTuningData::ms_globalFake2LightAngleModSubmarine = 0.83f; float CHeadlightTuningData::ms_globalFake2LightsDisplacementModSubmarine = 0.0f; bool CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods = true; float CHeadlightTuningData::ms_fullBeamHeadlightIntensityMult = 1.1f; float CHeadlightTuningData::ms_fullBeamHeadlightDistMult = 1.3f; float CHeadlightTuningData::ms_fullBeamHeadlightCoronaIntensityMult = 2.0f; float CHeadlightTuningData::ms_fullBeamHeadlightCoronaSizeMult = 2.0f; float CHeadlightTuningData::ms_aimFullBeamMod = 0.0f; float CHeadlightTuningData::ms_aimDippedBeamMod = -0.2f; bool CHeadlightTuningData::ms_makeHeadlightsCastFurtherForPlayer = true; float CHeadlightTuningData::ms_aimFullBeamAngle = 0.0f; float CHeadlightTuningData::ms_aimDippedBeamAngle = 0.0f; float CHeadlightTuningData::ms_playerHeadlightIntensityMult = 1.2f; float CHeadlightTuningData::ms_playerHeadlightDistMult = 2.5f; float CHeadlightTuningData::ms_playerHeadlightExponentMult = 1.0f; float CHeadlightTuningData::ms_volumeIntensityScale = 1.0f; float CHeadlightTuningData::ms_volumeSizeScale = 0.5f; Color32 CHeadlightTuningData::ms_outerVolumeColor = Color32(0,0,0); float CHeadlightTuningData::ms_outerVolumeIntensity = 0.0f; float CHeadlightTuningData::ms_outerVolumeFallOffExponent = 50.0f; #if __BANK bool CHeadlightTuningData::ms_useVehicleLodDist = false; u8 CHeadlightTuningData::ms_ForceHeadlightShadows = NO_HEADLIGHT_SHADOWS; bool CHeadlightTuningData::ms_globalForceRecessedCorona = false; #endif // __BANK float CNeonTuningData::ms_globalNeonIntensity = 1.0f; float CNeonTuningData::ms_globalNeonRadius = 0.5f; float CNeonTuningData::ms_globalNeonRadiusFallOffExponent = 8.0f; float CNeonTuningData::ms_globalNeonCapsuleExtentSides = 1.0f; float CNeonTuningData::ms_globalNeonCapsuleExtentFrontBack = 0.5f; float CNeonTuningData::ms_globalNeonClipPlaneHeight = 0.015f; float CNeonTuningData::ms_globalNeonBikeClipPlaneHeight = 0.25f; Color32 CNeonTuningData::ms_defaultNeonColor; #if __BANK bool CNeonTuningData::ms_neonsForcedOn = false; #endif // __BANK eVehicleTwinHeadLightMode CHeadlightTuningData::ms_VehicleTwinHeadLightMode = VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT; eVehicleTwinTailLightMode CHeadlightTuningData::ms_VehicleTwinTailLightMode = VTTL_MODE_SINGLE_WITH_FAKE_SPLIT; const u32 TimeNeededToHoldLightsButtonDown = 400; s32 CVehicle::ms_nNumAvailableShapetests = 0; float CVehicle::ms_fLightCutoffDistanceTweak = 0.0f; bank_bool CVehicle::ms_bSuperLowLod_DisableCollisions = true; bank_bool CVehicle::ms_bSuperLowLod_DisableGravity = true; bank_bool CVehicle::ms_bWreckVehicleOnFirstExplosion = false; bank_float CVehicle::ms_fVehicleExplosionDelayMin = 0.25f; bank_float CVehicle::ms_fVehicleExplosionDelayMax = 1.0f; dev_float CVehicle::ms_fBicyclePavementVelocitySquaredThreshold = 81.0f; dev_float CVehicle::ms_fVehiclePavementVelocitySquaredThreshold = 1.0f; dev_float CVehicle::ms_fTimeTouchingPavementBeforeAddingEvent = 2.0f; //Slipstream variables bool CVehicle::sm_bSlipstreamingEnabled = false; bank_float CVehicle::ms_fSpeedToStartSlipstream = 20.0f; bank_float CVehicle::ms_fSpeedToStartSlipstreamBoat = 10.0f; bank_float CVehicle::ms_fMaxSpeedOfSlipStream = 40.0f; bank_float CVehicle::ms_fSlipstreamMaxDragReduction = 1.0f; bank_float CVehicle::ms_fSlipstreamMaxDistance = 30.0f; bank_float CVehicle::ms_fSlipstreamMaxTime = 3.0f; bank_float CVehicle::ms_fSlipstreamMaxTimeBeforeBeingDisabled = 10.0f; bool CVehicle::ms_bSlipstreamApplyMaxTimeBeforeBeingDisabled = true; bank_bool CVehicle::ms_bRealisticSlipstreamMode = false; bank_float CVehicle::sm_HairScaleDisableThreshold = 0.1f; bank_float CVehicle::ms_fBikeGravityMult = 1.2f; bank_float CVehicle::ms_fQuadbikeGravityMult = 1.2f; bank_float CVehicle::ms_fOffRoadAbilityGravityMult = 1.1f; bank_float CVehicle::ms_fOffRoadAbility2GravityMult = 1.2f; // Rocket boost capacity const float CVehicle::sm_fInvalidPlaceOnRoadHeight = -1.0f; s8 CVehicle::ms_slotTurnedOff = -1; s32 CVehicle::ms_boneTurnedOff = -1; ObjectId CVehicle::ms_VehicleIdToLookFor = NETWORK_INVALID_OBJECT_ID; u32 CVehicle::ms_iSlipstreamingVehiclesFound = 0; bool CVehicle::ms_enableBikesCameraCollisionFlag = true; #define RAMP_CAR_DEFAULT_DAMAGE (2.0f) #define SCOOP_CAR_DEFAULT_DAMAGE (3.75f) float CVehicle::ms_rampCarCollisionDamageScale = RAMP_CAR_DEFAULT_DAMAGE; float CVehicle::ms_scoopCarCollisionDamageScale = SCOOP_CAR_DEFAULT_DAMAGE; bool CVehicle::ms_modifyPhantomCollisionWithPeds = true; bool CVehicle::ms_enablePatrolBoatSingleTimeSliceFix = true; static float ms_coronaFade_MainFadeRatio = 0.25f; static float ms_coronaFade_BeginStart = 50.0f; static float ms_coronaFade_BeginEnd = 300.0f; static float ms_coronaFade_CutoffStart = 290.0f; static float ms_coronaFade_CutoffEnd = 300.0f; static float ms_underwaterHeadlightCoronaFade = 0.4f; static u32 ms_sirenSpecularFade = 15; static u32 ms_shadowFade = 15; static float g_MainLights_ShutDownDistance; static float g_SecondaryLights_ShutDownDistance; static float g_NeonLights_ShutDownDistance; static float g_SirenLights_ShutDownDistance; static float g_AOVolume_ShutDownDistance; static float g_Lights_FadeLength = 10.0f; #define oo_Lights_FadeLength (1.0f / g_Lights_FadeLength) extern float g_Boat_InteriorLight_ShutDownDistance; extern float g_Boat_Lights_FadeLength; extern float g_Boat_corona_ShutDownDistance; extern float g_Boat_corona_FadeLength; extern float g_Boat_coronaProperties_size; extern float g_Boat_coronaProperties_intensity; extern float g_Boat_coronaProperties_zBias; bool CVehicle::m_consumePetrol = false; float CVehicle::ms_fGravityScale = 1.0f; RegdVeh CVehicle::ms_requestedHdVeh; u32 CVehicle::ms_lastTimeRequestedHd = 0; u32 CVehicle::ms_insideVehicleMode = 0; atHashString CVehicle::ms_speedBoostAirScreenEffect("AirRaceBoost", 0xC28E16E6); atHashString CVehicle::ms_speedBoostScreenEffect("StuntFast", 0xEE553F83); atHashString CVehicle::ms_slowDownScreenEffect("StuntSlow", 0x6824D91C); bool CVehicle::ms_DisableSlowDownEffect = false; bool CVehicle::ms_DisableSpeedBoostEffect = false; eHierarchyId CVehicle::ms_aDoorComponentIds[MAX_NUM_DOOR_COMPONENTS] = { VEH_DOOR_DSIDE_F, VEH_DOOR_DSIDE_R, VEH_DOOR_PSIDE_F, VEH_DOOR_PSIDE_R, VEH_WINDOW_LF, VEH_WINDOW_RF, VEH_WINDOW_LR, VEH_WINDOW_RR }; eHierarchyId CVehicle::ms_aBobbleHeadIds[ BHT_MAX ] = { VEH_BOBBLE_HEAD, VEH_BOBBLE_HAND, VEH_BOBBLE_ENGINE, VEH_BOBBLE_MISC_1, VEH_BOBBLE_MISC_2, VEH_BOBBLE_MISC_3, VEH_BOBBLE_MISC_4, VEH_BOBBLE_MISC_5, VEH_BOBBLE_MISC_6, VEH_BOBBLE_MISC_7, VEH_BOBBLE_MISC_8, }; #if USE_SIXAXIS_GESTURES bank_float CVehicle::ms_fDefaultMotionContolPitchMin = -0.5f; bank_float CVehicle::ms_fDefaultMotionContolPitchMax = 0.5f; bank_float CVehicle::ms_fDefaultMotionContolRollMin = -0.8f; bank_float CVehicle::ms_fDefaultMotionContolRollMax = 0.8f; bank_float CVehicle::ms_fDefaultMotionContolAftertouchMult = 1.2f; bank_float CVehicle::ms_fDefaultMotionContolSteerSmoothRate = 30.0f; #endif // PSN CSpatialArray* CVehicle::ms_spatialArray = NULL; atFixedArray CVehicle::ms_bobbleHeads; #if __BANK static bool sbRenderBones = false; static bool sbRenderBonesUnDamaged = false; static bool sbRenderBoneNames = false; static bool sbRenderBoneCoords = false; static bool sbRenderBoneMatrix = false; static bool sbRenderBoneGeometry = false; static bool sbRenderComponentBoundingBox = false; static bool sbRenderBoneNames_OneAtATime = false; static int siRenderBoneNamesOneAtATimeId = 0; bool CVehicle::sm_PreComputeImpacts = true; #endif bank_float CVehicle::ms_fJumpRechargeTime = 5.0f; #if __BANK PF_PAGE(FoliageDragPage, "Foliage drag"); PF_GROUP(FoliageDrag); PF_LINK(FoliageDragPage, FoliageDrag); PF_VALUE_FLOAT(FoliageForceMagSqrAppliedThisFrame, FoliageDrag); PF_PAGE(GTA_VehicleDynamics, "Vehicle Dynamics"); PF_GROUP(Vehicle_Dynamics); PF_LINK(GTA_VehicleDynamics, Vehicle_Dynamics); PF_VALUE_FLOAT(LeftRumbleIntensity, Vehicle_Dynamics); PF_VALUE_FLOAT(LeftPeakSlip, Vehicle_Dynamics); PF_VALUE_FLOAT(RightRumbleIntensity, Vehicle_Dynamics); PF_VALUE_FLOAT(RightPeakSlip, Vehicle_Dynamics); PF_VALUE_FLOAT(VehicleTimeStep, Vehicle_Dynamics); static bool gs_LightSwitchTweakMode = false; static bool gs_LightSwitchTweakLightOn = true; //static float boneFactor = 2.0f; //static float frequency = 0.01f; static bool Sirens_RenderBoneId = false; static bool Sirens_RenderBones = false; void CHeadlightTuningData::SetupHeadlightTuningBank(bkBank& bank) { bank.PushGroup("Headlight Tweaking", false); bank.AddToggle("Global Force Recessed Head/Tail Corona", &ms_globalForceRecessedCorona ); bank.AddSlider("Global Headlight Intensity Mult", &ms_globalHeadlightIntensityMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Global Headlight Dist Mult", &ms_globalHeadlightDistMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Global Cone Inner Angle Mod", &ms_globalConeInnerAngleMod, 0.0f, 3.0f, 0.05f ); bank.AddSlider("Global Cone Outer Angle Mod", &ms_globalConeOuterAngleMod, 0.0f, 3.0f, 0.05f ); bank.AddSlider("Global Only One Light Mod", &ms_globalOnlyOneLightMod, 0.0f, 3.0f, 0.05f ); bank.AddToggle("Use Full Beam Or Dipped Beam Mods", &ms_useFullBeamOrDippedBeamMods); bank.AddSlider("Full Beam Headlight Intensity Mult", &ms_fullBeamHeadlightIntensityMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Full Beam Headlight Dist Mult", &ms_fullBeamHeadlightDistMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Full Beam Headlight Corona Intensity Mult", &ms_fullBeamHeadlightCoronaIntensityMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Full Beam Headlight Corona Size Mult", &ms_fullBeamHeadlightCoronaSizeMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Aim Full Beam Mod", &ms_aimFullBeamMod, -1.0f, 1.0f, 0.05f ); bank.AddSlider("Aim Dipped Beam Mod", &ms_aimDippedBeamMod, -1.0f, 1.0f, 0.05f ); bank.AddToggle("Make Headlights Cast Further For Player", &ms_makeHeadlightsCastFurtherForPlayer); bank.AddSlider("Player Headlight Intensity Mult", &ms_playerHeadlightIntensityMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Player Headlight Dist Mult", &ms_playerHeadlightDistMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Player Headlight Exponent Mult", &ms_playerHeadlightExponentMult, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Underwater Coronas Fade", &ms_underwaterHeadlightCoronaFade, 0.0f, 1.0f, 0.001f); bank.AddTitle("Volume Light Setup for Vehicles"); bank.AddSlider("Volume Intensity Scale", &ms_volumeIntensityScale, 0.0f, 10.0f, 0.001f); bank.AddSlider("Volume Size Scale", &ms_volumeSizeScale, 0.0f, 10.0f, 0.001f); bank.AddColor("Outer Volume Color", &ms_outerVolumeColor); bank.AddSlider("Outer Volume Intensity", &ms_outerVolumeIntensity, 0.0f, 10.0f, 0.001f ); bank.AddSlider("Outer Volume Fall Off Exponent", &ms_outerVolumeFallOffExponent, 0.0f, 50.0f, 0.001f); const char* twinHeadLightModesStr[] = { "1 Light", "1 Light With Texture", "1 Light With Fake Split to 2", "2 Lights", "1 Light Textured with Fake Split to 2", "2 Lights Textured" }; CompileTimeAssert(NELEM(twinHeadLightModesStr) == VTHL_NUM_MODES); bank.AddCombo("Vehicle Twin Headlight Mode", (int*)(&ms_VehicleTwinHeadLightMode), NELEM(twinHeadLightModesStr), twinHeadLightModesStr); bank.AddSlider("Global Fake 2 lights with 1 Light Angle Mod", &ms_globalFake2LightAngleMod, 0.0f, 3.0f, 0.05f ); bank.AddSlider("Global Fake 2 lights with 1 Light Displacement Mod", &ms_globalFake2LightsDisplacementMod, 0.0f, 10.0f, 0.001f); bank.AddSlider("Submarine Fake 2 lights with 1 Light Angle Mod", &ms_globalFake2LightAngleModSubmarine, 0.0f, 3.0f, 0.05f ); bank.AddSlider("Submarine Fake 2 lights with 1 Light Displacement Mod", &ms_globalFake2LightsDisplacementModSubmarine, 0.0f, 10.0f, 0.001f); const char* twinTailLightModesStr[] = { "1 Light", "1 Light Filler only", "1 Light With Fake Split to 2" }; CompileTimeAssert(NELEM(twinTailLightModesStr) == VTTL_NUM_MODES); bank.AddCombo("Vehicle Other Lights Mode", (int*)(&ms_VehicleTwinTailLightMode), NELEM(twinTailLightModesStr), twinTailLightModesStr); bank.AddToggle("Use vehicle LOD Dist for light fade", &ms_useVehicleLodDist); bank.AddAngle("2 headlights Full Beam Angle", &ms_aimFullBeamAngle, bkAngleType::DEGREES,-90.0f,90.0f); bank.AddAngle("2 headlights Dipped Beam Angle", &ms_aimDippedBeamAngle, bkAngleType::DEGREES,-90.0f,90.0f); static const char * headLightShadowFlags[4] = { "NONE", "DYNAMIC ONLY", "STATIC ONLY", "STATIC AND DYNAMIC"}; bank.AddCombo("Force Headlight Shadows", &ms_ForceHeadlightShadows, 4, &headLightShadowFlags[0]); bank.PopGroup(); } static void NeonColorChangeCB() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if(veh) { veh->SetNeonColour(CNeonTuningData::ms_defaultNeonColor); } } void CNeonTuningData::SetupNeonTuningBank(bkBank& bank) { bank.PushGroup("Neon Tweaking", false); bank.AddToggle("Force neons on", &ms_neonsForcedOn ); bank.AddSlider("Global Intensity", &ms_globalNeonIntensity, 0.0f, 100.0f, 0.05f ); bank.AddSlider("Global Radius", &ms_globalNeonRadius, 0.0f, 10.0f, 0.05f ); bank.AddSlider("Global Fall Off Exponent", &ms_globalNeonRadiusFallOffExponent, 0.0f, 500.0f, 0.05f ); bank.AddSlider("Global Capsule Extent (Sides)", &ms_globalNeonCapsuleExtentSides, 0.0f, 10.0f, 0.01f ); bank.AddSlider("Global Capsule Extent (Front / Back)", &ms_globalNeonCapsuleExtentFrontBack, 0.0f, 10.0f, 0.01f ); bank.AddSlider("Global Clip Plane Height", &ms_globalNeonClipPlaneHeight, -5.0f, 20.0f, 0.001f ); bank.AddSlider("Global Clip Plane Height (Bikes Only)", &ms_globalNeonBikeClipPlaneHeight, -5.0f, 20.0f, 0.001f); bank.AddColor("Default Neon Colour", &ms_defaultNeonColor, NeonColorChangeCB); bank.PopGroup(); } static void RolldownLF() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RolldownWindow(VEH_WINDOW_LF); } } static void RolldownRF() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RolldownWindow(VEH_WINDOW_RF); } } static void RolldownLR() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RolldownWindow(VEH_WINDOW_LR); } } static void RolldownRR() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RolldownWindow(VEH_WINDOW_RR); } } static void RollupLF() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RollWindowUp(VEH_WINDOW_LF); } } static void RollupRF() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RollWindowUp(VEH_WINDOW_RF); } } static void RollupLR() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RollWindowUp(VEH_WINDOW_LR); } } static void RollupRR() { CVehicle *veh = CGameWorld::FindLocalPlayer()->GetVehiclePedInside(); if( veh ) { veh->RollWindowUp(VEH_WINDOW_RR); } } // Extern from Automobile.cpp extern float sfEngineRevVelocityThreshold; extern float sfEngineRevAcceleration; extern float sfEngineRevMinRevs; extern float sfEngineRevRandomMin; extern float bfSteeringBiasWhenWheelPopped; extern float bfSteeringBiasLifeAfterWheelPopped; extern float bfSteeringBiasWhenHitCarOnTheSide; extern float bfSteeringBiasLifeAfterHitCarOnTheSide; // Extern from CVehicle:: extern float VEHICLE_IN_AIR_CRASH_TIME; extern float VEHICLE_IN_AIR_CRASH_MIN_SPEED_SQ; extern float VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE; extern float VEHICLE_IN_AIR_CRASH_MAX_FRICTION; extern float VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE_TIME; extern float VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE; extern float VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE_TIME; extern float VEHICLE_UPSIDE_DOWN_COLLISION_FRICTION; extern float VEHICLE_UPSIDE_DOWN_COLLISION_SIDEWAYS_FRICTION; extern float VEHICLE_CORNER_WALL_COLLISION_FRICTION; void SetupVehicleBank(bkBank& bank) { bank.PushGroup("Emissive Light Intensity Tweaking", false); bank.AddToggle("Tweak Mode",&gs_LightSwitchTweakMode); bank.AddToggle("Lights On",&gs_LightSwitchTweakLightOn); bank.PushGroup("Day", false); bank.AddTitle("On Intensities"); bank.AddSlider("Default", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_DEFAULT],-10.0f,150.0f,0.01f); bank.AddSlider("Left Head Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_HEADLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Head Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_HEADLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Left Tail Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_TAILLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Tail Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_TAILLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Front Left Indicator", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_INDICATOR_FL],-10.0f,150.0f,0.01f); bank.AddSlider("Front Right Indicator", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_INDICATOR_FR],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Left Indicator", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_INDICATOR_RL],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Front Indicator", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_INDICATOR_RR],-10.0f,150.0f,0.01f); bank.AddSlider("Left Brake Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_BRAKELIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Brake Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_BRAKELIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Medium Brake Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_BRAKELIGHT_M],-10.0f,150.0f,0.01f); bank.AddSlider("Left Reversing Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_REVERSINGLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Reversing Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_REVERSINGLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 1", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_EXTRALIGHT_1],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 2", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_EXTRALIGHT_2],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 3", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_EXTRALIGHT_3],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 4", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_EXTRALIGHT_4],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_EXTRALIGHT],-10.0f,150.0f,0.01f); bank.AddSlider("Siren Light", &g_LightSwitchSettings.lights_On_day[CVehicleLightSwitch::LW_SIRENLIGHT], -10.0f,150.0f,0.01f); bank.AddTitle("Off Intensities"); bank.AddSlider("Default", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_DEFAULT],-10.0f,150.0f,0.01f); bank.AddSlider("Left Head Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_HEADLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Head Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_HEADLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Left Tail Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_TAILLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Tail Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_TAILLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Front Left Indicator", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_INDICATOR_FL],-10.0f,150.0f,0.01f); bank.AddSlider("Front Right Indicator", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_INDICATOR_FR],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Left Indicator", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_INDICATOR_RL],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Front Indicator", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_INDICATOR_RR],-10.0f,150.0f,0.01f); bank.AddSlider("Left Brake Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_BRAKELIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Brake Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_BRAKELIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Medium Brake Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_BRAKELIGHT_M],-10.0f,150.0f,0.01f); bank.AddSlider("Left Reversing Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_REVERSINGLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Reversing Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_REVERSINGLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 1", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_EXTRALIGHT_1],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 2", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_EXTRALIGHT_2],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 3", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_EXTRALIGHT_3],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 4", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_EXTRALIGHT_4],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_EXTRALIGHT],-10.0f,150.0f,0.01f); bank.AddSlider("Siren Light", &g_LightSwitchSettings.lights_Off_day[CVehicleLightSwitch::LW_SIRENLIGHT],-10.0f,150.0f,0.01f); bank.PopGroup(); bank.PushGroup("Night", false); bank.AddTitle("On Intensities"); bank.AddSlider("Default", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_DEFAULT],-10.0f,150.0f,0.01f); bank.AddSlider("Left Head Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_HEADLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Head Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_HEADLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Left Tail Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_TAILLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Tail Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_TAILLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Front Left Indicator", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_INDICATOR_FL],-10.0f,150.0f,0.01f); bank.AddSlider("Front Right Indicator", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_INDICATOR_FR],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Left Indicator", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_INDICATOR_RL],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Front Indicator", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_INDICATOR_RR],-10.0f,150.0f,0.01f); bank.AddSlider("Left Brake Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_BRAKELIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Brake Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_BRAKELIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Medium Brake Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_BRAKELIGHT_M],-10.0f,150.0f,0.01f); bank.AddSlider("Left Reversing Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_REVERSINGLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Reversing Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_REVERSINGLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 1", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_EXTRALIGHT_1],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 2", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_EXTRALIGHT_2],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 3", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_EXTRALIGHT_3],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 4", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_EXTRALIGHT_4],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_EXTRALIGHT],-10.0f,150.0f,0.01f); bank.AddSlider("Sirena Light", &g_LightSwitchSettings.lights_On_night[CVehicleLightSwitch::LW_SIRENLIGHT],-10.0f,150.0f,0.01f); bank.AddTitle("Off Intensities"); bank.AddSlider("Default", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_DEFAULT],-10.0f,150.0f,0.01f); bank.AddSlider("Left Head Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_HEADLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Head Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_HEADLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Left Tail Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_TAILLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Tail Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_TAILLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Front Left Indicator", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_INDICATOR_FL],-10.0f,150.0f,0.01f); bank.AddSlider("Front Right Indicator", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_INDICATOR_FR],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Left Indicator", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_INDICATOR_RL],-10.0f,150.0f,0.01f); bank.AddSlider("Rear Front Indicator", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_INDICATOR_RR],-10.0f,150.0f,0.01f); bank.AddSlider("Left Brake Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_BRAKELIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Brake Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_BRAKELIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Medium Brake Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_BRAKELIGHT_M],-10.0f,150.0f,0.01f); bank.AddSlider("Left Reversing Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_REVERSINGLIGHT_L],-10.0f,150.0f,0.01f); bank.AddSlider("Right Reversing Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_REVERSINGLIGHT_R],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 1", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_EXTRALIGHT_1],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 2", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_EXTRALIGHT_2],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 3", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_EXTRALIGHT_3],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light 4", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_EXTRALIGHT_4],-10.0f,150.0f,0.01f); bank.AddSlider("Extra Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_EXTRALIGHT],-10.0f,150.0f,0.01f); bank.AddSlider("Siren Light", &g_LightSwitchSettings.lights_Off_night[CVehicleLightSwitch::LW_SIRENLIGHT],-10.0f,150.0f,0.01f); bank.PopGroup(); bank.PopGroup(); bank.PushGroup("Vehicle Lights LOD",false); bank.AddSlider("Main Lights Shutdown Distance",&g_MainLights_ShutDownDistance,0.0f,500.0f,0.1f); bank.AddSlider("Secondary Lights ShutDown Distance",&g_SecondaryLights_ShutDownDistance,0.0f,200.0f,0.1f); bank.AddSlider("Neon Lights ShutDown Distance",&g_NeonLights_ShutDownDistance,0.0f,200.0f,0.1f); bank.AddSlider("AO Volume ShutDown Distance", &g_AOVolume_ShutDownDistance, 0.0f, 200.0f, 0.1f); bank.AddSlider("Sirens Lights ShutDown Distance",&g_SirenLights_ShutDownDistance,0.0f,200.0f,0.1f); bank.AddSlider("Lights Fade Length",&g_Lights_FadeLength,0.1f,50.0f,0.1f); bank.AddSlider("Lights Cutoff Distance Tweak",&CVehicle::ms_fLightCutoffDistanceTweak, 0.0f, 500.0f,0.1f); bank.PopGroup(); bank.PushGroup("Bones",false); bank.AddToggle("Render Vehicles",&g_DrawVehicles); bank.AddToggle("Render Bones",&sbRenderBones); bank.AddToggle("Render Bones (unDamaged)",&sbRenderBonesUnDamaged); bank.AddToggle("Render Bone Names",&sbRenderBoneNames); bank.AddToggle("Render Bone Coords", &sbRenderBoneCoords); bank.AddToggle("Render Bone Matrix", &sbRenderBoneMatrix); bank.AddToggle("Render Bone Geometry",&sbRenderBoneGeometry); bank.AddToggle("Render Bounding Box", &sbRenderComponentBoundingBox); bank.AddToggle("Render Bone Names one at a time",&sbRenderBoneNames_OneAtATime); bank.AddSlider("Render Bone Names Idx",&siRenderBoneNamesOneAtATimeId,0,VEH_NUM_NODES - 1,1); bank.AddToggle("Render Sirens Bones",&Sirens_RenderBones); bank.AddToggle("Render Sirens BoneID",&Sirens_RenderBoneId); bank.PopGroup(); bank.PushGroup("Windows",false); bank.AddButton("roll down Left Front window", RolldownLF); bank.AddButton("roll up Left Front window", RollupLF); bank.AddButton("roll down Right Front window", RolldownRF); bank.AddButton("roll up Right Front window", RollupRF); bank.AddButton("roll down Left Rear window", RolldownLR); bank.AddButton("roll up Left Rear window", RollupLR); bank.AddButton("roll down Right Rear window", RolldownRR); bank.AddButton("roll up Right Rear window", RollupRR); bank.PopGroup(); bank.PushGroup("Car idling Movement", false); bank.AddSlider("sfEngine rev angular velocity threshold",&sfEngineRevVelocityThreshold,0.0f,10.0f,0.01f); bank.AddSlider("sfEngine rev angular acceleration",&sfEngineRevAcceleration,0.0f,10.0f,0.01f); bank.AddSlider("sfEngine min revs to apply torque",&sfEngineRevMinRevs,0.0f,1.0f,0.01f); bank.AddSlider("sfEngine Random min rev ",&sfEngineRevRandomMin,0.0f,1.0f,0.01f); bank.PopGroup(); bank.PushGroup("Slip streaming", false); bank.AddSlider("ms_fSpeedToStartSlipstream",&CVehicle::ms_fSpeedToStartSlipstream, 0.0f, 100.0f, 0.01f); bank.AddSlider("ms_fMaxSpeedOfSlipStream",&CVehicle::ms_fMaxSpeedOfSlipStream, 0.0f, 100.0f, 0.01f); bank.AddSlider("ms_fSlipstreamMaxDragReduction",&CVehicle::ms_fSlipstreamMaxDragReduction, 0.0f, 1.0f, 0.01f); bank.AddSlider("ms_fSlipstreamMaxDistance",&CVehicle::ms_fSlipstreamMaxDistance, 0.0f, 100.0f, 0.01f); bank.AddSlider("ms_fSlipStreamMaxTime",&CVehicle::ms_fSlipstreamMaxTime, 0.0f, 100.0f, 0.01f); bank.AddToggle("ms_bRealisticSlipstreamMode",&CVehicle::ms_bRealisticSlipstreamMode); bank.PopGroup(); bank.PushGroup("Friction"); bank.PushGroup("Car crash in air", false); bank.AddSlider("Min speed for crash in air",&VEHICLE_IN_AIR_CRASH_MIN_SPEED_SQ,0.0f,10.0f,0.01f); bank.AddSlider("Time in air to start friction/elasticity increase",&VEHICLE_IN_AIR_CRASH_TIME,0.0f,10.0f,0.01f); bank.AddSlider("Friction increase on ground impact",&VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE,0.0f,10.0f,0.01f); bank.AddSlider("Max total friction on ground impact",&VEHICLE_IN_AIR_CRASH_MAX_FRICTION,0.0f,10.0f,0.01f); bank.AddSlider("Friction increase time",&VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE_TIME,0.0f,10.0f,0.01f); bank.AddSlider("Elasticity increase on ground impact",&VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE,0.0f,10.0f,0.01f); bank.AddSlider("Elasticity increase time",&VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE_TIME,0.0f,10.0f,0.01f); bank.AddSlider("Upside down collision friction",&VEHICLE_UPSIDE_DOWN_COLLISION_FRICTION,0.0f,10.0f,0.01f); bank.AddSlider("Upside down, sideways collision friction",&VEHICLE_UPSIDE_DOWN_COLLISION_SIDEWAYS_FRICTION,0.0f,10.0f,0.01f); bank.PopGroup(); bank.AddSlider("Corner against wall collision",&VEHICLE_CORNER_WALL_COLLISION_FRICTION,0.0f,10.0f,0.01f); bank.PopGroup(); bank.PushGroup("Steering noise", false); bank.AddSlider("Steering bias angle when wheel popped (in degrees)",&bfSteeringBiasWhenWheelPopped,0.0f,90.0f,1.0f); bank.AddSlider("Steering bias life time when wheel popped",&bfSteeringBiasLifeAfterWheelPopped,0.0f,10.0f,0.01f); bank.AddSlider("Steering bias angle when hit from the side (in degrees)",&bfSteeringBiasWhenHitCarOnTheSide,0.0f,90.0f,1.0f); bank.AddSlider("Steering bias life time when hit from the side",&bfSteeringBiasLifeAfterHitCarOnTheSide,0.0f,10.0f,0.01f); bank.PopGroup(); bank.PushGroup("Petrol tank damage", false); bank.AddSlider("Player petrol tank gun damage multiplier",&CVehicleDamage::sfVehDamPetrolTankApplyGunDamageMult,0.0f,100.0f,0.1f); bank.AddSlider("AI petrol tank gun damage multiplier",&CVehicleDamage::sfVehDamPetrolTankApplyGunDamageMultAI,0.0f,100.0f,0.1f); bank.PopGroup(); bank.PushGroup("Hair Scale", false); bank.AddSlider("Disable Threshold", &CVehicle::sm_HairScaleDisableThreshold, 0.0f, 10.0f, 0.01f); bank.PopGroup(); } #endif // __DEV void CHeadlightTuningData::Set(const CVisualSettings& visualSettings) { ms_globalHeadlightIntensityMult = visualSettings.Get("car.headlights.global.HeadlightIntensityMult", 1.0f); ms_globalHeadlightDistMult = visualSettings.Get("car.headlights.global.HeadlightDistMult", 1.0f); ms_globalConeInnerAngleMod = visualSettings.Get("car.headlights.global.ConeInnerAngleMod", 1.0f); ms_globalConeOuterAngleMod = visualSettings.Get("car.headlights.global.ConeOuterAngleMod", 1.0f); ms_globalOnlyOneLightMod = visualSettings.Get("car.headlights.global.OnlyOneLightMod", 0.8f); ms_globalFake2LightAngleMod = visualSettings.Get("car.headlights.global.Fake2LightsAngleMod", 0.95f); ms_globalFake2LightsDisplacementMod = visualSettings.Get("car.headlights.global.Fake2LightsDisplacementMod", 0.82f); ms_globalFake2LightAngleModSubmarine = visualSettings.Get("car.headlights.submarine.Fake2LightsAngleMod", 0.83f); ms_globalFake2LightsDisplacementModSubmarine = visualSettings.Get("car.headlights.submarine.Fake2LightsDisplacementMod", 0.0f); ms_fullBeamHeadlightIntensityMult = visualSettings.Get("car.headlights.fullbeam.IntensityMult", 1.1f); ms_fullBeamHeadlightDistMult = visualSettings.Get("car.headlights.fullbeam.DistMult", 1.3f); ms_fullBeamHeadlightCoronaIntensityMult = visualSettings.Get("car.headlights.fullbeam.CoronaIntensityMult", 2.0f); ms_fullBeamHeadlightCoronaSizeMult = visualSettings.Get("car.headlights.fullbeam.CoronaSizeMult", 2.0f); ms_aimFullBeamMod = visualSettings.Get("car.headlights.aim.fullbeam.mod", 0.0f); ms_aimDippedBeamMod = visualSettings.Get("car.headlights.aim.dippedbeam.mod", -0.2f); ms_aimFullBeamAngle = visualSettings.Get("car.headlights.aim.fullbeam.angle", 0.0f); ms_aimDippedBeamAngle = visualSettings.Get("car.headlights.aim.dipeedbeam.angle", 0.0f); ms_playerHeadlightIntensityMult = visualSettings.Get("car.headlights.player.intensitymult", 1.2f); ms_playerHeadlightDistMult = visualSettings.Get("car.headlights.player.distmult", 2.5f); ms_playerHeadlightExponentMult = visualSettings.Get("car.headlights.player.exponentmult", 1.0f); ms_volumeIntensityScale = visualSettings.Get("car.headlights.volume.intensityscale", 1.0f); ms_volumeSizeScale = visualSettings.Get("car.headlights.volume.sizescale", 0.5f); ms_outerVolumeIntensity = visualSettings.Get("car.headlights.volume.outerintensity", 0.0f); ms_outerVolumeFallOffExponent = visualSettings.Get("car.headlights.volume.outerexponent", 50.0f); ms_outerVolumeColor = visualSettings.GetColor("car.headlights.volume.outerVolumeColor"); ms_coronaFade_MainFadeRatio = visualSettings.Get("car.coronas.MainFadeRatio", 0.25f); ms_coronaFade_BeginStart = visualSettings.Get("car.coronas.BeginStart", 50.0f); ms_coronaFade_BeginEnd = visualSettings.Get("car.coronas.BeginEnd", 300.0f); ms_coronaFade_CutoffStart = visualSettings.Get("car.coronas.CutoffStart", 290.0f); ms_coronaFade_CutoffEnd = visualSettings.Get("car.coronas.CutoffEnd", 300.0f); ms_underwaterHeadlightCoronaFade = visualSettings.Get("car.coronas.underwaterFade", 0.4f); g_Boat_InteriorLight_ShutDownDistance = visualSettings.Get("boat.light.interiorshutdowndistance"); g_Boat_Lights_FadeLength = visualSettings.Get("boat.light.fadelength"); g_Boat_corona_ShutDownDistance = visualSettings.Get("boat.light.shutdowndistance"); g_Boat_corona_FadeLength = visualSettings.Get("boat.corona.fadelength"); g_Boat_coronaProperties_size = visualSettings.Get("boat.corona.size"); g_Boat_coronaProperties_intensity = visualSettings.Get("boat.corona.intensity"); g_Boat_coronaProperties_zBias = visualSettings.Get("boat.corona.zBias"); ms_sirenSpecularFade = (u32)visualSettings.Get("car.sirens.SpecularFade", 15); ms_shadowFade = (u32)visualSettings.Get("car.sirens.ShadowFade", 15); } void CNeonTuningData::Set(const CVisualSettings& visualSettings) { ms_globalNeonIntensity = visualSettings.Get("car.neon.intensity", 1.0f); ms_globalNeonRadius = visualSettings.Get("car.neon.radius", 1.0f); ms_globalNeonRadiusFallOffExponent = visualSettings.Get("car.neon.falloffexponent", 50.0f); ms_globalNeonCapsuleExtentSides = visualSettings.Get("car.neon.capsuleExtent.sides", 1.0f); ms_globalNeonCapsuleExtentFrontBack = visualSettings.Get("car.neon.capsuleExtent.frontback", 0.5f); ms_globalNeonClipPlaneHeight = visualSettings.Get("car.neon.clipplaneheight", 0.1f); ms_globalNeonBikeClipPlaneHeight = visualSettings.Get("car.neon.bikeclipplaneheight", 0.25f); ms_defaultNeonColor = visualSettings.GetColor("car.neon.defaultcolor"); } #define WINCHID (29) // Light Settings static float g_CarAngleSettings; static float g_CarSplitSettings; static ConfigLightSettings g_TaxiSettings; static ConfigLightSettings g_InteriorLightSettings; static ConfigLightSettings g_InteriorFatLightSettings; static ConfigLightSettings g_InteriorRedLightSettings; static ConfigLightSettings g_DoorLightSettings; static ConfigLightSettings g_DashLightSettings; static ConfigLightSettings g_PlateLightSettings; static ConfigLightSettings g_HeliInteriorLights; ConfigVehiclePositionLightSettings g_HeliPosLights; ConfigVehicleWhiteLightSettings g_HeliWhiteHeadLights; ConfigVehicleWhiteLightSettings g_HeliWhiteTailLights; ConfigVehicleDayNightLightSettings g_DayNightLights; ConfigVehiclePositionLightSettings g_PlanePosLights; ConfigVehicleWhiteLightSettings g_PlaneWhiteHeadLights; ConfigVehicleWhiteLightSettings g_PlaneWhiteTailLights; ConfigPlaneEmergencyLightsSettings g_PlaneLeftEmergencyLights; ConfigPlaneEmergencyLightsSettings g_PlaneRightEmergencyLights; ConfigPlaneInsideHullSettings g_PlaneInsideHullLights; ConfigPlaneControlPanelSettings g_PlaneControlPanelLights; ConfigLightSettings g_PlaneLuxe2Cabin; ConfigLightSettings g_HeliSwift2Cabin; ConfigLightSettings g_PlaneLuxe2CabinStrip; ConfigLightSettings g_PlaneLuxe2CabinTV; ConfigLightSettings g_PlaneLuxe2CabinWindow; ConfigLightSettings g_PlaneLuxe2CabinLOD; ConfigBoatLightSettings g_BoatLights; ConfigLightSettings g_SubmarineLightOne; ConfigLightSettings g_SubmarineLightTwo; //set #define AO_VEHICLE_TYPE_CAR 0 #define AO_VEHICLE_TYPE_BIKE 1 #define AO_VEHICLE_TYPE_BICYCLE 2 #define AO_VEHICLE_TYPE_HELI 3 int g_vehicleBottomTexKey[4]; float sfMaximumMassForPushingVehicles = 17000.0f; dev_float sfMinimumMassForCrushingVehicles = 35000.0f; bool CVehicle::sm_bEnableHeadlightShadows = false; bool CVehicle::sm_bEnableSirenShadows = false; bool CVehicle::sm_bJetWashEnabled = true; bool CVehicle::sm_bDisableExplosionDamage = false; bool CVehicle::sm_bDisableWeaponBladeForces = false; bool CVehicle::sm_bDoubleClickForJump = false; CVehicle* CVehicle::ms_formationLeader = NULL; Vector3 CVehicle::ms_formationOffset = Vector3( 0.0f, 0.0f, 0.0f ); float CVehicle::ms_formationRadius = 0.0f; bool CVehicle::ms_useBoostButtonForRetract = false; #if __BANK extern float gfPlayerCarInvMassScale; #endif // __BANK // // name: CVehicle::Initialise // description: Initialise vehicle related modules void CVehicle::InitSystem() { Displayf("Initialising CVehicle..."); CHandlingDataMgr::Init(); g_vehicleBottomTexKey[AO_VEHICLE_TYPE_CAR] = atHashValue("carbottom"); g_vehicleBottomTexKey[AO_VEHICLE_TYPE_BIKE] = atHashValue("motorbikebottom"); g_vehicleBottomTexKey[AO_VEHICLE_TYPE_BICYCLE] = atHashValue("bikebottom"); g_vehicleBottomTexKey[AO_VEHICLE_TYPE_HELI] = atHashValue("helibottom"); CVehicleVariationInstance::InitSystem(); int numPoolElements = (int) CVehicle::GetPool()->GetSize(); char* spatialArrayStorage = rage_aligned_new(16) char[CSpatialArray::kSizePerNode * numPoolElements]; ms_spatialArray = rage_new CSpatialArray(spatialArrayStorage, numPoolElements); CVehicleAILodManager::InitializeVehiclePriorityArray(); } // // name: CVehicle::Shutdown // description: Shutdown vehicle related modules void CVehicle::ShutdownSystem() { // TODO: delete the spatial array storage delete ms_spatialArray; CVehicleVariationInstance::ShutdownSystem(); } // end - CVehicle::Shutdown // // // CVehicle::CVehicle(const eEntityOwnedBy ownedBy, u32 popType, VehicleType vehType) : CPhysical (ownedBy), m_vehicleType (vehType), m_pIntelligence (NULL), m_eHomingLockOnState (CEntity::HLOnS_NONE), m_eHomingLockedOntoState (CEntity::HLOnS_NONE), m_fHomingProjectileDistance (-1.0f), m_nHomingLockOnStateResetFrame (-1), m_nHomingLockedOntoStateResetFrame (-1), m_nHomingProjectileDistanceResetFrame (-1), m_uLastTimeHomedAt (0), m_AirResistanceState (0), m_pLockOnTarget (0), m_pGroundPhysical (0), m_uGroundPhysicalTime (0), m_vFoliageDragForce (VEC3_ZERO), m_vFoliageDragTorque (VEC3_ZERO), m_fLodMult (1.f), m_wheelBrokenIndex (0), m_uRespottingCollisionCarPedFrameCount (0), m_uSecondaryRespotOverrideTimeout (0), m_bAllowRemoteDamageOnCreation (false), m_bNoDamageFromExplosionsOwnedByDriver (false), m_bRespottingCollisionCarPed (false), m_bVehicleOccupantsTakeExplosiveDamage (false), m_bCanEjectPassengersIfLocked (false), m_bAllowHomingMissleLockOnSynced (true), m_bSpecialEnterExit (false), m_bDontProcessVehicleGlass (false), m_bTyresDontBurstToRim (false), m_bDriftTyres (false), m_bUseDesiredZCruiseSpeedForLanding (false), m_bDontResetTurretHeadingInScriptedCamera (false), m_bDontOpenRearDoorsOnExplosion (false), m_bOnlyStartVehicleEngineOnThrottle (false), m_bClearWaitingOnCollisionOnPlayerEnter (false), m_iPrevSpeedBoostObjectID (0), m_iCurrentSpeedBoostObjectID (0), m_bBlockDueToEnterExit (false), m_bDisablePlayerCanStandOnTop (false), m_bSuppressNeons (false), m_bFlashRemotelyDuringRespotting (true), m_bFlashLocallyDuringRespotting (false), m_bUseMPDamageMultiplierForPlayerVehicle(true), m_bShouldVehicleExplodesOnExplosionDamageAtZeroBodyHealth(false), m_dialsRequestFrame (0), mf_BoostAppliedTimer (0.0f), mf_BoostAmount (0.0f), m_fSlowdownDuration (0.0f), m_ScriptRampImpulseScale (1.0f), m_VehicleTopSpeedPercent (-1.0f), m_fOverrideArriveDistForVehPersuitAttack (-1.0f), m_LaunchedInAirTimer (0), m_iPrevCurrentMissile (0), m_easyToLand (false), m_forceUpdateBoundsAndBodyFromSkeleton (false), m_hasHitRampCar (false), m_disableRampCarImpactDamage (true), m_applyRampCarSideImpulse (false), m_normaliseRampHitVelocity (false), m_activateBoostEffect (false), m_bSubmarineCarTransformPrevented (false), m_bDisableWantedConesResponse (false), m_resetBoostObjects (true), m_ParachuteModelHash (0), m_gliderState (NOT_GLIDING), m_wingDeployedRatio (0.0f), m_nozzleAngle (0.0f), m_sideShuntForce (0.0f), m_specialFlightModeRatio (0.0f), m_specialFlightModeTargetRatio (0.0f), m_targetGravityScale (0.0f), m_previousNormalisedVelocity (0.0f), m_previousThrottle (0.0f), m_cachedStickYfromNetwork (0.0f), m_currentWingRatio (0.0f), m_scriptMaxSpeed (-1.0f), m_outriggerRatio (0.0f), m_lastSideShuntVehicle (NULL), m_deployOutriggers (false), m_hitBySideShunt (false), m_shuntModifierActive (false), m_hitByWeaponBlade (false), m_disableHoverModeFlight ( false ), m_bWheeliesEnabled ( true ), m_allowSpecialFlightMode ( true ), m_numWeaponBlades (0), m_bExplosionEffectEMP (false), m_bExplosionEffectSlick (false), m_bIncreaseWheelCrushDamage (false), m_bWeaponModHasElectricEffect (false), m_bSirenSwitchPending (false), m_bUseIncreasedEMPDuration (false), m_uNumSequentialSirenPresses (0), m_uLastHitByExplosionEffectEMP (0), m_uLastHitByExplosionEffectSlick (0), m_detachedTombstone (nullptr), m_iFullThrottleRecoveryTime (0), m_bFullThrottleActive (false) BANK_ONLY(, m_pNonConversionReasonString("")) BANK_ONLY(, m_pLastVehPopRemovalFailReason("")) BANK_ONLY(, m_iLastVehPopRemovalFailFrame(0)) { for (u32 i=0; i= VEH_WINDOW_LF; i--) { m_bWindowsRolledDown[VEH_LAST_WINDOW - i] = false;//All windows rolled up to start } m_vecInternalForce.Zero(); m_vecInternalTorque.Zero(); m_VehicleDamage.SetParent(this); m_VehicleDamage.FixVehicleDamage(true); // Fix ? // We need to update our IsPetrolTankEmpty flag based on the current level, but can't // do that until m_VehicleDamage knows about its parent m_VehicleDamage.SetPetrolTankLevel(m_VehicleDamage.GetPetrolTankLevel()); BANK_ONLY(SetNonConversionReason("Conversion not yet requested.")); m_pVehicleWeaponMgr = NULL; m_destroyWeaponMgr = false; m_hoveringCloseToGround = false; #if !__FINAL m_strDebugName[0] = '\0'; #endif m_uHandbrakeOnTime = 0; m_bInvertControls = false; m_parentTrailer = NULL; m_DummyAttachmentParent = NULL; InitDummyAttachmentChildren(); m_vSuperDummyVelocity = VEC3_ZERO; m_vInactivePlaybackVelocity = VEC3_ZERO; m_vInactivePlaybackAngVelocity = VEC3_ZERO; m_fParentTrailerAttachStrength = 0.0f; m_vParentTrailerEntity1Offset.Zero(); m_vParentTrailerEntity2Offset.Zero(); m_qParentTrailerEntityRotation.Identity(); m_pedRemoteUsingDoor = false; m_remoteDriverDoorClosing = false; m_sirenData = NULL; m_DestructionInfo.Clear(); m_fDragCoeff = 0.0f; m_iLastActivationTime = 0; m_iNextValidHornTime = 0; m_nVehicleFlags.bIsInPopulationCache = false; m_nVehicleFlags.bPartsBrokenOff = false; m_nVehicleFlags.bHasBeenRepainted = false; m_nVehicleFlags.bForceHd = false; m_nVehicleFlags.bScriptForceHd = false; m_nVehicleFlags.bCanSaveInGarage = true; m_fTimeInSlipStream = 0.0f; m_fTimeProducingSlipStream = 0.0f; m_VehicleProducingSlipstream = NETWORK_INVALID_OBJECT_ID; m_bProducingSlipStreamForOther = false; m_fSlipStreamRechargeAndDechargeTimer = 0.0f; m_fTimeInAir = 0.0f; m_fTimeInAirCrash = 0.0f; m_fTimeTouchingPavement = 0.0f; m_bInAirCrash = false; m_fSpeedToRestoreAfterFix = 0.0f; m_fPreviousSpeedForDash = 0.0f; SetGravityForWheellIntegrator(-GRAVITY); #if __ASSERT m_notInWorldTimer = 0; #endif // __ASSERT #if DEBUG_DRAW m_vCreatedPos = Vec3V(V_ZERO); m_vCreatedDir = Vec3V(V_ZERO); #endif SetUseDynamicAmbientScale(true); InitAi(); // the time the vehicles lights turn on and off is slightly random, with more weight // being given to times where the sun's directional light is turned off. Not only does // it make things look more random (not everyone turning their lights on at the same time) // but it's also a performance consideration as we try to make sure few lights are on // at the same time as the sun light. m_timeLightsTurnOn = g_DayNightLights.sunset; m_timeLightsTurnOff = g_DayNightLights.sunrise; float fRandomSquared = fwRandom::GetRandomNumberInRange(0.0f, 0.99f); fRandomSquared *= fRandomSquared; m_timeLightsTurnOff -= fRandomSquared; m_timeLightsTurnOn -= fRandomSquared; m_StartNodes[0].SetEmpty(); m_StartNodes[1].SetEmpty(); m_StartLink = -1; m_StartLane = -1; m_clampedRenderLod = -1; #if __BANK m_CreationMethod = VEHICLE_CREATION_INVALID; m_iCreationCost = 0; m_RealPedFailReason = CVehicle::RPFR_SUCCEEDED; #endif BANK_ONLY(AssignDebugObjectID()); #if GTA_REPLAY m_replayVelocity = VEC3_ZERO; m_appliedVehicleDamage = 0; m_replayOverrideWheelsRot = false; #endif // GTA_REPLAY #if GPU_DAMAGE_WRITE_ENABLED m_bDamageWasUpdatedByGPU = false; m_bEnableDamage = false; #endif m_triggerRumbleTime = 0; m_EmblemMaterialGroup = 0; m_BeastVehicle = 0; m_RampOrRammingAllowed = 1; m_lightCoverState = LIGHTCOVER_CLOSED; m_lightCoverOpenRatio = 1.0f; m_fRocketBoostRemaining = 0.0f; m_bIsRocketBoosting = false; m_bRocketBoostedFromNetwork = false; m_bRocketBoostedStoppedFromNetwork = false; m_bCanRechargeRocketBoost = true; m_bTriggerRefillBoostEffect = false; m_bHasRocketBoostFXStarted = false; m_fRocketBoostRechargeRate = ROCKET_BOOST_RECHARGE_RATE; if( InheritsFromPlane() ) { m_fRocketBoostRechargeRate = ROCKET_BOOST_RECHARGE_RATE_PLANE; } m_boostToggledTime = 0; m_kersToggledTime = 0; m_nitrousActive = false; m_scriptOverridesNitrous.Init(); m_fCurrentWeaponGrillOffset = 0.0f; m_fCurrentWeaponRackOffset = 0.0f; m_fOverridenPlaceOnRoadHeight = sm_fInvalidPlaceOnRoadHeight; for(int i = 0; i < MAX_NUM_VEHICLE_WEAPONS; i++) { m_iRestrictedAmmoCount[i] = -1; } m_iBombAmmoCount = 0; m_iCountermeasureAmmoCount = 0; m_bHomingCanLockOnToObjects = false; m_bDoingJump = false; //m_bDoingForwardJump = false; m_carJumpState = NOT_JUMPING; m_fCarJumpTimer = 0.0f; m_vCarJumpGroundNormal.Zero(); m_bDoHigherJump = false; m_fJumpRechargeTimer = ms_fJumpRechargeTime; m_pumpedUpJumpState = PUMPED_UP_JUMP_IDLE; m_fInitialSuspensionRaiseRate = 0.0f; m_fPumpedUpJumpTimer = 0.0f; m_fJumpHeightScale = 1.0f; m_ParachuteModelHash = atHashWithStringNotFinal( "imp_Prop_ImpExp_Para_S" ); m_forceFixLinkMatrices = false; m_fDownforceModifierFront = 1.0f; m_fDownforceModifierRear = 1.0f; m_bHasDeformationBeenApplied = false; } CVehicle::~CVehicle() { DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnDestroyOfFocusEntity(), this ); DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfDestroyCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) ); Assertf(!m_spatialArrayNode.IsInserted(), "Veh 0x%p (%s) is being destroyed but is still in the spatial array.", this, GetModelName()); Assertf(!CVehicleFactory::GetFactory()->IsVehInDestroyedCache(this), "Veh 0x%p (%s) is being destroyed but it's also present in the population cache!", this, GetModelName()); GPU_VEHICLE_DAMAGE_ONLY(CApplyDamage::RemoveVehicleDeformation(this)); GPU_VEHICLE_DAMAGE_ONLY(CPhysics::RemoveVehicleDeformation(this)); if (m_nDEflags.nPopType == POPTYPE_RANDOM_PARKED) GetVehicleModelInfo()->RemoveVehicleModelRefParked(); else GetVehicleModelInfo()->RemoveVehicleModelRef(); if(m_nVehicleFlags.bIsInVehicleReusePool) GetVehicleModelInfo()->RemoveVehicleModelRefReusePool(); GetVehicleModelInfo()->RemoveVehicleInstance(this); CPathServerGta::MaybeRemoveDynamicObjectNavMesh(this); // Assert(!pDriver); for( s32 i = 0; i < MAX_VEHICLE_SEATS; i++ ) { CPed* pPed = m_SeatManager.GetPedInSeat(i); if( pPed ) { Assert (!pPed->IsNetworkClone()); const bool bIsNetworkClone = pPed->GetNetworkObject() && !pPed->GetNetworkObject()->CanDelete(); // Dont delete mission or player peds or network clones, flushing immediately sets them outside the vehicle. if( bIsNetworkClone || pPed->IsAPlayerPed() || pPed->PopTypeIsMission() ) { vehicleDebugf3("CVehicle::~CVehicle-->invoke pPed->GetPedIntelligence()->FlushImmediately(true) and ResetAllSolvers"); pPed->GetPedIntelligence()->FlushImmediately(true); pPed->GetIkManager().ResetAllSolvers(); } else { REPLAY_ONLY(if(CReplayMgr::IsEditModeActive() == false)) pPed->FlagToDestroyWhenNextProcessed(); // Mark the ped as invisible in case we're not removed until the next frame pPed->SetIsVisibleForModule(SETISVISIBLE_MODULE_WORLD, false, true); // This will skip some costy safe detach code pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_ParentCarIsBeingRemoved, true); } } } // vehicles created by a vehicle factory must have CVehicleFactory::Destroy() called on them Assert(!m_nVehicleFlags.bCreatedByFactory); CarAlarmState = CAR_ALARM_NONE; // needs to happen before drawHandler is deleted below GetVariationInstance().CleanUpPreloadMods(this); CVehicleModelInfo *pVModelInfo = static_cast(GetBaseModelInfo()); if (GetIsCurrentlyHD()) { GetVehicleDrawHandler().ShaderEffect_HD_DestroyInstance(this); } pVModelInfo->RemoveFromHDInstanceList((size_t)this); CVehicle::DeleteDrawable(); RemoveBlip(BLIP_TYPE_CAR); CVehiclePopulation::UpdateVehCount(this, CVehiclePopulation::SUB); //clear out the vehicle gadgets for(s32 i = 0; i < m_pVehicleGadgets.GetCount(); i++) { delete m_pVehicleGadgets[i]; } m_pVehicleGadgets.Reset(); DestroyVehicleWeaponMgr(); if(m_nVehicleFlags.bWheelShapetest) { ms_nNumAvailableShapetests++; } delete m_pIntelligence; m_pIntelligence = NULL; if(m_VehicleAudioEntity) { delete m_VehicleAudioEntity; m_VehicleAudioEntity = NULL; } if(m_parentTrailer) m_parentTrailer->RemoveCargoVehicle(this); delete m_sirenData; m_sirenData = NULL; if(m_DummyAttachmentParent) { m_DummyAttachmentParent->DummyDetachChild(this); } if(HasDummyAttachmentChildren()) { DummyDetachAllChildren(); } // Frag inst will be deleted in CEntity::DeleteInst(); SwitchCurrentPhysicsInst(GetVehicleFragInst()); RemoveDummyInst(); g_decalMan.RemoveAllVehicleBadges(this); for( int type = BHT_START; type < BHT_MAX; type++ ) { CBobbleHead* bobbleHead = GetBobbleHead( this, (eBobbleHeadType)type ); if( bobbleHead ) { bobbleHead->Reset(); } } if( ms_formationLeader == this ) { ResetFormationLeader(); } } #if !__NO_OUTPUT void CVehicle::PrintSkeletonData() { size_t bytes = 0; Displayf("Name, Num_Bones, Bytes"); for (s32 i = 0; i < CVehicle::GetPool()->GetSize(); ++i) { CVehicle* pEntity = CVehicle::GetPool()->GetSlot(i); if (pEntity) { pEntity->PrintSkeletonSummary(); bytes += pEntity->GetSkeletonSize(); } } Displayf("Total CVehicle Skeletons: %" SIZETFMT "d KB\n", bytes >> 10); } #endif // NAME : RemoveDummyInst // PURPOSE : Delete dummy inst // Derived classes could override this to clean up any dummy constraints, etc void CVehicle::RemoveDummyInst() { // Clean up dummy inst/constraints if( m_pDummyInst ) { if(m_pDummyInst->IsInLevel()) CPhysics::GetSimulator()->DeleteObject(m_pDummyInst->GetLevelIndex()); DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS); m_pDummyInst->SetInstFlag(phInstGta::FLAG_BEING_DELETED, true); delete m_pDummyInst; m_pDummyInst = NULL; } } // // name: DeleteRwObject // description: Specialised version that tidies up upgrade objects void CVehicle::DeleteDrawable() { GetVehicleDamage()->GetDeformation()->Shutdown(); m_VehicleLightSwitch.Shutdown(); CPhysical::DeleteDrawable(); } // // name: ProcessPrePhysics // description: poses a vehicles skeleton and drives muscles of joints void CVehicle::ProcessPrePhysics() { #if ENABLE_FRAG_OPTIMIZATION //////////////////////////////////////////////////////////////////////////// // Consider calling CVehiclePopulation::MakeVehicleIntoDummyBasedOnDistance() after all vehicle creations, and removing this if(!IsSuperDummy() && !GetHasFragCacheEntry()) { GiveFragCacheEntry(); } //////////////////////////////////////////////////////////////////////////// TryToReleaseFragCacheEntry(); #endif // ENABLE_FRAG_OPTIMIZATION PrefetchObject(&m_pVehicleGadgets); PrefetchObject(&m_nVehicleFlags); CPhysical::ProcessPrePhysics(); ProcessJetWash(); m_hitByWeaponBlade = false; for( int i = 0; i < m_pVehicleGadgets.size(); i++) { m_pVehicleGadgets[i]->ProcessPrePhysics(this); } if(m_nVehicleFlags.bDriveMusclesToAnim && GetAnimDirector()) { fragInst* pFragInst = GetFragInst(); if(pFragInst) { fragHierarchyInst* pHierInst = NULL; if(pFragInst && pFragInst->GetCached()) pHierInst = pFragInst->GetCacheEntry()->GetHierInst(); phArticulatedCollider* pArticulatedCollider = NULL; if(pHierInst) pArticulatedCollider = pHierInst->articulatedCollider; //if (pArticulatedCollider) if (GetCollider() && GetCollider()->IsArticulated()) { pFragInst->SetMusclesFromSkeleton(*GetSkeleton(), true); } } } ResetSkeletonForMods(); m_nVehicleFlags.bDrivingOnVehicle = false; m_nVehicleFlags.bCarHitBySuperHeavyVehicle = false; } void CVehicle::ResetSkeletonForMods() { // B*1868591: Make sure that vehicles with mod kits perform skeleton updates here to reset any changes to the bone object matrices in PostPreRender during the previous frame. // This needs to be done here so that processing attachments in CGameWorld::ProcessAttachmentAfterAllMovement doesn't get zeroed out matrices because it runs before the "pose" dependency is run. CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX && variationInstance.GetVehicleRenderGfx()) { crSkeleton *skeleton = GetSkeleton(); if(skeleton) { for (u8 i = 0; i < VMT_RENDERABLE; ++i) { if (variationInstance.GetMods()[i] == INVALID_MOD) continue; int iCount = variationInstance.GetNumBonesToTurnOffForSlot(i); for (s32 f = 0; f < iCount; ++f) { s32 bone = variationInstance.GetBoneToTurnOffForSlot(i, f); if (bone == -1) continue; int boneIdx = GetBoneIndex((eHierarchyId)bone); if (Verifyf(boneIdx != -1, "Invalid bone index in vehicle variation instance on slot %i, bone %d (%s)", i, boneIdx, GetModelName())) { skeleton->PartialUpdate(boneIdx); } } } } } } bool CVehicle::HasRocketBoost() { if( m_Transmission.GetKERSAllowed() ) { bool variation = false; return GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_ROCKET_BOOST) || ( GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_JATO_BOOST_MOD) && (s8)GetVariationInstance().GetModIndexForType( VMT_EXHAUST, this, variation ) != -1 ); } return false; } void CVehicle::SetOverrideNitrousLevel(bool overrideNitrous) { m_scriptOverridesNitrous.isOverrideActive = overrideNitrous; } void CVehicle::SetOverrideNitrousLevel(bool overrideNitrous, float durationModOverride, float powerModOverride, float rechargeRateModOverride, bool disableDefaultSound) { m_scriptOverridesNitrous.isOverrideActive = overrideNitrous; m_scriptOverridesNitrous.durationModifier = durationModOverride; m_scriptOverridesNitrous.powerModifier = powerModOverride; m_scriptOverridesNitrous.rechargeRateModifier = rechargeRateModOverride; m_scriptOverridesNitrous.disableDefaultSound = disableDefaultSound; } bool CVehicle::HasNitrousBoost() { bool result = false; if( m_scriptOverridesNitrous.isOverrideActive ) { result = true; } if( !result && m_Transmission.GetKERSAllowed() && GetVehicleModelInfo() && GetDrawHandlerPtr() ) { bool variation = false; s8 modIndex = -1; if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_NITROUS_MOD ) ) { static s8 sfMaxNumNitrousModLevels = 3; modIndex = (s8)GetVariationInstance().GetModIndexForType( VMT_ENGINEBAY2, this, variation ); result = modIndex > -1 && modIndex < sfMaxNumNitrousModLevels; } } if( !result ) { m_nitrousActive = false; } return result; } bool CVehicle::HasSideShunt() const { bool result = false; if( GetVehicleModelInfo() && GetDrawHandlerPtr() ) { bool variation = false; s8 modIndex = -1; if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_SIDE_SHUNT ) ) { static s8 sfSideShuntModindex = 3; modIndex = (s8)GetVariationInstance().GetModIndexForType( VMT_ENGINEBAY2, this, variation ); result = modIndex == sfSideShuntModindex; } } return result; } float CVehicle::GetNitrousPowerModifier() { if( HasNitrousBoost() ) { static dev_float sf_minNitrousRatio = 0.3f; static dev_float sf_maxNitrousRatio = 0.8f; static dev_float sf_maxNumberOfModLevels = 3.0f; static dev_float sf_maxNumberOfModLevelsInv = 1.0f / sf_maxNumberOfModLevels; if( m_scriptOverridesNitrous.isOverrideActive ) { return m_scriptOverridesNitrous.powerModifier; } bool variation = false; u8 modLevel = GetVariationInstance().GetModIndexForType( VMT_ENGINEBAY2, this, variation ); static u8 overrideModLevel = (u8)-1; if( overrideModLevel != (u8)-1 ) { modLevel = overrideModLevel; } float forceModifier = sf_minNitrousRatio + (float)modLevel * ( ( sf_maxNitrousRatio - sf_minNitrousRatio ) * sf_maxNumberOfModLevelsInv ); return forceModifier; } return 1.0f; } float CVehicle::GetNitrousDurationModifier() { if( HasNitrousBoost() ) { static dev_float sf_minNitrousRatio = 0.4f; static dev_float sf_maxNitrousRatio = 1.25f; static dev_float sf_maxNumberOfModLevels = 3.0f; static dev_float sf_maxNumberOfModLevelsInv = 1.0f / sf_maxNumberOfModLevels; if (m_scriptOverridesNitrous.isOverrideActive ) { return m_scriptOverridesNitrous.durationModifier; } bool variation = false; u8 modLevel = GetVariationInstance().GetModIndexForType( VMT_ENGINEBAY2, this, variation ); static u8 overrideModLevel = (u8)-1; if( overrideModLevel != (u8)-1 ) { modLevel = overrideModLevel; } float forceModifier = sf_minNitrousRatio + (float)modLevel * ( ( sf_maxNitrousRatio - sf_minNitrousRatio ) * sf_maxNumberOfModLevelsInv ); return forceModifier; } return 1.0f; } float CVehicle::GetNitrousRechargeModifier() { if( m_scriptOverridesNitrous.isOverrideActive ) { if (HasNitrousBoost() || HasSideShunt() ) { return m_scriptOverridesNitrous.rechargeRateModifier; } } return 1.0f; } static dev_bool sbTestJump = false; bool CVehicle::HasJump() { if( sbTestJump ) { return true; } if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_JUMP_MOD ) ) { bool variation = false; return (s8)GetVariationInstance().GetModIndexForType( VMT_ENGINEBAY3, this, variation ) != -1; } return GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_JUMPING_CAR ); } float CVehicle::GetJumpForceModifier() { if( sbTestJump ) { return 1.0f; } if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_JUMP_MOD ) ) { static dev_float sf_minJumpRatio = 0.5f; static dev_float sf_maxJumpRatio = 1.5f; static dev_float sf_maxNumberOfModLevels = 3.0f; static dev_float sf_maxNumberOfModLevelsInv = 1.0f / sf_maxNumberOfModLevels; bool variation = false; u8 modLevel = GetVariationInstance().GetModIndexForType( VMT_ENGINEBAY3, this, variation ); float forceModifier = sf_minJumpRatio + (float)modLevel * ( ( sf_maxJumpRatio - sf_minJumpRatio ) * sf_maxNumberOfModLevelsInv ); return Min( forceModifier, sf_maxJumpRatio ); } return 1.0f; } bool CVehicle::sm_bForceRecordedVehicleToBeInactive = false; bool CVehicle::sm_bForceRecordedVehicleToBeActive = false; void CVehicle::ProcessPostPhysics() { #if __BANK if(CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode) #endif { if(!GetIsStatic()) { if(IsSuperDummy()) { TryToDeactivateSuperDummy(); } else { //whist we've got a collider cache our speed for use by superdummy SetSuperDummyVelocity(GetVelocity()); } } //only allow to become parked super if we're flagged as parked and stationary, so we won't convert if in motion m_nVehicleFlags.bMayBecomeParkedSuperDummy = m_nVehicleFlags.bIsThisAParkedCar && m_nVehicleFlags.bIsThisAStationaryCar && ConsiderParkedForLodPurposes(); } if(IsRunningCarRecording()) { s32 slot = CVehicleRecordingMgr::GetPlaybackSlotFast(this); if(slot != -1 && m_nVehicleFlags.bSwitchToAiRecordingThisFrame) { CVehicleRecordingMgr::ChangeCarPlaybackToUseAI(this, CVehicleRecordingMgr::GetDelayUntilRevertingBack(slot)); m_nVehicleFlags.bSwitchToAiRecordingThisFrame = false; } if(CanBeInactiveDuringRecording() && CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex())) { int iCurTime = fwTimer::GetTimeInMilliseconds(); int iTimeSinceActivation = iCurTime - m_iLastActivationTime; static int iActivationDelay = 350; if(iTimeSinceActivation > iActivationDelay || iTimeSinceActivation < 0 || sm_bForceRecordedVehicleToBeInactive || m_nVehicleFlags.bForceInactiveDuringPlayback) { const CCollisionHistory* pCollisionHistory = GetFrameCollisionHistory(); if((pCollisionHistory && pCollisionHistory->GetCollisionImpulseMagSum() <= 0.0f) || sm_bForceRecordedVehicleToBeInactive || m_nVehicleFlags.bForceInactiveDuringPlayback) { m_vInactivePlaybackVelocity = CPhysical::GetVelocity(); m_vInactivePlaybackAngVelocity = CPhysical::GetAngVelocity(); m_nVehicleFlags.bIsDeactivatedByPlayback = true; DeActivatePhysics(); if(GetCollider() != NULL) { m_nVehicleFlags.bIsDeactivatedByPlayback = false; } } } } } if( pHandling->GetCarHandlingData() && pHandling->GetCarHandlingData()->aFlags & CF_USE_DOWNFORCE_BIAS && !IsNetworkClone()) { const CCollisionHistory* pCollisionHistory = GetFrameCollisionHistory(); if( pCollisionHistory ) { if( const CCollisionRecord* collisionRecord = pCollisionHistory->GetMostSignificantCollisionRecord() ) { static dev_float sfCollisionMagToBreakFrontWing = 7500.0f; static dev_float sfCollisionMagToBreakRearWing = 7000.0f; static dev_float sfDefaultMassInv = 1.0f / 505.0f; float massScale = GetMass() * sfDefaultMassInv; massScale *= massScale; float collisionMagToBreakFrontWing = sfCollisionMagToBreakFrontWing * massScale; float collisionMagToBreakRearWing = sfCollisionMagToBreakRearWing * massScale; if( collisionRecord->m_fCollisionImpulseMag > collisionMagToBreakFrontWing && collisionRecord->m_MyCollisionPosLocal.y > 1.4f && collisionRecord->m_MyCollisionNormal.y < -0.7f ) { int frontWingBoneIndex = GetBoneIndex( VEH_EXTRA_6 ); if( frontWingBoneIndex > -1 ) { int frontWingGroupIndex = GetVehicleFragInst()->GetGroupFromBoneIndex( frontWingBoneIndex ); if( frontWingGroupIndex > -1 && !GetVehicleFragInst()->GetGroupBroken( frontWingGroupIndex ) ) { GetVehicleFragInst()->BreakOffAboveGroup( frontWingGroupIndex ); } } } else if( collisionRecord->m_fCollisionImpulseMag > collisionMagToBreakRearWing && collisionRecord->m_MyCollisionPosLocal.y < -1.6f && collisionRecord->m_MyCollisionNormal.y > 0.6f ) { int rearWingBoneIndex = GetBoneIndex( VEH_EXTRA_1 ); if( rearWingBoneIndex > -1 ) { int rearWingGroupIndex = GetVehicleFragInst()->GetGroupFromBoneIndex( rearWingBoneIndex ); if( rearWingGroupIndex > -1 && !GetVehicleFragInst()->GetGroupBroken( rearWingGroupIndex ) ) { GetVehicleFragInst()->BreakOffAboveGroup( rearWingGroupIndex ); } } } } } } m_nVehicleFlags.bActAsIfHighSpeedForFragSmashing = false; m_nVehicleFlags.bDisableVehicleMapCollision = false; m_nVehicleFlags.bUseScriptedCeilingHeight = false; m_fFrictionOverride = -1.0f;//reset friction override. if(!IsDummy()) { // Apply any foliage drag forces accumulated this physics step. ApplyFoliageDrag(); //Applying wind disturbances if vehicle is not a dummy type. ApplyWindDisturbance(); } CPhysical::ProcessPostPhysics(); // Reset any cheat power increase #ifdef __BANK m_fDebugCheatPowerIncrease = m_fCheatPowerIncrease; #endif SetCheatPowerIncrease(1.0f); UpdateProducingSlipStreamTimer(fwTimer::GetTimeStep()); // This code has been refactored slightly due to what looks like // to be a compiler bug if( m_easyToLand && GetTransform().GetUp().GetZf() > 0.0f ) { m_nVehicleFlags.bAllowKnockOffVehicleForLargeVertVel = false; } else { m_nVehicleFlags.bAllowKnockOffVehicleForLargeVertVel = true; } } bool CVehicle::TestNoCollision(const phInst *pOtherInst) { if (NetworkInterface::IsGameInProgress()) { u32 frameCount = fwTimer::GetFrameCount(); if (frameCount != m_uRespottingCollisionCarPedFrameCount) { m_bRespottingCollisionCarPed = false; m_uRespottingCollisionCarPedFrameCount = frameCount; } u32 noCollisionFlags = GetNoCollisionFlags(); if(noCollisionFlags &NO_COLLISION_NETWORK_OBJECTS) { if (IsBeingRespotted()) { const CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInst); if(pOtherEntity && pOtherEntity->IsCollisionEnabled() && pOtherEntity->GetIsPhysical() && pOtherEntity!=this) { //network vehicles if respot counter is active will only not collide with other vehicles and peds - they will still collide with the world if (pOtherEntity->GetIsTypeVehicle() || pOtherEntity->GetIsTypePed()) { SetNoCollisionFlags((u8) (noCollisionFlags | NO_COLLISION_HIT_SHOULD_FIND_IMPACTS)); m_bRespottingCollisionCarPed = true; return true; } else { return false; //still should collide with everything else } } } } } return CPhysical::TestNoCollision(pOtherInst); } void CVehicle::LosingFragCacheEntry() { CPhysical::LosingFragCacheEntry(); // Losing our cache entry essentially fixes all damage done to the vehicle, we need to update game side flags for(int doorIndex = 0; doorIndex < GetNumDoors(); ++doorIndex) { GetDoor(doorIndex)->ClearFlag(CCarDoor::IS_BROKEN_OFF); } } void CVehicle::OnActivate(phInst* pInst, phInst* pOtherInst) { CPhysical::OnActivate(pInst,pOtherInst); ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry();) if(IsSuperDummy()) { // Give the collider the inactive superdummy velocity, rather than zero. SetVelocity(m_vSuperDummyVelocity); //if this activates during the AI update, re-cache velocity if (IsFiniteAll(m_CachedAiData.m_VelocityAndXYSpeed)) { CacheAiData(); } m_iLastActivationTime = fwTimer::GetTimeInMilliseconds(); if(CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,false); } ChangeDummyConstraints(VDM_SUPERDUMMY,false); m_nVehicleFlags.bCheckForMisalignmentOnDeactivation = !IsParkedSuperDummy(); #if __ASSERT if(CVehicleAILodManager::ms_bFreezeParkedSuperDummyWhenCollisionsNotLoaded && ConsiderParkedForLodPurposes()) { Assertf(CPhysical::IsCollisionLoadedAroundPosition(), "Parked super dummy should be stay as fixed vehicle when ground collision is not loaded. vehicle 0x%p, fixedUntilCollisionFlag %x, bShouldFixIfNoCollision %x, AllowFreezeIfNoCollision %x, ShouldFixIfNoCollisionLoadedAroundPosition %x", this, GetIsFixedUntilCollisionFlagSet(), m_nVehicleFlags.bShouldFixIfNoCollision, m_nPhysicalFlags.bAllowFreezeIfNoCollision, ShouldFixIfNoCollisionLoadedAroundPosition()); } #endif } else { // Shouldn't be strictly necessary, but may as well: m_nVehicleFlags.bCheckForMisalignmentOnDeactivation = false; } if(m_nVehicleFlags.bIsDeactivatedByPlayback) { m_iLastActivationTime = fwTimer::GetTimeInMilliseconds(); if(GetCurrentPhysicsInst()) { PHLEVEL->SetInactiveCollidesAgainstInactive(GetCurrentPhysicsInst()->GetLevelIndex(), false); GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,false); RestoreVelocityFromInactivePlayback(); } } if(m_nVehicleFlags.bIsCargoVehicle) { if(pInst == GetVehicleFragInst()) { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetVehicleFragInst()->GetLevelIndex(),false); GetVehicleFragInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,false); } } for(int wheelIndex = 0; wheelIndex < GetNumWheels(); ++wheelIndex) { CWheel& wheel = *GetWheel(wheelIndex); wheel.GetDynamicFlags().ClearFlag(WF_SLEEPING_ON_DEBRIS); } m_nVehicleFlags.bIsDeactivatedByPlayback = false; Assertf(!InheritsFromTrain() || static_cast(this)->AllowedToActivate(),"Activating a train. GetIsFixedFlagSet[%s]",GetIsFixedFlagSet() ? "T" : "F"); } void CVehicle::OnDeactivate(phInst* pInst) { if( m_destroyWeaponMgr ) { DestroyVehicleWeaponMgr(); } CPhysical::OnDeactivate(pInst); // Sync the skeleton one more time after deactivating the physics, in case if the skeleton never synced when it's active (ex. prerender never called when it's not visible) fragInst* pFragInst = GetFragInst(); CPed* pLocalPlayer = CPedFactory::GetFactory()->GetLocalPlayer(); if (pFragInst && IsColliderArticulated() && !m_nVehicleFlags.bAnimateJoints && !InheritsFromTrain() && !InheritsFromDraftVeh()) { // if the local player is in this vehicle then we know it must be visible and we don't need to re-sync the skeleton if( !pLocalPlayer || pLocalPlayer->GetMyVehicle() != this ) { pFragInst->SyncSkeletonToArticulatedBody(); } } if(IsSuperDummy()) { if(CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,true); } ChangeDummyConstraints(VDM_SUPERDUMMY,true); } if(m_pIntelligence && CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this) && m_nVehicleFlags.bIsDeactivatedByPlayback) { HandleDeactivateForPlayback(); } if(m_nVehicleFlags.bWheelsDisabled) { EnableWheelCollisions(); m_nVehicleFlags.bWheelsDisabledOnDeactivation = true; } // Update the wheels compression based on whether bWheelsDisabledOnDeactivation is set. for(int wheelIndex = 0; wheelIndex < GetNumWheels(); ++wheelIndex) { CWheel& wheel = *GetWheel(wheelIndex); if(wheel.GetPrevHitPhysical() || wheel.GetHitPhysical()) { wheel.GetDynamicFlags().SetFlag(WF_SLEEPING_ON_DEBRIS); } wheel.UpdateCompressionOnDeactivation(); } if(m_nVehicleFlags.bIsCargoVehicle) { if(pInst == GetVehicleFragInst()) { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetVehicleFragInst()->GetLevelIndex(),true); GetVehicleFragInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,true); } } if(m_nVehicleFlags.bCheckForMisalignmentOnDeactivation && IsSuperDummy() && GetVehicleType() == VEHICLE_TYPE_CAR) { const CVehicleFollowRouteHelper* pFollowRouteHelper = GetIntelligence()->GetFollowRouteHelper(); if(pFollowRouteHelper && pFollowRouteHelper->GetNumPoints() >= 2) { const int iNumWheelsVehicle = GetNumWheels(); const int iNumWheels = Min(iNumWheelsVehicle, 4); if(iNumWheels == 2 || iNumWheels >= 4) { static dev_float removeNotOnRoadSpeedThresholdSquared = 1.0f; static dev_float removeNotOnRoadDistanceThreshold = 0.4f; const Vec3V velV = VECTOR3_TO_VEC3V(GetVelocity()); if(IsLessThanAll(MagSquared(velV), ScalarV(removeNotOnRoadSpeedThresholdSquared))) { Vec3V tgtPosition; //QuatV tgtOrientation; if(pFollowRouteHelper->ComputeOnRoadPosition(*this, tgtPosition /*, tgtOrientation*/)) { const Mat34V& matCurrent = GetMatrixRef(); const Vec3V roadToCurrentDeltaV = Subtract(matCurrent.GetCol3(), tgtPosition); // We really only care about vertical displacement here, so project the delta on the local Z axis. const ScalarV roadToCurrentProjectedV = Dot(roadToCurrentDeltaV, matCurrent.GetCol2()); if(IsGreaterThanAll(Abs(roadToCurrentProjectedV), ScalarV(removeNotOnRoadDistanceThreshold))) { m_nVehicleFlags.bTryToRemoveAggressively = true; } } } } } } m_nVehicleFlags.bCheckForMisalignmentOnDeactivation = false; } void CVehicle::HandleDeactivateForPlayback() { Assert(m_nVehicleFlags.bIsDeactivatedByPlayback); Assert(GetCollider() == NULL); if(GetCurrentPhysicsInst()) { PHLEVEL->SetInactiveCollidesAgainstInactive(GetCurrentPhysicsInst()->GetLevelIndex(), true); GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,true); if(GetCurrentPhysicsInst()->HasLastMatrix()) { PHLEVEL->ReserveInstLastMatrix(GetCurrentPhysicsInst()); } } } void CVehicle::DeActivatePhysics() { #if __BANK // B*2011960: There was a case where a helicopter in a mission was suddenly freezing in mid-air for seemingly no reason. // Initial investigation revealed it was being deactivated by something, but the cause could not be determined. // It seems to have stopped happening, but this is here to catch it if it ever happens again. if(!GetIsAttached() && InheritsFromHeli() && PopTypeIsMission() && GetOwnedBy() == ENTITY_OWNEDBY_SCRIPT && !GetIsAnyFixedFlagSet() && GetVelocity().Mag2() >= 1.0f) { // Vehicles sometimes get legitimately deactivated when they're running recordings so don't assert; just print a message for debug purposes. if(m_nVehicleFlags.bIsDeactivatedByPlayback) { sysStack::PrintStackTrace(); Displayf("[B*2011960] Helicopter is being deactivated while it is moving (And probably in the air) by recording playback."); } else Assertf(false, "[B*2011960] Helicopter is being deactivated while it is moving (And probably in the air)."); } #endif CPhysical::DeActivatePhysics(); CacheAiData(); // Velocity changed because we lost our collider. Re-cache it in case this happened during AI update } bool CVehicle::TryToDeactivateSuperDummy() { int iCurTime = fwTimer::GetTimeInMilliseconds(); int iTimeSinceActivation = iCurTime - m_iLastActivationTime; static int iActivationDelay = 350; if(iTimeSinceActivation > iActivationDelay || iTimeSinceActivation < 0) { DeActivatePhysics(); return true; } return false; } CDynamicEntity* CVehicle::GetProcessControlOrderParent() const { // The driver (if present) needs to be updated before this vehicle. // Note: if changing this, make sure that the animations are scheduled in the right // order, probably by making sure GetStartAnimSceneUpdateFlag() returns consistent values. return GetDriver(); } #if GPU_DAMAGE_WRITE_ENABLED void CVehicle::SetDamageUpdatedByGPU(bool bDamageWasUpdatedByGPU, bool bEnableDamage) { m_bEnableDamage = bEnableDamage; if(CVehicleDeformation::ms_bUpdateDamageFromPhysicsThread) { if (bDamageWasUpdatedByGPU) { #if __ASSERT if(m_bDamageWasUpdatedByGPU) { Assertf(CPhysics::ms_pDamagedVehicles.Find(this) >= 0, "[VEHICLE_DEFORMATION]A vehicle with damage texture updated by GPU get lost from the collection, 0x%p, %s", this, GetVehicleModelInfo()->GetModelName()); } #endif CPhysics::QueueDamagedVehicleByGPU(this, m_bDamageWasUpdatedByGPU); } else { Assertf(CPhysics::ms_pDamagedVehicles.Find(this) == -1, "[VEHICLE_DEFORMATION]The vehicle needs to be removed from GPU deforamtion updated collection before turning off m_bDamageWasUpdatedByGPU flag, 0x%p, %s", this, GetVehicleModelInfo()->GetModelName()); m_bDamageWasUpdatedByGPU = bDamageWasUpdatedByGPU; } } Assertf(CPhysics::ms_pDamagedVehicles.GetCount() <= MAX_DAMAGED_VEHICLES, "[VEHICLE_DEFORMATION]The collection of vehicles with damage texture updated is leaking, number of vehicles been updated %d, total vehicles can have deformation %d", CPhysics::ms_pDamagedVehicles.GetCount(), MAX_DAMAGED_VEHICLES); } void CVehicle::HandleDamageUpdatedByGPU() { m_bDamageWasUpdatedByGPU = false; DLCPushTimebar("CVehicle::HandleDamageUpdatedByGPU"); CVehicleDamage* pVehDamage = GetVehicleDamage(); Assert(pVehDamage); CVehicleDeformation* pVehDeformation = pVehDamage ? pVehDamage->GetDeformation() : NULL; Assert(pVehDeformation); void* basePtr = /*m_bEnableDamage && */pVehDeformation && pVehDeformation->HasDamageTexture() ? pVehDeformation->LockDamageTexture(grcsRead) : NULL; pVehDeformation->HandleDamageAdded(basePtr, m_bEnableDamage, true, true, m_bEnableDamage, true, m_bEnableDamage); if (basePtr != NULL) { #if __BANK && 0 //Validation of the texture values being correct if (bEnableDamage) { bool bSomeDamageExists = false; //Make sure some damage is NON-zero for (int y=0; y < GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT; y++ ) { for (int x=0; x < GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH; x++ ) { Vec3V_Out vDamage = CVehicleDeformation::ReadFromPixel(basePtr, x, y); Vector3 damage = VEC3V_TO_VECTOR3(vDamage); // -1 to 1 if (damage.IsNonZero()) { bSomeDamageExists = true; } } } Assert(bSomeDamageExists); } else { //Make sure the damage is zero for (int y=0; y < GTA_VEHICLE_DAMAGE_TEXTURE_HEIGHT; y++ ) { for (int x=0; x < GTA_VEHICLE_DAMAGE_TEXTURE_WIDTH; x++ ) { Vec3V_Out vDamage = CVehicleDeformation::ReadFromPixel(basePtr, x, y); Vector3 damage = VEC3V_TO_VECTOR3(vDamage); // -1 to 1 Assert(damage.IsZero()); } } } #endif pVehDeformation->UnLockDamageTexture(); } PostBoundDeformationUpdate(); #if __BANK if (pVehDamage && CVehicleDeformation::ms_bAutoSaveDamageTexture) { pVehDamage->SaveDamageTexture(); } #endif DLCPopTimebar(); } void CVehicle::PostBoundDeformationUpdate() { if(GetVehicleDamage() && GetVehicleDamage()->IsBlowUpCarPartsPending() && CApplyDamage::GetNumDamagePending(this) == 0) { GetVehicleDamage()->BlowUpCarParts(NULL); } } bool CVehicle::HasBoundUpdatePending() const { return GetVehicleDamage() && GetVehicleDamage()->IsBlowUpCarPartsPending(); } #endif // GPU_DAMAGE_WRITE_ENABLED // // name: Render // description: Renders a vehicle static bool sb_disableWeaponBlades = false; ePrerenderStatus CVehicle::PreRender(const bool bIsVisibleInMainViewport) { CVehicleModelInfo *pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); const u32 modelNameHash = pVehModelInfo->GetModelNameHash(); #if GPU_DAMAGE_WRITE_ENABLED BANK_ONLY(if (CVehicleDamage::ms_iForcedDamageEveryFrame) { if (CVehicleDeformation::ms_bAutoFix) { //Fix(); GetVehicleDamage()->GetDeformation()->ResetDamage(); } for (int i=0; i < CVehicleDamage::ms_iForcedDamageEveryFrame; ++i) { Vector3 damageMag = Vector3(fwRandom::GetRandomNumberInRange(-1.0f, 1.0f), fwRandom::GetRandomNumberInRange(-1.0f, 1.0f), fwRandom::GetRandomNumberInRange(-0.8f, 0.8f)); Vector3 damageOffset = Vector3(fwRandom::GetRandomNumberInRange(-3.0f, 3.0f), fwRandom::GetRandomNumberInRange(-3.0f, 3.0f), fwRandom::GetRandomNumberInRange(-0.7f, 1.0f)); float radius = fwRandom::GetRandomNumberInRange(2000.0f, 200000.0f); CVehicleDamage::DamageVehicle(NULL, damageMag, damageOffset, radius, DAMAGE_TYPE_MELEE, this, true); } GetVehicleDamage()->GetDeformation()->ApplyDeformations(true); } ) #endif // GPU_DAMAGE_WRITE_ENABLED #if __BANK TUNE_GROUP_BOOL( ARENA_MODE, overrideArenaModeFlags, false ); TUNE_GROUP_BOOL( ARENA_MODE, arenaMode, false ); TUNE_GROUP_BOOL( ARENA_MODE, detonationMode, false ); TUNE_GROUP_BOOL( ARENA_MODE, disableVehicleExplosionsDamage, false ); TUNE_GROUP_BOOL( ARENA_MODE, updateExtraDrag, false ); TUNE_GROUP_BOOL( ARENA_MODE, setWheelDragToMax, false ); TUNE_GROUP_BOOL( ARENA_MODE, disableWeaponBlades, false ); TUNE_GROUP_BOOL( ARENA_MODE, disableWeaponBladeForces, false ); TUNE_GROUP_BOOL( ARENA_MODE, toggleTombstone, false ); if( overrideArenaModeFlags ) { sb_disableWeaponBlades = disableWeaponBlades; CVehicle::sm_bDisableWeaponBladeForces = disableWeaponBladeForces; } TUNE_GROUP_FLOAT( ARENA_MODE, wheelExtraDragLF, 0.0f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( ARENA_MODE, wheelExtraDragRF, 0.0f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( ARENA_MODE, wheelExtraDragLR, 0.0f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( ARENA_MODE, wheelExtraDragRR, 0.0f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( ARENA_MODE, sfMaxWheelDrag, 0.5f, 0.0f, 10.0f, 0.1f ); if( overrideArenaModeFlags ) { CPhysics::ms_bInArenaMode = arenaMode; CVehicle::sm_bDisableExplosionDamage = disableVehicleExplosionsDamage; //CVehicle::sm_bInDetonationMode = detonationMode; } if( toggleTombstone ) { static bool sbHideTombstone = true; HideTombstone( sbHideTombstone ); sbHideTombstone = !sbHideTombstone; toggleTombstone = false; } if( overrideArenaModeFlags || setWheelDragToMax ) { if( setWheelDragToMax ) { wheelExtraDragLF = sfMaxWheelDrag; wheelExtraDragRF = sfMaxWheelDrag; wheelExtraDragLR = sfMaxWheelDrag; wheelExtraDragRR = sfMaxWheelDrag; setWheelDragToMax = false; } CWheel* pWheel = GetWheelFromId( VEH_WHEEL_LF ); if( pWheel ) { pWheel->SetExtraWheelDrag( wheelExtraDragLF ); } pWheel = GetWheelFromId( VEH_WHEEL_RF ); if( pWheel ) { pWheel->SetExtraWheelDrag( wheelExtraDragRF ); } pWheel = GetWheelFromId( VEH_WHEEL_LR ); if( pWheel ) { pWheel->SetExtraWheelDrag( wheelExtraDragLR ); } pWheel = GetWheelFromId( VEH_WHEEL_RR ); if( pWheel ) { pWheel->SetExtraWheelDrag( wheelExtraDragRR ); } } else if( updateExtraDrag ) { CWheel* pWheel = GetWheelFromId( VEH_WHEEL_LF ); if( pWheel ) { wheelExtraDragLF = pWheel->GetExtraWheelDrag(); } pWheel = GetWheelFromId( VEH_WHEEL_RF ); if( pWheel ) { wheelExtraDragRF = pWheel->GetExtraWheelDrag(); } pWheel = GetWheelFromId( VEH_WHEEL_LR ); if( pWheel ) { wheelExtraDragLR = pWheel->GetExtraWheelDrag(); } pWheel = GetWheelFromId( VEH_WHEEL_RR ); if( pWheel ) { wheelExtraDragRR = pWheel->GetExtraWheelDrag(); } } #if __BANK if( GetDriver() && GetDriver()->IsLocalPlayer() ) { DrawDownforceDebug(); DrawQADebug(); } #endif #endif // __BANK DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this ); DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfPreRenderCallingEntity(), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) ); Assert(!CVehicleFactory::GetFactory()->IsVehInDestroyedCache(this)); if(bIsVisibleInMainViewport) { m_iTimeLastVisible = fwTimer::GetTimeInMilliseconds(); } const bool playerIsInside = ContainsLocalPlayer(); // Update the motionblur mask if(!PostFX::GetForceMotionBlur()) m_nFlags.bAddtoMotionBlurMask = playerIsInside; for(int iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++) { CPed *pPassenger = m_SeatManager.GetPedInSeat(iSeat); if(pPassenger) { Assert(pPassenger->GetMyVehicle() == this); Assert(pPassenger->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle )); if(bIsVisibleInMainViewport || (GetCanUseCachedVisibilityThisFrame() && GetIsVisibleInSomeViewportThisFrame())) { const CVehicleLayoutInfo *pLayoutInfo = GetLayoutInfo(); if(pLayoutInfo) { fwMvClipSetId clipSetId = CLIP_SET_ID_INVALID; int iSeatIndex = pPassenger->GetAttachCarSeatIndex(); if(iSeatIndex >= 0 && iSeatIndex < pLayoutInfo->GetNumSeats()) { const CVehicleSeatAnimInfo *pVehicleSeatAnimInfo = pLayoutInfo->GetSeatAnimationInfo(iSeatIndex); if(pVehicleSeatAnimInfo) { clipSetId = pPassenger->IsMale() ? pVehicleSeatAnimInfo->GetMaleGestureClipSetId() : pVehicleSeatAnimInfo->GetFemaleGestureClipSetId(); } } if(clipSetId != CLIP_SET_ID_INVALID) { g_pGestureManager->RequestVehicleGestureClipSet(clipSetId); } } } } } #if STENCIL_VEHICLE_INTERIOR SetUseVehicleInteriorMaterial(CVfxHelper::IsEntityInCurrentVehicle(this) BANK_ONLY(&& CVehicle::ms_StencilOutInterior)); #endif // STENCIL_VEHICLE_INTERIOR for( int i = 0; i < m_pVehicleGadgets.size(); i++) { m_pVehicleGadgets[i]->ProcessPreRender(this); } #if GTA_REPLAY if(CReplayMgr::IsRecording()) { CReplayMgr::SetVehicleVariationData(this); } #endif // Hack for killing natural ambient inside helicopters/planes if (IsInsideSpecificVehicleModeEnabled(MI_PLANE_CARGOPLANE.GetName().GetHash())) { CPlayerInfo *pPlayerInfo = CGameWorld::GetMainPlayerInfo(); if (pPlayerInfo && pPlayerInfo->GetPlayerPed()) { CPed* playerPed = pPlayerInfo->GetPlayerPed(); CVehicle* playerVehicle = playerPed->GetMyVehicle(); if (playerVehicle == this) { if (m_AmbientScaleOverriden == false) { m_AmbientScaleOverriden = true; m_prevNaturalAmbientScale = (u8)GetNaturalAmbientScale(); m_prevArtificialAmbientScale = (u8)GetArtificialAmbientScale(); } SetUseDynamicAmbientScale(false); SetNaturalAmbientScale(15); SetArtificialAmbientScale(255); } } } else if (m_AmbientScaleOverriden) { SetUseDynamicAmbientScale(true); SetNaturalAmbientScale(m_prevNaturalAmbientScale); SetArtificialAmbientScale(m_prevArtificialAmbientScale); m_AmbientScaleOverriden = false; m_prevArtificialAmbientScale = 0; m_prevArtificialAmbientScale = 0; } CDynamicEntity::PreRender(bIsVisibleInMainViewport); #if GTA_REPLAY if (CReplayMgr::IsEditModeActive()) { ProcessRamBarVFX(); return PRERENDER_NEED_SECOND_PASS; } #endif bool bAreAnyDoorsDrivenByPhysics = false; bool updateBoundsAndBodyFromSkeleton = (!IsDummy() && (m_nVehicleFlags.bAnimateJoints || (InheritsFromTrain() && static_cast(this)->AreDoorsMoving()))); if( m_forceUpdateBoundsAndBodyFromSkeleton ) { updateBoundsAndBodyFromSkeleton = true; m_forceUpdateBoundsAndBodyFromSkeleton = false; } if(updateBoundsAndBodyFromSkeleton) { if(fragInst* pInst = GetFragInst()) { // Determine if any frag bounds should not be posed from the skeleton const s8 maxFragsToDisable = 8; s8 fragsToDisable[maxFragsToDisable]; s8 numFragsToDisable = 0; for(int i = 0, count = GetNumDoors(); i < count; i ++) { const CCarDoor *pCarDoor = GetDoor(i); if(pCarDoor && pCarDoor->GetFlag(CCarDoor::DRIVEN_BY_PHYSICS)) { if(Verifyf(numFragsToDisable < maxFragsToDisable, "Too many frags to disable!")) { fragsToDisable[numFragsToDisable ++] = pCarDoor->GetFragChild(); bAreAnyDoorsDrivenByPhysics = true; } } } // If PreRender modified the skeleton, update the bounds and body once, here pInst->PoseBoundsFromSkeleton(true, true, false, numFragsToDisable, fragsToDisable); fragCacheEntry* entry = pInst->GetCacheEntry(); if(entry) { fragHierarchyInst* hierInst = entry->GetHierInst(); if(hierInst && hierInst->body != NULL) { pInst->PoseArticulatedBodyFromBounds(numFragsToDisable, fragsToDisable); } } // If the collider isn't articulated, recalculate the maximum extents of the vehicle if(!IsColliderArticulated()) { CalculateNonArticulatedMaximumExtents(); } } } // If this vehicle has a convertible roof animation, cache the bone matrices for each window so that they // still roll down as part of the animation even when the doors are articulating. Mat34V localBoneMatrix[VEH_LAST_WINDOW+1-VEH_FIRST_WINDOW]; bool bHasConvertibleRoofAnimation = ENABLE_FRAG_OPTIMIZATION_ONLY(GetHasFragCacheEntry() &&) DoesVehicleHaveAConvertibleRoofAnimation(); if(bHasConvertibleRoofAnimation) { int i = 0; for(int iWindow = VEH_FIRST_WINDOW; iWindow <= VEH_LAST_WINDOW; ++i, ++iWindow) { eHierarchyId windowId = (eHierarchyId)iWindow; s32 nBoneIndex = GetBoneIndex(windowId); if(nBoneIndex > -1) { // If this window has an associated door, check that it hasn't broken off first. CCarDoor* pDoor = GetDoorFromId(GetDoorIdFromWindow(windowId)); if(!pDoor || !pDoor->GetFlag(CCarDoor::IS_BROKEN_OFF)) { crSkeleton* pSkel = GetSkeleton(); if(Verifyf(pSkel, "Couldn't find a skeleton for this vehicle(%s)", GetModelName())) { localBoneMatrix[i] = pSkel->GetLocalMtx(nBoneIndex); } } } } } // update skeleton from the physics before the pre-render motion tree // Not sure what train is doing, but draft tends to have some wheels on articulated bones // - but they rely on suspension updating the skeleton during ProcessPreSimUpdate for the wheels that care // - Allowing this sync to occur actually overwrites the suspension position and rotation that the wheels set up earlier in pre-render fragInst* pFragInst = GetFragInst(); if (pFragInst && CPhysics::GetLevel()->IsActive(pFragInst->GetLevelIndex()) && IsColliderArticulated() && (!m_nVehicleFlags.bAnimateJoints || bAreAnyDoorsDrivenByPhysics) && !InheritsFromTrain() && !InheritsFromDraftVeh()) { pFragInst->SyncSkeletonToArticulatedBody(); ProcessLightCovers(); } else if (bHasConvertibleRoofAnimation && AreAnyBrokenFlagsSet()) { GetSkeleton()->Update(); } // if vehicle has any weapon, process recoil animation here if(m_pVehicleWeaponMgr) { m_pVehicleWeaponMgr->ProcessPreRender(this); } // This is for bikes that have their bike chains set up with the track effect so that the chain will animated as the wheels spin, but don't actually have proper tracks. // The track UVs are animated here for bikes so long as they aren't flagged as having actual tracks as that is handled in CVehicleTracks. CCustomShaderEffectVehicle* pShaderFx = static_cast(GetDrawHandler().GetShaderEffect()); const bool isCarWithTrack = (IsReverseTrike()) || (modelNameHash==MID_BLAZER4); if(pShaderFx && !(pHandling->mFlags & MF_HAS_TRACKS) && (InheritsFromBike() || InheritsFromBicycle() || isCarWithTrack) && !fwTimer::IsGamePaused()) { for(s32 i = 0; i < GetNumWheels(); i++) { CWheel *pWheel = GetWheel(i); if(pWheel->GetConfigFlags().IsFlagSet(WCF_REARWHEEL)) { const float animU = (pWheel->GetRotSpeed() / (2.0f * PI)) * fwTimer::GetTimeStep(); if( (modelNameHash == MID_SHOTARO) || (modelNameHash == MID_DEATHBIKE2) ) { pShaderFx->TrackUVAnimAdd(Vector2(animU, animU)); // B*3047341: animate both sides (left & right) of track shader } else { pShaderFx->TrackUVAnimAdd(Vector2(0, animU)); } break; } } } if(pShaderFx && !fwTimer::IsGamePaused()) { if ((modelNameHash == MID_POLICE5) || (modelNameHash == MID_POLICE6) || (modelNameHash == MID_POLBUFFALO) || (modelNameHash == MID_POLBATI) || (modelNameHash == MID_POLCARACARA) || (modelNameHash == MID_POLGAUNTLET) || (modelNameHash == MID_POLGRANGER) || (modelNameHash == MID_POLICEB2) || (modelNameHash == MID_POLICET2) || (modelNameHash == MID_POLPANTO) || (modelNameHash == MID_POLRIATA) ) { // track siren static dev_float sirenScale = 0.5f; const float animSirenU2 = sirenScale * fwTimer::GetTimeStep(); pShaderFx->Track2UVAnimAdd(Vector2(animSirenU2, animSirenU2)); } else if(m_nVehicleFlags.bEngineOn) { // general track float globalScale = 1.0f; if (modelNameHash == MID_PBUS2) { // B*4663456 - Track slowed down around 3rd of its current speed on pbus2 globalScale = 0.25f; } if (modelNameHash == MID_RCBANDITO) { // B*5471668 - Track shader slowed down on the RCBandito globalScale = 0.01f; } static dev_float scaleRevRatio = 100.0f / (2.0f * PI); const float animU2 = m_Transmission.GetRevRatio() * scaleRevRatio * globalScale * fwTimer::GetTimeStep(); if ((modelNameHash == MID_BOMBUSHKA) || (modelNameHash == MID_BRIOSO2) || (modelNameHash == MID_BRIOSO3) || (modelNameHash == MID_BTYPE2) || (modelNameHash == MID_BUFFALO4) || (modelNameHash == MID_CALICO) || (modelNameHash == MID_COMET4) || (modelNameHash == MID_COMET6) || (modelNameHash == MID_COMET7) || (modelNameHash == MID_CYPHER) || (modelNameHash == MID_DEVIANT) || (modelNameHash == MID_DOMINATOR3) || (modelNameHash == MID_DOMINATOR7) || (modelNameHash == MID_DOMINATOR8) || (modelNameHash == MID_DRAUGUR) || (modelNameHash == MID_DUKES3) || (modelNameHash == MID_DUNE4) || (modelNameHash == MID_ELEGY) || (modelNameHash == MID_ELLIE) || (modelNameHash == MID_FAGALOA) || (modelNameHash == MID_FUTO2) || (modelNameHash == MID_GAUNTLET3) || (modelNameHash == MID_GAUNTLET4) || (modelNameHash == MID_GAUNTLET5) || (modelNameHash == MID_GB200) || (modelNameHash == MID_GLENDALE2) || (modelNameHash == MID_GREENWOOD) || (modelNameHash == MID_GROWLER) || (modelNameHash == MID_HELLION) || (modelNameHash == MID_HOTRING) || (modelNameHash == MID_HUSTLER) || (modelNameHash == MID_IMPALER) || (modelNameHash == MID_IMPERATOR) || (modelNameHash == MID_IMPERATOR2) || (modelNameHash == MID_IMPERATOR3) || (modelNameHash == MID_ISSI3) || (modelNameHash == MID_ISSI4) || (modelNameHash == MID_ISSI5) || (modelNameHash == MID_JESTER4) || (modelNameHash == MID_MANANA2) || (modelNameHash == MID_MICROLIGHT) || (modelNameHash == MID_MONSTER3) || (modelNameHash == MID_MONSTER5) || (modelNameHash == MID_NEBULA) || (modelNameHash == MID_NEO) || (modelNameHash == MID_OPPRESSOR) || (modelNameHash == MID_OPPRESSOR2) || (modelNameHash == MID_PATRIOT2) || (modelNameHash == MID_PEYOTE2) || (modelNameHash == MID_PEYOTE3) || (modelNameHash == MID_PBUS2) || (modelNameHash == MID_PHANTOM2) || (modelNameHash == MID_PREVION) || (modelNameHash == MID_PRIMO3) || (modelNameHash == MID_RAPIDGT3) || (modelNameHash == MID_REMUS) || (modelNameHash == MID_RROCKET) || (modelNameHash == MID_RT3000) || (modelNameHash == MID_RUINER2) || (modelNameHash == MID_RUINER4) || (modelNameHash == MID_SAVESTRA) || (modelNameHash == MID_SCARAB) || (modelNameHash == MID_SCARAB2) || (modelNameHash == MID_SCARAB3) || (modelNameHash == MID_SENTINEL4) || (modelNameHash == MID_SLAMTRUCK) || (modelNameHash == MID_SLAMVAN4) || (modelNameHash == MID_SLAMVAN5) || (modelNameHash == MID_SLAMVAN6) || (modelNameHash == MID_SULTAN3) || (modelNameHash == MID_SULTANRS) || (modelNameHash == MID_THRUSTER) || (modelNameHash == MID_TORNADO6) || (modelNameHash == MID_TULIP) || (modelNameHash == MID_VAMOS) || (modelNameHash == MID_VECTRE) || (modelNameHash == MID_VISIONE) || (modelNameHash == MID_VIGILANTE) || (modelNameHash == MID_VISERIS) || (modelNameHash == MID_VOLATOL) || (modelNameHash == MID_VOLTIC2) || (modelNameHash == MID_WINKY) || (modelNameHash == MID_WEEVIL) || (modelNameHash == MID_WEEVIL2) || (modelNameHash == MID_YOSEMITE2) || (modelNameHash == MID_YOSEMITE3) || (modelNameHash == MID_Z190) || (modelNameHash == MID_ZION3) || (modelNameHash == MID_ZORRUSSO) ) { pShaderFx->Track2UVAnimAdd(Vector2(animU2, animU2)); // B*3055900 - Track2 shader working on one side of tornado6 & btype2 } else { pShaderFx->Track2UVAnimAdd(Vector2(0, animU2)); } } //if(m_nVehicleFlags.bEngineOn)... }//if(pShaderFx && !fwTimer::IsGamePaused())... // If this vehicle has a convertible roof animation, reinstate the cached bone matrices for each window now that we have // splatted the articulated body positions over the skeleton. if(bHasConvertibleRoofAnimation) { int i = 0; for(int iWindow = VEH_FIRST_WINDOW; iWindow <= VEH_LAST_WINDOW; ++i, ++iWindow) { eHierarchyId windowId = (eHierarchyId)iWindow; s32 nBoneIndex = GetBoneIndex(windowId); if(nBoneIndex > -1) { // If this window has an associated door, check that it hasn't broken off first. CCarDoor* pDoor = GetDoorFromId(GetDoorIdFromWindow(windowId)); if(!pDoor || !pDoor->GetFlag(CCarDoor::IS_BROKEN_OFF)) { crSkeleton* pSkel = GetSkeleton(); if(Verifyf(pSkel, "Couldn't find a skeleton for this vehicle(%s)", GetModelName())) { fragInst* pFragInst = GetFragInst(); if(pFragInst && pFragInst->GetCached()) { fragHierarchyInst* hierInst = pFragInst->GetCacheEntry()->GetHierInst(); if(hierInst && hierInst->body) { int nComponentIndex = pFragInst->GetComponentFromBoneIndex(nBoneIndex); if(Verifyf(nComponentIndex >= 0, "Window bone %i on vehicle %s doesn't have a bound.",nBoneIndex,GetModelName())) { int nParentBoneIndex = pSkel->GetSkeletonData().GetParentIndex(nBoneIndex); Mat34V objectBoneMatrix; Transform(objectBoneMatrix, pSkel->GetObjectMtx(nParentBoneIndex), localBoneMatrix[i]); // Make sure we aren't zeroing an active bound's matrix if(!IsEqualAll(objectBoneMatrix.GetCol0(),Vec3V(V_ZERO))) { pFragInst->GetCacheEntry()->GetBound()->SetCurrentMatrix(nComponentIndex, objectBoneMatrix); } pSkel->GetLocalMtx(nBoneIndex) = localBoneMatrix[i]; pSkel->GetObjectMtx(nBoneIndex) = objectBoneMatrix; } } } } } } } } // Make sure we didn't miss a call to WaitForPreRenderUpdateToComplete() somehow, last frame. Assert(!m_StartedAnimDirectorPreRenderUpdate); // schedule the pre-render motion tree, no one should use the skeleton until PreRender2 // Note: GetIsVisibleInSomeViewportThisFrame() may in some cases use the value from the previous // frame, since we do PreRender() calls early on some vehicles within a sphere. fwAnimDirector* pAnimDirector = GetAnimDirector(); if (pAnimDirector && (!GetCanUseCachedVisibilityThisFrame() || GetIsVisibleInSomeViewportThisFrame() || (GetVehicleType()==VEHICLE_TYPE_HELI) || InheritsFromSubmarineCar())) // BS#318707: always do pre-render for helis, so their rotors move in shadowmaps, etc. { pAnimDirector->StartUpdatePreRender(fwTimer::GetTimeStep()); m_StartedAnimDirectorPreRenderUpdate = true; } if( UsesSiren() && m_nVehicleFlags.GetIsSirenOn() ) { if(!sirenVehicleList.IsFull() ) { sirenPriorityItem &item = sirenVehicleList.Append(); item.vehicle = this; item.priority = 0; item.vehPos = GetTransform().GetPosition(); } } // we try to deal with HD assets in one frame. if they happen to be in memory but a camera cut caused this instance not to use them // we get an unnecessary one frame delay because of the state machine in Update_HD_Models. // we do a quick process here to set up the vehicle instance in case the hd assets are already in memory. bool streamHighModel; bool drawHighModel; GetHDModeRequests(drawHighModel, streamHighModel); switch (m_vehicleLodState) { case VLS_HD_REQUESTED: { CTexLod::AddHDTxdRequest(pVehModelInfo->GetAssetLocation(), GetModelIndex()); if (pVehModelInfo->GetAreHDFilesLoaded() && drawHighModel) { // HD has loaded, so let's set up this instance... GetVehicleDrawHandler().ShaderEffect_HD_CreateInstance(pVehModelInfo, this); UpdateBodyColourRemapping(false); m_vehicleLodState = VLS_HD_AVAILABLE; } } break; default : break; } ProcessBoostVFX(); ProcessRamBarVFX(); ProcessExplosionEffects(); bool isRuiner2 = ( MI_CAR_RUINER2.IsValid() && GetModelIndex() == MI_CAR_RUINER2 ); bool isTampa3 = ( MI_CAR_TAMPA3.IsValid() && GetModelIndex() == MI_CAR_TAMPA3 ); bool isNightshark = ( MI_CAR_NIGHTSHARK.IsValid() && GetModelIndex() == MI_CAR_NIGHTSHARK ); bool isOppressor = ( MI_BIKE_OPPRESSOR.IsValid() && GetModelIndex() == MI_BIKE_OPPRESSOR ) || (MI_BIKE_OPPRESSOR2.IsValid() && GetModelIndex() == MI_BIKE_OPPRESSOR2); bool isHunter = ( MI_HELI_HUNTER.IsValid() && GetModelIndex() == MI_HELI_HUNTER ); bool isVigilante = ( MI_CAR_VIGILANTE.IsValid() && GetModelIndex() == MI_CAR_VIGILANTE ); bool isAkula = ( MI_HELI_AKULA.IsValid() && GetModelIndex() == MI_HELI_AKULA ); bool isStromberg = ( MI_CAR_STROMBERG.IsValid() && GetModelIndex() == MI_CAR_STROMBERG ); bool isDeluxo = ( MI_CAR_DELUXO.IsValid() && GetModelIndex() == MI_CAR_DELUXO ); bool isStrikeforce = ( MI_PLANE_STRIKEFORCE.IsValid() && GetModelIndex() == MI_PLANE_STRIKEFORCE); bool isToreador = ( MI_CAR_TOREADOR.IsValid() && GetModelIndex() == MI_CAR_TOREADOR); bool isAnnihilator2 = (MI_HELI_ANNIHILATOR2.IsValid() && GetModelIndex() == MI_HELI_ANNIHILATOR2); // Weapon rack animations if( isRuiner2 || isTampa3 || isNightshark || isOppressor || isHunter || isVigilante || isAkula || isStromberg || isDeluxo || isStrikeforce || isToreador || isAnnihilator2) { // WeaponBones[] = { Sliding Grill Bone, Sliding Weapon Rack Bone, Up To Eight Individual Weapon Bones }; static const int RuinerWeaponBones[] = { VEH_MISC_L, VEH_MISC_C, VEH_MISC_D, VEH_MISC_E, VEH_MISC_F, VEH_MISC_G, VEH_MISC_H, VEH_MISC_I, VEH_MISC_J, VEH_MISC_K }; static const int Tampa3WeaponBones[] = { -1, -1, VEH_MISC_L, VEH_MISC_S, VEH_MISC_P, VEH_MISC_Q, -1, -1, -1 }; static const int NightsharkWeaponBones[] = { VEH_MISC_L, VEH_MISC_C, -1, -1, -1, -1, -1, -1, -1 }; static const int OppressorWeaponBones[] = { -1, -1, VEH_MISC_I, VEH_MISC_J, VEH_MISC_K, VEH_MISC_L, -1, -1, -1 }; static const int HunterMissileWeaponBones[] = { -1, -1, VEH_MOD_A, VEH_MOD_B, VEH_MOD_C, VEH_MOD_D, VEH_MOD_E, VEH_MOD_F, VEH_MOD_G, VEH_MOD_H }; static const int HunterBarrageWeaponBones[] = { -1, -1, VEH_MOD_I, VEH_MOD_J, VEH_MOD_K, VEH_MOD_L, VEH_MOD_M, VEH_MOD_N, VEH_MOD_O, VEH_MOD_P, VEH_MOD_Q, VEH_MOD_R, VEH_MOD_S, VEH_MOD_T, VEH_MOD_U, VEH_MOD_V }; static const int VigilanteWeaponBones[] = { -1, -1, VEH_MISC_A, VEH_MISC_B, VEH_MISC_C, VEH_MISC_D, VEH_MISC_E, VEH_MISC_F, VEH_MISC_G, VEH_MISC_H }; static const int StrombergWeaponBones[] = { VEH_MISC_L, VEH_MISC_K, VEH_MISC_P, VEH_MISC_S, VEH_MISC_Q, VEH_MISC_R, -1, -1, -1 }; static const int StrombergInWaterWeaponBones[] = { -1, -1, VEH_MISC_O, VEH_MISC_T, -1, -1, -1, -1, -1 }; static const int DeluxoWeaponBones[] = { VEH_MISC_L, -1, VEH_MISC_H, VEH_MISC_I, VEH_MISC_J, VEH_MISC_K, -1, -1, -1, -1 }; static const int StrikeforceMissileWeaponBones[] = { -1, -1, VEH_MOD_A, VEH_MOD_B, VEH_MOD_C, VEH_MOD_D, VEH_MOD_E, VEH_MOD_F, -1, -1 }; CVehicleWeaponBattery* pBattery = NULL; if(GetVehicleWeaponMgr()) { pBattery = GetVehicleWeaponMgr()->GetWeaponBatteryOfType(FIRE_TYPE_PROJECTILE); if (pBattery || isNightshark ) { if (isRuiner2) { UpdateWeaponBones(pBattery, &RuinerWeaponBones[0]); } else if (isTampa3) { UpdateWeaponBones(pBattery, &Tampa3WeaponBones[0]); } else if (isNightshark) { UpdateWeaponBones(pBattery, &NightsharkWeaponBones[0]); } else if (isOppressor) { UpdateWeaponBones(pBattery, &OppressorWeaponBones[0]); } else if (isHunter) { UpdateWeaponBones(pBattery, &HunterMissileWeaponBones[0], true); // Also need to update the second battery CVehicleWeapon* pSecondBattery = GetVehicleWeaponMgr()->GetVehicleWeapon(2); if(pSecondBattery && pSecondBattery->GetType() == VGT_VEHICLE_WEAPON_BATTERY) { pBattery = static_cast(pSecondBattery); UpdateWeaponBones(pBattery, &HunterBarrageWeaponBones[0], true); } } else if (isVigilante) { UpdateWeaponBones(pBattery, &VigilanteWeaponBones[0]); } else if (isStromberg) { static float sfStrombergHiddenWeaponOffset = 0.25f; UpdateWeaponBones(pBattery, &StrombergWeaponBones[0], false, true, sfStrombergHiddenWeaponOffset ); static dev_s32 strombergTorpedoWeaponIndex = 2; // Also need to update the second battery CVehicleWeapon* pSecondBattery = GetVehicleWeaponMgr()->GetVehicleWeapon( strombergTorpedoWeaponIndex ); if( pSecondBattery && pSecondBattery->GetType() == VGT_VEHICLE_WEAPON_BATTERY ) { pBattery = static_cast< CVehicleWeaponBattery* >( pSecondBattery ); UpdateWeaponBones( pBattery, &StrombergInWaterWeaponBones[0], false, true, sfStrombergHiddenWeaponOffset ); } } else if (isDeluxo) { UpdateWeaponBones(pBattery, &DeluxoWeaponBones[0]); } else if ((isAkula || isAnnihilator2) && pBattery->m_handlingIndex == 1) // Missiles, not barrage { UpdateWeaponBones(pBattery, &HunterMissileWeaponBones[0], true); } else if (isStrikeforce) { UpdateWeaponBones(pBattery, &StrikeforceMissileWeaponBones[0], true); static dev_s32 strikeforceBarrageWeaponIndex = 2; // Also need to update the second battery CVehicleWeapon* pSecondBattery = GetVehicleWeaponMgr()->GetVehicleWeapon(strikeforceBarrageWeaponIndex); if (pSecondBattery && pSecondBattery->GetType() == VGT_VEHICLE_WEAPON_BATTERY) { pBattery = static_cast(pSecondBattery); UpdateWeaponBones(pBattery, &HunterBarrageWeaponBones[0], true); } } else if (isToreador) { static float sfToreadorHiddenWeaponOffsetMG = 0.25f; TUNE_GROUP_FLOAT(VEHICLE_WEAPON_OFFSETS, fToreadorTargetGrillOpenOffset, 0.06f, 0.0f, 100.0f, 0.1f); TUNE_GROUP_FLOAT(VEHICLE_WEAPON_OFFSETS, fToreadorTargetRackOpenOffset, 0.06f, 0.0f, 100.0f, 0.1f); UpdateWeaponBones(pBattery, &StrombergWeaponBones[0], false, false, sfToreadorHiddenWeaponOffsetMG, fToreadorTargetGrillOpenOffset, fToreadorTargetRackOpenOffset); static dev_s32 toreadorTorpedoWeaponIndex = 2; // Also need to update the second battery CVehicleWeapon* pSecondBattery = GetVehicleWeaponMgr()->GetVehicleWeapon( toreadorTorpedoWeaponIndex ); if( pSecondBattery && pSecondBattery->GetType() == VGT_VEHICLE_WEAPON_BATTERY ) { pBattery = static_cast< CVehicleWeaponBattery* >( pSecondBattery ); UpdateWeaponBones( pBattery, &StrombergInWaterWeaponBones[0], false, true, sfToreadorHiddenWeaponOffsetMG ); } } } } } if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_UPDATE_WEAPON_BATTERY_BONES ) ) { UpdateWeaponBatteryBones(); } for( int i = (int)VEH_SUPERCHARGER_1; i <= (int)VEH_SUPERCHARGER_3; i++ ) { float currentOffset = 0.0f; int boneIndex = GetBoneIndex( (eHierarchyId)i ); float minAngle = ( 1.0f - m_Transmission.GetClutchRatio() ) * 0.02f; float throttle = Max( minAngle, Abs( GetThrottle() ) ); if( boneIndex > -1 ) { RotateMechanicalPart( boneIndex, throttle * PI * 0.5f, currentOffset, ROT_AXIS_LOCAL_X, 100000.0f ); } } return PRERENDER_NEED_SECOND_PASS; } atFixedArray CVehicle::sirenVehicleList; static int SirenDistSorter(const CVehicle::sirenPriorityItem *a, const CVehicle::sirenPriorityItem *b) { Vec3V aPos = a->vehPos; Vec3V bPos = b->vehPos; Vec3V camPos = VECTOR3_TO_VEC3V(camInterface::GetPos()); Vec3V camPosA = camPos - aPos; Vec3V camPosB = camPos - bPos; ScalarV distA = MagSquared(camPosA); ScalarV distB = MagSquared(camPosB); return IsGreaterThan(distA, distB).Getb() ? -1 : 1; } static int SirenPrioSorter(const CVehicle::sirenPriorityItem *a, const CVehicle::sirenPriorityItem *b) { return b->priority - a->priority; } void CVehicle::CalcSirenPriorities() { if(sirenVehicleList.IsEmpty()) return; // Sort the list based on distance to camera sirenVehicleList.QSort(0,-1,SirenDistSorter); // has a driver : // has a mission driver // is a mission vehicle // has the player as a driver const int hasDriver = 1; const int hasPlayerDriver = 5; const int hasMissionDriver = 2; const int isMissionVehicle = 2; const int isOnScreen = 5; int maxPrio = 0; int maxPrioIdx = -1; for(int i=0;iGetIsOnScreen() ) prio +=isOnScreen; if( veh->GetDriver() ) { prio += hasDriver; if( veh->GetDriver()->IsPlayer() ) prio += hasPlayerDriver; if( veh->GetDriver()->PopTypeIsMission()) prio += hasMissionDriver; } if( veh->PopTypeIsMission() ) prio += isMissionVehicle; if( prio > maxPrio ) { maxPrio = prio; maxPrioIdx = i; } sirenVehicleList[i].priority = prio; } // base on count // max Prio : keep specular, full on. // i<4 : full on, with specular fade. // i<8 : 3 full on, with specular fade, fade based on distance. // i>8 : full on, 3 no specular, 3 to 8 distance fade, 8 to 16 dead. // EARLY FADE // NO SPEC // EARLY FADE // NO LIGHTS // DAYLIGHT FADE // : HIGH sirenVehicleList.QSort(0,-1,SirenPrioSorter); int i = 0; CVehicle *veh = sirenVehicleList[i].vehicle; veh->m_nVehicleFlags.SetSirenUsesEarlyFade(false); veh->m_nVehicleFlags.SetSirenUseNoSpec(false); veh->m_nVehicleFlags.SetSirenUseNoLights(false); veh->m_nVehicleFlags.SetSirenUsesDayNightFade(false); i++; int count = sirenVehicleList.GetCount(); for(;im_nVehicleFlags.SetSirenUsesEarlyFade(true); veh->m_nVehicleFlags.SetSirenUseNoSpec(false); veh->m_nVehicleFlags.SetSirenUseNoLights(false); veh->m_nVehicleFlags.SetSirenUsesDayNightFade(false); } else { if( i < 3 ) { veh->m_nVehicleFlags.SetSirenUsesEarlyFade(true); veh->m_nVehicleFlags.SetSirenUseNoSpec(true); veh->m_nVehicleFlags.SetSirenUseNoLights(false); veh->m_nVehicleFlags.SetSirenUsesDayNightFade(true); } else if( i < 5 ) { veh->m_nVehicleFlags.SetSirenUsesEarlyFade(true); veh->m_nVehicleFlags.SetSirenUseNoSpec(true); veh->m_nVehicleFlags.SetSirenUseNoLights(false); veh->m_nVehicleFlags.SetSirenUsesDayNightFade(true); } else { veh->m_nVehicleFlags.SetSirenUsesEarlyFade(false); veh->m_nVehicleFlags.SetSirenUseNoSpec(false); veh->m_nVehicleFlags.SetSirenUseNoLights(true); veh->m_nVehicleFlags.SetSirenUsesDayNightFade(false); } } } sirenVehicleList.Reset(); } void CVehicle::PreRender2(const bool bIsVisibleInMainViewport) { #if ENABLE_FRAG_OPTIMIZATION if(!GetHasFragCacheEntry()) { return; } #endif if (m_StartedAnimDirectorPreRenderUpdate) { GetAnimDirector()->WaitForPreRenderUpdateToComplete(); m_StartedAnimDirectorPreRenderUpdate = false; } fragInst* pFragInst = GetFragInst(); if (pFragInst) { fragHierarchyInst* hierInst = pFragInst->GetCacheEntry()->GetHierInst(); pFragInst->ZeroSkeletonMatricesByGroup(pFragInst->GetSkeleton(), hierInst->damagedSkeleton); } // If the vehicle is articulated, the bone matrices will get overwritten so we need to refresh the scaling // if any doors have their windows rolled down. for(int i=VEH_WINDOW_LF; i <= VEH_WINDOW_RM; ++i) { eHierarchyId eWindowId = (eHierarchyId)i; if(IsWindowDown(eWindowId)) { RolldownWindow(eWindowId, true); } } //Need to call this after WaitForPreRenderUpdateToComplete and before PreRender2 finishes if( InheritsFromAutomobile() && !GetHasLandingGear() ) { (static_cast(this))->ProcessSuspensionTransforms(); } crSkeleton *pSkeleton = GetSkeleton(); if (!GetCanUseCachedVisibilityThisFrame() || GetIsVisibleInSomeViewportThisFrame()) ///Only update wheel scale when this is true as the skeleton isn't being updated otherwise { //Scale wheels last in case they are on an animated part that has just moved them and could blat their scaling, should all be in memory from previous call if (InheritsFromAutomobile()) { CVehicleStructure *pStructure = GetVehicleModelInfo()->GetStructure(); for(int i = 0; i < GetNumWheels(); i++) { GetWheel(i)->ProcessWheelScale(pSkeleton, pStructure, m_ppWheels); } } } CPhysical::PreRender2(bIsVisibleInMainViewport); CVehicleVariationInstance& variationInstance = GetVariationInstance(); for( int i = 0; i < m_pVehicleGadgets.size(); i++) { m_pVehicleGadgets[i]->ProcessPostPreRender(this); } if(m_pVehicleWeaponMgr) { m_pVehicleWeaponMgr->ProcessPostPreRender(this); } variationInstance.PreRender2(); #if GTA_REPLAY if( CReplayMgr::IsEditModeActive() == false ) #endif // GTA_REPLAY { // store bone matrices for mods before we hide any of them if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX && variationInstance.GetVehicleRenderGfx()) { variationInstance.GetVehicleRenderGfx()->StoreModMatrices(this); } } // this code is duplicated in CObject for bits that have broken off from cars // note that we don't do this for helis as they re-use the extras ids for other bones // Plane shouldn't override extras ids, and some planes still need to turn off extra bones for certain scenarios (ie. cargoplane needs to turn of VEH_EXTRA_7 as default) int iFirstExtra = VEH_EXTRA_1; if(GetVehicleType()==VEHICLE_TYPE_HELI)// for heli's we can turn off extra 12 and beyond { iFirstExtra = VEH_EXTRA_12; } if(m_nDisableExtras) { Matrix34 boneMat; Matrix34 mat; mat.Zero3x3(); fragInst* pFragInst = GetVehicleFragInst(); for(int i=iFirstExtra; i<=VEH_LAST_EXTRA; i++) { if(m_nDisableExtras &BIT(i - VEH_EXTRA_1 + 1)) { int boneIdx = GetBoneIndex((eHierarchyId)i); if(boneIdx != -1) { pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(boneMat)); mat.d = boneMat.d; pSkeleton->SetGlobalMtx(boneIdx, RCC_MAT34V(mat)); if(pFragInst) { const crBoneData* boneData = pFragInst->GetType()->GetSkeletonData().GetBoneData(boneIdx); Assert(boneData->GetIndex() == boneIdx); const crBoneData* next = boneData->GetChild(); while (next) { pSkeleton->SetGlobalMtx(next->GetIndex(), RCC_MAT34V(mat)); next = next->GetNext(); } } } } } } #if GTA_REPLAY if(!CReplayMgr::IsEditModeActive()) #endif //GTA_REPLAY { if ((m_dialsRequestFrame != fwTimer::GetFrameCount()) && !fwTimer::IsGamePaused()) { int boneIdx = GetBoneIndex((eHierarchyId)VEH_DIALS); if (boneIdx != -1) { Matrix34 mat; pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mat)); #if GTA_REPLAY if(CReplayMgr::IsRecording() && CReplayMgr::ShouldRegisterElement(this)) { Matrix34 zero; zero.Zero(); if(!mat.IsEqual(zero)) { ReplayVehicleExtension* pExt = ReplayVehicleExtension::ProcureExtension(this); if( pExt ) { Matrix34 vehicleMat = MAT34V_TO_MATRIX34(GetMatrix()); vehicleMat.Inverse(); mat.Dot(vehicleMat); pExt->SetVehicleDialBoneMtx(mat); } } } #endif //GTA_REPLAY mat.Zero3x3(); pSkeleton->SetGlobalMtx(boneIdx, RCC_MAT34V(mat)); } } } // let vehicle mods disable bones if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX) { Matrix34 boneMat; Matrix34 mat; mat.Zero3x3(); fragInst* pFragInst = GetVehicleFragInst(); for (u8 i = 0; i < VMT_RENDERABLE; ++i) { if (variationInstance.GetMods()[i] == INVALID_MOD) continue; int iCount = variationInstance.GetNumBonesToTurnOffForSlot(i); for (s32 f = 0; f < iCount; ++f) { s32 bone = variationInstance.GetBoneToTurnOffForSlot(i, f); if (bone == -1) continue; int boneIdx = GetBoneIndex((eHierarchyId)bone); if (Verifyf(boneIdx != -1, "Invalid bone index in vehicle variation instance on slot %i, bone %d (%s)", i, boneIdx, GetModelName())) { pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(boneMat)); mat.d = boneMat.d; pSkeleton->SetGlobalMtx(boneIdx, RCC_MAT34V(mat)); if(pFragInst) { const crBoneData* boneData = pFragInst->GetType()->GetSkeletonData().GetBoneData(boneIdx); Assert(boneData->GetIndex() == boneIdx); const crBoneData* next = boneData->GetChild(); while (next) { pSkeleton->SetGlobalMtx(next->GetIndex(), RCC_MAT34V(mat)); next = next->GetNext(); } } } } } } // if (sbRenderBoneNames) //{ // if (siRenderBoneNamesOneAtATimeId > -1 && siRenderBoneNamesOneAtATimeId < GetSkeletonData().GetNumBones()) // { // Matrix34 mat; // mat.Zero3x3(); // pSkeleton->SetGlobalMtx(siRenderBoneNamesOneAtATimeId, RCC_MAT34V(mat)); // } //} #if __BANK // Allow the flag to change on the fly so we can edit and change the siren tuning. CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); bool oldState = m_nVehicleFlags.bHasSiren; m_nVehicleFlags.bHasSiren = pModelInfo->HasSirenSettings(); if (m_nVehicleFlags.bHasSiren != oldState) { delete m_sirenData; m_sirenData = m_nVehicleFlags.bHasSiren ? rage_new sirenInstanceData() : NULL; } #endif // __BANK // vfx if (!IsDummy()) { // engine start vfx //static int engineStartCounter = 0; bool updateStartupEffect = false; if(!m_nVehicleFlags.bIsDrowning) { // For cars, synchronise the startup effect with the audio startup, otherwise we get smoke effects whilst the starter motor is // turning over if(m_VehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_CAR || m_VehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_PLANE) { updateStartupEffect = m_nVehicleFlags.bEngineOn && m_VehicleAudioEntity->IsStartupSoundPlaying(); if(m_VehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_CAR) { updateStartupEffect &= ((audCarAudioEntity*)m_VehicleAudioEntity)->GetVehicleEngine()->GetEngineMode() != ELECTRIC; } } else { updateStartupEffect = m_nVehicleFlags.bEngineStarting; } } if (updateStartupEffect) { g_vfxVehicle.UpdatePtFxEngineStartup(this); } // exhaust vfx if ((m_nVehicleFlags.bEngineOn || IsRunningCarRecording()) && !(pHandling->mFlags & MF_NO_EXHAUST) && !m_nVehicleFlags.bIsDrowning && !m_nVehicleFlags.bDisableParticles) { bool isAutoShutOffSystemEngaged = false; if(m_VehicleAudioEntity->GetAudioVehicleType() == AUD_VEHICLE_CAR) { isAutoShutOffSystemEngaged = ((audCarAudioEntity*)m_VehicleAudioEntity)->GetVehicleEngine()->IsAutoShutOffSystemEngaged() || ((audCarAudioEntity*)m_VehicleAudioEntity)->GetVehicleEngine()->GetEngineMode() == ELECTRIC; } if(!isAutoShutOffSystemEngaged) { if (m_nVehicleFlags.bIsNitrousBoostActive || m_scriptOverridesNitrous.isOverrideActive) { g_vfxVehicle.UpdatePtFxNitrous(this); } else { g_vfxVehicle.UpdatePtFxExhaust(this); } } } // backfire vfx if (m_nVehicleFlags.bAudioBackfired) { g_vfxVehicle.TriggerPtFxBackfire(this, 0.0f); m_nVehicleFlags.bAudioBackfired = false; } else if (m_nVehicleFlags.bAudioBackfiredBanger) { g_vfxVehicle.TriggerPtFxBackfire(this, 1.0f); m_nVehicleFlags.bAudioBackfiredBanger = false; } // misfire vfx if (m_nVehicleFlags.bEngineOn && m_Transmission.GetCurrentlyMissFiring()) { g_vfxVehicle.UpdatePtFxMisfire(this); } // rocket boost vfx if (HasRocketBoost() && IsEngineOn()) { g_vfxVehicle.UpdatePtFxRocketBoost(this); CPed* pFollowPlayer = CGameWorld::FindFollowPlayer(); const bool bFollowPlayerInThisVehicle = pFollowPlayer ? (pFollowPlayer->GetIsInVehicle() && (pFollowPlayer->GetMyVehicle() == this)) : false; atHashString rocketBoostScreenEffect = ATSTRINGHASH("VolticTurbo", 0xE43BB5E5); if(bFollowPlayerInThisVehicle REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive())) { const bool bExitingVehicle = pFollowPlayer->GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle); if(IsRocketBoosting() && !bExitingVehicle ) { if(!ANIMPOSTFXMGR.IsRunning(rocketBoostScreenEffect) && !m_bHasRocketBoostFXStarted) { ANIMPOSTFXMGR.Start(rocketBoostScreenEffect, 0, false, false, false, 0, AnimPostFXManager::kDefault); m_bHasRocketBoostFXStarted = true; } } else { if(ANIMPOSTFXMGR.IsRunning(rocketBoostScreenEffect)) { ANIMPOSTFXMGR.Stop(rocketBoostScreenEffect,AnimPostFXManager::kDefault); } else if( ANIMPOSTFXMGR.IsStartPending(rocketBoostScreenEffect) ) { ANIMPOSTFXMGR.CancelStartRequest(rocketBoostScreenEffect,AnimPostFXManager::kDefault); } m_bHasRocketBoostFXStarted = false; } } } //refill postfx if (m_bTriggerRefillBoostEffect) { bool triggerAudio = false; if(IsEngineOn() REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive())) { CPed* pFollowPlayer = CGameWorld::FindFollowPlayer(); const bool bFollowPlayerInThisVehicle = pFollowPlayer ? (pFollowPlayer->GetIsInVehicle() && (pFollowPlayer->GetMyVehicle() == this)) : false; atHashString refillBoostScreenEffect = ATSTRINGHASH("VolticGold", 0x89992DE1); if(bFollowPlayerInThisVehicle) { const bool bExitingVehicle = pFollowPlayer->GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle); if(!bExitingVehicle) { if(!ANIMPOSTFXMGR.IsRunning(refillBoostScreenEffect) && !ANIMPOSTFXMGR.IsStartPending(refillBoostScreenEffect)) { ANIMPOSTFXMGR.Start(refillBoostScreenEffect, 0, false, false, false, 0, AnimPostFXManager::kDefault); triggerAudio = true; } } } else { triggerAudio = true; } } if(triggerAudio) { m_VehicleAudioEntity->TriggerBoostInstantRecharge(); } m_bTriggerRefillBoostEffect = false; } // wheel vfx const bool playerIsInside = ContainsLocalPlayer(); for (int i=0; iProcessVFx(playerIsInside); } // slipstream vfx if (m_fTimeProducingSlipStream> 0.0f) { float slipstreamEvo = Clamp(m_fTimeProducingSlipStream/ms_fSlipstreamMaxTime, 0.0f, 1.0f); g_vfxVehicle.UpdatePtFxSlipstream(this, slipstreamEvo); } // vehicle extra vfx const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); if (pVehicleModelInfo) { for (int i=0; im_vfxExtraInfos.GetCount(); i++) { const CVfxExtraInfo* pExtraInfo = &pVehicleModelInfo->m_vfxExtraInfos[i]; if (pExtraInfo) { u32 ptFxExtras = pExtraInfo->GetPtFxExtras(); for (int j=VEH_EXTRA_1; j<=VEH_LAST_EXTRA; j++) { int extraIdx = j-VEH_EXTRA_1; int extraMask = BIT(extraIdx+1); if ((ptFxExtras & extraMask) && HasComponent((eHierarchyId)j)) { g_vfxVehicle.UpdatePtFxExtra(this, pExtraInfo, extraIdx); } } } } } } // low lod wheel vfx g_vfxVehicle.ProcessLowLodWheels(this); m_VehicleDamage.PreRender2(); if (GetIsVisibleInSomeViewportThisFrame() && AreAnyHiddenFlagsSet()) { CVehicleGlassManager::PreRender2(this); // required to force any smashed component matrices to zero after they've been animated } for( int i = 0; i < BHT_MAX; i++ ) { int boneIndex = GetBoneIndex( ms_aBobbleHeadIds[ i ] ); if( boneIndex != -1 ) { UpdateBobbleHead( boneIndex, (eBobbleHeadType)i ); } } ProcessLightCovers(); } // // // updates body colours with shaders // void CVehicle::UpdateBodyColourRemapping(bool repainted) { if(m_pDrawHandler->GetShaderEffect()) { CCustomShaderEffectVehicle *pShaderEffectVehicle = static_cast(m_pDrawHandler->GetShaderEffect()); if(pShaderEffectVehicle->UpdateVehicleColors(this) && repainted) { SetHasBeenRepainted(true); } // B*2335192 - update body colors also for CseSD: CCustomShaderEffectVehicle *pShaderEffectVehicleSD = static_cast(static_cast(m_pDrawHandler)->GetShaderEffectSD()); if(pShaderEffectVehicleSD) { pShaderEffectVehicleSD->UpdateVehicleColors(this); } } } fwDrawData* CVehicle::AllocateDrawHandler(rmcDrawable* pDrawable) { return rage_new CVehicleDrawHandler(this, pDrawable); } #if __BANK void CVehicle::UpdateVehicleHandlingInfo(CVehicleModelInfo* pVehicleModelInfo) { if(!pVehicleModelInfo->m_data) return; s32 nHandlingIndex = CHandlingDataMgr::GetHandlingIndex(pVehicleModelInfo->GetHandlingIdHash()); Displayf("Handling hash: %s index: %d",pVehicleModelInfo->GetHandlingIdHash().TryGetCStr(), nHandlingIndex); pVehicleModelInfo->SetHandlingId(nHandlingIndex); for(int i= 0; iGetNumVehicleInstances();i++) { CVehicle* veh = pVehicleModelInfo->GetVehicleInstance(i); veh->pHandling = CHandlingDataMgr::GetHandlingData(nHandlingIndex); } } #endif // __BANK // // // // void CVehicle::SetModelId(fwModelId modelId) { m_bAllowRemoteDamageOnCreation = true; CVehicleModelInfo *pModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); #if __ASSERT Assert(pModelInfo); Assert(pModelInfo->GetModelType() == MI_TYPE_VEHICLE); Assertf(pModelInfo->GetDrawable() || pModelInfo->GetFragType(), "%s:Vehicle model is not loaded", pModelInfo->GetModelName()); #endif NOTFINAL_ONLY(SetDebugName(pModelInfo->GetModelName())); CEntity::SetModelId(modelId); if (m_nDEflags.nPopType == POPTYPE_RANDOM_PARKED) GetVehicleModelInfo()->AddVehicleModelRefParked(); else GetVehicleModelInfo()->AddVehicleModelRef(); // get base handling data pointer int nHandlingIndex = pModelInfo->GetHandlingId(); pHandling = CHandlingDataMgr::GetHandlingData(nHandlingIndex); if (Verifyf(pHandling, "No handling data found for vehicle '%s'", pModelInfo->GetModelName())) { m_fDragCoeff = pHandling->m_fInitialDragCoeff; // setup the transmission m_Transmission.SetupTransmission(pHandling->m_fInitialDriveForce, pHandling->m_fInitialDriveMaxFlatVel, pHandling->m_fInitialDriveMaxVel, pHandling->m_nInitialDriveGears, this); if(GetSecondTransmission()) { GetSecondTransmission()->SetupTransmission(pHandling->m_fInitialDriveForce, pHandling->m_fInitialDriveMaxFlatVel, pHandling->m_fInitialDriveMaxVel, 1, this); } // copy handling flags across into Automobile structure so they can be modified // set other local handling values // setup flags if(pHandling->mFlags & MF_IS_VAN) m_nVehicleFlags.bIsVan = true; if(pHandling->mFlags & MF_IS_BIG) m_nVehicleFlags.bIsBig = true; if(pHandling->mFlags & MF_IS_BUS) { m_nVehicleFlags.bIsBus = true; m_nVehicleFlags.bIsBig = true; } if(pHandling->mFlags & MF_IS_LOW) m_nVehicleFlags.bLowVehicle = true; if(pHandling->mFlags & MF_NO_WHEEL_BURST) m_nVehicleFlags.bTyresDontBurst = true; if(pHandling->mFlags & MF_NO_WHEEL_BREAK) m_nVehicleFlags.bWheelsDontBreak = true; if(pHandling->mFlags & MF_INDESTRUCTIBLE) { m_nPhysicalFlags.bNotDamagedByBullets = true; m_nPhysicalFlags.bNotDamagedByFlames = true; m_nPhysicalFlags.bNotDamagedByMelee = true; m_nPhysicalFlags.bNotDamagedByCollisions = true; m_nPhysicalFlags.bIgnoresExplosions = true; m_nPhysicalFlags.bNotDamagedByAnything = true; } if(pHandling->mFlags &MF_ABS_STD) SetCheatFlag(VEH_SET_ABS); else if(pHandling->mFlags &MF_ABS_OPTION && fwRandom::GetRandomNumberInRange(0.0f, 1.0f) > 0.5f) SetCheatFlag(VEH_SET_ABS); else if(pHandling->mFlags &MF_ABS_ALT_STD) SetCheatFlag(VEH_SET_ABS_ALT); else if(pHandling->mFlags &MF_ABS_ALT_OPTION && fwRandom::GetRandomNumberInRange(0.0f, 1.0f) > 0.5f) SetCheatFlag(VEH_SET_ABS_ALT); if(IsTrike()) { m_nVehicleFlags.bUseDeformation = false; } } if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_DISABLE_DEFORMATION ) ) { m_nVehicleFlags.bCanBeVisiblyDamaged = false; } SetGravityForWheellIntegrator(-GRAVITY);// need the handling avialble to make sure this is set correctly. RefreshAirResistance(); if(IsTank() && !GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_TANK_WITH_FLAME_DAMAGE)) { m_nPhysicalFlags.bNotDamagedByFlames = true; } m_Swankness = pModelInfo->GetVehicleSwankness(); m_nVehicleFlags.bHasGlowingBrakes = (m_Swankness == SWANKNESS_5); // set initial carbody dirt level: const u32 defDirtLevelMin = pModelInfo->GetDefaultDirtLevelMin(); const u32 defDirtLevelMax = pModelInfo->GetDefaultDirtLevelMax(); this->SetBodyDirtLevel((float)fwRandom::GetRandomNumberInRange((int)defDirtLevelMin, (int)defDirtLevelMax)); // We need the dynamic component for GetCollider during InitPhys before it's normally created in CreateDrawable CreateDynamicComponentIfMissing(); InitPhys(); if(GetFragInst()) { // Disable bounds sync to skeleton in prerender update for bikes, as we update the bike seat bound to cover driver mesh on every frame GetFragInst()->SetBoundsPrerenderUpdate(false); } const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); m_SeatManager.Init(pVehicleModelInfo->GetModelSeatInfo()->GetNumSeats(), pVehicleModelInfo->GetModelSeatInfo()->GetDriverSeat()); CreateDrawable(); ENABLE_FRAG_OPTIMIZATION_ONLY(if(m_pVehicleFragInst->GetCached())) { CreateSkeleton(); Assertf(GetSkeleton(), "%s: doesn't have a skeleton", pModelInfo->GetModelName()); // Initialize the anim director CreateAnimDirector(*GetDrawable()); // Set local pose only for the inverse pose node crCreatureComponentSkeleton* skeleton = GetCreature()->FindComponent(); if(skeleton) { skeleton->SetSuppressReset(true); } } // CVehicleModelInfo::GetComponentsUsed(m_comp1, m_comp2); //Set up component reservations m_ComponentReservationMgr.Init(this, pModelInfo); GetVehicleDamage()->Init(this); m_VehicleLightSwitch.Init(this); // Initialise neon colour SetNeonColour(CNeonTuningData::ms_defaultNeonColor); m_nVehicleFlags.bIsLawEnforcementCar = IsLawEnforcementCarModelId(modelId); m_nVehicleFlags.bIsLawEnforcementVehicle = IsLawEnforcementVehicleModelId(modelId); m_nVehicleFlags.bReportCrimeIfStandingOn = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_REPORT_CRIME_IF_STANDING_ON); //Law enforcement vehicles should become stationary more quickly when they lose their driver. m_nVehicleFlags.bBecomeStationaryQuicker = m_nVehicleFlags.bIsLawEnforcementVehicle; // Sirens flag m_nVehicleFlags.bHasSiren = pModelInfo->HasSirenSettings(); if (m_sirenData) delete m_sirenData; m_sirenData = m_nVehicleFlags.bHasSiren ? rage_new sirenInstanceData() : NULL; // need to do wheel and door init AFTER skeleton is setup InitWheels(); ENABLE_FRAG_OPTIMIZATION_ONLY(if(m_pVehicleFragInst->GetCached())) { InitDoors(); InitCompositeBound(); } // Need to create the vehicle weapon manager for turreted vehicles to avoid issues like B*1279457, we destroy them when no peds are inside, // but there maybe a possibly to have too many if there are many vehicles created without having anyone driven them (see B*1082049) if(IsTank() || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_CREATE_WEAPON_MANAGER_ON_SPAWN) || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_TURRET_SEAT_ON_VEHICLE) || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_TURRET_MODS_ON_ROOF)) { CreateVehicleWeaponMgr(); } if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_LATCH_ALL_JOINTS ) ) { gtaFragType* type = GetVehicleModelInfo()->GetFragType(); if( type ) { FreezeAllJoints( type ); } } if (pModelInfo->GetHasHDFiles()){ m_vehicleLodState = VLS_HD_NONE; Update_HD_Models(); // in case hd models are already loaded, double tick to get the hd geometry from first frame Update_HD_Models(); } else { m_vehicleLodState = VLS_HD_NA; } // request associated navmesh if it exists, can only be done once we have a modelindex for this entity CPathServerGta::MaybeRequestDynamicObjectNavMesh(this); GetVehicleModelInfo()->AddVehicleInstance(this); m_parentCargenPos.Set(0.f, 0.f, 0.f); m_bAllowRemoteDamageOnCreation = false; //Add Hydraulics to lowrider vehicles. CVehicleModelInfo* pMi = GetVehicleModelInfo(); if( pMi->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_LOWRIDER_HYDRAULICS) || pMi->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_LOWRIDER_DONK_HYDRAULICS)) { if (pMi->GetNumModKits() > 0) { CVehicleVariationInstance& variation = GetVariationInstance(); // we store the kit index into the global array in the variation Assert(pMi->GetModKit(0) < CVehicleModelInfo::GetVehicleColours()->m_Kits.GetCount()); variation.SetKitIndex(pMi->GetModKit(0)); u8 uHydraulicsModsVariations = (u8)variation.GetNumModsForType(VMT_WHEELS_REAR_OR_HYDRAULICS, this); if(uHydraulicsModsVariations > 0) { variation.SetModIndexForType(VMT_WHEELS_REAR_OR_HYDRAULICS, uHydraulicsModsVariations - 1, this, false, 0); } } } if( MI_CAR_BANSHEE2.IsValid() && GetModelIndex() == MI_CAR_BANSHEE2.GetModelIndex() ) { CVehicleFactory::ModifyVehicleTopSpeed( this, 4.0f ); } m_fRocketBoostRemaining = GetRocketBoostCapacity(); } void UpdateCollisionOnAllChildrenRecursive( fragPhysicsLOD* physicsLOD, phBoundComposite* pBoundComp, fragTypeGroup* pGroup, bool enableCollision, bool includeMapCollision = true ); static const int snMaxNumRamps = 3; static const int snMaxNumSpikes = 3; static const int snNumWeaponBladeMods = 3; eHierarchyId weaponBladeIds[ CVehicle::MAX_NUM_WEAPON_BLADES * snNumWeaponBladeMods ] = { VEH_BLADE_R_1_MOD, VEH_BLADE_S_1_MOD, VEH_BLADE_S_1_MOD, VEH_BLADE_F_1_MOD, VEH_BLADE_R_2_MOD, VEH_BLADE_S_2_MOD, VEH_BLADE_S_2_MOD, VEH_BLADE_F_2_MOD, VEH_BLADE_R_2_MOD, VEH_BLADE_S_2_MOD, VEH_BLADE_S_2_MOD, VEH_BLADE_F_2_MOD }; eHierarchyId weaponFastBladeIds[ CVehicle::MAX_NUM_WEAPON_BLADES * snNumWeaponBladeMods ] = { VEH_BLADE_R_1_FAST, VEH_BLADE_S_1_L_FAST, VEH_BLADE_S_1_R_FAST, VEH_BLADE_F_1_FAST, VEH_BLADE_R_2_FAST, VEH_BLADE_S_2_L_FAST, VEH_BLADE_S_2_R_FAST, VEH_BLADE_F_2_FAST, VEH_BLADE_R_3_FAST, VEH_BLADE_S_3_L_FAST, VEH_BLADE_S_3_R_FAST, VEH_BLADE_F_3_FAST }; eRotationAxis weaponRotationAxis[ CVehicle::MAX_NUM_WEAPON_BLADES ] = { ROT_AXIS_LOCAL_Z, ROT_AXIS_LOCAL_Z, ROT_AXIS_LOCAL_Z, ROT_AXIS_LOCAL_X }; float weaponRotationSpeedMax[ CVehicle::MAX_NUM_WEAPON_BLADES ] = { 25.0f, 35.0f, -35.0f, 40.0f, }; float weaponMass[ CVehicle::MAX_NUM_WEAPON_BLADES ] = { 50.0f, 5.0f, 5.0f, 15.0 }; void CVehicle::InitialiseWeaponBladeCollision( int bladeMod, int bladeIndex ) { bool shouldBladeCollideWithMap = bladeIndex == VWB_FRONT || bladeIndex == VWB_REAR || GetVehicleType() == VEHICLE_TYPE_BIKE; if( bladeMod >= 0 && bladeMod < snNumWeaponBladeMods ) { fragInst* pFragInst = GetVehicleFragInst(); Assertf( pFragInst, "Vehicle '%s' has no fraginst!", GetModelName() ); phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); const s32 boneIdx = GetBoneIndex( weaponBladeIds[ ( bladeMod * MAX_NUM_WEAPON_BLADES ) + bladeIndex ] ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision, shouldBladeCollideWithMap ); } int fastBone = GetBoneIndex( weaponFastBladeIds[ ( bladeMod * MAX_NUM_WEAPON_BLADES ) + bladeIndex ] ); group = pFragInst->GetGroupFromBoneIndex( fastBone ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision, shouldBladeCollideWithMap ); } int fragChild = GetBaseModelInfo()->GetFragType()->GetComponentFromBoneIndex( 0, fastBone != -1 ? fastBone : boneIdx ); m_weaponBlades[ m_numWeaponBlades++ ].Init( boneIdx, fastBone, fragChild, weaponRotationAxis[ bladeIndex ], weaponRotationSpeedMax[ bladeIndex ], weaponMass[ bladeIndex ] ); } } } void CVehicle::InitialiseWeaponBlades() { fragInst* pFragInst = GetVehicleFragInst(); Assertf( pFragInst, "Vehicle '%s' has no fraginst!", GetModelName() ); phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); int bladeMod = 0; m_numWeaponBlades = 0; // disable collision on all of them first for( u32 i = 0; i < MAX_NUM_WEAPON_BLADES * snNumWeaponBladeMods; ++i ) { const s32 boneIdx = GetBoneIndex( weaponBladeIds[ i ] ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } const s32 fastBoneIdx = GetBoneIndex( weaponFastBladeIds[ i ] ); if( fastBoneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( fastBoneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } // enable collision on the ones that should be active for this mod. if the mod flag isn't set just assume mod 0 CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_WEAPON_BLADE_MODS ) ) { bool bVariation = false; bladeMod = Min( (s8)variation.GetModIndexForType( VMT_CHASSIS4, this, bVariation ), (s8)( snNumWeaponBladeMods - 1 ) ); } // we don't add the front blade in this loop as it is on a separate mod slot for( u32 i = 0; i < MAX_NUM_WEAPON_BLADES - 1; ++i ) { InitialiseWeaponBladeCollision( bladeMod, i ); } int numRammingScoopBones = 0; int numRampBones = 0; int numFrontSpikes = GetNumFrontSpikeBones(); int numRammingBarBones = GetNumRammingBarBones(); if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_RAMMING_SCOOP_MOD ) ) { numRammingScoopBones = GetNumRammingScoopBones(); } if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP_MOD ) ) { numRampBones = GetNumRampBones(); } // if we have a mod front blade it will be stored in the chassis2 mod after all the ramming scoop mods if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_WEAPON_BLADE_MODS ) ) { bool bVariation = false; bladeMod = Min( (s8)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ), (s8)( ( snNumWeaponBladeMods - 1 ) + numRammingScoopBones + numRampBones + numFrontSpikes + numRammingBarBones ) ); } bladeMod -= numRammingScoopBones + numRampBones + numFrontSpikes + numRammingBarBones; // initialise the front blade InitialiseWeaponBladeCollision( bladeMod, MAX_NUM_WEAPON_BLADES - 1 ); } int CVehicle::GetNumRammingScoopBones() const { int numScoops = 0; for( u32 i = VEH_FIRST_SCOOP_MOD; i <= VEH_LAST_SCOOP_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { numScoops++; } } return numScoops; } int CVehicle::GetNumFrontSpikeBones() const { int numFrontSpikes = 0; for( u32 i = VEH_FIRST_FRONT_SPIKE_MOD; i <= VEH_LAST_FRONT_SPIKE_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { numFrontSpikes++; } } return numFrontSpikes; } int CVehicle::GetNumRampBones() const { int numRamps = 0; for( u32 i = VEH_FIRST_RAMP_MOD; i <= VEH_LAST_RAMP_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { numRamps++; } } return numRamps; } int CVehicle::GetNumRammingBarBones() const { int numRammingBars = 0; for( u32 i = VEH_FIRST_RAMMING_BAR_MOD; i <= VEH_LAST_RAMMING_BAR_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { numRammingBars++; } } return numRammingBars; } void CVehicle::InitialiseRammingScoops() { // disable all the spike collision first fragInst* pFragInst = GetVehicleFragInst(); Assertf( pFragInst, "Vehicle '%s' has no fraginst!", GetModelName() ); phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); int scoopMod = -1; for( u32 i = (u32)VEH_FIRST_SCOOP_MOD; i <= (u32)VEH_LAST_SCOOP_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } // enable collision on the ones that should be active for this mod. if the mod flag isn't set just assume mod 0 CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); int numRampBones = 0; int numFrontSpikes = GetNumFrontSpikeBones(); if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP_MOD ) ) { numRampBones = GetNumRampBones(); } if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_RAMMING_SCOOP_MOD ) ) { bool bVariation = false; scoopMod = (s8)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ); } scoopMod -= numFrontSpikes; scoopMod -= numRampBones; scoopMod -= GetNumRammingBarBones(); static const u32 maxNumScoopMods = VEH_LAST_SCOOP_MOD - VEH_FIRST_SCOOP_MOD; if( scoopMod < 0 || scoopMod > maxNumScoopMods ) { return; } const s32 boneIdx = GetBoneIndex( (eHierarchyId)( (int)VEH_FIRST_SCOOP_MOD + scoopMod ) ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } void CVehicle::InitialiseRammingBars() { m_bWeaponModHasElectricEffect = false; // disable all the spike collision first fragInst* pFragInst = GetVehicleFragInst(); Assertf( pFragInst, "Vehicle '%s' has no fraginst!", GetModelName() ); phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); int barMod = -1; for( u32 i = (u32)VEH_FIRST_RAMMING_BAR_MOD; i <= (u32)VEH_LAST_RAMMING_BAR_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } // enable collision on the ones that should be active for this mod. if the mod flag isn't set just assume mod 0 CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); int numRampBones = 0; int numFrontSpikes = GetNumFrontSpikeBones(); if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP_MOD ) ) { numRampBones = GetNumRampBones(); } if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_RAMMING_BAR_MOD ) ) { bool bVariation = false; barMod = (s8)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ); } barMod -= numFrontSpikes; barMod -= numRampBones; if( barMod < 0 || barMod >= GetNumRammingBarBones() ) { return; } s32 boneIdx = GetBoneIndex( (eHierarchyId)( (int)VEH_FIRST_RAMMING_BAR_MOD + barMod ) ); if( boneIdx == -1 ) { boneIdx = GetBoneIndex( VEH_RAMMING_BAR_4 ); m_bWeaponModHasElectricEffect = boneIdx != -1; } if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } void CVehicle::InitialiseSpikeCollision() { // disable all the spike collision first fragInst* pFragInst = GetVehicleFragInst(); Assertf( pFragInst, "Vehicle '%s' has no fraginst!", GetModelName() ); phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); int spikeMod = 0; for( u32 i = (u32)VEH_FIRST_SPIKE_MOD; i <= (u32)VEH_LAST_SPIKE_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } for( u32 i = (u32)VEH_FIRST_FRONT_SPIKE_MOD; i <= (u32)VEH_LAST_FRONT_SPIKE_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } // enable collision on the ones that should be active for this mod. if the mod flag isn't set just assume mod 0 CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_WEAPON_SPIKE_MODS ) ) { bool bVariation = false; spikeMod = Min( (s8)variation.GetModIndexForType( VMT_CHASSIS3, this, bVariation ), (s8)( snMaxNumSpikes - 1 ) ); } static const u32 maxNumSpikeMods = 3; static const u32 maxNumBonesPerMod = 3; if( spikeMod >= 0 && spikeMod < maxNumSpikeMods ) { u32 startIndex = VEH_FIRST_SPIKE_MOD + ( maxNumBonesPerMod * spikeMod ); u32 endindex = startIndex + maxNumBonesPerMod; for( u32 i = startIndex; i < endindex; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } } if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_FRONT_SPIKE_MOD ) ) { bool bVariation = false; spikeMod = (s8)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ); if( spikeMod >= 0 && spikeMod < GetNumFrontSpikeBones() ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)( spikeMod + (int)VEH_FIRST_FRONT_SPIKE_MOD ) ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } } } void CVehicle::InitialiseRamps() { // disable all the ramp collision first fragInst* pFragInst = GetVehicleFragInst(); Assertf( pFragInst, "Vehicle '%s' has no fraginst!", GetModelName() ); phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); int rampMod = -1; for( u32 i = (u32)VEH_FIRST_RAMP_MOD; i <= (u32)VEH_LAST_RAMP_MOD; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = false; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } // enable collision on the ones that should be active for this mod. if the mod flag isn't set just assume mod 0 CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); int numFrontSpikes = GetNumFrontSpikeBones(); if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP_MOD ) ) { bool bVariation = false; rampMod = Min( (s8)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ), (s8)( snMaxNumRamps + numFrontSpikes - 1 ) ); } rampMod -= numFrontSpikes; static const u32 numRampMods = GetNumRampBones(); if( rampMod < 0 || rampMod >= numRampMods ) { return; } const s32 boneIdx = GetBoneIndex( (eHierarchyId)( (int)( VEH_FIRST_RAMP_MOD ) + rampMod ) ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = true; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } void CVehicle::InitDoors() { for(int i = VEH_LAST_WINDOW; i >= VEH_WINDOW_LF; i--) { m_bWindowsRolledDown[VEH_LAST_WINDOW - i] = false;//All windows rolled up to start } } /////////////////////////////////////////////////////////////////////////////////// void CVehicle::AddVehicleGadget(CVehicleGadget* pGadget) { m_pVehicleGadgets.PushAndGrow(pGadget); } /////////////////////////////////////////////////////////////////////////////////// CVehicleGadget* CVehicle::GetVehicleGadget(int iGadgetIndex) const { Assert(iGadgetIndex < m_pVehicleGadgets.size()); return m_pVehicleGadgets[iGadgetIndex]; } /////////////////////////////////////////////////////////////////////////////////// int CVehicle::GetGadgetEntities(int entityListSize, const CEntity ** entityListOut) const { // Gather up the entities from all the gadgets int numFoundTotal = 0; int entityListRemaining = entityListSize; const CEntity ** entityListCursor = entityListOut; for(int i=0; iGetGadgetEntities(entityListRemaining,entityListCursor); numFoundTotal += numFound; entityListRemaining -= numFound; entityListCursor = entityListCursor + numFound; } return numFoundTotal; } /////////////////////////////////////////////////////////////////////////////////// bool CVehicle::GetDefaultBonePositionForSetup(eHierarchyId nComponent, Vector3& vecResult) const { vecResult.Zero(); if(GetBoneIndex(nComponent)!=-1) { GetDefaultBonePosition(GetBoneIndex(nComponent), vecResult); return true; } return false; } bool CVehicle::GetDefaultBoneRotationForSetup(eHierarchyId nComponent, Quaternion& quatResult) const { quatResult.Identity(); if(GetBoneIndex(nComponent)!=-1) { GetDefaultBoneRotation(GetBoneIndex(nComponent), quatResult); return true; } return false; } void CVehicle::GetDefaultBonePosition(s32 boneIndex, Vector3& vecResult) const { vecResult = RCC_VECTOR3(GetSkeletonData().GetBoneData(boneIndex)->GetDefaultTranslation()); const crBoneData* pParentBoneData = GetSkeletonData().GetBoneData(boneIndex)->GetParent(); crSkeleton* pSkel = GetSkeleton(); const fragType* fragType = nullptr; if( GetVehicleFragInst() ) { fragType = GetVehicleFragInst()->GetType(); } while(pParentBoneData) { Matrix34 matParent; if( fragType ) { int parentBoneIdx = fragType->GetBoneIndexFromID( pParentBoneData->GetBoneId() ); matParent = MAT34V_TO_MATRIX34( pSkel->GetLocalMtx( parentBoneIdx ) ); } else { matParent.FromQuaternion(RCC_QUATERNION(pParentBoneData->GetDefaultRotation())); matParent.d = RCC_VECTOR3(pParentBoneData->GetDefaultTranslation()); } matParent.Transform(vecResult); pParentBoneData = pParentBoneData->GetParent(); } } void CVehicle::GetDefaultBoneRotation(s32 boneIndex, Quaternion& quatResult) const { quatResult = RCC_QUATERNION(GetSkeletonData().GetBoneData(boneIndex)->GetDefaultRotation()); const crBoneData* pParentBoneData = GetSkeletonData().GetBoneData(boneIndex)->GetParent(); crSkeleton* pSkel = GetSkeleton(); const fragType* fragType = nullptr; if( GetVehicleFragInst() ) { fragType = GetVehicleFragInst()->GetType(); } while(pParentBoneData) { if( fragType ) { int parentBoneIdx = fragType->GetBoneIndexFromID( pParentBoneData->GetBoneId() ); Mat34V parentMtx = pSkel->GetLocalMtx( parentBoneIdx ); Quaternion parentQuat; parentQuat.FromMatrix34( MAT34V_TO_MATRIX34( parentMtx ) ); quatResult.Multiply( parentQuat ); } else { quatResult.Multiply(RCC_QUATERNION(pParentBoneData->GetDefaultRotation())); } pParentBoneData = pParentBoneData->GetParent(); } } void CVehicle::GetDefaultBonePositionSimple(s32 boneIndex, Vector3& vecResult) const { Assert(boneIndex != -1); vecResult.Zero(); const crBoneData* boneData = GetSkeletonData().GetBoneData(boneIndex); vecResult = RCC_VECTOR3(boneData->GetDefaultTranslation()); const crBoneData* pParentBoneData = boneData->GetParent(); while(pParentBoneData) { vecResult += RCC_VECTOR3(pParentBoneData->GetDefaultTranslation()); pParentBoneData = pParentBoneData->GetParent(); } } int CVehicle::InitPhys() { CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); const Matrix34 matrix = MAT34V_TO_MATRIX34(GetMatrix()); if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_DISABLE_AUTO_VAULT_ON_VEHICLE)) { SetAutoVaultDisabled(true); } const gtaFragType* pFragType = pModelInfo->GetFragType(); #if ENABLE_FRAG_OPTIMIZATION const bool bHasEnvCloths = pFragType && pFragType->GetNumEnvCloths() > 0; const bool bDisableCacheOnInsertion = !bHasEnvCloths && (GetVehicleType() == VEHICLE_TYPE_CAR || GetVehicleType() == VEHICLE_TYPE_TRAILER); m_nVehicleFlags.bHasFragCacheEntry = !bDisableCacheOnInsertion; #endif // ENABLE_FRAG_OPTIMIZATION if(pFragType) { m_nFlags.bIsFrag = true; Assert(m_pVehicleFragInst == NULL); m_pVehicleFragInst = rage_new fragInstGta(PH_INST_FRAG_VEH, pModelInfo->GetFragType(), matrix); m_pVehicleFragInst->SetInstFlag(phInst::FLAG_HIGH_PRIORITY, true); m_pVehicleFragInst->SetManualSkeletonUpdate(true); SetPhysicsInst(m_pVehicleFragInst, true); m_pVehicleFragInst->SetActivateOnHit( true ); ENABLE_FRAG_OPTIMIZATION_ONLY(m_pVehicleFragInst->SetDisableCacheOnInsertion(bDisableCacheOnInsertion);) m_pVehicleFragInst->Insert(false); // Have to activate separately because the process of inserting as active sets up the cache entry which requires that the // instance already be active because it adds the inst behavior ... unfortunate. if(ENABLE_FRAG_OPTIMIZATION_ONLY(m_pVehicleFragInst->GetCached() &&) !m_nVehicleFlags.bCreatingAsInactive) { PHSIM->ActivateObject(m_pVehicleFragInst); } m_pVehicleFragInst->ReportMovedBySim(); #if GTA_REPLAY if(m_pVehicleFragInst->GetCacheEntry() && m_pVehicleFragInst->GetCacheEntry()->GetHierInst() && m_pVehicleFragInst->GetCacheEntry()->GetHierInst()->envCloth) { environmentCloth *pEnvCloth = m_pVehicleFragInst->GetCacheEntry()->GetHierInst()->envCloth; CReplayMgr::RecordCloth(this, pEnvCloth->GetClothController(), 0); } #endif // GTA_REPLAY #if __ASSERT CPhysical::RegisterPhysicsCreationBacktrace(static_cast(this)); #endif } Assertf(m_pVehicleFragInst != NULL, "Vehicle (%s) Doesn't Have A Frag Inst", GetDebugName()); // Find out if the vehicle has a dummy bound const int iBoneIndexChassisDummy=GetBoneIndex(VEH_CHASSIS_DUMMY); if(iBoneIndexChassisDummy!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndexChassisDummy); if(nGroup > -1) { m_nVehicleFlags.bHasDummyBound = true; } } #if ENABLE_FRAG_OPTIMIZATION if(!m_pVehicleFragInst->GetCached()) { phBoundComposite* pBoundComp = static_cast(GetVehicleFragInst()->GetArchetype()->GetBound()); Assert(pBoundComp); if(!pBoundComp->GetTypeAndIncludeFlags()) { pBoundComp->AllocateTypeAndIncludeFlags(); } } #endif InitAllExtras(); fragCacheEntry* cacheEntry = m_pVehicleFragInst->GetCacheEntry(); // Assert( cacheEntry ); if( cacheEntry ) { fragHierarchyInst* hierInst = cacheEntry->GetHierInst(); Assert( hierInst ); if(hierInst->articulatedCollider) { if(ms_bUseAutomobileBVHupdate) { hierInst->articulatedCollider->SetBvhRebuildPeriod(UPDATE_BVH_EACH_UPDATE); } } } InitDummyInst(); // The speedo should be more careful in MP if(GetModelIndex() == MI_CAR_SPEEDO || GetModelIndex() == MI_CAR_SPEEDO2 ) { atHashString truckHash("TRUCK",0x428100C5); atHashString averageHash("AVERAGE",0xBE32B83A); if(NetworkInterface::IsGameInProgress()) { //override to Truck handling if (pHandling) { pHandling->OverrideAIHandlingInfo(CAIHandlingInfoMgr::GetAIHandlingInfoByHash(truckHash.GetHash())); } } else { //reset to average handling if (pHandling) { pHandling->OverrideAIHandlingInfo(CAIHandlingInfoMgr::GetAIHandlingInfoByHash(averageHash.GetHash())); } } } if( HasGlider() || GetModelIndex() == MI_BIKE_OPPRESSOR2.GetModelIndex() ) { FinishGlidingInstant(); } return INIT_OK; } extern u32 GenerateUnlatchedCarDoorCollisionIncludeFlags(const CVehicle* pVehicle, eHierarchyId doorId); // from door.cpp dev_bool sbTestDisableVehicleBounds = true; static dev_u32 snDummyIncludeFlags = ArchetypeFlags::GTA_CAMERA_TEST|ArchetypeFlags::GTA_WEAPON_TEST; // void CVehicle::InitCompositeBound() { phBoundComposite* pBoundComp = static_cast(GetVehicleFragInst()->GetArchetype()->GetBound()); const fragPhysicsLOD* physicsLOD = GetVehicleFragInst()->GetTypePhysics(); #if __ASSERT const int N=pBoundComp->GetNumBounds(); s32 i; for(i=0;iGetBound(i); if(pSubBound) { vehicleAssertf(pSubBound->GetType()!=phBound::COMPOSITE, "Composite bound within a composite bound not allowed - Please re-export %s", GetVehicleModelInfo()->GetModelName() ); } } #endif if (UsesDummyPhysics(GetVehicleAiLod().GetDummyMode()) && HasDummyBound()) { for(int i=0; iGetNumBounds(); i++) { // Clear all the include flags (except for camera and weapon shape tests). pBoundComp->SetIncludeFlags(i, snDummyIncludeFlags); } const int iBoneIndexChassisDummy=GetBoneIndex(VEH_CHASSIS_DUMMY); if(iBoneIndexChassisDummy!=-1) { // Set include flags only for the dummy chassis int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndexChassisDummy); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetTypeFlags(iChild+k, ArchetypeFlags::GTA_BOX_VEHICLE_TYPE); pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES); } } } } else { bool bHasBvhBound = pBoundComp->GetContainsBVH(); // B*1766616, disable the cargobob's interior bound to prevent ped/player getting warpped inside. Make it MP only for now bool bDisableChassisBvhBound = NetworkInterface::IsGameInProgress() && InheritsFromHeli() && ((CHeli*)this)->GetIsCargobob() && (GetModelIndex() != MI_HELI_CARGOBOB4) && ((CHeli*)this)->GetIsJetPack(); if(pBoundComp->GetTypeAndIncludeFlags() && sbTestDisableVehicleBounds) { // Initialize all of the type/include flags for(int i=0; iGetNumBounds(); i++) { if(phBound* pBound = pBoundComp->GetBound(i)) { // Set the type of all vehicle components correctly so that other code can cheaply identify components // (we don't want them to be 0xffffffff). if(pBound->GetType() == phBound::BVH) { Assert(bHasBvhBound); // If we have a BVH then disable collision against the map and the BVH bounds of other vehicles. pBoundComp->SetTypeFlags(i, ArchetypeFlags::GTA_VEHICLE_BVH_TYPE); pBoundComp->SetIncludeFlags(i, ArchetypeFlags::GTA_VEH_BVH_CHASSIS_INCLUDE_TYPES); } else { pBoundComp->SetTypeFlags(i, ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE); if(physicsLOD->GetChild(i)->GetOwnerGroupPointerIndex() == 0) { // This is a chassis pBoundComp->SetIncludeFlags(i, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES|ArchetypeFlags::GTA_VEHICLE_BVH_TYPE); } else { // B*1782572: Prevent collision include flags from being removed from these components if the vehicle is a train. // This code allowed players to jump and ragdoll through the windows of the metro train. if(!InheritsFromTrain()) { // removing all include flags but the shape test ones, so these components won't collide with anything else // NOTE: The camera shape tests are explicitly included here. u32 nIncludeFlags = ArchetypeFlags::GTA_CAR_DOOR_LATCHED_INCLUDE_TYPES | ArchetypeFlags::GTA_CAMERA_TEST; // Only exclude shapetests if this isn't a smashable window. bool bIsSmashableGlass = false; for(int nMatIdx = 0; nMatIdx < pBound->GetNumMaterials(); ++nMatIdx) { if(PGTAMATERIALMGR->GetIsSmashableGlass(pBound->GetMaterialId(nMatIdx))) { bIsSmashableGlass = true; break; } } if(bHasBvhBound && !bIsSmashableGlass) { nIncludeFlags &= ~ArchetypeFlags::GTA_ALL_SHAPETEST_TYPES; } if( InheritsFromAmphibiousAutomobile() ) { int iBoneIndex = GetBoneIndex( AMPHIBIOUS_AUTOMOBILE_RUDDER ); if(iBoneIndex > -1) { if( GetVehicleFragInst()->GetComponentFromBoneIndex( iBoneIndex ) == i ) { nIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE; } } } pBoundComp->SetIncludeFlags(i, nIncludeFlags); } } } } } //If a low lod chassis exists then set it to collide with the map only and set the regular chassis //to not collide with the map. This is a performance optimisation. const int iBoneIndexChasis=GetBoneIndex(VEH_CHASSIS); const int iBoneIndexChassisLowLod=GetBoneIndex(VEH_CHASSIS_LOWLOD); if(iBoneIndexChasis!=-1 && iBoneIndexChassisLowLod!=-1) { //Set the low lod chassis to _ONLY_ collide with the map. int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndexChassisLowLod); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); Assertf(pGroup->GetStrength() == -1.0f, "Vehicle %s has low LOD chassis that is breakable, this should not happen.", GetVehicleFragInst()->GetArchetype()->GetFilename()); // Make the low LOD chassis transfer all impulses to the high LOD chassis without loss. This will make collisions with the low LOD chassis // break parts the same way that the high LOD chassis does. pGroup->SetForceTransmissionScaleUp(1.0f); //Set the low lod chassis to _ONLY_ collide with the map and foliage bounds (to apply small drag when driving through shrubbery). for(int k = 0; k < pGroup->GetNumChildren(); k++) { if(InheritsFromTrain()) { if(Unlikely(GetModelIndex() == MI_TRAIN_METRO)) { // We want to use the metro train's low LOD chassis bound to collide against vehicles to stop them popping // into the BVH interior required for peds. pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEHICLE_TYPE); } else { // All other trains do not need low lod as they don't collide with the map. pBoundComp->SetIncludeFlags(iChild+k, 0); } } else { u32 nLowLodChassisIncludeFlags = ArchetypeFlags::GTA_MAP_TYPE_VEHICLE | ArchetypeFlags::GTA_STAIR_SLOPE_TYPE | ArchetypeFlags::GTA_FOLIAGE_TYPE; // B*1918379: Allow certain vehicles to have certain objects collide with the low-LOD chassis rather than the regular chassis. // This is used to prevent thin objects (like fence segments) from getting stuck and intersect vehicles with open-topped, concave chassis bounds (Like the bodhi2). // Only objects that are set to collide with the low-LOD chassis will do so and only with vehicles that have been flagged to allow this. if(GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_ALLOW_OBJECT_LOW_LOD_COLLISION) && (!MI_SUB_KOSATKA.IsValid() || GetModelIndex() != MI_SUB_KOSATKA)) nLowLodChassisIncludeFlags |= ArchetypeFlags::GTA_OBJECT_TYPE; // We want the Caddy low lod to collide against vehicles as it's high lod is too thin. if(Unlikely( GetModelIndex() == MI_CAR_CADDY)) { nLowLodChassisIncludeFlags |= ArchetypeFlags::GTA_VEHICLE_TYPE; } pBoundComp->SetIncludeFlags(iChild+k, nLowLodChassisIncludeFlags); // // This can be removed once all boats have had the low-LOD chassis replaced or supplemented with a normal, non-BVH chassis bound. if(InheritsFromBoat()) pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_ALL_MAP_TYPES|ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE); // } } } if(!InheritsFromTrain()) { nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndexChasis); //Set the high lod chassis to collide with everything but the map. int iIncludeFlags= ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES; iIncludeFlags &= ~ArchetypeFlags::GTA_MAP_TYPE_VEHICLE; // We want the Caddy low lod to collide against vehicles not it's high lod, as it's high lod is too thin. if(Unlikely( GetModelIndex() == MI_CAR_CADDY)) { iIncludeFlags &= ~ArchetypeFlags::GTA_VEHICLE_TYPE; } // If the vehicle has a low LOD chassis bound, allow only that to provide contacts with foliage bounds. iIncludeFlags &= ~ArchetypeFlags::GTA_FOLIAGE_TYPE; if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); //Set the low lod chassis to _ONLY_ collide with the map. for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetIncludeFlags(iChild+k, iIncludeFlags); } } } } const int iBoneIndexChassisDummy=GetBoneIndex(VEH_CHASSIS_DUMMY); if(iBoneIndexChassisDummy!=-1) { // Clear all include flags for the dummy chassis int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndexChassisDummy); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetIncludeFlags(iChild+k, 0); } } } //List of car parts that should collide with the map const s32 CAR_PARTS_TO_COLLIDE_WITH_GROUND = 50; static eHierarchyId aCarPartIds[ CAR_PARTS_TO_COLLIDE_WITH_GROUND ] = { VEH_FORKS, VEH_CARRIAGE, VEH_HANDLER_FRAME_1, VEH_HANDLER_FRAME_2, VEH_FREIGHTCONT2_CONTAINER, VEH_FREIGHTGRAIN_SLIDING_DOOR, VEH_DOOR_HATCH_R, VEH_DOOR_HATCH_L, VEH_ARM1, VEH_ARM2, VEH_ARM3, VEH_ARM4, VEH_TOW_ARM, VEH_DIGGER_ARM, VEH_ARTICULATED_DIGGER_ARM_BASE, VEH_ARTICULATED_DIGGER_ARM_BOOM, VEH_ARTICULATED_DIGGER_ARM_STICK, VEH_ARTICULATED_DIGGER_ARM_BUCKET, VEH_CUTTER_ARM_1, VEH_CUTTER_ARM_2, VEH_CUTTER_BOOM_DRIVER, VEH_CUTTER_CUTTER_DRIVER, VEH_EXTRA_1, VEH_EXTRA_2, VEH_EXTRA_3, VEH_EXTRA_4, VEH_EXTRA_5, VEH_EXTRA_6, VEH_EXTRA_7, VEH_EXTRA_8, VEH_EXTRA_9, VEH_EXTRA_10, VEH_EXTRA_11, VEH_BREAKABLE_EXTRA_1, VEH_BREAKABLE_EXTRA_2, VEH_BREAKABLE_EXTRA_3, VEH_BREAKABLE_EXTRA_4, VEH_BREAKABLE_EXTRA_5, VEH_BREAKABLE_EXTRA_6, VEH_BREAKABLE_EXTRA_7, VEH_BREAKABLE_EXTRA_8, VEH_BREAKABLE_EXTRA_9, VEH_BREAKABLE_EXTRA_10, VEH_MISC_E, VEH_EXTENDABLE_SIDE_L, VEH_EXTENDABLE_SIDE_R, VEH_WING_RF, VEH_WING_LF, VEH_RAMMING_SCOOP, VEH_TOMBSTONE, }; //List of non car parts that should collide with the map, we have different arrays for this as hierarchy id's are reused for different vehicles. const s32 NON_CAR_PARTS_TO_COLLIDE_WITH_GROUND = 61; static eHierarchyId aNonCarPartIds[NON_CAR_PARTS_TO_COLLIDE_WITH_GROUND] = { VEH_MISC_A, VEH_MISC_B, VEH_MISC_C, VEH_MISC_D, VEH_MISC_E, VEH_MISC_F, VEH_MISC_G, VEH_MISC_H, VEH_MISC_I, VEH_MISC_J, VEH_MISC_K, VEH_MISC_L, VEH_MISC_M, VEH_MISC_N, VEH_MISC_O, VEH_MISC_P, VEH_MISC_Q, VEH_MISC_R, VEH_MISC_S, VEH_MISC_T, VEH_MISC_U, VEH_MISC_V, VEH_MISC_W, VEH_MISC_X, VEH_MISC_Y, VEH_MISC_Z, VEH_MISC_1, VEH_MISC_2, VEH_MISC_4, VEH_MISC_5, VEH_MISC_6, PLANE_PROP_1, PLANE_PROP_2, PLANE_PROP_3, PLANE_PROP_4, PLANE_PROP_5, PLANE_PROP_6, PLANE_PROP_7, PLANE_PROP_8, HELI_OUTRIGGERS_L, HELI_OUTRIGGERS_R, VEH_EXTRA_1, VEH_EXTRA_2, VEH_EXTRA_3, VEH_EXTRA_4, VEH_EXTRA_5, VEH_EXTRA_6, VEH_EXTRA_7, //VEH_EXTRA_8, VEH_EXTRA_9, VEH_EXTRA_10, VEH_EXTRA_11, VEH_BREAKABLE_EXTRA_1, VEH_BREAKABLE_EXTRA_2, VEH_BREAKABLE_EXTRA_3, VEH_BREAKABLE_EXTRA_4, VEH_BREAKABLE_EXTRA_5, VEH_BREAKABLE_EXTRA_6, VEH_BREAKABLE_EXTRA_7, VEH_BREAKABLE_EXTRA_8, VEH_BREAKABLE_EXTRA_9, VEH_BREAKABLE_EXTRA_10, }; // cars, bikes, trains and quadbikes use the same array for car parts that hit the map s16 iMaxVehiclePartsToCollideWithGround = CAR_PARTS_TO_COLLIDE_WITH_GROUND; eHierarchyId *aVehiclePartsIds = aCarPartIds; // planes, helis etc use a different array for hitting the map as they tend to reuse hierarchy id's if(GetVehicleType() == VEHICLE_TYPE_PLANE || GetVehicleType() == VEHICLE_TYPE_HELI || GetVehicleType() == VEHICLE_TYPE_SUBMARINE || GetVehicleType() == VEHICLE_TYPE_BOAT || GetVehicleType() == VEHICLE_TYPE_BLIMP) { iMaxVehiclePartsToCollideWithGround = NON_CAR_PARTS_TO_COLLIDE_WITH_GROUND; aVehiclePartsIds = aNonCarPartIds; } if( GetVehicleModelInfo()->GetModelNameHash() == MI_CAR_TECHNICAL2.GetName().GetHash() ) { int iBoneIndex = GetBoneIndex( VEH_MISC_D ); if( iBoneIndex != -1 ) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( iBoneIndex ); if( nGroup > -1 ) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for( int k = 0; k < pGroup->GetNumChildren(); k++ ) { pBoundComp->SetIncludeFlags( iChild + k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES ); } } } } // allow the glider wings to hit things if( HasGlider() ) { for( int boneId = FIRST_EXTENDABLE; boneId <= LAST_EXTENDABLE; boneId++ ) { int iBoneIndex = GetBoneIndex( (eHierarchyId)boneId ); if( iBoneIndex != -1 ) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( iBoneIndex ); if( nGroup > -1 ) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for( int k = 0; k < pGroup->GetNumChildren(); k++ ) { pBoundComp->SetIncludeFlags( iChild + k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES ); } } } } } for( int boneId = VEH_FIRST_ARENA_MOD; boneId <= VEH_LAST_ARENA_MOD; boneId++ ) { int iBoneIndex = GetBoneIndex( (eHierarchyId)boneId ); if( iBoneIndex != -1 ) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( iBoneIndex ); if( nGroup > -1 ) { fragTypeGroup* pGroup = physicsLOD->GetGroup( nGroup ); int iChild = pGroup->GetChildFragmentIndex(); for( int k = 0; k < pGroup->GetNumChildren(); k++ ) { pBoundComp->SetIncludeFlags( iChild + k, ArchetypeFlags::GTA_VEH_BVH_CHASSIS_INCLUDE_TYPES | ArchetypeFlags::GTA_WHEEL_TEST ); } } } } if( InheritsFromSubmarineCar() ) { const s32 EXTRA_SUBMARINE_CAR_BONES = 3; static eHierarchyId aSubmarineCarBones[ EXTRA_SUBMARINE_CAR_BONES ] = { SUBMARINECAR_FIN_MOUNT, SUBMARINECAR_DRIVE_PLANE_L, SUBMARINECAR_DRIVE_PLANE_R }; for(s16 j = 0; j < EXTRA_SUBMARINE_CAR_BONES; j++) { int iBoneIndex = GetBoneIndex( aSubmarineCarBones[j] ); if( iBoneIndex != -1 ) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( iBoneIndex ); if( nGroup > -1 ) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for( int k = 0; k < pGroup->GetNumChildren(); k++ ) { pBoundComp->SetIncludeFlags( iChild + k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES ); } } } } } // allow the jetpack stand and landing gear to collide with the ground if( InheritsFromHeli() && static_cast< CHeli* >( this )->GetIsJetPack() ) { const s32 EXTRA_JETPACK_BONES = 2; static eHierarchyId aJetpackBones[ EXTRA_JETPACK_BONES ] = { VEH_STAND, LANDING_GEAR_F }; for(s16 j = 0; j < EXTRA_JETPACK_BONES; j++) { int iBoneIndex = GetBoneIndex( aJetpackBones[j] ); if( iBoneIndex != -1 ) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( iBoneIndex ); if( nGroup > -1 ) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for( int k = 0; k < pGroup->GetNumChildren(); k++ ) { pBoundComp->SetIncludeFlags( iChild + k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES ); } } } } } for(s16 i = 0; i < iMaxVehiclePartsToCollideWithGround; i++) { if(GetVehicleModelInfo()->GetModelNameHash() == MI_CAR_TROPOS.GetName().GetHash() ) { if(aVehiclePartsIds[i] == VEH_WING_RF || aVehiclePartsIds[i] == VEH_WING_LF) { continue; } } //Allow vehicle parts to hit the ground int iBoneIndex=GetBoneIndex(aVehiclePartsIds[i]); if(iBoneIndex!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndex); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } } const s32 CAR_PARTS_TO_COLLIDE_WITH_EVERYTHING_BUT_THE_GROUND = 4; static eHierarchyId aCarPartToCollideWithPedsIds[CAR_PARTS_TO_COLLIDE_WITH_EVERYTHING_BUT_THE_GROUND] = { VEH_TRANSMISSION_F, VEH_TRANSMISSION_M, VEH_TRANSMISSION_R, VEH_TRANSMISSION_M1, }; for(s16 i = 0; i < CAR_PARTS_TO_COLLIDE_WITH_EVERYTHING_BUT_THE_GROUND; i++) { //Allow vehicle parts to hit everything but the ground int iBoneIndex=GetBoneIndex(aCarPartToCollideWithPedsIds[i]); if(iBoneIndex!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndex); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEH_BVH_CHASSIS_INCLUDE_TYPES); } } } } CVehicleModelInfoDoors* pOtherVehicleDoorExtension = GetVehicleModelInfo()->GetExtension(); for(int i = 0; i < GetNumDoors(); i++) { CCarDoor* pDoor = GetDoor(i); eHierarchyId nDoorId = pDoor->GetHierarchyId(); // Don't turn off the collision for this door if it is marked as not being covered by the vehicle's convex hull or // if it is set to be "driveable" (like the back door of the benson truck). if(pOtherVehicleDoorExtension && ( pOtherVehicleDoorExtension->ContainsThisDoorWithCollision(nDoorId) || pOtherVehicleDoorExtension->ContainsThisDriveableDoor(nDoorId) || nDoorId == VEH_BOOT_2) ) { int nGroup = pDoor->GetFragGroup(); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } if(pDoor->GetIsIntact(this) && !pDoor->GetIsLatched(this)) { //Ensure that all bounds associated with this door are flagged not to include the camera shapetests. u32 includeFlags = GenerateUnlatchedCarDoorCollisionIncludeFlags(this, nDoorId); int nGroup = pDoor->GetFragGroup(); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { pBoundComp->SetIncludeFlags(iChild+k, includeFlags); } } } } // Special collision requirements for bumpers. const s32 NUM_BUMPERS_TO_ENABLE_COLLISION = 2; static eHierarchyId aBumperPartsToEnableCollision[NUM_BUMPERS_TO_ENABLE_COLLISION] = { VEH_BUMPER_F, VEH_BUMPER_R, }; const fragPhysicsLOD* physicsLOD = GetVehicleFragInst()->GetTypePhysics(); for(u32 i = 0; i < NUM_BUMPERS_TO_ENABLE_COLLISION; ++i) { int nBoneIdx = GetBoneIndex(aBumperPartsToEnableCollision[i]); if(nBoneIdx!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(nBoneIdx); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); ++k) { u32 nIncludeFlags = ArchetypeFlags::GTA_CAR_BUMPER_INCLUDE_TYPES; const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); physicsFatalAssertf(pVehicleModelInfo, "This vehicle (%s) has no model info. It is unsafe to continue.", GetModelName()); const CVehicleModelInfoBumperCollision* pBumperColExtension = pVehicleModelInfo->GetExtension(); if(pBumperColExtension && pBumperColExtension->GetBumpersNeedMapCollision()) { nIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES; } pBoundComp->SetIncludeFlags(iChild+k, nIncludeFlags); } } } } // If we have a BVH then disable the chassis convex chassis collision with the player but enable it with the map. if(bHasBvhBound) { if(iBoneIndexChasis!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(iBoneIndexChasis); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); if(bDisableChassisBvhBound) { //Disable all BVH bounds, and keep other chassis bounds as is for(int k = 0; k < pGroup->GetNumChildren(); k++) { if(phBound* pBound = pBoundComp->GetBound(iChild+k)) { if(pBound->GetType() == phBound::BVH) { pBoundComp->SetIncludeFlags(iChild+k, 0); } } } } else { //Set the chassis to _ONLY_ collide with the map. for(int k = 0; k < pGroup->GetNumChildren(); k++) { if(phBound* pBound = pBoundComp->GetBound(iChild+k)) { // B*1806240: Vehicle flag prevents this code from removing all of the include types in GTA_VEH_BVH_CHASSIS_INCLUDE_TYPES from the bound. // The particular case that spawned this addition was peds being unable to collide with key parts of certain freight car models; this flag is not enabled for the majority of vehicles. if(pBound->GetType() != phBound::BVH && !GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_FORCE_ENABLE_CHASSIS_COLLISION)) { int gtaIntFlags = (ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES & ~ArchetypeFlags::GTA_VEH_BVH_CHASSIS_INCLUDE_TYPES); //gtaIntFlags |= ArchetypeFlags::GTA_ALL_SHAPETEST_TYPES; pBoundComp->SetIncludeFlags(iChild+k, gtaIntFlags); } else { pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEH_BVH_CHASSIS_INCLUDE_TYPES); } } } } } } } // turn collision with everything back on for wheels // Also set up 2nd surface config SetupWheelCollisions(pBoundComp); // turn on collisions with extra's that have frag children / bounds for(int nExtra=VEH_EXTRA_1; nExtra <= VEH_LAST_EXTRA; nExtra++) { if(GetBoneIndex((eHierarchyId)nExtra)!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(GetBoneIndex((eHierarchyId)nExtra)); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { if(GetIsExtraOn((eHierarchyId)nExtra)) { pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } } } // turn on collisions with any vehicle speakers for( int nSpeaker = VEH_SPEAKER_FIRST; nSpeaker <= VEH_SPEAKER_LAST; nSpeaker++ ) { if( GetBoneIndex( (eHierarchyId)nSpeaker ) != -1 ) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex( GetBoneIndex( (eHierarchyId)nSpeaker ) ); if( nGroup > -1 ) { fragTypeGroup* pGroup = physicsLOD->GetGroup( nGroup ); int iChild = pGroup->GetChildFragmentIndex(); for( int k = 0; k < pGroup->GetNumChildren(); k++ ) { pBoundComp->SetIncludeFlags( iChild + k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES ); } } } } // Turn on collision with physical turrets and weapons // turn on collisions with extra's that have frag children / bounds if( !MI_VAN_BOXVILLE5.IsValid() || GetModelIndex() != MI_VAN_BOXVILLE5 ) { for(int nTurret=VEH_WEAPON_1A; nTurret <= VEH_TURRET_4_BARREL; nTurret++) { if(GetBoneIndex((eHierarchyId)nTurret)!=-1) { int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex((eHierarchyId)nTurret)); if(nChildIndex != -1) { pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } // We added some more turrets... for(int nTurret=VEH_TURRET_A1_BASE; nTurret <= VEH_TURRET_A4_BARREL; nTurret++) { if(GetBoneIndex((eHierarchyId)nTurret)!=-1) { int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex((eHierarchyId)nTurret)); if(nChildIndex != -1) { pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } for(int nTurret=VEH_TURRET_B1_BASE; nTurret <= VEH_TURRET_B4_BARREL; nTurret++) { if(GetBoneIndex((eHierarchyId)nTurret)!=-1) { int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex((eHierarchyId)nTurret)); if(nChildIndex != -1) { pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } } InitialiseSeatCollision( pBoundComp ); // Temporarily turn off vehicle blocker bounds flags entirely. This will set the flags to collide with vehicles only once the art is ready if(GetBoneIndex(VEH_VEHICLE_BLOCKER)!=-1) { int nChildIndex = GetVehicleFragInst()->GetComponentFromBoneIndex(GetBoneIndex(VEH_VEHICLE_BLOCKER)); if(nChildIndex != -1) { pBoundComp->SetIncludeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE); pBoundComp->SetTypeFlags(nChildIndex, ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE); } } // Setup roof collision if(DoesVehicleHaveAConvertibleRoofAnimation()) { ToggleRoofCollision(GetConvertibleRoofState() == CTaskVehicleConvertibleRoof::STATE_RAISED); } InitialiseModCollision(); #if __ASSERT for(int i = 0; i < pBoundComp->GetNumBounds(); ++i) { if(const phBound* pBound = pBoundComp->GetBound(i)) { u32 concaveBoundTypes = ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE; Assertf(pBound->IsConvex() || (pBoundComp->GetIncludeFlags(i) & concaveBoundTypes) == 0, "Component %i of vehicle '%s' is non-convex and is set to collide with other non-convex bounds.",i,GetVehicleFragInst()->GetArchetype()->GetFilename()); } } #endif // __ASSERT } // InitCompositeBound gets called during transitions between dummy/real/superdummy, so need to set up the collision every time to make sure it's not reset SetupWeaponModCollision(); } // Turn off collision with the steps on the DODO so that the ped doesn't end up breaking the doors off (see B*2036883). if(pHandling && pHandling->GetSeaPlaneHandlingData()) { // Disable stair collision with these components. const u32 nNumComponents = 6; const eHierarchyId nStairBones[nNumComponents] = { VEH_DODO_NO_PED_COL_STEP_L, VEH_DODO_NO_PED_COL_STEP_R, VEH_DODO_NO_PED_COL_STRUT_1_L, VEH_DODO_NO_PED_COL_STRUT_1_R, VEH_DODO_NO_PED_COL_STRUT_2_L, VEH_DODO_NO_PED_COL_STRUT_2_R }; for(int i = 0; i < nNumComponents; ++i) { const s32 nBoneIdx = GetBoneIndex(nStairBones[i]); if(nBoneIdx!=-1) { fragInst* pFragInst = GetVehicleFragInst(); Assert(pFragInst); int nComponentId = pFragInst->GetComponentFromBoneIndex(nBoneIdx); u32 nFlags = pBoundComp->GetIncludeFlags(nComponentId); nFlags &= ~ArchetypeFlags::GTA_PED_TYPE; pBoundComp->SetIncludeFlags(nComponentId, nFlags); } } } // The forklift forks need to have special type flags set on them for collision purposes. for(int i = 0; i < GetNumberOfVehicleGadgets(); ++i) { CVehicleGadget* pVehicleGadget = GetVehicleGadget(i); if(pVehicleGadget->GetType() == VGT_FORKS) { CVehicleGadgetForks* pForks = static_cast(pVehicleGadget); pForks->Init(this); } } // Forgive me... if(this->GetModelIndex()==MI_CAR_POLICE_3 && pBoundComp) { // Component 1 of "police3" should correspond to the bull bars at the front unless it gets re-exported. We // want to turn off collision against objects to fix B*1613652. u32 nFlags = pBoundComp->GetIncludeFlags(1); nFlags &= ~(ArchetypeFlags::GTA_OBJECT_TYPE); pBoundComp->SetIncludeFlags(1, nFlags); } } void CVehicle::UpdateCollisionOnPartBrokenOff(int componentIndex, fragInst* newFragInst) { // Reset the include flags of all breaking components Assert(newFragInst); Assert(newFragInst->GetCacheEntry()); phBoundComposite* pBoundComp = newFragInst->GetCacheEntry()->GetBound(); Assert(componentIndex < pBoundComp->GetNumBounds()); pBoundComp->SetTypeFlags(componentIndex,ArchetypeFlags::GTA_OBJECT_TYPE); if(pBoundComp->GetIncludeFlags(componentIndex) != 0) { pBoundComp->SetIncludeFlags(componentIndex,ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } #if !__NO_OUTPUT void CVehicle::LogDeletionFailReason(const char *pReasonString, const bool bLogClearCarFromAreaFailures) const { BANK_ONLY(SetLastVehPopRemovalFailReason(pReasonString)); if (bLogClearCarFromAreaFailures) { CGameWorld::LogClearCarFromArea("CVehicle::CanBeDeletedSpecial", this, pReasonString); } } #endif // !__NO_OUTPUT // // // // bool CVehicle::CanBeDeletedNetwork(const bool OUTPUT_ONLY(bLogClearCarFromAreaFailures) ) const { if (IsNetworkClone()) // this object is being controlled by a remote machine { OUTPUT_ONLY(LogDeletionFailReason("Is Network Clone", bLogClearCarFromAreaFailures)); return false; } if (GetNetworkObject() && !GetNetworkObject()->CanDelete()) { OUTPUT_ONLY(LogDeletionFailReason("Network Object Can't Be Deleted", bLogClearCarFromAreaFailures)); return false; } return true; } bool CVehicle::CanBeDeletedSpecial(bool bTakeAccountOfMissionPopType, bool bDoNetworkChecks, bool bCheckForPedsEnteringAndExiting, bool bCheckPedStates, bool bCheckInterestingVehicles, bool bAllowCopRemovalWithWantedLevel, const bool bReturnCarsContainingAPlayer, const bool bAllowRemovalOfCarsInGarage, const bool bLogClearCarFromAreaFailures) const { if (bDoNetworkChecks && !CanBeDeletedNetwork(bLogClearCarFromAreaFailures)) { return false; } if( !bAllowRemovalOfCarsInGarage ) { if( CGarages::IsVehicleWithinHideOutGarageThatCanSaveIt(this) ) { OUTPUT_ONLY(LogDeletionFailReason("The vehicle is within a hideout garage that can save it", bLogClearCarFromAreaFailures)); return false; } } // Trains should be dealt with via VehicleCreatedBy==POPTYPE_RANDOM_PERMANENT but I suspect a script grabs and engine and releases it. This makes it a POPTYPE_RANDOM_AMBIENT and hence it got deleted. if (GetVehicleType() == VEHICLE_TYPE_TRAIN) { OUTPUT_ONLY(LogDeletionFailReason("Is Train", bLogClearCarFromAreaFailures)); return false; } if(bCheckInterestingVehicles) { if(NetworkInterface::IsGameInProgress()) { if(NetworkInterface::IsVehicleInterestingToAnyPlayer(this)) { OUTPUT_ONLY(LogDeletionFailReason("Is Interesting to at least one player", bLogClearCarFromAreaFailures)); return false; } } else { // In single player games, the last vehicle the player has driven should not be removed. if (CVehiclePopulation::IsVehicleInteresting(this)) { OUTPUT_ONLY(LogDeletionFailReason("Is Interesting Vehicle", bLogClearCarFromAreaFailures)); return false; } } } // Make sure the vehicle doesn't contain mission peds or players for (s32 iSeat= 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++) { CPed* pPedInSeat = m_SeatManager.GetPedInSeat(iSeat); if (pPedInSeat) { const bool bAllowedToDeleteMissionPeds = !bTakeAccountOfMissionPopType; //make sure the peds the in vehicle can be deleted as well ... if ( !pPedInSeat->CanBeDeleted(true, bDoNetworkChecks, bAllowedToDeleteMissionPeds) || ((pPedInSeat->PopTypeIsMission()) && bTakeAccountOfMissionPopType) || (pPedInSeat->IsPlayer() && !bReturnCarsContainingAPlayer) ) { OUTPUT_ONLY(LogDeletionFailReason("Occupant Can't Be Deleted", bLogClearCarFromAreaFailures)); return FALSE; } } } // check any children vehicles can also be deleted if (HasDummyAttachmentChildren()) { for (int i=0; i < GetMaxNumDummyAttachmentChildren(); i++) { CVehicle* pDummyAttachedVeh = GetDummyAttachmentChild(i); if (pDummyAttachedVeh && !pDummyAttachedVeh->CanBeDeletedSpecial(bTakeAccountOfMissionPopType, bDoNetworkChecks, bCheckForPedsEnteringAndExiting, bCheckPedStates, bCheckInterestingVehicles, bAllowCopRemovalWithWantedLevel, bReturnCarsContainingAPlayer)) { OUTPUT_ONLY(LogDeletionFailReason("Dummy attachment child can't be deleted", bLogClearCarFromAreaFailures)); return FALSE; } } } const fwAttachmentEntityExtension *attachExt = GetAttachmentExtension(); if(attachExt) { CPhysical* pCurChild = static_cast(attachExt->GetChildAttachment()); while(pCurChild) { fwAttachmentEntityExtension &curChildAttachExt = pCurChild->GetAttachmentExtensionRef(); CPhysical* pNextChild = static_cast(curChildAttachExt.GetSiblingAttachment()); if(pCurChild->GetIsTypeVehicle()) { CVehicle* pChildVeh = static_cast(pCurChild); if (pChildVeh && !pChildVeh->CanBeDeletedSpecial(bTakeAccountOfMissionPopType, bDoNetworkChecks, bCheckForPedsEnteringAndExiting, bCheckPedStates, bCheckInterestingVehicles, bAllowCopRemovalWithWantedLevel, bReturnCarsContainingAPlayer)) { OUTPUT_ONLY(LogDeletionFailReason("Attachment child can't be deleted", bLogClearCarFromAreaFailures)); return FALSE; } } pCurChild = pNextChild; } } if (!bAllowCopRemovalWithWantedLevel) { if (!CVehiclePopulation::VehCanBeRemoved(this)) { OUTPUT_ONLY(LogDeletionFailReason("Cop Removal Not Allowed", bLogClearCarFromAreaFailures)); return false; } } // If this vehicle is in a cluster that we are currently trying to spawn, // don't allow it to be deleted. Doing so could lead to a situation where // we can never spawn the whole cluster because some cluster vehicles are // being destroyed before we have finished spawning all the other vehicles. if(m_nVehicleFlags.bIsInClusterBeingSpawned) { OUTPUT_ONLY(LogDeletionFailReason("In Scenario Cluster Being Spawned", bLogClearCarFromAreaFailures)); return false; } switch (PopTypeGet()) { case POPTYPE_UNKNOWN: Assertf(false, "The vehicle should have a valid pop type."); OUTPUT_ONLY(LogDeletionFailReason("Has Unknown Pop Type", bLogClearCarFromAreaFailures)); return FALSE; case POPTYPE_RANDOM_PERMANENT : // Planes, trains and helicopters OUTPUT_ONLY(LogDeletionFailReason("Has Random Permanent Pop Type", bLogClearCarFromAreaFailures)); return FALSE; case POPTYPE_RANDOM_PARKED : // Purposeful fall through... case POPTYPE_RANDOM_PATROL : // Purposeful fall through... case POPTYPE_RANDOM_SCENARIO :// Purposeful fall through... case POPTYPE_RANDOM_AMBIENT : // This vehicle was created by the code and can be deleted return TRUE; case POPTYPE_PERMANENT : OUTPUT_ONLY(LogDeletionFailReason("Has Permanent Pop Type", bLogClearCarFromAreaFailures)); return FALSE; case POPTYPE_MISSION : // Mission created vehicles should exist for the duration #if !__NO_OUTPUT if(bTakeAccountOfMissionPopType) { OUTPUT_ONLY(LogDeletionFailReason("Has Mission Pop Type", bLogClearCarFromAreaFailures)); } #endif // !__NO_OUTPUT return !bTakeAccountOfMissionPopType;// of the mission case POPTYPE_REPLAY: OUTPUT_ONLY(LogDeletionFailReason("Has Replay Pop Type", bLogClearCarFromAreaFailures)); return FALSE; case POPTYPE_TOOL: case POPTYPE_CACHE: OUTPUT_ONLY(LogDeletionFailReason("Has Tool/Cache Pop Type", bLogClearCarFromAreaFailures)); return FALSE; case NUM_POPTYPES : Assert(0); } Assert(0); return TRUE; } //------------------------------------------------------------------------- // Population management. //------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : DelayedRemovalTimeReset // PURPOSE : Resets the delayed removal time, to make sure this veh // isn't culled immediately. // PARAMETERS : None // RETURNS : Nothing ///////////////////////////////////////////////////////////////////////////////// void CVehicle::DelayedRemovalTimeReset(u32 extraTimeMs) { // Store the current time. m_delayedRemovalResetTimeMs = fwTimer::GetTimeInMilliseconds(); // Set the removal delay amount to make sure that even if they go // out of range or out of view that they don't get removed for a // little while. m_delayedRemovalAmountMs = fwRandom::GetRandomNumberInRange( CVehiclePopulation::GetOffscreenRemovalDelayMin(), CVehiclePopulation::GetOffscreenRemovalDelayMax()); m_delayedRemovalAmountMs += extraTimeMs; } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : DelayedRemovalTimeHasPassed // PURPOSE : Checks delayed removal time, to make sure this veh // isn't culled immediately. // PARAMETERS : removalTimeScale - Use to make things disappear more quickly // (1.0 is normal and 0.5 causes things to disappear twice as fast). // RETURNS : Whether or not it is now okay to remove this veh. ///////////////////////////////////////////////////////////////////////////////// bool CVehicle::DelayedRemovalTimeHasPassed(float removalTimeScale, u32 removalTimeExtraMs) const { Assert(removalTimeScale != 0.0f); const u32 scaledRemovalAmountMs = static_cast(removalTimeScale * static_cast(m_delayedRemovalAmountMs)); const u32 scaledRemovalTimeMs = m_delayedRemovalResetTimeMs + scaledRemovalAmountMs + removalTimeExtraMs; const u32 currentTimeMs = fwTimer::GetTimeInMilliseconds(); const bool hasWrapped = currentTimeMs < m_delayedRemovalResetTimeMs; const bool hasPassed = currentTimeMs > scaledRemovalTimeMs; return (hasWrapped || hasPassed); } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : DelayedRemovalTimeGetPortion // PURPOSE : Checks delayed removal time, and returns a value going from 1.0f // down to zero depending on how close to being removed we are. // PARAMETERS : None // RETURNS : What portion of the max removal times remains. ///////////////////////////////////////////////////////////////////////////////// float CVehicle::DelayedRemovalTimeGetPortion(float removalTimeScale) const { Assert(removalTimeScale != 0.0f); const u32 scaledRemovalAmountMs = static_cast(removalTimeScale * static_cast(m_delayedRemovalAmountMs)); const u32 scaledRemovalTimeMs = m_delayedRemovalResetTimeMs + scaledRemovalAmountMs; const u32 currentTimeMs = fwTimer::GetTimeInMilliseconds(); const bool hasWrapped = currentTimeMs < m_delayedRemovalResetTimeMs; const bool hasPassed = currentTimeMs > scaledRemovalTimeMs; if(hasWrapped || hasPassed) { return 1.0f; } else { const u32 timeElapsedMs = currentTimeMs - m_delayedRemovalResetTimeMs; float portion = static_cast(timeElapsedMs) / static_cast(scaledRemovalAmountMs); return rage::Clamp(portion, 0.0f, 1.0f); } } bool CVehicle::OffScreenRemovalTimeHasPassed(s32& timeRemaining, float removalTimeScale) const { s32 dt = fwTimer::GetTimeInMilliseconds() - GetTimeLastVisible(); s32 vehicleRemovalDelayMin = static_cast(CVehiclePopulation::GetOffscreenRemovalDelayMin() * removalTimeScale); // If we're only two max frames away, call it good. vehicleRemovalDelayMin = Max(0, vehicleRemovalDelayMin - static_cast(fwTimer::GetMaximumFrameTime() * 1000.0f * 2.0f)); timeRemaining = vehicleRemovalDelayMin - dt; return timeRemaining <= 0; } //------------------------------------------------------------------------- // Dummy conversion management. //------------------------------------------------------------------------- //------------------------------------------------------------------------- // FUNCTION : GetCanMakeIntoDummy // PURPOSE : Lets us know if the vehicle is allowed to change into a dummy vehicle. // RETURNS : Whether or not the vehicle can convert. //------------------------------------------------------------------------- bool CVehicle::GetCanMakeIntoDummy(eVehicleDummyMode desiredDummyMode) { //if a script has set this to not use collision then don't mess with it by dummying or un-dummying. if(IsProtectedBaseFlagSet(fwEntity::USES_COLLISION) == false) { return false; } // B*1814876: Allow scripts to prevent certain vehicles from switching to superdummy. if(m_nVehicleFlags.bDisableSuperDummy) return false; // if we already tested positive, return true if(m_nVehicleFlags.uAllowedDummyConversion && m_nVehicleFlags.uAllowedDummyConversion == (u8)desiredDummyMode) return true; // we already did this test and passed (this frame) // Check for missing paths first to ensure bNoDummyPathAvailable is set if there is no path. m_nVehicleFlags.bNoDummyPathAvailable = false; // if (desiredDummyMode == VDM_DUMMY && (GetAttachedTrailer() || GetDummyAttachmentChild(0) && GetDummyAttachmentChild(0)->InheritsFromTrailer())) // { // BANK_ONLY(SetNonConversionReason("Cabs with trailers can't use DUMMY mode, only REAL/SUPERDUMMY");) // return false; // } const bool bDummyParentIsTrailer = m_nVehicleFlags.bHasParentVehicle && GetDummyAttachmentParent() && GetDummyAttachmentParent()->InheritsFromTrailer(); const bool bRealParentIsTrailer = m_nVehicleFlags.bHasParentVehicle && CVehicle::IsEntityAttachedToTrailer(this); // Cars that have a recording playing back on them can be turned into dummies. // TODO : as an optimisation vehicles should have a reset flag to indicate whether a car recording is active for them bool bCarRecordingActive = CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this); #if !__FINAL bCarRecordingActive |= CVehicleRecordingMgr::IsCarBeingRecorded(this); #endif // Check if this is a parked vehicle which we should perhaps allow to be super-dummy. bool bParkedVehSuperDummy = false; if(CVehicleAILodManager::ms_bUseSuperDummyLodForParked && ConsiderParkedForLodPurposes()) { if(desiredDummyMode == VDM_SUPERDUMMY) { if(!m_nVehicleFlags.bMayBecomeParkedSuperDummy) { BANK_ONLY(SetNonConversionReason("Parked car not allowed to superdummy, may have been activated.");) return false; } if(!GetIsStatic()) { BANK_ONLY(SetNonConversionReason("Parked car not allowed to superdummy, currently activated.");) return false; } bParkedVehSuperDummy = true; // Not sure if we should set this or not - we don't do it for trailers etc that // also bypass the route check, and things seem happy without setting it. // m_nVehicleFlags.bNoDummyPathAvailable } else { BANK_ONLY(SetNonConversionReason("No non-super dummy for parked vehicles.");) m_nVehicleFlags.bNoDummyPathAvailable = true; // Not exactly why we failed, but consistent with how they worked before. return false; } } // For super-dummys we need all wheels to be intact to place on the road. if(desiredDummyMode == VDM_SUPERDUMMY) { for(int w=0; wGetTyreHealth() <= 0.0f) { BANK_ONLY(SetNonConversionReason("Super-dummy requires healthy tyres")); return false; } } } // For all vehicles except trailers, those riding on trailers and those following the recording, we must have a nodelist & valid nodes if(!bCarRecordingActive && !InheritsFromTrailer() && !bRealParentIsTrailer && !bDummyParentIsTrailer && !bParkedVehSuperDummy) { if (!IsNetworkClone()) { const CVehicleFollowRouteHelper* pFollowRouteHelper = GetIntelligence()->GetFollowRouteHelper(); if (!pFollowRouteHelper) { BANK_ONLY(SetNonConversionReason("No valid follow route");) m_nVehicleFlags.bNoDummyPathAvailable = true; return false; } } else { CVehicleNodeList * pNodeList = GetIntelligence()->GetNodeList(); if( pNodeList == NULL ) { BANK_ONLY(SetNonConversionReason("No valid node list");) m_nVehicleFlags.bNoDummyPathAvailable = true; return false; } s32 iTargetNode = pNodeList->GetTargetNodeIndex(); s32 iOldNode = iTargetNode - 1; if(iTargetNode == 0 || pNodeList->GetPathNodeAddr(iOldNode).IsEmpty() || pNodeList->GetPathNodeAddr(iTargetNode).IsEmpty() ) { BANK_ONLY(SetNonConversionReason("No valid node list");) m_nVehicleFlags.bNoDummyPathAvailable = true; return false; } } } if(ContainsLocalPlayer()) { BANK_ONLY(SetNonConversionReason("The local player's vehicle cannot become a dummy vehicle");) return false; } if(IsNetworkClone() && GetDriver() && GetDriver()->IsPlayer()) { BANK_ONLY(SetNonConversionReason("Network clone vehicle driven by remote player");) return false; } if(!m_nVehicleFlags.bCanMakeIntoDummyVehicle || m_nVehicleFlags.bPreventBeingDummyThisFrame) { BANK_ONLY(SetNonConversionReason("Vehicle flags disallow conversion");) return false; } if(desiredDummyMode == VDM_SUPERDUMMY) { if(!GetDummyInst() && !HasDummyBound()) { BANK_ONLY(SetNonConversionReason("Super-dummy requires a dummy inst or a dummy bound");) return false; } if(m_nVehicleFlags.bPreventBeingSuperDummyThisFrame) { BANK_ONLY(SetNonConversionReason("Super-dummy prevented this frame");) return false; } } #if __BANK if (IsNetworkClone() && !NetworkDebug::ArePhysicsOptimisationsEnabled()) { BANK_ONLY(SetNonConversionReason("Network clones can not become dummies");) return false; } #endif // If we are are testing for conversion to SUPERDUMMY or DUMMY and aren't already in one of those, // and aren't attached to a trailer and aren't an attached trailer, then make sure we can find a // reasonably close road path to use by the dummy car (since they use paths not ground collision). const eVehicleDummyMode currentDummyMode = GetVehicleAiLod().GetDummyMode(); bool bSkipPathTest = (desiredDummyMode != VDM_DUMMY && desiredDummyMode != VDM_SUPERDUMMY) || currentDummyMode==VDM_DUMMY || currentDummyMode==VDM_SUPERDUMMY || bDummyParentIsTrailer || bRealParentIsTrailer || bParkedVehSuperDummy || bCarRecordingActive || (InheritsFromTrailer() && m_nVehicleFlags.bHasParentVehicle); if(!bSkipPathTest) { Vec3V vConstraintPos; float fConstraintMaxLength; bool bStrictPathDist = m_nVehicleFlags.bLodStrictRoadProximityTest; const bool validConstraintFound = CVirtualRoad::CalculatePathInfo(*this,true,bStrictPathDist,vConstraintPos,fConstraintMaxLength, true); if(!validConstraintFound) { // We can't become a dummy when we can't find a valid path to use. BANK_ONLY(SetNonConversionReason("No path constraint found");) return false; } // Make sure that if we apply a stay on roads constraint nothing crazy will happen. const bool bUseConstraints = (desiredDummyMode == VDM_DUMMY && (CVehicleAILodManager::GetDummyConstraintMode()==CVehicleAILodManager::DCM_PhysicalDistance || CVehicleAILodManager::GetDummyConstraintMode()==CVehicleAILodManager::DCM_PhysicalDistanceAndRotation)) || (desiredDummyMode == VDM_SUPERDUMMY && (CVehicleAILodManager::GetSuperDummyConstraintMode()==CVehicleAILodManager::DCM_PhysicalDistance)); if(CVehicleAILodManager::ms_convertEnforceDummyMustBeNearPaths || bUseConstraints) { if(GetIsLandVehicle() && !IsNetworkClone()) { //use strict road restraints, don't want dummies driving through pavements const Vector3 vToRoadCenter = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) - RCC_VECTOR3(vConstraintPos); if(vToRoadCenter.Mag2() > fConstraintMaxLength*fConstraintMaxLength) { BANK_ONLY(SetNonConversionReason("Too far from the stay-on-road constraint");) return false; } } else { // Use an ellipsoidal distance measure here to put more weight on vertical distances. const Vector3 vToRoadCenter = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) - RCC_VECTOR3(vConstraintPos); const float fModdedXDelta = vToRoadCenter.x; const float fModdedYDelta = vToRoadCenter.y; const float fModdedZDelta = vToRoadCenter.z * ((vToRoadCenter.z > 0.0f)?(CVehicleAILodManager::ms_dummyMaxDistFromRoadFromAboveMult):(CVehicleAILodManager::ms_dummyMaxDistFromRoadFromBelowMult)); const Vector3 vEllipsoidalDeltaToRoadCenter(fModdedXDelta, fModdedYDelta, fModdedZDelta); const float fDistToConstraintCenter2 = vEllipsoidalDeltaToRoadCenter.Mag2(); const float fConstraintTooFarDist2 = square(fConstraintMaxLength * (bStrictPathDist ? 1.0f : CVehicleAILodManager::ms_dummyMaxDistFromRoadsEdgeMult)); if(fDistToConstraintCenter2 > fConstraintTooFarDist2) { BANK_ONLY(SetNonConversionReason("Too far from the stay-on-road constraint");) return false; } } } } if (!NetworkInterface::CanMakeVehicleIntoDummy(this)) { BANK_ONLY(SetNonConversionReason("Prevented by network code");) return false; } BANK_ONLY(SetNonConversionReason("GetCanMakeIntoDummy success");) // Store that we can use this dummy mode m_nVehicleFlags.uAllowedDummyConversion = (u8)desiredDummyMode; return true; } bool CVehicle::UsesDummyPhysics(const eVehicleDummyMode vdm) const { if(!GetDummyInst() && !HasDummyBound()) return false; switch(vdm) { case VDM_REAL: return false; case VDM_DUMMY: return true; case VDM_SUPERDUMMY: return true; default: Assert(false); return false; } } void CVehicle::RemovePhysics() { // Detach from the parent vehicle before calling the base class version (which will not properly detach from towing vehicle) DetachFromParentVehicleGadget(); CPhysical::RemovePhysics(); } bool CVehicle::ShouldAlwaysTryToBeReal() const { if(IsNetworkClone()) { // If the parked super dummy car is activated by collision or something, we should try to switch to real physics to prevent it from clipping through ground or other objects if(ConsiderParkedForLodPurposes() && !GetIsStatic()) { return true; } // The clones shouldn't switch to real unless being told to. return false; } //Mission vehicles should be real. if(PopTypeIsMission() || m_nVehicleFlags.bPreventBeingDummyUnlessCollisionLoadedThisFrame || m_nVehicleFlags.bPreventBeingDummyThisFrame || InheritsFromPlane() || IsOnItsSide() || IsUpsideDown() || (GetDriver() && GetDriver()->IsLawEnforcementPed() && CGameWorld::FindLocalPlayerWanted() && CGameWorld::FindLocalPlayerWanted()->GetWantedLevel() > WANTED_CLEAN) ) { return true; } // Respect bTasksAllowDummying, but super-dummy parked vehicles are excepted. if(!m_nVehicleFlags.bTasksAllowDummying && !ConsiderParkedForLodPurposes()) { return true; } // If the parked super dummy car is activated by collision or something, we should try to switch to real physics to prevent it from clipping through ground or other objects if(ConsiderParkedForLodPurposes() && !GetIsStatic()) { return true; } return false; } bool CVehicle::ProcessHaveDummyConstraintsChanged(ScalarV exceedLimit, bool failOnNoConstraints) const { if (ConsiderParkedForLodPurposes()) { return false; } Vec3V vConstraintPos; float fConstraintMaxLength; bool bStrictPathDist = m_nVehicleFlags.bLodStrictRoadProximityTest; const bool validConstraintFound = CVirtualRoad::CalculatePathInfo(*this,true,bStrictPathDist,vConstraintPos,fConstraintMaxLength, true); if (!validConstraintFound) { return failOnNoConstraints; } const ScalarV sfConstraintMaxLength(fConstraintMaxLength); const ScalarV sfMaxDistFromConstraintPos = sfConstraintMaxLength + exceedLimit; const ScalarV fMaxDistFromConstraintPosSquared = sfMaxDistFromConstraintPos * sfMaxDistFromConstraintPos; return IsGreaterThanAll(DistSquared(GetVehiclePosition(), vConstraintPos), fMaxDistFromConstraintPosSquared) != 0; } eVehicleDummyMode CVehicle::ProcessOverriddenDummyMode(eVehicleDummyMode desiredDummyMode) { bool zeroVelocities = false; eVehicleDummyMode overriddenMode = GetOverriddenDummyMode(desiredDummyMode, zeroVelocities); eVehicleDummyMode returnMode = desiredDummyMode; if (overriddenMode != returnMode && overriddenMode == VDM_SUPERDUMMY && !GetCanMakeIntoDummy(overriddenMode)) { overriddenMode = VDM_DUMMY; } if(overriddenMode != returnMode && (overriddenMode == VDM_REAL || GetCanMakeIntoDummy(overriddenMode))) { returnMode = overriddenMode; } // Allow dev options to override desired mode #if __BANK eVehicleDummyMode devOverriddenMode = CVehicleAILodManager::DevOverrideLod(returnMode); if(devOverriddenMode != returnMode && (devOverriddenMode == VDM_REAL || GetCanMakeIntoDummy(devOverriddenMode))) { returnMode = devOverriddenMode; } #endif // Make sure the car isn't falling down or spinning if(returnMode != VDM_REAL && zeroVelocities) { ResetSuspension(); Vector3 vSpeed = GetVelocity(); vSpeed.z = 0.0f; SetVelocity(vSpeed); SetAngVelocity(Vector3(0.0f, 0.0f, 0.0f)); #if __BANK CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) Zeroing velocities\n", AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityIsCloneStringSafe(this)); #endif } return returnMode; } //------------------------------------------------------------------------- // FUNCTION : GetOverriddenDummyMode // PURPOSE : Some vehicles can dictate what dummy mode they should be in // PARAMETERS : desiredDummyMode - the desired mode chosen by the vehicle // lod manager. // RETURNS : The overridden dummy mode //------------------------------------------------------------------------- bool CVehicle::sm_bCanForceCarRecordingToDummy = false; eVehicleDummyMode CVehicle::GetOverriddenDummyMode(eVehicleDummyMode desiredDummyMode, bool &zeroVelocitiesOut) { // The box streamer, used below, is not threadsafe (g_StaticBoundsStore.GetBoxStreamer()) AI_DEFERRED_TASKS_BOXSTREAMER_LOCK eVehicleDummyMode overriddenMode = desiredDummyMode; // Bikes that are moving slowly are not allowed to use superdummy, since it's too noticeable // that they don't lean properly. if(desiredDummyMode == VDM_SUPERDUMMY && CVehicleAILodManager::ms_bDisableSuperDummyForSlowBikes && InheritsFromBike()) { float thresholdSpeed = CVehicleAILodManager::ms_fDisableSuperDummyForSlowBikesSpeed; if(!IsSuperDummy()) { thresholdSpeed += 1.0f; // MAGIC! Use some hysteresis so we don't try to switch back and forth. } if(GetAiXYSpeed() < thresholdSpeed) { overriddenMode = VDM_DUMMY; } } //Check if the vehicle should be real. bool bDoWheelCheck = GetNumWheels() > 0 && !InheritsFromHeli() && !InheritsFromPlane(); // If we just spawned, the HasContactWheels() check is probably not going to be reliable. // We probably just need to skip this for one frame, but may as well do a couple more. if(fwTimer::GetTimeInMilliseconds() - m_TimeOfCreation <= 100) { bDoWheelCheck = false; } bool constraintsExceeded = false; if(IsNetworkClone() && (desiredDummyMode == VDM_DUMMY || desiredDummyMode == VDM_SUPERDUMMY) && !InheritsFromTrailer() && !m_nVehicleFlags.bHasParentVehicle) { //clones don't have physical constraints as they mess with interpolation //however, if we've exceeded what would be our constraints then we really should be real constraintsExceeded = ProcessHaveDummyConstraintsChanged(ScalarV(V_TEN), false); } static dev_u32 s_nMSToRemainRealAfterCollisionWithMissionVeh = 3000; const bool bPreventBeingDummyDueToCollisionWMissionVeh = GetIntelligence()->m_uTimeLastTouchedRealMissionVehicleWhileDummy != 0 && fwTimer::GetTimeInMilliseconds() < GetIntelligence()->m_uTimeLastTouchedRealMissionVehicleWhileDummy + s_nMSToRemainRealAfterCollisionWithMissionVeh; const bool bLaunchedIntoAirOverride = NetworkInterface::IsSuperDummyStillLaunchedIntoAir(this); const bool bWheelCheckFailed = (bDoWheelCheck && (!HasContactWheels() || IsUpsideDown())); const bool bShouldBeReal = ShouldAlwaysTryToBeReal() || constraintsExceeded || bPreventBeingDummyDueToCollisionWMissionVeh || bWheelCheckFailed || bLaunchedIntoAirOverride; if (desiredDummyMode == VDM_REAL) { bool bHasCollision = true; if(!IsNetworkClone()) { bHasCollision = HasCollisionLoadedAroundVehicle(); } if (!bHasCollision) { // If a dummy path is available, then fall back to VDM_SUPERDUMMY. If no path is available, // then allow the vehicle to remain real but it's physics will be fixed to not fall. if(!m_nVehicleFlags.bNoDummyPathAvailable) { // super dummy when the vehicle is off the collidable map overriddenMode = VDM_SUPERDUMMY; // Make sure the car isn't falling down or spinning if (!IsDummy()) { zeroVelocitiesOut = true; } } } else { if (CVehicleAILodManager::ms_bConvertExteriorVehsToDummyWhenInInterior && CPedPopulation::GetPopulationControlPointInfo().m_locationInfo!=LOCATION_EXTERIOR && !GetIsInInterior() && !bShouldBeReal && !m_nVehicleFlags.bNoDummyPathAvailable) { // convert vehicles outside to dummies when in interior overriddenMode = VDM_DUMMY; } } } else { //Check if the vehicle should be real. if(bShouldBeReal) { //Check if there is collision around the vehicle. //If there is no collision and the vehicle is made real, they will fall through the map. if ( InheritsFromPlane() ) { // prevent wrecked airplane stuck in the air if(GetStatus() == STATUS_WRECKED && IsInAir()) { overriddenMode = VDM_REAL; } // check the aabb for planes // some planes are much bigger than 8m tuning for hasloadedaboutpos spdAABB box; GetAABB(box); if(g_StaticBoundsStore.GetBoxStreamer().HasLoadedWithinAABB(box, fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER)) { overriddenMode = VDM_REAL; } } else if (!IsNetworkClone()) { if(HasCollisionLoadedAroundVehicle()) { overriddenMode = VDM_REAL; } } else { //allow to become real if we've got collision, not doing wheel checks or we've failed our wheel check if(HasCollisionLoadedAroundVehicle() || !bDoWheelCheck || bWheelCheckFailed || bLaunchedIntoAirOverride) { overriddenMode = VDM_REAL; } } } } bool bForceCarRecordingToDummy = CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this) && !CVehicleRecordingMgr::IsPlaybackSwitchedToAiForCar(this); if(bForceCarRecordingToDummy) { s32 slot = CVehicleRecordingMgr::GetPlaybackSlot(this); if( slot != -1 && (CVehicleRecordingMgr::GetVehicleRecordingFlags(slot)&CVehicleRecordingMgr::VRF_ConvertToAIOnImpact_AnyVehicle)) { bForceCarRecordingToDummy = false; } else if(GetAttachParentVehicle() && (InheritsFromTrailer() || IsEntityAttachedToTrailer(this))) { bForceCarRecordingToDummy = false; } else if(IsColliderArticulated()) { bForceCarRecordingToDummy = false; } } #if !__FINAL bForceCarRecordingToDummy = bForceCarRecordingToDummy && !CVehicleRecordingMgr::IsCarBeingRecorded(this); #endif if(sm_bCanForceCarRecordingToDummy && bForceCarRecordingToDummy && GetCanMakeIntoDummy(GetVehicleAiLod().GetDummyMode())) { overriddenMode = VDM_SUPERDUMMY; } // force all hidden vehicles to superdummy during a network cutscene if (NetworkInterface::IsInMPCutscene() && !GetIsVisible()) { overriddenMode = VDM_SUPERDUMMY; } return overriddenMode; } bool CVehicle::TestForDummyInstClearance() const { // Check if the dummy instance (which is larger than the regular bound) will intersect with anything. int iNumExceptions = 0; const int kMaxExceptions = 4 + MAX_CARGO_VEHICLES; atRangeArray ppExceptions; // Gather up the collision exceptions const bool bIsEntityAttachedToTrailerParent = IsEntityAttachedToTrailer(this); const bool bIsTrailerOrAttachedToTrailer = InheritsFromTrailer() || bIsEntityAttachedToTrailerParent; // Exclude any attached trailer, and any cars on top of it // If we are a trailer, only exclude the cars on it const CTrailer* pAttachedTrailer = InheritsFromTrailer() ? (const CTrailer*)this : GetAttachedTrailer(); //if we're the child of a trailer, set pAttachedTrailer //to it so we can exclude all our siblings if (!pAttachedTrailer && bIsEntityAttachedToTrailerParent) { pAttachedTrailer = (CTrailer*)GetAttachParent(); } if(pAttachedTrailer) { //don't need to exclude ourselves //also don't exclude the trailer here if it's //also our parent. that will get excluded later if (this != pAttachedTrailer && pAttachedTrailer != GetAttachParent() && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = pAttachedTrailer; } //also exclude anything riding on the trailer if (pAttachedTrailer->HasCargoVehicles()) { for (int i = 0; i < MAX_CARGO_VEHICLES; i++) { CVehicle* pCargoVeh = pAttachedTrailer->GetCargoVehicle(i); if (pCargoVeh && this != pCargoVeh && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = pCargoVeh; } } } else if (pAttachedTrailer->GetChildAttachment()) { //this is a special case for the tank, which they wanted to be //attached physically CPhysical* pNextChild = (CPhysical*)pAttachedTrailer->GetChildAttachment(); while(pNextChild) { fwAttachmentEntityExtension *nextChildAttachExt = pNextChild->GetAttachmentExtension(); Assertf(nextChildAttachExt, "Attached child doesn't have an attachment extension?"); //const bool bIsTrailer = pNextChild->GetIsTypeVehicle() && static_cast(pNextChild)->InheritsFromTrailer(); if(nextChildAttachExt->GetAttachState() == ATTACH_STATE_PHYSICAL && pNextChild != this && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = pNextChild; } pNextChild = (CPhysical*)nextChildAttachExt->GetSiblingAttachment(); } } } // For trailers, exclude any attached parent if(bIsTrailerOrAttachedToTrailer && GetAttachParent() && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = (CEntity*)GetAttachParent(); } //for cars attached to trailers, exclude the cab as well if (bIsEntityAttachedToTrailerParent) { //if we're attached to a trailer, exclude its parent (the cab) as well if (GetAttachParent() && GetAttachParent()->GetAttachParent() && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = (CEntity*)(GetAttachParent()->GetAttachParent()); } } //now exclude our dummy parent, our dummy children, and all of their children //don't recurse any further than that though const CVehicle* pDummyAttachParent = GetDummyAttachmentParent(); if (pDummyAttachParent && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = (CEntity*)pDummyAttachParent; //if our parent has a parent, exclude that too. //this can happen if we are a car on the back of a trailer, //which is attached to a cab if (pDummyAttachParent->GetDummyAttachmentParent() && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = (CEntity*)(GetDummyAttachmentParent()->GetDummyAttachmentParent()); } //exclude all our dummy siblings as well if (pDummyAttachParent->HasDummyAttachmentChildren()) { for (int i = 0; i < pDummyAttachParent->GetMaxNumDummyAttachmentChildren(); i++) { const CVehicle* pThisChild = pDummyAttachParent->GetDummyAttachmentChild(i); if (pThisChild && pThisChild != this && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = pThisChild; } } } } if(HasDummyAttachmentChildren()) { for (int i = 0; i < m_DummyAttachmentChildren.GetMaxCount() && iNumExceptions < kMaxExceptions; i++) { const CVehicle* pThisChild = GetDummyAttachmentChild(i); ppExceptions[iNumExceptions++] = pThisChild; } } // Exclude gadget entities. if (iNumExceptions < kMaxExceptions) { int iGadgetEntitiesFound = GetGadgetEntities(kMaxExceptions-iNumExceptions,&ppExceptions[iNumExceptions]); iNumExceptions += iGadgetEntitiesFound; } // B*2062127: Exclude objects attached to the vehicle. // We had a case where a truck had a crate attached to the back and it was failing this test and preventing it from moving when there was no collision loaded around it. if(GetChildAttachment()) { CPhysical* pNextChild = (CPhysical*)GetChildAttachment(); while(pNextChild) { fwAttachmentEntityExtension *nextChildAttachExt = pNextChild->GetAttachmentExtension(); if(nextChildAttachExt) { if(nextChildAttachExt->GetAttachState() == ATTACH_STATE_BASIC && pNextChild != this && iNumExceptions < kMaxExceptions) { ppExceptions[iNumExceptions++] = pNextChild; } pNextChild = (CPhysical*)nextChildAttachExt->GetSiblingAttachment(); } else pNextChild = NULL; } } s32 nTestTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE; //check how far we are above virtual road surface //if a fair distance than check position we would be when converted instead, and also check ground collision const CVehicleFollowRouteHelper* pFollowRoute = GetIntelligence()->GetFollowRouteHelper(); if(pFollowRoute && GetIsLandVehicle() && !IsNetworkClone() && fwTimer::GetTimeInMilliseconds() - m_TimeOfCreation > 500) { Vec3V vTargetPosition; if(pFollowRoute->ComputeOnRoadPosition(*this, vTargetPosition, true)) { Mat34V matCurrent = GetMatrix(); const Vec3V roadToCurrentDeltaV = Subtract(matCurrent.GetCol3(), vTargetPosition); static float s_fZLimit = 0.15f; //roughly pavement level if(IsGreaterThanAll(roadToCurrentDeltaV.GetZ(), ScalarV(s_fZLimit))) { vTargetPosition.SetZ(vTargetPosition.GetZ() + ScalarV(0.02f)); //add on a tiny amount as ComputeOnRoadPosition returns point on road matCurrent.SetCol3(vTargetPosition); Matrix34 matTemp = MAT34V_TO_MATRIX34(matCurrent); nTestTypeFlags |= ArchetypeFlags::GTA_MAP_TYPE_MOVER; float dIntersectDepth = 0.0f; bool bIntesect = DoBoundingBoxIntersectionTest(nTestTypeFlags , iNumExceptions, &ppExceptions[0], false, &dIntersectDepth, &matTemp ); #if __BANK static bool s_bDisplayPredictedDummyPlacementIntesection = false; if(bIntesect && s_bDisplayPredictedDummyPlacementIntesection) { grcDebugDraw::Sphere(GetTransform().GetPosition(), 3.0f, Color_red, false, 500); } #endif return !bIntesect; } } } return !DoBoundingBoxIntersectionTest(nTestTypeFlags, iNumExceptions,&ppExceptions[0], false); } //------------------------------------------------------------------------- // FUNCTION : TryToMakeIntoDummy // PURPOSE : Attempts to convert vehicle into dummy mode. //------------------------------------------------------------------------- bool CVehicle::TryToMakeIntoDummy(eVehicleDummyMode dummyMode, bool bSkipClearanceTest) { Assert(dummyMode != VDM_REAL); DEV_BREAK_IF_FOCUS( CVehicleAILodManager::ms_focusVehBreakOnAttemptedPopConversion, this ); if(dummyMode==VDM_DUMMY || dummyMode==VDM_SUPERDUMMY) { m_dummyConvertAttemptTimeMs = fwTimer::GetTimeInMilliseconds(); } const eVehicleDummyMode currentMode = GetVehicleAiLod().GetDummyMode(); if(currentMode == dummyMode) { return true; // don't need to do any work } if(!GetCanMakeIntoDummy(dummyMode)) { return false; } if(!bSkipClearanceTest && currentMode!=VDM_DUMMY && currentMode!=VDM_SUPERDUMMY) { if(!TestForDummyInstClearance()) { BANK_ONLY(SetNonConversionReason("Dummy-inst clearance test failed");) return false; } } if(dummyMode == VDM_SUPERDUMMY) { // For super-dummys we need all wheels to be intact to place on the road. for(int w=0; wGetTyreHealth() <= 0.0f) { BANK_ONLY(SetNonConversionReason("Super-dummy requires healthy tyres")); return false; } } } if(CTrailer* pTrailer = GetAttachedTrailer()) { // Trailers don't support VDM_DUMMY right now (ever?), so always try VDM_SUPERDUMMY eVehicleDummyMode eTrailerDummyMode = dummyMode; if(eTrailerDummyMode == VDM_DUMMY) { eTrailerDummyMode = VDM_SUPERDUMMY; } if(!pTrailer->TryToMakeIntoDummy(eTrailerDummyMode)) { BANK_ONLY(SetNonConversionReason("Attached trailer couldn't convert to required dummy mode!")); return false; } Assertf(pTrailer->IsDummy(), "Vehicle %s (dummyMode: %d), TryToMakeIntoDummy %d, trailer %s (dummyMode: %d) should be dummy here!" , GetLogName(), (int)GetVehicleAiLod().GetDummyMode() , (int)dummyMode , pTrailer->GetLogName(), (int)pTrailer->GetVehicleAiLod().GetDummyMode()); BANK_ONLY(if (pTrailer->IsDummy() == false) sysStack::PrintStackTrace();); } if (InheritsFromTrailer()) { CTrailer* pMeAsTrailer = (CTrailer*)this; //if the vehicles are cargo vehicles, go through this //interface so we maintain their order if (pMeAsTrailer->HasCargoVehicles()) { for (int i=0; i < MAX_CARGO_VEHICLES; i++) { CVehicle* pCargoVeh = pMeAsTrailer->GetCargoVehicle(i); if (pCargoVeh && !pCargoVeh->TryToMakeIntoDummy(dummyMode,true)) { BANK_ONLY(SetNonConversionReason("Attached cargo vehicle(s) couldn't convert to required dummy mode!")); return false; } } } else if (pMeAsTrailer->GetChildAttachment()) { CPhysical* pNextChild = (CPhysical*)pMeAsTrailer->GetChildAttachment(); while(pNextChild) { fwAttachmentEntityExtension *nextChildAttachExt = pNextChild->GetAttachmentExtension(); bool bRemovedAttachment = false; if(nextChildAttachExt && nextChildAttachExt->GetAttachState() == ATTACH_STATE_PHYSICAL && pNextChild->GetIsTypeVehicle()) { CVehicle* pChildVeh = (CVehicle*)pNextChild; if (!pChildVeh->TryToMakeIntoDummy(dummyMode,true)) { BANK_ONLY(SetNonConversionReason("Attached cargo vehicle(s) couldn't convert to required dummy mode!")); return false; } bRemovedAttachment = true; } if (bRemovedAttachment) { pNextChild = (CPhysical*)pMeAsTrailer->GetChildAttachment(); } else { pNextChild = (CPhysical*)nextChildAttachExt->GetSiblingAttachment(); } } } } DEV_BREAK_IF_FOCUS( CVehicleAILodManager::ms_focusVehBreakOnPopulationConversion, this ); // make sure the gears are set correctly SelectAppropriateGearForSpeed(); //if we're trying to convert to superdummy, //get rid of any constraint modifiers we have if (dummyMode == VDM_SUPERDUMMY) { ResetRealConversionFailedData(); } const eVehicleDummyMode prevDummyMode = m_vehicleAiLod.GetDummyMode(); switch(dummyMode) { case VDM_DUMMY: CVehicleAILodManager::SetLodFlag(this, CVehicleAILod::AL_LodDummy); CVehicleAILodManager::ClearLodFlag(this, CVehicleAILod::AL_LodSuperDummy); break; case VDM_SUPERDUMMY: CVehicleAILodManager::ClearLodFlag(this, CVehicleAILod::AL_LodDummy); CVehicleAILodManager::SetLodFlag(this, CVehicleAILod::AL_LodSuperDummy); break; case VDM_REAL: default: Assert(0); break; } // Store dummy attachments here - because conversion to dummy breaks attachments. // Trailers are always converted just before the cabs, so we must set up the links // both ways at this point before either of the two vehicles become dummy. CVehicle * pAttachParentVehicle = GetAttachParentVehicle(); if(InheritsFromTrailer() && pAttachParentVehicle) { Assert(pAttachParentVehicle->GetAttachedTrailer() == this); ((CTrailer*)this)->DetachFromParent(0); pAttachParentVehicle->DummyAttachChild(*this); } else if (!InheritsFromTrailer() && pAttachParentVehicle) { // We're a car attached to the back of a trailer, either through cargo car system or manually like the tank in convoy steal CTrailer* pParentTrailer = IsEntityAttachedToTrailer(this) ? static_cast(pAttachParentVehicle) : NULL; if (pParentTrailer) { DetachFromParent(0); pParentTrailer->DummyAttachChild(*this); } else { Errorf("Non-trailer attached to a non-trailer. What situation is this? (Mission veh? %d/%d)",PopTypeIsMission(),pAttachParentVehicle->PopTypeIsMission()); const char * pThisVehicleName = (m_pVehicleFragInst && m_pVehicleFragInst->GetArchetype()) ? m_pVehicleFragInst->GetArchetype()->GetFilename() : NULL; if(pThisVehicleName) { Errorf("This vehicle name '%s'",pThisVehicleName); } const char * pParentVehicleName = (pAttachParentVehicle->m_pVehicleFragInst && pAttachParentVehicle->m_pVehicleFragInst->GetArchetype()) ? pAttachParentVehicle->m_pVehicleFragInst->GetArchetype()->GetFilename() : NULL; if(pParentVehicleName) { Errorf("Parent vehicle name '%s'",pParentVehicleName); } } } // Update the LOD for the gadgets for( int i = 0; i < m_pVehicleGadgets.size(); i++) { m_pVehicleGadgets[i]->ChangeLod(*this,dummyMode); } switch( dummyMode ) { case VDM_DUMMY: ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry();) SwitchToDummyPhysics(prevDummyMode); break; case VDM_SUPERDUMMY: SwitchToSuperDummyPhysics(prevDummyMode); break; default: Assert(false); break; } #if __DEV const Vec3V vVehPos(GetTransform().GetPosition()); CVehiclePopulation::VecMapDisplayVehiclePopulationEvent(RCC_VECTOR3(vVehPos),CVehiclePopulation::VehPopConvertToDummy); #endif // We have converted to dummy, so we can forget this flag. //m_nVehicleFlags.bLodStrictRoadProximityTest = false; // If we were real and we are about to be dummy, set the time stamp to the past, to // allow for an attempt to convert to superdummy any time. The delay wouldn't do much // good in that case, just forcing us to stay in dummy for longer than desired. if(currentMode == VDM_REAL && dummyMode == VDM_DUMMY) { m_dummyConvertAttemptTimeMs -= CVehicleAILodManager::ms_iTimeBetweenConversionAttempts; } BANK_ONLY(SetNonConversionReason("TryToMakeIntoDummy success");) return true; } //------------------------------------------------------------------------- // FUNCTION : TryToMakeFromDummyIncludingParents // PURPOSE : Attempts to convert the vehicle out of dummy mode, handling case where this is a child //------------------------------------------------------------------------- bool CVehicle::TryToMakeFromDummyIncludingParents(const bool bSkipClearanceTest) { // TODO: Only handles trailers right now. Extend to other attached vehicles if useful. if(InheritsFromTrailer() && GetDummyAttachmentParent()) { return GetDummyAttachmentParent()->TryToMakeFromDummy(bSkipClearanceTest); } return TryToMakeFromDummy(bSkipClearanceTest); } bool CVehicle::HasCollisionLoadedAroundVehicle() { bool bBoundsNotLoaded = !g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(GetVehiclePosition(), fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER); if (!bBoundsNotLoaded) { const CTrailer* pChildTrailer = GetDummyAttachmentChild(0) && GetDummyAttachmentChild(0)->InheritsFromTrailer() ? static_cast(GetDummyAttachmentChild(0)) : GetAttachedTrailer(); bBoundsNotLoaded = pChildTrailer && !g_StaticBoundsStore.GetBoxStreamer().HasLoadedAboutPos(pChildTrailer->GetVehiclePosition(), fwBoxStreamerAsset::FLAG_STATICBOUNDS_MOVER); } return !bBoundsNotLoaded; } //------------------------------------------------------------------------- // FUNCTION : TryToMakeFromDummy // PURPOSE : This attempts to convert the vehicle seamlessly out of // dummy mode. It can fail for several reasons, make sure to // check the return value to know if the conversion succeeded // or not. // PARAMETERS : None // RETURNS : Whether or not the vehicle converted. //------------------------------------------------------------------------- bool CVehicle::TryToMakeFromDummy(const bool bSkipClearanceTest) { DEV_BREAK_IF_FOCUS( CVehicleAILodManager::ms_focusVehBreakOnAttemptedPopConversion, this ); Assert( m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodDummy) || m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy)); m_realConvertAttemptTimeMs = fwTimer::GetTimeInMilliseconds(); // Check if by converting whether we will intersect with anything important or not. static dev_bool doIntersectionTest = true; // Dont check on planes as their bounding boxes are huge and can't be pulled back onto the road anyway const bool bSkipDueToVehicleType = GetIsAircraft() || InheritsFromBoat() || InheritsFromBike(); bool bDoCollisionTest = doIntersectionTest && !bSkipClearanceTest && !bSkipDueToVehicleType; if(bDoCollisionTest) { // This shouldn't be necessary because the collision mesh of the vehicle is within the bounding box that the dummy uses for collision, // so it will never be intersecting anything if the bounding box isn't const s32 nTestTypeFlags = ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE; float dIntersectDepth = 0.0f; if (DoBoundingBoxIntersectionTest(nTestTypeFlags, 0, NULL, true, &dIntersectDepth)) { #if __BANK CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) Unable to convert vehicle to real: Intersecting physics at depth %.3f.\n", AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityIsCloneStringSafe(this), dIntersectDepth); #endif ProcessTimeStartedFailingRealConversion(); // We intersect something important so don't become real (and // then have to deal with the instantaneous collision problems // that would be created). DEV_ONLY(SetNonConversionReason("Would intersect something.")); //DEV_ONLY(SetNonConversionColour(Color32(1.0f, 0.0f, 0.0f, 1.0f))); return FALSE; } } #if __BANK else if(!bSkipDueToVehicleType) { //do the test anyway and log the results float dIntersectDepth = 0.0f; const s32 nTestTypeFlags = ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE; if (DoBoundingBoxIntersectionTest(nTestTypeFlags, 0, NULL, true, &dIntersectDepth)) { CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) (0x%p) Vehicle skipped intersection test whilst intersecting at depth %.3f.\n", AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityIsCloneStringSafe(this), this, dIntersectDepth); } } #endif //shouldn't become real if there's no collision around us (but allow mission vehicles and network clones to if they really want to) bool flyingAirCraft = (( InheritsFromPlane() || InheritsFromHeli() ) && IsInAir() ); if(!flyingAirCraft && !PopTypeIsMission() && !IsNetworkClone()) { if(!HasCollisionLoadedAroundVehicle()) { #if __BANK CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) (0x%p) Unable to convert vehicle to real: No collision.\n", AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityIsCloneStringSafe(this), this); #endif return false; } } #if __BANK else if(IsNetworkClone()) { if(!HasCollisionLoadedAroundVehicle()) { CAILogManager::GetLog().Log("DUMMY CONVERSION: %s (%s) (0x%p) Skipped collision test as was network dummy. No collision, but vehicle made real.\n", AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityIsCloneStringSafe(this), this); } } #endif ResetRealConversionFailedData(); // If we have an attached trailer then ensure we convert it first CTrailer * pTrailerToAttach = NULL; atRangeArray pCargoVehsToAttach; int iNumVehsToAttach = 0; CVehicle * pDummyAttachChild0 = GetDummyAttachmentChild(0); if( !InheritsFromTrailer() && pDummyAttachChild0 ) { if(!pDummyAttachChild0->CVehicle::TryToMakeFromDummy(bSkipClearanceTest)) { BANK_ONLY(SetNonConversionReason("Attached trailer couldn't convert to real!")); return false; } Assert(pDummyAttachChild0->InheritsFromTrailer()); pTrailerToAttach = static_cast(pDummyAttachChild0); } else if (HasDummyAttachmentChildren() && InheritsFromTrailer()) { for (int i = 0; i < m_DummyAttachmentChildren.GetMaxCount(); i++) { CVehicle * pDummyAttachChild = GetDummyAttachmentChild(i); if(pDummyAttachChild) { if(!pDummyAttachChild->CVehicle::TryToMakeFromDummy(bSkipClearanceTest)) { BANK_ONLY(SetNonConversionReason("Attached cargo vehicle couldn't convert to real!")); return false; } Assert(!pDummyAttachChild->InheritsFromTrailer()); pDummyAttachChild->EnableCollision(pDummyAttachChild->GetCurrentPhysicsInst(), pDummyAttachChild->m_nRealVehicleIncludeFlags); pCargoVehsToAttach[iNumVehsToAttach++] = pDummyAttachChild; } } } if(CVehicle * pDummyParent = GetDummyAttachmentParent()) { pDummyParent->DummyDetachChild(this); } if(HasDummyAttachmentChildren()) { DummyDetachAllChildren(); } DEV_BREAK_IF_FOCUS( CVehicleAILodManager::ms_focusVehBreakOnPopulationConversion, this ); // Make sure to force an update of the mission entity room hash key. // Because dummy vehicles keep moving through the world instead of freezing // (like non-dummy vehicles) when the room they are in unloads this vehicle's // mission room hash key may be out of date... if(GetScriptThatCreatedMe()) { UpdatePortalTracker(); } // make sure the gears are set correctly SelectAppropriateGearForSpeed(); const eVehicleDummyMode prevDummyMode = m_vehicleAiLod.GetDummyMode(); CVehicleAILodManager::ClearLodFlag(this, CVehicleAILod::AL_LodDummy); CVehicleAILodManager::ClearLodFlag(this, CVehicleAILod::AL_LodSuperDummy); ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry();) SwitchToFullFragmentPhysics(prevDummyMode); if(InheritsFromTrailer() || pTrailerToAttach) { //reset collision flags that are set when converting SetNoCollision(NULL, 0); } if(pTrailerToAttach) { // Prevent dummy trailers from intersecting their parent on conversion Vec3V vParentB = GetMatrix().GetCol1(); Vec3V vTrailerB = pTrailerToAttach->GetMatrix().GetCol1(); ScalarV sTrailerDotParentB = Dot(vTrailerB, vParentB); bool bWarp = false; bool bAttach = true; if(IsLessThanAll(sTrailerDotParentB, ScalarV(V_ZERO))) { if(GetIsVisibleInSomeViewportThisFrame()) { //Just detach if the trailer is visible and penetrating something if(!TestForDummyInstClearance()) { bAttach = false; SetNoCollision(pTrailerToAttach, NO_COLLISION_RESET_WHEN_NO_IMPACTS); } } else { //Offscreen we can warp the trailer to a safe position behind the truck bWarp = true; } } if(bAttach) { pTrailerToAttach->AttachToParentVehicle(this, bWarp, 1.0f, true); // Instantly raise the trailer legs. Moving these requires that the trailer // be activated, so we don't want to do this when it is a super-dummy. Later // we might want to do this at init time to prevent a pop in the leg position. pTrailerToAttach->RaiseTrailerLegsInstantly(); } } if (iNumVehsToAttach > 0) { for (int i = 0; i < iNumVehsToAttach; i++) { if (pCargoVehsToAttach[i]) { Assert(InheritsFromTrailer()); CTrailer* pMeAsTrailer = static_cast(this); const bool bIsCargoVeh = pMeAsTrailer->FindCargoVehicle(pCargoVehsToAttach[i]) >= 0; if (bIsCargoVeh) { Vector3 vOffset = pCargoVehsToAttach[i]->GetParentTrailerEntity2Offset(); Quaternion offsetRot = pCargoVehsToAttach[i]->GetParentTrailerEntityRotation(); pCargoVehsToAttach[i]->AttachToPhysicalBasic(this, (s16)this->GetBoneIndex(TRAILER_ATTACH), ATTACH_STATE_BASIC | ATTACH_FLAG_INITIAL_WARP | ATTACH_FLAG_COL_ON | ATTACH_FLAG_DELETE_WITH_PARENT, &vOffset, &offsetRot); } else { //attach it like a tank u32 nPhysicalAttachFlags = ATTACH_FLAG_POS_CONSTRAINT|ATTACH_FLAG_ACTIVATE_ON_DETACH; nPhysicalAttachFlags |= ATTACH_STATE_PHYSICAL; nPhysicalAttachFlags |= ATTACH_FLAG_ROT_CONSTRAINT; nPhysicalAttachFlags |= ATTACH_FLAG_INITIAL_WARP; dev_float invMassScaleA = 1.0f; dev_float invMassScaleB = 0.1f; float fPhysicalStrength = pCargoVehsToAttach[i]->GetParentTrailerAttachStrength(); Vector3 vecEntity1Offset = pCargoVehsToAttach[i]->GetParentTrailerEntity1Offset(); Vector3 vecEntity2Offset = pCargoVehsToAttach[i]->GetParentTrailerEntity2Offset(); Quaternion quatRotate = pCargoVehsToAttach[i]->GetParentTrailerEntityRotation(); pCargoVehsToAttach[i]->AttachToPhysicalUsingPhysics(this, 0, 0, nPhysicalAttachFlags, &vecEntity2Offset, &quatRotate, &vecEntity1Offset, fPhysicalStrength, false, invMassScaleA, invMassScaleB); } pCargoVehsToAttach[i]->SetNoCollision(NULL, 0); } } } // Update the LOD for the gadgets for( int i = 0; i < m_pVehicleGadgets.size(); i++) { m_pVehicleGadgets[i]->ChangeLod(*this,VDM_REAL); } #if __DEV const Vec3V vVehPos(GetTransform().GetPosition()); CVehiclePopulation::VecMapDisplayVehiclePopulationEvent(RCC_VECTOR3(vVehPos),CVehiclePopulation::VehPopConvertToReal); #endif return TRUE; } // void CVehicle::DisableWheelCollisions(bool bDisableCameraCollision) { phBound * pVehBound = (GetVehicleFragInst()->GetArchetype() ? GetVehicleFragInst()->GetArchetype()->GetBound() : NULL); phBoundComposite * pVehCompositeBound = (pVehBound && pVehBound->GetType()==phBound::COMPOSITE ? static_cast(pVehBound) : NULL); if(pVehCompositeBound) { if(!pVehCompositeBound->GetTypeAndIncludeFlags()) { pVehCompositeBound->AllocateTypeAndIncludeFlags(); } for(int nWheel=0; nWheelGetFragChild() >= 0) { pVehCompositeBound->SetIncludeFlags(GetWheel(nWheel)->GetFragChild(), bDisableCameraCollision ? 0 : ArchetypeFlags::GTA_CAMERA_TEST);// Only disable the first frag child(which is the swept bound), leave the camera on though } } } m_nVehicleFlags.bWheelsDisabled = true; } void CVehicle::SetupWheelCollisions(phBoundComposite * pVehCompositeBound) { // turn collision with everything back on for wheels // Also set up 2nd surface config for(int nWheel=0; nWheelGetBound(mainWheelComponentIndex)) { bound->SetForceCCD(); bound->SetUseCurrentInstsanceMatrixOnly(); } u32 nMainWheelIncludeFlags = ArchetypeFlags::GTA_WHEEL_INCLUDE_TYPES | ArchetypeFlags::GTA_FORKLIFT_FORKS_TYPE; // if we don't have any visible wheels make sure weapons don't hit them if( pHandling->GetSpecialFlightHandlingData() && pHandling->GetSpecialFlightHandlingData()->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) { nMainWheelIncludeFlags = nMainWheelIncludeFlags & ~ArchetypeFlags::GTA_BASIC_ATTACHMENT_AND_CAMERA_INCLUDE_TYPES; m_WheelsAreJustProbes = true; } //other bounds are just used to prevent vehicles and peds walking into the wheel and causing the wheels to pop up. for(int nWheelBoundIndex = 1; nWheelBoundIndex < MAX_WHEEL_BOUNDS_PER_WHEEL; ++nWheelBoundIndex) { int extraWheelComponentIndex = wheel.GetFragChild(nWheelBoundIndex); if(extraWheelComponentIndex != -1) { nMainWheelIncludeFlags &= ~ArchetypeFlags::GTA_FORKLIFT_FORKS_TYPE; // Only allow forklift collision on the main bound if there are no extra bounds u32 nExtraWheelIncludeFlags = ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_PROJECTILE_TYPE | ArchetypeFlags::GTA_FORKLIFT_FORKS_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE; // GTAV - b*2761792 - Add camera test to dump truck wheels. if( GetVehicleModelInfo()->GetModelNameHash() == MI_CAR_DUMP_TRUCK.GetName().GetHash() ) { nExtraWheelIncludeFlags = nExtraWheelIncludeFlags | ArchetypeFlags::GTA_CAMERA_TEST; } if(pHandling->hFlags & HF_EXT_WHEEL_BOUNDS_COL) { if(nWheelBoundIndex == WHEEL_CLIMB_BOUND) { nExtraWheelIncludeFlags = ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_CAMERA_TEST | ArchetypeFlags::GTA_WHEEL_TEST; } else { nExtraWheelIncludeFlags &= ~(ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_WHEEL_TEST); } } if (phBound* bound = pVehCompositeBound->GetBound(extraWheelComponentIndex)) { if(bound->GetType() != phBound::DISC)// if we have a geometry bound on the wheel, like on the dump allow this to hit objects. { Assert((pHandling->mFlags & MF_DOUBLE_REAR_WHEELS) || (pHandling->mFlags & MF_DOUBLE_FRONT_WHEELS));// only vehicles with double wheels should get here. nExtraWheelIncludeFlags |= ArchetypeFlags::GTA_OBJECT_TYPE; } } pVehCompositeBound->SetTypeFlags(extraWheelComponentIndex, ArchetypeFlags::GTA_WHEEL_TEST); pVehCompositeBound->SetIncludeFlags(extraWheelComponentIndex, nExtraWheelIncludeFlags); } } // Set the main wheel include flags after iterating over the extra bounds since that can change the flags // Fix: B*1756261: Set the wheel bounds to collide with peds when the car is on its side or upside down. phBoundComposite *pVehCompositeBound = static_cast(GetVehicleFragInst()->GetArchetype()->GetBound()); if( ( IsOnItsSide() || IsUpsideDown() )&& ( !pHandling->GetSpecialFlightHandlingData() || !( pHandling->GetSpecialFlightHandlingData()->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) ) ) { // Just set the wheels to be treated in the same way as the rest of the vehicle in this situation. pVehCompositeBound->SetTypeFlags(mainWheelComponentIndex, ArchetypeFlags::GTA_VEHICLE_TYPE); pVehCompositeBound->SetIncludeFlags(mainWheelComponentIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } else { pVehCompositeBound->SetTypeFlags(mainWheelComponentIndex, ArchetypeFlags::GTA_WHEEL_TEST ); pVehCompositeBound->SetIncludeFlags(mainWheelComponentIndex, nMainWheelIncludeFlags); } } } } void CVehicle::EnableWheelCollisions() { phBound * pVehBound = (GetVehicleFragInst()->GetArchetype() ? GetVehicleFragInst()->GetArchetype()->GetBound() : NULL); phBoundComposite * pVehCompositeBound = (pVehBound && pVehBound->GetType()==phBound::COMPOSITE ? static_cast(pVehBound) : NULL); if(pVehCompositeBound) { if(!pVehCompositeBound->GetTypeAndIncludeFlags()) { pVehCompositeBound->AllocateTypeAndIncludeFlags(); } SetupWheelCollisions(pVehCompositeBound); } m_nVehicleFlags.bWheelsDisabled = false; } void CVehicle::SetNoGravity(phInst *physInst, bool noGravity) { if(physInst) { physInst->SetInstFlag(phInst::FLAG_NO_GRAVITY, noGravity); phCollider *collider = GetCollider(); if(collider && collider->IsArticulated()) { physInst->SetInstFlag(phInst::FLAG_NO_GRAVITY_ON_ROOT_LINK, noGravity); } } } void CVehicle::SwitchToSuperDummyPhysics(const eVehicleDummyMode UNUSED_PARAM(prevMode)) { if(m_nVehicleFlags.bWheelsDisabled) { EnableWheelCollisions(); } for(int i=0; iGetDynamicFlags().SetFlag(WF_DUMMY); } Vector3 vCurrentVel = VEC3_ZERO; Vector3 vCurrentAngVel = VEC3_ZERO; if (GetDummyInst()) { if(AssertVerify(GetVehicleFragInst()->IsInLevel())) { u32 nTypeFlags = 0; u32 nIncludeFlags = 0; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } if(GetDummyInst()->IsInLevel()) { u32 nTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetDummyInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } } else { if(AssertVerify(GetVehicleFragInst()->IsInLevel())) { u32 nTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES | ArchetypeFlags::GTA_CAMERA_TEST; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } } if(GetCollider()) { vCurrentVel = GetVelocity(); vCurrentAngVel = GetAngVelocity(); GetCollider()->SetVelocity( VEC3_ZERO ); GetCollider()->SetAngVelocity( VEC3_ZERO ); } else if(m_nVehicleFlags.bCreatingAsInactive) { // The desired velocity has been cached off during creation, so use that. vCurrentVel = m_vSuperDummyVelocity; } // Switch to the dummy inst, if not already active if (GetDummyInst()) { if(GetCurrentPhysicsInst()==GetVehicleFragInst()) { // Deactivate old vehicle frag inst GetVehicleFragInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION, true); if(CPhysics::GetLevel()->IsActive(GetVehicleFragInst()->GetLevelIndex())) { Assertf(!ConsiderParkedForLodPurposes(), "Parked car should not switch to superdummy while it's active"); CPhysics::GetSimulator()->DeactivateObject(GetVehicleFragInst()->GetLevelIndex()); } // if the vehicle has been fixed by network we need to clear this before switching to dummy physics if(IsBaseFlagSet(fwEntity::IS_FIXED_BY_NETWORK)) { SetFixedPhysics(false, true); } // Switch to dummy SwitchCurrentPhysicsInst(GetDummyInst(), true); } Assert(GetCurrentPhysicsInst()==GetDummyInst()); // Ensure dummy inst is in level and is inactive if(!GetDummyInst()->IsInLevel()) { GetDummyInst()->SetMatrix(GetMatrix()); CPhysics::GetSimulator()->AddInactiveObject(GetDummyInst()); } else if(!GetIsStatic()) { CPhysics::GetSimulator()->DeactivateObject(GetDummyInst()); } } else { InitCompositeBound(); if(CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex())) { CPhysics::GetSimulator()->DeactivateObject(GetCurrentPhysicsInst()->GetLevelIndex()); } } // Set up collision with other super-dummies & dummies if( CVehicleAILodManager::ms_bCollideWithOthersInSuperDummyMode ) { if(AssertVerify(GetCurrentPhysicsInst()->IsInLevel())) { u32 nTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES | ArchetypeFlags::GTA_CAMERA_TEST; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); if(CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && CVehicleAILodManager::ms_bCollideWithOthersInSuperDummyInactiveMode) { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetCurrentPhysicsInst()->GetLevelIndex(),true); } } GetCurrentPhysicsInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION,false); } else { GetCurrentPhysicsInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION, ms_bSuperLowLod_DisableCollisions); } //cars on the back of trailers never collide with anything if (GetDummyAttachmentParent() && GetDummyAttachmentParent()->InheritsFromTrailer()) { int nLevelIndex = GetCurrentPhysicsInst()->GetLevelIndex(); if(CPhysics::GetLevel()->IsInLevel(nLevelIndex)) { m_nRealVehicleIncludeFlags = CPhysics::GetLevel()->GetInstanceIncludeFlags(nLevelIndex); } DisableCollision(); } if( CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode ) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL, true); } // Disable gravity GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, ms_bSuperLowLod_DisableGravity); ChangeDummyConstraints(VDM_SUPERDUMMY,GetIsStatic()); if( !CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode ) { SetVelocity(vCurrentVel); SetAngVelocity(vCurrentAngVel); } else { if(!GetIsStatic()) { Warningf("Super-dummy vehicle should start off inactive."); } SetSuperDummyVelocity(vCurrentVel); } for(int w=0; wSetSteerAngle(0.0f); } } //-------------------------------------------------- // Switch off collision between cabs & trailers if (GetDummyAttachmentParent()) { SetNoCollision(GetDummyAttachmentParent(), NO_COLLISION_PERMENANT); //also switch off the cab's collision for us if we're a trailer if (InheritsFromTrailer()) { GetDummyAttachmentParent()->SetNoCollision(this, NO_COLLISION_PERMENANT); } } if(GetIntelligence() && CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this)) { GetIntelligence()->InvalidateCachedNodeList(); GetIntelligence()->InvalidateCachedFollowRoute(); GetIntelligence()->m_BackupFollowRoute.Invalidate(); CTask* pTask = GetIntelligence()->GetActiveTask(); while(pTask) { if(pTask && (pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_CRUISE_NEW || pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_GOTO_AUTOMOBILE_NEW)) { static_cast(pTask)->ForceRouteUpdate(); break; } pTask = pTask->GetSubTask(); } } m_nVehicleFlags.bLodSuperDummyWheelCompressionSet = false; } void CVehicle::SwitchToDummyPhysics(const eVehicleDummyMode prevMode) { if(m_nVehicleFlags.bWheelsDisabled) { EnableWheelCollisions(); } for(int i=0; iGetDynamicFlags().SetFlag(WF_DUMMY); } // cache the first wheel's material id if we're switch from real to dummy if (prevMode==VDM_REAL) { if (GetNumWheels()>0) { CWheel* pWheel = GetWheel(0); if (pWheel) { phMaterialMgr::Id mtlId = pWheel->GetMaterialId(); phMaterialMgr::Id unpackedMtlId = PGTAMATERIALMGR->UnpackMtlId(mtlId); if (Verifyf(unpackedMtlIdGetNumMaterials(), "vehicle has an invalid wheel material id (%" I64FMT "u - %" I64FMT "u)", mtlId, unpackedMtlId)) { SetDummyMtlId(mtlId); } } } } if( !UsesDummyPhysics(VDM_DUMMY) ) //GetDummyInst()==NULL) { if(GetVehicleFragInst()->IsInLevel()) { // For the case where we are entering VDM_DUMMY mode whilst keeping our frag physics, just ensure that it collides with less stuff. u32 nTypeFlags = ArchetypeFlags::GTA_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } } else { // Check if we are already in dummy physics mode. if(UsesDummyPhysics(prevMode)) { Assert(!GetDummyInst() || GetCurrentPhysicsInst() == GetDummyInst()); // If we are in VDM_SUPERDUMMY mode, then transition back to VDM_DUMMY // Turn back on collision, and re-enable dummy constraint if( prevMode == VDM_SUPERDUMMY ) { if( CVehicleAILodManager::ms_bCollideWithOthersInSuperDummyMode ) { if( CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && CVehicleAILodManager::ms_bCollideWithOthersInSuperDummyInactiveMode ) { if(GetCurrentPhysicsInst()->IsInLevel()) { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetCurrentPhysicsInst()->GetLevelIndex(),false); } } } else { GetCurrentPhysicsInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION, false); } if( CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode ) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL, false); SetVelocityFromSuperDummyVelocity(); } GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false); } } else if(GetCurrentPhysicsInst()==GetVehicleFragInst() && GetDummyInst()) { // if the vehicle has been fixed by network we need to clear this before switching to dummy physics if(IsBaseFlagSet(fwEntity::IS_FIXED_BY_NETWORK)) { SetFixedPhysics(false, true); } // This might be a good idea if one or other insts is fixed but it's not fully tested yet /* if(IsBaseFlagSet(fwEntity::IS_FIXED)) { SetFixedPhysics(true, false); } else { SetFixedPhysics(false, false); } */ Vector3 vecMoveSpeed = GetVelocity(); Vector3 vecTurnSpeed = GetAngVelocity(); SwitchCurrentPhysicsInst(GetDummyInst(), true); GetDummyInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION, false); GetDummyInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false); if(!GetDummyInst()->IsInLevel()) { GetDummyInst()->SetMatrix(GetMatrix()); CPhysics::GetSimulator()->AddInactiveObject(GetDummyInst()); } #if __BANK if(const phArchetype * pArchetype = GetCurrentPhysicsInst()->GetArchetype()) { float fSpeedSq = vecMoveSpeed.Mag2(); float fArchetypeMaxSpeed = pArchetype->GetMaxSpeed(); if(fSpeedSq > square(fArchetypeMaxSpeed)+0.01f) { Displayf("vecMoveSpeed (%f,%f,%f) exceeds archetype max speed (%f)",vecMoveSpeed.x,vecMoveSpeed.y,vecMoveSpeed.z,fArchetypeMaxSpeed); } } #endif SetVelocity(vecMoveSpeed); SetAngVelocity(vecTurnSpeed); if(GetVehicleFragInst() && GetVehicleFragInst()->IsInLevel()) { if(CPhysics::GetLevel()->IsActive(GetVehicleFragInst()->GetLevelIndex())) { CPhysics::GetSimulator()->DeactivateObject(GetVehicleFragInst()->GetLevelIndex()); } } } else { InitCompositeBound(); } // Instances should be in the physics level by now. if (GetDummyInst()) { if(AssertVerify(GetCurrentPhysicsInst()->IsInLevel())) { u32 nTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } if(AssertVerify(GetVehicleFragInst()->IsInLevel())) { u32 nTypeFlags = 0; u32 nIncludeFlags = 0; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } } else { if(AssertVerify(GetVehicleFragInst()->IsInLevel())) { u32 nTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES | ArchetypeFlags::GTA_CAMERA_TEST; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } } ChangeDummyConstraints(VDM_DUMMY,false); //-------------------------------------------------- // Switch off collision between cabs & trailers if (GetDummyAttachmentParent()) { SetNoCollision(GetDummyAttachmentParent(), NO_COLLISION_PERMENANT); //also switch off the cab's collision for us if we're a trailer if (InheritsFromTrailer()) { GetDummyAttachmentParent()->SetNoCollision(this, NO_COLLISION_PERMENANT); } } // if (HasDummyAttachmentChildren()) // { // for (int i = 0; i < m_DummyAttachmentChildren.GetMaxCount(); i++) // { // if (m_DummyAttachmentChildren[i]) // { // SetNoCollision(GetDummyAttachmentChild(i), NO_COLLISION_PERMENANT); // GetDummyAttachmentChild(i)->SetNoCollision(this, NO_COLLISION_PERMENANT); // } // } // } // // if(GetDummyAttachmentParent()) // { // GetDummyAttachmentParent()->SetNoCollision(this, NO_COLLISION_PERMENANT); // SetNoCollision(GetDummyAttachmentParent(), NO_COLLISION_PERMENANT); // } } } void CVehicle::SwitchToFullFragmentPhysics(const eVehicleDummyMode prevMode) { if(prevMode == VDM_SUPERDUMMY || prevMode == VDM_DUMMY) { if(!GetVehicleAiLod().IsLodFlagSet(CVehicleAILod::AL_LodTimeslicing)) { for(int i=0; iStartDummyTransition(); } } } for(int i=0; iGetDynamicFlags().ClearFlag(WF_DUMMY); } if(m_nVehicleFlags.bWheelsDisabled) { EnableWheelCollisions(); } if( !UsesDummyPhysics(prevMode) ) //GetDummyInst()==NULL) { if(AssertVerify(GetVehicleFragInst()->IsInLevel())) { u32 nTypeFlags = ArchetypeFlags::GTA_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } } else //UsesDummyPhysics(prevMode) { Assert(!GetDummyInst() || GetCurrentPhysicsInst() == GetDummyInst()); // If we were in VDM_SUPERDUMMY mode, we need to turn off inactive collides with inactive if( prevMode == VDM_SUPERDUMMY ) { if( CVehicleAILodManager::ms_bCollideWithOthersInSuperDummyMode ) { if( CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && CVehicleAILodManager::ms_bCollideWithOthersInSuperDummyInactiveMode ) { if(GetCurrentPhysicsInst()->IsInLevel()) { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetCurrentPhysicsInst()->GetLevelIndex(),false); } } } else { GetCurrentPhysicsInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION, false); } if( CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode ) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL, false); SetVelocityFromSuperDummyVelocity(); } GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false); } if(AssertVerify(GetVehicleFragInst()->IsInLevel())) { u32 nTypeFlags = ArchetypeFlags::GTA_VEHICLE_TYPE; u32 nIncludeFlags = ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES; CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetVehicleFragInst()->GetLevelIndex(), nTypeFlags, nIncludeFlags); } ChangeDummyConstraints(VDM_REAL,false); if (GetDummyInst()) { // Check if we are already in full physics mode if(GetCurrentPhysicsInst()==GetVehicleFragInst()) { Assertf(!GetVehicleAiLod().IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy), "Why is this flag still set then?"); // We don't need to do anything, so just return. return; } Vector3 vecMoveSpeed = (CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && prevMode == VDM_SUPERDUMMY && GetIsStatic()) ? m_vSuperDummyVelocity : GetVelocity(); Vector3 vecTurnSpeed = GetAngVelocity(); if(GetCurrentPhysicsInst()==GetDummyInst()) { if(GetDummyInst()->IsInLevel()) { CPhysics::GetSimulator()->DeleteObject(GetDummyInst()->GetLevelIndex()); } GetDummyInst()->SetInstFlag(phInstGta::FLAG_NO_COLLISION, true); SwitchCurrentPhysicsInst(GetVehicleFragInst(), true); // make sure cars activate when they switch back to real, because the road height might be different if(vecMoveSpeed.Mag2() < LARGE_FLOAT) vecMoveSpeed.z -= 0.2f; //Ensure the magnitude of velocity does not exceed a certain threshold. static float scMaxMagSq = rage::square(DEFAULT_MAX_SPEED); float scMagSq = vecMoveSpeed.Mag2(); if(scMagSq < scMaxMagSq) { SetVelocity(vecMoveSpeed); } else { Warningf("Not changing velocity: %f > %f", scMagSq, scMaxMagSq); } scMagSq = vecTurnSpeed.Mag2(); if(scMagSq < scMaxMagSq) { SetAngVelocity(vecTurnSpeed); } } else { Assert(false); } } else { InitCompositeBound(); if (CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && prevMode == VDM_SUPERDUMMY) { SetVelocityFromSuperDummyVelocity(); } if (!GetCollider() && GetIsStatic() && !m_nVehicleFlags.bHasParentVehicle && CPedPopulation::GetPopulationControlPointInfo().m_centreVelocity.Mag2() > 1.0f) { //Assertf(GetVelocity().Mag2() < 0.25f * 0.25f, "Placing vehicle on road that's expected to be static, but it has a vel of %.2f!", GetVelocity().Mag()); //CVehicle::ms_debugDraw.AddLine(GetVehiclePosition(), GetVehiclePosition() + Vec3V(0.0f, 0.0f, 5.0f), Color_orange); ActivatePhysics(); } } } // Other real vehicles have gravity controlled by the wheel integrator if(InheritsFromBoat()) { GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, false); } } void CVehicle::UpdateFlagsForTimeslicedLod() { m_nVehicleFlags.bLodShouldSkipUpdatesInTimeslicedLod = false; if(m_nVehicleFlags.bLodShouldUseTimeslicing) { BANK_ONLY(Color32 debugColor = Color_white;) if(CVehicleAILodManager::GetSkipTimeslicedUpdatesWhileParked() && IsParkedSuperDummy()) { m_fTimeSinceLastAiUpdate = 0.0f; m_nVehicleFlags.bLodShouldSkipUpdatesInTimeslicedLod = true; BANK_ONLY(debugColor = Color_black;) } else if(CVehicleAILodManager::GetSkipTimeslicedUpdatesWhileStopped()) { bool bLodShouldSkipUpdatesInTimeslicedLod = false; const CVehicle* pVehInFront = GetIntelligence()->GetCarWeAreBehind(); const bool bBehindStoppedVehicle = pVehInFront && pVehInFront->GetAiXYSpeed() == 0.0f; // Skip timesliced updates when stopped behind another stopped vehicle // OR // stopped at a red light, and not turning right if(GetIntelligence()->GetTrafficLightCommand() == TRAFFICLIGHT_COMMAND_STOP && GetIntelligence()->GetJunctionCommand() != JUNCTION_COMMAND_GO && GetIntelligence()->GetJunctionTurnDirection() != BIT_TURN_RIGHT) { const bool bOnJunction = GetIntelligence()->GetJunctionArrivalTime() != UINT_MAX; if((bBehindStoppedVehicle || bOnJunction) && GetAiXYSpeed() == 0.0f) { bLodShouldSkipUpdatesInTimeslicedLod = true; BANK_ONLY(debugColor = Color_cyan;) } } else if(bBehindStoppedVehicle && GetAiXYSpeed() == 0.0f) { bLodShouldSkipUpdatesInTimeslicedLod = true; BANK_ONLY(debugColor = Color_green;) } if(!bLodShouldSkipUpdatesInTimeslicedLod || m_iNumTimeslicedUpdatesSkipped >= CVehicleAILodManager::GetMaxTimeslicedUpdatesToSkipWhileStopped()) { bLodShouldSkipUpdatesInTimeslicedLod = false; } m_nVehicleFlags.bLodShouldSkipUpdatesInTimeslicedLod = bLodShouldSkipUpdatesInTimeslicedLod; } #if __BANK if(m_nVehicleFlags.bLodShouldSkipUpdatesInTimeslicedLod && CVehicleAILodManager::GetDisplaySkippedTimeslicedUpdates()) { grcDebugDraw::Sphere(GetTransform().GetPosition() + Vec3V(0.0f, 0.0f, 5.0f), 0.5f, debugColor, true, -1); } #endif // __BANK } } void CVehicle::SwitchAwayFromTimeslicedLod() { // Should be called after this got cleared. Assert(!m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodTimeslicing)); m_fTimeSinceLastAiUpdate = 0.0f; m_iNumTimeslicedUpdatesSkipped = 0; } void CVehicle::SwitchToTimeslicedLod() { // Should be called after this got set. Assert(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodTimeslicing)); // Make sure we don't have some old interpolation target set. // ClearInterpolationTargetAndTime(); // Compute a timesliced interpolation target if we currently have some velocity. // Normally this will be replaced by a more accurate target the next time we update // (which should be this or the next frame), but if for some reason that doesn't // happen, it prevents us from coming to a complete stop. SetInterpolationTargetFromExtrapolatedVelocity(false); // Make sure we get an AI update ASAP. If we are a clone or not a car, probably // safest to stick with the old behavior. if(GetVehicleType() == VEHICLE_TYPE_CAR && !IsNetworkClone()) { m_nVehicleFlags.bLodForceUpdateUntilNextAiUpdate = true; } else { m_nVehicleFlags.bLodForceUpdateThisTimeslice = true; } } PF_PAGE(GTAVehs, "GTA Vehs"); PF_GROUP(VehsProcessControl); PF_LINK(GTAVehs, VehsProcessControl); PF_TIMER(Physical, VehsProcessControl); PF_TIMER(SuperDummy, VehsProcessControl); void CVehicle::ProcessInterpolation(bool fullUpdate, float fFullUpdateTimeStep) { if(!fullUpdate) { if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodTimeslicing)) { ProcessInterpolationForTimeslicedLod(); CTask* pActiveTask = GetIntelligence()->GetActiveTask(); if(pActiveTask) { const int taskType = pActiveTask->GetTaskType(); //is this vehicle running a long-range task? //if so, keep requesting the nodes here if(taskType == CTaskTypes::TASK_VEHICLE_GOTO_LONGRANGE) { CTaskVehicleGotoLongRange* pLongRangeTask = static_cast(pActiveTask); pLongRangeTask->ProcessTimeslicedUpdate(); } else if(taskType == CTaskTypes::TASK_VEHICLE_GOTO_HELICOPTER) { CTaskVehicleGoToHelicopter* pHeliGoToTask = static_cast(pActiveTask); pHeliGoToTask->ProcessTimeslicedUpdate(); } // Note: if we add much more here, we should probably just make ProcessTimeslicedUpdate() virtual. // If we only call it on the active task and not all the subtasks, then it shouldn't be too expensive. } } // Parked super dummy needs recompute allowable dummy state all the time if(IsParkedSuperDummy()) { // Reset the computed allowable dummy state (used in GetCanMakeIntoDummy) m_nVehicleFlags.uAllowedDummyConversion = 0; } return; } // Reset the computed allowable dummy state (used in GetCanMakeIntoDummy) m_nVehicleFlags.uAllowedDummyConversion = 0; PF_START(SuperDummy); m_nVehicleFlags.bSuperDummyProcessed = false; if( m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy) ) { // If this vehicle is in superdummy mode then the new vehicle AI should have processed it // This below is the fallback for the old vehicle AI if( !m_nVehicleFlags.bDummyWheelImpactsSampled ) { TUNE_GROUP_BOOL(CAR_AI, DoBackupSuperDummyUpdate, true); if(ProcessSuperDummy(fFullUpdateTimeStep)) { m_nVehicleFlags.bDummyWheelImpactsSampled = true; UpdateDummyConstraints(); } else if (DoBackupSuperDummyUpdate && (!GetDummyAttachmentParent() || !GetDummyAttachmentParent()->InheritsFromTrailer()) && m_pIntelligence->m_BackupFollowRoute.GetNumPoints() > 1) { CTaskVehicleMissionBase* pActiveTask = m_pIntelligence->GetActiveTaskSimplest(); const float fCruiseSpeed = pActiveTask ? pActiveTask->GetCruiseSpeed() : 0.0f; const s32 iDrivingFlags = pActiveTask ? pActiveTask->GetDrivingFlags() : (m_pIntelligence->m_BackupFollowRoute.GetIsDrivingInReverse() ? DF_DriveInReverse : 0); if (m_pIntelligence->m_BackupFollowRoute.ProcessSuperDummy(this, ScalarV(fCruiseSpeed), iDrivingFlags, fFullUpdateTimeStep)) { m_nVehicleFlags.bDummyWheelImpactsSampled = true; } } else { UpdateDummyConstraints(); } } m_nVehicleFlags.bSuperDummyProcessed = m_nVehicleFlags.bDummyWheelImpactsSampled; } PF_STOP(SuperDummy); } void CVehicle::ProcessInterpolationForTimeslicedLod() { // This function should only be called when in timesliced mode. Assert(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodTimeslicing)); // Check if we have become active. If so, there has probably been a collision and we should // let the physics system handle it rather than trying to set the matrix. // Used to be: if(!GetIsStatic()) { ClearInterpolationTargetAndTime(); return; } phInst* pCurrentPhysInst = GetCurrentPhysicsInst(); if(!pCurrentPhysInst || !pCurrentPhysInst->IsInLevel()) { // If we don't have an instance in the physics level, something unusual is going on and // we probably don't need to bother with the interpolation stuff unless we have any bugs about // it. This allows us to simplify the rest of the code a bit. ClearInterpolationTargetAndTime(); return; } phLevelNew* pPhysLevel = CPhysics::GetLevel(); const int levelIndex = pCurrentPhysInst->GetLevelIndex(); if(pPhysLevel->IsActive(levelIndex)) { // Also, we should probably clear out the interpolation target, so we don't try to // get back to it after the physics simulation is over (the AI tasks should set a new // target the next time they get to update). ClearInterpolationTargetAndTime(); return; } // We already checked if we are active, shouldn't have a collider. Assert(!GetCollider()); // Check the W component, which is the time. If it's 0.0f it should also // be 0 as an integer, so we don't have to use float or vector comparisons. if(!m_InterpolationTargetAndTime.GetWi()) { // Nothing to do, but we are not moving now, so make sure the superdummy velocity // is set to zero. SetSuperDummyVelocity(ORIGIN); return; } // Load various variables we'll need. const Mat34V& currentMatrix = GetMatrixRef(); const ScalarV timeStepV(fwTimer::GetTimeStep()); const Vec4V targetAndTimeV = m_InterpolationTargetAndTime; const ScalarV timeToInterpolateV = targetAndTimeV.GetW(); const Vec3V currentPosV = currentMatrix.GetCol3(); const Vec3V tgtPosV = m_InterpolationTargetAndTime.GetXYZ(); const ScalarV zeroV(V_ZERO); const ScalarV oneV(V_ONE); // Compute the T value we should use to move between the current position // and the target, based on the update time step relative to the remaining time. const ScalarV tV = Min(InvScale(timeStepV, timeToInterpolateV), oneV); // Decrease the amount of time left. const ScalarV timeLeftV = Max(Subtract(timeToInterpolateV, timeStepV), zeroV); // Compute the new position. const Vec3V newPosV = Lerp(tV, currentPosV, tgtPosV); // Slerp the orientation. const QuatV tgtOrientationV = m_InterpolationTargetOrientation; // Check the W component of the stored current orientation, as an integer, and see if // it's 0. If it's not, we have an orientation stored, and don't have to recompute it. // It's theoretically possible that it's 0 even if it was a valid orientation, but should // be very rare and not cause any harm other than having to recompute it. QuatV currentOrientationV; if(reinterpret_cast(m_InterpolationCurrentOrientation).GetWi() == 0) { currentOrientationV = QuatVFromMat34V(currentMatrix); } else { currentOrientationV = m_InterpolationCurrentOrientation; } // Perform interpolation of the quaternion. I had great difficulty with Slerp() // and SlerpNear() in the new vector library, they seemed to frequently return NaN // if the two quaternions were close. Nlerp() is less accurate, but should be much // faster, and not suffer from this numerical problem. Probably accurate enough, // there are larger sources of imprecision here (the computation of the target). currentOrientationV = PrepareSlerp(tgtOrientationV, currentOrientationV); const QuatV interpolated = Nlerp(tV, currentOrientationV, tgtOrientationV); // Make sure we don't get a bad matrix from this. Assert(IsFiniteAll(interpolated)); // Store the current orientation. m_InterpolationCurrentOrientation = interpolated; // Construct the new matrix. Mat34V newMatrix; Mat34VFromQuatV(newMatrix, interpolated, newPosV); // Store the remaining time. m_InterpolationTargetAndTime.SetW(timeLeftV); // Not sure if this can happen at all, but the code further down doesn't support this // dummy instance thing so we may as well call the regular SetMatrix(). if(Unlikely(pCurrentPhysInst == GetDummyInst())) { CVehicle::SetMatrix(RCC_MATRIX34(newMatrix), true, true, false); return; } // The rest of the code is basically adapted from CVehicle::SetMatrix() and its parents. // By rolling that in here, we can make various simplifying assumptions to reduce branching, // and we avoid a bunch of function call overhead, etc. // Could potentially do something hacky like this, to prevent polymorphism overhead in // the fwMatrix call: // fwMatrixTransform* pTransform = const_cast(reinterpret_cast(&GetTransform())); // pTransform->SetMatrix(newMatrix); // PDR_ONLY(debugPlayback::RecordTaggedMatrixValue(*pCurrentPhysInst, newMatrix, "CVehicle::ProcessInterpolationForTimeslicedLod")); // Or, if we want to be even dirtier, we could eliminate the newMatrix variable and make sure // we write straight to the matrix... fwEntity::SetMatrix(RCC_MATRIX34(newMatrix), false, false, false); // Update the previous position in the fwDynamicEntityComponent. Could just call SetPreviousPosition() // on the entity, but there would be slightly less overhead that way. // Note: not entirely sure if this is strictly necessary. fwDynamicEntityComponent* pDynComp = GetDynamicComponent(); if(pDynComp) { pDynComp->SetPreviousPosition(VEC3V_TO_VECTOR3(newMatrix.GetCol3())); } // Update the position of the current physics instance. pCurrentPhysInst->SetMatrix(newMatrix); pPhysLevel->UpdateObjectLocation(levelIndex); // Update the entity's bounds in the pathserver system. // As an optimization, we try to get away without this. If the vehicle is moving fast, the // obstacle is removed from the path server anyway, and if it's slow or stationary, the // regular SetMatrix() calls from the full updates should be enough to position it. // if(IsInPathServer()) // { // CPathServerGta::UpdateDynamicObject(this); // } // Update the spatial array position. if(m_spatialArrayNode.IsInserted()) { Vec3V_ConstRef position = newMatrix.GetCol3ConstRef(); ms_spatialArray->Update(m_spatialArrayNode, position.GetXf(), position.GetYf(), position.GetZf()); } // The rest of this could potentially be skipped, for potentially significant performance benefits. // This seems to essentially just come down to calling the UpdateMovable functor, which is // CBreakable::Update(), which seems to not currently do anything. Can probably be removed. if(pCurrentPhysInst->GetClassType() >= PH_INST_FRAG_GTA) { ((fragInst*)pCurrentPhysInst)->ReportMovedBySim(); } // This is quite expensive, and it's worth looking into if we need to do it at all, and if so, // if we can get away with only updating a part of it (the center of the bounding sphere/box). UpdateWorldFromEntity(); // We should investigate not setting this. The cost of accessing the memory probably isn't a huge // deal here, but by setting it, we cause some lighting calculations to happen in PreRender(), // so by not setting it, it looks like we would save CPU time there - perhaps as much as 0.1-0.2 ms. m_nDEflags.bHasMovedSinceLastPreRender = true; // This is another questionable thing: can we get away without it to save some memory accesses? ResetWaterStatus(); // Note: we could call SetSuperDummyVelocityFromInterpolationTarget() here, but if nothing else // is teleporting us around, that shouldn't be needed, since it should have already been set // when we started the interpolation. // If we no longer have any more interpolation to do: if(!m_InterpolationTargetAndTime.GetWi()) { // Recompute a new target by extrapolating the current velocity, so we don't // come to a full stop on the next frame. SetInterpolationTargetFromExtrapolatedVelocity(true); // Force an update for the next frame (or whenever we can get in an actual AI update). // We don't want to keep extrapolating, since that may take us off our route. m_nVehicleFlags.bLodForceUpdateUntilNextAiUpdate = true; } } void CVehicle::SetInterpolationTargetFromExtrapolatedVelocity(bool allowSpeedReduction) { // Should only be called when we are already timesliced. Assert(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodTimeslicing)); // MAGIC! Would be nice to move these somewhere, but can't think of a really good place for them. static float s_ExtraTime = 0.7f; // The amount of time we plan for. static float s_VelocityDampingFactorXY = 0.7f; // Multiplier on the velocity in the X and Y directions. static float s_VelocityDampingFactorZ = 0.3f; // Multiplier on the velocity in the Z direction. static float s_VelocityThresholdSq = 0.25f; // Square of the velocity threshold (0.5 m/s). // Get the current velocity. const Vec3V currentVelV = VECTOR3_TO_VEC3V(GetVelocity()); // Check if the velocity is large enough to bother. if(IsGreaterThanAll(MagSquared(currentVelV), ScalarV(s_VelocityThresholdSq))) { const Mat34V& currentMatrix = GetMatrixRef(); if(reinterpret_cast(m_InterpolationCurrentOrientation).GetWi() == 0) { // m_InterpolationCurrentOrientation isn't already set, we are not already interpolating. // Compute it from the current matrix, and use it both as the current orientation and the // target (we don't extrapolate angular velocity at this time). QuatV currentOrientationV = QuatVFromMat34V(currentMatrix); m_InterpolationTargetOrientation = currentOrientationV; m_InterpolationCurrentOrientation = currentOrientationV; } else { // We already have a current orientation computed, use that as the target orientation as well. m_InterpolationTargetOrientation = m_InterpolationCurrentOrientation; } // Compute the new velocity, by multiplying the current velocity by some dampening factors. // The purpose of using a smaller value for Z is that it's generally more dangerous if we start drifting // in that direction. Vec3V newSuperDummyVelV; if(allowSpeedReduction) { const Vec3V dampingV(s_VelocityDampingFactorXY, s_VelocityDampingFactorXY, s_VelocityDampingFactorZ); newSuperDummyVelV = Scale(currentVelV, dampingV); } else { newSuperDummyVelV = currentVelV; } // Compute a future position by extrapolating the new velocity. const ScalarV interpolationTimeV(s_ExtraTime); const Vec3V currentPosV = currentMatrix.GetCol3(); const Vec3V futurePosV = AddScaled(currentPosV, newSuperDummyVelV, interpolationTimeV); // Store the target position and time. Vec4V targetAndTime(futurePosV.GetIntrin128()); targetAndTime.SetW(interpolationTimeV); m_InterpolationTargetAndTime = targetAndTime; // Update the velocity. SetSuperDummyVelocity(RCC_VECTOR3(newSuperDummyVelV)); } else { // Come to a full stop. ClearInterpolationTargetAndTime(); SetSuperDummyVelocity(ORIGIN); } } void CVehicle::SetSuperDummyVelocityFromInterpolationTarget() { if(!Verifyf(GetIsStatic(), "Expected a static vehicle when setting superdummy velocity from interpolation.")) { return; } // First, check if we have an interpolation target. Vec3V velV; if(m_InterpolationTargetAndTime.GetWi()) { // Get the current position and the interpolation target position, // compute a delta, and divide by the time we are interpolating over. // That should give us the velocity we will be moving at. const Vec3V currentPosV = GetTransform().GetPosition(); const Vec4V targetAndTimeV = m_InterpolationTargetAndTime; const ScalarV timeToInterpolateV = targetAndTimeV.GetW(); const Vec3V tgtPosV = targetAndTimeV.GetXYZ(); const Vec3V toTgtV = Subtract(tgtPosV, currentPosV); velV = InvScale(toTgtV, timeToInterpolateV); // Make sure we got something sensible. Assert(IsFiniteAll(velV)); } else { // Not interpolating, so no velocity. velV.ZeroComponents(); } SetSuperDummyVelocity(RCC_VECTOR3(velV)); } void CVehicle::LerpCloneSuperDummyMatrix(Matrix34 &matrix) { CNetBlenderPhysical *blender = SafeCast(CNetBlenderPhysical, GetNetworkObject()->GetNetBlender()); Matrix34 lastMatrixReceived = blender->GetLastMatrixReceived(); Quaternion currentOrientation; Quaternion targetOrientation; Assert(matrix.IsOrthonormal()); Assert(lastMatrixReceived.IsOrthonormal()); currentOrientation.FromMatrix34(matrix); targetOrientation.FromMatrix34(lastMatrixReceived); if((currentOrientation.Dot(targetOrientation)) < 0.0f) { currentOrientation.Negate(); } bool currentQuaternionValid = (currentOrientation.Mag2() >= square(0.999f) && currentOrientation.Mag2() <= square(1.001f)); bool targetQuaternionValid = (targetOrientation.Mag2() >= square(0.999f) && targetOrientation.Mag2() <= square(1.001f)); if(!currentQuaternionValid) currentOrientation.Normalize(); if(!targetQuaternionValid) targetOrientation.Normalize(); float angleDiff = targetOrientation.RelAngle(currentOrientation); if(angleDiff >= 0.785f) { matrix.Set3x3(lastMatrixReceived); } else { currentOrientation.SlerpNear(0.1f, targetOrientation); Assert(currentOrientation.Mag2() >= square(0.999f) && currentOrientation.Mag2() <= square(1.001f)); matrix.FromQuaternion(currentOrientation); float det = matrix.Determinant3x3(); if (det == 0.0f) { matrix.MakeUpright(); } } } // NAME : ProcessSuperDummy // PURPOSE : Legacy superdummy update; this is in the process of being replaced bool CVehicle::ProcessSuperDummy(float fTimeStep, bool bAttachPhase) { //------------------------------------------------------------------------- // Sample the wheels onto a road surface represented by the vehicle nodes Assert( !m_nVehicleFlags.bDummyWheelImpactsSampled ); Assert( m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy) ); // Don't process super dummies if they are supposed to be fixed. if(GetIsAnyFixedFlagSet()) { return false; } // Vehicles attached to trailers are processed in ProcessOtherAttachments. const bool bIsRidingOnTrailer = GetDummyAttachmentParent() && GetDummyAttachmentParent()->InheritsFromTrailer() && GetDummyAttachmentParent()->GetDummyAttachmentParent(); if(bIsRidingOnTrailer) { return false; } // Trailers are only processed here when called from ProcessOtherAttachments. const bool bIsTrailer = InheritsFromTrailer() && GetDummyAttachmentParent(); if(bIsTrailer && !bAttachPhase) { return false; } CVehicle * pTrailerParent = bIsTrailer ? GetDummyAttachmentParent() : NULL; const int kMaxWheelsForSuperDummy = 4; eHierarchyId iWheelIDs[kMaxWheelsForSuperDummy]; ScalarV fTimeStepV(fTimeStep); // Trailers use their parent's nodelist CVehicleIntelligence *pIntelligenceToUse = NULL; if(bIsTrailer) { pIntelligenceToUse = GetDummyAttachmentParent()->GetIntelligence(); iWheelIDs[0] = VEH_WHEEL_LM1; iWheelIDs[1] = VEH_WHEEL_LR; iWheelIDs[2] = VEH_WHEEL_RM1; iWheelIDs[3] = VEH_WHEEL_RR; } else { pIntelligenceToUse = GetIntelligence(); iWheelIDs[0] = VEH_WHEEL_LF; iWheelIDs[1] = VEH_WHEEL_LR; iWheelIDs[2] = VEH_WHEEL_RF; iWheelIDs[3] = VEH_WHEEL_RR; } CVehicleNodeList * pNodeList = pIntelligenceToUse->GetNodeList(); // Its possible for a vehicle to be made a super-dummy by VehicleAILodManager earlier in the frame, // and then lose its task/nodelist during the task update immediately before ProcessSuperDummy() is run bool bUseNodeList = true; const CVehicleFollowRouteHelper * pFollowRoute = NULL; if(!pNodeList || pNodeList->GetTargetNodeIndex()<=0 || !pNodeList->GetPathNode(pNodeList->GetTargetNodeIndex() - 1) || !pNodeList->GetPathNode(pNodeList->GetTargetNodeIndex()) ) { bUseNodeList = false; } if(!bUseNodeList) { pFollowRoute = pIntelligenceToUse->GetFollowRouteHelper(); if(!pFollowRoute || pFollowRoute->GetNumPoints() <= 1) { return false; } } // Supported wheel configurations are: // - 4 wheels, all wheels exist // - 2 wheels, not a trailer, wheels 0 & 1 exist // - 2 wheels, is a trailer, wheels 1 & 3 exist const int iNumWheels = Min(GetNumWheels(), kMaxWheelsForSuperDummy); bool bTrailerWithRearWheelsOnly = false; //if(!Verifyf(iNumWheels==2 || iNumWheels==4, "SuperDummy mode doesn't yet support trikes or unicycles!")) //{ // return false; //} CWheel * pWheels[kMaxWheelsForSuperDummy] = { GetWheelFromId(iWheelIDs[0]), GetWheelFromId(iWheelIDs[1]), GetWheelFromId(iWheelIDs[2]), GetWheelFromId(iWheelIDs[3]) }; if(bIsTrailer) { if(!Verifyf(pWheels[1] && pWheels[3] && (iNumWheels==2 || pWheels[0] && pWheels[3]),"Rear wheels are required for trailers. Mid wheels are optional.")) { return false; } if(!pWheels[0]) { bTrailerWithRearWheelsOnly = true; } } //else //{ // if(!Verifyf(pWheels[0] && pWheels[1] && (iNumWheels==2 || (pWheels[2] && pWheels[3])),"Non trailers need wheels 0 & 1. Four wheel non-trailers need wheels 2 & 3.")) // { // return false; // } //} Vector3 vWheelOffsets[kMaxWheelsForSuperDummy]; Vector3 vWheelPositions[kMaxWheelsForSuperDummy]; CVehicleModelInfo * pModelInfo = (CVehicleModelInfo*) GetBaseModelInfo(); for(int w=0; wGetWheelImpactOffset(); } else { // Trailers calculate their position from contact -> wheel -> vehicle. // Other vehicles calculate their position by making the offset // from the ground the same as the wheel's offset in the model's space. // Not sure if everything will have to later calculate from contact -> wheel // -> vehicle body. We'll see if this distinction matters. //this isn't necessary, see Ted's comments for B* 746339 //vWheelPositions[i].z -= vWheelOffsets[i].z + pWheels[i]->GetWheelRadius(); } vAverageWheelToRoadOffset -= vWheelSurfaceNormals[i] * (fWheelHeightAboveSurface[i] - pWheels[i]->GetWheelRadius()); } } if( iNumWheels == 3 ) { for(int w = 0; w < kMaxWheelsForSuperDummy; w++) { if( !pWheels[ w ] ) { if( w < 2 ) { vWheelPositions[ w ] = vWheelPositions[ w + 2 ]; } else { vWheelPositions[ w ] = vWheelPositions[ w - 2 ]; } } } } } if(iNumWheelsSampled != iNumWheels) return false; vAverageWheelToRoadOffset /= (float)iNumWheelsSampled; Assert(vAverageWheelToRoadOffset.FiniteElements() && (vAverageWheelToRoadOffset.Mag2() < 250000.0f)); //--------------------------------------------------------------- // Find trailer attachment positions for later use Vec3V vTrailerHitchCurrent, vTrailerParentHitch; if(bIsTrailer) { Matrix34 mParentAttach, mAttach; pTrailerParent->GetGlobalMtx( pTrailerParent->GetBoneIndex(VEH_ATTACH), mParentAttach); GetGlobalMtx( GetBoneIndex(TRAILER_ATTACH), mAttach); if(pTrailerParent->GetModelIndex() == MI_CAR_HAULER && NetworkInterface::IsGameInProgress()) { mParentAttach.d += mParentAttach.c * CTrailer::sm_fAttachOffsetHauler; } vTrailerParentHitch = RCC_VEC3V(mParentAttach.d); vTrailerHitchCurrent = RCC_VEC3V(mAttach.d); } //--------------------------------------------------------------- // Work out pitch & roll required to keep vehicle on the road ScalarV fTrailerHeadingChange(V_ZERO); Vector3 vWheelsMid, vToFront; if(iNumWheels == 2 && !bTrailerWithRearWheelsOnly) { vWheelsMid = (vWheelPositions[iWheelLeftFront] + vWheelPositions[iWheelLeftRear]) * 0.5f; vToFront = vWheelPositions[iWheelLeftFront] - vWheelsMid; } else { Vector3 vWheelsRearMid = (vWheelPositions[iWheelLeftRear] + vWheelPositions[iWheelRightRear]) * 0.5f; if(bTrailerWithRearWheelsOnly) { Assertf(bIsTrailer,"Rear-wheel-only trailer on non-trailer!"); vWheelsMid = vWheelsRearMid; } else { Vector3 vWheelFrontMid = (vWheelPositions[iWheelLeftFront] + vWheelPositions[iWheelRightFront]) * 0.5f; vWheelsMid = (vWheelFrontMid + vWheelsRearMid) * 0.5f; vToFront = vWheelFrontMid - vWheelsMid; } // Calc new vToFront for trailer if(bIsTrailer) { const Vec3V vWheelBalancedLocal = (pWheels[iWheelLeftRear]->GetWheelStaticPositionLocal() + pWheels[iWheelRightRear]->GetWheelStaticPositionLocal()) * ScalarV(V_HALF); const Vec3V vWheelBalanced = GetTransform().Transform(vWheelBalancedLocal); const CTrailer * pThisTrailer = static_cast(this); // vVirtualWheelContact is the point at which an imaginary wheel placed at the parent attach point of the trailer would hit the road. const Vec3V vVirtualWheelContact = pThisTrailer->CalcTrailerVirtualWheelAtAttachmentPoint(vTrailerParentHitch,RCC_VEC3V(vWheelsMid),vWheelBalanced,pWheels[iWheelLeftRear]->GetWheelRadius()); vToFront = VEC3V_TO_VECTOR3(vVirtualWheelContact - RCC_VEC3V(vWheelsMid)); Vec3V vTrailerParentHitchPositionToUse = vTrailerParentHitch; // If the trailer has already been moved by the physics system, then the delta between trailer and parent movement is lost. So, we simulate that // by moving the parent forward and using that to calculate the rotation angle. This is mathematically the same as moving back the trailer current hitch // _and_ moving back the current rear wheel position, but easier to calculate. if(!bIsStatic) { vTrailerParentHitchPositionToUse = vTrailerParentHitchPositionToUse + fTimeStepV * RCC_VEC3V(vCurrentVel); } // Calculate the heading for trailers here (calculated later for other vehicles). const Vec3V vToParentHitch = vTrailerParentHitchPositionToUse - RCC_VEC3V(vWheelsMid); const Vec3V vToCurrentTrailerHitch = vTrailerHitchCurrent - RCC_VEC3V(vWheelsMid); fTrailerHeadingChange = AngleZ(vToCurrentTrailerHitch,vToParentHitch); fTrailerHeadingChange = CanonicalizeAngle(fTrailerHeadingChange); if(!bIsStatic) { // Scale up the heading change if not static since it isn't teleported to directly // Not sure if this is 100% correct, but the angular velocity will be scaled up by fAngularVelocityScale below, and what we really want is the full effect to happen this frame, so 1 / (fAngularVelocityScale * fTimeStep) seems correct. const ScalarV fTrailerHeadingChangeActiveScale(1.0f / (fAngularVelocityScale * fTimeStep)); fTrailerHeadingChange = fTrailerHeadingChange * fTrailerHeadingChangeActiveScale; } } } const Vector3 vOffsetToModelOrigin(0.0f, 0.0f, 0.0f); Vector3 vToFrontFlat( vToFront.x, vToFront.y, 0.0f ); vToFront.NormalizeSafe(matCurrent.b, SMALL_FLOAT); // Catch error case where vehicle's forwards vector is orientated directly upwards if(vToFrontFlat.XYMag2() < SMALL_FLOAT) { SetVelocity(VEC3_ZERO); SetAngVelocity(VEC3_ZERO); return false; } vToFrontFlat.Normalize(); float fSign = DotProduct(ZAXIS, vToFront) > 0.0f ? 1.0f : -1.0f; const float fPitchDot = Clamp(DotProduct(vToFront, vToFrontFlat), -1.0f, 1.0f); const float fDesiredPitch = Acosf(fPitchDot) * fSign; //----------------------------------------------------- // Do the same for roll, since roads can have camber. Vector3 vToRight; if(iNumWheels==2 && !bTrailerWithRearWheelsOnly) { vToRight = CrossProduct(vToFront, ZAXIS); if(vToRight.Mag2() < SMALL_FLOAT) vToRight = CrossProduct(VEC3V_TO_VECTOR3(GetTransform().GetUp()), ZAXIS); } else { Vector3 vWheelLeftMid; Vector3 vWheelRightMid; if(bTrailerWithRearWheelsOnly) { vWheelLeftMid = vWheelPositions[iWheelLeftRear]; vWheelRightMid = vWheelPositions[iWheelRightRear]; } else { vWheelLeftMid = (vWheelPositions[iWheelLeftFront] + vWheelPositions[iWheelLeftRear]) * 0.5f; vWheelRightMid = (vWheelPositions[iWheelRightFront] + vWheelPositions[iWheelRightRear]) * 0.5f; } vToRight = vWheelRightMid - vWheelsMid; } vToRight.Normalize(); fSign = DotProduct( vToFront, CrossProduct(vToRight, matCurrent.a) ) < 0.0f ? 1.0f : -1.0f; const float fRollDot = Clamp(DotProduct(vToRight, matCurrent.a), -1.0f, 1.0f); const float fRollDelta = Acosf(fRollDot) * fSign; //----------------------------------------------------------------------------------------------------- // Calculate a new velocity and heading Vector3 vNewVelocity; float fHeadingChange; float fRotAnglesMult = 1.0f; if(bIsTrailer) { // Heading change for the trailer comes from calculation done when finding the pitch. fHeadingChange = fTrailerHeadingChange.Getf(); // Velocity of trailer is velocity of the vehicle (only used for GetExternallyControlledVelocity()?) vNewVelocity = pTrailerParent->GetVelocity(); Assertf(vNewVelocity.FiniteElements() && (vNewVelocity.Mag2() < 250000.0f),"Velocity of pTrailerParent is bad."); } else if(bIsNetworkClone) { fHeadingChange = 0.0f; vNewVelocity = vCurrentVel; Vector3 vFrontFlat(matCurrent.b.x, matCurrent.b.y, 0.0f); vFrontFlat.Normalize(); Vector3 vLastVelReceived = NetworkInterface::GetLastVelReceivedOverNetwork(this); Vector3 vVelocityFlat(vLastVelReceived.x, vLastVelReceived.y, 0.0f); TUNE_FLOAT(STEER_TO_VEL_THRESHOLD, 5.0f, 0.0f, 50.0f, 0.1f); if(vVelocityFlat.Mag2() > rage::square(STEER_TO_VEL_THRESHOLD)) { vVelocityFlat.Normalize(); float fFrontToVelFlatAngle = Acosf(Clamp(vFrontFlat.Dot(vVelocityFlat), -1.0f, 1.0f)); Vector3 vToRightFlat(vToRight.x, vToRight.y, 0.0f); vToRightFlat.Normalize(); float dotVelFlatToRight = vVelocityFlat.Dot(vToRightFlat); if(dotVelFlatToRight >= 0.0f) { fFrontToVelFlatAngle = -fFrontToVelFlatAngle; } fHeadingChange = fFrontToVelFlatAngle * CVehicleAILodManager::ms_fSuperDummyCloneHeadingToVelBlendRatio; } else { Matrix34 targetMatrix = NetworkInterface::GetLastMatrixReceivedOverNetwork(this); float headingDiff = SubtractAngleShorter(targetMatrix.GetEulers().z, matCurrent.GetEulers().z); fHeadingChange = headingDiff * CVehicleAILodManager::ms_fSuperDummyCloneHeadingToVelBlendRatio; } } else { // For now this is very simplistic - vehicles steer at a fixed rate // Calc new velocity from task's cruise speed, existing velocity & throttle/brake const float fCurrentVelMag = DotProduct(vCurrentVel, vToFront); const float fSteerAngle = GetSteerAngle(); fHeadingChange = fSteerAngle * CVehicleAILodManager::ms_fSuperDummySteerSpeed; const CTaskVehicleMissionBase* pActiveTask = GetIntelligence()->GetActiveTaskSimplest(); // if(!Verifyf(pActiveTask,"Vehicles that are here should have an active task (i.e. not just attached or towed.")) // { // return false; // } const float fCruiseSpeed = pActiveTask ? pActiveTask->GetCruiseSpeed() : 0.0f; const float fThrottle = Clamp( GetThrottle(), 0.0f, 1.0f); const float fBrake = Clamp( GetBrake(), 0.0f, 1.0f ); float fDriveSpeed = fCruiseSpeed * fThrottle; vNewVelocity = vToFront * fDriveSpeed; Assertf(vNewVelocity.FiniteElements() && (vNewVelocity.Mag2() < 250000.0f), "vNewVelocity bad after vToFront * fDriveSpeed."); float fNewVelMag = vNewVelocity.Mag(); // If driven velocity is less than current velocity, then scale up if(fNewVelMag < fCurrentVelMag) { if(fNewVelMag > 0.0f) vNewVelocity *= fCurrentVelMag/fNewVelMag; else vNewVelocity = vToFront * fCurrentVelMag; fNewVelMag = vNewVelocity.Mag(); Assertf(vNewVelocity.FiniteElements() && (vNewVelocity.Mag2() < 250000.0f), "vNewVelocity bad after vToFront * fCurrentVelMag."); } // If less than cruise speed, then accelerate up to cruise speed if(fNewVelMag < fCruiseSpeed) { static dev_float fAccel = 1.0f; vNewVelocity += (vToFront * fAccel * fTimeStep); fNewVelMag = vNewVelocity.Mag(); Assertf(vNewVelocity.FiniteElements() && (vNewVelocity.Mag2() < 250000.0f), "vNewVelocity bad after += (vToFront * fAccel * fTimeStep)"); } // If now greater than cruise speed, then reduce into range if(fNewVelMag > fCruiseSpeed) { if(fNewVelMag > 0.0f) vNewVelocity *= fCruiseSpeed/fNewVelMag; else vNewVelocity *= fCruiseSpeed; Assertf(vNewVelocity.FiniteElements() && (vNewVelocity.Mag2() < 250000.0f), "vNewVelocity bad after *= fCruiseSpeed."); } // Apply brake on top of all the above vNewVelocity *= (1.0f - fBrake); fNewVelMag = vNewVelocity.Mag(); Assert(fNewVelMag <= fCruiseSpeed + 0.3f); // Only accelerate from current velocity to target velocity at a set rate, // braking is managed by the AI tasks and sometimes needs sudden stops // Exception: if we currently have some speed and fNewVelMag is zero, assume // that the braking is actually not managed well by the AI tasks, and don't // let us come to a complete halt in a single frame. static dev_float fMaxVelChange = 1.0f; Vector3 velDiff = vNewVelocity - vCurrentVel; if(velDiff.Mag2() > rage::square(fMaxVelChange)) { if(fNewVelMag > fCurrentVelMag || fNewVelMag <= SMALL_FLOAT) { velDiff.Normalize(); velDiff.Scale(fMaxVelChange); vNewVelocity = vCurrentVel + velDiff; fNewVelMag = vNewVelocity.Mag(); } } // If we are traveling below a certain speed, then scale down the Z heading change // This stops vehicles turning on the spot when they are stationary if(fNewVelMag < 1.0f) fRotAnglesMult = fNewVelMag; } const float fPlaceOnRoadSpringConst = 7.0f; const float fPlaceOnRoadSpeedMax = 35.0f; const float fPlaceOnRoadSpeedMin = 3.0f; if(!bIsTrailer && !bIsNetworkClone) { // For vehicles that won't be teleported into place, add a velocity to drive the vehicle toward the road height. // TUNE_GROUP_BOOL(ASDF, DoNewPlaceOnRoadVelocity, false); // if (!DoNewPlaceOnRoadVelocity) // { // // TODO: Replace this with the calculation from CVehicleFollowRouteHelper::ProcessSuperDummy() // static float fPlaceOnRoadScale = 4.0f; // Previously this came from the sum of wheel offsets. // vNewVelocity += vAverageWheelToRoadOffset * fPlaceOnRoadScale; // Assertf(vNewVelocity.FiniteElements() && (vNewVelocity.Mag2() < 250000.0f), "vNewVelocity bad after vAverageWheelToRoadOffset * fPlaceOnRoadScale."); // } // else { // Set velocity in direction normal to the road to approach the road in proportion to the // distance from the road (within a min-max range) and to not go past the road on any frame. // TODO: Later we may want to add more sophisticated behavior for an active super dummy, but // first lets get them on the road cleanly and then define a more physical behavior later if desired. Vector3 vToUp(Vector3::ZeroType); vToUp.Cross(vToRight, vToFront); const float fCurSpeedToRoad = vNewVelocity.Dot(vToUp); const Vector3 vAverageRoadToWheelOffset = -vAverageWheelToRoadOffset; const float fCurDistToRoad = vAverageRoadToWheelOffset.Dot(vToUp); const float fPlaceOnRoadSpeedDesired = Abs(fCurDistToRoad * fPlaceOnRoadSpringConst); const float fPlaceOnRoadSpeedToHitNextFrame = Abs(fCurDistToRoad / fTimeStep); const float fPlaceOnRoadSpeedClamped = rage::Min(fPlaceOnRoadSpeedToHitNextFrame,rage::Clamp(fPlaceOnRoadSpeedDesired,fPlaceOnRoadSpeedMin,fPlaceOnRoadSpeedMax)); //const float fPlaceOnRoadSpeed = SelectFT(fCurDistToRoad>0.0f,fPlaceOnRoadSpeedClamped,-fPlaceOnRoadSpeedClamped); const float fPlaceOnRoadSpeed = Selectf(fCurDistToRoad, -fPlaceOnRoadSpeedClamped, fPlaceOnRoadSpeedClamped); const float fPlaceOnRoadCorrection = fPlaceOnRoadSpeed - fCurSpeedToRoad; const Vector3 vPlaceOntoRoadVelocity = vToUp * fPlaceOnRoadCorrection; Assert(vPlaceOntoRoadVelocity.FiniteElements() && (vPlaceOntoRoadVelocity.Mag2() < 250000.0f)); vNewVelocity += vPlaceOntoRoadVelocity; } } Vector3 vLocalRotAngles( fDesiredPitch - GetTransform().GetPitch(), // pitch delta fRollDelta, // roll delta fHeadingChange // yaw delta ); vLocalRotAngles.z *= fRotAnglesMult; //----------------------------------------------------------------------------------------------------- // Teleport or update velocities to position and orient the vehicle // Flags for teleporting static dev_bool bUpdateWorld = true; static dev_bool bUpdatePhysics = true; static dev_bool bWarp = false; // This determines how quickly a network clone is teleported toward the height of the road. Ideally it // can be set close to 1, but not until the syncing of nodes is improved. static float fNetworkCloneRoadApproachRate = 0.3f; if(CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && bIsStatic) { // Calc new orientation // Maximum rate of rotation in each dimension in radians per second. static Vec3V vRotationRateMin(V_NEGTWO); static Vec3V vRotationRateMax(V_TWO); Vec3V vRotationToApply = Clamp(RCC_VEC3V(vLocalRotAngles),fTimeStepV*vRotationRateMin,fTimeStepV*vRotationRateMax); Vec3V vCurEulers = VECTOR3_TO_VEC3V(matCurrent.GetEulers()); Vec3V vNewEulers = vCurEulers + vRotationToApply; Matrix34 mFinal; mFinal.FromEulers(RCC_VECTOR3(vNewEulers), "xyz"); // Calc new position if(bIsTrailer) { if(bIsNetworkClone) { mFinal.Set3x3(matCurrent); LerpCloneSuperDummyMatrix(mFinal); } // Set the matrix position to align the attachment points #if ENABLE_FRAG_OPTIMIZATION Matrix34 trailerAttachLocalMtx; ComputeObjectMtx(GetBoneIndex(TRAILER_ATTACH), RC_MAT34V(trailerAttachLocalMtx)); #else Matrix34 & trailerAttachLocalMtx = GetObjectMtx(GetBoneIndex(TRAILER_ATTACH)); #endif Vector3 vTrailerAttach; mFinal.Transform3x3(trailerAttachLocalMtx.d,vTrailerAttach); mFinal.d = RCC_VECTOR3(vTrailerParentHitch) - vTrailerAttach; } else if(bIsNetworkClone) { // Teleport to road, horizontal movement comes from the network blender mFinal.d = matCurrent.d + vAverageWheelToRoadOffset * fNetworkCloneRoadApproachRate; } else { // Set the position based on desired velocity mFinal.d = matCurrent.d + (vNewVelocity * fTimeStep); } SetMatrix(mFinal, bUpdateWorld, bUpdatePhysics, bWarp); SetSuperDummyVelocity(vNewVelocity); } // Apply through physics system? else { if(bIsTrailer) { // If it is a trailer, teleport to align to the parent attachment point. // Set the matrix position to align the attachment points. Matrix34 mFinal(matCurrent); if(bIsNetworkClone) { LerpCloneSuperDummyMatrix(mFinal); } #if ENABLE_FRAG_OPTIMIZATION Matrix34 trailerAttachLocalMtx; ComputeObjectMtx(GetBoneIndex(TRAILER_ATTACH), RC_MAT34V(trailerAttachLocalMtx)); #else Matrix34 & trailerAttachLocalMtx = GetObjectMtx(GetBoneIndex(TRAILER_ATTACH)); #endif Vector3 vTrailerAttach; mFinal.Transform3x3(trailerAttachLocalMtx.d,vTrailerAttach); mFinal.d = RCC_VECTOR3(vTrailerParentHitch) - vTrailerAttach; SetMatrix(mFinal, bUpdateWorld, bUpdatePhysics, bWarp); } else if(bIsNetworkClone) { Matrix34 matFinal(matCurrent); matFinal.d += vAverageWheelToRoadOffset * fNetworkCloneRoadApproachRate; SetMatrix(matFinal, bUpdateWorld, bUpdatePhysics, bWarp); } // Have to clamp to max speed or physics will assert const float fSpeedScale = vNewVelocity.Mag() / GetCurrentPhysicsInst()->GetArchetype()->GetMaxSpeed(); if(fSpeedScale > 1.0f) vNewVelocity /= fSpeedScale; // Set linear velocity SetVelocity(vNewVelocity); // Rotate angular velocity into worldspace & apply, not for network clone though, // as their angular velocity and orientation changes are managed by the network blenders if(!bIsNetworkClone/* || bIsTrailer*/) { Vector3 vRotAngles; matCurrent.Transform3x3(vLocalRotAngles, vRotAngles); vRotAngles *= fAngularVelocityScale; const float fAngSpeedScale = vRotAngles.Mag() / GetCurrentPhysicsInst()->GetArchetype()->GetMaxAngSpeed(); if(fAngSpeedScale > 1.0f) vRotAngles /= fAngSpeedScale; CPhysical::SetAngVelocity(vRotAngles.x, vRotAngles.y, vRotAngles.z); } } matCurrent = MAT34V_TO_MATRIX34(GetMatrix()); for(int w=0; wSetCompressionFromHitPos(&matCurrent, vWheelPositions[w], true); } } return true; } bool CVehicle::ShouldDisableImpactForPedExitingVehicle(const CPed& rPed, const CVehicle& rVeh, s32 iImpactComponentIndex) { // Check if this is a vehicle door that is colliding with a ped exiting the vehicle, if so, disable the impact // this allows us to not have the ped attached when they are closing the door // Note: used to be done like this, by checking the task, but the FindTaskActiveByType() call is relatively expensive: // if (rPed.GetMyVehicle() == &rVeh && rPed.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE)) if (rPed.GetMyVehicle() == &rVeh && rPed.GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle)) { return GetDoorHierachyIdFromImpact(rVeh, iImpactComponentIndex) != VEH_INVALID_ID; } return false; } eHierarchyId CVehicle::GetDoorHierachyIdFromImpact(const CVehicle& rVeh, s32 iImpactComponentIndex) { fragInst* thisFragInst = rVeh.GetFragInst(); if (thisFragInst) { // Check to see if the impact is with any of the door components, if so, disable the impact for (s32 i=0; iGetComponentFromBoneIndex(rVeh.GetBoneIndex(CVehicle::ms_aDoorComponentIds[i]))) { return CVehicle::ms_aDoorComponentIds[i]; } } } return VEH_INVALID_ID; } bool CVehicle::GetMinMaxBoundsFromDummyVehicleBound(const CVehicle& vehicle, Vec3V_Ref vMin, Vec3V_Ref vMax) { const phInstGta* pDummyVehicleInst = vehicle.GetDummyInst(); const phBound* pDummyBound = NULL; // If we have a dummy inst, get the dummy bound from there if (pDummyVehicleInst) { pDummyBound = static_cast(pDummyVehicleInst->GetArchetype()->GetBound()); } // Otherwise we should get the dummy bound from the frag inst const int iBoneIndexChassisDummy = vehicle.GetBoneIndex(VEH_CHASSIS_DUMMY); if(iBoneIndexChassisDummy!=-1) { fragInst* pFragInst = vehicle.GetVehicleFragInst(); int nGroup = pFragInst->GetGroupFromBoneIndex(iBoneIndexChassisDummy); if(nGroup > -1) { fragPhysicsLOD* pFragPhysicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = pFragPhysicsLOD->GetAllGroups()[nGroup]; int iChild = pGroup->GetChildFragmentIndex(); pDummyBound = pFragPhysicsLOD->GetCompositeBounds()->GetBound(iChild); } } if (pDummyBound && pDummyBound->GetType() == phBound::BOX && vehicle.GetLayoutInfo()) { vMin = pDummyBound->GetBoundingBoxMin(); vMax = pDummyBound->GetBoundingBoxMax(); const CVehicleCoverBoundOffsetInfo* pCoverInfo = vehicle.GetVehicleCoverBoundOffsetInfo(); if (pCoverInfo) { // Alter our bounding box to avoid poking our head out vMin.SetXf(vMin.GetXf() + pCoverInfo->GetExtraSideOffset()); vMax.SetXf(vMax.GetXf() - pCoverInfo->GetExtraSideOffset()); vMin.SetYf(vMin.GetYf() + pCoverInfo->GetExtraBackwardOffset()); vMax.SetYf(vMax.GetYf() - pCoverInfo->GetExtraForwardOffset()); vMax.SetZf(vMax.GetZf() - pCoverInfo->GetExtraZOffset()); } return true; } return false; } const void CVehicle::GetApproximateSize(Vector3 &vecBBoxMin, Vector3 &vecBBoxMax) const { if(!(GetFragInst() && GetFragInst()->GetCacheEntry() && GetFragInst()->GetCacheEntry()->GetBound())) return; fragPhysicsLOD* pPhysicsLOD = GetFragInst()->GetTypePhysics(); phBoundComposite *pBoundComp = GetFragInst()->GetCacheEntry()->GetBound(); int *pComponentToBone = GetFragInst()->GetCacheEntry()->GetComponentToBoneIndexMap(); const CVehicleVariationInstance &rVariationInst = GetVariationInstance(); const CVehicleKit *pKit = 0; const u8 *mods = 0; if(rVariationInst.GetMods() && rVariationInst.GetKit()) { pKit = rVariationInst.GetKit(); mods = rVariationInst.GetMods(); } vecBBoxMin.Set(LARGE_FLOAT, LARGE_FLOAT, LARGE_FLOAT); vecBBoxMax.Set(-LARGE_FLOAT, -LARGE_FLOAT, -LARGE_FLOAT); Vector3 vecTemp, vecTemp2; for(int nChild = 0; nChild < pPhysicsLOD->GetNumChildren(); ++nChild) { bool bInclude = true; // Ignore doors. Only interested in the base size of the vehicle. Doors contribute very little when they're all closed, but skew the size when open. for(int nDoor = 0; nDoor < GetNumDoors(); ++nDoor) { if(GetDoor(nDoor)->GetBoneIndex() == pComponentToBone[nChild] || GetBoneIndex(GetWindowIdFromDoor(GetDoor(nDoor)->GetHierarchyId())) == pComponentToBone[nChild]) bInclude = false; } // Don't include any extra mod kit bounds (Unless they're enabled, in which case they'll be included below). bool bIsModPart = false; for (s32 nModBone = VEH_MOD_COLLISION_1; nModBone <= VEH_LAST_MOD_COLLISION; ++nModBone) { if(pComponentToBone[nChild] == GetBoneIndex((eHierarchyId)nModBone)) { bIsModPart = true; bInclude = false; } } // Check the mod kit (if enabled) for any mods that have toggled on collision and include those bounds in the calculation. if(pKit && mods && bIsModPart) { for(s32 nSlot = 0; nSlot < VMT_RENDERABLE; ++nSlot) { if(mods[nSlot] != INVALID_MOD) { CVehicleModVisible::eVehicleModBone nCollisionBone = pKit->GetVisibleMods()[mods[nSlot]].GetCollisionBone(); if( nCollisionBone != -1 ) { if(pComponentToBone[nChild] == GetBoneIndex((eHierarchyId)nCollisionBone)) bInclude = true; } } } } if(bInclude && pBoundComp->GetBound(nChild)) { RCC_MATRIX34(pBoundComp->GetCurrentMatrix(nChild)).Transform(VEC3V_TO_VECTOR3(pBoundComp->GetBound(nChild)->GetBoundingBoxMin()), vecTemp); vecTemp2.Set(vecBBoxMin); vecBBoxMin.Min(vecTemp, vecTemp2); RCC_MATRIX34(pBoundComp->GetCurrentMatrix(nChild)).Transform(VEC3V_TO_VECTOR3(pBoundComp->GetBound(nChild)->GetBoundingBoxMax()), vecTemp); vecTemp2.Set(vecBBoxMax); vecBBoxMax.Max(vecTemp, vecTemp2); } } } CVehicle::eVehicleOrientation CVehicle::GetVehicleOrientation(const CVehicle& rVeh) { TUNE_GROUP_FLOAT(ENTER_VEHICLE_TUNE, ON_SIDE_Z_VALUE, 0.6f, -1.0f, 1.0f, 0.01f); TUNE_GROUP_FLOAT(ENTER_VEHICLE_TUNE, ON_SIDE_Z_VALUE_HIGH_TOLERANCE, 0.4f, -1.0f, 1.0f, 0.01f); bool bIsJetpack = MI_JETPACK_THRUSTER.IsValid() && rVeh.GetModelIndex() == MI_JETPACK_THRUSTER; bool bIsDodoPlane = MI_PLANE_DODO.IsValid() && rVeh.GetModelIndex() == MI_PLANE_DODO; float fOnSideZValue = ON_SIDE_Z_VALUE; if (bIsJetpack || (bIsDodoPlane && NetworkInterface::IsGameInProgress())) fOnSideZValue = ON_SIDE_Z_VALUE_HIGH_TOLERANCE; const float fVehUpAxisWorldZ = rVeh.GetTransform().GetC().GetZf(); if (Abs(fVehUpAxisWorldZ) < fOnSideZValue) { return VO_OnSide; } else if (fVehUpAxisWorldZ < 0.0f) { return VO_UpsideDown; } else { return VO_Upright; } } void CVehicle::ProcessOtherAttachments() { // Walk down the chain of attachments and update relative to your parent. // If we have a parent, then align to it. if(m_DummyAttachmentParent) { if(!m_nVehicleFlags.bSuperDummyProcessed) { // This is checking for a parent to the parent as well. Why? Can't we be attached to a detached trailer? const bool bIsRidingOnTrailer = m_DummyAttachmentParent && m_DummyAttachmentParent->InheritsFromTrailer() && m_DummyAttachmentParent->m_DummyAttachmentParent; const bool bIsTrailer = InheritsFromTrailer(); if (bIsRidingOnTrailer) { // Attachment type 1, vehicles attached to a trailer. //If we're riding on the back of a trailer, we don't want to sample the road or anything like that //just maintain the same relative position CTrailer* pParentTrailer = (CTrailer*)GetDummyAttachmentParent(); Assert(pParentTrailer); Matrix34 newMat; //if we were attached via the script command, set our position here //the positions we have are offset relative to the trailer center in this case //not the trailer hitch bone //entity2pos = trailer pos in car space if (!m_nVehicleFlags.bIsCargoVehicle) { Vector3 vTrailerPosInCarSpace = GetParentTrailerEntity2Offset(); Vec3V vecParentTrailerEntity2Offset = VECTOR3_TO_VEC3V(vTrailerPosInCarSpace); const Vec3V vTrailerAttachPosInWorldSpace = pParentTrailer->GetTransform().Transform(vecParentTrailerEntity2Offset); Matrix34 mParentTrailerMatrix = MAT34V_TO_MATRIX34(pParentTrailer->GetTransform().GetMatrix()); //go from car space to trailer space newMat.FromQuaternion(GetParentTrailerEntityRotation()); newMat.Dot(mParentTrailerMatrix); newMat.d = VEC3V_TO_VECTOR3(vTrailerAttachPosInWorldSpace/* + vCarAttachPosInWorldSpace*/); } else { newMat.FromQuaternion(GetParentTrailerEntityRotation()); newMat.d = GetParentTrailerEntity2Offset(); Matrix34 trailerAttachMat; pParentTrailer->GetGlobalMtx(pParentTrailer->GetBoneIndex(TRAILER_ATTACH), trailerAttachMat); newMat.Dot(trailerAttachMat); } static dev_bool bUpdateWorld = true; static dev_bool bUpdatePhysics = true; static dev_bool bWarp = false; SetMatrix(newMat, bUpdateWorld, bUpdatePhysics, bWarp); SetSuperDummyVelocity(pParentTrailer->GetVelocity()); if(!CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode || !GetIsStatic() ) { SetVelocity(m_vSuperDummyVelocity); } return; } else if(bIsTrailer) { // Attachment type 2, trailers attached to a vehicle (i.e. trailer wheels are on the ground) Assert(GetDummyAttachmentParent()->GetDummyAttachmentChild(0) == this); // assert on double-link ProcessSuperDummy(fwTimer::GetTimeStep(), true); } else { Errorf("Vehicle is dummy attached, but is neither a trailer nor riding on a trailer. What situation is this? (Mission veh? %d/%d)",PopTypeIsMission(),m_DummyAttachmentParent->PopTypeIsMission()); const char * pThisVehicleName = (m_pVehicleFragInst && m_pVehicleFragInst->GetArchetype()) ? m_pVehicleFragInst->GetArchetype()->GetFilename() : NULL; if(pThisVehicleName) { Errorf("This vehicle name '%s'",pThisVehicleName); } const char * pParentVehicleName = (m_DummyAttachmentParent->m_pVehicleFragInst && m_DummyAttachmentParent->m_pVehicleFragInst->GetArchetype()) ? m_DummyAttachmentParent->m_pVehicleFragInst->GetArchetype()->GetFilename() : NULL; if(pParentVehicleName) { Errorf("Parent vehicle name '%s'",pParentVehicleName); } } } } // Then attach children if(m_nDummyAttachmentChildren>0) { int nFound = 0; for(int i=0; iProcessOtherAttachments(); nFound++; } } Assert(nFound==m_nDummyAttachmentChildren); } } float CVehicle::CalculateInvMassScale(float fVehicleSpeed, bool bIsOtherInstanceFirst, const CVehicle& rOtherVehicle) const { //Check if this vehicle has an override for the inv mass scale, when colliding with the other vehicle. if(GetIntelligence()->GetInvMassScaleOverride().m_pVehicle == &rOtherVehicle) { return GetIntelligence()->GetInvMassScaleOverride().m_fValue; } if( m_outriggerRatio > 0.0f && GetVelocity().Mag2() < 1.0f ) { return 0.0f; } if( rOtherVehicle.GetOutriggerDeployRatio() > 0.0f && rOtherVehicle.GetVelocity().Mag2() < 1.0f ) { return 1.0f; } static float sfInvMassScale = 2.0f; static float sfInvMassHeliScaleMax = 9.0f; #if __BANK sfInvMassScale = gfPlayerCarInvMassScale; #endif static float relVelMin = 2.0f; static float relVelMinForHelis = 5.0f; if((GetMass() < sfMaximumMassForPushingVehicles) && (sfInvMassScale != 1.0f) && ContainsLocalPlayer() && (fVehicleSpeed > relVelMin)) { float fInvMassScale = sfInvMassScale; Vector3 vOtherVehicleVelocity = rOtherVehicle.GetVelocity(); //reduce inverse mass scale if the other vehicle is driving at speed towards the player float fDirectionDot = DotProduct(VEC3V_TO_VECTOR3(GetTransform().GetB()),vOtherVehicleVelocity); static dev_float sfOpposingSpeed = 10.0f; if(fVehicleSpeed > sfOpposingSpeed) { if(-fDirectionDot > sfOpposingSpeed) { fInvMassScale = 1.0f; } } if(bIsOtherInstanceFirst) { Vec3V vRelPosition = rOtherVehicle.GetTransform().GetPosition() - GetTransform().GetPosition(); vRelPosition = Normalize(vRelPosition); ScalarV scVelDotA = Abs(Dot(vRelPosition, VECTOR3_TO_VEC3V(rOtherVehicle.GetVelocity()))); ScalarV scVelDotB = Abs(Dot(vRelPosition, VECTOR3_TO_VEC3V(GetVelocity()))); if( IsGreaterThanAll(scVelDotB, scVelDotA) ) { return fInvMassScale; } } else { Vec3V vRelPosition = GetTransform().GetPosition() - rOtherVehicle.GetTransform().GetPosition(); vRelPosition = Normalize(vRelPosition); ScalarV scVelDotA = Abs(Dot(vRelPosition, VECTOR3_TO_VEC3V(GetVelocity()))); ScalarV scVelDotB = Abs(Dot(vRelPosition, VECTOR3_TO_VEC3V(rOtherVehicle.GetVelocity()))); if( IsGreaterThanAll(scVelDotA, scVelDotB) ) { return fInvMassScale; } } } else if(GetVehicleType() == VEHICLE_TYPE_HELI && (fVehicleSpeed < relVelMinForHelis))// reduce force applied by heli's to other vehicles as it looks strange. { if(rOtherVehicle.GetMass() < GetMass()) { return Min(GetMass()/rOtherVehicle.GetMass(), sfInvMassHeliScaleMax); } } return 1.0f; } bool CVehicle::DoBoundingBoxIntersectionTest(s32 iArchtypeFlags, int iNumExpections, const CEntity** ppExceptionEntities, const bool bDoHeightFixup, float* fIntersectDepth, Matrix34* pTestMatrix) const { // Test for intersections (but not against anything associated // with this vehicle). #if __ASSERT CBaseModelInfo* pModelInfo = GetBaseModelInfo(); Assert(pModelInfo->GetFragType()); phBound* pBound = static_cast(pModelInfo->GetFragType()->GetPhysics(0)->GetCompositeBounds()); Assert(pBound); #endif // s32 nTestTypeFlags = ArchetypeFlags::GTA_BOX_VEHICLE_TYPE | // ArchetypeFlags::GTA_VEHICLE_TYPE | // ArchetypeFlags::GTA_PED_TYPE | // ArchetypeFlags::GTA_OBJECT_TYPE; #define MAX_NUM_INTERSECTIONS (16) WorldProbe::CShapeTestHitPoint boundIsects[MAX_NUM_INTERSECTIONS]; WorldProbe::CShapeTestResults boundTestResults(boundIsects, MAX_NUM_INTERSECTIONS); const CEntity * pExceptions[PROCESS_LOS_MAX_EXCEPTIONS]; pExceptions[0] = this; iNumExpections = Min(iNumExpections, PROCESS_LOS_MAX_EXCEPTIONS-1); for(int e=0; eIsCollisionEnabled() ) { if(fIntersectDepth) { *fIntersectDepth = boundTestResults[i].GetDepth(); } return true; } } return false; } void CVehicle::HandleDummyProbesBottomingOut(const WorldProbe::CShapeTestResults & dummyProbeResults) { Assert(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodDummy)); if(GetIsStatic()) { return; } bool bFound = false; for(int i=0; iGetVelocity()); const ScalarV fNewVelZ = Clamp(vNewVel.GetZ(),fMinVerticalVelocity,fMaxVerticalVelocity); vNewVel.SetZ(fNewVelZ); pCollider->SetVelocity(vNewVel.GetIntrin128()); } } } } //------------------------------------------------------------------------- // Pretend occupant management. //------------------------------------------------------------------------- //------------------------------------------------------------------------- // Name : CanSetUpWithPretendOccupants // Purpose : Determine if the vehicle can be inited with pretend // occupants. // Parameters : None. // Returns : Whether of not the vehicle can be inited with pretend // occupants. //------------------------------------------------------------------------- bool CVehicle::CanSetUpWithPretendOccupants(void) const { if(!CVehicleAILodManager::ms_usePretendOccupantsSystem) { return false; } // Don't use pretend occupants on mission vehicles with or without // peds inside, otherwise they will have non-mission ped occupants // created later on and then the mission veh will drive away (which // would probably mess up the mission). if(PopTypeIsMission()) { return false; } // Mission trains will handle their own driver and occupants if( GetVehicleType()==VEHICLE_TYPE_TRAIN && ((CTrain*)this)->GetIsMissionTrain()) { return false; } // Make sure that parked vehicles don't get marked as using // pretend occupants, otherwise they will have their non-existent // occupants restored later on and then the parked vehicle will drive // away. if(PopTypeGet() == POPTYPE_RANDOM_PARKED) { return false; } //logic for which vehicles can get pretend occupants lives in vehicles.meta now if (!GetVehicleModelInfo()->GetAllowPretendOccupants()) { return false; } if (IsLawEnforcementVehicle()) { return false; } // Dont convert cars that aren't drivable if( !m_nVehicleFlags.bIsThisADriveableCar ) { return false; } // Script has disabled this functionality for this vehicle if(m_nVehicleFlags.bDisablePretendOccupants) { return false; } for( s32 i = 0; i < MAX_VEHICLE_SEATS; i++ ) { CPed* pPed = m_SeatManager.GetPedInSeat(i); // Dont convert cars that contain dead/injured peds if( pPed && pPed->IsInjured() ) return false; // Dont convert cars that contain peds with orders if( pPed && pPed->GetPedIntelligence()->GetOrder() ) return false; if( pPed && (pPed->GetPedResetFlag(CPED_RESET_FLAG_CullExtraFarAway) || pPed->GetPedResetFlag(CPED_RESET_FLAG_TaskCullExtraFarAway))) return false; // Dont convert cars that contain peds persueing the player if( pPed && CGameWorld::FindLocalPlayer() && pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() == CGameWorld::FindLocalPlayer() ) return false; if (pPed && pPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringOrExitingVehicle)) { return false; } } // Seems all the conditions have passed, so let the caller know. return true; } //------------------------------------------------------------------------- // Name : SetUpWithPretendOccupants // Purpose : Setup the vehicle with pretend occupants. // Parameters : None. // Returns : Nothing. //------------------------------------------------------------------------- void CVehicle::SetUpWithPretendOccupants(void) { SwitchEngineOn(); GetIntelligence()->LastTimeNotStuck = fwTimer::GetTimeInMilliseconds(); // So that it doesn't reverse straight away. if (!IsNetworkClone()) { //Give the vehicle a default task. GiveDefaultTask(); } m_nVehicleFlags.bUsingPretendOccupants = true; } //------------------------------------------------------------------------- // Name : IsUsingPretendOccupants // Purpose : Determine if we are using pretend occupants or real peds. // Parameters : None. // Returns : Nothing. //------------------------------------------------------------------------- bool CVehicle::IsUsingPretendOccupants(void) const { return m_nVehicleFlags.bUsingPretendOccupants; } //------------------------------------------------------------------------- // Name : TryToMakePedsIntoPretendOccupants // Purpose : Generates the pretend occupant data from the ped // occupants, removes the peds, and marks that we are now // using pretend occupants. // Parameters : None. // Returns : Whether or not the conversion was successful. //------------------------------------------------------------------------- bool CVehicle::TryToMakePedsIntoPretendOccupants(void) { // Make sure that we aren't already using pretend occupants. if(m_nVehicleFlags.bUsingPretendOccupants) { // The has already happened. return true; } // Don't allow a conversion to pretend occupants if there are real ones scheduled, which would mean there could // be cases of cars having both real and pretend occupants. if (HasScheduledOccupants()) { return false; } // We aren't allowed to use pretend occupants for cloned vehicles. if(IsNetworkClone() || (GetNetworkObject() && GetNetworkObject()->IsPendingOwnerChange())) { return false; } // make sure this type of vehicle can be setup with pretend occupants. if(!CanSetUpWithPretendOccupants()) { return false; } // Make sure that vehicles doing vehicle recordings don't // get the occupant made into pretend occupants. This is because // the record and playback mechanism rely on there being a driver. if( CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this) #if !__FINAL || CVehicleRecordingMgr::IsCarBeingRecorded(this) #endif ) { return false; } //if we don't have a driver, we can't convert. (Train cars can, because they don't always have drivers) if (!m_nVehicleFlags.bUsingPretendOccupants && !InheritsFromTrain() && !GetDriver() && GetLastDriver() != CGameWorld::FindLocalPlayer()) { return false; } // Make sure that empty vehicles don't get marked as using // pretend occupants, otherwise they will have their non-existent // occupants restored later on and then the empty vehicle will drive // away. if(!HasPedsInIt()) { return false; } // Make sure that mission peds are not deleted. if(HasMissionCharsInIt()) { return false; } // Make sure the players vehicle never tries to delete its occupants. // (was able to happen when the camera moved too far from the player). const CPed* pDriver = GetDriver(); if(pDriver) { if(pDriver->IsPlayer()) { return false; } // In networking games, make sure we don't remove the driver of a scenario vehicle // that's still on a chain - doing so can cause major problems since the vehicle // wouldn't run the proper tasks. if(NetworkInterface::IsGameInProgress()) { const CTask* pTask = pDriver->GetPedIntelligence()->GetTaskActive(); if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_UNALERTED) { const CTaskUnalerted* pUnalertedTask = static_cast(pTask); if(pUnalertedTask->GetScenarioPoint()) { return false; } } } } // Make sure police cars don't have their drivers removed. // The dispatch stuff relies heavily on the drivers ped // to remember who was being chased down. if(IsLawEnforcementVehicle()) { return false; } CEvent* pDriverEvent = pDriver ? pDriver->GetPedIntelligence()->GetCurrentEvent() : NULL; // we aren't allowed to convert peds that aren't doing cruise already // CTask* pActiveTask = GetIntelligence()->GetActiveTask(); // if (!pActiveTask || pActiveTask->GetTaskType() != CTaskTypes::TASK_VEHICLE_CRUISE_NEW) // { // return false; // } // we aren't allowed to convert cloned peds if (HasNetworkClonedOccupant()) { return false; } //save off this guy's driver personality settings so we can continue driving the same way //once converted to pretend occupants GetIntelligence()->SetPretendOccupantAggressivness(CDriverPersonality::FindDriverAggressiveness(GetDriver(), this)); GetIntelligence()->SetPretendOccupantAbility(CDriverPersonality::FindDriverAbility(GetDriver(), this)); // Mark that we are now using pretend occupants. m_nVehicleFlags.bUsingPretendOccupants = true; m_nVehicleFlags.bFailedToResetPretendOccupants = false; //let's set up our pretend occupant task, if necessary GetIntelligence()->SetPretendOccupantEventDataBasedOnCurrentTask(); //if the driver is responding to an event that vehicles care about, copy it over if (pDriverEvent && pDriverEvent->IsExposedToVehicles()) { GetIntelligence()->ClearCurrentEvent(); GetIntelligence()->SetCurrentEvent(static_cast(pDriverEvent->Clone())); } // MK: Moved Removing the occupants down here because when we add peds to the reuse pool we were causing a // crash when trying to access the event above. // Remove the vehicle occupants from the world entirely. // This could become more sophisticated and store information // about the occupants to be restored later, but we'll keep it // simple for now. // Remove the passengers from the world entirely. RemoveAllOccupants(); // The conversion was able to happen. return true; } void CVehicle::RemoveAllOccupants() { for (s32 i = 0; i < m_SeatManager.GetMaxSeats(); ++i) { if (CPed* pPed = m_SeatManager.GetPedInSeat(i)) { AssertEntityPointerValid_NotInWorld(pPed); // See if the ped can be reused if(CPedPopulation::CanPedBeReused(pPed, true)) { // Reuse the ped if(pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && pPed->GetMyVehicle()) { pPed->SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, FALSE ); RemovePedFromSeat(pPed, false); } CPedPopulation::AddPedToReusePool(pPed); Assert(!pPed->GetIsRetainedByInteriorProxy() && !pPed->GetOwnerEntityContainer()); } else { pPed->FlagToDestroyWhenNextProcessed(false); } } } // Trains don't use the SeatManager for passengers and instead have scenario points. // Let the train clean up its own passengers. if (InheritsFromTrain()) { CTrain* pTrain = static_cast(this); pTrain->RemoveAllOccupants(); } } bool CVehicle::HasNetworkClonedOccupant() { for (s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); ++iSeat) { CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat); if (pPassenger && (pPassenger->IsNetworkClone() || (pPassenger->GetNetworkObject() && pPassenger->GetNetworkObject()->IsPendingOwnerChange()))) { return true; } } // Trains don't use the seat manager for passengers, so ask the train if any are clones. if (InheritsFromTrain()) { CTrain* pTrain = static_cast(this); if (pTrain->HasNetworkClonedPassenger()) { return true; } } return false; } // set this vehicle and it's occupants in a state so that they will get safely // destroyed at the next opportunity void CVehicle::FlagToDestroyWhenNextProcessed(void){ #if GTA_REPLAY replayFatalAssertf(CReplayMgr::IsOkayToDelete() || GetOwnedBy() != ENTITY_OWNEDBY_REPLAY, "Entity being deleted during playback...but NOT by the replay system!"); #endif #if __BANK CVehicleFactory::LogDestroyedVehicle(this, "CVehicle::FlagToDestroyWhenNextProcessed", grcDebugDraw::GetScreenSpaceTextHeight(), Color_orange); #endif #if !__NO_OUTPUT if(m_LogDeletions) { EntityDebugfWithCallStack(this, "FlagToDestroyWhenNextProcessed"); } #endif // Remove the vehicle occupants from the world entirely. // This could become more sophisticated and store information // about the occupants to be restored later, but we'll keep it // simple for now. if (HasMissionCharsInIt()){ Assertf(false, "Cannot destroy vehicle containing mission peds!"); #if __DEBUG CDebug::DumpEntity(this); #endif //__DEBUG return; } else { // Remove the passengers from the world entirely. REPLAY_ONLY(if(CReplayMgr::IsEditModeActive() == false)) { for (s32 i = 0; i < m_SeatManager.GetMaxSeats(); ++i) { if (m_SeatManager.GetPedInSeat(i)) { AssertEntityPointerValid_NotInWorld(m_SeatManager.GetPedInSeat(i)); m_SeatManager.GetPedInSeat(i)->FlagToDestroyWhenNextProcessed(); } } } if (!PopTypeIsMission()){ Assertf(CanBeDeletedNetwork(), "Vehicle can't be deleted (%s)", m_pLastVehPopRemovalFailReason); SetBaseFlag(fwEntity::REMOVE_FROM_WORLD); } else { Assertf(false, "Cannot remove mission created vehicle. Vehicle needs to load collisions?"); #if __DEBUG CDebug::DumpEntity(this); #endif //__DEBUG } } } void CVehicle::SetVariationInstance(const CStoredVehicleVariations& newVariation) { CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); Assertf(!GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_CANNOT_BE_MODDED), "Setting variation instance on non-modable vehicle '%s'", GetModelName()); variation.SetColor1(newVariation.GetColor1()); variation.SetColor2(newVariation.GetColor2()); variation.SetColor3(newVariation.GetColor3()); variation.SetColor4(newVariation.GetColor4()); variation.SetColor5(newVariation.GetColor5()); variation.SetColor6(newVariation.GetColor6()); variation.SetWindowTint(newVariation.GetWindowTint()); variation.SetSmokeColorR(newVariation.GetSmokeColorR()); variation.SetSmokeColorG(newVariation.GetSmokeColorG()); variation.SetSmokeColorB(newVariation.GetSmokeColorB()); variation.SetNeonColour(Color32(newVariation.GetNeonColorR(), newVariation.GetNeonColorG(), newVariation.GetNeonColorB())); variation.SetNeonLOn((newVariation.GetNeonFlags() & 1) != 0); variation.SetNeonROn((newVariation.GetNeonFlags() & 2) != 0); variation.SetNeonFOn((newVariation.GetNeonFlags() & 4) != 0); variation.SetNeonBOn((newVariation.GetNeonFlags() & 8) != 0); variation.SetKitIndex(newVariation.GetKitIndex()); variation.ClearMods(); variation.SetWheelType((eVehicleWheelType)newVariation.GetWheelType()); u32 streamingFlags = STRFLAG_PRIORITY_LOAD; if (variation.GetKit()) { for (s32 i = 0; i < VMT_TOGGLE_MODS; ++i) { variation.SetModIndex((eVehicleModType)i, newVariation.GetModIndex((eVehicleModType)i), this, false, streamingFlags); } for (s32 i = VMT_TOGGLE_MODS; i < VMT_MISC_MODS; ++i) { variation.ToggleMod((eVehicleModType)i, newVariation.IsToggleModOn((eVehicleModType)i)); } variation.SetModIndex(VMT_WHEELS, newVariation.GetModIndex(VMT_WHEELS), this, newVariation.HasModVariation(0), streamingFlags); variation.SetModIndex(VMT_WHEELS_REAR_OR_HYDRAULICS, newVariation.GetModIndex(VMT_WHEELS_REAR_OR_HYDRAULICS), this, newVariation.HasModVariation(1), streamingFlags); } } void CVehicle::GetExhaustMatrix(eHierarchyId exhaustId, Mat34V_InOut mat, s32& boneIndex) const { boneIndex = GetBoneIndex(exhaustId); if (boneIndex != -1) GetGlobalMtx(boneIndex, RC_MATRIX34(mat)); else mat.SetCol0(Vec3V(V_ZERO)); if (!IsModded()) return; // see which bone the exhaust mod is attached to, if we have no mod fall back to checking the exhaust bone, it might exist on the car s32 modBoneIdx = GetVariationInstance().GetModBoneIndex((u8)VMT_EXHAUST); if (modBoneIdx == -1) return; s32 modBoneIndex = GetBoneIndex((eHierarchyId)modBoneIdx); if (modBoneIndex == -1) return; // get matrix of bone we currently attach the mod to Matrix34 attachBoneMat; GetGlobalMtx(modBoneIndex, attachBoneMat); #if __ASSERT Mat34V vAttachBoneMtx = RCC_MAT34V(attachBoneMat); if (IsZeroAll(vAttachBoneMtx.GetCol0())==false) { ptxAssertf(IsFiniteAll(vAttachBoneMtx.GetCol0()), "exhaust matrix col0 is invalid 1 (%.3f, %.3f, %.3f)", vAttachBoneMtx.GetCol0().GetXf(), vAttachBoneMtx.GetCol0().GetYf(), vAttachBoneMtx.GetCol0().GetZf()); ptxAssertf(IsFiniteAll(vAttachBoneMtx.GetCol1()), "exhaust matrix col1 is invalid 1 (%.3f, %.3f, %.3f)", vAttachBoneMtx.GetCol1().GetXf(), vAttachBoneMtx.GetCol1().GetYf(), vAttachBoneMtx.GetCol1().GetZf()); ptxAssertf(IsFiniteAll(vAttachBoneMtx.GetCol2()), "exhaust matrix col2 is invalid 1 (%.3f, %.3f, %.3f)", vAttachBoneMtx.GetCol2().GetXf(), vAttachBoneMtx.GetCol2().GetYf(), vAttachBoneMtx.GetCol2().GetZf()); ptxAssertf(IsFiniteAll(vAttachBoneMtx.GetCol3()), "exhaust matrix col3 is invalid 1 (%.3f, %.3f, %.3f)", vAttachBoneMtx.GetCol3().GetXf(), vAttachBoneMtx.GetCol3().GetYf(), vAttachBoneMtx.GetCol3().GetZf()); ptxAssertf(vAttachBoneMtx.IsOrthonormal3x3(ScalarV(V_FLT_SMALL_2)), "exhaust matrix isn't orthonormal 1 (%.3f, %.3f, %.3f - %.3f, %.3f, %.3f - %.3f, %.3f, %.3f)", vAttachBoneMtx.GetCol0().GetXf(), vAttachBoneMtx.GetCol0().GetYf(), vAttachBoneMtx.GetCol0().GetZf(), vAttachBoneMtx.GetCol1().GetXf(), vAttachBoneMtx.GetCol1().GetYf(), vAttachBoneMtx.GetCol1().GetZf(), vAttachBoneMtx.GetCol2().GetXf(), vAttachBoneMtx.GetCol2().GetYf(), vAttachBoneMtx.GetCol2().GetZf()); } #endif if (GetVariationInstance().GetVehicleRenderGfx()) { // set mod matrix but use the position from vehicle skeleton Mat34V modMat = Mat34V(MATRIX33_TO_MAT33V(GetVariationInstance().GetVehicleRenderGfx()->GetMatrix((u8)VMT_EXHAUST))); modMat.Setd(VECTOR3_TO_VEC3V(attachBoneMat.d)); // if we have a custom bone in the mod, transform the matrix by that bone to get a correct offset u8 exhaustModIndex = GetVariationInstance().GetVehicleRenderGfx()->GetExhaustModIndex(exhaustId); if (exhaustModIndex != INVALID_MOD) { fragType* frag = GetVariationInstance().GetVehicleRenderGfx()->GetFrag(exhaustModIndex); if (frag) { s8 exhaustBoneIndex = GetVariationInstance().GetVehicleRenderGfx()->GetExhaustBoneIndex(exhaustId); if (Verifyf(exhaustBoneIndex != -1, "Missing exhaust bone in exhaust mod!")) { Mat34V_ConstRef defaultModMat = frag->GetSkeletonData().GetDefaultTransform(exhaustBoneIndex); rage::Transform(mat, modMat, defaultModMat); // HACK: // vfx system later gets this matrix from the entity by just passing in the bone index // which is specific to a skeleton instance. we hack in our own value in higher nibbles // so we know it's an exhaust bone u32 exhaustBits = (exhaustId - VEH_EXHAUST) & 0xff; boneIndex = (s32)(((u32)modBoneIndex) | (exhaustBits << 24)); // use last nibble } } } } #if __ASSERT if (IsZeroAll(mat.GetCol0())==false) { ptxAssertf(IsFiniteAll(mat.GetCol0()), "exhaust matrix col0 is invalid 2 (%.3f, %.3f, %.3f)", mat.GetCol0().GetXf(), mat.GetCol0().GetYf(), mat.GetCol0().GetZf()); ptxAssertf(IsFiniteAll(mat.GetCol1()), "exhaust matrix col1 is invalid 2 (%.3f, %.3f, %.3f)", mat.GetCol1().GetXf(), mat.GetCol1().GetYf(), mat.GetCol1().GetZf()); ptxAssertf(IsFiniteAll(mat.GetCol2()), "exhaust matrix col2 is invalid 2 (%.3f, %.3f, %.3f)", mat.GetCol2().GetXf(), mat.GetCol2().GetYf(), mat.GetCol2().GetZf()); ptxAssertf(IsFiniteAll(mat.GetCol3()), "exhaust matrix col3 is invalid 2 (%.3f, %.3f, %.3f)", mat.GetCol3().GetXf(), mat.GetCol3().GetYf(), mat.GetCol3().GetZf()); ptxAssertf(mat.IsOrthonormal3x3(ScalarV(V_FLT_SMALL_2)), "exhaust matrix isn't orthonormal 2 (%.3f, %.3f, %.3f - %.3f, %.3f, %.3f - %.3f, %.3f, %.3f)", mat.GetCol0().GetXf(), mat.GetCol0().GetYf(), mat.GetCol0().GetZf(), mat.GetCol1().GetXf(), mat.GetCol1().GetYf(), mat.GetCol1().GetZf(), mat.GetCol2().GetXf(), mat.GetCol2().GetYf(), mat.GetCol2().GetZf()); } #endif } bool CVehicle::GetExhaustMatrixFromBoneIndex(Mat34V_InOut mat, s32 boneIndex) const { if (boneIndex == -1) return false; if ((0xff000000 & ((u32)boneIndex)) == 0) return false; // not a modded exhaust bone u32 exhaustId = (((u32)boneIndex) >> 24) & 0xff; s32 dummyIndex; GetExhaustMatrix((eHierarchyId)(VEH_EXHAUST + exhaustId), mat, dummyIndex); return true; } bool CVehicle::GetThrustMatrixFromBoneIndex(Mat34V_InOut mat, s32 boneIndex) const { static const Vec3V boneOffset[] = { Vec3V( 0.0f, 0.0f, 0.0f ), Vec3V( 0.011f, 0.0f, -0.413f ), Vec3V( 0.039f, 0.0f, -0.826f ), Vec3V( 0.097f, 0.0f, -1.239f ) }; if( boneIndex == -1 ) return false; if( (0xf0000000 & ((u32)boneIndex)) == 0 ) { return false; // not a modded thrust bone } s32 trustBoneIndex = (0xfffffff & (u32)boneIndex); if( trustBoneIndex != GetVehicleModelInfo()->GetBoneIndex(VEH_ROCKET_BOOST) ) { return false; // not a modded thrust bone } u32 exhaustId = (((u32)boneIndex) >> 28) & 0xf; GetGlobalMtx( trustBoneIndex, RC_MATRIX34(mat) ); Vec3V offset = boneOffset[ exhaustId % 4 ]; offset += GetTransform().UnTransform( mat.GetCol3() ); if( exhaustId >= 4 ) { offset.SetX( offset.GetX() * ScalarV( -1.0f ) ); } offset = GetTransform().Transform( offset ); mat.SetCol3( offset ); return true; } void CVehicle::GetExtraLightMatrix(eHierarchyId extraLightId, Mat34V_InOut mat, s32& boneIndex, const char** BANK_ONLY(boneName)) const { boneIndex = -1; const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (!IsModded() || variationInstance.GetKitIndex() == INVALID_VEHICLE_KIT_INDEX || !variationInstance.GetVehicleRenderGfx() || variationInstance.GetVehicleRenderGfx()->GetExtraLightModIndex(extraLightId) == INVALID_MOD) { boneIndex = GetBoneIndex(extraLightId); if (boneIndex != -1) { const CDynamicEntity* pDynamicEntity = static_cast(this); { if (pDynamicEntity->GetSkeleton()) { pDynamicEntity->GetGlobalMtx(boneIndex, RC_MATRIX34(mat)); return; } } } return; } u8 modIndex = variationInstance.GetVehicleRenderGfx()->GetExtraLightModIndex(extraLightId); Assert(modIndex != INVALID_MOD); if (modIndex != INVALID_MOD) { // find the bone this mod is attached to s32 modBoneIdx = GetVariationInstance().GetModBoneIndex(modIndex); if (modBoneIdx == -1) { Assertf(false, "Invalid bone specified on mod! veh: %s, modIndex %d", GetModelName(), modIndex); boneIndex = GetBoneIndex(extraLightId); if (boneIndex != -1) GetGlobalMtx(boneIndex, RC_MATRIX34(mat)); return; } boneIndex = GetBoneIndex((eHierarchyId)modBoneIdx); if (boneIndex == -1) return; // get matrix of bone we currently attach the mod to Matrix34 attachBoneMat; GetGlobalMtx(boneIndex, attachBoneMat); // set mod matrix but use the position from vehicle skeleton mat = Mat34V(MATRIX33_TO_MAT33V(variationInstance.GetVehicleRenderGfx()->GetMatrix(modIndex))); mat.Setd(VECTOR3_TO_VEC3V(attachBoneMat.d)); fragType* frag = variationInstance.GetVehicleRenderGfx()->GetFrag(modIndex); if (frag) { s8 lightBoneIndex = GetVariationInstance().GetVehicleRenderGfx()->GetExtraLightBoneIndex(extraLightId); if (Verifyf(lightBoneIndex != -1, "Missing extra light bone in extra light mod!")) { Mat34V_ConstRef modMat = frag->GetSkeletonData().GetDefaultTransform(lightBoneIndex); rage::Transform(mat, mat, modMat); #if __BANK if (boneName) *boneName = frag->GetSkeletonData().GetBoneData(lightBoneIndex)->GetName(); #endif // __BANK } else { boneIndex = -1; } } else { boneIndex = -1; } } } void CVehicle::GetExtraLightMatrix(const crSkeleton *skeleton, eHierarchyId extraLightId, Mat34V_InOut mat, s32& boneIndex) const { Assert(skeleton); boneIndex = -1; const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (!IsModded() || variationInstance.GetKitIndex() == INVALID_VEHICLE_KIT_INDEX || !variationInstance.GetVehicleRenderGfx() || variationInstance.GetVehicleRenderGfx()->GetExtraLightModIndex(extraLightId) == INVALID_MOD) { boneIndex = GetBoneIndex(extraLightId); if (boneIndex != -1) { skeleton->GetGlobalMtx(boneIndex, mat); return; } return; } u8 modIndex = variationInstance.GetVehicleRenderGfx()->GetExtraLightModIndex(extraLightId); Assert(modIndex != INVALID_MOD); if (modIndex != INVALID_MOD) { // find the bone this mod is attached to s32 modBoneIdx = GetVariationInstance().GetModBoneIndex(modIndex); if (modBoneIdx == -1) { Assertf(false, "Invalid bone specified on mod! veh: %s, modIndex %d", GetModelName(), modIndex); boneIndex = GetBoneIndex(extraLightId); if (boneIndex != -1) skeleton->GetGlobalMtx(boneIndex, mat); return; } boneIndex = GetBoneIndex((eHierarchyId)modBoneIdx); if (boneIndex == -1) return; // get matrix of bone we currently attach the mod to Mat34V attachBoneMat; skeleton->GetGlobalMtx(boneIndex, attachBoneMat); // set mod matrix but use the position from vehicle skeleton mat = Mat34V(MATRIX33_TO_MAT33V(variationInstance.GetVehicleRenderGfx()->GetMatrix(modIndex))); mat.Setd(attachBoneMat.d()); fragType* frag = variationInstance.GetVehicleRenderGfx()->GetFrag(modIndex); if (frag) { s8 lightBoneIndex = GetVariationInstance().GetVehicleRenderGfx()->GetExtraLightBoneIndex(extraLightId); if (Verifyf(lightBoneIndex != -1, "Missing extra light bone in extra light mod!")) { Mat34V_ConstRef modMat = frag->GetSkeletonData().GetDefaultTransform(lightBoneIndex); rage::Transform(mat, mat, modMat); } else { boneIndex = -1; } } else { boneIndex = -1; } } } int CVehicle::GetExtraLightPosition(eHierarchyId extraLightId, Vector3& vecResult) const { int boneIndex = -1; const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (!IsModded() || variationInstance.GetKitIndex() == INVALID_VEHICLE_KIT_INDEX || !variationInstance.GetVehicleRenderGfx() || variationInstance.GetVehicleRenderGfx()->GetExtraLightModIndex(extraLightId) == INVALID_MOD) { boneIndex = GetBoneIndex(extraLightId); if (boneIndex != -1) { GetDefaultBonePositionSimple(boneIndex, vecResult); } return boneIndex; } u8 modIndex = variationInstance.GetVehicleRenderGfx()->GetExtraLightModIndex(extraLightId); Assert(modIndex != INVALID_MOD); if (modIndex != INVALID_MOD) { // find the bone this mod is attached to s32 modBoneIdx = GetVariationInstance().GetModBoneIndex(modIndex); if (modBoneIdx == -1) { Assertf(false, "Invalid bone specified on mod! veh: %s, modIndex %d", GetModelName(), modIndex); boneIndex = GetBoneIndex(extraLightId); if (boneIndex != -1) { GetDefaultBonePositionSimple(boneIndex, vecResult); } return boneIndex; } boneIndex = GetBoneIndex((eHierarchyId)modBoneIdx); if (boneIndex == -1) return -1; // get matrix of bone we currently attach the mod to GetDefaultBonePositionSimple(boneIndex, vecResult); fragType* frag = variationInstance.GetVehicleRenderGfx()->GetFrag(modIndex); if (frag) { s8 lightBoneIndex = GetVariationInstance().GetVehicleRenderGfx()->GetExtraLightBoneIndex(extraLightId); if (Verifyf(lightBoneIndex != -1, "Missing extra light bone in extra light mod!")) { const crSkeletonData& skelData = frag->GetSkeletonData(); const crBoneData* boneData = skelData.GetBoneData(lightBoneIndex); vecResult += RCC_VECTOR3(boneData->GetDefaultTranslation()); const crBoneData* pParentBoneData = boneData->GetParent(); while(pParentBoneData) { vecResult += RCC_VECTOR3(pParentBoneData->GetDefaultTranslation()); pParentBoneData = pParentBoneData->GetParent(); } } } } return boneIndex; } bool CVehicle::AllowBonnetSlide() const { u8 bonnetModIndex = GetVariationInstance().GetModIndex(VMT_BONNET); if (bonnetModIndex != INVALID_MOD) { const CVehicleKit* kit = GetVariationInstance().GetKit(); if (kit) { const CVehicleModVisible& mod = kit->GetVisibleMods()[bonnetModIndex]; if (mod.GetType() == VMT_BONNET) { return mod.AllowBonnetSlide(); } } } return true; } void CVehicle::GetGlobalMtx(int boneIdx, Matrix34& matrix) const { CPhysical::GetGlobalMtx(boneIdx, matrix); const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (boneIdx == -1 || variationInstance.GetKitIndex() == INVALID_VEHICLE_KIT_INDEX || !variationInstance.GetVehicleRenderGfx()) return; Matrix34 boneMat; Matrix33 rotMat; for (u8 i = 0; i < VMT_RENDERABLE; ++i) { if (variationInstance.GetMods()[i] == INVALID_MOD) continue; s32 bone = variationInstance.GetBone(i); if (bone == -1) continue; int modBoneIdx = GetBoneIndex((eHierarchyId)bone); if (modBoneIdx != boneIdx) continue; Matrix33 modMat = variationInstance.GetVehicleRenderGfx()->GetMatrix(i); matrix.a = modMat.a; matrix.b = modMat.b; matrix.c = modMat.c; return; } for (u8 i = 0; i < MAX_LINKED_MODS; ++i) { s32 bone = variationInstance.GetBoneForLinkedMod(i); if (bone != -1) continue; int modBoneIdx = GetBoneIndex((eHierarchyId)bone); if (modBoneIdx != boneIdx) continue; Matrix33 modMat = variationInstance.GetVehicleRenderGfx()->GetMatrix(i); matrix.a = modMat.a; matrix.b = modMat.b; matrix.c = modMat.c; return; } } void CVehicle::GetLockOnTargetAimAtPos( Vector3& aimAtPos) const { TUNE_GROUP_BOOL(PLAYER_TARGETING, bUseRootPositionForLockonForAllHeliTargets, true); if (bUseRootPositionForLockonForAllHeliTargets && InheritsFromHeli()) { // Helicopter propellers continuously expand and contract the bounds while they're spinning aimAtPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); } else { GetBoundCentre( aimAtPos ); } // B*2163081: Lock-on position offset, set in vehicles.meta. if (GetVehicleModelInfo()) { if (GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_ROOT_AS_BASE_LOCKON_POS)) { aimAtPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); } // Make the local offset relative to vehicle orientation. Vector3 vOffset = GetVehicleModelInfo()->GetLockOnPositionOffset(); #if __BANK TUNE_GROUP_BOOL(PLAYER_TARGETING, bOverrideVehicleLockOnPositionOffset, false); TUNE_GROUP_FLOAT(PLAYER_TARGETING, fLockonOffsetOverrideX, 0.0f, -100.0f, 100.0f, 0.1f); TUNE_GROUP_FLOAT(PLAYER_TARGETING, fLockonOffsetOverrideY, 0.0f, -100.0f, 100.0f, 0.1f); TUNE_GROUP_FLOAT(PLAYER_TARGETING, fLockonOffsetOverrideZ, 0.0f, -100.0f, 100.0f, 0.1f); if (bOverrideVehicleLockOnPositionOffset) vOffset = Vector3(fLockonOffsetOverrideX,fLockonOffsetOverrideY,fLockonOffsetOverrideZ); #endif Matrix34 vehMat = MAT34V_TO_MATRIX34(GetTransform().GetMatrix()); vehMat.Transform3x3(vOffset); aimAtPos += vOffset; } } bool CVehicle::HasExtraLights() const { CVehicleModelInfo* vmi = GetVehicleModelInfo(); if (!vmi) return false; if (vmi->HasExtraLights()) return true; const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (!IsModded() || variationInstance.GetKitIndex() == INVALID_VEHICLE_KIT_INDEX || !variationInstance.GetVehicleRenderGfx()) return false; if (variationInstance.GetVehicleRenderGfx()->GetExtraLightCount() > 0) return true; return false; } bool CVehicle::HasNeons() const { CVehicleModelInfo* vmi = GetVehicleModelInfo(); if (!vmi) return false; if (vmi->HasNeons()) return true; return false; } bool CVehicle::HasDoubleExtraLights() const { CVehicleModelInfo* vmi = GetVehicleModelInfo(); if (!vmi) return false; if (vmi->DoubleExtraLights()) return true; const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (!IsModded() || variationInstance.GetKitIndex() == INVALID_VEHICLE_KIT_INDEX || !variationInstance.GetVehicleRenderGfx()) return false; if (variationInstance.GetVehicleRenderGfx()->GetExtraLightCount() > 2 || (variationInstance.GetVehicleRenderGfx()->GetExtraLightCount() > 0 && vmi->HasExtraLights())) return true; return false; } void CVehicle::PartHasBrokenOff(eHierarchyId id) { s32 boneIndex = GetBoneIndex(id); if (id >= VEH_WHEEL_LF && id <= VEH_WHEEL_LAST_WHEEL) { // wheels don't turn off bones so no need to do the relatively expensive check, we just record what wheel slot // was broken off if (id == VEH_WHEEL_LR && InheritsFromBike()) ms_slotTurnedOff = VMT_RENDERABLE + MAX_LINKED_MODS + 1; // rear wheel, bikes only else ms_slotTurnedOff = VMT_RENDERABLE + MAX_LINKED_MODS; // front wheels, or any wheel on non-bikes ms_boneTurnedOff = boneIndex; } else { // for non-wheels, we find out which slot was turned off and save this so the soon-to-be-created object can use it // as its drawable if (boneIndex != -1) if (!GetVehicleDrawHandler().GetVariationInstance().HideModOnBone(this, boneIndex, ms_slotTurnedOff)) ms_boneTurnedOff = boneIndex; } m_nVehicleFlags.bPartsBrokenOff = true; } bool CVehicle::CanCreateBrokenPart() { if (ms_boneTurnedOff == -1) return true; return !GetVehicleDrawHandler().GetVariationInstance().IsBoneTurnedOff(this, ms_boneTurnedOff); } void CVehicle::SetWheelBroken(u32 wheelIndex) { m_wheelBrokenIndex |= 1 << wheelIndex; } const bool CVehicle::GetWheelBroken(u32 wheelIndex) const { return (m_wheelBrokenIndex & (1 << wheelIndex)) ? true : false; } void CVehicle::TryToGiveEventReponseTaskToOccupant(CPed* pPed) { //check if we have a pretend occupant even saved off //we might already have a real driver, and have passengers scheduled //so the flag might be false, and we need to check if we've got more real peds to spawn //from pretend //for safety, only do this for non-drivers Assert(pPed); VehicleEventPriority::VehicleEventPriority vehEvent = GetIntelligence()->GetPretendOccupantEventPriority(); if (vehEvent > VehicleEventPriority::VEHICLE_EVENT_PRIORITY_NONE && !pPed->IsPlayer() && (m_nVehicleFlags.bUsingPretendOccupants || (!IsDriver(pPed) && GetIntelligence()->GetNumPedsThatNeedTaskFromPretendOccupant() > 0)) ) { if (vehEvent == VehicleEventPriority::VEHICLE_EVENT_PRIORITY_RESPOND_TO_THREAT) { //should we set up a smart flee here? CAITarget newTarget; const sVehicleMissionParams& oldParams = GetIntelligence()->GetPretendOccupantEventParams(); bool bHasValidTarget = false; if (oldParams.GetTargetEntity().GetEntity()) { bHasValidTarget = true; newTarget.SetEntity(oldParams.GetTargetEntity().GetEntity()); } else if (oldParams.IsTargetPositionValid()) { bHasValidTarget = true; newTarget.SetPosition(oldParams.GetTargetPosition()); } if (bHasValidTarget) { CTask* pFleeTask = rage_new CTaskSmartFlee(newTarget); CEvent* pVehicleCurrentEvent = GetIntelligence()->GetCurrentEvent(); if (pVehicleCurrentEvent) { pPed->GetPedIntelligence()->OverrideCurrentEventResponseEvent(*(static_cast(pVehicleCurrentEvent))); //override event response too? //pPed->GetPedIntelligence()->SetEventReponse(CEventResponse::EventResponse, pFleeTask); } pPed->GetPedIntelligence()->AddTaskAtPriority(pFleeTask, PED_TASK_PRIORITY_EVENT_RESPONSE_NONTEMP); } } else if (vehEvent == VehicleEventPriority::VEHICLE_EVENT_PRIORITY_RESPOND_TO_SIREN) { if (IsDriver(pPed)) { //return rage_new CTaskCarDrive(this, GetIntelligence()->GetActiveTask(), true); pPed->GetPedIntelligence()->AddTaskAtPriority(rage_new CTaskControlVehicle(this, GetIntelligence()->GetActiveTask()), PED_TASK_PRIORITY_PRIMARY); } else { //return rage_new CTaskInVehicleBasic( this, false ); pPed->GetPedIntelligence()->AddTaskDefault(rage_new CTaskInVehicleBasic( this, false )); } } } } #if __BANK void CVehicle::LogPretendOccupantsFail() const { if(NetworkInterface::IsGameInProgress() && PARAM_lognetpretendoccupantfailures.Get()) { NetworkLogUtils::WriteLogEvent(NetworkInterface::GetObjectManagerLog(), "PRETEND_OCCUPANTS_FAIL", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?"); NetworkInterface::GetObjectManagerLog().WriteDataValue("Fail Reason", "%s", CVehicle::GetRealPedFailReason(m_RealPedFailReason)); NetworkInterface::GetObjectManagerLog().WriteDataValue("Total Num Peds", "%d", CNetworkObjectPopulationMgr::GetTotalNumObjectsOfType(NET_OBJ_TYPE_PED)); NetworkInterface::GetObjectManagerLog().WriteDataValue("Local Num Peds", "%d", CNetworkObjectPopulationMgr::GetNumLocalObjects(NET_OBJ_TYPE_PED)); NetworkInterface::GetObjectManagerLog().WriteDataValue("Max Local Peds", "%d", CNetworkObjectPopulationMgr::GetMaxObjectsAllowedToOwnOfType(NET_OBJ_TYPE_PED)); } } #endif // __BANK //------------------------------------------------------------------------- // Name : TryToMakePedsFromPretendOccupants // Purpose : Restores ped occupants from pretend occupant data and // marks that we are no longer using pretend occupants. // Parameters : bool onlyAddDriver - if true will only add a driver to the vehicle // Returns : Whether or not the conversion was successful. //------------------------------------------------------------------------- bool CVehicle::TryToMakePedsFromPretendOccupants(bool onlyAddDriver /*= false*/, const int* in_pMaxNumPedsToMakeReal /*= NULL*/, const CAmbientModelSetFilter * pModelsFilter /* = NULL */ ) { // Make sure that we aren't already using real peds. if(!m_nVehicleFlags.bUsingPretendOccupants) { // The conversion has already happened. return true; } // We aren't allowed to use pretend occupants for cloned vehicles. if(IsNetworkClone() || (GetNetworkObject() && GetNetworkObject()->IsPendingOwnerChange())) { if(!IsNetworkClone()) { //NetworkInterface::GetObjectManagerLog().WriteMessageHeader(CNetworkLog::LOG_LEVEL_HIGH, "PRETEND_OCCUPANTS_FAIL", "PENDING_OWNER_CHANGE"); } #if __BANK m_RealPedFailReason = RPFR_IS_NET_CLONE; #endif // __BANK m_nVehicleFlags.bFailedToResetPretendOccupants = true; return false; } // check if we can create at least one ped (for a driver) if(NetworkInterface::IsGameInProgress()) { if(GetNetworkObject()==NULL) { #if __BANK m_RealPedFailReason = RPFR_IS_NOT_NET_OBJECT; LogPretendOccupantsFail(); #endif // __BANK m_nVehicleFlags.bFailedToResetPretendOccupants = true; return false; } // need to turn off restrictions on local population based on local factors - // so reservations of peds for pretend occupants are not enforced NetworkInterface::SetProcessLocalPopulationLimits(false); bool canRegisterPed = NetworkInterface::CanRegisterObject(NET_OBJ_TYPE_PED, false); NetworkInterface::SetProcessLocalPopulationLimits(true); if(!canRegisterPed) { #if __BANK //NetworkInterface::GetObjectManagerLog().WriteMessageHeader(CNetworkLog::LOG_LEVEL_HIGH, "PRETEND_OCCUPANTS_FAIL", "%s", NetworkInterface::GetLastRegistrationFailureReason()); m_RealPedFailReason = RPFR_NET_OBJ_REGISTER_FAILED; LogPretendOccupantsFail(); #endif m_nVehicleFlags.bFailedToResetPretendOccupants = true; return false; } } // Make sure there is a decent buffer of free spaces to creat peds from. CPed::Pool* pedPool = CPed::GetPool(); Assert(pedPool); if(pedPool->GetNoOfFreeSpaces() < 5) { #if __BANK m_RealPedFailReason = RPFR_PED_POOL_FULL; LogPretendOccupantsFail(); #endif // __BANK m_nVehicleFlags.bFailedToResetPretendOccupants = true; return false; } // Make sure there are some applicable peds for the zone the player // is in to use. if(!gPopStreaming.IsFallbackPedAvailable()) { #if __BANK m_RealPedFailReason = RPFR_NO_FALLBACK_PED; LogPretendOccupantsFail(); #endif // __BANK // We can't create peds because there are no applicable peds. m_nVehicleFlags.bFailedToResetPretendOccupants = true; return false; } // Restore the vehicle occupants. // This could become more sophisticated and use information // about the occupants stored earlier, but we'll keep it // simple for now. u32 nNumPedsCreatedOrScheduled = 0; bool bOccupantsAdded = false; CVehiclePopulation::ms_bUseExistingCarNodes = true; // We don't want to recalculate the nodes of this vehicle as this will result in sudden direction change if (IsLawEnforcementVehicle()) { // need to turn off restrictions on local population based on local factors - // so reservations of peds for pretend occupants are not enforced NetworkInterface::SetProcessLocalPopulationLimits(false); // this will add all police veh occupants or as many as can be added in a network game bOccupantsAdded = CVehiclePopulation::AddPoliceVehOccupants(this); NetworkInterface::SetProcessLocalPopulationLimits(true); //this isn't technically right but cops will get the right task anyway if (bOccupantsAdded) { nNumPedsCreatedOrScheduled = 1; } #if __BANK if( !bOccupantsAdded ) { m_RealPedFailReason = RPFR_ADD_POLICE_FAILED; } #endif // __BANK } else // NOT a law enforcement vehicle { if(NetworkInterface::IsGameInProgress()) { fwModelId driverPedModelId; if (pModelsFilter) { driverPedModelId.SetModelIndex(CScenarioManager::GetRandomDriverModelIndex(GetModelId(), pModelsFilter)); } else { driverPedModelId = CPedPopulation::FindSpecificDriverModelForCarToUse(GetModelId()); } if(driverPedModelId.IsValid() && !CModelInfo::HaveAssetsLoaded(driverPedModelId)) { CModelInfo::RequestAssets(driverPedModelId, STRFLAG_FORCE_LOAD | STRFLAG_DONTDELETE); #if __BANK m_RealPedFailReason = RPFR_NET_REQUEST_DRIVER_MODEL; #endif // __BANK } else { // need to turn off restrictions on local population based on local factors - // so reservations of peds for pretend occupants are not enforced NetworkInterface::SetProcessLocalPopulationLimits(false); //MP - Allow the SetUpDriver processing if this is any vehicle except a train - as before. //Now, allow the SetUpDriver processing also if this is a train and it is an engine. //This was precluding train drivers from being created in MP. (lavalley) bool bProcessSetupDriver = true; if (InheritsFromTrain()) { //Only allow the setupdriver processing if this is the engine CTrain* pTrain = static_cast(this); if (!pTrain || !pTrain->IsEngine()) bProcessSetupDriver = false; } if (bProcessSetupDriver) bOccupantsAdded = (SetUpDriver(true) != NULL); NetworkInterface::SetProcessLocalPopulationLimits(true); if (bOccupantsAdded) { nNumPedsCreatedOrScheduled = 1; if (driverPedModelId.IsValid()) { Assert(CModelInfo::HaveAssetsLoaded(driverPedModelId)); CModelInfo::SetAssetsAreDeletable(driverPedModelId); } } #if __BANK if( !bOccupantsAdded ) { m_RealPedFailReason = RPFR_NET_SET_UP_DRIVER; } #endif // __BANK } } else // NOT a network game #if GTA_REPLAY if(!CReplayMgr::IsEditModeActive()) #endif { if (onlyAddDriver) { // once the car has been added to the world we can add its driver fwModelId driverId; if (pModelsFilter) { driverId.SetModelIndex(CScenarioManager::GetRandomDriverModelIndex(GetModelId(), pModelsFilter)); bOccupantsAdded = (SetUpDriver(true, true, driverId.GetModelIndex(), NULL))? true : false; #if __BANK if( !bOccupantsAdded ) { m_RealPedFailReason = RPFR_FILTERED_SETUP_DRIVER; } #endif // __BANK } else { bOccupantsAdded = (SetUpDriver(true))? true : false; #if __BANK if( !bOccupantsAdded ) { m_RealPedFailReason = RPFR_UNFILTERED_SETUP_DRIVER; } #endif // __BANK } nNumPedsCreatedOrScheduled = 1; } else // NOT only driver { // default max peds to add is generous int maxNumPedsToAdd = 99; if( in_pMaxNumPedsToMakeReal ) { // if max peds to add is specified, use that value maxNumPedsToAdd = *in_pMaxNumPedsToMakeReal; } // if max is positive non-zero if( maxNumPedsToAdd > 0 ) { const bool bUseExistingNodes = true; if (this->InheritsFromTrain()) { const int minNumPedsToAdd = 1; nNumPedsCreatedOrScheduled = CVehiclePopulation::AddDriverAndPassengersForTrain(static_cast(this), minNumPedsToAdd, maxNumPedsToAdd, bUseExistingNodes); #if __BANK if( nNumPedsCreatedOrScheduled <= 0 ) { m_RealPedFailReason = RPFR_ADD_FOR_TRAIN; } #endif // __BANK } else { const int minNumPassengerPedsToAdd = 0; const int maxNumPassengerPedsToAdd = Max(0, maxNumPedsToAdd - 1); // subtract one for the driver nNumPedsCreatedOrScheduled = CVehiclePopulation::AddDriverAndPassengersForVeh(this, minNumPassengerPedsToAdd, maxNumPassengerPedsToAdd, bUseExistingNodes); #if __BANK if( nNumPedsCreatedOrScheduled <= 0 ) { m_RealPedFailReason = RPFR_ADD_FOR_VEH; } #endif // __BANK } bOccupantsAdded = nNumPedsCreatedOrScheduled > 0; } } } } CVehiclePopulation::ms_bUseExistingCarNodes = false; // Set this back. // bOccupantsAdded can be false if the ped pool is full or in a network game if // there is not an available network object for the new driver. if (!bOccupantsAdded) { #if __BANK LogPretendOccupantsFail(); #endif // __BANK m_nVehicleFlags.bFailedToResetPretendOccupants = true; return false; } //set the alpha fade values for (s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); ++iSeat) { CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat); if (pPassenger) { pPassenger->GetLodData().SetResetDisabled(false); pPassenger->GetLodData().SetResetAlpha(true); TryToGiveEventReponseTaskToOccupant(pPassenger); } } // Mark that we are no longer using pretend occupants. m_nVehicleFlags.bUsingPretendOccupants = false; m_nVehicleFlags.bFailedToResetPretendOccupants = false; //we need to keep this around only long enough until all the scheduled //peds are spawned. the driver's taken care of, so if there are no passengers, //reset it now Assert(nNumPedsCreatedOrScheduled > 0); GetIntelligence()->SetNumPedsThatNeedTaskFromPretendOccupant(nNumPedsCreatedOrScheduled - 1); if (nNumPedsCreatedOrScheduled - 1 < 1) { GetIntelligence()->ResetPretendOccupantEventData(); GetIntelligence()->FlushPretendOccupantEventGroup(); } // If the vehicle is out of sight, enable the neons if(!GetIsVisibleInSomeViewportThisFrame()) { CVehiclePopulation::EnableVehicleNeonsIfAppropriate(this); } // The conversion was able to happen. #if __BANK m_RealPedFailReason = CVehicle::RPFR_SUCCEEDED; #endif // __BANK return true; } void CVehicle::GiveDefaultTask() { sVehicleMissionParams params; //Add the driving flags for the ambient vehicle. s32 iDrivingFlags = params.m_iDrivingFlags; CVehiclePopulation::AddDrivingFlagsForAmbientVehicle(iDrivingFlags, InheritsFromBike()); params.m_iDrivingFlags = iDrivingFlags; params.m_fCruiseSpeed = rage::Min(CVehiclePopulation::PickCruiseSpeedWithVehModel(this, GetModelIndex()) , CDriverPersonality::FindMaxCruiseSpeed(GetDriver(), this, GetVehicleType() == VEHICLE_TYPE_BICYCLE)); const bool bSpeedComesFromVehPopulation = true; CTask* pTask = CVehicleIntelligence::CreateCruiseTask(*this, params, bSpeedComesFromVehPopulation); GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false); } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : FixupIntersectionWithWheelSurface // PURPOSE : Make sure that the bounding box is never so low that it // intersects the plane defined by the intersection points (from // the wheels). If it does then move it away from the plane until // it no longer intersects. // Note: this is done as an approximation since it only uses the // intersection points and not the actual road surface and is not // guaranteed to succeed (it can fail when the actual ground is // rough or oddly shaped). // INPUTS: testResults - a container for the probe intersection // data (usually from the wheel probe code). // OUTPUTS: Nothing. ///////////////////////////////////////////////////////////////////////////////// void CVehicle::FixupIntersectionWithWheelSurface(WorldProbe::CShapeTestResults& testResults) { // Determine the plane formed by the wheel intersection points. Vector3 wheelSurfacePlaneNormal (0.0f,0.0f,0.0f); float wheelSurfacePlaneDistance = 0.0f; { // Get the average normal of the wheel intersection points and then // use it as an approximation of the wheel surface plane normal. Vector3 cumulativeNormal(0.0f, 0.0f, 0.0f); u32 numIntersections = 0; WorldProbe::ResultIterator it = testResults.begin(); for(u32 i = 0; i < (u32)GetNumWheels(); ++i, ++it) { if(it->GetHitDetected()) { cumulativeNormal += it->GetHitNormal(); ++numIntersections; } } if(numIntersections == 0) { // Bail out and don't do anything since none of the test intersections // are actually intersecting. return; } else { // Determine the average normal to use for our wheel surface plane. wheelSurfacePlaneNormal = cumulativeNormal / static_cast(numIntersections); } // Find maximum height of all the points in reference to the // wheel surface plane's normal and then use the max height // from plane as an approximation of the wheel surface plane // distance. wheelSurfacePlaneDistance = -1000000.0f;// Small initial sentinel value. it = testResults.begin(); for(u32 i = 0; i < (u32) GetNumWheels(); ++i, ++it) { if(it->GetHitDetected()) { const float heightFromPlane = (it->GetHitPosition()).Dot(wheelSurfacePlaneNormal); if(heightFromPlane > wheelSurfacePlaneDistance) { wheelSurfacePlaneDistance = heightFromPlane; } } } Assert((wheelSurfacePlaneDistance != -1000000.0f)); } // Find the bounding box's minimum height relative to the plane. If // it is penetrating then we will need to move the bounding box along // the plane normal so that it doesn't. float minHeightFromPlane = 1000000.0f;// Large initial sentinel value. { // Get the bounding box points in world space. Vector3 boxPointsWorld[8]; { const Vector3 vecBoundMin = GetBoundingBoxMin(); const Vector3 vecBoundMax = GetBoundingBoxMax(); for(u32 i = 0; i < 8; ++i) { const Vector3 temp( (((i/4)%2)?(vecBoundMin.x):(vecBoundMax.x)), (((i/2)%2)?(vecBoundMin.y):(vecBoundMax.y)), (((i/1)%2)?(vecBoundMin.z):(vecBoundMax.z))); TransformIntoWorldSpace(boxPointsWorld[i], temp); } } // Find minimum height of all the points in reference to the // wheel surface plane. const Vector3 wheelSurfacePlanePoint(wheelSurfacePlaneNormal * wheelSurfacePlaneDistance); for(u32 i = 0; i < 8; ++i) { const float heightFromPlane = (boxPointsWorld[i] - wheelSurfacePlanePoint).Dot(wheelSurfacePlaneNormal); if(heightFromPlane < minHeightFromPlane) { minHeightFromPlane = heightFromPlane; } } } // Assert(minHeightFromPlane > -2.0f);// Make sure min height is always reasonable. // Check if we need to move the vehicle so that the box is // not penetrating anymore... // If so, translate the automobile along the plane normal by // the -minHeightFromPlane amount. if(minHeightFromPlane < 0) { static const float epsilon = 0.001f;// Just a little bit extra to make sure. const Vector3 translationRequired(wheelSurfacePlaneNormal * (-minHeightFromPlane + epsilon)); // Apply the translation. // Doing it here should be safe as it seems everywhere else where the // matrix is modified does it by first looking at the current matrix and // then using that to base their modifications on. Matrix34 newMatrix = MAT34V_TO_MATRIX34(GetMatrix()); newMatrix.d += translationRequired; // update contact points to compensate for the matrix being moved, so we don't apply forces with bad offsets for(int i=0; iUpdateContactsAfterNetworkBlend(MAT34V_TO_MATRIX34(GetMatrix()), newMatrix); SetMatrix(newMatrix); } } // // // // bool CVehicle::HasMissionCharsInIt(void) const { for (s32 iSeat= 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++) { CPed* pPedInSeat = m_SeatManager.GetPedInSeat(iSeat); if (pPedInSeat && pPedInSeat->PopTypeIsMission()) { return true; } } return false; } // // // // bool CVehicle::HasPedsInIt(void) const { if(m_nVehicleFlags.bUsingPretendOccupants) { return true; } if( m_SeatManager.CountPedsInSeats(true) > 0 ) { return true; } // Trains keep track of their own passengers outside of SeatManager. if (InheritsFromTrain()) { const CTrain* const pTrain = static_cast(this); if (pTrain->HasPassengers()) { return true; } } return false; } bool CVehicle::HasAlivePedsInIt(void) const { if(m_nVehicleFlags.bUsingPretendOccupants) { return true; } // Make sure the vehicle doesn't contain mission peds or players for (s32 iSeat= 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++) { CPed* pPedInSeat = m_SeatManager.GetPedInSeat(iSeat); if (pPedInSeat && !pPedInSeat->IsInjured()) { return true; } } return false; } bool CVehicle::HasAliveLawPedsInIt(void) const { if(IsLawEnforcementVehicle() && m_nVehicleFlags.bUsingPretendOccupants) { return true; } // Make sure the vehicle doesn't contain mission peds or players for (s32 iSeat= 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++) { CPed* pPedInSeat = m_SeatManager.GetPedInSeat(iSeat); if (pPedInSeat && !pPedInSeat->IsInjured() && pPedInSeat->IsLawEnforcementPed()) { return true; } } return false; } bool CVehicle::HasAliveVictimInIt(void) const { //not sure, I don't think we want this for the victim check -JM // if(m_nVehicleFlags.bUsingPretendOccupants) // { // return true; // } // Look for any non-medic that is a passenger for (s32 iSeat= 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++) { CPed* pPedInSeat = m_SeatManager.GetPedInSeat(iSeat); if (pPedInSeat && !pPedInSeat->IsInjured() && !pPedInSeat->GetIsDrivingVehicle() && pPedInSeat->GetPedType() != PEDTYPE_MEDIC) { return true; } } return false; } bool CVehicle::ForceNoSleep() { u32 iForcePhysics = 0; // Keep awake if we are resting on something that might move. iForcePhysics = (m_nVehicleFlags.bRestingOnPhysical && m_nVehicleFlags.bDontSleepOnThisPhysical); iForcePhysics |= m_nVehicleFlags.bWakeUpNextUpdate; // Keep awake while the no collisions for network flag is set, so the flag can be reset correctly. // NOTE: I'd think we could just do the reset in OnDeactivation. iForcePhysics |= GetNoCollisionFlags()&NO_COLLISION_NETWORK_OBJECTS; // Keep awake if muscles are driving the animation. iForcePhysics |= m_nVehicleFlags.bDriveMusclesToAnim; iForcePhysics |= m_specialFlightModeRatio == 1.0f; iForcePhysics |= m_hitByWeaponBlade == true; // This used to be a branch, but we don't call this function if we're a superdummy Assert((m_vehicleAiLod.GetDummyMode()!=VDM_SUPERDUMMY || !CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode)); if(WantsToBeAwake()) { iForcePhysics = TRUE; } else { for(int i=0; iWantsToBeAwake(this)); } for(int i=0; iWantsToBeAwake(this)); } for(int i=0; iIsControlledByLocalPlayer() // If vehicle is on ground then we are safe to go to sleep since aftertouch only affects vehicles in air && GetNumContactWheels() != GetNumWheels()) { #if USE_SIXAXIS_GESTURES if(CPadGestureMgr::GetMotionControlEnabled(CPadGestureMgr::MC_TYPE_AFTERTOUCH)) { iForcePhysics = TRUE; } else #endif { Assert(driver->IsAPlayerPed()); CControl* pControl = driver->GetControlFromPlayer(); if( (pControl->GetVehicleSteeringLeftRight().GetNorm() != 0.0f || pControl->GetVehicleSteeringUpDown().GetNorm() != 0.0f)) { iForcePhysics = TRUE; } } } } return iForcePhysics != 0; } bool CVehicle::ProcessIsAsleep() { bool isAsleep = false; // If no physics instance, or not in level, or flagged as fixed, then skip physics. bool bBasicAttachment = GetIsAttached() && GetAttachmentExtension() && GetAttachmentExtension()->IsAttachStateBasicDerived(); if(ENABLE_FRAG_OPTIMIZATION_ONLY(!GetHasFragCacheEntry() ||) GetCurrentPhysicsInst()==NULL || !GetCurrentPhysicsInst()->IsInLevel() || GetIsAnyFixedFlagSet() || bBasicAttachment) { isAsleep = true; } else { // If the vehicle isn't asleep, we don't need to do the expensive ForceNoSleep check (unless this is a vehicle recording) phCollider* collider = GetCollider(); if(collider == NULL || (collider->GetSleep() && collider->GetSleep()->IsAsleep())) { if(ForceNoSleep()) { // We need to force this vehicle to stay awake if(collider == NULL) { ActivatePhysics(); } else { collider->GetSleep()->Reset(true); } if(CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this)) { // Record that the game-side code wants this car awake this frame m_iLastActivationTime = fwTimer::GetTimeInMilliseconds(); } isAsleep = false; } else { // We don't want to keep the collider awake but the simulator might isAsleep = (collider == NULL); } } else { // This vehicle isn't asleep if(CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this) && ForceNoSleep()) { // Record that the game-side code wants this car awake this frame m_iLastActivationTime = fwTimer::GetTimeInMilliseconds(); } isAsleep = false; } } m_nVehicleFlags.bIsAsleep = isAsleep; m_nVehicleFlags.bWakeUpNextUpdate = false; return isAsleep; } // shared for cars and motorbikes void CVehicle::ProcessPlayerControlInputsForBrakeAndGasPedal(CControl* pControl, bool bForceBrake) { // Forwardness between -1 and 1 float fSpeed = DotProduct(GetVelocityIncludingReferenceFrame(), VEC3V_TO_VECTOR3(GetTransform().GetB())); float fAccelButton = pControl->GetVehicleAccelerate().GetNorm01(); float fBrakeButton = pControl->GetVehicleBrake().GetNorm01(); float fForwardness = fAccelButton - fBrakeButton; if(bForceBrake) { SetBrake(1.0f); SetThrottle(0.0f); } else if(!m_nVehicleFlags.bEngineOn && m_nVehicleFlags.bIsDrowning) { SetBrake(0.0f); SetThrottle(0.0f); } else if(GetDriver()->GetPedResetFlag(CPED_RESET_FLAG_PuttingOnHelmet)/* GetDriver() && GetDriver()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_PUT_ON_HELMET)*/) { SetBrake(0.0f); SetThrottle(0.0f); } // We've stopped. Go where we want to go. else if (rage::Abs(fSpeed) < 0.5f) { // if both brake and throttle are held down, do a burn out if(fAccelButton > 0.6f && fBrakeButton > 0.6f) { SetThrottle(fAccelButton); SetBrake(fBrakeButton); } else { SetThrottle(fForwardness); SetBrake(0.0f); } } else { // if both brake and throttle are held down, do a burn out if(fAccelButton > 0.6f && fBrakeButton > 0.6f) { SetThrottle(fAccelButton); SetBrake(fBrakeButton); } // going forwards else if (fSpeed >= 0.0f) { // we want to go forwards so go faster if (fForwardness >= 0.0f) { SetThrottle(fForwardness); SetBrake(0.0f); } // we don't want to go forwards - so brake else { SetThrottle(0.0f); SetBrake(-fForwardness); } } // going backwards else { // want to go forwards if (fForwardness >= 0.0f) { // let us sit on the gas pedal if we're trying to get up a slope but sliding back if(GetThrottle() > 0.5f && fSpeed > -10.0f) { SetThrottle(fForwardness); SetBrake(0.0f); } // oh dear we're sliding back too fast, go for the brakes else { SetThrottle(0.0f); SetBrake(fForwardness); } } // we want to go backwards, so apply the gas else { SetThrottle(fForwardness); SetBrake(0.0f); } } } // // Do some stuff here to stop the player from moving too far from the other player in cooperative mode. // if (GetThrottle() > 0.0f && pDriver && !CGameLogic::IsPlayerAllowedToGoInThisDirection(pDriver, GetB())) // { // SetThrottle(0.0f); // } // if (GetThrottle() < 0.0f && pDriver && !CGameLogic::IsPlayerAllowedToGoInThisDirection(pDriver, GetB() * -1.0f)) // { // SetThrottle(0.0f); // } } void CVehicle::TeleportWithoutUpdateGadgets(const Vector3& vecSetCoors, float fSetHeading, bool bTriggerPortalRescan, bool bWarp) { DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfTeleportCallingVehicle(), vecSetCoors ); if(fSetHeading >= -PI && fSetHeading <= TWO_PI) CEntity::SetHeading(fSetHeading); SetPosition(vecSetCoors, true, true, bWarp); // force portal tracker to update from new position (should eventually get data out of collisions under vehicle... hopefully...) if (bTriggerPortalRescan) { GetPortalTracker()->ScanUntilProbeTrue(); } ResetAfterTeleport(); if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel() && !GetIsAttached() && CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex()) && !GetIsInReusePool()) // don't activate physics if the vehicle is in the reuse pool { ActivatePhysics(); } if(IsInPathServer()) { CPathServerGta::UpdateDynamicObject(this, true, true); } } void CVehicle::Teleport(const Vector3& vecSetCoors, float fSetHeading, bool UNUSED_PARAM(bCalledByPedTask), bool bTriggerPortalRescan, bool UNUSED_PARAM(bCalledByPedTask2), bool bWarp, bool UNUSED_PARAM(bKeepRagdoll), bool UNUSED_PARAM(bResetPlants)) { #if __BANK if( GetAttachParent() && GetAttachParent()->GetType() == ENTITY_TYPE_VEHICLE ) { CVehicle* pParentVehicle = static_cast< CVehicle* >( GetAttachParent() ); if( InheritsFromTrailer() && !pParentVehicle->InheritsFromHeli() ) { Displayf("[CVehicle::Teleport() warping attached trailer]"); sysStack::PrintStackTrace(); } } #endif // #if __BANK Matrix34 matOld = MAT34V_TO_MATRIX34(GetMatrix()); TeleportWithoutUpdateGadgets(vecSetCoors, fSetHeading, bTriggerPortalRescan, bWarp); //if there's a trailer attached to this vehicle then move that as well UpdateGadgetsAfterTeleport(matOld, true); UpdateInertiasAfterTeleport(); for (int i = 0; i < GetNumberOfVehicleGadgets(); i++) { if(GetVehicleGadget(i) && GetVehicleGadget(i)->GetType() == VGT_DYNAMIC_SPOILER) { ((CVehicleGadgetDynamicSpoiler*)GetVehicleGadget(i))->ResetSpoilerBones(); } } } //To be called inside teleport function void CVehicle::ResetAfterTeleport() { ResetSuspension(); TeleportWheels(); m_Transmission.Reset(); if(GetSecondTransmission()) { GetSecondTransmission()->Reset(); } GetVehicleDamage()->ResetStuckCheck(); if(GetCollider()) { GetCollider()->SetMatrix(GetMatrix()); GetCollider()->SetLastInstanceMatrix(GetMatrix()); GetCollider()->Reset(); if(GetCollider()->GetSleep()) GetCollider()->GetSleep()->Reset(true); } m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = false;// Force the m_nFlags.bPossiblyTouchesWater to be updated // PossiblyTryToMakeIntoDummyOrFixBasedOnGroundLoaded(true); g_ptFxManager.RelocateEntityPtFx(this); if(NetworkInterface::IsGameInProgress()) { UpdateAttachedClonePedsAfterWarp(); } } void CVehicle::UpdateGadgetsAfterTeleport( const Matrix34 &matOld, bool bDetachFromParent, bool bUpdateTrailer, bool bUpdateTrailerParent, bool translateOnly) { for(int i = 0; i < GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetVehicleGadget(i); if(bUpdateTrailer && pVehicleGadget->GetType() == VGT_TRAILER_ATTACH_POINT) { CVehicleTrailerAttachPoint *pTrailerAttachPoint = static_cast(pVehicleGadget); CTrailer *pTrailer = pTrailerAttachPoint->GetAttachedTrailer(this); if(pTrailer) { pTrailer->WarpToPosition(this, translateOnly); pTrailer->ResetAfterTeleport(); } } else if(pVehicleGadget->GetType() == VGT_TOW_TRUCK_ARM) { CVehicleGadgetTowArm *pTowArm = static_cast(pVehicleGadget); //Disable extra hook movement when teleporting and reset heading to prevent spinning pTowArm->Teleport(this, matOld); } else if(pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET) { CVehicleGadgetPickUpRope *pPickUpRope = static_cast(pVehicleGadget); //Disable extra hook movement when teleporting and reset heading to prevent spinning pPickUpRope->Teleport(this, matOld); } } if( bUpdateTrailerParent && InheritsFromTrailer() && !IsNetworkClone() && NetworkInterface::IsGameInProgress() ) { CTrailer* pTrailer = static_cast< CTrailer* >( this ); if( pTrailer->GetAttachedToParent() ) { // set the parent position relative to this pTrailer->WarpParentToPosition(); } } if(!IsNetworkClone() && bDetachFromParent) { DetachFromParentVehicleGadget(); } } void CVehicle::UpdateAttachedClonePedsAfterWarp() { // check if any network clone peds are attached and detach them if so if(GetChildAttachment()) { CNetBlenderPed *netBlenders[MAX_NUM_PHYSICAL_PLAYERS]; unsigned numNetBlenders = 0; CPhysical *nextChild = SafeCast(CPhysical, GetChildAttachment()); while(nextChild) { fwAttachmentEntityExtension *nextChildAttachExt = nextChild->GetAttachmentExtension(); if(gnetVerifyf(nextChildAttachExt, "Attached child doesn't have an attachment extension?")) { if(nextChildAttachExt->GetAttachState() == ATTACH_STATE_RAGDOLL) { if(nextChild->GetIsTypePed() && nextChild->IsNetworkClone()) { CNetBlenderPed *netBlenderPed = SafeCast(CNetBlenderPed, nextChild->GetNetworkObject()->GetNetBlender()); if(AssertVerify(numNetBlenders < MAX_NUM_PHYSICAL_PLAYERS)) { netBlenders[numNetBlenders] = netBlenderPed; numNetBlenders++; } } } nextChild = (CPhysical *) nextChildAttachExt->GetSiblingAttachment(); } else { nextChild = 0; } } for(unsigned index = 0; index < numNetBlenders; index++) { CNetBlenderPed *netBlenderPed = netBlenders[index]; if(netBlenderPed && netBlenderPed->HasAttachedRagdoll()) { netBlenderPed->DetachRagdoll(); } } } } void CVehicle::TriggerHydraulicBounceSound() { // trigger sound effect locally GetVehicleAudioEntity()->RequestHydraulicBounce(); // trigger sound effect on the network if(GetNetworkObject() && GetNetworkObject()->IsClone() == false) { static_cast(GetNetworkObject())->RequestNetworkHydraulicBounceSound(); } } void CVehicle::TriggerHydraulicActivationSound() { // trigger sound effect locally GetVehicleAudioEntity()->RequestHydraulicActivation(); // trigger sound effect on the network if(GetNetworkObject() && GetNetworkObject()->IsClone() == false) { static_cast(GetNetworkObject())->RequestNetworkHydraulicActivationSound(); } } void CVehicle::TriggerHydraulicDeactivationSound() { // trigger sound effect locally GetVehicleAudioEntity()->RequestHydraulicDectivation(); // trigger sound effect on the network if(GetNetworkObject() && GetNetworkObject()->IsClone() == false) { static_cast(GetNetworkObject())->RequestNetworkHydraulicDeactivationSound(); } } void CVehicle::UpdateInertiasAfterTeleport() { if( !IsNetworkClone() && GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel() && CPhysics::GetLevel()->IsActive( GetCurrentPhysicsInst()->GetLevelIndex() ) ) { // after teleporting the vehicle we have to recalculate the intertias otherwise some of the doors might fall off fragInstGta* pFragInst = GetVehicleFragInst(); if( pFragInst ) { // find the associated joint fragHierarchyInst* pHierInst = NULL; if(pFragInst && pFragInst->GetCached()) { pHierInst = pFragInst->GetCacheEntry()->GetHierInst(); if(pHierInst) { phArticulatedCollider* pArticulatedCollider = NULL; pArticulatedCollider = pHierInst->articulatedCollider; if( pArticulatedCollider && pArticulatedCollider->GetBody() ) { pArticulatedCollider->GetBody()->CalculateInertias(); } } } } } } void CVehicle::UpdateAfterCutsceneMovement() { DetachFromParentVehicleGadget(); } bool CVehicle::GetIsBeingTowed() const { bool bIsBeingTowed = false; if(GetChildAttachment() && static_cast(GetChildAttachment())->GetModelIndex() == MI_PROP_HOOK) { bIsBeingTowed = true; } else if(GetAttachParentVehicle()) { for(int i = 0; i < GetAttachParentVehicle()->GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetAttachParentVehicle()->GetVehicleGadget(i); if(pVehicleGadget && (pVehicleGadget->GetType() == VGT_TOW_TRUCK_ARM || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET)) { bIsBeingTowed = true; } } } return bIsBeingTowed; } bool CVehicle::GetIsTowing() const { for(int i = 0; i < GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetVehicleGadget(i); if(pVehicleGadget && pVehicleGadget->GetType() == VGT_TOW_TRUCK_ARM && ((CVehicleGadgetTowArm*)pVehicleGadget)->GetAttachedVehicle()) { return true; } else if (pVehicleGadget && (pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET) && ((CVehicleGadgetPickUpRope*)pVehicleGadget)->GetAttachedEntity()) { return true; } } return false; } bool CVehicle::HasTowingGadget() const { for(int i = 0; i < GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetVehicleGadget(i); if(pVehicleGadget && pVehicleGadget->GetType() == VGT_TOW_TRUCK_ARM) { return true; } else if (pVehicleGadget && (pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET)) { return true; } } return false; } const CEntity* CVehicle::GetEntityBeingTowed() const { for(int i = 0; i < GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetVehicleGadget(i); if( pVehicleGadget && pVehicleGadget->GetType() == VGT_TOW_TRUCK_ARM && ((CVehicleGadgetTowArm*)pVehicleGadget)->GetAttachedVehicle() ) { return ((CVehicleGadgetTowArm*)pVehicleGadget)->GetAttachedVehicle(); } else if (pVehicleGadget && (pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET) && ((CVehicleGadgetPickUpRope*)pVehicleGadget)->GetAttachedEntity() ) { return ((CVehicleGadgetPickUpRope*)pVehicleGadget)->GetAttachedEntity(); } } return NULL; } void CVehicle::DetachFromParentVehicleGadget() { for(int i = 0; GetAttachParentVehicle() && i < GetAttachParentVehicle()->GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetAttachParentVehicle()->GetVehicleGadget(i); if(pVehicleGadget->GetType() == VGT_TOW_TRUCK_ARM) { CVehicleGadgetTowArm *pTowArm = static_cast(pVehicleGadget); vehicleDisplayf("[TOWTRUCK ROPE DEBUG] CVehicle::DetachFromParentVehicleGadget - Detaching entity."); pTowArm->DetachVehicle(GetAttachParentVehicle(), true, true); break; } else if(pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET) { CVehicleGadgetPickUpRope *pPickUpRope = static_cast(pVehicleGadget); pPickUpRope->DetachEntity(GetAttachParentVehicle(), true, true); break; } } } CWheel* CVehicle::GetWheelFromIdFromArray(eHierarchyId nId) { for(int i=0; iGetHierarchyId()==nId) return m_ppWheels[i]; } return NULL; } const CWheel* CVehicle::GetWheelFromIdFromArray(eHierarchyId nId) const { for(int i=0; iGetHierarchyId()==nId) return m_ppWheels[i]; } return NULL; } #define NO_WHEEL_INDEX 0xFF void CVehicle::ClearCacheWheelsById() { for(int i=0; iGetHierarchyId(); int iIdIndex = (eWheelId - VEH_WHEEL_FIRST_WHEEL); if(Verifyf(iIdIndex>=0 && iIdIndex=0 && iIdIndex=0 && iIdIndex 0.6f; } const CWheel* CVehicle::GetWheelFromId(eHierarchyId nId) const { int iIdIndex = (nId - VEH_WHEEL_FIRST_WHEEL); if(iIdIndex>=0 && iIdIndexGetIsTouching()) { numWheelsOnGround++; } else if(bMayBeAsleep && pWheel->GetWasTouching()) { // If we haven't already checked, grab the collider to see if we are considered asleep. if(!bIsAsleepKnown) { const phCollider* pCollider = GetCollider(); if(!pCollider || (pCollider->GetSleep() && pCollider->GetSleep()->IsAsleep())) { bIsAsleep = true; } else { // No need to call GetWasTouching() on any more wheels. bMayBeAsleep = false; } bIsAsleepKnown = true; } if(bIsAsleep) { numWheelsOnGround++; } } } return numWheelsOnGround; } bool CVehicle::HasContactWheels() const { bool bMayBeAsleep = true; const int numWheels = GetNumWheels(); for(int i = 0; i < numWheels; i++) { const CWheel* pWheel = GetWheel(i); if(pWheel->GetIsTouching()) { return true; } else if(bMayBeAsleep && pWheel->GetWasTouching()) { // Check if we are asleep: if so, GetWasTouching() is enough to // be counted as a contact wheel. const phCollider* pCollider = GetCollider(); if(!pCollider || (pCollider->GetSleep() && pCollider->GetSleep()->IsAsleep())) { return true; } else { bMayBeAsleep = false; } } } return false; } bool CVehicle::IsWheelContactPhysicalMoving() const { const int numWheels = GetNumWheels(); for( int i = 0; i < numWheels; i++ ) { const CWheel* pWheel = GetWheel(i); const CPhysical* physical = pWheel->GetHitPhysical(); if( physical && !physical->IsAsleep() ) { return true; } physical = pWheel->GetPrevHitPhysical(); if( physical && !physical->IsAsleep() ) { return true; } } return false; } void CVehicle::ResetSuspension() { // B*1855821: Prevent wheels from being reset to a compressed position for helis/planes that are in the air. bool resetExtendWheels = (InheritsFromHeli() || InheritsFromPlane()) && IsInAir(); for(int i=0; iReset(resetExtendWheels); } void CVehicle::TeleportWheels() { for(int i=0; iTeleport(); } } CCarDoor* CVehicle::GetDoorFromId(eHierarchyId nId) { for(int i=0; iGetHierarchyId()==nId) return GetDoor(i); } return NULL; } const CCarDoor* CVehicle::GetDoorFromId(eHierarchyId nId) const { for(int i=0; iGetHierarchyId()==nId) return GetDoor(i); } return NULL; } CCarDoor* CVehicle::GetDoorFromBoneIndex(int iBoneIndex) { for(int i=0; iGetHierarchyId()); if(iDoorBoneIndex==iBoneIndex) return GetDoor(i); } return NULL; } CCarDoor* CVehicle::GetSecondDoorFromEntryPointIndex(s32 iEntryPointIndex) { const CVehicleEntryPointInfo* pEntryInfo = GetEntryInfo(iEntryPointIndex); if (pEntryInfo && pEntryInfo->GetSecondDoorBoneName()) { const crSkeletonData* pSkelData = GetVehicleModelInfo()->GetFragType()->GetCommonDrawable()->GetSkeletonData(); if (pSkelData) { const crBoneData* pDoorBoneData = pEntryInfo->GetSecondDoorBoneName() ? pSkelData->FindBoneData(pEntryInfo->GetSecondDoorBoneName()) : NULL; int iDoorBoneIndex = pDoorBoneData ? (s16)pDoorBoneData->GetIndex() : -1; if (iDoorBoneIndex > -1) { return GetDoorFromBoneIndex(iDoorBoneIndex); } } } return NULL; } CCarDoor* CVehicle::GetArticulatedDoorFromComponent(int componentIndex) { if(const fragInst* pFragInst = GetVehicleFragInst()) { int linkIndex = pFragInst->GetLinkIndexFromComponent(componentIndex); if(linkIndex > 0) { for(int doorIndex = 0; doorIndex < GetNumDoors(); ++doorIndex) { CCarDoor* pDoor = GetDoor(doorIndex); int doorComponentIndex = pDoor->GetFragChild(); if(doorComponentIndex >= 0 && pFragInst->GetLinkIndexFromComponent(doorComponentIndex) == linkIndex) { return pDoor; } } } } return NULL; } const CCarDoor* CVehicle::GetDoorFromBoneIndex(int iBoneIndex) const { for(int i=0; iGetBoneIndex(); if(iDoorBoneIndex==iBoneIndex) return GetDoor(i); } return NULL; } ///////////////////////////////////////////////////////////////////////////////////// // CalculateHeightsAboveRoad // Calculates the height of front and rear wheels above the road. // This function now doesn't need the actual automobile to be created yet. // // NOTE: really want to move this into vehiclefactory as a virtual so we can deal // with different vehicle types across projects? ///////////////////////////////////////////////////////////////////////////////////// void CVehicle::CalculateHeightsAboveRoad(fwModelId modelId, float *pFrontHeight, float *pRearHeight) { Assert(modelId.IsValid()); CVehicleModelInfo *pModelInfo = (CVehicleModelInfo *)CModelInfo::GetBaseModelInfo(modelId); Assert(pModelInfo); // moved this code into CVehicleModelInfo so I can use it easily from there pModelInfo->CalculateHeightsAboveRoad(pFrontHeight, pRearHeight); } bool CVehicle::GetIsHandBrakePressed(const CControl* pControl) const { if(m_nVehicleFlags.bScriptSetHandbrake) { return true; } //Ensure the player is not aiming. //The aiming control has a conflict with the hand-brake control. //Note: This will only work if the control is the local player, // otherwise the code will need a bit of refactoring. if(CPlayerInfo::IsAiming(false)) { return false; } if(pControl) { // block the handbrake in arena mode if we holding the modifier button so we can re-use the handbrake button if( !HasSideShunt() || !pControl->GetVehicleArenaModeModifier().IsDown() ) { return pControl->GetVehicleHandBrake().IsDown() || m_nVehicleFlags.bScriptSetHandbrake /*|| (GetVehicleType() != VEHICLE_TYPE_BICYCLE && pControl->GetVehicleHandBrakeAlt().IsDown())*/; /* || (!CPhoneMgr::IsDisplayed() && (GetVehicleType() != VEHICLE_TYPE_BICYCLE && pControl->GetVehicleHandBrakeAlt().IsDown() && ( (GetVehicleType() != VEHICLE_TYPE_CAR && GetVehicleType() != VEHICLE_TYPE_BIKE) || (GetDriver() && GetDriver()->IsAPlayerPed() && !(GetDriver()->GetSpecialAbility() && GetDriver()->GetSpecialAbility()->GetType() == SAT_CAR_SLOWDOWN && GetDriver()->GetSpecialAbility()->IsActive()) ) )) );*/ } } return false; } CPed* CVehicle::GetLastDriver() const { return m_SeatManager.GetLastDriver(); } bool CVehicle::AddPedInSeat(CPed *pPed, s32 iSeat, bool UNUSED_PARAM(bTestConversionCollision), bool UNUSED_PARAM(bShouldApplyForce)) { Assert(pPed); // If this is a player make sure the veh isn't a dummy. const bool bSkipClearanceTest = true; if(iSeat == 0 && IsDummy() && pPed->IsLocalPlayer()) { if(!TryToMakeFromDummy(bSkipClearanceTest)) { Assertf(false, "Trying to put a player ped into a dummy veh, but the veh won't convert to a real veh (non-conversion reason: %s)!", GetNonConversionReason()); return false; } } // If this is a player make sure the veh isn't a dummy. if(IsDummy() && pPed->IsLocalPlayer()) { if(!TryToMakeFromDummy(bSkipClearanceTest)) { Assertf(false, "Trying to put a player ped into a dummy veh, but the veh won't convert to a real veh (non-conversion reason: %s)!", GetNonConversionReason()); return false; } } bool bSuccess = m_SeatManager.AddPedInSeat(pPed, iSeat); // Reset the ped's conversation look at time. if(bSuccess) { pPed->SetPrevConversationLookAtTime(0); } bool bSeatHasWeapons = false; if (IsTurretSeat(iSeat)) { bSeatHasWeapons = true; } else if (pHandling) { // Need to manually query weapon handling data to see if the seat has a weapon (and not query a vehicle weapon manager that doesn't exist...) CVehicleWeaponHandlingData* pWeaponHandling = pHandling->GetWeaponHandlingData(); if (pWeaponHandling) { for(s32 iWeaponIndex = 0; iWeaponIndex < MAX_NUM_VEHICLE_WEAPONS; iWeaponIndex++) { if (pWeaponHandling->GetWeaponSeat(iWeaponIndex) == iSeat) { bSeatHasWeapons = true; break; } } } } // Create vehicle weapons when a ped enters a seat with a vehicle weapon. //const bool bSeatHasWeapons = GetSeatHasWeaponsOrTurrets(iSeat) || IsTurretSeat(iSeat); if(bSuccess && !m_pVehicleWeaponMgr && (GetDriver() || bSeatHasWeapons) && GetStatus() != STATUS_WRECKED) { CreateVehicleWeaponMgr(); } else { if (m_pVehicleWeaponMgr) { // This prevents the weapon manager from subsequently getting destroyed via CVehicle::OnDeactivate AI_LOG_WITH_ARGS("Ped %s being set into vehicle %s already had a weapon mgr, resetting m_destroyWeaponMgr to false", AILogging::GetDynamicEntityNameSafe(pPed), AILogging::GetDynamicEntityNameSafe(this)); m_destroyWeaponMgr = false; } } if (IsNetworkClone() || pPed->IsNetworkClone()) { return(bSuccess); } // //static float EnterBikeForceMultiplier = -0.2f; // static float EnterVehicleForceMultiplier = -5.0f; // if(bShouldApplyForce) // { // if(!IsSuperDummy() || !CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode ) // { // if(!InheritsFromBike()) // { // Matrix34 matrix; // GetMatrixCopy(matrix); // Vector3 vLocalOffset; // matrix.UnTransform(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), vLocalOffset); // vLocalOffset.z = 0; // if((vLocalOffset).Mag2() < GetBoundRadius() * GetBoundRadius()) // { // ApplyImpulse(Vector3(0.0f,0.0f, EnterVehicleForceMultiplier * pPed->GetMassForApplyVehicleForce()), vLocalOffset); // } // } // } // } if (GetLayoutInfo() && !GetLayoutInfo()->GetBikeLeansUnlessMoving()) { if(InheritsFromBike() && iSeat == 0) ((CBike*)this)->SetBikeOnSideStand(false, true); } //clear out the RearBrake value if this isn't a player if (InheritsFromBike() && iSeat == 0 && !pPed->IsPlayer()) { ((CBike*)this)->SetRearBrake(0.0f); } //Reset the engine turned off by player flag if(bSuccess && InheritsFromPlane() && pPed == GetDriver()) { ((CPlane *)this)->ResetEngineTurnedOffByPlayer(); } return(bSuccess); } void CVehicle::RemovePedFromSeat(CPed *pPed, bool bChangeCarStatus, bool bDestroyWeaponMgr) { bool bUsingAutoPilot = GetIsUsingScriptAutoPilot(); if (!bUsingAutoPilot && bChangeCarStatus && m_SeatManager.GetPedInSeat(0) == pPed && GetStatus() != STATUS_WRECKED) { SetIsAbandoned(pPed); } s32 iSeat = m_SeatManager.RemovePedFromSeat(pPed); // Need to reset the seat bound immediately otherwise they could collide with the now detached ped... CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); fragInstGta* pVehicleFragInst = GetVehicleFragInst(); if (vehicleVerifyf(pVehicleModelInfo != NULL, "CVehicle::RemovePedFromSeat: Invalid vehicle") && physicsVerifyf(pVehicleFragInst != NULL && pVehicleFragInst->GetArchetype() != NULL && pVehicleFragInst->GetArchetype()->GetBound() != NULL, "CVehicle::RemovePedFromSeat: Invalid vehicle frag inst") && iSeat >= 0 && iSeat < pVehicleModelInfo->GetModelSeatInfo()->GetNumSeats()) { s32 iFragChild = pVehicleModelInfo->GetFragChildForSeat(iSeat); phBoundComposite* pBoundComposite = static_cast(pVehicleFragInst->GetArchetype()->GetBound()); if (physicsVerifyf(pBoundComposite->GetTypeAndIncludeFlags() != NULL, "CVehicle::RemovePedFromSeat: No type and include flags allocated for composite bound") && iFragChild >= 0 && iFragChild < pBoundComposite->GetNumBounds()) { // Reset the seat bound if(pVehicleModelInfo->GetHasSeatCollision() && pBoundComposite->GetIncludeFlags(iFragChild)) { ProcessSeatCollisionBound(true); } // Seat is empty, turn off collision pBoundComposite->SetIncludeFlags(iFragChild, 0); } } // Remove vehicle weapons if the ped left. bool bIsTurretedVehicle = false; bool bShouldDestroyWeaponMgr = false; if (!bUsingAutoPilot && !pPed->GetPedResetFlag(CPED_RESET_FLAG_IsSeatShuffling)) { bShouldDestroyWeaponMgr = bDestroyWeaponMgr && CTaskVehicleFSM::ShouldDestroyVehicleWeaponManager(*this, bIsTurretedVehicle); } if (bShouldDestroyWeaponMgr) { DestroyVehicleWeaponWhenInactive(); } // Start crashing behaviour in multiplayer if this ped is a player ped that is the last to be exiting the vehicle (Warping should not trigger this). if(NetworkInterface::IsGameInProgress() && !bUsingAutoPilot) { if(pPed->IsPlayer() && pPed->GetPedIntelligence()->GetTaskManager()->FindTaskByTypeActive(PED_TASK_TREE_PRIMARY, CTaskTypes::TASK_EXIT_VEHICLE_SEAT) && m_SeatManager.CountPedsInSeats() == 0 && InheritsFromHeli()) { // Don't attribute vehicle crash tasks to dead peds if (pPed->IsDead()) { static_cast(this)->StartCrashingBehavior(this, true); } else { static_cast(this)->StartCrashingBehavior(pPed, true); } } } } const CVehicleSeatAnimInfo* CVehicle::GetSeatAnimationInfo(s32 iSeat) const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); vehicleAssertf(iSeat < pVehicleModelInfo->GetModelSeatInfo()->GetNumSeats(),"Invalid seat index %i for model %s", iSeat, pVehicleModelInfo->GetModelName()); return pVehicleModelInfo->GetSeatAnimationInfo(iSeat); } const CVehicleSeatAnimInfo* CVehicle::GetSeatAnimationInfo(const CPed* pPed) const { int iSeatIndex = m_SeatManager.GetPedsSeatIndex(pPed); if(iSeatIndex > -1) { return GetSeatAnimationInfo(iSeatIndex); } return NULL; } const CVehicleSeatInfo* CVehicle::GetSeatInfo(s32 iSeat) const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); vehicleAssertf(iSeat < pVehicleModelInfo->GetModelSeatInfo()->GetNumSeats(),"Invalid seat index %i for model %s", iSeat, pVehicleModelInfo->GetModelName()); return pVehicleModelInfo->GetSeatInfo(iSeat); } const CVehicleSeatInfo* CVehicle::GetSeatInfo(const CPed* pPed) const { int iSeatIndex = m_SeatManager.GetPedsSeatIndex(pPed); if(iSeatIndex > -1) { return GetSeatInfo(iSeatIndex); } return NULL; } const CVehicleEntryPointAnimInfo* CVehicle::GetEntryAnimInfo(int iEntryPointIndex) const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); vehicleAssertf(iEntryPointIndex < pVehicleModelInfo->GetModelSeatInfo()->GetNumberEntryExitPoints(),"Invalid entry point index %i for model %s", iEntryPointIndex, pVehicleModelInfo->GetModelName()); return pVehicleModelInfo->GetEntryPointAnimInfo(iEntryPointIndex); } const CVehicleEntryPointInfo* CVehicle::GetEntryInfo(s32 iEntryPointIndex) const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); vehicleAssertf(iEntryPointIndex < pVehicleModelInfo->GetModelSeatInfo()->GetNumberEntryExitPoints(),"Invalid entry point index %i for model %s", iEntryPointIndex, pVehicleModelInfo->GetModelName()); return pVehicleModelInfo->GetEntryPointInfo(iEntryPointIndex); } const CVehicleLayoutInfo* CVehicle::GetLayoutInfo() const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); return pVehicleModelInfo->GetVehicleLayoutInfo(); } const CPOVTuningInfo* CVehicle::GetPOVTuningInfo() const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); return pVehicleModelInfo->GetPOVTuningInfo(); } const CVehicleCoverBoundOffsetInfo* CVehicle::GetVehicleCoverBoundOffsetInfo() const { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); return pVehicleModelInfo->GetVehicleCoverBoundOffsetInfo(); } void CVehicle::ResetSeatManagerForReuse() { m_SeatManager.ResetHasEverHadPedInAnySeat(); } void CVehicle::ResetExclusiveDriverForReuse() { for(int i = 0; i < MAX_NUM_EXCLUSIVE_DRIVERS; ++i) { m_pExclusiveDriverPeds[i] = NULL; } } bool CVehicle::IsEntryIndexValid(s32 iEntryIndex) const { if (iEntryIndex >= 0) { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); return iEntryIndex < pVehicleModelInfo->GetModelSeatInfo()->GetNumberEntryExitPoints(); } return false; } bool CVehicle::CanSeatBeAccessedViaAnEntryPoint(s32 iSeatIndex) const { return GetSeatAccessType(iSeatIndex) != CSeatAccessor::eSeatAccessTypeInvalid; } CSeatAccessor::SeatAccessType CVehicle::GetSeatAccessType(s32 iSeatIndex) const { vehicleAssert(IsSeatIndexValid(iSeatIndex)); CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); vehicleAssert(pVehicleModelInfo); for (s32 i=0; iGetModelSeatInfo()->GetNumberEntryExitPoints(); ++i) { const CEntryExitPoint* pEntryExitPoint = pVehicleModelInfo->GetModelSeatInfo()->GetEntryExitPoint(i); vehicleAssert(pEntryExitPoint); if (pEntryExitPoint->CanSeatBeReached(iSeatIndex) != SA_invalid) { return pEntryExitPoint->GetSeatAccessor().GetSeatAccessType(); } } return CSeatAccessor::eSeatAccessTypeInvalid; } /////////////////////////////////////////////////////////////////////////////////// // FUNCTION : SetUpDriver // PURPOSE : /////////////////////////////////////////////////////////////////////////////////// CPed* CVehicle::SetUpDriver(bool bUseExistingNodes, bool canLeaveVehicle, u32 desiredModel, const CAmbientModelVariations* variations) { if(GetDriver()) { AssertEntityPointerValid_NotInWorld(GetDriver()); return GetDriver(); } CPed* pNewDriver = CPedPopulation::AddPedInCar(this, true, 0, true, bUseExistingNodes, false, false, desiredModel, variations); if(!pNewDriver) { return NULL; } //firefighters get fire extinguishers if (GetModelIndex() == MI_CAR_FIRETRUCK && pNewDriver->GetPedType() == PEDTYPE_FIRE) { static const atHashWithStringNotFinal FIREMAN_LOADOUT("LOADOUT_FIREMAN",0x1FDC4A4C); CPedInventoryLoadOutManager::SetLoadOut(pNewDriver, FIREMAN_LOADOUT.GetHash()); } if(!canLeaveVehicle) { // Stop the driver trying to jump out of teh vehicle pNewDriver->GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanLeaveVehicle); } Assert(GetDriver() == pNewDriver); if (GetDriver()) { if (GetVehicleType() == VEHICLE_TYPE_BICYCLE) { CPedPropsMgr::SetRandomHelmet(GetDriver(), PV_FLAG_RANDOM_HELMET); if (NetworkInterface::IsGameInProgress() && !GetDriver()->IsPlayer() && GetModelIndex() == MI_BIKE_SCORCHER) { // Cannot wear goggles unless we got a helmet! if (CPedPropsMgr::GetPedPropIdx(GetDriver(), ANCHOR_HEAD) == -1 && CPedPropsMgr::GetPedPropIdx(GetDriver(), ANCHOR_EYES) != -1) { CPedPropsMgr::ClearPedProp(GetDriver(), ANCHOR_EYES, true); } } } else if (GetVehicleType() == VEHICLE_TYPE_BIKE) { if (GetDriver()->GetPedModelInfo()->GetCanRideBikeWithNoHelmet()) CPedPropsMgr::SetRandomHat(GetDriver()); else CPedPropsMgr::SetRandomHelmet(GetDriver(), PV_FLAG_DEFAULT_HELMET); } else if (GetVehicleType() == VEHICLE_TYPE_TRAIN || GetVehicleType() == VEHICLE_TYPE_BLIMP) { if(GetDriver()->PopTypeIsRandom()) { GetDriver()->SetBlockingOfNonTemporaryEvents(true); } } } return GetDriver(); } /////////////////////////////////////////////////////////////////////////////////// // FUNCTION : SetupPassenger // PURPOSE : /////////////////////////////////////////////////////////////////////////////////// CPed* CVehicle::SetupPassenger(s32 seat_num, bool canLeaveVehicle, u32 desiredModel, const CAmbientModelVariations* variations) { CPed *pNewPassenger = NULL; Assertf(seat_num < m_SeatManager.GetMaxSeats(), "Trying to add too many passengers"); if(m_SeatManager.GetPedInSeat(seat_num)) { return m_SeatManager.GetPedInSeat(seat_num); } pNewPassenger = CPedPopulation::AddPedInCar(this, false, seat_num, true, true, false, false, desiredModel, variations); if (!pNewPassenger) { return NULL; } if(!canLeaveVehicle) { // Stop the passenger trying to jump out of teh vehicle pNewPassenger->GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanLeaveVehicle); } weaponAssertf(pNewPassenger->GetInventory(), "Ped %s is missing an inventory", pNewPassenger->GetPedModelInfo()->GetModelName()); // Do any vehicle specific passenger setup here... if(GetIsRotaryAircraft()) { // Rear passenger in heli's get machine guns instead of shotguns. // This is really done to specifically set up swat helicopters... // if((seat_num >= Seat_backLeft) && (seat_num <= Seat_backRight)) // { // pNewPassenger->GetPedIntelligence()->GetPedPerception().SetSeeingRange(100.0f); // pNewPassenger->GetPedIntelligence()->GetCombatBehaviour().SetFlag(CCombatData::BF_CanDoDrivebys); // pNewPassenger->GetInventory()->RemoveItem(WEAPONTYPE_SHOTGUN); // pNewPassenger->GetInventory()->AddWeaponAndAmmo(WEAPONTYPE_ASSAULTRIFLE, INFINITE_AMMO); // } } CVehicleModelInfo *pVehModelInfo = GetVehicleModelInfo(); if(pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_LAW_ENFORCEMENT) && pVehModelInfo->GetIsBoat()) { static const atHashWithStringNotFinal POLICE_BOAT_LOADOUT("LOADOUT_COP_BOAT",0x17721F2F); CPedInventoryLoadOutManager::SetLoadOut(pNewPassenger, POLICE_BOAT_LOADOUT.GetHash()); } // Cops riding shotgun, get shotguns if (IsLawEnforcementCarModelId(GetModelId())) { static const atHashWithStringNotFinal COP_SHOTGUN_LOADOUT("LOADOUT_COP_SHOTGUN",0x2918E083); CPedInventoryLoadOutManager::SetLoadOut(pNewPassenger, COP_SHOTGUN_LOADOUT); } //firefighters get fire extinguishers if (GetModelIndex() == MI_CAR_FIRETRUCK && pNewPassenger->GetPedType() == PEDTYPE_FIRE) { static const atHashWithStringNotFinal FIREMAN_LOADOUT("LOADOUT_FIREMAN",0x1FDC4A4C); CPedInventoryLoadOutManager::SetLoadOut(pNewPassenger, FIREMAN_LOADOUT.GetHash()); } // Assertf(pnum == pPassengers[seat_num], "SetupPassenger - Graeme has broken this"); const u32 iModelIndex=pNewPassenger->GetModelIndex(); //Test if the vehicle contains two peds with the same model index. if(!pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_BUS) && (pNewPassenger->GetPedType()!=PEDTYPE_MEDIC) && (pNewPassenger->GetPedType()!=PEDTYPE_FIRE) && (pNewPassenger->GetPedType()!=PEDTYPE_COP) && (pNewPassenger->GetPedType()!=PEDTYPE_SWAT) && (!CPedType::IsGangType(pNewPassenger->GetPedType()) && !(pNewPassenger->GetPedType()==PEDTYPE_CRIMINAL) && // need this for police chases (want a veh full of criminal gang members) GetModelIndex() != MI_CAR_NOOSE && desiredModel == fwModelId::MI_INVALID) ) { for(s32 k=0;kGetModelIndex()==iModelIndex) { pNewPassenger->SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, false ); pNewPassenger->UpdateSpatialArrayTypeFlags(); RemovePedFromSeat(pNewPassenger); if( pNewPassenger->GetMyVehicle() ) { pNewPassenger->SetMyVehicle(NULL); } pNewPassenger->FlagToDestroyWhenNextProcessed(); pNewPassenger = NULL; // just to be safe } } } return pNewPassenger; } /////////////////////////////////////////////////////////////////////////////////// // FUNCTION : SetUpSwatDriver // PURPOSE : /////////////////////////////////////////////////////////////////////////////////// CPed* CVehicle::SetUpSwatDriver(bool bUseExistingNodes, bool canLeaveVehicle, u32 desiredModel) { if(GetDriver()) { AssertEntityPointerValid_NotInWorld(GetDriver()); return GetDriver(); } CPed* pNewDriver = CPedPopulation::AddPedInCar(this, true, 0, true, bUseExistingNodes, false, false, desiredModel); if(!pNewDriver) { return NULL; } static const atHashWithStringNotFinal SWAT_DRIVER_LOADOUT("LOADOUT_SWAT_NO_LASER",0xC9705531); CPedInventoryLoadOutManager::SetLoadOut(pNewDriver, SWAT_DRIVER_LOADOUT.GetHash()); if(!canLeaveVehicle) { // Stop the driver trying to jump out of the vehicle pNewDriver->GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanLeaveVehicle); } aiAssert(GetDriver() == pNewDriver); return GetDriver(); } /////////////////////////////////////////////////////////////////////////////////// // FUNCTION : SetupSwatPassenger // PURPOSE : /////////////////////////////////////////////////////////////////////////////////// CPed* CVehicle::SetupSwatPassenger(s32 seat_num, bool canLeaveVehicle, u32 desiredModel) { CPed *pNewPassenger = NULL; aiAssertf(seat_num < m_SeatManager.GetMaxSeats(), "Trying to add too many passengers"); if(m_SeatManager.GetPedInSeat(seat_num)) { return m_SeatManager.GetPedInSeat(seat_num); } pNewPassenger = CPedPopulation::AddPedInCar(this, false, seat_num, true, true, false, false, desiredModel); if (!pNewPassenger) { return NULL; } if(!canLeaveVehicle) { // Stop the passenger trying to jump out of the vehicle pNewPassenger->GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanLeaveVehicle); } static const atHashWithStringNotFinal SWAT_PASSANGER_LOADOUT("LOADOUT_SWAT_NO_LASER",0xC9705531); CPedInventoryLoadOutManager::SetLoadOut(pNewPassenger, SWAT_PASSANGER_LOADOUT.GetHash()); return pNewPassenger; } bool CVehicle::IsDriver(const CPed* pPed) const { if(!pPed) { return false; } if(pPed==GetDriver()) { return true; } else { return false; } } bool CVehicle::IsDriver(u32 iModelIndex) const { if(GetDriver()) { if(GetDriver()->GetModelIndex()==iModelIndex) { return true; } } return false; } // // name: KillPedsInVehicle // description: Kill all the peds in a vehicle void CVehicle::KillPedsInVehicle(CEntity *pCulprit, u32 weaponHash) { // If the veh is using pretend occupants, convert them so they're ready to be killed if( m_nVehicleFlags.bUsingPretendOccupants ) { // If the conversion fails, clear the pretend occupant settings, cos we dont want to create peds in thie vehicle again if( !TryToMakePedsFromPretendOccupants() ) { m_nVehicleFlags.bUsingPretendOccupants = false; GetIntelligence()->FlushPretendOccupantEventGroup(); GetIntelligence()->ResetPretendOccupantEventData(); GetIntelligence()->SetNumPedsThatNeedTaskFromPretendOccupant(0); } } //If there is no culprit then set it to the vehicle damager. if (!pCulprit) pCulprit = this->GetWeaponDamageEntity(); for( s32 seat = 0; seat < MAX_VEHICLE_SEATS; seat++ ) { TUNE_GROUP_BOOL(DEATH_IN_VEHICLE_TUNE, KILL_PED_USING_SEAT_ASWELL, true); CPed* pPed = KILL_PED_USING_SEAT_ASWELL ? CTaskVehicleFSM::GetPedInOrUsingSeat(*this, seat) : m_SeatManager.GetPedInSeat(seat); if( pPed ) { if (pCulprit && NetworkInterface::IsDamageDisabledInMP(*pCulprit, *pPed)) { continue; } if (pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_UseNormalExplosionDamageWhenBlownUpInVehicle)) { CTaskPlayerDrive* pTask = static_cast(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_DRIVE)); if (pTask) { pTask->SetForceExit(true); } continue; } if(!pPed->IsNetworkClone()) { const CHealthConfigInfo* pHealthInfo = pPed->GetPedHealthInfo(); Assert(pHealthInfo); AssertEntityPointerValid_NotInWorld(GetDriver()); static dev_float PERCENTAGE_OF_PEDS_WHO_GET_OUT_ON_FIRE = 0.0f; if(GetVehicleType() != VEHICLE_TYPE_HELI && GetVehicleType() != VEHICLE_TYPE_PLANE && !m_nVehicleFlags.bIsBus && !pPed->IsAPlayerPed() && !pPed->PopTypeIsMission() && pPed->GetHealth() <= pHealthInfo->GetDefaultHealth() && !pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE) && fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < PERCENTAGE_OF_PEDS_WHO_GET_OUT_ON_FIRE ) { CEventVehicleOnFire event(pPed->GetMyVehicle()); if(event.AffectsPed(pPed)) pPed->GetPedIntelligence()->AddEvent(event); } else { // want to make sure peds die if the veh they're attached to has exploded if(CWeaponDamage::GeneratePedDamageEvent(pCulprit, pPed, weaponHash, 9999.0f, VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), NULL, CPedDamageCalculator::DF_AllowDriverKill) <= 0.0f && pPed->IsLocalPlayer() && NetworkInterface::IsGameInProgress() && !pPed->GetPedResetFlag(CPED_RESET_FLAG_DisableAutoForceOutWhenBlowingUpCar)) { CTaskPlayerDrive* pTask = static_cast(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_DRIVE)); if (pTask) { pTask->SetForceExit(true); } } } } else { //! DMKH. If we own vehicle, ensure we send a kill event to all peds in the vehicle. if(!IsNetworkClone()) { CEntity *pNetworkCulprit = pCulprit; //! If culprit isn't a net object, blame the driver for network purposes. if(NetworkUtils::GetNetworkObjectFromEntity(pNetworkCulprit) == NULL) { pNetworkCulprit = GetLastDriver(); } if(pNetworkCulprit) { fwFlags32 flags( CPedDamageCalculator::DF_AllowDriverKill | CPedDamageCalculator::DF_IgnoreRemoteDistCheck ); CWeaponDamageEvent::Trigger(pNetworkCulprit, pPed, VEC3V_TO_VECTOR3( pPed->GetTransform().GetPosition() ), 0, true, weaponHash, 9999.0f, -1, -1, flags, 0, 0, 0); } } } } } } void CVehicle::InitAnimLazy() { } // // name: KillPedGettingInVehicleCB // description: Kill peds getting into a vehicle callback static bool KillPedGettingInVehicleCB(void* UNUSED_PARAM(pItem), void* UNUSED_PARAM(data)) { return true; } void CVehicle::KillPedsGettingInVehicle(CEntity *pCulprit) { ((void)pCulprit); CPed::GetPool()->ForAll(KillPedGettingInVehicleCB, this); } bool CVehicle::HasInfiniteFuel() const { return pHandling->m_fPetrolTankVolume == 0.0f; } float CVehicle::GetFuelConsumptionRate() const { return pHandling->m_fPetrolConsumptionRate; } void CVehicle::BlowUpCar(CEntity* UNUSED_PARAM(pCulprit), bool UNUSED_PARAM(bInACutscene), bool UNUSED_PARAM(bAddExplosion), bool UNUSED_PARAM(bNetCall), u32 UNUSED_PARAM(weaponHash), bool UNUSED_PARAM(bDelayedExplosion)) { #if __BANK if(NetworkInterface::IsNetworkOpen()) { bool prevDebugLevel = CVehicle::ms_bDebugVehicleIsDriveableFail; CVehicle::ms_bDebugVehicleIsDriveableFail = true; GetVehicleDamage()->GetIsDriveable(true, true, true); CVehicle::ms_bDebugVehicleIsDriveableFail = prevDebugLevel; Displayf("[CVehicle::BlowUpCar()]"); sysStack::PrintStackTrace(); } #endif // __BANK if( !IsNetworkClone() ) { for(int i = 0; i < GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetVehicleGadget(i); if( pVehicleGadget->GetType() == VGT_TRAILER_ATTACH_POINT ) { CVehicleTrailerAttachPoint *pTrailerAttachPoint = static_cast(pVehicleGadget); pTrailerAttachPoint->DetachTrailer( this ); } } } ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry(true);) MakeRealOrFixForExplosion(); // Remove all glass from the vehicle g_vehicleGlassMan.RemoveAllGlass(this); SetAllWindowComponentsDirty(); NetworkInterface::ForceHealthNodeUpdate(*this); m_nVehicleFlags.bBlownUp = true; } void CVehicle::AddVehicleExplosion(CEntity *pCulprit, bool bInACutscene, bool bDelayedExplosion ) { // try to get the explosion tag from the explosion info CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); if (pVehicleModelInfo) { const CVehicleExplosionInfo* pVehicleExplosionInfo = pVehicleModelInfo->GetVehicleExplosionInfo(); if (pVehicleExplosionInfo) { ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry(true);) // hack for ONLINE plane explosions bool onlinePlaneHack = false; if (/*NetworkInterface::IsGameInProgress() &&*/ GetVelocity().Mag()>10.0f && pVehicleExplosionInfo->GetName().GetHash()==atHashString("EXPLOSION_INFO_PLANE")) { onlinePlaneHack = true; } u32 initialDelay = GetExplosionDelay(bDelayedExplosion); int numExplosions = pVehicleExplosionInfo->GetNumExplosions(); if (onlinePlaneHack) { // add an extra explosion for planes numExplosions++; } for (int i=0; iGetExplosion(i-1); } else { pExplosionData = pVehicleExplosionInfo->GetExplosion(i); } eExplosionTag expTag = pExplosionData->m_ExplosionTag; // skip if no valid tag is found if (expTag==EXP_TAG_DONTCARE) { continue; } // get the local position of the explosion Vec3V vPosLcl = Vec3V(V_ZERO); if (pExplosionData->m_PositionInBoundingBox) { // if we are positioning relative to the bounding box // then the offset position tells us how far from min to max to go fragInstGta* pFragInst = GetVehicleFragInst(); if (pFragInst) { Vec3V bbMin = pFragInst->GetTypePhysics()->GetCompositeBounds()->GetBound(0)->GetBoundingBoxMin(); Vec3V bbMax = pFragInst->GetTypePhysics()->GetCompositeBounds()->GetBound(0)->GetBoundingBoxMax(); Vec3V bbDiff = bbMax-bbMin; vPosLcl = bbMin + (bbDiff*pExplosionData->m_PositionOffset); } } else { // otherwise we position with an absolute offset from either the root of the petrol tank position if (pExplosionData->m_PositionAtPetrolTank) { Vector3 vPetrolTankPosLclLeft; Vector3 vPetrolTankPosLclRight; if(GetVehicleDamage()->GetPetrolTankPosLcl(&vPetrolTankPosLclLeft, &vPetrolTankPosLclRight)) { Vec3V vLeft = VECTOR3_TO_VEC3V(vPetrolTankPosLclLeft); Vec3V vRight = VECTOR3_TO_VEC3V(vPetrolTankPosLclRight); if(vPetrolTankPosLclLeft.IsNonZero() && vPetrolTankPosLclRight.IsNonZero()) { // Average the position vectors of the left and right petrol tank bones to find a central position. vLeft = Scale(vLeft, ScalarV(V_HALF)); vRight = Scale(vRight, ScalarV(V_HALF)); Vec3V vCombinedLocalPos = rage::Add(vLeft, vRight); vPosLcl = vCombinedLocalPos; } else if(vPetrolTankPosLclLeft.IsNonZero()) { vPosLcl = vLeft; } else if(vPetrolTankPosLclRight.IsNonZero()) { vPosLcl = vRight; } //Fixup cars with petrol tank bones directly in the center X //otherwise they won't appear to move much from the explosion if(InheritsFromAutomobile() && vPosLcl.GetXf() == 0.0f) { vPosLcl.SetXf(fwRandom::GetRandomTrueFalse() ? GetBoundingBoxMax().x : -GetBoundingBoxMax().x); } } } // add any offsets to the position vPosLcl += pExplosionData->m_PositionOffset; } // bring into world space Vec3V vPosWld = Transform(GetTransform().GetMatrix(), vPosLcl); CExplosionManager::CExplosionArgs explosionArgs(expTag, VEC3V_TO_VECTOR3(vPosWld)); explosionArgs.m_pExplodingEntity = this; explosionArgs.m_pEntExplosionOwner = pCulprit; explosionArgs.m_bInAir = IsAirExplosion(VEC3V_TO_VECTOR3(vPosWld)); explosionArgs.m_bNoDamage = bInACutscene || sm_bDisableExplosionDamage; explosionArgs.m_pAttachEntity = this; explosionArgs.m_attachBoneTag = 0; explosionArgs.m_activationDelay = initialDelay + pExplosionData->m_DelayTimeMs; if (onlinePlaneHack && i==numExplosions-1) { // make this extra explosion have a 1s delay explosionArgs.m_activationDelay += 1000; } explosionArgs.m_sizeScale = pExplosionData->m_Scale; CExplosionManager::AddExplosion(explosionArgs); } } } } void CVehicle::MakeRealOrFixForExplosion() { if(IsDummy()) { if(CPhysical::IsCollisionLoadedAroundPosition()) { //trailers/children will try to convert through their parents if(!InheritsFromTrailer() && !m_nVehicleFlags.bHasParentVehicle) { ASSERT_ONLY(static dev_bool bCheckDummyExplosionConversion = false;) ASSERT_ONLY(bool makeFromDummySuccess = )TryToMakeFromDummy(); Assertf(!bCheckDummyExplosionConversion || makeFromDummySuccess, "Trying to explode a dummy veh, but the veh won't convert to a real veh (non-conversion reason: %s)!", GetNonConversionReason()); } } else if( ShouldFixIfNoCollisionLoadedAroundPosition()) { SetIsFixedWaitingForCollision(true); } } } void CVehicle::Fix(bool resetFrag, bool allowNetwork) { if (IsNetworkClone()) { if (GetStatus()==STATUS_WRECKED) return; } else { m_fHealth = CREATED_VEHICLE_HEALTH; } m_nNumInternalWindshieldHits = 0; m_nNumInternalWindshieldRearHits = 0; m_nVehicleFlags.bBlownUp = false; GetVehicleDamage()->FixVehicleDamage(resetFrag, allowNetwork); GetVehicleDamage()->ResetStuckCheck(); ResetBrokenAndHiddenFlags(); ResetCarDoorKnockedOpenStatus(); if( resetFrag ) { for( int i = 0; i < GetNumDoors(); i++ ) { CCarDoor * pDoor = GetDoor(i); if( pDoor && pDoor->GetIsIntact( this ) ) { pDoor->SetShutAndLatched( this, false ); } } } // For every ped in vehicle is it is a player tell them they needs to smash window again for( s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); iSeat++ ) { CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat); if( pPassenger && pPassenger->IsLocalPlayer()) { CTaskPlayerDrive* pDriveTask = static_cast(pPassenger->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_DRIVE)); if (pDriveTask) { pDriveTask->SetSmashedWindow(false); } break; } } InitFragmentExtras(); if(!IsColliderArticulated()) { CalculateNonArticulatedMaximumExtents(); } InitCompositeBound(); u32 decalTypeExceptionFlags = 0; for (int i=0; iFix(); m_VehicleAudioEntity->FixWindows(); GetVehicleDrawHandler().GetVariationInstance().ShowAllMods(); //Ensure all the windows are fixed up for(int nId = VEH_WINDOW_LF; nId <= VEH_LAST_WINDOW; nId++) { FixWindow((eHierarchyId)nId); RollWindowUp((eHierarchyId)nId); } //Reset light states based on mods. Break all lights invalidated by mods CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX) { int lightBoneIds[VEHICLE_LIGHT_COUNT]; int totallighBoneIds = VEHICLE_LIGHT_COUNT; for(int i=0; iOnVehicleFixed(); } Assert(GetDrawHandlerPtr()); CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandlerPtr()->GetShaderEffect()); if (AssertVerify(pShader)) { // flag that tyres don't need to be deformed pShader->SetEnableTyreDeform(false); // re-initialise vehicle cloth - could have been recreated in FixVehicleDamage() CVehicleDrawHandler *pVehDrawHandler = static_cast(GetDrawHandlerPtr()); if(pVehDrawHandler->GetShaderEffectSD()) { // grab SD CSE (if exists): CCustomShaderEffectVehicle* pShaderSD = static_cast(pVehDrawHandler->GetShaderEffectSD()); pShaderSD->GetCseType()->InitialiseClothDrawable(this); } else { pShader->GetCseType()->InitialiseClothDrawable(this); } } if(GetStatus()==STATUS_WRECKED) { Assert(GetStatus()!=STATUS_WRECKED || NetworkInterface::IsGameInProgress() REPLAY_ONLY(|| CReplayMgr::IsEditModeActive())); // this assert can fire very rarely in a network game if a vehicle is wrecked just bef m_nPhysicalFlags.bRenderScorched = false; if(GetDriver()) { if(GetDriver()->IsPlayer()) { SetStatus(STATUS_PLAYER); } else SetStatus(STATUS_PHYSICS); if(m_pVehicleWeaponMgr == NULL) { CreateVehicleWeaponMgr(); } } else { SetIsAbandoned(); } } //Enable any vehicle weapon that has been disabled if(m_pVehicleWeaponMgr) { for(int i = 0; i < m_pVehicleWeaponMgr->GetNumVehicleWeapons(); ++i) { m_pVehicleWeaponMgr->GetVehicleWeapon(i)->SetEnabled(this, true); } RecreateVehicleWeaponMgr(); } #if GTA_REPLAY if( CReplayMgr::ShouldRecord() ) { //On a replay we keep track of all deformations, if we've got a replay extension then mark it as having been fixed ReplayVehicleExtension *extension = ReplayVehicleExtension::GetExtension(this); if(extension) { extension->SetFixed(true); } } #endif //GTA_REPLAY // Reset spoiler parameters. for(int i=0; iFix(); } if( HasGlider() || GetModelIndex() == MI_BIKE_OPPRESSOR2.GetModelIndex() ) { FinishGlidingInstant(); } if(m_detachedTombstone) { m_detachedTombstone->FlagToDestroyWhenNextProcessed(); m_detachedTombstone = nullptr; } } bool CVehicle::IsUpsideDown() const { if (GetTransform().GetC().GetZf() <= -0.90f) { return true; } return false; } bool CVehicle::IsOnItsSide() const { float fRightZ = GetTransform().GetA().GetZf(); if ( (fRightZ >= 0.80f) || (fRightZ <= -0.80f) ) { return true; } return false; } bool CVehicle::IsRightWayUp() const { if( GetTransform().GetC().GetZf() > 0.5f ) { return true; } return false; } void CVehicle::SetCarDoorLocks(eCarLockState NewLockState) { Assertf(NewLockState != CARLOCK_NONE, "Vehicle door lock state being set to invalid CARLOCK_NONE state."); #if __BANK vehicledoorDebugf3("CVehicle::SetCarDoorLocks() [%s][%s][%p]: Current lock state - %s, NewLockState - %s", AILogging::GetDynamicEntityNameSafe(this), this->GetModelName(), this, VehicleLockStateStrings[m_eDoorLockState], VehicleLockStateStrings[NewLockState]); if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3) { sysStack::PrintStackTrace(); } #endif if(NewLockState == CARLOCK_PARTIALLY && m_eDoorLockState != CARLOCK_PARTIALLY) { for(int iDoor = 0; iDoor < GetNumDoors(); iDoor++) { if(GetDoor(iDoor)) { GetDoor(iDoor)->m_eDoorLockState = m_eDoorLockState; } } } m_eDoorLockState = NewLockState; } void CVehicle::SetRemoteCarDoorLocks(eCarLockState NewLockState) { #if __BANK vehicledoorDebugf3("CVehicle::SetRemoteCarDoorLocks() [%s][%s][%p]: Current lock state - %s, NewLockState - %s", AILogging::GetDynamicEntityNameSafe(this), this->GetModelName(), this, VehicleLockStateStrings[m_eDoorLockState], VehicleLockStateStrings[NewLockState]); if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3) { sysStack::PrintStackTrace(); } #endif m_eDoorLockState = NewLockState; } void CVehicle::ResetCarDoorKnockedOpenStatus() { for(int iDoor = 0; iDoor < GetNumDoors(); iDoor++) { if(GetDoor(iDoor)) { GetDoor(iDoor)->SetHasBeenKnockedOpen(false); } } } ///////////////////////////////////////////////////////////////////// // FUNCTION : CanPedOpenLocks // PURPOSE : Uses the type of ped and the state of this veh's locks // to decide whether the ped can open a veh door ///////////////////////////////////////////////////////////////////// bool CVehicle::CanPedOpenLocks(const CPed *pEnteringPed) const { if (!CanPedEnterCar(pEnteringPed)) return false; // the machine that is in control of this clone dictates whether the ped can open a door if (pEnteringPed->IsNetworkClone()) return true; Assert(m_eDoorLockState != CARLOCK_NONE); Assert(pEnteringPed != NULL); if (m_eDoorLockState == CARLOCK_LOCKED || m_eDoorLockState == CARLOCK_LOCKED_INITIALLY || m_eDoorLockState == CARLOCK_LOCKED_BUT_CAN_BE_DAMAGED || m_eDoorLockState == CARLOCK_LOCKED_BUT_BOOT_UNLOCKED) { return FALSE; } if( m_eDoorLockState == CARLOCK_LOCKED_PLAYER_INSIDE ) { // Players group members can freely get in and out of the veh CPedGroup* pPedsGroup = pEnteringPed->GetPedsGroup(); if( !pEnteringPed->IsPlayer() && pPedsGroup && pPedsGroup->GetGroupMembership()->GetLeader() && pPedsGroup->GetGroupMembership()->GetLeader()->IsPlayer() ) { return true; } return false; } if ((pEnteringPed->IsPlayer()) && (m_eDoorLockState == CARLOCK_LOCKOUT_PLAYER_ONLY)) { return FALSE; } return TRUE; } bool CVehicle::CanDoorsBeDamaged(void) const { if ( (m_eDoorLockState == CARLOCK_NONE) || (m_eDoorLockState == CARLOCK_UNLOCKED) || (m_eDoorLockState == CARLOCK_LOCKED_BUT_CAN_BE_DAMAGED) ) { return true; } return false; } eCarLockState CVehicle::GetDoorLockStateFromDoorIndex(int iDoor) const { if(m_eDoorLockState == CARLOCK_PARTIALLY) { if(iDoor >= 0 && iDoor < GetNumDoors()) { return GetDoor(iDoor)->m_eDoorLockState; } Assertf(false, "Invalid door index %d", iDoor); return CARLOCK_NONE; } return m_eDoorLockState; } eCarLockState CVehicle::GetDoorLockStateFromEntryPoint(int iEntry) const { if(m_eDoorLockState == CARLOCK_PARTIALLY) { if(iEntry >= 0 && iEntry < GetNumDoors()) { const CCarDoor* pDoor = CTaskVehicleFSM::GetDoorForEntryPointIndex(this, iEntry); if(pDoor) { return pDoor->m_eDoorLockState; } } return CARLOCK_NONE; } return m_eDoorLockState; } eCarLockState CVehicle::GetDoorLockStateFromSeatIndex(int iSeat) const { if(m_eDoorLockState == CARLOCK_PARTIALLY) { // HACK for B*2845779: BOXVILLE warp seats 4/5 don't have doors linked in meta (for various reasons), so check the seats next to them (2/3) that do have valid door links static u32 BOXVILLE_LAYOUT_HASH = atStringHash("LAYOUT_VAN_BOXVILLE"); if (iSeat >= 4 && GetLayoutInfo()->GetName() == BOXVILLE_LAYOUT_HASH) { iSeat -= 2; } const CModelSeatInfo* pModelSeatInfo = GetVehicleModelInfo()->GetModelSeatInfo(); if (pModelSeatInfo && IsSeatIndexValid(iSeat)) { s32 iEntryPointIndex = -1; // Wrapping in a layout hash check for mpExec release only (to be super safe) static u32 MULE_LAYOUT_HASH = atStringHash("LAYOUT_VAN_MULE"); if (GetLayoutInfo()->GetName() == MULE_LAYOUT_HASH) { iEntryPointIndex = pModelSeatInfo->GetEntryPointIndexForSeat(iSeat, this); } else { iEntryPointIndex = GetDirectEntryPointIndexForSeat(iSeat); } return GetDoorLockStateFromEntryPoint(iEntryPointIndex); } Assertf(false, "Invalid seat index %d", iSeat); return CARLOCK_NONE; } return m_eDoorLockState; } void CVehicle::SetDoorLockStateFromDoorIndex(int iDoor, eCarLockState eState) { if(m_eDoorLockState == CARLOCK_PARTIALLY && eState != CARLOCK_PARTIALLY) { if(iDoor >= 0 && iDoor < GetNumDoors()) { #if __BANK vehicledoorDebugf3("CVehicle::SetDoorLockStateFromDoorIndex() [%s][%s][%p]: index - %i, Current lock state - %s, NewLockState - %s", AILogging::GetDynamicEntityNameSafe(this), this->GetModelName(), this, iDoor, VehicleLockStateStrings[GetDoor(iDoor)->m_eDoorLockState], VehicleLockStateStrings[eState]); if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3) { sysStack::PrintStackTrace(); } #endif GetDoor(iDoor)->m_eDoorLockState = eState; } else { Assertf(false, "Can not set lock state to individual door, invalid door index %d", iDoor); } } else { Assertf(false, "Can not set lock state to individual door, the vehicle lock state is %d, desired door state is %d", m_eDoorLockState, eState); } } void CVehicle::SetDoorLockStateFromDoorID(eHierarchyId eDoorID, eCarLockState eState) { if(m_eDoorLockState == CARLOCK_PARTIALLY && eState != CARLOCK_PARTIALLY) { CCarDoor* pDoor = GetDoorFromId(eDoorID); if(pDoor) { #if __BANK vehicledoorDebugf3("CVehicle::SetDoorLockStateFromDoorID() [%s][%s][%p]: eDoorID - %i, Current lock state - %s, NewLockState - %s", AILogging::GetDynamicEntityNameSafe(this), this->GetModelName(), this, eDoorID, VehicleLockStateStrings[pDoor->m_eDoorLockState], VehicleLockStateStrings[eState]); if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3) { sysStack::PrintStackTrace(); } #endif pDoor->m_eDoorLockState = eState; } else { Assertf(false, "Can not set lock state to individual door, invalid door ID %d", eDoorID); } } else { Assertf(false, "Can not set lock state to individual door, the vehicle lock state is %d, desired door state is %d", m_eDoorLockState, eState); } } void CVehicle::SetDoorLockStateFromEntryPoint(int iEntry, eCarLockState eState) { if(m_eDoorLockState == CARLOCK_PARTIALLY && eState != CARLOCK_PARTIALLY) { if(iEntry >= 0 && iEntry < GetNumDoors()) { CCarDoor* pDoor = CTaskVehicleFSM::GetDoorForEntryPointIndex(this, iEntry); if(pDoor) { #if __BANK vehicledoorDebugf3("CVehicle::SetDoorLockStateFromEntryPoint() [%s][%s][%p]: iEntryPoint - %i, Current lock state - %s, NewLockState - %s", AILogging::GetDynamicEntityNameSafe(this), this->GetModelName(), this, iEntry, VehicleLockStateStrings[pDoor->m_eDoorLockState], VehicleLockStateStrings[eState]); if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3) { sysStack::PrintStackTrace(); } #endif pDoor->m_eDoorLockState = eState; } } else { Assertf(false, "Can not set lock state to individual door, invalid entry index %d", iEntry); } } else { Assertf(false, "Can not set lock state to individual door, the vehicle lock state is %d, desired door state is %d", m_eDoorLockState, eState); } } void CVehicle::SetDoorLockStateFromSeatIndex(int iSeat, eCarLockState eState) { if(m_eDoorLockState == CARLOCK_PARTIALLY && eState != CARLOCK_PARTIALLY) { const CModelSeatInfo* pModelSeatinfo = GetVehicleModelInfo()->GetModelSeatInfo(); if (pModelSeatinfo && IsSeatIndexValid(iSeat)) { const s32 iEntryPointIndex = GetDirectEntryPointIndexForSeat(iSeat); SetDoorLockStateFromEntryPoint(iEntryPointIndex, eState); } else { Assertf(false, "Can not set lock state to individual door, invalid seat index %d", iSeat); } } else { Assertf(false, "Can not set lock state to individual door, the vehicle lock state is %d, desired door state is %d", m_eDoorLockState, eState); } } bool CVehicle::IsPedUsingDoor() { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); if (pVehicleModelInfo) { const CModelSeatInfo* pModelSeatInfo = pVehicleModelInfo->GetModelSeatInfo(); if (pModelSeatInfo) { const s32 iNumberEntryExitPoints = pModelSeatInfo->GetNumberEntryExitPoints(); // Go through entry exit point and see if it can for( s32 i = 0; i < iNumberEntryExitPoints; i++ ) { CComponentReservation* pComponentReservation = m_ComponentReservationMgr.GetDoorReservation(i); if (pComponentReservation && pComponentReservation->IsComponentInUse()) return true; } } } return false; } bool CVehicle::IsPedUsingDoor( CCarDoor* pDoor ) { CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); if (pVehicleModelInfo) { const CModelSeatInfo* pModelSeatInfo = pVehicleModelInfo->GetModelSeatInfo(); if( pModelSeatInfo ) { const s32 iNumberEntryExitPoints = pModelSeatInfo->GetNumberEntryExitPoints(); // Go through entry exit point and see if it can for( s32 i = 0; i < iNumberEntryExitPoints; i++ ) { CComponentReservation* pComponentReservation = m_ComponentReservationMgr.GetDoorReservation(i); if( pComponentReservation && pComponentReservation->IsComponentInUse() ) { if( pDoor->GetBoneIndex() == pComponentReservation->GetBoneIndex() ) { return true; } } } } } return false; } #define MAXSPEED_CANOPENDOOR_AT_SQ (0.2f*0.2f) #define MAXTURNSPEED_CANOPENDOOR_AT_SQ (0.2f*0.2f*0.2f) dev_float MAX_VEH_ROLL_ANGLE_PLAYER_CANJUMPOUT = 45.0f * DtoR; dev_float MAX_VEH_PITCH_ANGLE_PLAYER_CANJUMPOUT = 55.0f * DtoR; dev_float MAXANGXSPEED_PLAYER_CANJUMPOUT = 45.0f * DtoR; dev_float MAXANGYSPEED_PLAYER_CANJUMPOUT = 45.0f * DtoR; // // CanPedEnterCar(): // bool CVehicle::CanPedEnterCar(const CPed *pEnteringPed) const { if (NetworkInterface::IsGameInProgress()) { // certain lock states are only open to certain teams in a network game if (pEnteringPed->IsPlayer() && m_eDoorLockState >= CARLOCK_LOCKED_EXCEPT_TEAM1) { if (pEnteringPed->GetNetworkObject()->GetPlayerOwner()->GetTeam() != (m_eDoorLockState-CARLOCK_LOCKED_EXCEPT_TEAM1)) return false; } // networked vehicles can be locked for individual players if (pEnteringPed->IsAPlayerPed() && pEnteringPed->GetNetworkObject()) { // DMKH. Add an override for players in custody. If the driver is our custodian, we always want them to get in. bool bDriverIsCustodian = GetDriver() && (pEnteringPed->GetCustodian() == GetDriver()); if(!bDriverIsCustodian) { // Let peds enter vehicles that have a driver (either as a passenger or to pull the driver out) if( (!GetDriver()) || m_nVehicleFlags.bRespectLocksWhenHasDriver ) { if (CTaskVehicleFSM::IsVehicleLockedForPlayer(*this, *pEnteringPed)) { return false; } } } } } // Can't enter vehicles if they are locked (we might try to open the locked door though) if (m_eDoorLockState == CARLOCK_LOCKED) { return false; } if (pEnteringPed->IsPlayer() && m_eDoorLockState == CARLOCK_LOCKOUT_PLAYER_ONLY) { return false; } // Cant enter vehicles if they have been locked for special edition only. // if(GetLayoutInfo()->GetLockedForSpecialEdition() && pEnteringPed->IsLocalPlayer()) // { // bool bSpecialEditionUnlocked = GetCanEnterLockedForSpecialEditionVehicles() || // #if __JAPANESE_BUILD // EXTRACONTENT.IsContentPresent(ATSTRINGHASH("dlc_jpn_bonus", 0xf0dc7df2)); // #else // EXTRACONTENT.IsContentPresent(ATSTRINGHASH("dlc_atomicblimp", 0xb6de61e2)) || // EXTRACONTENT.IsContentPresent(ATSTRINGHASH("dlc_specialedition", 0x2040A77)) || // EXTRACONTENT.IsContentPresent(ATSTRINGHASH("dlc_collectorsedition", 0x4f98c2a7)); // #endif // // if(!bSpecialEditionUnlocked) // { // return false; // } // } return true; } //#define SMALL_TURN_AMOUNT (0.01f) static dev_float BOAT_IN_WATER_STOPPED_SPEED_2D =(6.0f); static dev_float BOAT_STOPPED_SPEED_2D =(15.0f); static dev_float BOAT_STOPPED_SPEED_Z =(2.5f); static dev_float JET_SKI_IN_WATER_STOPPED_SPEED_2D =(6.0f); static dev_float JET_SKI_STOPPED_SPEED_2D =(15.0f); static dev_float JET_SKI_STOPPED_SPEED_Z =(2.5f); static dev_float HELI_STOPPED_SPEED_2D =(10.0f); // static dev_float HELI_STOPPED_SPEED_Z =(4.0f); static dev_float STOPPED_SPEED_2D =(1.0f); static dev_float STOPPED_SPEED_Z =(2.5f); #define STOPPED_TURN_SPEED (0.02f*50.0f) bool CVehicle::CanPedStepOutCar(const CPed* pPed, bool bIgnoreSpeedCheck, bool bDoGroundCheckForHelis) const { float fCarUpZ = GetTransform().GetC().GetZf(); // if (GetModelIndex() == MI_CAR_CABLECAR) // { // return false; // } // If we're on moving ground, compute the relative velocity, just take the first physical as the ground physical Vector3 vRelativeVelocity = GetVelocityIncludingReferenceFrame(); float fVelXY = vRelativeVelocity.XYMag(); float fVelZ = vRelativeVelocity.z; float fCarBikeStoppedSpeed2D = STOPPED_SPEED_2D; float fCarBikeStoppedSpeedZ = STOPPED_SPEED_Z; if (CPlayerInfo::AreCNCResponsivenessChangesEnabled(pPed)) { TUNE_GROUP_FLOAT(CNC_RESPONSIVENESS, fCanStepOutCarStoppedSpeed2D, 20.0f, 1.0f, 50.0f, 0.01f); TUNE_GROUP_FLOAT(CNC_RESPONSIVENESS, fCanStepOutCarStoppedSpeedZ, 20.0f, 1.0f, 50.0f, 0.01f); fCarBikeStoppedSpeed2D = fCanStepOutCarStoppedSpeed2D; fCarBikeStoppedSpeedZ = fCanStepOutCarStoppedSpeedZ; } TUNE_GROUP_FLOAT(VEHICLE_EXIT_TUNE, MAX_STEP_OFF_SPEED_HANGING_SEAT, 8.0f, 0.0f, 20.0f, 0.01f); const s32 iPedsSeatIndex = pPed ? GetSeatManager()->GetPedsSeatIndex(pPed) : -1; bool bIsHangingSeat = false; if (IsSeatIndexValid(iPedsSeatIndex)) { const CVehicleSeatAnimInfo* pSeatAnimInfo = GetSeatAnimationInfo(iPedsSeatIndex); if (pSeatAnimInfo && pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle()) { bIsHangingSeat = true; } } const float f2dStoppedSpeed = bIsHangingSeat ? MAX_STEP_OFF_SPEED_HANGING_SEAT : STOPPED_SPEED_2D; if ( (fCarUpZ <= 0.1f) && (fCarUpZ >= -0.1f) )// on it's side or up-ended { //only get out if stopped moving if(rage::Abs(vRelativeVelocity.z) > STOPPED_SPEED_Z || vRelativeVelocity.XYMag() > f2dStoppedSpeed || GetAngVelocity().Mag2() > STOPPED_TURN_SPEED*STOPPED_TURN_SPEED) return false; else return true; } else if(InheritsFromSubmarine()) { //! The sub can only jump out under water, so we do this check elsewhere. In CTaskCar::ExitVehicle_OnEnter(). return true; } else if(InheritsFromBoat()) { // Allow step out at any speed on the tugboat if(MI_BOAT_TUG.IsValid() && GetModelIndex() == MI_BOAT_TUG) return true; CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); if(pVehModelInfo && pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_TURRET_SEAT_ON_VEHICLE)) { return true; } if (!GetIsJetSki()) { float fBoatStopSpeed = GetIsInWater() ? BOAT_IN_WATER_STOPPED_SPEED_2D : BOAT_STOPPED_SPEED_2D; if((fVelXY > fBoatStopSpeed || rage::Abs(fVelZ) > BOAT_STOPPED_SPEED_Z)) return false; return true; } else { float fJetskiStopSpeed = GetIsInWater() ? JET_SKI_IN_WATER_STOPPED_SPEED_2D : JET_SKI_STOPPED_SPEED_2D; if(!bIgnoreSpeedCheck && (fVelXY > fJetskiStopSpeed || rage::Abs(fVelZ) > JET_SKI_STOPPED_SPEED_Z)) { return false; } } } else if(InheritsFromHeli()) { if(!bIgnoreSpeedCheck && ((fVelXY > HELI_STOPPED_SPEED_2D || fVelZ > HELI_STOPPED_SPEED_2D))) return false; if( pHandling->GetSeaPlaneHandlingData() && GetIsInWater() ) { if( !bIgnoreSpeedCheck && fVelXY > BOAT_IN_WATER_STOPPED_SPEED_2D ) return false; } if (NetworkInterface::IsGameInProgress() && bDoGroundCheckForHelis) { if(!CTaskExitVehicle::IsVehicleOnGround(*this)) { return false; } } } else if(pHandling->GetSeaPlaneHandlingData()) { float fSeaPlaneStopSpeed = GetIsInWater() ? BOAT_IN_WATER_STOPPED_SPEED_2D : STOPPED_SPEED_2D; if(!bIgnoreSpeedCheck && (fVelXY > fSeaPlaneStopSpeed || rage::Abs(fVelZ) > STOPPED_SPEED_Z)) return false; } else if(InheritsFromAmphibiousQuadBike()) { float fAmphiQuadSpeed2D = GetIsInWater() ? JET_SKI_IN_WATER_STOPPED_SPEED_2D : fCarBikeStoppedSpeed2D; float fAmphiQuadSpeedZ = GetIsInWater() ? JET_SKI_STOPPED_SPEED_Z : fCarBikeStoppedSpeedZ; if(!bIgnoreSpeedCheck && (fVelXY > fAmphiQuadSpeed2D || rage::Abs(fVelZ) > fAmphiQuadSpeedZ)) { return false; } } else if(!bIgnoreSpeedCheck && (fVelXY > (fCarBikeStoppedSpeed2D) || rage::Abs(fVelZ) > (fCarBikeStoppedSpeedZ))) { return false; } else if(GetAngVelocity().Mag2() > STOPPED_TURN_SPEED*STOPPED_TURN_SPEED) { return false; } return true; } bool CVehicle::CanPedJumpOutCar(CPed* pPed, bool bForceSpeedCheck) const { // Ignore the velocity threshold when the vehicle is underwater, we always want to be able to jump out // unless the vehicle is in shallow water if (!GetIsInWater() || (HasContactWheels() && !pPed->GetPedResetFlag(CPED_RESET_FLAG_IsDrowning))) { Vector3 vVelocity = GetVelocityIncludingReferenceFrame(); float fSpeedSqr = vVelocity.Mag2(); if (GetModelIndex() == MI_CAR_CABLECAR) { return false; } if(InheritsFromSubmarine()) { //! The sub can only jump out under water, so we do this check elsewhere. In CTaskCar::ExitVehicle_OnEnter(). return false; } bool bDoSpeedCheck = (HasContactWheels() || GetIsAquatic() || (MI_BIKE_OPPRESSOR2.IsValid() && GetModelIndex() == MI_BIKE_OPPRESSOR2)); if(bDoSpeedCheck || bForceSpeedCheck) { if( GetVehicleType() == VEHICLE_TYPE_BIKE || GetVehicleType() == VEHICLE_TYPE_BICYCLE ) { float fMinPlayerJumpOutSpeedBike = CTaskPlayerDrive::sm_Tunables.m_MinPlayerJumpOutSpeedBike; if (CPlayerInfo::AreCNCResponsivenessChangesEnabled(pPed)) { TUNE_GROUP_FLOAT(CNC_RESPONSIVENESS, fMinPlayerJumpOutSpeedBikeTune, 20.0f, 1.0f, 50.0f, 0.01f); fMinPlayerJumpOutSpeedBike = fMinPlayerJumpOutSpeedBikeTune; } if(fSpeedSqr < rage::square(fMinPlayerJumpOutSpeedBike)) return false; } else { float fMinPlayerJumpOutSpeedCar = CTaskPlayerDrive::sm_Tunables.m_MinPlayerJumpOutSpeedCar; if (CPlayerInfo::AreCNCResponsivenessChangesEnabled(pPed)) { TUNE_GROUP_FLOAT(CNC_RESPONSIVENESS, fMinPlayerJumpOutSpeedCarTune, 20.0f, 1.0f, 50.0f, 0.01f); fMinPlayerJumpOutSpeedCar = fMinPlayerJumpOutSpeedCarTune; } if(fSpeedSqr < rage::square(fMinPlayerJumpOutSpeedCar)) return false; } } } if (MI_CAR_TECHNICAL2.IsValid() && GetModelIndex() == MI_CAR_TECHNICAL2 && GetSeatManager()) { s32 iSeat = GetSeatManager()->GetPedsSeatIndex(pPed); if (iSeat == 2) { return false; } } // Always disable jump on the tugboat if(MI_BOAT_TUG.IsValid() && GetModelIndex() == MI_BOAT_TUG) return false; return true; } bool CVehicle::CanPedExitCar() { TUNE_BOOL(DISABLE_EXIT_VEHICLE_ORIENTATION_CHECKS, true); if (!DISABLE_EXIT_VEHICLE_ORIENTATION_CHECKS && IsInAir()) { const Vector3 vAngVelocity = GetAngVelocity(); if (Abs(vAngVelocity.x) > MAXANGXSPEED_PLAYER_CANJUMPOUT || Abs(vAngVelocity.y) > MAXANGYSPEED_PLAYER_CANJUMPOUT) { return false; } const float fVehRoll = GetTransform().GetRoll(); const float fVehPitch = GetTransform().GetPitch(); if (Abs(fVehRoll) > MAX_VEH_ROLL_ANGLE_PLAYER_CANJUMPOUT || Abs(fVehPitch) > MAX_VEH_PITCH_ANGLE_PLAYER_CANJUMPOUT) { return false; } } return true; } void CVehicle::ProcessCarAlarm(const float fFullUpdateTimeStep) { if ((!CarAlarmState) || CarAlarmState == CAR_ALARM_SET) return; const u32 nFullUpdateTimeStepInMS = (u32)ceilf(fFullUpdateTimeStep * 1000.0f); Assertf(nFullUpdateTimeStepInMS < MAX_UINT16, "Got suspicious update time step %f %u, for vehicle of type %s.", fFullUpdateTimeStep, nFullUpdateTimeStepInMS, GetVehicleModelInfo()->GetModelName()); if (CarAlarmState < nFullUpdateTimeStepInMS) { CarAlarmState = 0; if(m_VehicleAudioEntity) { m_VehicleAudioEntity->StopHorn(); } } else { CarAlarmState = (u16)(CarAlarmState - (u16)nFullUpdateTimeStepInMS); } } bool CVehicle::TriggerCarAlarm() { if(IsNetworkClone()) { return false; } // only cars can have a veh alarm if(GetVehicleType() == VEHICLE_TYPE_CAR && CarAlarmState == CAR_ALARM_SET && GetStatus()!=STATUS_WRECKED) { CarAlarmState = CAR_ALARM_DURATION + (u16)fwRandom::GetRandomNumberInRange(0, CAR_ALARM_RANDOM_DURATION); GetVehicleAudioEntity()->PlayVehicleAlarm(); return true; } return false; } ////////////////////////////////////////////////////////////////////////////// // NAME : AssertVehiclePointerValid // FUNCTION : Will assert if the ped pointer is not valid or the ped is not in // the world. ////////////////////////////////////////////////////////////////////////////// #ifdef DEBUG void AssertVehiclePointerValid(CVehicle* pVeh) { Assert(IsVehiclePointerValid(pVeh)); /* AssertVehiclePointerValid_NotInWorld(pVeh); // Make sure this ped is actually in the world at the moment Assert(pVeh->GetBaseVehicleType() == VEHICLE_TYPE_FAKE_PLANE || pVeh->m_listEntryInfo.GetHeadPtr()); */ } void AssertVehiclePointerValid_NotInWorld(CVehicle* pVeh) { Assert(IsVehiclePointerValid_NotInWorld(pVeh)); } #endif bool IsVehiclePointerValid(CVehicle* pVeh) { if(!IsVehiclePointerValid_NotInWorld(pVeh)) { return false; } /* // Make sure this ped is actually in the world at the moment return (pVeh->GetEntryInfoList().GetHeadPtr()); */ return true; } bool IsVehiclePointerValid_NotInWorld(CVehicle* pVeh) { return CVehicle::GetPool()->IsValidPtr(pVeh); } // // name: IsLawEnforcementVehicle // description: Return if this is a law enforcement vehicle bool CVehicle::IsLawEnforcementVehicleModelId(fwModelId modelId) { CVehicleModelInfo* pVehModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_LAW_ENFORCEMENT); } bool CVehicle::IsTunerVehicle() const { return ((MI_CAR_CALICO.IsValid() && GetModelIndex() == MI_CAR_CALICO) || (MI_CAR_COMET6.IsValid() && GetModelIndex() == MI_CAR_COMET6) || (MI_CAR_CYPHER.IsValid() && GetModelIndex() == MI_CAR_CYPHER) || (MI_CAR_DOMINATOR7.IsValid() && GetModelIndex() == MI_CAR_DOMINATOR7) || (MI_CAR_DOMINATOR8.IsValid() && GetModelIndex() == MI_CAR_DOMINATOR8) || (MI_CAR_EUROS.IsValid() && GetModelIndex() == MI_CAR_EUROS) || (MI_CAR_FREIGHTCAR2.IsValid() && GetModelIndex() == MI_CAR_FREIGHTCAR2) || (MI_CAR_FUTO2.IsValid() && GetModelIndex() == MI_CAR_FUTO2) || (MI_CAR_GROWLER.IsValid() && GetModelIndex() == MI_CAR_GROWLER) || (MI_CAR_JESTER4.IsValid() && GetModelIndex() == MI_CAR_JESTER4) || (MI_CAR_PREVION.IsValid() && GetModelIndex() == MI_CAR_PREVION) || (MI_CAR_REMUS.IsValid() && GetModelIndex() == MI_CAR_REMUS) || (MI_CAR_RT3000.IsValid() && GetModelIndex() == MI_CAR_RT3000) || (MI_CAR_SULTAN3.IsValid() && GetModelIndex() == MI_CAR_SULTAN3) || (MI_CAR_TAILGATER2.IsValid() && GetModelIndex() == MI_CAR_TAILGATER2) || (MI_CAR_VECTRE.IsValid() && GetModelIndex() == MI_CAR_VECTRE) || (MI_CAR_WARRENER2.IsValid() && GetModelIndex() == MI_CAR_WARRENER2) || (MI_CAR_ZR350.IsValid() && GetModelIndex() == MI_CAR_ZR350)); } bool CVehicle::IsTaxiModelId(fwModelId modelId) { CVehicleModelInfo* pVehModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_TAXI); } bool CVehicle::IsBusModelId(fwModelId modelId) { CVehicleModelInfo* pVehModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_BUS); } bool CVehicle::IsTractorModelId(fwModelId modelId) { CVehicleModelInfo* pVehModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); Assert(pVehModelInfo); const u32 vehModelHash = pVehModelInfo->GetModelNameHash(); return vehModelHash == MID_TRACTOR || vehModelHash == MID_TRACTOR2; } bool CVehicle::IsMowerModelId(fwModelId modelId) { CVehicleModelInfo* pVehModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); Assert(pVehModelInfo); const u32 vehModelHash = pVehModelInfo->GetModelNameHash(); return vehModelHash == MID_MOWER; } bool CVehicle::IsTank() const { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_TANK); } bool CVehicle::IsTaxi() const { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_TAXI); } bool CVehicle::IsTrike() const { if ((MI_TRIKE_CHIMERA.IsValid() && GetModelIndex() == MI_TRIKE_CHIMERA) || (MI_TRIKE_RROCKET.IsValid() && GetModelIndex() == MI_TRIKE_RROCKET)) { return true; } else { return false; } } bool CVehicle::IsReverseTrike() const { if( ( MI_CAR_RAPTOR.IsValid() && GetModelIndex() == MI_CAR_RAPTOR ) || ( MI_CAR_STRYDER.IsValid() && GetModelIndex() == MI_CAR_STRYDER ) ) { return true; } return false; } bool CVehicle::HasBulletResistantGlass() const { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_BULLET_RESISTANT_GLASS); } bool CVehicle::HasIncreasedRammingForce() { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); bool increasedRammingForce = pVehModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_INCREASED_RAMMING_FORCE ); if( !increasedRammingForce ) { if( MI_CAR_TECHNICAL3.IsValid() && GetModelIndex() == MI_CAR_TECHNICAL3 ) { CVehicleVariationInstance& variation = GetVariationInstance(); bool bVariation = false; if( variation.GetModIndexForType( VMT_GRILL, this, bVariation ) >= 3 ) { increasedRammingForce = true; } } if( pVehModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_INCREASED_RAMMING_FORCE_WITH_CHASSIS_MOD ) ) { CVehicleVariationInstance& variation = GetVariationInstance(); bool bVariation = false; u8 modIndex = variation.GetModIndexForType( VMT_CHASSIS, this, bVariation ); if( modIndex != INVALID_MOD && modIndex >= variation.GetNumModsForType( VMT_CHASSIS, this ) - 1 ) { increasedRammingForce = true; } } } return increasedRammingForce; } bool CVehicle::HasIncreasedRammingForceVsAllVehicles() { return GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_INCREASED_RAMMING_FORCE_VS_ALL_VEHICLES ); } bool CVehicle::HasRammingScoop() const { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); if( pVehModelInfo && pVehModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_RAMMING_SCOOP_MOD ) ) { const CVehicleVariationInstance& variation = GetVariationInstance(); bool bVariation = false; int modIndex = (int)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ); modIndex -= GetNumRampBones() + GetNumRammingBarBones() + GetNumFrontSpikeBones(); if( modIndex >= 0 && modIndex < GetNumRammingScoopBones() ) { return true; } } return pVehModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMMING_SCOOP ); } bool CVehicle::HasSuperBrakes() const { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert( pVehModelInfo ); static dev_bool sbTestSuperBrakes = false; if( sbTestSuperBrakes || pVehModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_SUPER_BRAKES_MOD ) ) { const CVehicleVariationInstance& variation = GetVariationInstance(); bool bVariation = false; // TODO: FIND OUT WHICH MOD SLOT THIS WILL USE u8 modIndex = variation.GetModIndexForType( VMT_CHASSIS, this, bVariation ); if( sbTestSuperBrakes || ( modIndex != INVALID_MOD && modIndex >= variation.GetNumModsForType( VMT_CHASSIS, this ) - 1 ) ) { return true; } } return false; } bool CVehicle::HasRamp() const { CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); bool result = pVehModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP ); if( !result ) { const CVehicleVariationInstance& variation = GetVariationInstance(); bool bVariation = false; int modIndex = (int)variation.GetModIndexForType( VMT_CHASSIS2, this, bVariation ); modIndex -= GetNumFrontSpikeBones(); if( modIndex > -1 && modIndex < GetNumRampBones() ) { result = true; } } return result; } // // name: IsLawEnforcementCar // description: Return if this is a law enforcement vehicle bool CVehicle::IsLawEnforcementCarModelId(fwModelId modelId) { CVehicleModelInfo* pVehModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfo(modelId); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_LAW_ENFORCEMENT) && (pVehModelInfo->GetVehicleType() == VEHICLE_TYPE_CAR); } bool CVehicle::FleesFromCombat() const { if(PopTypeIsMission()) { return false; } if(HasMissionCharsInIt()) { return false; } CVehicleModelInfo* pVehModelInfo = GetVehicleModelInfo(); Assert(pVehModelInfo); return pVehModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_FLEE_FROM_COMBAT); } const CAIHandlingInfo* CVehicle::GetAIHandlingInfo() const { if (GetIntelligence()->m_nHandlingOverrideHash != 0) { return CAIHandlingInfoMgr::GetAIHandlingInfoByHash(GetIntelligence()->m_nHandlingOverrideHash); } else { return pHandling->GetAIHandlingInfo(); } } bool CVehicle::ShufflePassengersToMakeSpace() { return true; } bool CVehicle::CanLeakPetrol() const { if (NetworkInterface::IsGameInProgress()) { // no vehicle petrol leaks in a network game return false; } CPed* pPlayerPed = CGameWorld::FindLocalPlayer(); bool isPlayerCarThatCanLeak = pPlayerPed && pPlayerPed->GetMyVehicle()==this && m_nVehicleFlags.bCanPlayerCarLeakPetrol; bool isOtherCarThatCanLeak = m_nVehicleFlags.bCanLeakPetrol; //If script called SetVehicleCanLeakPetrol(FALSE) to force leaking off we don't want any leaking, even if damaged by the player bool isCarRecentlyDamagedByPlayer = !HasScriptForcedLeakingOff() && g_vfxVehicle.HasRecentlyBeenDamagedByPlayer(this); return (isPlayerCarThatCanLeak || isOtherCarThatCanLeak || isCarRecentlyDamagedByPlayer); } bool CVehicle::HasScriptForcedLeakingOff() const { return !m_nVehicleFlags.bCanPlayerCarLeakPetrol && !m_nVehicleFlags.bCanLeakPetrol; } bool CVehicle::HasScriptForcedLeakingOn() const { return m_nVehicleFlags.bCanPlayerCarLeakPetrol && m_nVehicleFlags.bCanLeakPetrol; } bool CVehicle::CanLeakOil() const { CPed* pPlayerPed = CGameWorld::FindLocalPlayer(); bool isPlayerCarThatCanLeak = pPlayerPed && pPlayerPed->GetMyVehicle()==this && m_nVehicleFlags.bCanPlayerCarLeakOil; bool isOtherCarThatCanLeak = m_nVehicleFlags.bCanLeakOil; return (isPlayerCarThatCanLeak || isOtherCarThatCanLeak); } bool CVehicle::IsLeakingPetrol() const { // check if the vehicle is allowed to leak petrol if (CanLeakPetrol()) { // check that there is petrol in the tank if (GetStatus()!=STATUS_WRECKED && m_VehicleDamage.GetPetrolTankLevel() > 0.0f) { // check if the petrol tank is leaking float petrolTankHealth = m_VehicleDamage.GetPetrolTankHealth(); if (petrolTankHealthVEH_DAMAGE_HEALTH_PTANK_ONFIRE) { return true; } } } return false; } bool CVehicle::IsLeakingOil() const { // check if the vehicle is allowed to leak oil if (CanLeakOil()) { // check that there is oil in the engine if (GetStatus()!=STATUS_WRECKED && m_VehicleDamage.GetOilLevel() > 0.0f) { // check if the engine is leaking oil float engineHealth = m_VehicleDamage.GetEngineHealth(); if (engineHealthENGINE_DAMAGE_ONFIRE) { return true; } } } return false; } bool CVehicle::CanEngineDegrade() const { CPed* pPlayerPed = CGameWorld::FindLocalPlayer(); bool isPlayerAircraftEngineThatCanDegrade = pPlayerPed && pPlayerPed->GetMyVehicle()==this && m_nVehicleFlags.bCanPlayerAircraftEngineDegrade; bool isOtherAircraftEngineThatCanDegrade = m_nVehicleFlags.bCanEngineDegrade; return (isPlayerAircraftEngineThatCanDegrade || isOtherAircraftEngineThatCanDegrade); } bool CVehicle::IsOnFire() const { return GetVehicleDamage()->GetPetrolTankOnFire() || GetVehicleDamage()->GetEngineOnFire() || g_fireMan.IsEntityOnFire(this); } void CVehicle::ExtinguishCarFire(bool finishPtFx, bool bClampVehicleHealth) { if (GetStatus() != STATUS_WRECKED) // Don't reset health for cars that have exploded. This will cause them to be explodable again. { if (!NetworkUtils::IsNetworkCloneOrMigrating(this) && bClampVehicleHealth ) // can't set the health on network clones { SetHealth( MAX(GetHealth(), 300) ); } } if (NetworkInterface::IsNetworkOpen()) { CNetObjVehicle* pNetObjVehicle = (CNetObjVehicle*) GetNetworkObject(); if (pNetObjVehicle) pNetObjVehicle->OnVehicleFireExtinguished(); } g_fireMan.ExtinguishEntityFires(this, finishPtFx); if(m_Transmission.GetEngineOnFire() || m_VehicleDamage.GetPetrolTankOnFire()) { // If the petrol tank or engine was on fire then set the vehicle to not be drivable any more. // This would have happened anyway after the fire had been burning for long enough. m_VehicleDamage.SetPetrolTankHealth(1.0f); m_Transmission.SetEngineHealth(ENGINE_DAMAGE_FINSHED); } // m_VehicleDamage.SetPetrolTankHealth( MAX(m_VehicleDamage.GetPetrolTankHealth(), (VEH_DAMAGE_HEALTH_PTANK_ONFIRE + 1.0f) ) ); // m_Transmission.SetEngineHealth( MAX(m_Transmission.GetEngineHealth(), (ENGINE_DAMAGE_ONFIRE + 1.0f) ) ); for (int i=0; iClearWheelOnFire(); } if (InheritsFromAutomobile()) { MUST_FIX_THIS(sandy); } } void CVehicle::ClearCulpritFromCarFires(const CEntity* pCulprit) { // Clear the petrol tank fire culprit if(m_VehicleDamage.GetEntityThatSetUsOnFire() == pCulprit) { m_VehicleDamage.ClearPetrolTankFireCulprit(); } // Clear the transmission fire culprit if(m_Transmission.GetEntityThatSetUsOnFire() == pCulprit) { m_Transmission.ClearFireCulprit(); } } // returns TRUE if specified veh owns a roof // (useful for rain generation code inside veh models); // bool CVehicle::CarHasRoof(bool bCheckForRoofDown) const { if(((CVehicleModelInfo*)GetBaseModelInfo())->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_CONVERTIBLE)) { // For now any non-veh vehicle with the convertible flag set is always open topped. if( GetVehicleType() != VEHICLE_TYPE_CAR ) { return false; } // HACK: Mamba is a special case that needs to support multiple topless variants and only one roof (EXTRA_3) else if (MI_CAR_MAMBA.IsValid() && GetModelIndex() == MI_CAR_MAMBA) { if (HasComponent(VEH_EXTRA_3) && (!(m_nDisableExtras &BIT(3)) || bCheckForRoofDown )) { return true; } } // is this veh with changeable veh roof with roof in use? else if((HasComponent(VEH_EXTRA_2) && !(m_nDisableExtras &BIT(2))) || (HasComponent(VEH_EXTRA_3) && !(m_nDisableExtras &BIT(3))) || (HasComponent(VEH_EXTRA_4) && !(m_nDisableExtras &BIT(4)))) { return true; } else if(bCheckForRoofDown && (HasComponent(VEH_EXTRA_2) || HasComponent(VEH_EXTRA_3) || HasComponent(VEH_EXTRA_4))) { return true; } return false; } return true; } bool CVehicle::HasRaisedRoof() const { bool bDoesVehicleHaveAConvertibleRoofAnimation = DoesVehicleHaveAConvertibleRoofAnimation(); return CarHasRoof() && (!bDoesVehicleHaveAConvertibleRoofAnimation || (bDoesVehicleHaveAConvertibleRoofAnimation && GetConvertibleRoofState() == CTaskVehicleConvertibleRoof::STATE_RAISED)); } bool CVehicle::IsGangCar() const { if(((CVehicleModelInfo*)GetBaseModelInfo())->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_GANG)) { if((HasComponent(VEH_EXTRA_5) && !(m_nDisableExtras &BIT(5))) || (HasComponent(VEH_EXTRA_6) && !(m_nDisableExtras &BIT(6))) || (HasComponent(VEH_EXTRA_7) && !(m_nDisableExtras &BIT(7))) || (HasComponent(VEH_EXTRA_8) && !(m_nDisableExtras &BIT(8)))) { return true; } } return false; } CPed* CVehicle::GetVehicleWasSunkByAPlayerPed() { if (GetStatus() != STATUS_WRECKED) return NULL; if (!GetIsLandVehicle()) return NULL; if (!GetIsInWater() && !(IsNetworkClone() && IsDummy())) return NULL; CPed* damager = GetDriver(); if (!damager) damager = GetLastDriver(); //Check if the vehicle is attached to a car bob CVehicle* pAttachParentVehicle = GetAttachParentVehicle(); if (pAttachParentVehicle && (pAttachParentVehicle->GetDriver() || pAttachParentVehicle->GetLastDriver())) { damager = pAttachParentVehicle->GetDriver(); if (!damager) damager = pAttachParentVehicle->GetLastDriver(); } //Must be a player if (!damager || !damager->IsAPlayerPed()) return NULL; return damager; } int CVehicle::GetPersonalVehicleOwnerId() { atHashWithStringBank decKey("Player_Vehicle",0x299D7A40); if (fwDecoratorExtension::ExistsOn(*this, decKey)) { int decValue = 0; fwDecoratorExtension::Get(*this, decKey, decValue); return decValue; } return 0; } bool CVehicle::IsPersonalVehicle() { atHashWithStringBank decKey("Player_Vehicle",0x299D7A40); if (fwDecoratorExtension::ExistsOn(*this, decKey)) { return true; } return false; } bool CVehicle::IsPersonalOperationsVehicle() { if ((MI_CAR_PHANTOM3.IsValid() && GetModelIndex() == MI_CAR_PHANTOM3) || (MI_CAR_HAULER2.IsValid() && GetModelIndex() == MI_CAR_HAULER2)) { atHashWithStringBank decKeyMOC("Player_Truck",0x7B869B1A); if (fwDecoratorExtension::ExistsOn(*this, decKeyMOC)) { return true; } } else if (MI_PLANE_AVENGER.IsValid() && GetModelIndex() == MI_PLANE_AVENGER) { atHashWithStringBank decKeyAvenger("Player_Avenger",0x1802B6C7); if (fwDecoratorExtension::ExistsOn(*this, decKeyAvenger)) { return true; } } else if ((MI_CAR_TERBYTE.IsValid() && GetModelIndex() == MI_CAR_TERBYTE)) { atHashWithStringBank decKeyHackerTruck("Player_Hacker_Truck", 0x41B4ADCA); if (fwDecoratorExtension::ExistsOn(*this, decKeyHackerTruck)) { return true; } } return false; } void CVehicle::DeployFoldingWings(bool bDeploy, bool UNUSED_PARAM( bInstant) ) { 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(bInstant) //{ // pDoor->ClearFlag( CCarDoor::DRIVEN_MASK ); // if( bDeploy ) // { // pDoor->SetFlag( CCarDoor::DRIVEN_NORESET | CCarDoor::PROCESS_FORCE_AWAKE | CCarDoor::SET_TO_FORCE_OPEN | CCarDoor::DRIVEN_SPECIAL ); // } // else // { // pDoor->SetShutAndLatched( this ); // } //} //else { // 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( bDeploy ? 1.0f : 0.0f, bDeploy ? ( CCarDoor::DRIVEN_NORESET | CCarDoor::DRIVEN_SPECIAL ) : ( CCarDoor::DRIVEN_NORESET | CCarDoor::DRIVEN_SPECIAL | CCarDoor::WILL_LOCK_DRIVEN ), this ); } } } } bool CVehicle::GetAreFoldingWingsDeployed() const { for( int i = (int)VEH_FOLDING_WING_L; i <= (int)VEH_FOLDING_WING_R; i++ ) { eHierarchyId nDoorId = (eHierarchyId)i; const CCarDoor* pDoor = GetDoorFromId( nDoorId ); if(pDoor) { if( pDoor->GetDoorRatio() < 0.7f ) { return false; } } } return true; } #if !__FINAL bool CVehicle::IsLocalPersonalVehicle() { atHashWithStringBank decKey("Player_Vehicle",0x299D7A40); if (fwDecoratorExtension::ExistsOn(*this, decKey)) { CPed* pPed = FindPlayerPed(); if (pPed && pPed->GetPlayerInfo()) { int decValue = 0; fwDecoratorExtension::Get(*this, decKey, decValue); atHashWithStringBank playerValue(pPed->GetPlayerInfo()->m_GamerInfo.GetName()); return playerValue == (u32) decValue; } } return false; } bool CVehicle::IsRemotePersonalVehicle() { atHashWithStringBank decKey("Player_Vehicle",0x299D7A40); if (fwDecoratorExtension::ExistsOn(*this, decKey)) { CPed* pPed = FindPlayerPed(); if (pPed && pPed->GetPlayerInfo()) { int decValue = 0; fwDecoratorExtension::Get(*this, decKey, decValue); atHashWithStringBank playerValue(pPed->GetPlayerInfo()->m_GamerInfo.GetName()); return playerValue != (u32) decValue; } } return false; } #endif const CBoardingPoints * CVehicle::GetBoardingPoints() const { return pHandling->m_pBoardingPoints; } bool CVehicle::GetClosestBoardingPoint(const Vector3 & vWorldSpaceInputPos, CBoardingPoint & boardingPointLocalSpace) const { const CBoardingPoints * pBoardingPoints = GetBoardingPoints(); if(!pBoardingPoints || pBoardingPoints->GetNumBoardingPoints()==0) return false; Vector3 vLocalInputPos = VEC3V_TO_VECTOR3(GetTransform().UnTransform(VECTOR3_TO_VEC3V(vWorldSpaceInputPos))); int iClosest = -1; float fClosestDistSqr = FLT_MAX; for(int b=0; bGetNumBoardingPoints(); b++) { const CBoardingPoint & bp = pBoardingPoints->GetBoardingPoint(b); const Vector3 vDiff = vLocalInputPos - Vector3(bp.m_fPosition[0], bp.m_fPosition[1], bp.m_fPosition[2]); const float fDistSqr = vDiff.Mag2(); if(fDistSqr < fClosestDistSqr) { fClosestDistSqr = fDistSqr; iClosest = b; } } if(iClosest==-1) return false; boardingPointLocalSpace = pBoardingPoints->GetBoardingPoint(iClosest); return true; } void CVehicle::CalculateNonArticulatedMaximumExtents(bool bDontSweepWheels) { if(GetVehicleFragInst() && GetVehicleFragInst()->GetCacheEntry()) { phBoundComposite* compositeBound = GetVehicleFragInst()->GetCacheEntry()->GetBound(); phCollider* collider = GetCollider(); for(int i=0; i< GetNumWheels(); i++) { CWheel* wheel = GetWheel(i); Mat34V wheelExtentsTop, wheelExtentsBottom; int wheelComponent = wheel->GetFragChild(); if(wheelComponent >= 0) { // Calculate the maximum extents of the wheels bounding box and set the matrices to that if(bDontSweepWheels) { Matrix34 matWheelTransform(Matrix34::IdentityType); int iWheelBoneIndex = GetBoneIndex(wheel->GetHierarchyId()); // First find default transform of wheel to parent const crSkeletonData& skeletonData = GetSkeletonData(); if(iWheelBoneIndex > -1) { const crBoneData* pBoneData = skeletonData.GetBoneData(iWheelBoneIndex); // Use default translation because the wheels local matrix gets messed around with for rendering Quaternion defaultRotation = RCC_QUATERNION(pBoneData->GetDefaultRotation()); matWheelTransform.FromQuaternion(defaultRotation); matWheelTransform.d = RCC_VECTOR3(pBoneData->GetDefaultTranslation()); // Now figure out where parent is // This requires the skeleton's local matrices to be up to date! pBoneData = pBoneData->GetParent(); while(pBoneData) { const Matrix34& matParent = GetLocalMtx(pBoneData->GetIndex()); matWheelTransform.Dot(matParent); pBoneData = pBoneData->GetParent(); } compositeBound->SetCurrentMatrix(wheelComponent, MATRIX34_TO_MAT34V(matWheelTransform)); compositeBound->SetLastMatrix(wheelComponent, MATRIX34_TO_MAT34V(matWheelTransform)); } } else { wheel->CalculateMaximumExtents(wheelExtentsTop,wheelExtentsBottom,collider); compositeBound->SetCurrentMatrix(wheelComponent, wheelExtentsBottom); compositeBound->SetLastMatrix(wheelComponent, wheelExtentsTop); } } } compositeBound->CalculateCompositeExtents(true); if(GetVehicleFragInst()->IsInLevel()) { if(ms_bUseAutomobileBVHupdate) { CPhysics::GetLevel()->UpdateCompositeBvh(GetVehicleFragInst()->GetLevelIndex()); } else { CPhysics::GetLevel()->RebuildCompositeBvh(GetVehicleFragInst()->GetLevelIndex()); } } else { compositeBound->UpdateBvh(ms_bUseAutomobileBVHupdate); } } } bool CVehicle::IsColliderArticulated() const { if(GetVehicleFragInst() && GetVehicleFragInst()->GetCacheEntry() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider) { return GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider->IsArticulated(); } return false; } void CVehicle::RequestDials(const bool overriden) { if(!GetVehicleModelInfo()->HasDials()) return; static float sfFuelPressureDialDefault = 50.0f; static float sfFuelLevelMin = 0.2f; static float revs = 0.f; float revRatio = 0.f; if(m_nVehicleFlags.bEngineOn) { audVehicleEngine* vehicleEngine = GetVehicleAudioEntity()->GetVehicleEngine(); if(vehicleEngine) { revRatio = vehicleEngine->CalculateDialRPMRatio(); if(GetVehicleAudioEntity()->IsReversing() && revRatio > 0.1f) { revRatio = 0.1f + ((revRatio - 0.1f) * 0.25f); } } else { revRatio = m_Transmission.GetRevRatio(); } } else { revRatio = 0.0f; } if( GetModelIndex() == MI_CAR_RAIDEN || GetModelIndex() == MI_CAR_NEON ) { static dev_float sfNeonCarZeroKWh = 0.715f; static dev_float sfRaidenCarZeroKWh = 0.02f; static dev_float sfNeonCarMaxKWh = 0.6f; static dev_float sfRaidenCarMaxKWh = 0.52f; static dev_float sfNeonCarMinKWh = -0.7f; static dev_float sfRaidenCarMinKWh = -0.54f; bool useNeon = GetModelIndex() == MI_CAR_NEON; revRatio = useNeon ? sfNeonCarZeroKWh : sfRaidenCarZeroKWh; if( m_nVehicleFlags.bEngineOn ) { revRatio += 0.01f + ( m_nVehicleFlags.bLightsOn ? 0.01f : 0.0f ) + ( m_nVehicleFlags.bHeadlightsFullBeamOn ? 0.01f : 0.0f ); float throttle = Abs( GetThrottle() ); if( throttle > 0.0f ) { revRatio += throttle * ( useNeon ? sfNeonCarMaxKWh : sfRaidenCarMaxKWh ); } else { // wheel speed is too noisy so we'll use forward velocity even though that isn't really correct float forwardSpeed = Abs( GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ) ); if( forwardSpeed > 0.0f && !IsInAir() ) { static float sfMaxVelocityForFullRechargeInv = 1.0f / 40.0f; float maxRecharge = Min( 1.0f, forwardSpeed * sfMaxVelocityForFullRechargeInv ); float recharge = 0.2f * maxRecharge + ( GetBrake() * maxRecharge * 0.8f ); revRatio += ( recharge * ( useNeon ? sfNeonCarMinKWh : sfRaidenCarMinKWh ) ); } } } // we need to smooth the rev change a bit more for electric vehicles revs = ( (revs * 0.8f ) + ( revRatio * 0.205f ) ); } else { static float fakenumber = 0.575f; revs = (revs + revRatio) * fakenumber;//0.525f; } float speed = 0.f; float fuel = 1.f; float oil = 1.f; float temp = m_EngineTemperature / MAX_ENGINE_TEMPERATURE; bool engineOnFire = false; CVehicleDamage* dmg = GetVehicleDamage(); if (dmg && pHandling) { fuel = dmg->GetPetrolTankLevel() / pHandling->m_fPetrolTankVolume; oil = dmg->GetOilLevel() / pHandling->m_fOilVolume; engineOnFire = dmg->GetEngineOnFire(); } bool abs = false; bool bAmphiVehPropellerSubmerged = ( InheritsFromAmphibiousAutomobile() && ( static_cast(this)->IsPropellerSubmerged() ) ); bool bAmphiQuadWheelsTuckedIn = ( InheritsFromAmphibiousQuadBike() && !( static_cast(this)->IsWheelsFullyOut() ) ); bool bSpecialFlightModeActive = ( GetSpecialFlightModeRatio() >= 1.0f && ( MI_CAR_DELUXO.IsValid() && GetModelIndex() == MI_CAR_DELUXO ) ) || ( pHandling->GetSpecialFlightHandlingData() && pHandling->GetSpecialFlightHandlingData()->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ); if (GetVehicleModelInfo()->GetIsBoat()) { if (GetIsInWater()) { speed = GetVelocity().Mag(); } } else if( bAmphiVehPropellerSubmerged || bAmphiQuadWheelsTuckedIn || bSpecialFlightModeActive ) { speed = GetVelocity().Mag(); } else { for (s32 i = 0; i < GetNumWheels(); ++i) { CWheel* wheel = GetWheel(i); if (wheel) { if (wheel->GetDynamicFlags().IsFlagSet(WF_ABS_ACTIVE)) { abs = true; } if( !wheel->GetDynamicFlags().IsFlagSet(WF_BROKEN_OFF) ) { speed = rage::Max(speed, rage::Abs(wheel->GetGroundSpeed())); } } } if (abs && speed < 0.001f) { speed = GetVelocity().Mag(); } } float deltaSpeedForDash = (speed - m_fPreviousSpeedForDash); static dev_float sfDeltaSpeedIncrementMax = 15.0f; float deltaIncrement = sfDeltaSpeedIncrementMax * fwTimer::GetTimeStep(); float speedForDash = m_fPreviousSpeedForDash + Clamp(deltaSpeedForDash, -deltaIncrement, deltaIncrement); bool lightsOn = UpdateVehicleLightOverrideStatus() || m_nVehicleFlags.bEngineOn; bool bTurningLeft, bTurningRight = false; ProcessIndicators(bTurningLeft, bTurningRight, false); sVehicleDashboardData params; params.revs = revs; params.speed = speedForDash * 2.24f; // m/s -> mph params.fuel = fuel; float fTempForGauge = m_nVehicleFlags.bEngineOn ? temp : 0.0f; // hold the gauge in the middle when its not damaged and not too hot. if(fTempForGauge > 0.4f && fTempForGauge < 0.9f && (m_Transmission.GetEngineHealth() > ENGINE_DAMAGE_RADBURST || fTempForGauge < 0.5f)) { fTempForGauge = 0.5f; } static dev_float sfDeltaTempIncrementMax = 0.1f; float deltaTempIncrement = sfDeltaTempIncrementMax * fwTimer::GetTimeStep(); float fDeltaTempPressure = (fTempForGauge - CVehicleModelInfo::ms_cachedDashboardData.engineTemp); params.engineTemp = CVehicleModelInfo::ms_cachedDashboardData.engineTemp + Clamp(fDeltaTempPressure, -deltaTempIncrement, deltaTempIncrement); // Cube the temperature so the dial sits about half way. params.indicatorLeft = bTurningLeft; params.indicatorRight = bTurningRight; params.handBrakeLight = GetHandBrake(); params.engineLight = engineOnFire; params.absLight = m_nVehicleFlags.bEngineStarting; params.fuelLight = (fuel < sfFuelLevelMin) ? true : false; params.oilLight = (oil < 0.5f || m_nVehicleFlags.bEngineStarting) ? true : false; params.headLightsLight = m_nVehicleFlags.bLightsOn && lightsOn; params.fullBeamLight = m_nVehicleFlags.bHeadlightsFullBeamOn && lightsOn; params.batteryLight = m_nVehicleFlags.bEngineStarting; params.currentGear = (float)m_Transmission.GetGear(); float vacuum = 1.0f; float boost = 0.0f; // for superchargers just have a linear increase in vacume based on rpm if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_SUPERCHARGER ) ) { // use this to add a bit of noise to the values float randomThrottle = ( Max( 0.3f, GetThrottle() ) * fwRandom::GetRandomNumberInRange( 0.0f, 0.015f ) ); static dev_float minRPMRatioForPositivePressure = 0.35f; static dev_float minRPMRatioForPositivePressureInv = 1.0f / ( minRPMRatioForPositivePressure - 0.2f ); boost = Clamp( ( ( revs - minRPMRatioForPositivePressure ) * 0.5f - randomThrottle ), 0.0f, 0.5f ); vacuum = m_Transmission.GetManifoldPressure() > 0.0f || boost > 0.0f ? 1.0f : Clamp( Max( 0.0f, ( Max( 0.0f, ( revs - 0.2f ) ) * minRPMRatioForPositivePressureInv ) + ( randomThrottle * 2.0f ) ), 0.0f, 1.0f ); } float turboVacuum = ( m_Transmission.GetManifoldPressure() < 0.0f ? 1.0f + m_Transmission.GetManifoldPressure() : vacuum ); turboVacuum = Min( 1.0f, turboVacuum ); params.vacuum = Max( vacuum, turboVacuum ); params.boost = Max( boost, m_Transmission.GetManifoldPressure() > 0.0f ? m_Transmission.GetManifoldPressure() : 0.0f ); params.oilTemperature = (temp * temp * temp); // Cold oil has slightly higher pressure. Oil pressure increases with revs. float fOilPressure = rage::Clamp( revs + ((1.0f - params.oilTemperature)), 0.0f, 0.6f); fOilPressure = (oil < 0.5f || m_nVehicleFlags.bEngineStarting || m_Transmission.GetEngineOnFire() || !m_nVehicleFlags.bEngineOn) ? 0.0f : fOilPressure; static dev_float sfDeltaOilIncrementMax = 0.1f; float deltaOilIncrement = sfDeltaOilIncrementMax * fwTimer::GetTimeStep(); float fDeltaOilPressure = (fOilPressure - CVehicleModelInfo::ms_cachedDashboardData.oilPressure); params.oilPressure = CVehicleModelInfo::ms_cachedDashboardData.oilPressure + Clamp(fDeltaOilPressure, -deltaOilIncrement, deltaOilIncrement); if(InheritsFromPlane() || InheritsFromHeli() || InheritsFromSubmarine()) { if(InheritsFromPlane()) { CPlane *pPlane = static_cast(this); params.aircraftGearUp = pPlane->GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_UP; params.aircraftGearDown = pPlane->GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_DOWN; } else if(InheritsFromHeli()) { CHeli *pHeli = static_cast(this); params.aircraftGearUp = pHeli->GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_UP; params.aircraftGearDown = pHeli->GetLandingGear().GetPublicState() == CLandingGear::STATE_LOCKED_DOWN; } else if(InheritsFromSubmarine()) { CSubmarine *pSub = static_cast(this); if(pSub->GetSubHandling()->IsUnderDesignDepth(pSub)) { bool bIndicatorInterval = (((fwTimer::GetTimeInMilliseconds() + GetRandomSeed()) & 1023) & 512) != 0; params.pressureAlarm = bIndicatorInterval; } else { params.pressureAlarm = false; } } params.aircraftFuel = fuel; params.aircraftOil = oil; params.aircraftPitch = GetTransform().GetPitch() / (2.0f * PI); Matrix34 mat = MAT34V_TO_MATRIX34( GetMatrix() ); Vector3 eulers; mat.ToEulersYXZ(eulers); // get the roll params.aircraftRoll = -eulers.y / (2.0f * PI); if(InheritsFromSubmarine()) { params.aircraftAltitudeLarge = METERS_TO_FEET(-GetTransform().GetPosition().GetZf())/1000.0f; params.aircraftAltitudeSmall = METERS_TO_FEET(-GetTransform().GetPosition().GetZf())/10000.0f; params.aircraftOilPressure = ((1027.0f * GRAVITY * GetTransform().GetPosition().GetZf()) * 0.000145037738f) / 12.0f; // (density of sea water * gravity * depth converted into PSI . This is air pressure on a sub params.aircraftTemp = g_weather.GetTemperature(GetTransform().GetPosition())/0.3f; //scale to 30.0f being top } else { params.aircraftAltitudeLarge = METERS_TO_FEET(GetTransform().GetPosition().GetZf())/1000.0f; params.aircraftAltitudeSmall = METERS_TO_FEET(GetTransform().GetPosition().GetZf())/10000.0f; params.aircraftOilPressure = (oil < 0.5f || m_nVehicleFlags.bEngineStarting) ? 0.0f : (1.0f - (temp * temp * temp))*100.0f; // Oil pressure, need to find whats appropriate for a fighter jet, just hooking into temp so it moves for the time being + hook it up to revs so it looks vaguely accurate params.aircraftTemp = params.engineTemp * 100.0f; } params.aircraftAirSpeed = (GetVelocity().XYMag() * 1.9438f)/160.0f; // Should be in knots. Normalized to fit dial //Need to remove rotation float airspeed = DotProduct(GetVelocity(), ZAXIS); params.aircraftVerticalSpeed = (((airspeed * 196.850394f)/1000.0f)/4.0f); // should be in feet per minute. Normalized to fit dials that read a max of 2 in either direction. params.aircraftCompass = -eulers.z / (2.0f * PI);// GetTransform().GetHeading() / (2.0f * PI); //params.aircraftAir = (101325.0f * (1.0f - 2.255770f * pow(10.0f,-5.0f) * GetTransform().GetPosition().GetZf()) * 5.25588)/1000.0f; // air pressure, not needed anymore params.aircraftBattery = m_nVehicleFlags.bEngineOn ? 0.5f : 1.0f; // Hopefully this should look ok params.aircraftFuelPressure = (m_nVehicleFlags.bEngineStarting || (fuel < sfFuelLevelMin)) ? sfFuelPressureDialDefault - (sfFuelPressureDialDefault * (1.0f - pow(fuel / sfFuelLevelMin, 2.0f))) : sfFuelPressureDialDefault;// fudge it so when fuel drops below min the fuel pressure starts to drop params.aircraftVacuum = m_Transmission.GetManifoldPressure(); } GetVehicleModelInfo()->RequestDials(params); m_fPreviousSpeedForDash = speedForDash; m_dialsRequestFrame = fwTimer::GetFrameCount(); ms_bDialsScriptOveridden = overriden; } // // FUNCTION: WeAreBlockingSomeone // FUNCTION: Should be called when a vehicle is blocking another vehicle. // The vehicle can be marked to be removed by ai peds. // void CVehicle::WeAreBlockingSomeone() { // No point doing the tests if the result has already been set. if (m_nVehicleFlags.bShouldBeRemovedByAmbientPed == true) { return; } // Only test once in a while. (The garage check can be expensive) if ( ( (fwTimer::GetSystemFrameCount() + GetRandomSeed()) & 31) != 0) { return; } // If this veh has had a driver (ie not a parked veh) but hasn't had one for a while (has been abandoned) we can try to have it stolen. if ( (m_LastTimeWeHadADriver==0) || (m_LastTimeWeHadADriver + 14000 > NetworkInterface::GetSyncedTimeInMilliseconds()) ) { return; } // Only cars are removed. if (GetVehicleType() != VEHICLE_TYPE_CAR) { return; } // Leave mission cars alone if ( PopTypeIsMission() ) { return; } if (CTheScripts::GetPlayerIsOnAMission() && CVehiclePopulation::IsVehicleInteresting(this)) { return; } // Only attempt to remove if veh is right side up if (IsLessThanAll(GetVehicleUpDirection().GetZ(), ScalarV(0.7f))) { return; } // Vehicles that have blown up cannot be driven away if (m_nPhysicalFlags.bRenderScorched) { return; } // Vehicles with people in them should not be removed. if ( HasPedsInIt() ) { return; } if (CGarages::IsVehicleWithinHideOutGarageThatCanSaveIt(this) ) { return; } m_nVehicleFlags.bShouldBeRemovedByAmbientPed = true; } // // FUNCTION: ShouldBeRemovedByAmbientPed // FUNCTION: If this returns true an ambient ped (ie thief) should try to take it away. The veh may be blocking traffic. // bool CVehicle::ShouldBeRemovedByAmbientPed() const { if( m_nVehicleFlags.bShouldBeRemovedByAmbientPed ) { if( GetVehicleDamage()->GetEngineHealth() > 0.0f && GetVehicleDamage()->GetPetrolTankHealth() > 0.0f && !NetworkInterface::IsInvalidVehicleForAmbientPedDriving(this)) { return true; } } return false; } // // TEMP VARIABLES FOR AUTOGYRO HANDLING float AUTOGYRO_ROTORSPIN_MULT = 0.006f; float AUTOGYRO_ROTORSPIN_MULTLIMIT = 0.25f; float AUTOGYRO_ROTORSPIN_DAMP = 0.997f; float AUTOGYRO_ROTORLIFT_MULT = 4.5f; float AUTOGYRO_ROTORLIFT_FALLOFF = 0.75f; float AUTOGYRO_ROTORTILT_ANGLE = 0.25f; // #define HELI_AUTO_STABILISE_RATIO (0.25f) // // The following two functions work out whether the thrust of a plane/heli should // be cut because it is too high. dev_float CVehicle::FLIGHT_CEILING_PLANE = 2450.0f; dev_float CVehicle::FLIGHT_MICROLIGHT_CEILING_PLANE = 1450.0f; float CVehicle::ms_fFlightCeilingScripted = 2450.0f; //#define FLIGHT_CEILING_RC //#define FLIGHT_END_CEILING //#define FLIGHT_OFFSET_STADIUM float CVehicle::HeightAboveCeiling(float Height) const { Vector2 minMaxHeight(0.0f, 0.0f); if(CVehiclePopulation::GetFlightHeightsFromFlightZones(VEC3V_TO_VECTOR3(GetTransform().GetPosition()), minMaxHeight)) { return (Height - minMaxHeight.y); } else { // if(nType==FLIGHTMODEL_RCPLANE || nType==FLIGHTMODEL_RCHELI) // { // if (Height < FLIGHT_CEILING_RC) return -1.0f; // if (Height < FLIGHT_END_CEILING) return Height - FLIGHT_CEILING_RC; // if (Height < FLIGHT_OFFSET_STADIUM + FLIGHT_CEILING_RC) return -1.0f; // return Height - FLIGHT_OFFSET_STADIUM + FLIGHT_CEILING_RC; // } // else float fFlightCeilingHeight = m_nVehicleFlags.bUseScriptedCeilingHeight ? ms_fFlightCeilingScripted : FLIGHT_CEILING_PLANE; if( GetModelIndex() == MI_PLANE_MICROLIGHT ) { fFlightCeilingHeight = Min( fFlightCeilingHeight, FLIGHT_MICROLIGHT_CEILING_PLANE ); } if (Height < fFlightCeilingHeight) { return -1.0f; } return (Height - fFlightCeilingHeight); } } // pVecRotateAboutOffset - Lets you rotate something that has the deformation shader applied, need to undo the deformation, rotate and then reapply. // pVecShiftPos - Lets you shift something that's position has been deformed, just adds the position on afterwards. void CVehicle::SetComponentRotation(eHierarchyId compId, eRotationAxis nAxis, float fAngle, bool bSetRotate, Vector3* pVecRotateAboutOffset, Vector3* pVecShiftPos) { int nBoneIndex = GetBoneIndex((eHierarchyId)compId); if(nBoneIndex < 0) return; if(GetSkeleton()==NULL) return; SetBoneRotation(nBoneIndex,nAxis, fAngle, bSetRotate, pVecRotateAboutOffset, pVecShiftPos); } void CVehicle::SetComponentRotation(eHierarchyId compId, const Quaternion& q, bool bSetRotate, Vector3* pVecRotateAboutOffset, Vector3* pVecShiftPos) { int nBoneIndex = GetBoneIndex((eHierarchyId)compId); if(nBoneIndex < 0) return; if(GetSkeleton()==NULL) return; SetBoneRotation(nBoneIndex, q, bSetRotate, pVecRotateAboutOffset, pVecShiftPos); } void CVehicle::SetBoneRotation(int nBoneIndex, eRotationAxis nAxis, float fAngle, bool bSetRotate, Vector3* pVecRotateAboutOffset, Vector3* pVecShiftPos) { Assert(nBoneIndex > -1); Assert(GetSkeleton()); Matrix34 &boneMat = GetLocalMtxNonConst(nBoneIndex); const crBoneData* pBoneData = GetSkeletonData().GetBoneData(nBoneIndex); if(bSetRotate) { boneMat.FromQuaternion(RCC_QUATERNION(pBoneData->GetDefaultRotation())); } switch(nAxis) { case ROT_AXIS_Z: boneMat.RotateZ(fAngle); break; case ROT_AXIS_Y: boneMat.RotateY(fAngle); break; case ROT_AXIS_X: boneMat.RotateX(fAngle); break; case ROT_AXIS_LOCAL_X: boneMat.RotateLocalX(fAngle); break; case ROT_AXIS_LOCAL_Y: boneMat.RotateLocalY(fAngle); break; case ROT_AXIS_LOCAL_Z: boneMat.RotateLocalZ(fAngle); break; } if(pVecRotateAboutOffset) { Vector3 vecTransformedOffset; Matrix34 defaultMat; defaultMat.FromQuaternion(RCC_QUATERNION(pBoneData->GetDefaultRotation())); // transform to bone identity space defaultMat.UnTransform3x3(*pVecRotateAboutOffset, vecTransformedOffset); boneMat.Transform3x3(vecTransformedOffset, vecTransformedOffset); boneMat.d.Set(RCC_VECTOR3(pBoneData->GetDefaultTranslation())); boneMat.d.Add(*pVecRotateAboutOffset); boneMat.d.Subtract(vecTransformedOffset); if(pVecShiftPos) boneMat.d.Add(*pVecShiftPos); } else if(pVecShiftPos) { boneMat.d.Set(RCC_VECTOR3(pBoneData->GetDefaultTranslation())); boneMat.d.Add(*pVecShiftPos); } } void CVehicle::SetBoneRotation(int nBoneIndex, const Quaternion& q, bool bSetRotate, Vector3* pVecRotateAboutOffset, Vector3* pVecShiftPos) { Assert(nBoneIndex > -1); Assert(GetSkeleton()); Matrix34 &boneMat = GetLocalMtxNonConst(nBoneIndex); const crBoneData* pBoneData = GetSkeletonData().GetBoneData(nBoneIndex); if(bSetRotate) { boneMat.FromQuaternion(RCC_QUATERNION(pBoneData->GetDefaultRotation())); } Matrix34 m(Matrix34::IdentityType); m.FromQuaternion(q); boneMat.DotFromLeft(m); if(pVecRotateAboutOffset) { Vector3 vecTransformedOffset; boneMat.Transform3x3(*pVecRotateAboutOffset, vecTransformedOffset); boneMat.d.Set(RCC_VECTOR3(pBoneData->GetDefaultTranslation())); boneMat.d.Add(*pVecRotateAboutOffset); boneMat.d.Subtract(vecTransformedOffset); if(pVecShiftPos) boneMat.d.Add(*pVecShiftPos); } else if(pVecShiftPos) { boneMat.d.Set(RCC_VECTOR3(pBoneData->GetDefaultTranslation())); boneMat.d.Add(*pVecShiftPos); } } fwMove *CVehicle::CreateMoveObject() { return rage_new CMoveVehiclePooledObject(*this); } const fwMvNetworkDefId &CVehicle::GetAnimNetworkMoveInfo() const { return CClipNetworkMoveInfo::ms_NetworkObject; } #endif // !__SPU void SetTransmissionRotation(crSkeleton* pSkeleton, eHierarchyId compId, eHierarchyId wheelIdL, eHierarchyId wheelIdR, CVehicleStructure* pStructure) { if(!pSkeleton) return; if(pStructure->m_nBoneIndices[compId] < 0 || pStructure->m_nBoneIndices[wheelIdL] < 0) return; Vector3 wheelPosnL = RCC_VECTOR3(pSkeleton->GetSkeletonData().GetBoneData(pStructure->m_nBoneIndices[wheelIdL])->GetDefaultTranslation()); Matrix34 &wheelMatL = RC_MATRIX34(pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[wheelIdL])); Matrix34 &boneMat = RC_MATRIX34(pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[compId])); float fAveWheelZ = wheelMatL.d.z; float fDiffWheelZ = 0.0f; if(pStructure->m_nBoneIndices[wheelIdR] > -1) { Matrix34 &wheelMatR = RC_MATRIX34(pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[wheelIdR])); fAveWheelZ = (fAveWheelZ + wheelMatR.d.z) / 2.0f; fDiffWheelZ = wheelMatL.d.z - wheelMatR.d.z; } float fAngX = -rage::Atan2f(fAveWheelZ - wheelPosnL.z, boneMat.d.y - wheelPosnL.y); float fAngY = rage::Atan2f(fDiffWheelZ, 2.0f*rage::Abs(wheelPosnL.x)); if(wheelPosnL.y > 0.0f) fAngX += PI; boneMat.Identity3x3(); boneMat.RotateX(fAngX); boneMat.RotateY(fAngY); #if __DEV static float sfForceTestAngle = -10.0f; if(sfForceTestAngle > -TWO_PI) { boneMat.Identity3x3(); boneMat.RotateX(sfForceTestAngle); } #endif } void SetTransmissionRotationForTrike(crSkeleton* pSkeleton, eHierarchyId compId, eHierarchyId wheelIdL, CVehicleStructure* pStructure) { if (!pSkeleton) return; if (pStructure->m_nBoneIndices[compId] < 0 || pStructure->m_nBoneIndices[wheelIdL] < 0) return; Vector3 wheelPosnL = RCC_VECTOR3(pSkeleton->GetSkeletonData().GetBoneData(pStructure->m_nBoneIndices[wheelIdL])->GetDefaultTranslation()); Matrix34 &wheelMatL = RC_MATRIX34(pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[wheelIdL])); Matrix34 &boneMat = RC_MATRIX34(pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[compId])); float fAveWheelZ = wheelMatL.d.z; float fAngX = -rage::Atan2f(fAveWheelZ - wheelPosnL.z, boneMat.d.y - wheelPosnL.y); if (wheelPosnL.y > 0.0f) fAngX += PI; boneMat.Identity3x3(); boneMat.RotateX(fAngX); #if __DEV static float sfForceTestAngle = -10.0f; if (sfForceTestAngle > -TWO_PI) { boneMat.Identity3x3(); boneMat.RotateX(sfForceTestAngle); } #endif } void SetSuspensionTransformation(crSkeleton* pSkeleton, eHierarchyId compId, eHierarchyId wheelId, SuspensionType nType, CVehicleStructure* pStructure, CHandlingData *pHandlingData, const Vec3V *pDeformation) { if(pSkeleton==NULL) return; if(pStructure->m_nBoneIndices[compId] < 0 || pStructure->m_nBoneIndices[wheelId] < 0) return; Vec3V wheelPosn = pSkeleton->GetSkeletonData().GetBoneData(pStructure->m_nBoneIndices[wheelId])->GetDefaultTranslation(); Mat34V &wheelMat = pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[wheelId]); Mat34V &boneMat = pSkeleton->GetLocalMtx(pStructure->m_nBoneIndices[compId]); Mat34V &objMat = pSkeleton->GetObjectMtx(pStructure->m_nBoneIndices[compId]); ScalarV sSuspensionHeight = wheelMat.GetCol3().GetZ() - wheelPosn.GetZ(); ScalarV sSuspensionWidth = wheelPosn.GetX() - boneMat.GetCol3().GetX(); ScalarV sSuspensionLength = wheelPosn.GetY() - boneMat.GetCol3().GetY(); ScalarV sSuspensionLowerLimit = ScalarVFromF32(pHandlingData->m_fSuspensionLowerLimit); ScalarV sSuspensionUpperLimit = ScalarVFromF32(pHandlingData->m_fSuspensionUpperLimit); ScalarV sSuspensionBiasFront = ScalarVFromF32(pHandlingData->m_fSuspensionBiasFront); ScalarV sSuspensionBiasRear = ScalarVFromF32(pHandlingData->m_fSuspensionBiasRear); bool bUpdateDeformation = false; ////////////////////// // rotating type switch(nType) { case Sus_McPherson: { ScalarV sTriLengthInv = ScalarV(V_ONE) / (rage::Sqrt(rage::square(sSuspensionHeight) + rage::square(sSuspensionWidth))); ScalarV sSin = sSuspensionHeight * sTriLengthInv; ScalarV sCos = rage::Abs(sSuspensionWidth) * sTriLengthInv; if(IsGreaterThan(sSuspensionWidth, ScalarV(V_ZERO)).Getb()) sSin *= ScalarV(V_NEGONE); Vec3V vecA = Vec3V(sCos, ScalarV(V_ZERO), -sSin); Vec3V vecC = Vec3V(sSin, ScalarV(V_ZERO), sCos); boneMat.SetIdentity3x3(); boneMat.SetCol0(vecA); boneMat.SetCol2(vecC); objMat = boneMat; const crBoneData *pBonedata = pSkeleton->GetSkeletonData().GetBoneData(pStructure->m_nBoneIndices[compId]); const crBoneData* pParentBoneData = pBonedata->GetParent(); if(pParentBoneData->GetIndex() != -1) { Transform(objMat, pSkeleton->GetObjectMtx(pParentBoneData->GetIndex()), boneMat); } bUpdateDeformation = true; break; } ////////////////////// // squashing type case Sus_Solid: { bool bFrontSuspension = ( compId == VEH_SUSPENSION_LF || compId == VEH_SUSPENSION_RF || compId == VEH_SPRING_LF || compId == VEH_SPRING_RF ); if(bFrontSuspension) { sSuspensionLowerLimit += sSuspensionBiasFront; sSuspensionUpperLimit += sSuspensionBiasFront; } else { sSuspensionLowerLimit += sSuspensionBiasRear; sSuspensionUpperLimit += sSuspensionBiasRear; } sSuspensionWidth = wheelPosn.GetZ() - boneMat.GetCol3().GetZ(); ScalarV sSusScale = ScalarV(V_ONE) - (sSuspensionHeight / sSuspensionWidth); sSusScale = rage::Clamp(sSusScale, sSuspensionLowerLimit, sSuspensionUpperLimit); const crBoneData *pBonedata = pSkeleton->GetSkeletonData().GetBoneData(pStructure->m_nBoneIndices[compId]); Mat34VFromQuatV(boneMat, pBonedata->GetDefaultRotation(), pBonedata->GetDefaultTranslation()); Scale3x3(boneMat, boneMat, Vec3V(Vec2V(V_ONE), ScalarV(V_ONE)/sSusScale)); Scale3x3(objMat, objMat, Vec3V(Vec2V(V_ONE), ScalarV(V_ONE)/sSusScale)); bUpdateDeformation = true; break; } case Sus_Torsion: { const ScalarV sZ = sSuspensionHeight; ScalarV sY = sSuspensionLength; const ScalarV sBarLength = rage::Sqrt((sY*sY) + (sZ*sZ)); // Multiplying by sign here should mean it works for suspension pivots infront or behind the wheels const ScalarV sySign = rage::SelectFT(IsGreaterThan(sY, ScalarV(V_ZERO)), ScalarV(V_NEGONE), ScalarV(V_ONE)); const ScalarV sCos = sY / sBarLength * sySign; const ScalarV sSin = sZ / sBarLength * sySign; boneMat.SetIdentity3x3(); Vec3V vecB = Vec3V(ScalarV(V_ZERO),sCos,sSin); boneMat.SetCol1(vecB); // Stretch the forwards component to correct length sY = rage::Abs(sY); if(IsGreaterThan(sY, ScalarV(V_ZERO)).Getb()) { boneMat.GetCol1Ref() *= sBarLength / sY; } // Just set c vector to ZAXIS so that double struts work // This will skew the geometry but keep any bolts in place boneMat.SetCol2(Vec3V(V_Z_AXIS_WZERO)); rage::Transform3x3(objMat, objMat, boneMat); bUpdateDeformation = true; break; } case Sus_NoTilt: { break; } default: { Assertf(0,"Unhanlded suspension type"); } break; } // Apply deformation if(pDeformation && IsZeroAll(*pDeformation) == false && bUpdateDeformation) { Vec3V vDeformation = -(*pDeformation); vDeformation = Transform3x3(boneMat, vDeformation); vDeformation += (*pDeformation); const crBoneData *pBonedata = pSkeleton->GetSkeletonData().GetBoneData(pStructure->m_nBoneIndices[compId]); boneMat.SetCol3(pBonedata->GetDefaultTranslation() + vDeformation); Vec3V vPosLocal = boneMat.GetCol3(); const crBoneData* pParentBoneData = pBonedata->GetParent(); if(pParentBoneData->GetIndex() != -1) { vPosLocal = Transform(pSkeleton->GetObjectMtx(pParentBoneData->GetIndex()), vPosLocal); } objMat.SetCol3(vPosLocal); } } // This is can't be part of CVehicle because it can be called on SPU SuspensionType GetSuspensionTypeFromFlags(int iModelFlags, bool bRearSuspension) { if(bRearSuspension) { if(iModelFlags & MF_AXLE_R_MCPHERSON) { return Sus_McPherson; } if(iModelFlags & MF_AXLE_R_TORSION) { return Sus_Torsion; } if(iModelFlags & MF_AXLE_R_SOLID) { return Sus_Solid; } return Sus_NoTilt; } else { if(iModelFlags & MF_AXLE_F_MCPHERSON) { return Sus_McPherson; } if(iModelFlags & MF_AXLE_F_TORSION) { return Sus_Torsion; } if(iModelFlags & MF_AXLE_F_SOLID) { return Sus_Solid; } return Sus_NoTilt; } } #if !__SPU u32 CVehicle::GetMainSceneUpdateFlag() const { return CGameWorld::SU_UPDATE_VEHICLE; } u32 CVehicle::GetStartAnimSceneUpdateFlag() const { // To ensure that ProcessControl() gets called first for the driver and then for the // vehicle, we schedule the animation updates for vehicles after we've scheduled the // animation updates for peds (incl. the drivers). This isn't enough by itself, but it // allows us to declare the dependencies properly so we pop them from the queue for // calling ProcessControl() in the proper order. return CGameWorld::SU_START_ANIM_UPDATE_PRE_PHYSICS_PASS2; } bool CVehicle::ShouldGenerateMinorShockingEvent() const { // This was adapted from CShockingEventsManager::EventShouldBeAdded(). CPed* pDriver = GetDriver(); if(!pDriver) { return false; } if(!pDriver->IsAPlayerPed() ) { // Note: in older code, the distance threshold *squared* was actually 15, but a // threshold <4 m on a vehicle-to-player distance seems so small that I assumed it was a bug. /FF static const float s_DistThresholdSquared = square(15.0f); const Vec3V sourcePosV = GetTransform().GetPosition(); const Vec3V playerPosV = VECTOR3_TO_VEC3V(CGameWorld::FindLocalPlayerCentreOfWorld()); const ScalarV distSqV = DistSquared(sourcePosV, playerPosV); const ScalarV distThresholdSqV = LoadScalar32IntoScalarV(s_DistThresholdSquared); if(IsGreaterThanAll(distSqV, distThresholdSqV)) { return false; } } return true; } CVehicle* CVehicle::GetVehicleDrivingOn() const { //Ensure we are driving on a vehicle. if(!m_nVehicleFlags.bDrivingOnVehicle) { return NULL; } //Iterate over the wheels. for(s32 i = 0; i < GetNumWheels(); i++) { CPhysical* contactEntity = m_ppWheels[i]->GetHitPhysical(); if(contactEntity && contactEntity->GetIsTypeVehicle()) { return static_cast(contactEntity); } } return NULL; } bool CVehicle::GetBlockingOfNonTemporaryEventsThisFrame() const { //Check if the flag is set. if(CPed::GetRandomPedsBlockingNonTempEventsThisFrame()) { //Check if the vehicle is random (and not law-enforcement, as requested by script). if(PopTypeIsRandom() && !IsLawEnforcementVehicle()) { return true; } } return false; } bool CVehicle::GetIsUsingScriptAutoPilot() const { if (m_pIntelligence) return m_pIntelligence->GetUsingScriptAutopilot(); return false; } void CVehicle::SetTransmissionRotation(eHierarchyId compId, eHierarchyId wheelIdL, eHierarchyId wheelIdR) { ::SetTransmissionRotation(GetSkeleton(), compId, wheelIdL, wheelIdR, GetVehicleModelInfo()->GetStructure()); } void CVehicle::SetTransmissionRotationForTrike(eHierarchyId compId, eHierarchyId wheelIdL) { ::SetTransmissionRotationForTrike(GetSkeleton(), compId, wheelIdL, GetVehicleModelInfo()->GetStructure()); } void CVehicle::SetSuspensionTransformation(eHierarchyId compId, eHierarchyId wheelId, SuspensionType nType, const Vec3V *pDeformation) { ::SetSuspensionTransformation(GetSkeleton(), compId, wheelId, nType, GetVehicleModelInfo()->GetStructure(), pHandling, pDeformation); } SuspensionType CVehicle::GetSuspensionType(bool bRearSuspension) const { Assertf(pHandling,"Need handling data to find suspension type"); // Lets not check pointer because something has gone terribly wrong if we have a vehicle with no pHandling return GetSuspensionTypeFromFlags(pHandling->mFlags,bRearSuspension); } ///////////////////////////////////////////////////////////// // FUNCTION: PickRandomPassenger // If there are passengers in this vehicle a random one is returned. // CPed *CVehicle::PickRandomPassenger() { const s32 iNumSeats = m_SeatManager.GetMaxSeats(); s32 Random = fwRandom::GetRandomNumberInRange(0, iNumSeats); for (s32 C = 0; C < m_SeatManager.GetMaxSeats(); C++) { s32 TestPassenger = (Random + C) % iNumSeats; if (m_SeatManager.GetPedInSeat(TestPassenger)&& TestPassenger != GetDriverSeat()) { return m_SeatManager.GetPedInSeat(TestPassenger); } } return NULL; } bool CVehicle::PickRandomPassengers(int iNumToPick, atArray& aiSeatIndecesOut) { aiSeatIndecesOut.Reset(); aiSeatIndecesOut.Reserve(iNumToPick); s32 iNumSeats = m_SeatManager.GetMaxSeats(); s32 iRandom = fwRandom::GetRandomNumberInRange(0, iNumSeats); for (s32 scan = 0; scan < m_SeatManager.GetMaxSeats() && aiSeatIndecesOut.size() < iNumToPick; scan++) { s32 iTestSeatIndex = (iRandom + scan) % iNumSeats; if (m_SeatManager.GetPedInSeat(iTestSeatIndex) && iTestSeatIndex != GetDriverSeat()) { aiSeatIndecesOut.Push(iTestSeatIndex); } } return aiSeatIndecesOut.size() == iNumToPick; } CPed *CVehicle::GetPedInSeat(int iSeat) const { return m_SeatManager.GetPedInSeat(iSeat); } s32 CVehicle::GetNumberOfPassenger() const { s32 passengers = 0; for (s32 C = 0; C < m_SeatManager.GetMaxSeats(); C++) { if (m_SeatManager.GetPedInSeat(C) && C != GetDriverSeat()) { passengers++; } } return passengers; } bool CVehicle::ContainsLocalPlayer() const { if(m_SeatManager.GetNumPlayers()<=0) { return false; } for (s32 i = 0; i < m_SeatManager.GetMaxSeats(); i++) { if (m_SeatManager.GetPedInSeat(i) && m_SeatManager.GetPedInSeat(i)->IsLocalPlayer()) { return true; } } return false; } bool CVehicle::IsDriverAPlayer() const { CPed* driver = GetDriver(); if (driver == NULL) { return false; } if (CPedType::IsSinglePlayerType(GetDriver()->GetPedType())) { return true; } if (ContainsLocalPlayer()) { return true; } return false; } bool CVehicle::ContainsPed(const CPed* pPed) const { for (s32 i = 0; i < m_SeatManager.GetMaxSeats(); i++) { if (m_SeatManager.GetPedInSeat(i) == pPed) { return true; } } return false; } bool CVehicle::CanBeDriven() const { return (GetVehicleModelInfo()->GetModelSeatInfo()->GetDriverSeat()!=-1) && (GetSeatManager()->GetMaxSeats() > 0) && (m_eDoorLockState != CARLOCK_CANNOT_ENTER); } void CVehicle::ProcessSirenAndHorn(bool bHornAvailable) { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive()) return; #endif // GTA_REPLAY if(GetStatus() == STATUS_PLAYER && GetDriver() && GetDriver()->IsLocalPlayer() && !GetDriver()->IsDead()) { CControl *pControl = GetDriver()->GetControlFromPlayer(); if(pControl) { bool bExitingVehicle = GetDriver()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE); bool bEnteringVehicle = GetDriver()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE); const ioValue &ioVal = pControl->GetVehicleHorn(); // Cars with rocket boosts either // a) play the boost sound when the horn button is pressed, or... // b) play a boost failed sound when the horn button is pressed // so we never want to hear the horn sound if(HasRocketBoost()) { m_VehicleAudioEntity->SuppressHorn(true); } else if( InheritsFromAmphibiousAutomobile() && ms_useBoostButtonForRetract ) { m_VehicleAudioEntity->SuppressHorn(true); } else if( pHandling->hFlags & HF_HAS_KERS ) { m_VehicleAudioEntity->SuppressHorn( true ); } else { // Certain vehicle abilities share the same input as the horn, so suppress the horn if pressing the button // will activate the ability. The user can re-engage the horn by releasing and repressing the horn input. if(ioVal.IsDown()) { if(IsEngineOn()) { if( HasJump() && GetNumContactWheels() == GetNumWheels() && IsJumpFullyCharged() /* && VehicleCarJumpInput == hornInput */) { m_VehicleAudioEntity->SuppressHorn(true); } } } else if(ioVal.IsReleased()) { m_VehicleAudioEntity->SuppressHorn(false); } } // Stop the horn if the input is disabled. if(!ioVal.IsEnabled() || m_VehicleAudioEntity->ShouldSuppressHorn()) { if(m_VehicleAudioEntity) { m_VehicleAudioEntity->StopHorn(true); } } else if(UsesSiren() && !(m_VehicleAudioEntity && m_VehicleAudioEntity->IsInCarModShop())) { bool ignoreSirenSwitchDelay = !m_nVehicleFlags.GetIsSirenOn() || (m_VehicleAudioEntity && !m_VehicleAudioEntity->ShouldPlayPlayerVehSiren()); // If the button has just be released and was at least some point held down. if(!bExitingVehicle && (ioVal.IsReleased() && ioVal.HistoryPressed(500))) { if (m_nVehicleFlags.GetIsSirenOn()) { if (!m_bSirenSwitchPending) { m_uNumSequentialSirenPresses = 1; } else { m_uNumSequentialSirenPresses++; } } else { m_uNumSequentialSirenPresses = 0; } m_bSirenSwitchPending = true; } if(ioVal.HistoryHeldDown(200) && m_VehicleAudioEntity) { m_VehicleAudioEntity->PlayVehicleHorn(); m_bSirenSwitchPending = false; } else if(m_VehicleAudioEntity && !IsAlarmActivated()) { m_VehicleAudioEntity->StopHorn(); } if (m_bSirenSwitchPending && (ioVal.HistoryHeldUp(200) || ignoreSirenSwitchDelay)) { TurnSirenOn(!m_nVehicleFlags.GetIsSirenOn(), false); if (m_VehicleAudioEntity && !IsAlarmActivated()) { m_VehicleAudioEntity->StopHorn(); } m_bSirenSwitchPending = false; } } else if(bHornAvailable) { if(!IsAlarmActivated()) { //! If input was disabled, grab the real value last time round, to prevent missing activation. ioValue::ReadOptions options = ioValue::NO_DEAD_ZONE; options.SetFlags(ioValue::ReadOptions::F_READ_DISABLED, true); bool shouldPlayHorn = !m_VehicleAudioEntity->IsHornOn(); if(GetVehicleType() == VEHICLE_TYPE_BICYCLE) { shouldPlayHorn = (!ioVal.WasDown() || (bEnteringVehicle && ioVal.WasDown())); } if(ioVal.IsDown()&& !bExitingVehicle && m_VehicleAudioEntity && shouldPlayHorn) { m_VehicleAudioEntity->PlayVehicleHorn(); } else if(m_VehicleAudioEntity && (!ioVal.IsDown() || bExitingVehicle)) { m_VehicleAudioEntity->StopHorn(true); } } } } // check if we should fire a horn event for other players if(NetworkInterface::IsGameInProgress() && GetNetworkObject()) { if(IsHornOn() && !m_nVehicleFlags.bHornOnNetwork) { m_nVehicleFlags.bHornOnNetwork = true; u8 hornModIndex = GetVehicleAudioEntity()->IsInCarModShop() ? GetVariationInstance().GetModIndex(VMT_HORN) : 0; CCarHornEvent::Trigger(true, GetNetworkObject()->GetObjectID(), hornModIndex); //send the horn mod index when in a carmod shop as it can alter faster than it can replicate before horn is sounded } } } else if(GetStatus() == STATUS_PLAYER && !NetworkInterface::IsGameInProgress()) { if(m_VehicleAudioEntity) { m_VehicleAudioEntity->StopHorn(true,true); } } // this should be processed here in the event that the processing conditions fail after we've turned // the horn on via the network if(NetworkInterface::IsGameInProgress() && GetNetworkObject()) { if(m_nVehicleFlags.bHornOnNetwork && !IsHornOn()) { m_nVehicleFlags.bHornOnNetwork = false; CCarHornEvent::Trigger(false, GetNetworkObject()->GetObjectID()); } } } bool CVehicle::IsHornOn() const { if(m_VehicleAudioEntity) { return m_VehicleAudioEntity->IsHornOn(); } return false; } void CVehicle::StopHorn() { if(m_VehicleAudioEntity) { return m_VehicleAudioEntity->StopHorn(); } } static int s_NightRandomisation_Hour_Start = 19; static int s_NightRandomisation_Hour_End = 20; static int s_MorningRandomisation_Hour_Start = 6; static int s_MorningRandomisation_Hour_End = 7; bool IsClockInsideNightRandomisationRange(int hour, int minute) { return ((hour >= s_NightRandomisation_Hour_Start) && (minute > 0)) && (hour < s_NightRandomisation_Hour_End); } bool IsClockInsideMorningRandomisationRange(int hour, int minute) { return ((hour >= s_MorningRandomisation_Hour_Start) && (minute >= 0)) && (hour < s_MorningRandomisation_Hour_End); } void CVehicle::CalculateInitialLightStateBasedOnClock() { int hour = CClock::GetHour(); int minute = CClock::GetMinute(); //! turn on lights if spawned during low lod traffic nighttime so they match. m_forceLightsOnAtNightRandomisationTime = IsClockInsideNightRandomisationRange(hour, minute); //! turn off lights if we spawned during morning range. m_forceLightsOffAtMorningRandomisationTime = IsClockInsideMorningRandomisationRange(hour, minute); } void CVehicle::UpdateLightRandomisation() { //! Once outside light range, reset bools so that we don't continue to force light state after 1st day cycle. int hour = CClock::GetHour(); int minute = CClock::GetMinute(); if(m_forceLightsOnAtNightRandomisationTime && !IsClockInsideNightRandomisationRange(hour, minute)) { m_forceLightsOnAtNightRandomisationTime = false; } if(m_forceLightsOffAtMorningRandomisationTime && !IsClockInsideMorningRandomisationRange(hour, minute)) { m_forceLightsOffAtMorningRandomisationTime = false; } } void CVehicle::ProcessLightCovers() { // Animate the light covers opening and closing on vehicles that have them. int nBoneIndex = GetBoneIndex(VEH_LIGHTCOVER); if(nBoneIndex > -1) { TUNE_GROUP_FLOAT(LIGHT_COVERS, LightCoverOpenSpeed, 3, 0, 360, 1); TUNE_GROUP_FLOAT(LIGHT_COVERS, LightCoverOpenAngle, 7, 0, 360, 1); TUNE_GROUP_FLOAT(LIGHT_COVERS, LightCoverOpenDir, -1, -1, 1, 1); float fLightCoverSpeed = LightCoverOpenSpeed; float fLightCoverAngle = LightCoverOpenAngle; float fLightCoverDir = LightCoverOpenDir; bool bSetRotate = true; Vector3 vecShiftPos(0.0f, 0.0f, 0.0f); if( MI_CAR_TROPOS.IsValid() && GetModelNameHash() == MI_CAR_TROPOS.GetName().GetHash() ) { fLightCoverSpeed = 30.0f; fLightCoverAngle = 40.0f; fLightCoverDir = 1.0f; } if( MI_CAR_TORERO2.IsValid() && GetModelNameHash() == MI_CAR_TORERO2.GetName().GetHash() ) { /*static*/ float offsetz = 0.075f; fLightCoverSpeed = 0.25f; fLightCoverDir = -1.0f; fLightCoverAngle = offsetz; // used only for openratio purposes bSetRotate = false; vecShiftPos.z = offsetz; } if( pHandling->m_fPopUpLightRotation != 0.0f ) { fLightCoverSpeed = Abs( pHandling->m_fPopUpLightRotation ); fLightCoverAngle = Abs( pHandling->m_fPopUpLightRotation ); fLightCoverDir = Sign( pHandling->m_fPopUpLightRotation ); } if(MI_CAR_RUINER2.IsValid() && GetModelNameHash() == MI_CAR_RUINER2.GetName().GetHash()) { fLightCoverSpeed = 120.0f; fLightCoverAngle = 37.5f; fLightCoverDir = 1.0f; } float coverOpenDelta = (fLightCoverSpeed / fLightCoverAngle) * fwTimer::GetTimeStep() * (fwTimer::IsGamePaused() ? 0 : 1); bool shouldLightsBeOn = m_nVehicleFlags.bEngineOn && (m_nVehicleFlags.bLightsOn || m_OverrideLights == FORCE_CAR_LIGHTS_ON || m_nVehicleFlags.bHeadlightsFullBeamOn); eHeadlightCoverState targetState = m_lightCoverState; switch(m_lightCoverState) { case LIGHTCOVER_CLOSED: { if(shouldLightsBeOn) targetState = LIGHTCOVER_OPENING; m_lightCoverOpenRatio = 0; break; } case LIGHTCOVER_OPEN: { if(!shouldLightsBeOn) targetState = LIGHTCOVER_CLOSING; m_lightCoverOpenRatio = 1; break; } case LIGHTCOVER_OPENING: { m_lightCoverOpenRatio += coverOpenDelta; if(m_lightCoverOpenRatio >= 1.0f) { m_lightCoverOpenRatio = 1.0f; targetState = LIGHTCOVER_OPEN; } break; } case LIGHTCOVER_CLOSING: { m_lightCoverOpenRatio -= coverOpenDelta; if(m_lightCoverOpenRatio <= 0.0f) { m_lightCoverOpenRatio = 0.0f; targetState = LIGHTCOVER_CLOSED; } break; } } if(targetState != m_lightCoverState) { m_lightCoverState = targetState; if(m_VehicleAudioEntity) { m_VehicleAudioEntity->TriggerLightCoverStateChange(); } } if( !bSetRotate ) { vecShiftPos *= (m_lightCoverOpenRatio * fLightCoverDir); fLightCoverAngle = 0.0f; // zero out rotation } Vector3* pVecShiftPos = bSetRotate ? nullptr: &vecShiftPos; SetBoneRotation(nBoneIndex, ROT_AXIS_LOCAL_X, (DtoR * fLightCoverAngle) * m_lightCoverOpenRatio * fLightCoverDir, true, nullptr, pVecShiftPos); } else { // Make sure to set the light cover ratio fully open on vehicles without them so the headlight don't fade in/out. m_lightCoverOpenRatio = 1.0f; } } bool CVehicle::ShouldLightsBeOn() const { #if GTA_REPLAY //during replay, just return the light flag which is set by the vehicle update if(CReplayMgr::IsReplayInControlOfWorld()) { return m_nVehicleFlags.bLightsOn; } #endif bool lightsOn = false; int hour = CClock::GetHour(); int minute = CClock::GetMinute(); //! turn on lights if spawned during low lod traffic nighttime. bool bRandomiseNight = m_forceLightsOnAtNightRandomisationTime || ( (hour >= s_NightRandomisation_Hour_Start) && (minute > (GetRandomSeed() & 63))); //! don't turn on lights randomly if we spawned outside low LOD light range. bool bRandomiseMorning = !m_forceLightsOffAtMorningRandomisationTime && (hour < s_MorningRandomisation_Hour_End) && (minute < (GetRandomSeed() & 63)); // put lights on between certain times (with slight randomness) // and when its foggy or raining if ( (((hour > 19) || bRandomiseNight) || ((hour < 6) || bRandomiseMorning)) || (fwTimer::GetTimeInMilliseconds() < m_KeepLightsOnForWeatherTime)) { lightsOn = true; } else { // Lights should also be on if the vehicle is in a tunnel. switch (GetVehicleType()) { case VEHICLE_TYPE_BIKE: case VEHICLE_TYPE_CAR: case VEHICLE_TYPE_QUADBIKE: case VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE: case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE: { if (m_nVehicleFlags.bIsInTunnel) { lightsOn = true; } } break; case VEHICLE_TYPE_TRAIN: if (m_nFlags.bInMloRoom) { lightsOn = true; } break; case VEHICLE_TYPE_SUBMARINE: lightsOn = true; // Sub always starts with lights on break; default: break; } } if (MI_HELI_AKULA.IsValid() && GetModelIndex() == MI_HELI_AKULA) { lightsOn = GetAreFoldingWingsDeployed(); } return lightsOn; } void CVehicle::UpdateLightsOnFlag() { #if GTA_REPLAY //don't touch any of the flags here replay playback will have poked in the right values if(CReplayMgr::IsReplayInControlOfWorld()) { return; } #endif const bool bCloneVehicle = IsNetworkClone(); if(!bCloneVehicle) { // If it's foggy or raining, the vehicle should keep its lights on // for a certain period of time (otherwise, fluctuating weather intensity can cause // the lights to flicker on and off) if( (g_weather.GetFog() > (GetRandomSeed()/50000.0f)) || (g_weather.GetRain() > (GetRandomSeed()/50000.0f)) ) { m_KeepLightsOnForWeatherTime = fwTimer::GetTimeInMilliseconds() + fwRandom::GetRandomNumberInRange(2000,5000); } const bool lightsShouldBeOn = GetVehicleLightsStatus(); // sort out any lights that aren't in the correct state if (lightsShouldBeOn != this->m_nVehicleFlags.bLightsOn) { if(GetStatus() != STATUS_WRECKED) { bool bIsTrain = GetVehicleType() == VEHICLE_TYPE_TRAIN; if(GetStatus() != STATUS_ABANDONED || bIsTrain) { // Only change the light status if the driver is alive. // Unless you're a train, in which case you do it anyway. CPed* pDriver = GetDriver(); if (IsUsingPretendOccupants() || (pDriver && !pDriver->GetIsDeadOrDying()) || bIsTrain) { this->m_nVehicleFlags.bLightsOn = lightsShouldBeOn; } } else { // abandoned cars switch their lights of if they're far away Vec3V vPosition = GetTransform().GetPosition(); Vector3 camPos = camInterface::GetPos(); if(this->m_nVehicleFlags.bLightsOn && (ABS(camPos.x - vPosition.GetXf()) + ABS(camPos.y - vPosition.GetYf()) > 100.0f) ) { this->m_nVehicleFlags.bLightsOn = false; } } } } } } bool CVehicle::GetVehicleLightsStatus() { UpdateLightRandomisation(); bool lightsOn = ShouldLightsBeOn(); // interior state out of sync with tunnel flags if (m_nVehicleFlags.bIsInTunnel && !GetIsInInterior()) { GetPortalTracker()->RequestRescanNextUpdate(); } //if time of day thinks it should be daytime, reset the //"player has turned off low beams" state if (!lightsOn) { m_nVehicleFlags.bPlayerHasTurnedOffLowBeamsAtNight = false; } else if (m_nVehicleFlags.bPlayerHasTurnedOffLowBeamsAtNight) { lightsOn = false; } return lightsOn; } bool CVehicle::UpdateVehicleLightOverrideStatus() { bool forceOn = false; if (m_OverrideLights != NO_CAR_LIGHT_OVERRIDE) { if (m_OverrideLights == FORCE_CAR_LIGHTS_OFF) { m_nVehicleFlags.bLightsOn = false; m_nVehicleFlags.bHeadlightsFullBeamOn = false; } else if (m_OverrideLights == FORCE_CAR_LIGHTS_ON) { forceOn = true; } else if(m_OverrideLights == SET_CAR_LIGHTS_ON) { m_nVehicleFlags.bLightsOn = true; } else if(m_OverrideLights == SET_CAR_LIGHTS_OFF) { m_nVehicleFlags.bLightsOn = false; m_nVehicleFlags.bHeadlightsFullBeamOn = false; } } return forceOn; } static void AddVehicleAmbientOccluder( const Vector3& worldPosn, const Vector3& worldDir, const Vector3& worldTan, float scalex, float scaley, float scalez, float fade, float camDist, int type, CoronaAndLightList& coronaAndLightList) { int texKey = g_vehicleBottomTexKey[type]; // Fade distance = 40m (1 / 40) = 0.025 const float distanceFade = rage::Clamp((g_AOVolume_ShutDownDistance - camDist) * 0.025f, 0.0f, 1.0f); const Vector3 debugDownDir=Vector3(0,0,-1); float downfade = rage::Max(worldDir.Dot(debugDownDir),0.0f); float totalFade = fade * downfade * distanceFade; if (totalFade > 0.0f) { CLightSource light(LIGHT_TYPE_AO_VOLUME, 0, worldPosn, Vector3(0,0,0), totalFade, LIGHT_ALWAYS_ON); light.SetDirTangent(worldDir, worldTan); light.SetAOVolume(scalex, scaley, scalez); light.SetTexture(texKey, CShaderLib::GetTxdId()); light.SetRadius(rage::Max(scalex * 1.0f + 0.5f, rage::Max(scaley * 1.5f + 0.5f, scalez * 3.0f))); if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } } static void AddVehicleLight( const Vector3& worldPosn, const Vector3& worldDir, const Vector3& worldTan, const Vector3& colour, float intensity, float falloffMax, float falloffExponent, float innerConeAngle, float outerConeAngle, fwInteriorLocation intLoc, bool isHeadLight, CoronaAndLightList& coronaAndLightList, CVehicle* pVehicle, u8 castShadows=NO_HEADLIGHT_SHADOWS, int shadowTrackingID = 0, bool useTwinLight = false, float twinLightOffset = 0.0f, bool useVolumetricLight = false, bool forceLowResVolumetricLights = false, u32 textureHash = 0, bool flipLightTexture = false, bool enableSpecular = true, float capsuleExtent = 0.0f, float lightClipPlaneHeight = 0.0f) { Vec3V wd = VECTOR3_TO_VEC3V(worldDir); if ( IsEqualAll( wd,Vec3V(V_ZERO)) ) { return; } u32 flags = LIGHTFLAG_VEHICLE; if(useVolumetricLight) flags |= LIGHTFLAG_DRAW_VOLUME | LIGHTFLAG_USE_VOLUME_OUTER_COLOUR; if(castShadows&HEADLIGHTS_CAST_DYNAMIC_SHADOWS) flags |= LIGHTFLAG_CAST_SHADOWS|LIGHTFLAG_CAST_DYNAMIC_GEOM_SHADOWS|LIGHTFLAG_MOVING_LIGHT_SOURCE; if(castShadows&HEADLIGHTS_CAST_STATIC_SHADOWS) flags |= LIGHTFLAG_CAST_SHADOWS|LIGHTFLAG_CAST_STATIC_GEOM_SHADOWS|LIGHTFLAG_MOVING_LIGHT_SOURCE; if(!enableSpecular) flags |= LIGHTFLAG_NO_SPECULAR; if(isHeadLight) { if(useTwinLight) { if(CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_WITH_FAKE_SPLIT || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT) { flags |= LIGHTFLAG_USE_VEHICLE_TWIN; } if((CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT) && textureHash != 0) { flags |= LIGHTFLAG_TEXTURE_PROJECTION; } } else if((CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_2_LIGHTS_TEXTURED || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT) && textureHash != 0) { flags |= LIGHTFLAG_TEXTURE_PROJECTION; } } else { //use tail light mode for all the other types of lights if(CHeadlightTuningData::ms_VehicleTwinTailLightMode == VTTL_MODE_SINGLE_FILLER_ONLY) flags |= LIGHTFLAG_NO_SPECULAR; else if(useTwinLight && CHeadlightTuningData::ms_VehicleTwinTailLightMode == VTTL_MODE_SINGLE_WITH_FAKE_SPLIT) flags |= LIGHTFLAG_USE_VEHICLE_TWIN; } CLightSource light; if(capsuleExtent > 0.0f) { light = CLightSource(LIGHT_TYPE_CAPSULE, flags, worldPosn, colour, intensity, LIGHT_ALWAYS_ON); light.SetCapsule(capsuleExtent); light.SetDirTangent(worldTan, worldDir); light.SetRadius(falloffMax); } else { light = CLightSource(LIGHT_TYPE_SPOT, flags, worldPosn, colour, intensity, LIGHT_ALWAYS_ON); light.SetRadius(falloffMax); // this call needs to be done before we call SetSpotlight or the cone height will be incorrect light.SetSpotlight(innerConeAngle, outerConeAngle); light.SetDirTangent(worldDir, worldTan); } light.SetInInterior(intLoc); light.SetFalloffExponent(falloffExponent); light.SetShadowTrackingId(shadowTrackingID); if (isHeadLight && pVehicle && pVehicle->ContainsLocalPlayer()) light.SetExtraFlag(EXTRA_LIGHTFLAG_HIGH_PRIORITY); if(lightClipPlaneHeight != 0.0f) { light.SetFlag(LIGHTFLAG_USE_CULL_PLANE); Matrix34 mat(Matrix34::IdentityType); light.SetPlane(worldDir.x, worldDir.y, worldDir.z, lightClipPlaneHeight, mat); } if(useVolumetricLight) { light.SetLightVolumeIntensity( intensity * CHeadlightTuningData::ms_volumeIntensityScale, CHeadlightTuningData::ms_volumeSizeScale, Vec4V(CHeadlightTuningData::ms_outerVolumeColor.GetRGB(), ScalarV(CHeadlightTuningData::ms_outerVolumeIntensity)), CHeadlightTuningData::ms_outerVolumeFallOffExponent); if(forceLowResVolumetricLights) { light.SetExtraFlag(EXTRA_LIGHTFLAG_FORCE_LOWRES_VOLUME); } } if(useTwinLight) { light.SetVehicleHeadlightOffset(twinLightOffset); light.SetVehicleHeadlightSplitOffset(g_CarSplitSettings); } //if we're in any of the texture light modes and we have a texture provided set that texture if((CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_2_LIGHTS_TEXTURED || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT) && textureHash != 0) { light.SetTexture(textureHash, CShaderLib::GetTxdId()); if(flipLightTexture) { light.SetExtraFlag(EXTRA_LIGHTFLAG_FLIP_TEXTURE); } } if(IsLightVisible(light)) { //We need to prevent this vehicle and occupants from appearing in its own headlight shadows so add them to the exclusion list. if(pVehicle && castShadows ) { light.AddToShadowExclusionList( pVehicle ); for( u32 i = 0; i < pVehicle->GetSeatManager()->GetMaxSeats(); i++) { fwEntity* pPedEntity = pVehicle->GetSeatManager()->GetPedInSeat(i); if( pPedEntity ) light.AddToShadowExclusionList( pPedEntity ); } } light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } static void AddVehicleLight( const Matrix34 &matVehicle, const Vector3& worldPosn, const Vector3& dir, const Vector3& colour, float intensity, float falloffMax, float falloffExponent, float innerConeAngle, float outerConeAngle, fwInteriorLocation intLoc, bool isHeadLight, CoronaAndLightList& coronaAndLightList, CVehicle* pVehicle, u8 castShadows=NO_HEADLIGHT_SHADOWS, int shadowTrackingID = 0, bool useTwinLight = false, float twinLightOffset = 0.0f, bool enableSpecular = true, float capsuleExtent = 0.0f, float lightClipPlaneHeight = 0.0f) { //FindMinAbsAxis(dir) gives the tangent itself, no need to do the cross product //Vector3 tangent; //tangent.Cross(dir, FindMinAbsAxis(dir)); Vector3 tangent = FindMinAbsAxis(dir); tangent.Normalize(); Vector3 worldDir; Vector3 worldTan; matVehicle.Transform3x3(dir, worldDir); matVehicle.Transform3x3(tangent, worldTan); AddVehicleLight(worldPosn, worldDir, worldTan, colour, intensity, falloffMax, falloffExponent, innerConeAngle, outerConeAngle, intLoc, isHeadLight, coronaAndLightList, pVehicle, castShadows, shadowTrackingID, useTwinLight, twinLightOffset, false, false, 0, false, enableSpecular, capsuleExtent, lightClipPlaneHeight); } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoIndicatorLightEffect(s32 boneId, float fade, float daynightFade, float coronaFade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, const vehicleLightSettings *settings, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoIndicatorLightEffect(s32 boneId, float fade, float daynightFade, float coronaFade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, const vehicleLightSettings *settings, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #endif { const float alphaFadeLOD = this->GetAlpha() / 255.0f; static dev_float recessedCoronaDirThreshold = 0.65f; bool bUseRecessedCorona = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_RECESSED_TAILLIGHT_CORONAS) BANK_ONLY(|| CHeadlightTuningData::ms_globalForceRecessedCorona); const float coronaDirThreshold = bUseRecessedCorona ? recessedCoronaDirThreshold : 1.0f; Matrix34 mtx; s32 boneIdx = pModelInfo->GetBoneIndex(boneId); if (boneIdx>-1) { const CVehicleLightSwitch::LightId light = BONEID_TO_LIGHTID(boneId); Vector3 CornerVec; switch(boneId) { case VEH_INDICATOR_LF: CornerVec = Vector3(-0.7f, 0.7f, 0.0f); break; case VEH_INDICATOR_RF: CornerVec = Vector3(0.7f, 0.7f, 0.0f); break; case VEH_INDICATOR_LR: CornerVec = Vector3(-0.7f, -0.7f, 0.0f); break; case VEH_INDICATOR_RR: CornerVec = Vector3(0.7f, -0.7f, 0.0f); break; default: Assert(0); break; } #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdx, mtx); #else pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mtx)); #endif // if this is a light on the right side of the car, invert the rotations const float rotationScale = (boneId == VEH_INDICATOR_RR || boneId == VEH_INDICATOR_RF) ? -1.0f : 1.0f; float directionScale; vehicleCoronaRenderProp coronaProperties; if (boneId == VEH_INDICATOR_LR || boneId == VEH_INDICATOR_RR) { // rear facing indicator lights directionScale = -1.0f; PopulateCommonVehicleCoronaProperties(settings->rearIndicatorCorona, coronaFade, alphaFadeLOD, coronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness() ); } else // front facing indicator lights { directionScale = 1.0f; PopulateCommonVehicleCoronaProperties(settings->frontIndicatorCorona, coronaFade, alphaFadeLOD, coronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness()); } Matrix34 localBone = mtx; localBone.RotateLocalX( rotationScale * coronaProperties.xRotation ); localBone.RotateLocalY( rotationScale * coronaProperties.yRotation ); localBone.RotateLocalZ( rotationScale * coronaProperties.zRotation ); float pullDistance = 0.0f; if (coronaProperties.pullCoronaIn) { if (GetLodData().GetLodType() == LODTYPES_DEPTH_SLOD3 || GetLodData().GetLodType() == LODTYPES_DEPTH_SLOD4) { pullDistance = 1.0f; } } Vector3 forward = directionScale * localBone.b; // loop through and add all the coronas to the headlights (there could be multiple coronas for LED style lights) for (u32 i = 0; i < coronaProperties.numCoronas; i++) { Vector3 position = localBone.d; position = position + (coronaProperties.halfWidth * -localBone.a) + ((float)i * localBone.a * coronaProperties.distBetween); position += (pullDistance * -forward); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size, coronaProperties.color, coronaProperties.intensity, coronaProperties.zBias, RCC_VEC3V(forward), coronaDirThreshold, settings->headLight.innerConeAngle, settings->headLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } if( fade > 0.0f ) { AddVehicleLight(matVehicle, mtx.d, CornerVec, VEC3V_TO_VECTOR3(settings->indicator.color.GetRGB()), fade * settings->indicator.intensity, settings->indicator.falloffMax, settings->indicator.falloffExponent, settings->indicator.innerConeAngle, settings->indicator.outerConeAngle, interiorLocation, false, coronaAndLightList, this); } matVehicle.Transform3x3(CornerVec); CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); float intensity = g_LightSwitchSettings.GetLightOnValue(light,daynightFade) * ( settings->indicator.emmissiveBoost ? g_LightSwitchSettings.emissiveMultiplier : 1.0f ); pShader->SetLightValue(light, intensity); } } bool CVehicle::GetFullBeamStatus() { #if GTA_REPLAY //don't touch any of the flags here replay playback will have poked in the right values if(CReplayMgr::IsReplayInControlOfWorld()) { return m_nVehicleFlags.bHeadlightsFullBeamOn; } #endif bool fullBeam = false; if(CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods) { if( GetDriver() && //!CPhoneMgr::IsDisplayed() && //DMKG. script set input exclusive when using the phone, so don't need to worry about this anymore. GetDriver()->IsLocalPlayer() && !GetDriver()->IsDead()) { CPed* pPlayerPed = GetDriver(); const CControl* pControl = pPlayerPed->GetControlFromPlayer(); Assert(pControl); //Keep track of whether to toggle the lights. bool bToggleLights = false; //The lights can be toggled by a quick press of the button. if(pControl->GetVehicleHeadlight().IsReleased() && !pControl->GetVehicleHeadlight().IsReleasedAfterHistoryHeldDown(ms_uTimeToIgnoreButtonHeldDown)) { bToggleLights = true; } bool bCanSwitchVisors = pPlayerPed->GetHelmetComponent() && pPlayerPed->GetHelmetComponent()->IsHelmetEnabled() && pPlayerPed->GetHelmetComponent()->GetCurrentHelmetSupportsVisors(); //The lights can also be toggled by holding the button down, but only for cars with no roof animations. if( !bToggleLights && !GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HEADLIGHTS_ON_TAP_ONLY) && !DoesVehicleHaveAConvertibleRoofAnimation() && !HasTowingGadget() && !bCanSwitchVisors && !InheritsFromAmphibiousQuadBike() && !HasGlider() && !DoesVehicleHaveATransformAnimation()) { if(pControl->GetVehicleHeadlight().HistoryHeldDown(TimeNeededToHoldLightsButtonDown)) { m_nVehicleFlags.bLightsButtonHeldDown = true; } else { m_nVehicleFlags.bLightsButtonHeldDown = false; m_nVehicleFlags.bLightsAllowFullBeamSwitch = true; } if(m_nVehicleFlags.bLightsButtonHeldDown && m_nVehicleFlags.bLightsAllowFullBeamSwitch) { bToggleLights = true; } } if (MI_HELI_AKULA.IsValid() && GetModelIndex() == MI_HELI_AKULA) { bToggleLights = false; } if(bToggleLights) { CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)GetBaseModelInfo(); s32 boneIdxLeft = pModelInfo->GetBoneIndex(VEH_HEADLIGHT_L); s32 boneIdxRight = pModelInfo->GetBoneIndex(VEH_HEADLIGHT_R); if(boneIdxLeft > -1 || boneIdxRight > -1) { m_VehicleAudioEntity->TriggerHeadlightSwitch(); } //bicycles only have a low beam const bool bHighBeamAllowed = (GetVehicleType() != VEHICLE_TYPE_BICYCLE) && (GetVehicleType() != VEHICLE_TYPE_SUBMARINE); if (m_nVehicleFlags.bLightsOn || m_nVehicleFlags.bPlayerHasTurnedOffLowBeamsAtNight) { //if the lights are on, and fullbeams are off, set the override to turn lights off if (m_nVehicleFlags.bPlayerHasTurnedOffLowBeamsAtNight) { m_nVehicleFlags.bPlayerHasTurnedOffLowBeamsAtNight = false; } else if (!m_nVehicleFlags.bHeadlightsFullBeamOn && bHighBeamAllowed) { m_nVehicleFlags.bHeadlightsFullBeamOn = true; m_TimeFullBeamSwitchedOn = fwTimer::GetTimeInMilliseconds(); } else { m_nVehicleFlags.bPlayerHasTurnedOffLowBeamsAtNight = true; m_nVehicleFlags.bHeadlightsFullBeamOn = false; } } else { if(!m_nVehicleFlags.bHeadlightsFullBeamOn && bHighBeamAllowed) { m_nVehicleFlags.bHeadlightsFullBeamOn = true; m_TimeFullBeamSwitchedOn = fwTimer::GetTimeInMilliseconds(); } else { m_nVehicleFlags.bHeadlightsFullBeamOn = false; } } m_nVehicleFlags.bLightsAllowFullBeamSwitch = false; if(m_OverrideLights == SET_CAR_LIGHTS_OFF || m_OverrideLights == SET_CAR_LIGHTS_ON) { m_OverrideLights = NO_CAR_LIGHT_OVERRIDE; } } if (!m_nVehicleFlags.bLightsOn) // If the cars lights would be off (during day) we switch the full beam off after a while to help the frame rate { if (m_nVehicleFlags.bHeadlightsFullBeamOn && fwTimer::GetTimeInMilliseconds() > m_TimeFullBeamSwitchedOn + 40000 && ( GetVehicleType() != VEHICLE_TYPE_SUBMARINECAR || !GetIsInWater() ) ) { m_nVehicleFlags.bHeadlightsFullBeamOn = false; } } } fullBeam = m_nVehicleFlags.bHeadlightsFullBeamOn; } // if the lights are being forced off, we can't toggle them. We'll go ahead and run through all // the toggle logic above, so that timestamps and such are updated, but don't return saying // full beams should be turned on if (m_OverrideLights == FORCE_CAR_LIGHTS_OFF) { fullBeam = false; } return fullBeam; } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoHeadLightsEffect(bool leftOn, bool rightOn, s32 boneIdLeft, s32 boneIdRight, float fade, float daynightFade, float coronaFade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, bool fullBeam, bool noLight, const vehicleLightSettings *settings, float intensityMultiplierHeadLeft, float intensityMultiplierHeadRight, fwInteriorLocation interiorLocation, bool useVolumetricLights, bool forceLowResVolumetricLights, bool reduceHeadlightCoronas, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoHeadLightsEffect(bool leftOn, bool rightOn, s32 boneIdLeft, s32 boneIdRight, float fade, float daynightFade, float coronaFade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, bool fullBeam, bool noLight, const vehicleLightSettings *settings, float intensityMultiplierHeadLeft, float intensityMultiplierHeadRight, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, bool useVolumetricLights, bool forceLowResVolumetricLights, bool reduceHeadlightCoronas, CoronaAndLightList& coronaAndLightList) #endif { static dev_float zbiasCorona = 0.25f; const float alphaFadeLOD = this->GetAlpha() / 255.0f; // Recessed lights have restricted visibility, this causes them to fade out quicker based on view angle. This value isn't perfect, for // example the Tampa still has some minor issues, but it works pretty well for most other cars. See B*794858 for list of cars that need this. static dev_float recessedCoronaDirThreshold = 0.5f; bool bUseRecessedCorona = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_RECESSED_HEADLIGHT_CORONAS) BANK_ONLY(|| CHeadlightTuningData::ms_globalForceRecessedCorona); const float coronaDirThreshold = bUseRecessedCorona ? recessedCoronaDirThreshold : 1.0f; const bool bIsSubmarine = (GetVehicleType() == VEHICLE_TYPE_SUBMARINE); vehicleCoronaRenderProp coronaProperties; PopulateCommonVehicleCoronaProperties(settings->headLightCorona, coronaFade, alphaFadeLOD, coronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness()); // Allow light covers over the headlights to affect the light intensity as they animate open/closed. // Note: For vehicles without light covers, this ratio will always be 1. coronaProperties.intensity *= m_lightCoverOpenRatio; coronaProperties.size *= m_lightCoverOpenRatio; fade *= m_lightCoverOpenRatio; if( fullBeam ) { coronaProperties.intensity *= CHeadlightTuningData::ms_fullBeamHeadlightCoronaIntensityMult; coronaProperties.size *= CHeadlightTuningData::ms_fullBeamHeadlightCoronaSizeMult; } float xenonMultiplier = 1.f; Vector3 lightColor = VEC3V_TO_VECTOR3(settings->headLight.color.GetRGB()); if (GetVariationInstance().IsToggleModOn(VMT_XENON_LIGHTS)) { const CVehicleVariationGlobalData& varData = CVehicleModelInfo::GetVehicleColours()->GetGlobalVariationData(); const u8 xeonLightColorIdx = this->GetXenonLightColor(); if(xeonLightColorIdx != 0xff) { Assert(xeonLightColorIdx < CVehicleModelInfo::GetVehicleColours()->m_XenonLightColors.GetCount()); const CVehicleXenonLightColor *pXenonLightParams = &CVehicleModelInfo::GetVehicleColours()->m_XenonLightColors[xeonLightColorIdx]; lightColor = VEC3V_TO_VECTOR3(pXenonLightParams->m_lightColor.GetRGB()); coronaProperties.color = pXenonLightParams->m_coronaColor; coronaProperties.intensity *= pXenonLightParams->m_coronaIntensityModifier; xenonMultiplier = pXenonLightParams->m_lightIntensityModifier; } else { lightColor = VEC3V_TO_VECTOR3(varData.m_xenonLightColor.GetRGB()); coronaProperties.color = varData.m_xenonCoronaColor; coronaProperties.intensity *= varData.m_xenonCoronaIntensityModifier; xenonMultiplier = varData.m_xenonLightIntensityModifier; } } if( pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HEADLIGHTS_ON_LANDINGGEAR) ) { #if __ASSERT Assertf(InheritsFromPlane(), "Vehicle uses FLAG_HEADLIGHTS_ON_LANDINGGEAR but is not a plane"); if( InheritsFromPlane() ) #endif { const CPlane *plane = (const CPlane*)this; const CLandingGear& gear = plane->GetLandingGear(); if( gear.GetPublicState() == CLandingGear::STATE_BROKEN ) return; float gearRatio = gear.GetGearDeployRatio(); coronaProperties.intensity *= gearRatio; xenonMultiplier *= gearRatio; } } float intensityMult = ( settings->headLight.emmissiveBoost ? g_LightSwitchSettings.emissiveMultiplier : 1.0f ); Matrix34 mBoneLeft, mBoneRight; s32 boneIdxLeft = pModelInfo->GetBoneIndex(boneIdLeft); s32 boneIdxRight = pModelInfo->GetBoneIndex(boneIdRight); // Coronas { CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); bool useBoneOrientation = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HEADLIGHTS_USE_ACTUAL_BONE_POS) || bIsSubmarine ; if (boneIdxLeft>-1) { if (leftOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif Matrix34 localBone = mBoneLeft; localBone.RotateLocalX( coronaProperties.xRotation ); localBone.RotateLocalY( coronaProperties.yRotation ); localBone.RotateLocalZ( coronaProperties.zRotation ); Vector3 direction = useBoneOrientation ? mBoneLeft.b : matVehicle.b; // loop through and add all the coronas to the headlights (there could be multiple coronas for LED style lights) for (u32 i = 0; i < coronaProperties.numCoronas; i++) { Vector3 position = localBone.d; position = position + (coronaProperties.halfWidth * -localBone.a) + ((float)i * localBone.a * coronaProperties.distBetween); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size * (reduceHeadlightCoronas? ms_underwaterHeadlightCoronaFade:1.0f), coronaProperties.color, coronaProperties.intensity * intensityMultiplierHeadLeft * (reduceHeadlightCoronas?ms_underwaterHeadlightCoronaFade:1.0f), zbiasCorona, RCC_VEC3V(direction), coronaDirThreshold, settings->headLight.innerConeAngle, settings->headLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } pShader->SetLightValue(BONEID_TO_LIGHTID(boneIdLeft), g_LightSwitchSettings.GetLightOnValue(BONEID_TO_LIGHTID(boneIdLeft),daynightFade) * intensityMult); } else { pShader->SetLightValue(BONEID_TO_LIGHTID(boneIdLeft), g_LightSwitchSettings.GetLightOffValue(BONEID_TO_LIGHTID(boneIdLeft),daynightFade) * intensityMult); } } if (boneIdxRight>-1) { if (rightOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxRight, mBoneRight); #else pSkeleton->GetGlobalMtx(boneIdxRight, RC_MAT34V(mBoneRight)); #endif Matrix34 localBone = mBoneRight; localBone.RotateLocalX( -coronaProperties.xRotation ); localBone.RotateLocalY( -coronaProperties.yRotation ); localBone.RotateLocalZ( -coronaProperties.zRotation ); Vector3 direction = useBoneOrientation ? mBoneRight.b : matVehicle.b; // loop through and add all the coronas to the headlights (there could be multiple coronas for LED style lights) for (u32 i = 0; i < coronaProperties.numCoronas; i++) { Vector3 position = localBone.d; position = position + (coronaProperties.halfWidth * -localBone.a) + ((float)i * localBone.a * coronaProperties.distBetween); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size * (reduceHeadlightCoronas?ms_underwaterHeadlightCoronaFade:1.0f), coronaProperties.color, coronaProperties.intensity * intensityMultiplierHeadRight * (reduceHeadlightCoronas?ms_underwaterHeadlightCoronaFade:1.0f), zbiasCorona, RCC_VEC3V(direction), coronaDirThreshold, settings->headLight.innerConeAngle, settings->headLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } pShader->SetLightValue(BONEID_TO_LIGHTID(boneIdRight), g_LightSwitchSettings.GetLightOnValue(BONEID_TO_LIGHTID(boneIdRight),daynightFade) * intensityMult); } else { pShader->SetLightValue(BONEID_TO_LIGHTID(boneIdRight), g_LightSwitchSettings.GetLightOffValue(BONEID_TO_LIGHTID(boneIdRight),daynightFade) * intensityMult); } } } if (fade < 0.001f) { return; } bool bRightBoneIdxChanged = false; bool bLeftBoneIdxChanged = false; if( noLight == false ) { if( pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_FAKE_EXTRALIGHTS) ) { if( m_VehicleDamage.GetLightState(VEH_HEADLIGHT_R) == true) { // broke, lets check the other bone... boneIdxRight = pModelInfo->GetBoneIndex(VEH_EXTRALIGHT_2); bRightBoneIdxChanged = true; } if( m_VehicleDamage.GetLightState(VEH_HEADLIGHT_L) == true) { // broke, lets check the other bone... boneIdxLeft = pModelInfo->GetBoneIndex(VEH_EXTRALIGHT_1); bLeftBoneIdxChanged = true; } } // Note that making just the player's veh have bigger headlights shouldn't really // affect rendering at all since those lights are already almost full screen. // I.e. it won't change the number of pixel visited when calculating the players // headlights affect on the scene. // Determine the intesity and distance multiplier. float headlightIntensityMult = 1.0f * CHeadlightTuningData::ms_globalHeadlightIntensityMult; float headlightDistanceMult = 1.0f * CHeadlightTuningData::ms_globalHeadlightDistMult; float headlightExponentMult = 1.0f; { if(IsDriverAPlayer() || m_usePlayerLightSettings == true) { headlightExponentMult = CHeadlightTuningData::ms_playerHeadlightExponentMult; if(CHeadlightTuningData::ms_makeHeadlightsCastFurtherForPlayer) { headlightIntensityMult *= CHeadlightTuningData::ms_playerHeadlightIntensityMult; headlightDistanceMult *= CHeadlightTuningData::ms_playerHeadlightDistMult; } } if(CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods) { if(fullBeam) { headlightIntensityMult *= CHeadlightTuningData::ms_fullBeamHeadlightIntensityMult; headlightDistanceMult *= CHeadlightTuningData::ms_fullBeamHeadlightDistMult; } } } #if RSG_PC u8 castShadow; if (!IsDriverAPlayer() && !m_usePlayerLightSettings) castShadow = NO_HEADLIGHT_SHADOWS; else { if (CSettingsManager::GetInstance().GetSettings().m_graphics.m_ShadowQuality == CSettings::Ultra) { CHeadlightTuningData::ms_VehicleTwinHeadLightMode = VTHL_MODE_2_LIGHTS_TEXTURED; castShadow = HEADLIGHTS_CAST_FULL_SHADOWS; } else if (CSettingsManager::GetInstance().GetSettings().m_graphics.m_ShadowQuality == CSettings::High) { CHeadlightTuningData::ms_VehicleTwinHeadLightMode = VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT; castShadow = HEADLIGHTS_CAST_STATIC_SHADOWS; } else { CHeadlightTuningData::ms_VehicleTwinHeadLightMode = VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT; castShadow = NO_HEADLIGHT_SHADOWS; } } #else const u8 castShadow = m_HeadlightShadows BANK_ONLY(|CHeadlightTuningData::ms_ForceHeadlightShadows); #endif // We're switching the submarine over to the twin-light system on XENON & PS3 but we want to do it in a way that doesn't require any re-tuning // and keeps the light looking just like it used to with two real lights so there are a few hard coded overrides below to make this happen. const float intensity = (m_HeadlightMultiplier * fade* settings->headLight.intensity) * headlightIntensityMult; const float falloffMax = (10.0f + ((IsDriverAPlayer() || m_usePlayerLightSettings == true)?(settings->headLight.falloffMax):0.0f)) * headlightDistanceMult; const float falloffExponent = settings->headLight.falloffExponent * headlightExponentMult; //forcing fake mode to off when vehicle is submarine const bool bApplyFake2LightsMod = (CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_WITH_FAKE_SPLIT || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_SINGLE_TEXTURED_WITH_FAKE_SPLIT) && !bIsSubmarine; const float fFake2LightMod = bIsSubmarine ? CHeadlightTuningData::ms_globalFake2LightAngleModSubmarine : CHeadlightTuningData::ms_globalFake2LightAngleMod; const float fFake2LightDisplacementMod = bApplyFake2LightsMod? (bIsSubmarine ? CHeadlightTuningData::ms_globalFake2LightsDisplacementModSubmarine : CHeadlightTuningData::ms_globalFake2LightsDisplacementMod):0.0f; const float coneInnerAngleForBothLightsOn = settings->headLight.innerConeAngle * CHeadlightTuningData::ms_globalConeInnerAngleMod * (bApplyFake2LightsMod?fFake2LightMod:1.0f); const float coneOuterAngleForBothLightsOn = settings->headLight.outerConeAngle * CHeadlightTuningData::ms_globalConeOuterAngleMod * (bApplyFake2LightsMod?fFake2LightMod:1.0f); const float coneInnerAngleJustOneLightOn = settings->headLight.innerConeAngle * CHeadlightTuningData::ms_globalConeInnerAngleMod * CHeadlightTuningData::ms_globalOnlyOneLightMod; const float coneOuterAngleJustOneLightOn = settings->headLight.outerConeAngle * CHeadlightTuningData::ms_globalConeOuterAngleMod * CHeadlightTuningData::ms_globalOnlyOneLightMod; int shadowTrackingID = 0; if(castShadow != NO_HEADLIGHT_SHADOWS) shadowTrackingID = (int) fwIdKeyGenerator::Get(this, 0); // TODO: This may not actually be safe on x64 builds but correctly using fwUniqueObjId affects a lot of down-stream interfaces // Now deal with actual light casting if (boneIdxLeft>-1 && boneIdxRight>-1) { // Determine the angle of the light. float beamAngle = g_CarAngleSettings; if(CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods) { if(fullBeam) { beamAngle += CHeadlightTuningData::ms_aimFullBeamMod; } else { beamAngle += CHeadlightTuningData::ms_aimDippedBeamMod; } } // A bit nasty but simulates the same rotation we had before but on the right direction Vector3 dir = Vector3(0.0f, 1.0f, beamAngle); dir.Normalize(); const float cosAngle = dir.y; const float sinAngle = -sqrtf(1.0f - cosAngle*cosAngle); //forcing submarine to have 2 headlights as its setup differently if( CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_2_LIGHTS || CHeadlightTuningData::ms_VehicleTwinHeadLightMode == VTHL_MODE_2_LIGHTS_TEXTURED || pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HEADLIGHTS_USE_ACTUAL_BONE_POS) || bIsSubmarine ) { if( true == leftOn ) { Matrix34 mBoneLeft; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif // Only left light is on Vector3 rotatedB(mBoneLeft.b); rotatedB.Scale(cosAngle); rotatedB.AddScaled(mBoneLeft.c, sinAngle); //rotatedB is not perpendicular to mBoneLeft.c anymore //ajust due to the split, so it matches with dual rendering version rotatedB -= g_CarSplitSettings * mBoneLeft.a; rotatedB.Normalize(); mBoneLeft.b.Set(rotatedB); //Compute the proper tangent using existing binormal and updated direction Vector3 rotatedA; rotatedA.Cross(mBoneLeft.b, mBoneLeft.c); rotatedA.Normalize(); //doing a normalize because mBoneLeft.b is not exactly perpendicular to mBoneLeft.c mBoneLeft.a.Set(rotatedA); AddVehicleLight( mBoneLeft.d, mBoneLeft.b, mBoneLeft.a, lightColor, intensity * intensityMultiplierHeadLeft * xenonMultiplier * coronaAndLightList.GetTCVehicleIntensityScale(), falloffMax, falloffExponent, rightOn ? coneInnerAngleForBothLightsOn : coneInnerAngleJustOneLightOn, rightOn ? coneOuterAngleForBothLightsOn : coneOuterAngleJustOneLightOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, false, 0.0f, useVolumetricLights, forceLowResVolumetricLights, settings->headLight.textureName.GetHash(), false); } if( true == rightOn ) { if(bRightBoneIdxChanged) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxRight, mBoneRight); #else pSkeleton->GetGlobalMtx(boneIdxRight, RC_MAT34V(mBoneRight)); #endif } // Only right light is on Vector3 rotatedB(mBoneRight.b); rotatedB.Scale(cosAngle); rotatedB.AddScaled(mBoneRight.c, sinAngle); //rotatedB is not perpendicular to mBoneRight.c anymore //ajust due to the split, so it matches with dual rendering version rotatedB += g_CarSplitSettings * mBoneRight.a; rotatedB.Normalize(); mBoneRight.b.Set(rotatedB); //Compute the proper tangent using existing binormal and updated direction Vector3 rotatedA; rotatedA.Cross(mBoneRight.b, mBoneRight.c); rotatedA.Normalize(); //doing a normalize because mBoneRight.b is not exactly perpendicular to mBoneRight.c mBoneRight.a.Set(rotatedA); //Need a new tracking ID for the 2nd headlight otherwise we use get the same shadows in both lights. if( castShadow ) shadowTrackingID = (int) fwIdKeyGenerator::Get((void*)&m_VehicleDamage, 0); AddVehicleLight( mBoneRight.d, mBoneRight.b, mBoneRight.a, lightColor, intensity * intensityMultiplierHeadRight * xenonMultiplier * coronaAndLightList.GetTCVehicleIntensityScale(), falloffMax, falloffExponent, leftOn ? coneInnerAngleForBothLightsOn : coneInnerAngleJustOneLightOn, leftOn ? coneOuterAngleForBothLightsOn : coneOuterAngleJustOneLightOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, false, 0.0f, useVolumetricLights, forceLowResVolumetricLights, settings->headLight.textureName.GetHash(), settings->headLight.mirrorTexture); } } else { // Determine the angle of the light. Don't adjust it for the sub. float beamAngle = 0.0f; if ( !bIsSubmarine ) { beamAngle = g_CarAngleSettings; if(CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods) { if(fullBeam) { beamAngle += CHeadlightTuningData::ms_aimFullBeamMod; } else { beamAngle += CHeadlightTuningData::ms_aimDippedBeamMod; } } } // A bit nasty but simulates the same rotation we had before but on the right direction Vector3 dir = Vector3(0.0f, 1.0f, beamAngle); dir.Normalize(); const float cosAngle = dir.y; const float sinAngle = -sqrtf(1.0f - cosAngle*cosAngle); if (true == leftOn && false == rightOn) { Matrix34 mBoneLeft; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif // Only left light is on Vector3 rotatedB(mBoneLeft.b); rotatedB.Scale(cosAngle); rotatedB.AddScaled(mBoneLeft.c, sinAngle); //rotatedB is not perpendicular to mBoneLeft.c anymore //ajust due to the split, so it matches with dual rendering version rotatedB -= g_CarSplitSettings * mBoneLeft.a; rotatedB.Normalize(); mBoneLeft.b.Set(rotatedB); //Compute the proper tangent using existing binormal and updated direction Vector3 rotatedA; rotatedA.Cross(mBoneLeft.b, mBoneLeft.c); rotatedA.Normalize(); //doing a normalize because mBoneLeft.b is not exactly perpendicular to mBoneLeft.c mBoneLeft.a.Set(rotatedA); AddVehicleLight( mBoneLeft.d, mBoneLeft.b, mBoneLeft.a, lightColor, intensity * intensityMultiplierHeadLeft * xenonMultiplier * coronaAndLightList.GetTCVehicleIntensityScale(), falloffMax, falloffExponent, coneInnerAngleJustOneLightOn, coneOuterAngleJustOneLightOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, false, 0.0f, useVolumetricLights, forceLowResVolumetricLights, settings->headLight.textureName.GetHash(), settings->headLight.mirrorTexture); } else if (false == leftOn && true == rightOn) { if(bRightBoneIdxChanged) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxRight, mBoneRight); #else pSkeleton->GetGlobalMtx(boneIdxRight, RC_MAT34V(mBoneRight)); #endif } // Only right light is on Vector3 rotatedB(mBoneRight.b); rotatedB.Scale(cosAngle); rotatedB.AddScaled(mBoneRight.c, sinAngle); //rotatedB is not perpendicular to mBoneRight.c anymore //ajust due to the split, so it matches with dual rendering version rotatedB += g_CarSplitSettings * mBoneRight.a; rotatedB.Normalize(); mBoneRight.b.Set(rotatedB); //Compute the proper tangent using existing binormal and updated direction Vector3 rotatedA; rotatedA.Cross(mBoneRight.b, mBoneRight.c); rotatedA.Normalize(); //doing a normalize because mBoneRight.b is not exactly perpendicular to mBoneRight.c mBoneRight.a.Set(rotatedA); AddVehicleLight( mBoneRight.d, mBoneRight.b, mBoneRight.a, lightColor, intensity * intensityMultiplierHeadRight * xenonMultiplier * coronaAndLightList.GetTCVehicleIntensityScale(), falloffMax, falloffExponent, coneInnerAngleJustOneLightOn, coneOuterAngleJustOneLightOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, false, 0.0f, useVolumetricLights, forceLowResVolumetricLights, settings->headLight.textureName.GetHash(), settings->headLight.mirrorTexture); } else if (true == leftOn && true == rightOn) { // Both lights are on if(bLeftBoneIdxChanged) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif } const Vector3 vMax = CEntity::GetBoundingBoxMax(); Vector3 leftBonePos; Vector3 rightBonePos; GetDefaultBonePositionSimple(boneIdxLeft,leftBonePos); GetDefaultBonePositionSimple(boneIdxRight,rightBonePos); const Vector3 localLightPos = (leftBonePos + rightBonePos) *0.5f; const float distBetweenBoneAndCenterLight = localLightPos.Dist(leftBonePos); Vector3 lightPos(localLightPos.x,vMax.y,localLightPos.z); lightPos = TransformIntoWorldSpace(lightPos); // Picked left bone, could average but will require recalc of average up too Vector3 rotatedB(mBoneLeft.b); rotatedB.Scale(cosAngle); rotatedB.AddScaled(mBoneLeft.c, sinAngle); mBoneLeft.b.Set(rotatedB); //Pull back the position of the light based on the rotation lightPos -= mBoneLeft.b * fFake2LightDisplacementMod; const float fSubmarineLightBoost = 1.65f; const float fExtraMultiplier = (bIsSubmarine ? fSubmarineLightBoost : 1.0f) * xenonMultiplier; // Apply xenon and submarine multipliers (sub mod only need for dual headlights) const float midIntensityMultiplier = (intensityMultiplierHeadLeft + intensityMultiplierHeadRight) * fExtraMultiplier * 0.5f; AddVehicleLight( lightPos, mBoneLeft.b, mBoneLeft.a, lightColor, intensity * midIntensityMultiplier * coronaAndLightList.GetTCVehicleIntensityScale(), falloffMax, falloffExponent, coneInnerAngleForBothLightsOn, coneOuterAngleForBothLightsOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, true, distBetweenBoneAndCenterLight, useVolumetricLights, forceLowResVolumetricLights, settings->headLight.textureName.GetHash(), settings->headLight.mirrorTexture); } } } else if ( boneIdxLeft != -1 && (true == leftOn )) { if(bLeftBoneIdxChanged) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif } if( pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HEADLIGHTS_USE_ACTUAL_BONE_POS) ) { const float beamAngleMod = fullBeam ? CHeadlightTuningData::ms_aimFullBeamAngle : CHeadlightTuningData::ms_aimDippedBeamAngle; if( CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods ) { mBoneLeft.RotateLocalX(beamAngleMod*DtoR); } } else { // probably a bike float beamAngle = g_CarAngleSettings; if(CHeadlightTuningData::ms_useFullBeamOrDippedBeamMods) { if(fullBeam) { beamAngle += CHeadlightTuningData::ms_aimFullBeamMod; } else { beamAngle += CHeadlightTuningData::ms_aimDippedBeamMod; } } // A bit nasty but simulates the same rotation we had before but on the right direction Vector3 dir = Vector3(0.0f, 1.0f, beamAngle); dir.Normalize(); const float cosAngle = dir.y; const float sinAngle = -sqrtf(1.0f - cosAngle*cosAngle); // A bit nasty but simulates the same rotation we had before but on the right direction Vector3 rotatedB(mBoneLeft.b); rotatedB.Scale(cosAngle); rotatedB.AddScaled(mBoneLeft.c, sinAngle); mBoneLeft.b.Set(rotatedB); } AddVehicleLight( mBoneLeft.d, mBoneLeft.b, mBoneLeft.a, lightColor, intensity * intensityMultiplierHeadLeft * xenonMultiplier * coronaAndLightList.GetTCVehicleIntensityScale(), falloffMax, falloffExponent, coneInnerAngleJustOneLightOn, coneOuterAngleJustOneLightOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, false, 0.0f, useVolumetricLights, forceLowResVolumetricLights, settings->headLight.textureName.GetHash(), false); } } } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoExtraLightsEffect(s32 boneId1, s32 boneId2, s32 boneId3, s32 boneId4, bool on1, bool on2, bool on3, bool on4, bool doLight, float fade, float daynightFade, float coronaFade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, const vehicleLightSettings *settings, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList ) #else void CVehicle::DoExtraLightsEffect(s32 boneId1, s32 boneId2, s32 boneId3, s32 boneId4, bool on1, bool on2, bool on3, bool on4, bool doLight, float fade, float daynightFade, float coronaFade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, const vehicleLightSettings *settings, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList ) #endif { Matrix34 boneMtx[4]; s32 boneId[4] = { boneId1, boneId2, boneId3, boneId4 }; s32 boneIdx[4]; boneIdx[0] = pModelInfo->GetBoneIndex(boneId1); boneIdx[1] = pModelInfo->GetBoneIndex(boneId2); boneIdx[2] = pModelInfo->GetBoneIndex(boneId3); boneIdx[3] = pModelInfo->GetBoneIndex(boneId4); const float alphaFadeLOD = this->GetAlpha() / 255.0f; bool lightState[4] = {on1, on2, on3, on4}; CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); const float dist = 0.25f; vehicleCoronaRenderProp coronaProperties; PopulateCommonVehicleCoronaProperties(settings->headLightCorona, coronaFade, alphaFadeLOD, coronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness()); float intensityMult = ( settings->headLight.emmissiveBoost ? g_LightSwitchSettings.emissiveMultiplier : 1.0f ); float xenonMultiplier = 1.f; Vector3 lightColor = VEC3V_TO_VECTOR3(settings->headLight.color.GetRGB()); if (GetVariationInstance().IsToggleModOn(VMT_XENON_LIGHTS)) { const CVehicleVariationGlobalData& varData = CVehicleModelInfo::GetVehicleColours()->GetGlobalVariationData(); const u8 xeonLightColorIdx = this->GetXenonLightColor(); if(xeonLightColorIdx != 0xff) { Assert(xeonLightColorIdx < CVehicleModelInfo::GetVehicleColours()->m_XenonLightColors.GetCount()); const CVehicleXenonLightColor *pXenonLightParams = &CVehicleModelInfo::GetVehicleColours()->m_XenonLightColors[xeonLightColorIdx]; lightColor = VEC3V_TO_VECTOR3(pXenonLightParams->m_lightColor.GetRGB()); coronaProperties.color = pXenonLightParams->m_coronaColor; coronaProperties.intensity *= pXenonLightParams->m_coronaIntensityModifier; xenonMultiplier = pXenonLightParams->m_lightIntensityModifier; } else { lightColor = VEC3V_TO_VECTOR3(varData.m_xenonLightColor.GetRGB()); coronaProperties.color = varData.m_xenonCoronaColor; coronaProperties.intensity *= varData.m_xenonCoronaIntensityModifier; xenonMultiplier = varData.m_xenonLightIntensityModifier; } } for(int i=0;i<4;i++) { CVehicleLightSwitch::LightId lightId = BONEID_TO_LIGHTID(boneId[i]); Assertf(lightId < CVehicleLightSwitch::LW_LIGHTCOUNT,"Wrong light Id(%d) on %s",lightId,pModelInfo->GetModelName()); if (lightState[i] && boneIdx[i] > -1) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdx[i], boneMtx[i]); #else pSkeleton->GetGlobalMtx(boneIdx[i], RC_MAT34V(boneMtx[i])); #endif coronaAndLightList.RegisterCorona(RCC_VEC3V(boneMtx[i].d), coronaProperties.size, coronaProperties.color, coronaProperties.intensity, dist, RCC_VEC3V(matVehicle.b), 1.0f, settings->headLight.innerConeAngle, settings->headLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); pShader->SetLightValue(lightId, g_LightSwitchSettings.GetLightOnValue(lightId,daynightFade) * intensityMult); } else if (lightState[i]) { // if we don't have a valid boneIdx, this is an extra light brought in by a mod Mat34V mat; s32 dummyIndex = -1; #if ENABLE_FRAG_OPTIMIZATION GetExtraLightMatrix((eHierarchyId)boneId[i], mat, dummyIndex, NULL); #else GetExtraLightMatrix(pSkeleton, (eHierarchyId)boneId[i], mat, dummyIndex); #endif //zob if( dummyIndex == -1 ) { lightState[i] = false; } else { boneMtx[i] = MAT34V_TO_MATRIX34(mat); coronaAndLightList.RegisterCorona(mat.GetCol3(), coronaProperties.size, coronaProperties.color, coronaProperties.intensity, dist, RCC_VEC3V(matVehicle.b), 1.0f, settings->headLight.innerConeAngle, settings->headLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } pShader->SetLightValue(lightId, g_LightSwitchSettings.GetLightOnValue(lightId,daynightFade) * intensityMult); } else { pShader->SetLightValue(lightId, g_LightSwitchSettings.GetLightOffValue(lightId,daynightFade) * intensityMult); } } if( doLight ) { // Note that making just the player's veh have bigger headlights shouldn't really // affect rendering at all since those lights are already almost full screen. // I.e. it won't change the number of pixel visited when calculating the players // headlights affect on the scene. u8 castShadow = NO_HEADLIGHT_SHADOWS; #if RSG_PC if (IsDriverAPlayer() || m_usePlayerLightSettings) { if (CSettingsManager::GetInstance().GetSettings().m_graphics.m_ShadowQuality == CSettings::Ultra) { castShadow = HEADLIGHTS_CAST_FULL_SHADOWS; } else if (CSettingsManager::GetInstance().GetSettings().m_graphics.m_ShadowQuality == CSettings::High) { castShadow = HEADLIGHTS_CAST_STATIC_SHADOWS; } } #endif // Determine the intensity and distance multiplier. float headlightIntensityMult = 1.0f * CHeadlightTuningData::ms_globalHeadlightIntensityMult; float headlightDistanceMult = 1.0f * CHeadlightTuningData::ms_globalHeadlightDistMult; { if(CHeadlightTuningData::ms_makeHeadlightsCastFurtherForPlayer && GetDriver() && GetDriver()->IsLocalPlayer()) { headlightIntensityMult *= CHeadlightTuningData::ms_playerHeadlightIntensityMult; headlightDistanceMult *= CHeadlightTuningData::ms_playerHeadlightDistMult; } } const float intensity = (m_HeadlightMultiplier * fade* settings->headLight.intensity) * headlightIntensityMult * coronaAndLightList.GetTCVehicleIntensityScale() * xenonMultiplier; const float falloffMax = (10.0f + ((IsDriverAPlayer() || m_usePlayerLightSettings == true)?settings->headLight.falloffMax:0.0f)) * headlightDistanceMult; const float falloffExponent = settings->headLight.falloffExponent; const float coneInnerAngleJustOneLightOn = settings->headLight.innerConeAngle * CHeadlightTuningData::ms_globalConeInnerAngleMod * CHeadlightTuningData::ms_globalOnlyOneLightMod; const float coneOuterAngleJustOneLightOn = settings->headLight.outerConeAngle * CHeadlightTuningData::ms_globalConeOuterAngleMod * CHeadlightTuningData::ms_globalOnlyOneLightMod; bool needLight[2] = {false,false}; Matrix34 lightMtx[2]; if( HasDoubleExtraLights() ) { // up to 4 lights: 0+1 and 2+3 for(int i=0;i<2;i++) { int idx0 = i * 2; int idx1 = idx0 + 1; if( lightState[idx0] && lightState[idx1] ) { needLight[i] = true; lightMtx[i] = boneMtx[idx0]; lightMtx[i].Add(boneMtx[idx1]); lightMtx[i].Normalize(); lightMtx[i].d *= 0.5f; } else if( lightState[idx0] ) { needLight[0] = true; lightMtx[0] = boneMtx[idx0]; } else if( lightState[idx1] ) { needLight[0] = true; lightMtx[0] = boneMtx[idx1]; } } } else { int addedLight = 0; for(int i=0;i<4;i++) { if( addedLight < 2 && lightState[i] ) { needLight[addedLight] = true; lightMtx[addedLight] = boneMtx[i]; addedLight++; } } } for(int i=0;i<2;i++) { if( needLight[i] ) { int shadowTrackingID = (int) fwIdKeyGenerator::Get(this, boneId1 + i); //Don't treat this as a tail light as spec highlights is disable for them // We still want spec highlights for this one AddVehicleLight( lightMtx[i].d, lightMtx[i].b, lightMtx[i].a, lightColor, intensity, falloffMax, falloffExponent, coneInnerAngleJustOneLightOn, coneOuterAngleJustOneLightOn, interiorLocation, true, coronaAndLightList, this, castShadow, shadowTrackingID, false, 0.0f, false, false, settings->headLight.textureName.GetHash()); } } } } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoTailLightsEffect(s32 boneIdLeft, s32 boneIdMiddle, s32 boneIdRight, bool leftOn, bool middleOn, bool rightOn, float fade, float daynightFade, float coronaFade, bool noCorona, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, bool isBrakes, const vehicleLightSettings *settings, float intensityMultiplierTailLeft, float intensityMultiplierTailRight, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoTailLightsEffect(s32 boneIdLeft, s32 boneIdMiddle, s32 boneIdRight, bool leftOn, bool middleOn, bool rightOn, float fade, float daynightFade, float coronaFade, bool noCorona, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, bool isBrakes, const vehicleLightSettings *settings, float intensityMultiplierTailLeft, float intensityMultiplierTailRight, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #endif { const float alphaFadeLOD = this->GetAlpha() / 255.0f; // Recessed lights have restricted visibility, this causes them to fade out quicker based on view angle. See B*794858 for list of cars that need this. static dev_float recessedCoronaDirThreshold = 0.85f; bool bUseRecessedCorona = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_RECESSED_TAILLIGHT_CORONAS) BANK_ONLY(|| CHeadlightTuningData::ms_globalForceRecessedCorona); const float coronaDirThreshold = bUseRecessedCorona ? recessedCoronaDirThreshold : 1.0f; Matrix34 mBoneLeft, mBoneMiddle, mBoneRight; s32 boneIdxLeft = pModelInfo->GetBoneIndex(boneIdLeft); s32 boneIdxMiddle = -1; s32 boneIdxRight = pModelInfo->GetBoneIndex(boneIdRight); if (boneIdMiddle > -1) { boneIdxMiddle = pModelInfo->GetBoneIndex(boneIdMiddle); } CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); vehicleCoronaRenderProp coronaProperties; PopulateCommonVehicleCoronaProperties(settings->tailLightCorona, coronaFade, alphaFadeLOD, coronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness()); const float intensityMult = ( settings->tailLight.emmissiveBoost ? g_LightSwitchSettings.emissiveMultiplier : 1.0f ); float pullDistance = 0.0f; if (coronaProperties.pullCoronaIn) { if (GetLodData().GetLodType() == LODTYPES_DEPTH_SLOD3 || GetLodData().GetLodType() == LODTYPES_DEPTH_SLOD4) { pullDistance = 1.0f; } } if (boneIdxLeft>-1) { const CVehicleLightSwitch::LightId lightBone = BONEID_TO_LIGHTID(boneIdLeft); if (leftOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif Matrix34 localBone = mBoneLeft; localBone.RotateLocalX( coronaProperties.xRotation ); localBone.RotateLocalY( coronaProperties.yRotation ); localBone.RotateLocalZ( coronaProperties.zRotation ); Vector3 forward = -localBone.b; // loop through and add all the coronas to the headlights (there could be multiple coronas for LED style lights) if( !noCorona ) { for (u32 i = 0; i < coronaProperties.numCoronas; i++) { Vector3 position = localBone.d; position = position + (coronaProperties.halfWidth * -localBone.a) + ((float)i * localBone.a * coronaProperties.distBetween); position += (pullDistance * -forward); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size, coronaProperties.color, coronaProperties.intensity * intensityMultiplierTailLeft, coronaProperties.zBias, RCC_VEC3V(forward), coronaDirThreshold, settings->tailLight.innerConeAngle, settings->tailLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } } pShader->SetLightValue(lightBone, g_LightSwitchSettings.GetLightOnValue(lightBone,daynightFade) * intensityMult); } else { pShader->SetLightValue(lightBone, g_LightSwitchSettings.GetLightOffValue(lightBone,daynightFade) * intensityMult); } } if (boneIdxMiddle>-1) { // Only for brakes ? if (middleOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxMiddle, mBoneMiddle); #else pSkeleton->GetGlobalMtx(boneIdxMiddle, RC_MAT34V(mBoneMiddle)); #endif Matrix34 localBone = mBoneMiddle; vehicleCoronaRenderProp midCoronaProperties; PopulateCommonVehicleCoronaProperties(settings->tailLightMiddleCorona, coronaFade, alphaFadeLOD, midCoronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness()); localBone.RotateLocalX( midCoronaProperties.xRotation ); localBone.RotateLocalY( midCoronaProperties.yRotation ); localBone.RotateLocalZ( midCoronaProperties.zRotation ); Vector3 forward = -localBone.b; if( !noCorona ) { // loop through and add all the coronas to the headlights (there could be multiple coronas for LED style lights) for (u32 i = 0; i < midCoronaProperties.numCoronas; i++) { Vector3 position = localBone.d; position = position + (midCoronaProperties.halfWidth * -localBone.a) + ((float)i * localBone.a * midCoronaProperties.distBetween); if (midCoronaProperties.pullCoronaIn) { position += (pullDistance * -forward); } coronaAndLightList.RegisterCorona(RCC_VEC3V(position), midCoronaProperties.size, midCoronaProperties.color, midCoronaProperties.intensity, midCoronaProperties.zBias, RCC_VEC3V(forward), coronaDirThreshold, settings->tailLight.innerConeAngle, settings->tailLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } } pShader->SetLightValue(BONEID_TO_LIGHTID(boneIdMiddle), g_LightSwitchSettings.GetLightOnValue(BONEID_TO_LIGHTID(boneIdMiddle),daynightFade) * intensityMult); } else { pShader->SetLightValue(BONEID_TO_LIGHTID(boneIdMiddle), g_LightSwitchSettings.GetLightOffValue(BONEID_TO_LIGHTID(boneIdMiddle),daynightFade) * intensityMult); } } if (boneIdxRight>-1) { const CVehicleLightSwitch::LightId lightBone = BONEID_TO_LIGHTID(boneIdRight); if (rightOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxRight, mBoneRight); #else pSkeleton->GetGlobalMtx(boneIdxRight, RC_MAT34V(mBoneRight)); #endif Matrix34 localBone = mBoneRight; localBone.RotateLocalX( -coronaProperties.xRotation ); localBone.RotateLocalY( -coronaProperties.yRotation ); localBone.RotateLocalZ( -coronaProperties.zRotation ); Vector3 forward = -localBone.b; if( !noCorona ) { // loop through and add all the coronas to the headlights (there could be multiple coronas for LED style lights) for (u32 i = 0; i < coronaProperties.numCoronas; i++) { Vector3 position = localBone.d; position = position + (coronaProperties.halfWidth * -localBone.a) + ((float)i * localBone.a * coronaProperties.distBetween); position += (pullDistance * -forward); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size, coronaProperties.color, coronaProperties.intensity * intensityMultiplierTailRight, coronaProperties.zBias, RCC_VEC3V(forward), coronaDirThreshold, settings->tailLight.innerConeAngle, settings->tailLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } } pShader->SetLightValue(lightBone, g_LightSwitchSettings.GetLightOnValue(lightBone,daynightFade) * intensityMult); } else { pShader->SetLightValue(lightBone, g_LightSwitchSettings.GetLightOffValue(lightBone,daynightFade) * intensityMult); } } if (fade < 0.001f) { return; } // Now deal with actual light casting if (boneIdxLeft>-1 && boneIdxRight>-1) { const float lightStrength = fade * ((true == isBrakes) ? BrakeLightScaler : TailLightScaler); if (true == leftOn && false == rightOn) { // Only left light is on AddVehicleLight(matVehicle, mBoneLeft.d, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->tailLight.color.GetRGB()), lightStrength * settings->tailLight.intensity * intensityMultiplierTailLeft, settings->tailLight.falloffMax, settings->tailLight.falloffExponent, settings->tailLight.innerConeAngle * 0.6f, settings->tailLight.outerConeAngle * 0.6f, interiorLocation, false, coronaAndLightList, this); } else if (true == rightOn && false == leftOn) { // Only right light is on AddVehicleLight(matVehicle, mBoneRight.d, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->tailLight.color.GetRGB()), lightStrength * settings->tailLight.intensity * intensityMultiplierTailRight, settings->tailLight.falloffMax, settings->tailLight.falloffExponent, settings->tailLight.innerConeAngle * 0.6f, settings->tailLight.outerConeAngle * 0.6f, interiorLocation, false, coronaAndLightList, this); } else if (true == rightOn && true == leftOn) { // Both lights are on const Vector3 vMin = GetBoundingBoxMin(); Vector3 leftBonePos; Vector3 rightBonePos; GetDefaultBonePositionSimple(boneIdxLeft,leftBonePos); GetDefaultBonePositionSimple(boneIdxRight,rightBonePos); const Vector3 localLightPos = (leftBonePos + rightBonePos) *0.5f; const float distBetweenBoneAndCenterLight = localLightPos.Dist(leftBonePos); Vector3 lightPos(localLightPos.x,vMin.y,localLightPos.z); lightPos = TransformIntoWorldSpace(lightPos); const float midIntensityMultiplier = (intensityMultiplierTailLeft + intensityMultiplierTailRight) / 2.f; AddVehicleLight(matVehicle, lightPos, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->tailLight.color.GetRGB()), lightStrength * settings->tailLight.intensity * midIntensityMultiplier, settings->tailLight.falloffMax, settings->tailLight.falloffExponent, settings->tailLight.innerConeAngle, settings->tailLight.outerConeAngle, interiorLocation, false, coronaAndLightList, this, NO_HEADLIGHT_SHADOWS, 0, true, distBetweenBoneAndCenterLight); } } else if ( boneIdxLeft != -1 && (true == leftOn )) { // probably a bike const float lightStrength = fade * ((true == isBrakes) ? BrakeLightScaler : 1.0f); AddVehicleLight(matVehicle, mBoneLeft.d, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->tailLight.color.GetRGB()), lightStrength * settings->tailLight.intensity, settings->tailLight.falloffMax, settings->tailLight.falloffExponent, settings->tailLight.innerConeAngle * 0.6f, settings->tailLight.outerConeAngle * 0.6f, interiorLocation, false, coronaAndLightList, this); } } void CVehicle::DoAdditionalLightsEffect(s32 lightId, float fade, const ConfigLightSettings* lightConfig, fwInteriorLocation interiorLoc, bool restrictRadius, CoronaAndLightList& coronaAndLightList) { if (fade < 0.0001f) return; CVehicleModelInfo* pModelInfo = GetVehicleModelInfo(); s32 boneIndex = pModelInfo->GetBoneIndex(lightId); if (boneIndex == -1) return; Matrix34 mBone; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIndex, mBone); #else crSkeleton* pSkeleton = GetSkeleton(); pSkeleton->GetGlobalMtx(boneIndex, RC_MAT34V(mBone)); #endif float radius = lightConfig->radius; Vector3 lightPos = mBone.d; if (restrictRadius) { float lowerBound = 0.f; const int iBoneIndexChassisLowLod = GetBoneIndex(VEH_CHASSIS_LOWLOD); if (iBoneIndexChassisLowLod != -1) { int iComponent = GetVehicleFragInst()->GetComponentFromBoneIndex(iBoneIndexChassisLowLod); if (iComponent > -1) { const phBoundComposite* pBoundCompOrig = static_cast(GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds()); lowerBound = pBoundCompOrig->GetBound(iComponent)->GetBoundingBoxMin().GetZf(); } } else { lowerBound = GetBoundingBoxMin().z; } const float boneOffset = lightPos.z - (GetTransform().GetPosition().GetZf()); float newSchoolradius = boneOffset + Abs(lowerBound); radius = rage::Abs(rage::Min(lightConfig->radius, newSchoolradius * 0.95f)); } int lightFlags = LIGHTFLAG_VEHICLE; if (CutSceneManager::GetInstance()->IsCutscenePlayingBack()) { // performance optimization for cutscenes lightFlags |= LIGHTFLAG_DONT_LIGHT_ALPHA; } if (lightId == VEH_DASHLIGHT) { lightFlags |= LIGHTFLAG_NO_SPECULAR; } Vector3 lightDir = (lightId == VEH_DASHLIGHT) ? -mBone.b : -mBone.c; CLightSource light(LIGHT_TYPE_SPOT, lightFlags, lightPos, RCC_VECTOR3(lightConfig->colour), lightConfig->intensity * fade, LIGHT_ALWAYS_ON); light.SetDirTangent(lightDir, mBone.a); light.SetRadius(radius); light.SetSpotlight(lightConfig->innerConeAngle, lightConfig->outerConeAngle); light.SetFalloffExponent(lightConfig->falloffExp); light.SetInInterior(interiorLoc); if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoReversingLightsEffect(s32 boneIdLeft, s32 boneIdRight, bool leftOn, bool rightOn, float fade, float daynightFade, float coronaFade, bool noCorona, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, const vehicleLightSettings *settings, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoReversingLightsEffect(s32 boneIdLeft, s32 boneIdRight, bool leftOn, bool rightOn, float fade, float daynightFade, float coronaFade, bool noCorona, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, const vehicleLightSettings *settings, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #endif { Matrix34 mBoneLeft, mBoneRight; s32 boneIdxLeft = pModelInfo->GetBoneIndex(boneIdLeft); s32 boneIdxRight = pModelInfo->GetBoneIndex(boneIdRight); CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); const float intensityMult = ( settings->reversingLight.emmissiveBoost ? g_LightSwitchSettings.emissiveMultiplier : 1.0f ); if (boneIdxLeft>-1) { if ( leftOn ) { if(!rightOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif } pShader->SetLightValue( BONEID_TO_LIGHTID(boneIdLeft), g_LightSwitchSettings.GetLightOnValue(BONEID_TO_LIGHTID(boneIdLeft),daynightFade) * intensityMult); } else { pShader->SetLightValue( BONEID_TO_LIGHTID(boneIdLeft), g_LightSwitchSettings.GetLightOffValue(BONEID_TO_LIGHTID(boneIdLeft),daynightFade) * intensityMult); } } if (boneIdxRight>-1) { if ( rightOn ) { if(!leftOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxRight, mBoneRight); #else pSkeleton->GetGlobalMtx(boneIdxRight, RC_MAT34V(mBoneRight)); #endif } pShader->SetLightValue( BONEID_TO_LIGHTID(boneIdRight), g_LightSwitchSettings.GetLightOnValue(BONEID_TO_LIGHTID(boneIdRight),daynightFade) * intensityMult); } else { pShader->SetLightValue( BONEID_TO_LIGHTID(boneIdRight), g_LightSwitchSettings.GetLightOffValue(BONEID_TO_LIGHTID(boneIdRight),daynightFade) * intensityMult); } } // Add a corona for the reversing lights vehicleCoronaRenderProp coronaProperties; PopulateCommonVehicleCoronaProperties(settings->reversingLightCorona, coronaFade, 1.0f, coronaProperties, coronaAndLightList.GetTCSpriteSize(), coronaAndLightList.GetTCSpriteBrightness()); static dev_float recessedCoronaDirThreshold = 0.65f; bool bUseRecessedCorona = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_RECESSED_TAILLIGHT_CORONAS) BANK_ONLY(|| CHeadlightTuningData::ms_globalForceRecessedCorona); const float coronaDirThreshold = bUseRecessedCorona ? recessedCoronaDirThreshold : 1.0f; float pullDistance = 0.0f; if (coronaProperties.pullCoronaIn) { if (GetLodData().GetLodType() == LODTYPES_DEPTH_SLOD3 || GetLodData().GetLodType() == LODTYPES_DEPTH_SLOD4) { pullDistance = 1.0f; } } // Coronas if( !noCorona ) { if (boneIdxLeft>-1) { if (leftOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxLeft, mBoneLeft); #else pSkeleton->GetGlobalMtx(boneIdxLeft, RC_MAT34V(mBoneLeft)); #endif Vector3 position = mBoneLeft.d; Vector3 forward = -mBoneLeft.b; position += (pullDistance * -forward); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size, coronaProperties.color, coronaProperties.intensity, coronaProperties.zBias, RCC_VEC3V(forward), coronaDirThreshold, settings->reversingLight.innerConeAngle, settings->reversingLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } } if (boneIdxRight>-1) { if (rightOn) { #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdxRight, mBoneRight); #else pSkeleton->GetGlobalMtx(boneIdxRight, RC_MAT34V(mBoneRight)); #endif Vector3 position = mBoneRight.d; Vector3 forward = -mBoneLeft.b; position += (pullDistance * -forward); coronaAndLightList.RegisterCorona(RCC_VEC3V(position), coronaProperties.size, coronaProperties.color, coronaProperties.intensity, coronaProperties.zBias, RCC_VEC3V(forward), coronaDirThreshold, settings->reversingLight.innerConeAngle, settings->reversingLight.outerConeAngle, CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION); } } } if(fade < 0.001f) { return; } // Now deal with actual light casting if (boneIdxLeft>-1 && boneIdxRight>-1) { if (true == leftOn && false == rightOn) { // Only left light is on AddVehicleLight(matVehicle, mBoneLeft.d, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->reversingLight.color.GetRGB()), fade * settings->reversingLight.intensity, settings->reversingLight.falloffMax, settings->reversingLight.falloffExponent, settings->reversingLight.innerConeAngle, settings->reversingLight.outerConeAngle, interiorLocation, false, coronaAndLightList, this); } else if (false == leftOn && true == rightOn) { // Only right light is on AddVehicleLight(matVehicle, mBoneRight.d, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->reversingLight.color.GetRGB()), fade * settings->reversingLight.intensity, settings->reversingLight.falloffMax, settings->reversingLight.falloffExponent, settings->reversingLight.innerConeAngle, settings->reversingLight.outerConeAngle, interiorLocation, false, coronaAndLightList, this); } else if (true == leftOn && true == rightOn) { // Both lights are on const Vector3 vMin = GetBoundingBoxMin(); Vector3 leftBonePos; Vector3 rightBonePos; GetDefaultBonePositionSimple(boneIdxLeft,leftBonePos); GetDefaultBonePositionSimple(boneIdxRight,rightBonePos); const Vector3 localLightPos = (leftBonePos + rightBonePos) *0.5f; const float distBetweenBoneAndCenterLight = localLightPos.Dist(leftBonePos); Vector3 lightPos(localLightPos.x,vMin.y,localLightPos.z); lightPos = TransformIntoWorldSpace(lightPos); AddVehicleLight(matVehicle, lightPos, Vector3(0.0f, -1.0f, 0.0f), VEC3V_TO_VECTOR3(settings->reversingLight.color.GetRGB()), fade * settings->reversingLight.intensity, settings->reversingLight.falloffMax, settings->reversingLight.falloffExponent, settings->reversingLight.innerConeAngle, settings->reversingLight.outerConeAngle, interiorLocation, false, coronaAndLightList, this, NO_HEADLIGHT_SHADOWS, 0, true, distBetweenBoneAndCenterLight); } } } class sirenLightAccumulator { public: sirenLightAccumulator() { pos.ZeroComponents(); color.ZeroComponents(); intensity = 0.0f; count = 0; fCount = 0.0f; } void Add(Vec3V_In p, Vec3V_In c, float i) { pos += p; color += c; intensity += i; count++; fCount++; } Vec3V pos; Vec3V color; float intensity; int count; float fCount; }; #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoSirenLightsEffect(float fade, float daynightFade, float coronaFade, bool noActualLights, const CVehicleModelInfo* const pModelInfo, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoSirenLightsEffect(float fade, float daynightFade, float coronaFade, bool noActualLights, const CVehicleModelInfo* const pModelInfo, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #endif { #if ENABLE_FRAG_OPTIMIZATION GiveFragCacheEntry(true); crSkeleton* pSkeleton = GetSkeleton(); #endif CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); Assert(true == UsesSiren()); Assert(m_sirenData); const sirenSettings *settings = pModelInfo->GetSirenSettings(); Assert(settings); const u32 modCurrentTime = fwTimer::GetTimeInMilliseconds() + GetRandomSeed(); m_sirenData->sirenTimeDelta = (modCurrentTime - m_sirenData->sirenOnTime) * settings->timeMultiplier; const u32 maxBeats = sizeof(settings->sirens[0].rotation.sequencer) * 8; const float beatDuration = 60000.f / settings->sequencerBpm; const s32 currentBeat = (s32)(m_sirenData->sirenTimeDelta / beatDuration); m_sirenData->lastSirenBeat = currentBeat; u32 currentBit = currentBeat % maxBeats; // bit index currentBit = 1 << (maxBeats - 1 - currentBit); // actual bit, reversed so first bit is msb const float TCFade = coronaAndLightList.GetTCSpriteBrightness() * coronaFade; const float TCScaler = coronaAndLightList.GetTCVehicleIntensityScale(); sirenLightAccumulator lights[MAX_LIGHT_GROUPS]; // head and tail lights u32 lightSequencers[MAX_SIREN_LIGHTS] = { settings->leftHeadLight.sequencer, settings->rightHeadLight.sequencer, settings->leftTailLight.sequencer, settings->rightTailLight.sequencer }; u32 lightMultiples[MAX_SIREN_LIGHTS] = { settings->leftHeadLightMultiples, settings->rightHeadLightMultiples, settings->leftTailLightMultiples, settings->rightTailLightMultiples }; for (s32 i = 0; i < MAX_SIREN_LIGHTS; ++i) { if (lightSequencers[i] != 0) { if (m_sirenData->IsSirenLightOn(i) || ((lightSequencers[i] & currentBit) && m_sirenData->lastSirenLightBit[i] != currentBit)) { // keep track of when the light turned on if ((lightSequencers[i] & currentBit) && m_sirenData->lastSirenLightBit[i] != currentBit) { m_sirenData->lastSirenLightBit[i] = currentBit; m_sirenData->sirenLightStart[i] = modCurrentTime; } const float lightSpeed = 30.f * lightMultiples[i]; const float rotation = ((modCurrentTime - m_sirenData->sirenLightStart[i]) * settings->timeMultiplier) * (TWO_PI / 10000.0f); float angleRota = rotation * lightSpeed; // stop rotating after one turn or if synced to bpm after multiple turns const u32 numTurns = lightMultiples[i]; const bool isOn = angleRota <= (TWO_PI * numTurns); m_sirenData->SetIsLightOn(i, isOn); const float multiplier = rage::Max(0.0f,rage::Abs(rage::Sinf(angleRota*0.5f)) - 0.5f); m_sirenData->SetLightIntensityMultiplier(i, multiplier); } } } for (s32 boneId = VEH_SIREN_1, idx = 0; boneId <= VEH_SIREN_MAX; boneId++,idx++) { if( false == m_VehicleDamage.GetSirenState(boneId) ) { const s32 boneIdx = pModelInfo->GetBoneIndex(boneId); if (boneIdx > -1) { const sirenLight &curSiren = settings->sirens[idx]; const sirenCorona &curCorona = curSiren.corona; const sirenRotation &curRotation = curSiren.rotation; const Color32 color = curSiren.color; const float coronaPull = curCorona.pull; // rotate only if we have an active rotation or a new beat triggered a new rotation float angleRota = curRotation.start; if( curSiren.rotate ) { if (m_sirenData->IsSirenRotating(idx) || ((curRotation.sequencer & currentBit) && m_sirenData->lastSirenRotationBit[idx] != currentBit)) { // keep track of when the rotation was started if ((curRotation.sequencer & currentBit) && m_sirenData->lastSirenRotationBit[idx] != currentBit) { m_sirenData->lastSirenRotationBit[idx] = currentBit; m_sirenData->sirenRotationStart[idx] = modCurrentTime; } float speedRota = curRotation.speed; if (curRotation.syncToBpm) { speedRota = ((TWO_PI / beatDuration) / (TWO_PI / 10000.0f)) * curRotation.multiples; } const float deltaRota = curRotation.delta; const bool rotationDirectionRota = curRotation.direction; // rotation dependent on time, will be 0 same frame as a new rotation is triggered const float rotation = ((modCurrentTime - m_sirenData->sirenRotationStart[idx]) * settings->timeMultiplier) * (TWO_PI / 10000.0f); if(rotationDirectionRota) { angleRota += - rotation * speedRota + (MAX_SIRENS - idx) * deltaRota; } else { angleRota += rotation * speedRota + idx * deltaRota; } // stop rotating after one turn or if synced to bpm after multiple turns const u32 numTurns = curRotation.syncToBpm ? curRotation.multiples : 1; if (rotationDirectionRota) { const bool isRotating = angleRota >= (curRotation.start - (TWO_PI * numTurns) + (MAX_SIRENS - idx) * deltaRota); m_sirenData->SetIsRotating(idx, isRotating); } else { const bool isRotating = angleRota <= (curRotation.start + (TWO_PI * numTurns) + idx * deltaRota); m_sirenData->SetIsRotating(idx, isRotating); } if (m_sirenData->IsSirenRotating(idx)) { Matrix34 &localMtx = RC_MATRIX34(pSkeleton->GetLocalMtx(boneIdx)); localMtx.MakeRotateZ(angleRota); } } } float coronaIntensityMultiplier = (curSiren.rotate && m_sirenData->IsSirenRotating(idx)) ? 1.f : 0.f; const sirenRotation &curFlashiness = curSiren.flashiness; if (curSiren.flash) { if( m_sirenData->IsSirenFlashing(idx) || ((curFlashiness.sequencer & currentBit) && m_sirenData->lastSirenFlashBit[idx] != currentBit)) { // keep track of when the flashing was started if ((curFlashiness.sequencer & currentBit) && m_sirenData->lastSirenFlashBit[idx] != currentBit) { m_sirenData->lastSirenFlashBit[idx] = currentBit; m_sirenData->sirenFlashStart[idx] = modCurrentTime; } // rotation dependent on time, will be 0 same frame as a new flash is triggered const float rotation = ((modCurrentTime - m_sirenData->sirenFlashStart[idx]) * settings->timeMultiplier) * (TWO_PI / 10000.0f); float speedFlash = curFlashiness.speed; if (curFlashiness.syncToBpm) { speedFlash = ((TWO_PI / beatDuration) / (TWO_PI / 10000.0f)) * curFlashiness.multiples; } // Use separate values for intensity. float angleFlash = curFlashiness.start; const float deltaFlash = curFlashiness.delta; const bool rotationDirectionFlash = curFlashiness.direction; if(rotationDirectionFlash) { angleFlash += - rotation * speedFlash + (MAX_SIRENS - idx) * deltaFlash; } else { angleFlash += rotation * speedFlash + idx * deltaFlash; } coronaIntensityMultiplier = rage::Max(0.0f,rage::Abs(rage::Sinf(angleFlash*0.5f)) - 0.5f); // stop rotating after one turn or if synced to bpm after multiple turns const u32 numTurns = curFlashiness.syncToBpm ? curFlashiness.multiples : 1; if (rotationDirectionFlash) { const bool isFlashing = angleFlash >= (curFlashiness.start - (TWO_PI * numTurns) + (MAX_SIRENS - idx) * deltaFlash); m_sirenData->SetIsFlashing(idx, isFlashing); } else { const bool isFlashing = angleFlash <= (curFlashiness.start + (TWO_PI * numTurns) + idx * deltaFlash); m_sirenData->SetIsFlashing(idx, isFlashing); } if (m_sirenData->IsSirenFlashing(idx) && !m_sirenData->IsSirenRotating(idx)) { Matrix34 &localMtx = RC_MATRIX34(pSkeleton->GetLocalMtx(boneIdx)); localMtx.MakeRotateZ(curFlashiness.delta); } } } const float coronaIntensity = TCFade * curCorona.intensity * coronaIntensityMultiplier; Matrix34 worldMtx; pSkeleton->PartialUpdate(boneIdx); pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(worldMtx)); if( curSiren.scale ) { if( curSiren.scaleFactor == 0 ) // Old School { if( !m_sirenData->IsSirenFlashing(idx) ) {// Turned off, clear it. Matrix34 mat; mat.Zero(); mat.d = worldMtx.d; pSkeleton->SetGlobalMtx(boneIdx, RCC_MAT34V(mat)); } } else { if( m_sirenData->IsSirenFlashing(idx) ) {// Turned on, scale it. Matrix34 mat = worldMtx; float scaleFactor = (float)curSiren.scaleFactor; mat.Scale(scaleFactor); pSkeleton->SetGlobalMtx(boneIdx, RCC_MAT34V(mat)); } } } const float size = curCorona.size * coronaAndLightList.GetTCSpriteSize(); u8 coronaFlags = CORONA_DONT_REFLECT; coronaFlags |= (curCorona.faceCamera) ? 0 : CORONA_HAS_DIRECTION; coronaAndLightList.RegisterCorona(RCC_VEC3V(worldMtx.d), size, color, coronaIntensity, coronaPull, RCC_VEC3V(worldMtx.b), 1.0f, CORONAS_DEF_DIR_LIGHT_CONE_ANGLE_INNER, CORONAS_DEF_DIR_LIGHT_CONE_ANGLE_OUTER, coronaFlags); const float lightIntensity = fade * curSiren.intensity * TCScaler; if( noActualLights == false && curSiren.light && (m_sirenData->IsSirenRotating(idx) || m_sirenData->IsSirenFlashing(idx)) ) { if( settings->useRealLights ) { const Vector3 lightPos = worldMtx.d; const Vector3 worldDir = worldMtx.b; Vector3 worldTan; worldMtx.Transform3x3(Vector3(0,0,1), worldTan); Vec3V wd = VECTOR3_TO_VEC3V(worldDir); wd.SetW(ScalarV(V_ZERO)); if ( !IsTrueAll( wd == Vec3V(V_ZERO)) ) { u32 textureHash = settings->textureName.GetHash(); int lightFlag = LIGHTFLAG_VEHICLE; if( m_nVehicleFlags.GetSirenUseNoSpec() ) { lightFlag |= LIGHTFLAG_NO_SPECULAR; } if(curSiren.castShadows && sm_bEnableSirenShadows) { lightFlag |= LIGHTFLAG_CAST_SHADOWS|LIGHTFLAG_CAST_DYNAMIC_GEOM_SHADOWS|LIGHTFLAG_CAST_STATIC_GEOM_SHADOWS|LIGHTFLAG_MOVING_LIGHT_SOURCE; } if(textureHash != 0) { lightFlag |= LIGHTFLAG_TEXTURE_PROJECTION; } int shadowTrackingID = (int) fwIdKeyGenerator::Get(this, idx + 5); if(curSiren.spotLight) { CLightSource light(LIGHT_TYPE_SPOT, lightFlag, lightPos, VEC3V_TO_VECTOR3(color.GetRGB()), lightIntensity, LIGHT_ALWAYS_ON); light.SetDirTangent(Vector3(0,0,-1), Vector3(0,1,0)); light.SetRadius(settings->lightFalloffMax); light.SetDirTangent(worldDir, worldTan); light.SetSpotlight(settings->lightInnerConeAngle, settings->lightOuterConeAngle); light.SetFalloffExponent(settings->lightFalloffExponent); light.SetInInterior(interiorLocation); light.SetSpecularFadeDistance((u8)(ms_sirenSpecularFade & 0xff)); light.SetShadowFadeDistance((u8)ms_shadowFade & 0xff); light.SetShadowTrackingId(shadowTrackingID); if(textureHash != 0) { light.SetTexture(textureHash, CShaderLib::GetTxdId() ASSERT_ONLY(, settings->textureName.GetCStr())); } if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } else { CLightSource light(LIGHT_TYPE_POINT, lightFlag, lightPos, VEC3V_TO_VECTOR3(color.GetRGB()), lightIntensity, LIGHT_ALWAYS_ON); light.SetDirTangent(Vector3(0,0,-1), Vector3(0,1,0)); light.SetRadius(settings->lightFalloffMax); light.SetDirTangent(worldDir, worldTan); light.SetSpotlight(settings->lightInnerConeAngle, settings->lightOuterConeAngle); light.SetFalloffExponent(settings->lightFalloffExponent); light.SetInInterior(interiorLocation); light.SetSpecularFadeDistance((u8)(ms_sirenSpecularFade & 0xff)); light.SetShadowFadeDistance((u8)ms_shadowFade & 0xff); light.SetShadowTrackingId(shadowTrackingID); if(textureHash != 0) { light.SetTexture(textureHash, CShaderLib::GetTxdId()); } if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } } } else { float intensity = (curSiren.rotate && m_sirenData->IsSirenRotating(idx)) ? lightIntensity * rage::Max(0.0f,rage::Abs(rage::Sinf(angleRota*0.5f)) - 0.5f) : lightIntensity; lights[curSiren.lightGroup].Add(RCC_VEC3V(worldMtx.d),color.GetRGBA().GetXYZ(),intensity); } } } } } if( noActualLights == false && false == settings->useRealLights ) { for(int i=0;i 0) { float ooCount = 1.0f/lights[i].fCount; Vector3 lightPos = RCC_VECTOR3(lights[i].pos)*ooCount + VEC3V_TO_VECTOR3(GetTransform().GetUp()) * settings->lightOffset; Vector3 lightCol = RCC_VECTOR3(lights[i].color)*ooCount; float lightIntensity = lights[i].intensity*ooCount * TCScaler; CLightSource light( LIGHT_TYPE_POINT, LIGHTFLAG_VEHICLE | LIGHTFLAG_NO_SPECULAR, lightPos, lightCol, lightIntensity, LIGHT_ALWAYS_ON); light.SetDirTangent(Vector3(0,0,-1), Vector3(0,1,0)); light.SetRadius(settings->lightFalloffMax); light.SetInInterior(interiorLocation); if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } } } pShader->SetLightValue(CVehicleLightSwitch::LW_SIRENLIGHT, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_SIRENLIGHT, daynightFade)); } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoInteriorLightEffect(s32 boneID, float fade, CVehicleModelInfo* pModelInfo, fwInteriorLocation interiorLocation, bool isHeli) #else void CVehicle::DoInteriorLightEffect(s32 boneID, float fade, CVehicleModelInfo* pModelInfo, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, bool isHeli) #endif { if (fade > 0.0f) { s32 boneIdx = pModelInfo->GetBoneIndex(boneID); if (boneIdx>-1) { Matrix34 mBoneMtx; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdx, mBoneMtx); #else pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mBoneMtx)); #endif ConfigLightSettings *lightConfig = NULL; if(isHeli) { lightConfig = &g_HeliInteriorLights; } else { lightConfig = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_FAT_INTERIOR_LIGHT)? (&g_InteriorFatLightSettings) : (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_INTERIOR_RED_LIGHT)? &g_InteriorRedLightSettings : &g_InteriorLightSettings); } float lowerBound = 0.0f; const int iBoneIndexChassisLowLod=GetBoneIndex(VEH_CHASSIS_LOWLOD); if(iBoneIndexChassisLowLod!=-1) { int iComponent = GetVehicleFragInst()->GetComponentFromBoneIndex(iBoneIndexChassisLowLod); if(iComponent > -1) { const phBoundComposite* pBoundCompOrig = static_cast(GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds()); lowerBound = pBoundCompOrig->GetBound(iComponent)->GetBoundingBoxMin().GetZf(); } } else { lowerBound = GetBoundingBoxMin().z; } Vector3 lightPos = mBoneMtx.d; const float boneOffset = lightPos.z - (GetTransform().GetPosition().GetZf()); float newSchoolradius = boneOffset + Abs(lowerBound); const float radius = rage::Abs(rage::Min(lightConfig->radius,newSchoolradius*0.95f)); Vector3 worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(-Vec3V(V_Z_AXIS_WZERO))); Vector3 worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO))); if(isHeli) { lightPos += (mBoneMtx.c * lightConfig->coronaSize); } int lightFlags = LIGHTFLAG_VEHICLE; if( CutSceneManager::GetInstance()->IsCutscenePlayingBack() ) { // performance optimization for cutscenes lightFlags |= LIGHTFLAG_DONT_LIGHT_ALPHA; } CLightSource* pLightSource = CAsyncLightOcclusionMgr::AddLight(); if (pLightSource) { pLightSource->Reset(); pLightSource->SetCommon( LIGHT_TYPE_SPOT, lightFlags, lightPos, RCC_VECTOR3(lightConfig->colour), lightConfig->intensity * fade, LIGHT_ALWAYS_ON); pLightSource->SetDirTangent(worldDir, worldTan); pLightSource->SetRadius(radius); pLightSource->SetSpotlight(lightConfig->innerConeAngle, lightConfig->outerConeAngle); pLightSource->SetFalloffExponent(lightConfig->falloffExp); pLightSource->SetInInterior(interiorLocation); } } } } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoInteriorLightEffect(s32 boneID, float fade, CVehicleModelInfo* pModelInfo, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList, bool isHeli) #else void CVehicle::DoInteriorLightEffect(s32 boneID, float fade, CVehicleModelInfo* pModelInfo, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList, bool isHeli) #endif { if (fade > 0.0f) { s32 boneIdx = pModelInfo->GetBoneIndex(boneID); if (boneIdx>-1) { Matrix34 mBoneMtx; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdx, mBoneMtx); #else pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mBoneMtx)); #endif ConfigLightSettings *lightConfig = NULL; if(isHeli) { lightConfig = &g_HeliInteriorLights; } else { lightConfig = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_FAT_INTERIOR_LIGHT)? (&g_InteriorFatLightSettings) : (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_INTERIOR_RED_LIGHT)? &g_InteriorRedLightSettings : &g_InteriorLightSettings); } float lowerBound = 0.0f; const int iBoneIndexChassisLowLod=GetBoneIndex(VEH_CHASSIS_LOWLOD); if(iBoneIndexChassisLowLod!=-1) { int iComponent = GetVehicleFragInst()->GetComponentFromBoneIndex(iBoneIndexChassisLowLod); if(iComponent > -1) { const phBoundComposite* pBoundCompOrig = static_cast(GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds()); lowerBound = pBoundCompOrig->GetBound(iComponent)->GetBoundingBoxMin().GetZf(); } } else { lowerBound = GetBoundingBoxMin().z; } Vector3 lightPos = mBoneMtx.d; #if ENABLE_FRAG_OPTIMIZATION Matrix34& localBoneMatrix = GetLocalMtxNonConst(boneIdx); const float boneOffset = localBoneMatrix.d.GetZ(); #else Mat34V& localBoneMatrix = pSkeleton->GetLocalMtx(boneIdx); const float boneOffset = localBoneMatrix.d().GetZf(); #endif float newSchoolradius = boneOffset + Abs(lowerBound); const float radius = rage::Abs(rage::Min(lightConfig->radius,newSchoolradius*0.95f)); Vector3 worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(-Vec3V(V_Z_AXIS_WZERO))); Vector3 worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO))); if(isHeli) { lightPos += (mBoneMtx.c * lightConfig->coronaSize); } int lightFlags = LIGHTFLAG_VEHICLE; if( CutSceneManager::GetInstance()->IsCutscenePlayingBack() ) { // performance optimization for cutscenes lightFlags |= LIGHTFLAG_DONT_LIGHT_ALPHA; } CLightSource light(LIGHT_TYPE_SPOT, lightFlags, lightPos, RCC_VECTOR3(lightConfig->colour), lightConfig->intensity * fade, LIGHT_ALWAYS_ON); light.SetDirTangent(worldDir, worldTan); light.SetRadius(radius); light.SetSpotlight(lightConfig->innerConeAngle, lightConfig->outerConeAngle); light.SetFalloffExponent(lightConfig->falloffExp); light.SetInInterior(interiorLocation); if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } } } } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoTaxiLightEffect(float fade, float daynightFade, CVehicleModelInfo* pModelInfo, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoTaxiLightEffect(float fade, float daynightFade, CVehicleModelInfo* pModelInfo, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #endif { if(fade < 0.001f) { return; } s32 boneIdx = -1; s32 taxiLightBones[3] = { VEH_EXTRA_5, VEH_EXTRA_6, // VEH_EXTRA_7, those are used for the adverts on the side of the taxi // VEH_EXTRA_8, VEH_EXTRA_9 }; s32 taxiLightBits[3] = { 5, 6, // 7, those are used for the adverts on the side of the taxi // 8, 9 }; for(int i=0;i<3;i++) { if( !(m_nDisableExtras & BIT(taxiLightBits[i])) ) { boneIdx =pModelInfo->GetBoneIndex(taxiLightBones[i]); break; } } if (boneIdx>-1) { Matrix34 mBoneMtx; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdx, mBoneMtx); #else pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mBoneMtx)); #endif const Vector3 lightCoors = mBoneMtx.d; CLightSource light( LIGHT_TYPE_POINT, LIGHTFLAG_VEHICLE, lightCoors, RCC_VECTOR3(g_TaxiSettings.colour), g_TaxiSettings.intensity * fade, LIGHT_ALWAYS_ON); light.SetDirTangent(Vector3(0,0,-1), Vector3(0,1,0)); light.SetRadius(g_TaxiSettings.radius); light.SetFalloffExponent(g_TaxiSettings.falloffExp); light.SetInInterior(interiorLocation); if(IsLightVisible(light)) { light.SetFlag(LIGHTFLAG_ALREADY_TESTED_FOR_OCCLUSION); coronaAndLightList.AddLight(light); } CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_EXTRALIGHT,daynightFade)); } } // neonFlags param is a bit mask for each neon, indicating if it is on or off // Bit 0: VEH_NEON_L // Bit 1: VEH_NEON_R // Bit 2: VEH_NEON_F // Bit 3: VEH_NEON_B #if ENABLE_FRAG_OPTIMIZATION void CVehicle::DoNeonLightEffect(u32 neonFlags, float fade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #else void CVehicle::DoNeonLightEffect(u32 neonFlags, float fade, CVehicleModelInfo* pModelInfo, const Matrix34 &matVehicle, crSkeleton *pSkeleton, fwInteriorLocation interiorLocation, CoronaAndLightList& coronaAndLightList) #endif { if(fade < 0.001f) { return; } const float intensity = fade * CNeonTuningData::ms_globalNeonIntensity; const float radius = CNeonTuningData::ms_globalNeonRadius; const float falloffExponent = CNeonTuningData::ms_globalNeonRadiusFallOffExponent; const Vec3V lightColour = GetNeonColour().GetRGB(); float capsuleExtent = CNeonTuningData::ms_globalNeonCapsuleExtentSides; // NB: Front and back must be last const s32 neonLightBones[4] = {VEH_NEON_L, VEH_NEON_R, VEH_NEON_F, VEH_NEON_B}; Matrix34 mat(matVehicle); bool needFrontBack = true; for(int i=0; i<4; ++i) { if((neonFlags & (1 << i)) == 0) continue; s32 boneIdx = pModelInfo->GetBoneIndex(neonLightBones[i]); if (boneIdx == -1) continue; Matrix34 mBoneMtx; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(boneIdx, mBoneMtx); #else pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mBoneMtx)); #endif const Vector3 lightCoors = mBoneMtx.d; // Moving to front and back neons? if((neonLightBones[i] == VEH_NEON_F || neonLightBones[i] == VEH_NEON_B) && (needFrontBack)) { capsuleExtent = CNeonTuningData::ms_globalNeonCapsuleExtentFrontBack; mat = Matrix34(-matVehicle.b.x, -matVehicle.b.y, -matVehicle.b.z, matVehicle.a.x, matVehicle.a.y, matVehicle.a.z, matVehicle.c.x, matVehicle.c.y, matVehicle.c.z, matVehicle.d.x, matVehicle.d.y, matVehicle.d.z); needFrontBack = false; } const float fClipPlaneHeight = pModelInfo->GetIsBike()? CNeonTuningData::ms_globalNeonBikeClipPlaneHeight : CNeonTuningData::ms_globalNeonClipPlaneHeight; AddVehicleLight(mat, lightCoors, Vector3(0.0f, 0.0f, -1.0f), VEC3V_TO_VECTOR3(lightColour), intensity, radius, falloffExponent, 0.0f, 0.0f, interiorLocation, false, coronaAndLightList, this, NO_HEADLIGHT_SHADOWS, 0, false, 0.0f, false, capsuleExtent, fClipPlaneHeight); } } dev_float BRAKE_LIGHT_THRESHOLD = 0.1f; // void CVehicle::DoVehicleLights(u32 nLightFlags, Vec4V_In ambientVolumeParams /* = Vec4V(V_ZERO_WONE)*/) { CVehicleLightAsyncMgr::AddVehicle(this, GetInteriorLocation(), nLightFlags, ambientVolumeParams); } // void CVehicle::ProcessIndicators(bool &bTurningLeft, bool &bTurningRight, bool bForcedOn) { bool bIndicatorInterval = (((fwTimer::GetTimeInMilliseconds() + GetRandomSeed()) & 1023) & 512) != 0; bool alarmLight = IsAlarmActivated(); bTurningRight = m_nVehicleFlags.bEngineOn && m_nVehicleFlags.bRightIndicator && bIndicatorInterval; bTurningLeft = m_nVehicleFlags.bEngineOn && m_nVehicleFlags.bLeftIndicator && bIndicatorInterval; if( m_nVehicleFlags.bHazardLights ) { bTurningLeft = bTurningRight = bIndicatorInterval; } else if( alarmLight ) { bTurningLeft = bTurningRight = bForcedOn = m_VehicleAudioEntity->AlarmIndicatorActive(); } else if( m_nVehicleFlags.bIndicatorLights ) { bTurningLeft = bTurningRight = true; } } void CVehicle::DoVehicleLightsAsync(const Matrix34 &matVehicle, u32 nLightFlags, Vec4V_In ambientVolumeParams, const fwInteriorLocation interiorLoc, CoronaAndLightList& coronaAndLightList) { #if 0 == FORCELIGHTSON if (ms_forceVehicleLightsOff) { return; } #endif // 0 bool bFlashHeadLights = (fwTimer::GetTimeInMilliseconds() < m_FlashHeadLights) && (fwTimer::GetTimeInMilliseconds() & 256); // Get brightness scaler based on cam distance // two versions : one for "small" lights : Indicator, interior light, reversing, and brakes. // And one for the main one : Head and tail Light, Sirens. float camDist = 0.0f; Vec3V vPosition = GetTransform().GetPosition(); //Using this call for camera position as it gets the current viewports position (in cases when you use a different camera) Vector3 camPos = camInterface::GetPos(); float camDistSqr = CVfxHelper::GetDistSqrToCamera(vPosition); // under a veh light cutoff... //dev_float heightCutoffDistanceApply = 50.0f; dev_float heightCutoffHeightDiffereance = 2.0f; dev_float heightCutoffFadeDistance = 1.0f; float heightFade = 1.0f; //Disabling Height fade if car contains local player or if camera is underwater if(!(ContainsLocalPlayer() || Water::IsCameraUnderwater())) { // using direction of car to determine down direction // then use how much below the camera is based on the down vector const Vec3V vCamPos = VECTOR3_TO_VEC3V(camPos); const Vec3V downDir = -GetTransform().GetUp(); const ScalarV distance = Dot(downDir, vCamPos - vPosition); float heightDiff = distance.Getf(); // if this is a car or a bike that is on it's side or upside down, never fade out the light based // on the player being "underneath" the vehicle if( m_vehicleType == VEHICLE_TYPE_BIKE || m_vehicleType == VEHICLE_TYPE_CAR ) { if( IsOnItsSide() || IsUpsideDown() ) { // disable height fade heightDiff = -1.0f; } } if( heightDiff > 0.0f ) { heightFade = rage::Clamp((heightCutoffHeightDiffereance - heightDiff)*(1.0f/heightCutoffFadeDistance) ,0.0f,1.0f); } if( heightFade > 0.0f ) { camDist = rage::Sqrtf(camDistSqr); } } CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)GetBaseModelInfo(); #if __BANK const float mainLightShutDown = CHeadlightTuningData::ms_useVehicleLodDist ? Max(g_MainLights_ShutDownDistance,pModelInfo->GetLodDistance()) : g_MainLights_ShutDownDistance; #else const float mainLightShutDown = g_MainLights_ShutDownDistance; #endif //Get rid of the fadout for the target vehicle if we are in cinematic camera mode //This can be done by getting rid of the camDist term when calculating the brightness/distanceFade float tweakedCamDist = camDist; if(camInterface::GetCinematicDirector().IsRenderingAnyInVehicleCinematicCamera()) { const camBaseCamera* renderedCamera = camInterface::GetCinematicDirector().GetRenderedCamera(); if(renderedCamera && camInterface::IsRenderingCamera(*renderedCamera)) { const CEntity* target = renderedCamera->GetLookAtTarget(); if(target == this) tweakedCamDist = 0.0f; } } else if (GetLodMultiplier() > 1.f) { tweakedCamDist /= GetLodMultiplier(); } float coronaSirenMult = 1.0f; if (camInterface::IsRenderedCameraInsideVehicle()) { CPlayerInfo *pPlayerInfo = CGameWorld::GetMainPlayerInfo(); if (pPlayerInfo && pPlayerInfo->GetPlayerPed()) { const CPed* playerPed = pPlayerInfo->GetPlayerPed(); const CVehicle* playerVehicle = playerPed->GetMyVehicle(); if (playerVehicle == this) { coronaSirenMult = 0.0f; } } } const float alphaFadeLOD = this->GetAlpha() / 255.0f; const float forceLightOffFade = ms_forceActualVehicleLightsOff ? 0.0f : 1.0f; const float mainBrightness = forceLightOffFade * rage::Clamp((mainLightShutDown + ms_fLightCutoffDistanceTweak - tweakedCamDist )*oo_Lights_FadeLength,0.0f,1.0f) * alphaFadeLOD; const float mainLightsBrightness = forceLightOffFade * heightFade * mainBrightness; const float daynightFade = forceLightOffFade * Lights::CalculateTimeFade( m_timeLightsTurnOn - 1.0f, m_timeLightsTurnOn, m_timeLightsTurnOff - 1.0f, m_timeLightsTurnOff ); const float sirenEarlyFadeMultiplier = m_nVehicleFlags.GetSirenUsesEarlyFade() ? 0.5f : 1.0f; const float sirenDayNightFade = m_nVehicleFlags.GetSirenUsesDayNightFade() ? daynightFade : 1.0f; const float sirenCoronaBrightness = rage::Clamp((g_SirenLights_ShutDownDistance + ms_fLightCutoffDistanceTweak- tweakedCamDist)*oo_Lights_FadeLength,0.0f,1.0f) * alphaFadeLOD * coronaSirenMult; const float sirenLightsBrightness = heightFade * sirenDayNightFade * rage::Clamp((g_SirenLights_ShutDownDistance * sirenEarlyFadeMultiplier + ms_fLightCutoffDistanceTweak- tweakedCamDist)*oo_Lights_FadeLength,0.0f,1.0f) * alphaFadeLOD; //const float daynightFade = /*(GetDriver() && GetDriver()->IsControlledByLocalPlayer()) ? 1.0f : */CalculateVehicleDaylightFade(); const float distanceFade = rage::Clamp((g_SecondaryLights_ShutDownDistance + ms_fLightCutoffDistanceTweak - tweakedCamDist)*oo_Lights_FadeLength,0.0f,1.0f); const float secondaryLightsBrightness = forceLightOffFade * heightFade * distanceFade * daynightFade * alphaFadeLOD; const float neonDistanceFade = rage::Clamp((g_NeonLights_ShutDownDistance + ms_fLightCutoffDistanceTweak - tweakedCamDist)*oo_Lights_FadeLength,0.0f,1.0f); const float neonLightsBrightness = forceLightOffFade * heightFade * neonDistanceFade * alphaFadeLOD; const bool noCoronas = GetAttachedTrailer() != NULL; float coronaFade_DistFadeRatio = 1.0f - ms_coronaFade_MainFadeRatio; float ooFadeLength = 1.0f / (ms_coronaFade_CutoffEnd - ms_coronaFade_CutoffStart); float cutoffFade = rage::Clamp((ms_coronaFade_CutoffStart - camDist) * ooFadeLength,0.0f,1.0f); float ooShutDownEnd = 1.0f /(ms_coronaFade_BeginEnd - ms_coronaFade_BeginStart); float coronasFade = ((1.0f - ms_coronaFade_MainFadeRatio) * 0.25f + rage::Clamp((camDist - ms_coronaFade_BeginStart)*ooShutDownEnd * coronaFade_DistFadeRatio,0.0f,coronaFade_DistFadeRatio)) * cutoffFade; const vehicleLightSettings *lightSettings = pModelInfo->GetLightSettings(); CCustomShaderEffectVehicle* pShader = static_cast(GetDrawHandler().GetShaderEffect()); Assert(pShader); // Check whether this vehicle should have its lights on bool fullbeam = GetFullBeamStatus(); // check and update overrides to light status bool bForcedOn = UpdateVehicleLightOverrideStatus(); // Turn them lights off... if( daynightFade == 0.0f ) { pShader->SetAllLights(g_LightSwitchSettings.lights_Off_day); } else if( daynightFade == 1.0f ) { pShader->SetAllLights(g_LightSwitchSettings.lights_Off_night); } else { for(int i=0;iSetLightValue((CVehicleLightSwitch::LightId)i,g_LightSwitchSettings.GetLightOffValue(i,daynightFade)); } } #if DISABLE_FRAG_OPTIMIZATION crSkeleton *pSkeleton = GetSkeleton(); #endif if ((GetStatus()!=STATUS_WRECKED) && mainLightsBrightness>0.01f && (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_NOAMBIENTOCCLUSION)==false) ) { if (GetVehicleType()==VEHICLE_TYPE_BIKE || GetVehicleType()==VEHICLE_TYPE_BICYCLE) { int nBoneIndex0 = pModelInfo->GetBoneIndex(BIKE_WHEEL_F); int nBoneIndex1 = pModelInfo->GetBoneIndex(BIKE_WHEEL_R); if (nBoneIndex0>-1 && nBoneIndex1>-1) //this checks if its a 2 wheeled vehicle { Matrix34 wheelMat0; Matrix34 wheelMat1; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(nBoneIndex0, wheelMat0); GetGlobalMtx(nBoneIndex1, wheelMat1); #else pSkeleton->GetGlobalMtx(nBoneIndex0, RC_MAT34V(wheelMat0)); pSkeleton->GetGlobalMtx(nBoneIndex1, RC_MAT34V(wheelMat1)); #endif Vector3 wheelPos0 = wheelMat0.d; Vector3 wheelPos1 = wheelMat1.d;//pDrawable->GetSkeletonData()->GetBoneData(nBoneIndex1)->GetDefaultTranslation(); Vector3 down=Vector3(0.0f, 0.0f, -1.0f); Vector3 forward=Vector3(0.0f, 1.0f, 0.0f); static dev_float debugMidBalance=0.5f; Vector3 wheelMid=(wheelPos0*debugMidBalance)+(wheelPos1*(1.0f-debugMidBalance)); static dev_float debugBikeRadiusScale=2.0f; float wheelRadius = pModelInfo->GetTyreRadius(true)*debugBikeRadiusScale; static Vector3 debugMidOffset=Vector3(0,0,0); Vector3 occPos=wheelMid+debugMidOffset + VEC3V_TO_VECTOR3(ambientVolumeParams.GetXYZ()); static dev_float debugBikeWidthScale=1.0f; static dev_float debugBikeLengthScale=0.5f; static dev_float debugBikeHeightScale=1.0f; float width=wheelRadius*debugBikeWidthScale; float length=((wheelPos0-wheelPos1).Mag()+(wheelRadius*2.0f))*debugBikeLengthScale; float height=wheelRadius*debugBikeHeightScale; Vector3 worldPos; Vector3 worldDown; Vector3 worldForward; worldPos=occPos; matVehicle.Transform3x3(down, worldDown); matVehicle.Transform3x3(forward, worldForward); const int bottomType = GetVehicleType()==VEHICLE_TYPE_BIKE ? AO_VEHICLE_TYPE_BIKE : AO_VEHICLE_TYPE_BICYCLE; //Ambient volume scaler is used for adding more ambient volume strength if required. This term gets stored as the intensity in the shader AddVehicleAmbientOccluder(worldPos, worldDown, worldForward, width*0.5f, length*0.5f, height*0.5f, heightFade * ambientVolumeParams.GetWf() * alphaFadeLOD, tweakedCamDist, bottomType, coronaAndLightList); } } else { int nBoneIndex0 = pModelInfo->GetBoneIndex(VEH_WHEEL_LF); int nBoneIndex1 = pModelInfo->GetBoneIndex(VEH_WHEEL_RF); int nBoneIndex2 = pModelInfo->GetBoneIndex(VEH_WHEEL_LR); int nBoneIndex3 = pModelInfo->GetBoneIndex(VEH_WHEEL_RR); if (nBoneIndex0>-1 && nBoneIndex1>-1 && nBoneIndex2>-1 && nBoneIndex3>-1) //this checks if its a 4 wheeled vehicle { Matrix34 wheelMat0; Matrix34 wheelMat1; Matrix34 wheelMat2; Matrix34 wheelMat3; #if ENABLE_FRAG_OPTIMIZATION GetGlobalMtx(nBoneIndex0, wheelMat0); GetGlobalMtx(nBoneIndex1, wheelMat1); GetGlobalMtx(nBoneIndex2, wheelMat2); GetGlobalMtx(nBoneIndex3, wheelMat3); #else pSkeleton->GetGlobalMtx(nBoneIndex0, RC_MAT34V(wheelMat0)); pSkeleton->GetGlobalMtx(nBoneIndex1, RC_MAT34V(wheelMat1)); pSkeleton->GetGlobalMtx(nBoneIndex2, RC_MAT34V(wheelMat2)); pSkeleton->GetGlobalMtx(nBoneIndex3, RC_MAT34V(wheelMat3)); #endif Vector3 wheelPos0 = wheelMat0.d; Vector3 wheelPos1 = wheelMat1.d; Vector3 wheelPos2 = wheelMat2.d; Vector3 wheelPos3 = wheelMat3.d; Vector3 down=Vector3(0.0f, 0.0f, -1.0f); Vector3 forward=Vector3(0.0f, 1.0f, 0.0f); Vector3 wheelMid=(wheelPos0+wheelPos1+wheelPos2+wheelPos3)*0.25f; float wheelRadius = pModelInfo->GetTyreRadius(true); static Vector3 debugMidOffset=Vector3(0,0,0); Vector3 occPos=wheelMid+debugMidOffset + VEC3V_TO_VECTOR3(ambientVolumeParams.GetXYZ()); static dev_float debugCarWidthScale=1.0f; static dev_float debugCarLengthScale=1.0f; static dev_float debugCarHeightScale=1.0f; float width=((wheelPos1-wheelPos0).Mag()+(wheelPos2-wheelPos3).Mag())*0.5f*debugCarWidthScale; float length=((wheelPos0-wheelPos2).Mag()+(wheelPos1-wheelPos3).Mag())*0.5f*debugCarLengthScale; float height=wheelRadius*2.0f*debugCarHeightScale; // Scale the volume length for train carts with wheels close to the bound (B*1563500) if(GetVehicleType()==VEHICLE_TYPE_TRAIN) { static dev_float debugTrainLengthRatio = 0.9f; float boundLen = GetBoundingBoxMax().y - GetBoundingBoxMin().y; if(length >= boundLen * debugTrainLengthRatio) { static dev_float debugTrainLengthScale = 0.62f; length *= debugTrainLengthScale; } } Vector3 worldPos; Vector3 worldDown; Vector3 worldForward; worldPos=occPos; matVehicle.Transform3x3(down, worldDown); matVehicle.Transform3x3(forward, worldForward); //Ambient volume scaler is used for adding more ambient volume strength if required. This term gets stored as the intensity in the shader int bottomType = AO_VEHICLE_TYPE_CAR; bool addAmbientOccluder = true; // if this is an aerial vehicle, and it is too far off the ground, we don't want to add an AmbientOccluder if it's too high off the ground VehicleType vehicleType = GetVehicleType(); if (vehicleType == VEHICLE_TYPE_PLANE || vehicleType == VEHICLE_TYPE_HELI || vehicleType == VEHICLE_TYPE_BLIMP || vehicleType == VEHICLE_TYPE_AUTOGYRO ) { bottomType = AO_VEHICLE_TYPE_HELI; Vec3V vPosition = GetTransform().GetPosition(); float fMapMaxZ = CGameWorldHeightMap::GetMaxHeightFromWorldHeightMap(vPosition.GetXf(), vPosition.GetYf()); if (fMapMaxZ > ms_fAOHeightCutoff) { addAmbientOccluder = false; } } if (addAmbientOccluder) { AddVehicleAmbientOccluder(worldPos, worldDown, worldForward, width*0.5f, length*0.5f, height*0.5f, heightFade * ambientVolumeParams.GetWf() * alphaFadeLOD, tweakedCamDist, bottomType, coronaAndLightList); } } } } bool turningLeft, turningRight = false; ProcessIndicators(turningLeft, turningRight, bForcedOn); // check which lights are on and off. On trains only engine has front and caboose has rear light #if FORCELIGHTSON const bool frontLights = true; const bool rearLights = true; #else const bool frontLights = !(nLightFlags & LIGHTS_IGNORE_HEADLIGHTS); const bool rearLights = !(nLightFlags & LIGHTS_IGNORE_TAILLIGHTS); #endif const bool Damage = !(nLightFlags & LIGHTS_IGNORE_DAMAGE); const bool LightsHavePower = (nLightFlags & LIGHTS_ALWAYS_HAVE_POWER) != 0; const bool AssumeDriverExists = (nLightFlags & LIGHTS_IGNORE_DRIVER_CHECK) != 0; const bool useVolumetricLights = ((nLightFlags & LIGHTS_USE_VOLUMETRIC_LIGHTS) != 0); const bool forceLowResVolumetricLights = ((nLightFlags & LIGHTS_FORCE_LOWRES_VOLUME) != 0); const bool reduceHeadlightCoronas = ((nLightFlags & LIGHTS_UNDERWATER_CORONA_FADE) != 0); const sirenSettings* siren = NULL; if( UsesSiren() && m_nVehicleFlags.GetIsSirenOn() ) { #if ENABLE_FRAG_OPTIMIZATION DoSirenLightsEffect(sirenLightsBrightness, sirenDayNightFade, sirenCoronaBrightness, ms_forceActualVehicleLightsOff || m_nVehicleFlags.GetSirenUseNoLights(), pModelInfo, interiorLoc, coronaAndLightList); #else DoSirenLightsEffect(sirenLightsBrightness, sirenDayNightFade, sirenCoronaBrightness, ms_forceActualVehicleLightsOff || m_nVehicleFlags.GetSirenUseNoLights(), pModelInfo, pSkeleton, interiorLoc, coronaAndLightList); #endif siren = pModelInfo->GetSirenSettings(); } // if the lights on this veh are switched on we will render its head & tail lights. #if 0 == FORCELIGHTSON if (m_nVehicleFlags.bEngineOn || bForcedOn || LightsHavePower) #endif // FORCELIGHTSON { const bool reversing = (GetThrottle() < -0.01 && m_specialFlightModeRatio == 0.0f); bool forceBrakeLight = false; bool handBrake = m_vehControls.m_handBrake; if (GetDriver() && GetDriver()->IsControlledByLocalPlayer() && (false == fwTimer::IsGamePaused()) && m_specialFlightModeRatio == 0.0f) { CControl *pControl = GetDriver()->GetControlFromPlayer(); Assert(pControl); forceBrakeLight = ( pControl->GetVehicleAccelerate().IsDown(ioValue::ANALOG_BUTTON_DOWN_THRESHOLD) && pControl->GetVehicleBrake().IsDown(ioValue::ANALOG_BUTTON_DOWN_THRESHOLD) ); handBrake = false; // Actual brake pressure applied, we just ignore the handBrake. } const bool braking = (( ((GetBrake() > BRAKE_LIGHT_THRESHOLD) || (true == forceBrakeLight)) && !handBrake && (GetDriver() || AssumeDriverExists)) && !m_nVehicleFlags.bSuppressBrakeLight) || (nLightFlags&LIGHTS_FORCE_BRAKE_LIGHTS); #if FORCELIGHTSON const bool gotLight = true; #else const bool gotLight = (m_nVehicleFlags.bLightsOn || bForcedOn || fullbeam); #endif float intensityMultiplierHeadLeft = 1.f; float intensityMultiplierHeadRight = 1.f; float intensityMultiplierTailLeft = 1.f; float intensityMultiplierTailRight = 1.f; // Right Front bool headLight_R = ((gotLight && frontLights) || bFlashHeadLights) && !(Damage && m_VehicleDamage.GetLightState(VEH_HEADLIGHT_R)); if (siren && siren->rightHeadLight.sequencer) { headLight_R = m_sirenData->IsSirenLightOn(false, true) && !(Damage && m_VehicleDamage.GetLightState(VEH_HEADLIGHT_R)); intensityMultiplierHeadRight = m_sirenData->GetLightIntensityMutiplier(false, true); fullbeam |= headLight_R; } // Left Front bool headLight_L = ((gotLight && frontLights) || bFlashHeadLights) && !(Damage && m_VehicleDamage.GetLightState(VEH_HEADLIGHT_L)); if (siren && siren->leftHeadLight.sequencer) { headLight_L = m_sirenData->IsSirenLightOn(false, false) && !(Damage && m_VehicleDamage.GetLightState(VEH_HEADLIGHT_L)); intensityMultiplierHeadLeft = m_sirenData->GetLightIntensityMutiplier(false, false); fullbeam |= headLight_L; } // Right Rear bool tailLight_R = gotLight && rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_TAILLIGHT_R)); bool brakeLight_R = braking && ( rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_BRAKELIGHT_R)) ); bool reversingLight_R = reversing && (rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_REVERSINGLIGHT_R))); if (siren && siren->rightTailLight.sequencer) { const bool lightOn = m_sirenData->IsSirenLightOn(true, true); tailLight_R = lightOn && !(Damage && m_VehicleDamage.GetLightState(VEH_TAILLIGHT_R)); brakeLight_R = lightOn && !(Damage && m_VehicleDamage.GetLightState(VEH_BRAKELIGHT_R)); intensityMultiplierTailRight = m_sirenData->GetLightIntensityMutiplier(true, true); } // Left Rear bool tailLight_L = gotLight && rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_TAILLIGHT_L)); bool brakeLight_L = braking && (rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_BRAKELIGHT_L))); bool reversingLight_L = reversing && (rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_REVERSINGLIGHT_L))); if (siren && siren->leftTailLight.sequencer) { const bool lightOn = m_sirenData->IsSirenLightOn(true, false); tailLight_L = lightOn && !(Damage && m_VehicleDamage.GetLightState(VEH_TAILLIGHT_L)); brakeLight_L = lightOn && !(Damage && m_VehicleDamage.GetLightState(VEH_BRAKELIGHT_L)); intensityMultiplierTailLeft = m_sirenData->GetLightIntensityMutiplier(true, false); } // Center rear ? const bool brakeLight_M = braking && (rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_BRAKELIGHT_M))); bool extraLights = HasExtraLights(); if( extraLights && !(nLightFlags&LIGHTS_USE_EXTRA_AS_HEADLIGHTS)) { // Extra lights: get them, and asses damages. const bool extraLight_1 = ((gotLight && frontLights) ) && !(Damage && m_VehicleDamage.GetLightState(VEH_EXTRALIGHT_1)); const bool extraLight_2 = ((gotLight && frontLights) ) && !(Damage && m_VehicleDamage.GetLightState(VEH_EXTRALIGHT_2)); const bool extraLight_3 = ((gotLight && frontLights) ) && HasDoubleExtraLights() && !(Damage && m_VehicleDamage.GetLightState(VEH_EXTRALIGHT_3)); const bool extraLight_4 = ((gotLight && frontLights) ) && HasDoubleExtraLights() && !(Damage && m_VehicleDamage.GetLightState(VEH_EXTRALIGHT_4)); extraLights = extraLight_1 || extraLight_2 || extraLight_3 || extraLight_4; if( extraLights ) { // Make sure those really are extra lights, not just over priced collision sphere. bool fakeExtraLights = pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_FAKE_EXTRALIGHTS); extraLights = !fakeExtraLights; // Patch up broken light if required. if( fakeExtraLights ) { headLight_R = ((gotLight && frontLights) || bFlashHeadLights) && !(Damage && m_VehicleDamage.GetLightState(VEH_HEADLIGHT_R) && m_VehicleDamage.GetLightState(VEH_EXTRALIGHT_2)); headLight_L = ((gotLight && frontLights) || bFlashHeadLights) && !(Damage && m_VehicleDamage.GetLightState(VEH_HEADLIGHT_L) && m_VehicleDamage.GetLightState(VEH_EXTRALIGHT_1)); } if( extraLights == false || fullbeam == true) { DoExtraLightsEffect(VEH_EXTRALIGHT_1, VEH_EXTRALIGHT_2, VEH_EXTRALIGHT_3, VEH_EXTRALIGHT_4, extraLight_1, extraLight_2, extraLight_3, extraLight_4, extraLights, #if ENABLE_FRAG_OPTIMIZATION mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, interiorLoc, coronaAndLightList); #else mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, pSkeleton, interiorLoc, coronaAndLightList); #endif } } else { // They're all broken, no fullbeam. fullbeam = false; } } bool neons = HasNeons(); if( neons ) { u32 neonFlags = 0; bool forceOn = false; #if __BANK if(CNeonTuningData::ms_neonsForcedOn) forceOn = true; #endif neonFlags |= (forceOn || IsNeonLOn()) && (!Damage || !m_VehicleDamage.GetLightState(VEH_NEON_L)) ? 0x01 : 0; neonFlags |= (forceOn || IsNeonROn()) && (!Damage || !m_VehicleDamage.GetLightState(VEH_NEON_R)) ? 0x02 : 0; neonFlags |= (forceOn || IsNeonFOn()) && (!Damage || !m_VehicleDamage.GetLightState(VEH_NEON_F)) ? 0x04 : 0; neonFlags |= (forceOn || IsNeonBOn()) && (!Damage || !m_VehicleDamage.GetLightState(VEH_NEON_B)) ? 0x08 : 0; // Only suppress if we're not forcing on via debug widget if( m_bSuppressNeons BANK_ONLY(&& !forceOn) ) { neonFlags = 0; } if(neonFlags != 0) { #if ENABLE_FRAG_OPTIMIZATION DoNeonLightEffect(neonFlags, neonLightsBrightness, pModelInfo, matVehicle, interiorLoc, coronaAndLightList); #else DoNeonLightEffect(neonFlags, neonLightsBrightness, pModelInfo, matVehicle, pSkeleton, interiorLoc, coronaAndLightList); #endif } } if (headLight_L || headLight_R) { #if ENABLE_FRAG_OPTIMIZATION DoHeadLightsEffect(headLight_L, headLight_R, VEH_HEADLIGHT_L, VEH_HEADLIGHT_R, mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, (fullbeam && !extraLights) || bFlashHeadLights, fullbeam && extraLights, lightSettings, intensityMultiplierHeadLeft, intensityMultiplierHeadRight, interiorLoc, useVolumetricLights, forceLowResVolumetricLights, reduceHeadlightCoronas, coronaAndLightList); #else DoHeadLightsEffect(headLight_L, headLight_R, VEH_HEADLIGHT_L, VEH_HEADLIGHT_R, mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, (fullbeam && !extraLights) || bFlashHeadLights, fullbeam && extraLights, lightSettings, intensityMultiplierHeadLeft, intensityMultiplierHeadRight, pSkeleton, interiorLoc, useVolumetricLights, forceLowResVolumetricLights, reduceHeadlightCoronas, coronaAndLightList); #endif if(nLightFlags&LIGHTS_USE_EXTRA_AS_HEADLIGHTS) { #if ENABLE_FRAG_OPTIMIZATION DoHeadLightsEffect(headLight_L, headLight_R, VEH_EXTRALIGHT_1, VEH_EXTRALIGHT_2, mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, (fullbeam && !extraLights) || bFlashHeadLights, fullbeam && extraLights, lightSettings, intensityMultiplierHeadLeft, intensityMultiplierHeadRight, interiorLoc, useVolumetricLights, forceLowResVolumetricLights, reduceHeadlightCoronas, coronaAndLightList); DoHeadLightsEffect(headLight_L, headLight_R, VEH_EXTRALIGHT_3, VEH_EXTRALIGHT_4, mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, (fullbeam && !extraLights) || bFlashHeadLights, fullbeam && extraLights, lightSettings, intensityMultiplierHeadLeft, intensityMultiplierHeadRight, interiorLoc, useVolumetricLights, forceLowResVolumetricLights, reduceHeadlightCoronas, coronaAndLightList); #else DoHeadLightsEffect(headLight_L, headLight_R, VEH_EXTRALIGHT_1, VEH_EXTRALIGHT_2, mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, (fullbeam && !extraLights) || bFlashHeadLights, fullbeam && extraLights, lightSettings, intensityMultiplierHeadLeft, intensityMultiplierHeadRight, pSkeleton, interiorLoc, useVolumetricLights, forceLowResVolumetricLights, reduceHeadlightCoronas, coronaAndLightList); DoHeadLightsEffect(headLight_L, headLight_R, VEH_EXTRALIGHT_3, VEH_EXTRALIGHT_4, mainLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, (fullbeam && !extraLights) || bFlashHeadLights, fullbeam && extraLights, lightSettings, intensityMultiplierHeadLeft, intensityMultiplierHeadRight, pSkeleton, interiorLoc, useVolumetricLights, forceLowResVolumetricLights, reduceHeadlightCoronas, coronaAndLightList); #endif } } if (tailLight_L || tailLight_R) { #if ENABLE_FRAG_OPTIMIZATION DoTailLightsEffect(VEH_TAILLIGHT_L, -1, VEH_TAILLIGHT_R, tailLight_L, false, tailLight_R, mainLightsBrightness * daynightFade, daynightFade, coronasFade, noCoronas, pModelInfo, matVehicle, false, lightSettings, intensityMultiplierTailLeft, intensityMultiplierTailRight, interiorLoc, coronaAndLightList); #else DoTailLightsEffect(VEH_TAILLIGHT_L, -1, VEH_TAILLIGHT_R, tailLight_L, false, tailLight_R, mainLightsBrightness * daynightFade, daynightFade, coronasFade, noCoronas, pModelInfo, matVehicle, false, lightSettings, intensityMultiplierTailLeft, intensityMultiplierTailRight, pSkeleton, interiorLoc, coronaAndLightList); #endif } if (brakeLight_L || brakeLight_R) { #if ENABLE_FRAG_OPTIMIZATION DoTailLightsEffect(VEH_BRAKELIGHT_L, VEH_BRAKELIGHT_M, VEH_BRAKELIGHT_R, brakeLight_L, brakeLight_M, brakeLight_R, secondaryLightsBrightness, daynightFade, coronasFade, noCoronas, pModelInfo, matVehicle, true, lightSettings, intensityMultiplierTailLeft, intensityMultiplierTailRight, interiorLoc, coronaAndLightList); #else DoTailLightsEffect(VEH_BRAKELIGHT_L, VEH_BRAKELIGHT_M, VEH_BRAKELIGHT_R, brakeLight_L, brakeLight_M, brakeLight_R, secondaryLightsBrightness, daynightFade, coronasFade, noCoronas, pModelInfo, matVehicle, true, lightSettings, intensityMultiplierTailLeft, intensityMultiplierTailRight, pSkeleton, interiorLoc, coronaAndLightList); #endif } if (reversingLight_L || reversingLight_R) { #if ENABLE_FRAG_OPTIMIZATION DoReversingLightsEffect(VEH_REVERSINGLIGHT_L, VEH_REVERSINGLIGHT_R, reversingLight_L, reversingLight_R, secondaryLightsBrightness, daynightFade, coronasFade, noCoronas, pModelInfo, matVehicle, lightSettings, interiorLoc, coronaAndLightList); #else DoReversingLightsEffect(VEH_REVERSINGLIGHT_L, VEH_REVERSINGLIGHT_R, reversingLight_L, reversingLight_R, secondaryLightsBrightness, daynightFade, coronasFade, noCoronas, pModelInfo, matVehicle, lightSettings, pSkeleton, interiorLoc, coronaAndLightList); #endif } if (gotLight) { DoAdditionalLightsEffect(VEH_PLATELIGHT, secondaryLightsBrightness, &g_PlateLightSettings, interiorLoc, false, coronaAndLightList); if( GetVehicleType() == VEHICLE_TYPE_SUBMARINE && camInterface::IsDominantRenderedCameraAnyFirstPersonCamera() && ContainsLocalPlayer() ) { DoAdditionalLightsEffect(VEH_DASHLIGHT, secondaryLightsBrightness, &g_SubmarineLightOne, interiorLoc, false, coronaAndLightList); DoAdditionalLightsEffect(VEH_SIREN_1, secondaryLightsBrightness, &g_SubmarineLightTwo, interiorLoc, false, coronaAndLightList); } else { DoAdditionalLightsEffect(VEH_DASHLIGHT, secondaryLightsBrightness, &g_DashLightSettings, interiorLoc, true, coronaAndLightList); } } } // Turn on all other emissive lights, only if the engine's running or sirens are enabled, not for alarm though... if (m_nVehicleFlags.bEngineOn || LightsHavePower || (UsesSiren() && m_nVehicleFlags.GetIsSirenOn())) { pShader->SetLightValue(CVehicleLightSwitch::LW_DEFAULT,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_DEFAULT,daynightFade)); } const bool indicator_RF = turningRight && (frontLights && !(Damage && m_VehicleDamage.GetLightState(VEH_INDICATOR_RF))); const bool indicator_LF = turningLeft && (frontLights && !(Damage && m_VehicleDamage.GetLightState(VEH_INDICATOR_LF))); const bool indicator_RR = turningRight && (rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_INDICATOR_RR))); const bool indicator_LR = turningLeft && (rearLights && !(Damage && m_VehicleDamage.GetLightState(VEH_INDICATOR_LR))); if( indicator_LF ) { #if ENABLE_FRAG_OPTIMIZATION DoIndicatorLightEffect(VEH_INDICATOR_LF, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, interiorLoc, coronaAndLightList); #else DoIndicatorLightEffect(VEH_INDICATOR_LF, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, pSkeleton, interiorLoc, coronaAndLightList); #endif } if( indicator_RF ) { #if ENABLE_FRAG_OPTIMIZATION DoIndicatorLightEffect(VEH_INDICATOR_RF, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, interiorLoc, coronaAndLightList); #else DoIndicatorLightEffect(VEH_INDICATOR_RF, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, pSkeleton, interiorLoc, coronaAndLightList); #endif } if( indicator_LR ) { #if ENABLE_FRAG_OPTIMIZATION DoIndicatorLightEffect(VEH_INDICATOR_LR, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, interiorLoc, coronaAndLightList); #else DoIndicatorLightEffect(VEH_INDICATOR_LR, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, pSkeleton, interiorLoc, coronaAndLightList); #endif } if( indicator_RR ) { #if ENABLE_FRAG_OPTIMIZATION DoIndicatorLightEffect(VEH_INDICATOR_RR, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, interiorLoc, coronaAndLightList); #else DoIndicatorLightEffect(VEH_INDICATOR_RR, secondaryLightsBrightness, daynightFade, coronasFade, pModelInfo, matVehicle, lightSettings, pSkeleton, interiorLoc, coronaAndLightList); #endif } m_VehicleAudioEntity->TriggerIndicator(indicator_LF||indicator_LR||indicator_RF||indicator_RR); // Do care for interior lights, boats, for one, will take care of it themselves. if( !(nLightFlags & LIGHTS_IGNORE_INTERIOR_LIGHT) ) { // Interior lights can be on when the engine is not running if( !(nLightFlags & LIGHTS_FORCE_INTERIOR_LIGHT) ) { bool doInteriorLight = m_nVehicleFlags.bInteriorLightOn; for(int i=0; iGetHierarchyId() != VEH_BONNET && GetDoor(i)->GetHierarchyId() != VEH_BOOT && !GetDoor(i)->GetIsClosed()) { doInteriorLight |= true; break; } } const u32 vehNameHash = this->GetBaseModelInfo()->GetModelNameHash(); if((vehNameHash == MID_AKULA) || (vehNameHash == MID_MANANA2)) { // B*4030328 - Akula - Light emits from cockpit after vehicle is destroyed // B*6252330 - Manana2 - Dashboard light is active when vehicle is destroyed if(this->m_nPhysicalFlags.bRenderScorched) // disable interior light if vehicle is destroyed doInteriorLight = false; } if( doInteriorLight ) { #if ENABLE_FRAG_OPTIMIZATION DoInteriorLightEffect(VEH_INTERIORLIGHT, secondaryLightsBrightness, pModelInfo, interiorLoc, coronaAndLightList); #else DoInteriorLightEffect(VEH_INTERIORLIGHT, secondaryLightsBrightness, pModelInfo, pSkeleton, interiorLoc, coronaAndLightList); #endif } } else { #if ENABLE_FRAG_OPTIMIZATION DoInteriorLightEffect(VEH_INTERIORLIGHT, mainBrightness, pModelInfo, interiorLoc, coronaAndLightList); #else DoInteriorLightEffect(VEH_INTERIORLIGHT, mainBrightness, pModelInfo, pSkeleton, interiorLoc, coronaAndLightList); #endif } // additional door lights for (s32 i = 0; i < GetNumDoors(); ++i) { if (!GetDoor(i)->GetIsClosed()) { if (GetDoor(i)->GetHierarchyId() == VEH_DOOR_DSIDE_F) { DoAdditionalLightsEffect(VEH_DOORLIGHT_LF, secondaryLightsBrightness, &g_DoorLightSettings, interiorLoc, false, coronaAndLightList); } else if (GetDoor(i)->GetHierarchyId() == VEH_DOOR_DSIDE_R) { DoAdditionalLightsEffect(VEH_DOORLIGHT_LR, secondaryLightsBrightness, &g_DoorLightSettings, interiorLoc, false, coronaAndLightList); } else if (GetDoor(i)->GetHierarchyId() == VEH_DOOR_PSIDE_F) { DoAdditionalLightsEffect(VEH_DOORLIGHT_RF, secondaryLightsBrightness, &g_DoorLightSettings, interiorLoc, false, coronaAndLightList); } else if (GetDoor(i)->GetHierarchyId() == VEH_DOOR_PSIDE_R) { DoAdditionalLightsEffect(VEH_DOORLIGHT_RR, secondaryLightsBrightness, &g_DoorLightSettings, interiorLoc, false, coronaAndLightList); } } } } // Taxi lights can be on when the engine is not running if (GetVehicleType() == VEHICLE_TYPE_CAR && ((CAutomobile *)this)->m_nAutomobileFlags.bTaxiLight && (false == m_VehicleDamage.GetLightStateImmediate(VEHICLE_LIGHT_TAXI))) { #if ENABLE_FRAG_OPTIMIZATION DoTaxiLightEffect(secondaryLightsBrightness, daynightFade, pModelInfo, interiorLoc, coronaAndLightList); #else DoTaxiLightEffect(secondaryLightsBrightness, daynightFade, pModelInfo, pSkeleton, interiorLoc, coronaAndLightList); #endif } #if __DEV if( gs_LightSwitchTweakMode ) { if( gs_LightSwitchTweakLightOn ) { pShader->SetLightValue(CVehicleLightSwitch::LW_DEFAULT, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_DEFAULT,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_HEADLIGHT_L, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_HEADLIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_HEADLIGHT_R, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_HEADLIGHT_R,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_TAILLIGHT_L, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_TAILLIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_TAILLIGHT_R, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_TAILLIGHT_R ,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_FL, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_INDICATOR_FL,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_FR, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_INDICATOR_FR,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_RL, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_INDICATOR_RL,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_RR, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_INDICATOR_RR,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_BRAKELIGHT_L, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_BRAKELIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_BRAKELIGHT_R, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_BRAKELIGHT_R,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_BRAKELIGHT_M, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_BRAKELIGHT_M,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_L, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_R, g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_R,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_1,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_EXTRALIGHT_1,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_2,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_EXTRALIGHT_2,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_3,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_EXTRALIGHT_3,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_4,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_EXTRALIGHT_4,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_EXTRALIGHT,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_SIRENLIGHT,g_LightSwitchSettings.GetLightOnValue(CVehicleLightSwitch::LW_SIRENLIGHT, daynightFade)); } else { pShader->SetLightValue(CVehicleLightSwitch::LW_DEFAULT, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_DEFAULT,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_HEADLIGHT_L, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_HEADLIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_HEADLIGHT_R, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_HEADLIGHT_R,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_TAILLIGHT_L, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_TAILLIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_TAILLIGHT_R, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_TAILLIGHT_R ,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_FL, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_INDICATOR_FL,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_FR, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_INDICATOR_FR,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_RL, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_INDICATOR_RL,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_INDICATOR_RR, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_INDICATOR_RR,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_BRAKELIGHT_L, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_BRAKELIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_BRAKELIGHT_R, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_BRAKELIGHT_R,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_BRAKELIGHT_M, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_BRAKELIGHT_M,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_L, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_L,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_R, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_REVERSINGLIGHT_R,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_1,g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_EXTRALIGHT_1,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_2,g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_EXTRALIGHT_2,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_3,g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_EXTRALIGHT_3,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT_4,g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_EXTRALIGHT_4,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_EXTRALIGHT, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_EXTRALIGHT,daynightFade)); pShader->SetLightValue(CVehicleLightSwitch::LW_SIRENLIGHT, g_LightSwitchSettings.GetLightOffValue(CVehicleLightSwitch::LW_SIRENLIGHT, daynightFade)); } } #endif // __DEV if( CRenderer::GetDisableArtificialLights() && CRenderer::GetDisableArtificialVehLights() ) { if( daynightFade == 0.0f ) { pShader->SetAllLights(g_LightSwitchSettings.lights_Off_day); } else if( daynightFade == 1.0f ) { pShader->SetAllLights(g_LightSwitchSettings.lights_Off_night); } else { for(int i=0;iSetLightValue((CVehicleLightSwitch::LightId)i,g_LightSwitchSettings.GetLightOffValue(i,daynightFade)); } } } // Remap light assignment if needed... if( true == pModelInfo->HasLightRemap() ) { m_VehicleLightSwitch.LightRemapFixup(); } // B*1966110: Reset the brake light supress override now we're sure that anything that was going to read it has done so. // Was previously done at the end of CAutomobile::PreRender2, which could run before the async vehicle light processing had completed. m_nVehicleFlags.bSuppressBrakeLight = false; } //////////////////////////////////////////////////////////////////////////////////////////// // FUNCTION: DoVehicleLightsNoLights // FUNCTION: Does the equivalent of DoVehicleLights for vehicles that don't have lights (boats) // This will do the weapon dropping with 'X' button during network games. //////////////////////////////////////////////////////////////////////////////////////////// void CVehicle::DoVehicleLightsNoLights() { if( GetDriver() && !CPhoneMgr::IsDisplayed() && GetDriver()->IsLocalPlayer()) { CPed* pPlayerPed = GetDriver(); const CControl* pControl = pPlayerPed->GetControlFromPlayer(); Assert(pControl); if(pControl->GetVehicleHeadlight().HistoryHeldDown(TimeNeededToHoldLightsButtonDown)) { m_nVehicleFlags.bLightsButtonHeldDown = true; } else { m_nVehicleFlags.bLightsButtonHeldDown = false; m_nVehicleFlags.bLightsAllowFullBeamSwitch = true; } if(m_nVehicleFlags.bLightsButtonHeldDown && m_nVehicleFlags.bLightsAllowFullBeamSwitch) { #if 0 // JG // In Gta Races the X button doesn't toggle the headlights but it drops your weapon instead. if (CPickups::m_bPlayerCanDropWeaponsInCar) { weaponAssert(pPlayerPed->GetWeaponManager()); const CWeaponInfo* pChosenWeaponInfo = CWeaponInfoManager::GetItemInfo(pPlayerPed->GetWeaponManager()->GetEquippedWeaponHash()); if(pChosenWeaponInfo->CanBeUsedAsDriveBy(pPlayerPed)) { pPlayerPed->GetInventory()->RemoveItem(pPlayerPed->GetWeaponManager()->GetEquippedWeaponHash()); pPlayerPed->SetPedVehicleWeaponry(); g_FrontendAudioEntity.CreateAndPlaySound("FRONTEND_GAME_DROP_WEAPON"); } } #endif // 0 m_nVehicleFlags.bLightsAllowFullBeamSwitch = false; } } } void CVehicle::TurnSirenOn(bool on, bool randomDelay) { m_nVehicleFlags.SetSirenOn(on, randomDelay); m_VehicleAudioEntity->SirenStateChanged(on); if (m_sirenData) { m_sirenData->Reset(this); } } ePhysicsResult CVehicle::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice) { if( m_forceFixLinkMatrices ) { ForceUpdateLinkMatrices(); m_forceFixLinkMatrices = false; } if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy)) { return PHYSICS_DONE; } //Attempt to defer physics if the object is on something else. if(m_pGroundPhysical && bCanPostpone) { return PHYSICS_POSTPONE; } if( CPhysics::GetIsLastTimeSlice( nTimeSlice ) ) { if( m_resetBoostObjects && m_iCurrentSpeedBoostObjectID != 0 && mf_BoostAppliedTimer == 0.0f && m_fSlowdownDuration == 0.0f ) { m_iCurrentSpeedBoostObjectID = 0; } else { m_resetBoostObjects = true; } } #if RSG_DEV PF_SET(VehicleTimeStep, fTimeStep); #endif if( m_scriptMaxSpeed > 0.0f ) { Vector3 xyVelocity = GetVelocity(); xyVelocity.z = 0.0f; float velocityMag = xyVelocity.Mag(); if( velocityMag > m_scriptMaxSpeed ) { float accleration = ( velocityMag - m_scriptMaxSpeed ) / fTimeStep; float forceMag = accleration; Warningf("Large force being applied to: %s clamp max speed: Current Speed: %.2f Script Max Speed: %.2f", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "No Network Object", velocityMag, m_scriptMaxSpeed ); forceMag *= GetMass(); xyVelocity.Normalize(); xyVelocity *= -forceMag; ApplyForceCg( xyVelocity ); } } if( HasSuperBrakes() && IsShuntModifierActive() && GetBrake() > 0.8f ) { Vector3 xyVelocity = GetVelocity(); xyVelocity.z = 0.0f; float velocityMag2 = xyVelocity.Mag2(); float maxBrakingForce = velocityMag2 * ( GetMass() * 0.5f ); static dev_float sfSuperBrakingForceMax = 30.0f; float superBrakingForce = Min( maxBrakingForce, sfSuperBrakingForceMax ); superBrakingForce *= GetMass(); xyVelocity.Normalize(); xyVelocity *= -superBrakingForce; ApplyForceCg( xyVelocity ); } if( CPhysics::ms_bInArenaMode ) { // design want the flippers to open really fast, unfortunately that means they fire the vehicle in the air incredibly fast, to try and stop them going flying too far will apply some extra damping to the z velocity static dev_float extraDampingVelocityThreshold = 15.5f; float zVelocity = GetVelocity().GetZ(); if( zVelocity > extraDampingVelocityThreshold ) { float forceMag = extraDampingVelocityThreshold - zVelocity; forceMag *= forceMag; forceMag = Min( 250.0f, forceMag ); Vector3 extraGravity( 0.0f, 0.0f, -forceMag * GetMass() ); ApplyForceCg( extraGravity ); } } if( m_hasHitRampCar ) { TUNE_GROUP_FLOAT( VEHICLE_RAMP, resetExtraGravityVelocity, 1.0f, 0.0f, 10.0f, 0.1f ); if( GetVelocity().z > 0.0f ) { TUNE_GROUP_FLOAT( VEHICLE_RAMP, hitRampCarGravityScale, 2.0f, 1.0f, 20.0f, 0.1f ); Vector3 extraGravity( 0.0f, 0.0f, GRAVITY ); extraGravity *= ( hitRampCarGravityScale ) * GetMass(); ApplyForceCg( extraGravity ); if( GetVelocity().z < resetExtraGravityVelocity ) { m_hasHitRampCar = false; } } else if( GetNumContactWheels() == GetNumWheels() && GetVelocity().z < resetExtraGravityVelocity ) { m_hasHitRampCar = false; } } UpdateSideShunt( fTimeStep ); if(GetIsAquatic() || (pHandling && pHandling->GetSeaPlaneHandlingData())) { if(const phInst* pInst = GetCurrentPhysicsInst()) { Mat34V matrix; Vec3V boundingBoxLocalMin; Vec3V boundingBoxLocalMax; const phBound* bound = pInst->GetArchetype()->GetBound(); boundingBoxLocalMin = bound->GetBoundingBoxMin(); boundingBoxLocalMax = bound->GetBoundingBoxMax(); matrix = pInst->GetMatrix(); const Vec3V vBoundBoxLocalCentre = Average(boundingBoxLocalMin, boundingBoxLocalMax); const Vec3V vBoundBoxHalfExtents = geomBoxes::ComputeAABBExtentsFromOBB(matrix.GetMat33(), Subtract(boundingBoxLocalMax, vBoundBoxLocalCentre)); const Vec3V vCurPos = Transform(matrix, vBoundBoxLocalCentre); const Vec3V vCurBottomPos = Subtract(vCurPos, vBoundBoxHalfExtents); const float fCurBottomZ = vCurBottomPos.GetZf(); float fWaterHeight = 0.0f; const float kfMaxWaveHeight = 2.0f; bool bNearWater = Water::GetWaterLevelNoWaves(VEC3V_TO_VECTOR3(vCurBottomPos), &fWaterHeight, POOL_DEPTH, kfMaxWaveHeight, NULL); TUNE_BOOL(ALLOW_REACTIVATION_NEAR_WATER, true); if(bNearWater && fWaterHeight+kfMaxWaveHeight > fCurBottomZ) { bool bIsAmphibiousAndShouldSleep = ( InheritsFromAmphibiousAutomobile() && GetNumContactWheels() == GetNumWheels() ); if(ALLOW_REACTIVATION_NEAR_WATER && PHLEVEL->IsInactive(pInst->GetLevelIndex()) && !bIsAmphibiousAndShouldSleep ) { ActivatePhysics(); } } } } const CVehicleModelInfo* vehicleModelInfo = GetVehicleModelInfo(); float fAnimWeight = 1.0f; float fSteerAngle = GetSteerAngle(); TUNE_GROUP_BOOL(FIRST_PERSON_STEERING, bOverrideSteeringWheel, true); if(GetDriver() && !InheritsFromBike() && bOverrideSteeringWheel) { CTaskMotionBase *pCurrentMotionTask = GetDriver()->GetCurrentMotionTask(); if (pCurrentMotionTask && pCurrentMotionTask->GetTaskType() == CTaskTypes::TASK_MOTION_IN_AUTOMOBILE) { const CTaskMotionInAutomobile* pAutoMobileTask = static_cast(pCurrentMotionTask); fAnimWeight = pAutoMobileTask->GetSteeringWheelWeight(); fSteerAngle = pAutoMobileTask->GetSteeringWheelAngle(); } #if FPS_MODE_SUPPORTED else if(GetDriver()->IsFirstPersonShooterModeEnabledForPlayer(false)) { fAnimWeight = 0.0f; fSteerAngle = 0.0f; } #endif } if(vehicleModelInfo->GetHasSteeringWheelBone()) { if(GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE && (m_nVehicleFlags.bAnimateWheels==0)) { float fSteeringWheelMult = GetVehicleModelInfo()->GetSteeringWheelMult(GetDriver()); SetComponentRotation(VEH_STEERING_WHEEL, ROT_AXIS_LOCAL_Z, fSteerAngle * fSteeringWheelMult * fAnimWeight, true); } } // B*1812919: Rotate the steering wheel. s32 steeringWheelBoneIndex = GetBoneIndex(VEH_CAR_STEERING_WHEEL); if(steeringWheelBoneIndex > -1) { if(GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE && (m_nVehicleFlags.bAnimateWheels==0)) { SetComponentRotation(VEH_CAR_STEERING_WHEEL, ROT_AXIS_LOCAL_Y, fSteerAngle * (-GetVehicleModelInfo()->GetMaxSteeringWheelAngle() * DtoR) * fAnimWeight, true); } } if(vehicleModelInfo->GetHasSeatCollision()) { ProcessSeatCollisionFlags(); } //Account for the ground. if(m_pGroundPhysical) { //Need to keep the ground physical around for a frame (even though we don't set the reference frame velocity based on it) //in case this vehicle's impacts are handled out-of-order with other entities that need to know about our ground physical if(m_uGroundPhysicalTime >= fwTimer::GetPrevElapsedTimeInMilliseconds()) { //Calculate the ground velocity. Vec3V vGroundVelocity = Vec3V(V_ZERO); CalculateGroundVelocityIntegrated(m_pGroundPhysical, GetTransform().GetPosition(), 0, fTimeStep, vGroundVelocity); Assertf(IsFiniteAll(vGroundVelocity), "CVehicle::ProcessPhysics: Ground velocity is invalid."); //Calculate the ground angular velocity. Vec3V vGroundAngularVelocity = Vec3V(V_ZERO); CalculateGroundAngularVelocity(m_pGroundPhysical, 0, vGroundAngularVelocity); Assertf(IsFiniteAll(vGroundAngularVelocity), "CVehicle::ProcessPhysics: Angular ground velocity is invalid."); //Ensure the collider is valid. phCollider* pCollider = GetCollider(); if(pCollider) { //Set the collider's reference frame velocity. pCollider->SetReferenceFrameVelocity(VEC3V_TO_VECTOR3(vGroundVelocity)); //Set the collider's reference frame angular velocity. pCollider->SetReferenceFrameAngularVelocity(VEC3V_TO_VECTOR3(vGroundAngularVelocity)); //Don't let the collider go to sleep on top of vehicles. //TODO: Let the collider go to sleep, and attach to the ground. static bank_float sfMinVelocityToKeepAwake = 0.5f; if(m_pGroundPhysical->GetIsTypeVehicle() && m_pGroundPhysical->GetVelocity().Mag2() > square(sfMinVelocityToKeepAwake) && (static_cast(m_pGroundPhysical.Get())->InheritsFromTrain() || static_cast(m_pGroundPhysical.Get())->InheritsFromBoat() || static_cast(m_pGroundPhysical.Get())->InheritsFromPlane())) { //Check if the sleep is valid. phSleep* pSleep = pCollider->GetSleep(); if(pSleep) { pSleep->Reset(); } } } } else { m_pGroundPhysical = NULL; } } if( HasRocketBoost() ) { ProcessRocketBoost(); } ProcessBoostPhysics( fTimeStep ); if( HasGlider() || pHandling->GetSpecialFlightHandlingData() ) { ProcessGliderPhysics( fTimeStep ); } if( GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_OUTRIGGER_LEGS ) ) { UpdateOutriggers( fTimeStep ); } ProcessCarJump(); TUNE_GROUP_BOOL( VEHICLE_PUMPED_UP_JUMP, TRIGGER_JUMP, false ); TUNE_GROUP_FLOAT( VEHICLE_PUMPED_UP_JUMP, HEIGHT_SCALE, 1.0f, 0.0f, 10.0f, 0.1f ); if( TRIGGER_JUMP ) { TriggerPumpedUpJump( HEIGHT_SCALE ); } ProcessVehiclePumpedUpJump( fTimeStep ); #if __BANK TRIGGER_JUMP = false; #endif //#if __BANK return PHYSICS_DONE; } void CVehicle::UpdateEntityFromPhysics(phInst *pInst, int nLoop) { CPhysical::UpdateEntityFromPhysics(pInst, nLoop); if(GetDummyInst() && pInst==GetDummyInst()) { PHSIM->SetLastInstanceMatrix(GetVehicleFragInst(), GetVehicleFragInst()->GetMatrix()); GetVehicleFragInst()->SetMatrix(pInst->GetMatrix()); if(GetVehicleFragInst()->GetCacheEntry() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider) { phCollider* pCollider = GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider; pCollider->SetColliderMatrixFromInstance(); } CPhysics::GetLevel()->UpdateObjectLocation(GetVehicleFragInst()->GetLevelIndex()); } if (m_spatialArrayNode.IsInserted()) { UpdateInSpatialArray(); } } void CVehicle::UpdatePhysicsFromEntity(bool bWarp) { CPhysical::UpdatePhysicsFromEntity(bWarp); // if current phys inst is the dummy physics, we also need to update the real frag inst phInst* pCurrentInst = GetCurrentPhysicsInst(); fragInstGta* pVehFragInst = GetVehicleFragInst(); if(pCurrentInst && pCurrentInst==GetDummyInst()) { pVehFragInst->SetMatrix(pCurrentInst->GetMatrix()); if(bWarp) { PHSIM->SetLastInstanceMatrix(pVehFragInst, pCurrentInst->GetMatrix()); } if(pVehFragInst->IsInLevel()) { CPhysics::GetLevel()->UpdateObjectLocation(pVehFragInst->GetLevelIndex()); } } if(pVehFragInst) { fragCacheEntry* pFragCacheEntry = pVehFragInst->GetCacheEntry(); if(pFragCacheEntry) { fragHierarchyInst* pHierInst = pFragCacheEntry->GetHierInst(); if(pHierInst && pHierInst->articulatedCollider) { phCollider* pCollider = pHierInst->articulatedCollider; pCollider->SetColliderMatrixFromInstance(); if(bWarp) pCollider->SetLastInstanceMatrix(pVehFragInst->GetMatrix()); } } } if (m_spatialArrayNode.IsInserted()) { UpdateInSpatialArray(); } } dev_float sfVehicleVehicleFlattenMag = 0.866f; // About 60 degrees (i.e. 30 degrees between impact normal and forward vector). dev_float sfVehicleVehicleSideCollisionNormalX = 0.9f; // About 64 degrees dev_float sfVehicleVehicleSideImpactMag = 0.2f; /* dev_float VEHICLE_VEHICLE_COLLISION_MULT = 3.0f; dev_float VEHICLE_GROUND_COLLISION_MULT = 0.25f; dev_float VEHICLE_UPSIDE_DOWN_COLLISION_MULT = 2.5f; */ float VEHICLE_IN_AIR_CRASH_MIN_SPEED_SQ = 4.0f; float VEHICLE_IN_AIR_CRASH_TIME = 0.5f; float VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE = 1.1f; float VEHICLE_IN_AIR_CRASH_MAX_FRICTION = 0.8f; // the maximum friction we can set on an impact float VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE = 0.10f; float VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE_TIME = 0.45f; float VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE_TIME = 0.35f; float BIKE_MAX_ELASTICITY = 0.2f; float VEHICLE_VEHICLE_COLLISION_FRICTION = 0.75f; float VEHICLE_VEHICLE_COLLISION_MAX_FRICTION = 2.0f; float VEHICLE_VEHICLE_HEAD_ON_COLLISION_ELASTICITY = 1.0f; float VEHICLE_GROUND_COLLISION_FRICTION = 0.5f; float VEHICLE_BIKE_GROUND_COLLISION_FRICTION = 1.0f; float VEHICLE_TRAILER_LEG_COLLISION_FRICTION = 2.0f; float VEHICLE_CORNER_WALL_COLLISION_FRICTION = 0.2f; float VEHICLE_UPSIDE_DOWN_COLLISION_FRICTION = 0.65f; float VEHICLE_UPSIDE_DOWN_COLLISION_SIDEWAYS_FRICTION = 0.90f; float VEHICLE_UPSIDE_DOWN_PLANE_COLLISION_FRICTION = 1.75f; float VEHICLE_MIN_SPEED_TO_APPLY_SIDE_COLLISION_FRICTION = 16.67f; //about 60km/h float VEHICLE_MAX_SPEED_TO_APPLY_SIDE_COLLISION_FRICTION = 44.44f; //about 160km/h Vector3 CVehicle::smVecSelfRightBiasCGOffset (0.0f, 0.0f, -1.0f); bool CVehicle::sm_bUseNewSelfRighting = false; PF_GROUP(VehicleProcessPreComputeImpacts); PF_PAGE(VehicleImpacts, "Vehicle ProcessPreComputeImpacts"); PF_LINK(VehicleImpacts, VehicleProcessPreComputeImpacts); PF_TIMER(VehiclesTotal, VehicleProcessPreComputeImpacts); PF_TIMER(Wheels, VehicleProcessPreComputeImpacts); PF_TIMER(Audio, VehicleProcessPreComputeImpacts); PF_TIMER(FixStuck, VehicleProcessPreComputeImpacts); // bool CVehicle::ProcessShouldFindImpactsWithPhysical(const phInst* pOtherInst) const { // Trailer attached vehicles are forced inactive and therefore cause bad things if they manage to collide with the // vehicle towing the trailer they are attached to - We're avoiding that occurence with this if(m_nVehicleFlags.bIsCargoVehicle != 0) { CVehicle* parentVeh = GetAttachParentVehicle(); if(parentVeh == NULL) { parentVeh = GetDummyAttachmentParent(); } if(parentVeh != NULL && parentVeh->GetVehicleType() == VEHICLE_TYPE_TRAILER) { CTrailer* parentTrailer = static_cast(parentVeh); CVehicle* parentTrailerParentVeh = parentTrailer->GetAttachParentVehicle(); if(parentTrailerParentVeh != NULL && parentTrailerParentVeh->GetFragInst() == pOtherInst) { return false; } } } return true; } static dev_float SLOW_DOWN_DISTANCE = 10.0f; static dev_bool sbVerticalizeNormalsUnderSuperHeavy = true; // void CVehicle::ProcessPreComputeImpacts(phContactIterator rawImpacts) { #if __BANK if(!sm_PreComputeImpacts) return; #endif // PF_FUNC(CVehicle_ProcessPreComputeImpacts); PF_START(VehiclesTotal); if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy)) { if(GetVehicleType() == VEHICLE_TYPE_CAR) { ProcessPreComputeImpactsSuperDummyCar(rawImpacts); } return; } m_resetBoostObjects = false; int count = rawImpacts.CountElements(); phCachedContactIteratorElement *buffer = Alloca(phCachedContactIteratorElement, count); phCachedContactIterator impacts; impacts.InitFromIterator(rawImpacts, buffer, count); #if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE const CSecondSurfaceConfig& secondSurfaceConfig = pHandling->hFlags & HF_LESS_SNOW_SINK ? ms_vehicleSecondSurfaceConfigLessSink : ms_vehicleSecondSurfaceConfig; ProcessSecondSurfaceImpacts(impacts,secondSurfaceConfig); #endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE const int numWheels = GetNumWheels(); // Cache out some variables we frequently access. int *wheelFragChild = Alloca(int, numWheels * MAX_WHEEL_BOUNDS_PER_WHEEL); const VehicleType vehicleType = GetVehicleType(); //const ScalarV svVehicleVehicleFlattenMag = ScalarVFromF32(sfVehicleVehicleFlattenMag); const fwTransform &transform = GetTransform(); const Vector3 transformA(VEC3V_TO_VECTOR3(transform.GetA())); const Vector3 transformB(VEC3V_TO_VECTOR3(transform.GetB())); const Vector3 transformC(VEC3V_TO_VECTOR3(transform.GetC())); const float transformCZ = transformC.z; const float fVehicleSpeed = GetVelocity().Mag(); CVehicleModelInfoDoors* pVehicleDoorExtension = GetVehicleModelInfo()->GetExtension(); const Matrix34& matMyMatrix = RCC_MATRIX34(GetMatrixRef()); PF_START(Wheels); // We want to run PreComputeImpacts on impacts with extra bounds since those are kept. int maxWheelBoundsToNotPrecompute = BANK_ONLY(!ms_bPrecomputeExtraWheelBoundImpacts ? MAX_WHEEL_BOUNDS_PER_WHEEL : ) 1; TUNE_GROUP_BOOL( VEHICLE_SHUNT_TUNE, ENABLE_KURUMA_POLICE_SHUNT, true ); TUNE_GROUP_BOOL( VEHICLE_SHUNT_TUNE, ENABLE_KURUMA_SHUNT_AGAINST_ALL_VEHICLES, false ); TUNE_GROUP_FLOAT( VEHICLE_SHUNT_TUNE, MINIMUM_SPEED_FOR_SHUNT, 10.0f, 0.0f, 100.0f, 0.5f ); TUNE_GROUP_FLOAT( VEHICLE_SHUNT_TUNE, MINIMUM_Y_OFFSET_FOR_SHUNT, -0.75f, -1.0f, 4.0f, 0.25f ); TUNE_GROUP_FLOAT( VEHICLE_SHUNT_TUNE, SHUNT_FORCE_SCALE, 1.0f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_SHUNT_TUNE, OVERRIDE_INVERSE_MASS_SCALE, 1.0f, 0.0f, 10.0f, 0.1f ); bool bHasIncreasedRammingForce = ( HasIncreasedRammingForce() || ( GetVehicleModelInfo()->GetModelNameHash() == MI_CAR_KURUMA2.GetName().GetHash() ) ) && ( ENABLE_KURUMA_SHUNT_AGAINST_ALL_VEHICLES || ENABLE_KURUMA_POLICE_SHUNT ); // Cache out the frag child index for each wheel for later comparison. CPhysical* pDrivingOnPhysical = NULL; int numWheelFragChildren = 0; if(numWheels > 0) { pDrivingOnPhysical = m_ppWheels[0]->GetPrevHitPhysical(); //m_ppWheels[0]->GetHitPhysical(); // THE HIT PHYSICAL IS RESET AFTER THE WHEEL UPDATE AND ISN'T SET AGAIN UNTIL AFTER THIS bool drivingOnPhysicalFound = pDrivingOnPhysical != NULL; //Check if we are driving on one physical for(int i = 0; i < numWheels; ++i) { CWheel *wheel = m_ppWheels[i]; for(int k = 0; k < maxWheelBoundsToNotPrecompute; k++) { if(wheel->GetFragChild(k) > 0) { wheelFragChild[numWheelFragChildren] = wheel->GetFragChild(k); numWheelFragChildren++; } } if( wheel->GetPrevHitPhysical() != pDrivingOnPhysical && wheel->GetPrevHitPhysical() != NULL ) { if(drivingOnPhysicalFound) { pDrivingOnPhysical = NULL; } else { pDrivingOnPhysical = wheel->GetPrevHitPhysical(); drivingOnPhysicalFound = true; } } } } m_pGroundPhysical = pDrivingOnPhysical; PF_STOP(Wheels); //loop through vehicle gadgets and inform them about contacts. impacts.Reset(); for(int i=0; iProcessPreComputeImpacts(this, impacts); } if(m_pVehicleWeaponMgr) { m_pVehicleWeaponMgr->ProcessPreComputeImpacts(this, impacts); } bool processingAudioImpacts = false; if(count > 0) { processingAudioImpacts = m_VehicleAudioEntity->GetCollisionAudio().StartProcessImpacts(); } atUserArray aVehiclesFlattenNormalArray(Alloca(CVehicle*,count), (unsigned short)count); bool bDoFixStuckInGeometry = true; bool bImpactWithBeastVehicle = false; bool bIsTowing = GetIsTowing(); CVehicle* pRammedVehicle = NULL; CVehicle* pRampVehicle = NULL; impacts.Reset(); pRammedVehicle = FindRammingScoopVehicle( impacts ); impacts.Reset(); pRampVehicle = FindRampVehicle( impacts ); impacts.Reset(); ModifyPedImpacts( impacts ); bool resetSpeedBoostObjectID = true; CVehicle* sideShuntVehicle = NULL; // Main impact processing impacts.Reset(); while(!impacts.AtEnd()) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( ProcessFoliageImpact( otherInstance, impacts.GetMyPosition(), impacts.GetMyComponent(), impacts.GetOtherComponent(), impacts.GetOtherElement() ) ) { #if DEBUG_DRAW if( CPhysics::ms_bShowFoliageImpactsVehicles ) { grcDebugDraw::Sphere(impacts.GetMyPosition(), 0.2f, Color_yellow); grcDebugDraw::Sphere(impacts.GetOtherPosition(), 0.05f, Color_grey, false); grcDebugDraw::Line(impacts.GetMyPosition(), impacts.GetOtherPosition(), Color_grey); } #endif // DEBUG_DRAW // Now that we've noted the collision, disable the impact. impacts.DisableImpact(); ++impacts; continue; } else if( pOtherEntity && pOtherEntity->GetIsTypeObject() ) { if( ProcessBoostPadImpact( impacts, pOtherEntity ) ) { resetSpeedBoostObjectID = false; impacts.DisableImpact(); ++impacts; continue; } if( ( HasRammingScoop() || HasRamp() ) && static_cast( pOtherEntity )->m_nObjectFlags.bVehiclePart ) { if( !static_cast( pOtherEntity )->GetNetworkObject() || static_cast< CNetObjObject* >( static_cast( pOtherEntity )->GetNetworkObject() )->GetFragParentVehicleId() == NETWORK_INVALID_OBJECT_ID ) { Vec3V myNormal; impacts.GetMyNormal( myNormal ); const float verticalDot = Dot( myNormal, VECTOR3_TO_VEC3V( transformC ) ).Getf(); const float verticalThreshold = 0.3f; if( verticalDot > verticalThreshold ) { impacts.SetMyNormal( -transformC ); impacts.SetOtherNormal( transformC ); } impacts.SetFriction( 0.0f ); impacts.SetMassInvScales( 0.0f, 1.0f ); } } } #if !__NO_OUTPUT if(GetNetworkObject() && pOtherEntity && pOtherEntity->GetIsTypePed()) { CPed* otherPed = SafeCast(CPed, pOtherEntity); if(otherPed->GetNetworkObject() && otherPed->IsPlayer()) { CNetGamePlayer* playerOwner = otherPed->GetNetworkObject()->GetPlayerOwner(); if(playerOwner && playerOwner->GetPlayerPed() && playerOwner->GetPlayerPed()->GetNetworkObject() && SafeCast(CNetObjPlayer, playerOwner->GetPlayerPed()->GetNetworkObject())->IsSpectating()) { const bool bRunningTaskDyingDead = static_cast(playerOwner->GetPlayerPed()->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_DYING_DEAD)) ? true : false; if(!bRunningTaskDyingDead) { vehicleDebugf2("Vehicle %s is colliding with spectator player %s. Collision Enabled: %s", GetNetworkObject()->GetLogName(), otherPed->GetNetworkObject()->GetLogName(), otherPed->IsCollisionEnabled()?"TRUE":"FALSE"); gnetAssertf(0, "Vehicle %s is colliding with spectator player %s. Collision Enabled: %s", GetNetworkObject()->GetLogName(), otherPed->GetNetworkObject()->GetLogName(), otherPed->IsCollisionEnabled()?"TRUE":"FALSE"); } } } } #endif // !__NO_OUTPUT PF_START(Audio); // Process audio for one impact if(processingAudioImpacts) { m_VehicleAudioEntity->GetCollisionAudio().ProcessImpact(impacts); } PF_STOP(Audio); bool bWheelImpact = false; for(int i=0; i( pOtherEntity ); if( pOtherVehicle->HasRamp() ) { applyRamp = true; } } if( !applyRammingScoop && pOtherEntity && pOtherEntity->GetIsTypeVehicle() && pRammedVehicle == this ) { CVehicle* pOtherVehicle = static_cast< CVehicle* >( pOtherEntity ); if( pOtherVehicle->HasRammingScoop() ) { applyRammingScoop = true; } } if( pOtherEntity && pOtherEntity->GetIsTypeVehicle() && pOtherEntity != sideShuntVehicle ) { sideShuntVehicle = CheckForSideShuntImpact( impacts, static_cast( pOtherEntity ) ); } if( pOtherEntity ) { if( m_numWeaponBlades > 0 ) { if( IsWeaponBladeImpact( impacts ) ) { impacts.SetFriction( 0.0f ); ++impacts; continue; } } if( pOtherEntity->GetIsTypeVehicle() ) { CVehicle* otherVehicle = static_cast( pOtherEntity ); if( otherVehicle->GetNumWeaponBlades() > 0 ) { if( otherVehicle->IsWeaponBladeImpact( impacts ) ) { ++impacts; continue; } } } } ApplySpikeImpacts( impacts, pOtherEntity ); if( pOtherEntity && pOtherEntity->GetIsPhysical() ) { ApplyRammingBarToImpact( static_cast( pOtherEntity ), impacts ); } if( !bWheelImpact && !applyRammingScoop && !applyRamp ) { if( HasGlider() && !GetDriver() ) { FinishGliding(); } Vector3 vecNormal; impacts.GetMyNormal(vecNormal); const float fHitMaterialFrictionMult = PGTAMATERIALMGR->GetMaterial(impacts.GetOtherMaterialId()).GetFriction(); float fVecBDotvecNormal = DotProduct(transformB, vecNormal); float fVecCDotvecNormal = DotProduct(transformC, vecNormal); if (vehicleType!=VEHICLE_TYPE_TRAIN) { if(pOtherEntity && pOtherEntity->GetIsTypePed()) { CPed& hitPed = static_cast(*pOtherEntity); //If this vehicle hit a horse/quadruped, increase mass so we don't tip over B* 789145 #if ENABLE_HORSE if (hitPed.GetHorseComponent()) { static dev_float sf_ExtraVehMass = 0.5f; impacts.SetMassInvScales(1.0f-sf_ExtraVehMass,1.0f); } else #endif { // If we're not allowed to stand on this vehicle and the vehicle has a truck bed boot and we're colliding with a ragdolling ped... if (vehicleType == VEHICLE_TYPE_CAR && pVehicleDoorExtension != NULL && ((otherInstance->GetClassType() == PH_INST_FRAG_PED && PHLEVEL->IsActive(otherInstance->GetLevelIndex()) && !CanPedsStandOnTop()) || (otherInstance->GetClassType() == PH_INST_PED && GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_INTERIOR_BLOCKED_BY_BOOT))) ) { CCarDoor* pDoor = NULL; if( pVehicleDoorExtension->ContainsThisDoorWithCollision(VEH_BOOT) ) { CCarDoor* door = GetDoorFromId(VEH_BOOT); if(door != NULL && door->GetFragChild() == impacts.GetMyComponent()) { pDoor = door; } } else if( pVehicleDoorExtension->ContainsThisDoorWithCollision(VEH_DOOR_DSIDE_R) || pVehicleDoorExtension->ContainsThisDoorWithCollision(VEH_DOOR_PSIDE_R)) { // 'FLAG_INTERIOR_BLOCKED_BY_BOOT' meta flag can also be used for double door setups - this section handles selecting the right door for that case CCarDoor* door1 = GetDoorFromId(VEH_DOOR_DSIDE_R); CCarDoor* door2 = GetDoorFromId(VEH_DOOR_PSIDE_R); if(door1 != NULL && door1->GetFragChild() == impacts.GetMyComponent()) { pDoor = door1; } else if(door2 != NULL && door2->GetFragChild() == impacts.GetMyComponent()) { pDoor = door2; } } // Check that the ragdolling ped is in fact colliding with a closed truck bed boot... if (pDoor != NULL && pDoor->GetDoorRatio() < 1.0f) { // A ragdoll is hitting the boot of this vehicle, let's check to see if it's hitting from the inside of the vehicle... Vec3V vOtherNormal; impacts.GetOtherNormal(vOtherNormal); Vec3V vBootToVehicleCenter = NormalizeSafe(Subtract(GetTransform().GetPosition(), impacts.GetMyPosition()), Vec3V(V_ZERO)); if (IsGreaterThanAll(Dot(vBootToVehicleCenter, vOtherNormal), ScalarV(V_ZERO)) != 0) { bool useChassisBoundMax = MI_CAR_MULE4.IsValid() && GetModelIndex() == MI_CAR_MULE4; Vec3V vVehicleBoundMin = VECTOR3_TO_VEC3V( GetBoundingBoxMin() ); Vec3V vVehicleBoundMax = !useChassisBoundMax ? VECTOR3_TO_VEC3V( GetBoundingBoxMax() ) : VECTOR3_TO_VEC3V( GetChassisBoundMax() ); bool pedWithinBounds = true; Vec3V vPedPosition = otherInstance->GetPosition(); vPedPosition = GetTransform().UnTransform( vPedPosition ); if( !IsGreaterThanAll( vPedPosition, vVehicleBoundMin ) || !IsLessThanAll( vPedPosition, vVehicleBoundMax ) ) { pedWithinBounds = false; } if( pedWithinBounds ) { if(pDoor->GetTargetDoorRatio() < 1.0f && hitPed.IsPlayer()) { // Clear flags before setting them, avoid any flag conflicts pDoor->ClearFlag(CCarDoor::DRIVEN_AUTORESET | CCarDoor::WILL_LOCK_SWINGING | CCarDoor::DRIVEN_NORESET | CCarDoor::DRIVEN_SPECIAL); // Let's force it open! pDoor->SetTargetDoorOpenRatio(1.0f, CCarDoor::DRIVEN_AUTORESET | CCarDoor::WILL_LOCK_SWINGING, this); } else { // Set driven flag for another frame of contact pDoor->SetFlag(CCarDoor::DRIVEN_AUTORESET); } } } } } } if (ShouldDisableImpactForPedExitingVehicle(hitPed, *this, impacts.GetMyComponent())) { impacts.DisableImpact(); ++impacts; continue; } // Push peds away else { if(CCarDoor* pArticulatedDoor = GetArticulatedDoorFromComponent(impacts.GetMyComponent())) { if(pArticulatedDoor->GetFlag(CCarDoor::PED_DRIVING_DOOR)) { impacts.SetMassInvScales(sm_fDoorPushMassScale,1.0f); } else { // Reset the mass inverse scales once the door stops being driven impacts.SetMassInvScales(1.0f,1.0f); } } } } else if(pOtherEntity && pOtherEntity->GetIsTypeVehicle() && !impacts.IsConstraint()) { CVehicle* pOtherVehicle = static_cast(pOtherEntity); if( m_BeastVehicle || pOtherVehicle->GetIsBeastVehicle() ) { bImpactWithBeastVehicle = true; } //Check if the other instance is first in the impact. bool bIsOtherInstanceFirst = (impacts.GetRootManifold().GetInstanceA() == otherInstance); if(pOtherVehicle->InheritsFromTrain() && InheritsFromBike() && !GetDriver())// if we're on a train and don't have a rider we don't need the fix stuck in geometry stuff. { bDoFixStuckInGeometry = false; } // Want to avoid flattening normals if either impact is on a wheel so check the other vehicle. bool bOtherWheelImpact = false; for(int i=0; iGetNumWheels(); i++) { if(impacts.GetOtherComponent()==pOtherVehicle->GetWheel(i)->GetFragChild()) { bOtherWheelImpact = true; break; } } // If we're not driving on a physical (i.e. our wheels aren't touching anything physical) and we're impacting a part of another vehicle other than its wheels // then record the physical as our 'ground entity' if(pDrivingOnPhysical == NULL && !bOtherWheelImpact) { //Check that the normal is generally pointing up. Vector3 vNormal; impacts.GetMyNormal(vNormal); if(vNormal.z >= 0.5f) { //Check that this object is generally above the other entity. if(impacts.GetMyInstance() && (impacts.GetMyInstance()->GetPosition().GetZf() > impacts.GetOtherPosition().GetZf())) { //Ensure the other object is physical and is large enough to cause relative motion - otherwise we don't care about it. CEntity* pEntity = CPhysics::GetEntityFromInst(impacts.GetOtherInstance()); if(pEntity && pEntity->GetIsPhysical() && DoesGroundCauseRelativeMotion(static_cast(pEntity))) { if(bIsTowing && pEntity->GetIsTypeVehicle() && static_cast(pEntity)->GetAttachParentVehicle() == this) { // If the vehicle is being towed by me, it can not be considered as ground physical } else { //Assign the ground. m_pGroundPhysical = static_cast(pEntity); m_uGroundPhysicalTime = fwTimer::GetTimeInMilliseconds(); pDrivingOnPhysical = m_pGroundPhysical; } } } } } // Don't adjust contacts between articulated pieces on vehicles as they typically don't affect other vehicles and can have much more // complex collision bool isEitherVehiclePartArticulated = pOtherVehicle->GetFragInst()->GetLinkIndexFromComponent(impacts.GetOtherComponent()) != 0 || GetFragInst()->GetLinkIndexFromComponent(impacts.GetMyComponent()) != 0; // Don't mess with articulated link mass scales (doors mainly) if(!isEitherVehiclePartArticulated) { //Calculate the inv mass scale. float fInvMassScale = CalculateInvMassScale(fVehicleSpeed, bIsOtherInstanceFirst, *pOtherVehicle); if(fInvMassScale != 1.0f) { impacts.SetMassInvScales(fInvMassScale,1.0f); } // B*1788622: If a bulldozer is driving over a vehicle, fiddle with the inverse mass scales so that this vehicle experiences less of the impact and the bulldozer gets pushed back twice as much. // This helps balance out the huge mass difference which was causing the impacts to have a large depth and the vehicle being crushed would freak out. if(pOtherVehicle->GetModelIndex() == MI_CAR_BULLDOZER && GetModelIndex() != MI_CAR_BULLDOZER) { if(IsTrue(Dot(impacts.GetOtherPosition() - GetTransform().GetPosition(), RCC_VEC3V(ZAXIS)) > ScalarV(0.5f))) { impacts.SetMassInvScales(0.1f, 2.0f); } } } #if __DEV if(CDebugScene::bDisplayVehicleCollisionsOnVMap) { CVectorMap::MakeEventRipple(VEC3V_TO_VECTOR3(impacts.GetOtherPosition()),20.0f,1000,Color_bisque); } #endif // breaks the door off if it deeply intersects with other vehicle if (GetVehicleType()==VEHICLE_TYPE_CAR && impacts.GetDepth() > 0.2f) { CCarDoor* pMyDoor = NULL; for(int i = 0; i < GetNumDoors(); i++) { CCarDoor *pDoor = GetDoor(i); if(impacts.GetMyComponent()==pDoor->GetFragChild())// are we contacting this door. { pMyDoor = pDoor; break; } } if(pMyDoor && pMyDoor->GetHierarchyId() != VEH_BONNET && pMyDoor->GetHierarchyId() != VEH_BOOT && !pMyDoor->GetIsLatched(this) && pMyDoor->GetDoorAllowedToBeBrokenOff()) { // ...the break off on the next vehicle update pMyDoor->BreakOffNextUpdate(); } } static dev_bool sbFlattenNormals = true; if(sbFlattenNormals) { // Bikes should not need to flatten because they already do extra work to mess with their angular inertias // And flattening in general is much worse for them because they tend to be able to get inward pointing normals because their chassis // can be significantly more concave than cars if((GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_TRAILER /*|| GetVehicleType()==VEHICLE_TYPE_BIKE*/) && (GetVehicleModelInfo()->GetModelNameHash() != MI_CAR_PANTO.GetName().GetHash() || (GetVehicleModelInfo()->GetModelNameHash() == MI_CAR_PANTO.GetName().GetHash() && pOtherVehicle->GetVehicleType() != VEHICLE_TYPE_PLANE) )) { static dev_float sfVehicleVehicleFlattenMag = 0.85f; // GTAV - B*1799549 - Hack - if this is a car colliding with the titan // flatten the normals if the titan isn't moving and we're trying to go under it bool forceFlattenNormal = false; if( pOtherVehicle->GetModelIndex() == MI_PLANE_TITAN.GetModelIndex() && m_vehicleType == VEHICLE_TYPE_CAR && fVecCDotvecNormal < -0.3f && GetMass() < sfMaximumMassForPushingVehicles ) { if( !pOtherVehicle->IsInAir() && pOtherVehicle->GetVelocity().Mag2() < 1.0f ) { forceFlattenNormal = true; } } // GTAV HACK - If the kuruma2, which is armoured, hits a police vehicle side on // try and make the police vehicle spin out in a more exciting way. if( bHasIncreasedRammingForce ) { // make sure the other vehicle is a law enforcement one, we are going fast enough, // and the dot of the contact normal with the forward vector indicates a forward collision if( ( pOtherVehicle->m_nVehicleFlags.bIsLawEnforcementVehicle || ENABLE_KURUMA_SHUNT_AGAINST_ALL_VEHICLES || HasIncreasedRammingForceVsAllVehicles() ) && fVehicleSpeed > MINIMUM_SPEED_FOR_SHUNT && rage::Abs( fVecBDotvecNormal ) > 1.0f - sfVehicleVehicleSideImpactMag ) { Vector3 fVecBDotOtherNormal; const Matrix34& matOtherMatrix = RCC_MATRIX34( pOtherVehicle->GetMatrixRef() ); impacts.GetOtherNormal( fVecBDotOtherNormal ); float fBDotOtherNormal = DotProduct( matOtherMatrix.GetVector( 1 ), fVecBDotOtherNormal ); if( rage::Abs( fBDotOtherNormal ) < sfVehicleVehicleSideImpactMag ) { Vector3 vOtherHitPosLocal = VEC3V_TO_VECTOR3( impacts.GetOtherPosition() ); matOtherMatrix.UnTransform( vOtherHitPosLocal ); if( rage::Abs( vOtherHitPosLocal.GetY() ) > MINIMUM_Y_OFFSET_FOR_SHUNT ) { if( OVERRIDE_INVERSE_MASS_SCALE != 1.0f ) { impacts.SetMassInvScales( OVERRIDE_INVERSE_MASS_SCALE, 1.0f ); } vOtherHitPosLocal.SetY( 0.0f ); vOtherHitPosLocal.SetZ( 0.0f ); vOtherHitPosLocal.Normalize(); static dev_float sfMaxRammingForceAcceleration = 20.0f; float maxRammingForceScale = pOtherVehicle->GetMass() * sfMaxRammingForceAcceleration; float rammingForceScale = -GetMass() * fVehicleSpeed * rage::Abs( fVecBDotvecNormal ) * SHUNT_FORCE_SCALE * GetIncreasedRammingForceScale(); rammingForceScale = Clamp( rammingForceScale, -maxRammingForceScale, maxRammingForceScale ); vOtherHitPosLocal.Scale( rammingForceScale ); matOtherMatrix.Transform( vOtherHitPosLocal ); pOtherVehicle->ApplyForceCg( vOtherHitPosLocal ); m_nVehicleFlags.bCarHitWhileInRoadBlock = true; forceFlattenNormal = true; } } } } if( HasRamp() != pOtherVehicle->HasRamp() ) { if( pOtherVehicle->m_hasHitRampCar ) { impacts.SetFriction( 0.0f ); impacts.SetMassInvScales(0.0f,1.0f); } else if( m_hasHitRampCar ) { impacts.SetFriction( 0.0f ); impacts.SetMassInvScales(1.0f,0.0f); } } if( forceFlattenNormal || ( rage::Abs(fVecCDotvecNormal) < sfVehicleVehicleFlattenMag && pOtherVehicle != pDrivingOnPhysical && pOtherVehicle->m_pGroundPhysical != this ) ) { // Don't flatten normals if one of these vehicles is pushable and the other can push it if( forceFlattenNormal || !((GetMass() < sfMaximumMassForPushingVehicles) ^ (pOtherVehicle->GetMass() < sfMaximumMassForPushingVehicles))) { //Check if we are hitting a boot or bonnet, as these can be "driveable doors" and so need to be treated as normal contacts bool bPossibleDriveableDoorImpact = bIsOtherInstanceFirst; bool bVehicleHasOpenDriveableDoor = false; CVehicleModelInfoDoors* pOtherVehicleDoorExtension = pOtherVehicle->GetVehicleModelInfo()->GetExtension(); if( pOtherVehicleDoorExtension && !bPossibleDriveableDoorImpact) { for(int i = 0; i < pOtherVehicle->GetNumDoors(); i++) { CCarDoor* pDoor = pOtherVehicle->GetDoor(i); if(pOtherVehicleDoorExtension->ContainsThisDriveableDoor(pDoor->GetHierarchyId())) { if(!pDoor->GetIsLatched(pOtherVehicle))// Is this door open { bVehicleHasOpenDriveableDoor = true; if(impacts.GetOtherComponent()==pDoor->GetFragChild())// are we contacting this door. { bPossibleDriveableDoorImpact = true; } } } } } if( pVehicleDoorExtension && !bPossibleDriveableDoorImpact) { for(int i = 0; i < GetNumDoors(); i++) { CCarDoor* pDoor = GetDoor(i); if(pVehicleDoorExtension->ContainsThisDriveableDoor(pDoor->GetHierarchyId())) { if(!pDoor->GetIsLatched(this))// Is this door open { bVehicleHasOpenDriveableDoor = true; if(impacts.GetMyComponent()==pDoor->GetFragChild())// are we contacting this door. { bPossibleDriveableDoorImpact = true; } } } } } if(!bPossibleDriveableDoorImpact)//don't modify impacts when they are against doors that cars might want to drive along { Vector3 vecUp = transformC.DotV(vecNormal); vecNormal.Subtract(vecUp * transformC); vecNormal.NormalizeSafe(transformC); if(!bOtherWheelImpact) { impacts.SetMyNormal(vecNormal); if(!bVehicleHasOpenDriveableDoor && !(pOtherVehicle->GetVehicleModelInfo() && pOtherVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_CAN_BE_DRIVEN_ON)))// if the vehicle has a driveable door don't add it to the flattened normal array as its safer to just allow contacts with the vehicle. { aVehiclesFlattenNormalArray.Push(pOtherVehicle); } static dev_bool sbMoveImpacts = true; if(sbMoveImpacts) { if(pOtherVehicle && pOtherVehicle->pHandling) { //Move the vehicle to vehicle impact to the COG, so we don't flip cars as easily when ramming them Vector3 vecOtherPosition = VEC3V_TO_VECTOR3(impacts.GetOtherPosition()); Vector3 vCgOffset = RCC_VECTOR3(pOtherVehicle->pHandling->m_vecCentreOfMassOffset); Matrix34 TempMat(MAT34V_TO_MATRIX34(pOtherVehicle->GetMatrix())); TempMat.Transform3x3(vCgOffset); TempMat.d += vCgOffset; vecOtherPosition.z = TempMat.d.z; impacts.SetOtherPositionDoNotUse(VECTOR3_TO_VEC3V(vecOtherPosition)); } } } } if(rage::Abs(fVecBDotvecNormal) < sfVehicleVehicleSideImpactMag && (GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_TRAILER))//we've been hit in the side so reduce tyre grip { m_nVehicleFlags.bCarAgainstCarSideCollision = true; } const Vector3 otherTransformA(VEC3V_TO_VECTOR3(pOtherVehicle->GetTransform().GetA())); if(rage::Abs(DotProduct(transformA, vecNormal)) > sfVehicleVehicleSideCollisionNormalX && rage::Abs(DotProduct(otherTransformA, vecNormal)) > 0.9f) { float fMaxSpeed = Max(fVehicleSpeed, pOtherVehicle->GetVelocity().Mag()); if(fMaxSpeed > VEHICLE_MIN_SPEED_TO_APPLY_SIDE_COLLISION_FRICTION) { float fFrictionRatio = (fMaxSpeed - VEHICLE_MIN_SPEED_TO_APPLY_SIDE_COLLISION_FRICTION) / (VEHICLE_MAX_SPEED_TO_APPLY_SIDE_COLLISION_FRICTION - VEHICLE_MIN_SPEED_TO_APPLY_SIDE_COLLISION_FRICTION); fFrictionRatio = Clamp(fFrictionRatio, 0.0f, 1.0f); float fFriction = VEHICLE_VEHICLE_COLLISION_FRICTION + fFrictionRatio * (VEHICLE_VEHICLE_COLLISION_MAX_FRICTION - VEHICLE_VEHICLE_COLLISION_FRICTION); impacts.SetFriction(Max(fFriction, impacts.GetFriction())); if(GetCollider()) impacts.SetMyPositionDoNotUse(GetCollider()->GetPosition()); if(pOtherVehicle->GetCollider()) impacts.SetOtherPositionDoNotUse(pOtherVehicle->GetCollider()->GetPosition()); m_nVehicleFlags.bCarBrushAgainstCarSideCollision = true; } } if( GetVehicleType() == VEHICLE_TYPE_CAR && pHandling->GetCarHandlingData() && !GetIsAnyFixedFlagSet() && pOtherVehicle->GetVehicleType() == VEHICLE_TYPE_CAR && pOtherVehicle->pHandling->GetCarHandlingData() && !pOtherVehicle->GetIsAnyFixedFlagSet()) { // if car is running forward if(GetVelocity().Dot(matMyMatrix.b) > 5.0f) { Vector3 vMyHitPosLocal; matMyMatrix.UnTransform(VEC3V_TO_VECTOR3(impacts.GetMyPosition()), vMyHitPosLocal); // if the impact is happened at the front side of the car if(vMyHitPosLocal.y > 0.0f) { impacts.SetElasticity(VEHICLE_VEHICLE_HEAD_ON_COLLISION_ELASTICITY); Vector3 vMyPosition = VEC3V_TO_VECTOR3(impacts.GetMyPosition()); Matrix34 TempMat(MAT34V_TO_MATRIX34(GetMatrix())); TempMat.UnTransform(vMyPosition); if(vMyPosition.z > pHandling->m_vecCentreOfMassOffset.GetZf()) { vMyPosition.z = pHandling->m_vecCentreOfMassOffset.GetZf(); TempMat.Transform(vMyPosition); impacts.SetMyPosition(VECTOR3_TO_VEC3V(vMyPosition)); } } } } } else if(rage::Abs(fVecBDotvecNormal) < sfVehicleVehicleSideImpactMag && (GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_TRAILER))//we've been hit in the side so reduce tyre grip { m_nVehicleFlags.bCarHitByHeavyVehicle = true; } } } } if(GetVehicleType() == VEHICLE_TYPE_TRAILER) { CTrailer *pThisTrailer = static_cast(this); if(pThisTrailer->GetAttachParent() == pOtherVehicle) { // Flag the collision pThisTrailer->SetCollidedWithAttachParent(true); // And disable it // B*1811806: Allow the cargobob to collide with a trailer that is attached to its pickup rope. Allowing the intersection was causing the attachment and rope constraints to act erratically. if(!(pOtherVehicle->InheritsFromHeli() && static_cast(pOtherVehicle)->GetIsCargobob())) impacts.DisableImpact(); // We're no longer allowing any contacts between attached trailers and their parent // - Relying on rotation constraints to keep weird penetration from occurring // and the above flag will let us detach the trailer if it jackknifes too far } } if((GetVehicleType() != VEHICLE_TYPE_SUBMARINE) && rage::Abs(DotProduct(transformB, vecNormal)) > 0.7f && DotProduct(transformB, VEC3V_TO_VECTOR3(impacts.GetRelVelocity())) < -2.0f) { impacts.SetFriction(VEHICLE_VEHICLE_COLLISION_FRICTION*fHitMaterialFrictionMult); } //If we are a bike or hitting a bike, reduce the friction so the bike doesn't stick to cars. if( (InheritsFromBike() && GetDriver()) || (pOtherVehicle->InheritsFromBike() && pOtherVehicle->GetDriver())) { impacts.SetFriction(0.0f); } if(pOtherVehicle->InheritsFromBike() && (DotProduct(transformC, vecNormal) > 0.7f)) { pOtherVehicle->m_nVehicleFlags.bCarCrushingBike = true; } // Switch the car back to physics s32 slot = CVehicleRecordingMgr::GetPlaybackSlotFast(this); if(slot != -1 && !CVehicleRecordingMgr::GetUseCarAI(slot) ) { if( pOtherVehicle->ContainsLocalPlayer() && CVehicleRecordingMgr::GetVehicleRecordingFlags(slot)&CVehicleRecordingMgr::VRF_ConvertToAIOnImpact_PlayerVehcile ) { m_nVehicleFlags.bSwitchToAiRecordingThisFrame = true; } else if( CVehicleRecordingMgr::GetVehicleRecordingFlags(slot)&CVehicleRecordingMgr::VRF_ConvertToAIOnImpact_AnyVehicle ) { m_nVehicleFlags.bSwitchToAiRecordingThisFrame = true; } } // Special tank crushing hackery // - The way this ended up getting tuned there actually isn't very much change in normals at all // - Most of the effect comes from drastically altering the opposing vehicle's apparent mass // -- In particular, note that the change in mass scales applies to all contacts on the same manifold // and the angular inertia change will actually effect all interactions with that vehicle // (but should clear out each frame because we're just setting what the solver sees) const float throttleThresh = 0.0f; const ScalarV verticalThreshold(0.66f); const ScalarV veryVerticalThreshold(0.97f); const float myMassInvScale = 0.1f; const float otherMassInvScale = 0.018f; const ScalarV otherAngInertiaScale(0.15f); if(sbVerticalizeNormalsUnderSuperHeavy) { // Think about adding a sfMinimumMassForCrushingVehiclesTargets to add a bit of a buffer zone so similar but slightly lower masses don't get messed with here // - Really, though we should just change this into a handling flag or a pair of handling flags for crushing and crushable vehicles if(( GetMass() >= sfMinimumMassForCrushingVehicles || ( GetVehicleModelInfo() && GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_CRUSHES_OTHER_VEHICLES ) ) ) && pOtherVehicle->GetMass() < sfMinimumMassForCrushingVehicles) { Vec3V myContactNorm; impacts.GetMyNormal(myContactNorm); const float throttleMag = Abs(GetThrottle()); const Vec3V myVehUpDir = GetMatrix().GetCol2(); const ScalarV verticalDot = Dot(myContactNorm, myVehUpDir); if( IsGreaterThanAll(verticalDot, verticalThreshold) != 0 ) { // Don't move the normal if we aren't powering the engine if(throttleMag > throttleThresh) { if( IsGreaterThanOrEqualAll(verticalDot, veryVerticalThreshold) != 0 ) { impacts.SetMyNormal(myVehUpDir); } else { ScalarV normLerpVal = InvScale( Subtract(verticalDot, verticalThreshold), Subtract(veryVerticalThreshold, verticalThreshold) ); Vec3V newNorm = rage::Add( Scale(myContactNorm, Subtract(ScalarV(V_ONE), normLerpVal)), Scale(myVehUpDir, normLerpVal) ); newNorm = Normalize(newNorm); impacts.SetMyNormal(myVehUpDir); } impacts.SetMassInvScales(myMassInvScale, otherMassInvScale); phCollider* pOtherCollider = pOtherVehicle->GetCollider(); if(pOtherCollider) { Vec3V otherInvAngInertia = pOtherCollider->GetInvAngInertia(); otherInvAngInertia = Scale(otherInvAngInertia, otherAngInertiaScale); pOtherCollider->SetSolverInvAngInertia(otherInvAngInertia.GetIntrin128()); } } } // Mark the smaller vehicle as being in contact with something super heavy pOtherVehicle->m_nVehicleFlags.bCarHitBySuperHeavyVehicle = true; } } // !!! GTA V HACK !!! // Stop bikes from going to sleep if their chassis is touching another vehicle. if(GetVehicleType() == VEHICLE_TYPE_BIKE) { m_nVehicleFlags.bRestingOnPhysical = true; m_nVehicleFlags.bDontSleepOnThisPhysical = true; } } else if(IsFragInst(otherInstance)) { fragInst* otherFrag = static_cast(otherInstance); const fragPhysicsLOD* otherPhysics = otherFrag->GetTypePhysics(); int otherComponent = impacts.GetOtherComponent(); if(Verifyf(otherComponent < otherPhysics->GetNumChildren(), "Out of range component %i/%i on non-Ped type '%s'.",otherComponent,otherPhysics->GetNumChildren(),otherInstance->GetArchetype()->GetFilename())) { const fragTypeChild* otherChild = otherPhysics->GetChild(otherComponent); const fragTypeGroup* otherGroup = otherPhysics->GetGroup(otherChild->GetOwnerGroupPointerIndex()); if (otherGroup->GetDoesntAffectVehicles()) { impacts.SetMassInvScales(0.0f,1.0f); } if (!otherFrag->GetBroken() && otherGroup->GetDoesntPushVehiclesDown() && !InheritsFromBicycle()) { if (vecNormal.z < 0.0f) { vecNormal.z = 0.0f; vecNormal.NormalizeSafe(); impacts.SetMyNormal(vecNormal); } } if( pOtherEntity && pOtherEntity->GetIsTypeObject() && !InheritsFromPlane() && !InheritsFromHeli() && ( pOtherEntity->GetModelNameHash() == MI_PROP_FOOTBALL.GetName().GetHash() || pOtherEntity->GetModelNameHash() == MI_PROP_STUNT_FOOTBALL.GetName().GetHash() || pOtherEntity->GetModelNameHash() == MI_PROP_STUNT_FOOTBALL2.GetName().GetHash() || static_cast< CObject* >( pOtherEntity )->GetObjectIsBall() ) ) { Vec3V contactNorm; impacts.GetMyNormal( contactNorm ); static float minZNormal = 0.0f; if( contactNorm.GetZf() < minZNormal ) { static float velocityMagSquaredForMaxReduction = 400.0f; static float velocityMagSquaredForMinReduction = 1.0f; float reduction = ( GetVelocity().Mag2() - velocityMagSquaredForMinReduction ) / ( velocityMagSquaredForMaxReduction / velocityMagSquaredForMinReduction ); reduction = Clamp( reduction, 0.0f, 1.0f ); reduction *= 1.0f + minZNormal; reduction *= 0.75f; contactNorm.SetZ( minZNormal + ( ( contactNorm.GetZf() - minZNormal ) * ( 1.0f - reduction ) ) ); contactNorm = Normalize( contactNorm ); impacts.SetMyNormal( contactNorm ); Vector3 vecOtherPosition = VEC3V_TO_VECTOR3(impacts.GetOtherPosition()); Matrix34 TempMat(MAT34V_TO_MATRIX34(pOtherEntity->GetMatrix())); vecOtherPosition.z = vecOtherPosition.z + ( ( TempMat.d.z - vecOtherPosition.z ) * reduction ); impacts.SetOtherPosition(VECTOR3_TO_VEC3V(vecOtherPosition)); impacts.GetMyNormal( contactNorm ); float velocityAlongNormal = Dot( contactNorm, VECTOR3_TO_VEC3V( GetVelocity() ) ).Getf(); static float forceScale = 0.015f; contactNorm *= ScalarV( velocityAlongNormal * forceScale * GetMass() * reduction ); static_cast< CPhysical* >( pOtherEntity )->ApplyForceCg( VEC3V_TO_VECTOR3( contactNorm ) ); } } } } else if( vecNormal.z > 0.7f) { if( GetVehicleType() != VEHICLE_TYPE_SUBMARINE && (DotProduct(transformC, vecNormal) > 0.7f) ) { float finalMultiplier = VEHICLE_GROUND_COLLISION_FRICTION; if(GetVehicleType() == VEHICLE_TYPE_HELI) { finalMultiplier *= HELI_FRICTION_SCALE; } else if(GetVehicleType() == VEHICLE_TYPE_TRAILER) { if(static_cast(this)->AreTrailerLegsLoweredFully()) { // Increase friction if the trailer legs are down finalMultiplier = VEHICLE_TRAILER_LEG_COLLISION_FRICTION; } } impacts.SetFriction(finalMultiplier*fHitMaterialFrictionMult); } if( IsOnItsSide() && InheritsFromBike() && !GetDriver() ) { impacts.SetFriction( fHitMaterialFrictionMult * VEHICLE_BIKE_GROUND_COLLISION_FRICTION ); } if(InheritsFromAutomobile() && IsUpsideDown() && sm_bUseNewSelfRighting) { Vector3 vMyPosition = VEC3V_TO_VECTOR3(impacts.GetMyPosition()); Vector3 vBiasCgOffset = smVecSelfRightBiasCGOffset; Matrix34 TempMat(MAT34V_TO_MATRIX34(GetMatrix())); TempMat.Transform3x3(vBiasCgOffset); vMyPosition -= vBiasCgOffset; impacts.SetMyPositionDoNotUse(VECTOR3_TO_VEC3V(vMyPosition)); } if( HasRammingScoop() && transformCZ > 0.5f ) { static const int numRammingScoopBones = 4; static const eHierarchyId rammingScoopBoneIds[ numRammingScoopBones ] = { VEH_RAMMING_SCOOP, VEH_SCOOP_1_MOD, VEH_SCOOP_2_MOD, VEH_SCOOP_3_MOD }; for( int i = 0; i < numRammingScoopBones; i++ ) { int rammingScoopIndex = GetBoneIndex( rammingScoopBoneIds[ i ] ); if( rammingScoopIndex > -1 ) { rammingScoopIndex = GetVehicleFragInst()->GetComponentFromBoneIndex( rammingScoopIndex ); } if( impacts.GetMyComponent() == rammingScoopIndex ) { impacts.SetFriction( 0.0f ); } } } if( GetVehicleType() != VEHICLE_TYPE_PLANE && GetVehicleType() != VEHICLE_TYPE_SUBMARINE && !InheritsFromBike() && !InheritsFromBoat() && GetStatus() != STATUS_WRECKED) { if(transformCZ < 0.1f) { float m = (GetVehicleType() == VEHICLE_TYPE_HELI ? HELI_FRICTION_SCALE: 1.0f); if(rage::Abs(fVecBDotvecNormal) > sfVehicleVehicleSideImpactMag) { impacts.SetFriction(VEHICLE_UPSIDE_DOWN_COLLISION_FRICTION*fHitMaterialFrictionMult*m); impacts.SetElasticity(0.0f); } else { impacts.SetFriction(VEHICLE_UPSIDE_DOWN_COLLISION_SIDEWAYS_FRICTION*fHitMaterialFrictionMult*m); impacts.SetElasticity(0.0f); } } if(m_fTimeInAir > VEHICLE_IN_AIR_CRASH_TIME && GetVelocity().Mag2() > VEHICLE_IN_AIR_CRASH_MIN_SPEED_SQ) { m_bInAirCrash = true; if(m_fTimeInAirCrash < VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE_TIME) { impacts.SetFriction(Min(VEHICLE_IN_AIR_CRASH_MAX_FRICTION,VEHICLE_IN_AIR_CRASH_FRICTION_INCREASE*fHitMaterialFrictionMult)); } if(m_fTimeInAirCrash < VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE_TIME) { impacts.SetElasticity(VEHICLE_IN_AIR_CRASH_ELASTICITY_INCREASE); } } } } else if( GetVehicleType()==VEHICLE_TYPE_BIKE && vecNormal.z > 0.3f && rage::Abs(DotProduct(VEC3V_TO_VECTOR3(transform.GetA()), vecNormal)) > 0.7f ) { // For bikes that are on their sides, increase the friction to stop them sliding about // all over the place. impacts.SetFriction(pHandling->GetBikeHandlingData()->m_fBikeGroundSideFrictionMult*fHitMaterialFrictionMult); // Lock the wheels if bike is on its side with no driver. if(!GetDriver()) { SetHandBrake(true); SetBrake(1.0f); } } else if(pOtherEntity && pOtherEntity->GetIsTypeVehicle() && impacts.IsConstraint()) { CVehicle* pOtherVehicle = static_cast(pOtherEntity); // If we are attached to a trailer modify the mass scales so a very heavy trailer appears to be // lighter and then apply the remaining impulse directly to the root of the chassis if(pOtherVehicle->GetVehicleType() == VEHICLE_TYPE_TRAILER) { CTrailer* pOtherVehicle = static_cast(pOtherEntity); if(pOtherVehicle->GetAttachParent() == this) { static dev_float sfForceMultToApplyInConstraint = 0.8f; static dev_float sfForceBiasToApplyInConstraint = 0.6f; float vehicleMass = GetMass(); float trailerMass = pOtherVehicle->GetTotalMass(); // Change the amount of force applied in the constraint depending on the mass of the truck and the trailer being towed, to try and improve handling float forceToApply = 1.0f - sfForceMultToApplyInConstraint * rage::Clamp( (trailerMass/vehicleMass) - sfForceBiasToApplyInConstraint, 0.0f, 1.0f); float fForceMultToApplyInConstraint = rage::Clamp(forceToApply, 0.0f, 1.0f); impacts.SetMassInvScales(fForceMultToApplyInConstraint,1.0f); Vector3 myImpulse; impacts.GetMyImpulse(myImpulse); myImpulse *= (1.0f-fForceMultToApplyInConstraint); static dev_float sfMaxImpulseSpeed = 100.0f; //make sure we don't apply too big an impulse if( myImpulse.Mag2() < square(sfMaxImpulseSpeed * rage::Max(1.0f, GetMass())) ) { ApplyImpulseCg(myImpulse); } } } } else { if(GetVehicleType()==VEHICLE_TYPE_CAR && pOtherEntity && pOtherEntity->GetIsTypeBuilding()) { // Lower the friction on corner collisions // To determine if it's a corner, transform the normal into the car's space and then look at the // individual vector components to find the cosine. If the local normal is within the given angle // of the three axes we know it's not a corner const ScalarV upAngleCosine = ScalarV(V_HALF); // 60 degrees const ScalarV fowardAngleCosine = ScalarV(0.906f); // 25 degrees const ScalarV sideAngleCosine = ScalarV(0.965f); // 15 degrees Vec3V absLocalNormal = Abs(UnTransform3x3Ortho(transform.GetMatrix(),RCC_VEC3V(vecNormal))); if(IsLessThanAll(absLocalNormal,Vec3V(sideAngleCosine,fowardAngleCosine,upAngleCosine))) { impacts.SetFriction(VEHICLE_CORNER_WALL_COLLISION_FRICTION*fHitMaterialFrictionMult); } // if the contact is with the top of the car and we've got raised hydraulics then lower them Vec3V myContactNorm; impacts.GetMyNormal(myContactNorm); const Vec3V myVehUpDir = GetMatrix().GetCol2(); const ScalarV verticalDot = Dot(myContactNorm, myVehUpDir); const ScalarV verticalThreshold(-0.75f); if( IsLessThanAll( verticalDot, verticalThreshold ) != 0 ) { CAutomobile* pAutomobile = static_cast< CAutomobile* >( this ); if( pAutomobile->HasHydraulicSuspension() ) { for( int i = 0; i < m_nNumWheels; i++ ) { CWheel* pWheel = GetWheel( i ); if( pWheel && ( pWheel->GetIsTouching() || pWheel->GetWasTouching() ) && pWheel->GetSuspensionTargetRaiseAmount() > 0.0f ) { pWheel->SetSuspensionHydraulicTargetState( WHS_FREE ); pWheel->SetSuspensionTargetRaiseAmount( 0.0f, 1.0f ); } } } } } } // !!!! GTAV HACK !!!! if(pOtherEntity && (pOtherEntity->GetIsTypeBuilding() || pOtherEntity->GetIsTypeObject())) { int frontBumperComponent = GetBoneIndex( VEH_BUMPER_F ); int rearBumperBoneIndex = GetBoneIndex( VEH_BUMPER_R ); if( frontBumperComponent > -1 ) { frontBumperComponent = GetVehicleFragInst()->GetComponentFromBoneIndex( frontBumperComponent ); } Vec3V localNormal = UnTransform3x3Ortho( transform.GetMatrix(), RCC_VEC3V( vecNormal ) ); // if the contact normal points away from the vehicle discard this impact if( frontBumperComponent != -1 && impacts.GetMyComponent() == frontBumperComponent && localNormal.GetYf() > 0.75f ) { impacts.DisableImpact(); } else if( rearBumperBoneIndex != -1 ) { if( localNormal.GetYf() < -0.75f && GetFragInst()->GetTypePhysics()->GetChild( impacts.GetMyComponent() )->GetOwnerGroupPointerIndex() == GetVehicleFragInst()->GetGroupFromBoneIndex( rearBumperBoneIndex ) ) { impacts.DisableImpact(); } } } // !!!! GTAV HACK !!!! if( pOtherEntity && HasRammingScoop() && pOtherEntity->GetIsTypeObject() ) { Vec3V localNormal = UnTransform3x3Ortho( transform.GetMatrix(), RCC_VEC3V( vecNormal ) ); static const int numRammingScoopBones = 4; static const eHierarchyId rammingScoopBoneIds[ numRammingScoopBones ] = { VEH_RAMMING_SCOOP, VEH_SCOOP_1_MOD, VEH_SCOOP_2_MOD, VEH_SCOOP_3_MOD }; for( int i = 0; i < numRammingScoopBones; i++ ) { int rammingScoopComponent = GetBoneIndex( rammingScoopBoneIds[ i ] ); if( rammingScoopComponent > -1 ) { rammingScoopComponent = GetVehicleFragInst()->GetComponentFromBoneIndex( rammingScoopComponent ); } // if the contact normal points away from the vehicle discard this impact if( rammingScoopComponent != -1 && impacts.GetMyComponent() == rammingScoopComponent && localNormal.GetYf() > 0.75f ) { impacts.DisableImpact(); } } } if(InheritsFromBike()) { if(GetDriver()) { //If we are a bike and are hitting something we really want to slip off it. impacts.SetFriction(0.0f); impacts.SetElasticity(0.0f); if(pOtherEntity) { // Dont' want to fight collision with lateral springs. for(int i=0; i < GetNumWheels(); i++) { GetWheel(i)->GetDynamicFlags().SetFlag(WF_NO_LATERAL_SPRING); } } // help bike sliding off when it stuck on a fixed object if(pOtherEntity && pOtherEntity->GetIsTypeObject() && pOtherEntity->GetIsFixedFlagSet()) { // GTA V DLC - B* 1710481 - only do this if the bike is moving slowly otherwise it // stops it going up ramps properly if( GetVelocity().Mag2() < 10.0f && vecNormal.z > 0.9f) { Vector3 vecUp = transformC.DotV(vecNormal); vecNormal.Subtract(vecUp * transformC); vecNormal.NormalizeSafe(transformC); impacts.SetMyNormal(vecNormal); } } } else { // For vfx/sfx reasons some bike chassis are marked as rubber. Clamp elasticity to prevent excessive bouncing. impacts.SetElasticity(Min(impacts.GetElasticity(),BIKE_MAX_ELASTICITY)); } } // HACK ALERT! If we're a benson... if(GetModelIndex() == MI_CAR_BENSON_TRUCK) { // ...and our contact is deep... if (impacts.GetDepth() > 0.2f) { // ...and we're backing up... if (IsTrue(Dot(RCC_VEC3V(GetVelocity()), transform.GetMatrix().b()) < ScalarV(-0.01f))) { // ...and hitting something not active... if (otherInstance && !PHLEVEL->IsActive(otherInstance->GetLevelIndex())) { // ...with our ramp... const int rampIndex = GetBoneIndex(VEH_BOOT); fragInstGta* pBensonFragInst = GetVehicleFragInst(); if(rampIndex != -1 && pBensonFragInst != NULL) { const int fragChildIndex = pBensonFragInst->GetComponentFromBoneIndex(rampIndex); if(impacts.GetMyComponent() == fragChildIndex) { // ...with a normal pointing forwards (we're backing into a wall)... Vec3V localNormal = UnTransform3x3Ortho(transform.GetMatrix(),RCC_VEC3V(vecNormal)); if (IsTrue(localNormal.GetY() > ScalarV(0.9f))) { if (CCarDoor* pDoor = GetDoorFromId(VEH_BOOT)) { if(!pDoor->GetIsLatched(this) && pDoor->GetDoorAllowedToBeBrokenOff()) { // ...the break off on the next vehicle update pDoor->BreakOffNextUpdate(); } } } } } } } } } // Don't allow trailer boot contacts against fixed geometry to linger if(GetVehicleType() == VEHICLE_TYPE_TRAILER && otherInstance && PHLEVEL->GetState(otherInstance->GetLevelIndex()) == phLevelNew::OBJECTSTATE_FIXED) { const int rampIndex = GetBoneIndex(VEH_BOOT); fragInstGta* pTrailerFragInst = GetVehicleFragInst(); if(rampIndex != -1 && pTrailerFragInst != NULL) { const int fragChildIndex = pTrailerFragInst->GetComponentFromBoneIndex(rampIndex); if(impacts.GetMyComponent() == fragChildIndex) { const int maxLifetime = 1; const ScalarV vertNormThreshold(0.7f); Vec3V myImpactNorm; impacts.GetMyNormal(myImpactNorm); const Vec3V trailerUpVec = GetTransform().GetC(); if( (impacts.GetContact().GetLifetime() > maxLifetime) || (IsGreaterThanAll(Dot(myImpactNorm, trailerUpVec), vertNormThreshold) != 0) ) { impacts.SetMyNormal(trailerUpVec); } } } } if( pOtherEntity && pOtherEntity->GetIsTypeBuilding() ) { if( MI_CAR_WASTELANDER.IsValid() && GetModelIndex() == MI_CAR_WASTELANDER ) { // flatten slightly upward facing normals between building collisions and the wastelander Vec3V localNormal = UnTransform3x3Ortho( transform.GetMatrix(), RCC_VEC3V( vecNormal ) ); TUNE_GROUP_FLOAT( VEHICLE_WASTELANDER_NORMAL_FLATTENING, MAX_Z, 0.75f, -1.0f, 1.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_WASTELANDER_NORMAL_FLATTENING, MIN_Z, -0.1f, -1.0f, 1.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_WASTELANDER_NORMAL_FLATTENING, MAX_Y, -0.2f, -1.0f, 1.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_WASTELANDER_NORMAL_FLATTENING, MAX_HEIGHT_OFFSET, 1.5f, -2.0f, 2.0f, 0.1f ); if( localNormal.GetYf() < MAX_Y && localNormal.GetZf() > MIN_Z && localNormal.GetZf() < MAX_Z ) { Vec3V vMyPosition = impacts.GetMyPosition(); vMyPosition = transform.UnTransform( vMyPosition ); if( vMyPosition.GetZf() < MAX_HEIGHT_OFFSET ) { vMyPosition.SetZ( pHandling->m_vecCentreOfMassOffset.GetZ() ); vMyPosition = transform.Transform( vMyPosition ); impacts.SetMyPosition( vMyPosition ); } localNormal.SetZf( 0.0f ); localNormal = Normalize( localNormal ); localNormal = Transform3x3( transform.GetMatrix(), localNormal ); impacts.SetMyNormal( localNormal ); } } if( MI_CAR_HALFTRACK.IsValid() && GetModelIndex() == MI_CAR_HALFTRACK ) { // reduce friction on upward facing normals between building collisions and the halftrack Vec3V localNormal = UnTransform3x3Ortho( transform.GetMatrix(), RCC_VEC3V( vecNormal ) ); TUNE_GROUP_FLOAT( VEHICLE_HALFTRACK_FRICTION_REDUCTION, MIN_Z, 0.0f, -1.1f, 1.1f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_HALFTRACK_FRICTION_REDUCTION, MAX_Y, 0.5f, -1.1f, 1.1f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_HALFTRACK_FRICTION_REDUCTION, MAX_X, 0.3f, -1.1f, 1.1f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_HALFTRACK_FRICTION_REDUCTION, MAX_HEIGHT_OFFSET, 0.0f, -2.0f, 2.0f, 0.1f ); if( Abs( localNormal.GetYf() ) < MAX_Y && Abs( localNormal.GetXf() ) < MAX_X && localNormal.GetZf() > MIN_Z ) { Vec3V vMyPosition = impacts.GetMyPosition(); vMyPosition = transform.UnTransform( vMyPosition ); if( vMyPosition.GetZf() < MAX_HEIGHT_OFFSET ) { impacts.SetFriction( 0.0f ); } } } } if (m_fFrictionOverride >= 0.0f) { impacts.SetFriction(m_fFrictionOverride); } // B*1869950: Prevent vehicle doors from being blocked by the magical immovable money bag found in Capture jobs. if(pOtherEntity && pOtherEntity->GetIsTypeObject() && pOtherEntity->GetModelIndex() == MI_MONEY_BAG) { if(GetArticulatedDoorFromComponent(impacts.GetMyComponent())) impacts.DisableImpact(); } } //if we're a dummy our bounds will now intersect a trailer if we have one attached. if(IsDummy()) { //make sure we disable the impacts with trailers that we're attached to if we're a dummy CEntity* pEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance()); if(pEnt && pEnt->GetIsPhysical() && pEnt!=this) { if(pEnt->GetIsTypeVehicle() && static_cast(pEnt)->InheritsFromTrailer()) { // check whether the trailer is attached to the parent vehicle CTrailer* pTrailer = static_cast(pEnt); if(pTrailer->GetAttachParent() == this) { impacts.DisableImpact(); } } } } // If chassis hits something when car is either jumping or parachuting, end the action if( ( InheritsFromAutomobile() && HasParachute() ) || HasJump() ) { CEntity* pEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance()); if(pEnt && !pEnt->GetIsTypePed()) { bool dontFinishParachuting = CObject::ms_bDisableCarCollisionsWithCarParachute && ( pEnt->GetIsTypeVehicle() || ( pEnt->GetIsTypeObject() && ( static_cast< CObject* >( pEnt )->GetIsParachute() || static_cast< CObject* >( pEnt )->IsNetworkClone() ) ) ); if( GetIsDoingJump() ) { if( !CPhysics::ms_bInArenaMode || !pEnt->GetIsTypeObject() || !static_cast( pEnt )->GetObjectIsBall() ) { vehicleDisplayf( "CVehicle::ProcessPreComputeImpacts - Car Jump: %s, cancelled due to collision", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?" ); SetIsDoingJump( false ); } } else if( !dontFinishParachuting && HasParachute() ) { CAutomobile* pCar = static_cast( this ); if( !dontFinishParachuting && pCar->IsParachuting() && !pCar->IsFinishParachutingRequested() ) { pCar->RequestFinishParachuting(); vehicleDisplayf( "CVehicle::ProcessPreComputeImpacts - RequestFinishParachuting: Car Collisions with parachute disabled[%d] Name[%s] Hit entity name[%s] Network log name[%s]", (int)CObject::ms_bDisableCarCollisionsWithCarParachute, GetDebugName(), pEnt->GetDebugName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "Unknown" ); } } } } } // END if(!bWheelImpact) // If we're kinematic, treat all collisions as if we have infinite mass if (IsUsingKinematicPhysics()) { bool bSetMassScales = true; CEntity* pEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance()); if (pEnt && pEnt->GetIsTypeVehicle() && pEnt->GetCollider() && ((CVehicle*)pEnt)->GetMass()>0.0f) { // optionally allow contacts above a certain threshold from vehicle hits, // and break the entity out of the synced scene when these occur Vec3V otherVel = pEnt->GetCollider()->GetLocalVelocity(impacts.GetOtherPosition().GetIntrin128()); Vec3V myNormal; impacts.GetMyNormal(myNormal); float massRatio = ((CVehicle*)pEnt)->GetMass()/GetMass(); TUNE_GROUP_FLOAT(VEHICLE_SYNCED_SCENE, fMinVehMomentumForSyncedSceneAbort, 4.0f, 0.0f, 10000.0f, 0.01f); if ((Dot(otherVel, myNormal).Getf() * massRatio)>fMinVehMomentumForSyncedSceneAbort) { // find the cutscene task and notify it to exit CTaskSynchronizedScene* pTask = GetIntelligence() ? static_cast< CTaskSynchronizedScene* >(GetIntelligence()->GetTaskManager()->FindTaskByTypeActive(VEHICLE_TASK_TREE_SECONDARY, CTaskTypes::TASK_SYNCHRONIZED_SCENE)) : NULL; if (pTask && pTask->IsSceneFlagSet(SYNCED_SCENE_VEHICLE_ABORT_ON_LARGE_IMPACT)) { bSetMassScales = false; pTask->ExitSceneNextUpdate(); SetShouldUseKinematicPhysics(false); DisableKinematicPhysics(); } } } if (bSetMassScales) { impacts.SetMassInvScales(0.0f,1.0f); #if PDR_ENABLED if (impacts.GetCachedManifold().GetInstanceA() == otherInstance) { PDR_ONLY(rage::debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(kinematic[A])", 0.0f)); } else { PDR_ONLY(rage::debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(kinematic[B])", 0.0f)); } #endif // PDR_ENABLED } } if (m_nVehicleFlags.bDisableVehicleMapCollision) { phInst* pOtherInst = impacts.GetOtherInstance(); if ( pOtherInst && pOtherInst->IsInLevel()) { phLevelNew* pLevel = CPhysics::GetLevel(); u16 nLevelIndex = pOtherInst->GetLevelIndex(); if ((pLevel->GetInstanceTypeFlags(nLevelIndex)&ArchetypeFlags::GTA_MAP_TYPE_MOVER)!=0) { // Disable the impact impacts.DisableImpact(); PDR_ONLY(rage::debugPlayback::RecordModificationToContact(impacts, "Disabled", "Disabled Map Collision")); } else { if (pOtherInst->GetUserData()) { CEntity* pEnt = (CEntity*)pOtherInst->GetUserData(); if (pEnt) { switch (pEnt->GetType()) { case ENTITY_TYPE_PED: { CPed* pPed = static_cast(pEnt); if (pPed->GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsuleMapCollision)) { // Disable the impact impacts.DisableImpact(); PDR_ONLY(rage::debugPlayback::RecordModificationToContact(impacts, "Disabled", "Anim Disabled Collision")); } } break; case ENTITY_TYPE_OBJECT: { CObject* pObj = static_cast(pEnt); if (pObj->GetDisableObjectMapCollision()) { // Disable the impact impacts.DisableImpact(); PDR_ONLY(rage::debugPlayback::RecordModificationToContact(impacts, "Disabled", "Anim Disabled Collision")); } } break; case ENTITY_TYPE_MASK_VEHICLE: { CVehicle* pVeh = static_cast(pEnt); if (pVeh->m_nVehicleFlags.bDisableVehicleMapCollision) { // Disable the impact impacts.DisableImpact(); PDR_ONLY(rage::debugPlayback::RecordModificationToContact(impacts, "Disabled", "Anim Disabled Collision")); } } break; default: { // Do nothing } break; } } } } } } ++impacts; } // if we're not touching any boost pads reset the current object if( resetSpeedBoostObjectID ) { m_iCurrentSpeedBoostObjectID = 0; m_iPrevSpeedBoostObjectID = 0; } if( bImpactWithBeastVehicle ) { impacts.Reset(); ApplyBeastModeToImpacts( impacts ); } if( pRammedVehicle && pRammedVehicle != this ) { ApplyRammingScoopToImpact( pRammedVehicle, impacts ); } impacts.Reset(); if( pRampVehicle && pRampVehicle != this ) { ApplyRampToImpacts( pRampVehicle, impacts ); } if(processingAudioImpacts) { m_VehicleAudioEntity->GetCollisionAudio().EndProcessImpacts(); } impacts.Reset(); UpdateInvMassForRammingBarImpacts( impacts ); impacts.Reset(); PF_START(Wheels); CWheel::ProcessImpactCommonData wheelCommonData(*this); for(int i = 0; i < numWheels; ++i) { m_ppWheels[i]->ProcessImpact(wheelCommonData, impacts, aVehiclesFlattenNormalArray); } //Remove any impacts that have been disabled(maybe impacts that were thought to be deepest before a deeper one was found) phCachedContactIteratorElement* pNextContact = impacts.Reset(); #if __PPU phManifold* pManifoldWithRemovedContact = NULL; phManifold* pRootManifoldWithRemovedContact = NULL; #endif // __PPU while(pNextContact) { bool bWheelImpact = false; const int myComponent = impacts.GetMyComponent(); for(int i = 0; i < numWheelFragChildren; i++) { if(myComponent == wheelFragChild[i]) { bWheelImpact = true; break; } } if(bWheelImpact) { if(pNextContact->m_Disabled || !pNextContact->m_Contact->IsContactActive()) { #if __PPU phManifold* pNextManifoldWithRemovedContact = const_cast(pNextContact->m_CachedManifold); if (pManifoldWithRemovedContact && pManifoldWithRemovedContact != pNextManifoldWithRemovedContact) { pManifoldWithRemovedContact->RegenerateDmaPlan(); } pManifoldWithRemovedContact = pNextManifoldWithRemovedContact; phManifold* pNextRootManifoldWithRemovedContact = const_cast(pNextContact->m_RootManifold); if (pRootManifoldWithRemovedContact && pRootManifoldWithRemovedContact != pNextRootManifoldWithRemovedContact) { pRootManifoldWithRemovedContact->RegenerateDmaPlan(); } pRootManifoldWithRemovedContact = pNextRootManifoldWithRemovedContact; #endif // __PPU impacts.RemoveContactWithFinalize(); } } pNextContact = impacts.NextContact(); } #if __PPU if (pManifoldWithRemovedContact) { pManifoldWithRemovedContact->RegenerateDmaPlan(); } if (pRootManifoldWithRemovedContact) { pRootManifoldWithRemovedContact->RegenerateDmaPlan(); } #endif // __PPU impacts.Reset(); PF_STOP(Wheels); // Don't do fix stuck in geometry on the monster trucks. if(pHandling && pHandling->hFlags & HF_EXT_WHEEL_BOUNDS_COL) { bDoFixStuckInGeometry = false; } PF_START(FixStuck); //Prevent a vehicle being sucked through a wall if(bDoFixStuckInGeometry) { FixStuckInGeometry(impacts); } PF_STOP(FixStuck); PF_STOP(VehiclesTotal); } void CVehicle::ProcessPreComputeImpactsSuperDummyCar(phContactIterator& impacts) { static dev_float superDummyVehMaxNormalZ = 0.75f; static dev_float superDummyVehMinNormalZ = -0.75f; const ScalarV maxNormalV(superDummyVehMaxNormalZ); const ScalarV minNormalV(superDummyVehMinNormalZ); for(impacts.ResetToFirstActiveContact(); !impacts.AtEnd(); impacts.NextActiveContact()) { phInst* pOtherInst = impacts.GetOtherInstance(); if(!pOtherInst) { continue; } phInst* pMyInst = impacts.GetMyInstance(); if(!pMyInst) { continue; // Probably shouldn't happen? } //Check normal limits Vec3V myNormalV; impacts.GetMyNormal(myNormalV); const ScalarV myNormalZV = myNormalV.GetZ(); const BoolV normalLessThanOrEqualV = IsLessThanOrEqual(myNormalZV, maxNormalV); const BoolV normalGreaterThanOrEqualV = IsGreaterThanOrEqual(myNormalZV, minNormalV); const BoolV normalInRangeV = And(normalLessThanOrEqualV, normalGreaterThanOrEqualV); if(IsTrue(normalInRangeV)) { myNormalV.SetZ(V_ZERO); myNormalV = NormalizeSafe(myNormalV, Vec3V(V_X_AXIS_WONE)); impacts.SetMyNormal(myNormalV); // Flatten the impact and move it to the plane through the center of gravity. Vec3V posV = impacts.GetMyPosition(); posV.SetZ(pMyInst->GetCenterOfMass().GetZ()); impacts.SetMyPosition(posV); } } } void CVehicle::FixStuckInGeometry(phCachedContactIterator& iterator) { static const u32 MAX_NUM_VEHICLE_MANIFOLDS = 128; phManifold *manifolds[MAX_NUM_VEHICLE_MANIFOLDS]; u32 numManifolds = 0; phManifold *wheelManifolds[MAX_NUM_VEHICLE_MANIFOLDS]; u32 numWheelManifolds = 0; // Mark components that are wheels atFixedBitSet wheelComponents; atFixedBitSet wheelExtraComponents; const int numWheels = GetNumWheels(); bool bWheelComponentsFound(false); int wheelBoundsPerWheelToCheck = MAX_WHEEL_BOUNDS_PER_WHEEL; TUNE_FLOAT(fVelocityToCheckExtraWheelBoundsSq, 10.0f, 0.0f, 100.0f, 0.01f) if(GetVelocity().Mag2() < fVelocityToCheckExtraWheelBoundsSq) { wheelBoundsPerWheelToCheck = 1; //only check one wheel bound at low speed. } for(int wheelIndex = 0; wheelIndex < numWheels; ++wheelIndex) { const CWheel& wheel = *GetWheel(wheelIndex); for(int wheelBoundIndex = 0; wheelBoundIndex < MAX_WHEEL_BOUNDS_PER_WHEEL; ++wheelBoundIndex) { int wheelComponentIndex = wheel.GetFragChild(wheelBoundIndex); if(wheelComponentIndex != -1) { if(wheelBoundIndex < wheelBoundsPerWheelToCheck) { wheelComponents.Set(wheelComponentIndex); } else { wheelExtraComponents.Set(wheelComponentIndex); } bWheelComponentsFound = true; } } } //don't do fix stuck in geometry checks if we have no wheel components if(!bWheelComponentsFound) { return; } phInst *pPlayerInst = NULL; if(CPedFactory::GetFactory()->GetLocalPlayer() && CPedFactory::GetFactory()->GetLocalPlayer()->GetPedType() == PEDTYPE_ANIMAL) pPlayerInst = CPedFactory::GetFactory()->GetLocalPlayer()->GetCurrentPhysicsInst(); // Loop over all the manifolds and send them to their respective array for (phCachedContactIteratorElement* contact = iterator.Reset(); contact != NULL; contact = iterator.NextManifold()) { Assert(contact->m_Removed == false); // B*2068976 & 2070343: Ignore contacts with player animal peds. // Quadrupeds controlled by the player can get into situations where they touch both the chassis and the wheels at the same time, which allows them to walk through wheels and cause badness. if(pPlayerInst == iterator.GetOtherInstance()) continue; phManifold* manifold = const_cast(contact->m_CachedManifold); // Don't worry about self collisions, constraints or disabled impacts if (manifold->GetInstanceA() != manifold->GetInstanceB() && !contact->m_IsConstraint) { if(wheelComponents.IsSet(contact->m_MyComponent) || (wheelExtraComponents.IsSet(contact->m_MyComponent) && manifold->GetInstanceB()->CanMove())) { if(numWheelManifolds < MAX_NUM_VEHICLE_MANIFOLDS) { wheelManifolds[numWheelManifolds++] = manifold; } } else { if(numManifolds < MAX_NUM_VEHICLE_MANIFOLDS) { manifolds[numManifolds++] = manifold; } } } } iterator.Reset(); static const ScalarV svDirectionNormalCheck(-0.2f); static const ScalarV svDirectionNormalCheckBikes(0.1f); static const ScalarV svNormalCheck(0.0f); //Bikes have more of an issue with going through walls because of dodgy wheel contacts so adjust their thresholds ScalarV vDirectionNormalCheck = svDirectionNormalCheck; if(GetVehicleType() == VEHICLE_TYPE_BICYCLE || GetVehicleType() == VEHICLE_TYPE_QUADBIKE || GetVehicleType() == VEHICLE_TYPE_BIKE || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE) { vDirectionNormalCheck = svDirectionNormalCheckBikes; } //Now see if any wheel contacts have normals opposite to any chassis impact normals and remove them for (int vehicleManifoldIndex = 0; vehicleManifoldIndex < numManifolds; vehicleManifoldIndex++) { const phManifold *vehicleManifold = manifolds[vehicleManifoldIndex]; const BoolV isVehicleInstanceA = BoolV(vehicleManifold->GetInstanceA() == GetCurrentPhysicsInst()); const int numVehicleContacts = vehicleManifold->GetNumContacts(); for (int vehicleContactIndex = 0; vehicleContactIndex < numVehicleContacts; ++vehicleContactIndex) { const phContact& vehicleContact = vehicleManifold->GetContactPoint(vehicleContactIndex); const Vec3V vehicleContactNormal = vehicleContact.GetWorldNormal(); // The normal is on the vehicle, the position is on the object it's colliding with const Vec3V vehicleNormal = SelectFT(isVehicleInstanceA,Negate(vehicleContactNormal),vehicleContactNormal); const Vec3V vehicleContactPositionOnObject = SelectFT(isVehicleInstanceA,vehicleContact.GetWorldPosA(),vehicleContact.GetWorldPosB()); for (int wheelManifoldIndex = 0; wheelManifoldIndex < numWheelManifolds; wheelManifoldIndex++) { phManifold *wheelManifold = wheelManifolds[wheelManifoldIndex]; const BoolV isWheelInstanceA = BoolV(wheelManifold->GetInstanceA() == GetCurrentPhysicsInst()); const int numWheelContacts = wheelManifold->GetNumContacts(); for (int wheelContactIndex = 0; wheelContactIndex < numWheelContacts; ++wheelContactIndex) { const phContact& wheelContact = wheelManifold->GetContactPoint(wheelContactIndex); const Vec3V wheelContactNormal = wheelContact.GetWorldNormal(); // The normal is on the wheel, the position is on the object it's colliding with const Vec3V wheelNormal = SelectFT(isWheelInstanceA,Negate(wheelContactNormal),wheelContactNormal); const Vec3V wheelContactPositionOnObject = SelectFT(isWheelInstanceA,wheelContact.GetWorldPosA(),wheelContact.GetWorldPosB()); const Vec3V hitDirection = NormalizeFast(Subtract(wheelContactPositionOnObject,vehicleContactPositionOnObject)); // Does the chassis normal point away from the direction of the chassis contact to the wheel const BoolV removeWheelContacts = And(IsLessThan(Dot(hitDirection, vehicleNormal), vDirectionNormalCheck), IsLessThan(Dot(vehicleNormal, wheelNormal),svNormalCheck)); if(removeWheelContacts.Getb()) { // NOTE: This would be unsafe if people were using the cached iterator after this point since it doesn't know that the contacts are removed. wheelManifold->RemoveAllContacts(); break; } } } } } } bool CVehicle::CloneBounds() { if(Verifyf(GetVehicleFragInst() && GetVehicleFragInst()->GetCacheEntry(),"Can't clone bounds on '%s' without a frag instance and cache entry.",GetVehicleModelInfo()->GetModelName())) { GetVehicleFragInst()->GetCacheEntry()->CloneBoundParts(); return true; } return false; } void CVehicle::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) { if (GetIsInPopulationCache()) return; if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy)) return; if(!InheritsFromPlane()) // We want the wheel impacts to be recorded for landing gear impacts { for(int i = 0; i < GetNumWheels(); i++) { // Bail out before vehicle damage if we haven't bottomed out if(iMyComponent == GetWheel(i)->GetFragChild()) { return; } } } // If this vehicle is a bike/quad, knock any passengers off if the vertical component of velocity is above some threshold. Also // check that the velocity orthogonal to the ground poly is above the threshold. This allows landing on slopes after a big jump // while also stopping passengers being thrown off when colliding with a steep wall. Vector3 vVehicleVelocity = GetVelocity(); float fVelocityIntoGroundPoly = vMyNormal.Dot(vVehicleVelocity); if(m_nVehicleFlags.bAllowKnockOffVehicleForLargeVertVel && (InheritsFromBike()||InheritsFromQuadBike()||InheritsFromAmphibiousQuadBike()) && vVehicleVelocity.z<-m_fVerticalVelocityToKnockOffThisBike && fVelocityIntoGroundPoly<-m_fVerticalVelocityToKnockOffThisBike) { // Use a damage event to knock the passengers off. for(s32 iSeat = 0; iSeat < m_SeatManager.GetMaxSeats(); ++iSeat) { CPed* pPassenger = m_SeatManager.GetPedInSeat(iSeat); if(pPassenger && !pPassenger->IsInjured()) { static float sfDamageMultiplier = 70.0f; float fDamage = fabs(sfDamageMultiplier * fVelocityIntoGroundPoly/m_fVerticalVelocityToKnockOffThisBike); pPassenger->KnockPedOffVehicle(false, fDamage); } } } Vector3 vImpulse = vMyNormal*fImpulseMag; bool bVehicleCollision = pHitEnt && pHitEnt->GetIsTypeVehicle(); bool bVehicleTrainCollision = bVehicleCollision && static_cast(pHitEnt)->InheritsFromTrain(); if(bVehicleTrainCollision && vImpulse.IsNonZero()) { vImpulse = GetVehicleDamage()->RecomputeImpulseFromTrain(static_cast(pHitEnt), vImpulse); } if(bVehicleCollision) { CVehicle *pOtherVehicle = (CVehicle *)pHitEnt; if( GetVehicleType() == VEHICLE_TYPE_CAR && pHandling->GetCarHandlingData() && !GetIsAnyFixedFlagSet() && pOtherVehicle->GetVehicleType() == VEHICLE_TYPE_CAR && pOtherVehicle->pHandling->GetCarHandlingData() && !pOtherVehicle->GetIsAnyFixedFlagSet()) { const Matrix34& matMyMatrix = RCC_MATRIX34(GetMatrixRef()); // if car is running forward if(GetVelocity().Dot(matMyMatrix.b) > 0.0f) { vImpulse.z = Min(0.0f, vImpulse.z); } } } if(pHitEnt && pHitEnt->GetIsTypeObject()) { // Do not apply collision damage to vehicle if hits a parachute if(CTaskParachute::IsParachute(*((CObject *)pHitEnt))) { return; } } // Rocket boost - disable if we hit anything if( pHitEnt && HasRocketBoost() && m_bIsRocketBoosting && !InheritsFromPlane() ) { Vector3 vForward = VEC3V_TO_VECTOR3( GetTransform().GetB() ); Vector3 vecNormal = vMyNormal * -1.0f; float fFrontNormal = vecNormal.Dot(vForward); static dev_float sfFrontNormalLimit = 0.866f; // Check if it's a head-on collision, leave rocket boost on if it's an impact from the side bool bFrontImpact = ( fFrontNormal > sfFrontNormalLimit ); if( bFrontImpact ) { bool bIsDynamicNotFixed = pHitEnt->GetIsTypeObject() && !pHitEnt->GetIsFixedFlagSet(); if( !pHitEnt->GetIsTypePed() && !bIsDynamicNotFixed ) { SetRocketBoosting(false); } // For dynamic objects, only disable if we've hit an object with a big impact if(bIsDynamicNotFixed) { float fFwdImpulseMag = ( vMyNormal * fImpulseMag ).Dot( vForward ) * -1.0f; float fFwdVelocityTimesMass = GetVelocity().Dot(vForward) * GetMass(); if(fFwdVelocityTimesMass > 0.0f) { static dev_float sfVelocityTolerance = 0.05f; if( fFwdImpulseMag / fFwdVelocityTimesMass > sfVelocityTolerance ) { SetRocketBoosting(false); } } } } } // Don't apply collision damage if we hit something that doesn't affect vehicles if (IsFragInst(hitInst)) { const fragInst* otherFrag = static_cast(hitInst); const fragPhysicsLOD* otherPhysics = otherFrag->GetTypePhysics(); if(Verifyf(iOtherComponent < otherPhysics->GetNumChildren(), "Out of range component %i/%i on non-Ped type '%s'.",iOtherComponent,otherPhysics->GetNumChildren(),hitInst->GetArchetype()->GetFilename())) { const fragTypeChild* otherChild = otherPhysics->GetChild(iOtherComponent); const fragTypeGroup* otherGroup = otherPhysics->GetGroup(otherChild->GetOwnerGroupPointerIndex()); if (otherGroup->GetDoesntAffectVehicles()) { return; } } } for( int i = 0; i < m_numWeaponBlades; i++ ) { m_weaponBlades[ i ].ApplyImpacts( this, pHitEnt, VECTOR3_TO_VEC3V( vMyNormal ), VECTOR3_TO_VEC3V( vMyHitPos ), VECTOR3_TO_VEC3V( vOtherHitPos ), i, iMyComponent, iOtherComponent ); } if( pHitEnt && pHitEnt->GetIsTypeObject() ) { CObject* pObject = static_cast( pHitEnt ); if( pObject->IsObjectArticulated() && pObject->GetCollider() && pObject->GetCollider()->IsArticulated() && ( !pObject->IsJointAtMinAngle( 0 ) && !pObject->IsJointAtMaxAngle( 0 ) ) ) { GetVehicleDamage()->ApplyDamage( pObject->GetDamageInflictor(), DAMAGE_TYPE_COLLISION, WEAPONTYPE_FALL, 1.0f, vMyHitPos, vMyNormal, vMyNormal, iOtherComponent, 0, -1, false, true, 0.0f, false, false, false, false, true ); } } GetVehicleDamage()->GetDeformation()->ApplyCollisionImpact(vImpulse, vMyHitPos, pHitEnt, InheritsFromPlane()); // Only send in the unmodified vMyImpulse as it will be used for calculating damage for peds CPhysical::ProcessCollision(myInst, pHitEnt, hitInst, vMyHitPos, vOtherHitPos, fImpulseMag, vMyNormal, iMyComponent, iOtherComponent, iOtherMaterial, bIsPositiveDepth, bIsNewContact); // Further process vehicle collisions after CPhysical::ProcessCollision so collision history has been populated (if being recorded) if (bVehicleCollision) { CVehicle *pOtherVehicle = static_cast(pHitEnt); ProcessVehicleCollision(pOtherVehicle); } } void CVehicle::ProcessVehicleCollision(CVehicle* pOtherVehicle) { // TODO: Retrieve Arcade mode // Cops & Crooks: Crooks ramming Cops should be reported for CRIME_RECKLESS_DRIVING CPed* pOwnDriver = GetDriver(); bool bIsOwnDriverACrook = pOwnDriver && pOwnDriver->GetPlayerInfo() && pOwnDriver->GetPlayerInfo()->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_CROOK; if (bIsOwnDriverACrook) { bool bIsOtherPassengerACop = false; const s32 maxSeats = pOtherVehicle->GetSeatManager()->GetMaxSeats(); for (s32 seatIndex = 0; seatIndex < maxSeats; ++seatIndex) { const CPed* pPassenger = pOtherVehicle->GetPedInSeat(seatIndex); if (pPassenger && pPassenger->GetPlayerInfo() && pPassenger->GetPlayerInfo()->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_COP) { bIsOtherPassengerACop = true; break; } } if (bIsOtherPassengerACop) { // If this vehicle is responsible for the collision bool bIsOwnFault = false; bool bIsOtherFault = false; const float fAngleSameDirRadians = DEGREES_TO_RADIANS(CVehicleDamage::sm_Tunables.m_AngleVectorsPointSameDir); if (PhysicsHelpers::FindVehicleCollisionFault(this, pOtherVehicle, bIsOwnFault, fAngleSameDirRadians, CVehicleDamage::sm_Tunables.m_MinFaultVelocityThreshold) && bIsOwnFault && PhysicsHelpers::FindVehicleCollisionFault(pOtherVehicle, this, bIsOtherFault, fAngleSameDirRadians, CVehicleDamage::sm_Tunables.m_MinFaultVelocityThreshold) && !bIsOtherFault && GetFrameCollisionHistory()->GetCollisionImpulseMagSum() >= CVehicleDamage::sm_Tunables.m_MinImpulseForWantedStatus) { CCrime::ReportCrime(CRIME_RECKLESS_DRIVING, pOtherVehicle, pOwnDriver); } } } } u32 CVehicle::ms_nExtrasMasks[EXTRAS_MASK_NUM_MASKS] = { BIT(1) | BIT(2) | BIT(3) | BIT(4), // EXTRAS_MASK_GANG_OFF = 0, BIT(5) | BIT(6) | BIT(7) | BIT(8), // EXTRAS_MASK_GANG_ON, BIT(1) | BIT(2) | BIT(3) | BIT(4), // EXTRAS_MASK_CONV_DRY, BIT(2) | BIT(3) | BIT(4), // EXTRAS_MASK_CONV_RAIN, BIT(1), // EXTRAS_MASK_CONV_OPEN BIT(5) | BIT(6) | BIT(7) | BIT(8), // EXTRAS_MASK_TAXI, BIT(5) | BIT(6) | BIT(7) | BIT(8), // EXTRAS_MASK_SCRIPT BIT(1) | BIT(2) | BIT(3), // EXTRAS_BIKE_TANK BIT(4) | BIT(5), // EXTRAS_BIKE_FAIRING BIT(6) | BIT(7) | BIT(8), // EXTRAS_BIKE_EXHAUST BIT(9) | BIT(10), // EXTRAS_BIKE_MISC BIT(10) | BIT(11) | BIT(12), // EXTRAS_INTERIOR BIT(12) | BIT(13), // EXTRAS_MASK_HELI_VAR_1 BIT(13) | BIT(14), // EXTRAS_MASK_HELI_VAR_2 BIT(12) | BIT(14) // EXTRAS_MASK_HELI_VAR_3 }; void CVehicle::InitExtraFlags(u32 nExtraCommands) { m_nDisableExtras = 0; #if __DEV static bool sbSkipExtraFlags = false; if(sbSkipExtraFlags) return; #endif // disable script extras for planes, but only extra 5 and 6. planes don't have all extras if (GetVehicleType()==VEHICLE_TYPE_PLANE) { CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); if (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_SCRIPT)) { m_nDisableExtras |= BIT(7); // Need extra 8 for Cargoplane // m_nDisableExtras |= BIT(8); } } else if(GetVehicleType()==VEHICLE_TYPE_HELI) { CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_SCRIPT)) { m_nDisableExtras |= BIT(10); m_nDisableExtras |= BIT(11); m_nDisableExtras |= BIT(12); } if( ( MI_HELI_VALKYRIE.IsValid() && ( GetModelIndex() == MI_HELI_VALKYRIE.GetModelIndex() ) ) || ( MI_HELI_VALKYRIE2.IsValid() && ( GetModelIndex() == MI_HELI_VALKYRIE2.GetModelIndex() ) ) ) { m_nDisableExtras = 0; int variant = fwRandom::GetRandomNumberInRange( 0, 3 ); if( variant == 0 ) { m_nDisableExtras |= ms_nExtrasMasks[ EXTRAS_MASK_HELI_VAR_1 ]; } else if( variant == 1 ) { m_nDisableExtras |= ms_nExtrasMasks[ EXTRAS_MASK_HELI_VAR_2 ]; } else if( variant == 2 ) { m_nDisableExtras |= ms_nExtrasMasks[ EXTRAS_MASK_HELI_VAR_3 ]; } } } else if(MI_CAR_FREIGHTCAR2.IsValid() && GetModelIndex() == MI_CAR_FREIGHTCAR2) //Having to use a model index check here as FLAG_EXTRAS_SCRIPT is also on tankercar and I dont want to break it and this train is the only one with more than one extra so needs special logic { CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_SCRIPT)) { for (int i = VEH_EXTRA_1; i <= VEH_LAST_EXTRA; i++) { eHierarchyId extraId = static_cast(i); if (HasComponent(extraId)) { u32 extraBit = BIT(i - VEH_EXTRA_1 + 1); m_nDisableExtras |= extraBit; } } } } // parents of children that are turned on need switched on as well VehicleType vehicleType = GetVehicleType(); if( vehicleType == VEHICLE_TYPE_CAR || vehicleType == VEHICLE_TYPE_BOAT || vehicleType == VEHICLE_TYPE_BIKE || vehicleType == VEHICLE_TYPE_BICYCLE || vehicleType == VEHICLE_TYPE_TRAILER || vehicleType == VEHICLE_TYPE_DRAFT || vehicleType == VEHICLE_TYPE_SUBMARINECAR || vehicleType == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE || vehicleType == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE ) { u32 nExtraMaskAll = 0; u32 nExtraMaskOne = 0; u32 nExtraMaskOne2 = 0; u32 nExtraMaskOne3 = 0; u32 nExtraMaskRand = u32(-1); u32 nExtraMaskInterior = 0; CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); if(pModelInfo->GetVehicleType()==VEHICLE_TYPE_BIKE) { nExtraMaskOne = ms_nExtrasMasks[EXTRAS_MASK_BIKE_TANK]; nExtraMaskOne2 = ms_nExtrasMasks[EXTRAS_MASK_BIKE_FAIRING]; nExtraMaskOne3 = ms_nExtrasMasks[EXTRAS_MASK_BIKE_EXHAUST]; nExtraMaskRand = ms_nExtrasMasks[EXTRAS_MASK_BIKE_MISC]; } else if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_GANG)) { if(nExtraCommands &EXTRAS_SET_GANG) { nExtraMaskAll = ms_nExtrasMasks[EXTRAS_MASK_GANG_ON]; nExtraMaskOne = 0; nExtraMaskRand = 0; } else { if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_ALL)) { nExtraMaskAll = ~ms_nExtrasMasks[EXTRAS_MASK_GANG_ON]; nExtraMaskRand = 0; } else if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_REQUIRE)) { nExtraMaskOne = ms_nExtrasMasks[EXTRAS_MASK_GANG_OFF]; nExtraMaskRand = 0; } else nExtraMaskRand = ms_nExtrasMasks[EXTRAS_MASK_GANG_OFF]; } } else if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_CONVERTIBLE)) { if(nExtraCommands &EXTRAS_SET_RAIN) { nExtraMaskOne = ms_nExtrasMasks[EXTRAS_MASK_CONV_RAIN]; } else if(nExtraCommands &EXTRAS_SET_CONV_OPEN) { nExtraMaskOne = ms_nExtrasMasks[EXTRAS_MASK_CONV_OPEN]; } else { nExtraMaskOne = ms_nExtrasMasks[EXTRAS_MASK_CONV_DRY]; } // support extras_all for other extras if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_ALL)) { nExtraMaskAll = ~nExtraMaskOne; nExtraMaskRand = 0; } else nExtraMaskRand &= ~ms_nExtrasMasks[EXTRAS_MASK_CONV_DRY]; } else if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_TAXI)) { nExtraMaskOne = ms_nExtrasMasks[EXTRAS_MASK_TAXI]; // support extras_all for other extras if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_ALL)) { nExtraMaskAll = nExtraMaskOne; nExtraMaskRand = 0; } else nExtraMaskRand &= ~ms_nExtrasMasks[EXTRAS_MASK_TAXI]; } else if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_REQUIRE)) { nExtraMaskOne = u32(-1); nExtraMaskRand = 0; } else if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_ALL)) { nExtraMaskAll = u32(-1); nExtraMaskRand = 0; } // disable flags if extras 5-8 are script only if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_SCRIPT)) { nExtraMaskOne &= ~ms_nExtrasMasks[EXTRAS_MASK_SCRIPT]; nExtraMaskRand &= ~ms_nExtrasMasks[EXTRAS_MASK_SCRIPT]; } // Some vehicles can use extras 10-12 as special interior clutter extras. // One of these may be selected regardless of other flags being set like FLAG_EXTRAS_ALL or FLAG_EXTRAS_REQUIRE. if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_INTERIOR_EXTRAS)) { Assertf(!InheritsFromBike(), "Bike has FLAG_HAS_INTERIOR_EXTRAS set, but bikes use extra 10 for a non-interior part. This may not work as you intended."); nExtraMaskInterior = ms_nExtrasMasks[EXTRAS_MASK_INTERIOR]; // Remove the interior extras from the other masks. nExtraMaskAll &= ~ms_nExtrasMasks[EXTRAS_MASK_INTERIOR]; nExtraMaskOne &= ~ms_nExtrasMasks[EXTRAS_MASK_INTERIOR]; nExtraMaskRand &= ~ms_nExtrasMasks[EXTRAS_MASK_INTERIOR]; } // check all 3 masks are distinct (there's no overlap) Assert((nExtraMaskAll &nExtraMaskOne) == 0); Assert((nExtraMaskAll &nExtraMaskRand) == 0); Assert((nExtraMaskOne &nExtraMaskRand) == 0); // remove bit 9 from all the masks // it'll only get pulled in if it's the parent of an extra we want to display if(!pModelInfo->GetIsBike() && !IsTank()) { nExtraMaskAll &= ~BIT(9); nExtraMaskOne &= ~BIT(9); nExtraMaskRand &= ~BIT(9); } // B*2096112: HACK: Remove extra 1 and 2 for the tropic (and tropic2 - B*2575805) // They are roof extras that's slightly too low and causes issues, but it can't be removed from the asset because of reasons so hacking it out here. if(GetModelId() == MI_BOAT_TROPIC || GetModelId() == MI_BOAT_TROPIC2) { nExtraMaskAll &= ~BIT(1); nExtraMaskOne &= ~BIT(1); nExtraMaskRand &= ~BIT(1); nExtraMaskAll &= ~BIT(2); nExtraMaskOne &= ~BIT(2); nExtraMaskRand &= ~BIT(2); } // go through the bones and count up extras first int nNumExtras = 0; int nNumAllExtras = 0; int nNumOneExtras = 0; int nNumOneExtras2 = 0; int nNumOneExtras3 = 0; int nNumRandExtras = 0; int nNumInteriorExtras = 0; for(int nCarComponent=VEH_EXTRA_1; nCarComponent <= VEH_LAST_EXTRA; nCarComponent++) { if(HasComponent((eHierarchyId)nCarComponent)) { nNumExtras++; int nExtraIndex = BIT(nCarComponent - VEH_EXTRA_1 + 1); if(nExtraMaskAll &nExtraIndex) nNumAllExtras++; if(nExtraMaskOne &nExtraIndex) nNumOneExtras++; if(nExtraMaskOne2 &nExtraIndex) nNumOneExtras2++; if(nExtraMaskOne3 &nExtraIndex) nNumOneExtras3++; if(nExtraMaskRand &nExtraIndex) nNumRandExtras++; if(nExtraMaskInterior &nExtraIndex) nNumInteriorExtras++; } } if(nNumExtras > 0) { // decide which one to leave turned on - range starts with 1 so it'll definitely choose 1 int nLeaveOneComponent = -1; int nLeaveOneComponent2 = -1; int nLeaveOneComponent3 = -1; if(nNumOneExtras > 0) nLeaveOneComponent = fwRandom::GetRandomNumberInRange(1, nNumOneExtras + 1); if(nNumOneExtras2 > 0) nLeaveOneComponent2 = fwRandom::GetRandomNumberInRange(1, nNumOneExtras2 + 1); if(nNumOneExtras3 > 0) nLeaveOneComponent3 = fwRandom::GetRandomNumberInRange(1, nNumOneExtras3 + 1); // decide which one to leave turned on - range starts with 0 so may choose none int nLeaveRandomComponent = -1; if(nNumRandExtras > 0) { if(pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_RARE)) // lower the chances by increasing the range nLeaveRandomComponent = fwRandom::GetRandomNumberInRange(0, nNumRandExtras * 4 + 1); else nLeaveRandomComponent = fwRandom::GetRandomNumberInRange(0, nNumRandExtras + 1); if(nLeaveRandomComponent > nNumRandExtras) nLeaveRandomComponent = 0; } // Decide which interior extra to leave turned on. Range starts with zero so may choose none. int nLeaveInteriorComponent = -1; if(nNumInteriorExtras > 0) { nLeaveInteriorComponent = fwRandom::GetRandomNumberInRange(0, nNumInteriorExtras + 1); } // search through the extra bones again and turn off the ones we don't want on for(int nCarComponent=VEH_EXTRA_1; nCarComponent <= VEH_LAST_EXTRA; nCarComponent++) { // if bone exists in model if(HasComponent((eHierarchyId)nCarComponent)) { int nExtraBit = BIT(nCarComponent - VEH_EXTRA_1 + 1); if(nExtraMaskAll &nExtraBit) { // don't turn any of the all one ones off } else if(nExtraMaskOne &nExtraBit) { nLeaveOneComponent--; // if it's not the specific one we want to leave on, turn it off if(nLeaveOneComponent!=0) m_nDisableExtras |= nExtraBit; } else if(nExtraMaskOne2 &nExtraBit) { nLeaveOneComponent2--; // if it's not the specific one we want to leave on, turn it off if(nLeaveOneComponent2!=0) m_nDisableExtras |= nExtraBit; } else if(nExtraMaskOne3 &nExtraBit) { nLeaveOneComponent3--; // if it's not the specific one we want to leave on, turn it off if(nLeaveOneComponent3!=0) m_nDisableExtras |= nExtraBit; } else if(nExtraMaskRand &nExtraBit) { nLeaveRandomComponent--; // if it's not the random one we want to leave on, turn it off if(nLeaveRandomComponent!=0) m_nDisableExtras |= nExtraBit; } else if(nExtraMaskInterior &nExtraBit) { nLeaveInteriorComponent--; // If it's not the interior one we want to leave on, turn it off if(nLeaveInteriorComponent!=0) m_nDisableExtras |= nExtraBit; } else { // nothing wants it, so turn it off m_nDisableExtras |= nExtraBit; } } } // if there are any required extras and none of them have already been turned on, pick a random one and turn it on u32 requiredExtras = GetVehicleModelInfo()->GetRequiredExtras(); if (requiredExtras && (requiredExtras & m_nDisableExtras) == requiredExtras) { const u32 numExtras = VEH_LAST_EXTRA - VEH_EXTRA_1 + 1; u32 numRequiredExtras = 0; u32 extras[numExtras]; for (s32 i = 0; i < numExtras; ++i) { if ((1 << i) & requiredExtras) { extras[numRequiredExtras] = i; numRequiredExtras++; } } u32 extraToTurnOn = fwRandom::GetRandomNumberInRange(0, numRequiredExtras); m_nDisableExtras &= ~(1 << extras[extraToTurnOn]); } fragInst* pFragInst = GetVehicleFragInst(); if(!pFragInst) { pFragInst = static_cast(GetCurrentPhysicsInst()); Assert(pFragInst->GetClassType() >= PH_INST_FRAG_GTA); } if(pFragInst DISABLE_FRAG_OPTIMIZATION_ONLY(&& pFragInst->GetSkeleton())) { #if ENABLE_FRAG_OPTIMIZATION const crSkeletonData& skeletonData = GetSkeletonData(); #else const crSkeletonData* pSkeletonData = &pFragInst->GetSkeleton()->GetSkeletonData(); #endif // need to loop through the bones one final time and turn on the parents of any children that are turned on for(int nCarComponent=VEH_EXTRA_1; nCarComponent <= VEH_LAST_EXTRA; nCarComponent++) { // if bone exists in model if(HasComponent((eHierarchyId)nCarComponent)) { int nExtraBit = BIT(nCarComponent - VEH_EXTRA_1 + 1); // if this bit hasn't been disabled if((m_nDisableExtras &nExtraBit)==0) { int nBoneIndex = GetBoneIndex((eHierarchyId)nCarComponent); #if ENABLE_FRAG_OPTIMIZATION int nParentBoneIndex = skeletonData.GetBoneData(nBoneIndex)->GetParent()->GetIndex(); #else int nParentBoneIndex = pSkeletonData->GetBoneData(nBoneIndex)->GetParent()->GetIndex(); #endif // search for parent extra, if parent bone turns out to be an extra for(int nParentComponent=VEH_EXTRA_1; nParentComponent <= VEH_LAST_EXTRA; nParentComponent++) { if(GetBoneIndex((eHierarchyId)nParentComponent) == nParentBoneIndex) { int nParentExtraBit = BIT(nParentComponent - VEH_EXTRA_1 + 1); // if parent is an extra then force it on (i.e. force it to not be disabled) m_nDisableExtras &= ~nParentExtraBit; break; } } } } } } Assert(nLeaveOneComponent <= 0); Assert(nLeaveRandomComponent <= 0); } } } void CVehicle::InitFragmentExtras() { #if __DEV static bool sbSkipFragExtras = false; if(sbSkipFragExtras) return; #endif VehicleType vehicleType = GetVehicleType(); if( vehicleType == VEHICLE_TYPE_CAR || vehicleType == VEHICLE_TYPE_QUADBIKE || vehicleType == VEHICLE_TYPE_BOAT || vehicleType == VEHICLE_TYPE_BIKE || vehicleType == VEHICLE_TYPE_HELI || vehicleType == VEHICLE_TYPE_BICYCLE || vehicleType == VEHICLE_TYPE_TRAILER || vehicleType == VEHICLE_TYPE_DRAFT || vehicleType == VEHICLE_TYPE_SUBMARINECAR || vehicleType == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE || vehicleType == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE ) { SetAllWindowComponentsDirty(); fragInst* pFragInst = GetVehicleFragInst(); if(!pFragInst) { pFragInst = static_cast(GetCurrentPhysicsInst()); Assert(pFragInst->GetClassType() >= PH_INST_FRAG_GTA); } if(pFragInst) { // go thru all the fragment children (there's more of them) for(int nChild=0; nChildGetTypePhysics()->GetNumChildren(); nChild++) { int boneIndex = pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID()); // go through each of the possible extras for(int nExtra=VEH_EXTRA_1; nExtra<=VEH_LAST_EXTRA; nExtra++) { // if this extra exists, and it's turned off if(HasComponent((eHierarchyId)nExtra) && (m_nDisableExtras &BIT(nExtra - VEH_EXTRA_1 + 1))) { // 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)nExtra) == boneIndex) { pFragInst->DeleteAbove(nChild); g_vehicleGlassMan.RemoveComponent(this, nChild); } } } } } // If the roof has been removed, get rid of the front windows too if( GetVehicleType()==VEHICLE_TYPE_CAR && !CarHasRoof() ) { DeleteWindow(VEH_WINDOW_LF); DeleteWindow(VEH_WINDOW_RF); } } } void CVehicle::InitLightExtras() { // this code is duplicated in CObject for bits that have broken off from cars if(m_nDisableExtras) { Matrix34 boneMat; Matrix34 mat; mat.Zero3x3(); fragInst* pFragInst = GetVehicleFragInst(); if(!pFragInst) { pFragInst = static_cast(GetCurrentPhysicsInst()); Assert(pFragInst->GetClassType() >= PH_INST_FRAG_GTA); } bool updateLightBones = false; for(int i=VEH_EXTRA_1; i<=VEH_LAST_EXTRA; i++) { if(m_nDisableExtras &BIT(i - VEH_EXTRA_1 + 1)) { int boneIdx = GetBoneIndex((eHierarchyId)i); if(boneIdx != -1) { if(pFragInst) { const crBoneData* boneData = pFragInst->GetType()->GetSkeletonData().GetBoneData(boneIdx); Assert(boneData->GetIndex() == boneIdx); // Mark all lights on disabled extras to be dead. const crBoneData* next = boneData->GetChild(); while (next) { int boneIdx = next->GetIndex(); for(int j=VEH_HEADLIGHT_L; jGetNext(); } } } } } m_VehicleDamage.SetUpdateLightBones(updateLightBones); } } void CVehicle::InitAllExtras() { u32 nExtraFlags = 0; // tell the veh it's raining, might want to put up it's roof if it's a convertible if(g_weather.IsRaining()) nExtraFlags |= EXTRAS_SET_RAIN; InitExtraFlags(nExtraFlags); UpdateExtras(); ENABLE_FRAG_OPTIMIZATION_ONLY(if(m_pVehicleFragInst->GetCached())) { InitFragmentExtras(); } InitLightExtras(); } int CVehicle::GetFragmentComponentIndex(eHierarchyId hierarchyId) const { if(hierarchyId != VEH_INVALID_ID) { int boneIndex = GetBoneIndex(hierarchyId); if(boneIndex >= 0) { if(fragInst* pFragInst = GetVehicleFragInst()) { int iComponent = (s8)pFragInst->GetComponentFromBoneIndex(boneIndex); if(iComponent >= 0) { Assert(pFragInst->GetArchetype() && pFragInst->GetArchetype()->GetBound() && pFragInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE); if(static_cast(pFragInst->GetArchetype()->GetBound())->GetBound(iComponent) != NULL) { return iComponent; } } } } } return -1; } bool CVehicle::HasFragmentComponent(eHierarchyId hierarchyId) const { return GetFragmentComponentIndex(hierarchyId) != -1; } void CVehicle::RemoveFragmentComponent(eHierarchyId hierarchyId) { fragInst* pFragInst = GetVehicleFragInst(); if(!pFragInst) { pFragInst = static_cast(GetCurrentPhysicsInst()); Assert(pFragInst->GetClassType() >= PH_INST_FRAG_GTA); } if(pFragInst) { // if this extra exists, and it's turned off if(HasComponent(hierarchyId)) { // go thru all the fragment children (there's more of them) for(int nChild=0; nChildGetTypePhysics()->GetNumChildren(); nChild++) { // if the bone index of this child matches the bone index of the extra that's turned off then delete this component if(GetBoneIndex(hierarchyId) == pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID())) { pFragInst->DeleteAbove(nChild); g_vehicleGlassMan.RemoveComponent(this, nChild); } } } } } void CVehicle::UpdateExtras() { s32 numExtraIncludeGroups = GetVehicleModelInfo()->GetNumExtraIncludeGroups(); for (int nCarComponent = VEH_EXTRA_1; nCarComponent <= VEH_LAST_EXTRA; ++nCarComponent) { int nExtraBit = BIT(nCarComponent - VEH_EXTRA_1 + 1); // skip if this extra is disabled if ((nExtraBit & m_nDisableExtras)) continue; // go through all include groups and make sure we force include any extras // based on the extras we already have turned on for (s32 i = 0; i < numExtraIncludeGroups; ++i) { u32 includeGroup = GetVehicleModelInfo()->GetExtraIncludeGroup(i); if ((nExtraBit & includeGroup) == 0) { continue; } // turn on extras in include group m_nDisableExtras &= ~includeGroup; } } // Turn on collisions with extras that have frag children / bounds. phBoundComposite* pBoundComp = static_cast(GetVehicleFragInst()->GetArchetype()->GetBound()); const fragPhysicsLOD* physicsLOD = GetVehicleFragInst()->GetTypePhysics(); for(int nExtra=VEH_EXTRA_1; nExtra <= VEH_LAST_EXTRA; nExtra++) { if(GetBoneIndex((eHierarchyId)nExtra)!=-1) { int nGroup = GetVehicleFragInst()->GetGroupFromBoneIndex(GetBoneIndex((eHierarchyId)nExtra)); if(nGroup > -1) { fragTypeGroup* pGroup = physicsLOD->GetGroup(nGroup); int iChild = pGroup->GetChildFragmentIndex(); for(int k = 0; k < pGroup->GetNumChildren(); k++) { if(GetIsExtraOn((eHierarchyId)nExtra)) { pBoundComp->SetIncludeFlags(iChild+k, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES); } } } } } } void CVehicle::TurnOffExtra(eHierarchyId nId, bool bTurnOff) { if(nId < VEH_EXTRA_1 || nId > VEH_LAST_EXTRA) { Assertf(false, "TurnOffExtra - bone is outside extras range"); return; } if(HasComponent(nId)) { if(bTurnOff) { // set flag to zero this bone matrix in prerender m_nDisableExtras |= BIT(nId - VEH_EXTRA_1 + 1); int nBoneIndex = GetBoneIndex(nId); fragInst* pFragInst = GetVehicleFragInst(); if(pFragInst) { // check if this bone is connected to a fragment group, and delete if it is. for(int nChild=0; nChildGetTypePhysics()->GetNumChildren(); nChild++) { if(nBoneIndex == pFragInst->GetType()->GetBoneIndexFromID(pFragInst->GetTypePhysics()->GetAllChildren()[nChild]->GetBoneID())) { pFragInst->DeleteAbove(nChild); g_vehicleGlassMan.RemoveComponent(this, nChild); } } } } else { m_nDisableExtras &= ~BIT(nId - VEH_EXTRA_1 + 1); UpdateExtras(); Fix(); if(GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_MATCH_LIVERY)) { if (nId - VEH_EXTRA_1 < GetVehicleModelInfo()->GetLiveriesCount()) { SetLiveryId(nId - VEH_EXTRA_1); UpdateBodyColourRemapping(); } } } } // else // Assertf(false, "TurnOffExtra - bone doesn't exist"); } bool CVehicle::GetIsExtraOn(eHierarchyId nId) const { // if bone for this component doesn't exist, then it can't be turned on if(!HasComponent(nId)) return false; // check id is in the correct range Assert(nId >= VEH_EXTRA_1 && nId <= VEH_LAST_EXTRA); // m_DisableExtras bits store if a given extra has been turned off if(m_nDisableExtras &BIT(nId - VEH_EXTRA_1 + 1)) return false; return true; } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : GetIsJetSki // PURPOSE : // RETURNS : True if jetski. ///////////////////////////////////////////////////////////////////////////////// bool CVehicle::GetIsJetSki() const { if(InheritsFromBoat()) { if (GetVehicleModelInfo() && GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_JETSKI)) { return true; } } return false; } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : PopTypeSet // PURPOSE : Set the var that lets us know which population system is // controlling the add and removal of this entity. // PARAMETERS : newPopType - the pop type that will track this entity. // RETURNS : Nothing ///////////////////////////////////////////////////////////////////////////////// void CVehicle::PopTypeSet(ePopType newVehPopType) { if (m_nDEflags.nPopType != newVehPopType) { if (m_nDEflags.nPopType == POPTYPE_RANDOM_PARKED) GetVehicleModelInfo()->RemoveVehicleModelRefParked(); else GetVehicleModelInfo()->RemoveVehicleModelRef(); CVehiclePopulation::UpdateVehCount(this, CVehiclePopulation::SUB); m_nDEflags.nPopType = newVehPopType; CVehiclePopulation::UpdateVehCount(this, CVehiclePopulation::ADD); if (m_nDEflags.nPopType == POPTYPE_RANDOM_PARKED) GetVehicleModelInfo()->AddVehicleModelRefParked(); else GetVehicleModelInfo()->AddVehicleModelRef(); if (m_nDEflags.nPopType == POPTYPE_MISSION || m_nDEflags.nPopType == POPTYPE_PERMANENT){ Assert(GetPortalTracker()); GetPortalTracker()->SetLoadsCollisions(true); //force loading of interior collisions CPortalTracker::AddToActivatingTrackerList(GetPortalTracker()); // causes interiors to activate & stay active } } } /////////////////////////////////////////////////////////////////////////// // FUNCTION SwitchEngineOn // DOES Starts the engine. There may be a delay in starting depending on engine temperature. /////////////////////////////////////////////////////////////////////////// void CVehicle::SwitchEngineOn(bool bNoDelay, bool bNetCheck, bool forceFail) { if (!bNetCheck || !IsNetworkClone()) { if (bNoDelay && !forceFail) { m_nVehicleFlags.bEngineOn = true; m_nVehicleFlags.bEngineStarting = false; m_nVehicleFlags.bSkipEngineStartup = true; m_bOnlyStartVehicleEngineOnThrottle = false; } else { if (!m_nVehicleFlags.bEngineOn && !m_nVehicleFlags.bEngineStarting) { m_Transmission.Reset(); if(GetSecondTransmission()) { GetSecondTransmission()->Reset(); } m_nVehicleFlags.bSkipEngineStartup = false; m_nVehicleFlags.bEngineStarting = true; //Only applicable for the local - setting this on the remote can end up in bad situations where the remote will not start - visibly very bad for helicopters and planes bool bIsStartingThisTime = false; if(forceFail) { m_nVehicleFlags.bEngineWontStart = true; } else if (IsNetworkClone()) { m_nVehicleFlags.bEngineWontStart = false; //ensure this is set to false as it is causing problems with remote vehicle engines actually starting - should always be false on remotes } else { m_nVehicleFlags.bEngineWontStart = !m_nVehicleFlags.bCarNeedsToBeHotwired && audEngineUtil::ResolveProbability(ComputeEngineWontStartProbability()); bIsStartingThisTime = !m_nVehicleFlags.bEngineWontStart; } m_VehicleAudioEntity->ComputeIgnitionHoldTime(bIsStartingThisTime); m_EngineSwitchedOnTime = fwTimer::GetTimeInMilliseconds(); } } } if(!InheritsFromTrain() && m_nVehicleFlags.bEngineOn) { UpdateUserEmissiveMultiplier(m_nVehicleFlags.bEngineOn, false); } } /////////////////////////////////////////////////////////////////////////// // FUNCTION SwitchEngineOff // DOES Kills the engine. Engine has to be switched on again to be able to drive. /////////////////////////////////////////////////////////////////////////// void CVehicle::SwitchEngineOff(bool bNetCheck) { if(GetVehicleType() != VEHICLE_TYPE_BICYCLE) { if (Verifyf(!bNetCheck || !IsNetworkClone(), "SwitchEngineOff: can't call this on network clones")) { m_nVehicleFlags.bEngineOn = false; m_nVehicleFlags.bEngineStarting = false; m_failedEngineStartAttempts = 0; UpdateBladeRpmOnEngineOff(); SetNitrousActive(false); } } if(!InheritsFromTrain()) { UpdateUserEmissiveMultiplier(m_nVehicleFlags.bEngineOn, true); } } /////////////////////////////////////////////////////////////////////////// // FUNCTION AddCommentOnVehicleEvent // DOES Add a shocking event if the vehicle can be commented on /////////////////////////////////////////////////////////////////////////// void CVehicle::AddCommentOnVehicleEvent() { if(GetVehicleType() != VEHICLE_TYPE_CAR) { return; } //Add event CEventShockingSeenNiceCar ev(*this); CShockingEventsManager::Add(ev); } /////////////////////////////////////////////////////////////////////////// // FUNCTION ComputeEngineWontStartProbability // DOES Returns the probability that this veh engine won't start this time /////////////////////////////////////////////////////////////////////////// #define MAX_ENGINE_START_ATTEMPTS 3 extern float sfPlaneEngineBreakDownHealth; // from Planes.cpp extern float sfHeliEngineBreakDownHealth; // from Heli.cpp float CVehicle::ComputeEngineWontStartProbability() { float hotwireDifficultyFactor; // dont start a wrecked or on fire engine if(GetStatus() == STATUS_WRECKED || m_Transmission.GetEngineHealth() <= ENGINE_DAMAGE_ONFIRE) { return 1.f; } if(GetVehicleType() == VEHICLE_TYPE_PLANE) { if(GetVehicleDamage()->GetEngineHealth() <= sfPlaneEngineBreakDownHealth) { return 1.0f; } if(GetStatus()==STATUS_PLAYER && GetDriver() && GetDriver()->IsPlayer() && (((CPlane*)this)->IsEngineTurnedOffByPlayer() || ((CPlane*)this)->IsEngineTurnedOffByScript())) { if(CControl *pControl = GetDriver()->GetControlFromPlayer()) { float fThrottleControl = pControl->GetVehicleFlyThrottleUp().GetNorm01() - pControl->GetVehicleFlyThrottleDown().GetNorm01(); if(IsInAir() && fThrottleControl <= 0.0f) { return 1.0f; } else if(!IsInAir() && fThrottleControl == 0.0f) { return 1.0f; } } } } if(GetVehicleType() == VEHICLE_TYPE_HELI && GetVehicleDamage()->GetEngineHealth() <= sfHeliEngineBreakDownHealth) { return 1.0f; } if (m_failNextEngineStartAttempt) { m_failNextEngineStartAttempt = false; return 1.0f; } if(m_failedEngineStartAttempts >= MAX_ENGINE_START_ATTEMPTS || g_ScriptAudioEntity.IsFlagSet(audScriptAudioFlags::CarsStartFirstTime) || NetworkInterface::IsGameInProgress() || PopTypeIsMission()) { return 0.0f; } if(InheritsFromBike()) //bicycles always start. Motorbikes do now too, as we don't currently have support for re-triggered kickstart animations. { return 0.0f; } CTaskMotionInAutomobile* pTask = NULL; if( GetDriver() ) pTask = (CTaskMotionInAutomobile*)GetDriver()->GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_MOTION_IN_AUTOMOBILE); // if not hotwiring then this factor is irrelevant if(pTask && !pTask->IsHotwiringVehicle() && !m_VehicleAudioEntity->IsDamageModel()) { hotwireDifficultyFactor = 1.0f; } else { // if its a damage model (vigero2 etc) then often don't start if(m_VehicleAudioEntity->IsDamageModel()) { hotwireDifficultyFactor = 0.4f; } else { // NOTE: now reversed; swanky = starts easily switch(SWANKNESS_5 - m_Swankness) { case 0: // not swank - always start since easy to hotwire hotwireDifficultyFactor = 1.f; break; case 1: hotwireDifficultyFactor = 0.9f; break; case 2: // default hotwireDifficultyFactor = 0.8f; break; case 3: hotwireDifficultyFactor = 0.7f; break; case 4: hotwireDifficultyFactor = 0.6f; break; case 5: // super swank - fail to start 50% of the time since hard to hotwire hotwireDifficultyFactor = 0.5f; break; default: Assert(0); hotwireDifficultyFactor = 1.0f; break; } } } // damage const float damageFactor = GetHealth() * (1.f / (float)CREATED_VEHICLE_HEALTH); return (1.0f - (damageFactor * hotwireDifficultyFactor)); } /////////////////////////////////////////////////////////////////////////// // FUNCTION CreateVehicleWeaponMgr // DOES Used when we want to use the vehicle weapon manager without a driver. /////////////////////////////////////////////////////////////////////////// void UpdateCollisionOnAllChildrenRecursive( fragPhysicsLOD* physicsLOD, phBoundComposite* pBoundComp, fragTypeGroup* pGroup, bool enableCollision, bool includeMapCollision ) { int child = pGroup->GetChildFragmentIndex(); for( s32 i = 0; i < pGroup->GetNumChildren(); ++i ) { u32 includeFlags = enableCollision ? includeMapCollision ? ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES : ArchetypeFlags::GTA_CAR_BUMPER_INCLUDE_TYPES : 0; pBoundComp->SetIncludeFlags( child + i, includeFlags ); } for( s32 f = 0; f < pGroup->GetNumChildGroups(); ++f ) { fragTypeGroup* pChildGroup = physicsLOD->GetGroup( f + pGroup->GetChildGroupsPointersIndex() ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pChildGroup, enableCollision, includeMapCollision ); } } void CVehicle::SetupWeaponModCollision() { // disable all the turret mod collision before we create the weapon manager fragInst* pFragInst = GetVehicleFragInst(); Assertf(pFragInst, "Vehicle '%s' has no fraginst!", GetModelName()); phBoundComposite* pBoundComp = static_cast(pFragInst->GetArchetype()->GetBound()); CVehicleVariationInstance& variation = GetVehicleDrawHandler().GetVariationInstance(); int turretMod = -1; CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); bool resetSeatCollision = false; bool turretModsOnRoof = pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_TURRET_MODS_ON_ROOF ); bool turretModsOnChassis5 = pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_TURRET_MODS_ON_CHASSIS5 ); if( turretModsOnRoof || turretModsOnChassis5 ) { bool bVariation = false; turretMod = turretModsOnRoof ? (s8)variation.GetModIndexForType( VMT_ROOF, this, bVariation ) + 1 : (s8)variation.GetModIndexForType( VMT_CHASSIS5, this, bVariation ) + 1; // TODO: Metadata these offsets at some point... if (MI_CAR_POUNDER2.IsValid() && GetModelIndex() == MI_CAR_POUNDER2) { // Mod -1 is no weapons, mod 0/1 are front mounted guns, mod 2 is the first turret turretMod -= 3; } if (MI_CAR_SPEEDO4.IsValid() && GetModelIndex() == MI_CAR_SPEEDO4) { // Mod -1 is no weapons, mod 0 is front mounted guns, mod 1 is the first turret turretMod -= 2; } else if (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_TURRET_MOD_WITH_NO_STOCK_TURRET)) { turretMod -= 1; } u32 iStartTurretModBone = VEH_TURRET_FIRST_MOD; u32 iEndTurretModBone = VEH_TURRET_LAST_MOD; for( u32 i = iStartTurretModBone; i <= iEndTurretModBone; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = (int)( i - VEH_TURRET_FIRST_MOD ) == turretMod; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); resetSeatCollision = true; } } } } if( pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_SECOND_TURRET_MOD ) ) { bool bVariation = false; turretMod = (s8)variation.GetModIndexForType( VMT_CHASSIS, this, bVariation ) + 1; u32 iStartTurretModBone = VEH_TURRET_B_FIRST_MOD; u32 iEndTurretModBone = VEH_TURRET_B_LAST_MOD; for( u32 i = iStartTurretModBone; i <= iEndTurretModBone; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = (int)( i - VEH_TURRET_B_FIRST_MOD ) == turretMod; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); resetSeatCollision = true; } } } } bool tempDisableAllAdditionalTurretMods = true; if( tempDisableAllAdditionalTurretMods || pModelInfo->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_THIRD_TURRET_MOD ) ) { turretMod = 0; // Pounder2 Turret 1A = VMT_BUMPER_R Mod 0 if( MI_CAR_POUNDER2.IsValid() && GetModelIndex() == MI_CAR_POUNDER2 ) { bool bVariation = false; turretMod = (s8)variation.GetModIndexForType(VMT_BUMPER_R, this, bVariation); } u32 iStartTurretModBone = VEH_TURRET_A_FIRST_MOD; u32 iEndTurretModBone = VEH_TURRET_A_LAST_MOD; for( u32 i = iStartTurretModBone; i <= iEndTurretModBone; ++i ) { const s32 boneIdx = GetBoneIndex( (eHierarchyId)i ); if( boneIdx != -1 ) { int group = pFragInst->GetGroupFromBoneIndex( boneIdx ); if( group > -1 ) { bool enableCollision = (int)( i - VEH_TURRET_A_FIRST_MOD ) == turretMod; fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); resetSeatCollision = true; } } } } // HACK TO REMOVE COLLISION ON THE HAVOC WEAPON if( GetModelIndex() == MI_HELI_HAVOK || GetModelIndex() == MI_HELI_SEASPARROW || GetModelIndex() == MI_HELI_SEASPARROW2 || GetModelIndex() == MI_HELI_SEASPARROW3) { const s32 boneIndex = GetBoneIndex( VEH_MOD_A ); bool bVariation = false; bool enableCollision = (s8)variation.GetModIndexForType( VMT_ROOF, this, bVariation ) == 0; if( GetModelIndex() == MI_HELI_HAVOK ) { enableCollision = (s8)variation.GetModIndexForType( VMT_ROOF, this, bVariation ) != -1; } if( boneIndex > -1 ) { fragInst* pFragInst = GetVehicleFragInst(); if( pFragInst ) { phBoundComposite* pBoundComp = static_cast(pFragInst->GetArchetype()->GetBound()); int group = pFragInst->GetGroupFromBoneIndex( boneIndex ); if( group > -1 ) { fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } } if( GetModelIndex() == MI_HELI_SEASPARROW || GetModelIndex() == MI_HELI_SEASPARROW2 || GetModelIndex() == MI_HELI_SEASPARROW3 ) { const s32 boneIndex = GetBoneIndex( VEH_MOD_C ); bool bVariation = false; bool enableCollision = (s8)variation.GetModIndexForType( VMT_ROOF, this, bVariation ) == 1; if( boneIndex > -1 ) { fragInst* pFragInst = GetVehicleFragInst(); if( pFragInst ) { phBoundComposite* pBoundComp = static_cast( pFragInst->GetArchetype()->GetBound() ); int group = pFragInst->GetGroupFromBoneIndex( boneIndex ); if( group > -1 ) { fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics(); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); UpdateCollisionOnAllChildrenRecursive( physicsLOD, pBoundComp, pGroup, enableCollision ); } } } } if( resetSeatCollision ) { InitialiseSeatCollision( pBoundComp ); } } void CVehicle::FreezeAllTurretJoints( gtaFragType* type, bool freeze ) { if( GetVehicleFragInst() && GetVehicleFragInst()->GetCacheEntry() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider ) { phArticulatedCollider* pArticulatedCollider = GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider; GetVehicleFragInst()->GetCacheEntry()->LazyArticulatedInit(); eHierarchyId turretModBones[ 14 ] = { VEH_TURRET_1_BASE, VEH_TURRET_4_BARREL, VEH_TURRET_A1_BASE, VEH_TURRET_A4_BARREL, VEH_TURRET_B1_BASE, VEH_TURRET_B4_BARREL, VEH_WEAPON_1A, VEH_WEAPON_4D_ROT, VEH_TURRET_FIRST_MOD, VEH_TURRET_LAST_MOD, VEH_TURRET_A_FIRST_MOD, VEH_TURRET_A_LAST_MOD, VEH_TURRET_B_FIRST_MOD, VEH_TURRET_B_LAST_MOD }; for( int i = 0; i < 14; i += 2 ) { if( m_pVehicleWeaponMgr && freeze ) { for( int j = (int)turretModBones[ i ]; j <= (int)turretModBones[ i + 1 ]; j++ ) { bool updateFreezeJoint = true; int componentIndex = type->GetComponentFromBoneIndex( 0, GetBoneIndex( (eHierarchyId)j ) ); if( componentIndex != -1 ) { for( int k = 0; k < m_pVehicleWeaponMgr->GetNumTurrets(); k++ ) { CTurret* turret = m_pVehicleWeaponMgr->GetTurret( k ); CTurretPhysical* turretPhysical = turret->IsPhysicalTurret() ? static_cast( turret ) : NULL; if( turret->GetBaseBoneId() == (eHierarchyId)j || turret->GetBarrelBoneId() == (eHierarchyId)j ) { updateFreezeJoint = false; break; } if( turretPhysical && ( turretPhysical->GetBaseFragChild() == componentIndex || turretPhysical->GetBarrelFragChild() == componentIndex ) ) { updateFreezeJoint = false; break; } int linkIndex = pArticulatedCollider->GetLinkFromComponent( componentIndex ); int baseLinkIndex = pArticulatedCollider->GetLinkFromComponent( turretPhysical->GetBaseFragChild() ); int barrelLinkIndex = pArticulatedCollider->GetLinkFromComponent( turretPhysical->GetBarrelFragChild() ); if( linkIndex == baseLinkIndex || linkIndex == barrelLinkIndex ) { updateFreezeJoint = false; break; } } if( updateFreezeJoint ) { int componentIndex = type->GetComponentFromBoneIndex( 0, GetBoneIndex( (eHierarchyId)j ) ); if( componentIndex != -1 ) { Freeze1DofJointInCurrentPosition( GetVehicleFragInst(), componentIndex, freeze ); } } } } } else { for( int j = (int)turretModBones[ i ]; j <= (int)turretModBones[ i + 1 ]; j++ ) { int componentIndex = type->GetComponentFromBoneIndex( 0, GetBoneIndex( (eHierarchyId)j ) ); if( componentIndex != -1 ) { Freeze1DofJointInCurrentPosition( GetVehicleFragInst(), componentIndex, freeze ); } } } } } } void CVehicle::FreezeAllJoints( gtaFragType* type ) { if( GetVehicleFragInst() && GetVehicleFragInst()->GetCacheEntry() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider ) { GetVehicleFragInst()->GetCacheEntry()->LazyArticulatedInit(); int numChildren = type->GetPhysics( 0 )->GetNumChildren(); for( int i = 0; i < numChildren; i++ ) { Freeze1DofJointInCurrentPosition( GetVehicleFragInst(), i ); } } } void CVehicle::CreateVehicleWeaponMgr() { m_destroyWeaponMgr = false; gtaFragType* type = GetVehicleModelInfo()->GetFragType(); if( type ) { FreezeAllTurretJoints( type, false ); } SetupWeaponModCollision(); Assertf(!m_pVehicleWeaponMgr, "Vehicle weapon manager already exists"); if(!m_pVehicleWeaponMgr) { m_pVehicleWeaponMgr = CVehicleWeaponMgr::CreateVehicleWeaponMgr(this); } if( type ) { FreezeAllTurretJoints( type, true ); } } void CVehicle::DestroyVehicleWeaponMgr() { m_destroyWeaponMgr = false; if(m_pVehicleWeaponMgr) { //Make sure any physical turrets have their muscles reset so they don't keep spinning when dead. for(int i = 0; i < m_pVehicleWeaponMgr->GetNumTurrets(); i++) { CTurret *pTurret = m_pVehicleWeaponMgr->GetTurret(i); if(pTurret->GetType() == VGT_TURRET_PHYSICAL) { static_cast(pTurret)->ResetTurretsMuscles(this); } } #if __BANK Displayf("[B*2373228] Vehicle weapon manager is being destroyed. Callstack:"); sysStack::PrintStackTrace(); #endif m_pVehicleWeaponMgr->ResetIncludeFlags(this); delete m_pVehicleWeaponMgr; m_pVehicleWeaponMgr = NULL; } } void CVehicle::InitialiseSeatCollision( phBoundComposite* pBoundComp ) { // Turn off the seat collision // It will get turned on when passengers enter the vehicle for(int i = 0; i < m_SeatManager.GetMaxSeats(); i++) { const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); physicsFatalAssertf(pVehicleModelInfo, "This vehicle has no model info. It is unsafe to continue"); int iFragChild = pVehicleModelInfo->GetFragChildForSeat(i); if(iFragChild > -1) { pBoundComp->SetIncludeFlags(iFragChild,0); } } } void CVehicle::Freeze1DofJointInCurrentPosition(fragInstGta* pFragInst, int iChildIndex) { Assert(pFragInst); Assert(iChildIndex > -1); Assert(pFragInst->GetCacheEntry()); fragHierarchyInst* pHierInst = pFragInst->GetCacheEntry()->GetHierInst(); phArticulatedCollider* pArticulatedCollider = pHierInst->articulatedCollider; Assert(pArticulatedCollider); if(pArticulatedCollider == NULL) { return; } pFragInst->GetCacheEntry()->LazyArticulatedInit(); int linkIndex = pArticulatedCollider->GetLinkFromComponent(iChildIndex); if(linkIndex > 0) { phJoint1Dof* p1DofJoint = NULL; phJoint* pJoint = &pHierInst->body->GetJoint(linkIndex - 1); if (pJoint && pJoint->GetJointType() == phJoint::JNT_1DOF) p1DofJoint = static_cast(pJoint); if (physicsVerifyf(p1DofJoint,"Child is not connected by a 1 dof joint")) { const float fCurrentAngle = p1DofJoint->GetAngle(pHierInst->body); p1DofJoint->SetAngleLimits(fCurrentAngle,fCurrentAngle); p1DofJoint->SetMuscleTargetSpeed(0.0f); } } } void CVehicle::Freeze1DofJointInCurrentPosition(fragInstGta* pFragInst, int iChildIndex, bool freeze) { vehicleAssert(pFragInst); vehicleAssert(iChildIndex != -1); vehicleAssert(pFragInst->GetCacheEntry()); fragHierarchyInst* pHierInst = pFragInst->GetCacheEntry()->GetHierInst(); phArticulatedCollider* pArticulatedCollider = pHierInst->articulatedCollider; vehicleAssert(pArticulatedCollider); if(pArticulatedCollider == NULL) { return; } pFragInst->GetCacheEntry()->LazyArticulatedInit(); int linkIndex = pFragInst->GetLinkIndexFromComponent(iChildIndex); if(linkIndex > 0) { phJoint1Dof* p1DofJoint = NULL; phJoint* pJoint = &pHierInst->body->GetJoint(linkIndex - 1); if (pJoint && pJoint->GetJointType() == phJoint::JNT_1DOF) p1DofJoint = static_cast(pJoint); if (physicsVerifyf(p1DofJoint,"Child is not connected by a 1 dof joint")) { if(freeze) { p1DofJoint->SetAngleLimits(0.0f,0.0f); p1DofJoint->SetMuscleTargetSpeed(0.0f); } else { float minAngle = 0.0f; float maxAngle = 0.0f; p1DofJoint->GetAngleLimits( minAngle, maxAngle ); if( minAngle != maxAngle ) { return; } // Grab the limits out of the bone data // Ideally we could use the adjustment angles but they get reset often // See fragCacheEntry::CreateArticulatedJoint for original code u16 boneId = pFragInst->GetTypePhysics()->GetChild( iChildIndex )->GetBoneID(); s32 boneIndex; const fragType* type = pFragInst->GetType(); const crSkeletonData& skeletonData = pFragInst->GetSkeleton()->GetSkeletonData(); if(physicsVerifyf(skeletonData.ConvertBoneIdToIndex(boneId,boneIndex),"")) { bool hasXLimits, hasYLimits, hasZLimits, hasXTLimits, hasYTLimits, hasZTLimits; const crJointData* jointData = type->GetCommonDrawable()->GetJointData(); const crBoneData* boneData = skeletonData.GetBoneData((u32)boneIndex); if( type->WillBeArticulated(*boneData, *jointData, hasXLimits, hasYLimits, hasZLimits, hasXTLimits, hasYTLimits, hasZTLimits) ) { Vec3V minRot, maxRot; jointData->ConvertToEulers(*boneData,minRot,maxRot); float minAngle,maxAngle; if (hasXLimits) { minAngle = minRot.GetXf(); maxAngle = maxRot.GetXf(); } else if (hasYLimits) { minAngle = minRot.GetYf(); maxAngle = maxRot.GetYf(); } else { minAngle = minRot.GetZf(); maxAngle = maxRot.GetZf(); } p1DofJoint->SetAngleLimits(minAngle,maxAngle); } } } } } } /////////////////////////////////////////////////////////////////////////// // FUNCTION GetSeatHasWeaponsOrTurrets // DOES Function to see if a specific vehicle seat has any turrets or weapons associated with it /////////////////////////////////////////////////////////////////////////// bool CVehicle::GetSeatHasWeaponsOrTurrets(s32 seatIndex) const { if(!GetVehicleWeaponMgr()) { return false; } if(GetVehicleWeaponMgr()->GetNumWeaponsForSeat(seatIndex) > 0) { return true; } if(GetVehicleWeaponMgr()->GetNumTurretsForSeat(seatIndex) > 0) { return true; } return false; } /////////////////////////////////////////////////////////////////////////// // FUNCTION UpdateEngineTemperature // DOES Called once a frame. If engine is on temperature goes up. If engine off temperature goes down. /////////////////////////////////////////////////////////////////////////// // number of times A must be mashed to start the engine quickly #define FAST_ENGINE_START_MASH_A_THRESHOLD 9 void CVehicle::UpdateEngineTemperature(float dt) { // cancel any ignition if the driver leaves the veh before its started if(m_nVehicleFlags.bEngineStarting) { bool cancelStartup = false; if(GetDriver()) { if(GetDriver()->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE)) { cancelStartup = true; } } // Personal vehicles can be activated remotely via the keyfob and therefore not having a driver is not a valid reason to cancel the startup behaviour else if(!IsPersonalVehicle() && !IsFullThrottleActive()) { cancelStartup = true; } if(cancelStartup) { m_nVehicleFlags.bEngineStarting = false; m_failedEngineStartAttempts = 0; } } // Engine temperature debug is handled in RenderDebug(). if(m_nVehicleFlags.bEngineStarting&& CTaskMotionInAutomobile::IsVehicleDrivable(*this)) { // engine starts if any hostile peds are close to the player and trying to enter the vehicle if(GetStatus() == STATUS_PLAYER && GetDriver()) { if(GetDriver()->CountNearbyHostilePedsTryingToEnterMyVehicle(5.0f) >= 1) { Displayf("Starting engine due to %d hostile peds within 5m trying to enter vehicle", GetDriver()->CountNearbyHostilePedsTryingToEnterMyVehicle(2.0f)); m_nVehicleFlags.bEngineOn = true; m_nVehicleFlags.bEngineStarting = false; m_bOnlyStartVehicleEngineOnThrottle = false; } // else // { // CTaskInVehicleBasic* pTask = (CTaskInVehicleBasic*)GetDriver()->GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_IN_VEHICLE_BASIC); // if(pTask) // { // if(pTask->GetTimesAButtonMashedDuringHotwire() >= FAST_ENGINE_START_MASH_A_THRESHOLD) // { // Displayf("Starting engine since A was mashed %u times", pTask->GetTimesAButtonMashedDuringHotwire()); // m_nVehicleFlags.bEngineOn = true; // m_nVehicleFlags.bEngineStarting = false; // } // } // // } } } // check again for engine starting incase the above check starts the engine if(m_nVehicleFlags.bEngineStarting && !IsNetworkClone() && fwTimer::GetTimeInMilliseconds() > m_EngineSwitchedOnTime + (u32)(m_VehicleAudioEntity->GetIgnitionHoldTime()*1000.f)) { if(m_nVehicleFlags.bEngineWontStart) { CPed* pDriver = GetDriver(); if(pDriver && pDriver->IsPlayer()) { pDriver->NewSay("START_CAR_PANIC"); if(m_VehicleAudioEntity) { m_VehicleAudioEntity->TriggerEngineFailedToStart(); } } u8 failedAttempts = ++m_failedEngineStartAttempts; SwitchEngineOff(); if(CTaskMotionInAutomobile::IsVehicleDrivable(*this)) { // force an audio update to catch the state change - this happens sufficiently infrequently that I'm going to leave it here, // even though vehicles no longer update their audio entity in ProcessControl - it's just done from audio PreUpdateService now. m_VehicleAudioEntity->PreUpdateService(fwTimer::GetTimeInMilliseconds()); m_failedEngineStartAttempts = failedAttempts; SwitchEngineOn(false); } } else { m_nVehicleFlags.bEngineOn = true; m_nVehicleFlags.bEngineStarting = false; m_bOnlyStartVehicleEngineOnThrottle = false; } } // apply cooling effect of ambient temperature f32 coolingFactor = m_nVehicleFlags.bHasCoolingFan ? (m_nVehicleFlags.bIsCoolingFanOn ? 0.4f : 0.1f) : 0.2f; // reduce cooling based on engine damage. if(m_Transmission.GetEngineOnFire()) { coolingFactor = 0.0f; } else if(m_Transmission.GetEngineHealth() < ENGINE_DAMAGE_RADBURST) { coolingFactor *= 0.5f; } m_EngineTemperature += (g_weather.GetTemperature(GetTransform().GetPosition()) - m_EngineTemperature) * ENGINE_THERMAL_CONSTANT * coolingFactor * dt; if(m_nVehicleFlags.bEngineOn) { Vec3f aiVel, forward; LoadAsScalar(aiVel, GetAiVelocityConstRef()); LoadAsScalar(forward, GetMatrixRef().GetCol1ConstRef()); f32 speedRatio = Clamp(Abs(Dot(aiVel, forward)) / m_Transmission.GetDriveMaxVelocity(), 0.f, 1.f); if(GetVehicleType() == VEHICLE_TYPE_HELI || GetVehicleType() == VEHICLE_TYPE_PLANE) // not going to cool planes or helis with speed { speedRatio = 0.0f; } // approach max temp if engine is on, cooled by airflow if we're moving f32 coreTemp = MAX_ENGINE_TEMPERATURE - (speedRatio * (MAX_ENGINE_TEMPERATURE/2.f)); // apply affect of core temp on engine block m_EngineTemperature += (coreTemp - m_EngineTemperature) * ENGINE_THERMAL_CONSTANT * dt; // flag that this vehicle has been driven m_nVehicleFlags.bHasBeenDriven = true; } // update fan state if(m_nVehicleFlags.bHasCoolingFan && !m_nVehicleFlags.bIsCoolingFanOn && m_EngineTemperature > ENGINE_TEMP_FAN_ON) { m_nVehicleFlags.bIsCoolingFanOn = true; } else if(m_nVehicleFlags.bIsCoolingFanOn && m_EngineTemperature < ENGINE_TEMP_FAN_OFF) { m_nVehicleFlags.bIsCoolingFanOn = false; } } /////////////////////////////////////////////////////////////////////////// // FUNCTION AreTyresDamaged // DOES Returns whether any of the tyres are damaged /////////////////////////////////////////////////////////////////////////// bool CVehicle::AreTyresDamaged() const { bool tyresDamaged = false; for(u32 index = 0; index < GetNumWheels() && !tyresDamaged; index++) { const CWheel *wheel = GetWheel(index); Assert(wheel); if(wheel->GetTyreHealth() != TYRE_HEALTH_DEFAULT) { tyresDamaged = true; } if (!tyresDamaged) { tyresDamaged = GetWheelBroken(index); } if (!tyresDamaged) { tyresDamaged = wheel->IsWheelOnFire(); } } return tyresDamaged; } /////////////////////////////////////////////////////////////////////////// // FUNCTION IsSuspensionDamaged // DOES Returns whether the suspension is damaged /////////////////////////////////////////////////////////////////////////// bool CVehicle::IsSuspensionDamaged() const { bool suspensionDamaged = false; for(s32 index = 0; index < GetNumWheels() && !suspensionDamaged; index++) { const CWheel *wheel = GetWheel(index); Assert(wheel); if(wheel->GetSuspensionHealth() != SUSPENSION_HEALTH_DEFAULT) { suspensionDamaged = true; } } return suspensionDamaged; } u8 CVehicle::GetNumFlatTires() const { //Count the flat tires. u8 uCount = 0; for(int i = 0; i < GetNumWheels(); ++i) { //Grab the wheel. const CWheel* pWheel = GetWheel(i); Assert(pWheel); //Check if the wheel is flat. if(pWheel->GetIsFlat()) { ++uCount; } } return uCount; } // This is all tuned to some very specific numbers to try to get the flags set // quickly for nasty things like being run over by a tank but not occur under // almost any other game situation // Unfortunately deformation damage radii get scaled for a variety of arbitrary reasons // such as being the player or even being in first person camera // So it would be nice if we could feed this function some more normalized input // But this works pretty well for now and with the high thresholds helps protect us from // false positives in many cases void CVehicle::FlagWheelsWithDeformationImpact(Vec3V_In damageOffset, ScalarV_In damageRadius) { // Not allowed to do any bursting or breaking to mission vehicles or player vehicles while on a mission const bool vehIsOnMission = PopTypeIsMission() || (GetDriver() && GetDriver()->IsPlayer()) || HasMissionCharsInIt(); if( ( !vehIsOnMission || m_nVehicleFlags.bCarHitBySuperHeavyVehicle ) && m_nVehicleFlags.bCanBreakOffWheelsWhenBlowUp ) { const ScalarV baseDamageRadius = damageRadius; const ScalarV damageRadiusThreshold = ScalarVFromF32(sm_WheelDamageRadiusThreshold); if(IsLessThanAll(baseDamageRadius, damageRadiusThreshold) != 0) { return; } const ScalarV damageRadiusThreshWheelDamageMax = ScalarVFromF32(sm_WheelDamageRadiusThreshWheelDamageMax); Vec3V damageOff = damageOffset; // Actually just want to look at the 2D projection damageOff.SetZ(ScalarV(V_ZERO)); const phBoundComposite* compositeBound = GetVehicleFragInst()->GetCacheEntry()->GetBound(); Assert(compositeBound); // Think we must be cached in order to take damage, but I'm not sure if the very first hit might have this occurring too early in the frame const ScalarV damageRadiusScale = ScalarVFromF32(sm_WheelDamageRadiusScale); const ScalarV wheelRadiusScale = ScalarVFromF32(sm_WheelSelfRadiusScale); const ScalarV scaledDamageRadius = Scale(baseDamageRadius, damageRadiusScale); const ScalarV heavyDamageRadiusScale = ScalarVFromF32(sm_WheelHeavyDamageRadiusScale); const ScalarV heavyWheelRadiusScale = ScalarVFromF32(sm_WheelSelfHeavyWheelRadiusScale); const ScalarV heavyDamageRadius = Scale(baseDamageRadius, heavyDamageRadiusScale); for(s32 index = 0; index < GetNumWheels(); index++) { CWheel* wheel = GetWheel(index); Assert(wheel); // Until sufficient damage is accrued from past hits, we limit immediate breaking or bursting to only particularly large impacts const ScalarV wheelDamage = Scale(ScalarVFromF32(wheel->GetFrictionDamage()), ScalarVFromF32(sm_WheelDamageRadiusThreshWheelDamageScale)); if(IsLessThanAll( baseDamageRadius, rage::Add(damageRadiusThreshold, Max(Subtract(damageRadiusThreshWheelDamageMax, wheelDamage), ScalarV(V_ZERO))) ) != 0) { continue; } const ScalarV baseWheelRadius = ScalarVFromF32(wheel->GetWheelRadiusIncBurst()); const ScalarV wheelRadius = Scale(baseWheelRadius, wheelRadiusScale); const ScalarV totalRadius = rage::Add(scaledDamageRadius, wheelRadius); int wheelComponent = wheel->GetFragChild(); if(wheelComponent == -1) { continue; } const Mat34V currentWheelMat = compositeBound->GetCurrentMatrix(wheelComponent); Vec3V currentWheelPos = currentWheelMat.GetCol3(); // Again, 2D only currentWheelPos.SetZ(ScalarV(V_ZERO)); const Vec3V wheelToDamage = Subtract(damageOff, currentWheelPos); const ScalarV wheelToDamageDistSqrd = Dot(wheelToDamage, wheelToDamage); const ScalarV heavyWheelRadius = Scale(baseWheelRadius, heavyWheelRadiusScale); const ScalarV heavyTotalRadius = rage::Add(heavyDamageRadius, heavyWheelRadius); if(IsLessThanOrEqualAll(wheelToDamageDistSqrd, Scale(heavyTotalRadius, heavyTotalRadius)) != 0) { if(!m_nVehicleFlags.bWheelsDontBreak) { wheel->GetDynamicFlags().SetFlag(WF_WITHIN_HEAVYDAMAGE_REGION); } } if(IsLessThanOrEqualAll(wheelToDamageDistSqrd, Scale(totalRadius, totalRadius)) != 0) { if(!m_nVehicleFlags.bTyresDontBurst) { wheel->GetDynamicFlags().SetFlag(WF_WITHIN_DAMAGE_REGION); } } } } } /////////////////////////////////////////////////////////////////////////// // FUNCTION ProcessVehicle // DOES Little things that need to be processed for all vehicle types (cars, bikes, boats, heli etc) go here. /////////////////////////////////////////////////////////////////////////// dev_float s_fMinRumbleDriverSpeedSqr = rage::square(2.0f); dev_s32 s_vibrateDelay = 0; float CVehicle::ms_fEngineVarianceMaxModifier = 0.2f; float CVehicle::ms_fThrustDropOffVarianceMaxModifier = 0.15f; float CVehicle::ms_fBrakeVarianceMaxModifier = 0.10f; float CVehicle::ms_fSuspensionVarianceMaxModifier = 0.10f; float CVehicle::ms_fGearboxClutchSpeedVarianceMaxModifier = 1.0f; float CVehicle::ms_fHydraulicVarianceUpperBoundMaxModifier = 0.25f; float CVehicle::ms_fHydraulicVarianceLowerBoundMaxModifier = 0.04f; float CVehicle::ms_fHydraulicVarianceUpperBoundMinModifier = 0.10f; float CVehicle::ms_fHydraulicVarianceLowerBoundMinModifier = 0.015f; float CVehicle::ms_fHydraulicRateMax = 1.0f; float CVehicle::ms_fHydraulicRateMin = 0.25f; float CVehicle::ms_fPlaneHandlingVarianceMaxModifier = 0.25f; float CVehicle::ms_fRocketBoostMaxIncrease = 1.0f; bool CVehicle::ProcessControl() { PF_AUTO_PUSH_TIMEBAR("CVehicle::ProcessControl()"); if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy)) { perfClearingHouse::Increment(perfClearingHouse::SUPER_DUMMY_VEHICLES); } else if(m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodDummy)) { perfClearingHouse::Increment(perfClearingHouse::DUMMY_VEHICLES); } else { perfClearingHouse::Increment(perfClearingHouse::NORMAL_VEHICLES); } // TUNE_BOOL(BumpOrientation, true); // TUNE_BOOL(AngVel, true); // TUNE_BOOL(DummyOnly, true); // TUNE_FLOAT(OrientationBumpDegreesMax, 60.0f, 0.0f, 180.0f, 5.0f); // TUNE_FLOAT(AngVelBump, 2.0f, 0.0f, 100.0f, 0.1f); // TUNE_BOOL(AngVelBumpLocalOnly, false); // if(!ContainsPlayer()) // { // if(ioPad::GetPad(0).GetPressedButtons() & ioPad::L3) // { // if(!DummyOnly || GetVehicleAiLod().GetDummyMode() == VDM_DUMMY) // { // if(BumpOrientation) // { // float rotAngleLocalZ = OrientationBumpDegreesMax*DtoR*(g_ReplayRand.GetFloat()*2.0f - 1.0f); // float rotAngleLocalX = OrientationBumpDegreesMax*DtoR*(g_ReplayRand.GetFloat()*2.0f - 1.0f); // Mat34V transform; // Mat34VFromEulersXYZ(transform, Vec3V(rotAngleLocalX, 0.0f, rotAngleLocalZ)); // Mat34V mtrx = GetMatrixRef(); // Transform(mtrx, transform); // SetMatrix(RCC_MATRIX34(mtrx)); // } // // if(AngVel) // { // Vec3V angVel; // angVel.SetXf(g_ReplayRand.GetFloat()*2.0f - 1.0f); // angVel.SetYf(g_ReplayRand.GetFloat()*2.0f - 1.0f); // angVel.SetZf(g_ReplayRand.GetFloat()*2.0f - 1.0f); // angVel = Normalize(angVel); // angVel = Scale(angVel, ScalarV(AngVelBump)); // if(!AngVelBumpLocalOnly || !IsNetworkClone()) // { // SetAngVelocity(RCC_VECTOR3(angVel)); // } // } // } // } // } bool fullUpdate = CVehicleAILodManager::ShouldDoFullUpdate(*this); const float dt = fwTimer::GetTimeStep(); Assert(dt > 0.0f); const float fTimeslicedTimeStep = m_fTimeSinceLastAiUpdate + dt; Assertf(fTimeslicedTimeStep < 5.0f REPLAY_ONLY(|| CReplayMgr::IsReplayInControlOfWorld()), "Vehicle = %s dT=%.2f", CPhysical::GetDebugName(), dt); if(fullUpdate || GetIsInReusePool() REPLAY_ONLY(|| CReplayMgr::IsEditModeActive())) { m_fTimeSinceLastAiUpdate = 0.0f; } else { m_fTimeSinceLastAiUpdate = fTimeslicedTimeStep; } DoProcessControl(fullUpdate, fTimeslicedTimeStep); #if __BANK TUNE_GROUP_BOOL( VEHICLE_FORMATION_LEADER, setLeaderFromPicker, false ); if( setLeaderFromPicker && g_PickerManager.GetSelectedEntity() && g_PickerManager.GetSelectedEntity()->GetType() == ENTITY_TYPE_VEHICLE && g_PickerManager.GetSelectedEntity() != ms_formationLeader ) { ms_formationLeader = static_cast< CVehicle* >( g_PickerManager.GetSelectedEntity() ); if( ms_formationRadius == 0.0f ) { ms_formationOffset = Vector3( 0.0f, -10.0f, 0.0f ); ms_formationRadius = 5.0f; } } TUNE_GROUP_BOOL( VEHICLE_FORMATION_LEADER, overriderLeaderOffset, false ); if( overriderLeaderOffset ) { TUNE_GROUP_FLOAT( VEHICLE_FORMATION_LEADER, offsetX, 0.0f, -100.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_FORMATION_LEADER, offsetY, -10.0f, -100.0f, 100.0f, 0.1f ); ms_formationOffset.SetX( offsetX ); ms_formationOffset.SetY( offsetY ); } TUNE_GROUP_BOOL( VEHICLE_FORMATION_LEADER, overriderTargetRadius, false ); if( overriderTargetRadius ) { TUNE_GROUP_FLOAT( VEHICLE_FORMATION_LEADER, radius, 5.0f, 0.0f, 50.0f, 0.1f ); ms_formationRadius = radius; } #endif // #if __BANK if( GetDriver() && GetDriver()->IsLocalPlayer() && GetThrottle() > 0.9f ) { if( ms_formationLeader ) { const fwTransform& leaderTransform = ms_formationLeader->GetTransform(); Vector3 playerToLeaderVector = VEC3V_TO_VECTOR3( leaderTransform.UnTransform( GetTransform().GetPosition() ) ); Vector3 playerToTarget = ( ms_formationOffset - playerToLeaderVector ); float distanceToTarget = playerToTarget.Mag(); if( distanceToTarget < ms_formationRadius ) { //float targetToLeader = ms_formationOffset.Mag(); //float playerToLeader = Mag( leaderTransform.GetPosition() - GetTransform().GetPosition() ).Getf(); static float maxTimeToReachTarget = 1.0f; float timeToReachTarget = fwTimer::GetTimeStep(); distanceToTarget = Abs( playerToTarget.y ); float distanceFactor = ( distanceToTarget / ms_formationRadius ); float throttle = Max( ms_formationLeader->GetThrottle(), 0.0f ); if( playerToTarget.y < 0.0f ) { distanceToTarget = -distanceToTarget; distanceFactor = 0.0f; } else { throttle = 1.0f; timeToReachTarget = Max( timeToReachTarget, ( distanceFactor * distanceFactor ) * maxTimeToReachTarget ); //timeToReachTarget *= 10.0f; } Vector3 velocityDirection = GetVelocity(); velocityDirection.NormalizeSafe( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ); if( Dot( velocityDirection, VEC3V_TO_VECTOR3( ms_formationLeader->GetTransform().GetForward() ) ) > 0.35f ) { float targetSpeed = ms_formationLeader->GetVelocity().Mag(); targetSpeed += ( distanceToTarget / ms_formationRadius ) * targetSpeed * 0.2f; float playerVelocity = GetVelocity().Mag(); float acceleration = ( targetSpeed - playerVelocity ) / fwTimer::GetTimeStep(); // 2.0f * ( distanceToTarget - ( ( playerVelocity - targetSpeed ) * timeToReachTarget ) ) ) / ( timeToReachTarget * timeToReachTarget ); acceleration = Clamp( acceleration, -playerVelocity / fwTimer::GetTimeStep(), 30.0f ); float maxAcceleration = Min( Abs( acceleration ), 259.0f ); while( acceleration != 0.0f ) { float appliedAcceleration = Clamp( acceleration, -maxAcceleration, maxAcceleration ); if( acceleration > 0.0f ) { acceleration = Max( 0.0f, acceleration - maxAcceleration ); } else { acceleration = Min( 0.0f, acceleration + maxAcceleration ); } float force = GetMass() * appliedAcceleration; Vector3 forceVector = velocityDirection * force; ApplyForceCg( forceVector ); } } float throttleScale = ms_formationLeader->m_Transmission.GetDriveForce() / m_Transmission.GetDriveForce(); throttle *= ( 0.75f + ( throttleScale * 0.25f ) ) * distanceFactor; throttle = Clamp( throttle, 0.0f, 1.0f ); SetThrottle( throttle ); } } } ProcessTombstone(); // Jump button on horn if( HasJump() && IsEngineOn() ) { // Update the recharge bar UpdateJumpingCarRechargeTimer(); if( GetDriver() ) { static u32 lastTimeJumpPressed = 0; CControl *pControl = GetDriver()->GetControlFromPlayer(); static bool buttonUp = true; bool bJumpPressed = false; if( pControl != NULL ) { bool buttonDown = pControl->GetVehicleCarJump().IsPressed(); bJumpPressed = buttonDown && ( buttonUp || !sm_bDoubleClickForJump ); buttonUp = !buttonDown; if( bJumpPressed && sm_bDoubleClickForJump ) { if( fwTimer::GetSystemTimeInMilliseconds() - lastTimeJumpPressed > 500 ) { lastTimeJumpPressed = fwTimer::GetSystemTimeInMilliseconds(); bJumpPressed = false; } } } bool bWheelsOnGround = GetNumContactWheels() > ( GetNumWheels() / 2 ) || pHandling->GetSpecialFlightHandlingData(); bool bUsingPhoneCam = GetDriver()->IsLocalPlayer() && CPhoneMgr::CamGetState(); // If on ground and horn button pressed, do jump if( bJumpPressed && !( bUsingPhoneCam ) ) { if( !GetIsDoingJump() && bWheelsOnGround ) { if( GetNetworkObject() && !GetNetworkObject()->IsClone() ) { CActivateVehicleSpecialAbilityEvent::Trigger( *this, CActivateVehicleSpecialAbilityEvent::JUMP ); } SetIsDoingJump( true ); vehicleDisplayf( "CVehicle::ProcessControl() - Car Jump: %s, starting jump", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?" ); } //else if( CanTriggerForwardJump() ) //{ // if( !GetIsDoingJump() ) // { // SetIsDoingJump( true ); // SetJumpState( FORWARD_JUMP ); // } // SetIsDoingForwardJump( true ); //} } else if( bJumpPressed ) { vehicleDisplayf( "CVehicle::ProcessControl() - Car Jump: %s, button pressed but not jumping: on ground [%d] using phone cam [%d]", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?", (int)bWheelsOnGround, (int)bUsingPhoneCam ); } } } if(InheritsFromAmphibiousQuadBike() && IsEngineOn() && GetDriver()) { CAmphibiousQuadBike* pQuad = static_cast(this); CControl *pControl = GetDriver()->GetControlFromPlayer(); static dev_u32 suTimeToHoldForWheelTuckIn = 250; if (m_bToggleTuckInWheelsFromNetwork) { m_bToggleTuckInWheelsFromNetwork = false; pQuad->ToggleTuckInWheels(); } if(pControl != NULL) { ioValue retractButton = pControl->GetVehicleRoof(); if( ms_useBoostButtonForRetract ) { retractButton = pControl->GetVehicleRocketBoost(); } pQuad->SetRetractButtonHasBeenUp(pQuad->GetRetractButtonHasBeenUp() || retractButton.IsUp() ); if(pQuad->GetRetractButtonHasBeenUp() && ( retractButton.HistoryHeldDown( suTimeToHoldForWheelTuckIn ) || ( ms_useBoostButtonForRetract && retractButton.IsPressed() ) ) ) { pQuad->SetRetractButtonHasBeenUp(false); pQuad->ToggleTuckInWheels(); } } } ProcessNitrousControl(); // Rocket boost control if( HasRocketBoost() && IsEngineOn() ) { const f32 initialBoostRemaining = m_fRocketBoostRemaining; const bool initialBoostState = m_bIsRocketBoosting; static dev_float sfMaxVelocityForJatoActivation = 400.0f; bool hasJato = GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_JATO_BOOST_MOD); bool jatoAvailable = !hasJato || ( !IsInAir() && GetVelocity().Mag2() < sfMaxVelocityForJatoActivation ); jatoAvailable = jatoAvailable && ( !InheritsFromPlane() || !static_cast< CPlane* >( this )->GetVerticalFlightModeAvaliable() || static_cast< CPlane* >( this )->GetVerticalFlightModeRatio() < 0.5f ); if(m_bCanRechargeRocketBoost == false) { m_bCanRechargeRocketBoost = ( GetNumContactWheels() == GetNumWheels() ) || ( jatoAvailable && InheritsFromPlane() ) || ( !HasGlider() && GetSpecialFlightModeRatio() > 0.5f ) || (InheritsFromSubmarineCar() && IsInSubmarineMode() && GetIsInWater()); } float maxRocketBoostCapacity = GetRocketBoostCapacity(); // Recharge boost when touched upon ground if(!m_bIsRocketBoosting && m_bCanRechargeRocketBoost ) { static dev_float sfOnGroundRechargeScale = 50.0f; static dev_float sfMaxInAirRechargeScale = 0.2f; static dev_float sfMaxInAirVelocityToIncreaseRechargeRate = 0.6f; float rechargeRate = m_fRocketBoostRechargeRate; if( InheritsFromPlane() ) { if( GetStatus() == STATUS_WRECKED ) { rechargeRate = 0.0f; } else if( !IsInAir() ) { rechargeRate *= sfOnGroundRechargeScale; } else { float maxVelocityToIncreaseRechargeRate = sfMaxInAirVelocityToIncreaseRechargeRate * pHandling->m_fBoostMaxSpeed; maxVelocityToIncreaseRechargeRate *= maxVelocityToIncreaseRechargeRate; float velocityMag2 = GetVelocity().Mag2(); rechargeRate *= sfMaxInAirRechargeScale * ( sfOnGroundRechargeScale * ( 1.0f - Min( 1.0f, velocityMag2 / maxVelocityToIncreaseRechargeRate ) ) ); rechargeRate += m_fRocketBoostRechargeRate; } } else if( !HasGlider() && GetSpecialFlightModeRatio() > 0.5f ) { static dev_float sfSpecialFlightModeRechargeScale = 0.5f; rechargeRate *= sfSpecialFlightModeRechargeScale * Max( 0.0f, ( 1.0f - GetThrottle() ) ); } m_fRocketBoostRemaining = rage::Min( m_fRocketBoostRemaining + fwTimer::GetTimeStep() * rechargeRate, maxRocketBoostCapacity ); } bool canToggleRocketBoost = CanToggleRocketBoost(); // If boost recharged and driver is in the vehicle, check for rocket boost button if( GetDriver() && !GetIsAttached() && !( GetDriver()->IsLocalPlayer() && CPhoneMgr::CamGetState() ) && canToggleRocketBoost) { bool bInputActivated = false; CControl* pControl = GetDriver()->GetControlFromPlayer(); if (pControl) { const ioValue& ioVal = (InheritsFromPlane() || InheritsFromHeli()) ? pControl->GetVehicleFlyBoost() : HasJump() || m_WheelsAreJustProbes ? pControl->GetVehicleTransform() : pControl->GetVehicleRocketBoost(); if ( ioVal.IsPressed() || (ioVal.HistoryPressed(100) && ioVal.IsReleased()) ) { bInputActivated = true; } } if( bInputActivated || m_bRocketBoostedFromNetwork || m_bRocketBoostedStoppedFromNetwork) { if( !InheritsFromPlane() || hasJato ) { if( jatoAvailable || m_bRocketBoostedFromNetwork || m_bRocketBoostedStoppedFromNetwork) { float maxRocketBoostCapacity = GetRocketBoostCapacity(); //static dev_float sf_MinRocketBoostCapacityForToggle = 0.25f; if(m_fRocketBoostRemaining == maxRocketBoostCapacity )/*|| ( !IsRocketBoosting() && m_fRocketBoostRemaining > sf_MinRocketBoostCapacityForToggle && pHandling->m_fRocketBoostCapacity > 2.0f ) )*/ { if (!m_bRocketBoostedFromNetwork && GetNetworkObject() && !GetNetworkObject()->IsClone()) { CActivateVehicleSpecialAbilityEvent::Trigger(*this, CActivateVehicleSpecialAbilityEvent::ROCKET_BOOST); } vehicleDebugf3("ROCKET BOOSTING %s m_bRocketBoostedFromNetwork is %d", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "Not Networked!", m_bRocketBoostedFromNetwork); SetRocketBoosting(true); m_bRocketBoostedFromNetwork = false; } else { if(!IsRocketBoosting()) { audVehicleKersSystem* vehicleEngineAudio = m_VehicleAudioEntity? m_VehicleAudioEntity->GetVehicleKersSystem() : NULL; if(vehicleEngineAudio) { vehicleEngineAudio->TriggerKersBoostActivateFail(); } } else if( pHandling->m_fRocketBoostCapacity > 2.0f ) { m_fRocketBoostRemaining = 0.0f; SetRocketBoosting( false ); } } } } else { vehicleDebugf3("ROCKET BOOSTING TOGGLE %s m_bRocketBoostedFromNetwork is %d m_bRocketBoostedStoppedFromNetwork is %d", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "Not Networked!", m_bRocketBoostedFromNetwork, m_bRocketBoostedStoppedFromNetwork); // This path is hit when we have a toggle booster (rocket plane for example) if (!IsNetworkClone()) { SetRocketBoosting(!IsRocketBoosting()); if(GetNetworkObject()) { if (!IsRocketBoosting()) { CActivateVehicleSpecialAbilityEvent::Trigger(*this, CActivateVehicleSpecialAbilityEvent::ROCKET_BOOST_STOP); } else if (IsRocketBoosting()) { CActivateVehicleSpecialAbilityEvent::Trigger(*this, CActivateVehicleSpecialAbilityEvent::ROCKET_BOOST); } } } else { if (m_bRocketBoostedStoppedFromNetwork) { SetRocketBoosting(false); } else if (m_bRocketBoostedFromNetwork) { SetRocketBoosting(true); } m_bRocketBoostedStoppedFromNetwork = false; m_bRocketBoostedFromNetwork = false; } } } } // If the driver is in the vehicle, update the special ability bar if(GetDriver() && GetDriver()->IsLocalPlayer() ) { CNewHud::SetAbilityOverride((m_fRocketBoostRemaining/maxRocketBoostCapacity)*100.0f, 100.0f); } audVehicleKersSystem* vehicleEngineAudio = m_VehicleAudioEntity? m_VehicleAudioEntity->GetVehicleKersSystem() : NULL; if(vehicleEngineAudio) { if(m_bIsRocketBoosting && !initialBoostState) { vehicleEngineAudio->TriggerKersBoostActivate(); } const f32 rechargeRate = ((m_fRocketBoostRemaining - initialBoostRemaining)/maxRocketBoostCapacity) * fwTimer::GetInvTimeStep(); vehicleEngineAudio->SetKersRechargeRate(rechargeRate); if(m_bIsRocketBoosting) { vehicleEngineAudio->SetKERSState(audVehicleKersSystem::AUD_KERS_STATE_BOOSTING); } else if(rechargeRate > 0.f) { vehicleEngineAudio->SetKERSState(audVehicleKersSystem::AUD_KERS_STATE_RECHARGING); } else { vehicleEngineAudio->SetKERSState(audVehicleKersSystem::AUD_KERS_STATE_OFF); } } } else if( HasRocketBoost() && !IsEngineOn() ) { audVehicleKersSystem* vehicleEngineAudio = m_VehicleAudioEntity? m_VehicleAudioEntity->GetVehicleKersSystem() : NULL; if(vehicleEngineAudio) { vehicleEngineAudio->SetKERSState(audVehicleKersSystem::AUD_KERS_STATE_OFF); } } // Parachute on duck button if( HasParachute() && InheritsFromAutomobile() && GetDriver() && GetNumContactWheels() == 0) { CControl* pControl = GetDriver()->GetControlFromPlayer(); if( pControl != NULL && pControl->GetVehicleParachute().IsPressed() ) { CAutomobile* pCar = static_cast(this); if(!pCar->IsParachuting() && pCar->CanDeployParachute()) { pCar->StartParachuting(); } else if( pCar->GetCanPlayerDetachParachute() ) { pCar->FinishParachuting(); } } } if( HasGlider() ) { ProcessGlider(); } ProcessSpecialFlightMode(); ProcessOutriggers(); ProcessArenaMode(); #if __DEV TUNE_GROUP_BOOL( VEHICLE_FORMATION_LEADER, renderDebug, false ); if( ms_formationLeader && renderDebug ) { const fwTransform& leaderTransform = ms_formationLeader->GetTransform(); Vector3 targetPosition = VEC3V_TO_VECTOR3( leaderTransform.Transform( VECTOR3_TO_VEC3V( ms_formationOffset ) ) ); CPhysics::ms_debugDrawStore.AddSphere( RCC_VEC3V( targetPosition ), ms_formationRadius, Color_LightSeaGreen, (u32)(fwTimer::GetTimeStepInMilliseconds() * 1.25f), 0, false ); } #endif // #if __BANK return true; } void CVehicle::UpdateDoorsForNavigation() { if( fwPathServer::GetExcludeVehicleDoorsFromNavigation() ) { for(s32 d=0; dGetPathServerDynamicObjectIndex()==DYNAMIC_OBJECT_INDEX_UNABLE_TO_ADD || pDoor->GetPathServerDynamicObjectIndex()==DYNAMIC_OBJECT_INDEX_NONE) { CPathServerGta::AddVehicleDoorDynamicObject(this, pDoor); } else if(pDoor->GetPathServerDynamicObjectIndex()GetIsIntact(this) || (pDoor->GetIsClosed() && !pDoor->GetForceOpenForNavigation())) { CPathServerGta::RemoveVehicleDoorObject(pDoor); pDoor->SetPathServerDynamicObjectIndex(DYNAMIC_OBJECT_INDEX_NONE); } else { CPathServerGta::UpdateVehicleDoorDynamicObject(this, pDoor, true); } } } } } #if __STATS EXT_PF_TIMER(VehicleInterpolationTask_Run); #endif // __STATS void CVehicle::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep) { Assertf(!(IsDummy() && GetDriver() && (GetDriver()->IsLocalPlayer())),"The players vehicle has become a dummy vehicle!"); #if __ASSERT if (!IsRetainedFlagSet() && !GetOwnerEntityContainer()) { m_notInWorldTimer += fwTimer::GetTimeStepInMilliseconds(); Assertf(GetIsScheduled() || m_notInWorldTimer < 10000 || !GetCurrentPhysicsInst()->IsInLevel(), "Vehicle (0x%p) has existed for %f seconds and isn't in the level.", this, (float)m_notInWorldTimer/1000.0f); } else { m_notInWorldTimer = 0; } #endif // __ASSERT ProcessWeapons(); // check if the vehicle has been seen, do this every frame to be sure if(!m_nVehicleFlags.bHasBeenSeen) { if(GetIsVisibleInSomeViewportThisFrame()) m_nVehicleFlags.bHasBeenSeen = true; } // check for HD resource requests from any vehicles which may support HD // this needs to be called every frame if (m_vehicleLodState != VLS_HD_NA){ Update_HD_Models(); } // Do a one-frame update of the visibility if requested if(m_nVehicleFlags.bRefreshVisibility) { fwDrawData *drawData = GetDrawHandlerPtr(); if(drawData) { unsigned int queryId = drawData->GetOcclusionQueryId(); if( queryId ) { int drawCount = OcclusionQueries::OQGetQueryResult(queryId); if( drawCount != -1 ) { m_nNumPixelsVisible = drawCount; } m_nVehicleFlags.bRefreshVisibility = false; } } } // Destroy any vehicles that enter an air defence zone. bool bIsHoverVehicle = pHandling->GetSpecialFlightHandlingData() != NULL; bool bIsSpecialFlyingVehicle = bIsHoverVehicle || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_PARACHUTE) || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_JUMPING_CAR) || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_ROCKET_BOOST) || GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_GLIDER); if (!IsNetworkClone() && (GetIsAircraft() || bIsSpecialFlyingVehicle) && GetDriver() && GetDriver()->GetPedResetFlag(CPED_RESET_FLAG_InAirDefenceSphere) && CAirDefenceManager::AreAirDefencesActive() && GetHealth() > 0.0f && !m_bAirDefenceExplosionTriggered) { u8 uDefenceZoneIndex = 0; Vec3V vPosition = GetTransform().GetPosition(); if (CAirDefenceManager::IsPositionInDefenceZone(vPosition, uDefenceZoneIndex)) { CAirDefenceZone *pDefZone = CAirDefenceManager::GetAirDefenceZone(uDefenceZoneIndex); // For hover vehicles like the Deluxo, we don't want to trigger air defence if we're low to the water bool bPassesHeightCheck = true; if (pDefZone && bIsHoverVehicle) { bPassesHeightCheck = vPosition.GetZf() > (pDefZone->GetWeaponPosition().GetZf() - 10.0f); } if (pDefZone && pDefZone->IsEntityTargetable(this) && pDefZone->ShouldFireWeapon() && bPassesHeightCheck) { // Only trigger explosion on local machine (explosion is synced). if (!IsNetworkClone()) { m_bAirDefenceExplosionTriggered = true; m_uTimeAirDefenceExplosionTriggered = fwTimer::GetTimeInMilliseconds(); } // Shoot at the impact position. Vec3V vFiredFromPosition(V_ZERO); CAirDefenceManager::FireWeaponAtPosition(uDefenceZoneIndex, vPosition, vFiredFromPosition); } } } else if (m_bAirDefenceExplosionTriggered && (fwTimer::GetTimeInMilliseconds() - m_uTimeAirDefenceExplosionTriggered) > CAirDefenceManager::GetExplosionDetonationTime()) { m_bAirDefenceExplosionTriggered = false; m_uTimeAirDefenceExplosionTriggered = 0; BlowUpCar(NULL); } #if !__PS3 TUNE_GROUP_BOOL(AI_DEFERRED_TASKS, USE_DEFERRED_INTERPOLATION, true); if(USE_DEFERRED_INTERPOLATION) { aiDeferredTasks::TaskData taskData(NULL, this, fFullUpdateTimeStep, fullUpdate); aiDeferredTasks::g_VehicleInterpolation.Queue(taskData); } else { #endif PF_START(VehicleInterpolationTask_Run); ProcessInterpolation(fullUpdate, fFullUpdateTimeStep); DoProcessControl_OnDeferredTaskCompletion(fullUpdate, fFullUpdateTimeStep); PF_STOP(VehicleInterpolationTask_Run); #if !__PS3 } #endif } void CVehicle::DoProcessControl_OnDeferredTaskCompletion(bool fullUpdate, float fFullUpdateTimeStep) { if(!fullUpdate) { return; } // Fire off an asynchronous probe to test if this physical is in a river and how deep it // is submerged for the buoyancy code. bool isAttachedToCargobob = GetAttachParentVehicle() && GetAttachParentVehicle()->GetIsHeli() && static_cast(GetAttachParentVehicle())->GetIsCargobob(); if(!m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodDummy) && !m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy) && (!GetIsAttached() || isAttachedToCargobob)) { Vector3 vPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); // We don't want the player falling through a river if the river entity bounding information isn't streamed in yet. if(ContainsPlayer() || (InheritsFromBoat() && IsAScriptEntity())) { m_Buoyancy.m_buoyancyFlags.bUseWidestRiverBoundTolerance = 1; } m_Buoyancy.SubmitRiverBoundProbe(vPos); } UpdateEngineTemperature(fFullUpdateTimeStep); // reset the cheat flags ClearCheatFlag(VEH_CHEAT_RESET_MASK); // Reset steer bias m_fSteerInputBias = 0.0f; // Check if we should update the last time we had a driver or not. // Check if we are using pretend occupants, if so, update as if we // currently has a real driver. if( (GetDriver()) || (m_nVehicleFlags.bUsingPretendOccupants )) { m_LastTimeWeHadADriver = NetworkInterface::GetSyncedTimeInMilliseconds(); m_nVehicleFlags.bShouldBeRemovedByAmbientPed = false; } if(m_nVehicleFlags.bDoorRatioHasChanged) { UpdateDoorsForNavigation(); m_nVehicleFlags.bDoorRatioHasChanged = false; } //shocking events checks //looking for mad drivers, wheel spins, high revs, fast acceleration, loud veh, fast speed // Work out whether we're driving on the pavement // can do this for any vehicle type now int nOnPavement = 0; int nNumContactWheels = 0; for (s32 i=0; iGetIsTouching()) { if(GetWheel(i)->GetIsTouchingPavement()) { nOnPavement++; } nNumContactWheels++; } } if(nNumContactWheels == 0) { m_fTimeInAir += fwTimer::GetTimeStep(); } else if(nNumContactWheels == GetNumWheels()) { m_bInAirCrash = false; m_fTimeInAir = 0.0f; m_fTimeInAirCrash = 0.0f; } if(m_bInAirCrash) { m_fTimeInAirCrash += fwTimer::GetTimeStep(); } bool bAddPavementEvent = false; if (nOnPavement > 1 REPLAY_ONLY(&& CReplayMgr::IsEditModeActive() == false)) { // Increment the timer. m_fTimeTouchingPavement += fwTimer::GetTimeStep(); // Check if the car has been driving on pavement for long enough. if (m_fTimeTouchingPavement >= ms_fTimeTouchingPavementBeforeAddingEvent) { // Make sure the vehicle is traveling fast enough. ScalarV vMag2 = MagSquared(VECTOR3_TO_VEC3V(GetAiVelocity())); ScalarV vThresh = GetVehicleType() == VEHICLE_TYPE_BICYCLE ? ScalarV(ms_fBicyclePavementVelocitySquaredThreshold) : ScalarV(ms_fVehiclePavementVelocitySquaredThreshold); if (IsGreaterThanAll(vMag2, vThresh)) { bAddPavementEvent = true; } else { m_fTimeTouchingPavement = 0.0f; } } } else { m_fTimeTouchingPavement = 0.0f; } // Add the shocking event as appropriate. if (bAddPavementEvent) { if (GetVehicleType() == VEHICLE_TYPE_BICYCLE) { CEventShockingBicycleOnPavement ev(*this); CShockingEventsManager::Add(ev); } else { CEventShockingDrivingOnPavement ev(*this); CShockingEventsManager::Add(ev); } } m_nVehicleFlags.bHasWheelOnPavement = (nOnPavement > 0); // handle rumble if(ContainsLocalPlayer()) { float velocity = GetVelocity().Mag(); bool isTouchingVibrationMat = ProcessRumble(GetWheels(), GetNumWheels(), velocity); CTrailer* trailer = GetAttachedTrailer(); if (!isTouchingVibrationMat && trailer) { isTouchingVibrationMat = ProcessRumble(trailer->GetWheels(), trailer->GetNumWheels(), velocity); } // the motors are too slow, if we stop it as soon as the wheel is off the material it's barely noticeable // for smaller rumbles (e.g. manholes) //if (!isTouchingVibrationMat && CControlMgr::IsPlayerPadShaking()) // CControlMgr::StopPlayerPadShaking(true); } // People get freaked out if the player is driving in an interior if( ContainsLocalPlayer() && GetIsInInterior() && CInteriorInst::GetInteriorForLocation( this->GetInteriorLocation() )->IsSubwayMLO() == false) { CEventShockingDrivingOnPavement ev(*this); CShockingEventsManager::Add(ev); } // ShouldBeRemovedByAmbientPed debug is handled in RenderDebug(). // If we are drowning in water then extinguish fires // This won't affect boats since their bIsDrowning flag is never set if(m_nVehicleFlags.bIsDrowning && m_Buoyancy.GetStatus()==FULLY_IN_WATER) { ExtinguishCarFire(); } if (m_nVehicleFlags.bRepairWhenSafe) { Fix(); m_nVehicleFlags.bRepairWhenSafe = false; } #if USE_PHYSICAL_HISTORY static float SPEED_TO_GO_RED = 20.0f; float fSpeedScale = MIN(GetVelocity().Mag()/SPEED_TO_GO_RED,1.0f); Color32 colour; if( IsDummy() ) colour = Color_blue; else colour = Lerp(fSpeedScale, Color_green, Color_red); m_physicalHistory.UpdateHistory(GetMatrix(), this, colour); #endif for( int i = 0; i < m_pVehicleGadgets.size(); i++) { m_pVehicleGadgets[i]->ProcessControl(this); } if(m_pVehicleWeaponMgr) { m_pVehicleWeaponMgr->ProcessControl(this); } PF_START(Physical); CPhysical::ProcessControl(); // just call full function because it doesn't do much anyway now PF_STOP(Physical); if(m_nVehicleFlags.bDriveMusclesToAnim) { // If all animations have finished then clear this flag if ( !GetAnimDirector() || GetMoveVehicle().IsMechanismFullyClosedAndNotOpening() ) { m_nVehicleFlags.bDriveMusclesToAnim = false; } //can't drive to pose when no HierInst exists else if ( !GetVehicleFragInst()->GetCacheEntry() || !GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->body ) { m_nVehicleFlags.bDriveMusclesToAnim = false; } } if(GetAnimDirector()) { GetAnimDirector()->SetUseCurrentSkeletonForActivePose(false);//This is done in PreProcessPhysics } // reset stuff for frame m_nVehicleFlags.bDummyWheelImpactsSampled = false; m_nVehicleFlags.bIsBeingTowed = GetIsBeingTowed(); m_nVehicleFlags.bCarCrushingBike = false; if(m_VehicleAudioEntity) { // It's unsafe to call IsInAir() on the audio thread - so cache that on the audio entity here m_VehicleAudioEntity->SetIsInAirCached(IsInAir()); // fwExtensionLists are not safe to access from the north audio thread which is where the IsBroken flag is set for the vehicle windows, so cache CacheSmashableWindows(); } } /////////////////////////////////////////////////////////////////////////// // FUNCTION CalculateRumbleFrequencies // DOES Calculates the rumble frequencies from the given rumble profile /////////////////////////////////////////////////////////////////////////// void CVehicle::CalculateRumbleFrequencies( s32& freq0, s32& freq1, phRumbleProfile* rumbleProfile, float rumbleMult, float velFreqScale, float velModifier, bool bIgnoreVelocityForRumble ) { float minFreq0 = rumbleProfile->m_frequencyMin_1; float minFreq1 = rumbleProfile->m_frequencyMin_2; float maxFreq0 = rumbleProfile->m_frequencyMax_1; float maxFreq1 = rumbleProfile->m_frequencyMax_2; if( bIgnoreVelocityForRumble ) { freq0 = (s32)( minFreq0 + rumbleMult * ( maxFreq0 - minFreq0 ) ); freq1 = (s32)( minFreq1 + rumbleMult * ( maxFreq1 - minFreq1 ) ); } else { if( velFreqScale > 0.001f ) { freq0 = (s32)( minFreq0 + velModifier * ( maxFreq0 - minFreq0 ) ); freq1 = (s32)( minFreq1 + velModifier * ( maxFreq1 - minFreq1 ) ); } else { freq0 = (s32)fwRandom::GetRandomNumberInRange( minFreq0, maxFreq0 ); freq1 = (s32)fwRandom::GetRandomNumberInRange( minFreq1, maxFreq1 ); } } freq0 = Clamp( freq0, 0, (s32)MAX_VIBRATION_FREQUENCY ); freq1 = Clamp( freq1, 0, (s32)MAX_VIBRATION_FREQUENCY ); } static dev_float sfMinCompressionChangeForRumble = 0.1f; static dev_float sfMinCompressionChangePercentageForRumble = 0.065f; // Vehicles with very short suspension, e.g. the bmx, would not trigger rumble using the above absolute value. static dev_float sfMinZVelocityForLandingRumble = -5.0f; /////////////////////////////////////////////////////////////////////////// // FUNCTION ProcessRumble // DOES Iterates through given list of wheels and applies rumble if at least one is touching a rumble material. // Returns true if at least one wheel is touching a rumble material /////////////////////////////////////////////////////////////////////////// #if HAS_TRIGGER_RUMBLE static dev_float sfTriggerRumbleCutOutTime = 1500.0f; // Amount of time the triggers will rumble continuously before stopping. In milliseconds. void CVehicle::ProcessTriggerRumble(const CWheel* const * wheels, s32 numWheels, float UNUSED_PARAM(velocity), phMaterialMgr::Id padRumbleMaterialId, u32 padNextRumbleTime ) { bool touching = false; phMaterialMgr::Id leftTriggerRumbleMaterialId = padRumbleMaterialId; phMaterialMgr::Id rightTriggerRumbleMaterialId = padRumbleMaterialId; u32 leftTriggerNextRumbleTime = 0; u32 rumbleDuration = 0; u32 leftFrequency = 0; u32 rightFrequency = 0; static dev_float sfSkidRumbleThreshold = 1.8f; static dev_float sfSkidRumbleThresholdFrontWheelDrive = 2.17f; static dev_float sfSkidRumbleFrictionDamageThreshold = 0.2f; static dev_float sfForwardSlipMax = 500.0f; static dev_float sfForwardBrakingSlipMax = 30.0f; u32 timeInMilliseconds = fwTimer::GetTimeInMilliseconds(); //Reduce the rumble from skidding on front wheel drive cars float fSkidThreshold = sfSkidRumbleThreshold; if( pHandling && pHandling->GetDriveBias( 1.0f ) >= 1.0f && pHandling->GetDriveBias( -1.0f ) <= 0.0f ) { fSkidThreshold = sfSkidRumbleThresholdFrontWheelDrive; } #if RSG_DEV float fPeakSlip = 0.0f; float fPeakRumbleMult = 0.0f; #endif // now check to see if the left trigger should rumble for (s32 i = 0; i < numWheels; ++i) { if( wheels[i]->GetIsTouching() ) { leftTriggerRumbleMaterialId = wheels[i]->GetMaterialId(); phRumbleProfile* rumbleProfile = NULL; float fRumbleMult = 1.0f; // check to see if this is a front wheel and we're not in water if( !GetIsInWater() && ( !wheels[i]->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) || rage::Abs( GetThrottle() ) > 0.01 ) ) { float forwardSlipMax = sfForwardSlipMax; if( rage::Abs( wheels[ i ]->GetBrakeForce() ) > 0.0f ) { forwardSlipMax = sfForwardBrakingSlipMax; fSkidThreshold *= 2.0f; } if( rage::Abs( wheels[i]->GetFwdSlipAngle() ) > fSkidThreshold ) { // if we're moving forwards and braking or if we're in reverse and the wheel is slipping we can apply rumble if( wheels[i]->GetBrakeForce() > 0.0f || ( rage::Abs(wheels[i]->GetDriveForce()) > 0.0f && GetThrottle() < -0.01 ) ) { leftTriggerRumbleMaterialId = PGTAMATERIALMGR->g_idRubber; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( leftTriggerRumbleMaterialId ); fRumbleMult = rage::Clamp( ( rage::Abs(wheels[i]->GetFwdSlipAngle() ) - fSkidThreshold ), 0.0f, sfForwardSlipMax ) / sfForwardSlipMax; } } #if RSG_DEV fPeakSlip = rage::Max(fPeakSlip, rage::Abs(wheels[i]->GetFwdSlipAngle()) ); fPeakRumbleMult = rage::Max(fPeakRumbleMult, fRumbleMult ); #endif } touching = rumbleProfile != NULL; if( rumbleProfile && ( rumbleProfile->m_nextTriggerTime < timeInMilliseconds || ( padRumbleMaterialId == leftTriggerRumbleMaterialId && padNextRumbleTime <= timeInMilliseconds ) ) ) { leftTriggerNextRumbleTime = timeInMilliseconds; if( fwRandom::GetRandomNumberInRange(0.f, 1.f) < rumbleProfile->m_triggerProbability ) { s32 freq0 = 0; s32 freq1 = 0; u32 duration = (u32)fwRandom::GetRandomNumberInRange( rumbleProfile->m_durationMin, rumbleProfile->m_durationMax ); CalculateRumbleFrequencies( freq0, freq1, rumbleProfile, fRumbleMult, 1.0f, 1.0f, true ); rumbleDuration = duration; leftFrequency = Max( freq0, freq1 ); } if( padRumbleMaterialId != leftTriggerRumbleMaterialId ) { const u32 modifiedInterval = rumbleProfile->m_triggerInterval - (u32)(rumbleProfile->m_triggerInterval); rumbleProfile->m_nextTriggerTime = timeInMilliseconds + modifiedInterval; } break; } } } #if RSG_DEV PF_SET(LeftRumbleIntensity, fPeakRumbleMult); PF_SET(LeftPeakSlip, fPeakSlip); fPeakSlip = 0.0f; fPeakRumbleMult = 0.0f; #endif // now check to see if the right trigger should rumble for (s32 i = 0; i < numWheels; ++i) { if( wheels[i]->GetIsTouching() ) { rightTriggerRumbleMaterialId = wheels[i]->GetMaterialId(); phRumbleProfile* rumbleProfile = NULL; float fRumbleMult = 1.0f; float fSkidThreshold = sfSkidRumbleThreshold; // check to see if this is a front wheel and we're not in water if( !GetIsInWater() && ( !wheels[i]->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ) || rage::Abs( GetThrottle() ) > 0.01 ) ) { float forwardSlipMax = sfForwardSlipMax; if( rage::Abs( wheels[ i ]->GetBrakeForce() ) > 0.0f ) { forwardSlipMax = sfForwardBrakingSlipMax; fSkidThreshold *= 2.0f; } if( rage::Abs( wheels[i]->GetFwdSlipAngle() ) > fSkidThreshold ) { // if we're moving forwards and braking or if we're in reverse and the wheel is slipping we can apply rumble if( wheels[i]->GetBrakeForce() < 0.0f || GetThrottle() > 0.01 ) { rightTriggerRumbleMaterialId = PGTAMATERIALMGR->g_idRubber; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( rightTriggerRumbleMaterialId ); fRumbleMult = rage::Clamp( ( rage::Abs(wheels[i]->GetFwdSlipAngle() ) - fSkidThreshold ), 0.0f, sfForwardSlipMax) / sfForwardSlipMax; } } #if RSG_DEV fPeakSlip = rage::Max(fPeakSlip, rage::Abs(wheels[i]->GetFwdSlipAngle()) ); fPeakRumbleMult = rage::Max(fPeakRumbleMult, fRumbleMult ); #endif } touching = rumbleProfile != NULL; if( rumbleProfile && ( rumbleProfile->m_nextTriggerTime < timeInMilliseconds || ( ( padRumbleMaterialId == rightTriggerRumbleMaterialId && padNextRumbleTime <= timeInMilliseconds ) || ( rightTriggerRumbleMaterialId == leftTriggerRumbleMaterialId && leftTriggerNextRumbleTime <= timeInMilliseconds ) ) ) ) { if( fwRandom::GetRandomNumberInRange(0.f, 1.f) < rumbleProfile->m_triggerProbability ) { s32 freq0 = 0; s32 freq1 = 0; u32 duration = (u32)fwRandom::GetRandomNumberInRange( rumbleProfile->m_durationMin, rumbleProfile->m_durationMax ); CalculateRumbleFrequencies( freq0, freq1, rumbleProfile, fRumbleMult, 1.0f, 1.0f, true ); rumbleDuration = Max( duration, rumbleDuration ); rightFrequency = Max( freq0, freq1 ); } if( padRumbleMaterialId != rightTriggerRumbleMaterialId && leftTriggerRumbleMaterialId != rightTriggerRumbleMaterialId ) { const u32 modifiedInterval = rumbleProfile->m_triggerInterval - (u32)(rumbleProfile->m_triggerInterval); rumbleProfile->m_nextTriggerTime = timeInMilliseconds + modifiedInterval; } break; } } } leftFrequency *= 0.5f; rightFrequency *= 0.5f; #if RSG_DEV PF_SET(RightRumbleIntensity, fPeakRumbleMult); PF_SET(RightPeakSlip, fPeakSlip); #endif // B*1833759: Cut out the trigger rumble after a short amount of time so as to not have them continuously rumbling when you have a stuck wheel or something. // This way it no longer feels like you have thousands of tiny people with tiny hammers banging on your fingertips as you drive around. if(leftFrequency > 0 || rightFrequency > 0) m_triggerRumbleTime += fwTimer::GetTimeStep() * 1000.0f; else m_triggerRumbleTime = 0; if(m_triggerRumbleTime > sfTriggerRumbleCutOutTime) { leftFrequency = 0.0f; rightFrequency = 0.0f; } if(leftFrequency > 0 || rightFrequency > 0 || rumbleDuration > 0) CControlMgr::StartPlayerPadTriggerShake( rumbleDuration, leftFrequency, rumbleDuration, rightFrequency, s_vibrateDelay );//, s32 suppressedId } #endif // if HAS_TRIGGER_RUMBLE bool CVehicle::ProcessRumble(const CWheel* const * wheels, s32 numWheels, float velocity) { bool touching = false; float velocityZ = GetVelocity().GetZ(); phMaterialMgr::Id mtlId = 0; phRumbleProfile* rumbleProfile = NULL; #if HAS_TRIGGER_RUMBLE u32 nextTriggerTime = (u32)-1; #endif // #if HAS_TRIGGER_RUMBLE if( !GetAreOutriggersFullyDeployed() && GetModelIndex() != MI_BIKE_OPPRESSOR2.GetModelIndex() ) { for (s32 i = 0; i < numWheels; ++i) { if (wheels[i]->GetIsTouching()) { mtlId = wheels[i]->GetMaterialId(); rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile(mtlId); float compressionChange = wheels[i]->GetCompressionChange(); if( !rumbleProfile && ( wheels[i]->GetTyreHealth() == 0.0f || wheels[i]->GetSideImpact() || compressionChange > sfMinCompressionChangeForRumble ) ) { //Wheel either has burst to a rim, has a side impact or has a big compression change //so use the WHEEL_RIM rumble profile mtlId = PGTAMATERIALMGR->g_idCarMetal; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( mtlId ); } bool bIgnoreVelocityForRumble = false; // if we've not already got a rumble profile and the vehicle is a bicycle // check to see if the percentage of length change is big enough to trigger rumble // this is done to make bunny hops trigger rumble if( !rumbleProfile && GetVehicleType() == VEHICLE_TYPE_BICYCLE && compressionChange > sfMinCompressionChangePercentageForRumble * wheels[i]->GetSuspensionLength() ) { mtlId = PGTAMATERIALMGR->g_idCarMetal; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( mtlId ); // ignore velocity as bunny hops can be done while stationary bIgnoreVelocityForRumble = true; } float fRumbleMult = 1.0f; #if HAS_TRIGGER_RUMBLE static dev_float sfSkidRumbleThreshold = 3.5f; static dev_float sfSkidRumbleThresholdFrontWheelDrive = 5.17f; static dev_float sfSkidRumbleFrictionDamageThreshold = 0.2f; #else static dev_float sfSkidRumbleThreshold = 2.0f; static dev_float sfSkidRumbleThresholdFrontWheelDrive = 3.17f; static dev_float sfSkidRumbleFrictionDamageThreshold = 0.2f; #endif // #if HAS_TRIGGER_RUMBLE float fSkidThreshold = sfSkidRumbleThreshold; //Reduce the rumble from skidding on front wheel drive cars if(pHandling && pHandling->GetDriveBias(1.0f) >= 1.0f && pHandling->GetDriveBias(-1.0f) <= 0.0f) { fSkidThreshold = sfSkidRumbleThresholdFrontWheelDrive; } if(!GetIsInWater() && rage::Abs(wheels[i]->GetEffectiveSlipAngle())>fSkidThreshold && wheels[i]->GetFrictionDamage() < sfSkidRumbleFrictionDamageThreshold) { mtlId = PGTAMATERIALMGR->g_idRubber; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( mtlId ); #if HAS_TRIGGER_RUMBLE static dev_float sfSlipMax = 25.0f; #else static dev_float sfSlipMax = 10.0f; #endif // #if HAS_TRIGGER_RUMBLE if(rage::Abs(wheels[i]->GetEffectiveSlipAngle()) > fSkidThreshold && ( velocity > rumbleProfile->m_minVelocity || (wheels[i]->GetBrakeForce() > 0.0f || rage::Abs(wheels[i]->GetDriveForce()) > 0.0f)) ) { fRumbleMult = rage::Clamp((rage::Abs(wheels[i]->GetEffectiveSlipAngle())-fSkidThreshold), 0.0f, sfSlipMax) / sfSlipMax; fRumbleMult = rage::Clamp(fRumbleMult, 0.0f, 1.0f); bIgnoreVelocityForRumble = true; } } // apply rumble if the vehicle has landed after a jump if( !rumbleProfile && velocityZ < sfMinZVelocityForLandingRumble && compressionChange > sfMinCompressionChangePercentageForRumble * wheels[i]->GetSuspensionLength() ) { mtlId = PGTAMATERIALMGR->g_idCarMetal; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( mtlId ); } if(!rumbleProfile && GetVehicleAudioEntity() && GetVehicleAudioEntity()->CanCauseControllerRumble() && GetVehicleAudioEntity()->GetControllerRumbleIntensity() > 0.0f) { //Big bad V8 engine rumble fRumbleMult = GetVehicleAudioEntity()->GetControllerRumbleIntensity(); mtlId = PGTAMATERIALMGR->g_idCarEngine; rumbleProfile = PGTAMATERIALMGR->GetMtlRumbleProfile( mtlId ); } touching = rumbleProfile != NULL; if (rumbleProfile && rumbleProfile->m_nextTriggerTime < fwTimer::GetTimeInMilliseconds()) { if (bIgnoreVelocityForRumble || velocity >= rumbleProfile->m_minVelocity) { // if we get a valid range from min and max velocity, calculate a modifier for the frequency and clamp it to 1.f float velModifier = 1.f; const float velFreqScale = rumbleProfile->m_maxVelocity - rumbleProfile->m_minVelocity; if (!bIgnoreVelocityForRumble && velFreqScale > 0.001f) { velModifier = (velocity - rumbleProfile->m_minVelocity) / velFreqScale; velModifier = rage::Max(velModifier, 0.f); velModifier *= velModifier; velModifier *= fRumbleMult; } if (fwRandom::GetRandomNumberInRange(0.f, 1.f) < rumbleProfile->m_triggerProbability) { s32 freq0 = 0; s32 freq1 = 0; u32 duration = (u32)fwRandom::GetRandomNumberInRange(rumbleProfile->m_durationMin, rumbleProfile->m_durationMax); #if __PS3 // we don't use motor 2 for vehicle rumble on ps3, it's way too hectic while it's the only one that can show a gradual // rumble curve starting at 0 on 360. this is why we swap the values so the meta data cans tay the same for both platforms. float minFreq0 = rage::Max(80.f, (float)rumbleProfile->m_frequencyMin_2); float minFreq1 = 0; //rumbleProfile->m_frequencyMin_1; float maxFreq0 = rage::Max(minFreq0, (float)rumbleProfile->m_frequencyMax_2); float maxFreq1 = 0; //rumbleProfile->m_frequencyMax_1; #else float minFreq0 = rumbleProfile->m_frequencyMin_1; float minFreq1 = rumbleProfile->m_frequencyMin_2; float maxFreq0 = rumbleProfile->m_frequencyMax_1; float maxFreq1 = rumbleProfile->m_frequencyMax_2; #endif if (bIgnoreVelocityForRumble) { freq0 = (s32)(minFreq0 + fRumbleMult * (maxFreq0 - minFreq0)); freq1 = (s32)(minFreq1 + fRumbleMult * (maxFreq1 - minFreq1)); } else { if (velFreqScale > 0.001f) { freq0 = (s32)(minFreq0 + velModifier * (maxFreq0 - minFreq0)); freq1 = (s32)(minFreq1 + velModifier * (maxFreq1 - minFreq1)); } else { freq0 = (s32)fwRandom::GetRandomNumberInRange(minFreq0, maxFreq0); freq1 = (s32)fwRandom::GetRandomNumberInRange(minFreq1, maxFreq1); } } freq0 = Clamp(freq0, 0, (s32)MAX_VIBRATION_FREQUENCY); freq1 = Clamp(freq1, 0, (s32)MAX_VIBRATION_FREQUENCY); CControlMgr::StartPlayerPadShake(duration, freq0, duration, freq1, s_vibrateDelay); } // let velocity scale the interval by a third const float intervalModifier = rage::Min(1.f, rage::Max(0.33f, (1.f - velModifier))); const u32 modifiedInterval = rumbleProfile->m_triggerInterval - (u32)(rumbleProfile->m_triggerInterval * intervalModifier); #if HAS_TRIGGER_RUMBLE nextTriggerTime = fwTimer::GetTimeInMilliseconds(); #endif // #if HAS_TRIGGER_RUMBLE rumbleProfile->m_nextTriggerTime = fwTimer::GetTimeInMilliseconds() + modifiedInterval; break; } } } } } // B*1871296: Do a small controller rumble when the vehicle has just changed gear. Only do this for cars and bikes. if(!InheritsFromBoat() && !InheritsFromPlane() && !InheritsFromHeli() && !InheritsFromPlane() && !InheritsFromSubmarine() && GetDriver() && GetDriver()->IsLocalPlayer()) { if(m_Transmission.DidChangeGear()) { CControlMgr::StartPlayerPadShakeByIntensity(35, 0.2f); } if(GetVehicleType() == VEHICLE_TYPE_BIKE && GetDriver()->IsInFirstPersonVehicleCamera() && m_Transmission.GetNumGears() > 1 ) { //Motorbike rumble static dev_float sfTransmissionRatioStartRumble = 0.9f; float fRevRatio = m_Transmission.GetRevRatio(); if(fRevRatio > sfTransmissionRatioStartRumble) { static dev_float sfRumblePOW = 3.0f; static dev_float sfRumbleMult = 0.1f; float fNormalisedRumbleRevRatio = (fRevRatio - sfTransmissionRatioStartRumble) / (1.0f-sfTransmissionRatioStartRumble); float fRumbleMult = rage::Powf(fNormalisedRumbleRevRatio, sfRumblePOW) * sfRumbleMult; CControlMgr::StartPlayerPadShakeByIntensity(10, fRumbleMult); } } } #if HAS_TRIGGER_RUMBLE ProcessTriggerRumble( wheels, numWheels, velocity, mtlId, nextTriggerTime ); #endif // #if HAS_TRIGGER_RUMBLE #if __WIN32PC ProcessForceFeedback(); #endif // __WIN32PC return touching; } // dev_float sfCarRespotAlphaMin = 20.0f; // dev_float sfCarRespotAlphaMax = 220.0f; // dev_u32 snCarRespotFadeTimeScale = 512; // void CVehicle::ProcessRespotCounter(float fTimeStep) { //Note: SP will early out here right away. if (!NetworkInterface::IsGameInProgress()) { if (m_nNetworkRespotCounter > 0) ResetRespotCounter(); return; } // if network has re-spotted this veh and we don't want it to collide with other network vehicles for a while bool bResetRespotCounter = false; if(GetNetworkObject() && !GetNetworkObject()->IsClone() && (m_nNetworkRespotCounter > 0) && !GetDriver()) { bResetRespotCounter = true; } else if (m_nNetworkRespotCounter > 0) { // set no collision with other network objects SetNoCollision(this, NO_COLLISION_NETWORK_OBJECTS|NO_COLLISION_RESET_WHEN_NO_BBOX); m_nPhysicalFlags.bNotDamagedByAnything = true; if((!IsNetworkClone() && !NetworkInterface::IsInSpectatorMode() && ContainsLocalPlayer() && m_bFlashLocallyDuringRespotting) || (IsNetworkClone() && !NetworkInterface::IsInSpectatorMode() && !ContainsLocalPlayer() && m_bFlashRemotelyDuringRespotting)) { CNetObjPhysical* pNetObjPhysical = static_cast(GetNetworkObject()); if (pNetObjPhysical) { pNetObjPhysical->SetAlphaRampingContinuous(false); } } u16 nTimeStepInMilliseconds = (u16)(fTimeStep * 1000.0f); // clones take their timer value from the network updates if(IsNetworkClone()) { bResetRespotCounter = m_nNetworkRespotCounter <= 0; } else { if(m_nNetworkRespotCounter > nTimeStepInMilliseconds) { m_nNetworkRespotCounter = m_nNetworkRespotCounter - nTimeStepInMilliseconds; if (m_nNetworkRespotCounter <= 0) bResetRespotCounter = true; } else { bResetRespotCounter = true; } } //override if the vehicle is in collision with another players network object - keep in respot until collision isn't happening... if (bResetRespotCounter) { if (m_bRespottingCollisionCarPed) { vehicleDebugf3("CVehicle::ProcessRespotCounter -- m_bRespottingCollisionCarPed"); u32 uCurrentTimeMs = fwTimer::GetSystemTimeInMilliseconds(); if (m_uSecondaryRespotOverrideTimeout == 0) { vehicleDebugf3("CVehicle::ProcessRespotCounter -- m_bRespottingCollisionCarPed -- m_uSecondaryRespotOverrideTimeout == 0 -- set m_uSecondaryRespotOverrideTimeout"); static const u32 deltaOverrideTime = 30000; //30 seconds m_uSecondaryRespotOverrideTimeout = uCurrentTimeMs + deltaOverrideTime; } if (m_uSecondaryRespotOverrideTimeout >= uCurrentTimeMs) { vehicleDebugf3("CVehicle::ProcessRespotCounter -- m_bRespottingCollisionCarPed -- (m_uSecondaryRespotOverrideTimeout[%u] >= uCurrentTimeMs[%u]) --> set bResetRespotCounter = false",m_uSecondaryRespotOverrideTimeout,uCurrentTimeMs); bResetRespotCounter = false; } } } } if (bResetRespotCounter) { ResetRespotCounter(); } } #if __BANK void CVehicle::SetLastVehPopRemovalFailReason(const char *reason) const { m_pLastVehPopRemovalFailReason = reason; m_iLastVehPopRemovalFailFrame = fwTimer::GetFrameCount(); } bool CVehicle::GetCoordsAndAlphaForDebugText(Vector3 &vecReturnWorldCoors, u8 &ReturnAlpha) const { const Vector3 vecEntityPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); Vector3 vDiff = vecEntityPosition - camInterface::GetPos(); float fDist = vDiff.Mag(); if(fDist >= VEHICLE_VISUALISE_RANGE) return false; vecReturnWorldCoors = vecEntityPosition + Vector3(0,0,1.0f); float fScale = 1.0f - (fDist / VEHICLE_VISUALISE_RANGE); ReturnAlpha = (u8)Clamp((int)(255.0f * fScale), 0, 255); return true; } void CVehicle::RenderDebug(void) const { ms_debugDraw.Render(); if (CVehicleDebug::ms_bRenderInitialSpawnPoint) { CVehicle* pFocus = CVehicleDebug::GetFocusVehicle(); if (pFocus) { Vec3V spawnPos = pFocus->m_vecInitialSpawnPosition; Vec3V currPos = pFocus->GetTransform().GetPosition(); grcDebugDraw::Sphere(spawnPos, 0.25f, Color_red); grcDebugDraw::Line(currPos, spawnPos, Color_aquamarine3); } } RenderDebugBoneInfo(); // Render vehicle AI if(m_pIntelligence) { m_pIntelligence->RenderDebug(); } // ShouldBeRemovedByAmbientPed debug. if (CVehicleIntelligence::m_bDisplayCarAiDebugInfo) { if (ShouldBeRemovedByAmbientPed()) { grcDebugDraw::Text(VEC3V_TO_VECTOR3(GetTransform().GetPosition())+Vector3(0.f,0.f,3.0f), CRGBA(255, 255, 255, 255), "ShouldBeRemovedByAmbientPed==true"); } } if(CAnchorHelper::ms_bDebugModeEnabled) { if(CAnchorHelper::IsVehicleAnchorable(this)) { const CAnchorHelper* pAnchorHelper = CAnchorHelper::GetAnchorHelperPtr(this); pAnchorHelper->RenderDebug(); } } Vector3 WorldCoors; u8 iAlpha = 255; // no sense in rendering debug info if outside of the visualization distance if( !GetCoordsAndAlphaForDebugText( WorldCoors, iAlpha ) ) return; int iNoOfTexts=0; char debugText[256]; Color32 color = CRGBA(255, 255, 255, iAlpha); if (CVehicleFactory::ms_bDisplaySteerAngle) { CEntity* pSelected = (CEntity*)g_PickerManager.GetSelectedEntity(); if (pSelected && pSelected->GetIsTypeVehicle()) { Color32 textColor = Color_white; bool textBackgroundQuad = false; static float scaleX = 2.0f; static float scaleY = 2.0f; static float xPosCol0 = 0.2f; //static float xPosCol1 = 0.7f; static float yPos = 0.05f; static float fTextVerticalOffset = 0.012f * 1.8f; Vector2 textPos(xPosCol0, yPos); CVehicle* pVehicle = (CVehicle*)pSelected; for (int i = 0; i < pVehicle->GetNumWheels(); i++) { if (const CWheel* pWheel = pVehicle->GetWheel(i)) { int wheelBoneIndex = pVehicle->GetBoneIndex(pWheel->GetHierarchyId()); if (wheelBoneIndex != -1) { // if (crSkeleton* pSkeleton = pVehicle->GetSkeleton()) { /* // NOTE: 3d text doesn't scale, screenspace text looks better. keeping this for reference Mat34V boneMtx; pSkeleton->GetGlobalMtx(wheelBoneIndex, boneMtx); */ char buff[64]; formatf(buff, "Steer angle wheel #%d: %f ", i, pWheel->GetSteeringAngle()); grcDebugDraw::Text(textPos, textColor, buff, textBackgroundQuad, scaleX, scaleY); textPos.y += fTextVerticalOffset; } } } } } } // Vehicles names debug if( CVehicleFactory::ms_bDisplayVehicleNames ) { sprintf(debugText, "Name:%s ", GetVehicleModelInfo()->GetModelName()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } if( CVehicleFactory::ms_bDisplayTyreWearRate ) { for( int i = 0; i < GetNumWheels(); i++ ) { if( const CWheel* pWheel = GetWheel( i ) ) { int wheelBoneIndex = GetBoneIndex( pWheel->GetHierarchyId() ); sprintf( debugText, "Name:%s Wear Rate: %.2f Wear Rate Scale: %.2f Max Grip Difference Due TO Wear Rate: %.2f Tyre Health: %.2f", GetSkeleton()->GetSkeletonData().GetBoneData( wheelBoneIndex )->GetName(), pWheel->GetTyreWearRate(), pWheel->GetWearRateScale(), pWheel->GetMaxGripDiffFromWearRate(), pWheel->GetTyreHealth() ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText ); iNoOfTexts++; } } } if( CVehicleFactory::ms_bDisplayVehicleLayout ) { const CVehicleModelInfo *pVehicleModelInfo = GetVehicleModelInfo(); if(pVehicleModelInfo) { const CVehicleLayoutInfo *pVehicleLayoutInfo = pVehicleModelInfo->GetVehicleLayoutInfo(); const CModelSeatInfo *pModelSeatInfo = pVehicleModelInfo->GetModelSeatInfo(); if(pVehicleLayoutInfo && pModelSeatInfo) { sprintf(debugText, "Model: %s ", pVehicleModelInfo->GetModelName()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; const char *szVehicleType; switch(GetVehicleType()) { case VEHICLE_TYPE_CAR : szVehicleType = "VEHICLE_TYPE_CAR"; break; case VEHICLE_TYPE_PLANE : szVehicleType = "VEHICLE_TYPE_PLANE"; break; case VEHICLE_TYPE_TRAILER : szVehicleType = "VEHICLE_TYPE_TRAILER"; break; case VEHICLE_TYPE_QUADBIKE : szVehicleType = "VEHICLE_TYPE_QUADBIKE"; break; case VEHICLE_TYPE_DRAFT : szVehicleType = "VEHICLE_TYPE_DRAFT"; break; case VEHICLE_TYPE_SUBMARINECAR : szVehicleType = "VEHICLE_TYPE_SUBMARINECAR"; break; case VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE : szVehicleType = "VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE"; break; case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE : szVehicleType = "VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE"; break; case VEHICLE_TYPE_HELI : szVehicleType = "VEHICLE_TYPE_HELI"; break; case VEHICLE_TYPE_BLIMP : szVehicleType = "VEHICLE_TYPE_BLIMP"; break; case VEHICLE_TYPE_AUTOGYRO : szVehicleType = "VEHICLE_TYPE_AUTOGYRO"; break; case VEHICLE_TYPE_BIKE : szVehicleType = "VEHICLE_TYPE_BIKE"; break; case VEHICLE_TYPE_BICYCLE : szVehicleType = "VEHICLE_TYPE_BICYCLE"; break; case VEHICLE_TYPE_BOAT : szVehicleType = "VEHICLE_TYPE_BOAT"; break; case VEHICLE_TYPE_TRAIN : szVehicleType = "VEHICLE_TYPE_TRAIN"; break; case VEHICLE_TYPE_SUBMARINE : szVehicleType = "VEHICLE_TYPE_SUBMARINE"; break; default: { Assert(false); szVehicleType = "UNKNOWN"; } break; } sprintf(debugText, "VehicleType: %s ", szVehicleType); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "CarHasRoof: %s ", CarHasRoof() ? "TRUE" : "FALSE"); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Layout: %s ", pVehicleLayoutInfo->GetName().TryGetCStr()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "MinSeatHeight: %.3f ", pVehicleModelInfo->GetMinSeatHeight()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; for(int i = 0; i < pModelSeatInfo->GetNumSeats(); i ++) { bool bHasHit = false; float fHeight = 10.0f; Vec3V vSeat(V_ZERO); s32 seatBoneIdx = pModelSeatInfo->GetBoneIndexFromSeat(i); if (seatBoneIdx >= 0) { Matrix34 mSeatTemp; GetGlobalMtx(seatBoneIdx, mSeatTemp); Mat34V mSeat = RC_MAT34V(mSeatTemp); vSeat = Transform(mSeat, vSeat); Vector3 startPosition = RC_VECTOR3(vSeat); startPosition.z += 0.6f; Vector3 endPosition = RC_VECTOR3(vSeat); endPosition.z += 9.4f; WorldProbe::CShapeTestProbeDesc probeTest; WorldProbe::CShapeTestFixedResults<1> probeTestResults; probeTest.SetResultsStructure(&probeTestResults); probeTest.SetIncludeFlags(ArchetypeFlags::GTA_VEHICLE_TYPE); probeTest.SetIsDirected(false); probeTest.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS); probeTest.SetIncludeEntity(this, WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS); probeTest.SetStartAndEnd(startPosition, endPosition); bHasHit = WorldProbe::GetShapeTestManager()->SubmitTest(probeTest); if(bHasHit) { if(probeTestResults.GetNumHits() > 0) { WorldProbe::CShapeTestHitPoint &hitPoint = probeTestResults[0]; fHeight = (hitPoint.GetHitPosition().z - startPosition.z) + 0.6f; } } } else { fHeight = -1.0f; } const CVehicleSeatInfo *pSeatInfo = pVehicleLayoutInfo->GetSeatInfo(i); if(pSeatInfo) { sprintf(debugText, "Seat(%i): %-28s HairScale: %.3f Height:%.3f", i, pSeatInfo->GetName().TryGetCStr(), pSeatInfo->GetHairScale(), fHeight); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } CPed *pPedInSeat = GetPedInSeat(i); if(pPedInSeat) { float fSeatToHead = 0.59f; Vec3V vHead = rage::Add(vSeat, Scale(Vec3V(V_UP_AXIS_WZERO), ScalarV(fSeatToHead))); grcDebugDraw::Cross(vSeat, 0.1f, Color_green); grcDebugDraw::Arrow(vSeat, vHead, 0.1f, Color_green); float fHairHeight = pPedInSeat->GetHairHeight(); Vec3V vHair = rage::Add(vHead, Scale(Vec3V(V_UP_AXIS_WZERO), ScalarV(fHairHeight))); grcDebugDraw::Arrow(vHead, vHair, 0.1f, Color_yellow); float fClearance = CVehicle::sm_HairScaleDisableThreshold; Vec3V vClearance = rage::Add(vHair, Scale(Vec3V(V_UP_AXIS_WZERO), ScalarV(fClearance))); grcDebugDraw::Arrow(vHair, vClearance, 0.1f, Color_red); float fTotal = fSeatToHead + fHairHeight + fClearance; float fMinSeatHeight = pVehicleModelInfo->GetMinSeatHeight(); if(fTotal < fMinSeatHeight) { Vec3V vMinSeatHeight = rage::Add(vClearance, Scale(Vec3V(V_UP_AXIS_WZERO), ScalarV(fMinSeatHeight - fTotal))); grcDebugDraw::Arrow(vClearance, vMinSeatHeight, 0.1f, Color_green); } } else { Vec3V vRoof = rage::Add(vSeat, Scale(Vec3V(V_UP_AXIS_WZERO), ScalarV(fHeight))); grcDebugDraw::Cross(vSeat, 0.1f, bHasHit ? Color_green : Color_red); grcDebugDraw::Arrow(vSeat, vRoof, 0.1f, bHasHit ? Color_green : Color_red); } } } } } if( CVehicleExplosionInfo::ms_DrawVehicleExplosionInfoNames ) { if(GetVehicleModelInfo()->GetVehicleExplosionInfo()) { sprintf(debugText, "Explosion Info: %s ", GetVehicleModelInfo()->GetVehicleExplosionInfo()->GetName().GetCStr()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } } if( CVehicleExplosionInfo::ms_DrawVehicleExplosionInfoLodSpheres ) { if(const CVehicleExplosionInfo* vehicleExplosionInfo = GetVehicleModelInfo()->GetVehicleExplosionInfo()) { // Render the radius of all but the first lod (which should have 0 radius) Color32 lodColors[] = {Color_red, Color_green, Color_blue}; Assert(sizeof(lodColors)/sizeof(lodColors[0]) >= vehicleExplosionInfo->GetNumVehicleExplosionLODs() - 1); for(int lodIndex = 1; lodIndex < vehicleExplosionInfo->GetNumVehicleExplosionLODs(); ++lodIndex) { grcDebugDraw::Sphere(GetVehiclePosition(),vehicleExplosionInfo->GetVehicleExplosionLOD(lodIndex).GetRadius(),lodColors[lodIndex-1],false); } } } // Vehicle creation location if( CVehicleFactory::ms_bDisplayCreateLocation ) { grcDebugDraw::Cross(m_vCreatedPos, 0.3f, Color_LightBlue3, -1); grcDebugDraw::Line(m_vCreatedPos, m_vCreatedPos + m_vCreatedDir, Color_LightBlue3, -1); grcDebugDraw::Line(GetVehiclePosition(), m_vCreatedPos, Color_LightBlue2, -1); } // Engine temperature debug. if( m_bDebugEngineTemperature ) { char tempString[32]; sprintf(tempString, "Off"); if (m_nVehicleFlags.bEngineOn) { sprintf(tempString, "On"); } else if (m_nVehicleFlags.bEngineStarting) { sprintf(tempString, "Starting"); } sprintf(debugText, "Engine:%s Temp:%.1f fan: %s", tempString, m_EngineTemperature, (m_nVehicleFlags.bIsCoolingFanOn?"on":"off")); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } // Wheel temperature debug. if(ms_bDebugWheelTemperatures) { for(int nWheelId = 0; nWheelId < GetNumWheels(); ++nWheelId) { const CWheel* pWheel = GetWheel(nWheelId); Vector3 vWheelPos; pWheel->GetWheelPosAndRadius(vWheelPos); sprintf(debugText, "Temperature: %5.3f", pWheel->GetTyreTemp()); grcDebugDraw::Text( vWheelPos, color, 0, 0, debugText); } } if(ms_bDebugWheelFlags) { for(int nWheelId = 0; nWheelId < GetNumWheels(); ++nWheelId) { const CWheel* pWheel = GetWheel(nWheelId); Vector3 vWheelPos; pWheel->GetWheelPosAndRadius(vWheelPos); int iNoOfWheelTexts = 0; for(int bit = 0; bit < 32; bit++) { if(pWheel->GetConfigFlags().IsFlagSet(BIT(bit))) { const char *sConfigFlag; switch(BIT(bit)) { case WCF_BIKE_WHEEL : sConfigFlag = "WCF_BIKE_WHEEL" ; break; case WCF_LEFTWHEEL : sConfigFlag = "WCF_LEFTWHEEL" ; break; case WCF_REARWHEEL : sConfigFlag = "WCF_REARWHEEL" ; break; case WCF_STEER : sConfigFlag = "WCF_STEER" ; break; case WCF_POWERED : sConfigFlag = "WCF_POWERED" ; break; case WCF_TILT_INDEP : sConfigFlag = "WCF_TILT_INDEP" ; break; case WCF_TILT_SOLID : sConfigFlag = "WCF_TILT_SOLID" ; break; case WCF_BIKE_CONSTRAINED_COLLIDER : sConfigFlag = "WCF_BIKE_CONSTRAINED_COLLIDER" ; break; case WCF_BIKE_FALLEN_COLLIDER : sConfigFlag = "WCF_BIKE_FALLEN_COLLIDER" ; break; case WCF_INSTANCED : sConfigFlag = "WCF_INSTANCED" ; break; case WCF_DONT_RENDER_STEER : sConfigFlag = "WCF_DONT_RENDER_STEER" ; break; case WCF_UPDATE_SUSPENSION : sConfigFlag = "WCF_UPDATE_SUSPENSION" ; break; case WCF_QUAD_WHEEL : sConfigFlag = "WCF_QUAD_WHEEL" ; break; case WCF_HIGH_FRICTION_WHEEL : sConfigFlag = "WCF_HIGH_FRICTION_WHEEL" ; break; case WCF_DONT_REDUCE_GRIP_ON_BURNOUT : sConfigFlag = "WCF_DONT_REDUCE_GRIP_ON_BURNOUT" ; break; case WCF_IS_PHYSICAL : sConfigFlag = "WCF_IS_PHYSICAL" ; break; case WCF_BICYCLE_WHEEL : sConfigFlag = "WCF_BICYCLE_WHEEL" ; break; case WCF_TRACKED_WHEEL : sConfigFlag = "WCF_TRACKED_WHEEL" ; break; case WCF_PLANE_WHEEL : sConfigFlag = "WCF_PLANE_WHEEL" ; break; case WCF_DONT_RENDER_HUB : sConfigFlag = "WCF_DONT_RENDER_HUB" ; break; case WCF_SPOILER : sConfigFlag = "WCF_SPOILER" ; break; case WCF_ROTATE_BOUNDS : sConfigFlag = "WCF_ROTATE_BOUNDS" ; break; case WCF_EXTEND_ON_UPDATE_SUSPENSION : sConfigFlag = "WCF_EXTEND_ON_UPDATE_SUSPENSION" ; break; default: { Assert(false); sConfigFlag = "UNKNOWN"; } break; } grcDebugDraw::Text( vWheelPos, color, 0, iNoOfWheelTexts*grcDebugDraw::GetScreenSpaceTextHeight(), sConfigFlag); iNoOfWheelTexts++; } } for(int bit = 0; bit < 32; bit++) { if(pWheel->GetDynamicFlags().IsFlagSet(BIT(bit))) { const char *sDynamicFlag; switch(BIT(bit)) { case WF_HIT : sDynamicFlag = "WF_HIT" ; break; case WF_HIT_PREV : sDynamicFlag = "WF_HIT_PREV" ; break; case WF_ON_GAS : sDynamicFlag = "WF_ON_GAS" ; break; case WF_ON_FIRE : sDynamicFlag = "WF_ON_FIRE" ; break; case WF_CHEAT_TC : sDynamicFlag = "WF_CHEAT_TC" ; break; case WF_CHEAT_SC : sDynamicFlag = "WF_CHEAT_SC" ; break; case WF_CHEAT_GRIP1 : sDynamicFlag = "WF_CHEAT_GRIP1" ; break; case WF_CHEAT_GRIP2 : sDynamicFlag = "WF_CHEAT_GRIP2" ; break; case WF_BURNOUT : sDynamicFlag = "WF_BURNOUT" ; break; case WF_BURNOUT_NON_DRIVEN_WHEEL : sDynamicFlag = "WF_BURNOUT_NON_DRIVEN_WHEEL" ; break; case WF_INSHALLOWWATER : sDynamicFlag = "WF_INSHALLOWWATER" ; break; case WF_INDEEPWATER : sDynamicFlag = "WF_INDEEPWATER" ; break; case WF_TYRES_HEAT_UP : sDynamicFlag = "WF_TYRES_HEAT_UP" ; break; case WF_ABS_ACTIVE : sDynamicFlag = "WF_ABS_ACTIVE" ; break; case WF_ABS : sDynamicFlag = "WF_ABS" ; break; case WF_ABS_ALT : sDynamicFlag = "WF_ABS_ALT" ; break; case WF_SQUASHING_PED : sDynamicFlag = "WF_SQUASHING_PED" ; break; case WF_REDUCE_GRIP : sDynamicFlag = "WF_REDUCE_GRIP" ; break; case WF_TELEPORTED_NO_VFX : sDynamicFlag = "WF_TELEPORTED_NO_VFX" ; break; case WF_RESET : sDynamicFlag = "WF_RESET" ; break; case WF_BROKEN_OFF : sDynamicFlag = "WF_BROKEN_OFF" ; break; case WF_FULL_THROTTLE : sDynamicFlag = "WF_FULL_THROTTLE" ; break; case WF_SIDE_IMPACT : sDynamicFlag = "WF_SIDE_IMPACT" ; break; case WF_DUMMY_TRANSITION : sDynamicFlag = "WF_DUMMY_TRANSITION" ; break; case WF_DUMMY_TRANSITION_PREV : sDynamicFlag = "WF_DUMMY_TRANSITION_PREV" ; break; case WF_NO_LATERAL_SPRING : sDynamicFlag = "WF_NO_LATERAL_SPRING" ; break; case WF_WITHIN_DAMAGE_REGION : sDynamicFlag = "WF_WITHIN_DAMAGE_REGION" ; break; case WF_WITHIN_HEAVYDAMAGE_REGION : sDynamicFlag = "WWF_WITHIN_HEAVYDAMAGE_REGION"; break; case WF_TOUCHING_PAVEMENT : sDynamicFlag = "WF_TOUCHING_PAVEMENT" ; break; case WF_DUMMY : sDynamicFlag = "WF_DUMMY" ; break; case WF_FORCE_NO_SLEEP : sDynamicFlag = "WF_FORCE_NO_SLEEP" ; break; case WF_SLEEPING_ON_DEBRIS : sDynamicFlag = "WF_SLEEPING_ON_DEBRIS" ; break; default: { Assert(false); sDynamicFlag = "UNKNOWN"; } break; } grcDebugDraw::Text( vWheelPos, color, 0, iNoOfWheelTexts*grcDebugDraw::GetScreenSpaceTextHeight(), sDynamicFlag); iNoOfWheelTexts++; } } } } // Vehicle status debug. if(ms_bDebugVehicleStatus) { sprintf(debugText, "Wrecked: %s", GetStatus()==STATUS_WRECKED ? "true" : "false"); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Abandoned: %s", GetStatus()==STATUS_ABANDONED ? "true" : "false"); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Player disabled: %s", GetStatus()==STATUS_PLAYER_DISABLED ? "true" : "false"); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } if( ms_bDebugVehicleHealth ) { sprintf(debugText, "Health: %0.2f/%0.2f", GetHealth(), GetMaxHealth() ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Body Health: %0.2f/%0.2f", m_VehicleDamage.GetBodyHealth(), m_VehicleDamage.GetDefaultBodyHealthMax(this) ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Gas Tank Health: %0.2f/%0.2f", m_VehicleDamage.GetPetrolTankHealth(), VEH_DAMAGE_HEALTH_STD ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Engine Health: %0.2f/%0.2f", m_VehicleDamage.GetEngineHealth(), ENGINE_HEALTH_MAX ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Vehicle Health percentage: %0.2f", m_VehicleDamage.GetVehicleHealthPercentage(this)); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; if(InheritsFromHeli()) { CHeli *pHeli = (CHeli *) this; sprintf(debugText, "Main Rotor Health: %0.2f/%0.2f", pHeli->GetMainRotorHealth(), VEH_DAMAGE_HEALTH_STD ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Rear Rotor Health: %0.2f/%0.2f", pHeli->GetRearRotorHealth(), VEH_DAMAGE_HEALTH_STD ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Tail Boom Health: %0.2f/%0.2f", pHeli->GetTailBoomHealth(), VEH_DAMAGE_HEALTH_STD ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } else if(InheritsFromPlane()) { const char* strSectionNames[] = { "WING_L", "WING_R", "TAIL", "ENGINE_L", "ENGINE_R", "ELEVATOR_L", "ELEVATOR_R", "AILERON_L", "AILERON_R", "RUDDER", "RUDDER_2", "AIRBRAKE_L", "AIRBRAKE_R", "LANDING_GEAR" }; const char* strLandingGearNames[] = { "GEAR_F", "GEAR_RL", "GEAR_LM1", "GEAR_RM", "GEAR_RM1", "GEAR_RR" }; CPlane *pPlane = (CPlane *) this; for(int i = 0; i < CAircraftDamage::NUM_DAMAGE_SECTIONS; i++) { sprintf(debugText, "Section %s health: %0.2f", strSectionNames[i], pPlane->GetAircraftDamage().GetSectionHealth((CAircraftDamage::eAircraftSection)i)); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } for(int i = 0; i < CLandingGearDamage::NUM_DAMAGE_SECTIONS; i++) { sprintf(debugText, "Section %s health: %0.2f", strLandingGearNames[i], pPlane->GetLandingGearDamage().GetSectionHealth((CLandingGearDamage::eLandingGearSection)i)); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } } sprintf(debugText, "Drivable: %s", m_nVehicleFlags.bIsThisADriveableCar ? "true" : "false"); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Script Damage Scale: %0.2f", m_VehicleDamage.GetScriptDamageScale()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "Script Weapon Damage Scale: %0.2f", m_VehicleDamage.GetScriptWeaponDamageScale()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf( debugText, "Script Body Damage Scale: %0.2f", m_VehicleDamage.GetBodyDamageScale() ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText ); iNoOfTexts++; sprintf( debugText, "Script Petrol Tank Damage Scale: %0.2f", m_VehicleDamage.GetPetrolTankDamageScale() ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText ); iNoOfTexts++; sprintf( debugText, "Script Collision with Map Damage Scale: %0.2f", m_VehicleDamage.GetCollisionWithMapDamageScale() ); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText ); iNoOfTexts++; } if( ms_bDebugVehicleWeapons ) { if (m_pVehicleWeaponMgr) { sprintf(debugText, "VEHICLE WEAPONS - Vehicle 0x%p - Weapon Manager 0x%p", this, m_pVehicleWeaponMgr); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; const CVehicleWeaponHandlingData* pWeaponHandlingData = pHandling->GetWeaponHandlingData(); for(s32 i = 0; i < MAX_NUM_VEHICLE_WEAPONS; i++) { u32 uWeaponHash = pWeaponHandlingData->GetWeaponHash(i); const CWeaponInfo* pWeaponInfo = static_cast(CWeaponInfoManager::GetInfo(uWeaponHash ASSERT_ONLY(, true))); if (pWeaponInfo) { // Loop over all the weapons in the vehicle manager, and see if we have one matching this handling index CVehicleWeapon* pVehicleWeapon = NULL; for (int j = 0; j < m_pVehicleWeaponMgr->GetNumVehicleWeapons(); j++) { CVehicleWeapon* pTempVehicleWeapon = m_pVehicleWeaponMgr->GetVehicleWeapon(j); if (pTempVehicleWeapon && pTempVehicleWeapon->m_handlingIndex == i) { pVehicleWeapon = pTempVehicleWeapon; break; } } sprintf(debugText, " Slot %i: %s", i, pWeaponInfo->GetName()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, " %s : %s : Ammo Count %i", pVehicleWeapon ? "Visible " : "Inactive", pVehicleWeapon && pVehicleWeapon->GetIsEnabled() ? "Enabled " : "Disabled", m_iRestrictedAmmoCount[i]); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } else { sprintf(debugText, " Slot %i: Empty", i); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } } } else if (ContainsLocalPlayer()) { grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), "NO VEHICLE WEAPON MANAGER!"); iNoOfTexts++; } if (GetIsAircraft()) { sprintf(debugText, "AIRCRAFT BOMBS - Ammo Count %i", GetBombAmmoCount()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; sprintf(debugText, "COUNTERMEASURE - Ammo Count %i", GetCountermeasureAmmoCount()); grcDebugDraw::Text( WorldCoors, color, 0, iNoOfTexts*grcDebugDraw::GetScreenSpaceTextHeight(), debugText); iNoOfTexts++; } } // Siren debug. #if __DEV if( Sirens_RenderBones ) { CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)GetBaseModelInfo(); for (s32 boneId = VEH_SIREN_1; boneId <= VEH_SIREN_MAX; boneId++) { const s32 boneIdx = pModelInfo->GetBoneIndex(boneId); if (boneIdx > -1) { Matrix34 worldMtx; GetGlobalMtx(boneIdx, worldMtx); grcDebugDraw::Axis(worldMtx,0.2f); if( Sirens_RenderBoneId ) { sprintf(debugText,"%d",boneId - VEH_SIREN_1); grcDebugDraw::Text(worldMtx.d,Color32(1.0f,1.0f,1.0f),debugText); } } } } #endif // __DEV // Handling debug. #if __BANK if(CVehicle::ms_nVehicleDebug == VEH_DEBUG_HANDLING && (GetStatus()==STATUS_PLAYER || this == CDebugScene::FocusEntities_Get(0))) { float fLength = GetBoundingBoxMax().y + 1.0f; float fHeight = GetBoundingBoxMax().z + 1.0f; const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); grcDebugDraw::Line(vThisPosition, vThisPosition + fHeight*VEC3V_TO_VECTOR3(GetTransform().GetC()), Color32(1.0f, 0.0f, 0.0f, 1.0f)); grcDebugDraw::Line(vThisPosition, vThisPosition + fLength*VEC3V_TO_VECTOR3(GetTransform().GetB()), Color32(1.0f, 0.0f, 0.0f, 1.0f)); if(GetCollider()) { grcDebugDraw::Line(RCC_VECTOR3(GetCollider()->GetPosition()), RCC_VECTOR3(GetCollider()->GetPosition()) + fHeight*RCC_VECTOR3(GetCollider()->GetMatrix().GetCol2ConstRef()), Color32(0.0f, 1.0f, 0.0f, 1.0f)); grcDebugDraw::Line(RCC_VECTOR3(GetCollider()->GetPosition()), RCC_VECTOR3(GetCollider()->GetPosition()) + fLength*2.0f*RCC_VECTOR3(GetCollider()->GetMatrix().GetCol1ConstRef()), Color32(0.0f, 1.0f, 0.0f, 1.0f)); } Vector3 velocity = GetVelocity(); velocity.Normalize(); Vector3 forwardDirection = VEC3V_TO_VECTOR3(GetTransform().GetB()); Vector3 sideDirection = VEC3V_TO_VECTOR3(GetTransform().GetA()); Vector3 fSpeedAlongFwd = velocity.DotV(forwardDirection); Vector3 fSpeedAlongSide = velocity.DotV(sideDirection); float fLateralSlipAngle = rage::Atan2f(-fSpeedAlongSide.x, rage::Max(1.0f, rage::Abs(fSpeedAlongFwd.x))); Color32 drawGreen(0,255,0,255); static const float fScreenWidth = 1.0f; static const float fScreenHeight = 1.0f; static Vector2 vecStart(0.5f*fScreenWidth, 0.8f*fScreenHeight); Vector2 endPosition = Vector2(0.0f, -0.1f); endPosition.Rotate(fLateralSlipAngle); endPosition += Vector2(vecStart.x , vecStart.y); grcDebugDraw::Line( Vector2(vecStart.x, vecStart.y), endPosition, drawGreen ); } #endif #if __DEV if(CVehicle::ms_nVehicleDebug == VEH_DEBUG_PERFORMANCE && GetStatus()==STATUS_PLAYER) { static float sfSixtyMPHInMps = 26.8224f; //60mph in m/s float fSpeed = DotProduct(GetVelocity(), VEC3V_TO_VECTOR3(GetTransform().GetB())); if(fSpeed <= 0.0f) { if(ms_uZeroToSixtyEndTime > ms_uZeroToSixtyStartTime)// have we gone 60 to 0 { u32 uTimeToGoToZeroFromSixty = fwTimer::GetTimeInMilliseconds() - ms_uZeroToSixtyEndTime; float fTimeToGoToZeroFromSixtyInSeconds = uTimeToGoToZeroFromSixty / 1000.0f; if( ms_fBestSixtyToZeroTime == 0.0f || fTimeToGoToZeroFromSixtyInSeconds < ms_fBestSixtyToZeroTime) { ms_fBestSixtyToZeroTime = fTimeToGoToZeroFromSixtyInSeconds; } } if(GetVelocity().Mag() <= 0.05f)// Make sure we're almost still to start with { ms_uZeroToSixtyStartTime = fwTimer::GetTimeInMilliseconds(); } } else if(fSpeed > sfSixtyMPHInMps) { if(ms_uZeroToSixtyStartTime > ms_uZeroToSixtyEndTime)// have we gone 0 to 60 { ms_uZeroToSixtyEndTime = fwTimer::GetTimeInMilliseconds(); u32 uTimeToGetToSixtyFromZero = ms_uZeroToSixtyEndTime - ms_uZeroToSixtyStartTime; float fTimeToGoToSixtyFromZeroInSeconds = uTimeToGetToSixtyFromZero / 1000.0f; if( ms_fBestZeroToSixtyTime == 0 || fTimeToGoToSixtyFromZeroInSeconds < ms_fBestZeroToSixtyTime) { ms_fBestZeroToSixtyTime = fTimeToGoToSixtyFromZeroInSeconds; } } else { ms_uZeroToSixtyEndTime = fwTimer::GetTimeInMilliseconds(); } } grcDebugDraw::AddDebugOutput(""); sprintf(debugText, "0-60mph: %.2fs", ms_fBestZeroToSixtyTime); grcDebugDraw::AddDebugOutput(debugText); sprintf(debugText, "60-0mph: %.2fs", ms_fBestSixtyToZeroTime); grcDebugDraw::AddDebugOutput(debugText); } #endif #if __BANK if(CVehiclePopulation::ms_bDebugVehicleReusePool) { if(GetWasReused()) { grcDebugDraw::Sphere(GetTransform().GetPosition(), 2.f, Color_red, false, 1); } } if(CVehiclePopulation::ms_bDisplayVehicleCreationMethod) { Color32 color = Color_black; switch(m_CreationMethod) { case CVehicle::VEHICLE_CREATION_RANDOM: color = Color_red; formatf(debugText, "Random"); break; case CVehicle::VEHICLE_CREATION_PATHS: color = Color_blue; formatf(debugText, "Paths"); break; case CVehicle::VEHICLE_CREATION_CARGEN: color = Color_green; formatf(debugText, "Cargen"); break; case CVehicle::VEHICLE_CREATION_CRIMINAL: color = Color_red1; formatf(debugText, "Criminal"); break; case CVehicle::VEHICLE_CREATION_EMERGENCY: color = Color_red2; formatf(debugText, "Emergency"); break; case CVehicle::VEHICLE_CREATION_SCRIPT: color = Color_purple; formatf(debugText, "Script"); break; case CVehicle::VEHICLE_CREATION_TRAILER: color = Color_green1; formatf(debugText, "Trailer"); break; default: formatf(debugText, "Invalid"); color = Color_black; break; } grcDebugDraw::Sphere(GetTransform().GetPosition(), 2.f, color, false); grcDebugDraw::Text(GetTransform().GetPosition(), Color_red, debugText); } if(CVehiclePopulation::ms_bDisplayVehicleCreationCost) { formatf(debugText, "%uus", m_iCreationCost); if(m_iCreationCost > CVehiclePopulation::ms_iVehicleCreationCostHighlightThreshold) { grcDebugDraw::Sphere(GetTransform().GetPosition(), 2.5f, Color_tomato, false); } grcDebugDraw::Text(GetTransform().GetPosition() - Vec3V(0.f, 0.f, 0.6f), Color_tomato, debugText); } #endif } void CVehicle::RenderDebugBoneInfo(void) const { if(!sbRenderBones) { return; } const bool isPlayersVeh = GetDriver() && GetDriver()->IsControlledByLocalPlayer(); const bool isAFocusVeh = CDebugScene::FocusEntities_IsInGroup(this); if(isPlayersVeh || isAFocusVeh) { const crSkeletonData& skeletonData = GetSkeletonData(); for (s32 iBoneIndex = 0; iBoneIndex < skeletonData.GetNumBones(); iBoneIndex++) { const char *boneName = skeletonData.GetBoneData(iBoneIndex)->GetName(); if( strstr(boneName,"wheelmesh") != NULL) continue; Matrix34 worldMtx; GetGlobalMtx(iBoneIndex, worldMtx); Matrix34 objectMtx = GetObjectMtx(iBoneIndex); if( (( false == sbRenderBoneNames_OneAtATime ) || ( iBoneIndex == siRenderBoneNamesOneAtATimeId) )) { grcDebugDraw::Axis(worldMtx,0.2f); } if( sbRenderBonesUnDamaged ) { Vector3 vertPos; GetDefaultBonePosition(iBoneIndex,vertPos); Matrix34 undamWorldMtx; undamWorldMtx.Identity(); undamWorldMtx.d = vertPos; Mat34V mat = GetMatrix(); undamWorldMtx.Dot(RCC_MATRIX34(mat)); grcDebugDraw::Axis(undamWorldMtx,0.1f); if( worldMtx.a != Vector3(0.0f,0.0f,0.0f) ) { grcDebugDraw::Line(worldMtx.d,undamWorldMtx.d,Color32(0xFFFFFFFF)); } } char msg[1024]; int iLine = 0; if( sbRenderBoneNames && boneName && (( false == sbRenderBoneNames_OneAtATime ) || ( iBoneIndex == siRenderBoneNamesOneAtATimeId) )) { const int iGroupIndex = GetFragInst()->GetGroupFromBoneIndex(iBoneIndex); if(iGroupIndex!=-1) { const fragTypeGroup * pGroup = GetFragInst()->GetTypePhysics()->GetAllGroups()[iGroupIndex]; sprintf(msg,"%s (%d) Mass: %.1f", boneName, iBoneIndex, pGroup->GetTotalUndamagedMass()); } else { sprintf(msg,"%s (%d)", boneName, iBoneIndex); } grcDebugDraw::Text(worldMtx.d,Color32(1.0f,1.0f,1.0f),msg); iLine++; } if(sbRenderBoneCoords && (( false == sbRenderBoneNames_OneAtATime ) || ( iBoneIndex == siRenderBoneNamesOneAtATimeId) )) { sprintf(msg, "Pos: [%.1f,%.1f,%.2f]", objectMtx.d.x, objectMtx.d.y, objectMtx.d.z ); grcDebugDraw::Text( worldMtx.d, Color32(1.0f,1.0f,1.0f), 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), msg); } if(sbRenderBoneMatrix && (( false == sbRenderBoneNames_OneAtATime ) || ( iBoneIndex == siRenderBoneNamesOneAtATimeId) )) { sprintf(msg, "Mat: [%.2f,%.2f,%.2f]\n [%.2f,%.2f,%.2f]\n [%.2f,%.2f,%.2f]", objectMtx.a.x, objectMtx.a.y, objectMtx.a.z, objectMtx.b.x, objectMtx.b.y, objectMtx.b.z, objectMtx.c.x, objectMtx.c.y, objectMtx.c.z ); grcDebugDraw::Text( worldMtx.d, Color32(1.0f,1.0f,1.0f), 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), msg); } if( sbRenderBoneGeometry && boneName && (( false == sbRenderBoneNames_OneAtATime ) || ( iBoneIndex == siRenderBoneNamesOneAtATimeId) )) { CVehicleComponentVisualiser::VisualiseComponents(this, iBoneIndex, sbRenderComponentBoundingBox); } } const CVehicleVariationInstance& variationInstance = GetVariationInstance(); if (variationInstance.GetKitIndex() != INVALID_VEHICLE_KIT_INDEX && variationInstance.GetVehicleRenderGfx()) { for (u32 i = VEH_EXTRALIGHT_1; i <= VEH_EXTRALIGHT_4; ++i) { Mat34V mat; s32 boneIndex = -1; const char* boneName = NULL; GetExtraLightMatrix((eHierarchyId)i, mat, boneIndex, &boneName); if (boneIndex == -1) continue; Matrix34 worldMtx = MAT34V_TO_MATRIX34(mat); grcDebugDraw::Axis(worldMtx,0.2f); if( sbRenderBoneNames && boneName && (( false == sbRenderBoneNames_OneAtATime ) || ( boneIndex == siRenderBoneNamesOneAtATimeId) )) { char msg[1024]; sprintf(msg,"%s (%d)",boneName,boneIndex); grcDebugDraw::Text(worldMtx.d,Color32(1.0f,1.0f,1.0f),msg); } } } } } void CVehicle::InitDebugName() { sprintf(m_DebugLogName, "%s%d", PopTypeIsMission() ? "SCRIPT_VEHICLE_" : "VEHICLE_", GetDebugObjectID() ); } void CVehicle::ToggleScriptedAutopilot() { CEntity* pFocusEntity = CDebugScene::FocusEntities_Get(0); if (!pFocusEntity || !pFocusEntity->GetIsTypeVehicle()) return; CVehicle* pFocusVehicle = static_cast(pFocusEntity); sVehicleMissionParams params; params.m_iDrivingFlags.SetFlag(DF_DontTerminateTaskWhenAchieved); params.SetTargetPosition(VEC3V_TO_VECTOR3(pFocusVehicle->GetTransform().GetPosition())); static int fEngageScriptAutopilotFlightHeight = 30; static int fEngageScriptAutopilotMinHeightAboveTerrain = 20; static float fEngageScriptAutopilotDesiredHeadingDeg = 0.0f; float fAngleRad = fwAngle::LimitRadianAngleSafe(fEngageScriptAutopilotDesiredHeadingDeg*DtoR); if (!pFocusVehicle->GetIsUsingScriptAutoPilot()) { if (pFocusVehicle->InheritsFromHeli() || pFocusVehicle->InheritsFromPlane()) { CTaskVehicleGoToPlane* pPlaneTask = rage_new CTaskVehicleGoToPlane(params, fEngageScriptAutopilotFlightHeight, fEngageScriptAutopilotMinHeightAboveTerrain, true, true, fAngleRad); // Apply task directly to the vehicle pFocusVehicle->GetIntelligence()->GetTaskManager()->SetTask(VEHICLE_TASK_TREE_PRIMARY, pPlaneTask, VEHICLE_TASK_PRIORITY_PRIMARY, false); pFocusVehicle->GetIntelligence()->SetUsingScriptAutopilot(true); CVehicleNodeList * pNodeList = pFocusVehicle->GetIntelligence()->GetNodeList(); if(pNodeList) { pNodeList->ClearPathNodes(); } pFocusVehicle->SwitchEngineOn(true); } } else { pFocusVehicle->GetIntelligence()->GetTaskManager()->ClearTask(VEHICLE_TASK_TREE_PRIMARY, VEHICLE_TASK_PRIORITY_PRIMARY); } }; #endif // __BANK //------------------------------------------------------------------------- // Returns a specific entry/exit point //------------------------------------------------------------------------- const CEntryExitPoint* CVehicle::GetEntryExitPoint(s32 i) const { CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)GetBaseModelInfo(); return pModelInfo->GetModelSeatInfo()->GetEntryExitPoint(i); } //------------------------------------------------------------------------- // Returns a specific entry/exit point //------------------------------------------------------------------------- s32 CVehicle::GetEntryExitPointIndex(const CEntryExitPoint* pEntryExitPoint) const { CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)GetBaseModelInfo(); return pModelInfo->GetEntryExitPointIndex(pEntryExitPoint); } //------------------------------------------------------------------------- // Returns the number of entry/exit points //------------------------------------------------------------------------- s32 CVehicle::GetNumberEntryExitPoints() const { CVehicleModelInfo* pModelInfo=(CVehicleModelInfo*)GetBaseModelInfo(); return pModelInfo->GetModelSeatInfo()->GetNumberEntryExitPoints(); } //------------------------------------------------------------------------- // Returns if the passed in entry point index is within range //------------------------------------------------------------------------- bool CVehicle::IsEntryPointIndexValid(s32 i) const { if (i > -1 && i < GetNumberEntryExitPoints()) { return true; } return false; } float CVehicle::GetDoorRatioToConsiderDoorOpen(const CVehicle& rVeh, bool bIsCombatEntry) { if (bIsCombatEntry) { return CTaskEnterVehicle::ms_Tunables.m_DoorRatioToConsiderDoorOpenCombat; } if (rVeh.GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_SMALLER_OPEN_DOOR_RATIO_TOLERANCE)) { TUNE_GROUP_FLOAT(ENTER_VEHICLE_TUNE, SMALLER_OPEN_DOOR_RATIO_TOLERANCE, 0.45f, 0.0f, 1.0f, 0.01f); return SMALLER_OPEN_DOOR_RATIO_TOLERANCE; } return rVeh.GetLayoutInfo()->GetClimbUpAfterOpenDoor() ? CTaskEnterVehicle::ms_Tunables.m_DoorRatioToConsiderDoorOpenSteps : CTaskEnterVehicle::ms_Tunables.m_DoorRatioToConsiderDoorOpen; } bool CVehicle::IsEntryPointClearForPed(const CVehicle& vehicle, const CPed& ped, s32 iEntryPoint, const Vector3* pvPosModifier, bool bDoCapsuleTest) { Vector3 vEntryPos(Vector3::ZeroType); Quaternion qEntryRot; CModelSeatInfo::CalculateEntrySituation(&vehicle, &ped, vEntryPos, qEntryRot, iEntryPoint, 0, 0.0f, pvPosModifier); Matrix34 testMatrix; testMatrix.d = vEntryPos; testMatrix.FromQuaternion(qEntryRot); bool bIsClear; if(bDoCapsuleTest && vEntryPos.IsNonZero()) { //Set up a capsule probe. WorldProbe::CShapeTestCapsuleDesc probeDesc; WorldProbe::CShapeTestFixedResults<> probeResult; probeDesc.SetResultsStructure(&probeResult); probeDesc.SetCapsule(vEntryPos, vEntryPos - VEC3V_TO_VECTOR3(vehicle.GetTransform().GetForward()), 0.5f); probeDesc.SetTypeFlags(ArchetypeFlags::GTA_AI_TEST); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_MOVER|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE); //Generate the exclusion list. const CEntity* pExclusionList[2]; u8 uExclusionListCount = 0; pExclusionList[uExclusionListCount++] = &ped; pExclusionList[uExclusionListCount++] = &vehicle; probeDesc.SetExcludeEntities(pExclusionList, uExclusionListCount); // TODO - should we maybe recalculate the entry situation here with the full offset rather than forcing it? Vector3 vEntryPointPos = vEntryPos - (VEC3V_TO_VECTOR3(vehicle.GetTransform().GetRight()) * (CTaskArrestPed::sm_Tunables.m_TargetDistanceFromVehicleEntry * .5f)); bIsClear = !WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc) && !CTaskExitVehicle::IsVerticalProbeClear(vEntryPointPos, CTaskExitVehicleSeat::ms_Tunables.m_ArrestProbeDistance, ped); } else { bIsClear = CModelSeatInfo::IsPositionClearForPed(&ped, &vehicle, testMatrix); } #if __BANK CVehicle::eVehicleOrientation vehOrientation = CVehicle::GetVehicleOrientation(vehicle); Color32 iClearColor; switch (vehOrientation) { case CVehicle::VO_Upright: iClearColor = Color_green; break; case CVehicle::VO_OnSide: iClearColor = Color_orange; break; case CVehicle::VO_UpsideDown: iClearColor = Color_blue; break; default: iClearColor = Color_white; } CVehicleDebug::RenderTestCapsuleAtPosition(&ped, RCC_VEC3V(testMatrix.d), RCC_VEC3V(testMatrix.c), bIsClear ? iClearColor : Color_red, 1000, vehicle); CTask::ms_debugDraw.AddLine(RCC_VEC3V(vEntryPos), vehicle.GetTransform().GetPosition(), bIsClear ? iClearColor : Color_red, 1000); #endif // __BANK if (bIsClear && vehicle.InheritsFromAutomobile()) { return IsDoorClearForPed(testMatrix.d, vehicle, ped, iEntryPoint); } return bIsClear; } bool CVehicle::IsDoorClearForPed(const Vector3& vEntryPosition, const CVehicle& rVeh, const CPed& rPed, s32 iEntryPointIndex) { // Consider entry clear if we have no door, or the door is already open s32 iBoneIndex = rVeh.GetVehicleModelInfo()->GetModelSeatInfo()->GetBoneIndexFromEntryPoint(iEntryPointIndex); if (iBoneIndex == -1) { return true; } const CCarDoor* pDoor = rVeh.GetDoorFromBoneIndex(iBoneIndex); if (!pDoor || pDoor->GetIsFullyOpen() || !pDoor->GetIsIntact(&rVeh)) { return true; } Vector3 vEnd(Vector3::ZeroType); Matrix34 doorHingeMtx(Matrix34::IdentityType); // Door hinges aren't a good place to check on gull-wing doors, so check from seat bone... if (rVeh.GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_GULL_WING_DOORS)) { Vector3 vForwardDir = VEC3V_TO_VECTOR3(rVeh.GetTransform().GetB()); vForwardDir.Normalize(); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_HEIGHT_OFFSET, 0.3f, -10.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_LENGTH, 1.5f, -10.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_LENGTH_FELTZER3, 1.3f, -10.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_LENGTH_PROTO, 1.1f, -10.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_LENGTH_GP1, 1.675f, -10.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_LENGTH_DEVESTE, 1.6f, -10.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULL_DOOR_PARALLEL_TEST_LENGTH_S80, 0.75f, -10.0f, 10.0f, 0.01f); // Default length is based on the OSIRIS float fGullWingParallelTestLength = GULL_DOOR_PARALLEL_TEST_LENGTH; // Other gull-wing vehicles need custom lengths based on the model geometry const u32 vehModelHash = rVeh.GetVehicleModelInfo()->GetModelNameHash(); if (vehModelHash == MID_FELTZER3) { fGullWingParallelTestLength = GULL_DOOR_PARALLEL_TEST_LENGTH_FELTZER3; } else if (vehModelHash == MID_PROTOTIPO) { fGullWingParallelTestLength = GULL_DOOR_PARALLEL_TEST_LENGTH_PROTO; } else if (vehModelHash == MID_GP1) { fGullWingParallelTestLength = GULL_DOOR_PARALLEL_TEST_LENGTH_GP1; } else if (vehModelHash == MID_DEVESTE) { fGullWingParallelTestLength = GULL_DOOR_PARALLEL_TEST_LENGTH_DEVESTE; } else if (vehModelHash == MID_S80) { fGullWingParallelTestLength = GULL_DOOR_PARALLEL_TEST_LENGTH_S80; } vEnd = Vector3(vEntryPosition.x,vEntryPosition.y, vEntryPosition.z - GULL_DOOR_PARALLEL_TEST_HEIGHT_OFFSET); vEnd = vEnd + vForwardDir * fGullWingParallelTestLength; } else { rVeh.GetGlobalMtx(iBoneIndex, doorHingeMtx); const bool bLowerDoorTest = rVeh.GetLayoutInfo()->GetUseLowerDoorBlockTest(); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, DOOR_TEST_POSITION_SHIFT_BACK_DIST, -0.15f, -1.5f, 1.5f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, LOWER_DOOR_TEST_OFFSET, 0.3f, 0.0f, 1.0f, 0.01f); vEnd = doorHingeMtx.d + doorHingeMtx.b * DOOR_TEST_POSITION_SHIFT_BACK_DIST; if (bLowerDoorTest) { vEnd.z -= LOWER_DOOR_TEST_OFFSET; } } TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, DOOR_TEST_POLICE_ENTRYP_SHIFT_SIDEWAYS, 0.281f, 0.0f, 2.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, DOOR_TEST_POLICE_ENTRYP_SHIFT_FORWARD, 0.390f, -2.0f, 2.0f, 0.01f); Vector3 vStart(vEntryPosition.x, vEntryPosition.y, vEnd.z); const CEntryExitPoint* pEntryExitPoint = rVeh.GetVehicleModelInfo()->GetModelSeatInfo()->GetEntryExitPoint(iEntryPointIndex); const s32 iSeatIndex = pEntryExitPoint ? pEntryExitPoint->GetSeat(SA_directAccessSeat) : -1; const bool bIsFrontSeat = rVeh.IsSeatIndexValid(iSeatIndex) && rVeh.GetSeatInfo(iSeatIndex)->GetIsFrontSeat(); const u32 iVehModelNameHash = rVeh.GetVehicleModelInfo()->GetModelNameHash(); const bool bIsEmergencyServicesPed = CPedType::IsEmergencyType(rPed.GetPedType()) || CPedType::IsLawEnforcementType(rPed.GetPedType()); const bool bIsExitingVehicle = rPed.GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle); if (!rPed.IsLocalPlayer() && bIsEmergencyServicesPed && bIsExitingVehicle && bIsFrontSeat && (iVehModelNameHash == MID_POLICE || iVehModelNameHash == MID_POLICE2 || iVehModelNameHash == MID_POLICE3 || iVehModelNameHash == MID_POLICE4 || iVehModelNameHash == MID_SHERIFF)) { Vector3 vSideDir = VEC3V_TO_VECTOR3(rVeh.GetTransform().GetA()); vSideDir.Normalize(); vSideDir = rVeh.GetDriverSeat() == iSeatIndex ? vSideDir * -1.0f : vSideDir * 1.0f; Vector3 vForwardDir = VEC3V_TO_VECTOR3(rVeh.GetTransform().GetB()); vForwardDir.Normalize(); Vector3 vNewStart = Vector3(vEntryPosition.x, vEntryPosition.y, vEnd.z); vNewStart = vNewStart + vSideDir * DOOR_TEST_POLICE_ENTRYP_SHIFT_SIDEWAYS; vNewStart = vNewStart + vForwardDir * DOOR_TEST_POLICE_ENTRYP_SHIFT_FORWARD; vNewStart.z = vEnd.z; vStart = vNewStart; } if (iVehModelNameHash == MID_POLICE2 || iVehModelNameHash == MID_POLICE3) { TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, POLICE_HINGE_EXTRA_OFFSET, 0.01f, 0.0f, 1.0f, 0.01f); float fDirMod = rVeh.GetDriverSeat() == iSeatIndex ? -1.0f : 1.0f; Vector3 vHingeSideDir = doorHingeMtx.a; vHingeSideDir.Normalize(); vEnd = vEnd + vHingeSideDir * POLICE_HINGE_EXTRA_OFFSET * fDirMod; vEnd.z = vStart.z; } bool bSkipAlterDoorEndHeight = false; if (rVeh.GetVehicleModelInfo()) { const u32 vehModelHash = rVeh.GetVehicleModelInfo()->GetModelNameHash(); if (vehModelHash == MID_KALAHARI) { bSkipAlterDoorEndHeight = true; } } TUNE_GROUP_BOOL(VEHICLE_ENTRY_TUNE, ALTER_DOOR_END_HEIGHT_FOR_VEHICLE_ORIENTATION, true); if (!bSkipAlterDoorEndHeight && ALTER_DOOR_END_HEIGHT_FOR_VEHICLE_ORIENTATION) { TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, LOWER_DOOR_ROLL_MAX_OFFSET, 1.2f, 0.0f,4.0f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, LOWER_DOOR_PITCH_MAX_OFFSET, 1.2f, 0.0f, 4.0f, 0.01f); const float fRoll = rVeh.GetTransform().GetRoll(); const float fPitch = rVeh.GetTransform().GetPitch(); const float fRollOffset = Clamp(Abs(fRoll) / HALF_PI, 0.0f, 1.0f); const float fModifiedPitch = rVeh.IsUpsideDown() ? -Abs(fPitch) : fPitch; const float fPitchOffset = Clamp(fModifiedPitch / HALF_PI, -1.0f, 1.0f); const float fZOffset = fRollOffset * LOWER_DOOR_ROLL_MAX_OFFSET + fPitchOffset * LOWER_DOOR_PITCH_MAX_OFFSET; vStart.z -= fZOffset; } static const int nTestTypes = ArchetypeFlags::GTA_MAP_TYPE_MOVER|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE; const CEntity* exclusionList[MAX_INDEPENDENT_VEHICLE_ENTITY_LIST]; int nExclusions = 0; rPed.GeneratePhysExclusionList(exclusionList, nExclusions, MAX_NUM_ENTITIES_ATTACHED_TO_PED, nTestTypes,TYPE_FLAGS_ALL); // This will prevent WorldProbe::CShapeTestTaskData::ConvertExcludeEntitiesToInstances from adding the vehicle the ped is attached to u32 excludeEntityOptions = WorldProbe::EIEO_DONT_ADD_VEHICLE | WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS; // Exclude the ped exclusionList[nExclusions++] = &rPed; // Exclude the vehicle we are testing exclusionList[nExclusions++] = &rVeh; // Exclude objects attached to the vehicle also rVeh.GeneratePhysExclusionList(exclusionList, nExclusions, MAX_INDEPENDENT_VEHICLE_ENTITY_LIST, nTestTypes, ArchetypeFlags::GTA_OBJECT_TYPE); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, GULLWING_DOOR_TES_CAPSULE_RADIUS, 0.2f, 0.0f, 1.5f, 0.01f); TUNE_GROUP_FLOAT(VEHICLE_ENTRY_TUNE, CAPSULE_RADIUS_FOR_DOOR_TEST, 0.1f, 0.0f, 1.5f, 0.01f); WorldProbe::CShapeTestCapsuleDesc capsuleDesc; capsuleDesc.SetCapsule(vStart, vEnd, rVeh.GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_GULL_WING_DOORS) ? GULLWING_DOOR_TES_CAPSULE_RADIUS : CAPSULE_RADIUS_FOR_DOOR_TEST); capsuleDesc.SetIsDirected(false); capsuleDesc.SetDoInitialSphereCheck(false); capsuleDesc.SetIncludeFlags(nTestTypes); capsuleDesc.SetTypeFlags(TYPE_FLAGS_ALL); capsuleDesc.SetExcludeEntities(exclusionList, nExclusions, excludeEntityOptions); WorldProbe::CShapeTestFixedResults<> capsuleResult; capsuleDesc.SetResultsStructure(&capsuleResult); // So... If we are looking for objects but not pickups we need to make sure that we don't get false positive from pickups bool bDidCollide = WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc); if (bDidCollide) { bDidCollide = CPedGeometryAnalyser::TestIfCollisionTypeIsValid(&rPed, nTestTypes, capsuleResult); } if (bDidCollide) { for (int i = 0; i < capsuleResult.GetNumHits(); ++i) { const CEntity* pEntity = CPhysics::GetEntityFromInst(capsuleResult[i].GetHitInst()); if (pEntity) { AI_LOG_WITH_ARGS("[VehicleEntryExit][CVehicle::IsDoorClearForPed] - We think entity %s is blocking the door\n", pEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pEntity)) : pEntity->GetModelName()); if (pEntity->GetIsTypeVehicle() && !pEntity->IsCollisionEnabled()) { AI_LOG_WITH_ARGS("[VehicleEntryExit][CVehicle::IsDoorClearForPed] - Ignoring vehicle %s as it has collision disabled\n", pEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pEntity)) : pEntity->GetModelName()); bDidCollide = false; } else { bDidCollide = true; break; } } } } #if __BANK CTask::ms_debugDraw.AddCapsule(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), rVeh.GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_GULL_WING_DOORS) ? GULLWING_DOOR_TES_CAPSULE_RADIUS : CAPSULE_RADIUS_FOR_DOOR_TEST, !bDidCollide ? Color_green : Color_red, 1000, 0, false); #endif // __BANK return !bDidCollide; } bool CVehicle::HasWaterEntry() const { if(GetIsAquatic()) { return true; } if(pHandling && pHandling->GetSeaPlaneHandlingData()) { return true; } if(IsEntryPointIndexValid(0)) { const CVehicleEntryPointAnimInfo *pEntryAnimInfo = GetEntryAnimInfo(0); if(pEntryAnimInfo && (pEntryAnimInfo->GetHasClimbUpFromWater() || pEntryAnimInfo->GetHasGetInFromWater())) { return true; } } return false; } void CVehicle::UpdateVisualDataSettings() { if (g_visualSettings.GetIsLoaded()) { g_TaxiSettings.Set( g_visualSettings, "car.taxi" ); g_InteriorLightSettings.Set( g_visualSettings, "car.interiorlight" ); g_InteriorFatLightSettings.Set( g_visualSettings, "car.fatinteriorlight" ); g_InteriorRedLightSettings.Set( g_visualSettings, "car.redinteriorlight" ); g_PlateLightSettings.Set( g_visualSettings, "car.platelight" ); g_DashLightSettings.Set( g_visualSettings, "car.dashlight" ); g_DoorLightSettings.Set( g_visualSettings, "car.doorlight" ); g_CarAngleSettings = g_visualSettings.Get( "car.headlights.angle", 0.0f); g_CarSplitSettings = g_visualSettings.Get( "car.headlights.split", 0.0f); CHeadlightTuningData::Set(g_visualSettings); CNeonTuningData::Set(g_visualSettings); g_LightSwitchSettings.Set( g_visualSettings ); CTrafficLights::SetConfiguration(); CTrain::UpdateVisualDataSettings(g_visualSettings); g_DayNightLights.sunrise = g_visualSettings.Get( "vehicle.lights.sunrise" ); g_DayNightLights.sunset = g_visualSettings.Get( "vehicle.lights.sunset" ); g_MainLights_ShutDownDistance = g_visualSettings.Get( "vehicle.lights.fadeDistance.main"); // 80.0f; g_SecondaryLights_ShutDownDistance = g_visualSettings.Get( "vehicle.lights.fadeDistance.secondary"); // 40.0f; g_NeonLights_ShutDownDistance = g_visualSettings.Get( "vehicle.lights.fadeDistance.neon"); // 80.0f; g_SirenLights_ShutDownDistance = g_visualSettings.Get( "vehicle.lights.fadeDistance.sirens"); // 150.0f; g_AOVolume_ShutDownDistance = g_visualSettings.Get( "vehicle.lights.fadeDistance.AO"); // 150.0f; g_Lights_FadeLength = g_visualSettings.Get( "vehicle.lights.fadeLength"); // 10.0f; g_HeliPosLights.Set( g_visualSettings, "heli.poslight"); g_HeliWhiteHeadLights.Set( g_visualSettings, "heli.whiteheadlight"); g_HeliWhiteTailLights.Set( g_visualSettings, "heli.whitetaillight"); g_HeliInteriorLights.Set( g_visualSettings, "heli.interiorlight" ); g_PlanePosLights.Set( g_visualSettings, "plane.poslight"); g_PlaneWhiteHeadLights.Set( g_visualSettings, "plane.whiteheadlight"); g_PlaneWhiteTailLights.Set( g_visualSettings, "plane.whitetaillight"); g_PlaneControlPanelLights.Set( g_visualSettings, "plane.controlpanel.light"); g_PlaneLeftEmergencyLights.Set( g_visualSettings, "plane.emergency.left.light"); g_PlaneRightEmergencyLights.Set( g_visualSettings, "plane.emergency.right.light"); g_PlaneInsideHullLights.Set( g_visualSettings, "plane.insidehull.light"); g_PlaneLuxe2Cabin.Set(g_visualSettings, "plane.luxe2.cabin"); g_PlaneLuxe2CabinStrip.Set(g_visualSettings, "plane.luxe2.cabin.strip"); g_PlaneLuxe2CabinTV.Set(g_visualSettings, "plane.luxe2.cabin.tv"); g_PlaneLuxe2CabinLOD.Set(g_visualSettings, "plane.luxe2.cabin.lod"); g_PlaneLuxe2CabinWindow.Set(g_visualSettings, "plane.luxe2.cabin.window"); g_HeliSwift2Cabin.Set(g_visualSettings, "heli.swift2.cabin"); g_BoatLights.Set( g_visualSettings, "boat"); g_SubmarineLightOne.Set( g_visualSettings, "sub.lightOne"); g_SubmarineLightTwo.Set( g_visualSettings, "sub.lightTwo"); } } //------------------------------------------------------------------------- // Returns the name of the hierarchy id //------------------------------------------------------------------------- static const char* sUnknownName = "unknown_id"; // const char* CVehicle::GetHierarchyName(eHierarchyId nId) const { ObjectNameIdAssociation* pDesc = CVehicleFactory::GetFactory()->GetBaseVehicleTypeDesc(m_vehicleType); if(nId >= VEH_EXTRA_1 && m_vehicleType != VEHICLE_TYPE_CAR) CVehicleFactory::GetFactory()->GetExtraVehicleTypeDesc(m_vehicleType); int i=-1; while(pDesc[++i].pName) { if(pDesc[i].hierId == nId) return pDesc[i].pName; } // didn't find a matching id, something has gone wrong (wrong id for this vehicle type perhaps?) return sUnknownName; } void CVehicle::GetVehicleGetInOffset(Vector3& UNUSED_PARAM(vOffset), float& UNUSED_PARAM(fHeadingRotation), int UNUSED_PARAM(iEntryPointIndex)) const { } void CVehicle::GetSeatAttachmentOffsets(Vector3& vTransOffset, Quaternion& qRotOffset, s32 iSeatIndex) const { s32 iBoneIndex = GetVehicleModelInfo()->GetModelSeatInfo()->GetBoneIndexFromSeat(iSeatIndex); if (vehicleVerifyf(iBoneIndex >= 0, "Invalid Seat Bone Index in CVehicle::GetSeatAttachmentOffsets")) { // Compute attachment offsets relative to vehicle const Matrix34& seatObjMtx = GetObjectMtx(iBoneIndex); // Position offset vTransOffset = seatObjMtx.d; // Rotation offset seatObjMtx.ToQuaternion(qRotOffset); } } // dev_float MIN_PLATFORM_OFFSET_FROM_TRAIN = 0.2f; // dev_float PLATFORM_OFFSET_FROM_TRAIN_POS = 1.1105380058288574f; // dev_float MAX_HEIGHT_ABOVE_ORIGINAL_HEIGHT = 1.5f; // dev_float MAX_HEIGHT_ABOVE_ORIGINAL_HEIGHT_DYNAMIC = 0.5f; #if DEBUG_DRAW #define MAX_TASK_DRAWABLES 200 CDebugDrawStore CVehicle::ms_debugDraw(MAX_TASK_DRAWABLES); #endif // DEBUG_DRAW float CVehicle::sm_fDoorPushMassScale = 0.05f; /////////////////////////////////////////////////////////////////////////// // FUNCTION SetRandomEngineTemperature // DOES For cars that are created with their engine on this function sets a // believable initial temperature. /////////////////////////////////////////////////////////////////////////// void CVehicle::SetRandomEngineTemperature(float value) { Assert(value >= 0.0f && value <= 1.0f); float currTemp = g_weather.GetTemperature(GetTransform().GetPosition()); m_EngineTemperature = currTemp + value * (MAX_ENGINE_TEMPERATURE - currTemp); } Vec3V_Out CVehicle::GetWindowNormalForSmash(eHierarchyId windowId) const { switch ((int)windowId) { case VEH_WINDSCREEN: return GetTransform().GetB(); case VEH_WINDSCREEN_R: return -GetTransform().GetB(); case VEH_WINDOW_LF: case VEH_WINDOW_LR: case VEH_WINDOW_LM: return -GetTransform().GetA(); case VEH_WINDOW_RF: case VEH_WINDOW_RR: case VEH_WINDOW_RM: return GetTransform().GetA(); } return Vec3V(V_Z_AXIS_WZERO); } void CVehicle::SmashWindow( eHierarchyId iWindowHierarchy, float force, bool bIgnoreCracks, bool bDetachAll ) { if( HasWindowToSmash(iWindowHierarchy, bIgnoreCracks) ) { SetWindowComponentDirty(iWindowHierarchy); const int windowBoneId = GetBoneIndex(iWindowHierarchy); s32 iWindowComponent = GetVehicleFragInst()->GetComponentFromBoneIndex( windowBoneId ); if(iWindowComponent != -1) { const Vec3V vSmashNormal = GetWindowNormalForSmash(iWindowHierarchy); g_vehicleGlassMan.SmashCollision(this, iWindowComponent, force, vSmashNormal, bDetachAll); } } } void CVehicle::FixWindow( eHierarchyId iWindowHierarchy ) { if( HasWindowToSmash(iWindowHierarchy, true) REPLAY_ONLY(|| CReplayMgr::IsReplayInControlOfWorld()) ) { SetWindowComponentDirty(iWindowHierarchy); const int windowBoneId = GetBoneIndex(iWindowHierarchy); s32 iWindowComponent = GetVehicleFragInst()->GetComponentFromBoneIndex( windowBoneId ); if(iWindowComponent != -1) { g_vehicleGlassMan.RemoveComponent(this, iWindowComponent); ClearBrokenFlag(iWindowComponent); ClearHiddenFlag(iWindowComponent); if(InheritsFromPlane()) { static_cast(this)->UpdateWindowBound(iWindowHierarchy); } } } } bool CVehicle::HasWindow(eHierarchyId iWindowHierarchy) const { s32 iWindowComponent = GetFragmentComponentIndex(iWindowHierarchy); if (iWindowComponent < 0) { return false; } return true; } // NOTE -- this will return false if the window has crack decals applied to it but is still topologically intact bool CVehicle::HasWindowToSmash( eHierarchyId iWindowHierarchy, bool bIgnoreCracks ) const { if (iWindowHierarchy == VEH_INVALID_ID) { return false; } if (IsWindowDown(iWindowHierarchy)) { return false; } s32 iWindowComponent = GetFragmentComponentIndex(iWindowHierarchy); if (iWindowComponent < 0) { return false; } if (!bIgnoreCracks && IsBrokenFlagSet(iWindowComponent)) { return false; } return true; } // NOTE -- this will return false if any window has crack decals applied to it but is still topologically intact bool CVehicle::HasAnyWindowsToSmash() const { if (AreAnyBrokenFlagsSet()) { return false; } return true; } void CVehicle::DeleteWindow( eHierarchyId iWindowHierarchy ) { SetWindowComponentDirty(iWindowHierarchy); if( !HasWindowToSmash(iWindowHierarchy) ) return; RemoveFragmentComponent(iWindowHierarchy); } eHierarchyId CVehicle::GetWindowIdFromDoor(eHierarchyId doorId) { eHierarchyId windowId; switch(doorId) { case VEH_DOOR_DSIDE_F: windowId = VEH_WINDOW_LF; break; case VEH_DOOR_DSIDE_R: windowId = VEH_WINDOW_LR; break; case VEH_DOOR_PSIDE_F: windowId = VEH_WINDOW_RF; break; case VEH_DOOR_PSIDE_R: windowId = VEH_WINDOW_RR; break; default: windowId = VEH_INVALID_ID; break; } return windowId; } eHierarchyId CVehicle::GetDoorIdFromWindow(eHierarchyId windowId) { eHierarchyId doorId; switch(windowId) { case VEH_WINDOW_LF: doorId = VEH_DOOR_DSIDE_F; break; case VEH_WINDOW_RF: doorId = VEH_DOOR_PSIDE_F; break; case VEH_WINDOW_LR: doorId = VEH_DOOR_DSIDE_R; break; case VEH_WINDOW_RR: doorId = VEH_DOOR_PSIDE_R; break; default: doorId = VEH_INVALID_ID; break; } return doorId; } bool CVehicle::IsWindowDown(eHierarchyId eWindowId) const { bool bWindowDown = false; Assert(eWindowId<=VEH_LAST_WINDOW); if(eWindowId>=VEH_WINDOW_LF && GetBoneIndex(eWindowId) > -1) { bWindowDown = m_bWindowsRolledDown[VEH_LAST_WINDOW-eWindowId]; } return bWindowDown; } void CVehicle::RolldownWindows() { for(int nId = VEH_WINDOW_LF; nId <= VEH_WINDOW_RM; nId++) { RolldownWindow((eHierarchyId)nId); } } void CVehicle::RolldownWindow(eHierarchyId nWindowHierarchy, bool bCalledFromPreRender) { ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry(true);) Assertf(GetVehicleModelInfo(), "Vehicle didn't have an associated vehicle model info"); // Keep the frag component around, but just don't render it. Set a zero scaling on the bone matrix. s32 nBoneIndex = GetBoneIndex(nWindowHierarchy); Assert(nWindowHierarchy<=VEH_LAST_WINDOW); if(nWindowHierarchy>=VEH_WINDOW_LF && nBoneIndex > -1) { // Flag this window as having been rolled down. m_bWindowsRolledDown[VEH_LAST_WINDOW-nWindowHierarchy] = true; if(HasComponent(nWindowHierarchy)) { s32 nComponent = GetVehicleFragInst()->GetComponentFromBoneIndex(nBoneIndex); if(nComponent >= 0) { // Turn collision with the window bound off for weapons so it doesn't smash while rolled down. phBoundComposite* pBound = GetVehicleFragInst()->GetCacheEntry()->GetBound(); u32 nWindowIncludeFlags = pBound->GetIncludeFlags(nComponent); nWindowIncludeFlags &= ~ArchetypeFlags::GTA_WEAPON_TEST; pBound->SetIncludeFlags(nComponent, nWindowIncludeFlags); if(IsBrokenFlagSet(nComponent) && !bCalledFromPreRender) { SetWindowComponentDirty(nWindowHierarchy); // don't need to hack the bone matrices, we've already smashed the window out static const float force = 0.00001f; // Use a very small force because we want the broken shards to just drop down g_vehicleGlassMan.SmashCollision(this, nComponent, force, GetWindowNormalForSmash(nWindowHierarchy), false); return; } } } crSkeleton* pSkel = GetSkeleton(); if(Verifyf(pSkel, "Couldn't find a skeleton for this vehicle(%s)", GetModelName())) { Mat34V& localBoneMatrix = pSkel->GetLocalMtx(nBoneIndex); localBoneMatrix.Set3x3(Mat33V(V_ZERO)); Mat34V& objectBoneMatrix = pSkel->GetObjectMtx(nBoneIndex); objectBoneMatrix.Set3x3(Mat33V(V_ZERO)); } } } void CVehicle::RollupWindows(bool bSkeletonReset) { for(int nId = VEH_WINDOW_LF; nId <= VEH_LAST_WINDOW; nId++) { if(bSkeletonReset) { RollWindowUp((eHierarchyId)nId); } else { // Just flag this window as having been rolled up. m_bWindowsRolledDown[VEH_LAST_WINDOW-nId] = false; } } } void CVehicle::RollWindowUp(eHierarchyId nWindowHierarchy) { ENABLE_FRAG_OPTIMIZATION_ONLY(GiveFragCacheEntry(true);) // Keep the frag component around, but just don't render it. Set a zero scaling on the bone matrix. s32 nBoneIndex = GetBoneIndex(nWindowHierarchy); Assert(nWindowHierarchy<=VEH_LAST_WINDOW); if(nWindowHierarchy>=VEH_WINDOW_LF && nBoneIndex > -1) { crSkeleton* pSkel = GetSkeleton(); if(Verifyf(pSkel, "Couldn't find a skeleton for this vehicle(%s)", GetModelName())) { // Flag this window as having been rolled up. m_bWindowsRolledDown[VEH_LAST_WINDOW-nWindowHierarchy] = false; // Turn collision with the window bound on for weapons if(HasComponent(nWindowHierarchy)) { s32 nComponent = GetVehicleFragInst()->GetComponentFromBoneIndex(nBoneIndex); if(Verifyf(nComponent >= 0, "No bound component found for this bone (%d) on vehicle '%s'.", nBoneIndex, GetModelName())) { phBoundComposite* pBound = GetVehicleFragInst()->GetCacheEntry()->GetBound(); u32 nWindowIncludeFlags = pBound->GetIncludeFlags(nComponent); nWindowIncludeFlags |= ArchetypeFlags::GTA_WEAPON_TEST; pBound->SetIncludeFlags(nComponent, nWindowIncludeFlags); //if (!IsBrokenFlagSet(nComponent)) { pSkel->PartialReset(nBoneIndex); } } } } } } void CVehicle::CacheSmashableWindows() { // N.B. not yet 100% accurate - every few minutes of intense car destruction we'll miss a front L/R window smash due to nearby explosion for(s32 i = 0; i < audVehicleAudioEntity::NumWindowHierarchyIds; i++) { bool bSmashable = false; eHierarchyId iWindowHierarchy = (eHierarchyId)((s32)VEH_FIRST_WINDOW + i); s32 iWindowComponent; s32 shadowWindowComponent = m_iWindowComponent[i]; if (shadowWindowComponent == VEH_WINDOW_STATE_DIRTY || (m_nVehicleFlags.bBlownUp && shadowWindowComponent >= 0)) { iWindowComponent = GetFragmentComponentIndex(iWindowHierarchy); m_iWindowComponent[i] = iWindowComponent; } else { iWindowComponent = shadowWindowComponent; } if (iWindowComponent >= 0 && (iWindowHierarchy < VEH_WINDOW_LF || !m_bWindowsRolledDown[VEH_LAST_WINDOW-iWindowHierarchy])) { bSmashable = !IsBrokenFlagSet(iWindowComponent); } m_VehicleAudioEntity->SetHasWindowToSmashCache(i, bSmashable); } } ////////////////////////////////////////////////////////////////// // ProcessDirtLevel // Only called for the player veh/bike. It increases the dirt level so that // the vehicle appears to get dirty /////////////////////////////////////////////////////////////////// void CVehicle::ProcessDirtLevel( ) { //Dirt gets added from replay data on playback. #if GTA_REPLAY if( CReplayMgr::IsReplayInControlOfWorld() ) return; #endif // GTA_REPLAY float currentDirt = GetBodyDirtLevel(); const float dirtGrowScale = CPopCycle::GetCurrentZoneDirtGrowScale(); static dev_float dirtScale = 0.01f * dirtGrowScale; // Goes up by this much for every meter traveled. static dev_float dirtScaleClean = 0.001f * dirtGrowScale; // Goes up by this much for every meter traveled. if (dirtScale < 1.0f) { currentDirt += fwTimer::GetTimeStep() * GetVelocityIncludingReferenceFrame().XYMag() * dirtScaleClean; } else { currentDirt += fwTimer::GetTimeStep() * GetVelocityIncludingReferenceFrame().XYMag() * dirtScale; } currentDirt = rage::Min(currentDirt, 15.0f); SetBodyDirtLevel(currentDirt); } void CVehicle::EveryoneInsideWillFlyThroughWindscreen(bool bSetVal) { for (s32 i = 0; i < m_SeatManager.GetMaxSeats(); ++i) { if (m_SeatManager.GetPedInSeat(i)) { #if __DEV scriptDisplayf("Ped %s set to not fly through windscreen at frame %i, via code CPedIntelligence::UpdateCopPedVariables", m_SeatManager.GetPedInSeat(i)->GetDebugName() ? m_SeatManager.GetPedInSeat(i)->GetDebugName() : "NULL", fwTimer::GetFrameCount()); scrThread::PrePrintStackTrace(); #endif m_SeatManager.GetPedInSeat(i)->SetPedConfigFlag( CPED_CONFIG_FLAG_WillFlyThroughWindscreen, bSetVal ); } } } void CVehicle::ProcessSeatCollisionFlags() { fragInstGta* pVehFragInst = GetVehicleFragInst(); if(!pVehFragInst) { return; } const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); physicsFatalAssertf(pVehicleModelInfo, "This vehicle has no model info. It is unsafe to continue"); phBoundComposite* pBoundComposite = static_cast(pVehFragInst->GetArchetype()->GetBound()); if(!physicsVerifyf(pBoundComposite->GetTypeAndIncludeFlags(),"No type and include flags allocated for composite bound")) { return; } for(int i = 0; i < m_SeatManager.GetMaxSeats(); i++) { // Does this seat have collision? int iFragChild = pVehicleModelInfo->GetFragChildForSeat(i); if(iFragChild > -1) { // There is collision on this seat so its open top // When there is no occupier of this seat want no collision // When there is an occupier we want seat to collide with world if(m_SeatManager.GetPedInSeat(i)) { u32 nIncludeFlags = pBoundComposite->GetIncludeFlags(iFragChild); if(nIncludeFlags == 0 || (IsDummy() && (nIncludeFlags & snDummyIncludeFlags))) { // Want to turn the collision ON for the seat // But first need to do shapetest to make sure it isn't currently inside collision Matrix34 matTest(RCC_MATRIX34(pBoundComposite->GetCurrentMatrix(iFragChild))); matTest.Dot(RCC_MATRIX34(GetVehicleFragInst()->GetMatrix())); int nTestTypes = ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE; const int iNumExcludeEntities = 3; const int iNumSeatTestIntersections = 4; const CEntity* aEntityExcludeList[iNumExcludeEntities]; aEntityExcludeList[0] = this; aEntityExcludeList[1] = m_SeatManager.GetPedInSeat(i); aEntityExcludeList[2] = m_SeatManager.GetPedInSeat(i)->GetWeaponManager() ? m_SeatManager.GetPedInSeat(i)->GetWeaponManager()->GetEquippedWeaponObject() : NULL; bool bSeatClear = true; WorldProbe::CShapeTestBoundDesc seatTestDesc; WorldProbe::CShapeTestFixedResults seatTestResults; seatTestDesc.SetResultsStructure(&seatTestResults); seatTestDesc.SetBound(pBoundComposite->GetBound(iFragChild)); seatTestDesc.SetTransform(&matTest); seatTestDesc.SetIncludeFlags(nTestTypes); seatTestDesc.SetExcludeEntities(aEntityExcludeList, iNumExcludeEntities); if(WorldProbe::GetShapeTestManager()->SubmitTest(seatTestDesc)) { WorldProbe::ResultIterator it; for(it = seatTestResults.begin(); it < seatTestResults.end(); ++it) { if(it->GetHitDetected()) { CEntity* pHitEnt = CPhysics::GetEntityFromInst(it->GetHitInst()); if(pHitEnt && pHitEnt->IsCollisionEnabled()) { bSeatClear = false; break; } } } } if(bSeatClear) { u32 uFlags = ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS; // Allowing the seat to collide with peds causes bicycles to do big wheelies when hitting peds. I can't see an issue with preventing the seat hitting peds in this case, as we shouldn'y be walking about on bicycles anyway. if(GetVehicleType() == VEHICLE_TYPE_BICYCLE) { uFlags &= ~(ArchetypeFlags::GTA_PED_TYPE); } if( MI_BOAT_TUG.IsValid() && GetModelIndex() == MI_BOAT_TUG ) { uFlags |= (ArchetypeFlags::GTA_CAMERA_TEST); } if(GetVehicleType() == VEHICLE_TYPE_BIKE || GetVehicleType() == VEHICLE_TYPE_QUADBIKE || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE) { if(NetworkInterface::IsGameInProgress() && ms_enableBikesCameraCollisionFlag) { uFlags |= (ArchetypeFlags::GTA_CAMERA_TEST); } } pBoundComposite->SetIncludeFlags(iFragChild, uFlags); } } else { #if __ASSERT // We presume the seat collision has GTA_VEH_SEAT_INCLUDE_FLAGS set // Assert here to make sure they haven't been overwritten if( MI_BOAT_TUG.IsValid() && GetModelIndex() != MI_BOAT_TUG ) { if( ( GetVehicleType() == VEHICLE_TYPE_BIKE || GetVehicleType() == VEHICLE_TYPE_QUADBIKE || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE ) && NetworkInterface::IsGameInProgress() && ms_enableBikesCameraCollisionFlag ) { physicsAssertf(pBoundComposite->GetIncludeFlags(iFragChild) == (ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS | (ArchetypeFlags::GTA_CAMERA_TEST)),"Seat collision has unexpected include flags"); } else if(GetVehicleType() == VEHICLE_TYPE_BICYCLE) { physicsAssertf(pBoundComposite->GetIncludeFlags(iFragChild) == (ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS & ~(ArchetypeFlags::GTA_PED_TYPE)),"Seat collision has unexpected include flags"); } else { physicsAssertf(pBoundComposite->GetIncludeFlags(iFragChild) == ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS,"Seat collision has unexpected include flags"); } } #endif } } else { // Reset the seat bound if(pBoundComposite->GetIncludeFlags(iFragChild)) { ProcessSeatCollisionBound(true); } // Seat is empty, turn off collision pBoundComposite->SetIncludeFlags(iFragChild,0); } } } } void CVehicle::ProcessSeatCollisionBound(bool bReset) { fragInstGta* pVehFragInst = GetVehicleFragInst(); if(!pVehFragInst) { return; } const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); physicsFatalAssertf(pVehicleModelInfo, "This vehicle has no model info. It is unsafe to continue"); phBoundComposite* pBoundComposite = static_cast(pVehFragInst->GetArchetype()->GetBound()); if(!physicsVerifyf(pBoundComposite->GetTypeAndIncludeFlags(),"No type and include flags allocated for composite bound")) { return; } bool bBoundUpdated = false; const float fOldCompositeRadius = pBoundComposite->GetRadiusAroundCentroid(); for(int i = 0; i < m_SeatManager.GetMaxSeats(); i++) { // Does this seat have collision? int iFragChild = pVehicleModelInfo->GetFragChildForSeat(i); if(iFragChild > -1) { if(m_SeatManager.GetPedInSeat(i)) { if(pBoundComposite->GetIncludeFlags(iFragChild) && m_SeatManager.GetPedInSeat(i)->IsLocalPlayer() && InheritsFromBike() && !((CBike*)this)->IsConsideredStill(GetVelocityIncludingReferenceFrame())) { #if __ASSERT // We presume the seat collision has GTA_VEH_SEAT_INCLUDE_FLAGS set // Assert here to make sure they haven't been overwritten if( ( GetVehicleType() == VEHICLE_TYPE_BIKE || GetVehicleType() == VEHICLE_TYPE_QUADBIKE || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE) && NetworkInterface::IsGameInProgress() && ms_enableBikesCameraCollisionFlag ) { physicsAssertf(pBoundComposite->GetIncludeFlags(iFragChild) == (ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS | (ArchetypeFlags::GTA_CAMERA_TEST)),"Seat collision has unexpected include flags"); } else if(GetVehicleType() == VEHICLE_TYPE_BICYCLE) { physicsAssertf(pBoundComposite->GetIncludeFlags(iFragChild) == (ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS & ~(ArchetypeFlags::GTA_PED_TYPE)),"Seat collision has unexpected include flags"); } else { physicsAssertf(pBoundComposite->GetIncludeFlags(iFragChild) == ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS,"Seat collision has unexpected include flags"); } #endif if(pBoundComposite->GetBound(iFragChild) && pBoundComposite->GetBound(iFragChild)->GetType() == phBound::CAPSULE) { // Make sure we own the bounds before we modify them CloneBounds(); phBoundCapsule *pPassengerBound = (phBoundCapsule *)pBoundComposite->GetBound(iFragChild); CPed *pPassenger = m_SeatManager.GetPedInSeat(i); Vector3 vHeadPos, vRootPos; pPassenger->GetBonePosition(vHeadPos, BONETAG_HEAD); pPassenger->GetBonePosition(vRootPos, BONETAG_ROOT); const Matrix34& matBike = RCC_MATRIX34(GetMatrixRef()); matBike.UnTransform(vHeadPos); matBike.UnTransform(vRootPos); Vector3 vPassengerPos = (vHeadPos + vRootPos) * 0.5f; pPassengerBound->SetCapsuleLength(vHeadPos.Dist(vRootPos)); Assert(pPassenger->GetCapsuleInfo()->IsBiped()); pPassengerBound->SetCapsuleRadius(static_cast(pPassenger->GetCapsuleInfo())->GetRadius()); Matrix34 matPassenger; matPassenger.d = vPassengerPos; matPassenger.b = vHeadPos - vRootPos; matPassenger.b.Normalize(); matPassenger.c.Cross(XAXIS, matPassenger.b); matPassenger.c.Normalize(); matPassenger.a.Cross(matPassenger.b, matPassenger.c); pBoundComposite->SetCurrentMatrix(iFragChild, MATRIX34_TO_MAT34V(matPassenger)); pBoundComposite->SetLastMatrix(iFragChild, MATRIX34_TO_MAT34V(matPassenger)); bBoundUpdated = true; } } // B*2230558: Hard-coded offset for capsule in front-passenger seat of Predator boat. else if (pBoundComposite->GetIncludeFlags(iFragChild) && GetLayoutInfo() && GetLayoutInfo()->GetSeatAnimationInfo(i) && GetLayoutInfo()->GetSeatAnimationInfo(i)->GetSeatCollisionBoundsOffset().IsNonZero() && GetVehicleModelInfo() && GetVehicleModelInfo()->GetModelSeatInfo() && pBoundComposite->GetBound(iFragChild) && pBoundComposite->GetBound(iFragChild)->GetType() == phBound::CAPSULE) { // Make sure we own the bounds before we modify them CloneBounds(); if (pVehFragInst->GetCacheEntry() && pVehFragInst->GetCacheEntry()->HasClonedBounds()) { fragPhysicsLOD* pPhysicsType = pVehFragInst->GetTypePhysics(); if( pPhysicsType && pPhysicsType->GetCompositeBounds()) { phBoundComposite *pOriginalBound = pPhysicsType->GetCompositeBounds(); if(pOriginalBound->GetBound(iFragChild) && pOriginalBound->GetBound(iFragChild)->GetType() == phBound::CAPSULE) { Mat34V offsetMat = pOriginalBound->GetCurrentMatrix(iFragChild); Vector3 vRootPos = VEC3V_TO_VECTOR3(offsetMat.GetCol3()); vRootPos += GetLayoutInfo()->GetSeatAnimationInfo(i)->GetSeatCollisionBoundsOffset(); offsetMat.Setd(VECTOR3_TO_VEC3V(vRootPos)); pBoundComposite->SetCurrentMatrix(iFragChild, offsetMat); pBoundComposite->SetLastMatrix(iFragChild, offsetMat); bBoundUpdated = true; } } } } } else if( bReset && pVehFragInst->GetCacheEntry() && pVehFragInst->GetCacheEntry()->HasClonedBounds() ) // if the bounds have never been modified we don't need to reset them { fragPhysicsLOD* pPhysicsType = pVehFragInst->GetTypePhysics(); if( pPhysicsType && pPhysicsType->GetCompositeBounds()) { phBoundComposite *pOriginalBound = pPhysicsType->GetCompositeBounds(); if(pOriginalBound->GetBound(iFragChild) && pOriginalBound->GetBound(iFragChild)->GetType() == phBound::CAPSULE && pBoundComposite->GetBound(iFragChild) && pBoundComposite->GetBound(iFragChild)->GetType() == phBound::CAPSULE) { phBoundCapsule *pPassengerBound = (phBoundCapsule *)pBoundComposite->GetBound(iFragChild); phBoundCapsule *pOriginalPassengerBound = (phBoundCapsule *)pOriginalBound->GetBound(iFragChild); pPassengerBound->SetCapsuleLength(pOriginalPassengerBound->GetLength()); pBoundComposite->SetCurrentMatrix(iFragChild, pOriginalBound->GetCurrentMatrix(iFragChild)); pBoundComposite->SetLastMatrix(iFragChild, pOriginalBound->GetLastMatrix(iFragChild)); bBoundUpdated = true; } } } } } if(bBoundUpdated) { // Note: The internal motion is ignore during this extent calculation, because we don't want the wheel sweeping motion gets count into the bound extent. pBoundComposite->CalculateCompositeExtents(false, true); const float fNewCompositeRadius = GetPhysArch()->GetBound()->GetRadiusAroundCentroid(); if(GetCurrentPhysicsInst()->IsInLevel()) { if(fNewCompositeRadius != fOldCompositeRadius) { CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetCurrentPhysicsInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL)); } CPhysics::GetLevel()->RebuildCompositeBvh(GetCurrentPhysicsInst()->GetLevelIndex()); } else { pBoundComposite->UpdateBvh(true); } if(REPLAY_ONLY(!CReplayMgr::IsReplayInControlOfWorld() && )( (pVehFragInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid() != pVehFragInst->GetArchetype()->GetBound()->GetRadiusAroundCentroid()) || (!m_nPhysicalFlags.bModifiedBounds && GetVehicleFragInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid() > 5.0f * GetBaseModelInfo()->GetBoundingSphereRadius()))) { Assertf(false, "%s:Physics returned an invalid bounding sphere. Centroid radius=%5.3f (model radius=%5.3f)", GetDebugName(), GetVehicleFragInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid(), GetBaseModelInfo()->GetBoundingSphereRadius()); } } } bool CVehicle::CarPartsCanBreakOff() const { bool bBreakOff = true; // vehicles hidden in a multiplayer cutscene mustn't generate car parts if (NetworkInterface::IsInMPCutscene() && GetNetworkObject() && !GetNetworkObject()->IsLocalFlagSet(CNetObjGame::LOCALFLAG_SHOWINCUTSCENE)) { bBreakOff = false; } return bBreakOff; } const Vector3& CVehicle::GetVelocity() const { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive() && CReplayMgr::IsLoading() == false) return m_replayVelocity; #endif // GTA_REPLAY if(const phCollider* collider = GetCollider()) { return RCC_VECTOR3(collider->GetVelocity()); } else if(IsInactiveForRecording() || GetOwnedBy()==ENTITY_OWNEDBY_CUTSCENE || (m_nVehicleFlags.bIsCargoVehicle && GetParentTrailer())) { return m_vInactivePlaybackVelocity; } else if(CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode && m_vehicleAiLod.GetDummyMode()==VDM_SUPERDUMMY) { return m_vSuperDummyVelocity; } else { return VEC3_ZERO; } } const Vector3& CVehicle::GetAngVelocity() const { if(IsInactiveForRecording() || (m_nVehicleFlags.bIsCargoVehicle && GetParentTrailer())) { return m_vInactivePlaybackAngVelocity; } else { return CPhysical::GetAngVelocity(); } } Vector3 CVehicle::GetReferenceFrameVelocity() const { Vector3 vReferenceFrameVelocity(Vector3::ZeroType); const phCollider* pCollider = GetCollider(); if(pCollider != NULL) { int iNumberOfTouchingWheels = 0; for(int i=0; iGetHitPhysical(); if (pPhysical == NULL) { pPhysical = GetWheel(i)->GetPrevHitPhysical(); } if(pPhysical != NULL) { if( CPhysics::ms_bInArenaMode && pPhysical == GetWheel(i)->GetHitPhysical() && pPhysical->GetIsTypeObject() ) { vReferenceFrameVelocity += pPhysical->GetLocalSpeed( GetWheel( i )->GetHitPos(), true ); } else { vReferenceFrameVelocity += pPhysical->GetVelocity(); } iNumberOfTouchingWheels++; } } } if(iNumberOfTouchingWheels > 0) { vReferenceFrameVelocity /= static_cast(iNumberOfTouchingWheels); } else if(m_pGroundPhysical != NULL) { vReferenceFrameVelocity = RCC_VECTOR3(pCollider->GetReferenceFrameVelocity()); } } return vReferenceFrameVelocity; } Vector3 CVehicle::GetVelocityIncludingReferenceFrame() const { return GetVelocity() - GetReferenceFrameVelocity(); } Vector3 CVehicle::GetRelativeVelocity(const CPed& rPed) const { Vector3 vVehicleVelocity = GetLocalSpeed(VEC3V_TO_VECTOR3(rPed.GetTransform().GetPosition()), true); if (vVehicleVelocity.Mag2() < SMALL_FLOAT) { return VEC3_ZERO; } Vector3 vPedVelocity(rPed.GetVelocity()); if (MagSquared(rPed.GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2()) { vPedVelocity = VEC3V_TO_VECTOR3(rPed.GetGroundVelocityIntegrated()); } Vector3 vVehicleDirection(vVehicleVelocity); float fVehicleVelocityMag = NormalizeAndMag(vVehicleDirection); // Never want to counter more than the velocity of the vehicle itself float fDotProduct = Clamp(vPedVelocity.Dot(vVehicleDirection), 0.0f, fVehicleVelocityMag); // Figure out how much of the ped's velocity is in the same direction as the vehicle's and then subtract that out from the vehicle's velocity vPedVelocity = vVehicleDirection * fDotProduct; return vVehicleVelocity - vPedVelocity; } void CVehicle::SetVelocity(const Vector3& vel) { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive()) m_replayVelocity = vel; #endif // GTA_REPLAY if(IsInactiveForRecording() || GetOwnedBy()==ENTITY_OWNEDBY_CUTSCENE) { m_vInactivePlaybackVelocity.Set(vel); } else { CPhysical::SetVelocity(vel); } Assertf(vel.Mag2() < 62500.0f && IsFiniteAll(RCC_VEC3V(vel)), "SetVelocity got large or non-finite input velocity"); // If this vehicle is in super-dummy mode, then update the super dummy velocity since the vehicle might deactivate sometime later during the frame. if(m_vehicleAiLod.GetDummyMode()==VDM_SUPERDUMMY) { m_vSuperDummyVelocity.Set(vel); } CacheAiData(); // update cached velocity } void CVehicle::SetAngVelocity(const Vector3& vecTurnSpeed) { if(IsInactiveForRecording()) { m_vInactivePlaybackAngVelocity.Set(vecTurnSpeed); } else { CPhysical::SetAngVelocity(vecTurnSpeed); } } void CVehicle::SetInactivePlaybackVelocity(const Vector3& vel) { Assert(IsFiniteAll(RCC_VEC3V(vel))); m_vInactivePlaybackVelocity.Set(vel); } void CVehicle::SetInactivePlaybackAngVelocity(const Vector3& angVel) { Assert(IsFiniteAll(RCC_VEC3V(angVel))); m_vInactivePlaybackAngVelocity.Set(angVel); } void CVehicle::SetSuperDummyVelocity(const Vector3& vel) { Assertf(vel.Mag2() < (62500.0f + 0.0001f) && IsFiniteAll(RCC_VEC3V(vel)), "SetSuperDummyVelocity: %f (squared) got large or non-finite input velocity. Vehicle: %s %s", vel.Mag2(), GetModelName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "none" ); m_vSuperDummyVelocity.Set(vel); CacheAiData(); // update cached velocity } void CVehicle::SetVelocityFromSuperDummyVelocity() { Vector3 vVelocity(m_vSuperDummyVelocity); if (!m_nVehicleFlags.bHasParentVehicle && !InheritsFromTrailer() && !IsNetworkClone()) { //get the vehicle's local Z //then clamp the velocity along that axis //and transform back to worldspace static dev_float s_fVelocityZThreshold = 1.0f; const Vec3V vLocalVelocityOriginal = GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vVelocity)); Vec3V vLocalVelocityClamped = vLocalVelocityOriginal; vLocalVelocityClamped.SetZf(Clamp(vLocalVelocityOriginal.GetZf(), -s_fVelocityZThreshold, s_fVelocityZThreshold)); const Vector3 vClampedVelocityWorldSpace = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(vLocalVelocityClamped)); vVelocity = vClampedVelocityWorldSpace; } if(const phArchetype * pArchetype = GetCurrentPhysicsInst()->GetArchetype()) { float fSpeedSq = vVelocity.Mag2(); float fArchetypeMaxSpeed = pArchetype->GetMaxSpeed(); if(fSpeedSq > square(fArchetypeMaxSpeed)) { aiDisplayf("m_vSuperDummyVelocity (%f,%f,%f) exceeds archetype max speed (%f)",vVelocity.x,vVelocity.y,vVelocity.z,fArchetypeMaxSpeed); vVelocity.Scale(fArchetypeMaxSpeed/sqrtf(fSpeedSq)); } } SetVelocity(vVelocity); } void CVehicle::RestoreVelocityFromInactivePlayback() { Assertf(m_nVehicleFlags.bIsDeactivatedByPlayback, "Trying to restore velocity from inactive playback when the vehicle was NOT deactivated by playback optimization"); if (!m_nVehicleFlags.bIsDeactivatedByPlayback) { return; } CPhysical::SetVelocity(m_vInactivePlaybackVelocity); CPhysical::SetAngVelocity(m_vInactivePlaybackAngVelocity); if(GetIsFixedUntilCollisionFlagSet()) { m_nVehicleFlags.bRestorePlaybackVelAfterCollLoads = true; } CacheAiData(); } void CVehicle::SetForwardSpeed(float fSpeed) { SetForwardSpeedXY( fSpeed ); SetVelocity( VEC3V_TO_VECTOR3(GetTransform().GetB()) * fSpeed); SelectAppropriateGearForSpeed(); if(GetIsRotaryAircraft()) { ((CRotaryWingAircraft*)this)->SetMainRotorSpeed(MAX_ROT_SPEED_HELI_BLADES); } } void CVehicle::SetForwardSpeedXY(float fSpeed) { Vector3 forward( 0.0f, 1.0f, 0.0f ); CWheel* pFrontWheel = GetWheelFromId( VEH_WHEEL_LF ); CWheel* pRearWheel = GetWheelFromId(VEH_WHEEL_LR); if( pFrontWheel && pFrontWheel->GetIsTouching() ) { forward = pFrontWheel->GetHitNormal(); forward.Cross( VEC3V_TO_VECTOR3( GetTransform().GetA() ) ); forward.Normalize(); float steeringRotation = GetSteerAngle() * VEC3V_TO_VECTOR3( GetTransform().GetC() ).Dot( Vector3( 0.0f, 0.0f, 1.0f ) ); static dev_float maxSteeringRotChange = 0.25f; steeringRotation *= maxSteeringRotChange; static dev_float maxVelocityForSteeringIncrease = 400.0f; steeringRotation *= 1.0f - Min( 1.0f, GetVelocity().Mag2() / maxVelocityForSteeringIncrease ); forward.RotateZ( steeringRotation ); forward *= fSpeed; if(pRearWheel && !pRearWheel->GetIsTouching()) { forward.z = GetVelocity().GetZ() > 0.0f ? 0.0f : GetVelocity().GetZ(); } } else { forward = VEC3V_TO_VECTOR3(GetTransform().GetB()); forward.z = 0.0f; forward *= fSpeed; forward.z = GetVelocity().GetZ() > 0.0f ? 0.0f : GetVelocity().GetZ(); } SetVelocity( forward ); SelectAppropriateGearForSpeed(); if(GetIsRotaryAircraft()) { ((CRotaryWingAircraft*)this)->SetMainRotorSpeed(MAX_ROT_SPEED_HELI_BLADES); } } void CVehicle::ApplyInternalForceCg(const Vector3& vecForce) { Assert(vecForce.Mag2() < square(150.0f * rage::Max(1.0f, GetMass()))); Assert(vecForce==vecForce); m_vecInternalForce += vecForce; } void CVehicle::ApplyInternalTorque(const Vector3& vecTorque) { #if __ASSERT Assert(vecTorque==vecTorque); Vector3 vecAngAccel = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecTorque))); vecAngAccel.Multiply(GetInvAngInertia()); Assert(vecAngAccel.Mag2() < square(150.0f)); #endif m_vecInternalTorque += vecTorque; } void CVehicle::ApplyInternalTorque(const Vector3& vecForce, const Vector3& vecOffset) { Vector3 vecTorque(vecOffset); vecTorque.Cross(vecForce); #if __ASSERT Assert(vecForce==vecForce); Assert(vecOffset==vecOffset); Vector3 vecAngAccel = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecTorque))); vecAngAccel.Multiply(GetInvAngInertia()); Assert(vecAngAccel.Mag2() < square(150.0f)); Assert(vecOffset.Mag2() < square(2.0f*GetRadiusAroundLocalOrigin())); #endif m_vecInternalTorque += vecTorque; } void CVehicle::ApplyInternalTorqueAndForce(const Vector3& vecTorque, const Vector3& vecWorldPosition) { // Need a collider for this function to work const phCollider* pCollider = GetCollider(); if(!pCollider) { ActivatePhysics(); pCollider = GetCollider(); if(!pCollider) { physicsAssertf(false, "Failed to activate vehicle"); return; } } // Apply the given torque about the center of mass. ApplyInternalTorque(vecTorque); // Get the inverse inertia matrix, to find the acceleration at the given world position resulting from the torque. Mat33V invInertiaMatrix; pCollider->GetInverseInertiaMatrix(invInertiaMatrix); // Find the angular acceleration resulting from the torque. Vec3V force = Multiply( invInertiaMatrix, Vec3V(vecTorque) ); // Find the offset from the position to the given world position. Vec3V offset = Subtract(Vec3V(vecWorldPosition), pCollider->GetMatrix().GetCol3()); // Find and apply the force needed to keep the given world position from accelerating from the torque. ScalarV v_Mass = pCollider->GetMassV(); force = Cross(force, offset); force = Scale(force, -v_Mass); ApplyInternalForceCg(RCC_VECTOR3(force)); } void CVehicle::AddToFoliageDrag(const int nComponent, const Vector3& vImpactPosition, const Vector3& vCentreOfFoliageBound, const float fRadiusOfFoliageBound) { // Each impact between the vehicle's chassis (or any wheels it has) generates a drag force proportional to // the square of the vehicle's velocity. This value is scaled based on the distance of the contact from the centre of the // plant's spherical bound and on the size of the plant bound (so that bigger plants produce more drag). Vector3 vDragForce = GetVelocity(); // Bikes (and the drone) require a lower drag coefficient to look good since they have less surface area. bool bIsDrone = InheritsFromHeli() && static_cast(this)->GetIsDrone(); float fDragCoeff = InheritsFromBike() || bIsDrone ? static_cast(this)->ms_fFoliageBikeDragCoeff : ms_fFoliageVehicleDragCoeff; float fDragForceMag = -fDragCoeff * vDragForce.Mag2(); vDragForce.Normalize(); vDragForce.Scale(fDragForceMag); Vector3 vOffset = vImpactPosition - VEC3V_TO_VECTOR3(GetTransform().GetPosition()); // Since we will lose the offset information, we must calculate and store the torque here too. Vector3 vFoliageDragTorque; vFoliageDragTorque.Cross(vOffset, vDragForce); // Scale the force based on the distance of the impact from the centre of the bound. float fDistFromCentreSq = (vImpactPosition - vCentreOfFoliageBound).Mag2(); float fFoliageDragDistanceScaleCoeff = InheritsFromBike() ? static_cast(this)->ms_fFoliageBikeDragDistanceScaleCoeff : ms_fFoliageDragDistanceScaleCoeff; float fDistanceMult = 1.0f - fFoliageDragDistanceScaleCoeff*fDistFromCentreSq/(fRadiusOfFoliageBound*fRadiusOfFoliageBound); fDistanceMult = rage::Max(0.0f, fDistanceMult); vDragForce.Scale(fDistanceMult); // Scale the force so that bigger bounds have higher drag. float fFoliageBoundRadiusScaleCoeff = InheritsFromBike() ? static_cast(this)->ms_fFoliageBikeBoundRadiusScaleCoeff : ms_fFoliageBoundRadiusScaleCoeff; vDragForce.Scale(fFoliageBoundRadiusScaleCoeff*fRadiusOfFoliageBound); // Find out if this foliage impact is with an articulated part of the vehicle. If it is, we will apply the force directly // so that doors (for example) get shut by bushes instead of generating a force/torque on the vehicle chassis. bool bIsDoor = false; const CCarDoor* pDoor = NULL; for(int i=0; i < GetNumDoors(); ++i) { pDoor = GetDoor(i); Assert(pDoor); if(pDoor->GetFragChild()==nComponent) { bIsDoor = true; break; } } if(!bIsDoor) { m_VehicleAudioEntity->GetCollisionAudio().ProcessFoliageImpact(vDragForce.Mag2()); } if(pHandling && ((pHandling->hFlags & HF_OFFROAD_ABILITIES_X2) || (pHandling->hFlags & HF_OFFROAD_INCREASED_GRAVITY_NO_FOLIAGE_DRAG)) )// Don't do foliage drag for off road vehicles but calculate the drag for audio. { return; } if(bIsDoor && pDoor) { vDragForce.Scale(ms_fFoliageDoorForceScaleCoeff); float fDoorMass = GetFragInst()->GetTypePhysics()->GetChild(nComponent)->GetUndamagedMass(); vDragForce.ClampMag(0.0f, ms_fFoliageMaxDoorAccel*fDoorMass); ApplyForce(vDragForce, vOffset, nComponent); } else { m_vFoliageDragForce += vDragForce; m_vFoliageDragTorque += vFoliageDragTorque; } } void CVehicle::ApplyFoliageDrag() { // Now we clamp the drag force if necessary before applying it to the vehicle's phCollider. if(m_vFoliageDragForce.IsNonZero() || m_vFoliageDragTorque.IsNonZero()) { Vector3 vVehicleVelocity = GetVelocity(); if(vVehicleVelocity.Mag2() > ms_fMinVehicleSpeedForFoliageDrag*ms_fMinVehicleSpeedForFoliageDrag) { Vector3 vScaledDragForce = m_vFoliageDragForce; // Make sure the foliage drag force we have accumulated so far won't reverse the velocity. float fTimeStep = fwTimer::GetTimeStep()/CPhysics::GetNumTimeSlices(); float fInvTimeStep = 1.0f / fTimeStep; float fMass = GetMass(); Vector3 vMaxForce = -vVehicleVelocity * fInvTimeStep; vMaxForce.Scale(fMass); vScaledDragForce.ClampMag(0.0f, rage::Min(vMaxForce.Mag(), DEFAULT_ACCEL_LIMIT*fMass - 1.0f)); // Clamp the torque to a maximum angular acceleration so we get less spin-outs. Vector3 vScaledDragTorque = m_vFoliageDragTorque; vScaledDragTorque.ClampMag(0.0f, ms_fMaxAngAccel*GetMass()); if(vScaledDragForce.IsNonZero() || vScaledDragTorque.IsNonZero()) { ApplyForceCg(vScaledDragForce); ApplyTorque(vScaledDragTorque); // Give the control pad a shake as we drive through bushes. const float kfMinVehSpeedForRumble = 1.5f; if(ContainsLocalPlayer() && GetVelocity().Mag2() > rage::square(kfMinVehSpeedForRumble)) { float fRumbleScale = m_vFoliageDragForce.Mag2() + m_vFoliageDragTorque.Mag2(); // Scale the rumble intensity based on the drag applied above. const u32 knRumbleDuration = 10; const float kfVehSpeedForRumbleDelaySwitch = 10.0f; const u32 knRumbleDelay = GetVelocity().Mag2()>rage::square(kfVehSpeedForRumbleDelaySwitch) ? 200 : 700; const float kfMinRumbleIntensity = 0.01f; const float kfMaxRumbleIntensity = 0.1f; const float kfMinDragForRumble = 1.0e3; const float kfMaxDragForRumble = 1.0e9; float fRange = kfMaxDragForRumble - kfMinDragForRumble; fRumbleScale -= kfMinDragForRumble; fRumbleScale /= fRange; fRumbleScale = Clamp(fRumbleScale, 0.0f, 1.0f); // Make the rumble scale parameter a quadratic function to give more rumble at the high-drag end. fRumbleScale *= fRumbleScale; float fRumbleIntensity = Clamp(fRumbleScale*kfMaxRumbleIntensity, kfMinRumbleIntensity, kfMaxRumbleIntensity); CControlMgr::StartPlayerPadShakeByIntensity(knRumbleDuration, fRumbleIntensity, knRumbleDelay); } } } m_vFoliageDragForce.Zero(); m_vFoliageDragTorque.Zero(); } } void CVehicle::ApplyWindDisturbance() { Vec3V vVehVel = RCC_VEC3V(GetVelocity()); // check if the car is moving //if (MagSquared(vVehVel).Getf() > (1.0f)) if(IsGreaterThan(MagSquared(vVehVel), ScalarV(V_ONE)).Getb()) { float waterZ = phWindField::GetWaterLevelWld(); Vec3V_ConstRef vPos = GetTransform().GetPosition(); if (vPos.GetZf() >= waterZ) { CBaseModelInfo* pModelInfo = GetBaseModelInfo(); vVehVel *= ScalarVFromF32(WIND_VEH_WIND_DIST_MULT); const float radius = (pModelInfo->GetBoundingBoxMax().z - pModelInfo->GetBoundingBoxMin().z)/1.0f; Vec3V pos = ( GetVehicleType() != VEHICLE_TYPE_BOAT ) ? vPos: SubtractScaled(vPos, Normalize(vVehVel), ScalarVFromF32(2.0f*radius)); g_weather.AddWindSphere(pos, vVehVel, radius); } } } // void CVehicle::AutoLevelAndHeading( float fInAirSteerMult, bool bAdjustRoll, bool bAdjustHeading ) { //Adjust Pitch static dev_float desiredPitch = 0.10f; float currentPitch = GetTransform().GetPitch(); static dev_float autoLevelAmount = 5.0f; ApplyInternalTorque( ((currentPitch-desiredPitch) * autoLevelAmount) * fInAirSteerMult * GetAngInertia().x * VEC3V_TO_VECTOR3(GetTransform().GetA())); //Adjust Roll if(bAdjustRoll && !IsUpsideDown())// don't autoroll when upside down as it looks a bit strange { float currentRoll = GetTransform().GetRoll(); static dev_float autoRollAmount = -5.0f; ApplyInternalTorque( ((currentRoll) * autoRollAmount) * fInAirSteerMult * GetAngInertia().y * VEC3V_TO_VECTOR3(GetTransform().GetB())); } //Adjust Heading if(bAdjustHeading) { static dev_float sfVelocityMax = 50.0f; Vector3 vRelativeVelocity = GetVelocityIncludingReferenceFrame(); float fVelocityMag = vRelativeVelocity.Mag(); float fVelocityScale = fVelocityMag/sfVelocityMax; fVelocityScale = rage::Clamp(fVelocityScale, -1.0f, 1.0f); const float fVelocityHeading = rage::Atan2f(-vRelativeVelocity.x, vRelativeVelocity.y); float fCurrentHeading = GetTransform().GetHeading(); float fDeltaHeading = fCurrentHeading - fVelocityHeading; fDeltaHeading = fwAngle::LimitRadianAngleFast(fDeltaHeading); static dev_float autoHeadAmount = 5.0f; Vector3 vAutoHeadingTorque( ((fDeltaHeading) * autoHeadAmount * fVelocityScale) * fInAirSteerMult * GetAngInertia().z * VEC3V_TO_VECTOR3(GetTransform().GetC()) ); ApplyInternalTorque( vAutoHeadingTorque ); } } bool CVehicle::CanBeInactiveDuringRecording() const { if(BANK_ONLY(sm_bForceRecordedVehicleToBeActive ||) m_nVehicleFlags.bForceActiveDuringPlayback) { return false; } if(BANK_ONLY(sm_bForceRecordedVehicleToBeInactive ||) m_nVehicleFlags.bForceInactiveDuringPlayback) { return true; } return !CVehicleRecordingMgr::IsPlaybackSwitchedToAiForCar(this) && !GetAttachedTrailer() && !InheritsFromTrailer() && !InheritsFromDraftVeh() && !(m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate && m_nFlags.bPossiblyTouchesWater); } bool CVehicle::PlaceOnRoadAdjust(float HeightSampleRangeUp, float HeightSampleRangeDown, bool bJustSetCompression) { if(m_nVehicleFlags.bUseCutsceneWheelCompression) return true; if(IsCollisionLoadedAroundPosition()) { m_nVehicleFlags.bPlaceOnRoadQueued = false; Matrix34 matOld = MAT34V_TO_MATRIX34(GetMatrix()); if( PlaceOnRoadAdjustInternal(HeightSampleRangeUp, HeightSampleRangeDown, bJustSetCompression) ) { UpdateGadgetsAfterTeleport( matOld, false ); return true; } return false; } else { m_nVehicleFlags.bPlaceOnRoadQueued = true; return false; } } /////////////////////////////////////////////////////////////////// void CVehicle::SetHomingLockOnState(CEntity::eHomingLockOnState state) { m_eHomingLockOnState = state; m_nHomingLockOnStateResetFrame = 2; if(m_eHomingLockOnState != HLOnS_NONE) { SetLastTimeHomedAt(fwTimer::GetTimeInMilliseconds()); } } /////////////////////////////////////////////////////////////////// void CVehicle::SetHomingProjectileDistance( float fLockOnDistance ) { if( m_fHomingProjectileDistance == -1.0f || fLockOnDistance < m_fHomingProjectileDistance ) { m_fHomingProjectileDistance = fLockOnDistance; m_nHomingProjectileDistanceResetFrame = 2; } } /////////////////////////////////////////////////////////////////// void CVehicle::UpdateHomingLockedOntoState(CEntity::eHomingLockOnState state) { if(state >= m_eHomingLockedOntoState) { m_eHomingLockedOntoState = state; m_nHomingLockedOntoStateResetFrame = 2; SetLastTimeHomedAt(fwTimer::GetTimeInMilliseconds()); } } /////////////////////////////////////////////////////////////////// void CVehicle::UpdateLockOnStatus() { // Check target lock on state (vehicle locking onto a target) and only update if we need to if( m_eHomingLockOnState != CEntity::HLOnS_NONE ) { m_nHomingLockOnStateResetFrame--; if( m_nHomingLockOnStateResetFrame < 0 ) { m_eHomingLockOnState = CEntity::HLOnS_NONE; m_pLockOnTarget = 0; } } // Check locked onto state (another entity locking onto this vehicle) and only update if we need to if( m_eHomingLockedOntoState != CEntity::HLOnS_NONE ) { m_nHomingLockedOntoStateResetFrame--; if( m_nHomingLockedOntoStateResetFrame < 0 ) { m_eHomingLockedOntoState = CEntity::HLOnS_NONE; NetworkInterface::SetTargetLockOn(nullptr, CEntity::HLOnS_NONE); } } // Homing projectile distance if( m_fHomingProjectileDistance > 0.0f ) { m_nHomingProjectileDistanceResetFrame--; if( m_nHomingProjectileDistanceResetFrame < 0 ) { m_fHomingProjectileDistance = -1.0f; } } } /////////////////////////////////////////////////////////////////// bool CVehicle::DoesVehicleHaveAConvertibleRoofAnimation() const { if( GetVehicleModelInfo() && GetVehicleModelInfo()->GetClipDictionaryIndex() != -1 && GetVehicleModelInfo()->GetConvertibleRoofAnimNameHash() != 0 && GetVehicleType() == VEHICLE_TYPE_CAR ) { return true; } return false; } /////////////////////////////////////////////////////////////////// s32 CVehicle::GetConvertibleRoofState() const { aiTask *pTask = GetIntelligence()->GetTaskManager()->GetTree(VEHICLE_TASK_TREE_SECONDARY)->GetActiveTask(); if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_CONVERTIBLE_ROOF) { CTaskVehicleConvertibleRoof *pConvertibleRoofTask = static_cast(pTask); return pConvertibleRoofTask->GetRoofState(); } else if(m_nVehicleFlags.bRoofLowered) { return CTaskVehicleConvertibleRoof::STATE_LOWERED; } return CTaskVehicleConvertibleRoof::STATE_RAISED; } /////////////////////////////////////////////////////////////////// float CVehicle::GetConvertibleRoofProgress() const { aiTask *pTask = GetIntelligence()->GetTaskManager()->GetTree(VEHICLE_TASK_TREE_SECONDARY)->GetActiveTask(); if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_CONVERTIBLE_ROOF) { CTaskVehicleConvertibleRoof *pConvertibleRoofTask = static_cast(pTask); return pConvertibleRoofTask->GetRoofProgress(); } return 0.0f; } bool CVehicle::IsDoingInstantRoofTransition() const { aiTask *pTask = GetIntelligence()->GetTaskManager()->GetTree(VEHICLE_TASK_TREE_SECONDARY)->GetActiveTask(); if(pTask && pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_CONVERTIBLE_ROOF) { CTaskVehicleConvertibleRoof *pConvertibleRoofTask = static_cast(pTask); return pConvertibleRoofTask->IsInstantTransition(); } return false; } /////////////////////////////////////////////////////////////////// bool CVehicle::ShouldLowerConvertibleRoof() const { mthRandom rnd(GetRandomSeed()); const float fRandom = rnd.GetFloat(); const bool bWantsToKeepTopDownAtNight = fRandom >= 0.9f; const bool bWantsToKeepTopUpDuringDay = fRandom < 0.1f; const bool bWeatherIsNiceEnoughToLowerRoof = g_weather.GetRain() < 0.1f && g_weather.GetSnow() < 0.1f; const bool bIsNight = (CClock::GetHour() > 20 || (CClock::GetHour() > 19 && CClock::GetMinute() > (GetRandomSeed() & 63))) || (CClock::GetHour() < 6 || (CClock::GetHour() < 7 && CClock::GetMinute() < (GetRandomSeed() & 63))); return DoesVehicleHaveAConvertibleRoofAnimation() && bWeatherIsNiceEnoughToLowerRoof && (bWantsToKeepTopDownAtNight || (!bIsNight && !bWantsToKeepTopUpDuringDay)); } /////////////////////////////////////////////////////////////////// void CVehicle::LowerConvertibleRoof(bool bInstantlyLowerRoof) { s32 currRoofState = GetConvertibleRoofState(); if((currRoofState == CTaskVehicleConvertibleRoof::STATE_RAISED) || (currRoofState == CTaskVehicleConvertibleRoof::STATE_RAISING)) //only lower the roof if it is already raised or raising { CTaskVehicleConvertibleRoof *convertibleRoof= rage_new CTaskVehicleConvertibleRoof(bInstantlyLowerRoof); convertibleRoof->SetState(CTaskVehicleConvertibleRoof::State_Lower_Roof); GetIntelligence()->GetTaskManager()->GetTree(VEHICLE_TASK_TREE_SECONDARY)->SetTask(convertibleRoof, VEHICLE_TASK_SECONDARY_ANIM); } } /////////////////////////////////////////////////////////////////// void CVehicle::RaiseConvertibleRoof(bool bInstantlyRaiseRoof) { s32 currRoofState = GetConvertibleRoofState(); if((currRoofState == CTaskVehicleConvertibleRoof::STATE_LOWERED) || (currRoofState == CTaskVehicleConvertibleRoof::STATE_LOWERING)) //only raise the convertible roof if it is already lowered or lowering { CTaskVehicleConvertibleRoof *convertibleRoof= rage_new CTaskVehicleConvertibleRoof(bInstantlyRaiseRoof); convertibleRoof->SetState(CTaskVehicleConvertibleRoof::State_Raise_Roof); GetIntelligence()->GetTaskManager()->GetTree(VEHICLE_TASK_TREE_SECONDARY)->SetTask(convertibleRoof, VEHICLE_TASK_SECONDARY_ANIM); } } /////////////////////////////////////////////////////////////////// void CVehicle::ToggleRoofCollision(bool bSwitchOn) { if(GetBoneIndex(VEH_ROOF)!=-1 && GetFragInst()) { int roofComponent = GetFragInst()->GetType()->GetComponentFromBoneIndex( GetVehicleFragInst()->GetCurrentPhysicsLOD(), GetBoneIndex(VEH_ROOF)); phBoundComposite* pBoundComp = static_cast(GetVehicleFragInst()->GetArchetype()->GetBound()); if(roofComponent > 0 && roofComponent < pBoundComp->GetNumBounds()) { if(bSwitchOn) { pBoundComp->SetIncludeFlags(roofComponent, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES|ArchetypeFlags::GTA_VEHICLE_BVH_TYPE); } else { pBoundComp->SetIncludeFlags(roofComponent, INCLUDE_FLAGS_NONE); } } } } /////////////////////////////////////////////////////////////////// s32 CVehicle::GetTransformationState() const { aiTask *pTask = GetIntelligence()->GetTaskManager()->GetTree( VEHICLE_TASK_TREE_SECONDARY )->GetActiveTask(); if( pTask && pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_TRANSFORM_TO_SUBMARINE ) { CTaskVehicleTransformToSubmarine *pTransformTask = static_cast(pTask); return pTransformTask->GetTransformState(); } return CTaskVehicleTransformToSubmarine::STATE_CAR; } /////////////////////////////////////////////////////////////////// void CVehicle::TransformToSubmarine( bool bInstantlyTransform ) { s32 currTransformState = GetTransformationState(); if( currTransformState != CTaskVehicleTransformToSubmarine::STATE_SUBMARINE || bInstantlyTransform ) { CTaskVehicleTransformToSubmarine *transform = rage_new CTaskVehicleTransformToSubmarine( bInstantlyTransform ); transform->SetState( CTaskVehicleTransformToSubmarine::State_Transform_To_Submarine ); GetIntelligence()->GetTaskManager()->GetTree( VEHICLE_TASK_TREE_SECONDARY )->SetTask( transform, VEHICLE_TASK_SECONDARY_ANIM ); } } /////////////////////////////////////////////////////////////////// void CVehicle::TransformToCar( bool bInstantlyTransform ) { if (!m_bSubmarineCarTransformPrevented) { s32 currTransformState = GetTransformationState(); if( currTransformState != CTaskVehicleTransformToSubmarine::STATE_CAR || bInstantlyTransform ) { CTaskVehicleTransformToSubmarine *transform = rage_new CTaskVehicleTransformToSubmarine( bInstantlyTransform ); transform->SetState( CTaskVehicleTransformToSubmarine::State_Transform_To_Car ); GetIntelligence()->GetTaskManager()->GetTree( VEHICLE_TASK_TREE_SECONDARY )->SetTask( transform, VEHICLE_TASK_SECONDARY_ANIM ); } } else { gnetDebug1("CVehicle::TransformToCar prevented due to m_bSubmarineCarTransformPrevented being set!"); } } /////////////////////////////////////////////////////////////////// void CVehicle::SetTransformRateForCurrentAnimation(float fRate) { aiTask *pTask = GetIntelligence()->GetTaskManager()->GetTree( VEHICLE_TASK_TREE_SECONDARY )->GetActiveTask(); if( pTask && pTask->GetTaskType() == CTaskTypes::TASK_VEHICLE_TRANSFORM_TO_SUBMARINE ) { CTaskVehicleTransformToSubmarine* pTransformTask = static_cast(pTask); s32 iTransformState = pTransformTask->GetTransformState(); if (iTransformState == CTaskVehicleTransformToSubmarine::STATE_TRANSFORMING_TO_CAR || iTransformState == CTaskVehicleTransformToSubmarine::STATE_TRANSFORMING_TO_SUBMARINE) { pTransformTask->SetTransformRateForNextAnimation(fRate); } } } /////////////////////////////////////////////////////////////////// s32 CVehicle::GetSeatIndexFromDoor(const CCarDoor* pDoor) const { int iSeatIndex = -1; if (pDoor) { int iDoorBoneIndex = GetBoneIndex(pDoor->GetHierarchyId()); if (iDoorBoneIndex > -1) { s32 iEntryPointIndex = GetVehicleModelInfo()->GetModelSeatInfo()->GetEntryPointFromBoneIndex(iDoorBoneIndex); if (iEntryPointIndex > -1) { const CEntryExitPoint* pEntryExitPoint = GetVehicleModelInfo()->GetModelSeatInfo()->GetEntryExitPoint(iEntryPointIndex); iSeatIndex = pEntryExitPoint ? pEntryExitPoint->GetSeat(SA_directAccessSeat) : -1; } } } return iSeatIndex; } /////////////////////////////////////////////////////////////////// bool CVehicle::DoesVehicleHaveATransformAnimation() const { if( GetVehicleModelInfo() && GetVehicleModelInfo()->GetClipDictionaryIndex() != -1 && GetVehicleModelInfo()->GetConvertibleRoofAnimNameHash() != 0 && GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR ) { return true; } return false; } /////////////////////////////////////////////////////////////////// CVehicle* CVehicle::GetAttachParentVehicle() const { if(CEntity * pAttachParent = static_cast(GetAttachParent())) { return pAttachParent->GetIsTypeVehicle() ? static_cast(pAttachParent) : NULL; } return NULL; } CVehicle* CVehicle::GetAttachParentVehicleDummyOrPhysical() const { CVehicle* pDummyAttachParent = GetDummyAttachmentParent(); if( pDummyAttachParent ) { return pDummyAttachParent; } return GetAttachParentVehicle(); } CTrailer* CVehicle::GetAttachedTrailer() const { // See if we have a connected vehicle that is a trailer for(int i = 0; i < GetNumberOfVehicleGadgets(); i++) { CVehicleGadget *pVehicleGadget = GetVehicleGadget(i); if(pVehicleGadget->GetType() == VGT_TRAILER_ATTACH_POINT) { CVehicleTrailerAttachPoint *pTrailerAttachPoint = static_cast(pVehicleGadget); CTrailer *pTrailer = pTrailerAttachPoint->GetAttachedTrailer(this); return pTrailer; } } return NULL; } CTrailer* CVehicle::GetAttachedTrailerOrDummyTrailer() const { return (GetDummyAttachmentChild(0) && GetDummyAttachmentChild(0)->InheritsFromTrailer()) ? static_cast(GetDummyAttachmentChild(0)) : GetAttachedTrailer(); } bool CVehicle::IsEntityAttachedToTrailer(const CEntity* pEntity) { if (!pEntity) { return false; } const fwEntity* pfwAttachParent = pEntity->GetAttachParent(); const CVehicle* pAttachedVehicle = pfwAttachParent && pfwAttachParent->GetType() == ENTITY_TYPE_VEHICLE ? static_cast(pfwAttachParent) : NULL; if( pAttachedVehicle && pAttachedVehicle->InheritsFromTrailer() ) { return true; } return false; } void CVehicle::SetIsCargoVehicle(bool isCargoVehicle) { if(GetVehicleFragInst() && GetVehicleFragInst()->IsInLevel()) { if(m_nVehicleFlags.bIsCargoVehicle != isCargoVehicle) { if(isCargoVehicle) { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetVehicleFragInst()->GetLevelIndex(),true); GetVehicleFragInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,true); } else { CPhysics::GetLevel()->SetInactiveCollidesAgainstInactive(GetVehicleFragInst()->GetLevelIndex(),false); GetVehicleFragInst()->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL,false); SetInactivePlaybackVelocity(VEC3_ZERO); SetInactivePlaybackAngVelocity(VEC3_ZERO); CacheAiData(); } m_nVehicleFlags.bIsCargoVehicle = isCargoVehicle; } } } bool CVehicle::IsAirExplosion(const Vector3& expPos) { float distCheck = 2.5f; if (GetIsRotaryAircraft()) { distCheck = 10.0f; } WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetStartAndEnd(expPos, expPos - Vector3(0.0f, 0.0f, distCheck)); probeDesc.SetResultsStructure(NULL); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES); probeDesc.SetExcludeEntity(NULL); if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST)) { return false; } return true; } bool CVehicle::IsCamInsideVehicleInterior() { const bool playerIsInside = ContainsLocalPlayer(); bool isCurrentVehicle = playerIsInside && ( camInterface::IsRenderedCameraInsideVehicle() || CVfxHelper::GetCamInVehicleFlag() ); // Or if this vehicle instance has been tagged to be rendered last isCurrentVehicle |= (CVfxHelper::GetRenderLastVehicle() == this); return isCurrentVehicle; } void CVehicle::SetRespotCounter(u32 counter) { if (counter == 0) { ResetRespotCounter(); } else if (NetworkInterface::IsGameInProgress()) //only allow the m_nNetworkRespotCounter to be set > 0 in a network game { if (m_nNetworkRespotCounter == 0 && counter > 0) { m_nNetworkRespotCounter = (u16)counter; ProcessRespotCounter(0.0f); } else { m_nNetworkRespotCounter = (u16)counter; } } } void CVehicle::ResetRespotCounter(bool bForceResetDamagedByAnything) { bool bAlphaRamping = false; if (NetworkInterface::IsGameInProgress() && GetNetworkObject()) { CNetObjPhysical* pNetObjPhysical = static_cast(GetNetworkObject()); if (pNetObjPhysical) { bAlphaRamping = pNetObjPhysical->IsAlphaRamping(); if (bAlphaRamping) { pNetObjPhysical->StopAlphaRamping(); } } } if (!bAlphaRamping) { //Ensure the ramping is disabled for the vehicle if we just transitioned to SP from MP CPostScan::RemoveAlphaOverride(this); // restore the occupants visibility for(s32 seatIndex = 0; seatIndex < GetSeatManager()->GetMaxSeats(); seatIndex++) { CPed *occupant = GetSeatManager()->GetPedInSeat(seatIndex); if (occupant) { CPostScan::RemoveAlphaOverride(occupant); } } } if (bForceResetDamagedByAnything || (m_nNetworkRespotCounter > 0)) m_nPhysicalFlags.bNotDamagedByAnything = false; //Ensure that SetNoCollision for the vehicle is reset when ResetRespotCounter is called. Only //do this when the no collision entity is the vehicle itself, so we don't stomp over other uses //of SetNoCollision() if(GetNoCollisionEntity() == this) { SetNoCollision(0, 0); } m_nNetworkRespotCounter = 0; m_uSecondaryRespotOverrideTimeout = 0; } bool CVehicle::ShouldPreRenderBeyondSmallRadius() const { #if FORCELIGHTSON return true; #else if(GetVehicleType() == VEHICLE_TYPE_HELI) { return true; } if( m_nVehicleFlags.GetIsSirenOn() || m_OverrideLights == FORCE_CAR_LIGHTS_ON || (m_nVehicleFlags.bEngineOn && (fwTimer::GetTimeInMilliseconds() < m_FlashHeadLights || ShouldLightsBeOn()))) { return true; } return false; #endif } float CVehicle::CalcFrontRearSelector(eHierarchyId iCurrentWheel, eHierarchyId iFrontId, eHierarchyId iRearId) { float fFrontBackSelector = 0.0f; const CVehicleModelInfo* pModelInfo = GetVehicleModelInfo(); phBound* pBound = static_cast(pModelInfo->GetFragType()->GetPhysics(0)->GetCompositeBounds()); Assert(pBound); // Figure out how front / backwards this wheel is compared to the front and back wheels float fFrontY = 0.0f; float fRearY = 0.0f; if(pModelInfo->GetBoneIndex(iFrontId) > -1) fFrontY = GetLocalMtx(pModelInfo->GetBoneIndex(iFrontId)).d.y; else fFrontY = pBound->GetBoundingBoxMax().GetYf();//just use the front of the bounding box if we dont have a front wheel. Added for trailers as they have no front wheels if(pModelInfo->GetBoneIndex(iRearId) > -1) fRearY = GetLocalMtx(pModelInfo->GetBoneIndex(iRearId)).d.y; else fRearY = pBound->GetBoundingBoxMin().GetYf();//just use the rear of the bounding box if we dont have a rear wheel fFrontBackSelector = GetLocalMtx(pModelInfo->GetBoneIndex(iCurrentWheel)).d.y - fRearY; fFrontBackSelector *= 2.0f / (fFrontY - fRearY); fFrontBackSelector -= 1.0f; return fFrontBackSelector; } #define DISTANCE_LIGHT 35.0f #define DISTANCE_MODERATE 25.0f #define DISTANCE_HEAVY 16.0f #define DISTANCE_VERY_HEAVY 8.0f int CVehicle::ms_maxHdVehiclesInSp = MAX_HD_VEHICLES; int CVehicle::ms_maxHdVehiclesInMp = MAX_HD_VEHICLES_IN_MP; float CVehicle::ms_afVehicleHdDistanceMP[CContinuityState::SCENE_STREAMING_PRESSURE_TOTAL] = { DISTANCE_LIGHT, //SCENE_STREAMING_PRESSURE_LIGHT DISTANCE_MODERATE, //SCENE_STREAMING_PRESSURE_MODERATE DISTANCE_HEAVY, //SCENE_STREAMING_PRESSURE_HEAVY DISTANCE_VERY_HEAVY //SCENE_STREAMING_PRESSURE_VERY_HEAVY }; float CVehicle::ms_afVehicleHdDistanceSP[CContinuityState::SCENE_STREAMING_PRESSURE_TOTAL] = { DISTANCE_LIGHT, //SCENE_STREAMING_PRESSURE_LIGHT DISTANCE_MODERATE, //SCENE_STREAMING_PRESSURE_MODERATE DISTANCE_HEAVY, //SCENE_STREAMING_PRESSURE_HEAVY DISTANCE_VERY_HEAVY //SCENE_STREAMING_PRESSURE_VERY_HEAVY }; void CVehicle::RetrieveAndCacheCloudTunables() { ms_maxHdVehiclesInSp = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_VEHICLES_MAX_SP", 0x590e82ed), MAX_HD_VEHICLES); ms_maxHdVehiclesInMp = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_VEHICLES_MAX_MP", 0x9b62c820), MAX_HD_VEHICLES_IN_MP); //multiplayer values ms_afVehicleHdDistanceMP[CContinuityState::SCENE_STREAMING_PRESSURE_LIGHT] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_LIGHT_MP", 0xfc933d7b), DISTANCE_LIGHT); ms_afVehicleHdDistanceMP[CContinuityState::SCENE_STREAMING_PRESSURE_MODERATE] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_MODERATE_MP", 0x4ac6be14), DISTANCE_MODERATE); ms_afVehicleHdDistanceMP[CContinuityState::SCENE_STREAMING_PRESSURE_HEAVY] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_HEAVY_MP", 0xb4d1e281), DISTANCE_HEAVY); ms_afVehicleHdDistanceMP[CContinuityState::SCENE_STREAMING_PRESSURE_VERY_HEAVY] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_VERY_HEAVY_MP", 0xc7dd6304), DISTANCE_VERY_HEAVY); //singleplayer values ms_afVehicleHdDistanceSP[CContinuityState::SCENE_STREAMING_PRESSURE_LIGHT] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_LIGHT_SP", 0x9324f16c), DISTANCE_LIGHT); ms_afVehicleHdDistanceSP[CContinuityState::SCENE_STREAMING_PRESSURE_MODERATE] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_MODERATE_SP", 0xe5f46401), DISTANCE_MODERATE); ms_afVehicleHdDistanceSP[CContinuityState::SCENE_STREAMING_PRESSURE_HEAVY] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_HEAVY_SP", 0xf0d9ce74), DISTANCE_HEAVY); ms_afVehicleHdDistanceSP[CContinuityState::SCENE_STREAMING_PRESSURE_VERY_HEAVY] = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("HD_DIST_VERY_HEAVY_SP", 0x26b09cf1), DISTANCE_VERY_HEAVY); //camera collision flag for bikes ms_enableBikesCameraCollisionFlag = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("VEHICLE_ENABLE_BIKES_CAMERA_COLLISION_FLAG_MP", 0x597380f4), true); //camera collision flag for bikes ms_rampCarCollisionDamageScale = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("VEHICLE_RAMP_CAR_COLLISION_DAMAGE_SCALE", 0x3F1BFDCB), RAMP_CAR_DEFAULT_DAMAGE); ms_scoopCarCollisionDamageScale = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("VEHICLE_SCOOP_CAR_COLLISION_DAMAGE_SCALE", 0x7B418EF3), SCOOP_CAR_DEFAULT_DAMAGE); //modify collisions with phantoms and trailerlarge ms_modifyPhantomCollisionWithPeds = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("VEHICLE_MODIFY_PHANTOM_COLLISION_WITH_PEDS", 0xCC6BC48E), true); //double buoyancy on Patrolboat when on a single time slice ms_enablePatrolBoatSingleTimeSliceFix = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("VEHICLE_PATROL_BOAT_SINGLE_TIME_SLICE_FIX", 0xB23755CC), true); } void CVehicle::GetHDModeRequests(bool &requestDraw, bool & requestStream) { const Vector3& camPos = camInterface::GetPos(); float distSqr = camPos.Dist2(VEC3V_TO_VECTOR3(GetTransform().GetPosition())); const CVehicle *pPlayerVehicle = CGameWorld::FindFollowPlayerVehicle(); const bool forceHd = m_nVehicleFlags.bForceHd || m_nVehicleFlags.bScriptForceHd; CVehicleModelInfo *pVehModelInfo = GetVehicleModelInfo(); static dev_bool bForceHiDetail = false; #if __BANK distSqr = distSqr / CVehicleFactory::ms_vehicleHiStreamScaling; #endif //__BANK float hdRenderDist = pVehModelInfo->GetHDDist() * GetLodMultiplier() * g_LodScale.GetGlobalScale(); float hdRequestDist = pVehModelInfo->GetHDDist() * GetLodMultiplier(); //if we are in a garage make sure everything is in HD, the max vehicles is only 13 // 10 cars and 3 bicycles. if (NetworkInterface::IsGameInProgress() && !PARAM_NoHDGarage.Get()) { CPed* pFollowPlayer = CGameWorld::FindFollowPlayer(); if (pFollowPlayer && pFollowPlayer->GetIsInInterior()) { CInteriorProxy* pFollowPlayerInterior = CInteriorProxy::GetFromLocation(pFollowPlayer->GetInteriorLocation()); if ((pFollowPlayerInterior->GetNameHash() == ATSTRINGHASH("v_garagel", 0x9bd1c95d)) || (pFollowPlayerInterior->GetNameHash() == ATSTRINGHASH("v_garagem", 0x9137b429)) || (pFollowPlayerInterior->GetNameHash() == ATSTRINGHASH("v_garages", 0x5988c45c)) || (pFollowPlayerInterior->GetNameHash() == ATSTRINGHASH("hei_dlc_garage_high_new", 0x82270d43)) ) { // make all vehicles in MP garage HD hdRenderDist = 100.0f; hdRequestDist = 100.0f; } } } requestDraw = forceHd || (distSqr < rage::square(hdRenderDist)) || (this == pPlayerVehicle) || bForceHiDetail || m_nVehicleFlags.bRequestDrawHD; BANK_ONLY(gDrawListMgr->AdjustVehicleHD(false, requestDraw)); #if RSG_ORBIS || RSG_DURANGO || RSG_PC // make virtually all vehicles hd // don't allow the streaming distance to fall below 8, which are last gen values. CContinuityState::eSceneStreamingPressure state = g_ContinuityMgr.GetSceneStreamingPressure(); if (NetworkInterface::IsGameInProgress()) hdRequestDist = ms_afVehicleHdDistanceMP[state]; else hdRequestDist = ms_afVehicleHdDistanceSP[state]; #endif //streaming is based on the new distance requestStream = forceHd || (distSqr < rage::square(hdRequestDist)) || (this == pPlayerVehicle) || bForceHiDetail || m_nVehicleFlags.bRequestDrawHD; BANK_ONLY(gDrawListMgr->AdjustVehicleHD(false, requestStream)); u32 numAllowed = (pPlayerVehicle || ms_requestedHdVeh) ? ms_maxHdVehiclesInSp : ms_maxHdVehiclesInSp - 1; if (NetworkInterface::IsGameInProgress()) { numAllowed = (pPlayerVehicle || ms_requestedHdVeh) ? ms_maxHdVehiclesInMp : (ms_maxHdVehiclesInMp - 1); } if (!CutSceneManager::GetInstance()->IsCutscenePlayingBack() && !forceHd) { // don't request hd models if we have no more room for another hd vehicle if (m_vehicleLodState != VLS_HD_AVAILABLE && m_vehicleLodState != VLS_HD_REQUESTED && CVehicleModelInfo::GetNumHdVehicles() >= numAllowed) { requestStream = false; requestDraw = false; } if (CVehicleModelInfo::GetNumHdVehicles() > numAllowed && this != ms_requestedHdVeh && !CVehiclePopulation::IsVehicleInteresting(this)) { requestStream = false; requestDraw = false; } } // if this instance was specifically requested to be bumped to HD we allow it only if it was requested at most 2 seconds if (ms_requestedHdVeh == this && fwTimer::GetTimeInMilliseconds() < ms_lastTimeRequestedHd + 2000) requestDraw = true; // if the lod is clamped to anything we don't request HD geometry, the highest model the lod can be clamped to is 0 if (m_clampedRenderLod > -1) requestDraw = false; // Forget about HD if they're currently disabled. Memory woes and all. requestDraw &= !ms_DisableHDVehicles; requestStream &= !ms_DisableHDVehicles; if (this!=pPlayerVehicle && !pVehModelInfo->GetAreHDFilesLoaded()) { if(g_ContinuityMgr.IsSceneStreamingUnderHeavyPressure() || !CTexLod::AllowAmbientRequests()) { requestStream = false; } } } // processing required for requesting & switching to HD models for this vehicle instance void CVehicle::Update_HD_Models() { STRVIS_AUTO_CONTEXT(strStreamingVisualize::HDVEHICLES); CVehicleModelInfo *pVehModelInfo = (CVehicleModelInfo*)GetBaseModelInfo(); #if __ASSERT Assert(pVehModelInfo); Assert(pVehModelInfo->GetModelType() == MI_TYPE_VEHICLE); Assert(pVehModelInfo->GetHasHDFiles()); #endif // Do we need a high model ? // stream HD models in based on a certain distance // but only draw them based on bool streamHighModel, drawHighModel; #if NAVMESH_EXPORT if(CNavMeshDataExporter::WillExportCollision()) { streamHighModel = false; drawHighModel = false; } else #endif { GetHDModeRequests(drawHighModel, streamHighModel); BANK_ONLY( gDrawListMgr->AdjustVehicleHD(false, drawHighModel) ); } #if ENABLE_FRAG_OPTIMIZATION if (streamHighModel) { GiveFragCacheEntry(); } #endif switch(m_vehicleLodState){ case VLS_HD_NONE: if (streamHighModel) { pVehModelInfo->AddToHDInstanceList((size_t)this); m_vehicleLodState = VLS_HD_REQUESTED; } break; case VLS_HD_REQUESTED: CTexLod::AddHDTxdRequest(pVehModelInfo->GetAssetLocation(), GetModelIndex()); if( pVehModelInfo->GetAreHDFilesLoaded() && drawHighModel){ // HD has loaded, so let's set up this instance only if the HD drawing // is requested too GetVehicleDrawHandler().ShaderEffect_HD_CreateInstance(pVehModelInfo, this); UpdateBodyColourRemapping(false); m_vehicleLodState = VLS_HD_AVAILABLE; } //this has been bumped back down to non HD else if(!streamHighModel) { pVehModelInfo->RemoveFromHDInstanceList((size_t)this); m_vehicleLodState = VLS_HD_NONE; } break; case VLS_HD_AVAILABLE: CTexLod::AddHDTxdRequest(pVehModelInfo->GetAssetLocation(), GetModelIndex()); if (!drawHighModel) { GetVehicleDrawHandler().ShaderEffect_HD_DestroyInstance(this); UpdateBodyColourRemapping(false); m_vehicleLodState = VLS_HD_REMOVING; } break; case VLS_HD_REMOVING: if (!streamHighModel) { pVehModelInfo->RemoveFromHDInstanceList((size_t)this); m_vehicleLodState = VLS_HD_NONE; } else { m_vehicleLodState = VLS_HD_REQUESTED; } break; case VLS_HD_NA : default : vehicleAssert(false); // illegal cases break; } } // // // void CVehicle::InitAi() { m_pIntelligence = CVehicleIntelligenceFactory::GetFactory()->Create(this); //always add a no driver task aiTask *pTask = rage_new CTaskVehicleNoDriver(CTaskVehicleNoDriver::NO_DRIVER_TYPE_INITIALLY_CREATED); m_pIntelligence->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false); m_CachedAiData.m_VelocityAndXYSpeed.ZeroComponents(); } void CVehicle::PostCreationAiUpdate() { PreGameWorldAiUpdate(this, true); StartAiUpdate(this); ProcessIntelligence(true, fwTimer::GetTimeStep()); aiDeferredTasks::RunAll(); FinishAiUpdate(this, true); } // // // void CVehicle::ProcessIntelligence(bool fullUpdate, float fFullUpdateTimeStep) { #if GTA_REPLAY if(CReplayMgr::IsEditModeActive()) { return; } #endif // GTA_REPLAY // don't process vehicles in the reuse pool if ( GetIsInReusePool() ) { return; } if (m_iFullThrottleRecoveryTime > NetworkInterface::GetNetworkTime()) { m_bFullThrottleActive = true; if (!IsEngineOn()) { SwitchEngineOn(); } m_vehControls.SetBrake(0); m_vehControls.SetHandBrake(false); m_vehControls.SetThrottle(1); if (!GetDriver() || !GetDriver()->IsPlayer()) { m_vehControls.SetSteerAngle(0); m_vehControls.SetSecondSteerAngle(0); } m_nVehicleFlags.bTasksAllowDummying = false; } else { if (m_bFullThrottleActive) { m_bFullThrottleActive = false; if (!GetDriver()) { m_vehControls.SetThrottle(0); m_vehControls.SetHandBrake(true); } } m_pIntelligence->Process(fullUpdate, fFullUpdateTimeStep); } if(fullUpdate) { UpdateLockOnStatus(); // Clear this flag, since we have now done a full intelligence update. m_nVehicleFlags.bLodForceUpdateUntilNextAiUpdate = false; } m_nVehicleFlags.bHasProcessedAIThisFrame = true; } void CVehicle::ProcessPostCamera() { if (m_nVehicleFlags.bForcePostCameraAiSecondaryTaskUpdate && !IsNetworkClone()) { //ProcessIntelligence(true, fwTimer::GetTimeStep()); GetIntelligence()->GetTaskManager()->GetTree(VEHICLE_TASK_TREE_SECONDARY)->Process(fwTimer::GetTimeStep()); } if (m_nVehicleFlags.bForcePostCameraAnimUpdateUseZeroTimeStep) { StartAnimUpdate(0.0f); } else if (m_nVehicleFlags.bForcePostCameraAnimUpdate) { StartAnimUpdate(fwTimer::GetTimeStep()); } m_nVehicleFlags.bForcePostCameraAiSecondaryTaskUpdate = 0; m_nVehicleFlags.bForcePostCameraAnimUpdate = 0; m_nVehicleFlags.bForcePostCameraAnimUpdateUseZeroTimeStep = 0; if( NetworkInterface::IsGameInProgress() && IsNetworkClone()) { if( InheritsFromHeli()) { CHeli* pHeli = static_cast(this); if(pHeli->GetSearchLight() && pHeli->GetSearchLightOn()) { pHeli->GetSearchLight()->ProcessSearchLight(pHeli); } } } } bool CVehicle::IsAnimated() { CMoveVehicle& moveVehicle = GetMoveVehicle(); return moveVehicle.IsAnimating() || moveVehicle.IsMechanismActive() || moveVehicle.GetInputBuffer().GetInternalBuffer().GetCount() != 0; } void CVehicle::StartAnimUpdate(float fTimeStep) { #if ENABLE_FRAG_OPTIMIZATION if(!GetHasFragCacheEntry()) { return; } #endif #if ANIMUPDATE_IF_ANIMATED if(!IsAnimated()) { StopUpdatingThroughAnimQueue(); return; } #endif CPhysical::StartAnimUpdate(fTimeStep); } void CVehicle::StartAnimUpdateAfterCamera(float fTimeStep) { #if ENABLE_FRAG_OPTIMIZATION if(!GetHasFragCacheEntry()) { return; } #endif #if ANIMUPDATE_IF_ANIMATED if(!IsAnimated()) { return; } #endif CPhysical::StartAnimUpdateAfterCamera(fTimeStep); } void CVehicle::SetPlayerDriveTask(CPed *ASSERT_ONLY(pPed)) { Assert(IsDriver(pPed) && pPed->IsPlayer());//only create a vehicle driving task if the ped is a player and the driver. aiTask *pTask = NULL; //goto the state that corresponds with this vehicle type. switch(GetVehicleType()) { case VEHICLE_TYPE_CAR: case VEHICLE_TYPE_QUADBIKE: case VEHICLE_TYPE_DRAFT: pTask = rage_new CTaskVehiclePlayerDriveAutomobile; break; case VEHICLE_TYPE_SUBMARINECAR: pTask = rage_new CTaskVehiclePlayerDriveSubmarineCar; break; case VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE: case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE: pTask = rage_new CTaskVehiclePlayerDriveAmphibiousAutomobile; break; case VEHICLE_TYPE_PLANE: pTask = rage_new CTaskVehiclePlayerDrivePlane; break; case VEHICLE_TYPE_HELI: pTask = rage_new CTaskVehiclePlayerDriveHeli; break; case VEHICLE_TYPE_BOAT: pTask = rage_new CTaskVehiclePlayerDriveBoat; break; case VEHICLE_TYPE_BIKE: case VEHICLE_TYPE_BICYCLE: pTask = rage_new CTaskVehiclePlayerDriveBike; break; case VEHICLE_TYPE_SUBMARINE: pTask = rage_new CTaskVehiclePlayerDriveSubmarine; break; case VEHICLE_TYPE_AUTOGYRO: pTask = rage_new CTaskVehiclePlayerDriveAutogyro; break; case VEHICLE_TYPE_TRAILER: // Proxy automobile task (forwards the update when attached to a vehicle) // pTask = rage_new CTaskVehiclePlayerDriveAutomobile(true); break; case VEHICLE_TYPE_BLIMP: pTask = rage_new CTaskVehiclePlayerDriveHeli; break; case VEHICLE_TYPE_TRAIN: pTask = rage_new CTaskVehiclePlayerDriveTrain; break; default: Assertf(0,"Vehicle type not supported"); } if( pTask ) { Assertf(!IsNetworkClone(), "Trying to set a player drive task on a clone vehicle"); GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false ASSERT_ONLY(, true)); // B*1814990: Prevent the player vehicle from going into timeslice LOD mode. m_nVehicleFlags.bLodShouldUseTimeslicing = false; m_nVehicleFlags.bLodCanUseTimeslicing = false; } } void CVehicle::CacheAiData() { Vec3V vel = VECTOR3_TO_VEC3V(GetVelocity()); Assert(IsFiniteAll(vel)); m_CachedAiData.m_VelocityAndXYSpeed = Vec4V(vel, MagXY(vel)); #if __ASSERT #if GTA_REPLAY m_WhichVelocityFlags.Set(EDIT_MODE_ACTIVE, CReplayMgr::IsEditModeActive()); m_WhichVelocityFlags.Set(REPLAY_IS_LOADING, CReplayMgr::IsLoading()); #endif m_WhichVelocityFlags.Set(HAS_COLLIDER, GetCollider() != NULL); m_WhichVelocityFlags.Set(DISABLE_SUPERDUMMY_PHYSICS, CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode); m_WhichVelocityFlags.Set(IS_SUPERDUMMY, m_vehicleAiLod.GetDummyMode() == VDM_SUPERDUMMY); m_WhichVelocityFlags.Set(IS_DOING_PLAYBACK, CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this)); #endif } void CVehicle::InvalidateCachedAiData() { Vec4V invalidVel(V_NAN); m_CachedAiData.m_VelocityAndXYSpeed = invalidVel; } #if __ASSERT void CVehicle::VerifyCache() { atFixedBitSet32 newVelFlags; #if GTA_REPLAY newVelFlags.Set(EDIT_MODE_ACTIVE, CReplayMgr::IsEditModeActive()); newVelFlags.Set(REPLAY_IS_LOADING, CReplayMgr::IsLoading()); #endif newVelFlags.Set(HAS_COLLIDER, GetCollider() != NULL); newVelFlags.Set(DISABLE_SUPERDUMMY_PHYSICS, CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode); newVelFlags.Set(IS_SUPERDUMMY, m_vehicleAiLod.GetDummyMode() == VDM_SUPERDUMMY); newVelFlags.Set(IS_DOING_PLAYBACK, CVehicleRecordingMgr::IsPlaybackGoingOnForCar(this)); REPLAY_ONLY(if(!CReplayMgr::IsEditModeActive())) if (!vehicleVerifyf(IsEqualAll(m_CachedAiData.m_VelocityAndXYSpeed.GetXYZ(), VECTOR3_TO_VEC3V(GetVelocity())), "%p %p %d: Velocities changed during update! (%f %f %f) (%f %f %f) %s dummy/phys (%f %f %f)/(%f %f %f). Cached vel flags were 0x%08x, current flags are 0x%08x", this, GetCollider(), TIME.GetFrameCount(), m_CachedAiData.m_VelocityAndXYSpeed.GetXf(), m_CachedAiData.m_VelocityAndXYSpeed.GetYf(), m_CachedAiData.m_VelocityAndXYSpeed.GetZf(), GetVelocity().x, GetVelocity().y, GetVelocity().z, GetModelName(), m_vSuperDummyVelocity.x, m_vSuperDummyVelocity.y, m_vSuperDummyVelocity.z, CPhysical::GetVelocity().x, CPhysical::GetVelocity().y, CPhysical::GetVelocity().z, m_WhichVelocityFlags.GetRawBlock(0), newVelFlags.GetRawBlock(0))) { Displayf("Vehicle %p, Position (%.2f, %.2f, %.2f), Dummy State: %s", this, GetVehiclePosition().GetXf(), GetVehiclePosition().GetYf(), GetVehiclePosition().GetZf() , IsSuperDummy() ? "SuperDummy" : (IsDummy() ? "Dummy" : "Real")); //print the task tree GetIntelligence()->PrintTasks(); } } #endif void CVehicle::StartAiUpdate(CVehicle* pVehicle) { if (!pVehicle) { return; } pVehicle->m_nVehicleFlags.bHasProcessedAIThisFrame = false; if (pVehicle->GetIntelligence()->m_pCarThatIsBlockingUs) { pVehicle->GetIntelligence()->m_pCarThatIsBlockingUs->GetIntelligence()->m_iNumVehsWeAreBlockingThisFrame++; } SUPERCONDUCTOR.GetVehicleConductor().UpdateSirensInfo(pVehicle); } void CVehicle::StartAiUpdate() { // This may get called twice in one frame. When using the scenario point editor, we call this once before debug-spawning a // scenario vehicle to make sure the spawned vehicle can see cached AI velocities for other vehicles. #if DEBUG_DRAW ms_debugDraw.ClearAll(); #endif //DEBUG_DRAW SUPERCONDUCTOR.GetVehicleConductor().CleanUpSirensInfo(); CVehicle::Pool* pool = CVehicle::GetPool(); s32 i = (s32) pool->GetSize(); while(i--) { CVehicle* pVehicle = pool->GetSlot(i); if(pVehicle) { PrefetchBuffer<4>(pVehicle); // doesn't save anything for the next function call (which calls GetVelocity() and gets an L2 miss loading the vptr) PrefetchBuffer(&pVehicle->GetMatrixRef()); // but now these lines should be free because they PrefetchBuffer(&(pVehicle->m_CachedAiData)); // will be loaded by the time we need the data pVehicle->CacheAiData(); StartAiUpdate(pVehicle); } } } //this is actually called later in the frame than StartAiUpdate now, //since that happens before vehicle population void CVehicle::PreGameWorldAiUpdate(CVehicle* pVehicle, const bool bFullUpdate) { if (!pVehicle) { return; } if (bFullUpdate) { pVehicle->m_nVehicleFlags.bPreventBeingDummyThisFrame = false; pVehicle->m_nVehicleFlags.bPreventBeingSuperDummyThisFrame = false; pVehicle->m_nVehicleFlags.bPreventBeingDummyUnlessCollisionLoadedThisFrame = false; pVehicle->m_nVehicleFlags.bProducingSlipStream = false; } } void CVehicle::PreGameWorldAiUpdate() { CVehicle::Pool* pool = CVehicle::GetPool(); s32 i = (s32) pool->GetSize(); while(i--) { CVehicle* pVehicle = pool->GetSlot(i); if(pVehicle) { const bool bFullUpdate = CVehicleAILodManager::ShouldDoFullUpdate(*pVehicle); PreGameWorldAiUpdate(pVehicle, bFullUpdate); } } } void CVehicle::FinishAiUpdate(CVehicle* pVehicle, const bool bFullUpdate) { if (!pVehicle) { return; } //also reset the flags that we used to do //after the task update but still inside ProcessControl //doing it here helps us avoid update order issues if (bFullUpdate) { if (pVehicle->GetIntelligence()->GetRecordingNumber() >= 0 && !CVehicleRecordingMgr::GetUseCarAI(pVehicle->GetIntelligence()->GetRecordingNumber())) { pVehicle->GetIntelligence()->UpdateIsThisADriveableCar(); } else { const bool bWasOvertakingStationaryCar = pVehicle->m_nVehicleFlags.bIsOvertakingStationaryCar; pVehicle->GetIntelligence()->UpdateIsThisADriveableCar(); pVehicle->GetIntelligence()->UpdateIsThisAParkedCar(); pVehicle->GetIntelligence()->UpdateIsThisAStationaryCar(); if (bWasOvertakingStationaryCar) { pVehicle->m_nVehicleFlags.bForceOtherVehiclesToStopForThisVehicle = true; } pVehicle->m_nVehicleFlags.bOtherCarsShouldCheckTrajectoryBeforeSpawning = false; pVehicle->m_nVehicleFlags.bActAsIfHasSirenOn = false; pVehicle->m_nVehicleFlags.bTellOthersToHurry = false; } } pVehicle->m_nVehicleFlags.bRequestDrawHD = false; pVehicle->m_nVehicleFlags.bScriptForceHd = false; //reset this here as it will be incremented in StartAiUpdate pVehicle->GetIntelligence()->m_iNumVehsWeAreBlockingThisFrame = 0; } void CVehicle::FinishAiUpdate() { CVehicle::Pool* pool = CVehicle::GetPool(); s32 i = (s32) pool->GetSize(); while(i--) { CVehicle* pVehicle = pool->GetSlot(i); if (pVehicle) { #if __ASSERT if (!pVehicle->GetIsScheduled()) { pVehicle->VerifyCache(); } #endif const bool bFullUpdate = !pVehicle->IsNetworkClone() && CVehicleAILodManager::ShouldDoFullUpdate(*pVehicle); FinishAiUpdate(pVehicle, bFullUpdate); pVehicle->InvalidateCachedAiData(); } } CVehicle::UpdateVehicleBreaksPerSecond(); } void CVehicle::UpdateLightsOnFlagsForAllVehicles() { CVehicle *pVeh = NULL; s32 j = (s32) CVehicle::GetPool()->GetSize(); while(j--) { pVeh = CVehicle::GetPool()->GetSlot(j); if(pVeh) { pVeh->UpdateLightsOnFlag(); } } } void CVehicle::ProcessAfterCameraUpdate() { PF_PUSH_TIMEBAR("Vehicles"); // Process each vehicle's ai. CVehicle *pVeh = NULL; s32 j = (s32) CVehicle::GetPool()->GetSize(); while(j--) { pVeh = CVehicle::GetPool()->GetSlot(j); if(pVeh) { pVeh->ProcessPostCamera(); } } #if FPS_MODE_SUPPORTED //If we're in first person mode and we're driving a vehicle, lets remove it from the height map if(camInterface::IsRenderedCameraInsideVehicle() || CVfxHelper::GetCamInVehicleFlag()) { CPed *pLocalPlayer = CPedFactory::GetFactory()->GetLocalPlayer(); if(pLocalPlayer) { CVehicle* pLocalVehicle = pLocalPlayer->GetMyVehicle(); if(pLocalVehicle && pLocalVehicle->ContainsLocalPlayer()) { pLocalVehicle->GetRenderPhaseVisibilityMask().ClearFlag(VIS_PHASE_MASK_RAIN_COLLISION_MAP); pLocalVehicle->UpdateWorldFromEntity(); } } } #endif PF_POP_TIMEBAR(); } void CVehicle::ProcessPostVisibility() { #if FPS_MODE_SUPPORTED //If we're in first person mode and we're driving a vehicle, lets set the height map flag back because it was //disabled before visibility if(camInterface::IsRenderedCameraInsideVehicle() || CVfxHelper::GetCamInVehicleFlag()) { CPed *pLocalPlayer = CPedFactory::GetFactory()->GetLocalPlayer(); if(pLocalPlayer) { CVehicle* pLocalVehicle = pLocalPlayer->GetMyVehicle(); if(pLocalVehicle && pLocalVehicle->ContainsLocalPlayer()) { pLocalVehicle->GetRenderPhaseVisibilityMask().SetFlag(VIS_PHASE_MASK_RAIN_COLLISION_MAP); pLocalVehicle->UpdateWorldFromEntity(); } } } #endif } void CVehicle::UpdateInSpatialArray() { Vec3V_ConstRef position = GetMatrixRef().GetCol3ConstRef(); ms_spatialArray->Update(m_spatialArrayNode, position.GetXf(), position.GetYf(), position.GetZf()); // Could do something like this instead, if we want to keep all flags up to date: // u32 typeFlags = 0; // ms_spatialArray->UpdateWithTypeFlags(m_spatialArrayNode, position.GetXf(), position.GetYf(), position.GetZf(), ~0U, typeFlags); } dev_float VELOCITY_TO_GENERATE_SHOCKING_EVENT = 2.0f; // Update the timer determining how dangerously the vehicle is being driven void CVehicle::UpdateDangerousDriverEvents(float fTimeStep) { #if GTA_REPLAY if (CReplayMgr::IsEditModeActive()) { return; } #endif // GTA_REPLAY // Tyres are screeching at this point, generate a mad driver event# if( m_nVehicleFlags.bDrivenDangerouslyThisUpdate && (GetAiVelocity().XYMag2() > rage::square(VELOCITY_TO_GENERATE_SHOCKING_EVENT) )) { m_nVehicleFlags.bDrivenDangerouslyThisUpdate = false; m_fDrivenDangerouslyTime += fTimeStep; static float TIME_TO_GENERATE_SHOCKING_EVENT = 0.3f; if( m_fDrivenDangerouslyTime >= TIME_TO_GENERATE_SHOCKING_EVENT ) { if(ShouldGenerateMinorShockingEvent() && GetDriver()) { // Check if this is a bicycle if( GetVehicleType() == VEHICLE_TYPE_BICYCLE ) { CEventShockingMadDriverBicycle ev(*GetDriver()); CShockingEventsManager::Add(ev); } else // general case { if (m_nVehicleFlags.bDrivenDangerouslyExtreme) { CEventShockingMadDriverExtreme ev(*GetDriver()); CShockingEventsManager::Add(ev); } else { CEventShockingMadDriver ev(*GetDriver()); CShockingEventsManager::Add(ev); } } } } } else { m_nVehicleFlags.bDrivenDangerouslyThisUpdate = false; m_nVehicleFlags.bDrivenDangerouslyExtreme = false; m_fDrivenDangerouslyTime = 0.0f; } //Check for this vehicle being on top of another vehicle and add shocking event CVehicle* pVehicleDrivingOn = GetVehicleDrivingOn(); if(pVehicleDrivingOn) { //Check if the driver is valid. CPed* pDriver = GetDriver(); if(pDriver) { //Check the vehicle is under the other vehicle float heightdiff = VEC3V_TO_VECTOR3(GetTransform().GetPosition()).z - VEC3V_TO_VECTOR3(pVehicleDrivingOn->GetTransform().GetPosition()).z; if (heightdiff > 0.0f) { CEventShockingCarOnCar ev(*pDriver, pVehicleDrivingOn); CShockingEventsManager::Add(ev); //Check if the player is driving on a law enforcement vehicle. //Also check to see if the player is a cop. if(pDriver->IsLocalPlayer() && pVehicleDrivingOn->IsLawEnforcementVehicle() && !pDriver->IsLawEnforcementPed()) { //Report the crime. CCrime::ReportCrime(CRIME_STAND_ON_POLICE_CAR, pVehicleDrivingOn, pDriver); } } } } } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : GiveWarning // PURPOSE : Flashes the headlights or gives a shout, etc. // PARAMETERS : None // RETURNS : Nothing ///////////////////////////////////////////////////////////////////////////////// void CVehicle::GiveWarning() { if (GetVehicleType() == VEHICLE_TYPE_BIKE) { if(GetDriver()) { GetDriver()->NewSay("WATCH_IT"); } } else { const bool bLongHorn = (GetRandomSeed() & 255) < 128; #if __DEV const CPed* pPlayer = CGameWorld::FindLocalPlayer(); if (pPlayer && CPlayerInfo::GetDisplayRecklessDriving()) { Color32 honkColor = bLongHorn ? Color_red : Color_yellow; grcDebugDraw::Sphere(pPlayer->GetTransform().GetPosition(), 1.5f, honkColor, false); grcDebugDraw::Arrow(GetVehiclePosition(), pPlayer->GetTransform().GetPosition(), 1.0f, honkColor); } #endif //__DEV const u32 hornTypeHash = bLongHorn ? ATSTRINGHASH("HELDDOWN",0x839504CB) : ATSTRINGHASH("AGGRESSIVE",0xC91D8B07); PlayCarHorn(false, hornTypeHash); static dev_u32 s_nFlashLightsUnderThreshold = 32; if ((GetRandomSeed() & 255) < s_nFlashLightsUnderThreshold) { m_FlashHeadLights = fwTimer::GetTimeInMilliseconds() + 1000; } } } s32 CVehicle::GetLiveryId() const { Assert(m_pDrawHandler); CCustomShaderEffectVehicle *pShaderEffectVehicle = static_cast(m_pDrawHandler->GetShaderEffect()); if(pShaderEffectVehicle) { return pShaderEffectVehicle->GetLiveryId(); } return -1; } void CVehicle::SetLiveryModFrag(strLocalIndex fragSlotIdx) { CCustomShaderEffectVehicle *pShaderEffectVehicle = static_cast(m_pDrawHandler->GetShaderEffect()); if (pShaderEffectVehicle) { strLocalIndex oldLiveryFrag = pShaderEffectVehicle->GetLiveryFragSlotIdx(); if (oldLiveryFrag.IsValid()) { // if modifying the livery frag, add a temporary render ref to keep it alive for a bit - just in case... g_FragmentStore.AddRef(oldLiveryFrag, REF_RENDER); gDrawListMgr->AddRefCountedModuleIndex(oldLiveryFrag.Get(), &g_FragmentStore); } pShaderEffectVehicle->SetLiveryFrag(fragSlotIdx); CVehicleDrawHandler *pVehDrawHandler = (CVehicleDrawHandler*)GetDrawHandlerPtr(); Assert(pVehDrawHandler->GetShaderEffect()->GetType()==CSE_VEHICLE); CCustomShaderEffectVehicle *pOrigVehCSE = (CCustomShaderEffectVehicle*)pVehDrawHandler->GetShaderEffect(); Assert(pOrigVehCSE); if(pVehDrawHandler->GetShaderEffectSD()) { CCustomShaderEffectVehicle *pVehCseHD = pOrigVehCSE; CCustomShaderEffectVehicle *pVehCseSD = (CCustomShaderEffectVehicle*)pVehDrawHandler->GetShaderEffectSD(); // copy CSE HD->std settings: pVehCseSD->CopySettings(pVehCseHD, this); } } } s32 CVehicle::GetLivery2Id() const { Assert(m_pDrawHandler); CCustomShaderEffectVehicle *pShaderEffectVehicle = static_cast(m_pDrawHandler->GetShaderEffect()); if(pShaderEffectVehicle) { return pShaderEffectVehicle->GetLivery2Id(); } return -1; } void CVehicle::SetLiveryId(s32 id) { Assert(m_pDrawHandler); CCustomShaderEffectVehicle *pShaderEffectVehicle = static_cast(m_pDrawHandler->GetShaderEffect()); if(pShaderEffectVehicle) { #if __ASSERT if( id > -1 ) { CVehicleModelInfo *modelInfo = static_cast(GetBaseModelInfo()); Assertf(id <= modelInfo->GetLiveriesCount(),"trying to apply livery id[%d] to %s which only has %d",id,GetModelName(),modelInfo->GetLiveriesCount()); } #endif // __ASSERT pShaderEffectVehicle->SetLiveryId(id); } } void CVehicle::SetLivery2Id(s32 id2) { Assert(m_pDrawHandler); CCustomShaderEffectVehicle *pShaderEffectVehicle = static_cast(m_pDrawHandler->GetShaderEffect()); if(pShaderEffectVehicle) { #if __ASSERT if( id2 > -1 ) { CVehicleModelInfo *modelInfo = static_cast(GetBaseModelInfo()); Assertf(id2 <= modelInfo->GetLiveries2Count(),"trying to apply livery2 id[%d] to %s which only has %d",id2,GetModelName(),modelInfo->GetLiveries2Count()); } #endif // __ASSERT pShaderEffectVehicle->SetLivery2Id(id2); } } #if !__FINAL void CVehicle::SetDebugName(const char* strName) { strncpy( m_strDebugName, strName, DEBUG_VEH_NAME_STRING_LENGTH-1); m_strDebugName[DEBUG_VEH_NAME_STRING_LENGTH-1] = '\0'; } const char* CVehicle::GetDebugName() const { netObject *networkObject = GetNetworkObject(); if (networkObject) { return networkObject->GetLogName(); } return m_strDebugName; } #endif bool CVehicle::IsTurretSeat(s32 iSeatIndex) const { if (IsSeatIndexValid(iSeatIndex)) { const CVehicleSeatAnimInfo* pSeatAnimInfo = GetSeatAnimationInfo(iSeatIndex); if (pSeatAnimInfo && pSeatAnimInfo->IsTurretSeat()) { return true; } } return false; } s32 CVehicle::GetFirstTurretSeat() const { // Find first turret seat const s32 iNumSeats = GetVehicleModelInfo()->GetModelSeatInfo()->GetNumSeats(); for (s32 iSeatIndex = 0; iSeatIndex < iNumSeats; ++iSeatIndex) { const CVehicleSeatAnimInfo* pSeatAnimInfo = GetSeatAnimationInfo(iSeatIndex); if (pSeatAnimInfo && pSeatAnimInfo->IsTurretSeat()) { return iSeatIndex; } } return -1; } s32 CVehicle::GetDriverSeat() const { const CVehicleModelInfo* pVehicleModelInfo = GetVehicleModelInfo(); if( pVehicleModelInfo == NULL ) return 0; // Vehicle not streamed in if( !pVehicleModelInfo->m_data ) return 0; return pVehicleModelInfo->GetModelSeatInfo()->GetDriverSeat(); } s32 CVehicle::GetDirectEntryPointIndexForPed(const CPed* pPed) const { const s32 iSeatIndex = GetSeatManager()->GetPedsSeatIndex(pPed); if (IsSeatIndexValid(iSeatIndex)) { return GetDirectEntryPointIndexForSeat(iSeatIndex); } return -1; } s32 CVehicle::GetDirectEntryPointIndexForSeat(s32 iSeat) const { for( s32 i = 0; i < GetNumberEntryExitPoints(); i++ ) { //! Note: For multiple access points, this will return the seat at index zero. if( GetEntryExitPoint(i)->GetSeat(SA_directAccessSeat) == iSeat ) { return i; } } return -1; } s32 CVehicle::ChooseAppropriateJumpOutDirectEntryPointIndexForTurretDirection(s32 iSeat) const { for( s32 i = 0; i < GetNumberEntryExitPoints(); i++ ) { //! Note: For multiple access points, this will return the seat at index zero. if( GetEntryExitPoint(i)->GetSeat(SA_directAccessSeat) == iSeat ) { const CVehicleWeaponMgr* pVehWeaponMgr = GetVehicleWeaponMgr(); if (!pVehWeaponMgr) return -1; const CTurret* pTurret = pVehWeaponMgr->GetFirstTurretForSeat(iSeat); if (!pTurret) return -1; const CVehicleEntryPointInfo* pEntryPointInfo = GetVehicleModelInfo()->GetModelSeatInfo()->GetLayoutInfo()->GetEntryPointInfo(i); CVehicleEntryPointInfo::eSide side = pEntryPointInfo->GetVehicleSide(); const float fLocalHeading = -pTurret->GetLocalHeading(*this); switch (side) { case CVehicleEntryPointInfo::SIDE_LEFT: { TUNE_GROUP_FLOAT(TURRET_TUNE, JUMP_OUT_MIN_SIDE_LEFT, 0.785f, -PI, PI, 0.01f); TUNE_GROUP_FLOAT(TURRET_TUNE, JUMP_OUT_MAX_SIDE_LEFT, 2.356f, -PI, PI, 0.01f); if (fLocalHeading >= JUMP_OUT_MIN_SIDE_LEFT && fLocalHeading <= JUMP_OUT_MAX_SIDE_LEFT) { return i; } } break; case CVehicleEntryPointInfo::SIDE_RIGHT: { TUNE_GROUP_FLOAT(TURRET_TUNE, JUMP_OUT_MIN_SIDE_RIGHT, -2.356f, -PI, PI, 0.01f); TUNE_GROUP_FLOAT(TURRET_TUNE, JUMP_OUT_MAX_SIDE_RIGHT, -0.785f, -PI, PI, 0.01f); if (fLocalHeading >= JUMP_OUT_MIN_SIDE_RIGHT && fLocalHeading <= JUMP_OUT_MAX_SIDE_RIGHT) { return i; } } break; case CVehicleEntryPointInfo::SIDE_REAR: { TUNE_GROUP_FLOAT(TURRET_TUNE, JUMP_OUT_MAX_REAR_IGNORE_HEADING, 0.785f, -PI, PI, 0.01f); // Flip the heading so we're comparing to negative vehicle fwd const float fLocalHeadingForRear = fwAngle::LimitRadianAngle(fLocalHeading + PI); const float fLocalHeadingMag = Abs(fLocalHeadingForRear); if (fLocalHeadingMag > JUMP_OUT_MAX_REAR_IGNORE_HEADING) { return i; } } break; default: break; } } } return -1; } s32 CVehicle::ChooseAppropriateEntryPointForOnBoardJack(const CPed& rPed, s32 iSeat) const { Vector3 vPedPos = VEC3V_TO_VECTOR3(rPed.GetTransform().GetPosition()); Vector3 vEntryPos; Quaternion qEntryRot; s32 iBestEntryPoint = -1; float fClosestEntryDistSqd = 999999.0f; for( s32 i = 0; i < GetNumberEntryExitPoints(); i++ ) { //! Note: For multiple access points, this will return the seat at index zero. if( GetEntryExitPoint(i)->GetSeat(SA_directAccessSeat) == iSeat ) { if (CTaskVehicleFSM::ComputeTargetTransformInWorldSpace(*this, vEntryPos, qEntryRot, i, CExtraVehiclePoint::ON_BOARD_JACK)) { const float fDistSqd = (vEntryPos - vPedPos).Mag2(); if (fDistSqd < fClosestEntryDistSqd) { iBestEntryPoint = i; fClosestEntryDistSqd = fDistSqd; } } } } return iBestEntryPoint; } s32 CVehicle::FindSeatIndexForFirstHatchEntryPoint() const { for (s32 i = 0; i < GetNumberEntryExitPoints(); i++) { const CVehicleEntryPointInfo* pEntryInfo = GetEntryInfo(i); if (pEntryInfo && pEntryInfo->GetIsPlaneHatchEntry() && GetEntryExitPoint(i)) { return GetEntryExitPoint(i)->GetSeat(SA_directAccessSeat); } } return -1; } s32 CVehicle::GetIndirectEntryPointIndexForSeat(s32 iSeat) const { for( s32 i = 0; i < GetNumberEntryExitPoints(); i++ ) { if( GetEntryExitPoint(i)->IsSingleSeatAccess()) { if(GetEntryExitPoint(i)->GetSeat(SA_indirectAccessSeat) == iSeat) { return i; } else if (GetEntryExitPoint(i)->GetSeat(SA_indirectAccessSeat2) == iSeat) { return i; } } else { if(GetDirectEntryPointIndexForSeat(iSeat) != i) { for(int s = 0; s < GetEntryExitPoint(i)->GetSeatAccessor().GetNumSeatsAccessible(); ++s) { if(GetVehicleModelInfo()->GetModelSeatInfo()->GetShuffleSeatForSeat(i, s) == iSeat) { return i; } } } } } return -1; } bool CVehicle::ShouldFixIfNoCollisionLoadedAroundPosition() { if(CVehicleAILodManager::ms_bFreezeParkedSuperDummyWhenCollisionsNotLoaded && IsParkedSuperDummy()) { return true; } // really network clones should never be fixed waiting for collision, as the fixed by network checks manage // this. These more targetted checks are being done to limit the scope of a bug fix late in the GTAV project bool bClonePlaneOrHeliContainingPlayer = IsNetworkClone() && ContainsPlayer() && (InheritsFromPlane() || InheritsFromHeli()); bool bCloneInWaterLocallyOrRemotely = IsNetworkClone() && (GetIsInWater() || static_cast(GetNetworkObject())->IsNetworkObjectInWater()); bool result = (PopTypeIsMission() || m_nVehicleFlags.bShouldFixIfNoCollision || m_nVehicleFlags.bNoDummyPathAvailable) && !( IsDummy() || IsRunningCarRecording() || (InheritsFromTrailer() && GetAttachParent() != NULL && static_cast(GetAttachParent())->GetIsTypeVehicle() && static_cast(GetAttachParent())->IsRunningCarRecording()) ) && !ContainsLocalPlayer() && GetAllowFreezeWaitingOnCollision() && !bClonePlaneOrHeliContainingPlayer && !bCloneInWaterLocallyOrRemotely; // B*1890538: Don't allow planes that are in the air to fix when there's no collision around it. // GTAV - B*1904678 - Also do this for helicopters and blimps. bool flyingAirCraft = ( InheritsFromPlane() && static_cast(this)->IsInAir() ) || ( InheritsFromHeli() && static_cast(this)->IsInAir() ); result = result && !flyingAirCraft; return result; } void CVehicle::OnFixIfNoCollisionLoadedAroundPosition( bool bFix ) { if( bFix ) { DeActivatePhysics(); } else { #if __ASSERT if(CVehicleAILodManager::ms_bFreezeParkedSuperDummyWhenCollisionsNotLoaded && !IsCollisionLoadedAroundPosition()) { Assertf(!IsParkedSuperDummy(), "Parked super dummy should stay fixed while collision is not loaded. vehicle 0x%p, fixedUntilCollisionFlag %x, bShouldFixIfNoCollision %x, AllowFreezeIfNoCollision %x, ShouldFixIfNoCollisionLoadedAroundPosition %x", this, GetIsFixedUntilCollisionFlagSet(), m_nVehicleFlags.bShouldFixIfNoCollision, m_nPhysicalFlags.bAllowFreezeIfNoCollision, ShouldFixIfNoCollisionLoadedAroundPosition()); } #endif if (PopTypeGet() != POPTYPE_RANDOM_PARKED && (!IsSuperDummy() || !CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode)) { ActivatePhysics(); if(m_nVehicleFlags.bRestoreVelAfterCollLoads) { SetForwardSpeed(m_fSpeedToRestoreAfterFix); m_nVehicleFlags.bRestoreVelAfterCollLoads = false; m_fSpeedToRestoreAfterFix = 0.0f; } if(m_nVehicleFlags.bRestorePlaybackVelAfterCollLoads) { RestoreVelocityFromInactivePlayback(); m_nVehicleFlags.bRestorePlaybackVelAfterCollLoads = false; } } if(m_nVehicleFlags.bPlaceOnRoadQueued && !IsNetworkClone() && m_nVehicleFlags.bShouldFixIfNoCollision) { if(!GetAttachParent()) { if(PlaceOnRoadAdjust()) { m_nVehicleFlags.bPlaceOnRoadQueued = false; } else { SetIsFixedWaitingForCollision(true); } } } } } bool CVehicle::IsCollisionLoadedAroundPosition() { if(CVehicleAILodManager::ms_bFreezeParkedSuperDummyWhenCollisionsNotLoaded && IsParkedSuperDummy()) { return CPhysical::IsCollisionLoadedAroundPosition(); } return IsDummy() || CPhysical::IsCollisionLoadedAroundPosition(); } bool CVehicle::CanPedsStandOnTop() const { if( m_bDisablePlayerCanStandOnTop ) { return false; } //! HACK - REMOVE post V. //! url:bugstar:1599165. Shouldn't be able to stand on burrito vans. Can't edit metadata, so hardcoding. Use handling //! data as that is consistent across all burrito types. static u32 nBurritoHash = atStringHash("BURRITO"); CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData(GetVehicleModelInfo()->GetHandlingId()); if(pHandling && (pHandling->m_handlingName.GetHash() == nBurritoHash)) { return false; } if( MI_CAR_WASTELANDER.IsValid() && GetModelIndex() == MI_CAR_WASTELANDER ) { return true; } //! HACK - REMOVE post V. return GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_PEDS_CAN_STAND_ON_TOP); } int CVehicle::TestVehicleBoundForCollision(const Matrix34* pMatrix, const CVehicle* pVehicle, u32 nModelIndex, const CEntity** pExceptions, int nNumExceptions, WorldProbe::CShapeTestResults* pBoundTestResults /*Can be NULL*/, u32 includeFlags, bool bUseBoxBound, bool bDoHeightFixup) { Assert(pMatrix); if(!pMatrix) return false; const phBound * pCarBound = NULL; const fragType * pFragType = NULL; if( bUseBoxBound && pVehicle->InheritsFromAutomobile() && static_cast(pVehicle)->GetDummyInst()) { pCarBound = static_cast(pVehicle)->GetDummyInst()->GetArchetype()->GetBound(); } else { fwModelId modelId((strLocalIndex(nModelIndex))); CBaseModelInfo* pModelInfo = modelId.IsValid() ? (CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(nModelIndex)))) : NULL; if(pVehicle && pVehicle->GetFragInst()) { Assert(pVehicle->GetFragInst()->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE); pCarBound = static_cast(pVehicle->GetFragInst()->GetArchetype()->GetBound()); pFragType = pVehicle->GetFragInst()->GetType(); } else if(pModelInfo && pModelInfo->GetFragType()) { pFragType = pModelInfo->GetFragType(); pCarBound = pFragType->GetPhysics(0)->GetCompositeBounds(); } } if(pCarBound) { const phBound* pTempBound = pCarBound; if( pFragType ) { #if 0 && !NEW_MIDPHASE_COLLISION_2 phBoundComposite* pTempBound = static_cast(pCarBound->Clone()); for(int i=0; iGetPhysics(0)->GetNumChildren(); i++) { if(pFragType->GetPhysics(0)->GetAllChildren()[i]->GetOwnerGroupPointerIndex()!=0) { pTempBound->SetBound(i, NULL); } } #endif } WorldProbe::CShapeTestBoundDesc boundTestDesc; //this needs to be the same scope as SubmitTest phBoundBox* pVehicleBoundingBox = NULL; if (bUseBoxBound) { pVehicleBoundingBox = rage_new phBoundBox(VEC3V_TO_VECTOR3(pTempBound->GetBoundingBoxSize())); //the bb has been initialized above to the actual bounds of the vehicle. //if we want to adjust the height, instead of moving the matrix root as we did before, //we'll shorten the min in the Z direction so we've got a shorter car, not one that's //just been translated up into the air Vec3V vBoundBoxMin = pTempBound->GetBoundingBoxMin(); const Vec3V vBoundBoxMax = pTempBound->GetBoundingBoxMax(); if (bDoHeightFixup) { TUNE_GROUP_FLOAT(VEH_DUMMY_CONVERT, heightFixupAuto, 0.35f, 0.0f, 2.0f, 0.01f); TUNE_GROUP_FLOAT(VEH_DUMMY_CONVERT, heightFixupTrailer, 0.5f, 0.0f, 2.0f, 0.01f); const float fHeightFixup = pVehicle->InheritsFromTrailer() ? heightFixupTrailer : heightFixupAuto; vBoundBoxMin.SetZf(rage::Min(-pVehicle->GetHeightAboveRoad() + fHeightFixup, vBoundBoxMax.GetZf())); } pVehicleBoundingBox->SetBoundingBox(vBoundBoxMin, vBoundBoxMax); } boundTestDesc.SetResultsStructure(pBoundTestResults); boundTestDesc.SetTransform(pMatrix); boundTestDesc.SetIncludeFlags(includeFlags); boundTestDesc.SetExcludeEntities(pExceptions, nNumExceptions); if (bUseBoxBound) { Assert(pVehicleBoundingBox); boundTestDesc.SetBound(pVehicleBoundingBox); //draw dis box //grcDebugDraw::BoxOriented(pVehicleBoundingBox->GetBoundingBoxMin(), pVehicleBoundingBox->GetBoundingBoxMax() // , MATRIX34_TO_MAT34V(*pMatrix), Color_green, false, -5); } else { boundTestDesc.SetBound(pTempBound); boundTestDesc.SetTreatPolyhedralBoundsAsPrimitives(true); } bool bHitDetected = WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc); if (pVehicleBoundingBox) { pVehicleBoundingBox->Release(); } #if 0 && !NEW_MIDPHASE_COLLISION_2 pTempBound->Release(); #endif if(pBoundTestResults) { return pBoundTestResults->GetNumHits(); } else { return bHitDetected ? 1 : 0; } } return 0; } void CVehicle::SetupMissionState() { #if __BANK if (HasAnExclusiveDriver()) { for (s32 i=0; iGetModelName(), this, VehicleLockStateStrings[m_eDoorLockState]); if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3) { sysStack::PrintStackTrace(); } #endif m_eDoorLockState = CARLOCK_LOCKED; } m_nVehicleFlags.bCannotRemoveFromWorld = false; // SET_CAR_HEALTH set it to 1000 if it’s greater than 1000 if (GetHealth() > CREATED_VEHICLE_HEALTH) { SetHealth(CREATED_VEHICLE_HEALTH, true); // (this can be called on network clones) } // SET_ENGINE_HEALTH set it to 1000 if it’s greater than 1000 m_Transmission.ResetEngineHealthIfItsOverTheMaximum(); // SET_PETROL_TANK_HEALTH set it to 1000 if it’s greater than 1000 GetVehicleDamage()->ResetPetrolTankHealthIfItsOverTheMaximum(); m_nVehicleFlags.bTakeLessDamage = false; m_nVehicleFlags.bMissionVehToughAxles = false; m_nVehicleFlags.bCanBeVisiblyDamaged = !GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_DISABLE_DEFORMATION ); m_nVehicleFlags.bIsRacing = false; m_nVehicleFlags.bCanBeUsedByFleeingPeds = true; // STOP_CAR_BREAKING fragInstGta* pFragInst = static_cast(GetFragInst()); if(pFragInst) { // Fix B*1844932 - breakables on the blimp are broken. if( GetVehicleType() != VEHICLE_TYPE_BLIMP ) { pFragInst->ResetDisableBreakable(); } } //reset the door allowed to be broken off flag for(int i=0; iSetDoorAllowedToBeBrokenOff(TRUE,this);//they should be allowed to be broken off by default } CVehicleRecordingMgr::StopPlaybackRecordedCar(this); GetIntelligence()->SetCustomPathNodeStreamingRadius(-1.0f); } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : SetHandlingCheatValuesForPoliceCarInPursuit // PURPOSE : ///////////////////////////////////////////////////////////////////////////////// void CVehicle::SetHandlingCheatValuesForPoliceCarInPursuit(bool bCheat) { if (bCheat) { SetCheatFlags(VEH_SET_GRIP2); } else { SetCheatFlags(0); } } // // // void CVehicle::SetIsWrecked() { Assert(!m_nVehicleFlags.bIsInPopulationCache); Assertf((GetIsInExterior() || GetIsInInterior() || GetIsRetainedByInteriorProxy()), "Vehicle 0x%p (%s) got wrecked but wasn't in the game world!", this, GetModelName()); SetStatus(STATUS_WRECKED); // If someone blow up our car I guess it would not be interesting anymore if (CVehiclePopulation::IsVehicleInteresting(this)) CVehiclePopulation::ClearInterestingVehicles(); // Remove all vehicle weapons DestroyVehicleWeaponMgr(); //set the no driver task, to set the control inputs if (!IsNetworkClone()) { GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, rage_new CTaskVehicleNoDriver(CTaskVehicleNoDriver::NO_DRIVER_TYPE_WRECKED), VEHICLE_TASK_PRIORITY_PRIMARY, false); } } // // // void CVehicle::SetIsAbandoned(const CPed *UNUSED_PARAM(pOriginalDriver)) { SetStatus(STATUS_ABANDONED); //set the no driver task, to set the control inputs if (!IsNetworkClone()) { GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, rage_new CTaskVehicleNoDriver(CTaskVehicleNoDriver::NO_DRIVER_TYPE_ABANDONED), VEHICLE_TASK_PRIORITY_PRIMARY, false); } } // // // void CVehicle::SetIsOutOfControl() { SetStatus(STATUS_OUT_OF_CONTROL); //set the no driver task, to set the control inputs - use NO_DRIVER_TYPE_ABANDONED, but also //check for STATUS_OUT_OF_CONTROL to spin aircrafts as if they took tail damage if (!IsNetworkClone()) { GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, rage_new CTaskVehicleNoDriver(CTaskVehicleNoDriver::NO_DRIVER_TYPE_ABANDONED), VEHICLE_TASK_PRIORITY_PRIMARY, false); } } // // // void CVehicle::SetIsPlayerDisabled() { SetStatus(STATUS_PLAYER_DISABLED); //set the no driver task, to set the control inputs if (!IsNetworkClone()) { GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, rage_new CTaskVehicleNoDriver(CTaskVehicleNoDriver::NO_DRIVER_TYPE_PLAYER_DISABLED), VEHICLE_TASK_PRIORITY_PRIMARY, false); } } bool CVehicle::TestExplosionPosAndBlowRearDoorsopen(const Vector3& vExplosionPos ) { if (m_bDontOpenRearDoorsOnExplosion) return false; fragInst* pFragInst = GetVehicleFragInst(); if( pFragInst && pFragInst->IsDamageDisabled() ) { return false; } // Check for rear opening doors Matrix34 globalDoorMat( Matrix34::IdentityType ); Vector3 vRearDoorPosition( Vector3::ZeroType ); Vector3 vDoorBBMax, vDoorBBMin; float fDoorWidthSpace = 0.0f; const int nNumDoors = 2; eHierarchyId aDoorId[nNumDoors] = { VEH_DOOR_DSIDE_R, VEH_DOOR_PSIDE_R }; CCarDoor* paVehicleDoor[nNumDoors] = { NULL, NULL }; // Walk through the vehicle rear doors and accumulate averages int nDoorIdx = 0, nDoorCount = 0; for ( nDoorIdx = 0; nDoorIdx < nNumDoors; nDoorIdx++ ) { paVehicleDoor[ nDoorIdx ] = GetDoorFromId( aDoorId[ nDoorIdx ] ); if( paVehicleDoor[ nDoorIdx ] && !paVehicleDoor[ nDoorIdx ]->GetFlag( CCarDoor::IS_BROKEN_OFF ) ) { // Accumulate door position GetGlobalMtx( GetBoneIndex( aDoorId[ nDoorIdx ] ), globalDoorMat ); vRearDoorPosition += globalDoorMat.d; // Accumulate door width space if( paVehicleDoor[ nDoorIdx ]->GetLocalBoundingBox( this, vDoorBBMax, vDoorBBMin ) ) { fDoorWidthSpace += Abs( vDoorBBMax.x - vDoorBBMin.x ); } nDoorCount++; } } // Determine if the explosion detonated on the rear of the vehicle if( nDoorCount > 0 ) { // Average the doors if( nDoorCount > 1 ) { vRearDoorPosition /= (float)nDoorCount; fDoorWidthSpace /= (float)nDoorCount; } // Translate the rear position to the same height as the test position vRearDoorPosition.z = vExplosionPos.z; // Test against door space if( vExplosionPos.Dist2( vRearDoorPosition ) < rage::square( fDoorWidthSpace ) ) { // Walk through the vehicle rear doors and accumulate averages for ( nDoorIdx = 0; nDoorIdx < nNumDoors; nDoorIdx++ ) { if( paVehicleDoor[ nDoorIdx ] && !paVehicleDoor[ nDoorIdx ]->GetFlag( CCarDoor::IS_BROKEN_OFF ) && paVehicleDoor[ nDoorIdx ]->GetIsLatched( this ) ) { // Animate the rear doors open paVehicleDoor[ nDoorIdx ]->SetTargetDoorOpenRatio( 0.95f, VEHICLE_REAR_DOOR_BLOWN_DOOR_FLAGS ); // Clear all the lock flags as we don't want the doors to ever being shut after this point paVehicleDoor[ nDoorIdx ]->ClearFlag( CCarDoor::WILL_LOCK_SWINGING | CCarDoor::WILL_LOCK_DRIVEN); // Break open the latch paVehicleDoor[ nDoorIdx ]->BreakLatch( this ); } } // Mark the explosion to cause 0 damage to the vehicle return true; } } return false; } // // // void CVehicle::SetDestructionInfo(CEntity* pEntity, const u32 uWeapon) { SetSourceOfDestruction(pEntity); SetCauseOfDestruction(uWeapon); SetTimeOfDestruction(); } bool CVehicle::CalculateTimeUntilExplosion(float& fTime) const { //Set the time. fTime = FLT_MAX; //Check if the petrol tank is burning. if(GetVehicleDamage()->IsPetrolTankBurning()) { //Calculate the time until the petrol tank explosion. fTime = GetVehicleDamage()->CalculateTimeUntilPetrolTankExplosion(); return true; } return false; } Vector3 CVehicle::GetChassisBoundMin(bool includeDamage) const { Vector3 boundingBoxMin = GetBoundingBoxMin(); //Try to get a more precise bounding box following the chassis bone, if there is one const int iBoneIndexChassis = GetBoneIndex((eHierarchyId)VEH_CHASSIS); fragInstGta* fragInstPtr = GetVehicleFragInst(); if(AssertVerify(iBoneIndexChassis != -1) && AssertVerify(fragInstPtr)) { int nChildIndex = fragInstPtr->GetComponentFromBoneIndex(iBoneIndexChassis); if(nChildIndex != -1) { boundingBoxMin = VEC3V_TO_VECTOR3(fragInstPtr->GetTypePhysics()->GetCompositeBounds()->GetBound(nChildIndex)->GetBoundingBoxMin()); } phBoundComposite* pBoundComp = static_cast(fragInstPtr->GetArchetype()->GetBound()); if (includeDamage && pBoundComp) { Vector3 partMins = RCC_VECTOR3(pBoundComp->GetLocalBoxMins(iBoneIndexChassis)); if (partMins.IsNonZero()) { boundingBoxMin = partMins; } } } return boundingBoxMin; } Vector3 CVehicle::GetChassisBoundMax(bool includeDamage) const { Vector3 boundingBoxMax = GetBoundingBoxMax(); //Try to get a more precise bounding box following the chassis bone, if there is one const int iBoneIndexChassis = GetBoneIndex((eHierarchyId)VEH_CHASSIS); fragInstGta* fragInstPtr = GetVehicleFragInst(); if(AssertVerify(iBoneIndexChassis != -1) && AssertVerify(fragInstPtr)) { int nChildIndex = fragInstPtr->GetComponentFromBoneIndex(iBoneIndexChassis); if(nChildIndex != -1) { boundingBoxMax = VEC3V_TO_VECTOR3(fragInstPtr->GetTypePhysics()->GetCompositeBounds()->GetBound(nChildIndex)->GetBoundingBoxMax()); } phBoundComposite* pBoundComp = static_cast(fragInstPtr->GetArchetype()->GetBound()); if (includeDamage && pBoundComp) { Vector3 partMaxs = RCC_VECTOR3(pBoundComp->GetLocalBoxMaxs(nChildIndex)); if (partMaxs.IsNonZero()) { boundingBoxMax = partMaxs; } } } return boundingBoxMax; } // // // Called when a car is in the wrong gear for the speed it's going, for instance at the end of a car recording. void CVehicle::SelectAppropriateGearForSpeed() { // Make sure the transmission is in an appropriate state m_Transmission.SelectAppropriateGearForSpeed(); } bool CVehicle::IsInDriveableState() const { return !g_fireMan.IsEntityOnFire(this) && !IsOnItsSide() && !IsUpsideDown() && m_nVehicleFlags.bIsThisADriveableCar; } void CVehicle::ProcessLowLODAudioRequirements(float fTimeStep) { //We're in here because we're a dummy object with a real audio component. //This means we need to update some things to make sure the audio remains coherent //Clear wheel audio: int nNumDriveWheels = 0; for (int i=0 ; iProcessLowLODAudio(); if(m_ppWheels[i]->GetIsDriveWheel()) { nNumDriveWheels++; } } const Mat34V matrix = GetMatrix(); const Vec3V velocity = VECTOR3_TO_VEC3V(GetVelocityIncludingReferenceFrame()); //Transmission: float fSpeed = Dot(matrix.GetCol1(),velocity).Getf(); // quick function to jam into the right gear. m_Transmission.SelectGearForSpeed(fSpeed); // calculate the rav ratio m_Transmission.ProcessEngine(this, matrix, velocity, nNumDriveWheels, fSpeed, fSpeed, fTimeStep); //Displayf("Vehicle/Old/New:, 0x%p, %f, %d, %f, %d", this, fOldRevs, iOldGear, m_Transmission.GetRevRatio(), m_Transmission.GetGear()); } bool CVehicle::IsInEnterableState(bool bIgnoreSideTest) const { if(GetStatus() == STATUS_WRECKED) return false; if(m_nVehicleFlags.bIsDrowning) return false; // for heli's check rotors and tail, but ignore wheels if(GetIsRotaryAircraft()) { CRotaryWingAircraft* pAircraft = (CRotaryWingAircraft*)this; if(pAircraft->GetMainRotorHealth() <= 0.0f || pAircraft->GetRearRotorHealth() <= 0.0f) return false; if(pAircraft->GetIsTailBoomBroken()) return false; } // for planes make sure we have both wings else if(InheritsFromPlane()) { CPlane* pAircraft = (CPlane*)this; if( (pAircraft->GetAircraftDamage().GetSectionHealth( CAircraftDamage::WING_L ) <= 0.0f) || (pAircraft->GetAircraftDamage().GetSectionHealth( CAircraftDamage::WING_R ) <= 0.0f) ) return false; } bool bPassedOnSideTest = true; if (!bIgnoreSideTest) { bPassedOnSideTest = !IsOnItsSide(); } return bPassedOnSideTest; } float CVehicle::GetBrakeForce( bool includeModifier ) const { float brakeForce = 0.0f; if( m_SeatManager.GetNumPlayers() > 0 || includeModifier ) { brakeForce = pHandling->m_fBrakeForce * (1.0f + (ms_fBrakeVarianceMaxModifier * GetVariationInstance().GetBrakesModifier()/100.0f)); } else { brakeForce = pHandling->m_fBrakeForce; } return brakeForce; } extern float sfEngineRevMinRevs; bool CVehicle::WantsToBeAwake() { Assertf(m_vehicleAiLod.GetDummyMode()!=VDM_SUPERDUMMY || !CVehicleAILodManager::ms_bDisablePhysicsInSuperDummyMode,"Don't bother to call this virtual function if in SD LOD with physics turned off."); const CPed* driver = GetDriver(); bool bProcessEngineRevEffect = driver && driver->IsPlayer() && m_nVehicleFlags.bEngineOn && GetVehicleType() == VEHICLE_TYPE_CAR && m_Transmission.GetRevRatio() > sfEngineRevMinRevs; bool bActivateDueToSteering = false; if(pHandling && pHandling->hFlags & HF_STEER_ALL_WHEELS && pHandling->hFlags & HF_EXT_WHEEL_BOUNDS_COL) { bActivateDueToSteering = abs(GetSteerAngle()) > 0.1f;// If we are steering extra bounds then activate the vehicle } if( const CEntity* towedEntity = GetEntityBeingTowed() ) { if( towedEntity->GetIsTypeVehicle() ) { const CVehicle* pTowedVehicle = static_cast< const CVehicle* >( towedEntity ); if( !pTowedVehicle->IsAsleep() && pTowedVehicle->HasContactWheels() ) { return true; } } } bool checkForStationaryInAir = !driver || !driver->IsPlayer() || GetCollider() || !IsCollisionLoadedAroundPosition(); if((m_nVehicleFlags.bEngineOn && GetThrottle()!=0.0f) || bProcessEngineRevEffect || bActivateDueToSteering || m_bIsRocketBoosting || IsInAir( checkForStationaryInAir ) || GetIsDoingJump() || ( InheritsFromAutomobile() && static_cast(this)->IsParachuting() ) ) // If car either jumping or parachuting { bool bAllowActivation = !GetIsAttached() || GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION) || GetAttachmentExtension()->GetAttachState()!=ATTACH_STATE_BASIC; return bAllowActivation && !(IsRunningCarRecording() && CanBeInactiveDuringRecording()); } return false; } static dev_float sfMinVelSqToScaleGripAndGravity = 2.0f; static bank_float sfSpecialAbilityGripScaleMin = 1.15f; static bank_float sfSpecialAbilityGripScaleMax = 1.45f; static bank_float sfGravityScaleMin = 2.25f; static bank_float sfGravityScaleMax = 3.0f; static bank_float sfInvAngInertiaMultY= 0.0001f; void CVehicle::ProcessSlowMotionSpecialAbilityPrePhysics(const CPed *pDriver) { if( pDriver && pDriver->GetSpecialAbility() && pDriver->GetSpecialAbility()->GetType() == SAT_CAR_SLOWDOWN ) { if(CStuntJumpManager::IsAStuntjumpInProgress()) { const_cast(pDriver)->GetSpecialAbility()->DeactivateNoFadeOut(); } static dev_float sfSlowMoSpecialAbilityTimeWarpModification = 0.2f; float fTimeDifference = 1.0f - sfSlowMoSpecialAbilityTimeWarpModification; float fFxStrength = pDriver->GetSpecialAbility()->GetFxStrength(); if(pDriver->GetSpecialAbility()->ShouldApplyFx()) { ProcessSlowMotionVehiclePrePhysics(fFxStrength); fwTimer::SetTimeWarp(1.0f - (fTimeDifference * fFxStrength)); } } else { fwTimer::SetTimeWarp(1.0f); } } void CVehicle::ProcessSlowMotionVehiclePrePhysics(const float fFxStrength) { const float fVelMagSq = GetVelocity().Mag2(); float fMaxVelMagSq = square(m_Transmission.GetDriveMaxVelocity()); //loop through the wheels and increase their grip for(int i=0; i sfMinVelSqToScaleGripAndGravity) { float fGrip = GetWheel(i)->GetGripMult(); float fGripScale = Lerp(Clamp(fVelMagSq/fMaxVelMagSq, 0.0f, 1.0f), sfSpecialAbilityGripScaleMin, sfSpecialAbilityGripScaleMax); GetWheel(i)->SetGripMult(fGrip + (fGrip * fGripScale * fFxStrength)); } } //increase inertia so the car doesn't flip with it's increased grip if(GetVehicleType() == VEHICLE_TYPE_CAR || GetVehicleType() == VEHICLE_TYPE_QUADBIKE) { if(fVelMagSq > sfMinVelSqToScaleGripAndGravity) { float fGravityScale = Lerp(Clamp(fVelMagSq/fMaxVelMagSq, 0.0f, 1.0f), sfGravityScaleMin, sfGravityScaleMax); SetGravityForWheellIntegrator(-GRAVITY * (1.0f + (fGravityScale * fFxStrength))); } if(GetCollider()) { GetCollider()->UseSolverInvAngInertiaResetOverride(false, true); Vector3 vSolverInvAngularInertia = VEC3V_TO_VECTOR3(GetCollider()->GetInvAngInertia()); vSolverInvAngularInertia.y *= sfInvAngInertiaMultY; GetCollider()->SetSolverInvAngInertiaResetOverride(vSolverInvAngularInertia); } } } // // // void CVehicle::SetGravityForWheellIntegrator(float fGravity) { float fGravityMult = 1.0f; switch(GetVehicleType()) { case VEHICLE_TYPE_BIKE: { fGravityMult = ms_fBikeGravityMult; break; } case VEHICLE_TYPE_QUADBIKE: case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE: { fGravityMult = ms_fQuadbikeGravityMult; break; } default: { if(pHandling && pHandling->hFlags & HF_OFFROAD_ABILITIES) { fGravityMult = ms_fOffRoadAbilityGravityMult; } else if((pHandling && pHandling->hFlags & HF_OFFROAD_ABILITIES_X2) || (pHandling && pHandling->hFlags & HF_OFFROAD_INCREASED_GRAVITY_NO_FOLIAGE_DRAG)) { fGravityMult = ms_fOffRoadAbility2GravityMult; } //Improve the Mesa3 handling. if(GetArchetype() && GetModelIndex() == MI_CAR_MESA3) { fGravityMult = ms_fOffRoadAbility2GravityMult; } break; } } m_fGravityForWheelIntegrator = fGravity * fGravityMult; } // // // void CVehicle::ProcessSlowMotionSpecialAbilityPostPhysics() { if( GetDriver() && GetDriver()->GetSpecialAbility() && GetDriver()->GetSpecialAbility()->GetType() == SAT_CAR_SLOWDOWN ) { if(GetDriver()->GetSpecialAbility()->ShouldApplyFx()) { //loop through the wheels and reset their grip for(int i=0; iSetGripMult(1.0f); } if(GetVehicleType() == VEHICLE_TYPE_CAR || GetVehicleType() == VEHICLE_TYPE_QUADBIKE) { //reset gravity back to normal SetGravityForWheellIntegrator(-GRAVITY * ms_fGravityScale); if(GetCollider()) { GetCollider()->UseSolverInvAngInertiaResetOverride(false, false); } } } } } /////////////////////////////////////////////////////////////////////////////////// // Dummy attachment functions void CVehicle::InitDummyAttachmentChildren() { for(int i=0; iDummyDetachChild(&vehChild); } Assert(FindDummyAttachmentChild(&vehChild)==-1); int iChildSlot = -1; for(int i=0; iChildSlot<0 && i=0,"Dummy children slots full.")) { return -1; } Assertf(m_nDummyAttachmentChildren=0,"Didn't find child vehicle.")) { DummyDetachChild(iChildSlot); } } } void CVehicle::DummyDetachChild(int iChildSlot) { CVehicle * pChild = m_DummyAttachmentChildren[iChildSlot]; if(!Verifyf(pChild,"Child is missing. Should be up-to-date (e.g. detach on deletion).")) { return; } Assertf(pChild->GetDummyAttachmentParent()==this,"Child thinks it is attached to a different parent."); // Detach in both directions pChild->m_DummyAttachmentParent = NULL; m_DummyAttachmentChildren[iChildSlot] = NULL; m_nDummyAttachmentChildren--; // Update the root-of-dummy-attachment-tree flag if(m_nDummyAttachmentChildren==0) { m_nPhysicalFlags.bIsRootOfOtherAttachmentTree = false; } } void CVehicle::DummyDetachAllChildren() { for(int i=0; iGetDummyAttachmentParent()==this,"Child thinks it is attached to a different parent."); // Detach in both directions pChild->m_DummyAttachmentParent = NULL; m_DummyAttachmentChildren[i] = NULL; } } // Update the child count m_nDummyAttachmentChildren = 0; // Update the root-of-dummy-attachment-tree flag m_nPhysicalFlags.bIsRootOfOtherAttachmentTree = false; } int CVehicle::FindDummyAttachmentChild(const CVehicle* const pVehicle) { for(int i=0; iGetIsTypeVehicle()) { CVehicle* pHitVehicle = static_cast(pEntity); if (pHitVehicle->m_VehicleProducingSlipstream != NETWORK_INVALID_OBJECT_ID && pHitVehicle->m_VehicleProducingSlipstream == CVehicle::ms_VehicleIdToLookFor) { CVehicle::ms_iSlipstreamingVehiclesFound++; } } return true; } void CVehicle::UpdateProducingSlipStreamTimer(float fTimeStep) { bool bUpdateVFX = false; m_bProducingSlipStreamForOther = false; // Scan for nearby vehicles, check if we're producing slipstream for any of them if (GetDriver() && GetDriver()->IsLocalPlayer() && NetworkInterface::IsGameInProgress() && GetNetworkObject()) { if (CVehicle::sm_bSlipstreamingEnabled) { bUpdateVFX = true; spdAABB testBox; Vector3 corner1(VEC3V_TO_VECTOR3(GetTransform().GetPosition())); Vector3 corner2 = corner1; corner1.x += ms_fSlipstreamMaxDistance; corner1.y += ms_fSlipstreamMaxDistance; corner2.x -= ms_fSlipstreamMaxDistance; corner2.y -= ms_fSlipstreamMaxDistance; testBox.Invalidate(); testBox.GrowPoint(RCC_VEC3V(corner1)); testBox.GrowPoint(RCC_VEC3V(corner2)); fwIsBoxIntersectingApprox searchBox(testBox); ms_VehicleIdToLookFor = GetNetworkObject()->GetObjectID(); CGameWorld::ForAllEntitiesIntersecting(&searchBox, FindSlipstreamingVehiclesCB, NULL, ENTITY_TYPE_MASK_VEHICLE, (SEARCH_LOCATION_EXTERIORS|SEARCH_LOCATION_INTERIORS), SEARCH_LODTYPE_HIGHDETAIL, SEARCH_OPTION_NONE, WORLDREP_SEARCHMODULE_VEHICLEAI); ms_VehicleIdToLookFor = NETWORK_INVALID_OBJECT_ID; if (ms_iSlipstreamingVehiclesFound > 0) { m_bProducingSlipStreamForOther = true; ms_iSlipstreamingVehiclesFound = 0; } } else { m_fTimeProducingSlipStream = 0.0f; m_bProducingSlipStreamForOther = false; } } if(m_nVehicleFlags.bUpdateProducingSlipStream || bUpdateVFX) { //Update the amount of time producing slipstream if(CVehicle::sm_bSlipstreamingEnabled && (m_nVehicleFlags.bProducingSlipStream || m_bProducingSlipStreamForOther)) { m_fTimeProducingSlipStream = Min(m_fTimeProducingSlipStream + fTimeStep, ms_fSlipstreamMaxTime); } else { m_fTimeProducingSlipStream -= fTimeStep; if(m_fTimeProducingSlipStream < 0) { // Slip stream has completely trailed off m_fTimeProducingSlipStream = 0; m_nVehicleFlags.bUpdateProducingSlipStream = false; } } } } bool CVehicle::IsAnExclusiveDriverPed(const CPed* ped) const { for(int i = 0; i < MAX_NUM_EXCLUSIVE_DRIVERS; ++i) { if( ped == m_pExclusiveDriverPeds[i]) { return true; } } return false; } bool CVehicle::HasAnExclusiveDriver() const { for(int i = 0; i < MAX_NUM_EXCLUSIVE_DRIVERS; ++i) { if (m_pExclusiveDriverPeds[i]) { return true; } } return false; } bool CVehicle::HasExpandedMods() { return pHandling && pHandling->GetCarHandlingData() && pHandling->GetCarHandlingData()->aFlags & CF_ALLOWS_EXTENDED_MODS; } void CVehicle::SetExclusiveDriverPed(CPed *pPed, s32 i) { Assert(iGetVehicleEntryConfig() : NULL; if (pVehicleEntryConfig && (pVehicleEntryConfig->HasARearForcingFlagSetForThisVehicle(this))) { #if __BANK aiAssertf(0, "Ped %s has a rear forcing flag active on the vehicle %s, failed to set him as an exclusive driver", AILogging::GetDynamicEntityNameSafe(pPed), AILogging::GetDynamicEntityNameSafe(this)); #endif // __BANK bIsSafeToSetAnExclusiveDriver = false; } if (bIsSafeToSetAnExclusiveDriver) { m_pExclusiveDriverPeds[i] = pPed; } } bool CVehicle::IsAnExclusiveDriverPedOrOpenSeat(const CPed* ped) const { bool freeSeat = true; if(!ped->IsPlayer() && m_nVehicleFlags.bAICanUseExclusiveSeats) { return true; } for(int i = 0; i < MAX_NUM_EXCLUSIVE_DRIVERS; ++i) { if( ped == m_pExclusiveDriverPeds[i]) { return true; } else if( m_pExclusiveDriverPeds[i] != NULL) { freeSeat = false; } } return freeSeat; } void CVehicle::UpdateSlipStreamTimer(float fTimeStep) { // Work out if we are slipstreaming a car. if(CVehicle::sm_bSlipstreamingEnabled && GetIntelligence()) { CVehicle* pSlipStreamVehicle = GetIntelligence()->GetCarWeCouldBeSlipstreamingBehind(); if(!ms_bRealisticSlipstreamMode)// This mode reduces drag based on speed and distance from the car in front. { bool bSlipStreamingAVehicle = false; bool bDriverLocal = GetDriver() && GetDriver()->IsLocalPlayer(); // Only slip stream player vehicles and script-created vehicles. if(pSlipStreamVehicle && (pSlipStreamVehicle->PopTypeIsMission() || pSlipStreamVehicle->ContainsPlayer())) { float SlipStreamVehicleSpeed = pSlipStreamVehicle->GetVelocity().Mag2(); if( !InheritsFromBoat() ) { if((SlipStreamVehicleSpeed > ms_fSpeedToStartSlipstream*ms_fSpeedToStartSlipstream)) { bSlipStreamingAVehicle = true; } } else { if((SlipStreamVehicleSpeed > ms_fSpeedToStartSlipstreamBoat*ms_fSpeedToStartSlipstreamBoat)) { bSlipStreamingAVehicle = true; } } } if( bSlipStreamingAVehicle && !m_nVehicleFlags.bSlipStreamDisabledByTimeOut) { m_fTimeInSlipStream += fTimeStep; pSlipStreamVehicle->m_nVehicleFlags.bProducingSlipStream = true; pSlipStreamVehicle->m_nVehicleFlags.bUpdateProducingSlipStream = true; if (pSlipStreamVehicle->GetNetworkObject() && bDriverLocal) m_VehicleProducingSlipstream = pSlipStreamVehicle->GetNetworkObject()->GetObjectID(); } else { m_fTimeInSlipStream -= fTimeStep; } m_fTimeInSlipStream = Clamp( m_fTimeInSlipStream, 0.0f, ms_fSlipstreamMaxTime ); if (bDriverLocal && m_fTimeInSlipStream == 0.0f) { m_VehicleProducingSlipstream = NETWORK_INVALID_OBJECT_ID; } if(m_nVehicleFlags.bSlipStreamDisabledByTimeOut) { m_fSlipStreamRechargeAndDechargeTimer -= fTimeStep; // if we have counted down our slipstream timer then reenable slip stream if(m_fSlipStreamRechargeAndDechargeTimer <= 0.0f) { m_nVehicleFlags.bSlipStreamDisabledByTimeOut = false; } } else { if(bSlipStreamingAVehicle) { m_fSlipStreamRechargeAndDechargeTimer += fTimeStep; } else { m_fSlipStreamRechargeAndDechargeTimer -= fTimeStep; } // allow script to disable the slip stream timeout altogether if (ms_bSlipstreamApplyMaxTimeBeforeBeingDisabled) { m_fSlipStreamRechargeAndDechargeTimer = Clamp( m_fSlipStreamRechargeAndDechargeTimer, 0.0f, ms_fSlipstreamMaxTimeBeforeBeingDisabled ); // disable slip stream if we have been in it for 10 seconds if(m_fSlipStreamRechargeAndDechargeTimer >= ms_fSlipstreamMaxTimeBeforeBeingDisabled) { m_nVehicleFlags.bSlipStreamDisabledByTimeOut = true; m_fSlipStreamRechargeAndDechargeTimer = ms_fSlipstreamMaxTimeBeforeBeingDisabled; } } } } } else { m_fTimeInSlipStream = 0.0f; } } void CVehicle::NotifyPassengersOfRadioTrackChange() { for( s32 iSeat = 0; iSeat < GetSeatManager()->GetMaxSeats(); iSeat++ ) { CPed* pPedInSeat = GetSeatManager()->GetPedInSeat(iSeat); if (pPedInSeat && pPedInSeat->GetPedIntelligence()) { CTaskAmbientClips* pAmbientClipsTask = static_cast(pPedInSeat->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AMBIENT_CLIPS)); if (pAmbientClipsTask) { pAmbientClipsTask->TerminateIfChosenConditionalAnimConditionsAreNotMet(); } } } } u32 CVehicle::GetNpcHashForDriver(u32 driverModelIndex) const { if (driverModelIndex == fwModelId::MI_INVALID) return 0; const CVehicleModelInfo* vmi = GetVehicleModelInfo(); u32 driverCount = vmi->GetDriverCount(); for (u32 i = 0; i < driverCount; ++i) { if (vmi->GetDriver(i) == driverModelIndex) return vmi->GetDriverNpcHash(i); } return 0; } #if ENABLE_FRAG_OPTIMIZATION void CVehicle::TryToReleaseFragCacheEntry() { Assert(m_pVehicleFragInst); Assert(!m_pVehicleFragInst->GetCached() || m_nVehicleFlags.bHasFragCacheEntry); #if __BANK const bool displayDebugInfo = !CVehicleIntelligence::ms_debugDisplayFocusVehOnly || CDebugScene::FocusEntities_IsInGroup(this); if(displayDebugInfo && CVehicleAILodManager::ms_bDebugFragCacheInfo && m_pVehicleFragInst->GetCached()) { const Vec3V vOffset = Vec3V(0.0f, 0.0f, 5.0f); grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.4f, Color_black, true, -1); } #endif // __BANK if(!m_nVehicleFlags.bLockFragCacheEntry && IsSuperDummy() && GetIsStatic() && m_vehicleLodState == VLS_HD_NONE && m_VehicleDamage.GetBodyHealth() >= GetVehicleModelInfo()->GetDefaultBodyHealth() && GetVariationInstance().GetNumMods() == 0 && m_pVehicleFragInst->GetType()->GetNumEnvCloths() == 0 && (GetVehicleType() == VEHICLE_TYPE_CAR || GetVehicleType() == VEHICLE_TYPE_TRAILER)) { if(m_pVehicleFragInst->GetCached()) { DeleteAnimDirector(); if(CPhysics::GetLevel()->IsActive(m_pVehicleFragInst->GetLevelIndex())) { PHSIM->DeactivateObject(m_pVehicleFragInst, true); } // Kill stale contacts with bad collider pointers RemovePhysics(); AddPhysics(); // Release frag cache entry fragCacheEntry *pCacheEntry = m_pVehicleFragInst->GetCacheEntry(); if(pCacheEntry) { FRAGCACHEMGR->ReleaseCacheEntry(pCacheEntry); m_pVehicleFragInst->SetCacheEntry(NULL); } SetMatrix(MAT34V_TO_MATRIX34(GetMatrix()), true, true, true); m_nNumDoors = 0; #if __BANK if(CVehicleAILodManager::ms_bDebugFragCacheInfo) { Printf("ReleaseCacheEntry: %p\n", this); } #endif // __BANK } m_nVehicleFlags.bHasFragCacheEntry = false; } else { Assertf(m_nVehicleFlags.bHasFragCacheEntry, "Expected frag cache entry: %i, %i, %i, %i, %i, %i, %i, %i (%i)", !m_nVehicleFlags.bLockFragCacheEntry, IsSuperDummy(), GetIsStatic(), m_vehicleLodState == VLS_HD_NONE, m_VehicleDamage.GetBodyHealth() >= GetVehicleModelInfo()->GetDefaultBodyHealth(), GetVariationInstance().GetNumMods() == 0, m_pVehicleFragInst->GetType()->GetNumEnvCloths() == 0, (GetVehicleType() == VEHICLE_TYPE_CAR || GetVehicleType() == VEHICLE_TYPE_TRAILER), GetHasFragCacheEntry()); } #if __BANK if(displayDebugInfo && CVehicleAILodManager::ms_bDebugFragCacheInfo && m_pVehicleFragInst->GetCached()) { const Vec3V vOffset = Vec3V(0.0f, 0.0f, 5.0f); if(m_VehicleDamage.GetBodyHealth() != m_VehicleDamage.GetBodyHealthMax()) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_cyan, false, -1); } else if(GetVariationInstance().GetNumMods() != 0) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_pink, false, -1); } else if(m_nVehicleFlags.bLockFragCacheEntry) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_green, false, -1); } else if(IsDummy() && !IsSuperDummy()) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_orange, false, -1); } else if(!IsDummy()) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_white, false, -1); } else if(!GetIsStatic()) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_red, false, -1); } else if(m_vehicleLodState != VLS_HD_NONE) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_magenta, false, -1); } else if(m_pVehicleFragInst->GetType()->GetNumEnvCloths() != 0) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_purple, false, -1); } else if(GetVehicleType() != VEHICLE_TYPE_CAR && GetVehicleType() != VEHICLE_TYPE_TRAILER) { grcDebugDraw::Sphere(GetTransform().GetPosition() + vOffset, 0.5f, Color_yellow, false, -1); } } #endif // __BANK } void CVehicle::GiveFragCacheEntry(bool lock) { #if ENABLE_FRAG_OPTIMIZATION Assert(m_pVehicleFragInst); if(!m_nVehicleFlags.bHasFragCacheEntry) { m_nVehicleFlags.bHasFragCacheEntry = true; /////////////////////////////////////////////////////////////////////// if(!m_pVehicleFragInst->GetCached()) { m_pVehicleFragInst->PutIntoCache(); Assert(m_pVehicleFragInst->GetCached()); } m_pVehicleFragInst->ReportMovedBySim(); fragCacheEntry* cacheEntry = m_pVehicleFragInst->GetCacheEntry(); if(cacheEntry) { fragHierarchyInst* hierInst = cacheEntry->GetHierInst(); Assert(hierInst); if(hierInst->articulatedCollider) { if(ms_bUseAutomobileBVHupdate) { hierInst->articulatedCollider->SetBvhRebuildPeriod(UPDATE_BVH_EACH_UPDATE); } } } /////////////////////////////////////////////////////////////////////// CreateSkeleton(); DeleteAnimDirector(); CreateAnimDirector(*GetDrawable()); // Set local pose only for the inverse pose node crCreatureComponentSkeleton* skeleton = GetCreature()->FindComponent(); if(skeleton) { skeleton->SetSuppressReset(true); } InitDoors(); InitCompositeBound(); UpdateExtras(); InitFragmentExtras(); #if __BANK if(CVehicleAILodManager::ms_bDebugFragCacheInfo) { Printf("PutIntoCache: %p\n", this); } #endif // __BANK } m_nVehicleFlags.bLockFragCacheEntry |= lock; #else // ENABLE_FRAG_OPTIMIZATION UNUSED_VAR(lock); #endif // ENABLE_FRAG_OPTIMIZATION } #endif float CVehicle::GetSlipStreamEffect() const { if (CVehicle::sm_bSlipstreamingEnabled && GetDriver() && GetDriver()->IsPlayer())// Only do slip streaming on the player for the time being. { if(ms_bRealisticSlipstreamMode)// This mode reduces drag based on speed and distance from the car in front. { // Work out if we are slipstreaming a car. if(GetIntelligence()) { CVehicle* pSlipStreamVehicle = GetIntelligence()->GetCarWeCouldBeSlipstreamingBehind(); if(pSlipStreamVehicle && pSlipStreamVehicle->ContainsPlayer()) // Only slip stream other players { float fSlipStreamDistanceSq = GetIntelligence()->GetDistanceBehindCarWeCouldBeSlipstreamingBehindSq(); float SlipStreamVehicleSpeed = pSlipStreamVehicle->GetVelocity().Mag(); if(SlipStreamVehicleSpeed > ms_fSpeedToStartSlipstream) { // Clamp the vehicle speed so it doesn't go above the slip stream speed SlipStreamVehicleSpeed = Clamp(SlipStreamVehicleSpeed, ms_fSpeedToStartSlipstream, ms_fMaxSpeedOfSlipStream); // Slip stream effect increases with the speed of the vehicle float fSlipStreamSpeedEffect = (SlipStreamVehicleSpeed - ms_fSpeedToStartSlipstream)/ (ms_fMaxSpeedOfSlipStream - ms_fSpeedToStartSlipstream); // Slip stream effect decreases the further away from the lead car you are. float fSlipStreamDistanceEffect = 1.0f - (sqrt(fSlipStreamDistanceSq)/(ms_fSlipstreamMaxDistance)); float fSlipStreamEffect = ms_fSlipstreamMaxDragReduction * ((fSlipStreamSpeedEffect + fSlipStreamDistanceEffect)/2.0f); return fSlipStreamEffect; } } } } else { // We want to reduce the drag on the vehicle when it's outside of the slip stream of the vehicle in front for a small amount of fun so a player can overtake. if(m_fTimeInSlipStream > 0.0f) { float fSlipStreamEffect = ms_fSlipstreamMaxDragReduction * (m_fTimeInSlipStream/ms_fSlipstreamMaxTime); return fSlipStreamEffect; } } } return 0.0f; } void CVehicle::SetGravityScale(float fGravityScale) { ms_fGravityScale = fGravityScale; CVehicle::Pool* pVehiclePool = CVehicle::GetPool(); Assert(pVehiclePool); for(u32 i = 0; iGetSize(); i++){ CVehicle* pVehicle = pVehiclePool->GetSlot(i); if (pVehicle) { pVehicle->SetGravityForWheellIntegrator(-GRAVITY * fGravityScale); } } } void CVehicle::SetGravitationalAcceleration(float fGravitationalAcceleration) { if(fGravitationalAcceleration==0.0f) { fGravitationalAcceleration = 0.00001f; } SetGravityScale(fGravitationalAcceleration/(-GRAVITY)); } void CVehicle::ResetGravityScale() { SetGravityScale(1.0f); } #if __ASSERT bool CVehicle::IsVehicleAllowedInMultiplayer() const { u32 modelIndex = GetModelIndex(); // Allow MI_TRAILER_TR2 but ONLY for missions if (modelIndex == MI_TRAILER_TR2 && PopTypeIsMission()) { return true; } else if(modelIndex != MI_CAR_TOWTRUCK && modelIndex != MI_CAR_TOWTRUCK_2 && modelIndex != MI_TRAILER_TR2 && modelIndex != MI_TRAILER_TR4) { return true; } return false; } #endif /* __ASSERT */ bool CVehicle::IsTypeAllowedInMP(u32 modelIndex) { if ((modelIndex == MI_TRAILER_TR2) || (modelIndex == MI_TRAILER_TR4)) { return false; } return true; } bool CVehicle::IsEnteringInsideOrExiting() { bool enteringInsideExitingAVehicle = false; CPlayerInfo *pPlayerInfo = CGameWorld::GetMainPlayerInfo(); if (pPlayerInfo && pPlayerInfo->GetPlayerPed()) { const CPed* playerPed = pPlayerInfo->GetPlayerPed(); CQueriableInterface* pedInterface = playerPed->GetPedIntelligence()->GetQueriableInterface(); CTaskMotionInVehicle* pMotionTask = static_cast(playerPed->GetPedIntelligence()->FindTaskActiveMotionByType( CTaskTypes::TASK_MOTION_IN_VEHICLE)); enteringInsideExitingAVehicle = (pedInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE) || pedInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE) || (pMotionTask != NULL)); } return enteringInsideExitingAVehicle; } void CVehicle::AddLight(eLightType type, s32 boneIdx, const ConfigLightSettings &lightParam, float distFade) { if (boneIdx > -1) { Matrix34 worldMtx; GetGlobalMtx(boneIdx, worldMtx); fwInteriorLocation interiorLocation = this->GetInteriorLocation(); Vector3 lightColour = VEC3V_TO_VECTOR3(lightParam.colour); float lightIntensity = lightParam.intensity; if ((lightParam.extraFlags & LIGHTFLAG_CALC_FROM_SUN) != 0) { //use hacked non-modified directional lights Vec4V vBaseDirLight = g_timeCycle.GetBaseDirLight(); Vector4 TimeCycle_Directional = RCC_VECTOR4(vBaseDirLight); lightColour *= Vector3(TimeCycle_Directional.x, TimeCycle_Directional.y, TimeCycle_Directional.z); float dirScale = TimeCycle_Directional.w; lightIntensity *= dirScale; } CLightSource light( type, LIGHTFLAG_VEHICLE | LIGHTFLAG_MOVING_LIGHT_SOURCE | lightParam.extraFlags, worldMtx.d, lightColour, lightIntensity * distFade, LIGHT_ALWAYS_ON); if (type == LIGHT_TYPE_SPOT || type == LIGHT_TYPE_CAPSULE) { light.SetDirTangent(worldMtx.c, worldMtx.b); } light.SetRadius(lightParam.radius); light.SetFalloffExponent(lightParam.falloffExp); light.SetInInterior(interiorLocation); light.SetShadowBlur(lightParam.shadowBlur); light.SetInInterior(GetInteriorLocation()); if ((lightParam.extraFlags & LIGHTFLAG_CAST_DYNAMIC_GEOM_SHADOWS) != 0) { light.SetShadowTrackingId(fwIdKeyGenerator::Get(this) + boneIdx); } if (type == LIGHT_TYPE_SPOT) { light.SetSpotlight(lightParam.innerConeAngle, lightParam.outerConeAngle); } if (type == LIGHT_TYPE_CAPSULE) { light.SetCapsule(lightParam.capsuleLength); } Lights::AddSceneLight(light); } } /////////////////////////////////////////////////////////////////////////////////// // CPoolHelpers namespace CPoolHelpers { int g_VehiclePoolSize = 0; int GetVehiclePoolSize() { Assertf(g_VehiclePoolSize != 0, "Trying to get the vehicle pool size before it's been set."); // We also do a trap here, to catch it in non-assert builds, but more importantly, // to catch it in case it's used from static initialization code when asserts may // not yet be working. TrapZ(g_VehiclePoolSize); return g_VehiclePoolSize; } void SetVehiclePoolSize(int poolSize) { Assert(g_VehiclePoolSize == 0); // Not really meant to call this more than once at this time. g_VehiclePoolSize = poolSize; } } #endif // !__SPU #if __DEV void CVehicle::PoolFullCallback(void* pItem) { static const char *pPopTypeNames[] = { "POPTYPE_UNKNOWN", "POPTYPE_RANDOM_PERMANENT", "POPTYPE_RANDOM_PARKED", "POPTYPE_RANDOM_PATROL", "POPTYPE_RANDOM_SCENARIO", "POPTYPE_RANDOM_AMBIENT", "POPTYPE_PERMANENT", "POPTYPE_MISSION", "POPTYPE_REPLAY", "POPTYPE_CACHE", "POPTYPE_TOOL" }; static const char *pVehicleTypeNames[] = { "VEHICLE_TYPE_CAR", "VEHICLE_TYPE_PLANE", "VEHICLE_TYPE_TRAILER", "VEHICLE_TYPE_QUADBIKE", "VEHICLE_TYPE_DRAFT", "VEHICLE_TYPE_SUBMARINECAR", "VEHICLE_TYPE_HELI", "VEHICLE_TYPE_BLIMP", "VEHICLE_TYPE_AUTOGYRO", "VEHICLE_TYPE_BIKE", "VEHICLE_TYPE_BICYCLE", "VEHICLE_TYPE_BOAT", "VEHICLE_TYPE_TRAIN", "VEHICLE_TYPE_SUBMARINE" }; static const char *pOwnedByTypeNames[] = { "ENTITY_TYPE_NOTHING", "ENTITY_TYPE_BUILDING", "ENTITY_TYPE_ANIMATED_BUILDING", "ENTITY_TYPE_VEHICLE", "ENTITY_TYPE_PED", "ENTITY_TYPE_OBJECT", "ENTITY_TYPE_DUMMY_OBJECT", "ENTITY_TYPE_PORTAL", "ENTITY_TYPE_MLO", "ENTITY_TYPE_NOTINPOOLS", "ENTITY_TYPE_PARTICLESYSTEM", "ENTITY_TYPE_LIGHT", "ENTITY_TYPE_COMPOSITE" }; if (!pItem) { Printf("ERROR - Vehicle Pool FULL!\n"); } else { CVehicle* pVehicle = static_cast(pItem); const char *pDebugName = pVehicle->GetDebugName(); const char *pName = (pDebugName && pDebugName[0] != '\0') ? pDebugName : pVehicle->GetModelName(); float fTimeSinceCreation = (fwTimer::GetTimeInMilliseconds() - pVehicle->m_TimeOfCreation) * 0.001f; Displayf(" \"%s\", model index %u, memory address %p, type %s, poptype %s, owned by %s, time since creation %3.2f", pName, pVehicle->GetModelIndex(), pVehicle, pVehicleTypeNames[pVehicle->GetVehicleType()], pPopTypeNames[pVehicle->m_nDEflags.nPopType], pOwnedByTypeNames[pVehicle->GetOwnedBy()], fTimeSinceCreation); } } #endif void CVehicle::RegisterVehicleBreak() { // If we don't have room for more breaks replace the oldest break time with the newest one if(ms_RecentVehicleBreakTimes.IsFull()) { ms_RecentVehicleBreakTimes.Pop(); } ms_RecentVehicleBreakTimes.Push(fwTimer::GetTimeInMilliseconds()); // Update the breaks/second in case we break a lot before CVehicle::UpdateVehicleBreaksPerSecond is called ms_VehicleBreaksPerSecond = (float)ms_RecentVehicleBreakTimes.GetCount()/ms_VehicleBreakWindowSize; } void CVehicle::UpdateVehicleBreaksPerSecond() { // Cull all vehicle breaks that happened outside the rolling window u32 rollingWindowEndTime = fwTimer::GetTimeInMilliseconds(); u32 rollingWindowStartTime = rollingWindowEndTime - ms_VehicleBreakWindowSize*1000; while(!ms_RecentVehicleBreakTimes.IsEmpty() && (ms_RecentVehicleBreakTimes.Top() < rollingWindowStartTime || ms_RecentVehicleBreakTimes.Top() > rollingWindowEndTime)) { ms_RecentVehicleBreakTimes.Pop(); } ms_VehicleBreaksPerSecond = (float)ms_RecentVehicleBreakTimes.GetCount()/ms_VehicleBreakWindowSize; } float CVehicle::GetDayNightFade() { const float forceLightOffFade = ms_forceActualVehicleLightsOff ? 0.0f : 1.0f; const float daynightFade = forceLightOffFade * Lights::CalculateTimeFade(g_DayNightLights.sunset, g_DayNightLights.sunset, g_DayNightLights.sunrise, g_DayNightLights.sunrise); return daynightFade; } float CVehicle::GetLightOffValue(s32 light, float dayNightFade) { return g_LightSwitchSettings.GetLightOffValue(light, dayNightFade); } #if __WIN32PC void CVehicle::ProcessForceFeedback() { float wheelAngle = 0.0f; int numFrontWheels = 0; bool applyForce = 0; CWheel* wheel = GetWheelFromId(VEH_WHEEL_LF); if(wheel) { if(wheel->GetIsTouching()) { wheelAngle += (wheel->GetSideSlipAngle() / wheel->GetHandlingData()->m_fTractionCurveLateral_Inv); applyForce = true; } ++numFrontWheels; } wheel = GetWheelFromId(VEH_WHEEL_RF); if(wheel) { if(wheel->GetIsTouching()) { wheelAngle += (wheel->GetSideSlipAngle() / wheel->GetHandlingData()->m_fTractionCurveLateral_Inv); applyForce = true; } ++numFrontWheels; } if(applyForce) { // Used to tune the strength of a car's force feedback. TUNE_FLOAT(CAR_FORCE_FEEDBACK_SCALER, 2.5f, 0.0f, 10.0f, 0.1f); TUNE_FLOAT(CAR_FORCE_FEEDBACK_POW, 1.5f, 0.0f, 10.0f, 0.1f); float force = (Sign((wheelAngle / numFrontWheels)) * Powf(Abs((wheelAngle / numFrontWheels)),CAR_FORCE_FEEDBACK_POW)) * CAR_FORCE_FEEDBACK_SCALER; static dev_float sfReduceForceAtSpeedBelow = 10.0f; const float speed = Abs(GetVelocity().Mag()); if(speed < sfReduceForceAtSpeedBelow) { force *= speed/sfReduceForceAtSpeedBelow; } CControlMgr::ApplyDirectionalForce(force, 0, 100); } } #endif // __WIN32PC #if __BANK const char *CVehicle::GetRealPedFailReason(int realPedFailReason) { switch(realPedFailReason) { case CVehicle::RPFR_SUCCEEDED: return "SUCCEEDED"; case CVehicle::RPFR_IS_NET_CLONE: return "IS_NET_CLONE"; case CVehicle::RPFR_IS_NOT_NET_OBJECT: return "IS_NOT_NET_OBJECT"; case CVehicle::RPFR_NET_OBJ_REGISTER_FAILED: return "NET_OBJ_REGISTER_FAILED"; case CVehicle::RPFR_PED_POOL_FULL: return "PED_POOL_FULL"; case CVehicle::RPFR_NO_FALLBACK_PED: return "NO_FALLBACK_PED"; case CVehicle::RPFR_ADD_POLICE_FAILED: return "ADD_POLICE_FAILED"; case CVehicle::RPFR_NET_REQUEST_DRIVER_MODEL: return "NET_REQUEST_DRIVER_MODEL"; case CVehicle::RPFR_NET_SET_UP_DRIVER: return "NET_SET_UP_DRIVER"; case CVehicle::RPFR_FILTERED_SETUP_DRIVER: return "FILTERED_SETUP_DRIVER"; case CVehicle::RPFR_UNFILTERED_SETUP_DRIVER: return "UNFILTERED_SETUP_DRIVER"; case CVehicle::RPFR_ADD_FOR_TRAIN: return "ADD_FOR_TRAIN"; case CVehicle::RPFR_ADD_FOR_VEH: return "ADD_FOR_VEH"; default: return "Unknown Real Ped Failure"; } } #endif // __BANK bool CVehicle::GetIsShelteredFromRain() { // check if undercover if (GetAudioEnvironmentGroup()) { // use the audio data if it exists if (((naEnvironmentGroup*)GetAudioEnvironmentGroup())->IsUnderCover()) { return true; } } // #if __TRACK_PEDS_IN_NAVMESH // else // { // // otherwise query the navmesh // if (GetNavMeshTracker().GetIsValid()) // { // if (GetNavMeshTracker().GetNavPolyData().m_bSheltered) // { // return true; // } // } // } // #endif // or in an interior if (GetIsInInterior()) { return true; } return false; } void CVehicle::PreVisUpdate() { // Work out if we need to enable inside-vehicle rendering mode ms_insideVehicleMode = 0; if (camInterface::IsRenderedCameraInsideVehicle()) { CPlayerInfo *pPlayerInfo = CGameWorld::GetMainPlayerInfo(); if (pPlayerInfo && pPlayerInfo->GetPlayerPed()) { const CPed* playerPed = pPlayerInfo->GetPlayerPed(); const CVehicle* playerVehicle = playerPed->GetMyVehicle(); if ((playerPed->GetAttachCarSeatIndex() > 1) && (playerVehicle != NULL) && playerVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_LIGHTING_INTERIOR_OVERRIDE)) { ms_insideVehicleMode = playerVehicle->GetVehicleModelInfo()->GetModelNameHash(); } } } } void CVehicle::UpdateBobbleHead( int boneIndex, eBobbleHeadType type ) { if( boneIndex != -1 ) { CBobbleHead* pBobbleHead = GetBobbleHead( this, type ); if( !pBobbleHead ) { pBobbleHead = AddBobbleHead( this, type ); } if( pBobbleHead ) { pBobbleHead->Update(); } } } CBobbleHead* CVehicle::GetBobbleHead( CVehicle* pVehicle, eBobbleHeadType type ) { for( int i = 0; i < ms_bobbleHeads.GetCount(); i++ ) { if( ms_bobbleHeads[ i ].GetVehicle() == pVehicle && ms_bobbleHeads[ i ].GetType() == type ) { return &ms_bobbleHeads[ i ]; } } return NULL; } CBobbleHead* CVehicle::AddBobbleHead( CVehicle* pVehicle, eBobbleHeadType type ) { // Add this vehicle / bobble head to the list if it is closer than one of the existing ones if( !ms_bobbleHeads.IsFull() ) { CBobbleHead* pBobbleHead = &ms_bobbleHeads.Append(); pBobbleHead->SetVehicle( pVehicle ); pBobbleHead->SetType( type ); return pBobbleHead; } CPed* pPed = FindPlayerPed(); float distanceFromPlayer = FLT_MAX; if( pPed ) { if( pPed->GetMyVehicle() == pVehicle ) { distanceFromPlayer = 0.0f; } else { Vec3V playerPosition = pPed->GetTransform().GetPosition(); distanceFromPlayer = Mag( playerPosition - pVehicle->GetVehiclePosition() ).Getf(); } } int greatestDistanceIndex = -1; float greatestDistance = distanceFromPlayer; for( int i = 0; i < ms_bobbleHeads.GetCount(); i++ ) { float distance = ms_bobbleHeads[ i ].GetDistanceFromPlayer(); if( distance > greatestDistance ) { greatestDistance = distance; greatestDistanceIndex = i; } } if( greatestDistanceIndex != -1 ) { ms_bobbleHeads[ greatestDistanceIndex ].Reset(); ms_bobbleHeads[ greatestDistanceIndex ].SetVehicle( pVehicle ); ms_bobbleHeads[ greatestDistanceIndex ].SetType( type ); return &ms_bobbleHeads[ greatestDistanceIndex ]; } return NULL; } Vec3V CBobbleHead::ms_bobbleHeadStiffness( 750.0f, 750.0f, 750.0f ); ScalarV CBobbleHead::ms_bobbleHeadMass( 3.0f ); ScalarV CBobbleHead::ms_bobbleHeadMassInv( ScalarV( 1.0f ) / ms_bobbleHeadMass ); Vec3V CBobbleHead::ms_bobbleHeadDamping( 0.25f, 0.25f, 0.25f ); Vec3V CBobbleHead::ms_bobbleHeadLimits( 0.3f, 0.3f, 0.0f ); Vec3V CBobbleHead::ms_bobbleArmStiffness( 750.0f, 750.0f, 750.0f ); ScalarV CBobbleHead::ms_bobbleArmMass( 7.0f ); ScalarV CBobbleHead::ms_bobbleArmMassInv( ScalarV( 1.0f ) / ms_bobbleHeadMass ); Vec3V CBobbleHead::ms_bobbleArmDamping( 5.0f, 3.0f, 3.0f ); Vec3V CBobbleHead::ms_bobbleArmLimits( 0.1f, 0.4f, 0.4f ); Vec3V CBobbleHead::ms_bobbleEngineStiffness( 5000.0f, 5000.0f, 5000.0f ); ScalarV CBobbleHead::ms_bobbleEngineMass( 5.0f ); ScalarV CBobbleHead::ms_bobbleEngineMassInv( ScalarV( 1.0f ) / ms_bobbleHeadMass ); Vec3V CBobbleHead::ms_bobbleEngineDamping( 3.0f, 3.0f, 3.0f ); Vec3V CBobbleHead::ms_bobbleEngineLimits( 0.02f, 0.0f, 0.0f ); void CBobbleHead::Reset() { Vec3V vZero( V_ZERO ); m_bobbleHeadPrevDistance = vZero; m_bobbleHeadVelocity = vZero; m_bobbleHeadVehiclePrevVelocity = vZero; m_bobbleHeadDistance = FLT_MAX; } eHierarchyId CBobbleHead::GetBoneIdFromType() { switch( m_type ) { case BHT_HEAD: { return VEH_BOBBLE_HEAD; } case BHT_MISC_1: { return VEH_BOBBLE_MISC_1; } case BHT_MISC_2: { return VEH_BOBBLE_MISC_2; } case BHT_MISC_3: { return VEH_BOBBLE_MISC_3; } case BHT_MISC_4: { return VEH_BOBBLE_MISC_4; } case BHT_MISC_5: { return VEH_BOBBLE_MISC_5; } case BHT_MISC_6: { return VEH_BOBBLE_MISC_6; } case BHT_MISC_7: { return VEH_BOBBLE_MISC_7; } case BHT_MISC_8: { return VEH_BOBBLE_MISC_8; } case BHT_HAND: { return VEH_BOBBLE_HAND; } case BHT_ENGINE: { return VEH_BOBBLE_ENGINE; } default: { return VEH_INVALID_ID; } } } void CBobbleHead::Update() { int boneIndex; Vec3V stiffness; Vec3V damping; Vec3V limits; ScalarV mass; ScalarV massInv; boneIndex = m_vehicle->GetBoneIndex( GetBoneIdFromType() ); switch( m_type ) { case BHT_HEAD: case BHT_MISC_1: case BHT_MISC_2: case BHT_MISC_3: case BHT_MISC_4: case BHT_MISC_5: case BHT_MISC_6: case BHT_MISC_7: case BHT_MISC_8: { stiffness = ms_bobbleHeadStiffness; damping = ms_bobbleHeadDamping; limits = ms_bobbleHeadLimits; mass = ms_bobbleHeadMass; massInv = ms_bobbleHeadMassInv; break; } case BHT_HAND: { stiffness = ms_bobbleArmStiffness; damping = ms_bobbleArmDamping; limits = ms_bobbleArmLimits; mass = ms_bobbleArmMass; massInv = ms_bobbleArmMassInv; break; } case BHT_ENGINE: { stiffness = ms_bobbleEngineStiffness; damping = ms_bobbleEngineDamping; limits = ms_bobbleEngineLimits; mass = ms_bobbleEngineMass; massInv = ms_bobbleEngineMassInv; break; } default: { return; } } Vector3 defaultHeadPosition; m_vehicle->GetDefaultBonePosition( boneIndex, defaultHeadPosition ); ScalarV deltaTimeInv = ScalarV( fwTimer::GetInvTimeStep() ); Vec3V acceleration = Vec3V( 0.0f, 0.0f, 0.0f ); Vec3V vehicleVelocity = Vec3V( 0.0f, 0.0f, 0.0f ); if( m_type == BHT_HEAD || m_type == BHT_HAND ) { vehicleVelocity = VECTOR3_TO_VEC3V( m_vehicle->GetLocalSpeed( defaultHeadPosition ) ); vehicleVelocity = m_vehicle->GetTransform().UnTransform3x3( vehicleVelocity ); if( m_type == BHT_HEAD ) { vehicleVelocity[ 2 ] = 0.0f; } } else { float revRatio = 0.0f; if( m_vehicle->IsEngineOn() ) { revRatio = m_vehicle->m_Transmission.GetRevRatio(); static float randomRangeAtMinRpm = 0.1f; static float randomRangeAtMaxRpm = 0.0025f; static float minRandomFrequency = 0.02f; static float randomFrequency = minRandomFrequency + ( revRatio * ( 1.0f - minRandomFrequency ) ); static float revRatioScale = 1.0f; if( fwRandom::GetRandomNumberInRange( 0.0f, 1.0f ) <= randomFrequency ) { float randomRange = randomRangeAtMinRpm + ( ( randomRangeAtMaxRpm - randomRangeAtMinRpm ) * revRatio ); revRatio += fwRandom::GetRandomNumberInRange( -randomRange, randomRange ); } // if the engine was previously turned off give a bigger rev ratio to make the engine shake a bit more at start up if( MagSquared( m_bobbleHeadVehiclePrevVelocity ).Getf() == 0.0f ) { static float engineStartVelocityIncrease = 0.2f; revRatio += engineStartVelocityIncrease; } vehicleVelocity = Vec3V( revRatio * revRatioScale, 0.0f, 0.0f ); } } acceleration = ( vehicleVelocity - m_bobbleHeadVehiclePrevVelocity ) * deltaTimeInv; int numUpdates = Max( (int)fwTimer::GetTimeStepInMilliseconds(), 1 ); Vec3V accelerationForce = acceleration * mass; const ScalarV fTimeStep( 0.001f ); for( int i = 0; i < numUpdates; i++ ) { Vec3V springForce = stiffness * m_bobbleHeadPrevDistance; Vec3V newVelocity = m_bobbleHeadVelocity + ( fTimeStep * ( ( accelerationForce - springForce ) * massInv ) ); static bool applyDamping = true; if( applyDamping ) { Vec3V dampingForce = fTimeStep * damping * newVelocity; newVelocity -= dampingForce; } Vec3V distanceFromTarget = m_bobbleHeadPrevDistance + ( fTimeStep * newVelocity ); m_bobbleHeadVelocity = newVelocity; m_bobbleHeadPrevDistance = distanceFromTarget; } m_bobbleHeadPrevDistance = Clamp( m_bobbleHeadPrevDistance, -limits, limits ); Matrix34 &boneMat = m_vehicle->GetLocalMtxNonConst( boneIndex ); const crBoneData* pBoneData = m_vehicle->GetSkeletonData().GetBoneData( boneIndex ); boneMat.FromQuaternion( RCC_QUATERNION( pBoneData->GetDefaultRotation() ) ); boneMat.RotateY( m_bobbleHeadPrevDistance[0] ); boneMat.RotateX( m_bobbleHeadPrevDistance[1] ); boneMat.RotateZ( m_bobbleHeadPrevDistance[2] ); m_bobbleHeadVehiclePrevVelocity = vehicleVelocity; // update the distance to the player CPed* pPed = FindPlayerPed(); if( pPed ) { Vec3V playerPosition = pPed->GetTransform().GetPosition(); m_bobbleHeadDistance = Mag( playerPosition - m_vehicle->GetVehiclePosition() ).Getf(); } } void CVehicle::SetBobbleHeadVelocity( Vec3V& velocity ) { CPed* pPed = FindPlayerPed(); if( pPed ) { CBobbleHead* bobbleHead = GetBobbleHead( pPed->GetMyVehicle(), BHT_HEAD ); if( bobbleHead ) { bobbleHead->SetVelocity( velocity ); } } } void CVehicle::ApplyBeastModeToImpacts( phCachedContactIterator& impacts ) { while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity && pOtherEntity->GetIsTypeVehicle() ) { CVehicle* pOtherVehicle = static_cast< CVehicle* >( pOtherEntity ); TUNE_GROUP_FLOAT( VEHICLE_BEAST_MODE, MASS_SCALE, 10.0f, 0.1f, 20.0f, 0.1f ); if( m_BeastVehicle ) { float massScale = Min( 1.0f, pOtherVehicle->GetMass() / ( GetMass() * MASS_SCALE ) ); impacts.SetMassInvScales( massScale, 1.0f ); } else if( pOtherVehicle->GetIsBeastVehicle() ) { float massScale = Min( 1.0f, GetMass() / ( pOtherVehicle->GetMass() * MASS_SCALE ) ); impacts.SetMassInvScales( 1.0f, massScale ); } if( m_BeastVehicle || pOtherVehicle->GetIsBeastVehicle() ) { TUNE_GROUP_FLOAT( VEHICLE_BEAST_MODE, ELASTICITY_SCALE, 5.0f, 0.1f, 10.0f, 0.1f ); float elasticty = impacts.GetElasticity(); elasticty = Min( 1.0f, elasticty * ELASTICITY_SCALE ); impacts.SetElasticity( elasticty ); } } ++impacts; } } CVehicle* CVehicle::FindRammingScoopVehicle( phCachedContactIterator& impacts ) { static const int numRammingScoopBones = 4; static const eHierarchyId rammingScoopBoneIds[ numRammingScoopBones ] = { VEH_RAMMING_SCOOP, VEH_SCOOP_1_MOD, VEH_SCOOP_2_MOD, VEH_SCOOP_3_MOD }; while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity && pOtherEntity->GetIsTypeVehicle() ) { CVehicle* pOtherVehicle = static_cast< CVehicle* >( pOtherEntity ); if( HasRammingScoop() && pOtherVehicle->m_RampOrRammingAllowed && GetAttachedTrailer() != pOtherVehicle ) { for( int i = 0; i < numRammingScoopBones; i++ ) { int rammingScoopIndex = GetBoneIndex( rammingScoopBoneIds[ i ] ); if( rammingScoopIndex > -1 ) { rammingScoopIndex = GetVehicleFragInst()->GetComponentFromBoneIndex( rammingScoopIndex ); if( impacts.GetMyComponent() == rammingScoopIndex ) { return pOtherVehicle; } } } } else if( pOtherVehicle->HasRammingScoop() && m_RampOrRammingAllowed && pOtherVehicle->GetAttachedTrailer() != this ) { for( int i = 0; i < numRammingScoopBones; i++ ) { int rammingScoopIndex = pOtherVehicle->GetBoneIndex( rammingScoopBoneIds[ i ] ); if( rammingScoopIndex > -1 ) { rammingScoopIndex = pOtherVehicle->GetVehicleFragInst()->GetComponentFromBoneIndex( rammingScoopIndex ); if( impacts.GetOtherComponent() == rammingScoopIndex ) { return this; } } } } } ++impacts; } return NULL; } void CVehicle::ApplyRammingScoopToImpact( CVehicle* pOtherVehicle, phCachedContactIterator& impacts ) { Vector3 applyImpulsePosition = GetVelocity(); float relativeVelocity = Abs( ( applyImpulsePosition - pOtherVehicle->GetVelocity() ).Mag() ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, MIN_VELOCITY, 1.0f, 0.0f, 100.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, MAX_VELOCITY, 3.0f, 0.0f, 100.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, FORCE_SCALE, 90.0f, 0.0f, 500.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, OTHER_BOUND_SCALE, 0.8f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, MIN_FORCE_OFFSET_SCALE_X, 1.5f, 0.0f, 2.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, FORCE_OFFSET_Z, 0.0f, -10.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, MIN_MASS, 200.0f, 0.0f, 100000.0f, 1.0f ); TUNE_GROUP_BOOL( VEHICLE_RAMMING_SCOOP, APPLY_AS_IMPULSE, true ); TUNE_GROUP_FLOAT( VEHICLE_RAMMING_SCOOP, IMPACT_DAMAGE, ms_scoopCarCollisionDamageScale, 0.0f, 10000.0f, 0.1f ); if( relativeVelocity > MIN_VELOCITY && pOtherVehicle->GetMass() > MIN_MASS ) { const Matrix34& matMatrix = RCC_MATRIX34( GetMatrixRef() ); applyImpulsePosition = matMatrix.d; applyImpulsePosition += GetBoundRadius() * matMatrix.b; const Matrix34& matOtherMatrix = RCC_MATRIX34( pOtherVehicle->GetMatrixRef() ); matOtherMatrix.UnTransform( applyImpulsePosition ); applyImpulsePosition.Normalize(); applyImpulsePosition.z = FORCE_OFFSET_Z; if( Abs( applyImpulsePosition.x ) < MIN_FORCE_OFFSET_SCALE_X ) { if( applyImpulsePosition.x < 0.0f ) { applyImpulsePosition.x = -MIN_FORCE_OFFSET_SCALE_X; } else { applyImpulsePosition.x = MIN_FORCE_OFFSET_SCALE_X; } } applyImpulsePosition.Normalize(); float otherBoundRadius = pOtherVehicle->GetBoundRadius(); applyImpulsePosition *= otherBoundRadius * OTHER_BOUND_SCALE; float impulseForce = Min( relativeVelocity, MAX_VELOCITY ) * FORCE_SCALE; Vector3 impulse = matMatrix.c * impulseForce; if( APPLY_AS_IMPULSE ) { pOtherVehicle->ApplyImpulse( impulse, applyImpulsePosition, 0, true ); } else { impulse *= fwTimer::GetInvTimeStep(); matOtherMatrix.Transform3x3( applyImpulsePosition ); pOtherVehicle->ApplyTorque( impulse, applyImpulsePosition ); } //static float debugLineScale = 0.001f; //Vector3 debugLineStart = applyImpulsePosition + matOtherMatrix.d; //Vector3 debugLineEnd = debugLineStart + ( impulse * debugLineScale ); //grcDebugDraw::Line( debugLineStart, debugLineEnd, Color_red, 500 ); //static bool useSolidSphere = true; //grcDebugDraw::Sphere( debugLineStart, 0.25f, Color_blue, useSolidSphere, -1); if( !m_disableRampCarImpactDamage && !CPhysics::ms_bInArenaMode ) { Vector3 VecEnginePosLocal = VEC3_ZERO; if( GetBoneIndex( VEH_ENGINE ) > -1 ) { Matrix34 matEngineLocal = GetLocalMtx( GetBoneIndex( VEH_ENGINE ) ); VecEnginePosLocal = matEngineLocal.d; } #if __DEBUG GetVehicleDamage()->ApplyDamageToEngine( this, DAMAGE_TYPE_COLLISION, IMPACT_DAMAGE, VecEnginePosLocal, -ZAXIS, ZAXIS, false, true, 0.0f ); #else GetVehicleDamage()->ApplyDamageToEngine( this, DAMAGE_TYPE_COLLISION, ms_scoopCarCollisionDamageScale, VecEnginePosLocal, -ZAXIS, ZAXIS, false, true, 0.0f ); #endif // #if __DEBUG } impacts.Reset(); while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity == pOtherVehicle ) { impacts.SetFriction( 0.0f ); impacts.SetMassInvScales( 0.5f, 1.0f ); } ++impacts; } impacts.Reset(); } } void CVehicle::ApplyRammingBarToImpact( CPhysical* pOtherEntity, phCachedContactIterator& impacts ) { static const int numRammingBarBones = 4; static const eHierarchyId rammingBarBoneIds[ numRammingBarBones ] = { VEH_RAMMING_BAR_1, VEH_RAMMING_BAR_2, VEH_RAMMING_BAR_3, VEH_RAMMING_BAR_4 }; if( !GetWeaponModHasElectricEffect() || CVehicle::sm_bDisableWeaponBladeForces ) { return; } for( int i = 0; i < numRammingBarBones; i++ ) { int rammingBarIndex = GetBoneIndex( rammingBarBoneIds[ i ] ); if( rammingBarIndex > -1 ) { rammingBarIndex = GetVehicleFragInst()->GetComponentFromBoneIndex( rammingBarIndex ); if( impacts.GetMyComponent() == rammingBarIndex ) { Vector3 normalisedVelocity = GetVelocity(); normalisedVelocity.Normalize(); if( pOtherEntity->GetIsTypeVehicle() ) { static_cast< CVehicle* >( pOtherEntity )->GetVehicleDamage()->ApplyDamage( this, DAMAGE_TYPE_ELECTRIC, WEAPONTYPE_RAMMEDBYVEHICLE, 1.0f, Vector3( 0.0f, 0.0f, 0.0f ), -normalisedVelocity, -normalisedVelocity, impacts.GetOtherComponent(), 0, -1, false, true, 0.0f, false, false, false, false, true ); } else if( pOtherEntity->GetIsTypePed() ) { CWeaponDamage::GeneratePedDamageEvent( this, static_cast( pOtherEntity ), WEAPONTYPE_ELECTRICFENCE, 0.1f, VEC3V_TO_VECTOR3( pOtherEntity->GetTransform().GetPosition() ), NULL, CPedDamageCalculator::DF_None ); } break; } } } } void CVehicle::UpdateInvMassForRammingBarImpacts( phCachedContactIterator& impacts ) { static const int numRammingBarBones = 4; static const eHierarchyId rammingBarBoneIds[ numRammingBarBones ] = { VEH_RAMMING_BAR_1, VEH_RAMMING_BAR_2, VEH_RAMMING_BAR_3, VEH_RAMMING_BAR_4 }; if( CVehicle::sm_bDisableWeaponBladeForces ) { return; } int myRammingBarComponent = -1; for( int i = 0; i < numRammingBarBones; i++ ) { int rammingBarIndex = GetBoneIndex( rammingBarBoneIds[ i ] ); if( rammingBarIndex > -1 ) { myRammingBarComponent = GetVehicleFragInst()->GetComponentFromBoneIndex( rammingBarIndex ); if( myRammingBarComponent > -1 && GetVehicleFragInst()->GetArchetype() && GetVehicleFragInst()->GetArchetype()->GetBound() && ( static_cast< phBoundComposite* >( GetVehicleFragInst()->GetArchetype()->GetBound() )->GetIncludeFlags( myRammingBarComponent ) & ( ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE ) ) ) { break; } } } while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity && pOtherEntity->GetIsTypeVehicle() ) { CVehicle* pOtherVehicle = static_cast( pOtherEntity ); int otherRammingBarComponent = -1; for( int i = 0; i < numRammingBarBones; i++ ) { int rammingBarIndex = pOtherVehicle->GetBoneIndex( rammingBarBoneIds[ i ] ); if( rammingBarIndex > -1 ) { otherRammingBarComponent = pOtherVehicle->GetVehicleFragInst()->GetComponentFromBoneIndex( rammingBarIndex ); if( otherRammingBarComponent > -1 && pOtherVehicle->GetVehicleFragInst()->GetArchetype() && pOtherVehicle->GetVehicleFragInst()->GetArchetype()->GetBound() && ( static_cast< phBoundComposite* >( pOtherVehicle->GetVehicleFragInst()->GetArchetype()->GetBound() )->GetIncludeFlags( otherRammingBarComponent ) & ( ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE ) ) ) { break; } } } TUNE_GROUP_FLOAT( ARENA_MODE, RAMMING_BAR_MASS_SCALE, 2.0f, 0.1f, 20.0f, 0.1f ); if( impacts.GetMyComponent() == myRammingBarComponent && impacts.GetOtherComponent() != otherRammingBarComponent ) { float massScale = Min( 1.0f, pOtherVehicle->GetMass() / ( GetMass() * RAMMING_BAR_MASS_SCALE ) ); impacts.SetMassInvScales( massScale, 1.0f ); } else if( impacts.GetMyComponent() != myRammingBarComponent && impacts.GetOtherComponent() == otherRammingBarComponent ) { float massScale = Min( 1.0f, GetMass() / ( pOtherVehicle->GetMass() * RAMMING_BAR_MASS_SCALE ) ); impacts.SetMassInvScales( 1.0f, massScale ); } } ++impacts; } } void CVehicle::ModifyPedImpacts( phCachedContactIterator& impacts ) { bool vehicleNeedsModifiedImpacts = GetModelIndex() == MI_CAR_PHANTOM || ( MI_CAR_PHANTOM3.IsValid() && GetModelIndex() == MI_CAR_PHANTOM3 ) || ( MI_TRAILER_TRAILERLARGE.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERLARGE ); static dev_float minSpeedToModifyImpacts = 25.0f; if( ms_modifyPhantomCollisionWithPeds && vehicleNeedsModifiedImpacts && GetVelocity().Mag2() > minSpeedToModifyImpacts ) { const fwTransform &transform = GetTransform(); while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity && pOtherEntity->GetIsTypePed() ) { Vec3V newNormal = impacts.GetMyPosition(); newNormal = transform.UnTransform( newNormal ); newNormal = NormalizeSafe( newNormal, Vec3V( 0.0f, 1.0f, 0.0f ) ); Vec3V newPosition = newNormal; newPosition *= ScalarV( GetBoundRadius() ); newPosition = Min( VECTOR3_TO_VEC3V( GetBoundingBoxMax() ), newPosition ); newPosition = Max( VECTOR3_TO_VEC3V( GetBoundingBoxMin() ), newPosition ); newNormal = transform.Transform3x3( -newNormal ); newPosition = transform.Transform( newPosition ); impacts.SetMyNormal( newNormal ); impacts.SetMyPosition( newPosition ); } ++impacts; } } } CVehicle* CVehicle::FindRampVehicle( phCachedContactIterator& impacts ) { const Matrix34& matMatrix = RCC_MATRIX34( GetMatrixRef() ); const Vector3 myVehUpDir = matMatrix.c; const float verticalThreshold = 0.3f; CVehicle* pResult = NULL; if( myVehUpDir.GetZ() < verticalThreshold ) { return NULL; } if( !HasRamp() && m_RampOrRammingAllowed ) { return NULL; } while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity && pOtherEntity->GetIsTypeVehicle() ) { CVehicle* pOtherVehicle = static_cast< CVehicle* >( pOtherEntity ); Vec3V myNormal; impacts.GetMyNormal( myNormal ); Vec3V otherNormal; impacts.GetOtherNormal( otherNormal ); if( ShouldApplyRampToImpact( this, pOtherVehicle, myNormal ) && ( pResult == NULL || ( pResult->m_hasHitRampCar && !pOtherVehicle->m_hasHitRampCar ) ) ) { pResult = pOtherVehicle; } else if( ShouldApplyRampToImpact( pOtherVehicle, this, otherNormal ) && ( pResult == NULL || ( pResult->m_hasHitRampCar && !this->m_hasHitRampCar ) ) ) { pResult = this; } } ++impacts; } return pResult; } bool CVehicle::ShouldApplyRampToImpact( CVehicle* pRampCar, CVehicle* pOtherVehicle, Vec3V& rampCarNormal ) { TUNE_GROUP_FLOAT( VEHICLE_RAMP, MAX_NORMAL_Z_THRESHOLD, 0.4f, -1.0f, 1.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, MIN_NORMAL_Z_THRESHOLD, -0.66f, -1.0f, 1.0f, 0.1f ); // don't apply ramp impacts to other ramp cars // or if the other vehicle has been blocked from having ramp impacts applied if( pRampCar->HasRamp() && !pOtherVehicle->HasRamp() && pOtherVehicle->m_RampOrRammingAllowed ) { const Mat34V& rampCarMatrix = pRampCar->GetMatrixRef(); const Mat34V& otherCarMatrix = pOtherVehicle->GetMatrixRef(); // only apply the ramp impact if the contact opposite direction to the vehicle forward // and the up part of the normal isn't facing up too much // or the other vehicle is underneath the ramp car if( Dot( rampCarNormal, rampCarMatrix.GetCol1() ).Getf() < 0.0f && ( rampCarNormal.GetZf() < MIN_NORMAL_Z_THRESHOLD || ( ( ( rampCarMatrix.d().GetZf() <= otherCarMatrix.d().GetZf() || pRampCar->GetNumContactWheels() > 1 ) && rampCarNormal.GetZf() < MAX_NORMAL_Z_THRESHOLD ) ) ) ) { return true; } } return false; } void CVehicle::ApplyRampToImpacts( CVehicle* pOtherVehicle, phCachedContactIterator& impacts ) { Vector3 applyImpulsePosition = GetVelocity(); float relativeVelocity = Abs( ( applyImpulsePosition - pOtherVehicle->GetVelocity() ).Mag() ); Vec3V averageNormal( VEC3_ZERO ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, MIN_VELOCITY, 0.0f, 0.0f, 100.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, MAX_VELOCITY, 20.0f, 0.0f, 100.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, FORCE_SCALE, 10.0f, 0.0f, 500.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, OTHER_BOUND_SCALE, 1.0f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, FORCE_OFFSET_Z, 0.0f, -10.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, MIN_MASS_FOR_EXTRA_FORCE, 200.0f, 0.0f, 10000.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, MAX_MASS_FOR_EXTRA_FORCE, 100000.0f, 0.0f, 100000.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, REDUCE_VELOCITY_SCALE, 0.15f, 0.0f, 10000.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, OTHER_VEHICLE_LOW_SPEED_VELOCITY_REDUCTION, 0.1f, 0.0f, 1.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, OTHER_VEHICLE_HIGH_SPEED_VELOCITY_REDUCTION, 0.5f, 0.0f, 1.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, OTHER_VEHICLE_LOW_SPEED_VELOCITY_THRESHOLD, 1.0f, 0.0f, 1.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, OTHER_VEHICLE_HIGH_SPEED_VELOCITY_THRESHOLD, 20.0f, 0.0f, 10000.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, IMPACT_DAMAGE, ms_rampCarCollisionDamageScale, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, SIDE_IMPULSE_FORCE_SCALE, -0.75f, -1000.0f, 1000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, SIDE_IMPULSE_MIN_OFFSET, 0.1f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, SIDE_IMPULSE_MAX_MASS_SCALE, 2000.0f, 0.0f, 100000.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, NORMALISED_TORQUE_SCALE, 0.75f, -1000.0f, 1000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, NORMALISED_TORQUE_MAX_MASS_SCALE, 2000.0f, 0.0f, 100000.0f, 1.0f ); TUNE_GROUP_FLOAT( VEHICLE_RAMP, MIN_VELOCITY_FOR_VEHICLE_DAMAGE, 20.0f, 0.0f, 100.0f, 1.0f ); // If we hit a police car, make sure wanted level gets set if(pOtherVehicle && pOtherVehicle->HasAliveLawPedsInIt()) { CPed* pInflictorPed = GetDriver(); if( pInflictorPed && pInflictorPed->IsLocalPlayer() ) { dev_float sfMinInflictorVelocitySetWantedLevel = 1.0f; float fVehicleVelocitySq = GetVelocity().Mag2(); if(fVehicleVelocitySq > square(sfMinInflictorVelocitySetWantedLevel) && fVehicleVelocitySq > pOtherVehicle->GetVelocity().Mag2() ) { CWanted* pPlayerWanted = pInflictorPed->GetPlayerWanted(); if(pPlayerWanted && pPlayerWanted->GetWantedLevel() == WANTED_CLEAN && pPlayerWanted->m_fMultiplier ) { pPlayerWanted->SetWantedLevelNoDrop(VEC3V_TO_VECTOR3(pInflictorPed->GetTransform().GetPosition()), WANTED_LEVEL1, WANTED_CHANGE_DELAY_INSTANT, WL_FROM_LAW_RESPONSE_EVENT); } } } } if( relativeVelocity > MIN_VELOCITY ) { Assertf( !( GetDriver() && GetDriver()->IsLocalPlayer() ) || pOtherVehicle->m_vehicleAiLod.GetDummyMode() != VDM_SUPERDUMMY, "CVehicle::ApplyRampToImpacts - colliding with a dummy vehicle" ); const Matrix34& matMatrix = RCC_MATRIX34( GetMatrixRef() ); float otherVehicleMass = pOtherVehicle->GetMass(); applyImpulsePosition = matMatrix.d; const Matrix34& matOtherMatrix = RCC_MATRIX34( pOtherVehicle->GetMatrixRef() ); matOtherMatrix.UnTransform( applyImpulsePosition ); Vector3 otherVehiclePositionInLocalSpace = matOtherMatrix.d; matMatrix.UnTransform( otherVehiclePositionInLocalSpace ); if( otherVehicleMass > MIN_MASS_FOR_EXTRA_FORCE || m_ScriptRampImpulseScale > 1.0f ) { applyImpulsePosition.Normalize(); applyImpulsePosition.z = FORCE_OFFSET_Z; Vector3 otherBoundMax = pOtherVehicle->GetBoundingBoxMax(); applyImpulsePosition *= otherBoundMax * OTHER_BOUND_SCALE; float impulseForce = Min( relativeVelocity, MAX_VELOCITY ) * FORCE_SCALE; float impulseMassScale = 0.0f; if( m_ScriptRampImpulseScale > 1.0f ) { impulseMassScale = Min( 1.0f, otherVehicleMass / MAX_MASS_FOR_EXTRA_FORCE ); } else { impulseMassScale = Min( 1.0f, ( otherVehicleMass - MIN_MASS_FOR_EXTRA_FORCE ) / MAX_MASS_FOR_EXTRA_FORCE ); } impulseForce *= impulseMassScale; Vector3 impulse = matMatrix.c * impulseForce; if( m_applyRampCarSideImpulse ) { #if __BANK physicsDisplayf( "CVehicle::ApplyRampToImpacts: Applying side impluse to vehicle impact: ramp car %s, other car: %s", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "", pOtherVehicle->GetNetworkObject() ? pOtherVehicle->GetNetworkObject()->GetLogName() : "" ); #endif // #if __BANK if( Abs( otherVehiclePositionInLocalSpace.x ) > SIDE_IMPULSE_MIN_OFFSET ) { Vector3 sideImpulse = matMatrix.a * impulseForce * SIDE_IMPULSE_FORCE_SCALE * Min( SIDE_IMPULSE_MAX_MASS_SCALE, pOtherVehicle->GetMass() ); if( otherVehiclePositionInLocalSpace.x > 0.0f ) { sideImpulse *= -1.0f; } pOtherVehicle->ApplyImpulseCg( sideImpulse ); } } impulse *= m_ScriptRampImpulseScale; if( m_ScriptRampImpulseScale > 1.0f ) { Vector3 upImpulse = Vector3( 0.0f, 0.0f, impulseForce * m_ScriptRampImpulseScale ); pOtherVehicle->ApplyImpulseCg( upImpulse ); } matOtherMatrix.Transform3x3( applyImpulsePosition ); pOtherVehicle->ApplyImpulse( impulse, applyImpulsePosition ); } const Vec3V myVehUpDir = GetMatrix().GetCol2(); const ScalarV verticalThreshold( -0.66f ); while( !impacts.AtEnd() ) { phInst* otherInstance = impacts.GetOtherInstance(); CEntity* pOtherEntity = otherInstance ? (CEntity*)otherInstance->GetUserData() : NULL; if( pOtherEntity == pOtherVehicle ) { Vec3V myNormal; impacts.GetMyNormal( myNormal ); const ScalarV verticalDot = Dot( myNormal, myVehUpDir ); if( IsGreaterThanAll( verticalDot, verticalThreshold ) != 0 ) { impacts.SetMyNormal( -myVehUpDir ); impacts.SetOtherNormal( myVehUpDir ); } impacts.SetFriction( 0.0f ); impacts.SetMassInvScales(0.0f,1.0f); } if( otherVehicleMass > MAX_MASS_FOR_EXTRA_FORCE ) { Vec3V myNormal; impacts.GetMyNormal( myNormal ); averageNormal += myNormal; } ++impacts; } if( otherVehicleMass > MAX_MASS_FOR_EXTRA_FORCE ) { averageNormal = Normalize( averageNormal ); Vector3 slowDownImpulse = matMatrix.b * GetVelocity().Dot( VEC3V_TO_VECTOR3( averageNormal ) ); slowDownImpulse *= REDUCE_VELOCITY_SCALE; slowDownImpulse *= GetMass(); float massRatio = ( otherVehicleMass - MAX_MASS_FOR_EXTRA_FORCE ) / GetMass(); massRatio *= REDUCE_VELOCITY_SCALE; slowDownImpulse *= massRatio; float maxImpulse = Min( 0.5f * GetMass() * GetVelocity().Mag2(), 250.0f * GetMass() ); if( slowDownImpulse.Mag2() > maxImpulse * maxImpulse ) { slowDownImpulse.Normalize(); slowDownImpulse *= maxImpulse; } ApplyImpulseCg( slowDownImpulse ); } Vector3 otherVehicleVelocity = pOtherVehicle->GetVelocity(); //float otherVelocityMag2 = otherVehicleVelocity.Mag2(); CPed* pOtherDriver = pOtherVehicle->GetDriver(); if( relativeVelocity > OTHER_VEHICLE_LOW_SPEED_VELOCITY_THRESHOLD && ( !pOtherDriver || !pOtherDriver->IsControlledByLocalOrNetworkPlayer() ) ) { float velocityReductionScale = Min( 1.0f, ( relativeVelocity - OTHER_VEHICLE_LOW_SPEED_VELOCITY_THRESHOLD ) / ( OTHER_VEHICLE_HIGH_SPEED_VELOCITY_THRESHOLD - OTHER_VEHICLE_LOW_SPEED_VELOCITY_THRESHOLD ) ); velocityReductionScale = OTHER_VEHICLE_LOW_SPEED_VELOCITY_REDUCTION + ( velocityReductionScale * ( OTHER_VEHICLE_HIGH_SPEED_VELOCITY_REDUCTION - OTHER_VEHICLE_LOW_SPEED_VELOCITY_REDUCTION ) ); // slow down the vehicle that has been hit to try and get them to land sooner so they can convert to dummy mode otherVehicleVelocity.z = 0.0f; Vector3 velocityReductionImpulse = otherVehicleVelocity * -velocityReductionScale * pOtherVehicle->GetMass(); pOtherVehicle->ApplyImpulseCg( velocityReductionImpulse ); pOtherVehicle->m_hasHitRampCar = true; } if( m_normaliseRampHitVelocity ) { float targetUpVelocity = Abs( GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ) ); static float sf_upVelocityFactor = 0.3f; targetUpVelocity *= sf_upVelocityFactor; Vector3 otherVehicleVelocity = pOtherVehicle->GetVelocity(); otherVehicleVelocity.z = targetUpVelocity; pOtherVehicle->SetVelocity( otherVehicleVelocity ); if( Abs( otherVehiclePositionInLocalSpace.x ) > SIDE_IMPULSE_MIN_OFFSET ) { Vector3 rotationTorque = matMatrix.b * otherVehicleVelocity.z * otherVehiclePositionInLocalSpace.x * NORMALISED_TORQUE_SCALE * Min( NORMALISED_TORQUE_MAX_MASS_SCALE, pOtherVehicle->GetMass() ); pOtherVehicle->ApplyTorque( rotationTorque ); } } if( !m_disableRampCarImpactDamage && relativeVelocity > MIN_VELOCITY_FOR_VEHICLE_DAMAGE ) { Vector3 VecEnginePosLocal = VEC3_ZERO; if( GetBoneIndex( VEH_ENGINE ) > -1 ) { Matrix34 matEngineLocal = GetLocalMtx( GetBoneIndex( VEH_ENGINE ) ); VecEnginePosLocal = matEngineLocal.d; } #if __DEBUG GetVehicleDamage()->ApplyDamageToEngine( this, DAMAGE_TYPE_COLLISION, IMPACT_DAMAGE, VecEnginePosLocal, -ZAXIS, ZAXIS, false, true, 0.0f ); #else GetVehicleDamage()->ApplyDamageToEngine( this, DAMAGE_TYPE_COLLISION, ms_rampCarCollisionDamageScale, VecEnginePosLocal, -ZAXIS, ZAXIS, false, true, 0.0f ); #endif // #if __DEBUG } } } CVehicle* CVehicle::CheckForSideShuntImpact( phCachedContactIterator& impacts, CVehicle* otherVehicle ) { float sideShuntForce = otherVehicle->GetSideShuntForce(); if( sideShuntForce != 0.0f ) { Vec3V shuntVec = otherVehicle->GetTransform().GetRight() * ScalarV( sideShuntForce ); Vec3V myNormal; impacts.GetMyNormal( myNormal ); float shuntAlongNormalForce = Dot( shuntVec, myNormal ).Getf(); if( m_sideShuntForce != 0.0f ) { Vec3V myShuntVec = GetTransform().GetRight() * ScalarV( m_sideShuntForce ); Vec3V otherNormal; impacts.GetOtherNormal( otherNormal ); float myShuntAlongForce = Abs( Dot( myShuntVec, otherNormal ).Getf() ); if( myShuntAlongForce > Abs( shuntAlongNormalForce ) ) { shuntAlongNormalForce = 0.0f; } else { shuntAlongNormalForce -= myShuntAlongForce * Sign( shuntAlongNormalForce ); } float speedForFullShunt = pHandling->m_fInitialDriveMaxFlatVel * 0.1f; float speedThreshold = pHandling->m_fInitialDriveMaxFlatVel * 0.025f; float shuntVelocityScale = Clamp( ( Abs( Dot( VECTOR3_TO_VEC3V( GetVelocity() ), GetTransform().GetForward() ).Getf() ) - speedThreshold ) / speedForFullShunt, 0.0f, 1.0f ); shuntAlongNormalForce *= shuntVelocityScale; } if( shuntAlongNormalForce > 0.0f ) { Vec3V shuntImpulse = myNormal * ScalarV( shuntAlongNormalForce ); static ScalarV shuntImpulseScale( 7.5f ); float massScale = Clamp( otherVehicle->GetMass() / GetMass(), 0.5f, 2.0f ); shuntImpulse *= ScalarV( massScale * GetMass() ) * shuntImpulseScale; ApplyImpulseCg( VEC3V_TO_VECTOR3( shuntImpulse ) ); m_hitBySideShunt = true; otherVehicle->SetHitBySideShunt( true, this ); m_lastSideShuntVehicle = otherVehicle; vehicleDebugf1( "CVehicle::CheckForSideShuntImpact: inflictor: %s, Inflictor Pos: %.2f, %.2f, %.2f, ShuntImpulse: %.2f, %.2f, %.2f Victim: %s", GetNetworkObject() ? GetNetworkObject()->GetLogName() : GetDebugName(), GetTransform().GetPosition().GetXf(), GetTransform().GetPosition().GetYf(), GetTransform().GetPosition().GetZf(), shuntImpulse.GetXf(), shuntImpulse.GetYf(), shuntImpulse.GetZf(), otherVehicle->GetNetworkObject() ? otherVehicle->GetNetworkObject()->GetLogName() : otherVehicle->GetDebugName() ); return otherVehicle; } } return NULL; } void CVehicle::ProcessBoostPhysics( float fTimeStep ) { m_activateBoostEffect = false; if( m_fSlowdownDuration > 0.0f ) { m_fSlowdownDuration -= fTimeStep; bool endSlowDown = false; Vector3 velocity = GetVelocity(); velocity = VEC3V_TO_VECTOR3( GetTransform().UnTransform3x3( VECTOR3_TO_VEC3V( velocity ) ) ); float acceleration = velocity.y - mf_BoostAmount; if( acceleration <= 0.0f ) { m_fSlowdownDuration = 0.0f; } if( m_fSlowdownDuration <= 0.0f ) { endSlowDown = true; m_fSlowdownDuration = 0.0f; acceleration /= fTimeStep; } else { acceleration /= m_fSlowdownDuration; } if( acceleration > 0.0f ) { Vector3 slowDownForce( 0.0f, Min( acceleration, 259.0f ), 0.0f ); velocity.Normalize(); slowDownForce.y += ( GetTransform().GetForward().GetZf() * GRAVITY ) * ( GetTransform().GetForward().GetZf() * GRAVITY ); slowDownForce.y = Min( slowDownForce.y, 259.0f ); slowDownForce = VEC3V_TO_VECTOR3( Transform3x3( GetTransform().GetMatrix(), VECTOR3_TO_VEC3V( slowDownForce ) ) ); slowDownForce *= -GetMass(); ApplyForceCg( slowDownForce ); m_activateBoostEffect = true; } } else if( mf_BoostAppliedTimer > 0.0f ) { mf_BoostAppliedTimer -= fTimeStep; if( mf_BoostAppliedTimer < 0.0f ) { mf_BoostAmount *= 1.0f - ( -mf_BoostAppliedTimer / fTimeStep ); mf_BoostAppliedTimer = 0.0f; } TUNE_GROUP_FLOAT( STUNT_SPEED_TUNE, MIN_SPEED_FOR_SPEED_BOOST, 4.0f, 0.0f, 100.0f, 0.1f ); Vector3 velocity = GetVelocity(); float velocityMag2 = GetVelocity().Mag2(); if( mf_BoostAmount > 0.0f && velocityMag2 > MIN_SPEED_FOR_SPEED_BOOST ) { velocity.Normalize(); float speedBoostScale = Max( 0.0f, Dot( GetTransform().GetForward(), VECTOR3_TO_VEC3V( velocity ) ).Getf() ); if( speedBoostScale > 0.0f ) { TUNE_GROUP_FLOAT( STUNT_SPEED_TUNE, MAX_SPEED_FOR_SPEED_BOOST, 3400.0f, 0.0f, 10000.0f, 1.0f ); TUNE_GROUP_FLOAT( STUNT_SPEED_TUNE, MAX_SPEED_FOR_SPEED_BOOST_FOR_PANES, 6400.0f, 0.0f, 10000.0f, 1.0f ); float maxSpeedForBoost = !InheritsFromPlane() ? MAX_SPEED_FOR_SPEED_BOOST : MAX_SPEED_FOR_SPEED_BOOST_FOR_PANES; if( velocityMag2 > maxSpeedForBoost ) { speedBoostScale *= Max( 1.1f - ( velocityMag2 / maxSpeedForBoost ), 0.0f ) * 10.0f; } if( speedBoostScale > 0.0f ) { Vector3 speedBoost( 0.0f, mf_BoostAmount, 0.0f ); speedBoost *= GetMass(); speedBoost = VEC3V_TO_VECTOR3( Transform3x3( GetTransform().GetMatrix(), VECTOR3_TO_VEC3V( speedBoost ) ) ); speedBoost *= speedBoostScale; ApplyForceCg( speedBoost ); } m_activateBoostEffect = true; } } } } void CVehicle::ProcessBoostVFX() { if( m_fSlowdownDuration > 0.0f ) { CPed* pFollowPlayer = CGameWorld::FindFollowPlayer(); const bool bFollowPlayerInThisVehicle = pFollowPlayer ? (pFollowPlayer->GetIsInVehicle() && (pFollowPlayer->GetMyVehicle() == this)) : false; const bool applySlowDownVFX = m_activateBoostEffect && m_fSlowdownDuration > fwTimer::GetTimeStep(); if( bFollowPlayerInThisVehicle && !ms_DisableSlowDownEffect ) { if( applySlowDownVFX && m_iCurrentSpeedBoostObjectID != 0 && m_iPrevSpeedBoostObjectID != m_iCurrentSpeedBoostObjectID ) { if( !ANIMPOSTFXMGR.IsRunning( ms_slowDownScreenEffect ) ) { ANIMPOSTFXMGR.Start( ms_slowDownScreenEffect, 0U, false, false, false, 0U, AnimPostFXManager::kKERSBoost ); } } else { if( ANIMPOSTFXMGR.IsStartPending( ms_slowDownScreenEffect ) ) { ANIMPOSTFXMGR.CancelStartRequest( ms_slowDownScreenEffect, AnimPostFXManager::kKERSBoost ); } } } if( m_VehicleAudioEntity && m_activateBoostEffect && m_iCurrentSpeedBoostObjectID != 0 && m_iPrevSpeedBoostObjectID != m_iCurrentSpeedBoostObjectID ) { if(!ms_DisableSlowDownEffect) { m_VehicleAudioEntity->TriggerStuntRaceSlowDown(); } m_iPrevSpeedBoostObjectID = m_iCurrentSpeedBoostObjectID; } } else if( mf_BoostAppliedTimer > 0.0f ) { CPed* pFollowPlayer = CGameWorld::FindFollowPlayer(); const bool bFollowPlayerInThisVehicle = pFollowPlayer ? (pFollowPlayer->GetIsInVehicle() && (pFollowPlayer->GetMyVehicle() == this)) : false; const bool applyBoostVFX = m_activateBoostEffect && mf_BoostAppliedTimer > 0.0f; atHashString boostEffectTUse = InheritsFromPlane() || InheritsFromHeli() ? ms_speedBoostAirScreenEffect : ms_speedBoostScreenEffect; if( bFollowPlayerInThisVehicle && !ms_DisableSpeedBoostEffect ) { if( applyBoostVFX && m_iCurrentSpeedBoostObjectID != 0 && m_iPrevSpeedBoostObjectID != m_iCurrentSpeedBoostObjectID ) { if( !ANIMPOSTFXMGR.IsRunning( boostEffectTUse ) ) { u32 boostDuration = (u32)Max( ( mf_BoostAppliedTimer * 1100.0f ), 1000.0f ); ANIMPOSTFXMGR.Start( boostEffectTUse, boostDuration, false, false, false, 0U, AnimPostFXManager::kKERSBoost ); } } else { if( ANIMPOSTFXMGR.IsStartPending( boostEffectTUse ) ) { ANIMPOSTFXMGR.CancelStartRequest( boostEffectTUse, AnimPostFXManager::kKERSBoost ); } } } if( m_VehicleAudioEntity && applyBoostVFX && m_iCurrentSpeedBoostObjectID != 0 && m_iPrevSpeedBoostObjectID != m_iCurrentSpeedBoostObjectID ) { if(!ms_DisableSpeedBoostEffect) { m_VehicleAudioEntity->TriggerStuntRaceSpeedBoost(); } m_iPrevSpeedBoostObjectID = m_iCurrentSpeedBoostObjectID; } } } void CVehicle::ProcessRamBarVFX() { if (GetWeaponModHasElectricEffect()) { g_vfxVehicle.UpdatePtFxElectricRamBar(this); } } void CVehicle::ProcessWeapons() { CVehicleWeaponMgr* pWeaponMgr = GetVehicleWeaponMgr(); if(pWeaponMgr) { for(int i = 0; i < pWeaponMgr->GetNumVehicleWeapons(); i++) { CVehicleWeapon* pWeapon = pWeaponMgr->GetVehicleWeapon(i); if(pWeapon) { // Separate variables to track the change so we don't call SetEnabled() on every weapon on every vehicle every frame bool bEnableWeapon = false; bool bDisableWeapon = false; const CWeaponInfo* pWeaponInfo = pWeapon->GetWeaponInfo(); if (pWeaponInfo) { if (pWeaponInfo->GetShouldBeEnabledOnlyWhenVehTransformed()) { if ((MI_HELI_AKULA.IsValid() && GetModelIndex() == MI_HELI_AKULA) || (MI_HELI_ANNIHILATOR2.IsValid() && GetModelIndex() == MI_HELI_ANNIHILATOR2)) { if (GetAreFoldingWingsDeployed()) { bEnableWeapon = true; } else { bDisableWeapon = true; } } else if (MI_CAR_CHERNOBOG.IsValid() && GetModelIndex() == MI_CAR_CHERNOBOG) { if (GetAreOutriggersFullyDeployed()) { bEnableWeapon = true; } else { bDisableWeapon = true; } } } } if (bEnableWeapon) { pWeapon->SetEnabled(this, true); } else if (bDisableWeapon) { pWeapon->SetEnabled(this, false); if (pWeapon->GetType() == VGT_VEHICLE_WEAPON_BATTERY) { CVehicleWeaponBattery* pBattery = static_cast(pWeapon); for (int i=0; i < pBattery->GetNumWeaponsInBattery(); i++) { CVehicleWeapon* pBatteryWeapon = pBattery->GetVehicleWeapon(i); if (pBatteryWeapon && pBatteryWeapon->GetType() == VGT_FIXED_VEHICLE_WEAPON) { const CFixedVehicleWeapon* pFixedVehicleWeapon = static_cast(pBatteryWeapon); CWeapon* pBatteryWeapon = pFixedVehicleWeapon->GetWeapon(); pBatteryWeapon->GetAudioComponent().SpinDownWeapon(); } } } } } } } if( !sb_disableWeaponBlades ) { for( int i = 0; i < m_numWeaponBlades; i++ ) { m_weaponBlades[ i ].Update( this ); } } } /////////////////////////////////////////////////////////////////////////// // FUNCTION UpdateUserEmissiveMultiplier // DOES Switches the user emissive multiplier value on/off // (For example, wheel lights on the Shotaro bike) /////////////////////////////////////////////////////////////////////////// void CVehicle::UpdateUserEmissiveMultiplier(bool bNewEngineOnValue, bool bOldEngineOnValue) { // Do this to switch Shotaro bike wheels on/off if( (MI_BIKE_SHOTARO.IsValid() && GetModelIndex() == MI_BIKE_SHOTARO) || (MI_CAR_VOLTIC2.IsValid() && GetModelIndex() == MI_CAR_VOLTIC2) || (MI_CAR_PATRIOT2.IsValid() && GetModelIndex() == MI_CAR_PATRIOT2) || (MI_BIKE_DEATHBIKE2.IsValid() && GetModelIndex() == MI_BIKE_DEATHBIKE2) ) { if(bNewEngineOnValue != bOldEngineOnValue) { float fEmissiveValue = 0.0f; if(true == bNewEngineOnValue) { fEmissiveValue = 1.0f; } CCustomShaderEffectVehicle* pShaderFx = static_cast(GetDrawHandler().GetShaderEffect()); pShaderFx->SetUserEmissiveMultiplier(fEmissiveValue); } } } bool CVehicle::CanToggleRocketBoost() { static dev_u32 sMinTimeToRetriggerRocketBoost = 500; return (fwTimer::GetTimeInMilliseconds() > ( m_boostToggledTime + sMinTimeToRetriggerRocketBoost )); } // static dev_float sfBoostOffset = -0.5f; static dev_float sfBoostForceMult = 100.0f; void CVehicle::ProcessRocketBoost() { Assert(HasRocketBoost()); if( GetDriver() && GetDriver()->IsLocalPlayer() && !GetDriver()->GetSpecialAbility() && !CTheScripts::GetIsInDirectorMode() && !CNewHud::GetIsOverrideSpecialAbilitySet() ) { CNewHud::SetToggleAbilityBar(true); uiDebugf3("CVehicle::ProcessRocketBoost::SetToggleAbilityBar true"); } if(m_bIsRocketBoosting) { // Deplete the boost bar if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) ) { float scaleRocketDrainSpeed = 1.0f; if( InheritsFromPlane() && GetStatus() == STATUS_WRECKED ) { scaleRocketDrainSpeed = 10.0f; } m_fRocketBoostRemaining = rage::Max( m_fRocketBoostRemaining - ( fwTimer::GetTimeStep() * scaleRocketDrainSpeed ), 0.0f ); } if(m_fRocketBoostRemaining != 0.0f) { Vector3 vForward = VEC3V_TO_VECTOR3(GetTransform().GetB()); bool hasVTOLBoost = GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_VERTICAL_ROCKET_BOOST ); if( hasVTOLBoost ) { vForward = VEC3V_TO_VECTOR3(GetTransform().GetC()); } float fFwdVelocity = GetVelocity().Dot(vForward); // Multiplier decreases as we get closer to the boost max speed float fVelocityMult = pHandling->m_fBoostMaxSpeed; if( MI_CAR_TOREADOR.IsValid() && GetModelIndex() == MI_CAR_TOREADOR && ( GetIsInWater() || IsInSubmarineMode() ) ) { TUNE_FLOAT( sfInWaterMaxSpeedMult, 0.5f, 0.0f, 100.0f, 0.01f ); fVelocityMult *= sfInWaterMaxSpeedMult; } fVelocityMult = ( fVelocityMult - fFwdVelocity ) / fVelocityMult; float fBoostForceMult = sfBoostForceMult; if(MI_CAR_TOREADOR.IsValid() && GetModelIndex() == MI_CAR_TOREADOR && GetIsInWater()) { TUNE_FLOAT(sfInWaterBoostMult, 1.5f, 0.0f, 100.0f,0.01f); fBoostForceMult = 1.0f + ( m_Buoyancy.GetSubmergedLevel() * sfInWaterBoostMult ); } // Apply a force based on speed at an offset ApplyForceCg( vForward * fBoostForceMult * GetMass() * fVelocityMult );//, vForward * sfBoostOffset ); if (ContainsLocalPlayer()) { // Give the controller a big kick CControlMgr::StartPlayerPadShakeByIntensity(50, 0.95f); // If in stunt camera mode, disable it if(SStuntJumpManager::IsInstantiated() && CStuntJumpManager::IsAStuntjumpInProgress()) { SStuntJumpManager::GetInstance().AbortStuntJumpInProgress(); } } } else { // Ran out of boost SetRocketBoosting(false); } SelectAppropriateGearForSpeed(); } } void CVehicle::SetRocketBoosting(bool bNewValue) { // If switching between states if(bNewValue != m_bIsRocketBoosting) { // If finishing a rocket boost if(bNewValue == false) { m_bCanRechargeRocketBoost = false; } m_boostToggledTime = fwTimer::GetTimeInMilliseconds(); } m_bIsRocketBoosting = bNewValue; } void CVehicle::SlideMechanicalPart( int iBoneIndex, float fDesiredOffset, float& rfCurrentOffset, Vector3 vSlideDirection, float fMovementSpeed ) { Assertf(iBoneIndex > -1, "Bone index for sliding vehicle part is invalid!"); const crBoneData* pBoneData = GetSkeletonData().GetBoneData(iBoneIndex); if(pBoneData) { Matrix34& rLocalMtx = RC_MATRIX34( GetSkeleton()->GetLocalMtx(iBoneIndex) ); float fMovementDirection = ( fDesiredOffset < rfCurrentOffset ? -1.0f : 1.0f); float fMinClampValue = ( fDesiredOffset < rfCurrentOffset ? fDesiredOffset : rfCurrentOffset ); float fMaxClampValue = ( fDesiredOffset > rfCurrentOffset ? fDesiredOffset : rfCurrentOffset ); rfCurrentOffset = Clamp(rfCurrentOffset + fwTimer::GetTimeStep() * fMovementDirection * fMovementSpeed, fMinClampValue, fMaxClampValue); rLocalMtx.d = VEC3V_TO_VECTOR3(pBoneData->GetDefaultTranslation()) + vSlideDirection * rfCurrentOffset; } } void CVehicle::OffsetBonePosition( int iBoneIndex, float fDesiredOffset, Vector3 vOffsetDirection ) { Assertf(iBoneIndex > -1, "Bone index for sliding vehicle part is invalid!"); const crBoneData* pBoneData = GetSkeletonData().GetBoneData(iBoneIndex); if(pBoneData) { Matrix34& rLocalMtx = RC_MATRIX34( GetSkeleton()->GetLocalMtx(iBoneIndex) ); rLocalMtx.d = VEC3V_TO_VECTOR3(pBoneData->GetDefaultTranslation()) + vOffsetDirection * fDesiredOffset; } } void CVehicle::RotateMechanicalPart( int iBoneIndex, float fDesiredOffset, float& rfCurrentOffset, eRotationAxis axis, float fMovementSpeed ) { Assertf(iBoneIndex > -1, "Bone index for rotating vehicle part is invalid!"); if( iBoneIndex > -1 ) { float fMovementDirection = ( fDesiredOffset < rfCurrentOffset ? -1.0f : 1.0f); float fMinClampValue = ( fDesiredOffset < rfCurrentOffset ? fDesiredOffset : rfCurrentOffset ); float fMaxClampValue = ( fDesiredOffset > rfCurrentOffset ? fDesiredOffset : rfCurrentOffset ); rfCurrentOffset = Clamp( rfCurrentOffset + fwTimer::GetTimeStep() * fMovementDirection * fMovementSpeed, fMinClampValue, fMaxClampValue ); SetBoneRotation( iBoneIndex, axis, rfCurrentOffset ); } } const int diJetWashEffectProbeResults = 10; void CVehicle::ProcessJetWash() { if(NetworkInterface::IsGameInProgress() && IsBeingRespotted()) { return; } // Blow away everything behind the jet if( sm_bJetWashEnabled && m_nVehicleFlags.bEngineOn && HasRocketBoost() && IsRocketBoosting() ) { //Find out the thrust force Vector3 vBlowDirection = VEC3V_TO_VECTOR3(GetTransform().GetB()); vBlowDirection.Negate(); TUNE_GROUP_FLOAT( VEHICLE_JET_WASH, EFFECT_DISTANCE, 2.5f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_JET_WASH, EFFECT_RADIUS, 0.5f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_JET_WASH, FORCE_SCALE, 1.0f, 0.0f, 10.0f, 0.1f ); float fBlowForce = sfBoostForceMult * GetMass() * FORCE_SCALE; bool hasJato = GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_JATO_BOOST_MOD ) || GetModelIndex() == MI_CAR_SCRAMJET.GetModelIndex(); // use the jato bones for the scramjet static const int numBones = 9; int startIndex = hasJato ? 1 : 0; eHierarchyId boostBoneIDs[ numBones ] = { VEH_OVERHEAT, VEH_ROCKET_BOOST, VEH_ROCKET_BOOST_2, VEH_ROCKET_BOOST_3, VEH_ROCKET_BOOST_4, VEH_ROCKET_BOOST_5, VEH_ROCKET_BOOST_6, VEH_ROCKET_BOOST_7, VEH_ROCKET_BOOST_8 }; for( int i = startIndex; i < numBones; i++ ) { // get the rocket boost bone Mat34V boneMtx; CVehicleModelInfo* pModelInfo = GetVehicleModelInfo(); s32 rocketBoostBoneId = pModelInfo->GetBoneIndex( boostBoneIDs[ i ] ); if( hasJato && rocketBoostBoneId == -1 && GetModelIndex() == MI_PLANE_BOMBUSHKA ) { rocketBoostBoneId = pModelInfo->GetBoneIndex( VEH_ROCKET_BOOST ); rocketBoostBoneId = rocketBoostBoneId | ((i) << 28); } if( rocketBoostBoneId > -1 ) { Matrix34 matGlobal; if( !GetThrustMatrixFromBoneIndex( RC_MAT34V( matGlobal ), rocketBoostBoneId ) ) { GetGlobalMtx( rocketBoostBoneId, matGlobal ); } //setup the capsule test. WorldProbe::CShapeTestHitPoint testHitPoints[ diJetWashEffectProbeResults ]; WorldProbe::CShapeTestResults capsuleResults( testHitPoints, diJetWashEffectProbeResults ); const u32 iTestFlags = ( ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE ); WorldProbe::CShapeTestCapsuleDesc capsuleDesc; capsuleDesc.SetResultsStructure( &capsuleResults ); capsuleDesc.SetIncludeFlags( iTestFlags ); capsuleDesc.SetExcludeEntity( this ); capsuleDesc.SetIsDirected( true ); capsuleDesc.SetDoInitialSphereCheck( true ); capsuleDesc.SetCapsule( matGlobal.d, matGlobal.d + vBlowDirection * EFFECT_DISTANCE, EFFECT_RADIUS ); WorldProbe::GetShapeTestManager()->SubmitTest( capsuleDesc ); for( WorldProbe::ResultIterator it = capsuleResults.begin(); it < capsuleResults.last_result(); ++it ) { if( it->IsAHit() ) { CEntity* pHitEntity = CPhysics::GetEntityFromInst( it->GetHitInst() ); bool bProcessHit = (pHitEntity && pHitEntity->GetIsPhysical() && pHitEntity->IsCollisionEnabled() && !NetworkInterface::AreInteractionsDisabledInMP(*SafeCast(const CEntity, pHitEntity), *SafeCast(const CEntity, this))); if( NetworkInterface::IsGameInProgress() && bProcessHit ) { if (pHitEntity->GetIsTypePed()) { CPed* pVictim = static_cast( pHitEntity ); bProcessHit = NetworkInterface::IsFriendlyFireAllowed( pVictim, GetDriver() ); } else if (pHitEntity->GetIsTypeVehicle()) { CVehicle* pVictimVehicle = static_cast( pHitEntity ); bProcessHit = NetworkInterface::IsFriendlyFireAllowed( pVictimVehicle, GetDriver() ); // Don't jet wash vehicles being respotted if (pVictimVehicle && pVictimVehicle->IsBeingRespotted()) { bProcessHit = false; } } } if( bProcessHit ) { CPhysical *pHitPhysical = (CPhysical *)pHitEntity; Vector3 vWorldCentroid; float fCentroidRadius = 0.0f; if( pHitEntity->GetCurrentPhysicsInst() && pHitEntity->GetCurrentPhysicsInst()->GetArchetype() ) { vWorldCentroid = VEC3V_TO_VECTOR3( pHitEntity->GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetWorldCentroid( pHitEntity->GetCurrentPhysicsInst()->GetMatrix() ) ); fCentroidRadius = pHitEntity->GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid(); } else { fCentroidRadius = pHitEntity->GetBoundCentreAndRadius(vWorldCentroid); } Vector3 vOffset = it->GetHitPosition() - VEC3V_TO_VECTOR3(pHitPhysical->GetMatrix().GetCol3()); if( it->GetHitPosition().Dist2( vWorldCentroid ) > square( fCentroidRadius ) ) { Vector3 vOffsetToCentroid = it->GetHitPosition() - vWorldCentroid; vOffsetToCentroid *= (fCentroidRadius - 0.1f) / it->GetHitPosition().Dist(vWorldCentroid); Vector3 vHitPositionAdjusted = vWorldCentroid + vOffsetToCentroid; vOffset = vHitPositionAdjusted - VEC3V_TO_VECTOR3(pHitPhysical->GetMatrix().GetCol3()); } if (pHitEntity->GetIsTypePed()) { CPed* pHitPed = static_cast(pHitEntity); if( pHitPed && !pHitPed->GetUsingRagdoll() && !pHitPed->GetIsParachuting() && !pHitPed->IsNetworkClone() && CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_EXPLOSION ) ) { CEventSwitch2NM event( 10000, rage_new CTaskNMRelax( 1000, 10000 ) ); pHitPed->SwitchToRagdoll(event); //Calculate and apply damage from jetwash { u32 weaponHash = WEAPONTYPE_FALL; int nComponent = RAGDOLL_SPINE0; float fDamage = 50.f; //Apply damage. CEventDamage tempDamageEvent(GetDriver(), fwTimer::GetTimeInMilliseconds(), weaponHash); CPedDamageCalculator damageCalculator(GetDriver(), fDamage, weaponHash, nComponent, false); damageCalculator.ApplyDamageAndComputeResponse(pHitPed, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None); } } } float fForceMulti = 1.0f - Clamp( it->GetHitPosition().Dist( matGlobal.d ) / ( EFFECT_DISTANCE + EFFECT_RADIUS ), 0.1f, 1.0f ); float fAcceleration = fBlowForce * fForceMulti / pHitPhysical->GetMass(); fAcceleration = Min( fAcceleration, DEFAULT_ACCEL_LIMIT - 0.1f); Vector3 vBlowForce = vBlowDirection * ( pHitPhysical->GetMass() * fAcceleration ); pHitPhysical->ApplyForce( vBlowForce, vOffset, it->GetHitComponent() ); } } } #if __BANK static bool renderJetWashDebug = false; if( renderJetWashDebug ) { grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(matGlobal.d), VECTOR3_TO_VEC3V(matGlobal.d + vBlowDirection * EFFECT_DISTANCE), EFFECT_RADIUS, Color_red, false ); } #endif } } } } void CVehicle::ApplyExplosionEffectSlowdown( Vector3 explosionPosition, float explosionRadius ) { // apply extra drag to each wheel within the explosion radius // increase the drag the close the wheel is to the explosion centre float explosionRadius2 = explosionRadius * explosionRadius; for( int i = 0; i < m_nNumWheels; i++ ) { CWheel* pWheel = GetWheel( i ); if( pWheel->GetIsTouching() ) { float distanceToExplosion = ( pWheel->GetHitPos() - explosionPosition ).Mag2(); if( distanceToExplosion < explosionRadius2 ) { static dev_float maxDrag = 0.5f; static dev_float minDrag = 0.01f; float dragDiff = maxDrag - minDrag; float drag = maxDrag - ( dragDiff * ( distanceToExplosion / explosionRadius2 ) ); // Only increase from explosion contact, don't decrease when moving from center to edge if (drag > pWheel->GetExtraWheelDrag()) { pWheel->SetExtraWheelDrag( drag ); } } } } } void CVehicle::InitialiseModCollision() { // turn off mod specific collision bones (and let mods enable them) for (u32 i = VEH_MOD_COLLISION_1; i <= VEH_LAST_MOD_COLLISION; ++i) { GetVariationInstance().SetModCollision(this, i, false); } for( u32 i = VEH_FIRST_SCOOP_MOD; i <= VEH_LAST_SCOOP_MOD; i++ ) { GetVariationInstance().SetModCollision( this, i, false ); } bool hasExtendedModCollision = GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_EXTENDED_COLLISION_MODS ); // Turn on collision for any enabled mods on this vehicle. CVehicleVariationInstance& rVariationInst = GetVariationInstance(); if(rVariationInst.GetMods() && rVariationInst.GetKit()) { const CVehicleKit* pKit = rVariationInst.GetKit(); const u8* mods = rVariationInst.GetMods(); for(s32 nSlot = 0; nSlot < VMT_RENDERABLE; ++nSlot) { if(mods[nSlot] != INVALID_MOD) { CVehicleModVisible::eVehicleModBone nCollisionBone = pKit->GetVisibleMods()[mods[nSlot]].GetCollisionBone(); if( nCollisionBone != -1 ) { rVariationInst.SetModCollision(this, nCollisionBone, true); } if( hasExtendedModCollision ) { for( int i = 0; i < pKit->GetVisibleMods()[ mods[ nSlot ] ].GetNumBonesToTurnOff(); i++ ) { CVehicleModVisible::eVehicleModBone nDisableCollisionBone = pKit->GetVisibleMods()[ mods[ nSlot ] ].GetBoneToTurnOff( i ); if( nDisableCollisionBone != -1 ) { rVariationInst.SetModCollision( this, nCollisionBone, false ); } } } } } } InitialiseWeaponBlades(); InitialiseSpikeCollision(); InitialiseRammingScoops(); InitialiseRammingBars(); InitialiseRamps(); } bool CVehicle::ProcessBoostPadImpact( phCachedContactIterator& impacts, CEntity* pOtherEntity ) { phMaterialMgr::Id otherMaterialId = PGTAMATERIALMGR->UnpackMtlId( impacts.GetOtherMaterialId() ); fwUniqueObjId otherObjectID = fwIdKeyGenerator::Get(pOtherEntity, 0); if( otherMaterialId == PGTAMATERIALMGR->g_idPhysVehicleSpeedUp ) { if( mf_BoostAppliedTimer == 0.0f && m_iCurrentSpeedBoostObjectID != otherObjectID && m_iPrevSpeedBoostObjectID != otherObjectID ) { mf_BoostAppliedTimer = ( (float)static_cast< CObject* >( pOtherEntity )->GetSpeedBoostDuration() ) / 25.0f; mf_BoostAmount = (float)static_cast< CObject* >( pOtherEntity )->GetSpeedBoost(); static dev_float sfSubmarineCarInWaterBoostScale = 0.75f; if( InheritsFromSubmarineCar() && GetIsInWater() && IsInSubmarineMode() ) { mf_BoostAmount *= sfSubmarineCarInWaterBoostScale; } // estimate the amount of time the vehicle will be inside the boost pad static float sf_approxBoostPadSize = 20.0f; float approxMaxBoostDistance = sf_approxBoostPadSize + GetPhysArch()->GetBound()->GetRadiusAroundCentroid() * 2.0f; Vector3 velocity = GetVelocity(); velocity = VEC3V_TO_VECTOR3( GetTransform().UnTransform3x3( VECTOR3_TO_VEC3V( velocity ) ) ); approxMaxBoostDistance += velocity.Mag() * fwTimer::GetTimeStep(); float initialSpeed = velocity.y; float speedBoostSpeedThreshold = InheritsFromAmphibiousQuadBike() && static_cast< CAmphibiousQuadBike* >( this )->IsWheelsFullyIn() ? 2.0f : 0.0f; if( initialSpeed > speedBoostSpeedThreshold ) { float gravity = ( pOtherEntity->GetTransform().GetRight().GetZf() * -GRAVITY ); float acceleration = mf_BoostAmount - gravity; float calculatedDistance = ( initialSpeed * mf_BoostAppliedTimer ) + ( acceleration * ( mf_BoostAppliedTimer * mf_BoostAppliedTimer ) ); // if the vehicle will still be within the boost pad when the boost ends increase the duration of the boost and lessen the strength // so it should still reach the same velocity but over a longer time and distance if( calculatedDistance < approxMaxBoostDistance ) { float targetVelocity = initialSpeed + ( acceleration * mf_BoostAppliedTimer ); float targetVelocitySqr = targetVelocity * targetVelocity; float newBoostAmount = ( ( ( targetVelocitySqr - ( initialSpeed * initialSpeed ) ) * 0.5f ) / approxMaxBoostDistance ) + gravity; if( newBoostAmount > 0.0f ) { mf_BoostAmount = newBoostAmount; mf_BoostAppliedTimer = ( targetVelocity - initialSpeed ) / mf_BoostAmount; mf_BoostAppliedTimer += fwTimer::GetTimeStep(); } } // When we initially released this pack the boosts where much more effective on pc. // The fix for that makes the console version as powerful as the pc so we now need // to scale down the boost duration to make them about the same as they were. mf_BoostAppliedTimer *= 0.5f; } else { mf_BoostAppliedTimer = 0.0f; } m_iCurrentSpeedBoostObjectID = otherObjectID; } return true; } else if( otherMaterialId == PGTAMATERIALMGR->g_idPhysVehicleSlowDown ) { float velocityMagSqr = GetVelocity().Mag2(); if( m_fSlowdownDuration == 0.0f ) { // calculate the acceleration required to slow the car down over the size of the prop, approx 10m + the length of the vehicle float targetVelocity = (float)static_cast< CObject* >( pOtherEntity )->GetSpeedBoost() * 0.3f; float targetVelocitySqr = targetVelocity * targetVelocity; float slowDownDistance = SLOW_DOWN_DISTANCE + ( GetPhysArch()->GetBound()->GetRadiusAroundCentroid() * 2.0f ) + ( sqrt( velocityMagSqr ) * fwTimer::GetTimeStep() ); float slowdownAcceleration = ( ( ( velocityMagSqr - targetVelocitySqr ) * 0.5f ) / slowDownDistance ); if( slowdownAcceleration > 0.0f ) { m_fSlowdownDuration = ( sqrt( velocityMagSqr ) - targetVelocity ) / slowdownAcceleration; m_fSlowdownDuration += fwTimer::GetTimeStep(); mf_BoostAmount = targetVelocity; } m_iCurrentSpeedBoostObjectID = otherObjectID; } return true; } else if( otherMaterialId == PGTAMATERIALMGR->g_idPhysVehicleRefill ) { // refill all special bars, rocket boost, jump, kers? m_fRocketBoostRemaining = GetRocketBoostCapacity(); RechargeJumpTimer(); if(m_iCurrentSpeedBoostObjectID != otherObjectID) m_bTriggerRefillBoostEffect = true; m_iCurrentSpeedBoostObjectID = otherObjectID; return true; } else if( otherMaterialId == PGTAMATERIALMGR->g_idPhysVehicleBoostCancel || otherMaterialId == PGTAMATERIALMGR->g_idPhysPropPlacement ) { SetRocketBoosting( false ); m_fRocketBoostRemaining = 0.0f; return true; } return false; } void CVehicle::UpdateWeaponBones( const CVehicleWeaponBattery* pBattery, const int* weaponBoneIndex, bool bScaleIndividualWeaponBones, bool bRotateGrillBone, float hiddenOffset /*= 0.11f*/, float fGrillOpenOffset /*= 0.07f*/, float fRackOpenOffset /*= 0.1f*/, float fGrillOpenRotation /*= 28.0f*/) { CPed* pDriver = GetDriver(); if(pDriver && pDriver->GetEquippedWeaponInfo() ) { const CWeaponInfo* pEquippedWeaponInfo = pDriver->GetEquippedWeaponInfo(); bool bShouldBeOpen = pEquippedWeaponInfo->GetIsVehicleWeapon(); dev_float sfGrillOpenOffset = fGrillOpenOffset; dev_float sfRackOpenOffset = fRackOpenOffset; dev_float sfGrillOpenRotation = DtoR * fGrillOpenRotation; float fTargetGrillOffset = 0.0f; float fTargetRackOffset = 0.0f; if(bShouldBeOpen) { fTargetGrillOffset = bRotateGrillBone ? sfGrillOpenRotation : sfGrillOpenOffset; fTargetRackOffset = sfRackOpenOffset; } if( InheritsFromSubmarineCar() && pEquippedWeaponInfo->GetFireType() != FIRE_TYPE_PROJECTILE ) { fTargetGrillOffset = 0.0f; } bool bGrillAtTargetPosition = ( m_fCurrentWeaponGrillOffset == fTargetGrillOffset ); bool bRackAtTargetPosition = ( m_fCurrentWeaponRackOffset == fTargetRackOffset ); // If the grill is not where it should be and the weapons should be open then slide first, otherwise wait for the rack to close first if(( bShouldBeOpen || bRackAtTargetPosition ) && *weaponBoneIndex != -1 ) { if( !bRotateGrillBone ) { if(MI_CAR_TOREADOR.IsValid() && GetModelIndex() == MI_CAR_TOREADOR) { //Slide the grill bone at an angle SlideMechanicalPart(GetBoneIndex((eHierarchyId)( *weaponBoneIndex )), fTargetGrillOffset, m_fCurrentWeaponGrillOffset, Vector3(0.0f, -1.0f, -1.0f), 0.15f); } else { SlideMechanicalPart(GetBoneIndex((eHierarchyId)( *weaponBoneIndex )), fTargetGrillOffset, m_fCurrentWeaponGrillOffset, Vector3(0.0f, 0.0f, -1.0f) ); } } else { RotateMechanicalPart(GetBoneIndex((eHierarchyId)( *weaponBoneIndex )), fTargetGrillOffset, m_fCurrentWeaponGrillOffset, ROT_AXIS_LOCAL_X ); } } weaponBoneIndex++; // If the rack is not where it should be and the weapons should be closed then slide first, otherwise wait for the grill to open first if(( !bShouldBeOpen || bGrillAtTargetPosition ) && *weaponBoneIndex != -1 ) { SlideMechanicalPart(GetBoneIndex((eHierarchyId)( *weaponBoneIndex )), fTargetRackOffset, m_fCurrentWeaponRackOffset, Vector3(0.0f, 1.0f, 0.0f) ); } weaponBoneIndex++; if( pBattery ) { int iBatteryIndex = GetVehicleWeaponMgr()->GetIndexOfVehicleWeapon(pBattery); // Get remaining ammo in battery int iBatteryAmmo = -1; if(iBatteryIndex > -1) { iBatteryAmmo = GetRestrictedAmmoCount(pBattery->m_handlingIndex); } // Figure out which rocket mesh should be invisible according to the fired weapon counter in the battery int iCurrentRocket = ( pBattery->GetNumWeaponsInBattery() + pBattery->GetFiredWeaponCounter() - 1 ) % pBattery->GetNumWeaponsInBattery(); //eHierarchyId firstRocketMeshId = VEH_MISC_D; for(int i = 0; i < pBattery->GetNumWeaponsInBattery(); i++) { // Update rocket mesh visibility (turn fired rocket invisible briefly) eHierarchyId currentRocketMeshId = (eHierarchyId) ( *weaponBoneIndex ); if( currentRocketMeshId != VEH_INVALID_ID ) { int iWeaponMeshBoneIndex = GetBoneIndex( currentRocketMeshId ); const crBoneData* pWeaponMeshBoneData = NULL; if( iWeaponMeshBoneIndex != -1 ) { pWeaponMeshBoneData = GetSkeleton()->GetSkeletonData().GetBoneData(iWeaponMeshBoneIndex); } if( pWeaponMeshBoneData ) { Matrix34& weaponMeshBoneMatrix = ( RC_MATRIX34( GetSkeleton()->GetLocalMtx( pWeaponMeshBoneData->GetIndex() ) ) ); // Update the invisible rocket (the one that has been fired) if(i <= iCurrentRocket ) { if (bScaleIndividualWeaponBones) { float fRocketScale = 1.0f; if(iCurrentRocket != pBattery->GetNumWeaponsInBattery() - 1 || iBatteryAmmo == 0 ) { fRocketScale = 0.0f; } weaponMeshBoneMatrix.MakeScale(fRocketScale); } else { static dev_float sfRocketOffsetMult = 0.001f; static dev_float sfRocketBaseLargeOffset = 0.16f; bool bShouldUseLargeOffset = (MI_CAR_TAMPA3.IsValid() && GetModelIndex() == MI_CAR_TAMPA3) || (MI_CAR_VIGILANTE.IsValid() && GetModelIndex() == MI_CAR_VIGILANTE); float baseOffset = bShouldUseLargeOffset ? sfRocketBaseLargeOffset : hiddenOffset; float fTimeToRefill = pBattery->GetTimeToNextLaunch() * sfRocketOffsetMult; // Don't refill the rack with new rockets until the last rocket was fired // or if we're out of ammo if(iCurrentRocket != pBattery->GetNumWeaponsInBattery() - 1 || iBatteryAmmo == 0 ) { fTimeToRefill = baseOffset; } // Offset the fired rocket backwards so that it isn't visible, and then slide it back in as the weapon finished recharging Vector3 vRocketOffset = Vector3(0.0f, 1.0f, 0.0f) * rage::Min( fTimeToRefill, baseOffset ); if( GetModelIndex() == MI_CAR_STROMBERG || GetModelIndex() == MI_CAR_TOREADOR ) { static dev_float sfStrombergZOffsetHack = -1.1f; vRocketOffset += Vector3( 0.0f, 0.0f, sfStrombergZOffsetHack * m_fCurrentWeaponGrillOffset ) * rage::Min( fTimeToRefill, baseOffset ); } weaponMeshBoneMatrix.d = VEC3V_TO_VECTOR3(pWeaponMeshBoneData->GetDefaultTranslation()) - vRocketOffset; } } else { if (bScaleIndividualWeaponBones) { weaponMeshBoneMatrix.MakeScale(VEC3V_TO_VECTOR3(pWeaponMeshBoneData->GetDefaultScale())); } else { weaponMeshBoneMatrix.d = VEC3V_TO_VECTOR3(pWeaponMeshBoneData->GetDefaultTranslation()); } } } } weaponBoneIndex++; } } } } void CVehicle::UpdateWeaponBatteryBones() { // Find the rocket battery CVehicleWeaponBattery* pBattery = NULL; if(GetVehicleWeaponMgr()) { pBattery = GetVehicleWeaponMgr()->GetWeaponBatteryOfType(FIRE_TYPE_PROJECTILE); } if( pBattery ) { TUNE_GROUP_BOOL(TURRET_TUNE, ENABLE_NEW_MISSILE_FLAP_UPDATE_FOR_TRAILER_SMALL2, true); const bool bUseNewMissileFlapUpdate = ENABLE_NEW_MISSILE_FLAP_UPDATE_FOR_TRAILER_SMALL2 && IsTrailerSmall2(); if (bUseNewMissileFlapUpdate) { UpdateMissileFlapBones(pBattery); } else { // Get remaining ammo in battery int iBatteryAmmo = -1; int iBatteryIndex = GetVehicleWeaponMgr()->GetIndexOfVehicleWeapon(pBattery); if(iBatteryIndex > -1) { iBatteryAmmo = GetRestrictedAmmoCount(pBattery->m_handlingIndex); } UpdateMissileFlapBonesOld(pBattery, iBatteryAmmo); } } } void CVehicle::UpdateMissileFlapBonesOld(CVehicleWeaponBattery* pBattery, s32 iBatteryAmmo) { // Figure out which rocket mesh should be invisible according to the fired weapon counter in the battery int iCurrentMissle = pBattery->GetFiredWeaponCounter() % pBattery->GetNumWeaponsInBattery(); float reloadingRotation = 0.0f; float openRotation = 0.0f; static float sfFullyOpenFlapRotationXAxis = -4.0f; static float sfHatchOpenDurationScaleXAxis = -0.036f; static float sfFullyOpenFlapRotationZAxis = 4.0f; static float sfHatchOpenDurationScaleZAxis = 0.036f; float fullyOpenFlapRotation = sfFullyOpenFlapRotationXAxis; float timeToNextLauch = (float)pBattery->GetTimeToNextLaunch(); eRotationAxis rotationAxis = ROT_AXIS_LOCAL_X; if( MI_TRAILER_TRAILERSMALL2.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERSMALL2 ) { rotationAxis = ROT_AXIS_LOCAL_Z; openRotation = Clamp( sfFullyOpenFlapRotationZAxis - Max( 0.0f, -32.0f + ( timeToNextLauch * sfHatchOpenDurationScaleZAxis ) ), 0.0f, sfFullyOpenFlapRotationZAxis ); if( iCurrentMissle == 0 ) { if(m_iPrevCurrentMissile != 0) { if(m_VehicleAudioEntity) { m_VehicleAudioEntity->TriggerMissileBatteryReloadSound(); } } static float sfHatchCloseDurationScale = -0.02f; reloadingRotation = Clamp( -sfFullyOpenFlapRotationZAxis - ( timeToNextLauch * sfHatchCloseDurationScale ), 0.0f, sfFullyOpenFlapRotationZAxis ); } m_iPrevCurrentMissile = iCurrentMissle; fullyOpenFlapRotation = sfFullyOpenFlapRotationZAxis; } else { openRotation = Clamp( sfFullyOpenFlapRotationXAxis - Min( 0.0f, 32.0f + ( timeToNextLauch * sfHatchOpenDurationScaleXAxis ) ), sfFullyOpenFlapRotationXAxis, 0.0f ); if( iCurrentMissle == 0 ) { static float sfHatchCloseDurationScale = 0.02f; reloadingRotation = Clamp( -sfFullyOpenFlapRotationXAxis - ( timeToNextLauch * sfHatchCloseDurationScale ), sfFullyOpenFlapRotationXAxis, 0.0f ); } } // Loop through all the bones of the missiles that have been fired // hide the missile bone and set the flap as open for( int i = 0; i < pBattery->GetNumWeaponsInBattery(); i++ ) { // Update the flap rotation for fired missiles eHierarchyId currentMissileFlapMeshId = (eHierarchyId) ( (int)FIRST_MISSILE_FLAP + i ); int iMissileFlapBoneIndex = GetBoneIndex( currentMissileFlapMeshId ); //// Update missile visiblity //eHierarchyId currentMissileMeshId = (eHierarchyId) ( (int)VEH_WEAPON_2_FIRST + i ); //int iMissileBoneIndex = GetBoneIndex( currentMissileMeshId ); // // Update the invisible rocket (the one that has been fired) // Also keep the flaps open if we're at the stage where we should reload but we have no ammo if( i < iCurrentMissle || ( iCurrentMissle == 0 && iBatteryAmmo == 0 ) ) { // rotate the flap bone if the missile has already been fired if( iMissileFlapBoneIndex != -1 ) { if( i == iCurrentMissle - 1 ) { SetBoneRotation( iMissileFlapBoneIndex, rotationAxis, openRotation ); } else { SetBoneRotation( iMissileFlapBoneIndex, rotationAxis, fullyOpenFlapRotation ); } } //// and hide the missile mesh //if( iMissileBoneIndex != -1 ) //{ // const crBoneData* pMissileMeshBoneData = GetSkeleton()->GetSkeletonData().GetBoneData( iMissileBoneIndex ); // Matrix34& missileMeshBoneMatrix = ( RC_MATRIX34( GetSkeleton()->GetLocalMtx( pMissileMeshBoneData->GetIndex() ) ) ); // static Vector3 missileFiredOffset( 0.0f, -1.0f, 0.0f ); // missileMeshBoneMatrix.d = VEC3V_TO_VECTOR3( pMissileMeshBoneData->GetDefaultTranslation() ) - missileFiredOffset; //} } else { // restore default values if( iMissileFlapBoneIndex != -1 ) { float rotation = reloadingRotation; if( i == pBattery->GetNumWeaponsInBattery() - 1 ) { if( openRotation > 0.0f ) { rotation = Min( rotation, openRotation ); } else { rotation = Max( rotation, openRotation ); } } SetBoneRotation( iMissileFlapBoneIndex, rotationAxis, rotation ); } //if( iMissileBoneIndex != -1 && // reloadingRotation == 0.0f ) //{ // const crBoneData* pMissileMeshBoneData = GetSkeleton()->GetSkeletonData().GetBoneData( iMissileBoneIndex ); // Matrix34& missileMeshBoneMatrix = ( RC_MATRIX34( GetSkeleton()->GetLocalMtx( pMissileMeshBoneData->GetIndex() ) ) ); // missileMeshBoneMatrix.d = VEC3V_TO_VECTOR3( pMissileMeshBoneData->GetDefaultTranslation() ); //} } } } void CVehicle::UpdateMissileFlapBones(CVehicleWeaponBattery* pBattery) { const int iLastFiredMissile = pBattery->GetLastFiredWeapon(); const float timeToNextLaunch = (float)pBattery->GetTimeToNextLaunch(); // Rotation flaps that are opening should be at (could do with being decoupled from the time to next launch in case we want to overlap firing) static float sfFullyOpenFlapRotationZAxis = 4.0f; static float sfHatchOpenDurationScaleZAxis = 0.036f; const float fOpeningDelta = Max(0.0f, -32.0f + (timeToNextLaunch * sfHatchOpenDurationScaleZAxis)); const float openingRotation = Clamp(sfFullyOpenFlapRotationZAxis - fOpeningDelta , 0.0f, sfFullyOpenFlapRotationZAxis); // Rotation flaps that are closing should be at float closingRotation = 0.0f; if (pBattery->GetMissileBatteryState() == CVehicleWeaponBattery::MBS_RELOADING) { static float sfHatchCloseDurationScale = -0.02f; const float fClosingDelta = timeToNextLaunch * sfHatchCloseDurationScale; closingRotation = Clamp(-sfFullyOpenFlapRotationZAxis - fClosingDelta, 0.0f, sfFullyOpenFlapRotationZAxis); } eRotationAxis rotationAxis = ROT_AXIS_LOCAL_Z; bool bAllFlapsClosed = true; for (int i = 0; i < pBattery->GetNumWeaponsInBattery(); i++) { eHierarchyId currentMissileFlapMeshId = (eHierarchyId) ((int)FIRST_MISSILE_FLAP + i); int iMissileFlapBoneIndex = GetBoneIndex(currentMissileFlapMeshId); if (iMissileFlapBoneIndex == -1) { continue; } float fDesiredRotation = UpdateMissileFlapStateAndGetDesiredRotation(i, iLastFiredMissile, sfFullyOpenFlapRotationZAxis, openingRotation, closingRotation, pBattery); SetBoneRotation(iMissileFlapBoneIndex, rotationAxis, fDesiredRotation); if (pBattery->GetMissileFlapState(i) != CVehicleWeaponBattery::MFS_CLOSED) { bAllFlapsClosed = false; } } if (pBattery->GetMissileBatteryState() == CVehicleWeaponBattery::MBS_RELOADING && bAllFlapsClosed) { pBattery->SetMissileBatteryState(CVehicleWeaponBattery::MBS_IDLE); } } float CVehicle::UpdateMissileFlapStateAndGetDesiredRotation(s32 i, s32 iLastFiredMissile, float fullyOpenFlapRotation, float openingRotation, float closingRotation, CVehicleWeaponBattery* pBattery) { static float sfDefaultFullyClosedRotation = 0.0f; float fDesiredRotation = sfDefaultFullyClosedRotation; CVehicleWeaponBattery::eMissileFlapState flapState = pBattery->GetMissileFlapState(i); switch (flapState) { case CVehicleWeaponBattery::MFS_CLOSED: { // Use default closed rotation break; } case CVehicleWeaponBattery::MFS_OPENING: { fDesiredRotation = openingRotation; if (openingRotation == fullyOpenFlapRotation) { pBattery->SetMissileFlapState(i, CVehicleWeaponBattery::MFS_OPEN); } break; } case CVehicleWeaponBattery::MFS_OPEN: { fDesiredRotation = fullyOpenFlapRotation; break; } case CVehicleWeaponBattery::MFS_CLOSING: { // Reloading, use the closing rotation fDesiredRotation = closingRotation; // Last fired flap has special case logic if (i == iLastFiredMissile) { if (openingRotation > 0.0f) { fDesiredRotation = Min(closingRotation, openingRotation); } else { fDesiredRotation = Max(closingRotation, openingRotation); } } if (fDesiredRotation == sfDefaultFullyClosedRotation) { // Finished reloading, we should be fully closed at this point and ready to fire again pBattery->SetMissileFlapState(i, CVehicleWeaponBattery::MFS_CLOSED); } break; } default: break; } return fDesiredRotation; } dev_float WING_RETRACTED_DISTANCE = -0.15f; dev_float WING_DEPLOY_DISTANCE = 0.0f; float CVehicle::GetGliderNormaliseDeployedRatio() const { return ( m_wingDeployedRatio - WING_RETRACTED_DISTANCE ) / ( WING_DEPLOY_DISTANCE - WING_RETRACTED_DISTANCE ); } float CVehicle::GetSpecialFlightModeRatio() const { if( HasGlider() ) { return GetGliderNormaliseDeployedRatio(); } return m_specialFlightModeRatio; } void CVehicle::ProcessGlider() { CPed* pDriver = GetDriver(); if (IsNetworkClone()) { CNetObjVehicle* pNetObj = SafeCast(CNetObjVehicle, GetNetworkObject()); EGliderState cloneGliderState = (EGliderState)pNetObj->GetCloneGliderState(); if (m_gliderState != cloneGliderState) { switch (m_gliderState) { case NOT_GLIDING: if (cloneGliderState >= DEPLOYING_WINGS && cloneGliderState < RETRACTING_WINGS) { StartGliding(); } break; case GLIDING: if (cloneGliderState == NOT_GLIDING || cloneGliderState == RETRACTING_WINGS) { FinishGliding(); } break; case DEPLOYING_WINGS: case RETRACTING_WINGS: break; default: Assertf(0, "Unrecognised glider state"); } } } else if( pDriver ) { CControl* pControl = pDriver->GetControlFromPlayer(); if (pControl && pControl->GetVehicleBikeWings().IsPressed()) { if( m_gliderState != GLIDING ) { StartGliding(); } else { FinishGliding(); } } } if( !m_nVehicleFlags.bEngineOn ) { FinishGliding(); } float targetRatio = m_wingDeployedRatio; bool movingWings = false; switch( m_gliderState ) { case DEPLOYING_WINGS: { movingWings = true; targetRatio = WING_DEPLOY_DISTANCE; break; } case RETRACTING_WINGS: { movingWings = true; targetRatio = WING_RETRACTED_DISTANCE; break; } default: break; } UpdateWingBones( targetRatio, false ); if( movingWings ) { if( m_wingDeployedRatio >= WING_DEPLOY_DISTANCE ) { m_gliderState = GLIDING; } else if( m_wingDeployedRatio <= WING_RETRACTED_DISTANCE ) { m_gliderState = NOT_GLIDING; } } if( m_gliderState != NOT_GLIDING ) { // If in stunt camera mode, disable it if(SStuntJumpManager::IsInstantiated() && CStuntJumpManager::IsAStuntjumpInProgress()) { SStuntJumpManager::GetInstance().AbortStuntJumpInProgress(); } } } void CVehicle::ProcessSpecialFlightMode() { CSpecialFlightHandlingData* pSpecialFlightHandling = pHandling->GetSpecialFlightHandlingData(); if( pSpecialFlightHandling && m_allowSpecialFlightMode && !( pSpecialFlightHandling->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) ) { CPed* pDriver = GetDriver(); if( pDriver ) { if( pDriver && pDriver->IsLocalPlayer() ) { CControl *pControl = pDriver->GetControlFromPlayer(); bool bDeluxo = MI_CAR_DELUXO.IsValid() && GetModelIndex() == MI_CAR_DELUXO; const ioValue& inputValueToUse = bDeluxo ? pControl->GetVehicleTransform() : pControl->GetVehicleRoof(); static bool sbHasButtonBeenUp = true; sbHasButtonBeenUp |= inputValueToUse.IsUp(); bool bInputPressed = inputValueToUse.HistoryHeldDown( CTaskVehicleConvertibleRoof::ms_uTimeToHoldButtonDown ); if (bDeluxo) { bInputPressed = inputValueToUse.IsReleased() && !inputValueToUse.IsReleasedAfterHistoryHeldDown(ms_uTimeToIgnoreButtonHeldDown); if (pDriver->GetPedResetFlag(CPED_RESET_FLAG_IsDoingDriveby)) { bInputPressed = false; } if (bInputPressed && GetSpecialFlightModeRatio() >= 1.0f) { for(int wheelIndex = 0; wheelIndex < GetNumWheels(); ++wheelIndex) { CWheel* pWheel = GetWheel(wheelIndex); Vec3V vWheelPos = VECTOR3_TO_VEC3V( pWheel->GetProbeSegment().A ); vWheelPos = GetTransform().Transform( vWheelPos ); float fWaterZAtWheelPos = 0.0f; if (CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(VEC3V_TO_VECTOR3(vWheelPos), &fWaterZAtWheelPos)) { if (fWaterZAtWheelPos < vWheelPos.GetZf()) { float fHeightDistanaceToWaterLevel = Abs(vWheelPos.GetZf() - fWaterZAtWheelPos); TUNE_GROUP_FLOAT(VEHICLE_DEBUG, fDeluxoWaterHeightDiffThresh, 10.0f, 0.0f, 100.0f, 0.1f); if (fHeightDistanaceToWaterLevel <= fDeluxoWaterHeightDiffThresh) { // We're flying close to the water float fWheelProbeHeight = pWheel->GetHitPos().z; bool bWheelProbeHitSomething = !IsClose(fWheelProbeHeight, 0.0f, SMALL_FLOAT); bool bWheelProbePosIsLowerThanWater = fWheelProbeHeight < fWaterZAtWheelPos; if (!bWheelProbeHitSomething || bWheelProbePosIsLowerThanWater) { // The wheel probe either did not hit anything or whatever it hit is below the water // That means that there's nothing between the wheels and the water and we're within close distance to it. bInputPressed = false; } else { // If even one wheel probe hit something and whatever it hit is above water, we should (be on the safe side and) not disable the transform bInputPressed = true; break; } } } } } } } if( sbHasButtonBeenUp && m_nVehicleFlags.bEngineOn && bInputPressed && ( m_specialFlightModeRatio == 1.0f || // DON'T TOGGLE THE MODE IF IT IS STILL TRANSITIONING m_specialFlightModeRatio == 0.0f ) ) { m_specialFlightModeTargetRatio = m_specialFlightModeRatio > 0.5f ? 0.0f : 1.0f; sbHasButtonBeenUp = false; audVehicleAudioEntity* vehicleAudioEntity = GetVehicleAudioEntity(); if(vehicleAudioEntity) { vehicleAudioEntity->TriggerSpecialFlightModeTransform(m_specialFlightModeTargetRatio == 1.0f); } } } } //float positionDeltaMax = fwTimer::GetTimeStep() / pSpecialFlightHandling->m_fTransitionDuration; //float positionDelta = Clamp( m_specialFlightModeTargetRatio - m_specialFlightModeRatio, -positionDeltaMax, positionDeltaMax ); //m_specialFlightModeRatio += positionDelta; } } void CVehicle::ProcessOutriggers() { if( GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_OUTRIGGER_LEGS ) ) { // if we're attached to something then force the legs back up if( !IsNetworkClone() && GetAttachParentVehicle() ) { SetDeployOutriggers( false ); return; } // if we're retracting the outriggers and the weapon barrel is in collision with something // then we're going to have to re-extend the outriggers if( !IsNetworkClone() && m_pVehicleWeaponMgr && !m_deployOutriggers && m_outriggerRatio < 0.35f ) { for( int i = 0; i < m_pVehicleWeaponMgr->GetNumTurrets(); i++ ) { CTurret *pTurret = m_pVehicleWeaponMgr->GetTurret( i ); if( pTurret->GetType() == VGT_TURRET_PHYSICAL && static_cast( pTurret )->GetIsBarrelInCollision() ) { SetDeployOutriggers( true ); return; } } } CPed* pDriver = GetDriver(); if( pDriver ) { if( pDriver && pDriver->IsLocalPlayer() && ( m_outriggerRatio == 1.0f || GetNumContactWheels() > GetNumWheels() * 0.5f ) ) { static dev_float sfMaxVelocityToDeployOutriggers = 20000000.0f; CControl *pControl = pDriver->GetControlFromPlayer(); static bool sbHasButtonBeenUp = true; sbHasButtonBeenUp |= pControl->GetVehicleRoof().IsUp(); if( sbHasButtonBeenUp && pControl->GetVehicleRoof().HistoryHeldDown( CTaskVehicleConvertibleRoof::ms_uTimeToHoldButtonDown ) && ( m_outriggerRatio == 1.0f || // DON'T TOGGLE THE MODE IF IT IS STILL TRANSITIONING m_outriggerRatio == 0.0f ) && GetVelocity().Mag2() < sfMaxVelocityToDeployOutriggers ) { SetDeployOutriggers(!m_deployOutriggers); sbHasButtonBeenUp = false; if(!IsNetworkClone()) { NetworkInterface::ForceScriptGameStateNodeUpdate(*this); } } } } } } void CVehicle::SetDeployOutriggers( bool deploy ) { if(m_deployOutriggers != deploy) { m_deployOutriggers = deploy; if(m_VehicleAudioEntity) { m_VehicleAudioEntity->DeployOutriggers(deploy); } } } void CVehicle::ProcessArenaMode() { { CPed* pDriver = GetDriver(); if( m_hitBySideShunt ) { m_hitBySideShunt = false; static dev_bool sb_reduceSideShuntAfterImpact = false; if( sb_reduceSideShuntAfterImpact ) { m_sideShuntForce *= 0.5f; } } SetHitByWeaponBlade( false ); if( pDriver && pDriver->IsLocalPlayer() && HasSideShunt() ) { // process the side shunt CControl *pControl = pDriver->GetControlFromPlayer(); m_shuntModifierActive = pControl->GetVehicleArenaModeModifier().IsDown(); bool shuntLeft = false; bool shuntRight = false; static bool sbHasButtonBeenUp = true; if( m_shuntModifierActive ) { sbHasButtonBeenUp |= pControl->GetVehicleArenaModeShuntLeft().IsUp() && pControl->GetVehicleArenaModeShuntRight().IsUp(); if( sbHasButtonBeenUp && m_Transmission.IsNitrousFullyCharged( this ) && m_sideShuntForce == 0.0f ) { if( pControl->GetVehicleArenaModeShuntLeft().IsDown() ) { shuntLeft = true; sbHasButtonBeenUp = false; } else if( pControl->GetVehicleArenaModeShuntRight().IsDown() ) { shuntRight = true; sbHasButtonBeenUp = false; } } } TUNE_GROUP_FLOAT( ARENA_MODE, sfMaxSpeedForSideShunt, 2.0f, 0.0f, 50.0f, 0.1f ); float rightVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetRight() ) ); if( shuntRight && rightVelocity > sfMaxSpeedForSideShunt ) { shuntRight = false; } if( shuntLeft && rightVelocity < -sfMaxSpeedForSideShunt ) { shuntLeft = false; } if( shuntLeft ) { m_sideShuntForce = -SIDE_SHUNT_DURATION; CActivateVehicleSpecialAbilityEvent::Trigger( *this, CActivateVehicleSpecialAbilityEvent::SHUNT_L ); m_VehicleAudioEntity->TriggerSideShunt(); m_Transmission.ClearNitrousDuration(); } else if( shuntRight ) { m_sideShuntForce = SIDE_SHUNT_DURATION; CActivateVehicleSpecialAbilityEvent::Trigger( *this, CActivateVehicleSpecialAbilityEvent::SHUNT_R ); m_VehicleAudioEntity->TriggerSideShunt(); m_Transmission.ClearNitrousDuration(); } } } } void CVehicle::UpdateSideShunt( float fTimeStep ) { // if( CVehicle::sm_bVehicleCombatMode || // CVehicle::sm_bInDetonationMode ) { if( m_sideShuntForce != 0.0f ) { static dev_float sf_SideShuntForceScale = 2.5f; static dev_float sf_SpeedForShuntFactor = 1.0f; static dev_bool sb_scaleShuntForceWillSpeed = true; static dev_float sf_fullSpeedShuntFactorIncrease = 1.5f; static dev_float sf_minSpeedShuntFactor = 0.0f; float shuntScale = sf_SideShuntForceScale; //if( sm_bInDetonationMode ) { static dev_float sf_DetonationSideShuntForceScale = 5.0f; shuntScale = sf_DetonationSideShuntForceScale; } float speedForFullShunt = pHandling->m_fInitialDriveMaxFlatVel * sf_SpeedForShuntFactor; float shutVelocityScale = sb_scaleShuntForceWillSpeed ? sf_minSpeedShuntFactor + ( Clamp( Abs( Dot( VECTOR3_TO_VEC3V( GetVelocity() ), GetTransform().GetForward() ).Getf() ) / speedForFullShunt, 0.0f, 1.0f ) * sf_fullSpeedShuntFactorIncrease ) : 1.0f; Vector3 impulse = VEC3V_TO_VECTOR3( GetTransform().GetRight() ) * m_sideShuntForce * m_sideShuntForce * m_sideShuntForce * GetMass() * shuntScale * shutVelocityScale; static float sfDefaultTimeStep = 60.0f; impulse *= fTimeStep * sfDefaultTimeStep; ApplyImpulseCg( impulse ); if( m_sideShuntForce > 0.0f ) { m_sideShuntForce = Max( m_sideShuntForce - fTimeStep, 0.0f ); } else { m_sideShuntForce = Min( m_sideShuntForce + fTimeStep, 0.0f ); } } //if( m_fowardShuntForce > 0.0f ) //{ // static dev_float sf_ForwardShuntForceScale = 5.0f; // float shuntScale = sf_ForwardShuntForceScale; // //if( sm_bInDetonationMode ) // { // static dev_float sf_DetonationForwardShuntForceScale = 9.5f; // shuntScale = sf_DetonationForwardShuntForceScale; // } // Vector3 impulse = VEC3V_TO_VECTOR3( GetTransform().GetForward() ) * m_fowardShuntForce * m_fowardShuntForce * m_fowardShuntForce * GetMass() * shuntScale; // ApplyImpulseCg( impulse ); // m_fowardShuntForce = Max( m_fowardShuntForce - fTimeStep, 0.0f ); //} } } void CVehicle::StartGliding() { if( m_gliderState != GLIDING ) { if(m_gliderState != DEPLOYING_WINGS) { if(m_VehicleAudioEntity) { m_VehicleAudioEntity->StartGliding(); } } m_gliderState = DEPLOYING_WINGS; } } void CVehicle::FinishGliding() { if( m_gliderState != NOT_GLIDING ) { if(m_gliderState != RETRACTING_WINGS) { if(m_VehicleAudioEntity) { m_VehicleAudioEntity->StopGliding(); } } m_gliderState = RETRACTING_WINGS; } } void CVehicle::StartGlidingInstant() { m_gliderState = GLIDING; UpdateWingBones( WING_DEPLOY_DISTANCE, true ); } void CVehicle::FinishGlidingInstant() { m_gliderState = NOT_GLIDING; UpdateWingBones( WING_RETRACTED_DISTANCE, true ); } void CVehicle::UpdateWingBones( float targetRatio, bool instant ) { TUNE_GROUP_FLOAT( VEHICLE_GLIDER, WING_DEPLOY_RATE, 0.3f, 0.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, WING_REAR_DEPLOY_SCALE, 2.0f, 0.0f, 100.0f, 0.1f ); float invTimeStep = fwTimer::GetInvTimeStep(); float movementSpeed = instant ? 1.0f * invTimeStep : WING_DEPLOY_RATE; float previousRatio = m_wingDeployedRatio; float direction = -1.0f; // rotate the wings based on steering input // we can't do this a the moment due to the way the wings are skinned float fStickX = 0.0f; float fStickY = 0.0f; CPed* pDriver = GetDriver(); if( pDriver ) { CControl* pControl = pDriver->GetControlFromPlayer(); if( pControl ) { CAutomobile::GetVehicleMovementStickInput( *pControl, fStickX, fStickY ); } } TUNE_GROUP_FLOAT( VEHICLE_GLIDER, WING_REAR_ROTATION_MAX, 0.0f, -100.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, WING_FRONT_ROTATION_MAX, 0.0f, -100.0f, 100.0f, 0.1f ); bool moveWings = instant || ( targetRatio != m_wingDeployedRatio ); float normalisedDeployedRatio = GetGliderNormaliseDeployedRatio(); for( int boneId = FIRST_EXTENDABLE; boneId <= LAST_EXTENDABLE; boneId++ ) { int boneIndex = GetBoneIndex( (eHierarchyId)boneId ); if( boneIndex != -1 ) { if( moveWings ) { previousRatio = m_wingDeployedRatio; if( boneId >= FIRST_REAR_EXTENDABLE && HasGlider() ) { previousRatio *= WING_REAR_DEPLOY_SCALE; SlideMechanicalPart( boneIndex, targetRatio * WING_REAR_DEPLOY_SCALE, previousRatio, Vector3( direction, 0.0f, 0.0f ), movementSpeed * WING_REAR_DEPLOY_SCALE ); previousRatio /= WING_REAR_DEPLOY_SCALE; } else { SlideMechanicalPart( boneIndex, targetRatio, previousRatio, Vector3( direction, 0.0f, 0.0f ), movementSpeed ); } } if( boneId >= FIRST_REAR_EXTENDABLE ) { float wingAngle = fStickX * WING_REAR_ROTATION_MAX; SetBoneRotation( boneIndex, ROT_AXIS_LOCAL_X, wingAngle * direction * normalisedDeployedRatio ); } else { float wingAngle = fStickX * WING_FRONT_ROTATION_MAX; SetBoneRotation( boneIndex, ROT_AXIS_LOCAL_X, wingAngle * direction * normalisedDeployedRatio ); } } direction *= -1.0f; } previousRatio = Clamp( previousRatio, WING_RETRACTED_DISTANCE, WING_DEPLOY_DISTANCE ); if( previousRatio != m_wingDeployedRatio ) { m_wingDeployedRatio = previousRatio; } } void CVehicle::ProcessGliderPhysics( float fTimeStep ) { float normalisedDeployedRatio = GetSpecialFlightModeRatio(); CSpecialFlightHandlingData* flightHandling = pHandling->GetSpecialFlightHandlingData(); if( flightHandling ) { if( flightHandling->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) { if( GetDriver() && m_nVehicleFlags.bEngineOn ) { m_specialFlightModeTargetRatio = 1.0f; } else { m_specialFlightModeTargetRatio = 0.0f; } } float positionDeltaMax = fwTimer::GetRagePhysicsUpdateTimeStep() / flightHandling->m_fTransitionDuration; //if( flightHandling->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) //{ // if( m_specialFlightModeTargetRatio < m_specialFlightModeRatio ) // { // static dev_float sfSpecialFightModeTransitionScale = 1.0f; // positionDeltaMax *= sfSpecialFightModeTransitionScale; // } //} float positionDelta = Clamp( m_specialFlightModeTargetRatio - m_specialFlightModeRatio, -positionDeltaMax, positionDeltaMax ); m_specialFlightModeRatio += positionDelta; } if( normalisedDeployedRatio > 0.0f || CPhysics::ms_bInStuntMode ) { float fStickX = 0.0f; float fStickY = 0.0f; CPed* pDriver = GetDriver(); if( pDriver ) { CControl* pControl = pDriver->GetControlFromPlayer(); if( pControl ) { CAutomobile::GetVehicleMovementStickInput( *pControl, fStickX, fStickY ); } } if( flightHandling && flightHandling->m_mode == 1 ) { ProcessHoverMode( fTimeStep, normalisedDeployedRatio, flightHandling, fStickX, fStickY ); } else { TUNE_GROUP_FLOAT( VEHICLE_GLIDER, LIFT_COEFFICIENT, 9.0f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, CRITICAL_LIFT_ANGLE, 45.0f, 0.0f, 90.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, INITIAL_LIFT_ANGLE, 1.5f, 0.0f, 90.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_STICK_LIFT_ANGLE, 25.0f, -90.0f, 90.0f, 0.1f ); float initialliftCoefficient = flightHandling ? flightHandling->m_fLiftCoefficient : LIFT_COEFFICIENT; float criticalLiftAngle = flightHandling ? flightHandling->m_fCriticalLiftAngle : CRITICAL_LIFT_ANGLE; float initialLiftAngle = flightHandling ? flightHandling->m_fInitialLiftAngle : INITIAL_LIFT_ANGLE; float maxInputLiftAngle = flightHandling ? flightHandling->m_fMaxLiftAngle : MAX_STICK_LIFT_ANGLE; TUNE_GROUP_FLOAT( VEHICLE_GLIDER, DRAG_COEFFICIENT, 0.4f, -100.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, EXTRA_DRAG, 10.0f, -100.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, VELOCITY_FOR_MAX_LIFT, 2000.0f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, VELOCITY_FOR_MIN_LIFT, 1.0f, 0.0f, 10000.0f, 0.1f ); float dragCoefficient = flightHandling ? flightHandling->m_fDragCoefficient : DRAG_COEFFICIENT; float brakingDrag = flightHandling ? flightHandling->m_fBrakingDrag : EXTRA_DRAG; float maxLiftVelocity = flightHandling ? flightHandling->m_fMaxLiftVelocity : VELOCITY_FOR_MAX_LIFT; float minLiftVelocity = flightHandling ? flightHandling->m_fMinLiftVelocity : VELOCITY_FOR_MIN_LIFT; TUNE_GROUP_FLOAT( VEHICLE_GLIDER, ROLL_TORQUE, 3.0f, -1000.0f, 1000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, SPEED_FOR_MAX_TORQUE, 100.0f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, SPEED_FOR_MIN_TORQUE, 40000.0f, 0.0f, 100000.0f, 0.1f ); float rollTorqueScale = flightHandling ? flightHandling->m_fRollTorqueScale : ROLL_TORQUE; float maxTorqueVelocity = flightHandling ? flightHandling->m_fMaxTorqueVelocity : SPEED_FOR_MAX_TORQUE; float minTorqueVelocity = flightHandling ? flightHandling->m_fMinTorqueVelocity : SPEED_FOR_MIN_TORQUE; TUNE_GROUP_FLOAT( VEHICLE_GLIDER, YAW_TORQUE_SCALE, -900.0f, -10000.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, SELF_LEVELING_TORQUE_SCALE, -5.0f, -100.0f, 100.0f, 0.1f ); float yawTorqueScale = flightHandling ? flightHandling->m_fYawTorqueScale : YAW_TORQUE_SCALE; float selfLevelingTorqueScale = flightHandling ? flightHandling->m_fSelfLevelingPitchTorqueScale : SELF_LEVELING_TORQUE_SCALE; TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_PITCH_TORQUE, 1500.0f, 0.0f, 5000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_STEERING_ROLL_TORQUE, 250.0f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, STEERING_PITCH_TORQUE, 400.0f, -10000.0f, 10000.0f, 0.1f ); float maxPitchTorque = flightHandling ? flightHandling->m_fMaxPitchTorque : MAX_PITCH_TORQUE; float maxSteeringRollTorque = flightHandling ? flightHandling->m_fMaxSteeringRollTorque : MAX_STEERING_ROLL_TORQUE; float pitchTorqueScale = flightHandling ? flightHandling->m_fPitchTorqueScale : STEERING_PITCH_TORQUE; TUNE_GROUP_FLOAT( VEHICLE_GLIDER, EXTRA_STEERING_FORCE, 1000.0f, -10000.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, THRUST, 0.0f, -10000.0f, 10000.0f, 0.1f ); float steeringTorqueScale = flightHandling ? flightHandling->m_fSteeringTorqueScale : EXTRA_STEERING_FORCE; float maxThrust = flightHandling ? flightHandling->m_fMaxThrust : THRUST; float forwardVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetB() ) ); float velSqrd = Min( forwardVelocity * forwardVelocity, maxLiftVelocity ); Vec3V normalisedVelocity = VECTOR3_TO_VEC3V( GetVelocity() ); normalisedVelocity = NormalizeSafe( normalisedVelocity, GetTransform().GetB() ); float vehiclePitch = -Dot( normalisedVelocity, GetTransform().GetC() ).Getf(); float vehicleYaw = Dot( normalisedVelocity, GetTransform().GetA() ).Getf(); Vec3V localVelocity = GetTransform().UnTransform3x3( VECTOR3_TO_VEC3V( GetVelocity() ) ); Vec3V wingForce = Vec3V( 0.0f, 0.0f, 0.0f ); if( forwardVelocity > minLiftVelocity ) { float inputAngle = fStickY; float angleOfAttack = initialLiftAngle + ( RtoD * vehiclePitch ); inputAngle *= maxInputLiftAngle; angleOfAttack += inputAngle; float angleOfAttackRatio = Abs( ( ( criticalLiftAngle - angleOfAttack ) / criticalLiftAngle ) ); float liftCoefficient = Clamp( 1.0f - angleOfAttackRatio, -1.0f, 1.0f ) * initialliftCoefficient; float lift = velSqrd * liftCoefficient; Vec3V wingUp( 0.0f, 0.0f, 1.0f ); wingUp = RotateAboutXAxis( wingUp, ScalarV( DtoR * angleOfAttack ) ); wingUp = GetTransform().Transform3x3( wingUp ); ScalarV dragVelocity = Dot( wingUp, localVelocity ); float extraDrag = brakingDrag * GetBrake(); Vec3V drag = ScalarV( dragCoefficient + extraDrag ) * dragVelocity * wingUp; wingForce = GetTransform().GetC() * ScalarV( lift ); wingForce += drag; wingForce *= ScalarV( normalisedDeployedRatio ); } float fForceMult = 1.0f - Clamp( ( velSqrd - maxTorqueVelocity ) / ( minTorqueVelocity - maxTorqueVelocity ), 0.0f, 1.0f ); float fPitchTorque = 0.0f; fPitchTorque += vehiclePitch * velSqrd * selfLevelingTorqueScale * ( 1.1f - Abs( fStickY ) ); fPitchTorque += ( pitchTorqueScale * Max( fForceMult, 0.25f ) * normalisedDeployedRatio * fStickY ); fPitchTorque = Clamp( fPitchTorque, -maxPitchTorque, maxPitchTorque ); float fRollTorque = 0.0f; float fYawTorque = 0.0f; if( IsInAir() ) { fRollTorque = rollTorqueScale; float targetRoll = fStickX * 0.8f * ( PI * 0.5f ); float currentRoll = -GetTransform().GetA().GetZf() * ( PI * 0.5f ); float rollVelocity = Dot( VECTOR3_TO_VEC3V( GetAngVelocity() ), GetTransform().GetB() ).Getf(); fRollTorque = ( ( targetRoll - currentRoll ) * GetAngInertia().y ); fRollTorque *= rollTorqueScale; fRollTorque -= ( rollVelocity * Abs( rollVelocity ) ) * ( 0.5f * GetAngInertia().y ); fRollTorque /= fTimeStep; fRollTorque = Clamp( fRollTorque, -maxSteeringRollTorque, maxSteeringRollTorque ); fRollTorque *= normalisedDeployedRatio; fYawTorque = vehicleYaw * fForceMult * yawTorqueScale; Vec3V extraTurningForce = GetTransform().GetC(); extraTurningForce.SetZf( 0.0f ); extraTurningForce *= ScalarV( steeringTorqueScale );//* currentRoll ); wingForce += extraTurningForce; } Vec3V pitchTorque = GetTransform().GetA() * ScalarV( fPitchTorque ); Vec3V rollTorque = GetTransform().GetB() * ScalarV( fRollTorque ); Vec3V yawTorque = GetTransform().GetC() * ScalarV( fYawTorque ); Vec3V torque = pitchTorque + rollTorque + yawTorque; ApplyInternalTorque( VEC3V_TO_VECTOR3( torque ) ); ApplyInternalForceCg( VEC3V_TO_VECTOR3( wingForce ) ); if( !m_nVehicleFlags.bEngineOn ) { Vector3 thrustVec = VEC3V_TO_VECTOR3( GetTransform().GetForward() ) * maxThrust * Clamp( GetThrottle(), 0.0f, 1.0f) * GetMass(); ApplyInternalForceCg( thrustVec ); } } } } REGISTER_TUNE_GROUP_FLOAT( MAX_HEIGHT_FOR_VELOCITY_DAMPING, 5.0f ) void CVehicle::ProcessHoverMode( float fTimeStep, float normalisedDeployedRatio, CSpecialFlightHandlingData* flightHandling, float fStickX, float fStickY ) { if (IsNetworkClone()) { fStickY = m_cachedStickYfromNetwork; } else { m_cachedStickYfromNetwork = fStickY; } float heightAboveCeiling = HeightAboveCeiling( GetTransform().GetPosition().GetZf() ); if( heightAboveCeiling > 0.0f && !IsNetworkClone() ) { SwitchEngineOff(); } TUNE_GROUP_FLOAT( VEHICLE_GLIDER, GRAVITY_REDUCTION, 0.1f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, GRAVITY_REDUCTION_IN_FLIGHT, 0.9f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, TARGET_SUSPENSION_RAISE_LOW_SPEED, 0.1f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, TARGET_SUSPENSION_RAISE_HIGH_SPEED, 0.4f, 0.0f, 10000.0f, 0.1f ); INSTANTIATE_TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_HEIGHT_FOR_VELOCITY_DAMPING, 0.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_LIFT_FORCE_SCALE, 2.0f, 0.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MIN_EXTRA_LIFT_FACTOR, 0.2f, 0.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, SUSPENSION_RAISE_INCREASE_FOR_BIKES, 4.0f, 0.0f, 10.0f, 0.001f ); CAutomobile* pAutomobile = nullptr; if( InheritsFromAutomobile() ) { pAutomobile = static_cast( this ); } if( GetIsDoingJump() ) { m_targetGravityScale = 1.0f; } if( m_disableHoverModeFlight && !CPhysics::ms_bInStuntMode ) { m_targetGravityScale = 0.0f; } // don't do anything if we're upside down bool worksUpsideDown = flightHandling && flightHandling->m_flags & SF_WORKS_UPSIDE_DOWN; float gravityReduction = GRAVITY_REDUCTION + ( m_targetGravityScale * ( GRAVITY_REDUCTION_IN_FLIGHT - GRAVITY_REDUCTION ) ); float antiGravityForce = normalisedDeployedRatio * m_fGravityForWheelIntegrator * GetMass() * gravityReduction; Vector3 upVec = VEC3V_TO_VECTOR3( GetTransform().GetUp() ); if( !worksUpsideDown ) { upVec.x = 0.0f; upVec.y = 0.0f; } static const int MaxNumHoverWheels = 4; Assertf( GetNumWheels() <= MaxNumHoverWheels, "Increase the maximum number of wheels used for hover mode!" ); Vector3 wheelHoverForces[ MaxNumHoverWheels ] = { Vector3( 0.0f, 0.0f, 0.0f ), Vector3( 0.0f, 0.0f, 0.0f ), Vector3( 0.0f, 0.0f, 0.0f ), Vector3( 0.0f, 0.0f, 0.0f ) }; bool applyForces = true; if( ( !worksUpsideDown && ( upVec.z < 0.0f ) ) || m_nVehicleFlags.bIsDrowning || ( !m_nVehicleFlags.bEngineOn && m_specialFlightModeRatio == m_specialFlightModeTargetRatio && !( flightHandling->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) ) ) { applyForces = false; } if( CPhysics::GetIsFirstTimeSlice( CPhysics::GetCurrentTimeSlice() ) ) { m_hoveringCloseToGround = false; } float maxSpeedInMode = pHandling->m_fInitialDriveMaxFlatVel * 1.2f; if( GetModelIndex() != MI_BIKE_OPPRESSOR2 ) { maxSpeedInMode *= flightHandling->m_fHoverVelocityScale; } maxSpeedInMode += ( maxSpeedInMode * ( ( 1.0f - m_targetGravityScale ) * 0.2f ) ); // make it go 20% slower in the air float forwardVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ); float normalisedForwardVelocity = forwardVelocity; if( normalisedForwardVelocity > 0.0f ) { normalisedForwardVelocity /= maxSpeedInMode; } else { static dev_float sfMaxSpeedInReverseScale = -0.5f; maxSpeedInMode *= sfMaxSpeedInReverseScale; normalisedForwardVelocity /= maxSpeedInMode; } if( !IsRocketBoosting() && worksUpsideDown ) { // apply extra drag to slow the vehicle down if it is exceeding its maximum speed Vector3 xyVelocity = GetVelocity(); xyVelocity.z = 0.0f; float velocityMag = xyVelocity.Mag(); if( velocityMag > Abs( maxSpeedInMode ) ) { float acceleration = ( velocityMag - maxSpeedInMode ) / fTimeStep; static dev_float maxAcceleration = 25.0f; acceleration = Clamp( acceleration, -maxAcceleration, maxAcceleration ); float forceMag = acceleration; forceMag *= GetMass(); xyVelocity.Normalize(); xyVelocity *= -forceMag; ApplyForceCg( xyVelocity ); normalisedForwardVelocity = 1.0f; } } normalisedForwardVelocity = Min( normalisedForwardVelocity, 1.0f ); if( !( flightHandling->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) ) { // self-righting doesn't work in hover mode so switch back to wheels out if upside down and in contact with something if( ( upVec.z < 0.1f || ( CPhysics::ms_bInStuntMode && upVec.z < 0.2f ) ) && ( normalisedForwardVelocity < 0.1f || ( CPhysics::ms_bInStuntMode && normalisedForwardVelocity < 4.0f ) ) && GetFrameCollisionHistory() && GetFrameCollisionHistory()->GetCollisionImpulseMagSum() > 0.0f ) { m_specialFlightModeTargetRatio = 0.0f; audVehicleAudioEntity* vehicleAudioEntity = GetVehicleAudioEntity(); if( vehicleAudioEntity ) { vehicleAudioEntity->CancelSpecialFlightModeTransform(); } } } if( InheritsFromBike() && !GetDriver() && normalisedForwardVelocity < 0.1f ) { m_specialFlightModeTargetRatio = 0.0f; audVehicleAudioEntity* vehicleAudioEntity = GetVehicleAudioEntity(); if( vehicleAudioEntity ) { vehicleAudioEntity->CancelSpecialFlightModeTransform(); } } // if we're transitioning too or from hover mode update the suspension length so the suspension forces position the vehicle at the same hight the hover mode will if( normalisedDeployedRatio < 1.0f || Abs( normalisedForwardVelocity - m_previousNormalisedVelocity ) > 0.1f ) { antiGravityForce *= ( m_specialFlightModeTargetRatio + normalisedDeployedRatio ) * gravityReduction; for( int wheelIndex = 0; wheelIndex < GetNumWheels(); ++wheelIndex ) { CWheel* wheel = GetWheel( wheelIndex ); wheel->SetDisableWheelForces( false ); float forwardOffset = 0.0f; float highSpeedRaiseAmount = TARGET_SUSPENSION_RAISE_HIGH_SPEED; float lowSpeedRaiseAmount = TARGET_SUSPENSION_RAISE_LOW_SPEED; float raiseRateScale = InheritsFromBike() && m_specialFlightModeTargetRatio == 0.0f ? 1.0f : 1.1f; if( InheritsFromBike() ) { highSpeedRaiseAmount *= SUSPENSION_RAISE_INCREASE_FOR_BIKES; lowSpeedRaiseAmount *= SUSPENSION_RAISE_INCREASE_FOR_BIKES; TUNE_GROUP_FLOAT( VEHICLE_GLIDER, HIGH_SPEED_WHEEL_OFFSET, 0.5f, -10.0f, 10.0f, 0.001f ); forwardOffset = Max( 0.0f, normalisedForwardVelocity * HIGH_SPEED_WHEEL_OFFSET ); } // set the suspension target raise amount higher so that we still get wheel contacts when hovering float targetSuspensionRaise = lowSpeedRaiseAmount + ( normalisedForwardVelocity * ( highSpeedRaiseAmount - TARGET_SUSPENSION_RAISE_LOW_SPEED ) ); wheel->SetSuspensionRaiseAmount( targetSuspensionRaise * Min( 1.0f, normalisedDeployedRatio * raiseRateScale ) ); wheel->SetSuspensionForwardOffset( forwardOffset * normalisedDeployedRatio ); wheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION ); } m_previousNormalisedVelocity = normalisedForwardVelocity; } if( !worksUpsideDown ) // TEMP: might need to put something in the handling data so we can move any parts correctly { float currentOffset = m_specialFlightModeRatio > 0.0f ? 0.0f : 1.0f; static dev_float sfGrillOpenRotation = DtoR * 90.0f; static dev_float sfThrusterTargetOffset = -0.11f; float rotationTarget = ( ( m_specialFlightModeRatio - 0.1f ) * 2.0f ); float thrusterOffset = -1.0f + rotationTarget; thrusterOffset = Clamp( thrusterOffset, 0.0f, 1.0f ); thrusterOffset *= sfThrusterTargetOffset; rotationTarget = Clamp( rotationTarget, 0.0f, 1.0f ); rotationTarget *= sfGrillOpenRotation; RotateMechanicalPart( GetBoneIndex( VEH_MISC_C ), rotationTarget, currentOffset, ROT_AXIS_LOCAL_X, 10000.0f ); SlideMechanicalPart( GetBoneIndex( VEH_MISC_E ), thrusterOffset, currentOffset, Vector3( 0.0f, 1.0f, 0.0f ), 10000.0f ); } ActivatePhysics(); m_targetGravityScale = Min( m_targetGravityScale, normalisedDeployedRatio ); if( m_vehControls.GetHandBrake() && worksUpsideDown ) { m_targetGravityScale = 0.0f; } static dev_bool strafeMode = false; if( strafeMode ) { m_targetGravityScale = 0.0f; } float gravityScaleDelta = 0.0f; static dev_float sfGravityTransitionRate = 1.0f; int numContactWheels = 0; if( normalisedDeployedRatio > 0.0f ) { if( GetCollider() ) { if( applyForces ) { CBuoyancyInfo* pBuoyancyInfo = m_Buoyancy.GetBuoyancyInfo(this); int numWaterSamples = pBuoyancyInfo->m_nNumWaterSamples; // the wheels are ordered LF, RF, LR, RR // the buoyancy samples go LR, LF, RR, RF int halfNumWheels = GetNumWheels() / 2; static const int maxNumWaterWheelsForWaterSamples = 4; int sampleIndices[ maxNumWaterWheelsForWaterSamples ] = { ( ( numWaterSamples / halfNumWheels ) - 1 ), numWaterSamples - 1, 0, ( numWaterSamples / halfNumWheels ) }; float invTimeStep = 1.0f / fTimeStep; for(int wheelIndex = 0; wheelIndex < GetNumWheels(); ++wheelIndex) { int sampleIndex = sampleIndices[ wheelIndex % maxNumWaterWheelsForWaterSamples ]; CWheel* wheel = GetWheel(wheelIndex); wheelHoverForces[ wheelIndex ] = Vector3( 0.0f, 0.0f, 0.0f ); if( wheel->GetDisableWheelForces() || wheel->GetIsTouching() ) { numContactWheels += CalculateWheelHoverForce( wheel, wheelHoverForces[ wheelIndex ], upVec, normalisedForwardVelocity, normalisedDeployedRatio, fStickY, invTimeStep, sampleIndex, worksUpsideDown ); } } } if( numContactWheels == GetNumWheels() || m_disableHoverModeFlight ) { SetIsDoingJump( false ); gravityScaleDelta = -( fTimeStep * sfGravityTransitionRate * 2.0f ); m_targetGravityScale = Clamp( m_targetGravityScale + gravityScaleDelta, 0.0f, 1.0f ); } else if( numContactWheels == 0 && fStickY > 0.0f ) { gravityScaleDelta = ( fStickY * fTimeStep * sfGravityTransitionRate ); m_targetGravityScale = Clamp( m_targetGravityScale + gravityScaleDelta, 0.0f, 1.0f ); } } } Vector3 combinedWheelForce( 0.0f, 0.0f, 0.0f ); for( int i = 0; i < numContactWheels; i++ ) { combinedWheelForce += wheelHoverForces[ i ]; } if( flightHandling->m_flags & SF_LIMIT_FORCE_DELTA && numContactWheels > 0 ) { Vector3 averageWheelForce( 0.0f, 0.0f, 0.0f ); for( int i = 0; i < numContactWheels; i++ ) { averageWheelForce += wheelHoverForces[ i ]; } averageWheelForce /= (float)numContactWheels; Vector3 maxDelta = ( combinedWheelForce * 0.5f ) * Clamp( Abs( GetTransform().GetForward().GetZf() ), 0.2f, 0.75f ); maxDelta.Abs(); for( int i = 0; i < numContactWheels; i++ ) { bool isRearWheel = GetWheel( i )->GetConfigFlags().IsFlagSet( WCF_REARWHEEL ); Vector3 wheelForceWithBias = ( averageWheelForce * ( isRearWheel ? pHandling->m_fSuspensionBiasRear : pHandling->m_fSuspensionBiasFront ) ); wheelHoverForces[ i ].Max( wheelHoverForces[ i ], wheelForceWithBias - maxDelta ); wheelHoverForces[ i ].Min( wheelHoverForces[ i ], wheelForceWithBias + maxDelta ); } combinedWheelForce = Vector3( 0.0f, 0.0f, 0.0f ); for( int i = 0; i < numContactWheels; i++ ) { combinedWheelForce += wheelHoverForces[ i ]; } } for( int i = 0; i < GetNumWheels(); i++ ) { CWheel* wheel = GetWheel( i ); Vector3 wheelPos = wheel->GetProbeSegment().A; wheelPos = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( wheelPos ) ) ); ApplyForce( wheelHoverForces[ i ], wheelPos ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_HOVER_FORCE, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_HOVER_FORCE_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_HOVER_FORCE ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition() + VECTOR3_TO_VEC3V( wheelPos ), GetVehiclePosition() + VECTOR3_TO_VEC3V( wheelPos ) + VECTOR3_TO_VEC3V( wheelHoverForces[ i ] * RENDER_HOVER_FORCE_SCALE ), Color_orange ); } #endif } if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) ) { UpdateHoverModeBones(); } if( !applyForces ) { if( flightHandling->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) { Vector3 desiredUp = CalculateDesiredUp( normalisedDeployedRatio, normalisedForwardVelocity, 0.0f, 0.0f ); ApplyHoverModeStabilisation( desiredUp, normalisedDeployedRatio ); } return; } static dev_float sfStrafeModeDesiredUpScale = 0.2f; Vector3 desiredUp = CalculateDesiredUp( normalisedDeployedRatio, normalisedForwardVelocity, fStickX, strafeMode ? fStickY * sfStrafeModeDesiredUpScale : fStickY ); ApplyHoverModeThrust( flightHandling, Vector3( 0.0f, 1.0f, 0.0f ), fTimeStep, normalisedDeployedRatio, forwardVelocity, normalisedForwardVelocity, GetThrottle(), fStickY, strafeMode ? 0.0f : fStickX, strafeMode ); if( strafeMode ) { float sideVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetA() ) ); float normalisedSideVelocity = sideVelocity; normalisedSideVelocity /= maxSpeedInMode; ApplyHoverModeThrust( flightHandling, Vector3( 1.0f, 0.0f, 0.0f ), fTimeStep, normalisedDeployedRatio, sideVelocity, normalisedForwardVelocity, 0.0f, -fStickX, 0.0f, strafeMode ); } Vector3 linearDamping = VEC3V_TO_VECTOR3( flightHandling->m_vecLinearDamping ); Vector3 velocity = GetVelocity(); Vector3 localVelocity = VEC3V_TO_VECTOR3( GetTransform().UnTransform3x3( VECTOR3_TO_VEC3V( velocity ) ) ); linearDamping = linearDamping; Vector3 velocityForLinearDamping = localVelocity; // If we're reducing drag with speed. Use velocity to control the linear damping and stick input to scale lateral. // this is so the top speed isn't affected by increasing forward drag to stop the vehicle at low speed // and the turning is more responsive when off throttle if( flightHandling->m_flags & SF_REDUCE_DRAG_WITH_SPEED ) { Vector3 desiredRight; desiredRight.Cross( VEC3V_TO_VECTOR3( GetTransform().GetForward() ), desiredUp ); Vector3 desiredForward; desiredForward.Cross( desiredUp, desiredRight ); velocityForLinearDamping.x = Dot( desiredRight, velocity ); velocityForLinearDamping.y = Dot( desiredForward, velocity ); velocityForLinearDamping.z = Dot( desiredUp, velocity ); float absNormalisedVelocity = normalisedForwardVelocity; linearDamping.y = flightHandling->m_vecLinearDamping.GetYf() - ( absNormalisedVelocity * ( flightHandling->m_vecLinearDamping.GetYf() - flightHandling->m_vecLinearDampingMin.GetYf() ) ); float dampingFactor = ( 1.0f - ( 1.0f - absNormalisedVelocity * Abs( m_previousThrottle ) ) ); linearDamping.x = flightHandling->m_vecLinearDamping.GetXf() - ( dampingFactor * ( flightHandling->m_vecLinearDamping.GetXf() - flightHandling->m_vecLinearDampingMin.GetXf() ) ); } linearDamping *= velocityForLinearDamping * -1.0f * normalisedDeployedRatio; linearDamping.ClampMag( 0.0f, 149.0f ); linearDamping *= GetMass(); linearDamping = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( linearDamping ) ) ); ApplyInternalForceCg( linearDamping ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_LINEAR_DAMPING, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_LINEAR_DAMPING_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_LINEAR_DAMPING ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( linearDamping * RENDER_LINEAR_DAMPING_SCALE ), Color_orange ); } #endif bool useDesiredUpForYaw = false; if( GetNumWheels() < 4 ) { if( Abs( desiredUp.z ) < 0.95f ) { useDesiredUpForYaw = true; } } Vector3 yawAxis = useDesiredUpForYaw ? desiredUp : VEC3V_TO_VECTOR3( GetTransform().GetUp() ); Vector3 yawTorque = yawAxis * flightHandling->m_fYawTorqueScale * ( strafeMode ? 0.0f : fStickX ); if( flightHandling->m_flags & SF_STEER_TOWARDS_VELOCITY && forwardVelocity > 0.0f ) { Vec3V normalisedVelocity = VECTOR3_TO_VEC3V( GetVelocity() ); normalisedVelocity = NormalizeSafe( normalisedVelocity, GetTransform().GetB() ); // get the right component of the local velocity and apply a torque to reduce it to 0 float vehicleYaw = -Dot( normalisedVelocity, GetTransform().GetA() ).Getf(); yawTorque += VEC3V_TO_VECTOR3( GetTransform().GetUp() ) * flightHandling->m_fStabilityAssist * ( 1.0f - Abs( fStickX ) ) * vehicleYaw * Abs( m_previousThrottle ); } if( strafeMode ) { const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame(); float dirToCam = aimFrame.GetFront().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ); // only rotate towards the camera position if we are reasonable close to it if( dirToCam > 0.0f && desiredUp.z > 0.75f ) { Vector3 aimPos = aimFrame.GetPosition() + ( aimFrame.GetFront() * 30.0f ); aimPos = VEC3V_TO_VECTOR3( GetTransform().UnTransform( VECTOR3_TO_VEC3V( aimPos ) ) ); aimPos.Normalize(); static dev_float sfYawTorqueScale = 25.0f; float targetYaw = -aimPos.x * Abs( aimPos.x ); float torque = targetYaw; torque *= sfYawTorqueScale; torque -= GetAngVelocity().GetZ(); yawTorque = Vector3( 0.0f, 0.0f, 1.0f ) * torque; } } yawTorque *= GetAngInertia().z * normalisedDeployedRatio; ApplyInternalTorque( yawTorque ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_YAW_TORQUE, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_YAW_TORQUE_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_YAW_TORQUE ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( yawTorque * RENDER_YAW_TORQUE_SCALE ), Color_yellow ); } #endif Vector3 rollTorque = VEC3V_TO_VECTOR3( GetTransform().GetForward() ) * flightHandling->m_fRollTorqueScale * GetAngInertia().y * fStickX * normalisedDeployedRatio; ApplyInternalTorque( rollTorque ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_ROLL_TORQUE, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_ROLL_TORQUE_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_ROLL_TORQUE ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( rollTorque * RENDER_ROLL_TORQUE_SCALE ), Color_green ); } #endif if( !m_disableHoverModeFlight ) { if( !InheritsFromBike() || m_targetGravityScale > 0.0f ) { Vector3 pitchTorque = VEC3V_TO_VECTOR3( GetTransform().GetRight() ) * flightHandling->m_fPitchTorqueScale * GetAngInertia().x * fStickY * normalisedDeployedRatio; ApplyInternalTorque( pitchTorque ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_PITCH_TORQUE, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_PITCH_TORQUE_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_PITCH_TORQUE ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( pitchTorque * RENDER_PITCH_TORQUE_SCALE ), Color_blue ); } #endif } } Vector3 angularDamping = VEC3V_TO_VECTOR3( flightHandling->m_vecAngularDamping ); if( flightHandling->m_flags & SF_REDUCE_DRAG_WITH_SPEED ) { angularDamping = VEC3V_TO_VECTOR3( flightHandling->m_vecAngularDamping - ( ScalarV( Abs( normalisedForwardVelocity ) ) * ( flightHandling->m_vecAngularDamping - flightHandling->m_vecAngularDampingMin ) ) ); } angularDamping *= GetAngInertia() * GetAngVelocity() * -1.0f * normalisedDeployedRatio; ApplyInternalTorque( angularDamping ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_ANGULAR_DAMPING, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_ANGULAR_DAMPING_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_ANGULAR_DAMPING ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( angularDamping * RENDER_ANGULAR_DAMPING_SCALE ), Color_tan ); } #endif float maxLiftForce = m_fGravityForWheelIntegrator * GetMass(); float totalLiftForce = antiGravityForce - ( combinedWheelForce.z * 1.1f ); if( normalisedDeployedRatio > 0.0f ) { ApplyHoverModeStabilisation( desiredUp, normalisedDeployedRatio ); // Generate lift with velocity float extraLift = GetVelocity().Mag2() * flightHandling->m_fLiftCoefficient * normalisedDeployedRatio; totalLiftForce += extraLift * m_targetGravityScale; maxLiftForce = Abs( maxLiftForce ); } totalLiftForce = Clamp( totalLiftForce, -maxLiftForce, maxLiftForce ); upVec *= totalLiftForce; ApplyForceCg( upVec ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_TOTAL_LIFT_FORCE, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_TOTAL_LIFT_FORCE_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_TOTAL_LIFT_FORCE ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( upVec * RENDER_TOTAL_LIFT_FORCE_SCALE ), Color_pink ); } #endif // reset wheel state if( normalisedDeployedRatio == 1.0f ) { for( int i = 0; i < GetNumWheels(); i++ ) { CWheel* wheel = GetWheel( i ); float maxRaiseAmount = MAX_HEIGHT_FOR_VELOCITY_DAMPING * normalisedDeployedRatio * 1.1f; if( wheel->GetSuspensionRaiseAmount() != maxRaiseAmount ) { wheel->SetSuspensionRaiseAmount( maxRaiseAmount ); wheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION ); } // reset some hit flags so other bits of the code know not to process these wheels wheel->ResetHitPos(); wheel->GetDynamicFlags().ClearFlag( WF_HIT_PREV ); wheel->GetDynamicFlags().ClearFlag( WF_HIT ); wheel->SetDisableWheelForces( true ); } } static dev_float sfMinNormalisedVelocityForLowSpeedMovement = 0.05f; static dev_float sfMinNormalisedVelocityForLowSpeedMovementInv = 1.0f / sfMinNormalisedVelocityForLowSpeedMovement; bool anchorPosition = worksUpsideDown && GetDriver() && GetDriver()->IsLocalPlayer() && !ms_formationLeader; if( anchorPosition && ms_formationOffset.Mag2() == 0.0f ) { ms_formationOffset = VEC3V_TO_VECTOR3( GetTransform().GetPosition() ); } if( worksUpsideDown && normalisedForwardVelocity < sfMinNormalisedVelocityForLowSpeedMovement ) { ApplyLowSpeedMovement( normalisedDeployedRatio, 1.0f - ( normalisedForwardVelocity * sfMinNormalisedVelocityForLowSpeedMovementInv ), anchorPosition ); } } void CVehicle::UpdateOutriggers( float fTimeStep ) { static dev_float sfOutriggerDeployedDistance = -0.45f; static dev_float sfOutriggerDeploySpeed = 0.016f; float targetPosition = m_deployOutriggers ? 1.0f : 0.0f; float previousRatio = m_outriggerRatio; float invTimeStep = 1.0f / fTimeStep; float movementSpeed = invTimeStep * sfOutriggerDeploySpeed; fragPhysicsLOD* physicsLOD = nullptr; phArticulatedCollider* pCollider = nullptr; if( GetVehicleFragInst() ) { physicsLOD = GetVehicleFragInst()->GetTypePhysics(); if( GetVehicleFragInst()->GetCacheEntry() && GetVehicleFragInst()->GetCacheEntry()->GetHierInst() ) { pCollider = GetVehicleFragInst()->GetCacheEntry()->GetHierInst()->articulatedCollider; } } for( int i = (int)VEH_LEG_FL; i <= (int)VEH_LEG_RR; i++ ) { previousRatio = m_outriggerRatio; int boneIndex = GetBoneIndex( (eHierarchyId)i ); SlideMechanicalPart( boneIndex, targetPosition, previousRatio, Vector3( 0.0f, 0.0f, sfOutriggerDeployedDistance ), movementSpeed ); bool updateCollision = previousRatio != m_outriggerRatio; bool enableCollision = targetPosition == 1.0f && !m_nVehicleFlags.bRestingOnPhysical; if( physicsLOD && boneIndex != -1 ) { int group = GetVehicleFragInst()->GetGroupFromBoneIndex( boneIndex ); fragTypeGroup* pGroup = physicsLOD->GetGroup( group ); if( group > -1 ) { phBoundComposite* pBound = (phBoundComposite*)GetVehicleFragInst()->GetArchetype()->GetBound(); int childFragmentIndex = pGroup->GetChildFragmentIndex(); if( updateCollision ) { for( s32 i = 0; i < pGroup->GetNumChildren(); ++i ) { pBound->SetIncludeFlags( childFragmentIndex + i, enableCollision ? ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES : 0 ); } ActivatePhysics(); } const Matrix34 &matrixParentBone = GetObjectMtx( boneIndex ); Matrix34 matLink = matrixParentBone; matLink.d -= VEC3V_TO_VECTOR3( pBound->GetCGOffset() ); for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++) { pBound->SetCurrentMatrix( childFragmentIndex + iChild, MATRIX34_TO_MAT34V( matrixParentBone ) ); pBound->SetLastMatrix( childFragmentIndex + iChild, MATRIX34_TO_MAT34V( matrixParentBone ) ); // Keep articulated collider link attachments in sync. if( pCollider ) { if( Matrix34 *pMat = (Matrix34 *)pCollider->GetLinkAttachmentMatrices() ) { pMat[ childFragmentIndex + iChild ] = matLink; } } } } } } m_outriggerRatio = previousRatio; } void CVehicle::UpdateLinkAttachmentMatricesRecursive( phBoundComposite* pBoundComp, fragTypeGroup* pGroup ) { fragInst* pFragInst = GetFragInst(); if( pFragInst ) { pFragInst->ForceArticulatedColliderMode(); } if(pFragInst && pFragInst->GetCacheEntry() && pFragInst->GetCacheEntry()->GetHierInst() && pFragInst->GetCacheEntry()->GetHierInst()->articulatedCollider && pFragInst->GetCacheEntry()->GetHierInst()->articulatedCollider->GetBody()) { phArticulatedCollider *pCollider = pFragInst->GetCacheEntry()->GetHierInst()->articulatedCollider; int childFragmentIndex = pGroup->GetChildFragmentIndex(); u32 boneIndex = pFragInst->GetCacheEntry()->GetComponentToBoneIndexMap()[childFragmentIndex]; const Matrix34 &matrixParentBone = GetObjectMtx(boneIndex); Matrix34 matLink = matrixParentBone; matLink.d -= VEC3V_TO_VECTOR3(pBoundComp->GetCGOffset()); for(int iChild = 0; iChild < pGroup->GetNumChildren(); iChild++) { pBoundComp->SetCurrentMatrix(childFragmentIndex + iChild, MATRIX34_TO_MAT34V(matrixParentBone)); pBoundComp->SetLastMatrix(childFragmentIndex + iChild, MATRIX34_TO_MAT34V(matrixParentBone)); // Keep articulated collider link attachments in sync. if(pCollider) { if(Matrix34 *pMat = (Matrix34 *)pCollider->GetLinkAttachmentMatrices()) { pMat[childFragmentIndex + iChild] = matLink; } } } for( s32 f = 0; f < pGroup->GetNumChildGroups(); ++f ) { fragTypeGroup* pChildGroup = pFragInst->GetTypePhysics()->GetGroup( f + pGroup->GetChildGroupsPointersIndex() ); UpdateLinkAttachmentMatricesRecursive( pBoundComp, pChildGroup ); } } } bool CVehicle::GetHasLandingGear() { if( InheritsFromPlane() ) { return true; } if( InheritsFromHeli() ) { return static_cast< CHeli* >( this )->HasLandingGear(); } return false; } bool CVehicle::ProcessFoliageImpact( phInst* otherInstance, Vec3V myPosition, int myComponent, int otherComponent, int otherElement ) { bool bReallyFoliageBound = false; // This is where we can hook up event generation for vehicles colliding against foliage bounds. if( otherInstance && otherInstance->GetArchetype()->GetTypeFlags()&ArchetypeFlags::GTA_FOLIAGE_TYPE ) { // If the bound hit was actually a composite, check that the child bound that we hit is really a foliage bound. if( otherInstance->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE ) { phBoundComposite* pBoundComposite = static_cast(otherInstance->GetArchetype()->GetBound()); if(pBoundComposite->GetTypeFlags( otherComponent ) & ArchetypeFlags::GTA_FOLIAGE_TYPE ) { bReallyFoliageBound = true; } } else { bReallyFoliageBound = true; } if(bReallyFoliageBound) { float fFoliageBoundRadius = CPhysics::GetFoliageRadius(otherInstance, otherComponent, otherElement); AddToFoliageDrag(myComponent, VEC3V_TO_VECTOR3(myPosition), VEC3V_TO_VECTOR3(otherInstance->GetPosition()), fFoliageBoundRadius); } } return bReallyFoliageBound; } void CVehicle::ForceUpdateLinkMatrices() { // There is a bug with the link matricies on some of the turreted vehicles. Just forcing it to update the skeleton based on the bound positions doesn't fix it. // it seems we to actually update the physics to fix this so // just force it to activate, unless it is waiting on collision GetCurrentPhysicsInst()->SetInstFlag( phInst::FLAG_NEVER_ACTIVATE, false ); ClearBaseFlag( fwEntity::IS_FIXED ); ClearBaseFlag( fwEntity::IS_FIXED_BY_NETWORK ); ActivatePhysics(); } float CVehicle::sfRollStabilisationMult = 20.0f; float CVehicle::sfPitchStabilisationMult = 60.0f; void CVehicle::ApplyParachuteStabilisation( Vector3& vStabilisationAxis, float fAngle, float fMaxTorque /* = 0.0f */, float pitchStabilisationMult /* = sfPitchStabilisationMult */, float rollStabilisationMult /* = sfRollStabilisationMult */ ) { // Apply a pitch stabilisation torque so that we rotate towards the desired rotation Vector3 vRightAxis = VEC3V_TO_VECTOR3( GetTransform().GetA() ); Vector3 vRightTorque = vStabilisationAxis.Dot( vRightAxis ) * vRightAxis * fAngle * pitchStabilisationMult; // Apply a roll stabilisation force so that we rotate towards the desired rotation Vector3 vFwdAxis = VEC3V_TO_VECTOR3( GetTransform().GetB() ); Vector3 vFwdTorque = vStabilisationAxis.Dot( vFwdAxis ) * vFwdAxis * fAngle * rollStabilisationMult; // If these forces should be capped, make sure they don't exceed the cap if( fMaxTorque != 0.0f ) { float fRightTorqueMag = vRightTorque.Mag(); if( fRightTorqueMag > fMaxTorque ) { vRightTorque *= ( fMaxTorque / fRightTorqueMag ); } float fFwdTorqueMag = vFwdTorque.Mag(); if( fFwdTorqueMag > fMaxTorque ) { vFwdTorque *= ( fMaxTorque / fFwdTorqueMag ); } } ApplyInternalTorque( vRightTorque * GetAngInertia().x ); ApplyInternalTorque( vFwdTorque * GetAngInertia().y ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_PARACHUTE_STABILISATION_TORQUE, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_PARACHUTE_STABILISATION_TORQUE_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_PARACHUTE_STABILISATION_TORQUE ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( vRightTorque * RENDER_PARACHUTE_STABILISATION_TORQUE_SCALE ), Color_OliveDrab ); CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( vRightTorque * RENDER_PARACHUTE_STABILISATION_TORQUE_SCALE ), Color_grey ); } #endif } void CVehicle::ApplyHoverModeStabilisation( Vector3& vDesiredUp, float normalisedDeployedRatio ) { Vector3 vCurrentUp = VEC3V_TO_VECTOR3( GetTransform().GetC() ); // Get the angle between the current up and the desired up float fAngle = AcosfSafe( Dot( vCurrentUp, vDesiredUp ) ); // Avoid cross product breaking down if( Abs( fAngle ) > 0.001f ) { static float sfMaxStabilisationTorque = 10.0f; float stablisationTorque = sfMaxStabilisationTorque; // Calculate the rotation axis by crossing the current and desired up vectors Vector3 vAxisToRotateOn = vCurrentUp; vAxisToRotateOn.Cross( vDesiredUp ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_CURRENT_AND_DESIRED_UP, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_CURRENT_AND_DESIRED_UP_SCALE, 1.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_CURRENT_AND_DESIRED_UP ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( vCurrentUp * RENDER_CURRENT_AND_DESIRED_UP_SCALE ), Color_azure ); CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( vDesiredUp * RENDER_CURRENT_AND_DESIRED_UP_SCALE ), Color_DarkOrange ); } #endif float rollStabilisationMult = sfRollStabilisationMult; if( InheritsFromBike() ) { vAxisToRotateOn.Normalize(); static dev_float sfBikeStabilisationTorqueScale = 5.0f; stablisationTorque *= sfBikeStabilisationTorqueScale; static dev_float sfBikeRoolStabilisationMultScale = 2.0f; rollStabilisationMult *= 1.0f + ( Abs( GetTransform().GetRight().GetZf() ) * sfBikeRoolStabilisationMultScale ); } // Subtract the current angular velocity from the rotation axis to keep the vehicle from spinning out of control static dev_float sfScaleAngVelocityForStabilisation = 0.5f; vAxisToRotateOn -= GetAngVelocity() * sfScaleAngVelocityForStabilisation; ApplyParachuteStabilisation( vAxisToRotateOn, fAngle, stablisationTorque * normalisedDeployedRatio, sfPitchStabilisationMult, rollStabilisationMult ); } } void CVehicle::ApplyHoverModeThrust( CSpecialFlightHandlingData* flightHandling, Vector3 thrustDirection, float fTimeStep, float normalisedDeployedRatio, float forwardVelocity, float normalisedForwardVelocity, float fThrottle, float fStickY, float fStickX, bool strafeMode ) { float throttle = strafeMode ? Clamp( fThrottle - fStickY, -1.0f, 1.0f ) : fThrottle; float thrustDelta = throttle - m_previousThrottle; static dev_float sfMaxThrustDelta = 2.0f; if( !InheritsFromBike() || thrustDelta * m_previousThrottle > 0.0f ) { thrustDelta = Min( thrustDelta, sfMaxThrustDelta * fTimeStep ); } if( !strafeMode ) { m_previousThrottle += thrustDelta; m_previousThrottle = Clamp( m_previousThrottle, -0.75f, 1.0f ); } float throttleForThrust = strafeMode ? throttle : m_previousThrottle; static dev_float minVelocityToForceThrottle = 5.0f; static dev_float maxVelocityToForceThrottle = 4000.0f; float currentVelocityMag2 = GetVelocity().Mag2(); float absStickX = Abs( fStickX ); if( flightHandling->m_flags & SF_FORCE_MIN_THROTTLE_WHEN_TURNING && currentVelocityMag2 > minVelocityToForceThrottle && m_previousThrottle >= 0.0f && absStickX > 0.0f ) { static dev_float sfForcedMinThrottle = 0.3f; float minThrottlePercent = absStickX * ( ( currentVelocityMag2 - minVelocityToForceThrottle ) / ( maxVelocityToForceThrottle - minVelocityToForceThrottle ) ); throttleForThrust = Max( sfForcedMinThrottle * minThrottlePercent, throttleForThrust ); } float thrustForce = flightHandling->m_fMaxThrust * throttleForThrust; if( forwardVelocity * thrustForce > 0.0f ) { //if( flightHandling->m_fMinSpeedForThrustFalloff > 0.0f && // forwardVelocity > 0.0f ) //{ // float hoverModeMaxSpeed = pHandling->m_fInitialDriveMaxFlatVel * Lerp( m_targetGravityScale, flightHandling->m_fHoverVelocityScale, 1.0f ); // if( strafeMode ) // { // static float hoverModeMaxSpeedScale = 0.5f; // hoverModeMaxSpeed *= hoverModeMaxSpeedScale; // } // float thrustFallOffValue = flightHandling->m_fMinSpeedForThrustFalloff * hoverModeMaxSpeed; // thrustFallOffValue *= thrustFallOffValue; // thrustForce *= 1.0f - Clamp( ( currentVelocityMag2 - thrustFallOffValue ) / ( ( hoverModeMaxSpeed * hoverModeMaxSpeed ) - thrustFallOffValue ), 0.0f, 1.0f ); //} if( flightHandling->m_fMinSpeedForThrustFalloff > 0.0f ) { if( normalisedForwardVelocity > flightHandling->m_fMinSpeedForThrustFalloff ) { thrustForce *= 1.0f - Clamp( ( normalisedForwardVelocity - flightHandling->m_fMinSpeedForThrustFalloff ) / ( 1.0f - flightHandling->m_fMinSpeedForThrustFalloff ), 0.0f, 1.0f ); } } else { if( strafeMode ) { thrustForce *= Max( 0.0f, 1.0f - ( normalisedForwardVelocity * 2.0f ) ); } else { thrustForce *= 1.0f - normalisedForwardVelocity; } } } if( flightHandling->m_fBrakingThrustScale > 0.0f ) { float accelerationDueToGravity = GRAVITY * GetTransform().GetForward().GetZf() * fTimeStep; accelerationDueToGravity *= 2.0f; if( Abs( forwardVelocity ) > Abs( accelerationDueToGravity ) && forwardVelocity * thrustForce < 0.0f ) { thrustForce *= flightHandling->m_fBrakingThrustScale; // clamp the max thrust to stop the vehicle from changing direction too quickly float maxThrust = Abs( forwardVelocity ) / fTimeStep; static dev_float sfMinThrustToChangeDir = 0.1f; // TODO: MAYBE THIS SHOULD BE A PARAM IN HANDLING DATA ALONG WITH REVERSE THRUST SCALE maxThrust = Max( maxThrust, flightHandling->m_fMaxThrust * sfMinThrustToChangeDir ); thrustForce = Clamp( thrustForce, -maxThrust, maxThrust ); } } Vector3 thrustVec = thrustDirection * thrustForce * GetMass() * normalisedDeployedRatio; static dev_float sfMaxThrustVector = DtoR * 25.0f; if( !InheritsFromBike() || m_targetGravityScale > 0.0f ) { thrustVec.RotateX( fStickY * sfMaxThrustVector ); } thrustVec = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( thrustVec ) ) ); ApplyInternalForceCg( thrustVec ); #if __BANK TUNE_GROUP_BOOL( VEHICLE_GLIDER, RENDER_THRUST, false ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RENDER_THRUST_SCALE, 0.001f, 0.0f, 1.0f, 0.001f ); if( CPhysics::GetIsLastTimeSlice( CPhysics::GetCurrentTimeSlice() ) && RENDER_THRUST ) { CVehicle::ms_debugDraw.AddLine( GetVehiclePosition(), GetVehiclePosition() + VECTOR3_TO_VEC3V( thrustVec * RENDER_THRUST_SCALE ), Color_chocolate ); } #endif } int CVehicle::CalculateWheelHoverForce( CWheel* wheel, Vector3& wheelForce, Vector3& upVec, float normalisedForwardVelocity, float normalisedDeployedRatio, float fStickY, float invTimeStep, int sampleIndex, bool worksUpsideDown ) { TUNE_GROUP_FLOAT( VEHICLE_GLIDER, TARGET_HOVER_HEIGHT_LOW_SPEED, 1.1f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, TARGET_HOVER_HEIGHT_HIGH_SPEED, 1.5f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, TARGET_HOVER_HEIGHT_SCALE_WHEN_NOT_RETRACTING_WHEELS, 1.25f, 0.0f, 10.0f, 0.001f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_FORCE, 140.0f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, HOVER_FORCE_SCALE, 80.0f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, MAX_DISTANCE_FROM_HOVER_HEIGHT, 1.5f, 0.0f, 100.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, HOVER_HEIGHT_TO_DISABLE_GRASS, 1.5f, 0.0f, 10.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, VELOCITY_DAMPING_FACTOR, 0.05f, 0.0f, 1.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, RANDOM_FORCE_SCALE_FOR_ENGINE_DAMAGE, 0.4f, 0.0f, 10.0f, 0.001f ); Vec3V wheelPos = VECTOR3_TO_VEC3V( wheel->GetProbeSegment().A ); wheelPos = GetTransform().Transform( wheelPos ); Vec3V wheelVelocity = GetCollider()->GetLocalVelocity( VEC3V_TO_VECTOR3( wheelPos ) ); static dev_float waterSampleOffset = -0.4f; float targetHoverHeight = TARGET_HOVER_HEIGHT_LOW_SPEED + ( normalisedForwardVelocity * ( TARGET_HOVER_HEIGHT_HIGH_SPEED - TARGET_HOVER_HEIGHT_LOW_SPEED ) ); static dev_bool sbEnableVeryLowSpeedHoverHeight = false; if( sbEnableVeryLowSpeedHoverHeight && pHandling->GetSpecialFlightHandlingData() && pHandling->GetSpecialFlightHandlingData()->m_flags & SF_FORCE_SPECIAL_FLIGHT_WHEN_DRIVEN ) { TUNE_GROUP_FLOAT( VEHICLE_GLIDER, TARGET_HOVER_HEIGHT_VERY_LOW_SPEED, 0.9f, 0.0f, 10000.0f, 0.1f ); TUNE_GROUP_FLOAT( VEHICLE_GLIDER, NORMALISED_VERY_LOW_SPEED, 0.05f, 0.0f, 10000.0f, 0.1f ); if( normalisedForwardVelocity < NORMALISED_VERY_LOW_SPEED ) { targetHoverHeight = TARGET_HOVER_HEIGHT_VERY_LOW_SPEED + ( ( normalisedForwardVelocity / NORMALISED_VERY_LOW_SPEED ) * ( TARGET_HOVER_HEIGHT_LOW_SPEED - TARGET_HOVER_HEIGHT_VERY_LOW_SPEED ) ); } } if( InheritsFromBike() ) { targetHoverHeight *= TARGET_HOVER_HEIGHT_SCALE_WHEN_NOT_RETRACTING_WHEELS; } float waterHeight = m_nFlags.bPossiblyTouchesWater ? ( m_Buoyancy.GetWaterLevelOnSample( sampleIndex ) + waterSampleOffset ) : -1000.0f; waterHeight += wheelPos.GetZf(); float wheelHitPosZ = wheel->GetHitPos().z; // if the wheel hit pos is above the wheel pos then it probably isn't valid so we'll ignore it and set to to underneath the wheel bool discardHit = !worksUpsideDown && wheelHitPosZ > wheelPos.GetZf(); Vector3 hitDirection = wheel->GetHitPos() - VEC3V_TO_VECTOR3( wheelPos ); if( hitDirection.Dot( upVec ) > 0.0f ) { discardHit = !worksUpsideDown; } if( discardHit ) { wheelHitPosZ = wheelPos.GetZf() - MAX_HEIGHT_FOR_VELOCITY_DAMPING; } float heightFromGround = 0.0f; float distanceFromTargetHeight = 0.0f; float hoverForce = 0.0f; if( worksUpsideDown ) { Vector3 localWheelHitPos = VEC3V_TO_VECTOR3( GetTransform().UnTransform( VECTOR3_TO_VEC3V( wheel->GetHitPos() ) ) ); Vector3 offsetFromGround = wheel->GetProbeSegment().A - localWheelHitPos; heightFromGround = offsetFromGround.Dot( upVec ); heightFromGround = Min( Abs( heightFromGround ), Abs( wheelPos.GetZf() - waterHeight ) ); distanceFromTargetHeight = targetHoverHeight - heightFromGround; distanceFromTargetHeight = Clamp( distanceFromTargetHeight, -MAX_DISTANCE_FROM_HOVER_HEIGHT, MAX_DISTANCE_FROM_HOVER_HEIGHT ); distanceFromTargetHeight *= Abs( distanceFromTargetHeight ); hoverForce = distanceFromTargetHeight * HOVER_FORCE_SCALE; } else { heightFromGround = Abs( wheelPos.GetZf() - ( Max( waterHeight, wheelHitPosZ ) ) ); distanceFromTargetHeight = targetHoverHeight - heightFromGround; distanceFromTargetHeight = Clamp( distanceFromTargetHeight, -MAX_DISTANCE_FROM_HOVER_HEIGHT, MAX_DISTANCE_FROM_HOVER_HEIGHT ); distanceFromTargetHeight *= Abs( distanceFromTargetHeight ); hoverForce = distanceFromTargetHeight * HOVER_FORCE_SCALE; } if( pHandling->GetSpecialFlightHandlingData() && pHandling->GetSpecialFlightHandlingData()->m_flags & SF_MAINTAIN_HOVER_HEIGHT ) { static dev_float sfForceDownScale = 0.75f; bool scaleHoverForce = ( !worksUpsideDown && hoverForce < 0.0f ) || ( worksUpsideDown && upVec.z > 0.0f && hoverForce < 0.0f ) || ( worksUpsideDown && upVec.z < 0.0f && hoverForce > 0.0f ); if( scaleHoverForce ) { hoverForce *= sfForceDownScale; static dev_float sfMaxVelocityForIncreaseDownForce = 10.0f; float forceScale = 0.0f; if( !worksUpsideDown ) { forceScale = Clamp( ( GetVelocity().GetZ() + sfMaxVelocityForIncreaseDownForce ) / sfMaxVelocityForIncreaseDownForce, 0.0f, 1.0f ); } else { forceScale = Clamp( ( upVec.Dot( GetVelocity() ) + sfMaxVelocityForIncreaseDownForce ) / sfMaxVelocityForIncreaseDownForce, 0.0f, 1.0f ); } hoverForce *= forceScale; } if( m_vehControls.GetHandBrake() && worksUpsideDown && upVec.z > 0.5f ) { static dev_float sfVerticalDropForceScale = 1.5f; hoverForce = Clamp( hoverForce, -MAX_FORCE * sfVerticalDropForceScale, MAX_FORCE ); } else { hoverForce = Clamp( hoverForce, -MAX_FORCE * ( 1.0f - m_targetGravityScale ) * sfForceDownScale, MAX_FORCE ); } } else { float minHoverForce = -( Max( 0.0f, distanceFromTargetHeight + MAX_DISTANCE_FROM_HOVER_HEIGHT ) ); minHoverForce *= MAX_FORCE * ( 1.0f - m_targetGravityScale ); hoverForce = Clamp( hoverForce, minHoverForce, MAX_FORCE ); } if( heightFromGround < HOVER_HEIGHT_TO_DISABLE_GRASS ) { m_hoveringCloseToGround = true; } float wheelMass = GetMass() / (float)GetNumWheels(); hoverForce *= wheelMass; Vector3 resultantForce = upVec * hoverForce; float velocityDampingScale = 1.0f - ( Clamp( ( heightFromGround - targetHoverHeight ) / ( MAX_HEIGHT_FOR_VELOCITY_DAMPING - targetHoverHeight ), 0.0f, 1.0f ) ); velocityDampingScale *= VELOCITY_DAMPING_FACTOR; if( !worksUpsideDown ) { resultantForce.z -= ( ( wheelVelocity.GetZf() * wheelMass ) * invTimeStep ) * velocityDampingScale; } else { resultantForce.z -= ( ( upVec.Dot( VEC3V_TO_VECTOR3( wheelVelocity ) ) * wheelMass ) * invTimeStep ) * velocityDampingScale; } resultantForce *= normalisedDeployedRatio; float engineHealthScale = Max( 0.0f, ENGINE_HEALTH_MAX - m_VehicleDamage.GetEngineHealth() ) / ENGINE_HEALTH_MAX; resultantForce += resultantForce * fwRandom::GetRandomNumberInRange( -RANDOM_FORCE_SCALE_FOR_ENGINE_DAMAGE * engineHealthScale, RANDOM_FORCE_SCALE_FOR_ENGINE_DAMAGE * engineHealthScale ); float engineMisFireScale = m_Transmission.GetCurrentlyMissFiring() ? fwRandom::GetRandomNumberInRange( -RANDOM_FORCE_SCALE_FOR_ENGINE_DAMAGE, 0.0f ) : 0.0f; resultantForce += resultantForce * engineMisFireScale; wheelForce += resultantForce; if( fStickY <= 0.0f ) { if( heightFromGround < targetHoverHeight ) { return 1; } } return 0; } void CVehicle::UpdateHoverModeBones() { static const int numBonesPerWing = 3; static const int numBonesPerSide = numBonesPerWing * 2; static const int numWingBones = numBonesPerSide * 2; static const eHierarchyId wingBones[ numWingBones ] = { VEH_MISC_O, VEH_MISC_T, VEH_MISC_R, VEH_MISC_W, VEH_MISC_V, VEH_MISC_U, VEH_MISC_F, VEH_MISC_Q, VEH_MISC_P, VEH_MISC_Z, VEH_MISC_Y, VEH_MISC_X }; static dev_float sfWingTargetOffsetFront = -0.095f; static dev_float sfWingTargetOffsetRear = -0.25f; static dev_float sfWingTargetAngle = 40.577f * DtoR; float targetPosition = 0.0f; Vector3 slideDirection( sfWingTargetOffsetFront, 0.0f, 0.0f ); for( int i = 0; i < numBonesPerSide; i++ ) { if( i == numBonesPerWing ) { slideDirection *= -1.0f; targetPosition = 0.0f; } targetPosition += m_targetGravityScale; int boneIndex = GetBoneIndex( wingBones[ i ] ); if( boneIndex > -1 ) { OffsetBonePosition( GetBoneIndex( wingBones[ i ] ), targetPosition, slideDirection ); } } slideDirection.x = sfWingTargetOffsetRear; targetPosition = 0.0f; if( !InheritsFromBike() ) { for( int i = numBonesPerSide; i < numWingBones; i++ ) { if( i == numBonesPerSide + numBonesPerWing ) { slideDirection *= -1.0f; targetPosition = 0.0f; } targetPosition += m_targetGravityScale; int boneIndex = GetBoneIndex( wingBones[ i ] ); if( boneIndex > -1 ) { OffsetBonePosition( GetBoneIndex( wingBones[ i ] ), targetPosition, slideDirection ); } } } else { UpdateWingBones( Abs( m_targetGravityScale - 1.0f ) * WING_RETRACTED_DISTANCE, false ); } audVehicleAudioEntity* vehicleAudioEntity = GetVehicleAudioEntity(); static dev_float sfWingRotationRate = 1.0f; float tempOffset = -m_currentWingRatio; if( m_targetGravityScale >= 1.0f ) { int boneIndex = GetBoneIndex( VEH_MISC_X ); if( boneIndex > -1 ) { RotateMechanicalPart( boneIndex, -sfWingTargetAngle, tempOffset, ROT_AXIS_LOCAL_Y, sfWingRotationRate ); } boneIndex = GetBoneIndex( VEH_MISC_P ); if( boneIndex > -1 ) { RotateMechanicalPart( boneIndex, sfWingTargetAngle, m_currentWingRatio, ROT_AXIS_LOCAL_Y, sfWingRotationRate ); if( vehicleAudioEntity ) { vehicleAudioEntity->TriggerSpecialFlightModeWingDeploy( true ); } } } else { int boneIndex = GetBoneIndex( VEH_MISC_X ); if( boneIndex > -1 ) { RotateMechanicalPart( boneIndex, 0.0f, tempOffset, ROT_AXIS_LOCAL_Y, sfWingRotationRate * 2.0f ); } boneIndex = GetBoneIndex( VEH_MISC_P ); if( boneIndex > -1 ) { RotateMechanicalPart( boneIndex, 0.0f, m_currentWingRatio, ROT_AXIS_LOCAL_Y, sfWingRotationRate * 2.0f ); if( vehicleAudioEntity ) { vehicleAudioEntity->TriggerSpecialFlightModeWingDeploy( false ); } } } static dev_float sfRotatorAmount = 25.0f * DtoR; float targetRatio = -m_specialFlightModeRatio * sfRotatorAmount; bool hasRotators = false; for( int i = 0; i <= (int)( LAST_ROTATOR - FIRST_ROTATOR ); i++ ) { int boneIndex = GetBoneIndex( (eHierarchyId)( (int)FIRST_ROTATOR + i ) ); if( boneIndex > -1 ) { RotateMechanicalPart( boneIndex, 0.0f, targetRatio, ROT_AXIS_LOCAL_Y, sfWingRotationRate ); targetRatio = m_specialFlightModeRatio * -1.0f * Sign( targetRatio ) * sfRotatorAmount; hasRotators = true; } } //if( hasRotators ) //{ // static dev_float sfRotatorSlideAmount = -0.02f; // targetRatio = -m_specialFlightModeRatio * sfRotatorSlideAmount; // for( int i = 0; i <= (int)( VEH_MISC_N - VEH_MISC_M ); i++ ) // { // int boneIndex = GetBoneIndex( (eHierarchyId)( (int)VEH_MISC_M + i ) ); // if( boneIndex > -1 ) // { // OffsetBonePosition( boneIndex, targetRatio, Vector3( 1.0f, 0.0f, 0.0f ) ); // targetRatio = m_specialFlightModeRatio * Sign( targetRatio ) * sfRotatorSlideAmount; // } // } //} static const int numNozzleBones = 2; static const eHierarchyId nozzleBones[ numNozzleBones ] = { VEH_NOZZLE_F, VEH_NOZZLE_R }; static dev_float sfMaxNozzleAngleOnGround = PI * -0.5f; static dev_float sfMaxNozzleAngleInFlight = PI * -0.25f; static dev_float sfMaxNozzleAngle = PI * 0.75f; static dev_float nozzleRotationSpeed = 2.0f; float maxNozzleAngle = Lerp( m_targetGravityScale, sfMaxNozzleAngleOnGround, sfMaxNozzleAngleInFlight ); float targetAngle = GetThrottle() * maxNozzleAngle; float pitchAngle = Asinf( GetTransform().GetForward().GetZf() ); if( GetThrottle() == 0.0f || m_targetGravityScale == 0.0f ) { targetAngle -= pitchAngle; } else { targetAngle += pitchAngle * m_targetGravityScale; } if( GetThrottle() == 0.0f ) { float forwardVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ); static dev_float invMaxVelocityForAngleChange = 0.0f; // Currently 0 as it doesn't pitch forwards anyway and it doesn't look right if it just pitches back targetAngle -= Clamp( forwardVelocity * invMaxVelocityForAngleChange, -1.0f, 1.0f ) * sfMaxNozzleAngleInFlight; } m_nozzleAngle += Clamp( targetAngle - m_nozzleAngle, -nozzleRotationSpeed * fwTimer::GetTimeStep(), nozzleRotationSpeed * fwTimer::GetTimeStep() ); for( int i = 0; i < numNozzleBones; i++ ) { int boneIndex = GetBoneIndex( nozzleBones[ i ] ); if( boneIndex > -1 ) { // if we had more bones we could make them rotate in opposite directions when turning but we don't so nevermind float tempTargetAngle = Clamp( m_nozzleAngle, -sfMaxNozzleAngle, sfMaxNozzleAngle ); RotateMechanicalPart( boneIndex, 0.0f, tempTargetAngle, ROT_AXIS_LOCAL_X, sfWingRotationRate ); } } } Vector3 CVehicle::CalculateGroundHeight( float rightOffset, float forwardOffset, float probeLength ) { Vector3 vProbeDirection( 0.0f, 0.0f, probeLength ); 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 += VEC3V_TO_VECTOR3( GetTransform().GetRight() ) * rightOffset; matGlobal.d += VEC3V_TO_VECTOR3( GetTransform().GetForward() ) * forwardOffset; //setup the capsule test. WorldProbe::CShapeTestHitPoint testHitPoints[ 10 ]; WorldProbe::CShapeTestResults probeResults( testHitPoints, 10 ); const u32 iTestFlags = ( ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_MAP_TYPE_VEHICLE ); WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetResultsStructure( &probeResults ); probeDesc.SetIncludeFlags( iTestFlags ); probeDesc.SetExcludeEntity( this ); probeDesc.SetIsDirected( true ); probeDesc.SetStartAndEnd( matGlobal.d, matGlobal.d + vProbeDirection ); WorldProbe::GetShapeTestManager()->SubmitTest( probeDesc ); Vector3 result = probeDesc.GetEnd(); for( WorldProbe::ResultIterator it = probeResults.begin(); it < probeResults.last_result(); ++it ) { if( it->IsAHit() ) { 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; } } if( useThisHit ) { netObject* netEntity = hitPhysical->GetNetworkObject(); if( netEntity ) { CNetObjPhysical* netPhysicalEntity = SafeCast( CNetObjPhysical, netEntity ); if( netPhysicalEntity->IsInGhostCollision() || ( GetNetworkObject() && static_cast( GetNetworkObject() )->IsInGhostCollision() ) ) { useThisHit = false; } } } } if( useThisHit && it->GetHitPosition().z > result.z ) { result = it->GetHitPosition(); } } } return result; } Vector3 CVehicle::CalculateDesiredUp( float UNUSED_PARAM( normalisedDeployedRatio ), float normalisedForwardVelocity, float fStickX, float fStickY ) { Vector3 vDesiredUp( 0.0f, 0.0f, 1.0f ); if( GetNumWheels() >= 4 ) { Vector3 averageLeftPos( 0.0f, 0.0f, 0.0f ); Vector3 averageRightPos( 0.0f, 0.0f, 0.0f ); Vector3 averageFrontPos( 0.0f, 0.0f, 0.0f ); Vector3 averageRearPos( 0.0f, 0.0f, 0.0f ); int numLeftHits = 0; int numRightHits = 0; int numFrontHits = 0; int numRearHits = 0; for( int i = 0; i < 4; i++ ) { CWheel* wheel = GetWheel( i ); if( wheel && wheel->GetHitPos().z > 0.0f ) { if( wheel->GetProbeSegment().A.x < 0.0f ) { averageLeftPos += wheel->GetHitPos(); numLeftHits++; } else { averageRightPos += wheel->GetHitPos(); numRightHits++; } if( wheel->GetProbeSegment().A.y > 0.0f ) { averageFrontPos += wheel->GetHitPos(); numFrontHits++; } else { averageRearPos += wheel->GetHitPos(); numRearHits++; } } } averageRightPos = numRightHits ? averageRightPos / (float)numRightHits : averageRightPos; averageLeftPos = numLeftHits ? averageLeftPos / (float)numLeftHits : averageLeftPos; averageFrontPos = numFrontHits ? averageFrontPos / (float)numFrontHits : averageFrontPos; averageRearPos = numRearHits ? averageRearPos / (float)numRearHits : averageRearPos; Vector3 desiredRight = averageRightPos - averageLeftPos; Vector3 desiredFoward = averageFrontPos - averageRearPos; desiredRight.NormalizeSafe( Vector3( 1.0f, 0.0f, 0.0f ) ); desiredFoward.NormalizeSafe( Vector3( 0.0f, 1.0f, 0.0f ) ); vDesiredUp.Cross( desiredRight, desiredFoward ); } else { bool hasWheelHits = false; // we don't have enough wheels to calculate this using their hit positions so use an average of the ground normal if( GetWheel( 0 )->GetHitPos().z > 0.0f && GetWheel( 1 )->GetHitPos().z > 0.0f ) { Vector3 desiredForward = GetWheel( 1 )->GetHitPos() - GetWheel( 0 )->GetHitPos(); desiredForward.NormalizeSafe( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) ); // if the wheels are on the ground do a couple of probes either side of the vehicle so it rolls correctly on uneven ground static dev_float probeOffsetLowSpeed = 0.25f; static dev_float probeOffsetHighSpeed = 0.75f; float probeOffset = probeOffsetLowSpeed + ( ( probeOffsetHighSpeed - probeOffsetLowSpeed ) * normalisedForwardVelocity ); Vector3 hitPositions[ 2 ]; float probeLength = -( GetWheel( 0 )->GetSuspensionRaiseAmount() + 1.0f ); hitPositions[ 0 ] = CalculateGroundHeight( probeOffset, 0.0f, probeLength ); hitPositions[ 1 ] = CalculateGroundHeight( -probeOffset, 0.0f, probeLength ); Vector3 averageRight = hitPositions[ 0 ] - hitPositions[ 1 ]; averageRight.NormalizeSafe( VEC3V_TO_VECTOR3( GetTransform().GetRight() ) ); vDesiredUp.Cross( averageRight, desiredForward ); hasWheelHits = true; } // Pitch and roll the desired up vector based on stick input Quaternion q; static dev_float sfPitchMult = 0.3f; static dev_float sfRollMult = 0.5f; if( !hasWheelHits ) { Vector3 currentUp = VEC3V_TO_VECTOR3( GetTransform().GetUp() ); // if there is too big a difference between the desired up and the current up then blend back in the desired up if( currentUp.Dot( vDesiredUp ) < 0.2f ) { vDesiredUp = ( vDesiredUp * 0.2f ) + ( currentUp * 0.8f ); } } if( m_targetGravityScale > 0.1f || !hasWheelHits ) { q.FromRotation( VEC3V_TO_VECTOR3( GetTransform().GetA() ), fStickY * sfPitchMult ); q.Transform( vDesiredUp ); } q.FromRotation( VEC3V_TO_VECTOR3( GetTransform().GetB() ), fStickX * sfRollMult ); q.Transform( vDesiredUp ); } return vDesiredUp; } void CVehicle::ApplyLowSpeedMovement( float normalisedDeployedRatio, float normalisedVelocity, bool anchorPosition ) { int randomInterval = ( (int)fwTimer::GetFrameCount_ScaledNonClipped() + (int)GetRandomSeed() ); static dev_s32 pitchInterval = 127; static dev_s32 rollInterval = 255; static dev_s32 yawInterval = 31; float pitch = (float)( randomInterval & pitchInterval ); float roll = (float)( ( randomInterval / 3 ) & rollInterval ); float yaw = (float)( ( randomInterval / 11 ) & yawInterval ); pitch = sinf( ( pitch / (float)pitchInterval ) * ( PI * 2.0f ) ); roll = sinf( ( roll / (float)rollInterval ) * ( PI * 2.0f ) ); yaw = sinf( ( yaw / (float)yawInterval ) * ( PI * 2.0f ) ); pitch *= normalisedDeployedRatio * normalisedVelocity; roll *= normalisedDeployedRatio * normalisedVelocity; yaw *= normalisedDeployedRatio * normalisedVelocity; static dev_float sfPitchTorqueScale = 2.5f; static dev_float sfRollTorqueScale = 0.4f; static dev_float sfYawTorqueScale = 0.12f; Vector3 torque = VEC3V_TO_VECTOR3( GetTransform().GetA() ) * GetAngInertia().x * ( ( pitch + roll ) / 2.0f ) * sfPitchTorqueScale; torque += VEC3V_TO_VECTOR3( GetTransform().GetB() ) * GetAngInertia().y * ( ( yaw + pitch + roll ) / 3.0f ) * sfRollTorqueScale; torque += VEC3V_TO_VECTOR3( GetTransform().GetC() ) * GetAngInertia().z * ( ( yaw + roll ) / 2.0f ) * sfYawTorqueScale; ApplyInternalTorque( torque ); static dev_float sfPitchForceScale = 7.5f; static dev_float sfRollForceScale = 0.4f; static dev_float sfYawForceScale = 0.4f; Vector3 force = VEC3V_TO_VECTOR3( GetTransform().GetC() ) * GetMass() * ( ( pitch + roll + yaw ) / 3.0f ) * sfPitchForceScale; force += VEC3V_TO_VECTOR3( GetTransform().GetA() ) * GetMass() * ( ( roll + yaw ) / 2.0f ) * sfRollForceScale; force += VEC3V_TO_VECTOR3( GetTransform().GetB() ) * GetMass() * ( ( yaw + pitch ) / 2.0f ) * sfYawForceScale; force *= 1.0f - Abs( GetThrottle() ); ApplyInternalForceCg( force ); if( anchorPosition ) { static dev_float maxDistanceFromInitialPos = 1.0f; static dev_float maxForce = 10.0f; Vector3 distance = ms_formationOffset - VEC3V_TO_VECTOR3( GetTransform().GetPosition() ); static dev_float sfMaxDistanceFromTarget = 0.25f; if( distance.Mag2() > sfMaxDistanceFromTarget ) { ms_formationOffset = VEC3V_TO_VECTOR3( GetTransform().GetPosition() ); return; } distance.z = 0.0f; for( int i = 0; i < 3; i++ ) { distance[ i ] = Clamp( distance[ i ], -maxDistanceFromInitialPos, maxDistanceFromInitialPos ); } distance *= maxForce; distance *= GetMass(); distance *= 1.0f - Abs( GetThrottle() ); ApplyInternalForceCg( distance ); } } void CVehicle::ApplyExplosionEffectEMP(u32 weaponHash) { if(weaponHash == ATSTRINGHASH("WEAPON_EMPLAUNCHER", 0xDB26713A)) { m_bUseIncreasedEMPDuration = true; } m_bExplosionEffectEMP = true; m_uLastHitByExplosionEffectEMP = fwTimer::GetTimeInMilliseconds(); if (m_VehicleAudioEntity) { m_VehicleAudioEntity->TriggerEMPShutdownSound(); } } void CVehicle::ProcessExplosionEffects() { TUNE_GROUP_INT(VEHICLE_EXPLOSION_EFFECTS, TIME_TO_APPLY_EMP_EFFECT, 2500, 0, 100000, 1); TUNE_GROUP_INT(VEHICLE_EXPLOSION_EFFECTS, TIME_TO_APPLY_SLICK_EFFECT, 2000, 0, 100000, 1); TUNE_GROUP_INT(VEHICLE_EXPLOSION_EFFECTS, TIME_TO_APPLY_EMP_EFFECT_INCREASED_DURATION, 10000, 0, 100000, 1); const u32 uCurrentTime = fwTimer::GetTimeInMilliseconds(); u32 uTimeToApplyEMPEffect = TIME_TO_APPLY_EMP_EFFECT; if (NetworkInterface::IsInCopsAndCrooks() && (CPlayerSpecialAbilityManager::ms_CNCAbilityEMPVehicleDuration > 0)) { uTimeToApplyEMPEffect = CPlayerSpecialAbilityManager::ms_CNCAbilityEMPVehicleDuration; } u32 uTimeToApplySlickEffect = TIME_TO_APPLY_SLICK_EFFECT; if (NetworkInterface::IsInCopsAndCrooks() && (CPlayerSpecialAbilityManager::ms_CNCAbilityOilSlickVehicleDuration > 0)) { uTimeToApplySlickEffect = CPlayerSpecialAbilityManager::ms_CNCAbilityOilSlickVehicleDuration; } if(m_bUseIncreasedEMPDuration && (InheritsFromHeli() || InheritsFromPlane())) { uTimeToApplyEMPEffect = TIME_TO_APPLY_EMP_EFFECT_INCREASED_DURATION; } // Update Timers if (uCurrentTime > m_uLastHitByExplosionEffectEMP + uTimeToApplyEMPEffect) { m_bExplosionEffectEMP = false; m_bUseIncreasedEMPDuration = false; } if (uCurrentTime > m_uLastHitByExplosionEffectSlick + uTimeToApplySlickEffect) { m_bExplosionEffectSlick = false; } // Update Effects if (m_bExplosionEffectEMP) { if (!IsNetworkClone()) { SwitchEngineOff(); } m_failNextEngineStartAttempt = true; } } void CVehicle::ProcessNitrousControl() { if (GetDriver() && GetDriver()->IsLocalPlayer()) { if( ( HasNitrousBoost() || HasSideShunt() ) && !GetDriver()->GetSpecialAbility() && !CTheScripts::GetIsInDirectorMode() ) { if( !CNewHud::GetIsOverrideSpecialAbilitySet() ) { CNewHud::SetToggleAbilityBar( true ); } } else if( CNewHud::GetIsOverrideSpecialAbilitySet() && !HasJump() && !HasRocketBoost() && !( pHandling->hFlags & HF_HAS_KERS ) ) { CNewHud::SetToggleAbilityBar( false ); } } // Nitrous boost control if( HasNitrousBoost() && IsEngineOn() ) { static dev_float sfMinNitrousDurationToToggle = 0.5f; bool canToggleRocketBoost = CanToggleRocketBoost() && m_Transmission.GetRemainingNitrousDuration() > sfMinNitrousDurationToToggle; bool bInputActivated = false; // If boost recharged and driver is in the vehicle, check for rocket boost button if( GetDriver() && !(GetDriver()->IsLocalPlayer() && CPhoneMgr::CamGetState() ) ) { CControl* pControl = GetDriver()->GetControlFromPlayer(); if( pControl ) { const ioValue& ioVal = pControl->GetVehicleTransform(); if( ioVal.IsReleased() && !ioVal.IsReleasedAfterHistoryHeldDown( 250 ) ) { bInputActivated = true; } } } if( bInputActivated ) { if (canToggleRocketBoost) { m_nitrousActive = !m_nitrousActive; m_boostToggledTime = fwTimer::GetTimeInMilliseconds(); if( !m_nitrousActive ) { GetVehicleAudioEntity()->SetBoostActive( false ); } } // Trigger the fail sound if we try to activate but can't, or activate on an empty tank if((!m_nitrousActive && !canToggleRocketBoost) || (m_nitrousActive && m_Transmission.GetRemainingNitrousDuration() <= 0.f)) { GetVehicleAudioEntity()->TriggerBoostActivateFail(); } } } else { GetVehicleAudioEntity()->SetBoostActive( false ); } } void CVehicle::FullyChargeNitrous() { m_Transmission.FullyChargeNitrous(this); } void CVehicle::ClearNitrous() { m_Transmission.ClearNitrousDuration(); } void CVehicle::UpdateBladeRpmOnEngineOff() { if( m_numWeaponBlades == 0 || sb_disableWeaponBlades ) { return; } for( int i = 0; i < MAX_NUM_WEAPON_BLADES; i++ ) { m_weaponBlades[ i ].UpdateBladeRpmWhenEngineSetToOff(); } } void CVehicle::ApplySpikeImpacts( phCachedContactIterator& impacts, CEntity* otherEntity ) { static const eHierarchyId modSpikes[ snMaxNumSpikes ] = { VEH_SPIKE_1_MOD, VEH_SPIKE_2_MOD, VEH_SPIKE_3_MOD }; static const eHierarchyId carSpikes[ snMaxNumSpikes ] = { VEH_SPIKE_1_CAR, VEH_SPIKE_2_CAR, VEH_SPIKE_3_CAR }; static const eHierarchyId pedSpikes[ snMaxNumSpikes ] = { VEH_SPIKE_1_PED, VEH_SPIKE_2_PED, VEH_SPIKE_3_PED }; static const eHierarchyId frontSpikes[ snMaxNumSpikes ] = { VEH_FRONT_SPIKE_1, VEH_FRONT_SPIKE_2, VEH_FRONT_SPIKE_3 }; Vec3V myNormal; impacts.GetMyNormal( myNormal ); float myVelocity = -GetVelocity().Dot( VEC3V_TO_VECTOR3( myNormal ) ); Vec3V otherNormal; impacts.GetOtherNormal( otherNormal ); int childBoneIndex = GetBaseModelInfo()->GetFragType()->GetBoneIndexFromID( GetBaseModelInfo()->GetFragType()->GetPhysics( 0 )->GetChild( impacts.GetMyComponent() )->GetBoneID() ); // the mod spikes shouldn't really be used but the only test vehicle we have only has mod spikes and no ped or car ones // so we'll make mod spikes collide with peds and vehicles for( int i = 0; i < snMaxNumSpikes; i++ ) { int spikeBone = GetBoneIndex( modSpikes[ i ] ); if( spikeBone != -1 ) { if( childBoneIndex == spikeBone ) { if( otherEntity && otherEntity->GetIsTypePed() ) { ApplySpikeDamageToPed( static_cast( otherEntity ), otherNormal, myVelocity ); } else if( otherEntity && otherEntity->GetIsTypeVehicle() ) { ApplySpikeDamageToVehicle( static_cast( otherEntity ), impacts.GetOtherComponent(), otherNormal, myVelocity ); } else { impacts.SetFriction( 0.0f ); } return; } } } for( int i = 0; i < snMaxNumSpikes; i++ ) { int spikeBone = GetBoneIndex( carSpikes[ i ] ); if( spikeBone != -1 ) { if( childBoneIndex == spikeBone ) { if( otherEntity && otherEntity->GetIsTypeVehicle() ) { ApplySpikeDamageToVehicle( static_cast( otherEntity ), impacts.GetOtherComponent(), otherNormal, myVelocity ); } else if( otherEntity && otherEntity->GetIsTypePed() ) { impacts.DisableImpact(); } else { impacts.SetFriction( 0.0f ); } return; } } } for( int i = 0; i < snMaxNumSpikes; i++ ) { int spikeBone = GetBoneIndex( pedSpikes[ i ] ); if( spikeBone != -1 ) { if( childBoneIndex == spikeBone ) { if( otherEntity && otherEntity->GetIsTypePed() ) { ApplySpikeDamageToPed( static_cast( otherEntity ), otherNormal, myVelocity ); } else if( otherEntity && otherEntity->GetIsTypeVehicle() ) { impacts.DisableImpact(); } else { impacts.SetFriction( 0.0f ); } return; } } } for( int i = 0; i < snMaxNumSpikes; i++ ) { int spikeBone = GetBoneIndex( frontSpikes[ i ] ); if( spikeBone != -1 ) { if( childBoneIndex == spikeBone ) { if( otherEntity->GetIsTypePed() ) { ApplySpikeDamageToPed( static_cast( otherEntity ), otherNormal, myVelocity ); } else if( otherEntity->GetIsTypeVehicle() ) { ApplySpikeDamageToVehicle( static_cast( otherEntity ), impacts.GetOtherComponent(), otherNormal, myVelocity ); } } } } } void CVehicle::ApplySpikeDamageToPed( CPed* pPed, Vec3V& pedNormal, float vehicleVelocity ) { const float pedVelocity = -pPed->GetVelocity().Dot(VEC3V_TO_VECTOR3(pedNormal)); const float combinedVelocity = Abs(pedVelocity + vehicleVelocity); static dev_float sfVelocityForMaxDamage = 20.0f; static dev_float sfVelocityThreshold = 3.0f; if (combinedVelocity > sfVelocityThreshold) { static dev_float sfMaxPedDamageAmount = 1000.0f; float damageAmount = sfMaxPedDamageAmount * ( Min( 1.0f, combinedVelocity / sfVelocityForMaxDamage ) ); //scale the damage amount by the speed diff and maybe the normal CEventDamage tempDamageEvent( this, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE ); CPedDamageCalculator damageCalculator( this, damageAmount, WEAPONTYPE_RAMMEDBYVEHICLE, 0, false ); damageCalculator.ApplyDamageAndComputeResponse( pPed, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None ); } } void CVehicle::ApplySpikeDamageToVehicle( CVehicle* pOtherVehicle, int otherComponent, Vec3V& normal, float vehicleVelocity ) { // only apply damage to car tyres static dev_float sfMaxTyreDamageAmount = 1000.0f; float damageAmount = sfMaxTyreDamageAmount; //scale the damage amount by the speed diff and maybe the normal for( int i = 0; i < pOtherVehicle->GetNumWheels(); i++ ) { if( CWheel* wheel = pOtherVehicle->GetWheel( i ) ) { if( otherComponent == wheel->GetFragChild( 0 ) ) { float combinedVelocity = -pOtherVehicle->GetVelocity().Dot( VEC3V_TO_VECTOR3( normal ) ); combinedVelocity += vehicleVelocity; wheel->ApplyTyreDamage( this, damageAmount, Vector3( 0.0f, 0.0f, 0.0f ), Vector3( 0.0f, 0.0f, 0.0f ), DAMAGE_TYPE_COLLISION, i ); } } } } static dev_float sfJumpPistonOffset = 0.1f; static dev_float sfJumpImpulseStrength = 12.0f; static dev_float sfImpulseRotationFwd = -0.5f; static dev_float sfStabilisationDelay = 0.6f; static dev_float sfStabilisationDesiredPitchAngle = -0.4f; static dev_float sfStabilisationStrengthMult = -2.0f; static dev_float sfHigherJumpStrenghIncrease = 2.5f; void CVehicle::ProcessCarJump() { if( !HasJump() ) { return; } // Jump requested but not charged yet - Cancel jump if( m_fJumpRechargeTimer != ms_fJumpRechargeTime && m_bDoingJump && m_carJumpState == NOT_JUMPING ) { vehicleDisplayf( "CVehicle::ProcessCarJump - Car Jump: %s, Jump requested but not charged yet - Cancel jump", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?" ); m_bDoingJump = false; return; } bool bResetJump = false; switch( m_carJumpState ) { case NOT_JUMPING: { if( ( ( GetNumContactWheels() > ( GetNumWheels() / 2 ) ) || m_specialFlightModeRatio == 1.0f ) && m_bDoingJump ) { //m_bDoingForwardJump = false; // Rotate the up vector forwards a bit so that the jump also pushes forward a bit Vector3 vImpulseDirection = VEC3V_TO_VECTOR3( GetTransform().GetC() ); Quaternion q; float rotationFwd = sfImpulseRotationFwd; //if( CVehicle::sm_bInDetonationMode ) //{ // TUNE_GROUP_FLOAT( ARENA_MODE, detonationModeJumpFwdRotation, -0.1f, -1.0f, 1.0f, 0.1f ); // rotationFwd = detonationModeJumpFwdRotation; //} q.FromRotation( VEC3V_TO_VECTOR3( GetTransform().GetA() ), rotationFwd ); q.Transform( vImpulseDirection ); float jumpForceScale = pHandling->GetCarHandlingData() ? pHandling->GetCarHandlingData()->m_fJumpForceScale : 1.0f; float fJumpImpulseStrength = sfJumpImpulseStrength * jumpForceScale * GetJumpForceModifier(); if( m_bDoHigherJump ) { fJumpImpulseStrength += sfHigherJumpStrenghIncrease * jumpForceScale; } //if( CVehicle::sm_bInDetonationMode ) //{ // TUNE_GROUP_FLOAT( ARENA_MODE, detonationModeJumpHeightScale, 0.8f, 0.0f, 2.0f, 0.1f ); // fJumpImpulseStrength *= detonationModeJumpHeightScale; //} g_vfxVehicle.TriggerPtFxJump( this ); float pistonOffset = sfJumpPistonOffset; if( GetModelIndex() == MI_CAR_RCBANDITO ) { pistonOffset *= 0.1f; } ApplyImpulse( vImpulseDirection * fJumpImpulseStrength * GetMass(), VEC3V_TO_VECTOR3( GetTransform().GetB() ) * pistonOffset ); // Store the ground normal so that we can measure angles for stabilisation for( int i = 0; i < GetNumWheels(); i++ ) { m_vCarJumpGroundNormal += GetWheel( i )->GetHitNormal(); } m_vCarJumpGroundNormal /= (float)GetNumWheels(); m_carJumpState = JUMPING; m_fCarJumpTimer = sfStabilisationDelay; m_fJumpRechargeTimer = 0.0f; if( m_VehicleAudioEntity ) { m_VehicleAudioEntity->OnJumpingVehicleJumpActivate(); } } break; } case JUMPING: { // Wait until we leave the ground and then apply a timer pause before starting to do in-air stabilisation if( GetNumContactWheels() == 0 ) { m_fCarJumpTimer = rage::Max( m_fCarJumpTimer - fwTimer::GetTimeStep(), 0.0f ); if( m_fCarJumpTimer == 0.0f ) { m_carJumpState = IN_AIR_STABILISATION; } //if( m_bDoingForwardJump ) //{ // m_carJumpState = FORWARD_JUMP; //} } else if( m_fCarJumpTimer != sfStabilisationDelay ) { m_carJumpState = IN_AIR_STABILISATION; } break; } //case FORWARD_JUMP: //case BACKWARDS_JUMP: //{ // if( m_bDoingForwardJump ) // { // static dev_float sfForwardJumpBoostDuration = 0.5f; // static dev_float sfForwardJumpBoostAmount = 20.0f; // mf_BoostAppliedTimer = sfForwardJumpBoostDuration; // Vector3 vDamping = GetVelocity(); // static dev_float sfMaxSpeedForBoost = 50.0f * 50.0f; // static dev_float sfMinSpeedForBoost = 25.0f * 25.0f; // float velocityMag2 = vDamping.Mag2(); // float velocityScale = 1.0f; // if( velocityMag2 > sfMinSpeedForBoost ) // { // velocityScale = 1.0f - Min( 1.0f, ( velocityMag2 - sfMinSpeedForBoost ) / ( sfMaxSpeedForBoost - sfMinSpeedForBoost ) ); // } // if( m_carJumpState == BACKWARDS_JUMP ) // { // velocityScale *= -1.0f; // } // Vector3 vImpulseDirection = VEC3V_TO_VECTOR3( GetTransform().GetB() ); // ApplyImpulseCg( vImpulseDirection * sfForwardJumpBoostAmount * GetMass() * velocityScale ); // m_bDoingForwardJump = false; // m_fCarJumpTimer = sfForwardJumpBoostDuration; // // damp the velocity in the directions we don't want to travel // vDamping = VEC3V_TO_VECTOR3( GetTransform().UnTransform3x3( VECTOR3_TO_VEC3V( vDamping ) ) ); // vDamping.y = 0.0f; // vDamping *= GetMass(); // static dev_float sfVelocityDamping = -0.75f; // vDamping *= sfVelocityDamping; // vDamping = VEC3V_TO_VECTOR3( GetTransform().Transform3x3( VECTOR3_TO_VEC3V( vDamping ) ) ); // ApplyImpulseCg( vDamping ); // } // else if( CVehicle::sm_bInDetonationMode ) // { // // If either landed, or exceeded stabilisation angle, or became attached to something, reset the jump // if( GetNumContactWheels() != 0 || GetIsAttached() || ( InheritsFromAutomobile() && static_cast( this )->IsParachuting() ) ) // { // bResetJump = true; // } // } // else // { // m_fCarJumpTimer = rage::Max( m_fCarJumpTimer - fwTimer::GetTimeStep(), 0.0f ); // if( m_fCarJumpTimer == 0.0f ) // { // m_carJumpState = IN_AIR_STABILISATION; // } // } // // fall through so we also get the in air stabilisation when in this state //} case IN_AIR_STABILISATION: { bool bOverPitched = false; if( m_specialFlightModeRatio == 1.0f ) { if( m_fJumpRechargeTimer == ms_fJumpRechargeTime ) { m_carJumpState = HOVER_CAR_END; } } //else if( m_bDoingForwardJump ) //{ // m_carJumpState = FORWARD_JUMP; //} else// if( !CVehicle::sm_bInDetonationMode ) { Vector3 vCurrentUp = VEC3V_TO_VECTOR3( GetTransform().GetC() ); Vector3 vCurrentRight = VEC3V_TO_VECTOR3( GetTransform().GetA() ); Vector3 vCurrentFwd = VEC3V_TO_VECTOR3( GetTransform().GetB() ); // Determinte the desired up angle for pitch normalisation Vector3 vDesiredUp = m_vCarJumpGroundNormal; // Pitch the ground normal by the desired amount Quaternion qStabilisationRotation; qStabilisationRotation.FromRotation( vCurrentRight, sfStabilisationDesiredPitchAngle ); qStabilisationRotation.Transform( vDesiredUp ); float fCurrentRoll = GetTransform().GetRoll(); // Rotate the ground normal to match car roll so that we're only comparing pitch angles qStabilisationRotation.FromRotation( vCurrentFwd, -fCurrentRoll ); qStabilisationRotation.Transform( vDesiredUp ); float fCarForwardAndGroundForwardAngle = Asinf( Dot( m_vCarJumpGroundNormal, VEC3V_TO_VECTOR3( GetTransform().GetB() ) ) ); bOverPitched = Dot( vCurrentUp, m_vCarJumpGroundNormal ) < Dot( vDesiredUp, m_vCarJumpGroundNormal ) && fCarForwardAndGroundForwardAngle < 0.0f; float fDotProduct = Dot( vCurrentUp, vDesiredUp ); // Get the angle between the current up and the desired up float fAngleBetweenCurrentAndDesiredUp = AcosfSafe( fDotProduct ); // Apply a pitch stabilisation torque Vector3 vStabilisationTorque = vCurrentRight * fAngleBetweenCurrentAndDesiredUp * GetAngInertia().x; vStabilisationTorque -= GetAngVelocity(); ApplyInternalTorque( vStabilisationTorque * sfStabilisationStrengthMult ); } // If either landed, or exceeded stabilisation angle, or became attached to something, reset the jump if( GetNumContactWheels() != 0 || bOverPitched || GetIsAttached() || ( InheritsFromAutomobile() && static_cast( this )->IsParachuting() ) ) { bResetJump = true; } break; } case HOVER_CAR_END: bResetJump = true; break; default: break; } // Check if jump was finished (m_bDoingJump is set to false upon impact) and reset state data if needed if( ( !m_bDoingJump && m_carJumpState != NOT_JUMPING ) || bResetJump ) { if( m_bDoingJump ) { vehicleDisplayf( "CVehicle::ProcessCarJump - Car Jump: %s, jump reset", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "?" ); } m_bDoingJump = false; m_vCarJumpGroundNormal.Zero(); m_carJumpState = NOT_JUMPING; } } bool CVehicle::CanTriggerForwardJump() { //if( !CVehicle::sm_bInDetonationMode ) //{ // return false; //} //if( GetVehicleOrientation( *this ) != CVehicle::VO_Upright && // GetFrameCollisionHistory() && // GetFrameCollisionHistory()->GetCollisionImpulseMagSum() > 0.0f ) //{ // return false; //} return false;// ( m_carJumpState == JUMPING || m_carJumpState == IN_AIR_STABILISATION || ( IsInAir() && m_carJumpState != FORWARD_JUMP && m_carJumpState != BACKWARDS_JUMP ) ); } void CVehicle::UpdateJumpingCarRechargeTimer() { Assert( HasJump() ); const bool wasRecharged = m_fJumpRechargeTimer == ms_fJumpRechargeTime; m_fJumpRechargeTimer = rage::Min( m_fJumpRechargeTimer + fwTimer::GetTimeStep(), ms_fJumpRechargeTime ); //if( CVehicle::sm_bInDetonationMode ) //{ // if( !m_bDoingJump ) // { // m_fJumpRechargeTimer = ms_fJumpRechargeTime; // } //} float fRechargeTimeProgress = ( m_fJumpRechargeTimer / ms_fJumpRechargeTime ); if( GetDriver() && GetDriver()->IsLocalPlayer() && !HasNitrousBoost() ) { if( !wasRecharged && fRechargeTimeProgress == 1.f ) { m_VehicleAudioEntity->OnJumpingVehicleJumpPrimed(); } CNewHud::SetAbilityOverride( fRechargeTimeProgress*100.0f, 100.0f ); } } void CVehicle::TriggerPumpedUpJump( float fHeightScale ) { if( m_pumpedUpJumpState == PUMPED_UP_JUMP_IDLE ) { m_pumpedUpJumpState = PUMPED_UP_JUMP_BEGIN; m_fJumpHeightScale = fHeightScale; } } void CVehicle::ProcessVehiclePumpedUpJump( float fTimeStep ) { switch( m_pumpedUpJumpState ) { default: { break; } case PUMPED_UP_JUMP_IDLE: { break; } case PUMPED_UP_JUMP_COMPRESS_SUSPENSION: { if( GetNumContactWheels() > ( GetNumWheels() * 0.5f ) ) { ActivatePhysics(); // int numWheels = GetNumWheels(); if( InheritsFromAutomobile() ) { // seems that internaly the rate is being clamped to 1 is that a problem? m_fInitialSuspensionRaiseRate = static_cast< CAutomobile* >( this )->GetHydraulicsRate(); TUNE_GROUP_FLOAT( VEHICLE_PUMPED_UP_JUMP, SUSPENSION_COMPRESSION_RATE, 1.0, 0.0f, 10.0f, 0.1f ); static_cast< CAutomobile* >( this )->SetHydraulicsRate( SUSPENSION_COMPRESSION_RATE ); } bool suspensionAtTargetPosition = true; for( int i = 0; i < numWheels; i++ ) { CWheel* pWheel = GetWheel( i ); pWheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION ); TUNE_GROUP_FLOAT( VEHICLE_PUMPED_UP_JUMP, SUSPENSION_COMPRESSION_SCALE, 0.95f, 0.0f, 1.0f, 0.1f ); pWheel->SetSuspensionTargetRaiseAmount( SUSPENSION_COMPRESSION_SCALE * -pWheel->GetStaticDelta(), 1.0f ); if( pWheel->GetSuspensionRaiseAmount() > pWheel->GetSuspensionTargetRaiseAmount() * 0.9f ) { suspensionAtTargetPosition = false; } } if( suspensionAtTargetPosition ) { m_pumpedUpJumpState = PUMPED_UP_JUMP_EXTEND_SUSPENSION; } } else { m_pumpedUpJumpState = PUMPED_UP_JUMP_IDLE; } break; } case PUMPED_UP_JUMP_EXTEND_SUSPENSION: { int numWheels = GetNumWheels(); if( InheritsFromAutomobile() ) { TUNE_GROUP_FLOAT( VEHICLE_PUMPED_UP_JUMP, SUSPENSION_EXTENSION_RATE, 1.0f, 0.0f, 10.0f, 0.1f ); static_cast( this )->SetHydraulicsRate( SUSPENSION_EXTENSION_RATE ); } bool suspensionAtTargetPosition = true; for( int i = 0; i < numWheels; i++ ) { CWheel* pWheel = GetWheel( i ); pWheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION ); TUNE_GROUP_FLOAT( VEHICLE_PUMPED_UP_JUMP, SUSPENSION_RAISE_AMOUNT_SCALE, 0.5f, 0.0f, 1.0f, 0.1f ); pWheel->SetSuspensionTargetRaiseAmount( ( pWheel->GetSuspensionLength() - pWheel->GetStaticDelta() ) * SUSPENSION_RAISE_AMOUNT_SCALE, 1.0f ); } if( suspensionAtTargetPosition ) { m_pumpedUpJumpState = PUMPED_UP_JUMP_ACCELERATE; static dev_float sfDefaultTimeToReachLaunchVelocity = 0.25f; m_fPumpedUpJumpTimer = sfDefaultTimeToReachLaunchVelocity; } break; } case PUMPED_UP_JUMP_ACCELERATE: { float downwardsAccelerationForce = GetGravityForWheellIntegrator(); static dev_float sfTargetJumpHeight = 1.2f; float upVelocity = Dot( VECTOR3_TO_VEC3V( GetVelocity() ), GetTransform().GetC() ).Getf(); float forwardsVelocity = GetVelocity().Dot( VEC3V_TO_VECTOR3( GetTransform().GetB() ) ); float averageDownforce = 0.0f; int numWheels = GetNumWheels(); for( int i = 0; i < numWheels; i++ ) { CWheel* pWheel = GetWheel( i ); averageDownforce += pWheel->m_fDownforceMult; } averageDownforce /= numWheels; averageDownforce = Abs( forwardsVelocity ) * averageDownforce; downwardsAccelerationForce += averageDownforce; float targetVelocity = sqrt( 2.0f * downwardsAccelerationForce * sfTargetJumpHeight * m_fJumpHeightScale ); float acceleration = targetVelocity - upVelocity; acceleration /= Max( fTimeStep, m_fPumpedUpJumpTimer ); float force = GetMass() * acceleration; ApplyInternalForceCg( VEC3V_TO_VECTOR3( ScalarV( force ) * GetTransform().GetC() ) ); m_fPumpedUpJumpTimer -= fTimeStep; if( m_fPumpedUpJumpTimer <= 0.0f && GetNumContactWheels() == 0.0f ) { for( int i = 0; i < GetNumWheels(); i++ ) { CWheel* pWheel = GetWheel( i ); if( pWheel->GetSuspensionRaiseAmount() == pWheel->GetSuspensionTargetRaiseAmount() ) { m_pumpedUpJumpState = PUMPED_UP_JUMP_END; m_fPumpedUpJumpTimer = 0.0f; } } } break; } case PUMPED_UP_JUMP_END: { int numWheels = GetNumWheels(); for( int i = 0; i < numWheels; i++ ) { CWheel* pWheel = GetWheel( i ); pWheel->GetConfigFlags().SetFlag( WCF_UPDATE_SUSPENSION ); pWheel->SetSuspensionTargetRaiseAmount( 0.0f, 1.0f ); } if( InheritsFromAutomobile() ) { static_cast( this )->SetHydraulicsRate( m_fInitialSuspensionRaiseRate ); } m_pumpedUpJumpState = PUMPED_UP_JUMP_IDLE; } } } void CVehicle::ProcessTombstone() { if( GetDriver() && GetDriver()->IsLocalPlayer() && GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_HAS_TOMBSTONE ) ) { bool bInputActivated = false; CControl* pControl = GetDriver()->GetControlFromPlayer(); if( pControl ) { static u32 uTombstoneTimeToHoldMS = 250; if(pControl->GetVehicleTombstoneDeploy().HistoryHeldDown(uTombstoneTimeToHoldMS)) { bInputActivated = true; } } if( bInputActivated ) { DetachTombstone(); } } } void CVehicle::ApplyWeaponBladeWheelImpacts( CWheel* wheel, int hitComponent ) { for( int i = 0; i < m_numWeaponBlades; i++ ) { m_weaponBlades[ i ].ApplyWheelImpact( this, wheel, hitComponent ); } } void CVehicle::DetachTombstone() { if(m_detachedTombstone) { return; } int tombstoneBoneIndex = GetBoneIndex( VEH_TOMBSTONE ); if( tombstoneBoneIndex > -1 ) { int tombstoneGroupIndex = GetVehicleFragInst()->GetGroupFromBoneIndex( tombstoneBoneIndex ); if( tombstoneGroupIndex > -1 && !GetVehicleFragInst()->GetGroupBroken( tombstoneGroupIndex ) ) { // Break off tombstone fragInst* pBrokenOffInst = GetVehicleFragInst()->BreakOffAboveGroup( tombstoneGroupIndex ); CEntity* pBrokenOffEntity = CPhysics::GetEntityFromInst( pBrokenOffInst ); // Trigger detach audio GetVehicleAudioEntity()->TriggerSimpleSoundFromSoundset(GetVehicleAudioEntity()->IsFocusVehicle() ? ATSTRINGHASH("DLC_AWXM2018_Mod_Vehicle_Player_Sounds", 0x1D430286) : ATSTRINGHASH("DLC_AWXM2018_Mod_Vehicle_NPC_Sounds", 0x96866E58), ATSTRINGHASH("tombstone_detach", 0xE18C2761), true); // Set mass on tombstone TUNE_GROUP_BOOL( TOMBSTONE_MOD, SET_TOMBSTONE_MASS, true ); if( pBrokenOffInst && pBrokenOffInst->GetCacheEntry() && pBrokenOffInst->GetCacheEntry()->GetBound() && pBrokenOffInst->GetCacheEntry()->GetPhysArchetype() && SET_TOMBSTONE_MASS ) { phBoundComposite* compBound = pBrokenOffInst->GetCacheEntry()->GetBound(); compBound->CalcCenterOfBound(); compBound->CalculateCompositeExtents(); if( pBrokenOffInst->IsInLevel() ) { if( compBound->HasBVHStructure() ) { PHLEVEL->RebuildCompositeBvh( pBrokenOffInst->GetLevelIndex() ); } if( phCollider* collider = PHSIM->GetCollider( pBrokenOffInst ) ) { collider->SetColliderMatrixFromInstance(); } } else { compBound->UpdateBvh( true ); } TUNE_GROUP_FLOAT( TOMBSTONE_MOD, TOMBSTONE_MASS, 5000.0f, 1.0f, 10000.0f, 1.0f ); pBrokenOffInst->GetCacheEntry()->GetPhysArchetype()->SetMass( TOMBSTONE_MASS ); // If necessary, propagate our changes into the current collider if( pBrokenOffInst->IsInLevel() ) { if( phCollider* collider = PHSIM->GetCollider( pBrokenOffInst ) ) { // Load the mass and inertial from the archetype into the collider collider->InitInertia(); } } } // Apply forces on tombstone if( pBrokenOffEntity && pBrokenOffEntity->GetIsPhysical() && pBrokenOffInst && pBrokenOffInst->GetCacheEntry() ) { if(!GetNetworkObject() || !GetNetworkObject()->IsClone()) { CPhysical* pBrokenOffPhysical = static_cast< CPhysical* >( pBrokenOffEntity ); phCollider* pBrokenOffCollider = pBrokenOffEntity->GetCollider(); float fMassScale = pBrokenOffCollider ? pBrokenOffCollider->GetMass() : pBrokenOffPhysical->GetMass(); TUNE_GROUP_FLOAT( TOMBSTONE_MOD, IMPULSE_Y_MAG, 10.0f, 0.0f, 1000.0f, 0.1f ); TUNE_GROUP_FLOAT( TOMBSTONE_MOD, IMPULSE_Z_MAG, 3.0f, 0.0f, 1000.0f, 0.1f ); TUNE_GROUP_FLOAT( TOMBSTONE_MOD, ANG_IMPULSE_X_MAG, 10.0f, -1000.0f, 1000.0f, 0.1f ); // Arc trajectory pBrokenOffPhysical->ApplyImpulseCg( VEC3V_TO_VECTOR3( GetMatrix().b() ) * -1.0f * IMPULSE_Y_MAG * fMassScale ); pBrokenOffPhysical->ApplyImpulseCg( VEC3V_TO_VECTOR3( GetMatrix().c() ) * IMPULSE_Z_MAG * fMassScale ); // Spin pBrokenOffPhysical->ApplyAngImpulse( VEC3V_TO_VECTOR3( GetMatrix().a() ) * ANG_IMPULSE_X_MAG * fMassScale ); // Counter impulse TUNE_GROUP_FLOAT( TOMBSTONE_MOD, COUNTER_IMPULSE_Y_MAG, 1.0f, -1000.0f, 1000.0f, 0.1f ); TUNE_GROUP_FLOAT( TOMBSTONE_MOD, COUNTER_ANG_IMPULSE_X_MAG, -0.8f, -1000.0f, 1000.0f, 0.1f ); ApplyImpulseCg( VEC3V_TO_VECTOR3( GetMatrix().b() ) * COUNTER_IMPULSE_Y_MAG * GetMass() ); ApplyAngImpulse( VEC3V_TO_VECTOR3( GetMatrix().a() ) * COUNTER_ANG_IMPULSE_X_MAG * GetMass() ); } if(NetworkInterface::IsGameInProgress() && pBrokenOffEntity && pBrokenOffEntity->GetIsTypeObject()) { m_detachedTombstone = SafeCast(CObject, pBrokenOffEntity); m_detachedTombstone->m_nObjectFlags.bIsNetworkedFragment = true; if(GetNetworkObject()) { if(GetNetworkObject()->IsClone()) { CNetObjObject::AssignVehicleFrags(*m_detachedTombstone); } else { CNetObjVehicle* netVehicle = SafeCast(CNetObjVehicle, GetNetworkObject()); netVehicle->SetDetachedTombStone(true); NetworkInterface::RegisterObject(m_detachedTombstone.Get(), 0, 0, true); if(m_detachedTombstone->GetNetworkObject()) { CNetObjObject* netStoneObj = SafeCast(CNetObjObject, m_detachedTombstone->GetNetworkObject()); netStoneObj->BelongToVehicleFragment(GetNetworkObject()->GetObjectID()); } } } } } } } } void CVehicle::HideTombstone( bool hide ) { if( m_detachedTombstone ) { return; } int tombstoneBoneIndex = GetBoneIndex( VEH_TOMBSTONE ); if( tombstoneBoneIndex > -1 ) { int tombstoneGroupIndex = GetVehicleFragInst()->GetGroupFromBoneIndex( tombstoneBoneIndex ); if( tombstoneGroupIndex > -1 ) { if( hide ) { if( !GetVehicleFragInst()->GetGroupBroken( tombstoneGroupIndex ) ) { // delete the tombstone GetVehicleFragInst()->DeleteAboveGroup( tombstoneGroupIndex ); } } else { // restore the tombstone GetVehicleFragInst()->RestoreAboveGroup( tombstoneGroupIndex ); } } } } bool CVehicle::HasTombstone() const { int tombstoneBoneIndex = GetBoneIndex( VEH_TOMBSTONE ); if( tombstoneBoneIndex > -1 ) { int tombstoneGroupIndex = GetVehicleFragInst()->GetGroupFromBoneIndex( tombstoneBoneIndex ); if( tombstoneGroupIndex > -1 && !GetVehicleFragInst()->GetGroupBroken( tombstoneGroupIndex ) ) { return true; } } return false; } bool CVehicle::IsWeaponBladeImpact( phCachedContactIterator& impacts ) { for( int i = 0; i < m_numWeaponBlades; i++ ) { if( impacts.GetMyComponent() == m_weaponBlades[ i ].GetFragChild() ) { return true; } } return false; } #if __BANK void CVehicle::DrawDownforceDebug() { if( CVehicle::ms_bVehicleDownforceDebug ) { char debugText[ 256 ]; static const float fScreenWidth = 1.0f; static const float fScreenHeight = 1.0f; Vector2 vecStart( 0.5f * fScreenWidth, 0.95f * fScreenHeight ); int numWheelsOnRow = (int)( ( (float)m_nNumWheels / 2.0f ) + 0.5f ); int numRowsOfWheels = (int)( ( (float)m_nNumWheels / (float)numWheelsOnRow ) + 0.5f ); float fTotalDownforce = CWheel::ms_fDownforceMult; if( pHandling->m_fDownforceModifier > 0.0f ) { if( pHandling->GetCarHandlingData() && pHandling->GetCarHandlingData()->aFlags & CF_USE_DOWNFORCE_BIAS ) { fTotalDownforce = CWheel::ms_fDownforceMult * pHandling->m_fDownforceModifier * ( ( GetDownforceModifierFront() + GetDownforceModifierRear() ) * 0.5f ); } else if( GetVariationInstance().HasSpoiler() && !GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_SPOILER_MOD_DOESNT_INCREASE_GRIP ) ) { fTotalDownforce = ( CWheel::ms_fDownforceMultSpoiler ) * pHandling->m_fDownforceModifier; } else { fTotalDownforce *= pHandling->m_fDownforceModifier; } } else { if( GetVariationInstance().HasSpoiler() && !GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_SPOILER_MOD_DOESNT_INCREASE_GRIP ) ) { fTotalDownforce = CWheel::ms_fDownforceMultSpoiler; } } Vector2 vTextRenderPos( vecStart.x, vecStart.y ); vTextRenderPos.y -= numRowsOfWheels * 0.12f; static dev_float sfTextLength = 0.05f; Vector2 textPos = vTextRenderPos; textPos.x -= sfTextLength; sprintf( debugText, "Total downforce %.2f", fTotalDownforce ); grcDebugDraw::Text( textPos, Color_white, debugText, true, 1.0f ); vTextRenderPos.y += 0.04f; for( int i = 0; i < m_nNumWheels; i++ ) { float downforceBias = 1.0f; textPos = vTextRenderPos; if( numWheelsOnRow == 1 || ( m_nNumWheels == 3 && m_ppWheels[ i ]->GetOppositeWheelIndex() < 0 ) ) { textPos.x -= sfTextLength; } else if( i % numWheelsOnRow == 0 ) { textPos.x -= sfTextLength * 2.1f; } else { textPos.x += sfTextLength * 0.1f; } if( pHandling->GetCarHandlingData() && pHandling->GetCarHandlingData()->aFlags & CF_USE_DOWNFORCE_BIAS ) { float frontRearSelector = m_ppWheels[ i ]->GetFrontRearSelector(); downforceBias = Selectf( frontRearSelector, GetDownforceModifierFront(), GetDownforceModifierRear() ); } sprintf( debugText, "Bias modifier %.2f", downforceBias ); grcDebugDraw::Text( textPos, Color_white, debugText, true, 1.0f ); textPos.y += 0.02f; float currentDownforce = m_ppWheels[ i ]->m_fDownforceMult; sprintf( debugText, "Current modifier %.2f", currentDownforce ); grcDebugDraw::Text( textPos, Color_white, debugText, true, 1.0f ); if( numWheelsOnRow == 1 || ( m_nNumWheels == 3 && m_ppWheels[ i ]->GetOppositeWheelIndex() < i ) || ( m_nNumWheels != 3 && i % numWheelsOnRow == numWheelsOnRow - 1 ) ) { vTextRenderPos.y += 0.06f; } } } } void CVehicle::DrawQADebug() { if( CVehicle::ms_bVehicleQADebug ) { char debugText[ 256 ]; static const float fScreenWidth = 1.0f; static const float fScreenHeight = 1.0f; Vector2 vecStart( 0.05f * fScreenWidth, 0.05f * fScreenHeight ); float fSpeed = Abs( Dot( GetTransform().GetForward(), VECTOR3_TO_VEC3V( GetVelocity() ) ).Getf() ); float fSpeedInMph = fSpeed * 2.23694f; sprintf( debugText, "Speed: %.2f Mph", fSpeedInMph ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; int currentGear = m_Transmission.GetGear(); sprintf( debugText, "Current Gear: %d", currentGear ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; float fEnginePowerIncreasePercentage = ms_fEngineVarianceMaxModifier * GetVariationInstance().GetEngineModifier(); sprintf( debugText, "Engine Power Increase %.2f%%", fEnginePowerIncreasePercentage ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; float fBrakeForceIncreasePercentage = ms_fBrakeVarianceMaxModifier * GetVariationInstance().GetBrakesModifier(); sprintf( debugText, "Brake Force Increase %.2f%%", fBrakeForceIncreasePercentage ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; float fArmourDamageMultiplier = GetVariationInstance().GetArmourDamageMultiplier(); sprintf( debugText, "Armour Damage Multiplier %.2f", fArmourDamageMultiplier ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; float fTurboPowerIncrease = GetVariationInstance().IsToggleModOn( VMT_TURBO ) ? CTransmission::GetTurboPowerModifier(this) : 0.0f; if( GetVehicleModelInfo()->GetVehicleFlag( CVehicleModelInfoFlags::FLAG_IS_ELECTRIC ) ) { fTurboPowerIncrease = 0.0f; } sprintf( debugText, "Turbo Power Increase %.2f%%", fTurboPowerIncrease ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; int numGears = m_Transmission.GetNumGears(); sprintf( debugText, "Number of Gears: %d", numGears ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; float fMass = GetMass(); sprintf( debugText, "Mass %.2f", fMass ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; Vector3 vecLinearCDamping = GetVehicleFragInst()->GetTypePhysics()->GetDampingConstant( phArchetypeDamp::LINEAR_C ); Vector3 vecLinearVDamping = GetVehicleFragInst()->GetTypePhysics()->GetDampingConstant( phArchetypeDamp::LINEAR_V ); Vector3 vecLinearV2Damping = GetVehicleFragInst()->GetTypePhysics()->GetDampingConstant( phArchetypeDamp::LINEAR_V2 ); sprintf( debugText, "Constant Damping %.2f", vecLinearCDamping.y ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; sprintf( debugText, "Velocity Damping %.2f", vecLinearVDamping.y ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; sprintf( debugText, "Velocity Squared Damping %.4f", vecLinearV2Damping.y ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; #if __BANK if(CVehicle::ms_bDebugIgnoreHoldGearWithWheelspinFlag) { sprintf( debugText, " Ignore CF_HOLD_GEAR_WITH_WHEELSPIN flag " ); grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; } #endif // Tire Grip for( int i = 0; i < m_nNumWheels; i++ ) { if( m_bDriftTyres ) { sprintf( debugText, "Low grip tires. Wheel %d: Traction Loss %.2f", i, m_ppWheels[ i ]->m_fLastCalculatedTractionLoss ); } else { sprintf( debugText, "Wheel %d: Traction Loss %.2f", i, m_ppWheels[ i ]->m_fLastCalculatedTractionLoss ); } bool bHasHoldGearWheelSpinFlag = (m_ppWheels[ i ]->GetHandlingData() && m_ppWheels[ i ]->GetHandlingData()->GetCarHandlingData() && (m_ppWheels[ i ]->GetHandlingData()->GetCarHandlingData()->aFlags & CF_HOLD_GEAR_WITH_WHEELSPIN )) ? true : false; #if __BANK bHasHoldGearWheelSpinFlag &= !CVehicle::ms_bDebugIgnoreHoldGearWithWheelspinFlag; #endif bool bHasHardRevLimitFlag = (m_ppWheels[ i ]->GetHandlingData() && m_ppWheels[ i ]->GetHandlingData()->GetCarHandlingData() && (m_ppWheels[ i ]->GetHandlingData()->GetCarHandlingData()->aFlags & CF_HARD_REV_LIMIT )) ? true : false; if( bHasHoldGearWheelSpinFlag ) { if(m_bDriftTyres) { sprintf( debugText, "%s CF_HOLD_GEAR_WITH_WHEELSPIN (ignored)", debugText ); } else { sprintf( debugText, "%s CF_HOLD_GEAR_WITH_WHEELSPIN", debugText ); } } if(bHasHardRevLimitFlag) { sprintf( debugText, "%s CF_HARD_REV_LIMIT", debugText ); } grcDebugDraw::Text( vecStart, Color_white, debugText, true, 1.0f ); vecStart.y += 0.03f; } } } const char* CVehicle::GetVehicleTypeString(VehicleType vehicleType) const { static const char* typeStrings[] = { "VEHICLE_TYPE_NONE", "VEHICLE_TYPE_CAR", "VEHICLE_TYPE_PLANE", "VEHICLE_TYPE_TRAILER", "VEHICLE_TYPE_QUADBIKE", "VEHICLE_TYPE_DRAFT", "VEHICLE_TYPE_SUBMARINECAR", "VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE", "VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE", "VEHICLE_TYPE_HELI", "VEHICLE_TYPE_BLIMP", "VEHICLE_TYPE_AUTOGYRO", "VEHICLE_TYPE_BIKE", "VEHICLE_TYPE_BICYCLE", "VEHICLE_TYPE_BOAT", "VEHICLE_TYPE_TRAIN", "VEHICLE_TYPE_SUBMARINE"}; //Add +1 to include VEHICLE_TYPE_NONE which is -1 CompileTimeAssert(NELEM(typeStrings) == VehicleType::VEHICLE_TYPE_NUM + 1); return typeStrings[vehicleType]; } #endif // #if __BANK bool CWeaponBlade::ms_bDisableRetractingWeaponBlades = false; void CWeaponBlade::Init( int modBone, int fastBone, int fragChild, eRotationAxis rotationAxis, float maxRotationSpeed, float mass ) { m_modBone = modBone; m_fastBone = fastBone; m_fragChild = fragChild; m_rotationAxis = rotationAxis; m_maxRPM = maxRotationSpeed; m_currentRPM = 0.0f; m_mass = mass; } void CWeaponBlade::Update( CVehicle* parentVehicle ) { if( m_modBone != -1 ) { static dev_float sf_accelerationRate = 5.0f; static dev_float sf_deccelerationRate = 10.0f; float accelerationRate = sf_deccelerationRate; float targetRpm = 0.0f; if( parentVehicle->m_nVehicleFlags.bEngineOn ) { accelerationRate = sf_accelerationRate; targetRpm = m_maxRPM; } // spin up rate should be determined by engine power and blade mass // target rpm should maybe always be 1 we could look at having blades spin at different speeds m_currentRPM += ( ( ( targetRpm - m_currentRPM ) * fwTimer::GetTimeStep() ) * accelerationRate ) / m_mass; m_bladeAngle += m_currentRPM * fwTimer::GetTimeStep(); parentVehicle->SetBoneRotation( m_fastBone != -1 ? m_fastBone : m_modBone, m_rotationAxis, m_bladeAngle ); if( m_bladeAngle > PI * 2.0f ) { m_bladeAngle -= PI * 4.0f; } if( m_fastBone != -1 && parentVehicle->InheritsFromAutomobile() && !ms_bDisableRetractingWeaponBlades ) { static dev_float fractionOfMaxRpmToUse = 0.5f; float targetOffsetRatio = 1.0f; if( parentVehicle->m_nVehicleFlags.bEngineOn ) { targetOffsetRatio = Min( 1.0f, Abs( m_currentRPM / ( m_maxRPM * fractionOfMaxRpmToUse ) ) ); } else { float maxRPM = m_maxRPM * fractionOfMaxRpmToUse; targetOffsetRatio = Clamp( ( m_currentRPM - ( maxRPM ) ) / ( m_maxRPM - maxRPM ), 0.0f, 1.0f ); if( Abs( targetOffsetRatio ) < 0.1f ) { m_currentRPM = 0.0f; } } const crBoneData* pBoneData = parentVehicle->GetSkeletonData().GetBoneData( m_fastBone ); if( pBoneData ) { Matrix34& rLocalMtx = RC_MATRIX34( parentVehicle->GetSkeleton()->GetLocalMtx( m_fastBone ) ); rLocalMtx.d.x = pBoneData->GetDefaultTranslation().GetXf() * targetOffsetRatio; } } } } bool CWeaponBlade::ApplyImpacts( CVehicle* parentVehicle, CEntity* otherEntity, Vec3V myNormal, Vec3V myImpactPosition, Vec3V otherImpactPosition, int bladeIndex, int hitComponent, int otherComponent ) { bool result = false; if( Abs( m_currentRPM ) < 0.001f ) { return false; } if( hitComponent == m_fragChild && otherEntity && otherEntity->GetCollider() ) { float bladeForce = m_mass * m_currentRPM * PI; if( parentVehicle->InheritsFromBike() ) { static dev_float sfBikeBladeForceScale = 0.25f; bladeForce *= sfBikeBladeForceScale; } bool bApplyImpulse = true; static dev_float sf_appliedForceScale = 10.0f; Vec3V otherNormal = Negate( myNormal ); // calculate the impact position offset from the bone that controls it myImpactPosition = parentVehicle->GetTransform().UnTransform( myImpactPosition ); myImpactPosition -= parentVehicle->GetSkeleton()->GetObjectMtx( m_fastBone != -1 ? m_fastBone : m_modBone ).GetCol3(); myImpactPosition = parentVehicle->GetTransform().Transform3x3( myImpactPosition ); myImpactPosition = Normalize( myImpactPosition ); Vec3V impulse = myImpactPosition; if( m_rotationAxis == ROT_AXIS_LOCAL_X ) { impulse = Cross( parentVehicle->GetTransform().GetRight(), impulse ); } else { impulse = Cross( parentVehicle->GetTransform().GetUp(), impulse ); } // dot the velocity * mass with the impulse phCollider* otherCollider = otherEntity->GetCollider(); Vec3V contactPointVelocity = otherCollider->GetVelocity() * ScalarV( otherCollider->GetMass() ); // cross the angular velocity with the local contact point contactPointVelocity += otherEntity->GetTransform().Transform3x3( Cross( otherCollider->GetAngVelocity() * otherCollider->GetAngInertia(), otherEntity->GetTransform().UnTransform( otherImpactPosition ) ) ); // rotate that into world space and dot with velocity * angular inertia ScalarV relativeVelocityMag = Dot( contactPointVelocity, impulse ); contactPointVelocity = impulse * relativeVelocityMag; // we can then compare that to the blade force + the velocity of the parent vehicle at contact point impulse *= ScalarV( bladeForce * sf_appliedForceScale ); contactPointVelocity = SelectFT( IsLessThan( contactPointVelocity * impulse, Vec3V( 0.0f, 0.0f, 0.0f ) ), contactPointVelocity, Vec3V( 0.0f, 0.0f, 0.0f ) ); Vec3V relativeVelocity = contactPointVelocity - impulse; relativeVelocity = SelectFT( IsGreaterThan( relativeVelocity * contactPointVelocity, Vec3V( 0.0f, 0.0f, 0.0f ) ), contactPointVelocity, relativeVelocity ); Vec3V changeInImpulse = SelectFT( IsLessThan( relativeVelocity, Vec3V( 0.0f, 0.0f, 0.0f ) ), SelectFT( IsLessThan( relativeVelocity, impulse ), impulse, relativeVelocity ), SelectFT( IsLessThan( relativeVelocity, impulse ), relativeVelocity, impulse ) ); impulse -= changeInImpulse; float bladeAcceleration = Max( 0.0f, Mag( impulse ).Getf() / m_mass ); // reduce the blade force by the vehicle inertia if they are going the same direction so that we don't accelerate the vehicle faster than the blade // the remaining blade force should be reduced by if( otherEntity->GetIsTypeVehicle() ) { CVehicle* otherVehicle = static_cast( otherEntity ); if( otherVehicle->GetHitByWeaponBlade() ) { impulse *= ScalarV( 0.0f ); } else { TUNE_GROUP_BOOL( ARENA_MODE, applyWeaponBladeDamage, true ); if( applyWeaponBladeDamage && NetworkInterface::IsFriendlyFireAllowed( otherVehicle, parentVehicle->GetDriver() ) ) { if( !NetworkInterface::IsGameInProgress() || !otherVehicle->IsNetworkClone() ) { static dev_float sfWeaponBladeDamageScale = 6.0f; otherVehicle->GetVehicleDamage()->ApplyDamage( parentVehicle, DAMAGE_TYPE_COLLISION, WEAPONTYPE_RAMMEDBYVEHICLE, bladeForce * sf_appliedForceScale * sfWeaponBladeDamageScale, VEC3V_TO_VECTOR3( otherImpactPosition ), VEC3V_TO_VECTOR3( otherNormal ), VEC3V_TO_VECTOR3( Normalize( impulse ) ), otherComponent ); } } else { vehicleDebugf3( "CWeaponBlade::ApplyImpacts: Not applying blade damage because: applyWeaponBladeDamage: %d, IsFriendlyFireAllowed: %d", (int)applyWeaponBladeDamage, (int)NetworkInterface::IsFriendlyFireAllowed( otherVehicle, parentVehicle->GetDriver() ) ); } otherVehicle->SetHitByWeaponBlade( true ); } } else if( otherEntity && otherEntity->GetIsTypePed() ) { if( !static_cast( otherEntity )->IsNetworkClone() && ( !parentVehicle->InheritsFromBike() || parentVehicle->GetDriver() ) ) { static dev_float sfMaxPedDamageAmount = 100.0f; static dev_float sfMinDamageForResponse = 100.0f; float damageAmount = sfMaxPedDamageAmount * Abs( m_currentRPM ); if( damageAmount > sfMinDamageForResponse && !static_cast( otherEntity )->GetIsDeadOrDying() ) { //scale the damage amount by the speed diff and maybe the normal CEventDamage tempDamageEvent( parentVehicle, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE ); CPedDamageCalculator damageCalculator( parentVehicle, damageAmount, WEAPONTYPE_RAMMEDBYVEHICLE, 0, false ); damageCalculator.ApplyDamageAndComputeResponse( static_cast( otherEntity ), tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None ); } static dev_float sfPedImpulseScale = 0.025f; static dev_float sfPedBladeAccelerationScale = 0.001f; impulse *= ScalarV( sfPedImpulseScale ); bladeAcceleration *= sfPedBladeAccelerationScale; } bApplyImpulse = false; } if( otherEntity && otherEntity->GetIsTypeObject() && static_cast( otherEntity )->GetNetworkObject() && static_cast< CNetObjObject* >( static_cast( otherEntity )->GetNetworkObject() )->GetFragParentVehicleId() == NETWORK_INVALID_OBJECT_ID ) { bApplyImpulse = false; } if( bApplyImpulse ) { static dev_float maxAcceleration = 10.0f; float impulseMag = Mag( impulse ).Getf(); float impulseMax = maxAcceleration * otherCollider->GetMass(); if( impulseMag > impulseMax ) { impulse *= ScalarV( impulseMax / impulseMag ); } if( !CVehicle::sm_bDisableWeaponBladeForces ) { otherCollider->ApplyImpulse( impulse.GetIntrin128(), otherImpactPosition.GetIntrin128() ); } } bladeAcceleration = Min( bladeAcceleration, Abs( m_currentRPM ) ); m_currentRPM -= bladeAcceleration * Sign( m_currentRPM ); parentVehicle->GetVehicleAudioEntity()->NotifyWeaponBladeImpact(bladeIndex, otherEntity->GetIsTypePed()); result = true; } return result; } void CWeaponBlade::ApplyWheelImpact( CVehicle* parentVehicle, CWheel* wheel, int hitComponent ) { if( Abs( m_currentRPM ) > 0.001f ) { if( hitComponent == m_fragChild ) { static dev_float sfMaxTyreDamageAmount = 100.0f; float damageAmount = Abs( sfMaxTyreDamageAmount * m_currentRPM ); wheel->ApplyTyreDamage( parentVehicle, damageAmount, Vector3( 0.0f, 0.0f, 0.0f ), Vector3( 0.0f, 0.0f, 0.0f ), DAMAGE_TYPE_COLLISION, -1 ); } } } void CWeaponBlade::UpdateBladeRpmWhenEngineSetToOff() { static dev_float fractionOfMaxRpmToUse = 0.5f; float engineRPMFraction = m_maxRPM * fractionOfMaxRpmToUse; // if the blade rpm is greater than the fraction we use to move the blades out we need // to make sure the rpm is at the correct rpm for the position it is in to go back in nicely m_currentRPM = Clamp( m_currentRPM + engineRPMFraction, -(Abs( m_maxRPM ) ), Abs( m_maxRPM ) ); }