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

35520 lines
1.2 MiB

// Title : Ped.cpp
// Author : Gordon Speirs
// Started : 02/09/99
// Class header
#include "Peds/Ped.h"
// Rage headers
#include "cloth/charactercloth.h"
#include "creature/creature.h"
#include "creature/componentextradofs.h"
#include "crmotiontree/requestik.h"
#include "debug/DebugVehicleInteraction.h"
#include "fragmentnm/messageparams.h"
#include "grmodel/matrixset.h"
#include "physics/constraintcylindrical.h"
#include "physics/constraintrotation.h"
#include "profile/timebars.h"
#include "script/thread.h"
#include "security/obfuscatedtypes.h"
#include "system/param.h"
#include "system/memory.h"
#include "fragmentnm/manager.h"
#include "fragmentnm/nm_channel.h "
#include "vectormath/classes.h"
#include <art/art.h>
// Framework headers
#include "entity/altskeletonextension.h"
#include "entity/camerarelativeextension.h"
#include "fwanimation/animdirector.h"
#include "fwanimation/animmanager.h"
#include "fwanimation/directorcomponentmotiontree.h"
#include "fwanimation/directorcomponentragdoll.h"
#include "fwanimation/directorcomponentsyncedscene.h"
#include "fwanimation/directorcomponentfacialrig.h"
#include "fwanimation/directorcomponentmove.h"
#include "fwanimation/expressionsets.h"
#include "fwanimation/facialclipsetgroups.h"
#include "fwdecorator/decoratorExtension.h"
#include "fwmaths/angle.h"
#include "fwmaths/vector.h"
#include "fwnet/netblender.h"
#include "fwpheffects/ropemanager.h"
#include "fwscene/stores/expressionsdictionarystore.h"
#include "fwscene/stores/framefilterdictionarystore.h"
#include "fwscript/scriptguid.h"
#include "fwsys/metadatastore.h"
#include "grcore/debugdraw.h"
#include "ai/navmesh/datatypes.h"
// Game headers
#include "ai/ambient/AmbientModelSet.h"
#include "ai/stats.h"
#include "Animation/AnimBones.h"
#include "Animation/AnimDefines.h"
#include "Animation/CreatureMetadata.h"
#include "Animation/GestureManager.h"
#include "Animation/MovePed.h"
#include "animation/EventTags.h"
#include "animation/debug/AnimViewer.h"
#include "animation/debug/GestureEditor.h"
#include "animation/FacialData.h"
#include "audio/northaudioengine.h"
#include "audio/scriptaudioentity.h"
#include "audio/speechmanager.h"
#include "camera/CamInterface.h"
#include "Camera/Cutscene/CutsceneDirector.h"
#include "camera/Gameplay/GameplayDirector.h"
#include "camera/Replay/ReplayDirector.h"
#include "Camera/Gameplay/Aim/FirstPersonShooterCamera.h"
#include "Camera/Scripted/ScriptDirector.h"
#include "camera/viewports/ViewportManager.h"
#include "control/Gen9ExclusiveAssets.h"
#include "control/garages.h"
#include "control/record.h"
#include "control/replay/ReplayExtensions.h"
#include "control/replay/ReplayRecording.h"
#include "control/replay/Misc/LightPacket.h"
#include "core/game.h"
#include "Cutscene/CutSceneManagerNew.h"
#include "cutscene/CutSceneCameraEntity.h"
#include "Debug/DebugGlobals.h"
#include "debug/debugscene.h"
#include "debug/MarketingTools.h"
#include "debug/DebugRecorder.h"
#include "debug/DebugAnimation.h"
#include "debug/GtaPicker.h"
#include "Event/EventDamage.h"
#include "event/EventNetwork.h"
#include "event/EventGroup.h"
#include "event/EventResponseFactory.h"
#include "Event/EventShocking.h"
#include "Event/EventWeapon.h"
#include "Event/Events.h"
#include "Event/ShockingEvents.h"
#include "Frontend/MiniMap.h"
#include "frontend/MobilePhone.h"
#include "Frontend/PauseMenu.h"
#include "Game/cheat.h"
#include "Game/Dispatch/Orders.h"
#include "Game/localisation.h"
#include "game/Wanted.h"
#include "Game/Weather.h"
#include "Game/zones.h"
#include "Game/Clock.h"
#include "game/ModelIndices.h"
#include "ik/solvers/ArmIkSolver.h"
#include "ModelInfo/PedModelInfo.h"
#include "ModelInfo/VehicleModelInfo.h"
#include "modelinfo/VehicleModelInfoEnums.h"
#include "modelinfo/VehicleModelInfoMods.h"
#include "modelinfo/VehicleModelInfoVariation.h"
#include "ModelInfo/WeaponModelInfo.h"
#include "network/Events/NetworkEventTypes.h"
#include "network/Objects/Entities/NetObjGame.h"
#include "network/objects/entities/NetObjPlayer.h"
#include "network/players/NetGamePlayer.h"
#include "network/Network.h"
#include "network/Voice/NetworkVoice.h"
#include "Objects/Door.h"
#include "Objects/object.h"
#include "pathserver/ExportCollision.h"
#include "PedGroup/PedGroup.h"
#include "Peds/Action/BrawlingStyle.h"
#include "Peds/FleeBehaviour.h"
#include "Peds/CombatBehaviour.h"
#include "Peds/HealthConfig.h"
#include "Peds/Horse/horseComponent.h"
#include "Peds/Horse/horseTune.h"
#include "peds/PedCapsule.h"
#include "peds/PedCloth.h"
#include "peds/PedComponentSetInfo.h"
#include "peds/ped_channel.h"
#include "Peds/rendering/PedDamage.h"
#include "Peds/rendering/PedHeadshotManager.h"
#include "Peds/PedDebugVisualiser.h"
#include "Peds/PedFactory.h"
#include "peds/PedGeometryAnalyser.h"
#include "Peds/PedHelmetComponent.h"
#include "Peds/PedIntelligence.h"
#include "Peds/PedIntelligenceFactory.h"
#include "Peds/PedPhoneComponent.h"
#include "Peds/PedPlacement.h"
#include "Peds/rendering/PedProps.h"
#include "Peds/PedTuning.h"
#include "Peds/PedType.h"
#include "Peds/rendering/PedVariationDS.h"
#include "Peds/rendering/pedVariationPack.h"
#include "peds/rendering/PedVariationDebug.h"
#include "peds/rendering/pedVariationStream.h"
#include "Peds/Ped.h"
#include "Peds/PedIKSettings.h"
#include "Peds/TaskFlags.h"
#include "Peds/Pedpopulation.h"
#include "Peds/PlayerSpecialAbility.h"
#include "Peds/popcycle.h"
#include "performance/clearinghouse.h"
#include "Physics/Breakable.h"
#include "Physics/debugcontacts.h"
#include "Physics/debugEvents.h"
#include "Physics/GtaArchetype.h"
#include "Physics/gtaArchetype.h"
#include "Physics/gtaInst.h"
#include "Physics/GtaMaterialManager.h"
#include "Physics/Physics.h"
#include "physics/WorldProbe/worldprobe.h"
#include "Physics/RagdollConstraints.h"
#include "Pickups/PickupManager.h"
#include "renderer/DrawLists/drawListMgr.h"
#include "renderer/Entities/PedDrawHandler.h"
#include "renderer/lights/lights.h"
#include "renderer/Mirrors.h"
#include "renderer/OcclusionQueries.h"
#include "Renderer/PlantsMgr.h"
#include "renderer/RenderPhases/RenderPhaseDebugOverlay.h"
#include "renderer/RenderPhases/RenderPhaseCascadeShadows.h"
#include "renderer/RenderPhases/RenderPhaseFX.h"
#include "Renderer/ZoneCull.h"
#include "scene/lod/LodScale.h"
#include "Scene/scene.h"
#include "scene/texLod.h"
#include "scene/EntityIterator.h"
#include "fwscene/search/SearchVolumes.h"
#include "script/commands_entity.h"
#include "script/commands_ped.h"
#include "script/Handlers/GameScriptEntity.h"
#include "Scene/PlayerSwitch/PlayerSwitchInterface.h"
#include "scene/Physical.h"
#include "script/script.h"
#include "script/script_brains.h"
#include "script/script_channel.h"
#include "script/script_debug.h"
#include "script/streamedscripts.h"
#include "Shaders/CustomShaderEffectPed.h"
#include "Stats/StatsInterface.h"
#include "streaming/streaming.h" // For CStreaming::SetMissionDoesntRequireObject(), maybe other things.
#include "system/TamperActions.h"
#include "system/TaskScheduler.h"
#include "system/controlMgr.h"
#include "Task/Animation/TaskCutScene.h"
#include "Task/Combat/CombatMeleeDebug.h"
#include "Task/Combat/Cover/Cover.h"
#include "Task/Combat/TaskCombat.h"
#include "Task/Combat/TaskCombatMelee.h"
#include "task/Combat/TaskNewCombat.h"
#include "Task/Combat/TaskDamageDeath.h"
#include "Task/Default/TaskAmbient.h"
#include "Task/Default/TaskCuffed.h"
#include "Task/Default/TaskPlayer.h"
#include "Task/Default/TaskFlyingWander.h"
#include "Task/Default/TaskSwimmingWander.h"
#include "Task/Default/TaskUnalerted.h"
#include "task/Default/TaskWander.h"
#include "Task/General/TaskBasic.h"
#include "Task/General/TaskSecondary.h"
#include "Task/Motion/Locomotion/TaskBirdLocomotion.h"
#include "Task/Motion/Locomotion/TaskCombatRoll.h"
#include "Task/Motion/Locomotion/TaskLocomotion.h"
#include "Task/Motion/Locomotion/TaskFishLocomotion.h"
#include "Task/Motion/Locomotion/TaskHorseLocomotion.h"
#include "Task/Motion/Locomotion/TaskMotionAnimal.h"
#include "Task/Motion/Locomotion/TaskInWater.h"
#include "Task/Motion/Locomotion/TaskFlightlessBirdLocomotion.h"
#include "Task/Motion/Locomotion/TaskMotionAiming.h"
#include "Task/Motion/Locomotion/TaskMotionPed.h"
#include "Task/Motion/Locomotion/TaskQuadLocomotion.h"
#include "Task/Movement/Climbing/TaskVault.h"
#include "Task/Movement/Climbing/TaskDropDown.h"
#include "Task/Movement/Jumping/TaskFallGetUp.h"
#include "Task/Movement/Jumping/TaskJump.h"
#include "task/Movement/TaskFall.h"
#include "Task/Movement/TaskGetUp.h"
#include "task/Movement/TaskParachute.h"
#include "Task/Movement/TaskSeekEntity.h"
#include "Task/Physics/TaskNM.h"
#include "Task/Physics/TaskNMRelax.h"
#include "Task/Physics/TaskNMBalance.h"
#include "Task/Physics/TaskNMBrace.h"
#include "Task/Physics/TaskNMHighFall.h"
#include "Task/Physics/TaskNMThroughWindscreen.h"
#include "Task/Physics/TaskRageRagdoll.h"
#include "Task/Response/TaskFlee.h"
#include "Task/Scenario/Info/ScenarioInfo.h"
#include "Task/Scenario/ScenarioManager.h"
#include "Task/Scenario/TaskScenario.h"
#include "Task/Scenario/Types/TaskUseScenario.h"
#include "Task/Service/Army/TaskArmy.h"
#include "Task/Service/Police/TaskPolicePatrol.h"
#include "Task/Service/Fire/TaskFirePatrol.h"
#include "Task/Service/Medic/TaskAmbulancePatrol.h"
#include "Task/Service/Swat/TaskSwat.h"
#include "Task/System/DefaultTaskData.h"
#include "Task/System/MotionTaskData.h"
#include "Task/Vehicle/TaskCar.h"
#include "Task/Vehicle/TaskCarAccessories.h"
#include "Task/Vehicle/TaskCarUtils.h"
#include "Task/Vehicle/TaskEnterVehicle.h"
#include "Task/Vehicle/TaskExitVehicle.h"
#include "Task/Vehicle/TaskInVehicle.h"
#include "Task/Vehicle/TaskRideHorse.h"
#include "Task/Vehicle/TaskVehicleWeapon.h"
#include "task/Weapons/Gun/TaskAimGunOnFoot.h"
#include "task/Weapons/Gun/TaskAimGunScripted.h"
#include "Task/Weapons/Gun/TaskReloadGun.h"
#include "Task/Weapons/Gun/TaskVehicleDriveBy.h"
#include "Task/Weapons/TaskProjectile.h"
#include "Task/General/Phone/TaskMobilePhone.h"
#include "TimeCycle/TimeCycle.h"
#include "TimeCycle/TimeCycleAsyncQueryMgr.h"
#include "TimeCycle/TimeCycleConfig.h"
#include "Vehicles/Automobile.h"
#include "Vehicles/Bike.h"
#include "Vehicles/Boat.h"
#include "Vehicles/Heli.h"
#include "Vehicles/Planes.h"
#include "Vehicles/VehicleGadgets.h"
#include "Vehicles/vehiclepopulation.h"
#include "Vehicles/Metadata/VehicleSeatInfo.h"
#include "Vehicles/Metadata/VehicleMetadataManager.h"
#include "Vfx/Decals/DecalManager.h"
#include "vfx/misc/Coronas.h"
#include "Vfx/misc/Fire.h"
#include "Vfx/Particles/PtFxManager.h"
#include "Vfx/Systems/VfxBlood.h"
#include "Vfx/Systems/VfxPed.h"
#include "Vfx/Systems/VfxWater.h"
#include "Vfx/Systems/VfxWeapon.h"
#include "Vfx/Systems/VfxLens.h"
#include "Vfx/VfxHelper.h"
#include "shaders/ShaderLib.h"
#include "Task/System/TaskTree.h"
#include "Task/System/TaskTreeMotion.h"
#include "Task/System/TaskTreeMovement.h"
#include "VehicleAI/driverpersonality.h"
#include "VehicleAi/vehicleintelligence.h"
#include "VehicleAi/Task/TaskVehicleGoToHelicopter.h"
#include "VehicleAi/Task/TaskVehicleGoToPlane.h"
#include "VehicleAi/Task/TaskVehicleCruise.h"
#include "VehicleAi/Task/TaskVehiclePark.h"
#include "VehicleAi/Task/TaskVehicleAnimation.h"
#include "Weapons/Inventory/PedInventoryLoadOut.h"
#include "Weapons/AirDefence.h"
#include "Weapons/Weapon.h"
#include "Weapons/WeaponDamage.h"
#include "Weapons/WeaponManager.h"
#include "Weapons/Components/WeaponComponentClip.h"
#include "Peds/rendering/PedOverlay.h"
#include "task/Default/TaskArrest.h"
#include "task/Default/ArrestHelpers.h"
#include "Task/Movement/TaskJetpack.h"
#include "camera/cinematic/camera/mounted/CinematicMountedCamera.h"
#include "control/replay/Audio/SpeechAudioPacket.h"
#include "pickups/Data/PickupDataManager.h"
#include "scene/ExtraMetadataMgr.h"
#if __BANK
#include "debug/Rendering/DebugLighting.h"
#endif
// network headers
#include "Network/NetworkInterface.h"
#include "Stats/StatsMgr.h"
//rage headers
#include "vectormath/classfreefuncsv.h"
#include "cranimation/animtrack.h"
#include "crskeleton/skeleton.h"
#include "crskeleton/skeletondata.h"
#include "fragment/cache.h"
#include "fragment/typeChild.h"
#include "math/angmath.h"
#include "phbound/boundCapsule.h"
#include "phbound/boundComposite.h"
#include "phBound/boundcurvedgeom.h"
#include "phBound/support.h"
#include "phcore/segment.h"
#include "phsolver/contactmgr.h"
#include "physics/intersection.h"
#include "physics/Sleep.h"
#include "physics/constrainthalfspace.h"
#include "rmptfx/ptxmanager.h"
#include "system/timeMgr.h"
#include "debug/debugscene.h"
#include "profile/page.h"
using namespace AIStats;
/* //uncomment to check the offsets
CompileTimeAssert(offsetof(CEntity,m_nFlags)==36);
CompileTimeAssert(offsetof(CEntity,RandomSeed)==44);
CompileTimeAssert(offsetof(CEntity,pReferences)==48);
CompileTimeAssert(offsetof(CEntity,m_nScanCode)==60);
CompileTimeAssert(offsetof(CEntity,m_blockIdx)==86);
CompileTimeAssert(offsetof(CEntity,m_ambientOcclusionScale)==92);
CompileTimeAssert(offsetof(CEntity,pad)==95);
*/
INSTANTIATE_RTTI_CLASS(CPed,0x94821659);
AUTOID_IMPL(CRewardedVehicleExtension);
PF_PAGE(GTAPeds, "GTA peds");
PF_GROUP(ProcessControl);
PF_LINK(GTAPeds, ProcessControl);
PF_TIMER(ProcessControlFunc, ProcessControl);
PF_TIMER(ProcessControl_AnimStateUpdate, ProcessControl);
PF_TIMER(ProcessControl_Data, ProcessControl);
PF_TIMER(ProcessControl_Audio, ProcessControl);
PF_TIMER(ProcessControl_Animation, ProcessControl);
PF_TIMER(ProcessControl_ResetVariables, ProcessControl);
PF_TIMER(ProcessControl_Graphics, ProcessControl);
PF_TIMER(ProcessControl_Population, ProcessControl);
PF_TIMER(ProcessControl_Physics, ProcessControl);
PF_TIMER(ProcessControl_WeaponsAndAccessoriesPreIntelligence, ProcessControl);
PF_TIMER(ProcessControl_WeaponsAndAccessoriesPostIntelligence, ProcessControl);
PF_TIMER(ProcessControl_Intelligence, ProcessControl);
PF_TIMER(ProcessFrozen, ProcessControl);
PF_TIMER(UpdateMovementMode, ProcessControl);
#if !__FINAL
PARAM(disableHurtCombat, "Disable Ped Hurt Combat mode including bleed out");
PARAM(debugUnmanagedRagdolls,"Collects callstacks of PrepareForActivation and SwitchToRagdoll calls on peds in order to identify the causes of unmanaged ragdolls");
PARAM(assertOnMissingSpeechContext,"Throw an assert if NewSay attempts to use a context that has no associated gameobject.");
#endif
PARAM(disableFix7458166, "Restore ClearClothController to original place for B*7458166");
PARAM(disableFix7459244, "Disable nullptr checks for B*7459244");
XPARAM(noheadblend);
XPARAM(headblenddebug);
XPARAM(useFPIdleMovementOnClones);
#if __BANK
XPARAM(debugGestures);
XPARAM(debugGesturesOnSelectedPed);
PARAM(headblendcheck, "Renders debug text above non finalized blend peds");
PARAM(pedstreamingcheck, "Renders debug text above peds with outstanding stream requests");
PARAM(trackPlayerTeleport, "Adds information about player teleports to the TTY");
XPARAM(noKeyboardMouseControls);
#endif
AI_OPTIMISATIONS()
AI_MOTION_OPTIMISATIONS()
AI_MOVEMENT_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
ENTITY_OPTIMISATIONS()
PED_OPTIMISATIONS()
RAGE_DEFINE_CHANNEL(ped)
RAGE_DEFINE_CHANNEL(respawn)
RAGE_DEFINE_CHANNEL(wanted)
RAGE_DEFINE_CHANNEL(viseme)
float g_CapsuleRadius=0.1f;
float gPedAmbientVolumeFadeStartSetting;
float gPedAmbientVolumeFadeEndSetting;
float gPedAmbientVolumeMaxStrength;
float gPedAmbientVolumeBaseIntensity;
float gPedInCarAmbientScale;
CLightSource gPedPhoneLight;
#if __BANK
bool gPedWetnessLevelOverrideEnabled = false;
float gPedWetnessLevelOverrideAmount = 0.0f;
// Physics.
// Toggle processing of PreComputeImpacts via RAG.
bool CPed::sm_PreComputeImpacts = true;
bool CPed::sm_PreComputeCapsuleProbeImpact = true;
bool CPed::sm_PreComputeHorseLowerLegImpact = true;
bool CPed::sm_PreComputeHorsePropBlockerImpact = true;
bool CPed::sm_PreComputeImpactsForMover = true;
bool CPed::sm_PreComputeImpactsForRagdoll = true;
bool CPed::sm_PreComputeMainCapsuleImpact = true;
bool CPed::sm_PreComputePlayerLowerLegImpact = true;
bool CPed::sm_PreComputeQuadrupedLowerLegImpact = true;
bool CPed::sm_EnableRagdollForHighVelImpactsWhileOnTrain = true;
bool CPed::sm_deadRagdollDebug = false;
#endif
s32 CPed::ms_MoneyCarriedByAllNewPeds=-1; // If this is -1 all rage_new peds are given some money. If >=0 this amount is given to all peds.
u32 CPed::ms_HealthInSnackCarriedByAllNewPeds=0; // The amount of health given by a health snack pickup dropped by a dead ped
float CPed::ms_ProbabilityPedsWillDropHealthSnacks=0.0f; // This is set in script and determines how frequently a dead ped will drop a snack health pickup
// Accuracy
float CPed::ms_shortLockonAccuracyModifier = 0.5f;
float CPed::ms_shortLockonTime = 0.5f;
dev_bool CPed::ms_bApplyExtraHeadingChangeToPeds = true;
PARAM(disableMoverCapsuleRadiusChanges, "Don't allow the mover capsule to change size. Keep at standing radius.");
PARAM(enableActionModeHeadLookAt, "Enable head look at, at target during action mode");
#if !__FINAL
bool CPed::ms_bUseHurtCombat = true;
bool CPed::ms_bActivateRagdollOnCollision = true;
#endif
bank_float CPed::ms_fMinDepthForFixStuckInGeometry = 0.05f;
#if __BANK
bool CPed::ms_bAlwaysFixStuckInGeometry = false;
#endif // __BANK
bank_float CPed::ms_fActivateRagdollOnCollisionDefaultAllowedSlope = -0.95f;
// maximum time ped ragdoll is allowed to be in contact with player's lower legs before we step over the ragdoll
bank_u32 CPed::ms_uMaxRagdollPlayerLegContactTime[4] = { 500, 250, 150, 66 };
bank_u32 CPed::ms_uMaxRagdollAILegContactTime[4] = { 500, 300, 100, 0 };
bank_u32 CPed::ms_uMaxRagdollDoorContactTime = 1000;
bank_float CPed::ms_CounterRagdollImpulseStrength = 2.0f;
// Only apply this percentage of the counter impulse when kicking dead peds
bank_float CPed::ms_DeadPedCounterRagdollImpulseStrengthModifier = 0.8f;
bank_float CPed::ms_fMinimumKickDirectionAngle = 20.0f;
bank_float CPed::ms_fMinimumKickDirectionAngleSprintScaler = 1.5;
bank_float CPed::ms_fForceKickDirectionAngle = 12.5f;
extern const char* parser_RagdollComponent_Strings[];
bank_u32 CPed::ms_RagdollWeaponDisableCollisionMask = 1 << RAGDOLL_HAND_RIGHT | 1 << RAGDOLL_LOWER_ARM_RIGHT | 1 << RAGDOLL_UPPER_ARM_RIGHT |
1 << RAGDOLL_HAND_LEFT | 1 << RAGDOLL_LOWER_ARM_LEFT | 1 << RAGDOLL_UPPER_ARM_LEFT;
#if __DEV
bool CPed::ms_bWindyClothingOverride = false;
float CPed::ms_fWindyClothingOverride = 1.0f;
#endif //__DEV
float CPed::ms_fWindyClothingMinVehicleSpeed = 10.0f;
float CPed::ms_fWindyClothingMaxVehicleSpeed = 24.0f;
float CPed::ms_fWindyClothingMinInAirSpeed = 7.5;
float CPed::ms_fWindyClothingMaxInAirSpeed = 15.0f;
float CPed::ms_fWindyClothingMinFallSpeed = 2.0f;
float CPed::ms_fWindyClothingMaxFallSpeed = 10.0f;
float CPed::ms_fWindyClothingCullDistance = 30.0f;
float CPed::ms_fWindyClothingMinDistance = 2.0f;
float CPed::ms_fWindyClothingMaxDistance = 10.0f;
float CPed::ms_fWindyClothingScaleAtMinDistance = 0.3f;
float CPed::ms_fWindyClothingScaleAtMaxDistance = 1.0f;
float CPed::ms_fWindyClothingClavicleScale = 1.0f;
float CPed::ms_fWindyClothingForearmScale = 0.9f;
float CPed::ms_fWindyClothingThighScale = 0.2f;
float CPed::ms_fWindyClothingCalfScale = 0.5f;
float CPed::ms_fWindyClothingMinError = -0.1f;
float CPed::ms_fWindyClothingMaxError = 0.40f;
u32 CPed::ms_uIgnoredMaterialCollisionWeaponHash = 0;
bool CPed::ms_bBikeHelmetsDisabledForAllPeds = false;
bool CPed::ms_bAllowHurtForAllMissionPeds = true;
bool CPed::ms_bRandomPedsDropMoney = true;
bool CPed::ms_bRandomPedsDropWeapons = true;
bool CPed::ms_bRandomPedsBlockingNonTempEventsThisFrame = false;
u32 CPed::ms_uMissionPedLodDistance = RSG_PC ? 300 : 150;
u32 CPed::ms_uDefaultPedLodDistance = RSG_PC ? 240 : 120;
float CPed::ms_LodMultiplierBias = 0.0f;
// If you update this list of bone tags please update ProcessColarDOFs as it
// relies on BONETAG_HEAD and BONETAG_NECK being index 0 and 7 repectively
const u16 CPed::ms_cachedBoneTags[kOffsetCount] = { BONETAG_HEAD, BONETAG_L_PH_FOOT, BONETAG_R_PH_FOOT, BONETAG_L_FOOT, BONETAG_R_FOOT, BONETAG_L_HAND, BONETAG_R_HAND, BONETAG_NECK, BONETAG_SPINE1 };
const fwMvFrameId CPed::ms_ExternallyDrivenDOFFrameId("ExternallyDrivenDOFFrame",0x5B32392C);
const fwMvFloatId CPed::ms_secondaryWeightId("SecondaryWeight",0x4EC37DB1);
const fwMvFloatId CPed::ms_secondaryWeightOutId("SecondaryWeightOut",0xFE4AF7A0);
const fwMvClipId CPed::ms_gestureClipId("GestureClip",0x5CF861EF);
const fwMvFloatId CPed::ms_gesturePhaseId("GesturePhase",0x950536FE);
const fwMvFloatId CPed::ms_gestureRateId("GestureRate",0x8AE74687);
const fwMvFloatId CPed::ms_gesturePhaseOutId("GesturePhaseOut",0xE6585E24);
const fwMvFloatId CPed::ms_gestureMaxWeightId("GestureMaxWeight",0x202EF210);
const fwMvFilterId CPed::ms_gestureFilterId("GestureFilter",0x6C20C1EA);
const fwMvFloatId CPed::ms_gestureExpressionWeightId("GestureExpressionWeight",0x6326698D);
const fwMvRequestId CPed::ms_gestureRequestId("GestureRequest",0x4216F90E);
const fwMvFloatId CPed::ms_gestureDurationId("GestureDuration",0xCB073F10);
const fwMvFlagId CPed::ms_gestureBlendInFlagId("Gesture_Gesture",0x866549BC);
const fwMvFlagId CPed::ms_gestureBlendOutFlagId("Gesture_NoGesture",0x7AF4CCBD);
const fwMvClipId CPed::ms_visemeBodyAdditiveClipId("VisemeBodyAdditiveClip",0x1ad7be0f);
const fwMvFloatId CPed::ms_visemeBodyAdditivePhaseId("VisemeBodyAdditivePhase",0xae6cfeb4);
const fwMvRequestId CPed::ms_visemeBodyAdditiveRequestId("VisemeBodyAdditiveRequest",0xc9f6a795);
const fwMvFloatId CPed::ms_visemeBodyAdditiveWeightId("VisemeBodyAdditiveWeight",0x3bc802bf);
const fwMvFloatId CPed::ms_visemeBodyAdditiveDurationId("VisemeBodyAdditiveDuration",0x2257d301);
const fwMvFlagId CPed::ms_visemeBodyAdditiveBlendInFlagId("VisemeBodyAdditive_VisemeBodyAdditive",0x4b7a9a59);
const fwMvFlagId CPed::ms_visemeBodyAdditiveBlendOutFlagId("VisemeBodyAdditive_NoVisemeBodyAdditive",0x305fd765);
atFixedArray<CPed*, CPed::MAX_PEDS_THAT_CAN_GET_WET_THIS_FRAME> CPed::ms_pedsThatCanGetWetThisFrame;
dev_float CPed::ms_minAmbientDrawingScaleFactor = 0.5f;
dev_float CPed::ms_maxAmbientDrawingScaleFactor = 1.0f;
CSpatialArray* CPed::ms_spatialArray = NULL;
bank_float CPed::ms_fLargeFoliageRadius = 0.9f;
bank_float CPed::ms_fOnFireDelayTime = 1.0f;
bank_float CPed::ms_fOnFireDelayTimeResistant = 1.25f;
const char *CPed::GestureContextNames[] =
{
"GC_DEFAULT",
"GC_TWO_HANDED_WEAPON",
"GC_CONVERSATION_PHONE",
"GC_CONVERSATION_HANGOUT",
"GC_OBJECT_LEFT_HAND",
"GC_OBJECT_RIGHT_HAND",
"GC_VEHICLE_DRIVER",
"GC_VEHICLE_PASSENGER",
"GC_WALKING",
"GC_SITTING",
"GC_LOCAL_PLAYER",
"GC_COMBAT_TASK",
};
#if !__FINAL
bool bShowMissingLowLOD = false;
bool bShowPedLODLevelData = false;
bool bShowRenderPedLOD = false;
bool bShowPedHDWireframe = false;
bool bShowPedHDSolid = false;
bool bOverrideLODFadeValue = false;
bool bShowVisibilityQueryResult = false;
bool bShowPedsVisibilityTrackingStats = false;
bool bShowPedsVisibilityBoundingBox = false;
bool bTestVisibilityQuery = false;
float fLodMultiplier = 1.f;
u32 ForceLODFadeValue = 255;
float TweakLodThreshold;
float TweakLowLodThreshold;
float TweakSuperLodThreshold;
float TweakInCarLodThreshold;
#endif // !__FINAL
bank_bool g_bEnableFPV = true;
// Light info
pedLight CPed::sm_mainLight;
pedLight CPed::sm_fpsLight;
pedLight CPed::sm_footLight;
#if PED_APPLY_HORSE_PROP_BLOCKER
bank_bool s_bEnableBlockerBound = true;
#endif
struct CQuadrupedSpringTuning
{
float m_fShallowSpringForce;
float m_fShallowSpringForceRange;
float m_fVelocityFactor;
float m_fMaxVelocityForScaling;
float m_fMaxForceIncreasePerSecond;
float m_fSpringDamping;
float m_fDampingDropOffRange;
float m_fTopSpringExtension;
float m_fExtraOffsetForTorque;
CQuadrupedSpringTuning()
:m_fShallowSpringForce(50.0f)
,m_fShallowSpringForceRange(0.20f)
,m_fVelocityFactor(0.0f)
,m_fMaxVelocityForScaling(5.0f)
,m_fMaxForceIncreasePerSecond(20.0f)
,m_fSpringDamping(-17.8f)
,m_fDampingDropOffRange(2.0)
,m_fTopSpringExtension(0.0f)
,m_fExtraOffsetForTorque(1.0f)
{ }
#if __BANK
void AddWidgets(bkBank &rBank)
{
rBank.PushGroup("Quadruped tuning", false);
rBank.AddSlider("Shallow Spring force", &m_fShallowSpringForce, 0.0f, 250.0f, 0.01f);
rBank.AddSlider("Max Shallow spring range", &m_fShallowSpringForceRange, 0.0f, 1.0f, 0.01f);
rBank.AddSlider("Spring force velocity factor", &m_fVelocityFactor, 0.0f, 10.0f, 0.01f);
rBank.AddSlider("Spring force max increase", &m_fMaxForceIncreasePerSecond, 0.0f, 40.0f, 0.01f);
rBank.AddSlider("Spring damping", &m_fSpringDamping, -100.0f, 100.0f, 0.005f);
rBank.AddSlider("Damping drop off range", &m_fDampingDropOffRange, 0.0f, 10.0f, 0.01f);
rBank.AddSlider("Top Spring Extension", &m_fTopSpringExtension, -1.0f, 1.0f, 0.01f);
rBank.AddSlider("Offset for torque", &m_fExtraOffsetForTorque, 0.0f, 5.0f, 0.01f);
#if PED_APPLY_HORSE_PROP_BLOCKER
rBank.PushGroup("Bounds control");
rBank.AddToggle("Enable prop blocker", &s_bEnableBlockerBound);
rBank.PopGroup();
#endif
rBank.PopGroup();
}
#endif
};
static CQuadrupedSpringTuning s_QuadrupedSpringTuning;
dev_s8 kNumFramesBeforeQPedRagdoll = 30;
bank_u32 g_SpeechOffsetForVisemesMs = 600;
#if __ASSERT
sysStackCollector CPed::ms_RagdollStackCollector;
#endif //__ASSERT
#if __BANK
bank_bool CPed::ms_bToggleCollidesAgainstGlassRagdoll = false;
bank_bool CPed::ms_bToggleCollidesAgainstGlassWeapon = false;
bkBank* CPed::ms_pBank = NULL;
bkButton* CPed::ms_pCreateButton = NULL;
bank_bool CPed::ms_bShowAOVolumeDebug = false;
#endif
bank_float CPed::ms_fMaxSideNormalForSideNM = 0.8f;
bank_float CPed::ms_fVelThroughNormalForSideNM = 0.5f;
bank_float CPed::ms_fMaxNMAccumulateForSideNM = 5.0f;
bank_bool CPed::ms_bTriggerNMInCover = true;
bank_float CPed::ms_fVelLimitForKnockFromCover = 37.0f;
bank_float CPed::ms_fVelLimitForHitByGateKnockFromCover = 0.5f;
bank_float CPed::ms_fMinObjectVelForKnockFromCover = 0.08f;
bank_float CPed::ms_fSizeToSpeedTolForCoverGroundInstance = 2.0f;
s32 CPed::ms_iMinKnockFromCoverFrames = 1;
#if __BANK
bank_bool CPed::ms_bEnableFakeCylindricalConstraints = true;
#endif
bank_bool CPed::ms_bAllowNormalFlattening = true;
bank_bool CPed::ms_bOnlyFlattenMovable = false;
bank_float CPed::ms_bNormalFlatteningMinDepth = 0.01f;
bank_float CPed::ms_bNormalFlatteningMaxDepth = 0.03f;
bank_bool CPed::ms_bAllowNormalVerticalizing = true;
bank_float CPed::ms_fNormalVerticalizingFactor = 0.8f;
bank_float CPed::ms_MinStandingHalfCapsuleWidthRatio = 1.0f;
bank_bool CPed::ms_bRecomputeGroundPhysicalOnVehicles = true;
bank_bool CPed::ms_bEnableGroundPhysicalCounterForce = true;
bank_bool CPed::ms_bDeleteProbeCapsuleImpacts = true;
bank_s32 CPed::ms_nCapsuleAsleepTicks = 10;
bank_s32 CPed::ms_nCapsuleMotionlessTicks = 7;
bank_float CPed::ms_fVolumeOfExpectedHardToPushObjects = 3.0f;
bank_float CPed::ms_fMassOfExpectedHardToPushObjects = 400.0f;
bank_float CPed::ms_fLargePedMass = 200.0f;
bank_float CPed::ms_fLargeComponentMass = 50.0f;
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
CPhysical::CSecondSurfaceConfig CPed::ms_pedSecondSurfaceConfig(
0.2f, // Sink lift factor
1.0f, // TangV2 lift factor
1.5f, // Max TangV2 lift factor
0.0f // Drag factor
);
CPhysical::CSecondSurfaceConfig CPed::ms_ragdollSecondSurfaceConfig(
0.0f, // Sink lift factor
0.0f, // TangV2 lift factor
0.0f, // Max TangV2 lift factor
0.0f // Drag factor
);
CPhysical::CSecondSurfaceConfig CPed::ms_skiSecondSurfaceConfig(
0.2f, // Sink lift factor
1.0f, // TangV2 lift factor
1.5f, // Max TangV2 lift factor
0.5f // Drag factor
);
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
// Bluetooth headset data
static const Vector3 headSetLightOffset(0.031f,0.007f,-0.08000000f);
static dev_bool headSetLightDontRenderFlare = true;
static dev_bool headSetLightFadeOffDirMult = false;
static dev_bool headSetLightHasDirection = false;
static dev_float headSetLightSize = 0.0875f;
static const Color32 headSetLightCol(0,0,255);
static dev_float headSetLightIntensity = 1.3f;
static dev_float headSetLightZBias = 0.02f;
static dev_float headSetLightDirViewThreshold = 1.0f;
static dev_float headSetLightDirLightConeAngle = 1.0f;
static const atHashString headSet0NameHash = ATSTRINGHASH("MP_M_Freemode_01_p_Male_Heist/p_ears_000" ,0x6bf945f7);
static const atHashString headSet1NameHash = ATSTRINGHASH("MP_M_Freemode_01_p_Male_Heist/p_ears_001" ,0x79b3e16c);
static const atHashString headSet2NameHash = ATSTRINGHASH("MP_M_Freemode_01_p_Male_Heist/p_ears_002" ,0x17c21dba);
// --- CPed -----------------------------------------------------------------------------------------------
CTask* CPed::ComputeWanderTask(const CPed & ped, CPopCycleConditions* pSpawnConditions)
{
if( ped.GetPedType() == PEDTYPE_COP )
{
return rage_new CTaskPolice();
}
else if ( ped.GetPedType() == PEDTYPE_ARMY )
{
return rage_new CTaskArmy();
}
else if( ped.GetPedType() == PEDTYPE_FIRE )
{
return rage_new CTaskFirePatrol();
}
else if( ped.GetPedType() == PEDTYPE_MEDIC )
{
return rage_new CTaskAmbulancePatrol();
}
CPedGroup* pPedGroup=ped.GetPedsGroup();
if(pPedGroup)
{
if (ped.GetPedType() == PEDTYPE_SWAT && !ped.PopTypeIsMission())
{
return rage_new CTaskSwat();
}
else if (!pPedGroup->GetGroupMembership()->IsLeader(&ped) && pPedGroup->GetGroupMembership()->GetLeader())
{
return rage_new CTaskFollowLeaderAnyMeans(pPedGroup, pPedGroup->GetGroupMembership()->GetLeader());
}
}
if (pSpawnConditions)
{
if (pSpawnConditions->m_bSpawnInAir)
{
return rage_new CTaskFlyingWander;
}
else if (pSpawnConditions->m_bSpawnInWater)
{
return rage_new CTaskSwimmingWander;
}
}
return rage_new CTaskUnalerted;
}
CTask* CPed::ComputeDefaultDrivingTask(const CPed& ped, CVehicle* pVehicle, bool bUseExistingNodes )
{
if( ped.IsLocalPlayer() )
{
return rage_new CTaskPlayerDrive();
}
if( ped.IsNetworkClone() )
{
return rage_new CTaskNetworkClone();
}
//don't do anything if we're a train driver
if (pVehicle && pVehicle->InheritsFromTrain())
{
return rage_new CTaskInVehicleBasic(pVehicle, false);
}
// Mission chars stay still by default
if( ped.PopTypeIsMission() )
{
// The wander task will eventually calculate the hover task
if( pVehicle && (pVehicle->InheritsFromHeli() || (pVehicle->InheritsFromPlane() && static_cast<CPlane*>(pVehicle)->GetVerticalFlightModeAvaliable())) && pVehicle->IsDriver(&ped) )
{
return rage_new CTaskCarDriveWander( pVehicle, DMode_StopForCars, 0, bUseExistingNodes);
}
else
{
return rage_new CTaskInVehicleBasic( pVehicle, false );
}
}
if( ped.GetPedType() == PEDTYPE_COP)
{
return rage_new CTaskPolice();
}
else if ( ped.GetPedType() == PEDTYPE_ARMY )
{
return rage_new CTaskArmy();
}
else if( ped.GetPedType() == PEDTYPE_FIRE )
{
return rage_new CTaskFirePatrol();
}
else if( ped.GetPedType() == PEDTYPE_MEDIC )
{
return rage_new CTaskAmbulancePatrol();
}
else if( ped.GetPedType() == PEDTYPE_SWAT && !ped.PopTypeIsMission() )
{
return rage_new CTaskSwat();
}
CPedGroup* pPedGroup=ped.GetPedsGroup();
if(pPedGroup)
{
if (!pPedGroup->GetGroupMembership()->IsLeader(&ped) && pPedGroup->GetGroupMembership()->GetLeader())
{
return rage_new CTaskInVehicleBasic( pVehicle, false );
}
}
// Drivers use the wander task, non-drivers just use the basic in vehicle task.
// Unless they're running a recording, in which case they should continue to do in-vehicle basic -JM
if( pVehicle && pVehicle->IsDriver(&ped) && !CVehicleRecordingMgr::IsPlaybackGoingOnForCar(pVehicle))
{
//Generate the flags.
s32 iFlags = GetPedConfigFlag(CPED_CONFIG_FLAG_JackedAbandonedCar) ? DMode_AvoidCars : DMode_StopForCars;
CVehiclePopulation::AddDrivingFlagsForAmbientVehicle(iFlags, pVehicle->InheritsFromBike());
// Airplanes don't do well in regular traffic, so we just pass in zero as the speed, which will
// make the plane sit still. Not ideal, but at least should prevent them from getting into more trouble.
float cruiseSpeed;
if(pVehicle->InheritsFromPlane())
{
cruiseSpeed = 0.0f;
}
else
{
cruiseSpeed = CDriverPersonality::FindMaxCruiseSpeed(&ped, pVehicle, pVehicle->GetVehicleType() == VEHICLE_TYPE_BICYCLE);
}
return rage_new CTaskCarDriveWander(pVehicle, iFlags, cruiseSpeed, bUseExistingNodes);
}
else
{
return rage_new CTaskInVehicleBasic( pVehicle, false );
}
}
CTask* CPed::ComputePrimaryMotionTask(const bool bLowLod) const
{
CTask* pMotionTask = NULL;
// Verify our motion task data set exists
if(m_pMotionTaskDataSet)
{
// If mounted use the mounted set
#if ENABLE_HORSE
if (GetMyMount())
{
pMotionTask = rage_new CTaskMotionRideHorse();
}
#endif
ENABLE_HORSE_ONLY(else) if(GetIsSwimming() && m_pMotionTaskDataSet->GetInWaterData())
{
// If swimming and we have a motion task data entry for in water, use that
pMotionTask = GetMotionTaskFromType(m_pMotionTaskDataSet->GetInWaterData(), bLowLod);
}
else
{
// Otherwise assert that we have an on foot data entry and use that
Assert(m_pMotionTaskDataSet->GetOnFootData());
pMotionTask = GetMotionTaskFromType(m_pMotionTaskDataSet->GetOnFootData(), bLowLod);
}
}
else
{
Assertf(0, "Ped does not have a motion task data set defined in peds.meta");
}
return pMotionTask;
};
CTask* CPed::GetMotionTaskFromType(const sMotionTaskData* pMotionTaskData, const bool bLowLod) const
{
CTaskMotionBase* pMotionTask = NULL;
fwMvClipSetId oldOnFootClipSetId = CLIP_SET_ID_INVALID;
fwMvClipSetId oldWeaponClipSetId = CLIP_SET_ID_INVALID;
fwMvClipSetId oldStrafingClipSetId = CLIP_SET_ID_INVALID;
bool oldWeaponClipSetFromAnims = false;
switch(pMotionTaskData->m_Type)
{
// Peds/Humans currently only will return the CTaskMotionPed, which was in place before the restructuring for animals
case PED_ON_FOOT:
case PED_IN_WATER:
{
static dev_float BLEND_DURATION = 0.25f;
CTaskMotionBase* pCurrentMotionTask;
if(bLowLod)
{
pMotionTask = rage_new CTaskMotionPedLowLod();
pCurrentMotionTask = static_cast<CTaskMotionBase*>(m_pPedIntelligence->GetTaskManager()->FindTaskByTypeActive(PED_TASK_TREE_MOTION, CTaskTypes::TASK_MOTION_PED));
if(pCurrentMotionTask)
{
pCurrentMotionTask->SetCleanupMotionTaskNetworkOnQuit(false);
((CTaskMotionPedLowLod*)pMotionTask)->SetNetworkInsertDuration(BLEND_DURATION);
// Copy the current clip set
oldOnFootClipSetId = pCurrentMotionTask->GetDefaultOnFootClipSet(true);
oldWeaponClipSetId = pCurrentMotionTask->GetOverrideWeaponClipSet();
oldStrafingClipSetId = pCurrentMotionTask->GetOverrideStrafingClipSet();
oldWeaponClipSetFromAnims = pCurrentMotionTask->HasWeaponClipSetSetFromAnims();
}
}
else
{
pMotionTask = rage_new CTaskMotionPed();
pCurrentMotionTask = static_cast<CTaskMotionBase*>(m_pPedIntelligence->GetTaskManager()->FindTaskByTypeActive(PED_TASK_TREE_MOTION, CTaskTypes::TASK_MOTION_PED_LOW_LOD));
if(pCurrentMotionTask)
{
pCurrentMotionTask->SetCleanupMotionTaskNetworkOnQuit(false);
((CTaskMotionPed*)pMotionTask)->SetNetworkInsertDuration(BLEND_DURATION);
// Copy the current clip set
oldOnFootClipSetId = pCurrentMotionTask->GetDefaultOnFootClipSet(true);
oldWeaponClipSetId = pCurrentMotionTask->GetOverrideWeaponClipSet();
oldStrafingClipSetId = pCurrentMotionTask->GetOverrideStrafingClipSet();
oldWeaponClipSetFromAnims = pCurrentMotionTask->HasWeaponClipSetSetFromAnims();
}
}
// Copy over the active alternate clips
if(pCurrentMotionTask)
{
for(s32 i = 0; i < CTaskMotionBase::ACT_MAX; i++)
{
CTaskMotionBase::AlternateClipType acType = static_cast<CTaskMotionBase::AlternateClipType>(i);
const CTaskMotionBase::sAlternateClip& ac = pCurrentMotionTask->GetAlternateClip(acType);
if(ac.IsValid())
{
pMotionTask->SetAlternateClip(acType, ac, pCurrentMotionTask->GetAlternateClipLooping(acType));
}
}
}
}
break;
#if ENABLE_HORSE
case PED_ON_MOUNT:
pMotionTask = rage_new CTaskMotionRideHorse();
break;
#endif
// Bird uses an clip set hash that is part of the data entry
case BIRD_ON_FOOT:
pMotionTask = rage_new CTaskBirdLocomotion(0, 0, 0, GetPedModelInfo()->GetMovementClipSet());
break;
// Flightless bird uses an clip set hash that is part of the data entry
case FLIGHTLESS_BIRD_ON_FOOT:
pMotionTask = rage_new CTaskFlightlessBirdLocomotion(0, 0, 0, GetPedModelInfo()->GetMovementClipSet());
break;
// HORSE_ON_FOOT uses an clip set hash that is part of the data entry
#if ENABLE_HORSE
case HORSE_ON_FOOT:
case HORSE_SWIMMING:
pMotionTask = rage_new CTaskMotionAnimal();
break;
#endif
case BOAR_ON_FOOT:
case COW_ON_FOOT:
case COUGAR_ON_FOOT:
case COYOTE_ON_FOOT:
case DEER_ON_FOOT:
case PIG_ON_FOOT:
case RAT_ON_FOOT:
case RETRIEVER_ON_FOOT:
case ROTTWEILER_ON_FOOT:
case CAT_ON_FOOT:
case RABBIT_ON_FOOT:
pMotionTask = rage_new CTaskQuadLocomotion(0, 0, 0, GetPedModelInfo()->GetMovementClipSet());
break;
case FISH_IN_WATER:
pMotionTask = rage_new CTaskFishLocomotion(0, 0, 0, GetPedModelInfo()->GetMovementClipSet());
break;
default:
Assertf(0, "Motion task type invalid");
break;
}
if (pMotionTask)
{
// set the default clipsets
pMotionTask->SetDefaultClipSets(*this);
// if we have an OnFoot clipset
if (oldOnFootClipSetId != CLIP_SET_ID_INVALID)
{
// set it on the new task
pMotionTask->SetOnFootClipSet(oldOnFootClipSetId);
}
// if we have an OverrideWeapon clipset
if (oldWeaponClipSetId != CLIP_SET_ID_INVALID)
{
// set it on the new task
pMotionTask->SetOverrideWeaponClipSet(oldWeaponClipSetId);
pMotionTask->SetWeaponClipSetFromAnims(oldWeaponClipSetFromAnims);
}
// if we have an OverrideStrafing clipset
if (oldStrafingClipSetId != CLIP_SET_ID_INVALID)
{
// set it on the new task
pMotionTask->SetOverrideStrafingClipSet(oldStrafingClipSetId);
}
}
return pMotionTask;
}
const CHealthConfigInfo* CPed::GetPedHealthInfo() const
{
return CHealthConfigInfoManager::GetInfo( GetPedModelInfo()->GetPersonalitySettings().GetHealthConfigHash().GetHash() );
}
s32 CPed::GetAge() const
{
return GetPedModelInfo()->GetAge();
}
bool CPed::IsMale() const
{
return GetPedModelInfo()->GetPersonalitySettings().GetIsMale();
}
bool CPed::IsFiring() const
{
CTaskAimGunOnFoot* pAimGunTask = static_cast<CTaskAimGunOnFoot*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_ON_FOOT));
CTaskAimGunScripted* pAimGunScripted = static_cast<CTaskAimGunScripted*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_SCRIPTED));
CTaskAimGunBlindFire* pAimGunBlindFire = static_cast<CTaskAimGunBlindFire*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_BLIND_FIRE));
CTaskAimGunVehicleDriveBy* pAimGunDriveBy = static_cast<CTaskAimGunVehicleDriveBy*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_VEHICLE_DRIVE_BY));
CTaskVehicleMountedWeapon* pVehicleWeapon = static_cast<CTaskVehicleMountedWeapon*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_VEHICLE_MOUNTED_WEAPON));
bool isFiring = pAimGunTask ? pAimGunTask->GetIsFiring() && !pAimGunTask->GetIsUsingFiringVariation() : false ||
pAimGunScripted ? pAimGunScripted->GetIsFiring() : false ||
pAimGunBlindFire ? pAimGunBlindFire->GetIsFiring() : false ||
pAimGunDriveBy ? pAimGunDriveBy->GetIsFiring() : false ||
pVehicleWeapon ? pVehicleWeapon->IsFiring() : false;
return isFiring;
}
bool CPed::IsUsingFiringVariation() const
{
CTaskAimGunOnFoot* pAimGunTask = static_cast<CTaskAimGunOnFoot*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_ON_FOOT));
return pAimGunTask ? pAimGunTask->GetIsFiring() && pAimGunTask->GetIsUsingFiringVariation() : false;
}
float CPed::GetAttackStrength() const
{
return GetPedModelInfo()->GetPersonalitySettings().GetAttackStrength(GetRandomSeed());
}
u32 CPed::GetMotivation() const
{
return GetPedModelInfo()->GetPersonalitySettings().GetMotivation(GetRandomSeed());
}
bool CPed::CheckBraveryFlags(u32 flags) const
{
return GetPedModelInfo()->GetPersonalitySettings().CheckBraveryFlags(flags);
}
bool CPed::CheckCriminalityFlags(u32 flags) const
{
return GetPedModelInfo()->GetPersonalitySettings().CheckCriminalityFlags(flags);
}
bool CPed::CheckSexinessFlags(u32 flags) const
{
return GetPedModelInfo()->CheckSexinessFlags(flags);
}
bool CPed::CheckAgilityFlags(u32 flags) const
{
return GetPedModelInfo()->GetPersonalitySettings().CheckAgilityFlags(flags);
}
CTask* CPed::ComputeDefaultTask(const CPed& ped)
{
CTask *pDefaultTask = 0;
if(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
{
pDefaultTask = ComputeDefaultDrivingTask(ped, ped.GetMyVehicle(), false);
}
#if ENABLE_TASKS_ARREST_CUFFED
else if(m_pCustodianPed && ped.GetPedConfigFlag( CPED_CONFIG_FLAG_IsInCustody))
{
pDefaultTask = rage_new CTaskInCustody(m_pCustodianPed, true);
}
#endif // ENABLE_TASKS_ARREST_CUFFED
else
{
if( ped.IsLocalPlayer() )
{
CTaskMotionBase* pTask = ped.GetCurrentMotionTask();
if (!pTask)
{
StartPrimaryMotionTask();
pTask = GetCurrentMotionTask();
}
aiAssertf(pTask, "No motion task found while computing player control task");
return pTask->CreatePlayerControlTask();
}
// Default parameters for CTaskDoNothing as root task:
const int iDurationMS = -1; // indefinite
const int iNumFramesToRun = 0; // no limit
const bool bMakeMountStandStill = true;
const bool bEnableTimeslicing = true;
CPedGroup* pPedGroup=ped.GetPedsGroup();
if (pPedGroup && ped.GetPedType() == PEDTYPE_SWAT && !ped.PopTypeIsMission())
{
pDefaultTask = rage_new CTaskSwat();
}
else if (pPedGroup && ped.GetPedType() == PEDTYPE_ARMY && !ped.PopTypeIsMission())
{
pDefaultTask = rage_new CTaskArmy();
}
else if(pPedGroup && !pPedGroup->GetGroupMembership()->IsLeader(&ped) && pPedGroup->GetGroupMembership()->GetLeader())
{
pDefaultTask = rage_new CTaskFollowLeaderAnyMeans( pPedGroup, pPedGroup->GetGroupMembership()->GetLeader());
}
else if(ped.PopTypeIsMission())
{
pDefaultTask = rage_new CTaskDoNothing(iDurationMS,iNumFramesToRun,bMakeMountStandStill, bEnableTimeslicing);
}
#if ENABLE_HORSE
else if (m_pSeatManager && m_pSeatManager->GetDriver()) //if I have a rider, just continue letting them drive
{
pDefaultTask = rage_new CTaskDoNothing(iDurationMS,iNumFramesToRun,bMakeMountStandStill, bEnableTimeslicing);
}
#endif
else if(ped.GetPedConfigFlag(CPED_CONFIG_FLAG_DoNothingWhenOnFootByDefault))
{
pDefaultTask = rage_new CTaskDoNothing(iDurationMS,iNumFramesToRun,bMakeMountStandStill, bEnableTimeslicing);
}
else
{
if (m_pDefaultTaskDataSet && m_pDefaultTaskDataSet->GetOnFootData() )
{
switch (m_pDefaultTaskDataSet->GetOnFootData()->m_Type)
{
case WANDER:
pDefaultTask = ComputeWanderTask(ped);
break;
case DO_NOTHING:
pDefaultTask = rage_new CTaskDoNothing(iDurationMS,iNumFramesToRun,bMakeMountStandStill, bEnableTimeslicing);
break;
default:
Assertf(0, "Unknown Default Task Type (%d)\n", m_pDefaultTaskDataSet->GetOnFootData()->m_Type);
pDefaultTask = ComputeWanderTask(ped);
break;
}
}
else
{
pDefaultTask = ComputeWanderTask(ped);
}
}
}
return pDefaultTask;
}
// ------------------------------------------------------------------------------------------------------------
// --- CPed ---------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------------------
// Name : Constructor
// Purpose : Default constructor for CPed class
// Parameters : modelIndex: type of ped, Constructor requires this as it uses this variable in the function
// Returns : Nothing
//
CPed::CPed(const eEntityOwnedBy ownedBy, const CControlledByInfo& pedControlInfo, u32 modelIndex)
: // Initializer list.
CPhysical(ownedBy),
m_nPedType(PEDTYPE_CIVMALE),
m_isInOnFootPedCount(false),
m_isInInVehPedCount(false),
m_bRemoveAsSoonAsPossible(false),
m_delayedRemovalAmountMs(0),// Also set in body, below, by DelayedRemovalTimeReset.
m_delayedRemovalResetTimeMs(0), // Also set in body, below.
m_delayedConversionResetTimeMs(0),// Also set in body, below.
m_delayedConversionAmountMs(0),// Also set in body, below.
m_InFovTime(0),
m_creationTime(0),
m_SavedClanId(0),
m_uiAssociatedCreationData(0),
m_fRemoveRangeMultiplier(1.0f),
m_pExternallyDrivenDOFFrame(NULL),
m_pMyVehicle(NULL),
#if ENABLE_HORSE
m_pMyMount(NULL),
#endif
m_pPedIntelligence(NULL),
m_pPlayerInfo(NULL),
m_inventory(NULL),
m_weaponManager(NULL),
m_facialData(NULL),
m_ragdollConstraintData(NULL),
m_pCapsuleInfo(NULL),
m_pComponentClothInfo(NULL),
m_pComponentSetInfo(NULL),
m_pPhoneComponent(NULL),
m_pHelmetComponent(NULL),
m_pSpeechAudioEntity(NULL),
m_pShockingEvent(NULL),
m_pPedVfx(NULL),
#if ENABLE_HORSE
m_pSeatManager(NULL),
m_pHorseComponent(NULL),
m_pComponentReservationMgr(NULL),
#endif
m_InDangerTimer(0),
m_WeaponBlockingDelay(0),
m_forcePedHasNoMassInImpacts( false ),
m_disableHighFallInstantDeath( false ),
m_iFailedQPedRagdollTests(-1),
m_bUseVehicleBoundingBox(false),
m_lastValidResult(false),
m_bEnableFootLight(false),
m_selectedAbilitySlot(PSAS_PRIMARY),
m_Attacker(NULL),
m_pIKSettings(NULL),
m_pTaskData(NULL),
m_propRequestGfx(NULL),
m_pCachedMotionTask(NULL),
m_iMovementModeType(CPedModelInfo::PersonalityMovementModes::MM_Invalid),
m_iMovementModeIndex(-1),
m_pMovementModes(NULL),
m_uMovementModeWeaponHash(0),
m_iMovementModeOverrideHash(0),
m_iLastActionModeHeadIKTest(0),
m_nActionModeExpiryTime(0),
m_nActionModeForcingRunExpiryTime(0),
m_uForcedRunDelayTime(0),
m_IdleTransitionClipSetID(CLIP_SET_ID_INVALID),
m_bMovementModeApplied(false),
m_MovementModeStreamAnimsThenExit(false),
m_bActionModeIdleActive(false),
m_bIsInPopulationCache(false),
m_bResetPreviousAnimatedVelocity(true),
m_bCachedMotionTaskDirty(true),
m_TimeCapsulePushedByVehicle(0.0f),
m_TimeRampDownCapsulePushedByVehicle(0.0f),
m_TimeCapsuleFirstPushedByPlayerCapsule(0),
m_ClothCollision(NULL),
m_fLodMult(1.f),
m_bPickupInRange(false),
m_bMainBoundIntersectsPedThatCausesActivations(false),
m_NeedToInitAudio(false),
m_CachedSceneUpdateFlags(0),
m_fCurrentHairScale(0.0f),
m_fTargetHairScale(0.0f),
m_fHairHeight(-1.0f),
m_bHairScaleLerp(true),
m_bMpLightEnabled(false),
m_iPhonePaletteIdx(0xff),
m_PackageIndex(0),
m_PoseIndex(0),
m_RagdollGroundProbeResults(8),
m_pActivateRagdollOnCollisionEvent(NULL),
m_pIndependentMoverFrame(NULL),
m_bExitVehicleOnChangeOwner(false),
m_iNumArmouredHelmetHits(0),
m_pVehicleEntryScriptConfig(NULL),
m_bStartOutOfWaterTimer(false),
m_fTimeOutOfWater(0.0f),
m_bEnableCrewEmblem(true),
#if ENABLE_JETPACK
m_pJetpack(NULL),
#endif
m_bPortalTrackerUsingRagdollorRagdolFrame(false),
m_ContactedFoliageHash(g_NullSoundHash),
m_uWeaponAnimationsIndex(0)
#if __ASSERT
, m_LastRagdollControllingTaskType(CTaskTypes::TASK_NONE)
#endif //__ASSERT
#if __BANK
, m_bCachedGestureAnimsAllowed(false)
, m_pDebugPlayerTargetingRejectionString(NULL)
, m_nPlayerTargetingLastRejectedFrame(0)
#endif // __BANK
#if GTA_REPLAY
, m_ClanIdForReplay(RL_INVALID_CLAN_ID)
, m_replayUsingRagdoll(false)
, m_replayIsEnteringVehicle(false)
#endif //GTA_REPLAY
{
// Set the control type (is the an NPC, a local player, or a network player?)
m_isControlledByNetwork = pedControlInfo.IsControlledByNetwork();
m_isControlledByPlayer = pedControlInfo.IsControlledByLocalOrNetworkPlayer();
ClearClothController();
// Mark our entity type.
SetTypePed();
SetVisibilityType( VIS_ENTITY_PED );
DelayedRemovalTimeReset();
DelayedConversionTimeReset();
// Set the ped type (are they civilian, cop, a a member of some gang).
const CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(modelIndex))));
Assert(pModelInfo);
Assertf( (pModelInfo->GetModelType() == MI_TYPE_PED), "Trying to create a ped with non ped model: %s [type=%d]", pModelInfo->GetModelName(), pModelInfo->GetModelType());
// Assertf(pModelInfo->GetDrawable() || pModelInfo->GetFragType(), "%s:Ped model is not loaded", pModelInfo->GetModelName());
const ePedType pedType = pModelInfo->GetDefaultPedType();
SetPedType(pedType);
m_BrawlingStyle = pModelInfo->GetDefaultBrawlingStyle();
m_DefaultUnarmedWeapon = pModelInfo->GetDefaultWeapon();
// Set the capsule info
SetCapsuleInfo(CPedCapsuleInfoManager::GetInfo(pModelInfo->GetPedCapsuleHash().GetHash()));
Assertf(GetCapsuleInfo(), "Ped (%s) can't find CapsuleInfo (%s)", pModelInfo->GetModelName(), pModelInfo->GetPedCapsuleHash().GetCStr());
// Set the cloth set ( it is ok to be NULL )
m_pComponentClothInfo = CPedComponentClothManager::GetInfo(pModelInfo->GetPedComponentClothHash().GetHash());
// Set the component set
m_pComponentSetInfo = CPedComponentSetManager::GetInfo(pModelInfo->GetPedComponentSetHash().GetHash());
Assertf(m_pComponentSetInfo, "Ped (%s) can't find ComponentSetInfo (%s)", pModelInfo->GetModelName(), pModelInfo->GetPedComponentSetHash().GetCStr());
// Set the motion task data set
m_pMotionTaskDataSet = CMotionTaskDataManager::GetDataSet(pModelInfo->GetMotionTaskDataSetHash().GetHash());
Assertf(m_pMotionTaskDataSet, "Ped (%s) can't find MotionTaskDataSet (%s)", pModelInfo->GetModelName(), pModelInfo->GetMotionTaskDataSetHash().GetCStr());
// Set the default task data set
m_pDefaultTaskDataSet = CDefaultTaskDataManager::GetDataSet(pModelInfo->GetDefaultTaskDataSetHash().GetHash());
Assertf(m_pDefaultTaskDataSet, "Ped (%s) can't find DefaultTaskDataSet (%s)", pModelInfo->GetModelName(), pModelInfo->GetDefaultTaskDataSetHash().GetCStr());
// Set the Ped AI owner pointer
m_pedAiLod.SetOwner(this);
memset(m_nActionModeTimes, 0, sizeof(m_nActionModeTimes));
// We need to pass this pointer down as the inventory initialization needs a valid ped model info. At this point, the ped's model index is not set internally.
CPed::Init(false, pModelInfo);
}
void CPed::Init(bool reinit/*=false*/, const CPedModelInfo* pPedModelInfo/*=NULL*/)
{
USE_MEMBUCKET(MEMBUCKET_GAMEPLAY);
RAGE_TRACK(CPed);
pedAssert(CPedFactory::IsCurrentlyWithinFactory());
if (m_pComponentSetInfo->GetHasInventory() && !reinit)
{
pedAssert(m_inventory == NULL);
m_inventory = rage_new CPedInventory();
m_inventory->Init(this, pPedModelInfo);
// Must come after m_inventory as the Init of weapon manager uses the inventory
pedAssert(m_weaponManager == NULL);
m_weaponManager = rage_new CPedWeaponManager();
m_weaponManager->Init(this);
pedAssert( m_weaponManager && m_inventory );
}
// init special abilities
for (int i = 0; i < PSAS_MAX; ++i)
{
m_specialAbilities[i] = NULL;
}
if (m_pComponentSetInfo->GetHasFacial() && !reinit)
{
m_facialData = rage_new CFacialDataComponent();
}
if (m_pComponentSetInfo->GetHasRagdollConstraints() && !reinit)
{
m_ragdollConstraintData = rage_new CRagdollConstraintComponent();
}
m_PlaceableTracker.Init(this);
#if ENABLE_HORSE
if (m_pComponentSetInfo->GetIsRidable() && !reinit)
{
m_pSeatManager = rage_new CSeatManager();
m_pComponentReservationMgr = rage_new CComponentReservationManager();
m_pHorseComponent = rage_new CHorseComponent();
m_pHorseComponent->Init(this);
}
#endif
//m_fCurrentHeading = 0.0f;
m_creationTime = fwTimer::GetTimeInMilliseconds();
m_WoundPrimary.valid = false;
m_WoundSecondary.valid = false;
SetBaseFlag(fwEntity::HAS_PRERENDER);
m_dummyHeightBlendHeight = 0.0f;;
m_dummyHeightBlendStartTimeMs = 0;
m_dummyHeightBlendInProgress = false;
m_pMyVehicle = NULL;
#if ENABLE_HORSE
m_pMyMount = NULL;
#endif
for(u32 i=0; i < kOffsetCount; i++)
{
m_vecOffsets[i] = Vec3V(V_ZERO);
m_cachedBoneIndices[i] = -1;
}
for(int i = 0; i < kRotOffsetCount; ++i)
{
m_rotOffsets[i] = QuatV(V_IDENTITY);
}
m_cachedBoneSignature = 0;
m_DeathInfo.Clear();
m_vDesiredVelocity = VEC3_ZERO;
m_vDesiredAngularVelocity = VEC3_ZERO;
m_vPreviousAnimatedVelocity = VEC3_ZERO;
m_vGroundVelocity = Vec3V(V_ZERO);
m_vGroundVelocityIntegrated = Vec3V(V_ZERO);
m_vGroundAngularVelocity = Vec3V(V_ZERO);
m_vGroundAcceleration = Vec3V(V_ZERO);
m_vGroundOffset = Vec3V(V_ZERO);
m_vGroundNormalLocal = Vec3V(V_ZERO);
m_vGroundVelocityForJump = VEC3_ZERO;
m_pKinematicPed = NULL;
m_pCustodianPed = NULL;
SetGroundPhysical(NULL);
m_pLastValidGroundPhysical = NULL;
m_pTrainRidingOn = NULL;
m_uLastValidGroundPhysicalTime = 0;
m_uTimeGroundPhysicalWasSet = 0xFFFFFFFF;
m_groundPhysicalComponent = 0;
m_lastGroundPhysicalComponent = 0;
m_vecGroundPos.ZeroComponents();
m_vecGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_vecRearGroundPos.ZeroComponents();
m_vecRearGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_vecGroundPosLocal.ZeroComponents();
m_vecGroundPosHistory[0].Zero();
m_vecGroundPosHistory[1].Zero();
m_CurrentSpringForceZ = 0.0f;
CompileTimeAssert(PED_GROUND_POS_HISTORY_SIZE==2);
m_vecSteepSlopePos.Zero();
m_vecGroundNormal.Set(ZAXIS);
m_vecMaxGroundNormal = Vec3V(V_Z_AXIS_WZERO);
m_vecInterpMaxGroundNormal = Vec3V(V_Z_AXIS_WZERO);
m_vecRearGroundNormal = Vec3V(V_Z_AXIS_WZERO);
m_vSlopeNormal.Set(ZAXIS);
m_fGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_bValidGroundNormal = false;
m_bIsStandingOnMovableObject = false;
m_bKickedByPlayer = false;
m_bPreviouslyKickedByPlayer = false;
m_bHitByDoor = false;
m_bPreviouslyHitByDoor = false;
m_uRagdollPlayerLegContactTime = 0;
m_uRagdollDoorContactTime = 0;
m_fRearGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_fGroundOffsetForPhysics = PED_GROUNDPOS_RESET_Z;
m_fHeightFudgeRecoveryFactor[0] = m_fHeightFudgeRecoveryFactor[1] = 0.0f;
m_fFallingHeight = 0.0f;
m_nInteriorFreeze_WasInsideCounter = 0;
m_fTimeSinceLastShotFired = 0.1f;
m_fCurrentMainMoverCapsuleRadius = 0.0f;
m_fOverrideCapsuleRadiusGrowSpeed = 0.0f;
m_fDesiredMainMoverCapsuleRadius = 0.0f;
m_fCurrentCapsultHeightOffset = 0.0f;
m_bMoverCapsuleIsChanging = false;
m_bResizePlayerLegBound = false;
m_modelLodIdx = 0;
m_uSuppressKinematicModeTimer = 0;
#if LAZY_RAGDOLL_BOUNDS_UPDATE
m_bRagdollBoundsUpToDate = false;
m_bRagdollBoundsUpdateRequestFrames = 0;
#endif
m_arrestState = ArrestState_None;
m_deathState = DeathState_Alive;
m_deathTime = 0;
m_bDeathTimeHasSet = false;
m_weaponDamageComponent = -1;
m_nAmmoDiminishingCount = 0;
m_TimeOfFirstBeingUnderAnotherRagdoll = 0;
m_fCustodyFollowDistanceOverride = 0.0f;
m_damageSetID=kInvalidPedDamageSet;
m_compressedDamageSetID=kInvalidPedDamageSet;
m_heatScaleOverride=0;
m_PedConfigFlags.Init(this);
m_PedResetFlags.Init(this);
m_DamagedBodyParts = DAMAGED_NONE;
// Register this ped with the audio stuff
m_bVoiceGroupSelected = false;
m_selectedVoiceGroup = 0;
m_LastTimeStartedAutoFireSound = 0;
m_LastTimeWeBargedThroughDoor = 0;
if(!reinit)// !m_PedAudioEntity.IsInitialized()
{
// Peds can be created due to bullet impacts in PreRender when a vehicle explodes, and we can't safely init audio entities/environment groups
// on the main thread while the audio update is running - in that situation, defer initialisation until the first update frame.
if(audNorthAudioEngine::IsAudioUpdateCurrentlyRunning())
{
m_NeedToInitAudio = true;
}
else
{
InitAudio();
}
}
else
{
// Adding asserts to try and catch B* 1699247
naAssertf(m_PedAudioEntity.IsInitialised(),"ReInitializing a ped with an uninitialize audio entity");
naAssertf(m_PedAudioEntity.GetOwningPed() == this,"Wrong ped reference, ped : %p, referenced ped : %p",this,m_PedAudioEntity.GetOwningPed());
}
m_SpeakerSpeechAudioEntity = NULL;
m_pGestureClip = NULL;
m_GestureFilterId = FILTER_ID_INVALID;
m_pGestureData = NULL;
if(reinit && m_WaveslotHeldForGestures)
{
m_WaveslotHeldForGestures->RemoveReference();
}
m_WaveslotHeldForGestures = NULL;
m_GestureClipSet = CLIP_SET_ID_INVALID;
m_GestureContext = GC_DEFAULT;
m_pVisemeBodyAdditiveClip = NULL;
m_fVisemeBodyAdditiveWeight = 0.0f;
m_mCurrentWeaponLeftGripBoneOffset = Matrix34::ZeroType;
m_fElapsedBlendOutTime = 0.0f;
// Pointer to a speaking ped (or global speech audio entity) that this ped is listening to.
// We might play facial animations and/or gestures based on what the speaking ped is saying.
m_SpeakerListenedTo = NULL;
m_GlobalSpeakerListenedTo = NULL;
m_fPrevVisemeTime = 0.0f;
m_fVisemeDuration = 0.0f;
m_bVisemePlaying = false;
m_uPrevConversationLookAtTime = 0;
m_uConversationLookAtWaitTime = 0;
m_bWasSpeaking = false;
m_bLookingRandomPosition = false;
m_bLookingCarMovingDirection = false;
m_uStickyCount = 0;
m_uStickToPedProjCount = 0;
m_uFlareGunProjCount = 0;
m_MoneyCarried = 0;
m_pCoverPoint = NULL;
m_pDesiredCoverPoint = NULL;
#if FPS_MODE_SUPPORTED
m_fLowCoverHeightOffsetFromMover = -1.0f;
#endif
m_fTargetableDistance = 0.0f;
m_fTargetThreatOverride = -1.0f;
m_vLocalOffsetToCoverPoint.Zero();
m_nDamageDelayTimer = 0;
if(!reinit)
m_pPedIntelligence = CPedIntelligenceFactory::GetFactory()->Create(this);
SetCharParamsBasedOnManagerType();
m_RagdollGroundProbeResults.Reset();
m_pPlayerInfo = NULL;
if(!reinit)
{
m_pAnimatedInst = NULL;
m_pRagdollInst = NULL;
}
#if __ASSERT
else
{
Assert(m_pAnimatedInst);
Assert(m_pRagdollInst);
}
#endif
SetRagdollStateInternal(RAGDOLL_STATE_ANIM_PRESETUP);
if(!reinit)
{
//setup the tasktrees for the ped
m_pPedIntelligence->GetTaskManager()->SetTree(PED_TASK_TREE_PRIMARY, rage_new CTaskTreePed(PED_TASK_PRIORITY_MAX, this, PED_TASK_PRIORITY_EVENT_RESPONSE_TEMP, PED_TASK_PRIORITY_DEFAULT));
m_pPedIntelligence->GetTaskManager()->SetTree(PED_TASK_TREE_MOVEMENT, rage_new CTaskTreeMovement(PED_TASK_MOVEMENT_MAX, this, PED_TASK_MOVEMENT_GENERAL, PED_TASK_MOVEMENT_EVENT_RESPONSE, PED_TASK_MOVEMENT_DEFAULT));
m_pPedIntelligence->GetTaskManager()->SetTree(PED_TASK_TREE_SECONDARY, rage_new CTaskTree(this, PED_TASK_SECONDARY_MAX));
m_pPedIntelligence->GetTaskManager()->SetTree(PED_TASK_TREE_MOTION, rage_new CTaskTreeMotion(this, PED_TASK_MOTION_MAX));
// at least give the ped a default task
m_pPedIntelligence->AddTaskDefault(rage_new CTaskDoNothing(-1));
m_pPedIntelligence->GetTaskManager()->SetTask(PED_TASK_TREE_MOVEMENT, rage_new CTaskMoveStandStill(), PED_TASK_MOVEMENT_DEFAULT);
}
m_StreamedScriptBrainToLoad = -1;
#if !__FINAL
m_SayWhat = -1;
m_StartTimeSay = -1;
m_debugPedName[0] = '\0';
#endif
m_fAttachHeading = 0.0f;
m_fAttachHeadingLimit = 0.0f;
m_nAttachCarSeatIndex = -1;
m_nAttachCarEntryExitPoint = -1;
m_SlideSpeed = 0.0f;
m_RagdollCorpseFriction = -1.0f;
m_iPedGroupIndex = PEDGROUP_INDEX_NONE;
m_lastSignificantShotBoneTag = -1;
m_pLastPedShoved = NULL;
m_nLastPedShoveTime = 0;
m_pPrimaryLookAt = NULL;
m_pSecondaryLookAt = NULL;
// Create the mobile phone component
if (m_pComponentSetInfo->GetHasPhone() && !reinit)
m_pPhoneComponent = rage_new CPedPhoneComponent();
// Create the helmet component
if (m_pComponentSetInfo->GetHasHelmet() && !reinit)
m_pHelmetComponent = rage_new CPedHelmetComponent(this);
ResetWaterTimers();
m_OverrideMaxTimeInWater = -1;
m_fTimeSincePedInWater = 60.0f;
m_fWindyClothingScale = 0.0f;
m_fScriptCapsuleRadius = 0.0f;
m_fTaskCapsuleRadius = 0;
m_BlockCapsuleResizeTime = 0;
m_ProcessBoundsCountdown = 0;
m_bPedCapsuleFullStop = false;
m_bNewGesture = false;
ClearWetClothing();
m_uRenderDelayFlags = 0;
ASSERT_ONLY(m_uRenderFlagFrameCount = 0);
m_ShaveTimeInSeconds=-1;
m_HurtEndTime = 0;
if(!reinit)
{
m_pPhysicsArchetype = NULL; // don't set this to null on reinit, or we'll leak it!
}
m_PackedGroundMaterialId = 0;
SetStairFlags(NULL, NULL, 0, 0);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
m_secondSurfaceDepth = 0.0f;
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
m_deepSurfaceInfo.Init();
m_RotationalConstraintX.Reset();
m_RotationalConstraintY.Reset();
m_CylindricalConstraint.Reset();
m_fSpringForceFront = 0.0f;
m_fSpringForceBack = 0.0f;
m_fCurrentBoundPitch = 0.0f;
m_fCurrentBoundHeading = 0.0f;
m_vCurrentBoundOffset.Zero();
m_bHidingHelmetInVehicle = false;
m_bKeepingCurrentHelmetInVehicle = false;
if (m_pComponentSetInfo->GetHasShockingEventResponse() && !reinit)
m_pShockingEvent = rage_new CPedShockingEvent(); //optional component
DelayedRemovalTimeReset();
DelayedConversionTimeReset();
m_InFovTime = 0;
m_SavedClanId = 0;
m_fAccumulatedFire = 0.0f;
// Always update ped anims
m_nDEflags.bForcePrePhysicsAnimUpdate = true;
ASSERT_ONLY(m_PreRenderLock = false;)
m_WaitingForSpeechToPreload = false;
m_MinOnGroundTimeForStunGun = -1;
m_PedFootStepHelper.Init(this);
m_dirtColRedf = 0.f;
m_dirtColGreenf = 0.f;
m_dirtColBluef = 0.f;
m_dirtColAlphaf = 0.f;
m_dirtColScale = 0.f;
m_dirtColScaleCustom = 0.f;
m_sweatScale = 0.0f;
m_bKeepInactiveRagdollContacts = false;
m_bShouldActivateRagdollOnCollision = false;
m_bAllowedRagdollFixed = true;
ClearActivateRagdollOnCollisionEvent();
m_fAllowedRagdollPenetration = 0.0f;
m_fAllowedRagdollSlope = ms_fActivateRagdollOnCollisionDefaultAllowedSlope;
m_iAllowedRagdollPartsMask = 0xFFFFFFFF;
SetPedResetFlag(CPED_RESET_FLAG_FirstPhysicsUpdate, true);
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollOnPedCollisionWhenDead, false);
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollOnVehicleCollisionWhenDead, false);
SetPedConfigFlag(CPED_CONFIG_FLAG_AllowedToDetachTrailer, true);
SetPedConfigFlag(CPED_CONFIG_FLAG_WillTakeDamageWhenVehicleCrashes, true);
SetPedConfigFlag(CPED_CONFIG_FLAG_ThrownFromVehicleDueToExhaustion, false);
SetPedConfigFlag(CPED_CONFIG_FLAG_RagdollingOnBoat, false);
// For now default all ambient population to ignore auto open doors
if( PopTypeGet() == POPTYPE_RANDOM_AMBIENT )
SetPedConfigFlag(CPED_CONFIG_FLAG_IgnoredByAutoOpenDoors, true);
if (NetworkInterface::IsGameInProgress())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_PhoneDisableTextingAnimations, true);
SetPedConfigFlag(CPED_CONFIG_FLAG_PhoneDisableTalkingAnimations, true);
}
#if __ASSERT
m_CanUseRagdollSuccessfull = false;
m_uLastRagdollInfoTime = 0;
#endif //__ASSERT
#if DEBUG_DRAW
m_bDrownDisabledByScript = false;
#endif
m_bRenderAoBlobs = true;
SetDesiredRagdollPool(CTaskNMBehaviour::kRagdollPoolInvalid);
SetCurrentRagdollPool(CTaskNMBehaviour::kRagdollPoolInvalid);
m_pCarJacker = NULL;
m_PushedByObjectInCoverFrameCount = 0;
m_SimPhysForLowLodMtrxMulCtr = 0;
m_bExitVehicleOnChangeOwner = false;
for(u32 i = 0; i < LOD_COUNT; i++)
{
m_renderBucketInfoPerLODs[i].m_bLodBucketInfoExists = false;
m_renderBucketInfoPerLODs[i].m_bLodHasAlpha = false;
m_renderBucketInfoPerLODs[i].m_bLodHasDecal = false;
m_renderBucketInfoPerLODs[i].m_bLodHasCutout = false;
}
m_specialNetworkLeaveTimeRemaining = 0;
m_iAlphaOverride = 255; //default the initial visibility override to 100% seen with no alpha
m_specialNetworkLeaveActive = false;
m_specialNetworkLeaveWhenDead = false;
if (reinit)
{
DeleteVehicleEntryConfig();
#if __BANK
if(m_pDebugPlayerTargetingRejectionString)
{
delete m_pDebugPlayerTargetingRejectionString;
m_pDebugPlayerTargetingRejectionString = NULL;
}
m_nPlayerTargetingLastRejectedFrame = 0;
#endif
}
#if ENABLE_JETPACK
m_pJetpack = NULL;
#endif
BANK_ONLY(AssignDebugObjectID());
m_uWeaponAnimationsIndex = 0;
#if __BANK
for(int i = 0; i < 32; i++)
{
m_lastAddPhysicsCallstack[i] = 0;
}
m_lastLevelIndex = -1;
#endif
} // end - CPed::CPed
void CPed::InitAudio()
{
m_PedAudioEntity.Init(this);
if (m_pComponentSetInfo->GetHasSpeech())
{
m_pSpeechAudioEntity = rage_new audSpeechAudioEntity();
m_pSpeechAudioEntity->Init(this);
}
m_NeedToInitAudio = false;
}
// Name : Destructor
// Purpose : Default destructor for CPed class
// Parameters : None
// Returns : Nothing
CPed::~CPed()
{
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnDestroyOfFocusEntity(), this );
#if __DEV
Assertf(CPedFactory::IsCurrentlyInDestroyPedFunction(), "Deleting a ped from outside of the factory destroy method, this will cause problems for networked peds");
#endif
if( HasEnabledFixFor7458166() )
{
ClearClothController();
}
if(!Verifyf(!GetOwnerEntityContainer(), "Ped 0x%p (%s) should not be in the scene graph at point of destruction. Remove from world first. Fixed that for you...", this, GetModelName()))
{
CGameWorld::Remove(this);
}
Assertf(!GetIsRetainedByInteriorProxy(), "Ped 0x%p (%s) should not be on the retain list at point of destruction. Remove from world first", this, GetModelName());
Assertf(!CPedFactory::GetFactory()->IsPedInDestroyedCache(this), "Ped 0x%p (%s) is eing destroyed but it's also present in the population cache!", this, GetModelName());
pedAssert(!m_spatialArrayNode.IsInserted());
CTaskNMBehaviour::RemoveFromRagdollPool(*this);
GetPedModelInfo()->RemovePedInstance(this);
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
// Account for counting this model ref in the reuse pool
GetPedModelInfo()->SetNumTimesInReusePool(GetPedModelInfo()->GetNumTimesInReusePool() - 1);
}
fwCameraRelativeExtension::RemoveExtension(*this);
WaitForAnyActiveAnimUpdateToComplete(false);
//RAGE CReplay::RecordPedDeleted(this);
ClearPedBrainWhenDeletingPed();
// peds created by a ped factory must have CPedFactory::Destroy() called on them
Assert(!GetPedConfigFlag( CPED_CONFIG_FLAG_CreatedByFactory ));
Assert(!IsInOnFootPedCount()); // Make sure it is already removed from the population.
delete m_pPhoneComponent;
m_pPhoneComponent = NULL;
delete m_pHelmetComponent;
m_pHelmetComponent = NULL;
if( m_ClothCollision )
{
m_ClothCollision->Shutdown();
m_ClothCollision = NULL;
}
if (m_OverhangConstraint.IsValid())
{
PHCONSTRAINT->Remove( m_OverhangConstraint );
m_OverhangConstraint.Reset();
}
ClearCurrentPhysicsInst();
// force the ragdoll state to avoid asserts elsewhere (when removing the ragdoll from the world)
SetRagdollStateInternal(RAGDOLL_STATE_ANIM);
if(m_pAnimatedInst)
{
if(m_pAnimatedInst->IsInLevel())
{
DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS);
m_RotationalConstraintX.Reset();
m_RotationalConstraintY.Reset();
m_CylindricalConstraint.Reset();
CPhysics::GetSimulator()->DeleteObject(m_pAnimatedInst->GetLevelIndex());
}
delete m_pAnimatedInst;
m_pAnimatedInst = NULL;
}
if(m_pRagdollInst)
{
m_pRagdollInst->SetInstFlag(phInstGta::FLAG_BEING_DELETED, true);
DetachFromParentAndChildren(DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS);
// these fragment instances were created by the game code
if(m_pRagdollInst->GetInserted() && CPhysics::GetFragManager())
{
// NOTE: remove non-drawable cloth part from ped variation data - svetli
CPedVariationData& pedVarData = this->GetPedDrawHandler().GetVarData();
pedVarData.RemoveAllCloth();
CPedStreamRenderGfx* pRenderGfx = this->GetPedDrawHandler().GetPedRenderGfx();
if( pRenderGfx )
pRenderGfx->DisableAllCloth();
m_pRagdollInst->Remove();
}
else
Assert(0);
delete m_pRagdollInst;
m_pRagdollInst = NULL;
}
if(m_pPhysicsArchetype)
{
m_pPhysicsArchetype->Release();
m_pPhysicsArchetype = NULL;
}
RemoveBlip(BLIP_TYPE_CHAR);
//RAGE CConversations::RemoveConversationForPed(this);
m_pGroundPhysical = NULL;//SetGroundPhysical(NULL); // Don't call function so we can avoid ground attachment assert. The ground attachment is cleaned up in ~CPhysical
m_pLastValidGroundPhysical = NULL;
m_pTrainRidingOn = NULL;
m_groundPhysicalComponent = 0;
m_lastGroundPhysicalComponent = 0;
m_pMyVehicle = NULL;
#if ENABLE_HORSE
m_pMyMount = NULL;
#endif
ReleaseCoverPoint();
// m_PedWeapons.SetWeaponObject(-1);//Handles deletion of the m_pWeaponObject.
// m_PedWeapons.ClearWeapons();
if(m_pPedIntelligence)
{
delete m_pPedIntelligence;
m_pPedIntelligence = NULL;
}
if(m_weaponManager)
{
delete m_weaponManager;
m_weaponManager = NULL;
}
if(m_inventory)
{
delete m_inventory;
m_inventory = NULL;
}
if(m_facialData)
{
delete m_facialData;
m_facialData = NULL;
}
if(m_ragdollConstraintData)
{
delete m_ragdollConstraintData;
m_ragdollConstraintData = NULL;
}
// Don't actually delete this pointer as we don't own it
SetCapsuleInfo(NULL);
// Don't actually delete this pointer as we don't own it
m_pComponentSetInfo = NULL;
m_pComponentClothInfo = NULL;
// clean up ALL special abilities
for (int i = 0; i < PSAS_MAX; ++i)
{
if (m_specialAbilities[i])
{
CPlayerSpecialAbilityManager::Destroy(m_specialAbilities[i]);
m_specialAbilities[i] = NULL;
}
}
if(m_pPedVfx)
{
delete m_pPedVfx;
m_pPedVfx = NULL;
}
#if ENABLE_HORSE
if(m_pSeatManager)
{
delete m_pSeatManager;
m_pSeatManager = NULL;
}
if (m_pHorseComponent)
{
delete m_pHorseComponent;
m_pHorseComponent = NULL;
}
if (m_pComponentReservationMgr)
{
delete m_pComponentReservationMgr;
m_pComponentReservationMgr = NULL;
}
#endif
if(m_pGestureData)
BlendOutGestures(false);
// Remove any listener references
RemoveSpeakerListenedTo();
RemoveSpeakerListenedToSecondary();
if (m_pSpeechAudioEntity)
{
delete m_pSpeechAudioEntity;
m_pSpeechAudioEntity = NULL;
}
// Reset the the primary and secondary look ats
m_pPrimaryLookAt = NULL;
m_pSecondaryLookAt = NULL;
// if (m_pPedStreamGfx){
// delete m_pPedStreamGfx;
// m_pPedStreamGfx = NULL;
// }
ReleaseDamageSet();
delete m_pShockingEvent;
m_pShockingEvent = NULL;
SetPropRequestGfx(NULL);
SetActivateRagdollOnCollision(false);
ClearActivateRagdollOnCollisionEvent();
DeleteExternallyDrivenDOFs();
DeleteVehicleEntryConfig();
REPLAY_ONLY(CReplayMgr::OnDelete(this));
#if __ASSERT
ms_RagdollStackCollector.UnregisterTag( CalcRagdollStackKey(kStackPrepareForActivation) );
ms_RagdollStackCollector.UnregisterTag( CalcRagdollStackKey(kStackSwitchToRagdoll) );
#endif //__ASSERT
if( !HasEnabledFixFor7458166() )
{
ClearClothController();
}
if (m_pIndependentMoverFrame)
{
m_pIndependentMoverFrame->Release();
m_pIndependentMoverFrame = NULL;
}
#if ENABLE_JETPACK
DestroyJetpack();
#endif
#if __BANK
if(m_pDebugPlayerTargetingRejectionString)
{
delete m_pDebugPlayerTargetingRejectionString;
m_pDebugPlayerTargetingRejectionString = NULL;
}
m_nPlayerTargetingLastRejectedFrame = 0;
#endif
} // end - CPed::~CPed
void CPed::ClearClothController()
{
for( int i = 0; i < PV_MAX_COMP; ++i)
m_CClothController[i] = NULL;
}
#if !__NO_OUTPUT
void CPed::PrintSkeletonData()
{
size_t bytes = 0;
Displayf("Name, Num_Bones, Bytes");
for (s32 i = 0; i < CPed::GetPool()->GetSize(); ++i)
{
CPed* pEntity = CPed::GetPool()->GetSlot(i);
if (pEntity)
{
pEntity->PrintSkeletonSummary();
bytes += pEntity->GetSkeletonSize();
}
}
Displayf("Total CPed Skeletons: %" SIZETFMT "d KB\n", bytes >> 10);
}
#endif
bool CPed::HasEnabledFixFor7458166()
{
return !ms_DisableFixFor7458166 && !PARAM_disableFix7458166.Get();
}
bool CPed::HasEnabledFixFor7459244()
{
return !ms_DisableFixFor7459244 && !PARAM_disableFix7459244.Get();
}
void CPed::ClearPedBrainWhenDeletingPed()
{
if (GetPedConfigFlag( CPED_CONFIG_FLAG_WaitingForScriptBrainToLoad ))
{
strLocalIndex id = g_StreamedScripts.FindSlot(CTheScripts::GetScriptsForBrains().GetScriptFileName(m_StreamedScriptBrainToLoad));
Assertf(id != -1, "%s:CPed::~CPed - Brain Script doesn't exist", CTheScripts::GetScriptsForBrains().GetScriptFileName(m_StreamedScriptBrainToLoad));
CStreaming::SetMissionDoesntRequireObject(id, g_StreamedScripts.GetStreamingModuleId());
SetPedConfigFlag( CPED_CONFIG_FLAG_WaitingForScriptBrainToLoad, false );
CTheScripts::GetScriptBrainDispatcher().RemoveWaitingPed(this, m_StreamedScriptBrainToLoad);
m_StreamedScriptBrainToLoad = -1;
}
}
// Name : InitSystem
// Purpose : Init CPed stuff
// Parameters : None
// Returns : Nothing
void CPed::InitSystem()
{
Displayf("Initialising CPed...\n");
CPedDamageManager::Init();
PedDecorationManager::ClassInit();
PedHeadshotManager::Init();
CCustomShaderEffectPed::InitClass();
CPedGroups::Init();
CTaskSequences::Init();
CPedResetFlags::StaticInit();
#if DEBUG_DRAW && !__PROFILE
CPedDebugVisualiserMenu::Initialise();
#endif
CAi::Init();
CPedVariationStream::InitSystem();
CPedPropsMgr::InitSystem();
int numPoolElements = (CPed::GetPool()->GetSize() + 3) & ~0x3;
pedAssert(!ms_spatialArray);
char* spatialArrayStorage = rage_aligned_new(16) char[CSpatialArray::kSizePerNode * numPoolElements];
ms_spatialArray = rage_new CSpatialArray(spatialArrayStorage, numPoolElements);
#if !__FINAL
if (PARAM_disableHurtCombat.Get())
ms_bUseHurtCombat = false;
#endif
CPedFootStepHelper::InitClass();
} // end - CPed::Initialise
//
// name: CPed::ShutdownSystem
// description: Shutdown ped related modules
void CPed::ShutdownSystem()
{
CPedGroups::Shutdown();
CTaskSequences::Shutdown();
#if DEBUG_DRAW && !__PROFILE
CPedDebugVisualiserMenu::Shutdown();
#endif
CAi::Shutdown();
CPedVariationStream::ShutdownSystem();
CPedPropsMgr::ShutdownSystem();
CPedDamageManager::Shutdown();
PedDecorationManager::ClassShutdown();
PedHeadshotManager::Shutdown();
delete ms_spatialArray;
ms_spatialArray = NULL;
} // end - CPed::Shutdown
void CPed::Init(unsigned initMode)
{
if(initMode == INIT_SESSION)
{
CPedVariationPack::Initialise();
CPedVariationStream::Initialise();
#if __BANK
CPedVariationDebug::Initialise();
#endif // __BANK
//set up motivations
if (CStreaming::ShouldLoadStaticData())
{
CPedMotivationInfoManager::Init();
}
CPedPropsMgr::Init();
// set up ped type information
CRelationshipManager::Init();
ms_MoneyCarriedByAllNewPeds = -1;
ms_HealthInSnackCarriedByAllNewPeds = 0;
ms_ProbabilityPedsWillDropHealthSnacks = 0.0f;
CEventGunShot::ResetLastTimeShotFired();
CEventResponseFactory::ResetNextNiceCarPictureTimeMS();
if (CStreaming::ShouldLoadStaticData())
{
sPedAccuracyModifiers::Init();
}
}
}
void CPed::Shutdown(unsigned shutdownMode)
{
if(shutdownMode == SHUTDOWN_SESSION)
{
CPedVariationPack::Shutdown(shutdownMode);
CPedPropsMgr::Shutdown();
if (CStreaming::GetReloadPackfilesOnRestart())
{
CPedMotivationInfoManager::Shutdown();
}
// remove ped type information
CRelationshipManager::Shutdown();
}
if (shutdownMode == SHUTDOWN_CORE)
{
CPedMotivationInfoManager::Shutdown();
}
}
#if __BANK
void CPed::InitBank()
{
#if DEBUG_DRAW && !__PROFILE
CPedDebugVisualiserMenu::InitBank();
#endif
if(ms_pBank)
{
ShutdownBank();
}
// Create the weapons bank
ms_pBank = &BANKMGR.CreateBank("Peds", 0, 0, false);
if(weaponVerifyf(ms_pBank, "Failed to create Peds bank"))
{
ms_pCreateButton = ms_pBank->AddButton("Create Peds widgets", &CPed::CreateBank);
}
}
void setLODDistanceCB(void)
{
CPedTuning::SetLodThresholds(TweakLodThreshold, TweakLowLodThreshold, TweakSuperLodThreshold, TweakInCarLodThreshold);
}
void showPedHDWireframeCB(void)
{
if (bShowPedHDWireframe)
{
CRenderPhaseDebugOverlayInterface::TogglePedHDModeSetState(0); // clear settings
CRenderPhaseDebugOverlayInterface::TogglePedHDModeSetState(1); // set to 'wireframe'
bShowPedHDSolid = false;
}
else
{
CRenderPhaseDebugOverlayInterface::TogglePedHDModeSetState(0);
}
}
void showPedHDSolidCB(void)
{
if (bShowPedHDSolid)
{
CRenderPhaseDebugOverlayInterface::TogglePedHDModeSetState(0); // clear settings
CRenderPhaseDebugOverlayInterface::TogglePedHDModeSetState(2); // set to 'solid'
bShowPedHDWireframe = false;
}
else
{
CRenderPhaseDebugOverlayInterface::TogglePedHDModeSetState(0);
}
}
void setLODMultiplierCB(void)
{
CEntity *pEntity = static_cast<CEntity*>(g_PickerManager.GetSelectedEntity());
if (pEntity && pEntity->GetIsTypePed())
((CPed*)pEntity)->SetLodMultiplier(fLodMultiplier);
}
void DisableEnableRagdollWeaponCollisionMaskCB(void)
{
if (CPed::ms_RagdollWeaponDisableCollisionMask != 0)
{
CPed::ms_RagdollWeaponDisableCollisionMask = 0;
}
else
{
CPed::ms_RagdollWeaponDisableCollisionMask = UINT_MAX;
}
}
void CPed::CreateBank()
{
aiAssertf(ms_pBank, "Peds bank needs to be created first");
if(ms_pCreateButton)//delete the create bank button
{
ms_pCreateButton->Destroy();
ms_pCreateButton = NULL;
}
else
{
//bank must already be setup as the create button doesn't exist so just return.
return;
}
bkBank& bank = *ms_pBank;
CPedDamageManager::AddWidgets(bank);
CPedAcquaintanceScanner::AddWidgets(bank);
CCollisionEventScanner::AddRagdollDamageWidgets(bank);
CCollisionEventScanner::AddKnockOffVehicleWidgets(bank);
PEDDECORATIONMGR.AddWidgets(bank);
PEDHEADSHOTMANAGER.AddWidgets(bank);
CPedPropsMgr::SetupWidgets(bank);
CPedVariationDebug::SetupWidgets(bank);
CPlayerSpecialAbilityManager::AddWidgets(bank);
CBrawlingStyleManager::AddWidgets(bank);
CPedModelInfo::AddWidgets(bank);
CPedFootStepHelper::AddWidgets(bank);
InitDebugData(); // no leak
//TMS: This will become per quadruped tuning
s_QuadrupedSpringTuning.AddWidgets(bank);
//Horse control tuning
#if ENABLE_HORSE
bank.PushGroup("Horse Tuning");
HRSSIMTUNEMGR.AddWidgets(bank);
bank.PopGroup();
#endif
// -- ped rendering group -- //
{
bank.PushGroup("Ped Rendering", false);
#if __DEV
bank.AddToggle("Display peds (on VectorMap)", &CDebugScene::bDisplayPedsOnVMap);
bank.AddToggle("Display spawn point raw density (on VM)", &CDebugScene::bDisplaySpawnPointsRawDensityOnVMap);
bank.AddToggle("Display ped population events (on VM)", &CDebugScene::bDisplayPedPopulationEventsOnVMap);
bank.AddToggle("Display peds to stream out (on VM)", &CDebugScene::bDisplayPedsToBeStreamedOutOnVMap);
bank.AddToggle("Display candidate scenario points (on VM)", &CDebugScene::bDisplayCandidateScenarioPointsOnVMap);
// bank.AddToggle("Display ped model names",&CDebugScene::bDisplayPedModelNames);
#endif //__DEV
bank.AddSlider("radius for capsule",&g_CapsuleRadius,0.001f,0.3f,0.01f);
bank.AddToggle("Show LOD level data", &bShowPedLODLevelData);
bank.AddToggle("Show rendered LOD", &bShowRenderPedLOD);
bank.AddToggle("Show HD peds in wireframe", &bShowPedHDWireframe, showPedHDWireframeCB);
bank.AddToggle("Show HD peds in solid", &bShowPedHDSolid, showPedHDSolidCB);
bank.AddSlider("Ped Hi LOD distance", &TweakLodThreshold, 1, 25, 1, setLODDistanceCB);
bank.AddSlider("Ped Low LOD distance", &TweakLowLodThreshold, 15, 60, 2, setLODDistanceCB);
bank.AddSlider("Ped super LOD distance", &TweakSuperLodThreshold, 20, 100, 5, setLODDistanceCB);
bank.AddSlider("Ped in car LOD distance", &TweakInCarLodThreshold, 4, 20, 1, setLODDistanceCB);
bank.AddToggle("Show missing low LODs",&bShowMissingLowLOD);
bank.AddToggle("Override LOD fade value",&bOverrideLODFadeValue);
bank.AddSlider("LOD fade value", &ForceLODFadeValue, 0, 255, 1);
bank.AddToggle("Show visibility query results",&bShowVisibilityQueryResult);
bank.AddToggle("Show ped visibility stats",&bShowPedsVisibilityTrackingStats);
bank.AddToggle("Show ped visibility bounding box",&bShowPedsVisibilityBoundingBox);
bank.AddToggle("Test visibility query",&bTestVisibilityQuery);
bank.AddSlider("Lod multiplier", &fLodMultiplier, 0.1f, 10.f, 0.01f, setLODMultiplierCB);
bank.AddToggle("Override ped wetness", &gPedWetnessLevelOverrideEnabled);
bank.AddSlider("Ped wetness amount", &gPedWetnessLevelOverrideAmount, 0.0f, 1.0f, 0.01f);
bank.AddToggle("First person camera space enabled?", &g_bEnableFPV);
bank.AddToggle("Render initial spawn point", &CPedDebugVisualiserMenu::ms_menuFlags.m_bRenderInitialSpawnPoint);
{
bank.PushGroup("Ped Wrinkle Map", false);
bank.AddToggle("Enable Sliders",&CCustomShaderEffectPed::ms_wrinkleEnableSliders);
bank.AddToggle("Override Anim", &CCustomShaderEffectPed::ms_wrinkleEnableOverride);
// Make sure order and id matches s_wrinkleTrans, in CustomShaderEffectPed.cpp
CCustomShaderEffectPed::ms_wrinkleDebugComponentStrings.Grow() = "PV_COMP_HEAD";
CCustomShaderEffectPed::ms_wrinkleDebugComponentStrings.Grow() = "PV_COMP_UPPR";
CCustomShaderEffectPed::ms_wrinkleDebugComponentStrings.Grow() = "PV_COMP_LOWR";
bank.AddCombo("Ped component", &CCustomShaderEffectPed::ms_wrinkleDebugComponent, CCustomShaderEffectPed::ms_wrinkleDebugComponentStrings.GetCount(), &CCustomShaderEffectPed::ms_wrinkleDebugComponentStrings[0]);
char buffer[256];
for(int i=0; i<NELEM(CCustomShaderEffectPed::ms_wrinkleMaskStrengths); i++)
{
sprintf(buffer,"wrinkle mask strengths %d",(i));
bank.AddSlider(buffer,&CCustomShaderEffectPed::ms_wrinkleMaskStrengths[i],0.0f,1.0f,0.1f);
}
bank.PopGroup();
}
#if CSE_PED_EDITABLEVALUES
CCustomShaderEffectPed::InitWidgets(bank);
#endif
bank.PopGroup();
}
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
bank.PushGroup("Ped second surface config");
bank.PushGroup("Ragdoll");
ms_ragdollSecondSurfaceConfig.AddWidgetsToBank(bank);
bank.PopGroup();
bank.PushGroup("Animated");
ms_pedSecondSurfaceConfig.AddWidgetsToBank(bank);
bank.PopGroup();
bank.PopGroup();
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
bank.PushGroup("Glass collision");
bank.AddToggle("Toggle player ragdoll glass collision", &ms_bToggleCollidesAgainstGlassRagdoll);
bank.AddToggle("Toggle player weapon glass collision", &ms_bToggleCollidesAgainstGlassWeapon);
bank.PopGroup();
bank.PushGroup("Mover collision");
bank.AddToggle("Allow force flattening", &ms_bAllowNormalFlattening);
bank.AddToggle("Only flatten against movable bounds", &ms_bOnlyFlattenMovable);
bank.AddSlider("Min depth for flattening",&ms_bNormalFlatteningMinDepth,0.0f, 0.2f, 0.01f);
bank.AddSlider("Max depth for flattening",&ms_bNormalFlatteningMaxDepth,0.0f, 0.2f, 0.01f);
bank.AddToggle("Allow normal verticalizing", &ms_bAllowNormalVerticalizing);
bank.AddSlider("Normal verticalizing factor", &ms_fNormalVerticalizingFactor, 0.1f, 5.0f, 0.01f);
bank.AddSlider("Large foliage size", &CPed::ms_fLargeFoliageRadius, 0.0f, 100.0f, 0.01f);
bank.AddToggle("Recompute Vehicle Ground Physical",&CPed::ms_bRecomputeGroundPhysicalOnVehicles);
bank.AddToggle("Enable Ground Physical Counter Force",&CPed::ms_bEnableGroundPhysicalCounterForce);
bank.AddToggle("Delete Probe Capsule Impacts",&CPed::ms_bDeleteProbeCapsuleImpacts);
bank.AddSlider("Capsule Asleep Ticks",&CPed::ms_nCapsuleAsleepTicks,0,30,1);
bank.AddSlider("Capsule Motionless Ticks",&CPed::ms_nCapsuleMotionlessTicks,0,30,1);
bank.AddSlider("Maximum walking ragdoll player leg contact time", &CPed::ms_uMaxRagdollPlayerLegContactTime[0], 0, 100000, 1, NullCB, "Maximum amount of time the player will kick a ragdoll corpse while walking before no contacts are registered");
bank.AddSlider("Maximum running ragdoll player leg contact time", &CPed::ms_uMaxRagdollPlayerLegContactTime[1], 0, 100000, 1, NullCB, "Maximum amount of time the player will kick a ragdoll corpse while running before no contacts are registered");
bank.AddSlider("Maximum sprinting ragdoll player leg contact time", &CPed::ms_uMaxRagdollPlayerLegContactTime[2], 0, 100000, 1, NullCB, "Maximum amount of time the player will kick a ragdoll corpse while sprinting before no contacts are registered");
bank.AddSlider("Maximum cover entry ragdoll player leg contact time", &CPed::ms_uMaxRagdollPlayerLegContactTime[3], 0, 100000, 1, NullCB, "Maximum amount of time the player will kick a ragdoll corpse while entering cover before no contacts are registered");
bank.AddSlider("Maximum walking ragdoll A.I. leg contact time", &CPed::ms_uMaxRagdollAILegContactTime[0], 0, 100000, 1, NullCB, "Maximum amount of time the A.I. will kick a ragdoll corpse while walking before no contacts are registered");
bank.AddSlider("Maximum running ragdoll A.I. leg contact time", &CPed::ms_uMaxRagdollAILegContactTime[1], 0, 100000, 1, NullCB, "Maximum amount of time the A.I. will kick a ragdoll corpse while running before no contacts are registered");
bank.AddSlider("Maximum sprinting ragdoll A.I. leg contact time", &CPed::ms_uMaxRagdollAILegContactTime[2], 0, 100000, 1, NullCB, "Maximum amount of time the A.I. will kick a ragdoll corpse while sprinting before no contacts are registered");
bank.AddSlider("Maximum cover entry ragdoll A.I. leg contact time", &CPed::ms_uMaxRagdollAILegContactTime[3], 0, 100000, 1, NullCB, "Maximum amount of time the A.I. will kick a ragdoll corpse while entering cover before no contacts are registered");
bank.AddSlider("Counter ragdoll impulse strength", &CPed::ms_CounterRagdollImpulseStrength, 0.0f, 1000.0f, 0.1f, NullCB, "Amount by which to counter the ped's velocity when pushing a ragdoll");
bank.AddSlider("Dead ped counter ragdoll impulse strength modifier", &CPed::ms_DeadPedCounterRagdollImpulseStrengthModifier, 0.0f, 1.0f, 0.01f, NullCB, "Amount by which to modify the counter strength when pushing a dead ragdoll");
bank.AddSlider("Minimum kick direction angle", &CPed::ms_fMinimumKickDirectionAngle, 0.0f, 90.0f, 0.1f, NullCB, "Angle to define a cone within which we try to push peds to the side");
bank.AddSlider("Minimum kick direction angle sprint scalar", &CPed::ms_fMinimumKickDirectionAngleSprintScaler, 0.0f, 10.0f, 0.01f, NullCB, "Scalar to multiply the minimum kick direction angle when sprinting");
bank.AddSlider("Force kick direction angle", &CPed::ms_fForceKickDirectionAngle, 0.0f, 90.0f, 0.1f, NullCB, "Angle to define a cone within which we force peds to be pushed to a certain side");
bank.PushGroup("Ragdoll Weapon Disable Collision Mask", false);
bank.AddButton("Disable/Enable All", DisableEnableRagdollWeaponCollisionMaskCB);
for (int i = 0; i < RAGDOLL_NUM_COMPONENTS; i++)
{
bank.AddToggle(parser_RagdollComponent_Strings[i], &ms_RagdollWeaponDisableCollisionMask, 1 << i, NullCB, "Mask defining which body parts of a ragdoll a weapon will not collide with");
}
bank.PopGroup();
bank.AddToggle("Allow fake cylindrical constraints.",&CPed::ms_bEnableFakeCylindricalConstraints);
bank.AddSlider("Side normal tolerance for Vehicle Side NM", &CPed::ms_fMaxSideNormalForSideNM, 0.0f, 1.0f, 0.01f, NullCB, "Abs(Dot(Normal, VehicleXAxis)) > X before we consider NM activation for side swipe");
bank.AddSlider("Vel through normal for Vehicle Side NM", &CPed::ms_fVelThroughNormalForSideNM, 0.0f, 1.0f, 0.01f, NullCB, "Dot(ClosingVel, Normal)) > -V before we consider NM activation for side swipe");
bank.AddSlider("Accumulate before triggering NM for Vehicle Side NM", &CPed::ms_fMaxNMAccumulateForSideNM, 0.0f, 50.0f, 0.01f, NullCB, "Accumulate of acceleration applied * scraping velocity before NM is triggered");
bank.AddToggle("Disable allow transition to NM from cover", &CPed::ms_bTriggerNMInCover);
bank.AddSlider("Impulse limit before triggering NM in cover", &CPed::ms_fVelLimitForKnockFromCover, 0.0f, 50.0f, 0.01f, NullCB, "otherMass*hitvel > mymass*X to engage NM");
bank.AddSlider("Impulse limit when hit by gate before triggering NM in cover", &CPed::ms_fVelLimitForHitByGateKnockFromCover, 0.0f, 50.0f, 0.01f, NullCB, "otherMass*hitvel > mymass*X to engage NM");
bank.AddSlider("Size -> Speed scale for max speed for ground instance in NM", &CPed::ms_fSizeToSpeedTolForCoverGroundInstance, 0.0f, 50.0f, 0.01f, NullCB, "small objects need to be slower to be ground instances");
bank.AddSlider("Volume of expected hard to move objects", &CPed::ms_fVolumeOfExpectedHardToPushObjects, 0.0f, 100.0f, 1.0f);
bank.AddSlider("Mass of expected hard to move objects", &CPed::ms_fMassOfExpectedHardToPushObjects, 0.0f, 10000.0f, 10.0f);
bank.PopGroup();
bank.AddSlider("Catch fire delay time", &ms_fOnFireDelayTime, 0.0f, 5.0f, 0.1f);
bank.AddSlider("Catch fire delay time for fire resistant peds", &ms_fOnFireDelayTimeResistant, 0.0f, 5.0f, 0.1f);
#if __BANK
bank.PushGroup("Physics");
bank.AddToggle("PreComputeImpacts", &CPed::sm_PreComputeImpacts);
bank.AddToggle("PreComputeCapsuleProbeImpact", &CPed::sm_PreComputeCapsuleProbeImpact);
bank.AddToggle("PreComputeHorseLowerLegImpact", &CPed::sm_PreComputeHorseLowerLegImpact);
bank.AddToggle("PreComputeHorsePropBlockerImpact", &CPed::sm_PreComputeHorsePropBlockerImpact);
bank.AddToggle("PreComputeImpactsForMover", &CPed::sm_PreComputeImpactsForMover);
bank.AddToggle("PreComputeImpactsForRagdoll", &CPed::sm_PreComputeImpactsForRagdoll);
bank.AddToggle("PreComputeMainCapsuleImpact", &CPed::sm_PreComputeMainCapsuleImpact);
bank.AddToggle("PreComputePlayerLowerLegImpact", &CPed::sm_PreComputePlayerLowerLegImpact);
bank.AddToggle("PreComputeQuadrupedLowerLegImpact", &CPed::sm_PreComputeQuadrupedLowerLegImpact);
bank.AddToggle("Enable ragdoll for high velocity impacts with fixed geometry while standing on a train", &CPed::sm_EnableRagdollForHighVelImpactsWhileOnTrain);
bank.AddToggle("Dead Ragdoll Activation Debug", &CPed::sm_deadRagdollDebug);
bank.PopGroup();
#endif
bank.PushGroup("AO Volumes");
bank.AddToggle("Show debug", &CPed::ms_bShowAOVolumeDebug);
bank.PopGroup();
}
void CPed::ShutdownBank()
{
if(ms_pBank)
{
BANKMGR.DestroyBank(*ms_pBank);
}
ms_pBank = NULL;
ms_pCreateButton = NULL;
}
// initialise any debug data so widgets have sensible values
void CPed::InitDebugData()
{
CPedVariationDebug::InitDebugData();
CPedPropsMgr::InitDebugData();
}
bool CPed::IsAnyPedInInterior(CInteriorInst* pIntInst){
Assert(pIntInst);
CPed::Pool* pPedPool = CPed::GetPool();
for(u32 i=0 ; i<pPedPool->GetSize() ; i++){
CPed* pPed = pPedPool->GetSlot(i);
if (pPed && pPed->GetIsInInterior()){
if ( CInteriorInst::GetInteriorForLocation( pPed->GetInteriorLocation() ) == pIntInst){
return(true);
}
}
}
return(false);
}
void CPed::DrawDebugVisibilityQuery()
{
if( bTestVisibilityQuery || bShowVisibilityQueryResult)
{
CEntityIterator entityIterator( IteratePeds );
CPed* pPed = static_cast<CPed*>(entityIterator.GetNext());
while(pPed)
{
if( bTestVisibilityQuery )
{
pPed->SetUseOcclusionQuery(true);
pPed->SetUseVehicleBoundingBox(true);
pPed->SetUseRestrictedVehicleBoundingBox(true);
pPed->SetUseHnSBoundingBox(true);
}
if( pPed->GetUseOcclusionQuery() )
{
if( bShowVisibilityQueryResult )
{
char msg[1024];
sprintf(msg,"%d",pPed->IsPedVisible());
grcDebugDraw::Text(pPed->GetTransform().GetPosition(),Color32(0xffffffff),msg);
}
}
pPed = static_cast<CPed*>(entityIterator.GetNext());
}
}
if( bShowPedsVisibilityTrackingStats)
{
CEntityIterator entityIterator( IteratePeds );
CPed* pPed = static_cast<CPed*>(entityIterator.GetNext());
while(pPed)
{
fwDrawData *drawData = pPed->GetDrawHandlerPtr();
bool isPedTracked = pPed->GetUseOcclusionQuery();
bool isUseVehicleBound = pPed->GetUseVehicleBoundingBox();
bool isUseRestrictedBound = pPed->GetUseHnSBoundingBox();
bool isInVehicle = pPed->GetVehiclePedInside() != NULL;
bool isQVisible = pPed->IsPedVisible();
bool isPlayer = pPed->IsPlayer();
const char *gamerTag = NULL;
bool hasDrawData = drawData != NULL;
unsigned int queryId = 0;
int pixelCount = -1;
bool isVisible = pPed->GetIsVisibleInSomeViewportThisFrame();
if( drawData )
{
queryId = drawData->GetOcclusionQueryId();
if( queryId )
{
pixelCount = OcclusionQueries::OQGetQueryResult(queryId);
}
}
if( isPlayer )
{
gamerTag = pPed->GetPlayerInfo()->m_GamerInfo.GetName();
}
if( bShowPedsVisibilityTrackingStats )
{
grcDebugDraw::AddDebugOutput("%p - %s - %d - %d - %d - %d - %d - %d - %d - %d - %d",
pPed,
gamerTag ? gamerTag : "No Tags",
isPedTracked,
isUseVehicleBound,
isUseRestrictedBound,
isInVehicle,
hasDrawData,
isVisible,
queryId,
pixelCount,
isQVisible);
}
pPed = static_cast<CPed*>(entityIterator.GetNext());
}
}
}
#endif//__BANK
#define DEFAULT_MISSION_PED_COMMUNICATION_DISTANCE (15.0f)
void CPed::UpdateVisualDataSettings()
{
if (g_visualSettings.GetIsLoaded())
{
CPedTuning::SetLodThresholds(g_visualSettings.Get( "ped.lod.distance.high" ),
g_visualSettings.Get( "ped.lod.distance.medium" ),
g_visualSettings.Get( "ped.lod.distance.low" ),
g_visualSettings.Get( "pedincar.lod.distance.high" ));
gPedAmbientVolumeFadeStartSetting = g_visualSettings.Get("ped.ambientvolume.fadestart");
gPedAmbientVolumeFadeEndSetting = g_visualSettings.Get("ped.ambientvolume.fadeend");
gPedAmbientVolumeMaxStrength = g_visualSettings.Get("ped.ambientvolume.maxstrength");
gPedAmbientVolumeBaseIntensity = g_visualSettings.Get("ped.ambientvolume.baseintensity",0.0f);
gPedInCarAmbientScale = g_visualSettings.Get("ped.incarAmbientScale",0.7f);
gPedPhoneLight.SetType(LIGHT_TYPE_SPOT);
gPedPhoneLight.SetFlag(LIGHTFLAG_BOTH_INTERIOR_AND_EXTERIOR | LIGHTFLAG_MOVING_LIGHT_SOURCE);
gPedPhoneLight.SetTimeFlags(LIGHT_ALWAYS_ON);
Vector3 pedPhonelightColor;
pedPhonelightColor.SetX(g_visualSettings.Get("ped.phonelight.color.r", 1.0f));
pedPhonelightColor.SetY(g_visualSettings.Get("ped.phonelight.color.g", 1.0f));
pedPhonelightColor.SetZ(g_visualSettings.Get("ped.phonelight.color.b", 1.0f));
gPedPhoneLight.SetColor(pedPhonelightColor);
gPedPhoneLight.SetIntensity(g_visualSettings.Get("ped.phonelight.intensity", 2.0f));
gPedPhoneLight.SetRadius(g_visualSettings.Get("ped.phonelight.radius", 0.75f));
gPedPhoneLight.SetFalloffExponent(g_visualSettings.Get("ped.phonelight.falloffexp", 64.0f));
gPedPhoneLight.SetSpotlight(g_visualSettings.Get("ped.phonelight.cone.inner", 0.0f),
g_visualSettings.Get("ped.phonelight.cone.outer", 90.0f));
}
#if __BANK
TweakLodThreshold = CPedTuning::GetLodThreshold();
TweakLowLodThreshold = CPedTuning::GetLowLodThreshold();
TweakSuperLodThreshold = CPedTuning::GetSuperLodThreshold();
TweakInCarLodThreshold = CPedTuning::GetInCarLodThreshold();
#endif //__BANK
}
// EJ: Memory Optimization
u8 CPed::GetNumProps() const
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
if (pModelInfo)
{
return static_cast<u8>(CPedPropsMgr::GetPropBoneCount(pModelInfo->GetVarInfo(), GetPedDrawHandler().GetPropData()));
}
return 0;
}
void CPed::SetModelId(fwModelId modelId)
{
Assertf(modelId.IsValid(), "given modelId for ped is invalid.");
if (modelId.IsValid())
{
SetModelId(modelId, CLIP_SET_ID_INVALID);
if (m_pPlayerInfo)
m_pPlayerInfo->SetModelId(modelId);
}
}
void CPed::SetBehaviorFromTaskData()
{
CPedModelInfo* pPedModelInfo = GetPedModelInfo();
Assert(pPedModelInfo);
m_pTaskData = CTaskDataInfoManager::GetInfo(pPedModelInfo->GetTaskDataHash().GetHash());
Assert(m_pTaskData);
if (m_pTaskData->GetIsFlagSet(CTaskFlags::PreferFleeOnPavements))
{
m_pPedIntelligence->GetFleeBehaviour().GetFleeFlags().SetFlag(CFleeBehaviour::BF_PreferPavements);
}
if (m_pTaskData->GetIsFlagSet(CTaskFlags::CanScreamDuringFlee))
{
m_pPedIntelligence->GetFleeBehaviour().GetFleeFlags().SetFlag(CFleeBehaviour::BF_CanScream);
}
if (m_pTaskData->GetIsFlagSet(CTaskFlags::DontInfluenceWantedLevel))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontInfluenceWantedLevel, true);
}
if (m_pTaskData->GetIsFlagSet(CTaskFlags::UseAmbientScaling) && !IsAPlayerPed())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_UseAmbientModelScaling, true);
}
}
void CPed::SetupSkeletonData(crSkeletonData& skelData) const
{
// Dirty hack until we re-export the skeletondatas, make these bones animatable in translation
eAnimBoneTag bones[] = {
BONETAG_SPINE1
};
for (int i = 0; i < sizeof(bones) / sizeof(eAnimBoneTag); ++i)
{
int iBoneIndex = GetBoneIndexFromBoneTag(bones[i]);
if (iBoneIndex != -1)
{
crBoneData* pBoneData = skelData.GetBoneData(iBoneIndex);
pBoneData->SetDofs(pBoneData->GetDofs()|crBoneData::TRANSLATION);
}
}
}
void CPed::SetModelId(fwModelId modelId, const fwMvClipSetId &UNUSED_PARAM(overrideMotionClipSetId), bool UNUSED_PARAM(bBlendIdle))
{
RAGE_TRACK( CPed_SetModelId );
CPedModelInfo* pPedModelInfo = (CPedModelInfo*)CModelInfo::GetBaseModelInfo(modelId);
Assertf( (pPedModelInfo->GetModelType() == MI_TYPE_PED), "Trying to create a ped with non ped model: %s", pPedModelInfo->GetModelName());
Assertf(pPedModelInfo->GetDrawable() || pPedModelInfo->GetFragType(), "%s:Ped model is not loaded", pPedModelInfo->GetModelName());
SetIsVisibleForAllModules();
CEntity::SetModelId(modelId);
// give ped a physics instance
InitPhys();
// Create the default drawable (specific components will be selected later in SetVariation)
ASSERT_ONLY(bool bValidDrawable =) CreateDrawable();
Assertf(bValidDrawable, "Failed to create drawable for: %s", pPedModelInfo->GetModelName());
// Create the default skeleton
CreateSkeleton();
// Dirty hack until we re-export the skeletondatas, make these bones animatable in translation
SetupSkeletonData(const_cast<crSkeletonData&>(GetSkeletonData()));
CCustomShaderEffectBase *pCustomShaderEffectBase = NULL;
if (!pPedModelInfo->GetIsStreamedGfx())
{
pCustomShaderEffectBase = static_cast<CCustomShaderEffectBase*>(GetDrawHandler().GetShaderEffect());
}
// Initialise the ik manager
m_IkManager.Init(this);
CreateAnimDirector(*GetDrawable(), true, true, true, pCustomShaderEffectBase, GetTargetManager());
if (m_facialData && pPedModelInfo)
{
fwFacialClipSetGroup* pFacialClipSetGroup = fwFacialClipSetGroupManager::GetFacialClipSetGroup(pPedModelInfo->GetFacialClipSetGroupId());
if (pFacialClipSetGroup)
{
fwMvClipSetId facialBaseClipSetId(pFacialClipSetGroup->GetBaseClipSetName());
Assertf(fwClipSetManager::GetClipSet(facialBaseClipSetId), "%s:Unrecognised facial base clipset", pFacialClipSetGroup->GetBaseClipSetName().GetCStr());
m_facialData->SetFacialClipSet(this, facialBaseClipSetId);
}
}
// now that we've setup the skeleton from the Ragdoll (fragInst), can switch to using animated Ped
SwitchToAnimated(false, false, false);
StartPrimaryMotionTask();
// set ped stats pointer to default stored in type modelinfo
m_MotionData.SetHeadingChangeRate( CPedType::ms_fMaxHeadingChange );
// set ped decision maker to the default (defined in pedstat.dat)
// this must be set after the model model is set as it uses that for the tickbox stuff
SetDefaultDecisionMaker();
// Now that we have our model index we can set our default relationship group
m_pPedIntelligence->SetDefaultRelationshipGroup();
// Add the motivation to the ped
m_pPedIntelligence->SetPedMotivation(this);
// Add Nav Capabilities
m_pPedIntelligence->SetNavCapabilities(this);
// Add Perception
m_pPedIntelligence->SetPedPerception(this);
// Set any info the task data is requesting us to set
SetBehaviorFromTaskData();
// Set the combat info
m_pPedIntelligence->GetCombatBehaviour().InitFromCombatData(pPedModelInfo->GetCombatInfoHash());
//This is a bit of a hack.
//We want this flag set for all ambient, non law-enforcement ped types.
//There is no way to do this through metadata with the way peds are currently set up.
if(PopTypeIsRandom() && !IsLawEnforcementPed())
{
m_pPedIntelligence->GetCombatBehaviour().SetFlag(CCombatData::BF_IgnoreHatedPedsInFastMovingVehicles);
}
const CPedModelInfo::PersonalityData& personalityData = pPedModelInfo->GetPersonalitySettings();
if (!IsPlayer() && personalityData.GetShouldRewardMoneyOnDeath() )
{
if (ms_MoneyCarriedByAllNewPeds >= 0)
{
m_MoneyCarried = ms_MoneyCarriedByAllNewPeds;
}
else
{
m_MoneyCarried = fwRandom::GetRandomNumber() % 25;
// peds drop more money in network game
if (NetworkInterface::IsGameInProgress())
m_MoneyCarried += 10;
}
}
// the player model doesn't require this setup (but a player using a standard ped model does)
if (!pPedModelInfo->GetIsStreamedGfx())
{
CPedVariationPack::RenderShaderSetup(pPedModelInfo, GetPedDrawHandler().GetVarData(),
static_cast<CCustomShaderEffectPed*>(GetDrawHandler().GetShaderEffect()));
}
InitHealth();
if(!IsAPlayerPed() REPLAY_ONLY(&& (CReplayMgr::IsEditModeActive() == false)))
{
CPedInventoryLoadOutManager::SetLoadOut(this, personalityData.GetDefaultWeaponLoadoutHash());
}
if (pPedModelInfo->GetHasHDFiles())
{
m_pedLodState = PLS_HD_NONE;
}
else
{
m_pedLodState = PLS_HD_NA;
}
if (pPedModelInfo->GetIsHDTxdCapable())
{
SetBaseFlag(fwEntity::HAS_HD_TEX_DIST);
}
if(m_pSpeechAudioEntity)
m_pSpeechAudioEntity->InitAnimalParams();
if (m_pComponentSetInfo->GetHasVfx())
{
if (m_pPedVfx)
{
delete m_pPedVfx;
m_pPedVfx = NULL;
}
m_pPedVfx = rage_new CPedVfx();
m_pPedVfx->Init(this);
}
if(!IsAPlayerPed() && GetCapsuleInfo()->GetUseInactiveRagdollCollision())
{
SetKeepInactiveRagdollContacts(true);
}
SetupExternallyDrivenDOFs();
GetPedModelInfo()->AddPedInstance(this);
CachePersonalityMovementMode();
if(pPedModelInfo->GetPersonalitySettings().GetNumWeaponAnimations() > 1)
{
m_uWeaponAnimationsIndex = (u8)fwRandom::GetRandomNumberInRange(0, pPedModelInfo->GetPersonalitySettings().GetNumWeaponAnimations());
}
}
CPed::PedConstraintType CPed::GetConstraintType() const
{
return m_ConstraintType;
}
void CPed::SetConstraintType(PedConstraintType constraintType)
{
#if __BANK
if(!ms_bEnableFakeCylindricalConstraints && constraintType == PCT_CylindricalFake)
{
constraintType = PCT_Cylindrical;
}
#endif
if(m_ConstraintType == constraintType)
return;
RemoveRotationalConstraint();
m_ConstraintType = constraintType;
AddRotationalConstraint();
}
bool CPed::IsConstraintValid()
{
switch(m_ConstraintType)
{
case PCT_RollAndPitch:
return m_RotationalConstraintX.IsValid() && m_RotationalConstraintY.IsValid();
break;
case PCT_Cylindrical:
return m_CylindricalConstraint.IsValid();
break;
case PCT_CylindricalFake:
return true;
break;
case PCT_None:
return true;
break;
}
return false;
}
void CPed::ProcessRotationalConstraint()
{
// If we are ragdolling early out
if(GetUsingRagdoll())
{
return;
}
if(!GetAnimatedInst() || !GetAnimatedInst()->IsInLevel())
{
return;
}
if(GetPedResetFlag(CPED_RESET_FLAG_DisablePedConstraints) || GetPedConfigFlag(CPED_CONFIG_FLAG_DisablePedConstraints))
{
if(IsConstraintValid())
{
// We don't want a constraint but have one, so remove it
RemoveRotationalConstraint();
}
}
else if(!IsConstraintValid())
{
// We want a constraint but don't have one
AddRotationalConstraint();
}
// May want to allow some pitch
// Ask the move blender
float fMinPitch = 0.0f;
float fMaxPitch = 0.0f;
CTaskMotionBase* pTask = GetCurrentMotionTask(false);
if(pTask)
{
pTask->GetPitchConstraintLimits(fMinPitch,fMaxPitch);
}
// If we do have constraints, make sure the limits and orientations are correct
// Pitch and roll are defined by our heading direction:
Matrix34 matPhysicsIntance = RCC_MATRIX34(GetCurrentPhysicsInst()->GetMatrix());
if(phCollider* pCollider = GetCollider())
{
if(fMaxPitch == 0.0f && fMinPitch == 0.0f)
{
static dev_float minCosForCylinderFake = 0.9998f; // ~1.15 degrees
if(matPhysicsIntance.c.z < minCosForCylinderFake ||
!IsZeroAll(pCollider->GetTorque(ScalarV(V_ONE).GetIntrin128()).GetXY()))
{
// The capsule has become significantly tilted or somebody is applying torque which will translate into angular velocity due to
// numerical error.
SetConstraintType(PCT_Cylindrical);
}
else
{
// The ped is already upright so just use a fake constraint
SetConstraintType(PCT_CylindricalFake);
}
}
else
{
SetConstraintType(PCT_RollAndPitch);
}
}
else
{
// No collider means a constraint isn't necessary. We will add it back in OnActivate
SetConstraintType(PCT_None);
}
if(m_ConstraintType == PCT_RollAndPitch)
{
// PITCH
if(phConstraintBase* pConstraint = PHCONSTRAINT->GetTemporaryPointer(m_RotationalConstraintX))
{
Assert(pConstraint->GetType() == phConstraintBase::ROTATION);
phConstraintRotation* pRotationalConstraint = static_cast<phConstraintRotation*>(pConstraint);
pRotationalConstraint->SetWorldAxis(RCC_VEC3V(matPhysicsIntance.a));
// Subtract off our current pitch
float fCurrentPitch = matPhysicsIntance.b.XYMag();
if(matPhysicsIntance.c.z < 0.0f) fCurrentPitch *= -1.0f;
fCurrentPitch = rage::Atan2f(matPhysicsIntance.b.z, fCurrentPitch);
// Now set up the constraint to the required orientation (this is in world space
// Constraint limits are defined in world space but the origin of the constraint has just snapped
// to our current pitch so need to adjust the limits
fMinPitch -= fCurrentPitch;
fMaxPitch -= fCurrentPitch;
fMinPitch = fwAngle::LimitRadianAngleForPitch(fMinPitch);
fMaxPitch = fwAngle::LimitRadianAngleForPitch(fMaxPitch);
if(fMinPitch > fMaxPitch)
{
SwapEm(fMinPitch, fMaxPitch);
}
pRotationalConstraint->SetMinLimit(fMinPitch);
pRotationalConstraint->SetMaxLimit(fMaxPitch);
}
// ROLL
if(phConstraintBase* pConstraint = PHCONSTRAINT->GetTemporaryPointer(m_RotationalConstraintY))
{
Assert(pConstraint->GetType() == phConstraintBase::ROTATION);
phConstraintRotation* pRotationalConstraint = static_cast<phConstraintRotation*>(pConstraint);
// Fix all degrees of rotation (the direction here is irrelevent)
pRotationalConstraint->SetWorldAxis(RCC_VEC3V(matPhysicsIntance.b));
// Make sure we undo any roll that might have crept in
float fCurrentRoll = matPhysicsIntance.a.XYMag();
if(matPhysicsIntance.c.z < 0.0f)
{
fCurrentRoll *= -1.0f;
}
fCurrentRoll = rage::Atan2f(matPhysicsIntance.a.z, fCurrentRoll);
pRotationalConstraint->SetMinLimit(fCurrentRoll);
pRotationalConstraint->SetMaxLimit(fCurrentRoll);
}
}
}
void CPed::AddRotationalConstraint()
{
phInst* pInst = GetAnimatedInst();
Assertf(pInst, "Unexpected null animated phys inst");
if (pInst->GetLevelIndex() != phInst::INVALID_INDEX)
{
switch(m_ConstraintType)
{
case PCT_RollAndPitch:
{
// Get a constraint from the contact manager
phConstraintRotation::Params constraint;
constraint.instanceA = pInst;
constraint.minLimit = constraint.maxLimit = 0.0f; // Default to no rotation allowed
if(!m_RotationalConstraintX.IsValid())
{
constraint.worldAxis = Vec3V(V_X_AXIS_WZERO);
m_RotationalConstraintX = PHCONSTRAINT->Insert(constraint);
}
if(!m_RotationalConstraintY.IsValid())
{
constraint.worldAxis = Vec3V(V_Y_AXIS_WZERO);
m_RotationalConstraintY = PHCONSTRAINT->Insert(constraint);
}
physicsAssertf(m_RotationalConstraintX.IsValid(), "Failed to create ped rotational constraint");
physicsAssertf(m_RotationalConstraintY.IsValid(), "Failed to create ped rotational constraint");
break;
}
case PCT_Cylindrical:
{
phConstraintCylindrical::Params constraint;
constraint.instanceA = pInst;
if(!m_CylindricalConstraint.IsValid())
{
constraint.worldAxis = Vec3V(V_Z_AXIS_WZERO);
//Make sure there is no rotation in the instance matrix when initializing the constraint
Mat34V instMatrix = pInst->GetMatrix();
Mat34V noRotationMatrix(V_IDENTITY);
noRotationMatrix.SetCol3(instMatrix.GetCol3());
pInst->SetMatrix(noRotationMatrix);
m_CylindricalConstraint = PHCONSTRAINT->Insert(constraint);
pInst->SetMatrix(instMatrix);
}
physicsAssertf(m_CylindricalConstraint.IsValid(), "Failed to create ped cylindrical constraint");
break;
}
case PCT_CylindricalFake:
{
phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(GetAnimatedInst());
if(Verifyf(pCollider,"Adding a fake ped cylinder constraint while inactive"))
{
// Zero out the xy angular inertia and xy angular velocity
const Vec3V currentVelocity = pCollider->GetAngVelocity();
pCollider->SetAngVelocity(Vec3V(ScalarV(V_ZERO),ScalarV(V_ZERO),currentVelocity.GetZ()).GetIntrin128());
const Vec3V currentInvAngInertia = pCollider->GetInvAngInertia();
pCollider->SetSolverInvAngInertiaResetOverride(Vec3V(ScalarV(V_ZERO),ScalarV(V_ZERO),currentInvAngInertia.GetZ()).GetIntrin128());
pCollider->UseSolverInvAngInertiaResetOverride(true);
}
break;
}
case PCT_None:
break;
}
}
}
void CPed::RemoveRotationalConstraint()
{
switch(m_ConstraintType)
{
case PCT_RollAndPitch:
PHCONSTRAINT->Remove(m_RotationalConstraintX);
m_RotationalConstraintX.Reset();
PHCONSTRAINT->Remove(m_RotationalConstraintY);
m_RotationalConstraintY.Reset();
break;
case PCT_Cylindrical:
PHCONSTRAINT->Remove(m_CylindricalConstraint);
m_CylindricalConstraint.Reset();
break;
case PCT_CylindricalFake:
{
if(phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(GetAnimatedInst()))
{
// Don't the 0 xy inverse inertia anymore
pCollider->UseSolverInvAngInertiaResetOverride(false);
}
break;
}
case PCT_None:
break;
}
}
void CPed::RemoveOverhangConstraint()
{
m_OverhangConstraintCountdown = 0;
if (m_OverhangConstraint.IsValid())
{
PHCONSTRAINT->Remove( m_OverhangConstraint );
m_OverhangConstraint.Reset();
}
}
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE_DATA
// static dev_float sfMaxAnimSecondSurfaceDepth = PED_MAX_SECOND_SURFACE_DEPTH_ANIM;
// static dev_float sfMaxRagdollSecondSurfaceDepth = PED_MAX_SECOND_SURFACE_DEPTH_RAGDOLL;
#endif
// NOTE: at this time only strippers and main playable character get pedclothcollision bounds
const u32 modelStripper1Hash = ATSTRINGHASH("S_F_Y_Stripper_01", 0x052580019);
const u32 modelStripper2Hash = ATSTRINGHASH("S_F_Y_Stripper_02", 0x06e0fb794);
const u32 modelStripper3Hash = ATSTRINGHASH("mp_f_stripperlite", 0x02970a494);
const u32 modelStripper4Hash = ATSTRINGHASH("csb_stripper_01", 0xAEEA76B5);
const u32 modelStripper5Hash = ATSTRINGHASH("csb_stripper_02", 0x81441B71);
const u32 modelPlayerOneHash = ATSTRINGHASH("Player_One", 0x9B22DBAF);
const u32 modelPlayerTwoHash = ATSTRINGHASH("Player_Two", 0x9B810FA2);
const u32 modelPlayerZeroHash = ATSTRINGHASH("Player_Zero", 0xD7114C9);
int CPed::InitPhys()
{
CPedModelInfo* pModelInfo = (CPedModelInfo*)GetBaseModelInfo();
Assert(pModelInfo->HasPhysics());
if(!m_pPhysicsArchetype)
{
m_pPhysicsArchetype = pModelInfo->GetPhysics()->Clone();
Assert(m_pPhysicsArchetype);
m_pPhysicsArchetype->AddRef();
m_pPhysicsArchetype->SetFilename("Ped");
// Need to clone the composite parts of the bound too
Assert(m_pPhysicsArchetype->GetBound());
Assert(m_pPhysicsArchetype->GetBound()->GetType() == phBound::COMPOSITE);
if( HasEnabledFixFor7459244() || m_pPhysicsArchetype->GetBound())
{
(static_cast<phBoundComposite*>(m_pPhysicsArchetype->GetBound()))->CloneParts();
}
}
//Set up component reservations
#if ENABLE_HORSE
if ( m_pComponentReservationMgr )
m_pComponentReservationMgr->Init(this, pModelInfo);
#endif
#if __BANK
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
Assertf(false, "Ped getting physics inited while in re-use pool");
}
#endif
const Matrix34 matrix = MAT34V_TO_MATRIX34(GetMatrix());
phInst* pNewInst = GetAnimatedInst() ? GetAnimatedInst() : rage_new phInstGta(PH_INST_PED);
pNewInst->Init(*m_pPhysicsArchetype, matrix);
Assert(pNewInst->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_PED_TYPE
|| (GetCapsuleInfo()&&GetCapsuleInfo()->GetQuadrupedCapsuleInfo()&&GetCapsuleInfo()->GetQuadrupedCapsuleInfo()->GetUseHorseMapCollision()
&&pNewInst->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_HORSE_TYPE));
pNewInst->SetInstFlag(phInst::FLAG_HIGH_PRIORITY, true);
//Do not use sleep islands for the ped's capsule.
pNewInst->SetInstFlag(phInst::FLAG_SLEEPS_ALONE, true);
SetAnimatedInst(pNewInst);
// We don't need a constraint until we activate
m_ConstraintType = PCT_None;
if(pModelInfo->GetFragType())
{
bool bAlreadyHasFrag = GetRagdollInst() != NULL;
fragInstNMGta* pFragInst = bAlreadyHasFrag ? GetRagdollInst() : rage_new fragInstNMGta(PH_INST_FRAG_PED, pModelInfo->GetFragType(), matrix);
// Ragdoll insts should never wake up as part of a sleep island. If the ped is alive then it shouldn't be allowed to sleep in the first place
// so this wouldn't be an issue. Mainly I'm setting this flag here so that I can be sure that there's no way that a dead ped can sleep
// without the flag being set.
pFragInst->SetInstFlag(phInst::FLAG_DONT_WAKE_DUE_TO_ISLAND, true);
// The default ragdoll LOD is low for AIs if available
//if (pModelInfo->GetFragType()->GetPhysics(fragInst::LOW) && !IsPlayer())
// pFragInst->SetCurrentPhysicsLOD(fragInst::RAGDOLL_LOD_LOW);
//else
pFragInst->SetCurrentPhysicsLOD(fragInst::RAGDOLL_LOD_HIGH);
pFragInst->SetInstFlag(phInst::FLAG_HIGH_PRIORITY, true);
pFragInst->SetManualSkeletonUpdate(true);
if(pFragInst->HasLastMatrix())
PHLEVEL->ReleaseInstLastMatrix(pFragInst);
PHLEVEL->ReserveInstLastMatrix(pFragInst);//make sure we record the last matrix.
SetRagdollInst(pFragInst);
if(!bAlreadyHasFrag) // don't insert if we already had a fragInst
pFragInst->Insert(false);
// The ragdoll inst should have just been added to the physics level.
Assert(pFragInst->IsInLevel());
Assert(pFragInst->GetArchetype()->GetTypeFlags() == ArchetypeFlags::GTA_RAGDOLL_TYPE
|| (GetCapsuleInfo()&&GetCapsuleInfo()->GetQuadrupedCapsuleInfo()&&GetCapsuleInfo()->GetQuadrupedCapsuleInfo()->GetUseHorseMapCollision()
&&pFragInst->GetArchetype()->GetTypeFlags()==ArchetypeFlags::GTA_HORSE_RAGDOLL_TYPE));
Assert(CPhysics::GetLevel()->GetInstanceTypeFlags(pFragInst->GetLevelIndex()) == ArchetypeFlags::GTA_RAGDOLL_TYPE
|| (GetCapsuleInfo()&&GetCapsuleInfo()->GetQuadrupedCapsuleInfo()&&GetCapsuleInfo()->GetQuadrupedCapsuleInfo()->GetUseHorseMapCollision()
&&CPhysics::GetLevel()->GetInstanceTypeFlags(pFragInst->GetLevelIndex())==ArchetypeFlags::GTA_HORSE_RAGDOLL_TYPE));
// If game code / script has requested that this ped has collision disabled, we set the appropriate include flags on the ragdoll
// here. The ped's capsule will get the same treatment when it is added to the physics level in CPed::AddPhysics().
if(!IsCollisionEnabled())
{
DisableCollision();
}
SwitchCurrentPhysicsInst(GetRagdollInst());
}
else
{
SwitchCurrentPhysicsInst(GetAnimatedInst());
}
#if ENABLE_HORSE
if (m_pComponentSetInfo->GetIsRidable())
{
if( m_pComponentSetInfo->GetHasReins() )
{
Assert( m_pHorseComponent );
m_pHorseComponent->InitReins();
}
}
#endif
const u32 modelHash = pModelInfo->GetModelNameHash();
if( modelHash == modelStripper1Hash ||
modelHash == modelStripper2Hash ||
modelHash == modelStripper3Hash ||
modelHash == modelStripper4Hash ||
modelHash == modelStripper5Hash ||
modelHash == modelPlayerOneHash ||
modelHash == modelPlayerTwoHash ||
modelHash == modelPlayerZeroHash
)
{
SetPedConfigFlag( CPED_CONFIG_FLAG_HasClothCollisionBounds, true );
}
return INIT_OK;
}
//PARAM(nosleep, "[peds] Dont let any peds' physics go to sleep");
void CPed::AddPhysics()
{
if(GetCurrentPhysicsInst())
{
if(IsFragInst(GetCurrentPhysicsInst()))
{
// don't do anything - fragInst's take care of themselves!
#if __ASSERT
SpewRagdollTaskInfo();
#endif //__ASSERT
Assertf(false, "ragdoll shouldn't be getting added in this way");
}
else
{
#if __BANK
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
Assertf(false, "Ped %s (%p) getting physics added while in re-use pool", GetDebugName(), this);
}
#endif
// update physics instance's matrix to match physics entity matrix
GetCurrentPhysicsInst()->SetMatrix(GetMatrix());
// ped has own constrained collider
if(GetCollider())
{
GetCollider()->SetInstanceAndReset(GetCurrentPhysicsInst());
GetCollider()->Reset();
GetCollider()->SetColliderMatrixFromInstance();
GetCollider()->SetLastInstanceMatrix(GetCollider()->GetMatrix());
if(GetCollider()->GetSleep())
GetCollider()->GetSleep()->Reset();
}
ProcessRotationalConstraint();
// This normally happens in ProcessPostPhysics(), on each frame after physics simulation.
// It seems natural to do it here too, so that the values of the related variables are the
// same on the first frame as on subsequent frames.
ResetDesiredMainMoverCapsuleData();
if(Verifyf(!GetCurrentPhysicsInst()->IsInLevel(), "Trying to add ped instance to the physics level twice. Animated inst level index: %i | Ragdoll inst level index: %i | Current inst level index: %i", GetAnimatedInst() ? GetAnimatedInst()->GetLevelIndex() : -1, GetRagdollInst() ? GetRagdollInst()->GetLevelIndex() : -1, GetCurrentPhysicsInst()->GetLevelIndex()))
{
if(!IsBaseFlagSet(fwEntity::SPAWN_PHYS_ACTIVE) || GetIsAnyFixedFlagSet() || !IsCollisionEnabled())
{
CPhysics::GetSimulator()->AddInactiveObject(GetCurrentPhysicsInst());
}
else
{
CPhysics::GetSimulator()->AddActiveObject(GetCurrentPhysicsInst(), false);
}
#if __BANK
m_lastLevelIndex = GetCurrentPhysicsInst()->GetLevelIndex();
sysStack::CaptureStackTrace(m_lastAddPhysicsCallstack, 32, 1);
#endif
}
#if __BANK
else
{
Displayf("[B*1972222: The last thing to add physics (level index: %i) to this ped was: ", m_lastLevelIndex);
sysStack::PrintCapturedStackTrace(m_lastAddPhysicsCallstack, 32);
}
#endif
// If game code / script has requested that this ped has collision disabled, we set the appropriate include flags on the animated
// phys inst here. The ped's ragdoll will already have received the same treatment when it was added to the physics level
// in CPed::InitPhys().
if(!IsCollisionEnabled())
{
DisableCollision();
}
}
if(GetCurrentPhysicsInst()->IsInLevel())
{
fwAttachmentEntityExtension *extension = GetAttachmentExtension();
if(extension && extension->IsAttachStateBasicDerived())
{
extension->SetAttachFlag(ATTACH_FLAG_INST_ADDED_LATE, true);
if( !extension->GetAttachFlag(ATTACH_FLAG_COL_ON) )
{
CPhysics::GetLevel()->SetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES));
}
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE,true);
if(CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex()))
{
PHSIM->DeactivateObject(GetCurrentPhysicsInst());
}
}
}
}
#if __ASSERT
// Track the physics creation backtrace for this ped - used for debugging peds that were incorrectly added to the world
CPhysical::RegisterPhysicsCreationBacktrace(static_cast<CPhysical*>(this));
#endif
}
void CPed::RemovePhysics()
{
// if ragdolling, switch back to animation, but don't add animated inst back into world
if(GetUsingRagdoll())
{
#if __BANK
if(GetCurrentPhysicsInst())
Displayf("[B*1972222] Removing physics on ragdolled ped. Current level index: %i", GetCurrentPhysicsInst()->GetLevelIndex());
#endif
SwitchToAnimated(false, true, false);
}
else
{
CPhysical::RemovePhysics();
// No need to call RemoveRotationalConstraint because CPhysical removes all constraints for us
m_RotationalConstraintX.Reset();
m_RotationalConstraintY.Reset();
m_CylindricalConstraint.Reset();
}
}
void CPed::DeActivatePhysics()
{
if(IsNetworkClone())
{
DeactivatePhysicsAndDealWithRagdoll();
return;
}
CPhysical::DeActivatePhysics();
}
void CPed::DeactivatePhysicsAndDealWithRagdoll(bool ragdollIsAsleep)
{
// can't deactivate the ragdoll, so need to switch back to animation somehow
if(GetUsingRagdoll())
{
CTaskDyingDead* pTaskDead = static_cast<CTaskDyingDead*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_DYING_DEAD));
if( pTaskDead )
{
nmEntityDebugf(this, "CPed::DeactivatePhysicsAndDealWithRagdoll - Deactivating dead task ragdoll. ragdollIsAsleep=%s", ragdollIsAsleep ? "true" : "false");
pTaskDead->DealWithDeactivatedPhysics(this, ragdollIsAsleep);
}
else
{
nmEntityDebugf(this, "CPed::DeactivatePhysicsAndDealWithRagdoll - Switching to animated. ragdollIsAsleep=%s", ragdollIsAsleep ? "true" : "false");
SwitchToAnimated();
}
}
CPhysical::DeActivatePhysics();
}
// Decrement the ped's number of controlled sticky bombs by 1, unless it was already at 0
void CPed::DecrementStickyCount()
{
if (m_uStickyCount > 0)
{
m_uStickyCount--;
}
}
// Decrement the ped's number of harpoon projectiles by 1, unless it was already at 0
void CPed::DecrementStickToPedProjectileCount()
{
if (m_uStickToPedProjCount > 0)
{
m_uStickToPedProjCount--;
}
}
// Decrement the ped's number of harpoon projectiles by 1, unless it was already at 0
void CPed::DecrementFlareGunProjectileCount()
{
if (m_uFlareGunProjCount > 0)
{
m_uFlareGunProjCount--;
}
}
void CPed::EnableCollision(phInst* UNUSED_PARAM(pInst), u32 UNUSED_PARAM(nIncludeflags))
{
CEntity::EnableCollision(GetAnimatedInst());
CEntity::EnableCollision(GetRagdollInst());
}
void CPed::DisableCollision(phInst* UNUSED_PARAM(pInst), bool bCompletelyDisabled)
{
CEntity::DisableCollision(GetAnimatedInst(), bCompletelyDisabled);
// Disable collision for the ragdoll instance (we do this separately because we still want ragdolls to at least get
// notified about collisions against the explosion sphere and this is faster than getting and setting the include flags
// to zero just to reset them later):
Assert(!IsProtectedBaseFlagSet(fwEntity::USES_COLLISION));
phInst* pRagdollInst = GetRagdollInst();
if(pRagdollInst)
{
int nLevelIndex = pRagdollInst->GetLevelIndex();
if(CPhysics::GetLevel()->IsInLevel(nLevelIndex))
{
u32 includeFlags = ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES;
if(bCompletelyDisabled)
{
includeFlags = 0;
}
#if __BANK
Assertf( !( GetPedConfigFlag( CPED_CONFIG_FLAG_PedIsInReusePool ) &&
CPhysics::GetLevel()->GetInstanceIncludeFlags( nLevelIndex ) == 0 && includeFlags != 0 ), "CPed::DisableCollision: Changing include flags from %d to %d when disabling collision on Ragdoll - level index: %d", CPhysics::GetLevel()->GetInstanceIncludeFlags( nLevelIndex ), includeFlags, pRagdollInst->GetLevelIndex() );
#endif // #if __BANK
CPhysics::GetLevel()->SetInstanceIncludeFlags(nLevelIndex, includeFlags);
}
}
}
void CPed::SetHeading(float new_heading)
{
if (GetPedResetFlag( CPED_RESET_FLAG_DisablePedCapsuleControl ))
return;
if(GetUsingRagdoll())
{
#if __ASSERT
SpewRagdollTaskInfo();
#endif //__ASSERT
Assertf(false,"SetHeading shouldn't be called when peds are ragdolling");
return;
}
CDynamicEntity::SetHeading(new_heading);
SetCurrentHeading(new_heading);
if(GetCurrentPhysicsInst())
{
Matrix34 tempMat = RCC_MATRIX34(GetCurrentPhysicsInst()->GetMatrix());
// check if the matrix is vertical before we make it so
bool bNonVertical = tempMat.c.z < 1.0f;
tempMat.MakeRotateZ(new_heading);
tempMat.RotateLocalX(m_MotionData.GetCurrentPitch());
GetCurrentPhysicsInst()->SetMatrix(RCC_MAT34V(tempMat));
if(GetCurrentPhysicsInst()->IsInLevel())
{
PHSIM->SetLastInstanceMatrix(GetCurrentPhysicsInst(), RCC_MAT34V(tempMat));
}
if(GetCollider())
{
GetCollider()->SetColliderMatrixFromInstance();
GetCollider()->SetLastInstanceMatrix(RCC_MAT34V(tempMat));
}
ProcessRotationalConstraint();
// if we've rotated the matrix about anything other than the vertical axis, need to update position in physics level
if(bNonVertical && GetCurrentPhysicsInst()->IsInLevel())
CPhysics::GetLevel()->UpdateObjectLocation(GetCurrentPhysicsInst()->GetLevelIndex());
// Don't want to adjust the ped's position
tempMat.d = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
CEntity::SetMatrix(tempMat);
// Keep ragdoll inst in sync with animated inst when setting a new heading (since the ped's skeleton's parent matrix points to the ragdoll instance's matrix).
// Otherwise, any systems (like IK) that need to read the skeleton in PreRender will see a skeleton at the old heading if physics has already updated this frame.
UpdateRagdollRootTransformFromAnimatedSkel();
}
}
void CPed::SetMatrix(const Matrix34& mat, bool bUpdateGameWorld, bool bUpdatePhysics, bool bWarp)
{
CPhysical::SetMatrix(mat, bUpdateGameWorld, bUpdatePhysics,bWarp);
if(!GetUsingRagdoll())
{
// Keep current pitch and heading up to date
m_MotionData.SetCurrentHeadingAndPitchFromMatrix(RCC_MAT34V(mat));
if(bUpdatePhysics && GetAnimatedInst())
ProcessRotationalConstraint();
}
}
void CPed::SetHeadingAndPitch(float fHeading, float fPitch)
{
if (GetPedResetFlag( CPED_RESET_FLAG_DisablePedCapsuleControl ))
return;
if(GetUsingRagdoll())
{
Assertf(false,"SetHeadingAndPitch shouldn't be called when peds are ragdolling");
return;
}
Matrix34 matNew(Matrix34::IdentityType);
matNew.RotateLocalZ(fHeading);
matNew.RotateLocalX(fPitch);
matNew.d = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
SetMatrix(matNew);
}
void CPed::SetPitch(float fPitch)
{
if (GetPedResetFlag( CPED_RESET_FLAG_DisablePedCapsuleControl ))
return;
if(GetUsingRagdoll())
{
Assertf(false,"SetPitch shouldn't be called when peds are ragdolling");
return;
}
Matrix34 matNew(Matrix34::IdentityType);
matNew.RotateLocalZ(GetCurrentHeading());
matNew.RotateLocalX(fPitch);
matNew.d = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
SetMatrix(matNew);
}
void CPed::UpdateEntityFromPhysics(phInst *pInst, int nLoop)
{
#if GTA_REPLAY
if( CReplayMgr::IsReplayInControlOfWorld() )
return;
#endif //GTA_REPLAY
CPhysical::UpdateEntityFromPhysics(pInst, nLoop);
// Update the ragdoll matrix every physics loop if we're being forced or we have (or have recently had) a ground physical
if((CPhysics::GetIsLastTimeSlice(nLoop) || GetPedResetFlag(CPED_RESET_FLAG_ForceUpdateRagdollMatrix) || MagSquared(GetGroundVelocityIntegrated()).Getf() > SMALL_FLOAT || GetActivateRagdollOnCollision()) &&
pInst==GetAnimatedInst() && GetRagdollInst() && GetRagdollInst()->IsInLevel() && !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()))
{
UpdateRagdollMatrix(false);
}
if(!GetUsingRagdoll())
{
// Keep current pitch and heading up to date
SetCurrentHeading(GetTransform().GetHeading());
SetCurrentPitch(GetTransform().GetPitch());
}
else
{
if(CPhysics::GetIsLastTimeSlice(nLoop))
{
// Update the skeleton if we've told the fragment to not do it on its own
if(GetRagdollInst()->GetManualSkeletonUpdate())
{
GetRagdollInst()->SyncSkeletonToArticulatedBody();
}
}
}
if( CPhysics::GetIsLastTimeSlice( nLoop ) )
{
m_forcePedHasNoMassInImpacts = false;
}
}
void CPed::UpdatePhysicsFromEntity(bool bWarp)
{
CPhysical::UpdatePhysicsFromEntity(bWarp);
if(GetUsingRagdoll())
{
Assertf(false, "CPed::UpdatePhysicsFromEntity called while ragdoll active");
}
else if(GetRagdollInst())
{
Vector3 vecDistMoved = VEC3V_TO_VECTOR3(GetTransform().GetPosition() - GetRagdollInst()->GetMatrix().GetCol3());
if(bWarp || vecDistMoved.Mag2() > PED_MAX_DIST_TO_WARP_SQR)
UpdateRagdollMatrix(true);
else
UpdateRagdollMatrix(false);
}
if(bWarp)
{
RemoveOverhangConstraint();
m_vecGroundPosHistory[0].Zero();
m_vecGroundPosHistory[1].Zero();
m_vecSteepSlopePos.Zero();
if(GetIkManager().IsActive(IKManagerSolverTypes::ikSolverTypeLeg))
{
GetIkManager().SetFlag(PEDIK_LEGS_AND_PELVIS_OFF);
GetIkManager().SetFlag(PEDIK_LEGS_AND_PELVIS_FADE_OFF);
}
}
}
bool CPed::IsCollisionLoadedAroundPosition()
{
if(m_fGroundZFromImpact == PED_GROUNDPOS_RESET_Z)
{
return CPhysical::IsCollisionLoadedAroundPosition();
}
else
{
return true;
}
}
void CPed::ProcessGlassCollisionTest()
{
#if __BANK
bool bForceGlassCollisionRagdoll = (IsPlayer() && ms_bToggleCollidesAgainstGlassRagdoll);
bool bForceGlassCollisionWeapon = (IsPlayer() && ms_bToggleCollidesAgainstGlassWeapon);
#endif
if(GetPedResetFlag( CPED_RESET_FLAG_CollideWithGlassRagdoll )
BANK_ONLY(|| bForceGlassCollisionRagdoll) )
CPhysics::CollideInstAgainstGlass(GetRagdollInst());
if(GetPedResetFlag( CPED_RESET_FLAG_CollideWithGlassWeapon )
BANK_ONLY(|| bForceGlassCollisionWeapon))
{
if( GetWeaponManager() && GetWeaponManager()->GetEquippedWeaponObject() && GetWeaponManager()->GetEquippedWeaponObject()->GetCurrentPhysicsInst())
{
CPhysics::CollideInstAgainstGlass(GetWeaponManager()->GetEquippedWeaponObject()->GetCurrentPhysicsInst());
}
}
}
void CPed::OnActivate(phInst* pInst, phInst* pOtherInst)
{
CPhysical::OnActivate(pInst,pOtherInst);
SetPedResetFlag(CPED_RESET_FLAG_OnActivationUpdate, true);
if(pInst == GetAnimatedInst())
{
// Update the rotation constraint since we won't have one while inactive
ProcessRotationalConstraint();
phCollider* pAnimatedCollider = GetCollider();
if(AssertVerify(pAnimatedCollider))
{
// Update the sleep counters on our new collider
phSleep* pSleep = pAnimatedCollider->GetSleep();
if(AssertVerify(pSleep))
{
pSleep->SetMaxMotionlessTicks(CPed::ms_nCapsuleMotionlessTicks);
pSleep->SetMaxAsleepTicks(CPed::ms_nCapsuleAsleepTicks);
}
// B*1942901: Ensure the collider has its reference frame velocities set up.
// Ragdoll peds are handled differently here since they aren't getting their groundPhysical set like animated peds.
// Instead they use an asynch probe every few frames so they can't have their ref frame velocities getting cleared each frame.
if (!GetUsingRagdoll())
{
//Set the reference frame velocity.
pAnimatedCollider->SetReferenceFrameVelocity(VEC3V_TO_VECTOR3(m_vGroundVelocityIntegrated));
//Set the reference frame angular velocity.
pAnimatedCollider->SetReferenceFrameAngularVelocity(VEC3V_TO_VECTOR3(m_vGroundAngularVelocity));
}
}
// Allow control constraint to be added
SetPedConfigFlag( CPED_CONFIG_FLAG_DisablePedConstraints, false );
//Check if the other entity is valid.
const CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInst);
if(pOtherEntity)
{
//Note that we were bumped by the other entity.
BumpedByEntity(*pOtherEntity);
}
//Ensure the ped thinks they are attached to the ground.
if(!GetIsAttachedToGround())
{
return;
}
//Detach from the ground.
DetachFromGround();
//Assert that the ped is still awake.
Assertf(!IsAsleep(), "Ped went to sleep during activation callback.");
}
else
{
Assert(pInst == GetRagdollInst());
Assertf(!GetKeepInactiveRagdollContacts(),"Ragdolls in this mode shouldn't activate.");
SetKeepInactiveRagdollContacts(false);
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
Assertf(false, "Ped %s (%p) ragdoll getting activated while in re-use pool. Inst level index: %i | Ragdoll level index: %i",
GetDebugName(), this, GetCurrentPhysicsInst() ? GetCurrentPhysicsInst()->GetLevelIndex() : -1, GetRagdollInst() ? GetRagdollInst()->GetLevelIndex() : -1);
}
}
}
void CPed::OnDeactivate(phInst* pInst)
{
CPhysical::OnDeactivate(pInst);
if(pInst == GetAnimatedInst())
{
// Remove the constraint
ProcessRotationalConstraint();
// Mark for removal of control constraint
SetPedConfigFlag( CPED_CONFIG_FLAG_DisablePedConstraints, true );
if(GetRagdollState() < RAGDOLL_STATE_PHYS_ACTIVATE)
{
//Ensure the ped can attach to the ground.
if(!CanAttachToGround())
{
return;
}
//Attach to the ground.
AttachToGround();
}
//Assert that the ped is still asleep.
Assertf(IsAsleep(), "Ped woke up during deactivation callback.");
}
}
void CPed::AttachToGround()
{
//Assert that the ped can attach to ground.
Assert(CanAttachToGround());
//Calculate the ground heading.
Vec3V vForward = GetGroundPhysical()->GetTransform().GetForward();
float fGroundHeading = rage::Atan2f(-vForward.GetXf(), vForward.GetYf());
//Calculate the heading.
float fHeading = fwAngle::LimitRadianAngle(GetCurrentHeading() - fGroundHeading);
//Attach the ped to the ground.
AttachPedToGround(GetGroundPhysical(), -1, ATTACH_STATE_PED_ON_GROUND|ATTACH_FLAG_INITIAL_WARP|ATTACH_FLAG_AUTODETACH_ON_DEATH|ATTACH_FLAG_AUTODETACH_ON_RAGDOLL|ATTACH_FLAG_COL_ON, &RCC_VECTOR3(m_vGroundOffset), fHeading, 0.0f);
//Note that the ped was attached to the ground.
OnAttachToGround();
}
bool CPed::CanAttachToGround() const
{
//Ensure the ped is eligible for a ground attachment.
if(!IsEligibleForGroundAttachment())
{
return false;
}
//Ensure the ground is eligible for attachment.
if(!IsGroundEligibleForAttachment())
{
return false;
}
return true;
}
void CPed::DetachFromGround()
{
//Assert that we are attached to ground.
Assert(GetIsAttachedToGround());
fwAttachmentEntityExtension *extension = GetAttachmentExtension();
if( extension
&& extension->GetAttachState() == ATTACH_STATE_PED_ON_GROUND
//Prevent recursion of detachment (due to detach function activating the inst which causes this function to get called again)
&& !extension->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION) )
{
DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS);
}
//The detach call above will handle notification of the detach. This is done
//so that detaches triggered from the framework (death, ragdoll, etc) are
//handled in the same way as detaching explicitly through this call.
}
bool CPed::IsEligibleForGroundAttachment() const
{
//Ensure the ped is not attached.
if(GetIsAttached())
{
return false;
}
//Ensure the ped is alive.
if(IsInjured())
{
return false;
}
//Ensure the ped is not using a ragdoll.
if(GetUsingRagdoll())
{
return false;
}
if (GetPedResetFlag(CPED_RESET_FLAG_DisableGroundAttachment))
{
return false;
}
return true;
}
bool CPed::IsGroundEligibleForAttachment() const
{
//Ensure the ground is valid.
if(!GetGroundPhysical())
{
return false;
}
//Ensure the root ground is valid.
const CPhysical* pRootGround = GetRootGroundPhysicalForPhysical(*this);
if(!pRootGround)
{
return false;
}
//Ensure the root ground is a vehicle.
//This will allow peds to be attached to vehicles or props on top of vehicles,
//but they will not attach to props lying on the ground.
if(!pRootGround->GetIsTypeVehicle())
{
return false;
}
//Ensure the vehicle can be stood on top of.
const CVehicle* pVehicle = static_cast<const CVehicle *>(pRootGround);
if(!pVehicle->CanPedsStandOnTop())
{
return false;
}
return true;
}
void CPed::OnAttachToGround()
{
//Grab the physics instance.
phInst* pInst = GetAnimatedInst();
if(pInst && pInst->IsInLevel())
{
//Allow the instance to activate.
pInst->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false);
//Grab the level index.
u16 uLevelIndex = pInst->GetLevelIndex();
//Allow the ped to ragdoll when colliding with other inactive/fixed objects.
PHLEVEL->SetInactiveCollidesAgainstInactive(uLevelIndex, true);
PHLEVEL->SetInactiveCollidesAgainstFixed(uLevelIndex, true);
//Query the external velocity when attached to the ground, otherwise the physics engine
//will always assume the velocity is zero (which will result in bad physics behavior).
pInst->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL, true);
}
}
void CPed::OnDetachFromGround()
{
//Grab the instance.
phInst* pInst = GetAnimatedInst();
if(pInst && pInst->IsInLevel())
{
//Grab the level index.
u16 uLevelIndex = pInst->GetLevelIndex();
//Restore the collision flags.
PHLEVEL->SetInactiveCollidesAgainstInactive(uLevelIndex, false);
PHLEVEL->SetInactiveCollidesAgainstFixed(uLevelIndex, false);
//Restore the query external velocity flag.
pInst->SetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL, false);
}
}
void CPed::ProcessGroundMovement(float fTimeStep)
{
//Use the animated inst.
phInst* pInst = GetAnimatedInst();
//Check if the ground is valid.
if(!GetGroundPhysical())
{
//Clear the ground velocity.
m_vGroundVelocity = Vec3V(V_ZERO);
//Clear the ground angular velocity.
m_vGroundAngularVelocity = Vec3V(V_ZERO);
//Clear the ground acceleration.
m_vGroundAcceleration = Vec3V(V_ZERO);
//Clear the ground offset.
m_vGroundOffset = Vec3V(V_ZERO);
//Clear the local ground normal.
m_vGroundNormalLocal = Vec3V(V_ZERO);
if(GetPedResetFlag(CPED_RESET_FLAG_IsClimbing)) // Note: we check CPED_RESET_FLAG_IsClimbing to avoid having to do a full function non-inlined function call in the common case.
{
CPhysical* pClimbPhysical = GetClimbPhysical();
if (pClimbPhysical)
{
CalculateGroundVelocityIntegrated(pClimbPhysical, GetTransform().GetPosition(), 0, fTimeStep, m_vGroundVelocityIntegrated);
CalculateGroundVelocity(pClimbPhysical, GetTransform().GetPosition(), 0, m_vGroundVelocity);
CalculateGroundAngularVelocity(pClimbPhysical, 0, m_vGroundAngularVelocity);
}
else if (GetPedIntelligence()->GetTaskClimbLadder())
{
m_vGroundVelocityIntegrated = Vec3V(V_ZERO);
}
}
else if(IsOnGround())
{
m_vGroundVelocityIntegrated = Vec3V(V_ZERO);
}
else if(GetPedResetFlag(CPED_RESET_FLAG_IsJumping))
{
m_vGroundVelocityIntegrated = VECTOR3_TO_VEC3V(GetGroundVelocityForJump());
}
else
{
bool bStartFallOff = fwTimer::GetTimeInMilliseconds() > (m_uLastValidGroundPhysicalTime + 1000);
if(bStartFallOff)
{
//! Integrated velocity falls off to zero. This prevents a hard reset of velocity when we step off fast moving objects.
//! Note: This is just a simple approximation of deceleration - may be better tied in to physics step?
Vector3 vGroundVelocityTemp = VEC3V_TO_VECTOR3(m_vGroundVelocityIntegrated);
static dev_float s_fVelocityFalloff = 0.25f;
vGroundVelocityTemp *= (1.0f-(s_fVelocityFalloff*fTimeStep));
m_vGroundVelocityIntegrated = VECTOR3_TO_VEC3V(vGroundVelocityTemp);
}
}
}
else
{
//Check if the ground physical was valid last frame.
//It is basically impossible for the ground offset to be zero if the ground was valid,
//so using it to test instead of an extra cache/boolean/flag. If this causes issues,
//change this to be driven by a reset flag.
bool bGroundWasValidLastFrame = (!IsZeroAll(m_vGroundOffset));
//Cache the last ground velocity.
Vec3V vLastGroundVelocity = m_vGroundVelocity;
// Our ground position rotates with the ground physical, so use that rather than the center of the ped
Vec3V groundPos = VECTOR3_TO_VEC3V(GetGroundPos());
//Assign the ground velocity.
CalculateGroundVelocity(GetGroundPhysical(), groundPos, m_groundPhysicalComponent, m_vGroundVelocity);
Assertf(IsFiniteAll(m_vGroundVelocity), "Ground velocity is invalid.");
//Assign the ground velocity integrated.
CalculateGroundVelocityIntegrated(GetGroundPhysical(), groundPos, m_groundPhysicalComponent, fTimeStep, m_vGroundVelocityIntegrated);
Assertf(IsFiniteAll(m_vGroundVelocityIntegrated), "Integrated ground velocity is invalid.");
//Assign the ground angular velocity.
CalculateGroundAngularVelocity(GetGroundPhysical(), m_groundPhysicalComponent, m_vGroundAngularVelocity);
Assertf(IsFiniteAll(m_vGroundAngularVelocity), "Ground angular velocity is invalid.");
//Check if the ground was valid last frame.
if(bGroundWasValidLastFrame)
{
//Assign the ground acceleration.
m_vGroundAcceleration = (m_vGroundVelocity - vLastGroundVelocity);
m_vGroundAcceleration = Scale(m_vGroundAcceleration, ScalarVFromF32(1.0f / fTimeStep));
}
else
{
//Clear the ground acceleration.
m_vGroundAcceleration = Vec3V(V_ZERO);
}
//Check if the inst is valid.
if(pInst)
{
//Check if the inst is active.
if(pInst->IsInLevel() && CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()))
{
//Start with the ped's position.
Vec3V vPos = GetTransform().GetPosition();
//Move to the ped's feet.
vPos.SetZf(vPos.GetZf() - m_pCapsuleInfo->GetGroundToRootOffset());
//Assign the ground offset.
m_vGroundOffset = UnTransformOrtho(GetGroundPhysical()->GetMatrixPrePhysicsUpdate(), vPos);
}
}
}
}
void CPed::CacheGroundVelocityForJump()
{
if(GetGroundPhysical())
{
m_vGroundVelocityForJump = VEC3V_TO_VECTOR3(m_vGroundVelocityIntegrated);
}
}
void CPed::BumpedByEntity(const CEntity& rEntity, float fEntitySpeedSq)
{
//Note that we were bumped by the entity.
GetPedIntelligence()->Bumped(rEntity, fEntitySpeedSq);
}
void CPed::UpdateRagdollMatrix(bool bWarp)
{
fragInstNMGta* pRagdollInst = GetRagdollInst();
if(pRagdollInst)
{
pedAssertf(!PHSIM->GetCollider(pRagdollInst), "UpdateRagdollMatrixInactive() called with an active ragdoll instance.");
const Mat34V& mtrx = GetMatrixRef();
Assertf(!GetUsingRagdoll(), "CPed::UpdateRagdollMatrix called while ragdoll active (callstack in bug report please)");
if(bWarp)
{
pRagdollInst->SetMatrixNoZeroAssert(mtrx);
if(pRagdollInst->HasLastMatrix())
{
CPhysics::GetLevel()->SetLastInstanceMatrix(pRagdollInst, mtrx);
}
SetPedResetFlag( CPED_RESET_FLAG_SetLastMatrixDone, false );
}
else
{
if(!GetPedResetFlag( CPED_RESET_FLAG_SetLastMatrixDone ))
{
if(pRagdollInst->HasLastMatrix())
{
CPhysics::GetLevel()->SetLastInstanceMatrix(pRagdollInst, pRagdollInst->GetMatrix());
}
SetPedResetFlag( CPED_RESET_FLAG_SetLastMatrixDone, true );
}
pRagdollInst->SetMatrix(mtrx);
}
if(pRagdollInst->IsInLevel())
{
CPhysics::GetLevel()->UpdateObjectLocation(pRagdollInst->GetLevelIndex());
}
}
}
void CPed::SetUsingLowLodPhysics(bool bUseLowLodPhysics)
{
// GSALES 07/12/2011 : Fix to stop low-lod physics processing from
// reactivating collision when peds are in vehicles. Maybe we need multiple
// flags for disabling collisions?
bool bUseCollision = !bUseLowLodPhysics;
if (GetIsInVehicle())
{
bUseCollision = false;
}
// url:bugstar:516623
// If script has explicitly disabled collision on a ped, don't let AI lod reactivate it
if(GetPedConfigFlag(CPED_CONFIG_FLAG_ScriptHasDisabledCollision))
{
bUseCollision = false;
}
bUseCollision ? EnableCollision() : DisableCollision(NULL, GetPedConfigFlag(CPED_CONFIG_FLAG_ScriptHasCompletelyDisabledCollision));
SetUseExtractedZ(bUseLowLodPhysics);
// Deactivate or activate the animated inst
phInst* pInst = GetAnimatedInst();
if(pInst)
{
fwAttachmentEntityExtension *extension = GetAttachmentExtension();
bool bBasicallyAttached = extension && (extension->IsAttachStateBasicDerived() || extension->GetAttachState()==ATTACH_STATE_WORLD);
//When the ped is basically attached it shouldn't be allowed to activate
pInst->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, bUseLowLodPhysics || bBasicallyAttached);
if(pInst->IsInLevel())
{
if((bUseLowLodPhysics || bBasicallyAttached))
{
if(CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()))
{
CPhysics::GetSimulator()->DeactivateObject(pInst->GetLevelIndex());
}
else
{
// We are switching to low LOD physics, and we were not active before.
// Then, we probably won't need to activate physics when switching back
// to high LOD later, so clear this flag if it happened to be set:
SetPedConfigFlag(CPED_CONFIG_FLAG_ActivateOnSwitchFromLowPhysicsLod, false);
}
}
else if(GetPedConfigFlag(CPED_CONFIG_FLAG_ActivateOnSwitchFromLowPhysicsLod))
{
// In this case, we are switching away from low LOD physics, and
// CPED_CONFIG_FLAG_ActivateOnSwitchFromLowPhysicsLod was set. That means
// we should try to activate ourselves. This is currently used for scenario
// positioning. In most cases, this should only happen once, but we keep the
// flag on and clear it when switching back into low LOD again, if not active.
// That should ensure that the activation finished and we settled properly.
if(!CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()))
{
CPhysics::GetSimulator()->ActivateObject(pInst->GetLevelIndex());
// Could consider this here, in case we fail to activate, but I'm not confident
// that it won't fail, and that that would be a real problem:
// Assertf(CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()));
}
}
}
}
}
/*
#if __TRACK_PEDS_IN_NAVMESH
bool CPed::GetCanConstrainToNavmesh() const
{
return(m_NavMeshTracker.GetNavMeshAndPoly().m_iNavMeshIndex != NAVMESH_NAVMESH_INDEX_NONE &&
m_NavMeshTracker.GetNavMeshAndPoly().m_iPolyIndex != NAVMESH_POLY_INDEX_NONE);
}
void CPed::SetIsConstrainedToNavmesh(bool bConstrain)
{
SetPedConfigFlag( CPED_CONFIG_FLAG_ConstrainToNavMesh, bConstrain );
GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().ResetStaticCounter();
}
bool CPed::GetIsConstrainedToNavmesh() const
{
return GetPedConfigFlag( CPED_CONFIG_FLAG_ConstrainToNavMesh );
}
#endif // __TRACK_PEDS_IN_NAVMESH
void CPed::SetUsingLowLodNavigation(bool b)
{
// If switching back to regular navigation, we'll need to rescan the navmesh tracker for the navmesh/poly underfoot
if(GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodNavigation) && !b)
{
GetNavMeshTracker().Rescan(VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
}
}
*/
void CPed::SetUsingLowLodMotionTask(bool ASSERT_ONLY(b))
{
if(GetMotionData()->GetIsWalking())
{
GetMotionData()->SetForcedMotionStateThisFrame(CPedMotionStates::MotionState_Walk);
}
else if(GetMotionData()->GetIsRunning())
{
GetMotionData()->SetForcedMotionStateThisFrame(CPedMotionStates::MotionState_Run);
}
else if(GetMotionData()->GetIsSprinting())
{
GetMotionData()->SetForcedMotionStateThisFrame(CPedMotionStates::MotionState_Sprint);
}
Assert( b == GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodMotionTask));
// Only create a new motion task if the ped has a unique low lod motion task. Otherwise, all this does is restart the current
// motion task, which will cause pops.
if (HasLowLodMotionTask())
{
StartPrimaryMotionTask();
}
}
bool CPed::HasLowLodMotionTask() const
{
const CMotionTaskDataSet* pSet = GetMotionTaskDataSet();
if (Verifyf(pSet, "Ped did not have a valid MotionTaskDataSet. Check peds.meta."))
{
return pSet->HasLowLodMotionTask();
}
return false;
}
void CPed::SetAnimatedInst(phInst *pInst)
{
Assert(pInst);
m_pAnimatedInst = pInst;
m_pAnimatedInst->SetUserData((void*)this);
m_pAnimatedInst->SetInstFlag(phfwInst::FLAG_USERDATA_ENTITY, true);
}
void CPed::SetRagdollInst(fragInstNMGta *pInst)
{
Assert(pInst);
m_pRagdollInst = pInst;
// utilise userdata pointer in phInst to point back to the parent entity
m_pRagdollInst->SetUserData((void*)this);
m_pRagdollInst->SetInstFlag(phfwInst::FLAG_USERDATA_ENTITY, true);
}
float CPed::GetMassForApplyVehicleForce() const
{
if (IsUsingKinematicPhysics() && GetCapsuleInfo())
{
return GetCapsuleInfo()->GetMass();
}
return GetMass();
}
void CPed::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;
CPhysical::ProcessCollision(myInst, pHitEnt, hitInst, vMyHitPos, vOtherHitPos, fImpulseMag, vMyNormal, iMyComponent, iOtherComponent,
iOtherMaterial, bIsPositiveDepth, bIsNewContact);
}
void CPed::UpdatePaused()
{
// check for HD resource requests from any peds which may support HD
if (m_pedLodState != PLS_HD_NA)
{
// pgDictionaryBase::SetListOwnerThread(sysIpcGetCurrentThreadId());
Update_HD_Models();
// pgDictionaryBase::SetListOwnerThread(sysIpcCurrentThreadIdInvalid);
}
if (m_pPlayerInfo)
{
m_pPlayerInfo->ProcessLightEffects();
}
}
void CPed::SetRagdollStateInternal(eRagdollState nState)
{
#if __BANK
if (m_nRagdollState != nState)
{
nmEntityDebugf(this, "SetRagdollStateInternal - Switching to %s ", GetRagdollStateName(nState));
sysStack::PrintStackTrace();
}
#endif
m_nRagdollState = nState;
if(phInst* pInst = GetAnimatedInst())
{
pInst->SetInstFlag(phInstGta::FLAG_NO_COLLISION,m_nRagdollState > RAGDOLL_STATE_ANIM_DRIVEN);
}
if(m_nRagdollState == RAGDOLL_STATE_PHYS_ACTIVATE && GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
Assertf(false, "Ped %s (%p) ragdoll state set to RAGDOLL_STATE_PHYS_ACTIVATE while in re-use pool. Inst level index: %i | Ragdoll level index: %i",
GetDebugName(), this, GetCurrentPhysicsInst() ? GetCurrentPhysicsInst()->GetLevelIndex() : -1, GetRagdollInst() ? GetRagdollInst()->GetLevelIndex() : -1);
}
}
void CPed::SetRagdollState(eRagdollState nState, bool ASSERT_ONLY(bUnlocking))
{
// must call SwitchToRagdoll or SwitchToAnimated for these two states
Assert(nState!=RAGDOLL_STATE_PHYS);
Assert(nState!=RAGDOLL_STATE_ANIM || (m_nRagdollState==RAGDOLL_STATE_ANIM_PRESETUP && bUnlocking) || (m_nRagdollState==RAGDOLL_STATE_ANIM_LOCKED && bUnlocking));
SetRagdollStateInternal(nState);
m_nRagdollTimer = fwTimer::GetTimeInMilliseconds();
}
/*
bool CPed::CanUseRagdoll() const
{
// probably want to put check in here for distance to camera,
// number of ragdolls active etc..
if(GetIsAnyFixedFlagSet())
return false;
if(!CPhysics::CanUseRagdolls())
return false;
if(GetRagdollState()==RAGDOLL_STATE_ANIM_LOCKED ||
GetRagdollInst()==NULL || GetRagdollInst()->GetType()->GetARTAssetID()==-1)
{
return false;
}
return true;
}
*/
#define PED_RAGDOLL_MAXTIME_TO_SWITCH (500)
#define PED_RAGDOLL_MAXTIME_WITHOUT_CONTROL (1000)
//
void CPed::ProcessRagdollState()
{
if(GetRagdollState()>=RAGDOLL_STATE_PHYS_ACTIVATE)
{
if(fwTimer::GetTimeInMilliseconds() - m_nRagdollTimer > PED_RAGDOLL_MAXTIME_WITHOUT_CONTROL)
{
// keep the ped in ragdoll if it is migrating as a new task cannot be started on the ped
if (GetNetworkObject() && GetNetworkObject()->IsPendingOwnerChange())
{
m_nRagdollTimer = fwTimer::GetTimeInMilliseconds();
return;
}
#if __ASSERT
if(!IsNetworkClone())
{
SpewRagdollTaskInfo();
taskAssertf(false, "A task has gone too long without notifying CPed that it is in control of the ragdoll. Unmanaged ragdoll timed out and reset to animation.");
}
#endif
nmEntityDebugf(this, "CPed::ProcessRagdollState switching to animated");
SwitchToAnimated();
}
}
}
#if __ASSERT
void CPed::SpewRagdollTaskInfo() const
{
if ((fwTimer::GetTimeInMilliseconds() - m_uLastRagdollInfoTime) < 3000 )
return;
m_uLastRagdollInfoTime = fwTimer::GetTimeInMilliseconds();
GetPedIntelligence()->GetTaskManager()->SpewAllTasks(aiTaskSpew::SPEW_STD);
//spit out the task history too
for (s32 treeIndex=0; treeIndex<PED_TASK_TREE_MAX; treeIndex++)
{
nmDisplayf("TASK HISTORY FOR TREE %d:", treeIndex);
for( s32 historyIndex = 0; historyIndex < CTaskTree::MAX_TASK_HISTORY; historyIndex++ )
{
nmDisplayf("%d: %s", historyIndex, TASKCLASSINFOMGR.GetTaskName(GetPedIntelligence()->GetTaskManager()->GetHistory(treeIndex, historyIndex)));
}
}
#if __DEV
nmDisplayf("EVENTS:");
GetPedIntelligence()->PrintEvents();
#endif //___DEV
nmDisplayf("SCRIPT HISTORY:");
for (s32 i=0; i<MAX_DEBUG_SCRIPT_TASK_HISTORY; i++)
{
nmDisplayf("%s - task:%s, time:%u", GetPedIntelligence()->m_aScriptHistoryName[i],GetPedIntelligence()->m_aScriptHistoryTaskName[i], GetPedIntelligence()->m_aScriptHistoryTime[i]);
}
if (GetModelName())
{
nmDisplayf("PED MODEL: %s", GetModelName());
}
nmDisplayf("LAST RAGDOLL CONTROLLING TASK TYPE: %s", TASKCLASSINFOMGR.GetTaskName(m_LastRagdollControllingTaskType));
nmDisplayf("LAST PREPARE FOR ACTIVATION CALL STACK:");
ms_RagdollStackCollector.PrintInfoForTag( CalcRagdollStackKey(kStackPrepareForActivation));
nmDisplayf("LAST SWITCH TO RAGDOLL CALL STACK:");
ms_RagdollStackCollector.PrintInfoForTag( CalcRagdollStackKey(kStackSwitchToRagdoll) );
nmDisplayf("CURRENT RAGDOLL STATE: %s", GetRagdollStateName(GetRagdollState()));
const char * pCurrentInst = "NULL";
if (GetCurrentPhysicsInst())
{
if (GetCurrentPhysicsInst()==GetAnimatedInst())
{
pCurrentInst = "ANIMATED";
}
else if (GetCurrentPhysicsInst()==GetRagdollInst())
{
pCurrentInst = "RAGDOLL";
}
else
{
pCurrentInst = "UNKNOWN";
}
}
nmDisplayf("CURRENT PHYSICS INST :%s", pCurrentInst);
nmDisplayf("TIME SINCE LAST ACTIVATE / TICK: %u ms", fwTimer::GetTimeInMilliseconds() - m_nRagdollTimer);
nmDisplayf("CURRENT TIME: %u ms", fwTimer::GetTimeInMilliseconds());
nmDisplayf("CURRENT TIME: %u ms", fwTimer::GetTimeInMilliseconds());
nmDisplayf("CALL STACK:");
sysStack::PrintStackTrace();
}
bool CPed::VerifyRagdollHandled()
{
// It would be good to be able to assert on this for network clones too, but currently they seem to allow frquent unmanaged ragdolls
bool bRagdollHandled = IsNetworkClone() || GetPedIntelligence()->HasRagdollEvent() || GetPedIntelligence()->IsRunningRagdollTask();
if (!bRagdollHandled)
SpewRagdollTaskInfo();
return bRagdollHandled;
}
void CPed::CollectRagdollStack(eRagdollStackType type)
{
if (PARAM_debugUnmanagedRagdolls.Get())
{
ms_RagdollStackCollector.CollectStack( CalcRagdollStackKey(type), 1);
}
}
#endif // __ASSERT
void CPed::SwitchToRagdoll(CTask& ASSERT_ONLY(controllingTask))
{
#if __ASSERT
m_LastRagdollControllingTaskType = (CTaskTypes::eTaskType)controllingTask.GetTaskType();
nmEntityDebugf(this, "Switch to ragdoll called: Controlling task:%s", controllingTask.GetName().c_str());
#endif //__ASSERT
SwitchToRagdollInternal();
}
CEvent* CPed::SwitchToRagdoll(CEvent& event)
{
CEvent* pEvent = NULL;
#if __ASSERT
const CTask* pNmTask = NULL;
if (event.GetEventType()==EVENT_SWITCH_2_NM_TASK)
{
pNmTask = static_cast<const CTask*>(((CEventSwitch2NM&)event).GetTaskNM());
}
else if (event.GetEventType()==EVENT_DAMAGE)
{
pNmTask = static_cast<const CTask*>(((CEventDamage&)event).GetPhysicalResponseTask());
}
nmEntityDebugf(this, "Switch to ragdoll called: Event:%s, Task:%s, RequiresAbortForRagdoll:%s", event.GetName().c_str(), pNmTask ? pNmTask->GetName().c_str() : "?", event.RequiresAbortForRagdoll() ? "TRUE" : "FALSE");
#endif //__ASSERT
if (event.RequiresAbortForRagdoll())
{
Assertf(event.GetEventPriority()>=E_PRIORITY_SWITCH_2_NM, "NM events must be high priority! Event name: %s, event priority %d", event.GetName().c_str(), event.GetEventPriority());
// ok to use this event
pEvent = GetPedIntelligence()->AddEvent(event);
if (pEvent)
{
SwitchToRagdollInternal();
CTask* pNmTask = NULL;
if (pEvent->GetEventType()==EVENT_SWITCH_2_NM_TASK)
{
pNmTask = static_cast<CTask*>(static_cast<CEventSwitch2NM*>(pEvent)->GetTaskNM());
}
else if (event.GetEventType()==EVENT_DAMAGE)
{
pNmTask = static_cast<CTask*>(static_cast<CEventDamage*>(pEvent)->GetPhysicalResponseTask());
}
if (pNmTask)
{
if (pNmTask->GetTaskType()==CTaskTypes::TASK_NM_CONTROL)
{
smart_cast<CTaskNMControl*>(pNmTask)->SendStartMessages(this);
}
else if (pNmTask->IsNMBehaviourTask())
{
smart_cast<CTaskNMBehaviour*>(pNmTask)->SendStartMessages(this);
}
}
}
#if !__NO_OUTPUT
else
{
nmEntityDebugf(this, "SwitchToRagdoll - Failed to switch to ragdoll because the ragdoll event '%s' couldn't be added. AffectsPed: %s", event.GetName().c_str(), event.AffectsPed(this) ? "yes": "no");
}
#endif //!__NO_OUTPUT
}
else
{
taskAssertf(event.RequiresAbortForRagdoll(), "Switch to ragdoll events must require abort for ragdoll! Sending switch2nm event instead. Event passed in: %s", event.GetName().c_str());
// no deal - this event doesn't guarantee the switch to nm.
// Use a standard switch to nm event instead
CEventSwitch2NM nmEvent(10000, rage_new CTaskNMRelax(1000, 10000));
pEvent = GetPedIntelligence()->AddEvent(nmEvent);
if (pEvent)
{
SwitchToRagdollInternal();
}
#if !__NO_OUTPUT
else
{
nmEntityDebugf(this, "SwitchToRagdoll - Failed to switch to ragdoll because the backup ragdoll event '%s' couldn't be added. AffectsPed: %s", nmEvent.GetName().c_str(), nmEvent.AffectsPed(this) ? "yes": "no");
}
#endif //!__NO_OUTPUT
}
return pEvent;
}
void CPed::SwitchToRagdollPostPhysicsSimUpdate()
{
if (!GetUsingRagdoll())
{
#if __ASSERT
if (GetRagdollState()!=RAGDOLL_STATE_PHYS_ACTIVATE)
{
SpewRagdollTaskInfo();
}
Assertf(GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE, "Trying to activate the ragdoll in the post sim update, but no switch to ragdoll event has been sent!");
#endif //__ASSERT
if (GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE)
{
nmEntityDebugf(this, "Switch to ragdoll called: Post sim update");
SwitchToRagdollInternal();
}
}
}
void CPed::SwitchToRagdollInternal()
{
#if GTA_REPLAY
//Dont process rag dolls when replay is in control of the world - they should already be recorded correctly.
if(CReplayMgr::IsReplayInControlOfWorld())
return;
#endif
DR_ONLY(debugPlayback::phInstId rLastID(GetCurrentPhysicsInst()));
Assert(m_pRagdollInst);
// Assert that an Endorphin rig has been setup
Assertf(m_pRagdollInst->GetTypePhysics()->GetBodyType(), "CPed::SwitchToRagdoll - No Endorphin physics rig found for model: %s", CModelInfo::GetBaseModelInfoName(GetModelId()));
//assert that we have an appropriate ragdoll event or ragdoll controlling task running
Assertf(VerifyRagdollHandled(), "Switching to ragdoll, but no task or event exists to handle it!");
// B*2061829: We were getting an odd case where a ped was having physics added twice when being reused from the re-use pool.
// The debug shows that the ped was getting switched to animated when the ped intelligence were being flushed because the ped was in ragdoll mode.
// Given that the ped is switched to animated and has its physics removed when it's added to the re-use pool, there must be something trying to switch it between being added to the re-use pool and getting re-used.
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
Assertf(false, "Ped %s (%p) getting switched to ragdoll while in re-use pool. Inst level index: %i | Ragdoll level index: %i",
GetDebugName(), this, GetCurrentPhysicsInst() ? GetCurrentPhysicsInst()->GetLevelIndex() : -1, GetRagdollInst() ? GetRagdollInst()->GetLevelIndex() : -1);
return;
}
#if __ASSERT
CollectRagdollStack(kStackSwitchToRagdoll);
if (!m_CanUseRagdollSuccessfull && !IsNetworkClone())
{
SpewRagdollTaskInfo();
nmWarningf("Switching to ragdoll, but CanUseRagdoll was not checked successfully this frame!");
}
// DAN TEMP - Output callstack to catch bug where ped is switched to ragdoll while running an exit vehicle clone task
if(IsNetworkClone())
{
CTaskExitVehicle *pExitVehicleTask = (CTaskExitVehicle *)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE);
if(pExitVehicleTask && pExitVehicleTask->GetSubTask() && pExitVehicleTask->GetSubTask()->GetTaskType() == CTaskTypes::TASK_EXIT_VEHICLE_SEAT)
{
taskDebugf2("%s is switching to ragdoll when running an exit vehicle task!", GetNetworkObject()->GetLogName());
sysStack::PrintStackTrace();
}
}
#endif // __ASSERT
// swap instanced if there is rope attached
phInst* instToSwap = GetCurrentPhysicsInst();
ropeInstance* ropeInst = CPhysics::GetRopeManager()->FindRope( instToSwap );
if( ropeInst )
{
phConstraintDistance::Params params;
ropeInst->CopyConstraintParams(params);
if( instToSwap == params.instanceA )
params.instanceA = GetRagdollInst();
else
params.instanceB = GetRagdollInst();
ropeInst->SwapConstraint( params );
}
bool bRemoveAnimatedPhysics = true;
bool bActivateRagdollPhysics = true;
GetMotionData()->SetIsCrouching(0, -1);
if (GetAnimDirector())
{
GetAnimDirector()->WaitForPrePhysicsUpdateToComplete();
}
// always reset the ped capsule size on switching to ragdoll, to avoid popping issues when we swuitch back to animation
if (GetCurrentPhysicsInst() && (GetCurrentPhysicsInst()==GetAnimatedInst()))
ResetDesiredMainMoverCapsuleData(true);
// Ensure that the skeleton is up to date, since the bounds are about to synch to it
UpdateSkeleton();
if (IsUsingKinematicPhysics())
{
ASSERT_ONLY(bool bDisabled =) DisableKinematicPhysics();
Assert(bDisabled);
}
// Set a higher number of force solver iterations
//m_pRagdollInst->SetInstFlag(phInst::FLAG_OPTIONAL_ITERATIONS, true);
if(PHSIM->IsUpdateRunning())
{
// Since this function deletes the animated physics instance, we only want to activate the
// ragdoll inst here to mimic the normal activation of the ragdoll in the simulator. This way,
// the content of this function will be deferred until CPhysics::PostSimUpdate().
PHSIM->ActivateObject(m_pRagdollInst);
// Flag that we will activate later.
SetRagdollStateInternal(RAGDOLL_STATE_PHYS_ACTIVATE);
return;
}
// Allocate this ragdoll to the appropriate pool
if (GetDesiredRagdollPool() == CTaskNMBehaviour::kRagdollPoolInvalid)
{
SetDesiredRagdollPool(CTaskNMBehaviour::kRagdollPoolNmGameplay);
}
CTaskNMBehaviour::AddToRagdollPool(GetDesiredRagdollPool(), *this);
#if __BANK
nmEntityDebugf(this, "Switching to ragdoll");
#endif
Vec3V colliderVel = Vec3V(V_ZERO);
if(CPhysics::GetLevel()->IsInactive(GetRagdollInst()->GetLevelIndex()))
colliderVel = GetRagdollInst()->SendAnimationAverageVelToNM(fwTimer::GetTimeStep());
// Inform the vehicle ejection code of any vehicle association
GetRagdollInst()->CheckForVehicleAssociation();
bool bPedSetOutOfVehicle = false;
// always block timeslicing on the first frame we enter ragdoll.
// This is to ensure the necessary ai event / task level transitions occur
// to handle the switch.
GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
// if we're knocking a ped of their bike, or maybe other things
// (need to do this before the phys inst is swapped)
if((GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle) || GetHasJustLeftVehicle())
{
// Perform normal collision to avoid popping upon activation
GetRagdollInst()->SetDontZeroMatricesOnActivation();
bool bWasDriver = false;
// Don't remove the ped from the vehicle if we're attached
if (GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && !GetPedConfigFlag( CPED_CONFIG_FLAG_AttachedToVehicle ))
{
if (m_pMyVehicle && m_pMyVehicle->GetDriver()==this)
bWasDriver = true;
SetPedOutOfVehicle(CPed::PVF_Warp|CPed::PVF_IgnoreSafetyPositionCheck|CPed::PVF_DontResetDefaultTasks|PVF_NoNetBlenderTeleport);
bPedSetOutOfVehicle = true;
}
if(bWasDriver && m_pMyVehicle && m_pMyVehicle->GetVehicleAudioEntity()->IsHornOn())
{
m_pMyVehicle->GetVehicleAudioEntity()->StopHorn();
}
// If knocked out of a helicopter or van, ignore collision as we're probably inside it
if( m_pMyVehicle && (m_pMyVehicle->GetIsRotaryAircraft() || m_pMyVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_VAN)))
SetNoCollision(m_pMyVehicle, NO_COLLISION_RESET_WHEN_NO_IMPACTS );
if (m_pMyVehicle && m_pMyVehicle->InheritsFromBike() && bPedSetOutOfVehicle)
{
if (bWasDriver)
{
// Knock off any non-player passengers too (players will check angle / speed to radgoll in CBike::ProcessPhysics)
for (s32 i=0; i<m_pMyVehicle->GetSeatManager()->GetMaxSeats(); i++)
{
CPed* pPed = m_pMyVehicle->GetPedInSeat(i);
if (pPed && pPed!=this && !pPed->IsPlayer() && CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_VEHICLE_FALLOUT))
{
pPed->KnockPedOffVehicle(false);
}
}
}
((CBike*)m_pMyVehicle.Get())->SetBikeOnSideStand(false);
}
}
// If we're entering or exiting a vehicle then we aren't considered to be 'in' it
// We're still attached to the vehicle though and need to gracefully detach from it before activating
else if(GetPedResetFlag( CPED_RESET_FLAG_IsEnteringOrExitingVehicle ) && GetIsAttachedInCar())
{
// Perform normal collision to avoid popping upon activation
GetRagdollInst()->SetDontZeroMatricesOnActivation();
// Don't remove the ped from the vehicle if we're attached
if (!GetPedConfigFlag( CPED_CONFIG_FLAG_AttachedToVehicle ))
DetachFromParent(DETACH_FLAG_SKIP_CURRENT_POSITION_CHECK|DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK);
}
//If on a mount just pop off for now
if(GetPedConfigFlag( CPED_CONFIG_FLAG_OnMount ) && GetMyMount())
{
SetPedOffMount();
GetRagdollInst()->SetDontZeroMatricesOnActivation();
}
//If I'm mounted pop them off.
#if ENABLE_HORSE
if (m_pSeatManager && m_pSeatManager->GetDriver())
m_pSeatManager->GetDriver()->SetPedOffMount();
#endif
// Flag that we're about to activate, so that the physics has got more info about why its being removed.
// specifically so the assert that a ped is getting deactivated from a vehicle doesn't fire/
SetRagdollStateInternal(RAGDOLL_STATE_PHYS_ACTIVATE);
// need to check for special attached activation before we call RemovePhysics, as that will remove the attachment
if(GetIsAttached() && GetAttachFlag(ATTACH_FLAG_COL_ON))
GetRagdollInst()->SetDontZeroMatricesOnActivation();
// If ejecting from a vehicle, detach from the vehicle without teleporting
if( GetIsAttached() && (!GetIsInVehicle() || GetRagdollInst()->IsEjectingFromVehicle()))
DetachFromParent(DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK);
// Ragdolls can't be attached to the ground
if(GetIsAttachedToGround())
{
DetachFromGround();
}
// remove std inst from the physics world
if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel() && bRemoveAnimatedPhysics)
RemovePhysics();
#if __BANK
else
{
if(GetCurrentPhysicsInst())
Displayf("[B*1972222] Ped switching to ragdoll but isn't removing animated physics from level. Current (animated) level index: %i | bRemoveAnimatedPhysics: %i", GetCurrentPhysicsInst()->GetLevelIndex(), bRemoveAnimatedPhysics);
}
#endif
// if( m_pHorseComponent )
// {
// m_pHorseComponent->HideReins();
// }
SwitchCurrentPhysicsInst(m_pRagdollInst);
SetRagdollStateInternal(RAGDOLL_STATE_PHYS);
m_nRagdollTimer = fwTimer::GetTimeInMilliseconds();
SwitchCurrentPhysicsInst(GetRagdollInst());
if(bActivateRagdollPhysics && GetRagdollInst() && AssertVerify(GetRagdollInst()->IsInLevel()))
{
// Clear the main entity Fixed flag to allow the ragdoll to activate
// It must have been activated from a fixed seated position
ClearBaseFlag(fwEntity::IS_FIXED);
if(CPhysics::GetLevel()->IsInactive(GetRagdollInst()->GetLevelIndex()))
{
CPhysics::GetSimulator()->ActivateObject(GetRagdollInst()->GetLevelIndex());
PHSIM->SetLastInstanceMatrix(GetRagdollInst(), GetRagdollInst()->GetMatrix());
}
// If not an NM agent, add the colliderVel to all parts since the collider / last matrix / externally controlled velocity was not available when
// that calculation normally takes place in fragInst::PrepareForActivation
if (GetRagdollInst()->GetCacheEntry() && GetRagdollInst()->GetCacheEntry()->GetHierInst() && GetRagdollInst()->GetCacheEntry()->GetHierInst()->body &&
GetRagdollInst()->GetNMAgentID() == -1)
{
phArticulatedBody *body = GetRagdollInst()->GetCacheEntry()->GetHierInst()->body;
Vec3V newVel;
for (int iPart = 0; iPart < body->GetNumBodyParts(); iPart++)
{
newVel = body->GetLinearVelocity(iPart);
newVel = newVel + colliderVel;
body->SetLinearVelocity(iPart, newVel);
}
}
if(GetWeaponManager())
GetWeaponManager()->SwitchToRagdoll();
}
//m_Buoyancy.SetupBuoyancy(m_aWaterSamples, PED_WATER_SAMPLES_RAGDOLL, PED_WATER_SAMPLES_RAGDOLL, this);
Assertf(CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()), "Ragdoll didn't activate in SwitchToRagdoll");
// finally
SetMatrix(RCC_MATRIX34(GetRagdollInst()->GetMatrix()), false, false, false);
GetMovePed().SwitchToRagdoll(GetPedModelInfo()->GetInvalidRagdollFilter());
if (GetRagdollConstraintData())
{
GetRagdollConstraintData()->InsertEnabledConstraints(this);
}
// If the ped's capsule data says it should die on entering ragdoll, kill the ped here.
bool bDieFromCapsuleWhenRagdolling = !GetPedResetFlag(CPED_RESET_FLAG_NeverDieFromCapsuleRagdollSettings) && GetCapsuleInfo() && GetCapsuleInfo()->GetDiesOnEnteringRagdoll();
if ((bDieFromCapsuleWhenRagdolling ||
GetPedConfigFlag(CPED_CONFIG_FLAG_DieWhenRagdoll) || (!IsAPlayerPed() && GetPedConfigFlag(CPED_CONFIG_FLAG_CowerInsteadOfFlee))) &&
!NetworkUtils::IsNetworkCloneOrMigrating(this))
{
CEventDeath deathEvent(false, true);
GetPedIntelligence()->AddEvent(deathEvent);
}
SetActivateRagdollOnCollision(false);
SetRagdollOnCollisionIgnorePhysical(NULL);
ClearActivateRagdollOnCollisionEvent();
if (GetFacialData())
GetFacialData()->SetFacialIdleMode(this, CFacialDataComponent::kModeRagdoll);
if(NetworkInterface::IsGameInProgress())
{
NetworkInterface::OnSwitchToRagdoll(*this, bPedSetOutOfVehicle);
}
SetClothIsProne(true);
DR_ONLY(debugPlayback::CurrentInstChange(rLastID, GetCurrentPhysicsInst()));
}
void CPed::ApplyAnimatedVelocityWhilstAttached(float fTimeStep)
{
pedAssertf(GetAttachParent(), "Ped Wasn't Attached in CPed::ApplyAnimatedVelocityWhilstAttached");
// Get the current attach offsets these are in object space, so we will apply animated deltas in object space to update the offsets
Vec3V vPosAttachOffset = VECTOR3_TO_VEC3V(GetAttachOffset());
QuatV qRotAttachOffset = QUATERNION_TO_QUATV(GetAttachQuat());
// Compute the deltas for this timestep (deltas are relative to the ped)
Vec3V vPositionalDelta = VECTOR3_TO_VEC3V(GetAnimatedVelocity() * fTimeStep);
QuatV qOrientationDelta = QuatVFromZAxisAngle(ScalarVFromF32(GetAnimatedAngularVelocity() * fTimeStep));
// Transform offsets to object space
qRotAttachOffset = Normalize(Multiply(qRotAttachOffset, qOrientationDelta));
vPosAttachOffset += Transform(qRotAttachOffset, vPositionalDelta);
// Re-apply offsets
SetAttachOffset(RCC_VECTOR3(vPosAttachOffset));
SetAttachQuat(RCC_QUATERNION(qRotAttachOffset));
}
void CPed::GetZeroPoseForNM(crSkeleton& skel, bool& bHasWeaponInLeft, bool& bHasWeaponInRight)
{
if(!GetIsDeadOrDying() && GetWeaponManager()
&& GetWeaponManager()->GetEquippedWeaponObject()
&& GetWeaponManager()->GetEquippedWeaponObject()->GetWeapon())
{
bHasWeaponInRight = true;
if(GetWeaponManager()->GetEquippedWeaponObject()->GetWeapon()->GetWeaponInfo()->GetIsGun2Handed()) {
bHasWeaponInLeft = true;
}
}
//fwMvClipSetId clipSetId = CLIP_SET_ID_INVALID;
const crClip* pClip = NULL;
// If armed, set the current ITMs to get a firing stance so that NM will know
// how to point a gun correctly
if ((bHasWeaponInRight || bHasWeaponInLeft) && GetWeaponManager())
{
bool bHurt = HasHurtStarted();
crFrame frameLowerBody;
crFrame frameUpperBody;
static fwMvFilterId s_upperBodyFilter("Upperbody_filter",0x35B1D869);
crFrameDataInitializerBoneAndMover initFrame(skel.GetSkeletonData(), true);
crFrameData pedFrameData;
initFrame.InitializeFrameData(pedFrameData);
frameLowerBody.Init(pedFrameData);
frameUpperBody.Init(pedFrameData);
//find the lower body clip set
CTaskMotionBase* pBaseMotionTask = GetPrimaryMotionTask();
fwMvClipSetId lowerBodyClipSet;
if (pBaseMotionTask)
lowerBodyClipSet = bHurt ? GetPedModelInfo()->GetInjuredStrafeClipSet() : pBaseMotionTask->GetDefaultAimingStrafingClipSet(false);
fwMvClipSetId upperBodyClipSet = bHurt ? GetWeaponManager()->GetEquippedWeaponInfo()->GetInjuredWeaponClipSetId(*this) : GetWeaponManager()->GetEquippedWeaponInfo()->GetAppropriateWeaponClipSetId(this);
//get the lower body aiming anim
const crClip* pLowerClip = fwAnimManager::GetClipIfExistsBySetId(lowerBodyClipSet, CLIP_IDLE);
if (pLowerClip)
pLowerClip->Composite(frameLowerBody, 0.0f);
const crClip* pUpperClip = fwAnimManager::GetClipIfExistsBySetId(upperBodyClipSet, CLIP_AIM_MED_LOOP);
if (pUpperClip)
pUpperClip->Composite(frameUpperBody, 0.0f);
frameLowerBody.Blend(1.0f, frameUpperBody, true, g_FrameFilterDictionaryStore.FindFrameFilter(s_upperBodyFilter));
frameLowerBody.Pose(skel);
return;
}
else
{
// Let NM analyze the base pose to configure how the character should balance.
pClip = GetPrimaryMotionTask()->GetBaseIdleClip();
if (pClip)
{
GetAnimDirector()->PoseSkeletonUsingRagdollFrame(skel, pClip);
}
}
}
void CPed::SwitchToStaticFrame(float blendDuration, bool poseFromSkeleton)
{
if (poseFromSkeleton && GetAnimDirector())
{
fwAnimDirectorComponentRagDoll* componentRagDoll = GetAnimDirector()->GetComponent<fwAnimDirectorComponentRagDoll>();
if(componentRagDoll)
{
// Pose the static frame using the current skeleton
componentRagDoll->PoseFromRagDoll(*GetAnimDirector()->GetCreature());
}
}
GetMovePed().SwitchToStaticFrame(blendDuration);
}
void CPed::SwitchToAnimated(bool bAddAnimatedPhysics, bool bDeactivateRagdollPhysics, bool bFixMatrix, bool bRemoveArticulatedBodyFromCache, bool bSwitchMoveNetwork, bool bForceMotionState, bool bSwitchToStaticFrame)
{
DR_ONLY(debugPlayback::phInstId rLastID(GetCurrentPhysicsInst()));
if (GetAnimDirector())
{
GetAnimDirector()->WaitForPrePhysicsUpdateToComplete();
}
nmEntityDebugf(this, "Switching to animated: AddCapsule:%d, DeactivateRagdoll:%d, FixMatrix:%d, RemoveFromCache:%d", bAddAnimatedPhysics, bDeactivateRagdollPhysics, bFixMatrix, bRemoveArticulatedBodyFromCache);
CTaskNMBehaviour::RemoveFromRagdollPool(*this);
phInst* instToSwap = GetCurrentPhysicsInst();
ropeInstance* ropeInst = CPhysics::GetRopeManager()->FindRope( instToSwap );
phConstraintDistance::Params params;
if( ropeInst )
{
ropeInst->CopyConstraintParams(params);
}
// Revert to the default number of force solver iterations
m_pRagdollInst->SetInstFlag(phInst::FLAG_OPTIONAL_ITERATIONS, false);
Assert(GetAnimatedInst());
SwitchCurrentPhysicsInst(GetAnimatedInst());
if(m_nRagdollState > RAGDOLL_STATE_ANIM)
SetRagdollStateInternal(RAGDOLL_STATE_ANIM);
m_nRagdollTimer = fwTimer::GetTimeInMilliseconds();
// reset player control of ragdoll
if(GetPlayerInfo())
GetPlayerInfo()->SetPlayerDataHasControlOfRagdoll( false );
if (bRemoveArticulatedBodyFromCache)
GetRagdollInst()->RemoveArticulatedBodyFromCacheEntry(); // This will also deactivate the object
// deactivate the ragdoll before trying to move the ped's position
if(bDeactivateRagdollPhysics && GetRagdollInst())
{
if(GetWeaponManager())
GetWeaponManager()->SwitchToAnimated();
// Clear any constraints that might be attached to this ped.
// This calls RemoveRagdollConstraints
RemoveConstraints( GetRagdollInst() );
// Deactivate the ragdoll instance in the simulator. Do this after releasing the constraints because
// the ragdoll object may get deleted during this process which will not release the constraints for reuse.
if(CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()))
{
// Clear manifolds with a deactivating ragdoll inst if the ragdoll isn't deactivating due to sleep. This is needed because otherwise contacts with the
// deactivated ragdoll inst will stay around until the next velocity solve and the inactive inst will be treated as if it has infinite mass.
// At first I though contact screening in PreComputeImpacts would fix this, but that won't get called until after the velocity solve.
CPhysics::ClearManifoldsForInst(GetRagdollInst());
CPhysics::GetSimulator()->DeactivateObject(GetRagdollInst()->GetLevelIndex());
}
}
if(bSwitchToStaticFrame)
{
SwitchToStaticFrame(0.0f);
}
if(bFixMatrix)
{
Matrix34 rootMatrix;
GetBoneMatrix(rootMatrix, BONETAG_ROOT);
Matrix34 origRootMatrix(rootMatrix);
// get orientation from the root matrix too
float fSwitchHeading = rage::Atan2f(-rootMatrix.b.x, rootMatrix.b.y);
//fSwitchHeading -= HALF_PI; // BLEND_FROM_NM_TODO: why is this here??
rootMatrix.MakeRotateZ(fSwitchHeading);
m_MotionData.SetDesiredHeading(fSwitchHeading);
m_MotionData.SetCurrentHeading(fSwitchHeading);
WorldProbe::CShapeTestFixedResults<> probeResults;
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(rootMatrix.d, rootMatrix.d - Vector3(0.0f, 0.0f, 1.2f));
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES|ArchetypeFlags::GTA_OBJECT_TYPE|ArchetypeFlags::GTA_VEHICLE_TYPE);
probeDesc.SetExcludeEntity(this);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST))
rootMatrix.d = probeResults[0].GetHitPosition() + Vector3(0.0f, 0.0f, m_pCapsuleInfo->GetGroundToRootOffset());
SetMatrix(rootMatrix, false, false, false);
if(bSwitchToStaticFrame)
{
// Compensate for the capsule fix-up by adding an opposite offset to the root bone in the ragdoll frame
Matrix34 deltaTransform(M34_IDENTITY);
deltaTransform.DotTranspose(origRootMatrix, rootMatrix);
fwAnimDirectorComponentRagDoll* pComponentRagDoll = GetAnimDirector()->GetComponent<fwAnimDirectorComponentRagDoll>();
if(pComponentRagDoll)
{
crFrame* pFrame = pComponentRagDoll->GetFrame();
if(pFrame)
{
pFrame->SetMatrix(kTrackBoneTranslation, kTrackBoneRotation , BONETAG_ROOT, RCC_MAT34V(deltaTransform), true);
}
}
// Update the skel local matrix too. We've already generated this frame's pose, so we need to patch it with the new root value now that
// the matrix has been updated.
Mat34V_Ref localMtx = GetSkeleton()->GetLocalMtx(BONETAG_ROOT);
localMtx = MATRIX34_TO_MAT34V(deltaTransform);
UpdateSkeleton();
}
}
RecomputeCachedBoneOffsets();
//m_Buoyancy.SetupBuoyancy(m_aWaterSamples, PED_WATER_SAMPLES_RAGDOLL, PED_WATER_SAMPLES_STD, this);
// Free the ragdoll intersection memory since we won't need it anymore.
m_RagdollGroundProbeResults.FreeIntersections();
// reset some persistent stuff
m_vecGroundPosHistory[0].Zero();
m_vecGroundPosHistory[1].Zero();
m_vecSteepSlopePos.Zero();
// !! Ragdoll fixup is no longer needed now that NM runs off of rage ragdolls.
// need to get the ped's now inactive ragdoll into a state where it can be re-activated
// again immediately without screwing up
if(GetRagdollInst() && GetRagdollInst()->IsInLevel() && GetSkeleton())
{
// matrix should have been updated by now but lets make sure
GetRagdollInst()->SetMatrix(GetMatrix());
// and set last matrix as well to avoid any unwanted velocity if it activates again
PHSIM->SetLastInstanceMatrix(GetRagdollInst(), GetRagdollInst()->GetMatrix());
// we've probably already done an inverse skeleton update if we're running CTaskSimpleBlendFromNM
// but we don't know that for sure so best be safe
InverseUpdateSkeleton();
// need to pose bounds because the old fragment bounds that have been re-inserted are oriented differently from the old ones
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
// need to do it twice so that there's no unwanted velocities calculated if the ragdoll activates again straight away
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
// do all that update object location crap
Assert(GetRagdollInst()->GetArchetype());
Assert(GetRagdollInst()->GetArchetype()->GetBound());
Assert(GetRagdollInst()->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* bound = static_cast<phBoundComposite*>(GetRagdollInst()->GetArchetype()->GetBound());
float oldRadius = bound->GetRadiusAroundCentroid();
bound->CalculateCompositeExtents(true);
if(bound->GetBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetRagdollInst()->GetLevelIndex());
}
if (oldRadius != bound->GetRadiusAroundCentroid())
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetRagdollInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
else
CPhysics::GetLevel()->UpdateObjectLocation(GetRagdollInst()->GetLevelIndex());
}
// reinsert non-ragdoll phys inst as the last step
// (wants to be after CPhysics::GetLevel()->UpdateObjectLocation for ragdoll)
if(!GetCurrentPhysicsInst()->IsInLevel())
{
if(bAddAnimatedPhysics)
{
GetCurrentPhysicsInst()->SetMatrix(GetMatrix());
AddPhysics();
}
/* else if(m_pSpecialCollider)
{
// if animated inst is not getting reinserted into the world
// then flush it's associated collider so it doesn't contain old non-zero velocities and such
m_pSpecialCollider->Freeze();
}*/
}
GetPedIntelligence()->ClearRagdollEventResponse();
if (GetAnimDirector())
{
// If switching to static frame then we already switched the move network earlier
if(bSwitchMoveNetwork && !bSwitchToStaticFrame)
{
GetMovePed().SwitchToAnimated(0.f);
}
GetAnimDirector()->GetMove().SetFrame(ms_ExternallyDrivenDOFFrameId, m_pExternallyDrivenDOFFrame);
}
// This has to be called after animinst is activated
if( ropeInst )
{
if( instToSwap == params.instanceA )
params.instanceA = GetCurrentPhysicsInst();
else
params.instanceB = GetCurrentPhysicsInst();
ropeInst->SwapConstraint( params );
}
if (GetFacialData())
GetFacialData()->SetFacialIdleMode(this, CFacialDataComponent::kModeAnimated);
GetMovePed().ClearHandAnimation(INSTANT_BLEND_DURATION, CMovePed::kHandRight);
GetMovePed().ClearHandAnimation(INSTANT_BLEND_DURATION, CMovePed::kHandLeft);
if (bForceMotionState)
{
// get the motion and secondary task trees back into a good state after having been in ragdoll
// (any animation they've been playing will have been overridden by the switch to the ragdoll state)
ForceMotionStateThisFrame(CPedMotionStates::MotionState_Idle, true);
}
static bool s_bFlushSecondaryTasks = false;
if (!IsInjured() && GetPedIntelligence() && s_bFlushSecondaryTasks)
{
GetPedIntelligence()->ClearTasks(false, true);
}
if (GetRagdollInst())
{
GetRagdollInst()->ResetVehicleEjectionMembers();
}
SetClothIsProne(false);
#if __ASSERT
m_LastRagdollControllingTaskType = CTaskTypes::TASK_NONE;
ms_RagdollStackCollector.UnregisterTag( CalcRagdollStackKey(kStackPrepareForActivation) );
ms_RagdollStackCollector.UnregisterTag( CalcRagdollStackKey(kStackSwitchToRagdoll) );
#endif //__ASSERT
DR_ONLY(debugPlayback::CurrentInstChange(rLastID, GetCurrentPhysicsInst()));
}
void CPed::ApplyRagdollBlockingFlags(const eRagdollBlockingFlagsBitSet& flags)
{
ASSERT_ONLY(int nFlagsChecked = 0;)
if (flags.BitSet().IsSet(RBF_PLAYER_IMPACT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromAnyPedImpact, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_VEHICLE_IMPACT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromVehicleImpact, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_BULLET_IMPACT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromBulletImpact, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_EXPLOSION))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromExplosions, true);
aiAssert(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_RIDE_TRAIN) == NULL);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_ELECTROCUTION))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromElectrocution, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_FIRE))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFire, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_IMPACT_OBJECT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromImpactObject, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_MELEE))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromMelee, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_WATER_JET))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromWaterJet, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_FALLING))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFalling, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_DROWNING))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromDrowning, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_RUBBER_BULLET))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromRubberBullet, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_ALLOW_BLOCK_DEAD_PED))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_AllowBlockDeadPedRagdollActivation, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_PLAYER_BUMP))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromPlayerPedImpact, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_PLAYER_RAGDOLL_BUMP))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromPlayerRagdollImpact, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_PED_RAGDOLL_BUMP))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromAiRagdollImpact, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_VEHICLE_GRAB))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollForVehicleGrab, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_SMOKE_GRENADE))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromSmokeGrenade, true);
}
ASSERT_ONLY(nFlagsChecked++;)
Assertf(nFlagsChecked == eRagdollBlockingFlags_NUM_ENUMS, "New Ragdoll Blocking Flag added. Need to add check in CPed::ApplyRagdollBlockingFlags!");
}
void CPed::ClearRagdollBlockingFlags(const eRagdollBlockingFlagsBitSet& flags)
{
ASSERT_ONLY(int nFlagsChecked = 0;)
if (flags.BitSet().IsSet(RBF_PLAYER_IMPACT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromAnyPedImpact, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_VEHICLE_IMPACT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromVehicleImpact, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_BULLET_IMPACT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromBulletImpact, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_EXPLOSION))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromExplosions, false);
aiAssert(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_RIDE_TRAIN) == NULL);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_ELECTROCUTION))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromElectrocution, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_FIRE))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFire, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_IMPACT_OBJECT))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromImpactObject, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_MELEE))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromMelee, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_WATER_JET))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromWaterJet, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_FALLING))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFalling, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_DROWNING))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromDrowning, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_RUBBER_BULLET))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromRubberBullet, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_ALLOW_BLOCK_DEAD_PED))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_AllowBlockDeadPedRagdollActivation, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_PLAYER_BUMP))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromPlayerPedImpact, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_PLAYER_RAGDOLL_BUMP))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromPlayerRagdollImpact, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_PED_RAGDOLL_BUMP))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromAiRagdollImpact, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_VEHICLE_GRAB))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollForVehicleGrab, false);
}
ASSERT_ONLY(nFlagsChecked++;)
if (flags.BitSet().IsSet(RBF_SMOKE_GRENADE))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromSmokeGrenade, false);
}
ASSERT_ONLY(nFlagsChecked++;)
Assertf(nFlagsChecked == eRagdollBlockingFlags_NUM_ENUMS, "New Ragdoll Blocking Flag added. Need to add check in CPed::ClearRagdollBlockingFlags!");
}
const eRagdollBlockingFlagsBitSet CPed::GetRagdollBlockingFlags()
{
eRagdollBlockingFlagsBitSet flags;
ASSERT_ONLY(int nFlagsChecked = 0;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromAnyPedImpact))
{
flags.BitSet().Set(RBF_PLAYER_IMPACT, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromVehicleImpact))
{
flags.BitSet().Set(RBF_VEHICLE_IMPACT, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromBulletImpact))
{
flags.BitSet().Set(RBF_BULLET_IMPACT, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromExplosions))
{
flags.BitSet().Set(RBF_EXPLOSION, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromElectrocution))
{
flags.BitSet().Set(RBF_ELECTROCUTION, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFire))
{
flags.BitSet().Set(RBF_FIRE, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromImpactObject))
{
flags.BitSet().Set(RBF_IMPACT_OBJECT, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromMelee))
{
flags.BitSet().Set(RBF_MELEE, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromWaterJet))
{
flags.BitSet().Set(RBF_WATER_JET, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFalling))
{
flags.BitSet().Set(RBF_FALLING, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromDrowning))
{
flags.BitSet().Set(RBF_DROWNING, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromRubberBullet))
{
flags.BitSet().Set(RBF_RUBBER_BULLET, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_AllowBlockDeadPedRagdollActivation))
{
flags.BitSet().Set(RBF_ALLOW_BLOCK_DEAD_PED, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromPlayerPedImpact))
{
flags.BitSet().Set(RBF_PLAYER_BUMP, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromPlayerRagdollImpact))
{
flags.BitSet().Set(RBF_PLAYER_RAGDOLL_BUMP, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromAiRagdollImpact))
{
flags.BitSet().Set(RBF_PED_RAGDOLL_BUMP, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollForVehicleGrab))
{
flags.BitSet().Set(RBF_VEHICLE_GRAB, true);
}
ASSERT_ONLY(nFlagsChecked++;)
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromSmokeGrenade))
{
flags.BitSet().Set(RBF_SMOKE_GRENADE, true);
}
ASSERT_ONLY(nFlagsChecked++;)
Assertf(nFlagsChecked == eRagdollBlockingFlags_NUM_ENUMS, "New Ragdoll Blocking Flag added. Need to add check in CPed::GetRagdollBlockingFlags!");
return flags;
}
void CPed::SwitchToAnimDrivenRagdoll()
{
/* Assert(m_pRagdollInst);
Assert(GetRagdollState()==RAGDOLL_STATE_ANIM);
m_nRagdollState = RAGDOLL_STATE_ANIM_DRIVEN;
m_nRagdollTimer = fwTimer::GetTimeInMilliseconds();
if(GetRagdollInst())
{
if(CPhysics::GetLevel()->IsInactive(GetRagdollInst()->GetLevelIndex()))
CPhysics::GetSimulator()->ActivateObject(GetRagdollInst()->GetLevelIndex());
phConstraintMgr* pConstraintMgr = CPhysics::GetSimulator()->GetConstraintMgr();
phConstraint* pConstraint = NULL;
pConstraint = pConstraintMgr->AttachObjects(GetRagdollInst()->GetPosition(), GetAnimatedInst()->GetPosition(), GetRagdollInst(), GetAnimatedInst(), 0, 0);
pConstraint = pConstraintMgr->AttachObjects(GetRagdollInst()->GetPosition(), GetAnimatedInst()->GetPosition(), GetRagdollInst(), GetAnimatedInst(), 0, 0);
pConstraint->SetFixedRotation();
GetRagdollInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, true);
}*/
}
// reset ragdoll bounds to original pose (script command to help force peds to prerender
// if entity position is onscreen, but animated skeleton was offscreen last time bounds were posed
void CPed::ResetRagdollMatrices()
{
Assert(m_pRagdollInst);
Assert(GetRagdollState()==RAGDOLL_STATE_ANIM);
phBoundComposite *pBoundOriginal = GetRagdollInst()->GetTypePhysics()->GetCompositeBounds();
phBoundComposite *pBoundCurrent = (phBoundComposite*)GetRagdollInst()->GetArchetype()->GetBound();
for(int i=0; i<pBoundCurrent->GetNumBounds(); i++)
{
pBoundCurrent->SetCurrentMatrix(i, pBoundOriginal->GetCurrentMatrix(i));
}
float oldRadius = pBoundCurrent->GetRadiusAroundCentroid();
pBoundCurrent->CalculateCompositeExtents(true);
if(pBoundCurrent->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetRagdollInst()->GetLevelIndex());
}
if (oldRadius != pBoundCurrent->GetRadiusAroundCentroid())
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetRagdollInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
else
CPhysics::GetLevel()->UpdateObjectLocation(GetRagdollInst()->GetLevelIndex());
}
void CPed::SetDefaultDecisionMaker()
{
if(IsPlayer())
{
const u32 PLAYER = ATSTRINGHASH("PLAYER", 0x06f0783f5);
m_pPedIntelligence->SetDecisionMakerId(PLAYER);
}
else if (IsRegularCop())
{
const u32 COP = ATSTRINGHASH("COP", 0x0a49e591c);
m_pPedIntelligence->SetDecisionMakerId(COP);
}
else
{
CBaseModelInfo* pModelInfo = GetBaseModelInfo();
Assert(pModelInfo && pModelInfo->GetModelType() == MI_TYPE_PED);
CPedModelInfo *pPedInfo = (CPedModelInfo *) pModelInfo;
if(pPedInfo && pPedInfo->GetDecisionMakerHash().IsNotNull())
{
m_pPedIntelligence->SetDecisionMakerId(pPedInfo->GetDecisionMakerHash().GetHash());
}
else
{
Assertf(pPedInfo, "An actor is missing their model info");
Assertf(pPedInfo->GetDecisionMakerHash().IsNotNull(), "%s has a null decision maker, using DEFAULT. Add a decision maker for this actor", pModelInfo->GetModelName());
const u32 DEFAULT = ATSTRINGHASH("DEFAULT", 0x0e4df46d5);
m_pPedIntelligence->SetDecisionMakerId(DEFAULT);
}
}
}
void CPed::SetCharParamsBasedOnManagerType()
{
if(PopTypeIsMission())
{
Assert(GetPedIntelligence());
GetPedIntelligence()->GetPedPerception().SetSeeingRange(CPedPerception::ms_fSenseRangeOfMissionPeds);
GetPedIntelligence()->GetPedPerception().SetHearingRange(CPedPerception::ms_fSenseRangeOfMissionPeds);
if(!IsPlayer())
{
GetPedIntelligence()->SetInformRespectedFriends(DEFAULT_MISSION_PED_COMMUNICATION_DISTANCE, CPedIntelligence::MAX_NUM_FRIENDS_TO_INFORM_MAX_VALUE);
}
Assert(GetPortalTracker());
GetPortalTracker()->SetLoadsCollisions(true); //force loading of interior collisions
CPortalTracker::AddToActivatingTrackerList(GetPortalTracker()); // causes interiors to activate & stay active
if(!IsPlayer())
{
#if __DEV
if (GetPedConfigFlag(CPED_CONFIG_FLAG_WillFlyThroughWindscreen))
{
scriptDebugf1("Ped %s set to not fly through windscreen at frame %i, via code CPed::SetCharParamsBasedOnManagerType", GetDebugName() ? GetDebugName() : "NULL", fwTimer::GetFrameCount());
scrThread::PrePrintStackTrace();
}
#endif
SetPedConfigFlag( CPED_CONFIG_FLAG_WillTakeDamageWhenVehicleCrashes, false);
SetPedConfigFlag( CPED_CONFIG_FLAG_WillFlyThroughWindscreen, false );
}
}
}
void CPed::SetSpecialNetworkLeave()
{
m_specialNetworkLeaveTimeRemaining = NetworkUtils::GetSpecialAlphaRampingFadeOutTime();
m_specialNetworkLeaveActive = true;
m_specialNetworkLeaveWhenDead = false;
}
void CPed::SetSpecialNetworkLeaveWhenDead()
{
m_specialNetworkLeaveTimeRemaining = 2000;
m_specialNetworkLeaveActive = false;
m_specialNetworkLeaveWhenDead = true;
}
// Adjust the cull range for the ped based on model info.
void CPed::SetDefaultRemoveRangeMultiplier()
{
CBaseModelInfo* pModelInfo = GetBaseModelInfo();
Assert(pModelInfo && pModelInfo->GetModelType() == MI_TYPE_PED);
CPedModelInfo *pPedInfo = (CPedModelInfo *) pModelInfo;
if (Verifyf(pPedInfo, "Invalid ped model info!"))
{
SetRemoveRangeMultiplier(pPedInfo->GetDefaultRemoveRangeMultiplier());
}
}
// Name : Update
// Purpose : Updates CPed stuff
// Parameters : None
// Returns : Nothing
void CPed::Update()
{
// This is used to determine if the ped will run a full update (building ped lists
// and other stuff) or just a simple update
// ms_nUpdateTimer++;
// ms_nUpdateTimer %= PED_UPDATE_DELAY;
} // end - CPed::Update
bool CPed::CreateDrawable()
{
bool rt = CPhysical::CreateDrawable();
// Peds are skinned, it's a thing now.
AssignRenderFlag(RenderFlag_IS_SKINNED, true);
// Peds always use those, no matter what the artists say.
SetUseAmbientScale(true);
SetUseDynamicAmbientScale(true);
if (rt)
{
// determine if this instance needs to be flagged to draw last
CPedPropsMgr::UpdatePedForAlphaProps(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData());
CPedVariationPack::UpdatePedForAlphaComponents(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData());
return true;
}
return false;
}
void CPed::DeleteDrawable()
{
// This is done in the destructor.
/* if(m_pWeaponAtomic)
{
RwFrame* pFrame = RpAtomicGetFrame(m_pWeaponAtomic);
RpAtomicDestroy(m_pWeaponAtomic);
RwFrameDestroy(pFrame);
m_pWeaponAtomic = NULL;
}*/
CEntity::DeleteDrawable();
}
bool CPed::CanSeeEntity(CEntity *pEntity, float viewAngle)
{
AssertEntityPointerValid(pEntity);
return CanSeePosition(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()), viewAngle);
}
bool CPed::CanSeePosition(const Vector3 & vPos, float viewAngle)
{
float fViewAngle, fHeadingAngle, fAngleDiff;
const Vector3 vecThisPosition(VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
fViewAngle = ( DtoR * fwAngle::GetDegreeAngleBetweenPoints( vPos.x, vPos.y, vecThisPosition.x, vecThisPosition.y));
fViewAngle = fwAngle::LimitRadianAngleFast(fViewAngle);
fHeadingAngle = m_MotionData.GetCurrentHeading();
fHeadingAngle = fwAngle::LimitRadianAngleFast(fHeadingAngle);
fAngleDiff = rage::Abs(fViewAngle - fHeadingAngle);
if((fAngleDiff < viewAngle) || (fAngleDiff > ((2.0f*PI)-viewAngle))) // Entity is within my fov (120 deg default)
{
return true;
}
return false;
}
struct TPosPedOutOfCollision
{
static const int ms_iMaxNumAvoidSpheres = 80;
TPosPedOutOfCollision() { m_iNumAvoidSpheres = 0; }
int m_iNumAvoidSpheres;
spdSphere m_AvoidSpheres[ms_iMaxNumAvoidSpheres];
};
bool CreateAvoidSpheresListCB(CEntity * pEntity, void * pData)
{
static const Vector3 vPedOffsetToFoot(0.0f, 0.0f, -1.0f);
TPosPedOutOfCollision * pSpheresStruct = (TPosPedOutOfCollision*)pData;
if(Verifyf(pSpheresStruct->m_iNumAvoidSpheres < TPosPedOutOfCollision::ms_iMaxNumAvoidSpheres, "CPed::PositionAnyPedOutOfCollision - ran out of avoid spheres!"))
{
Vector3 vAvoidPos;
float fAvoidRadius;
if(pEntity->GetIsTypePed())
{
vAvoidPos = VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()) + vPedOffsetToFoot;
fAvoidRadius = static_cast<CPed*>(pEntity)->GetCapsuleInfo()->GetHalfWidth()*4.0f;
}
else
{
vAvoidPos = VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition());
fAvoidRadius = pEntity->GetBoundRadius();
}
pSpheresStruct->m_AvoidSpheres[pSpheresStruct->m_iNumAvoidSpheres++].Set(RCC_VEC3V(vAvoidPos), ScalarV(fAvoidRadius));
}
return (pSpheresStruct->m_iNumAvoidSpheres < TPosPedOutOfCollision::ms_iMaxNumAvoidSpheres);
}
bool CPed::PositionAnyPedOutOfCollision()
{
TPosPedOutOfCollision spheresList;
//***************************************************************************
// Create a list of nearby peds, objects & vehicles which we want to avoid.
static const float fSearchRadius = 25.0f;
fwIsSphereIntersecting searchSphere(spdSphere(GetTransform().GetPosition(), ScalarV(fSearchRadius)));
const u32 iTypeFlags = ENTITY_TYPE_MASK_OBJECT | ENTITY_TYPE_MASK_VEHICLE | ENTITY_TYPE_MASK_PED;
const u32 iCtrlFlags = SEARCH_LOCATION_EXTERIORS | SEARCH_LOCATION_INTERIORS;
CGameWorld::ForAllEntitiesIntersecting(&searchSphere, CreateAvoidSpheresListCB, &spheresList, iTypeFlags, iCtrlFlags,
SEARCH_LODTYPE_HIGHDETAIL, SEARCH_OPTION_NONE, WORLDREP_SEARCHMODULE_PEDS);
Vector3 vBestPos;
const u32 iFlags = (GetClosestPos_ClearOfObjects | GetClosestPos_ExtraThorough);
const EGetClosestPosRet eRet = CPathServer::GetClosestPositionForPed(
VEC3V_TO_VECTOR3(GetTransform().GetPosition()), // in pos
vBestPos, // out pos
fSearchRadius, // max search radius
iFlags, // flags
false, // fail if no thread access straight away
spheresList.m_iNumAvoidSpheres, // num avoid spheres
spheresList.m_AvoidSpheres // avoid spheres array
);
if(eRet==ENoPositionFound)
{
return false; // TODO: Try an alternative approach?
}
else if(eRet!=ENoPositionFound)
{
SetPosition(vBestPos, true, true, true);
return true;
}
return false;
}
// Name : OurPedCanSeeThisEntity (formerly OurPedCanSeeThisOne)
// Purpose : Works out whether this ped can see the other entity
// Parameters : None
// Returns : Nothing
bool CPed::OurPedCanSeeThisEntity(const CEntity *pEntity, bool bForTargetingPurposes, u32 nIncludeFlags, bool bAdjustForCover) const
{
float fDotProduct;
Vector2 vec2DTargetEntity; // vec2DPedHeading,
Vector3 vecMyPos(VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
Vector3 vecTargetPos(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
// vec2DPedHeading.x = ANGLE_TO_VECTOR_X(( RtoD * GetPedHeading()));
// vec2DPedHeading.y = ANGLE_TO_VECTOR_Y(( RtoD * GetPedHeading()));
vec2DTargetEntity.x = vecTargetPos.x - vecMyPos.x;
vec2DTargetEntity.y = vecTargetPos.y - vecMyPos.y;
// fDotProduct = vec2DPedHeading.x * vec2DTargetPed.x + vec2DPedHeading.y * vec2DTargetPed.y;
Vector3 ForwardVector(VEC3V_TO_VECTOR3(GetTransform().GetB()));
fDotProduct = ForwardVector.x * vec2DTargetEntity.x + ForwardVector.y * vec2DTargetEntity.y;
if(bForTargetingPurposes || ((fDotProduct >= 0.0f)&&(vec2DTargetEntity.Mag() < GetPedIntelligence()->GetPedPerception().GetSeeingRange())))
{
// now process line of sight
vecMyPos.z += 0.75f;// look from head
bool bIsTargetPedInBankInterior = false;
if(pEntity->GetType() == ENTITY_TYPE_PED)
{
const CPed* pTargetPed = static_cast<const CPed*>(pEntity);
static bool s_bAlwaysAdjustForCover = false;
bool bUseHeadPosition = (bAdjustForCover || s_bAlwaysAdjustForCover) && NetworkInterface::IsGameInProgress() && pTargetPed->IsPlayer() && pTargetPed->GetIsInCover();
if (bUseHeadPosition)
{
vecTargetPos = pTargetPed->GetBonePositionCached(BONETAG_HEAD);
}
else
{
vecTargetPos.z += 0.75f; // check to see if can shoot head (might be behind a wall, but still a visible target)
}
//url:bugstar:7001657 - Can't update old map data, so we need to determine whether we are inside this particular interior
CPortalTracker* pPortalTracker = const_cast<CPortalTracker*>(pTargetPed->GetPortalTracker());
if (pPortalTracker && pPortalTracker->IsInsideInterior() && pPortalTracker->GetInteriorNameHash() == ATSTRINGHASH("v_genbank", 0x4FCB840E))
{
bIsTargetPedInBankInterior = true;
}
}
//RAGE INC_PEDAI_LOS_COUNTER;
int nTestOptions = WorldProbe::LOS_IGNORE_SEE_THRU;
// Want to exclude the entities which define the start and end of the probe from the test.
static const s32 iNumExceptions = 2;
const CEntity* ppExcludeEnts[iNumExceptions] = { this, pEntity };
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vecMyPos, vecTargetPos);
probeDesc.SetIncludeFlags(nIncludeFlags);
probeDesc.SetOptions(nTestOptions);
probeDesc.SetExcludeEntities(ppExcludeEnts, 2);
if(bForTargetingPurposes)
probeDesc.SetContext(WorldProbe::LOS_Weapon);
#if __BANK
static bool s_bRenderOurPedCanSeeThisEntityLOStest = false;
static float s_fArrowHeadSize = 0.25f;
static u32 s_iTestDebugTTL = 100;
if (s_bRenderOurPedCanSeeThisEntityLOStest)
{
grcDebugDraw::Arrow(VECTOR3_TO_VEC3V(vecMyPos), VECTOR3_TO_VEC3V(vecTargetPos), s_fArrowHeadSize, Color_green, s_iTestDebugTTL);
}
#endif
if (!bIsTargetPedInBankInterior)
{
if (WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
return false;
}
}
else
{
// url:bugstar:7001657
// If the ped is in this interior, determine whether the intersection point of the probe and the movers, which haven't been marked correctly as see-through,
// lies within the bounds of the glass area of the bank counter in each of location
// Because we don't know which location the interior will be used in, we need to test against each one
// Bank location A is not referenced as it uses the updated heist version of the bank interior, hei_generic_bank_dlc, in which the movers are correctly marked
WorldProbe::CShapeTestHitPoint probeHitPoint;
WorldProbe::CShapeTestResults probeResults(probeHitPoint);
probeDesc.SetResultsStructure(&probeResults);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
if (probeResults[0].GetHitDetected())
{
Vector3 BankLocationB_Start = Vector3(-1211.74f, -330.98f, 38.0f);
Vector3 BankLocationB_End = Vector3(-1214.21f, -332.21f, 39.33f);
Vector3 BankLocationC_Start = Vector3(-350.50f, -50.75f, 49.26f);
Vector3 BankLocationC_End = Vector3(-353.09f, -49.79f, 50.59f);
Vector3 BankLocationD_Start = Vector3(150.32f, -1041.57f, 29.60f);
Vector3 BankLocationD_End = Vector3(147.75f, -1040.63f, 30.87f);
Vector3 BankLocationE_Start = Vector3(314.64f, -279.94f, 54.38f);
Vector3 BankLocationE_End = Vector3(312.11f, -279.02f, 55.64f);
Vector3 BankLocationF_Start = Vector3(1174.35f, 2707.47f, 38.31f);
Vector3 BankLocationF_End = Vector3(1177.08f, 2707.44f, 39.61f);
if (!CScriptAreas::IsPointInAngledArea(probeResults[0].GetHitPosition(), BankLocationB_Start, BankLocationB_End, 0.5f, false, true, false) &&
!CScriptAreas::IsPointInAngledArea(probeResults[0].GetHitPosition(), BankLocationC_Start, BankLocationC_End, 0.5f, false, true, false) &&
!CScriptAreas::IsPointInAngledArea(probeResults[0].GetHitPosition(), BankLocationD_Start, BankLocationD_End, 0.5f, false, true, false) &&
!CScriptAreas::IsPointInAngledArea(probeResults[0].GetHitPosition(), BankLocationE_Start, BankLocationE_End, 0.5f, false, true, false) &&
!CScriptAreas::IsPointInAngledArea(probeResults[0].GetHitPosition(), BankLocationF_Start, BankLocationF_End, 0.5f, false, true, false))
{
return false;
}
}
}
return true;
}
return false;
}
// processing required for requesting & switching to HD models for this ped instance
void CPed::Update_HD_Models()
{
#if 0
CPedModelInfo* modelInfo = (CPedModelInfo*)GetBaseModelInfo();
#if __ASSERT
pedAssert(modelInfo);
pedAssert(modelInfo->GetModelType() == MI_TYPE_PED);
pedAssert(modelInfo->GetHasHDFiles());
#endif
const float hddist = modelInfo->GetHDDist();
{
const Vector3& camPos = camInterface::GetPos();
const float distSqr = camPos.Dist2(VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
static dev_bool bForceHiDetail = false;
// Do we need a high model ?
bool requireHighModel = (distSqr < rage::square(hddist) && CTexLod::AllowAmbientRequests()) || bForceHiDetail || CutSceneManager::GetInstance()->IsCutscenePlayingBack();
requireHighModel = requireHighModel && (!NetworkInterface::IsGameInProgress());
#if __BANK
if (CPedVariationDebug::GetForceLdAssets())
requireHighModel = false;
if (CPedVariationDebug::GetForceHdAssets())
requireHighModel = true;
#endif // __BANK
switch (m_pedLodState)
{
case PLS_HD_NONE:
if (requireHighModel)
{
if (GetPedModelInfo()->GetIsStreamedGfx())
{
if (CPedVariationStream::RequestHdAssets(this))
m_pedLodState = PLS_HD_REQUESTED;
}
else
{
m_pedLodState = PLS_HD_REQUESTED;
}
}
break;
case PLS_HD_REQUESTED:
if (GetPedModelInfo()->GetIsStreamedGfx())
CPedVariationStream::TriggerHDTxdUpgrade(this);
m_pedLodState = PLS_HD_AVAILABLE;
break;
case PLS_HD_AVAILABLE:
if (GetPedModelInfo()->GetIsStreamedGfx())
CPedVariationStream::TriggerHDTxdUpgrade(this);
if (!requireHighModel)
{
m_pedLodState = PLS_HD_REMOVING;
}
break;
case PLS_HD_REMOVING:
if (GetPedModelInfo()->GetIsStreamedGfx())
CPedVariationStream::RemoveHdAssets(this);
m_pedLodState = PLS_HD_NONE;
break;
case PLS_HD_NA :
default :
pedAssertf(false, "Unknown Ped State %d", m_pedLodState); // illegal cases
break;
}
}
#endif
}
void CPed::UpdateCachedSceneUpdateFlags()
{
fwSceneUpdateExtension* pExt = GetExtension<fwSceneUpdateExtension>();
if(pExt)
{
m_CachedSceneUpdateFlags = pExt->m_sceneUpdateFlags;
}
else
{
m_CachedSceneUpdateFlags = 0;
}
}
#if __ASSERT
void CPed::ValidateCachedSceneUpdateFlags()
{
// If the asserts here fail, we will need to track down where the flag changed
// value, and make sure we call UpdateCachedSceneUpdateFlags() from there.
// We don't necessarily expect to have found all places where all flags can change
// value, so we only assert on the ones we really care about.
static const u32 flagsToCareAbout = CGameWorld::SU_PRESIM_PHYSICS_UPDATE;
fwSceneUpdateExtension* pExt = GetExtension<fwSceneUpdateExtension>();
if(pExt)
{
Assert((m_CachedSceneUpdateFlags & flagsToCareAbout) == (pExt->m_sceneUpdateFlags & flagsToCareAbout));
}
else
{
Assert((m_CachedSceneUpdateFlags & flagsToCareAbout) == 0);
}
}
#endif // __ASSERT
/*
// Name : SortPeds
// Purpose : Quicksorts ped list into ascending order
// Parameters : apPeds - array of ptrs to peds we want to sort
// nStartIndex, nEndIndex - start and end points to sort between
// Returns : Nothing
void CPed::SortPeds(CPed* apPeds[], s32 nStartIndex, s32 nEndIndex)
{
if (nStartIndex < nEndIndex)
{
s32 i = nStartIndex;
s32 j = nEndIndex;
float fDistance = Vector3(GetPosition() - apPeds[(i + j) / 2]->GetPosition()).Mag();
do
{
while (Vector3(GetPosition() - apPeds[i]->GetPosition()).Mag() < fDistance)
{
i++;
}
while (fDistance < Vector3(GetPosition() - apPeds[j]->GetPosition()).Mag())
{
j--;
}
if (i <= j)
{
CPed* pTempPed = apPeds[i];
apPeds[i] = apPeds[j];
apPeds[j] = pTempPed;
i++;
j--;
}
}
while (i <= j);
SortPeds(apPeds, nStartIndex, j);
SortPeds(apPeds, i, nEndIndex);
}
} // end - CPed::SortPeds
*/
void CPed::StartAiUpdate()
{
CPedFootStepHelper::UpdateClass();
CPed::Pool* pool = CPed::GetPool();
s32 i = pool->GetSize();
while(i--)
{
CPed* pPed = pool->GetSlot(i);
if(pPed && pPed->m_spatialArrayNode.IsInserted())
{
pPed->UpdateInSpatialArray();
}
}
}
void CPed::UpdateInSpatialArray()
{
Vec3V_ConstRef position = GetMatrixRef().GetCol3ConstRef();
ms_spatialArray->Update(m_spatialArrayNode, position.GetXf(), position.GetYf(), position.GetZf());
// If the assert below fails, somebody probably changed some data that FindDesiredSpatialArrayTypeFlags()
// depends on, without calling UpdateSpatialArrayTypeFlags().
#if __ASSERT
const u32 flagsFromArray = ms_spatialArray->GetTypeFlags(m_spatialArrayNode);
const u32 flagsDesired = FindDesiredSpatialArrayTypeFlags();
pedAssertf(flagsFromArray == flagsDesired, "Ped spatial array type flag mismatch, %08x != %08x", flagsFromArray, flagsDesired);
#endif // __ASSERT
}
void CPed::UpdateSpatialArrayTypeFlags()
{
if(m_spatialArrayNode.IsInserted())
{
// Find the flag values that we want.
u32 flags = FindDesiredSpatialArrayTypeFlags();
// Update all the flag values.
ms_spatialArray->SetTypeFlags(m_spatialArrayNode, ~0U, flags);
}
}
u32 CPed::FindDesiredSpatialArrayTypeFlags() const
{
u32 flags = 0;
if(GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle))
{
flags |= kSpatialArrayTypeFlagInVehicle;
}
if(GetPedConfigFlag(CPED_CONFIG_FLAG_UsingScenario))
{
flags |= kSpatialArrayTypeFlagUsingScenario;
}
if(GetPedConfigFlag(CPED_CONFIG_FLAG_VisibleOnScreen))
{
flags |= kSpatialArrayTypeFlagVisibleOnScreen;
}
#if ENABLE_HORSE
if (GetHorseComponent())
{
flags |= kSpatialArrayTypeFlagHorse;
}
#endif
return flags;
}
bool
CPed::GetIsCrouching() const
{
// If we are in cover and want to disable the concept of being crouched
// Used in special cases where cover objects are at awkward angles
if( GetPedResetFlag( CPED_RESET_FLAG_DisableCrouchWhileInCover ) &&
GetIsInCover() )
return false;
return GetMotionData()->GetIsCrouching();
}
void
CPed::SetIsCrouching(bool bCrouched, s32 iTimeInMs, bool bDoStandUpCheck, bool bForceAllow)
{
// Changing the status of the moveblenddata should now cause the
// on foot and strafing motion task to choose an appropriate crouching clip set.
if (!GetPedResetFlag(CPED_RESET_FLAG_NotAllowedToChangeCrouchState))
{
if(bCrouched && (bForceAllow || CGameConfig::Get().AllowCrouchedMovement()))
{
GetMotionData()->SetIsCrouching(fwTimer::GetTimeInMilliseconds(), iTimeInMs, bForceAllow);
}
else if ((!bDoStandUpCheck || !GetIsCrouching() || CanPedStandUp())) // GetIsCrouching is checked so if already stood up we don't call the expensive CanPedStandUp function
{
GetMotionData()->SetIsCrouching(0, -1, bForceAllow);
}
}
}
bool CPed::CanPedCrouch(void) const
{
if(IsPlayer() && GetMotionData()->GetIsSprinting())
return false;
const CWeapon* pWeapon = GetWeaponManager() ? GetWeaponManager()->GetEquippedWeapon() : NULL;
if(pWeapon && (pWeapon->GetWeaponInfo()->GetFireType() != FIRE_TYPE_MELEE
&& !pWeapon->GetWeaponInfo()->GetCanCrouchFire()))
{
return false;
}
if( GetPedConfigFlag( CPED_CONFIG_FLAG_NotAllowedToCrouch ) )
{
return false;
}
return true;
}
bool CPed::CanPedStealth(void) const
{
if(IsPlayer() && GetMotionData()->GetIsSprinting())
return false;
const CWeaponInfo* pWeaponInfo = NULL;
if(GetWeaponManager())
{
// Use the weapon in the peds hands
const CWeapon* pWeapon = GetWeaponManager()->GetEquippedWeapon();
if(pWeapon)
{
pWeaponInfo = pWeapon->GetWeaponInfo();
}
}
const bool bDisableStealth = pWeaponInfo ? pWeaponInfo->GetDisableStealth() : false;
if( bDisableStealth )
{
return false;
}
return true;
}
bool CPed::CanPedStandUp()
{
// Do an intersection check to see if the coast is clear
// Use the model info bounds instead of this ped's bounds since that will the correct size for standing up
CBaseModelInfo* pModelInfo = GetBaseModelInfo();
Assert(pModelInfo);
Assert(pModelInfo->HasPhysics());
if(pModelInfo->GetPhysics())
{
Assert(pModelInfo->GetPhysics()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* pPedBound = static_cast<phBoundComposite*>(pModelInfo->GetPhysics()->GetBound());
phBound* pMainGeomBound = pPedBound->GetBound(0);
Matrix34 boundMat = RCC_MATRIX34(pPedBound->GetCurrentMatrix(0));
boundMat.Dot(MAT34V_TO_MATRIX34(GetMatrix()));
CPed* thisPed = this;
int iIncludeTypes = ArchetypeFlags::GTA_PED_INCLUDE_TYPES;
// Don't want to test against ragdolls or pickups.
iIncludeTypes &= ~(ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_HORSE_RAGDOLL_TYPE | ArchetypeFlags::GTA_PICKUP_TYPE);
const int nNumIntersections = 10;
WorldProbe::CShapeTestBoundDesc boundTestDesc;
WorldProbe::CShapeTestFixedResults<nNumIntersections> boundTestResults;
boundTestDesc.SetResultsStructure(&boundTestResults);
boundTestDesc.SetBound(pMainGeomBound);
boundTestDesc.SetTransform(&boundMat);
boundTestDesc.SetIncludeFlags(iIncludeTypes);
boundTestDesc.SetExcludeEntity(thisPed);
boundTestDesc.SetTypeFlags(ArchetypeFlags::GTA_AI_TEST);
WorldProbe::GetShapeTestManager()->SubmitTest(boundTestDesc);
bool bHeadBlocked = false;
// Process the hits.
WorldProbe::ResultIterator it;
for(it = boundTestResults.begin(); it < boundTestResults.end() && !bHeadBlocked; ++it)
{
static dev_float fTolerance = 0.05f;
if(it->GetHitDetected() && it->GetHitDepth() > fTolerance)
{
bHeadBlocked = true;
}
}
return !bHeadBlocked;
}
return true;
}
bool CPed::IsPlayingAGestureAnim() const
{
return m_pGestureData != NULL;
}
CTaskMotionBase* CPed::StartPrimaryMotionTask() const
{
const bool bLowLod = GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodMotionTask);
CTask* pTask = ComputePrimaryMotionTask(bLowLod);
Assertf(pTask && dynamic_cast<CTaskMotionBase*>(pTask), "Motion tasks must inherit from CTaskMotionBase! - new task type %s", pTask->GetTaskName());
m_pPedIntelligence->GetTaskManager()->SetTask(PED_TASK_TREE_MOTION, pTask, PED_TASK_MOTION_DEFAULT);
return smart_cast<CTaskMotionBase*>(pTask);
}
CTaskMotionBase* CPed::CalculateCurrentMotionTask() const
{
CTaskMotionBase* pTask = 0;
pTask = smart_cast<CTaskMotionBase*>(GetPrimaryMotionTaskIfExists());
while (pTask && pTask->GetSubTask() && !pTask->ForceLeafTask())
{
pTask = smart_cast<CTaskMotionBase*>(pTask->GetSubTask());
}
return pTask;
}
CTaskMotionBase* CPed::GetCurrentMotionTask(bool bStartIfNoTask) const
{
CTaskMotionBase* pTask = 0;
if(Unlikely(m_bCachedMotionTaskDirty))
{
m_pCachedMotionTask = CalculateCurrentMotionTask();
m_bCachedMotionTaskDirty = false;
}
#if __ASSERT
else
{
Assert(m_pCachedMotionTask == CalculateCurrentMotionTask());
}
#endif
pTask = m_pCachedMotionTask.Get();
return (pTask==NULL && bStartIfNoTask ) ? StartPrimaryMotionTask() : pTask;
}
CTaskMotionBase* CPed::GetPrimaryMotionTask() const
{
CTaskMotionBase* pTask = smart_cast<CTaskMotionBase*>(m_pPedIntelligence->GetTaskManager()->GetTask(PED_TASK_TREE_MOTION, PED_TASK_MOTION_DEFAULT));
return pTask==NULL ? StartPrimaryMotionTask() : pTask;
}
CTaskMotionBase* CPed::GetPrimaryMotionTaskIfExists() const
{
CTaskMotionBase* pTask = smart_cast<CTaskMotionBase*>(m_pPedIntelligence->GetTaskManager()->GetTask(PED_TASK_TREE_MOTION, PED_TASK_MOTION_DEFAULT));
return pTask;
}
bool CPed::ForceMotionStateThisFrame( CPedMotionStates::eMotionState state, bool restartState )
{
AI_LOG_WITH_ARGS_IF_SCRIPT_OR_PLAYER_PED(this, "[PED] - %s Ped %s forcing motion state to %s (%p)\n", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), CPedMotionData::GetMotionStateString(state), this);
AI_LOG_STACK_TRACE_IF_SCRIPT_OR_PLAYER_PED(this, 8);
CTaskMotionBase* pPrimaryTask = GetPrimaryMotionTask();
GetMotionData()->SetForcedMotionStateThisFrame(state);
if (!restartState && pPrimaryTask && pPrimaryTask->IsInMotionState(state))
{
// no need to restart the task tree if the state is already correct
return true;
}
else
{
// cache the current clip sets here
CTaskMotionPed::CPersistentData data;
if (pPrimaryTask && pPrimaryTask->GetTaskType()==CTaskTypes::TASK_MOTION_PED)
{
CTaskMotionPed* pTask = static_cast<CTaskMotionPed*>(pPrimaryTask);
data.Init(*pTask);
}
m_pPedIntelligence->GetTaskManager()->GetTree(PED_TASK_TREE_MOTION)->AbortTasks();
StartPrimaryMotionTask();
pPrimaryTask = GetPrimaryMotionTask();
// Set the default clip sets back on the ped
if (pPrimaryTask && pPrimaryTask->GetTaskType()==CTaskTypes::TASK_MOTION_PED)
{
CTaskMotionPed* pTask = static_cast<CTaskMotionPed*>(pPrimaryTask);
data.Apply(*pTask);
}
return pPrimaryTask ? pPrimaryTask->SupportsMotionState(state) : false;
}
}
void CPed::RequestNetworkClonePedMotionTaskStateChange(s32 state)
{
aiAssert(IsNetworkClone());
aiAssert(GetPrimaryMotionTask());
CTaskMotionBase* pTask = GetPrimaryMotionTask();
if (pTask && pTask->GetState()!=state)
{
// When the TaskMotionPed task updates next time it will switch to the correct state....
pTask->RequestTaskStateForNetworkClone(state);
}
}
void CPed::DoPostTaskUpdateAnimChecks()
{
static const u32 MAX_NETWORK_PLAYERS_CREATED_WITHOUT_ANIM_UPDATE = 10;
if (GetAnimDirector())
{
const fwAnimDirectorComponentMove* pComponentMove = static_cast<const fwAnimDirectorComponentMove*>(GetAnimDirector()->GetComponentByType(fwAnimDirectorComponent::kComponentTypeMove));
if (pComponentMove && pComponentMove->GetNumNetworkPlayersAddedSinceLastDelete() >= MAX_NETWORK_PLAYERS_CREATED_WITHOUT_ANIM_UPDATE)
{
GetPedAiLod().SetBlockedLodFlag(CPedAILod::AL_LodTimesliceAnimUpdate);
}
}
}
bool CPed::IsAllowedToForceMotionState(const CPedMotionStates::eMotionState state, const CPed* pPed)
{
switch(state)
{
case CPedMotionStates::MotionState_Crouch_Idle:
case CPedMotionStates::MotionState_Crouch_Walk:
case CPedMotionStates::MotionState_Crouch_Run:
if(!CGameConfig::Get().AllowCrouchedMovement())
{
pedWarningf("Force motion state - Crouch is disabled");
return false;
}
return true;
case CPedMotionStates::MotionState_ActionMode_Idle:
case CPedMotionStates::MotionState_ActionMode_Walk:
case CPedMotionStates::MotionState_ActionMode_Run:
if(pPed && !pPed->IsUsingActionMode())
{
pedWarningf("Force motion state - Ped is in action mode [%s], or animation not streamed in", pPed->WantsToUseActionMode() ? "true" : "false");
return false;
}
return true;
default:
return true;
}
}
void CPed::SetSpeakerListenedTo(CEntity *speaker)
{
// Can happen during regular game update, or during audio update, so need to do correct type of REGREFing
m_SpeakerListenedTo = speaker;
if(speaker!= NULL)
{
m_SpeakerListenedToSecondary = NULL;
}
}
void CPed::RemoveSpeakerListenedTo()
{
// Remove both the speaker AND global speaker listened to
if (m_GlobalSpeakerListenedTo)
{
// Remove the existing one - there's no need for references here, this is a global speech audio entity
m_GlobalSpeakerListenedTo = NULL;
}
if (m_SpeakerListenedTo)
{
m_SpeakerListenedTo = NULL;
}
}
void CPed::SetSpeakerListenedToSecondary(CEntity *speaker)
{
// Can happen during regular game update, or during audio update, so need to do correct type of REGREFing
m_SpeakerListenedToSecondary = speaker;
}
void CPed::RemoveSpeakerListenedToSecondary()
{
if (m_SpeakerListenedToSecondary)
{
m_SpeakerListenedToSecondary = NULL;
}
}
void CPed::SetGlobalSpeakerListenedTo(audSpeechAudioEntity *globalSpeaker)
{
// And set our global one - no need for references, this is a global speech audio entity
m_GlobalSpeakerListenedTo = globalSpeaker;
}
void CPed::StartAnimUpdate(float fTimeStep)
{
fwDynamicEntityComponent *dynComp = CreateDynamicComponentIfMissing();
if (m_bResetPreviousAnimatedVelocity)
{
SetPreviousAnimatedVelocity(dynComp->GetAnimatedVelocity());
m_bResetPreviousAnimatedVelocity = false;
}
else
SetPreviousAnimatedVelocity(GetPreviousAnimatedVelocity() + dynComp->GetAnimatedVelocity());
// need to deal with woken ragdolls before we generate the pose we're going to render.
// this should only be able to happen on dead peds who's ragdolls have been
// activated by a sleep island.
if (!GetUsingRagdoll() && GetRagdollInst() && GetRagdollInst()->IsInLevel() && CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()))
{
taskAssertf(IsDead(), "Unexpectedly woken ragdoll on living ped!");
CTaskDyingDead* pDeadTask = static_cast<CTaskDyingDead*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_DYING_DEAD));
if (pDeadTask)
{
pDeadTask->SwitchToRagdoll(this);
}
}
const CPedAILod& lod = GetPedAiLod();
if(!lod.ShouldUpdateAnimsThisFrame())
{
// If we're not going to start the animation update, we can't update AI through the
// animation queue either.
StopUpdatingThroughAnimQueue();
return;
}
#if __BANK
if(CPedAILodManager::ms_bDisplayFullAnimUpdatesLines || CPedAILodManager::ms_bDisplayFullAnimUpdatesSpheres)
{
CPedAILodManager::DrawFullAnimUpdate(*this);
}
#endif // __BANK
REPLAY_ONLY(if(!CReplayMgr::IsEditModeActive()))
{
// Process any facial clips embedded in the audio
ProcessVisemes();
}
// Invalidate DOFs for this update since the upperbodyfixup expression doesn't guarantee their validity across updates.
InvalidateUpperBodyFixupDOFs();
// Normally, we want to use lod.GetUpdateAnimsTimeStep() for the update. However, this function
// gets called with a zero time step from InstantAnimUpdateStart(), and in that case, we do want
// to use zero for the update, so we treat that as a special case here.
const float lodTimeStep = lod.GetUpdateAnimsTimeStep();
const float effectiveTimeStep = Selectf(-fTimeStep, fTimeStep, lodTimeStep); // lodTimeStep unless fTimeStep is 0.
CPhysical::StartAnimUpdate(effectiveTimeStep);
}
void CPed::StartAnimUpdateAfterCamera(float UNUSED_PARAM(fTimeStep))
{
const CPedAILod& lod = GetPedAiLod();
if(!lod.ShouldUpdateAnimsThisFrame())
{
return;
}
// Invalidate DOFs for this update since the upperbodyfixup expression doesn't guarantee their validity across updates.
InvalidateUpperBodyFixupDOFs();
CPhysical::StartAnimUpdateAfterCamera(lod.GetUpdateAnimsTimeStep());
}
void CPed::EndAnimUpdate(float UNUSED_PARAM(fTimeStep))
{
const CPedAILod& lod = GetPedAiLod();
if(!lod.ShouldUpdateAnimsThisFrame())
{
return;
}
CPhysical::EndAnimUpdate(lod.GetUpdateAnimsTimeStep());
}
void CPed::UpdateVelocityAndAngularVelocity(float fTimeStep)
{
CPhysical::UpdateVelocityAndAngularVelocity(fTimeStep);
}
bool CPed::BlockConversationLookAt(CPed* pTargetPed)
{
if (!pTargetPed)
{
return true;
}
if (GetHeadIkBlocked())
{
return true;
}
// Block ambient lookats if we are :
// in ragdoll
// are running or sprinting etc
if ( GetUsingRagdoll() || GetIsCrouching() || GetMotionData()->GetIsRunning() || GetMotionData()->GetIsSprinting() )
{
return true;
}
// Do not do ambient lookats while driving
if (m_pMyVehicle && m_pMyVehicle->IsDriver(this) && !IsPlayer())
{
return true;
}
// Do not do ambient lookats while a passenger on a motorcycle
if (m_pMyVehicle && m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE && m_pMyVehicle->ContainsPed(this) && !m_pMyVehicle->IsDriver(this))
{
return true;
}
// Do not do ambient lookats while :
// in combat or driving or getting into or out of vehicles or getting up from ragdoll
// or climbing or on the phone
if ( GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_GUN)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_VEHICLE_GUN)
||(GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE) && GetPedIntelligence()->GetQueriableInterface()->GetStateForTaskType(CTaskTypes::TASK_ENTER_VEHICLE) >= CTaskEnterVehicle::State_Align)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_BLEND_FROM_NM)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_VAULT)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMPLEX_USE_MOBILE_PHONE)
|| GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_VEHICLE_MOUNTED_WEAPON) )
{
return true;
}
// Do not do ambient lookats while they are too far apart and can't see each other, e.g., talking through radio.
static dev_float fDistanceThreshold = 7.0f;
if(IsGreaterThanAll(MagSquared(Subtract(GetTransform().GetPosition(), pTargetPed->GetTransform().GetPosition())), ScalarV(square(fDistanceThreshold))))
{
Vector3 vLOSStartPos;
GetBonePosition(vLOSStartPos, BONETAG_HEAD);
const CEntity* exclusionList[2] = { this, pTargetPed};
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vLOSStartPos, VEC3V_TO_VECTOR3(pTargetPed->GetTransform().GetPosition()));
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
probeDesc.SetExcludeEntities(exclusionList, 2);
probeDesc.SetContext(WorldProbe::LOS_GeneralAI);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
return true;
}
}
static float sideAngle = 135.0f;
if (IsPlayer())
sideAngle = 180.0f;
Vector3 _min(( DtoR * -sideAngle), 0.0f, ( DtoR * -90.0f));
Vector3 _max(( DtoR * sideAngle), 0.0f, ( DtoR * 90.0f));
if (!GetIkManager().IsTargetWithinFOV(VEC3V_TO_VECTOR3(pTargetPed->GetTransform().GetPosition()), _min, _max) )
{
return true;
}
return false;
}
bool CPed::BlockGestures(BANK_ONLY(bool bTryingToPlayAGesture /* = true */)) const
{
#if __BANK
if (CGestureEditor::m_bEnabled)
{
if (CGestureEditor::m_pSpeaker == this)
{
CGestureEditor::m_pSpeaker->SetGestureAnimsAllowed(true);
return false; /* Alway allow gestures when played through the tool */
}
if (CGestureEditor::m_pListener == this)
{
CGestureEditor::m_pListener->SetGestureAnimsAllowed(true);
return false; /* Alway allow gestures when played through the tool */
}
}
if (g_ForceAllowGestures)
{
fwEntity *pSelectedEntity = g_PickerManager.GetSelectedEntity();
if(pSelectedEntity == NULL)
{
pSelectedEntity = CGameWorld::FindLocalPlayer();
}
if(pSelectedEntity)
{
CPed *pSelectedPed = static_cast< CEntity * >(pSelectedEntity)->GetIsTypePed() ? static_cast< CPed * >(pSelectedEntity) : NULL;
if(pSelectedPed)
{
pSelectedPed->SetGestureAnimsAllowed(true);
}
}
}
#endif // __BANK
#if __BANK
if ((bTryingToPlayAGesture && !GetGestureAnimsAllowed()) || ( bTryingToPlayAGesture && GetGestureAnimsBlockedFromScript()) || (!bTryingToPlayAGesture && !m_bCachedGestureAnimsAllowed))
{
m_GestureBlockReason = atVarString("BLOCKED CPED_RESET_FLAG_GestureAnimsAllowed not set!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
return true;
}
#else
if (!GetGestureAnimsAllowed() || GetGestureAnimsBlockedFromScript())
{
return true;
}
#endif // !__BANK
// Do not do gestures if we are: in ragdoll, crouching, running or sprinting etc
if ( GetUsingRagdoll() || GetIsCrouching() || GetMotionData()->GetIsRunning() || GetMotionData()->GetIsSprinting() )
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Ragdoll, Crouching, Running or Sprinting!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Do not gesture is we are in action mode
if ( IsUsingActionMode() )
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Action Mode!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Do not gesture is we are in steath
if ( GetMotionData()->GetUsingStealth() )
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Steath!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Do not do gestures while a passenger on a motorcycle
if (m_pMyVehicle && m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE && m_pMyVehicle->ContainsPed(this))
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Passenger on a motorcycle!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Do not do gestures if we are in a scenario that is blocking the gestures
CPedIntelligence* pIntelligence = GetPedIntelligence();
s32 scenarioType = pIntelligence->GetQueriableInterface()->GetRunningScenarioType();
if (scenarioType != Scenario_Invalid)
{
const CScenarioInfo* pScenarioInfo = CScenarioManager::GetScenarioInfo(scenarioType);
if (pScenarioInfo && pScenarioInfo->GetIsFlagSet(CScenarioInfoFlags::BlockGestures))
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Scenario blocking gestures (%s)!", pScenarioInfo->GetName());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
}
//Block for pending dust off animation
bool bDustOffPending = pIntelligence->GetLastGetUpTime() > 0 && (pIntelligence->GetLastGetUpTime() + CTaskAmbientClips::GetMaxTimeSinceGetUpForAmbientIdles()) > fwTimer::GetTimeInMilliseconds();
if (bDustOffPending)
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Just got up, dust off pending");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
GestureContext gestureContext = GetGestureContext();
if(m_GestureContext != GC_DEFAULT && m_GestureContext != gestureContext)
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Gesture context has changed from %s to %s!", GetGestureContextName(m_GestureContext), GetGestureContextName(gestureContext));
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Get gesture context
if (gestureVerifyf(gestureContext >= GC_DEFAULT && gestureContext < GC_MAX, "Unknown gesture context!"))
{
// Are we using the default gesture clip set?
CPedModelInfo *pModelInfo = static_cast< CPedModelInfo * >(CModelInfo::GetBaseModelInfo(GetModelId()));
if (pModelInfo && GetGestureClipSet() == pModelInfo->GetDefaultGestureClipSet())
{
// Handle vehicle gesture clip sets
if (gestureContext == GC_VEHICLE_DRIVER || gestureContext == GC_VEHICLE_PASSENGER)
{
fwMvClipSetId clipSetId = CLIP_SET_ID_INVALID;
const CVehicleLayoutInfo *pLayoutInfo = m_pMyVehicle->GetLayoutInfo();
if (pLayoutInfo)
{
int iSeatIndex = GetAttachCarSeatIndex();
if(iSeatIndex >= 0 && iSeatIndex < pLayoutInfo->GetNumSeats())
{
const CVehicleSeatAnimInfo *pVehicleSeatAnimInfo = pLayoutInfo->GetSeatAnimationInfo(iSeatIndex);
if(pVehicleSeatAnimInfo)
{
clipSetId = IsMale() ? pVehicleSeatAnimInfo->GetMaleGestureClipSetId() : pVehicleSeatAnimInfo->GetFemaleGestureClipSetId();
}
}
}
if (clipSetId == CLIP_SET_ID_INVALID)
{
// No vehicle gesture set, no vehicle gestures!
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED No vehicle gesture clip set (CLIP_SET_ID_INVALID)!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
else if(!g_pGestureManager->CanUseVehicleGestureClipSet(clipSetId))
{
// No vehicle gesture set, no vehicle gestures!
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Vehicle gesture clip set not streamed in (%s)!", clipSetId.GetCStr());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
else if(GetIsInVehicle() && GetMyVehicle()->GetDriver() == this && IsLocalPlayer())
{
CTask* pInVehicleTask = GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_MOTION_IN_AUTOMOBILE);
if (pInVehicleTask && pInVehicleTask->GetState() == CTaskMotionInAutomobile::State_Horn)
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Vehicle gesture clip set because playing horn anim (%s)!", clipSetId.GetCStr());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Block gestures on phone when driving.
if (GetPedResetFlag(CPED_RESET_FLAG_UsingMobilePhone))
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Vehicle gesture clip set because on mobile in car (%s)!", clipSetId.GetCStr());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
// Block gestures when ducking in a vehicle
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsDuckingInVehicle))
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Vehicle gesture clip set because ducking in car (%s)!", clipSetId.GetCStr());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
}
}
// Handle scenario gesture clip sets
if (GetPedResetFlag(CPED_RESET_FLAG_IsInStationaryScenario))
{
CTaskUseScenario *pTaskUseScenario = static_cast< CTaskUseScenario * >(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_USE_SCENARIO));
if (pTaskUseScenario)
{
const CConditionalAnimsGroup *pConditionalAnimsGroup = pTaskUseScenario->GetConditionalAnimsGroup();
if (pConditionalAnimsGroup)
{
s32 conditionalAnimIndex = pTaskUseScenario->GetConditionalAnimIndex();
if (conditionalAnimIndex >= 0 && conditionalAnimIndex < pConditionalAnimsGroup->GetNumAnims())
{
const CConditionalAnims *pConditionalAnims = pConditionalAnimsGroup->GetAnims(conditionalAnimIndex);
if (pConditionalAnims)
{
fwMvClipSetId clipSetId = pConditionalAnims->GetGestureClipSetId();
if (clipSetId == CLIP_SET_ID_INVALID)
{
// No scenario gesture set, no scenario gestures!
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED No scenario gesture clip set (CLIP_SET_ID_INVALID, %s)!", pTaskUseScenario->GetScenarioInfo().GetName());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
else if(!g_pGestureManager->CanUseScenarioGestureClipSet(clipSetId))
{
// No scenario gesture set, no scenario gestures!
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Scenario gesture clip set not streamed in (%s)!", clipSetId.GetCStr());
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s", fwTimer::GetFrameCount(), GetModelName(), m_GestureBlockReason.c_str()); }
#endif // __BANK
return true;
}
}
}
}
}
}
}
}
// Do any of the currently playing anims contain tags effecting gesture playback?
bool bAllowGesture = false;
if (m_PedConfigFlags.GetPedGestureMode() == GESTURE_MODE_DEFAULT)
{
bAllowGesture = true;
}
else if (m_PedConfigFlags.GetPedGestureMode() == GESTURE_MODE_USE_ANIM_BLOCK_TAGS)
{
// gestures will be blended out during gesture block tags
bAllowGesture = true;
CMovePed& move = GetMovePed();
if (move.GetTaskNetwork())
{
const CClipEventTags::CGestureControlEventTag* pProp = static_cast<const CClipEventTags::CGestureControlEventTag*>(move.GetTaskNetwork()->GetProperty(CClipEventTags::GestureControl));
if (pProp && !pProp->GetAllowed())
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Gesture clip block tag!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
bAllowGesture = false;
}
}
}
else if (m_PedConfigFlags.GetPedGestureMode() == GESTURE_MODE_USE_ANIM_ALLOW_TAGS)
{
// gestures will only be blended in during gesture allow tags
bAllowGesture = false;
CMovePed& move = GetMovePed();
if (move.GetTaskNetwork())
{
const CClipEventTags::CGestureControlEventTag* pProp = static_cast<const CClipEventTags::CGestureControlEventTag*>(move.GetTaskNetwork()->GetProperty(CClipEventTags::GestureControl));
if (pProp && pProp->GetAllowed())
{
bAllowGesture = true;
}
}
if(!bAllowGesture)
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Gesture clip missing allow tags!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
}
}
else
{
#if __BANK
m_GestureBlockReason = atVarString("BLOCKED Unknown gesture mode!");
if(bTryingToPlayAGesture) { gestureManagerDebugf("%u %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetGestureClipSet().TryGetCStr(), m_GestureBlockReason.c_str()); }
#endif // __BANK
}
return !bAllowGesture;
}
CPed::GestureContext CPed::GetGestureContext() const
{
const CPedWeaponManager *pWeaponManager = GetWeaponManager();
if (pWeaponManager)
{
const CWeaponInfo *pWeaponInfo = pWeaponManager->GetCurrentWeaponInfoForHeldObject();
if (pWeaponInfo && pWeaponInfo->GetIsTwoHanded())
{
return GC_TWO_HANDED_WEAPON;
}
}
if (GetIsInVehicle())
{
if (GetIsDrivingVehicle())
{
return GC_VEHICLE_DRIVER;
}
else
{
return GC_VEHICLE_PASSENGER;
}
}
if (GetPedResetFlag(CPED_RESET_FLAG_UsingMobilePhone))
{
return GC_CONVERSATION_PHONE;
}
if (GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_AMBIENT_CLIPS))
{
CTaskAmbientClips* pAmbientTask = static_cast<CTaskAmbientClips*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AMBIENT_CLIPS));
if (pAmbientTask)
{
if (pAmbientTask->IsInPhoneConversation())
{
return GC_CONVERSATION_PHONE;
}
else if (pAmbientTask->IsInHangoutConversation())
{
return GC_CONVERSATION_HANGOUT;
}
}
}
const CObject *pHeldObject = GetHeldObject(*this);
if (pHeldObject)
{
// The is ped holding an object
s16 iAttachBoneIndex = pHeldObject->GetAttachBone();
if (gestureVerifyf(iAttachBoneIndex != -1, "Ped is holding an object but the attached bone is -1?"))
{
if (iAttachBoneIndex == GetBoneIndexFromBoneTag(BONETAG_L_PH_HAND))
{
return GC_OBJECT_LEFT_HAND;
}
else if (iAttachBoneIndex == GetBoneIndexFromBoneTag(BONETAG_R_PH_HAND))
{
return GC_OBJECT_RIGHT_HAND;
}
}
}
const CPedMotionData *pPedMotionData = GetMotionData();
if (pPedMotionData && pPedMotionData->GetIsWalking())
{
return GC_WALKING;
}
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsSitting))
{
return GC_SITTING;
}
if (IsLocalPlayer())
{
return GC_LOCAL_PLAYER;
}
const CQueriableInterface *pQueriableInterface = GetPedIntelligence()->GetQueriableInterface();
if (pQueriableInterface &&
(pQueriableInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_MELEE) ||
pQueriableInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT) ||
pQueriableInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_GUN)))
{
return GC_COMBAT_TASK;
}
return GC_DEFAULT;
}
void CPed::ProcessGestureHashKey(const audGestureData *pGestureData, bool BANK_ONLY(fromAudio), bool BANK_ONLY(speaker))
{
// Check gesture data exists
if (gestureVerifyf(pGestureData, "Gesture data is NULL!"))
{
// Get gesture context
GestureContext gestureContext = GetGestureContext();
if (gestureVerifyf(gestureContext >= GC_DEFAULT && gestureContext < GC_MAX, "Unknown gesture context!"))
{
// Are we using the default gesture clip set?
CPedModelInfo *pModelInfo = static_cast< CPedModelInfo * >(CModelInfo::GetBaseModelInfo(GetModelId()));
if (pModelInfo && GetGestureClipSet() == pModelInfo->GetDefaultGestureClipSet())
{
// Handle vehicle gesture clip sets
if (gestureContext == GC_VEHICLE_DRIVER || gestureContext == GC_VEHICLE_PASSENGER)
{
fwMvClipSetId clipSetId = CLIP_SET_ID_INVALID;
const CVehicleLayoutInfo *pLayoutInfo = m_pMyVehicle->GetLayoutInfo();
if (pLayoutInfo)
{
int iSeatIndex = GetAttachCarSeatIndex();
if(iSeatIndex >= 0 && iSeatIndex < pLayoutInfo->GetNumSeats())
{
const CVehicleSeatAnimInfo *pVehicleSeatAnimInfo = pLayoutInfo->GetSeatAnimationInfo(iSeatIndex);
if(pVehicleSeatAnimInfo)
{
clipSetId = IsMale() ? pVehicleSeatAnimInfo->GetMaleGestureClipSetId() : pVehicleSeatAnimInfo->GetFemaleGestureClipSetId();
}
}
}
if (clipSetId != CLIP_SET_ID_INVALID && g_pGestureManager->CanUseVehicleGestureClipSet(clipSetId))
{
SetGestureClipSet(clipSetId);
}
else
{
if (clipSetId == CLIP_SET_ID_INVALID)
{
// No vehicle gesture set, no vehicle gestures!
gestureManagerDebugf("%u %s BLOCK No vehicle gesture set (CLIP_SET_ID_INVALID), no vehicle gestures!", fwTimer::GetFrameCount(), GetModelName());
}
else
{
// No vehicle gesture set, no vehicle gestures!
gestureManagerDebugf("%u %s BLOCK No vehicle gesture set (!CanUseVehicleGestureClipSet(%s)), no vehicle gestures!", fwTimer::GetFrameCount(), GetModelName(), clipSetId.GetCStr());
}
return;
}
}
// Handle scenario gesture clip sets
if (GetPedResetFlag(CPED_RESET_FLAG_IsInStationaryScenario))
{
CTaskUseScenario *pTaskUseScenario = static_cast< CTaskUseScenario * >(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_USE_SCENARIO));
if (pTaskUseScenario)
{
const CConditionalAnimsGroup *pConditionalAnimsGroup = pTaskUseScenario->GetConditionalAnimsGroup();
if (pConditionalAnimsGroup)
{
s32 conditionalAnimIndex = pTaskUseScenario->GetConditionalAnimIndex();
if (conditionalAnimIndex >= 0 && conditionalAnimIndex < pConditionalAnimsGroup->GetNumAnims())
{
const CConditionalAnims *pConditionalAnims = pConditionalAnimsGroup->GetAnims(conditionalAnimIndex);
if (pConditionalAnims)
{
fwMvClipSetId clipSetId = pConditionalAnims->GetGestureClipSetId();
if (clipSetId != CLIP_SET_ID_INVALID && g_pGestureManager->CanUseScenarioGestureClipSet(clipSetId))
{
SetGestureClipSet(clipSetId);
}
else
{
if (clipSetId == CLIP_SET_ID_INVALID)
{
// No scenario gesture set, no scenario gestures!
gestureManagerDebugf("%u %s BLOCK No scenario gesture set (CLIP_SET_ID_INVALID), no scenario gestures (%s)!", fwTimer::GetFrameCount(), GetModelName(), pTaskUseScenario->GetScenarioInfo().GetName());
}
else
{
// No scenario gesture set, no scenario gestures!
gestureManagerDebugf("%u %s BLOCK No scenario gesture set (!CanUseScenarioGestureClipSet(%s)), no scenario gestures!", fwTimer::GetFrameCount(), GetModelName(), clipSetId.GetCStr());
}
return;
}
}
}
}
}
}
}
// Get gesture clip set
fwMvClipSetId clipSetId = GetGestureClipSet();
if (clipSetId != CLIP_SET_ID_INVALID)
{
// Get gesture clip set
fwClipSet *pClipSet = fwClipSetManager::GetClipSet(clipSetId);
if (pClipSet)
{
// Get gesture clip
const crClip* pGestureClip = pClipSet->GetClip(fwMvClipId(pGestureData->ClipNameHash));
if (pGestureClip)
{
// Figure out which bones to mask out based on the gesture context
fwMvFilterId gestureFilterId(FILTER_UPPERBODYANDIK);
float rootStabilizationWeight = 1.0f;
switch (gestureContext)
{
case GC_TWO_HANDED_WEAPON:
{
// Can't move the spine or arms
gestureFilterId = BONEMASK_HEADONLY;
rootStabilizationWeight = 0.0f;
} break;
case GC_CONVERSATION_PHONE:
{
// Can't move the spine, neck or head or right arm
gestureFilterId = BONEMASK_ARMONLY_L;
rootStabilizationWeight = 0.0f;
} break;
case GC_CONVERSATION_HANGOUT:
{
// Can't move the spine
gestureFilterId = BONEMASK_HEAD_NECK_AND_ARMS;
rootStabilizationWeight = 0.0f;
} break;
case GC_OBJECT_LEFT_HAND:
{
// Can't move spine or left arm
gestureFilterId = BONEMASK_HEAD_NECK_AND_R_ARM;
rootStabilizationWeight = 0.0f;
} break;
case GC_OBJECT_RIGHT_HAND:
{
// Can't move spine or right arm
gestureFilterId = BONEMASK_HEAD_NECK_AND_L_ARM;
rootStabilizationWeight = 0.0f;
} break;
case GC_VEHICLE_DRIVER:
{
// Can't move the spine or left arm
gestureFilterId = BONEMASK_HEAD_NECK_AND_R_ARM;
rootStabilizationWeight = 0.0f;
} break;
case GC_VEHICLE_PASSENGER:
{
// Can't move the spine
gestureFilterId = BONEMASK_HEAD_NECK_AND_ARMS;
rootStabilizationWeight = 0.0f;
} break;
case GC_WALKING:
{
// Can't move the spine
gestureFilterId = BONEMASK_HEAD_NECK_AND_ARMS;
rootStabilizationWeight = 0.0f;
} break;
case GC_SITTING:
{
// Can't move the spine
gestureFilterId = BONEMASK_HEAD_NECK_AND_ARMS;
rootStabilizationWeight = 0.0f;
} break;
case GC_LOCAL_PLAYER:
{
// Can't move the spine
gestureFilterId = BONEMASK_HEAD_NECK_AND_ARMS;
rootStabilizationWeight = 0.0f;
} break;
case GC_COMBAT_TASK:
{
// Can't move the spine
gestureFilterId = BONEMASK_HEAD_NECK_AND_ARMS;
rootStabilizationWeight = 0.0f;
} break;
default:
{
} break;
}
// Do any of the currently playing anims contain tags effecting gesture playback?
if (m_PedConfigFlags.GetPedGestureMode() == GESTURE_MODE_USE_ANIM_BLOCK_TAGS)
{
CMovePed& move = GetMovePed();
if (move.GetTaskNetwork())
{
const CClipEventTags::CGestureControlEventTag* pProp = static_cast<const CClipEventTags::CGestureControlEventTag*>(move.GetTaskNetwork()->GetProperty(CClipEventTags::GestureControl));
if (pProp && pProp->GetOverrideDefaultSettings())
{
// Override the default gestures settings?
if (pProp->GetDefaultRootStabilization())
{
rootStabilizationWeight = 1.0f;
}
else
{
rootStabilizationWeight = 0.0f;
}
gestureFilterId = (fwMvFilterId)pProp->GetDefaultFilterID();
}
}
}
BlendInGesture(pGestureClip, gestureFilterId, rootStabilizationWeight, pGestureData, gestureContext);
#if __BANK
if (PARAM_debugGestures.Get() || (PARAM_debugGesturesOnSelectedPed.Get() && CDebugScene::FocusEntities_IsInGroup(this)))
{
PrintGestureInformation(GetGestureClipSet(), pGestureData, "body, success", fromAudio, speaker);
}
#endif // __BANK
}
else
{
#if __BANK
if (PARAM_debugGestures.Get() || (PARAM_debugGesturesOnSelectedPed.Get() && CDebugScene::FocusEntities_IsInGroup(this)))
{
PrintGestureInformation(GetGestureClipSet(), pGestureData, "body, failed to find hash key", fromAudio, speaker);
}
#endif // __BANK
gestureWarningf("Ped %s Gesture clip %s %u from clip set %s does not exist!", GetModelName(), atHashString(pGestureData->ClipNameHash).TryGetCStr(), pGestureData->ClipNameHash, clipSetId.TryGetCStr());
}
}
else
{
gestureWarningf("Ped %s Gesture clip set %s does not exist!", GetModelName(), clipSetId.TryGetCStr());
}
}
else
{
gestureWarningf("Ped %s Gesture clip set is invalid!", GetModelName());
}
}
}
}
u32 FramesToMs(u32 frames)
{
const float framesToMs = 1000.0f / 30.0f;
return static_cast< u32 >(static_cast< float >(frames) * framesToMs);
}
u32 SecsToMs(float secs)
{
const float secsToMs = 1000.0f;
return static_cast< u32 >(secs * secsToMs);
}
float MsToSecs(u32 ms)
{
const float msToSecs = 1.0f / 1000.0f;
return static_cast< float >(ms) * msToSecs;
}
void CPed::ControlSecondaryTaskBlend()
{
float blend = GetMovePed().GetFloat(ms_secondaryWeightOutId);
if (GetPedResetFlag(CPED_RESET_FLAG_BlockSecondaryAnim) && blend > 0.0f)
{
GetMovePed().SetFloat(ms_secondaryWeightId, Clamp(blend + NORMAL_BLEND_OUT_DELTA * fwTimer::GetGameTimer().GetTimeStep(), 0.0f, 1.0f));
}
else if (!GetPedResetFlag(CPED_RESET_FLAG_BlockSecondaryAnim) && blend < 1.0f)
{
GetMovePed().SetFloat(ms_secondaryWeightId, Clamp(blend + NORMAL_BLEND_IN_DELTA * fwTimer::GetGameTimer().GetTimeStep(), 0.0f, 1.0f));
}
}
void CPed::BlendInGesture(const crClip* pGestureClip, fwMvFilterId& gestureFilterId, float rootStabilizationWeight, const audGestureData *pGestureData, GestureContext gestureContext)
{
gestureManagerDebugf("%u %s GESTURE %s %s %s", fwTimer::GetFrameCount(), GetModelName(), GetIsInVehicle() ? "In Vehicle" : "", GetGestureClipSet().GetCStr(), pGestureClip->GetName());
/* Set gesture data */
pgRef<const crClip> pOldGestureClip = m_pGestureClip;
m_pGestureClip = pGestureClip;
if(m_pGestureClip)
{
m_pGestureClip->AddRef();
}
if(pOldGestureClip)
{
pOldGestureClip->Release(); pOldGestureClip = NULL;
}
m_GestureFilterId = gestureFilterId;
m_pGestureData = pGestureData;
m_GestureContext = gestureContext;
m_bNewGesture = true;
/* Start gesture clip */
GetMovePed().SetClip(ms_gestureClipId, m_pGestureClip);
GetMovePed().SetFloat(ms_gestureDurationId, m_pGestureData->BlendInTime);
GetMovePed().SetFilter(ms_gestureFilterId, g_FrameFilterDictionaryStore.FindFrameFilter(m_GestureFilterId));
GetMovePed().SetFloat(ms_gestureExpressionWeightId, rootStabilizationWeight);
GetMovePed().SetFloat(ms_gesturePhaseId, m_pGestureData->StartPhase);
GetMovePed().SetFloat(ms_gestureRateId, m_pGestureData->Rate);
GetMovePed().SetFloat(ms_gestureMaxWeightId, m_pGestureData->MaxWeight);
GetMovePed().SetFlag(ms_gestureBlendInFlagId, true);
GetMovePed().SetFlag(ms_gestureBlendOutFlagId, false);
GetMovePed().BroadcastRequest(ms_gestureRequestId);
}
void CPed::BlendOutGestures(bool bBlockGestures)
{
if(m_pGestureData)
{
float fBlendOutTime = m_pGestureData->BlendOutTime;
if (bBlockGestures)
{
// Use the smaller of the authored blend out time and SLOW_BLEND_DURATION
fBlendOutTime = rage::Min(m_pGestureData->BlendOutTime, SLOW_BLEND_DURATION);
}
gestureManagerDebugf("%u %s GESTURE BLEND OUT %.2f", fwTimer::GetFrameCount(), GetModelName(), fBlendOutTime);
GetMovePed().SetFloat(ms_gestureDurationId, fBlendOutTime);
GetMovePed().SetFlag(ms_gestureBlendInFlagId, false);
GetMovePed().SetFlag(ms_gestureBlendOutFlagId, true);
GetMovePed().BroadcastRequest(ms_gestureRequestId);
Assertf(m_WaveslotHeldForGestures, "NULL m_WaveslotHeldForGestures when we should be removing a reference.");
if(m_WaveslotHeldForGestures)
{
#if __BANK
if(m_pSpeechAudioEntity)
{
if(g_AudioEngine.GetSoundManager().GetTimeInMilliseconds(0) - m_pSpeechAudioEntity->GetLastTimeAnySpeechPlayed() > 1000)
{
char waveName[64];
m_pSpeechAudioEntity->GetCurrentlyPlayingWaveName(&waveName[0]);
naWarningf("Gesture system is holding onto slot for too long. context:%s pedModel:%s voiceHash:%u",
waveName, GetModelName(), m_pSpeechAudioEntity->GetAmbientVoiceName());
}
}
#endif
m_WaveslotHeldForGestures->RemoveReference();
m_WaveslotHeldForGestures = NULL;
}
#if __ASSERT
else
{
Assertf(0, "GestureData type doesn't match entity we're using to remove the waveslot ref. THIS IS VERY BAD!!! ALERT AUDIO!!!");
}
#endif
/* Reset gesture data */
if(m_pGestureData)
{
m_pGestureClip->Release(); m_pGestureClip = NULL;
}
m_GestureFilterId = FILTER_ID_INVALID;
m_pGestureData = NULL;
m_GestureContext = GC_DEFAULT;
}
}
#if __BANK
void CPed::PrintGestureInformation(const fwMvClipSetId &clipSetId, const audGestureData *pGestureData, const char *result, bool fromAudio, bool speaker)
{
int clipDictionaryIndex = -1;
const char *clipDictionaryName = NULL;
const crClip* pClip = NULL;
const char *clipName = NULL;
// Check gesture data exists+
if(gestureVerifyf(pGestureData, "Gesture data is NULL!"))
{
// Get clip set
fwClipSet *pClipSet = fwClipSetManager::GetClipSet(clipSetId);
if(pClipSet)
{
// Get clip dictionary index
clipDictionaryIndex = pClipSet->GetClipDictionaryIndex().Get();
// Get clip dictionary name
clipDictionaryName = pClipSet->GetClipDictionaryName().TryGetCStr();
// Get clip
pClip = pClipSet->GetClip(fwMvClipId(pGestureData->ClipNameHash));
if(pClip)
{
// Get clip name
clipName = pClip->GetName();
}
}
}
const char* pModelName = GetBaseModelInfo()->GetModelName();
if(fromAudio)
{
char voiceName[64] = "\0";
char waveName[64] = "\0";
u32 activeScriptedSpeechPlayTimeMs = 0;
u32 predelay = 0;
u32 remainingPredelay = 0;
if (m_pSpeechAudioEntity)
{
m_pSpeechAudioEntity->GetCurrentlyPlayingScriptedVoiceName(voiceName);
m_pSpeechAudioEntity->GetCurrentlyPlayingWaveName(waveName);
activeScriptedSpeechPlayTimeMs = m_pSpeechAudioEntity->GetActiveSpeechPlayTimeMs(predelay, remainingPredelay);
}
if(strlen(voiceName) == 0) { strcpy(voiceName, "(no voice)"); }
if(strlen(waveName) == 0) { strcpy(waveName, "(no wave)"); }
Printf("GESTURE -> audio, (0x%p = %s), %s, %s, (GT=%d), (ST=%d), (%s = %d), (%s = %d), , (BI=%6.4f), (BO=%6.4f), (SP=%6.4f), (EP=%6.4f), (MT=%d), (MW=%6.4f), (R=%6.4f), %s, %s\n",
this,
pModelName,
voiceName,
waveName,
fwTimer::GetTimeInMilliseconds(),
activeScriptedSpeechPlayTimeMs,
clipDictionaryName, clipDictionaryIndex,
clipName, pGestureData->ClipNameHash,
pGestureData->BlendInTime,
pGestureData->BlendOutTime,
pGestureData->StartPhase,
pGestureData->EndPhase,
pGestureData->MarkerTime,
pGestureData->MaxWeight,
pGestureData->Rate,
speaker ? "speaker" : "listener",
result);
}
fflush(stdout);
}
#endif // __BANK
void CPed::ProcessInCarConversationLookAtDuringWait(CPed* pPed, bool bDriver, u32 uPauseDuration, s32 sBlendInTime, s32 sBlendOutTime)
{
Assertf(pPed->GetIsInVehicle() || (pPed->GetAttachParent() && pPed->GetAttachParent()->GetType() == ENTITY_TYPE_VEHICLE), "The ped need to be in a vehicle.");
CVehicle* pMyVehicle = pPed->GetMyVehicle();
if(!pMyVehicle)
{
pMyVehicle = (CVehicle*)(pPed->GetAttachParent());
}
Assert(pMyVehicle);
Vector3 vVehicleVelocity= pMyVehicle->GetVelocity();
float fVehicleSpeed = vVehicleVelocity.Mag();
bool bLookAtVehicleMovingDirection = false;
TUNE_GROUP_FLOAT(CONVERSATION_LOOK_AT, fUseVehicleVelocityDirectionSpeedThreshold, 10.0f, 0.0f, 50.0f, 1.0f);
if(fVehicleSpeed > fUseVehicleVelocityDirectionSpeedThreshold)
{
if(bDriver || pPed->GetLookingCarMovingDirection())
{
bLookAtVehicleMovingDirection = true;
}
else
{
bLookAtVehicleMovingDirection = fwRandom::GetRandomTrueFalse();
}
}
Vector3 vLookDirection;
if(bLookAtVehicleMovingDirection)
{
vLookDirection = vVehicleVelocity;
vLookDirection.NormalizeSafe();
pPed->SetLookingCarMovingDirection(true);
}
else
{
TUNE_GROUP_FLOAT(CONVERSATION_LOOK_AT, fMinRandomLookAngle, -45.0f, -180.0f, 180.0f, 1.0f);
TUNE_GROUP_FLOAT(CONVERSATION_LOOK_AT, fMaxRandomLookAngle, 45.0f, -180.0f, 180.0f, 1.0f);
float fLookAtAngle = fwRandom::GetRandomNumberInRange(DtoR*(fMinRandomLookAngle), DtoR*(fMaxRandomLookAngle));
Matrix34 playerMat = MAT34V_TO_MATRIX34(pPed->GetMatrix());
playerMat.RotateLocalZ(fLookAtAngle);
vLookDirection = playerMat.b;
pPed->SetLookingRandomPosition(true);
}
Vector3 vPlayerHeadPos;
pPed->GetBonePosition(vPlayerHeadPos, BONETAG_HEAD);
Vector3 vOffset = vPlayerHeadPos + vLookDirection*100.0f;
const u32 uLookAtSpeakerHash = ATSTRINGHASH("camFollowPed::LookAtSpeaker", 0x064b6a006);
pPed->GetIkManager().LookAt(
uLookAtSpeakerHash,
NULL,
(s32)uPauseDuration,
BONETAG_INVALID,
&vOffset,
LF_FROM_SCRIPT | LF_WHILE_NOT_IN_FOV | LF_SLOW_TURN_RATE,
sBlendInTime,
sBlendOutTime,
CIkManager::IK_LOOKAT_LOW);
}
u32 CPed::ComputeConversationLookAtHoldTimeAndFlags(CPed* pPed, bool bDriver, bool bPassenger, u32 uHoldTimeDefault, u32 uHoldTimeMin, s32 uHoldTimeMax, float fMaxSpeed, float fMaxHoldTimeScale, float fFastTurnSpeedThreshold, u32* uOutFlags)
{
u32 uHoldTime = uHoldTimeDefault;
bool bUseFastTurnRate = false;
if(bDriver)
{
uHoldTime = (s32)fwRandom::GetRandomNumberInRange((s32)uHoldTimeMin, (s32)uHoldTimeMax);
// Scale the hold time based on speed
float fVehicleSpeed = pPed->GetMyVehicle()->GetVelocity().Mag();
float fHoldTimeModifier = Lerp(Min(fVehicleSpeed/fMaxSpeed, 1.0f), 1.0f, fMaxHoldTimeScale);
uHoldTime = (u32)((float)uHoldTime * fHoldTimeModifier);
if(fVehicleSpeed > fFastTurnSpeedThreshold)
{
bUseFastTurnRate = true;
}
}
else if(bPassenger)
{
uHoldTime = (s32)fwRandom::GetRandomNumberInRange((s32)uHoldTimeMin, (s32)uHoldTimeMax);
}
*uOutFlags = LF_FROM_SCRIPT | LF_WHILE_NOT_IN_FOV;
if(bUseFastTurnRate)
{
// Random between fast and normal
if(fwRandom::GetRandomTrueFalse())
{
*uOutFlags |= LF_FAST_TURN_RATE;
}
}
else
{
// Random between slow and normal
if(fwRandom::GetRandomTrueFalse())
{
*uOutFlags |= LF_SLOW_TURN_RATE;
}
}
switch (fwRandom::GetRandomNumberInRange(0, 5))
{
case 0:
// Use the default normal limit. Do nothing.
break;
case 1:
*uOutFlags |= LF_WIDE_YAW_LIMIT;
break;
case 2:
*uOutFlags |= LF_WIDEST_YAW_LIMIT;
break;
case 3:
*uOutFlags |= LF_NARROW_YAW_LIMIT;
break;
case 4:
*uOutFlags |= LF_NARROWEST_YAW_LIMIT;
break;
default:
;
}
return uHoldTime;
}
void CPed::ProcessConversationDrivenLookAts()
{
// Conversation driven lookats
CPlayerInfo* pPlayerInfo = CGameWorld::GetMainPlayerInfo();
if (pPlayerInfo && !pPlayerInfo->IsControlsScriptDisabled())
{
if ((m_SpeakerListenedTo && m_SpeakerListenedTo->GetIsTypePed()) || (m_SpeakerListenedToSecondary && m_SpeakerListenedToSecondary->GetIsTypePed()))
{
bool bSpeakerListenedToSecondary = false;
CPed* pSpeakerListenedTo = static_cast<CPed*>(m_SpeakerListenedTo.Get());
if(!pSpeakerListenedTo)
{
pSpeakerListenedTo = static_cast<CPed*>(m_SpeakerListenedToSecondary.Get());
bSpeakerListenedToSecondary = true;
}
if (pSpeakerListenedTo)
{
// Is the person I am listening to speaking?
audSpeechAudioEntity *pSpeechAudioEntity = pSpeakerListenedTo->GetSpeechAudioEntity();
if (pSpeechAudioEntity && (pSpeechAudioEntity->IsScriptedSpeechPlaying() || pSpeechAudioEntity->IsAmbientSpeechPlaying()))
{
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverListenWaitTimeInitMin, 100, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverListenWaitTimeInitMax, 300, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverListenWaitTimeMin, 1500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverListenWaitTimeMax, 2500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverListenHoldTimeMin, 1800, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverListenHoldTimeMax, 2800, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerListenWaitTimeInitMin, 100, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerListenWaitTimeInitMax, 200, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerListenWaitTimeMin, 750, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerListenWaitTimeMax, 1500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerListenHoldTimeMin, 2000, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerListenHoldTimeMax, 3000, 0, 10000, 10);
TUNE_GROUP_FLOAT(CONVERSATION_LOOK_AT, fMaxSpeed, 30.0f, 0.0f, 100.0f, 1.0f);
TUNE_GROUP_FLOAT(CONVERSATION_LOOK_AT, fMaxHoldTimeScale, 0.5f, 0.0f, 1.0f, 0.05f);
TUNE_GROUP_FLOAT(CONVERSATION_LOOK_AT, fFastTurnSpeedThreshold, 10.0f, 0.0f, 50.0f, 1.0f);
bool bDriver = false;
bool bPassenger = false;
if(GetIsInVehicle() || (GetAttachParent() && GetAttachParent()->GetType() == ENTITY_TYPE_VEHICLE))
{
if(GetIsDrivingVehicle())
{
bDriver = true;
}
else
{
bPassenger = true;
}
}
const u32 uLookAtSpeakerHash = ATSTRINGHASH("camFollowPed::LookAtSpeaker", 0x064b6a006);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uHoldTimeDefault, 500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, sBlendInTime, 750, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, sBlendOutTime, 500, 0, 10000, 10);
// Add some delay if it's the first look at in car
if(m_uPrevConversationLookAtTime == 0 || m_bWasSpeaking)
{
m_uPrevConversationLookAtTime = fwTimer::GetTimeInMilliseconds();
m_uPrevConversationLookAtDuration = 0;
m_uConversationLookAtWaitTime = 0;
if(bDriver)
{
m_uConversationLookAtWaitTime = (s32)fwRandom::GetRandomNumberInRange((s32)uDriverListenWaitTimeInitMin, (s32)uDriverListenWaitTimeInitMax);
}
else if(bPassenger)
{
m_uConversationLookAtWaitTime = (s32)fwRandom::GetRandomNumberInRange((s32)uPassengerListenWaitTimeInitMin, (s32)uPassengerListenWaitTimeInitMax);
}
m_bWasSpeaking = false;
}
// Make sure the next request is sent out before the previous one times out. Fixed the eyes flickering.
if(fwTimer::GetTimeInMilliseconds() >= (m_uPrevConversationLookAtTime + m_uConversationLookAtWaitTime + m_uPrevConversationLookAtDuration - fwTimer::GetTimeStepInMilliseconds()) )
{
// Is it appropriate for me to look at the speaker?
if (GetAutoConversationLookAts() && !BlockConversationLookAt(pSpeakerListenedTo))
{
u32 uHoldTimeMin = uHoldTimeDefault;
u32 uHoldTimeMax = uHoldTimeDefault;
if(bDriver)
{
uHoldTimeMin = uDriverListenHoldTimeMin;
uHoldTimeMax = uDriverListenHoldTimeMax;
}
else if(bPassenger)
{
uHoldTimeMin = uPassengerListenHoldTimeMin;
uHoldTimeMax = uPassengerListenHoldTimeMax;
}
u32 lookAtFlags;
u32 uHoldTime = ComputeConversationLookAtHoldTimeAndFlags(this, bDriver, bPassenger, uHoldTimeDefault, uHoldTimeMin, uHoldTimeMax, fMaxSpeed, fMaxHoldTimeScale, fFastTurnSpeedThreshold, &lookAtFlags);
GetIkManager().LookAt(
uLookAtSpeakerHash,
pSpeakerListenedTo,
(s32)uHoldTime,
BONETAG_HEAD,
NULL,
lookAtFlags,
sBlendInTime,
sBlendOutTime,
CIkManager::IK_LOOKAT_LOW);
// Add wait time for the next look at.
m_uConversationLookAtWaitTime = 0;
if(bDriver)
{
m_uConversationLookAtWaitTime = (s32)fwRandom::GetRandomNumberInRange((s32)uDriverListenWaitTimeMin, (s32)uDriverListenWaitTimeMax);
}
else if(bPassenger)
{
m_uConversationLookAtWaitTime = (s32)fwRandom::GetRandomNumberInRange((s32)uPassengerListenWaitTimeMin, (s32)uPassengerListenWaitTimeMax);
}
m_uPrevConversationLookAtTime = fwTimer::GetTimeInMilliseconds();
m_uPrevConversationLookAtDuration = uHoldTime;
m_bLookingRandomPosition = false;
m_bLookingCarMovingDirection = false;
}
}
else if((bDriver || bPassenger) && m_uPrevConversationLookAtTime > 0 && (fwTimer::GetTimeInMilliseconds() - m_uPrevConversationLookAtTime) > (m_uPrevConversationLookAtDuration - sBlendOutTime) )
{
if(!GetLookingRandomPosition())
{
ProcessInCarConversationLookAtDuringWait(this, bDriver, m_uConversationLookAtWaitTime, sBlendInTime, sBlendOutTime);
}
}
// Is it appropriate for the speaker to look at me?
bool bTalkingToPlayer = (IsAPlayerPed() && !pSpeakerListenedTo->PopTypeIsMission());
if (!bSpeakerListenedToSecondary && (bTalkingToPlayer || pSpeakerListenedTo->GetAutoConversationLookAts()) && !pSpeakerListenedTo->BlockConversationLookAt(this))
{
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverSpeakWaitTimeInitMin, 0, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverSpeakWaitTimeInitMax, 200, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverSpeakWaitTimeMin, 750, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverSpeakWaitTimeMax, 1500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverSpeakHoldTimeMin, 2500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uDriverSpeakHoldTimeMax, 3500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerSpeakWaitTimeInitMin, 0, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerSpeakWaitTimeInitMax, 200, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerSpeakWaitTimeMin, 500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerSpeakWaitTimeMax, 1000, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerSpeakHoldTimeMin, 2500, 0, 10000, 10);
TUNE_GROUP_INT(CONVERSATION_LOOK_AT, uPassengerSpeakHoldTimeMax, 3000, 0, 10000, 10);
bool bSpeakerIsDriver = false;
bool bSpeakerIsPassenger = false;
if(pSpeakerListenedTo->GetIsInVehicle() || (pSpeakerListenedTo->GetAttachParent() && pSpeakerListenedTo->GetAttachParent()->GetType() == ENTITY_TYPE_VEHICLE))
{
if(pSpeakerListenedTo->GetIsDrivingVehicle())
{
bSpeakerIsDriver = true;
}
else
{
bSpeakerIsPassenger = true;
}
}
// Add some delay if it's the first look at in car
if(pSpeakerListenedTo->GetPrevConversationLookAtTime() == 0 || !pSpeakerListenedTo->GetWasSpeaking())
{
pSpeakerListenedTo->SetPrevConversationLookAtTime(fwTimer::GetTimeInMilliseconds());
pSpeakerListenedTo->SetPrevConversationLookAtDuration(0);
pSpeakerListenedTo->SetConversationLookAtWaitTime(0);
if(bSpeakerIsDriver)
{
pSpeakerListenedTo->SetConversationLookAtWaitTime((s32)fwRandom::GetRandomNumberInRange((s32)uDriverSpeakWaitTimeInitMin, (s32)uDriverSpeakWaitTimeInitMax));
}
else if(bSpeakerIsPassenger)
{
pSpeakerListenedTo->SetConversationLookAtWaitTime((s32)fwRandom::GetRandomNumberInRange((s32)uPassengerSpeakWaitTimeInitMin, (s32)uPassengerSpeakWaitTimeInitMax));
}
pSpeakerListenedTo->SetWasSpeaking(true);
}
// Make sure the next request is sent out before the previous one times out. Fixed the eyes flickering.
if(fwTimer::GetTimeInMilliseconds() >= (pSpeakerListenedTo->GetPrevConversationLookAtTime() + pSpeakerListenedTo->GetConversationLookAtWaitTime() + pSpeakerListenedTo->GetPrevConversationLookAtDuration() - fwTimer::GetTimeStepInMilliseconds()))
{
u32 uHoldTimeMin = uHoldTimeDefault;
u32 uHoldTimeMax = uHoldTimeDefault;
if(bSpeakerIsDriver)
{
uHoldTimeMin = uDriverSpeakHoldTimeMin;
uHoldTimeMax = uDriverSpeakHoldTimeMax;
}
else if(bSpeakerIsPassenger)
{
uHoldTimeMin = uPassengerSpeakHoldTimeMin;
uHoldTimeMax = uPassengerSpeakHoldTimeMax;
}
u32 lookAtFlags;
u32 uHoldTime = ComputeConversationLookAtHoldTimeAndFlags(pSpeakerListenedTo, bSpeakerIsDriver, bSpeakerIsPassenger, uHoldTimeDefault, uHoldTimeMin, uHoldTimeMax, fMaxSpeed, fMaxHoldTimeScale, fFastTurnSpeedThreshold, &lookAtFlags);
pSpeakerListenedTo->GetIkManager().LookAt(
uLookAtSpeakerHash,
this,
(s32)uHoldTime,
BONETAG_HEAD,
NULL,
lookAtFlags,
sBlendInTime,
sBlendOutTime,
CIkManager::IK_LOOKAT_LOW);
// Add wait time for the next look at.
pSpeakerListenedTo->SetConversationLookAtWaitTime(0);
if(bSpeakerIsDriver)
{
pSpeakerListenedTo->SetConversationLookAtWaitTime((s32)fwRandom::GetRandomNumberInRange((s32)uDriverSpeakWaitTimeMin, (s32)uDriverSpeakWaitTimeMax));
}
else if(bSpeakerIsPassenger)
{
pSpeakerListenedTo->SetConversationLookAtWaitTime((s32)fwRandom::GetRandomNumberInRange((s32)uPassengerSpeakWaitTimeMin, (s32)uPassengerSpeakWaitTimeMax));
}
pSpeakerListenedTo->SetPrevConversationLookAtTime(fwTimer::GetTimeInMilliseconds());
pSpeakerListenedTo->SetPrevConversationLookAtDuration(uHoldTime);
pSpeakerListenedTo->SetLookingRandomPosition(false);
pSpeakerListenedTo->SetLookingCarMovingDirection(false);
}
else if((bSpeakerIsDriver || bSpeakerIsPassenger) && pSpeakerListenedTo->GetPrevConversationLookAtTime() > 0 && (fwTimer::GetTimeInMilliseconds() - pSpeakerListenedTo->GetPrevConversationLookAtTime()) > (pSpeakerListenedTo->GetPrevConversationLookAtDuration() - sBlendOutTime))
{
if(!pSpeakerListenedTo->GetLookingRandomPosition())
{
ProcessInCarConversationLookAtDuringWait(pSpeakerListenedTo, bSpeakerIsDriver, pSpeakerListenedTo->GetConversationLookAtWaitTime(), sBlendInTime, sBlendOutTime);
}
}
}
}
}
}
}
};
void CPed::ProcessBodyAndFacialGesturesEmbeddedInAudioAssets()
{
bool bBlockGestures = false;
bool bBlockGesturesChecked = false;
// Am I currently speaking?
const audGestureData *speakerGestureData = NULL;
if(m_pSpeechAudioEntity)
{
if(!m_pGestureData)
{
m_WaveslotHeldForGestures = m_pSpeechAudioEntity->AddRefToActiveSoundSlot();
}
speakerGestureData = m_pSpeechAudioEntity->GetLastGestureDataForCategoryID(AUD_SPEECH_METADATA_CATEGORY_GESTURE_SPEAKER);
if(speakerGestureData)
{
if (!bBlockGesturesChecked )
{
bBlockGestures = BlockGestures();
bBlockGesturesChecked = true;
}
if (bBlockGestures)
{
#if __BANK
if(PARAM_debugGestures.Get() || (PARAM_debugGesturesOnSelectedPed.Get() && CDebugScene::FocusEntities_IsInGroup(this)))
{
PrintGestureInformation(GetGestureClipSet(), speakerGestureData, "body, blocked", true, true);
}
#endif // __BANK
}
else
{
// We have a valid speaker gesture this frame.
ProcessGestureHashKey(speakerGestureData, true, true);
}
}
if(m_WaveslotHeldForGestures && !m_pGestureData)
{
m_WaveslotHeldForGestures->RemoveReference();
m_WaveslotHeldForGestures = NULL;
}
}
// Am I currently listening to someone?
// This could be a real ped, or a static audio entity, check for both
m_SpeakerSpeechAudioEntity = NULL;
if (m_SpeakerListenedTo)
{
m_SpeakerSpeechAudioEntity = ((CPed*)m_SpeakerListenedTo.Get())->GetSpeechAudioEntity();
}
else if (m_GlobalSpeakerListenedTo)
{
m_SpeakerSpeechAudioEntity = m_GlobalSpeakerListenedTo;
}
if(m_SpeakerSpeechAudioEntity)
{
if(!m_pGestureData)
{
m_WaveslotHeldForGestures = m_pSpeechAudioEntity->AddRefToActiveSoundSlot();
}
const audGestureData *listenerGestureData = m_SpeakerSpeechAudioEntity->GetLastGestureDataForCategoryID(AUD_SPEECH_METADATA_CATEGORY_GESTURE_LISTENER);
if(listenerGestureData)
{
if (!bBlockGesturesChecked )
{
bBlockGestures = BlockGestures();
bBlockGesturesChecked = true;
}
if (bBlockGestures)
{
#if __BANK
if(PARAM_debugGestures.Get() || (PARAM_debugGesturesOnSelectedPed.Get() && CDebugScene::FocusEntities_IsInGroup(this)))
{
PrintGestureInformation(GetGestureClipSet(), listenerGestureData, "body, blocked", true, false);
}
#endif // __BANK
}
else
{
// We have a valid listener gesture this frame.
ProcessGestureHashKey(listenerGestureData, true, false);
}
}
if(m_WaveslotHeldForGestures && !m_pGestureData)
{
m_WaveslotHeldForGestures->RemoveReference();
m_WaveslotHeldForGestures = NULL;
}
}
// Process gestures
if(m_pGestureData && m_pGestureClip)
{
if (!bBlockGesturesChecked )
{
bBlockGestures = BlockGestures();
bBlockGesturesChecked = true;
}
if (!m_bNewGesture)
{
float clipDuration = m_pGestureClip->GetDuration();
float gestureDuration = clipDuration / m_pGestureData->Rate;
float endPhase = m_pGestureData->EndPhase - (m_pGestureData->BlendOutTime / gestureDuration);
float gesturePhase = GetMovePed().GetFloat(ms_gesturePhaseOutId);
if(gesturePhase >= endPhase || bBlockGestures)
{
#if __BANK
if(PARAM_debugGestures.Get() || (PARAM_debugGesturesOnSelectedPed.Get() && CDebugScene::FocusEntities_IsInGroup(this)))
{
if (gesturePhase >= endPhase)
{
PrintGestureInformation(GetGestureClipSet(), m_pGestureData, "body, blending out (reached the blendout phase)", true, true);
}
else
{
PrintGestureInformation(GetGestureClipSet(), m_pGestureData, "body, blending out (became blocked)", true, true);
}
}
#endif // __BANK
BlendOutGestures(bBlockGestures);
}
}
else
{
m_bNewGesture = false;
}
}
// Does this ped support facial?
if(GetFacialData())
{
// Am I currently speaking to someone?
u32 speakerFacialHash;
if(m_pSpeechAudioEntity && m_pSpeechAudioEntity->GetLastMetadataHash(AUD_SPEECH_METADATA_CATEGORY_FACIAL_SPEAKER, speakerFacialHash))
{
// We have a valid speaker facial gesture this frame.
GetFacialData()->ProcessFacialHashKey(this, speakerFacialHash, 0, true, true);
}
// Am I currently listening to someone?
if(m_SpeakerSpeechAudioEntity)
{
u32 listenerFacialHash;
if(m_SpeakerSpeechAudioEntity->GetLastMetadataHash(AUD_SPEECH_METADATA_CATEGORY_FACIAL_LISTENER, listenerFacialHash))
{
// We have a valid listener facial gesture this frame
GetFacialData()->ProcessFacialHashKey(this, listenerFacialHash, 0, true, false);
}
}
}
// Are any conversation driven lookats needed?
ProcessConversationDrivenLookAts();
// Reset the gesture and facial gesture sets to their defaults
CPedModelInfo* pModelInfo = (CPedModelInfo*)GetBaseModelInfo();
Assert(pModelInfo);
if (pModelInfo)
{
SetGestureClipSet(pModelInfo->GetDefaultGestureClipSet());
/*
CFacialDataComponent* pFacialData = GetFacialData();
if(pFacialData)
{
fwFacialClipSetGroup* pFacialClipSetGroup = fwFacialClipSetGroupManager::GetFacialClipSetGroup(pModelInfo->GetFacialClipSetGroupId());
if (pFacialClipSetGroup)
{
fwMvClipSetId facialBaseClipsetId(pFacialClipSetGroup->GetBaseClipSetName());
Assertf(fwClipSetManager::GetClipSet(facialBaseClipsetId), "%s:Unrecognised facial base clipset", pFacialClipSetGroup->GetBaseClipSetName().GetCStr());
pFacialData->SetFacialClipSet(this, facialBaseClipsetId);
}
}
*/
}
// Clear the gesture anims allowed ped reset flag
#if __BANK
m_bCachedGestureAnimsAllowed = GetPedResetFlag( CPED_RESET_FLAG_GestureAnimsAllowed );
#endif
SetPedResetFlag( CPED_RESET_FLAG_GestureAnimsAllowed, false );
SetPedResetFlag( CPED_RESET_FLAG_GestureAnimsBlockedFromScript, false );
}
void CPed::BlendInVisemeBodyAdditive()
{
if(GetPedResetFlag(CPED_RESET_FLAG_DisableVisemeBodyAdditive))
{
return;
}
static const fwMvClipSetId visemeBodyAdditiveClipSetId("facials@gestures", 0x5b27db15);
static const fwMvClipId visemeBodyAdditiveShoutedClipId("TALK_ADDITIVE_SHOUTED", 0xfe9150a8);
static const fwMvClipId visemeBodyAdditiveSpokenClipId("TALK_ADDITIVE_SPOKEN", 0x770fec72);
fwMvClipId visemeBodyAdditiveClipId = (m_pSpeechAudioEntity->GetCachedVolType() == AUD_SPEECH_SHOUT) ? visemeBodyAdditiveShoutedClipId : visemeBodyAdditiveSpokenClipId;
/* Set viseme data */
pgRef<const crClip> pOldVisemeBodyAdditiveClip = m_pVisemeBodyAdditiveClip;
m_pVisemeBodyAdditiveClip = fwAnimManager::GetClipIfExistsBySetId(visemeBodyAdditiveClipSetId, visemeBodyAdditiveClipId);
if(m_pVisemeBodyAdditiveClip)
{
m_pVisemeBodyAdditiveClip->AddRef();
}
if(pOldVisemeBodyAdditiveClip)
{
pOldVisemeBodyAdditiveClip->Release(); pOldVisemeBodyAdditiveClip = NULL;
}
m_fVisemeBodyAdditiveWeight = 0.0f;
/* Start viseme clip */
GetMovePed().SetClip(ms_visemeBodyAdditiveClipId, m_pVisemeBodyAdditiveClip);
GetMovePed().SetFloat(ms_visemeBodyAdditivePhaseId, fwRandom::GetRandomNumberInRange(0.0f, 1.0f));
GetMovePed().SetFloat(ms_visemeBodyAdditiveDurationId, NORMAL_BLEND_DURATION);
GetMovePed().SetFloat(ms_visemeBodyAdditiveWeightId, 0.0f);
GetMovePed().SetFlag(ms_visemeBodyAdditiveBlendInFlagId, true);
GetMovePed().SetFlag(ms_visemeBodyAdditiveBlendOutFlagId, false);
GetMovePed().BroadcastRequest(ms_visemeBodyAdditiveRequestId);
}
void CPed::BlendOutVisemeBodyAdditive()
{
/* Stop viseme clip */
GetMovePed().SetFloat(ms_visemeBodyAdditiveDurationId, SLOW_BLEND_DURATION);
GetMovePed().SetFlag(ms_visemeBodyAdditiveBlendInFlagId, false);
GetMovePed().SetFlag(ms_visemeBodyAdditiveBlendOutFlagId, true);
GetMovePed().BroadcastRequest(ms_visemeBodyAdditiveRequestId);
/* Reset viseme data */
if(m_pVisemeBodyAdditiveClip)
{
m_pVisemeBodyAdditiveClip->Release(); m_pVisemeBodyAdditiveClip = NULL;
}
m_fVisemeBodyAdditiveWeight = 0.0f;
#if __BANK
m_visemeDebugInfo.m_clipName.SetHash((u32)0);
#endif // __BANK
}
void CPed::ProcessVoiceDrivenMouthMovement()
{
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fDurangoVoiceLoudnessActivateThreshold, 0.01f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fDurangoVoiceLoudnessDeactivateThreshold, 0.05f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fDurangoVoiceDebounceThreshold, 0.005f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_INT(VOICE_DRIVEN_MOUTH_MOVEMENT, iDurangoVoiceMinimumActivationTimeMs, 200, 0, 1000, 10);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fOrbisVoiceLoudnessActivateThreshold, 0.2f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fOrbisVoiceLoudnessDeactivateThreshold, 0.7f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fOrbisVoiceDebounceThreshold, 0.1f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_INT(VOICE_DRIVEN_MOUTH_MOVEMENT, iOrbisVoiceMinimumActivationTimeMs, 200, 0, 1000, 10);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fOtherVoiceLoudnessActivateThreshold, 0.1f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fOtherVoiceLoudnessDeactivateThreshold, 0.33f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fOtherVoiceDebounceThreshold, 0.033f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_INT(VOICE_DRIVEN_MOUTH_MOVEMENT, iOtherVoiceMinimumActivationTimeMs, 200, 0, 1000, 10);
#if RSG_DURANGO
float fLoudnessActivateThreshold = fDurangoVoiceLoudnessActivateThreshold;
float fLoudnessDeactivateThreshold = fDurangoVoiceLoudnessDeactivateThreshold;
float fDebounceThreshold = fDurangoVoiceDebounceThreshold;
s32 iMinimumActivationTimeMs = iDurangoVoiceMinimumActivationTimeMs;
#elif RSG_ORBIS
float fLoudnessActivateThreshold = fOrbisVoiceLoudnessActivateThreshold;
float fLoudnessDeactivateThreshold = fOrbisVoiceLoudnessDeactivateThreshold;
float fDebounceThreshold = fOrbisVoiceDebounceThreshold;
s32 iMinimumActivationTimeMs = iOrbisVoiceMinimumActivationTimeMs;
#else
float fLoudnessActivateThreshold = fOtherVoiceLoudnessActivateThreshold;
float fLoudnessDeactivateThreshold = fOtherVoiceLoudnessDeactivateThreshold;
float fDebounceThreshold = fOtherVoiceDebounceThreshold;
s32 iMinimumActivationTimeMs = iOtherVoiceMinimumActivationTimeMs;
#endif
bool bVoiceDrivenMouthMovement = false;
if (NetworkInterface::IsGameInProgress() && IsPlayer())
{
if (GetAnimDirector())
{
fwAnimDirectorComponentFacialRig* pFacialRigComp = GetAnimDirector()->GetComponentByPhase<fwAnimDirectorComponentFacialRig>(fwAnimDirectorComponent::kPhaseAll);
if (pFacialRigComp)
{
strLocalIndex clipDictionarySlot = g_ClipDictionaryStore.FindSlotFromHashKey(ATSTRINGHASH("mp_facial", 0x4503F6B9));
if (Verifyf(g_ClipDictionaryStore.IsValidSlot(clipDictionarySlot), "Could not find clip dictionary mp_facial in the clip dictionary store!"))
{
if (Verifyf(g_ClipDictionaryStore.HasObjectLoaded(clipDictionarySlot), "Clip dictionary mp_facial has not been loaded!"))
{
CNetObjPlayer *netObjPlayer = static_cast< CNetObjPlayer * >(GetNetworkObject());
if (netObjPlayer && !IsDead())
{
if (IsLocalPlayer())
{
float fVoiceLoudness = pFacialRigComp->GetVoiceLoudness();
float fOldVoiceLoudness = fVoiceLoudness;
const CNetGamePlayer *pPlayerOwner = netObjPlayer->GetPlayerOwner();
if (pPlayerOwner)
{
bool bHasMicrophone = NetworkInterface::GetVoice().GamerHasHeadset(pPlayerOwner->GetGamerInfo().GetGamerHandle());
if (bHasMicrophone)
{
fVoiceLoudness = NetworkInterface::GetVoice().GetPlayerLoudness(NetworkInterface::GetLocalGamerIndex());
}
else
{
fVoiceLoudness = 0.0f;
}
pFacialRigComp->SetVoiceLoudness(fVoiceLoudness);
}
s32 iVoiceStartTimeMs = pFacialRigComp->GetVoiceStartTimeMs();
if (iVoiceStartTimeMs == -1)
{
bVoiceDrivenMouthMovement = (fVoiceLoudness > fLoudnessActivateThreshold) && (fVoiceLoudness > (fOldVoiceLoudness + fDebounceThreshold));
}
else
{
if (fwTimer::GetTimeInMilliseconds() > (iVoiceStartTimeMs + iMinimumActivationTimeMs))
{
bVoiceDrivenMouthMovement = fVoiceLoudness > fLoudnessDeactivateThreshold;
}
else
{
bVoiceDrivenMouthMovement = true;
}
}
}
else
{
const CPlayerInfo *pPlayerInfo = GetPlayerInfo();
if (Verifyf(pPlayerInfo, "Couldn't get player info for player ped!"))
{
bVoiceDrivenMouthMovement = NetworkInterface::GetVoice().IsGamerTalking(pPlayerInfo->m_GamerInfo.GetGamerId());
}
}
}
}
if (!GetPedResetFlag(CPED_RESET_FLAG_DisableVoiceDrivenMouthMovement) && (bVoiceDrivenMouthMovement || GetPedResetFlag(CPED_RESET_FLAG_EnableVoiceDrivenMouthMovement)))
{
StartVoiceDrivenMouthMovement();
}
else
{
StopVoiceDrivenMouthMovement();
}
}
}
}
}
}
void CPed::StartVoiceDrivenMouthMovement()
{
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fBlendInDuration, 0.1f, 0.00f, 1.00f, 0.01f);
if (GetAnimDirector())
{
fwAnimDirectorComponentFacialRig* pFacialRigComp = GetAnimDirector()->GetComponentByPhase<fwAnimDirectorComponentFacialRig>(fwAnimDirectorComponent::kPhaseAll);
if (pFacialRigComp)
{
if (pFacialRigComp->GetVoiceStartTimeMs() == -1)
{
pFacialRigComp->SetVoiceDrivenMouthMovementBlend(1.0f, fBlendInDuration);
pFacialRigComp->SetVoiceStartTimeMs(fwTimer::GetTimeInMilliseconds());
}
}
}
}
void CPed::StopVoiceDrivenMouthMovement()
{
TUNE_GROUP_FLOAT(VOICE_DRIVEN_MOUTH_MOVEMENT, fBlendOutDuration, 0.1f, 0.00f, 1.00f, 0.01f);
if (GetAnimDirector())
{
fwAnimDirectorComponentFacialRig* pFacialRigComp = GetAnimDirector()->GetComponentByPhase<fwAnimDirectorComponentFacialRig>(fwAnimDirectorComponent::kPhaseAll);
if (pFacialRigComp)
{
if (pFacialRigComp->GetVoiceStartTimeMs() != -1)
{
pFacialRigComp->SetVoiceDrivenMouthMovementBlend(0.0f, fBlendOutDuration);
pFacialRigComp->SetVoiceStartTimeMs(-1);
if (IsLocalPlayer() && NetworkInterface::IsGameInProgress())
{
CVoiceDrivenMouthMovementFinishedEvent::Trigger();
}
}
}
}
}
void CPed::ProcessVisemes()
{
#if ENABLE_VISEMES
bool bPlayViseme = false;
#if !__NO_OUTPUT && !__FINAL
bool bIsPedFocused = CDebugScene::FocusEntities_IsInGroup(this);
#endif // !__NO_OUTPUT
// Are the viseme anims audio being blocked?
if (!GetVisemeAnimsAudioBlocked())
{
if(m_pSpeechAudioEntity)
{
// Is the speech loaded?
if (!m_WaitingForSpeechToPreload || m_pSpeechAudioEntity->IsPreloadedSpeechReady())
{
// Is the ped speaking?
if (m_pSpeechAudioEntity->IsAmbientSpeechPlaying() || m_pSpeechAudioEntity->IsScriptedSpeechPlaying() || m_pSpeechAudioEntity->IsPainPlaying())
{
bPlayViseme = true;
}
else
{
visemeCondDebugf3(bIsPedFocused, "%u %p %s BLOCKED ped is not speaking!", fwTimer::GetFrameCount(), this, GetModelName());
}
}
else
{
visemeCondDebugf3(bIsPedFocused, "%u %p %s BLOCKED speech is not loaded!", fwTimer::GetFrameCount(), this, GetModelName());
}
}
else
{
visemeCondDebugf3(bIsPedFocused, "%u %p %s BLOCKED m_pSpeechAudioEntity == NULL!", fwTimer::GetFrameCount(), this, GetModelName());
}
}
else
{
visemeCondDebugf3(bIsPedFocused, "%u %p %s BLOCKED GetVisemeAnimsAudioBlocked() == true!", fwTimer::GetFrameCount(), this, GetModelName());
}
if (!bPlayViseme)
{
m_fPrevVisemeTime = 0.0f;
m_fVisemeDuration = 0.0f;
// Viseme was playing, needs clearing
if (m_bVisemePlaying)
{
static const fwMvBooleanId facialVisemeClipEndedId("Viseme_ClipEnded",0x89360EF2);
GetMovePed().SetBoolean(facialVisemeClipEndedId, true);
static const fwMvFloatId visemeRateId("FacialVisemeRate",0x35F5F7AA);
GetMovePed().SetFloat(visemeRateId, 1.0f);
//BlendOutVisemeBodyAdditive();
m_bVisemePlaying = false;
visemeCondDebugf3(bIsPedFocused, "%u %p %s Viseme was playing, needs clearing", fwTimer::GetFrameCount(), this, GetModelName());
}
return;
}
u32 predelay = 0;
u32 remainingPredelay = 0;
u32 speechPlayTimeMs = m_pSpeechAudioEntity ? m_pSpeechAudioEntity->GetActiveSpeechPlayTimeMs(predelay, remainingPredelay) : 0;
#if __BANK
m_visemeDebugInfo.m_preDelay = predelay;
m_visemeDebugInfo.m_preDelayRemaining = remainingPredelay;
m_visemeDebugInfo.m_time = speechPlayTimeMs;
#endif // __BANK
if(m_WaitingForSpeechToPreload)
{
// Does an associated clip exist?
crClip* pAssociatedClip = NULL;
m_pSpeechAudioEntity->GetVisemeData((void **)&pAssociatedClip);
if(pAssociatedClip)
{
#if __BANK
m_visemeDebugInfo.m_clipName.SetFromString(pAssociatedClip->GetName());
#endif //__BANK
// Speech is pre-loaded and there is an associated clip
u32 preDelayFromClipProperty = 0;
if(pAssociatedClip->GetProperties())
{
const crProperty* property = pAssociatedClip->GetProperties()->FindProperty(CClipEventTags::TimeOfPreDelay);
if(property)
{
const crPropertyAttribute* attribute = property->GetAttribute(CClipEventTags::TimeOfPreDelay);
if(attribute && attribute->GetType() == crPropertyAttribute::kTypeFloat)
{
const crPropertyAttributeFloat* attributeFloat = static_cast<const crPropertyAttributeFloat*>(attribute);
preDelayFromClipProperty = (u32)(attributeFloat->GetFloat()*1000);
}
}
}
m_fVisemeDuration = pAssociatedClip->GetDuration();
if(!GetVisemeAnimsBlocked())
{
GetMovePed().PlayVisemeClip(pAssociatedClip, NORMAL_BLEND_DURATION, 0.0f);
//BlendInVisemeBodyAdditive();
}
m_bVisemePlaying = true;
m_pSpeechAudioEntity->PlayPreloadedSpeech(preDelayFromClipProperty);
m_WaitingForSpeechToPreload = false;
visemeCondDebugf3(bIsPedFocused, "%u %p %s Loading %s, speechPlayTimeMs %u", fwTimer::GetFrameCount(), this, GetModelName(), pAssociatedClip->GetName(), speechPlayTimeMs);
}
else
{
// Speech is pre-loaded but there is no associated clip?
m_pSpeechAudioEntity->PlayPreloadedSpeech(0);
m_WaitingForSpeechToPreload = false;
}
}
else
{
if(speechPlayTimeMs > 0)
speechPlayTimeMs += predelay;
else
speechPlayTimeMs = predelay - remainingPredelay;
float time = static_cast< float >(speechPlayTimeMs) / 1000.0f;
float phase = m_fVisemeDuration > 0.0f ? time / m_fVisemeDuration : 0.0f;
phase = Clamp(phase, 0.0f, 1.0f);
if(time >= m_fPrevVisemeTime)
{
static const fwMvFloatId facialVisemePhaseId("FacialVisemePhase",0x380D8D8A);
GetMovePed().SetFloat(facialVisemePhaseId, phase);
/*if(GetPedResetFlag(CPED_RESET_FLAG_DisableVisemeBodyAdditive))
{
BlendOutVisemeBodyAdditive();
}
else
{
float fSpeechHeadroom = m_pSpeechAudioEntity->GetSpeechHeadroom();
m_fVisemeBodyAdditiveWeight = Lerp(0.5f, m_fVisemeBodyAdditiveWeight, fSpeechHeadroom);
GetMovePed().SetFloat(ms_visemeBodyAdditiveWeightId, m_fVisemeBodyAdditiveWeight);
}*/
m_fPrevVisemeTime = time;
visemeCondDebugf3(bIsPedFocused, "%u %p %s Playing speechPlayTimeMs %u, time %.3f, phase %.3f", fwTimer::GetFrameCount(), this, GetModelName(), speechPlayTimeMs, time, phase);
}
else
{
static const fwMvBooleanId facialVisemeClipEndedId("Viseme_ClipEnded",0x89360EF2);
GetMovePed().SetBoolean(facialVisemeClipEndedId, true);
static const fwMvFloatId visemeRateId("FacialVisemeRate",0x35F5F7AA);
GetMovePed().SetFloat(visemeRateId, 1.0f);
//BlendOutVisemeBodyAdditive();
m_bVisemePlaying = false;
visemeCondDebugf3(bIsPedFocused, "%u %p %s Blending Out speechPlayTimeMs %u, time %.3f, phase %.3f", fwTimer::GetFrameCount(), this, GetModelName(), speechPlayTimeMs, time, phase);
}
}
#endif // ENABLE_VISEMES
}
// Update the LOD status for this ped using the given entity position
void CPed::UpdateLodState()
{
Assert(GetIsTypePed());
bool bIsPlayer = false;
bool bIsInVehicle = false;
bool bIsOnJetski = false;
bool bIsOnBike = false;
bool bIsInAircraft = false;
bool bIsInDriveBy = false;
bool bIsPlayerOrMissionPedExitingVehicle = false;
bool bLocalPlayerIsInMyVehicle = false;
CPedModelInfo *pPedMI = GetPedModelInfo();
Assert(pPedMI);
float fLodThreshold, fLowLodThreshold, fSuperLodThreshold;
if(!pPedMI->m_bIs_IG_Ped)
{
fLodThreshold = CPedTuning::GetLodThreshold();
fLowLodThreshold = CPedTuning::GetLowLodThreshold();
fSuperLodThreshold = CPedTuning::GetSuperLodThreshold();
}
else
{
fLodThreshold = 25.f;
fLowLodThreshold = 50.f;
fSuperLodThreshold = 80.f;
}
if (GetIsTypePed())
{
bIsPlayer = IsPlayer();
bIsInVehicle = GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle );
if (bIsInVehicle)
{
bIsOnJetski = (m_pMyVehicle && m_pMyVehicle->GetIsJetSki());
bIsOnBike = (m_pMyVehicle && (m_pMyVehicle->InheritsFromBike() || m_pMyVehicle->InheritsFromQuadBike() || m_pMyVehicle->InheritsFromAmphibiousQuadBike() || bIsOnJetski));
bIsInAircraft = (m_pMyVehicle && (m_pMyVehicle->InheritsFromPlane() || m_pMyVehicle->InheritsFromHeli()));
bLocalPlayerIsInMyVehicle = (m_pMyVehicle && m_pMyVehicle->ContainsLocalPlayer());
if (GetPedIntelligence() && GetPedIntelligence()->GetQueriableInterface())
{
bIsInDriveBy = GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_VEHICLE_GUN);
bIsPlayerOrMissionPedExitingVehicle = (bIsPlayer || PopTypeIsMission()) && GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE)
&& GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE)
&& ((IsLocalPlayer() && static_cast<CTaskExitVehicle*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE))->GetState() != CTaskExitVehicle::State_WaitForCarToSlow)
|| (!IsLocalPlayer() && static_cast<CTaskExitVehicle*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE))->GetState() != CTaskExitVehicle::State_Start));
}
}
// if a player ped has tattoos (or decals) push the lod switching back a bit or the decorations will be lost
// fairly close to the camera
if (CNetwork::IsGameInProgress() && HasHeadBlend() && PEDDAMAGEMANAGER.HasDecorations(this))
{
fLodThreshold *= 1.8f;
fLowLodThreshold *= 1.5f;
fSuperLodThreshold *= 1.2f;
}
}
Vector3 distV = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
// get distance from camera
distV -= camInterface::GetPos();
float dist = distV.Mag();
bool bIsPropHighLOD = false;
float scaledLODDistance = 0.0f;
if (bIsInDriveBy)
{
scaledLODDistance = CPedTuning::GetDrivebyLodThreshold()*g_LodScale.GetGlobalScale()*GetLodMultiplier();
}
else if (bIsInVehicle)
{
if (bIsPlayer || bLocalPlayerIsInMyVehicle)
{
scaledLODDistance = CPedTuning::GetPlayerInCarLodThreshold()*g_LodScale.GetGlobalScale()*GetLodMultiplier();
}
else
{
float tempLodThreshold = CPedTuning::GetInCarLodThreshold();
CVehicle* pVeh = GetMyVehicle();
if(pVeh)
{
if(pVeh->InheritsFromBike() || pVeh->InheritsFromQuadBike() || pVeh->InheritsFromAmphibiousQuadBike())
tempLodThreshold = fLodThreshold;
else if (pVeh->GetVehicleModelInfo() && pVeh->GetVehicleModelInfo()->GetIsJetSki())
tempLodThreshold = fLodThreshold;
else if (!pVeh->CarHasRoof())
tempLodThreshold = fLodThreshold;
}
scaledLODDistance = tempLodThreshold*g_LodScale.GetGlobalScale()*GetLodMultiplier();
}
}
else
{
scaledLODDistance = fLodThreshold*g_LodScale.GetGlobalScale()*GetLodMultiplier();
}
// force the driver of the players vehicle to have a high LOD
// (so hands don't change when switching cameras)
// force the passenger of the players motorbike to have a high LOD
float scaledPropsLodDistance = CPedTuning::GetPropsLodThreshold() * g_LodScale.GetGlobalScale() * GetLodMultiplier();
if (dist > scaledPropsLodDistance)
{
bIsPropHighLOD = false;
}
else
{
bIsPropHighLOD = true;
}
float scaledSuperLodDist = fSuperLodThreshold * g_LodScale.GetGlobalScale() * GetLodMultiplier();
float scaledLowLodDist = fLowLodThreshold * g_LodScale.GetGlobalScale() * GetLodMultiplier();
// Skip superlods if render phase for thermal vision is active, slods don't set shader data for the effect
bool bSkipSuperLods = g_SeeThroughPhase->IsActive();
if (dist > scaledSuperLodDist && !bSkipSuperLods)
{
m_modelLodIdx = 3;
}
else if (dist > scaledLowLodDist)
{
m_modelLodIdx = 2;
}
else if (dist > scaledLODDistance)
{
m_modelLodIdx = 1;
}
else
{
m_modelLodIdx = 0;
}
// for streamed peds we use a separate superlod distance. here we make sure to drop to the lowest lod until we reach that distance
float hiLodScaledDist = 0.0f;
if (pPedMI->GetIsStreamedGfx() && pPedMI->GetNumAvailableLODs() > 0)
{
const s32 unscaledSlodClamp = pPedMI->GetIsPlayerModel() ? PED_SUPER_LOD_THRESHOLD : PED_SUPER_LOD_CLAMP;
const float slodClamp = unscaledSlodClamp * g_LodScale.GetGlobalScale() * GetLodMultiplier();
if ((m_modelLodIdx >= pPedMI->GetNumAvailableLODs()))
{
if (dist < slodClamp)
{
m_modelLodIdx = pPedMI->GetNumAvailableLODs() - 1;
Assert(m_modelLodIdx < pPedMI->GetNumAvailableLODs());
}
}
// if only 1 lod is available then the hi lod distance will be overridden
if (pPedMI->GetNumAvailableLODs()==1)
{
hiLodScaledDist = slodClamp;
}
}
u32 fadeValue;
if (m_modelLodIdx >= 3)
{
fadeValue = 0;
}
else if (m_modelLodIdx >= 1)
{
if (dist < scaledSuperLodDist - CPedTuning::GetSlodCrossFadeDist())
{
fadeValue = 128;
}
else
{
fadeValue = (u32)Clamp((s32)(127.0f * ((scaledSuperLodDist - dist) / CPedTuning::GetSlodCrossFadeDist())), 1, 127);
}
}
else
{
if (dist < (scaledLODDistance - CPedTuning::GetLodCrossFadeDist()))
{
fadeValue = 255;
}
else
{
fadeValue = (u32)Clamp(128 + (s32)(127.0f * ((scaledLODDistance - dist) / CPedTuning::GetLodCrossFadeDist())), 128, 255);
}
}
CPedVariationData& vardata = GetPedDrawHandler().GetVarData();
vardata.SetLODFadeValue(fadeValue);
// calc hi lod fade value
m_hiLodFadeValue = 0;
if (m_modelLodIdx==0)
{
if (hiLodScaledDist==0.0f)
{
hiLodScaledDist = scaledLODDistance;
}
static float hiLodFadeRatio = 0.75f;
float hiLodFadeDistStart = hiLodScaledDist*hiLodFadeRatio;
if (dist<(hiLodScaledDist-hiLodFadeDistStart))
{
m_hiLodFadeValue = 255;
}
else
{
m_hiLodFadeValue = (u8)Clamp((s32)(255.0f*((hiLodScaledDist-dist)/hiLodFadeDistStart)), 0, 255);
}
}
#if __BANK
if (bOverrideLODFadeValue)
{
vardata.SetLODFadeValue(ForceLODFadeValue);
}
#endif //__BANK
vardata.SetPropLODState(bIsPropHighLOD);
CPedStreamRenderGfx* pGfxData = GetPedDrawHandler().GetPedRenderGfx();
if (pGfxData)
{
pGfxData->m_culledComponents = 0;
for (s32 i = 0; i < PV_MAX_COMP; ++i)
{
// B*2144788: Don't disable in-vehicle blocking on a bike if ped is the driver and has a passenger and has a prop in the hand slot (ie a bag on their back).
// Fixes bug with heist bags on the driver clipping through the passenger.
bool bDontDisableBlockingOnBike = (i == PV_COMP_HAND) && bIsInVehicle && bIsOnBike && !bIsOnJetski && GetIsDrivingVehicle() && (GetVehiclePedInside()->GetNumberOfPassenger() > 0);
// B*2023617: Don't hide props if collision is enabled for peds seat (ie hanging off the side of a vehicle).
bool bKeepCollisionOnWhenInVehicle = false;
if (GetVehiclePedInside())
{
const s32 iMySeat = GetVehiclePedInside()->GetSeatManager() ? GetVehiclePedInside()->GetSeatManager()->GetPedsSeatIndex(this) : -1;
if (GetVehiclePedInside()->IsSeatIndexValid(iMySeat))
{
const CVehicleSeatAnimInfo* pSeatAnimInfo = GetMyVehicle()->GetSeatAnimationInfo(iMySeat);
bKeepCollisionOnWhenInVehicle = pSeatAnimInfo && pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle();
}
}
bool bDisableInVehicleBlocking = bKeepCollisionOnWhenInVehicle || ((bIsOnBike && !bDontDisableBlockingOnBike)|| bIsPlayerOrMissionPedExitingVehicle || GetPedResetFlag(CPED_RESET_FLAG_DisableInVehiclePedVariationBlocking));
if ((!vardata.GetIsHighLOD() && vardata.HasComponentHiLOD((u8)i)) || // if we don't render hi lod flag all components that should be culled
(bIsInVehicle && !bDisableInVehicleBlocking && GetPedModelInfo()->GetVarInfo()->HasComponentFlagsSet((ePedVarComp)i, vardata.GetPedCompIdx((ePedVarComp)i), PV_FLAG_NOT_IN_CAR)))
{
// Don't cull the pilot helmet hose if we're in an aircraft
if (!(bIsInAircraft && (ePedVarComp)i == PV_COMP_TEEF && GetPedModelInfo()->GetVarInfo()->HasComponentFlagsSet((ePedVarComp)i, vardata.GetPedCompIdx((ePedVarComp)i), PV_FLAG_PILOT_HELMET)))
{
pGfxData->m_culledComponents |= 1 << i;
}
}
// Cull DECL components with crew emblem decorations if we're not rendering a high lod (ped_default doesn't support decorations)
if (((ePedVarComp)i == PV_COMP_DECL) && vardata.IsCurrVarDecalDecoration() && vardata.GetIsHighLOD() == false)
{
pGfxData->m_culledComponents |= 1 << i;
}
}
}
}
u8 CPed::GetModelLodIndex() const
{
u8 modelLodIdx = m_modelLodIdx;
#if __BANK
if (bShowMissingLowLOD)
{
modelLodIdx = 1;
}
#endif // __BANK
return modelLodIdx;
}
rmcDrawable* CPed::GetComponent(ePedVarComp eComponent)
{
if (GetPedModelInfo()->GetIsStreamedGfx())
{
return CPedVariationStream::GetComponent(eComponent, this, GetPedDrawHandler().GetVarData());
}
else
{
return CPedVariationPack::GetComponent(eComponent, this, GetPedDrawHandler().GetVarData());
}
}
void CPed::SetVarRandom(u32 setFlags, u32 clearFlags, atFixedBitSet32* outInclusions, atFixedBitSet32* outExclusions, const CAmbientModelVariations* variations, ePVRaceType race, u32 streamingFlags)
{
const CPedCompRestriction* pRestrictions = NULL;
int numRestrictions = 0;
if(variations)
{
if(taskVerifyf(variations->GetIsClass<CAmbientPedModelVariations>(), "Got unexpected variations type for ped, expected CAmbientPedModelVariations."))
{
const CAmbientPedModelVariations& pedVar = *static_cast<const CAmbientPedModelVariations*>(variations);
numRestrictions = pedVar.m_CompRestrictions.GetCount();
if(numRestrictions > 0)
{
pRestrictions = pedVar.m_CompRestrictions.GetElements();
}
}
}
if (GetPedModelInfo()->GetVarInfo())
{
GetPedModelInfo()->GetVarInfo()->SetVarRandom(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData(), setFlags, clearFlags, outInclusions, outExclusions, pRestrictions, numRestrictions, race, streamingFlags);
#if !__NO_OUTPUT
if(m_LogVariationCalls)
{
EntityDebugfWithCallStackAlways(this, "SetVarRandom");
}
#endif
//pedDisplayf("FC:%d CPed::SetVarRandom ModelName (%s)", fwTimer::GetFrameCount(), this->GetModelName());
}
}
bool CPed::SetVariation(ePedVarComp slotId, u32 drawblId, u32 drawblAltId, u32 texId, u32 paletteId, s32 streamFlags, bool force)
{
#if GTA_REPLAY
// Get the current variation data before setting the new one.
// This is used by the replay engine for being able to set the old data
// during rewinding of replay clips.
u32 compIdx = CPedVariationPack::GetCompVar(this, static_cast<ePedVarComp>(slotId));
u8 oldTexId = CPedVariationPack::GetTexVar(this, static_cast<ePedVarComp>(slotId));
u32 oldVariationHash;
u32 oldVarationData;
CPedVariationPack::GetLocalCompData(this, (ePedVarComp)slotId, compIdx, oldVariationHash, oldVarationData);
#endif // GTA_REPLAY
bool result = false;
if (!CGen9ExclusiveAssets::CanCreatePedDrawable(this, slotId, drawblId, true))
{
return false;
}
if (GetPedModelInfo()->GetIsStreamedGfx())
{
result = CPedVariationStream::SetVariation(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData(), slotId, drawblId, drawblAltId, texId, paletteId, streamFlags, force);
}
else
{
result = CPedVariationPack::SetVariation(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData(), slotId, drawblId, drawblAltId, texId, paletteId);
}
// B*2004318/1949041: If we're setting a new item in the BERD slot, reset the helmet shot flag and number of hits it's taken
if (slotId == PV_COMP_BERD)
{
SetPedConfigFlag(CPED_CONFIG_FLAG_HelmetHasBeenShot, false);
ResetNumArmouredHelmetHits();
}
pedDebugf3("CPed::SetVariation pPed[%p][%s][%s] result[%d] slotId[%d][%s] drawbldId[%d] drawblAltId[%d] texId[%d] palettedId[%d] streamFlags[%d] force[%d]",this,GetModelName(),GetNetworkObject() ? GetNetworkObject()->GetLogName() : "",result,slotId,varSlotNames[slotId],drawblId,drawblAltId,texId,paletteId,streamFlags,force);
#if GTA_REPLAY
if(result && CReplayMgr::ShouldRecord())
{
CReplayMgr::RecordFx<CPacketPedVariationChange>(CPacketPedVariationChange(this, (u8)slotId, oldVariationHash, oldVarationData, oldTexId), this);
}
#endif
#if !__NO_OUTPUT
if(m_LogVariationCalls)
{
EntityDebugfWithCallStackAlways(this, "SetVariation");
}
#endif
//pedDisplayf("FC:%d CPed::SetVariation ModelName (%s/%p), slotId (%d | %s), drawblId (%d), texId (%d), result (%d)", fwTimer::GetFrameCount(), this->GetModelName(), this, slotId, varSlotNames[slotId], drawblId, texId, result);
return result;
}
bool CPed::IsVariationValid(ePedVarComp slotId, u32 drawblId, u32 texId)
{
if (GetPedModelInfo()->GetIsStreamedGfx())
{
return CPedVariationStream::IsVariationValid(this, slotId, drawblId, texId);
}
else
{
return CPedVariationPack::IsVariationValid(this, slotId, drawblId, texId);
}
}
bool CPed::IsVariationInRange(ePedVarComp slotId, s32 drawblId, s32 texId)
{
bool rv = false;
if (drawblId > -1 && texId > -1 && GetPedModelInfo() && GetPedModelInfo()->GetVarInfo())
{
s32 maxTextures = GetPedModelInfo()->GetVarInfo()->GetMaxNumTex(static_cast<ePedVarComp>(slotId), drawblId);
s32 maxDrawables = GetPedModelInfo()->GetVarInfo()->GetMaxNumDrawbls(static_cast<ePedVarComp>(slotId));
if (drawblId < maxDrawables && texId < maxTextures)
{
rv = true;
}
}
return rv;
}
void CPed::SetVarDefault(u32 streamingFlags)
{
if (GetPedModelInfo()->GetIsStreamedGfx())
{
CPedVariationStream::SetVarDefault(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData(), streamingFlags);
}
else
{
CPedVariationPack::SetVarDefault(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData());
}
#if !__NO_OUTPUT
if(m_LogVariationCalls)
{
EntityDebugfWithCallStackAlways(this, "SetVarDefault");
}
#endif
//pedDisplayf("FC:%d CPed::SetVarDefault ModelName (%s)", fwTimer::GetFrameCount(), this->GetModelName());
}
void CPed::ApplySelectionSet(u32 index)
{
CPedModelInfo* pmi = GetPedModelInfo();
u32 setCount = pmi->GetVarInfo()->GetSelectionSetCount();
if (index >= setCount)
return;
const CPedSelectionSet* set = pmi->GetVarInfo()->GetSelectionSet(index);
if (!set)
return;
ApplySelectionSetByHash(set->m_name.GetHash());
}
void CPed::ApplySelectionSetByHash(u32 hash)
{
if (GetPedModelInfo()->GetIsStreamedGfx())
{
// TODO: if needed
//CPedVariationStream::ApplySelectionSet(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData(), index);
}
else
{
CPedVariationPack::ApplySelectionSetByHash(this, GetPedDrawHandler().GetPropData(), GetPedDrawHandler().GetVarData(), hash);
}
}
bool CPed::HasVariation(ePedVarComp slotId, u32 drawblId, u32 texId)
{
if (GetPedModelInfo()->GetIsStreamedGfx())
{
return CPedVariationStream::HasVariation(this, slotId, drawblId, texId);
}
else
{
return CPedVariationPack::HasVariation(this, slotId, drawblId, 0, texId);
}
}
bool CPed::IsBoneHelmeted(eAnimBoneTag boneTag)
{
return (boneTag == BONETAG_HEAD) ? CPedPropsMgr::CheckPropFlags(this, ANCHOR_HEAD, PV_FLAG_DEFAULT_HELMET|PV_FLAG_RANDOM_HELMET|PV_FLAG_SCRIPT_HELMET|PV_FLAG_FLIGHT_HELMET|PV_FLAG_PILOT_HELMET) : false;
}
bool CPed::IsBoneArmoured(eAnimBoneTag boneTag)
{
ePedVarComp comp = CPedVariationData::GetComponentSlotFromBoneTag(boneTag);
if (comp == PV_COMP_INVALID )
return false;
if (!GetPedModelInfo()->GetVarInfo())
return false;
u32 drawableId = GetPedDrawHandler().GetVarData().GetPedCompIdx(comp);
if (GetPedModelInfo()->GetVarInfo()->HasComponentFlagsSet(comp, drawableId, PV_FLAG_ARMOURED))
return true;
u32 secondFlags = GetPedModelInfo()->GetVarInfo()->LookupSecondaryFlags(this, (s32)comp);
return (secondFlags & PV_FLAG_ARMOURED) != 0;
}
bool CPed::IsBoneLightlyArmoured(eAnimBoneTag boneTag) const
{
ePedVarComp comp = CPedVariationData::GetComponentSlotFromBoneTag(boneTag);
if (comp == PV_COMP_INVALID )
return false;
if (!GetPedModelInfo()->GetVarInfo())
return false;
u32 drawableId = GetPedDrawHandler().GetVarData().GetPedCompIdx(comp);
if (GetPedModelInfo()->GetVarInfo()->HasComponentFlagsSet(comp, drawableId, PV_FLAG_LIGHTLY_ARMOURED))
return true;
u32 secondFlags = GetPedModelInfo()->GetVarInfo()->LookupSecondaryFlags(this, (s32)comp);
return (secondFlags & PV_FLAG_LIGHTLY_ARMOURED) != 0;
}
// NOTE: This will try to randomize a blended ped, but it's not strictly accurate. It won't hesitate to blend
// between two males or two females so results might not match that of the the MP ui
void CPed::SetRandomHeadBlendData(bool async)
{
int flags = STRFLAG_FORCE_LOAD | STRFLAG_PRIORITY_LOAD;
if (!GetPedModelInfo()->GetVarInfo())
return;
// random basic clothing
// hair
{
u32 maxDraw = GetPedModelInfo()->GetVarInfo()->GetMaxNumDrawbls(PV_COMP_HAIR);
u32 draw = fwRandom::GetRandomNumberInRange(0, maxDraw);
u32 maxTex = GetPedModelInfo()->GetVarInfo()->GetMaxNumTex(PV_COMP_HAIR, draw);
u32 tex = fwRandom::GetRandomNumberInRange(0, maxTex);
u32 pal = fwRandom::GetRandomNumberInRange(0, 16);
SetVariation(PV_COMP_HAIR, draw, 0, tex, pal, flags, true);
}
// uppr
{
u32 maxDraw = GetPedModelInfo()->GetVarInfo()->GetMaxNumDrawbls(PV_COMP_UPPR);
u32 draw = fwRandom::GetRandomNumberInRange(0, maxDraw);
u32 maxTex = GetPedModelInfo()->GetVarInfo()->GetMaxNumTex(PV_COMP_UPPR, draw);
u32 tex = fwRandom::GetRandomNumberInRange(0, maxTex);
u32 pal = fwRandom::GetRandomNumberInRange(0, 16);
SetVariation(PV_COMP_UPPR, draw, 0, tex, pal, flags, true);
}
// lowr
{
u32 maxDraw = GetPedModelInfo()->GetVarInfo()->GetMaxNumDrawbls(PV_COMP_LOWR);
u32 draw = fwRandom::GetRandomNumberInRange(0, maxDraw);
u32 maxTex = GetPedModelInfo()->GetVarInfo()->GetMaxNumTex(PV_COMP_LOWR, draw);
u32 tex = fwRandom::GetRandomNumberInRange(0, maxTex);
u32 pal = fwRandom::GetRandomNumberInRange(0, 16);
SetVariation(PV_COMP_LOWR, draw, 0, tex, pal, flags, true);
}
// random head blend data
u32 maxHeads = GetPedModelInfo()->GetVarInfo()->GetMaxNumDrawbls(PV_COMP_HEAD);
u32 head0 = fwRandom::GetRandomNumberInRange(0, maxHeads);
u32 head1 = fwRandom::GetRandomNumberInRange(0, maxHeads);
u32 head2 = fwRandom::GetRandomNumberInRange(0, maxHeads);
float geomBlend = fwRandom::GetRandomNumberInRange(0.3f, 0.7f);
float texBlend = fwRandom::GetRandomNumberInRange(0.3f, 0.7f);
float varBlend = fwRandom::GetRandomNumberInRange(0.f, 0.4f);
CPedHeadBlendData data;
data.m_head0 = (u8) head0;
data.m_head1 = (u8) head1;
data.m_head2 = (u8) head2;
data.m_headBlend = geomBlend;
data.m_texBlend = texBlend;
data.m_varBlend = varBlend;
data.m_async = async;
data.m_isParent = true;
SetHeadBlendData(data);
for (s32 i = 0; i < HOS_MAX; ++i)
{
s32 maxOverlays = 0;
switch (i)
{
case HOS_BLEMISHES:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_blemishes.GetCount();
break;
case HOS_FACIAL_HAIR:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_facialHair.GetCount();
break;
case HOS_EYEBROW:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_eyebrow.GetCount();
break;
case HOS_AGING:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_aging.GetCount();
break;
case HOS_MAKEUP:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_makeup.GetCount();
break;
case HOS_BLUSHER:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_blusher.GetCount();
break;
case HOS_DAMAGE:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_damage.GetCount();
break;
case HOS_BASE_DETAIL:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_baseDetail.GetCount();
break;
case HOS_SKIN_DETAIL_1:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_skinDetail1.GetCount();
break;
case HOS_SKIN_DETAIL_2:
maxOverlays = CPedVariationStream::GetFacialOverlays().m_skinDetail2.GetCount();
break;
default:
maxOverlays = 0;
}
u32 overlay = fwRandom::GetRandomNumberInRange(0, (u32)maxOverlays);
float blend = fwRandom::GetRandomNumberInRange(0.8f, 1.f);
SetHeadOverlay((eHeadOverlaySlots)i, overlay, blend, blend);
}
}
void CPed::RequestBlendFromParents(CPed* parent0, CPed* parent1, float geomBlend, float texBlend)
{
if (PARAM_noheadblend.Get())
return;
if (!parent0 || !parent1)
return;
CPedHeadBlendData* blendData0 = parent0->GetExtensionList().GetExtension<CPedHeadBlendData>();
if (!blendData0)
return;
CPedHeadBlendData* blendData1 = parent1->GetExtensionList().GetExtension<CPedHeadBlendData>();
if (!blendData1)
return;
if (!MESHBLENDMANAGER.IsHandleValid(blendData0->m_blendHandle))
return;
if (!MESHBLENDMANAGER.IsHandleValid(blendData1->m_blendHandle))
return;
CPedHeadBlendData* blendDataChild = GetExtensionList().GetExtension<CPedHeadBlendData>();
if (blendDataChild && MESHBLENDMANAGER.IsHandleValid(blendDataChild->m_blendHandle))
{
// same data, no need to overwrite
if (blendDataChild->m_parentPed0.Get() == parent0 && blendDataChild->m_parentPed1.Get() == parent1)
return;
}
else
{
blendDataChild = rage_new CPedHeadBlendData();
blendDataChild->m_isParent = false;
GetExtensionList().Add(*blendDataChild);
}
blendDataChild->m_parentPed0 = parent0;
blendDataChild->m_parentPed1 = parent1;
blendDataChild->m_headBlend = geomBlend;
blendDataChild->m_texBlend = texBlend;
blendDataChild->m_varBlend = 0.f;
blendDataChild->m_hasParents = true;
#if 0
// set a random base detail overlay
if (CPedVariationStream::GetFacialOverlays().m_baseDetail.GetCount() > 0)
{
blendDataChild->m_overlayTex[HOS_BASE_DETAIL] = (u8)fwRandom::GetRandomNumberInRange(0, CPedVariationStream::GetFacialOverlays().m_baseDetail.GetCount());
blendDataChild->m_overlayAlpha[HOS_BASE_DETAIL] = fwRandom::GetRandomNumberInRange(0.8f, 1.f);
blendDataChild->m_overlayNormAlpha[HOS_BASE_DETAIL] = fwRandom::GetRandomNumberInRange(0.8f, 1.f);
}
#endif
blendDataChild->UpdateFromParents();
CPedVariationStream::RequestStreamPedFiles(this, &GetPedDrawHandler().GetVarData());
}
void CPed::UpdateChildBlend()
{
if (PARAM_noheadblend.Get())
return;
CPedHeadBlendData* blendData = GetExtensionList().GetExtension<CPedHeadBlendData>();
if (!blendData)
return;
blendData->UpdateFromParents();
CPedVariationStream::RequestStreamPedFiles(this, &GetPedDrawHandler().GetVarData());
}
bool CPed::SetHeadBlendData(const CPedHeadBlendData& data, bool bForce)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
{
pedDebugf1("[HEAD BLEND] CPed::SetHeadBlendData(%p): Not a streamed ped!", this);
return false;
}
if (!Verifyf((data.m_head0 & ~0xff) == 0, "Head 0 index is too large: %d", data.m_head0) ||
!Verifyf((data.m_head1 & ~0xff) == 0, "Head 1 index is too large: %d", data.m_head1) ||
!Verifyf((data.m_head2 & ~0xff) == 0, "Head 2 index is too large: %d", data.m_head2) ||
!Verifyf((data.m_tex0 & ~0xff) == 0, "Texture 0 index is too large: %d", data.m_tex0) ||
!Verifyf((data.m_tex1 & ~0xff) == 0, "Texture 1 index is too large: %d", data.m_tex1) ||
!Verifyf((data.m_tex2 & ~0xff) == 0, "Texture 2 index is too large: %d", data.m_tex2))
{
pedDebugf1("[HEAD BLEND] CPed::SetHeadBlendData(%p): Invalid indices!", this);
return false;
}
GetPedDrawHandler().GetVarData().SetHeadBlendData(this, data, bForce);
return true;
}
void CPed::CloneHeadBlend(const CPed* cloneFromPed, bool linkBlends)
{
if (!cloneFromPed)
return;
const CPedHeadBlendData* cloneBlendData = cloneFromPed->GetExtensionList().GetExtension<CPedHeadBlendData>();
if (!cloneBlendData)
return;
CPedHeadBlendData* blendData = GetExtensionList().GetExtension<CPedHeadBlendData>();
if (blendData)
GetExtensionList().Destroy(CPedHeadBlendData::GetAutoId());
#if GTA_REPLAY
//if we don't have the extension then we need to get one
if(ReplayPedExtension::HasExtension(this))
{
ReplayPedExtension::GetExtension(this)->ResetHeadBlendData();
}
#endif //GTA_REPLAY
if (linkBlends)
{
MeshBlendHandle handle = MESHBLENDMANAGER.CloneBlend(cloneBlendData->m_blendHandle, this);
blendData = rage_new CPedHeadBlendData();
*blendData = *cloneBlendData;
blendData->m_blendHandle = handle;
GetExtensionList().Add(*blendData);
//Ensure that internally the new colors that were just set for this ped are refreshed and put on the ped correctly
SetHeadBlendRefreshPaletteColor();
for (u32 i = 0; i < PV_MAX_COMP; ++i)
if (i != PV_COMP_HAND) // hand comp is the parachute for mp peds, uses a different palette, no need to reset it
CPedVariationStream::SetPaletteTexture(this, (ePedVarComp)i);
}
else
{
if (cloneBlendData->HasParents())
{
blendData = rage_new CPedHeadBlendData();
*blendData = *cloneBlendData;
blendData->m_blendHandle = MESHBLEND_HANDLE_INVALID;
GetExtensionList().Add(*blendData);
// this blend will have no handle at this point but once the streaming requests are done we'll get one assigned
CPedVariationStream::RequestStreamPedFiles(this, &GetPedDrawHandler().GetVarData());
}
else
{
SetHeadBlendData(*cloneBlendData);
for (s32 i = 0; i < HOS_MAX; ++i)
{
SetHeadOverlay((eHeadOverlaySlots)i, cloneBlendData->m_overlayTex[i], cloneBlendData->m_overlayAlpha[i], cloneBlendData->m_overlayNormAlpha[i]);
SetHeadOverlayTintIndex((eHeadOverlaySlots)i, (eRampType)cloneBlendData->m_overlayRampType[i], cloneBlendData->m_overlayTintIndex[i], cloneBlendData->m_overlayTintIndex2[i]);
}
for (s32 i = 0; i < MMT_MAX; ++i)
{
MicroMorph((eMicroMorphType)i, cloneBlendData->m_microMorphBlends[i]);
}
for (u8 i = 0; i < MAX_PALETTE_COLORS; ++i)
{
SetHeadBlendPaletteColor(cloneBlendData->m_paletteRed[i], cloneBlendData->m_paletteGreen[i], cloneBlendData->m_paletteBlue[i], i);
}
SetHeadBlendEyeColor(cloneBlendData->m_eyeColor);
SetHairTintIndex(cloneBlendData->m_hairTintIndex, cloneBlendData->m_hairTintIndex2);
if (!cloneBlendData->m_usePaletteRgb)
SetHeadBlendPaletteColorDisabled();
}
}
#if GTA_REPLAY
if(CReplayMgr::ShouldRecord() && (linkBlends || cloneBlendData->HasParents()))
{
//if we don't have the extension then we need to get one
if(!ReplayPedExtension::HasExtension(this))
{
if(!ReplayPedExtension::Add(this))
replayAssertf(false, "Failed to add a ped extension...ran out?");
}
ReplayPedExtension::CloneHeadBlendData(this, cloneBlendData);
}
#endif //GTA_REPLAY
}
bool CPed::HasHeadBlend() const
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return false;
if (!GetExtensionList().GetExtension<CPedHeadBlendData>())
return false;
return true;
}
bool CPed::SetHeadBlendPaletteColor(u8 r, u8 g, u8 b, u8 colorIndex, bool bforce)
{
pedDebugf3("CPed::SetHeadBlendPaletteColor i[%d] rgb[%d %d %d] bforce[%d]",colorIndex,r,g,b,bforce);
if (!GetPedModelInfo()->GetIsStreamedGfx())
{
pedDebugf3("!GetPedModelInfo()->GetIsStreamedGfx()-->return false");
return false;
}
bool success = GetPedDrawHandler().GetVarData().SetHeadBlendPaletteColor(this, r, g, b, colorIndex, bforce);
#if GTA_REPLAY
if(HasHeadBlend() && CReplayMgr::ShouldRecord())
{
//if we don't have the extension then we need to get one
if(!ReplayPedExtension::HasExtension(this))
{
if(!ReplayPedExtension::Add(this))
replayAssertf(false, "Failed to add a ped extension...ran out?");
}
ReplayPedExtension::SetHeadBlendDataPaletteColor(this, r, g, b, colorIndex, bforce);
}
#endif //GTA_REPLAY
return success;
}
void CPed::GetHeadBlendPaletteColor(u8& r, u8& g, u8& b, u8 colorIndex)
{
GetPedDrawHandler().GetVarData().GetHeadBlendPaletteColor(this, r, g, b, colorIndex);
}
bool CPed::SetHeadBlendRefreshPaletteColor()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return false;
return GetPedDrawHandler().GetVarData().SetHeadBlendRefreshPaletteColor(this);
}
void CPed::SetHeadBlendPaletteColorDisabled()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().SetHeadBlendPaletteColorDisabled(this);
}
void CPed::SetHeadBlendEyeColor(s32 colorIndex)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().SetHeadBlendEyeColor(this, colorIndex);
}
s32 CPed::GetHeadBlendEyeColor()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return -1;
return(GetPedDrawHandler().GetVarData().GetHeadBlendEyeColor(this));
}
void CPed::MicroMorph(eMicroMorphType type, float blend)
{
if (!HasHeadBlend() || type >= MMT_MAX)
return;
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().MicroMorph(this, type, blend);
}
void CPed::SetHairTintIndex(u32 index, u32 index2)
{
if (!HasHeadBlend())
return;
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().SetHairTintIndex(this, index, index2);
}
bool CPed::GetHairTintIndex(u32& outIndex, u32& outIndex2)
{
if (!HasHeadBlend())
return false;
if (!GetPedModelInfo()->GetIsStreamedGfx())
return false;
return GetPedDrawHandler().GetVarData().GetHairTintIndex(this, outIndex, outIndex2);
}
bool CPed::SetHeadOverlay(eHeadOverlaySlots slot, u32 tex, float blend, float normBlend)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return false;
if (!Verifyf((tex & ~0xff) == 0, "Overlay texture index is too large: %d", tex))
return false;
GetPedDrawHandler().GetVarData().SetHeadOverlay(this, slot, (u8)tex, blend, normBlend);
return true;
}
bool CPed::UpdateHeadOverlayBlend(eHeadOverlaySlots slot, float blend, float normBlend)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return false;
GetPedDrawHandler().GetVarData().UpdateHeadOverlayBlend(this, slot, blend, normBlend);
return true;
}
s32 CPed::GetHeadOverlay(eHeadOverlaySlots slot)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return -1;
CPedHeadBlendData* blendData = GetExtensionList().GetExtension<CPedHeadBlendData>();
if (!blendData)
return -1;
return blendData->m_overlayTex[slot];
}
bool CPed::SetHeadOverlayTintIndex(eHeadOverlaySlots slot, eRampType rampType, u32 tint, u32 tint2)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return false;
if (!Verifyf((tint & ~0xff) == 0, "Overlay tint index is too large: %d", tint))
return false;
GetPedDrawHandler().GetVarData().SetHeadOverlayTintIndex(this, slot, rampType, (u8)tint, (u8)tint2);
return true;
}
void CPed::SetNewHeadBlendValues(float headBlend, float texBlend, float varBlend)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().SetNewHeadBlendValues(this, headBlend, texBlend, varBlend);
}
bool CPed::HasHeadBlendFinished()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return true;
return GetPedDrawHandler().GetVarData().HasHeadBlendFinished(this);
}
void CPed::FinalizeHeadBlend()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
{
pedDebugf1("[HEAD BLEND] CPed::FinalizeHeadBlend(%p) called on non-streaming ped", this);
return;
}
// if there are requests in progress, finalize afterwards otherwise it'll just be undone
CPedStreamRequestGfx* req = GetExtensionList().GetExtension<CPedStreamRequestGfx>();
if (req)
{
CPedHeadBlendData* blendData = GetExtensionList().GetExtension<CPedHeadBlendData>();
if (blendData)
{
#if !__FINAL
pedDebugf1("[HEAD BLEND] CPed::FinalizeHeadBlend(%p): Streaming requests in progress, flagging deferred finalize. Waiting on the following assets (in peddebug2):", this);
for (u32 i = 0; i < PV_MAX_COMP; ++i)
{
if(req->IsDwdSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetDwdNames().size() > i)
pedDebugf2(" %s drawable (%s)", varSlotNames[i], req->GetDwdNames()[i].TryGetCStr());
else
#endif
pedDebugf2(" %s drawable", varSlotNames[i]);
}
if(req->IsTxdSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetTxdNames().size() > i)
pedDebugf2(" %s texture (%s)", varSlotNames[i], req->GetTxdNames()[i].TryGetCStr());
else
#endif
pedDebugf2(" %s texture", varSlotNames[i]);
}
if(req->IsCldSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetCldNames().size() > i)
pedDebugf2(" %s cld (%s)", varSlotNames[i], req->GetCldNames()[i].TryGetCStr());
else
#endif
pedDebugf2(" %s cld", varSlotNames[i]);
}
if(req->IsFpAltSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetFpAltNames().size() > i)
pedDebugf2(" %s FP alt (%s)", varSlotNames[i], req->GetFpAltNames()[i].TryGetCStr());
else
#endif
pedDebugf2(" %s FP alt", varSlotNames[i]);
}
}
for (u32 i = 0; i < HBS_MAX; ++i)
{
if(req->IsHeadBlendSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetHeadBlendNames().size() > i)
pedDebugf2(" head blend slot %d drawable (%s)", i, req->GetHeadBlendNames()[i].TryGetCStr());
else
#endif
pedDebugf2(" head blend slot %d drawable", i);
}
}
if (PARAM_headblenddebug.Get())
{
sysStack::PrintStackTrace();
scrThread::PrePrintStackTrace();
}
#endif // !__FINAL
blendData->m_needsFinalizing = true;
}
else
{
pedDebugf1("[HEAD BLEND] CPed::FinalizeHeadBlend(%p): Streaming requests in progress, but no head blend", this);
}
return;
}
GetPedDrawHandler().GetVarData().FinalizeHeadBlend(this);
}
u32 CPed::SetPreloadData(ePedVarComp slotId, u32 drawableId, u32 textureId, u32 streamingFlags)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return 0;
return GetPedDrawHandler().GetVarData().SetPreloadData(this, slotId, drawableId, textureId, streamingFlags);
}
bool CPed::HasPreloadDataFinished()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return true;
return GetPedDrawHandler().GetVarData().HasPreloadDataFinished();
}
bool CPed::HasPreloadDataFinished(u32 handle)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return true;
return GetPedDrawHandler().GetVarData().HasPreloadDataFinished(handle);
}
void CPed::CleanUpPreloadData()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().CleanUpPreloadData();
}
void CPed::CleanUpPreloadData(u32 handle)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
GetPedDrawHandler().GetVarData().CleanUpPreloadData(handle);
}
ePVRaceType CPed::GetPedRace() const
{
if (!GetPedModelInfo()->GetVarInfo())
return PV_RACE_INVALID;
u32 drawable = GetPedDrawHandler().GetVarData().GetPedCompIdx(PV_COMP_HEAD);
u8 texture = GetPedDrawHandler().GetVarData().GetPedTexIdx(PV_COMP_HEAD);
return GetPedModelInfo()->GetVarInfo()->GetRaceType(PV_COMP_HEAD, drawable, texture, GetPedModelInfo());
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DelayedRemovalTimeReset
// PURPOSE : Resets the delayed removal time, to make sure this ped
// isn't culled immediately.
// PARAMETERS : None
// RETURNS : Nothing
/////////////////////////////////////////////////////////////////////////////////
void CPed::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.
if( (GetPedType() == PEDTYPE_COP) ||
(GetPedType() == PEDTYPE_MEDIC) ||
(GetPedType() == PEDTYPE_FIRE))
{
m_delayedRemovalAmountMs =
fwRandom::GetRandomNumberInRange( CPedPopulation::ms_removalDelayMsEmergencyRefreshMin,
CPedPopulation::ms_removalDelayMsEmergencyRefreshMax);
}
else
{
m_delayedRemovalAmountMs =
fwRandom::GetRandomNumberInRange( CPedPopulation::ms_removalDelayMsCivRefreshMin,
CPedPopulation::ms_removalDelayMsCivRefreshMax);
}
m_delayedRemovalAmountMs += extraTimeMs;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DelayedRemovalTimeHasPassed
// PURPOSE : Checks delayed removal time, to make sure this ped
// isn't culled immediately.
// PARAMETERS : removalRateScale - Use to make things disappear more quickly
// (1.0 is normal and 2.0 causes things to disappear twice as fast).
// RETURNS : Whether or not it is now okay to remove this ped.
/////////////////////////////////////////////////////////////////////////////////
bool CPed::DelayedRemovalTimeHasPassed(float removalRateScale, u32 removalTimeExtraMs) const
{
Assert(removalRateScale != 0.0f);
const float removalAmountScale = 1.0f / removalRateScale;
const u32 scaledRemovalAmountMs = static_cast<u32>(removalAmountScale * static_cast<float>(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 CPed::DelayedRemovalTimeGetPortion(float removalRateScale) const
{
Assert(removalRateScale != 0.0f);
const float removalAmountScale = 1.0f / removalRateScale;
const u32 scaledRemovalAmountMs = static_cast<u32>(removalAmountScale * static_cast<float>(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<float>(timeElapsedMs) / static_cast<float>(scaledRemovalAmountMs);
return rage::Clamp(portion, 0.0f, 1.0f);
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DelayedConversionTimeReset
// PURPOSE : Resets the delayed conversion time, to make sure this ped
// or dummy ped isn't converted immediately.
// PARAMETERS : None
// RETURNS : Nothing
/////////////////////////////////////////////////////////////////////////////////
void CPed::DelayedConversionTimeReset(u32 extraTimeMs)
{
// Store the current time.
m_delayedConversionResetTimeMs = fwTimer::GetTimeInMilliseconds();
// Set the conversion 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.
if( (GetPedType() == PEDTYPE_COP) ||
(GetPedType() == PEDTYPE_MEDIC) ||
(GetPedType() == PEDTYPE_FIRE))
{
m_delayedConversionAmountMs =
fwRandom::GetRandomNumberInRange( CPedPopulation::ms_conversionDelayMsEmergencyRefreshMin,
CPedPopulation::ms_conversionDelayMsEmergencyRefreshMax);
}
else
{
m_delayedConversionAmountMs =
fwRandom::GetRandomNumberInRange( CPedPopulation::ms_conversionDelayMsCivRefreshMin,
CPedPopulation::ms_conversionDelayMsCivRefreshMax);
}
m_delayedConversionAmountMs += extraTimeMs;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DelayedConversionTimeHasPassed
// PURPOSE : Checks delayed conversion time, to make sure this ped
// isn't converted immediately.
// PARAMETERS : conversionRateScale - Use to make things disappear more quickly
// (1.0 is normal and 2.0 causes things to disappear twice as fast).
// RETURNS : Whether or not it is now okay to remove this ped.
/////////////////////////////////////////////////////////////////////////////////
bool CPed::DelayedConversionTimeHasPassed(float conversionRateScale) const
{
Assert(conversionRateScale != 0.0f);
const float conversionAmountScale = 1.0f / conversionRateScale;
const u32 scaledConversionAmountMs = static_cast<u32>(conversionAmountScale * static_cast<float>(m_delayedConversionAmountMs));
const u32 scaledConversionTimeMs = m_delayedConversionResetTimeMs + scaledConversionAmountMs;
const u32 currentTimeMs = fwTimer::GetTimeInMilliseconds();
const bool hasWrapped = currentTimeMs < m_delayedConversionResetTimeMs;
const bool hasPassed = currentTimeMs > scaledConversionTimeMs;
return (hasWrapped || hasPassed);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : DelayedConversionTimeGetPortion
// PURPOSE : Checks delayed conversion time, and returns a value going from 1.0f
// down to zero depending on how close to being able to be converted
// we are.
// PARAMETERS : None
// RETURNS : What portion of the max conversion times remains.
/////////////////////////////////////////////////////////////////////////////////
float CPed::DelayedConversionTimeGetPortion(float conversionRateScale) const
{
Assert(conversionRateScale != 0.0f);
const float conversionAmountScale = 1.0f / conversionRateScale;
const u32 scaledConversionAmountMs = static_cast<u32>(conversionAmountScale * static_cast<float>(m_delayedConversionAmountMs));
const u32 scaledConversionTimeMs = m_delayedConversionResetTimeMs + scaledConversionAmountMs;
const u32 currentTimeMs = fwTimer::GetTimeInMilliseconds();
const bool hasWrapped = currentTimeMs < m_delayedConversionResetTimeMs;
const bool hasPassed = currentTimeMs > scaledConversionTimeMs;
if(hasWrapped || hasPassed)
{
return 1.0f;
}
else
{
const u32 timeElapsedMs = currentTimeMs - m_delayedConversionResetTimeMs;
float portion = static_cast<float>(timeElapsedMs) / static_cast<float>(scaledConversionAmountMs);
return rage::Clamp(portion, 0.0f, 1.0f);
}
}
void CPed::SetRemoveAsSoonAsPossible(bool bVal)
{
m_bRemoveAsSoonAsPossible = bVal;
CPed* pAmbientFriend = GetPedIntelligence()->GetAmbientFriend();
// Try and destroy the ambient friend too if it is dependent on you.
if (pAmbientFriend && pAmbientFriend->GetTaskData().GetIsFlagSet(CTaskFlags::DependentAmbientFriend))
{
pAmbientFriend->SetRemoveAsSoonAsPossible(bVal);
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : RemovalIsPriority
// PURPOSE : If the streaming code would like this ped to be
// removed pronto; this function will return true
// PARAMETERS : None
// RETURNS : If the ped or dummy ped is deletable.
/////////////////////////////////////////////////////////////////////////////////
bool CPed::RemovalIsPriority(fwModelId modelId) const
{
// Don't remove with priority if this is a cop and player has a wanted level
if (CGameWorld::FindLocalPlayerWanted()->GetWantedLevel() > WANTED_CLEAN &&
(GetPedType() == PEDTYPE_COP))
{
return false;
}
// If we have too many ambient peds around, and this ped came from a scenario but is now considered
// an ambient ped, and enough time has passed since we last removed a ped for this reason, consider
// it a priority to remove this ped.
if(CPedPopulation::ShouldUsePriRemovalOfAmbientPedsFromScenario()
&& PopTypeGet() == POPTYPE_RANDOM_AMBIENT
&& GetPedConfigFlag(CPED_CONFIG_FLAG_SpawnedAtScenario))
{
return true;
}
// If we are at the limit for scenario peds, and this is a scenario ped,
// and we are frozen by the interior, remove aggressively - we probably
// need to make some space so we can spawn more peds inside.
if(CPedPopulation::IsAtScenarioPedLimit()
&& PopTypeGet() == POPTYPE_RANDOM_SCENARIO
&& m_nDEflags.bFrozenByInterior
&& CGameWorld::GetFreezeExteriorPeds())
{
return true;
}
if (m_bRemoveAsSoonAsPossible) return true;
// Respect config flags.
if (GetPedConfigFlag(CPED_CONFIG_FLAG_PreferNoPriorityRemoval))
{
return false;
}
return(CModelInfo::GetAssetsAreDeletable(modelId));
}
// Touch the fov time when any player see the ped
// Couldn't fit this into playerinfo or delayed death
void CPed::TouchInFovTime(u32 extraTimeMs/* = 0*/)
{
// We can increase but never decrease
m_InFovTime = Max(fwTimer::GetTimeInMilliseconds() + extraTimeMs, m_InFovTime);
}
// Will be true if no player saw the ped within this time
bool CPed::HasInFovTimePassed()
{
return (fwTimer::GetTimeInMilliseconds() > m_InFovTime);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetScenarioType
// PURPOSE : Returns a scenario if the ped or dummy is running one
// PARAMETERS : dynamicEntity (ped or dummy)
// RETURNS : s32
/////////////////////////////////////////////////////////////////////////////////
s32 CPed::GetScenarioType( const CDynamicEntity& dynamicEntity, const bool bMustBeRunning )
{
const CPed* pPed = dynamicEntity.GetIsTypePed()?static_cast<const CPed*>(&dynamicEntity):NULL;
if( pPed )
{
s32 iScenarioType = (s32) pPed->GetPedIntelligence()->GetQueriableInterface()->GetRunningScenarioType(bMustBeRunning);
if(iScenarioType == (s32)Scenario_Invalid)
{
CTask* pActiveTask = pPed->GetPedIntelligence()->GetTaskActive();
if(pActiveTask)
{
CScenarioPoint* pt = pActiveTask->GetScenarioPoint();
if(pt)
{
iScenarioType = pActiveTask->GetScenarioPointType();
taskAssert(iScenarioType >= 0);
}
}
}
return iScenarioType;
}
return (s32)Scenario_Invalid;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetScenarioPoint
// PURPOSE : Returns a scenario point if the ped or dummy is using one
// PARAMETERS : dynamicEntity (ped or dummy)
// RETURNS : The scenario point
/////////////////////////////////////////////////////////////////////////////////
CScenarioPoint const* CPed::GetScenarioPoint( const CDynamicEntity& dynamicEntity, const bool bMustBeRunning )
{
const CPed* pPed = dynamicEntity.GetIsTypePed()?static_cast<const CPed*>(&dynamicEntity):NULL;
if( pPed )
{
CScenarioPoint const* sp = pPed->GetPedIntelligence()->GetQueriableInterface()->GetRunningScenarioPoint(bMustBeRunning);
if(sp)
{
return sp;
}
else
{
CTask* pActiveTask = pPed->GetPedIntelligence()->GetTaskActive();
if(pActiveTask)
{
return pActiveTask->GetScenarioPoint();
}
}
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetWeaponType
// PURPOSE : Returns the weapon type held by the ped or dummyped
// PARAMETERS : dynamicEntity (ped or dummy)
// RETURNS : The weapon type.
/////////////////////////////////////////////////////////////////////////////////
u32 CPed::GetWeaponHash( const CDynamicEntity& dynamicEntity )
{
const CPed* pPed = dynamicEntity.GetIsTypePed()?static_cast<const CPed*>(&dynamicEntity):NULL;
if( pPed && pPed->GetWeaponManager())
{
return pPed->GetWeaponManager()->GetEquippedWeaponHash();
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetHeldObject
// PURPOSE : Returns the weapon object held by the ped or dummyped
// PARAMETERS : dynamicEntity (ped or dummy)
// RETURNS : CObject*
/////////////////////////////////////////////////////////////////////////////////
const CObject* CPed::GetHeldObject( const CDynamicEntity& dynamicEntity )
{
const CPed* pPed = dynamicEntity.GetIsTypePed()?static_cast<const CPed*>(&dynamicEntity):NULL;
if( pPed && pPed->GetWeaponManager())
{
return pPed->GetWeaponManager()->GetEquippedWeaponObject();
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IsSuperLodFromIndex
// PURPOSE : Returns if the given ped model index is a superlod or now
// PARAMETERS : Ped model index
// RETURNS : true when specified model index is a superlod
/////////////////////////////////////////////////////////////////////////////////
bool CPed::IsSuperLodFromIndex(u32 index)
{
return index == MI_PED_SLOD_HUMAN || index == MI_PED_SLOD_SMALL_QUADPED || index == MI_PED_SLOD_LARGE_QUADPED;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : IsSuperLodFromHash
// PURPOSE : Returns if the given ped model hash is a superlod or now
// PARAMETERS : Ped model hash
// RETURNS : true when specified model hash is a superlod
/////////////////////////////////////////////////////////////////////////////////
bool CPed::IsSuperLodFromHash(u32 hash)
{
return hash == ATSTRINGHASH("slod_human", 0x03f039cba)
|| hash == ATSTRINGHASH("slod_small_quadped", 0x02d7030f3)
|| hash == ATSTRINGHASH("slod_large_quadped", 0x0856cfb02);
}
void CPed::SetStairFlags(const WorldProbe::CShapeTestHitPoint* pIntersection, const phInst* pInst, const s32 iInstComponent, phMaterialMgr::Id materialId)
{
bool bIsStairs = IsStairs(pIntersection, pInst, iInstComponent, materialId);
bool bIsStairSlope = false;
// TODO: Remove once guaranteed all stairs have stair slope bounds
if (bIsStairs)
{
bIsStairSlope = IsStairSlope(pIntersection, pInst, iInstComponent);
}
SetPedConfigFlag(CPED_CONFIG_FLAG_OnStairs, bIsStairs);
SetPedConfigFlag(CPED_CONFIG_FLAG_OnStairSlope, bIsStairSlope);
}
bool CPed::IsStairs(const WorldProbe::CShapeTestHitPoint* pIntersection, const phInst* pInst, const s32 iInstComponent, phMaterialMgr::Id materialId)
{
if(materialId != 0 && PGTAMATERIALMGR->GetPolyFlagStairs(materialId))
{
return true;
}
if(pIntersection)
{
if(PGTAMATERIALMGR->GetPolyFlagStairs(pIntersection->GetHitMaterialId()))
{
return true;
}
const phInst* pIntersectionInst = pIntersection->GetHitInst();
if(pIntersectionInst && pIntersectionInst->IsInLevel() && (CPhysics::GetLevel()->GetInstanceTypeFlags(pIntersectionInst->GetLevelIndex()) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE))
{
// If the bound hit was actually a composite, check the child bound
if(pIntersectionInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pIntersectionInst->GetArchetype()->GetBound());
if(pBoundComposite->GetTypeFlags(pIntersection->GetHitComponent()) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE)
{
return true;
}
return false;
}
else
{
return true;
}
}
}
if(pInst)
{
if(pInst->IsInLevel() && (CPhysics::GetLevel()->GetInstanceTypeFlags(pInst->GetLevelIndex()) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE))
{
// If the bound hit was actually a composite, check the child bound
if(pInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pInst->GetArchetype()->GetBound());
if(pBoundComposite->GetTypeFlags(iInstComponent) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE)
{
return true;
}
return false;
}
else
{
return true;
}
}
}
return false;
}
bool CPed::IsStairSlope(const WorldProbe::CShapeTestHitPoint* pIntersection, const phInst* pInst, const s32 iInstComponent)
{
if(pIntersection)
{
const phInst* pIntersectionInst = pIntersection->GetHitInst();
if(pIntersectionInst && pIntersectionInst->IsInLevel() && (CPhysics::GetLevel()->GetInstanceTypeFlags(pIntersectionInst->GetLevelIndex()) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE))
{
// If the bound hit was actually a composite, check the child bound
if(pIntersectionInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pIntersectionInst->GetArchetype()->GetBound());
if(pBoundComposite->GetTypeFlags(pIntersection->GetHitComponent()) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE)
{
return true;
}
return false;
}
else
{
return true;
}
}
}
if(pInst)
{
if(pInst->IsInLevel() && (CPhysics::GetLevel()->GetInstanceTypeFlags(pInst->GetLevelIndex()) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE))
{
// If the bound hit was actually a composite, check the child bound
if(pInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pInst->GetArchetype()->GetBound());
if(pBoundComposite->GetTypeFlags(iInstComponent) & ArchetypeFlags::GTA_STAIR_SLOPE_TYPE)
{
return true;
}
return false;
}
else
{
return true;
}
}
}
return false;
}
bool CPed::sbForceRagDollIfPedForcedThroughWall = true;
// both fixes enabled by default
bool CPed::ms_DisableFixFor7458166 = false;
bool CPed::ms_DisableFixFor7459244 = false;
void CPed::InitTunables()
{
sbForceRagDollIfPedForcedThroughWall = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("RAGDOLL_ON_TANK_WALL_BREACH", 0x6CF24164), sbForceRagDollIfPedForcedThroughWall);
ms_DisableFixFor7458166 = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("PED_DISABLE_FIX_FOR_7458166", 0xbae10c1d), ms_DisableFixFor7458166);
ms_DisableFixFor7459244 = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("PED_DISABLE_FIX_FOR_7459244", 0x9a6d98a1), ms_DisableFixFor7459244);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : GetLoadingObjectIndex
// PURPOSE : Returns the model index being loaded by this ped
// PARAMETERS : dynamicEntity (ped)
// RETURNS : model index
/////////////////////////////////////////////////////////////////////////////////
u32 CPed::GetLoadingObjectIndex( const CDynamicEntity& dynamicEntity )
{
const CPed* pPed = dynamicEntity.GetIsTypePed()?static_cast<const CPed*>(&dynamicEntity):NULL;
if( pPed )
{
CTaskAmbientClips* pTask = static_cast<CTaskAmbientClips*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AMBIENT_CLIPS));
if( pTask )
{
strStreamingObjectName prop = pTask->GetForcedProp();
if(prop.IsNotNull())
{
return CModelInfo::GetModelIdFromName(prop).GetModelIndex();
}
}
}
return fwModelId::MI_INVALID;
}
/////////////////////////////////////////////////////////////////////////////////
// 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 CPed::PopTypeSet(ePopType newPopType)
{
const ePopType oldPopType = PopTypeGet();
if (oldPopType != newPopType)
{
CPedPopulation::RemovePedFromPopulationCount(this);
CDynamicEntity::PopTypeSet(newPopType); // set the type variable (down in the base class)
CPedPopulation::AddPedToPopulationCount(this);
if((PopTypeIsMission() || IsLawEnforcementPed()) && !IsPlayer() && GetInventory() && !GetPedConfigFlag( CPED_CONFIG_FLAG_UseDiminishingAmmoRate ) )
{
// Mission peds default to using infinite ammo
GetInventory()->GetAmmoRepository().SetUsingInfiniteAmmo(true);
}
else if (IsPlayer() && newPopType == POPTYPE_PERMANENT && GetInventory())
{
// We need to reset this for the player ped when hotswapping
GetInventory()->GetAmmoRepository().SetUsingInfiniteAmmo(false);
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetIsStrafing
// PURPOSE : Tell the ped whether or not to be in strafing movement mode.
// PARAMETERS : s - whether to strafe or not.
// RETURNS : Whether or not the change was successfully applied.
/////////////////////////////////////////////////////////////////////////////////
bool CPed::SetIsStrafing(bool s)
{
return GetMotionData()->SetIsStrafing(s);
}
//-------------------------------------------------------------------------
// Let the ped know that we are transitioning from being a dummy ped, so we
// may need to visually interpolate our height to avoid popping (the dummy
// peds wander on the navmesh as opposed to peds which move on the the
// collision mesh and there can be differences in the height of the two).
//-------------------------------------------------------------------------
static const u32 DUMMY_HEIGHT_BLEND_TIME_MS = 500;// half a second.
void CPed::DummyHeightBlendBegin(float dummyHeight)
{
// Store the original height of the dummy at transition time (the
// height to visually blend from).
m_dummyHeightBlendHeight = dummyHeight;
// Store the current time (the start of the visual blend).
m_dummyHeightBlendStartTimeMs = fwTimer::GetTimeInMilliseconds();
// Mark that we should now be doing a height blend.
m_dummyHeightBlendInProgress = true;
}
//-------------------------------------------------------------------------
// Get the blend amount from the dummy peds position to the physical position
// that we should be using. This is used for visual position
// blending to avoid height pops, it is described further above.
//-------------------------------------------------------------------------
float CPed::DummyHeightBlendGetBlendPortion(void)
{
// Check if a height blend is in progress.
if(m_dummyHeightBlendInProgress)
{
const u32 fullyBlendedTimeMs = m_dummyHeightBlendStartTimeMs + DUMMY_HEIGHT_BLEND_TIME_MS;
const u32 currentTimeMs = fwTimer::GetTimeInMilliseconds();
const bool hasWrapped = currentTimeMs < fullyBlendedTimeMs;
const bool hasPassed = currentTimeMs > fullyBlendedTimeMs;
if(hasWrapped || hasPassed)
{
m_dummyHeightBlendInProgress = false;
return 1.0f;
}
else
{
float blendPortion = (static_cast<float>(currentTimeMs) - static_cast<float>(m_dummyHeightBlendStartTimeMs)) / static_cast<float>(DUMMY_HEIGHT_BLEND_TIME_MS);
return blendPortion;
}
}
else
{
return 1.0f;
}
}
//-------------------------------------------------------------------------
// Get the original dummy peds position. This is used for visual position
// blending to avoid height pops, it is described further above.
//-------------------------------------------------------------------------
float CPed::DummyHeightBlendGetDummyHeight(void)
{
return m_dummyHeightBlendHeight;
}
void
CPed::SetCurrentHeading(const float fCurrentHeading)
{
Assertf(fCurrentHeading >= -PI && fCurrentHeading <= PI, "%s[0x%p] Invalid fCurrentHeading: %f", GetLogName(), this, fCurrentHeading);
m_MotionData.SetCurrentHeading(fCurrentHeading);
}
void
CPed::SetDesiredHeading(const float fDesiredHeading)
{
Assertf(fDesiredHeading >= -PI && fDesiredHeading <= PI, "%s[0x%p] has invalid fDesiredHeading: %f", GetLogName(), this, fDesiredHeading);
m_MotionData.SetDesiredHeading(fDesiredHeading);
}
void
CPed::SetDesiredHeading(const Vector3 & vTarget)
{
taskAssert(rage::FPIsFinite(vTarget.x) && rage::FPIsFinite(vTarget.y) && rage::FPIsFinite(vTarget.z));
const Vector3 vPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
m_MotionData.SetDesiredHeading( fwAngle::GetRadianAngleBetweenPoints(vTarget.x, vTarget.y, vPos.x, vPos.y) );
}
float
CPed::GetFacingDirectionHeading() const
{
float fFacingDirectionHeading = GetCurrentHeading();
s32 iFacingDirectionBoneId = GetBoneIndexFromBoneTag(BONETAG_FACING_DIR);
if(taskVerifyf(iFacingDirectionBoneId != -1, "Ped [%s] has no facing direction bone but we are trying to query it", GetModelName()))
{
Matrix34 mFacingDirection;
mFacingDirection = GetLocalMtx(iFacingDirectionBoneId);
fFacingDirectionHeading += rage::Atan2f(-mFacingDirection.b.x, mFacingDirection.b.y);
fFacingDirectionHeading = fwAngle::LimitRadianAngle(fFacingDirectionHeading);
}
return fFacingDirectionHeading;
}
bool
CPed::GetVelocityHeading(float& fVelocityHeading) const
{
Vector3 vVel(GetVelocity());
if(vVel.Mag2() > SMALL_FLOAT)
{
vVel.NormalizeFast();
fVelocityHeading = fwAngle::LimitRadianAngle(rage::Atan2f(-vVel.x, vVel.y));
return true;
}
return false;
}
float
CPed::GetHeadingChangeRate() const
{
return m_MotionData.GetHeadingChangeRate();
}
void
CPed::SetCurrentPitch(const float fCurrentPitch)
{
m_MotionData.SetCurrentPitch(fCurrentPitch);
}
void
CPed::SetDesiredPitch(const float fDesiredPitch)
{
m_MotionData.SetDesiredPitch(fDesiredPitch);
}
void
CPed::SetDesiredPitch(const Vector3 & vTarget)
{
const Vector3 vPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
const Vector3 vDiff = vTarget - vPos;
Vector2 vDir;
vDiff.GetVector2XY(vDir);
vDir.Normalize();
float fDesiredPitch = acosf(vDir.Mag() / vDiff.Mag());
fDesiredPitch = fwAngle::LimitRadianAngleForPitch(fDesiredPitch);
m_MotionData.SetDesiredPitch(fDesiredPitch);
}
// Returns whether the ped is moving OR turning at the moment.
bool CPed::IsInMotion() const
{
return GetCurrentMotionTask()->IsInMotion(this);
}
void CPed::StopAllMotion(bool UNUSED_PARAM(bImmediately))
{
m_MotionData.StopAllMotion();
}
//*********************************************************************
// BlendOutAnyNonMovementAnims
// Blends out any anims with a priority greater than AP_LOW.
// Use fBlendDelta = INSTANT_BLEND_OUT_DELTA for instant.
//*********************************************************************
void
CPed::BlendOutAnyNonMovementAnims(const float fBlendDelta)
{
CMovePed& move = GetMovePed();
move.SetTaskNetwork(NULL, fBlendDelta);
move.SetSecondaryTaskNetwork(NULL, fBlendDelta);
move.SetAdditiveNetwork(NULL,fBlendDelta);
move.SetFacialNetwork(NULL,fBlendDelta);
}
void
CPed::RemoveMovementAnims()
{
}
void CPed::SetUseExtractedZ(const bool bUseExtractedZ)
{
m_MotionData.SetUseExtractedZ(bUseExtractedZ);
if (GetAnimatedInst())
{
GetAnimatedInst()->SetInstFlag(phInst::FLAG_NO_GRAVITY, bUseExtractedZ);
}
}
void CPed::RecomputeCachedBoneOffsets()
{
crSkeleton& skel = *GetSkeleton();
const crSkeletonData& skelData = skel.GetSkeletonData();
#if !__WIN32PC
// Prefetch the matrices here before checking the signature. It's possible that we'll end up prefetching the
// wrong matrices in the case where the signature changes, but that is rare and moving the prefetch up
// reduces the total number of cache misses.
const Mat34V* pMtxs = skel.GetObjectMtxs();
for(u32 i=1; i < kOffsetCount; i++)
{
PrefetchDC(pMtxs + m_cachedBoneIndices[i]);
}
#endif
if(Unlikely(skelData.GetSignature() != m_cachedBoneSignature))
{
for(u32 i=0; i < kOffsetCount; i++)
{
if(Unlikely(!skelData.ConvertBoneIdToIndex(ms_cachedBoneTags[i], m_cachedBoneIndices[i])))
{
m_cachedBoneIndices[i] = -1;
}
}
m_cachedBoneSignature = skelData.GetSignature();
}
for(u32 i=0; i < kOffsetCount; i++)
{
#if __ASSERT
s32 iBoneIndex;
if (skelData.ConvertBoneIdToIndex(ms_cachedBoneTags[i], iBoneIndex))
{
Assert(iBoneIndex == m_cachedBoneIndices[i]);
}
else
{
Assert(m_cachedBoneIndices[i] == -1);
}
#endif
if (m_cachedBoneIndices[i] >= 0)
{
Mat34V_ConstRef mat = skel.GetObjectMtx(m_cachedBoneIndices[i]);
m_vecOffsets[i] = mat.GetCol3();
// cache the rotation too
switch(ms_cachedBoneTags[i])
{
case BONETAG_HEAD: m_rotOffsets[kRotOffsetHead] = QuatVFromMat34V(mat); break;
#if GTA_REPLAY
case BONETAG_L_PH_FOOT: m_rotOffsets[kRotOffsetPHLeftFoot] = QuatVFromMat34V(mat); break;
case BONETAG_R_PH_FOOT: m_rotOffsets[kRotOffsetPHRightFoot] = QuatVFromMat34V(mat); break;
#endif
default: // nothing to see here
break;
}
}
}
ProcessColarDOFs();
}
//CPhysical * CPed::FindAttachedObjectByHash( u32 modelNameHash )
//{
// // search through the attached props to find the correct one
// // get the child (if there is one), then check through its siblings if necessary
//
// int modelIndex = CModelInfo::GetModelIdFromName(modelNameHash).GetModelIndex();
//
// if (modelIndex>-1)
// {
// if (m_pAttachChild)
// {
// CPhysical * pChild = m_pAttachChild;
//
// if (pChild->GetModelIndex()==modelIndex)
// {
// return pChild;
// }
// else
// {
// //search the siblings
// while(pChild)
// {
// pChild = pChild->GetAttachSibling();
// if (pChild)
// {
// if (pChild->GetModelIndex()==modelIndex)
// {
// return pChild;
// }
// }
// }
// return NULL;
// }
// }
// else
// {
// //no attached children
// return NULL;
// }
// }
// else
// {
// // invalid model name given - might as well ditch now
// return NULL;
// }
//
//}
//**************************************************************************************************************
// GTA RAGE PHYSICS
// no longer have line tests done in ProcessEntityCollision to make the peds stand on the ground
// so need to do separate line probes, like a suspension line, to make the ped stand up and move around
//
float PED_STAND_COMPRESSION_DAMP = -20.0f;
float PED_STAND_REBOUND_DAMP = -20.0f;
float PED_STAND_SPRING_STRENGTH = 2.0f;
/*
Experimental process probes, that works with a physics capsule that isnt vertical
Used by skiing, which has a capsule that orientates to the slope
Which allows us to raycast in the orientation of the slope, so on steep slopes we still hit the floor
*/
float EXTRA_HEIGHT_PER_MPS=0.005f;
void CPed::ProcessProbesNonVertical()
{
// reset ground physical pointer
SetGroundPhysical(NULL);
m_vecGroundPosLocal.ZeroComponents();
m_groundPhysicalComponent = 0;
if(GetIsStanding())
{
SetIsStanding(false);
SetWasStanding(true);
}
// network clones need to be processed even when they have collisions turned off,
// as height positions transmitted over the network are approximate, and can lead
// to peds falling through the ground when collisions are turned off, e.g. when
// jacking/being jacked from a car
bool bNetworkClone = false;
bNetworkClone = IsNetworkClone();
// ragdoll is in control
if(m_nRagdollState > RAGDOLL_STATE_ANIM_DRIVEN)
return;
// ped isn't inserted into the physics level
else if(!GetCurrentPhysicsInst()->IsInLevel() || (!bNetworkClone && (!IsCollisionEnabled() || GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))))
return;
else if(GetPedResetFlag( CPED_RESET_FLAG_IsClimbing ))
return;
else if(GetIsAttached())
{
SetIsStanding(true);
return;
}
Assert(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()!=GetRagdollInst());
Assert(GetRagdollInst()==NULL || !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()));
Vector3 vecStart = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 vecEnd = vecStart;
Matrix34 PhysicsMat = RCC_MATRIX34(GetCurrentPhysicsInst()->GetMatrix());
// PhysicsMat.c=Vector3(0,0,1);
vecEnd -= PhysicsMat.c*m_pCapsuleInfo->GetGroundToRootOffset();
Vector3 vecEndFloor = vecEnd;
float fGroundColLineOffset = PED_GROUNDCOLLINE_OFFSET_TIMESTEP*rage::Max(2.0f, 50.0f*fwTimer::GetTimeStep());
if(GetWasStanding())
{
/*
if(m_pMoveBlender->CanFlyThroughAir())
{
// fast moving sports (like skiing) need to ray cast a bit further to the floor
fGroundColLineOffset*=3.0f;
}
*/
// vecEnd.z -= fGroundColLineOffset;
vecEnd -= PhysicsMat.c*fGroundColLineOffset;
vecEnd -= EXTRA_HEIGHT_PER_MPS*PhysicsMat.c*GetVelocity().Mag()*50.0f*fwTimer::GetTimeStep();//fwTimer::GetSeconds();
}
u32 nIncludeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_PED_TYPE
| ArchetypeFlags::GTA_OBJECT_TYPE;
WorldProbe::CShapeTestHitPoint probeHitPoint;
WorldProbe::CShapeTestResults probeResults(probeHitPoint);
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vecStart, vecEnd);
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetExcludeEntity(this);
probeDesc.SetExcludeTypeFlags(ArchetypeFlags::GTA_PICKUP_TYPE);
probeDesc.SetIncludeFlags(nIncludeFlags);
probeDesc.SetIsDirected(true);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc) && GetCurrentMotionTask()->ShouldStickToFloor())
{
physicsAssert(probeResults[0].IsAHit());
// grcDebugDraw::Line(vecStart,vecEnd,Color32(255,0,0,255),Color32(255,0,0,255));
if(probeResults[0].GetHitInst() && CPhysics::GetEntityFromInst(probeResults[0].GetHitInst())
&& CPhysics::GetEntityFromInst(probeResults[0].GetHitInst())==this)
{
Assertf(false, "Ped is trying to stand on themself, doh");
return;
}
SetIsStanding(true);
// get intersection along probe a distance centered from default standing height
Vector3 fStandRatio = probeResults[0].GetHitPosition() - vecEndFloor;
// Vector3 vold=CalcForce(fStandRatio.z,GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor(),GetMass(),probeResults[0].GetHitNormal(), GetCollider()->GetVelocity());
Vector3 scaledGravity(CPhysics::GetSimulator()->GetGravity());
scaledGravity.Scale(GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor());
ApplyForceCg(-1.0f*scaledGravity*GetMass());
// Vector3 Force=-1.0f*scaledGravity*GetMass();
fStandRatio*=(PED_STAND_SPRING_STRENGTH/PED_GROUNDCOLLINE_OFFSET_TIMESTEP);
// now scale distance to get a ratio shifted to give ratio of 1.0 at default standing height, and desired spring strength
// AddData(0,GetMatrix().GetEulers().x*100.0f/PI);
// AddData(0,GetMatrix().GetEulers().y*100.0f/PI);
// AddData(0,GetMatrix().GetEulers().z*100.0f/PI);
// AddData(0,(fStandRatio.z*scaledGravity.Mag())*GetMass()*1.0f);
ApplyForceCg((fStandRatio*scaledGravity.Mag())*GetMass());
// Force+=fStandRatio*scaledGravity.Mag()*GetMass();
Vector3 damp=DotProduct(GetVelocity(),probeResults[0].GetHitNormal())*PhysicsMat.c;
damp*=PED_STAND_COMPRESSION_DAMP;
damp*= GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor();
if(damp.Mag() > 50.0f)
damp*= 50.0f/damp.Mag();
ApplyForceCg((damp)*GetMass());
// Force=(damp)*GetMass();
// Assert(fabsf(vold.x-Force.x)<0.001f && fabsf(vold.y-Force.y)<0.001f && fabsf(vold.z-Force.z)<0.001f);
SetGroundPos(probeResults[0].GetHitPosition());
// move rendered matrix around so that feet touch the ground properly
Vector3 pedPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
if(GetCurrentMotionTask()->CanFlyThroughAir())
{
Vector3 destNorm=probeResults[0].GetHitNormal();
pedPos=probeResults[0].GetHitPosition()+m_pCapsuleInfo->GetGroundToRootOffset()*PhysicsMat.c;
CDynamicEntity::SetPosition(pedPos);
// if(destNorm.z>0.005f) //now that we arent just casting straight down we can hit vertical polys or even downwards facing ones
{
m_vecGroundNormal=destNorm;
/*
float delta=0.1f;//g_smoothNorm*(MaxTurnSpeed-speed)*1.0f/MaxTurnSpeed;
m_vecGroundNormal = (destNorm-m_vecGroundNormal)*delta+m_vecGroundNormal;
m_vecGroundNormal.Normalize();
*/
}
// when travelling slow or on shallow slopes just use up vector
if(destNorm.z>0.9961f||GetVelocity().Mag()<10.0f) //cos of 5 degree slope
{
// float blend=(GetVelocity().Mag()/10.0f); // 0 use up vect 1 use surface normal
Vector3 StraightUp=Vector3(0,0,1);
destNorm = StraightUp;//(destNorm-StraightUp)*blend+StraightUp;
}
/*
float speed=GetVelocity().Mag();
float delta=g_smoothNorm*(MaxTurnSpeed-speed)*1.0f/MaxTurnSpeed;
delta=MIN(delta,1.0f);
delta=MAX(delta,0.0f);
if(destNorm.z>0.05f)
{
destNorm = (destNorm-m_vecGroundNormal)*delta+m_vecGroundNormal;
destNorm.Normalize();
}
else
{
speed=0.0f;
}
*/
//m_pMoveBlender->SetFloorNormal(destNorm);
}
else
{
m_vecGroundNormal = probeResults[0].GetHitNormal();
pedPos.z = m_vecGroundPos.GetZf() + m_pCapsuleInfo->GetGroundToRootOffset();
//if(!GetIkManager().IKControlsPedPos())
{
CDynamicEntity::SetPosition(pedPos);
}
}
/*
*/
// get the material we are standing on
// m_LastMaterialToHaveBeenStandingOn = probeResults[0].GetHitInst()->GetArchetype()->GetBound()->GetMaterialIndexFromPartIndex(probeResults[0].GetHitPartIndex(), probeResults[0].GetHitComponent());
m_PackedGroundMaterialId = probeResults[0].GetHitMaterialId();//(u8)phInstGta::GetMaterialIdFromIsect(&testIntersection);
SetStairFlags(&probeResults[0], NULL, 0, 0);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
m_secondSurfaceDepth = 0.0f;
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
}
else
{
// grcDebugDraw::Line(vecStart,vecEnd,Color32(0,0,255,255),Color32(0,0,255,255));
// really want to turn the skier to be in this direction before he lands
Vector3 destNorm=Vector3(0.0f,0.0f,1.0f);
float delta=0.1f;//g_smoothNorm*(MaxTurnSpeed-GetVelocity().Mag())*1.0f/MaxTurnSpeed;
m_vecGroundNormal = (destNorm-m_vecGroundNormal)*delta+m_vecGroundNormal;
m_vecGroundNormal.Normalize();
//m_pMoveBlender->SetFloorNormal(destNorm);
}
//
// Our physics entity is non vertical, and postSimUpdate (which has called us, has just copied over the physics matrix to the placeable)
// we want the placeable to be vertical so the IKslope and pitch can tilt the character smoothly
Matrix34 heading;
heading.MakeRotateZ(GetCurrentHeading());
heading.d=VEC3V_TO_VECTOR3(GetTransform().GetPosition()); // CEntity::GetPosition();
CDynamicEntity::SetMatrix(heading);
Assertf(!GetIsAttachedToGround() || GetGroundPhysical(),"Attached to ground without a ground physical.");
}
int g_skiing_nvp=1;
int g_skiing_fnvp=0;
//
// r&D into casting a cylinder of rays, and using the lowest point as the floor height
//
// If it is useful then rewrite to use batch probes
bool GetMinMaxUnderCapsule(const Vector3 &pos,float &min,float &max,float radius,float steps,float distUnderneath,Vector3 &normal,phMaterialMgr::Id &ProbeHitMaterial)
{
float stepAngle=2.0f*PI/steps;
min=999999.0f;
max=-999999.0f;
bool hitFloor=false;
for(float angle=0.0f;angle<2.0f*PI;angle+=stepAngle)
{
Vector3 vecStart = pos+Vector3(rage::Cosf(angle)*radius,-rage::Sinf(angle)*radius,0.0f);
Vector3 vecEnd = vecStart;
vecEnd.z -= distUnderneath;
u32 nIncludeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE;
WorldProbe::CShapeTestHitPoint probeHitPoint;
WorldProbe::CShapeTestResults probeResults(probeHitPoint);
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vecStart, vecEnd);
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetIncludeFlags(nIncludeFlags);
probeDesc.SetIsDirected(true);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
if(probeResults[0].GetHitPosition().z < min)
min = probeResults[0].GetHitPosition().z;
if(probeResults[0].GetHitPosition().z > max)
max=probeResults[0].GetHitPosition().z;
hitFloor=true;
normal=probeResults[0].GetHitNormal();
// grcDebugDraw::Line(vecStart,vecEnd,Color32(0,255,0,255),Color32(0,255,0,255));
ProbeHitMaterial=probeResults[0].GetHitMaterialId();
}
else
{
// grcDebugDraw::Line(vecStart,vecEnd,Color32(255,0,0,255),Color32(255,0,0,255));
}
}
return hitFloor;
}
bool g_probe_lots_of_rays=false;
float g_probe_radius=0.1f;
#define NUM_INTERSECTIONS_FOR_PED_PROBES (16)
#define NUM_INTERSECTIONS_FOR_RAGDOLL_PROBES (8)
#define NUM_FRAMES_PER_RAGDOLL_PROBE (3)
bool CPed::IsOnGround() const
{
return GetIsStanding() || (m_fGroundOffsetForPhysics > -m_pCapsuleInfo->GetGroundToRootOffset());
}
void CPed::SetRidingOnTrain(bool bRiding, CPhysical* pTrain)
{
SetPedConfigFlag(CPED_CONFIG_FLAG_RidingTrain, bRiding);
m_pTrainRidingOn = pTrain;
}
CPhysical * CPed::GetClimbPhysical(bool bAcceptVaultingState) const
{
if(GetPedResetFlag(CPED_RESET_FLAG_IsClimbing) || (bAcceptVaultingState && GetPedResetFlag(CPED_RESET_FLAG_IsVaulting)))
{
const CTaskVault* pClimbTask = static_cast<const CTaskVault*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_VAULT));
if(pClimbTask)
{
return pClimbTask->GetHandHold().GetPhysical();
}
else
{
const CTaskDropDown* pDropDown = static_cast<const CTaskDropDown*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_DROP_DOWN));
if(pDropDown)
{
return pDropDown->GetDropDown().GetPhysical();
}
}
}
return NULL;
}
//
void CPed::ProcessProbes(const Matrix34& testMat)
{
//Ensure probes have not been disabled.
if(GetPedResetFlag(CPED_RESET_FLAG_DisableProcessProbes))
{
return;
}
#if PED_USE_CAPSULE_PROBES
/*if (m_pCapsuleInfo->IsQuadruped()) { //reneable for non-physics based model rotation
//rotate model independent of capsule
Assert(GetPhysArch()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(GetPhysArch()->GetBound());
Matrix34 rotateModel = MAT34V_TO_MATRIX34(pBoundComposite->GetCurrentMatrix(0));
Matrix34 heading;
heading.MakeRotateZ(GetCurrentHeading());
rotateModel.Dot3x3(heading);
rotateModel.d = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
CEntity::SetMatrix(rotateModel, false, false, false);
}*/
if(DEV_ONLY(CPhysics::ms_bDoPedProbesWithImpacts &&) !GetUsingRagdoll())
{
Vector3 vGroundPos = GetGroundPos();
if(GetIsStanding())
{
SetIsStanding(false);
SetWasStanding(true);
}
// Figure out how far the ground is from the peds root
// This is used to balance the peds capsule using a spring force in ProcessPedStanding
static dev_float fVerticalThreshold = 0.01f;
float fOffset = PED_GROUNDPOS_RESET_Z;
if(rage::IsNearZero(testMat.c.z - 1.0f, fVerticalThreshold))
{
// We are vertical so lets go cheap
fOffset = m_fGroundZFromImpact - testMat.d.z;
}
// We are non vertical, make sure ground impact is valid before doing transform
else if(vGroundPos.z > PED_GROUNDPOS_RESET_Z)
{
Vector3 vLocalGroundPos = vGroundPos;
testMat.UnTransform(vLocalGroundPos);
fOffset = vLocalGroundPos.z;
}
const float fPedProbeBottom = m_pCapsuleInfo->GetMinProbeHeight();
bool bAllowStand = false;
// static dev_float s_fSmallFallHeightMin = 0.5f;
// static dev_float s_fSmallFallHeightMax = 1.5f;
bool bCheckProbeBottom = GetWasStanding() ||
GetPedResetFlag(CPED_RESET_FLAG_FirstPhysicsUpdate) ||
GetPedResetFlag(CPED_RESET_FLAG_IsLanding);
bAllowStand = (bCheckProbeBottom && fOffset > fPedProbeBottom);
if( (fOffset > -m_pCapsuleInfo->GetGroundToRootOffset()) || bAllowStand )
{
m_fGroundOffsetForPhysics = fOffset;
if(m_bValidGroundNormal)
{
SetIsStanding(true);
// snap the height of the ped's entity to the correct distance above the ground
// don't do this all the time because we want to let the physics smooth the movement up/down stairs, curbs etc.
TUNE_GROUP_BOOL(PED_MOVEMENT, bEnableSetEntityZ, true);
if(m_PedResetFlags.GetEntityZFromGround() > 0 && bEnableSetEntityZ)
{
Vector3 vecPedPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
// blend the effect out smoothly
float fSetRatio = rage::Min(1.0f, 0.05f * m_PedResetFlags.GetEntityZFromGround());
vecPedPos.z = fSetRatio * (vGroundPos.z + m_pCapsuleInfo->GetGroundToRootOffset()) + (1.0f - fSetRatio) * vecPedPos.z;
// DMKH. Prevent snap occurring if we have set a height that we want to snap within. If we attempt to snap
// to a different height, check it against the threshold.
if(m_PedResetFlags.GetEntityZFromGroundZThreshold() > 0.0f)
{
float fDiff = abs(vecPedPos.z - (m_PedResetFlags.GetEntityZFromGroundZHeight()+m_pCapsuleInfo->GetGroundToRootOffset()));
if(fDiff < m_PedResetFlags.GetEntityZFromGroundZThreshold())
{
CDynamicEntity::SetPosition(vecPedPos);
}
}
else
{
CDynamicEntity::SetPosition(vecPedPos);
}
}
}
}
else
{
m_fGroundOffsetForPhysics = PED_GROUNDPOS_RESET_Z;
}
SetPedResetFlag(CPED_RESET_FLAG_FirstPhysicsUpdate, false);
return;
}
#endif
if( (GetCurrentMotionTask()->CanFlyThroughAir() && g_skiing_nvp) || g_skiing_fnvp)
{
ProcessProbesNonVertical();
return;
}
// reset ground physical pointer
if(GetGroundPhysical())
{
SetGroundPhysical(NULL);
m_vecGroundPosLocal.ZeroComponents();
m_groundPhysicalComponent = 0;
m_lastGroundPhysicalComponent = 0;
}
if(GetIsStanding())
{
SetIsStanding(false);
SetWasStanding(true);
}
// network clones need to be processed even when they have collisions turned off,
// as height positions transmitted over the network are approximate, and can lead
// to peds falling through the ground when collisions are turned off, e.g. when
// jacking/being jacked from a car
bool bNetworkClone = false;
bNetworkClone = IsNetworkClone();
// ragdoll is in control
if(m_nRagdollState > RAGDOLL_STATE_ANIM_DRIVEN)
{
// Send off an asynch probe every few frames to set the ground velocity (so that the ped can damp relative to it, among other things)
if (m_RagdollGroundProbeResults.GetResultsReady() && GetCollider())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_RagdollingOnBoat, false);
phCollider* pCollider = GetCollider();
pCollider->SetReferenceFrameVelocity(Vec3V(V_ZERO).GetIntrin128ConstRef());
static float defDampingRate = 0.0f;
float dampingRate = defDampingRate;
WorldProbe::ResultIterator it;
for(it = m_RagdollGroundProbeResults.begin(); it < m_RagdollGroundProbeResults.end(); ++it)
{
if(it->GetHitDetected())
{
CEntity* pHitEntity = CPhysics::GetEntityFromInst(it->GetHitInst());
if(pHitEntity && pHitEntity->IsCollisionEnabled())
{
if(pHitEntity->GetIsPhysical() && ValidateGroundPhysical(*it->GetHitInst(), it->GetHitPositionV(), it->GetHitComponent()))
{
//float currRefFrameVel = Mag(pCollider->GetReferenceFrameVelocity()).Getf();
//float probedVel = ((CPhysical*)pHitEntity)->GetVelocity().Mag();
//if (probedVel > currRefFrameVel)
//{
CTaskNMBehaviour* pTask = GetPedIntelligence()->GetLowestLevelNMTask(this);
if (!pHitEntity->GetIsTypeVehicle() || static_cast<CVehicle*>(pHitEntity)->CanPedsStandOnTop() || (pTask != NULL && pTask->GetIsStuckOnVehicle()))
{
//Calculate the ground velocity.
Vec3V vGroundVelocity = Vec3V(V_ZERO);
CalculateGroundVelocity(static_cast<CPhysical*>(pHitEntity), it->GetHitPositionV(), it->GetHitComponent(), vGroundVelocity);
Assertf(IsFiniteAll(vGroundVelocity), "Ground velocity is invalid.");
pCollider->SetReferenceFrameVelocity(vGroundVelocity.GetIntrin128());
//Calculate the ground angular velocity.
Vec3V vGroundAngularVelocity = Vec3V(V_ZERO);
CalculateGroundAngularVelocity(static_cast<CPhysical*>(pHitEntity), it->GetHitComponent(), vGroundAngularVelocity);
Assertf(IsFiniteAll(vGroundAngularVelocity), "Angular ground velocity is invalid.");
pCollider->SetReferenceFrameAngularVelocity(vGroundAngularVelocity.GetIntrin128());
}
// currRefFrameVel = probedVel;
//}
//// Control the damping rate so that damping doesn't occur if the probed velocity is roughly equal or greater to the reference frame velocity
//static float margin = 0.1f;
//if (probedVel > 0.0f && probedVel + margin > currRefFrameVel)
//{
// dampingRate = 0.0f;
//}
// We don't want ragdolls in boats processing buoyancy.
if(pHitEntity->GetIsTypeVehicle() && static_cast<CVehicle*>(pHitEntity)->InheritsFromBoat())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_RagdollingOnBoat, true);
}
}
}
}
}
GetCollider()->SetReferenceFrameVelocityDampingRate(dampingRate);
GetCollider()->SetReferenceFrameAngularVelocityDampingRate(1.0f);
m_RagdollGroundProbeResults.Reset();
}
else if (!m_RagdollGroundProbeResults.GetWaitingOnResults() && (fwTimer::GetFrameCount() + (size_t)this) % NUM_FRAMES_PER_RAGDOLL_PROBE == 0)
{
Assert(GetRagdollInst() && GetRagdollInst()->GetCacheEntry() && GetRagdollInst()->GetCacheEntry()->GetBound());
phBoundComposite *bound = GetRagdollInst()->GetCacheEntry()->GetBound();
Matrix34 pelvisMat;
pelvisMat.Dot(RCC_MATRIX34(bound->GetCurrentMatrix(0)), RCC_MATRIX34(GetRagdollInst()->GetMatrix()));
// Send off a probe
WorldProbe::CShapeTestProbeDesc losDesc;
losDesc.SetResultsStructure(&m_RagdollGroundProbeResults);
static float depth = 1.2f;
Vector3 vecStart = pelvisMat.d;
Vector3 vecEnd = pelvisMat.d;
vecEnd.z -= depth;
losDesc.SetStartAndEnd(vecStart, vecEnd);
u32 nIncludeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_VEHICLE_TYPE;
losDesc.SetIncludeFlags(nIncludeFlags);
losDesc.SetExcludeTypeFlags(ArchetypeFlags::GTA_PICKUP_TYPE);
losDesc.SetExcludeInstance(GetCurrentPhysicsInst());
WorldProbe::GetShapeTestManager()->SubmitTest(losDesc, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
}
return;
}
// ped isn't inserted into the physics level
else if(!GetCurrentPhysicsInst()->IsInLevel() || (!bNetworkClone && (!IsCollisionEnabled() || GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))))
return;
else if(GetUseExtractedZ())
{
SetIsStanding(true);
return;
}
Assert(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()!=GetRagdollInst());
Assert(GetRagdollInst()==NULL || !GetRagdollInst()->IsInLevel() || !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()) || GetRagdollState()==RAGDOLL_STATE_ANIM_DRIVEN);
bool bStanding = false;
float fGroundColLineOffset = PED_GROUNDCOLLINE_OFFSET_TIMESTEP*rage::Max(2.0f, 50.0f*fwTimer::GetTimeStep());
float floorMin=0.0f,floorMax;
Vector3 floorNormal;
phMaterialMgr::Id ProbeHitMaterial=0;
SetStairFlags(NULL, NULL, 0, 0);
u32 iComponentStoodOn = 0;
phInst* pInstStandingOn = NULL;
float distUnder=m_pCapsuleInfo->GetGroundToRootOffset();
if(GetWasStanding())
{
distUnder += fGroundColLineOffset*2.0f;
}
Vector3 vecStart = testMat.d;
Vector3 vecEnd = testMat.d;
vecEnd.z -= distUnder;
u32 nIncludeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_PED_TYPE
| ArchetypeFlags::GTA_OBJECT_TYPE;
WorldProbe::CShapeTestHitPoint probeHitPoints[NUM_INTERSECTIONS_FOR_PED_PROBES];
WorldProbe::CShapeTestResults probeResults(probeHitPoints, NUM_INTERSECTIONS_FOR_PED_PROBES);
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vecStart, vecEnd);
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetExcludeEntity(this);
probeDesc.SetIncludeFlags(nIncludeFlags);
probeDesc.SetExcludeTypeFlags(ArchetypeFlags::GTA_PICKUP_TYPE);
probeDesc.SetIsDirected(true);
WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
WorldProbe::CShapeTestHitPoint* pUseProbeIsect = NULL;
for(WorldProbe::ResultIterator it = probeResults.begin(); it < probeResults.last_result(); ++it)
{
if(it->IsAHit())
{
CEntity* pHitEntity = CPhysics::GetEntityFromInst(it->GetHitInst());
if(pHitEntity && pHitEntity->IsCollisionEnabled())
{
// Check we've got the first intersection along the probe.
if(!pUseProbeIsect || it->GetHitTValue() < pUseProbeIsect->GetHitTValue())
{
pUseProbeIsect = &(*it);
}
}
}
else
{
break;
}
}
if(pUseProbeIsect)
{
bStanding = true;
if(pUseProbeIsect->GetHitInst() && CPhysics::GetEntityFromInst(pUseProbeIsect->GetHitInst())
&& CPhysics::GetEntityFromInst(pUseProbeIsect->GetHitInst())->GetIsPhysical()
&& static_cast<CPhysical*>(CPhysics::GetEntityFromInst(pUseProbeIsect->GetHitInst()))->GetAttachParent()==this)
{
//Assertf(false, "Ped is trying to stand on something he's attached to");
return;
}
floorMin = floorMax = pUseProbeIsect->GetHitPosition().z;
floorNormal = pUseProbeIsect->GetHitNormal();
ProbeHitMaterial = pUseProbeIsect->GetHitMaterialId();
SetStairFlags(pUseProbeIsect, NULL, 0, 0);
// Need to grab component ID and instance ptr to enable audio material lookups.
iComponentStoodOn = pUseProbeIsect->GetHitComponent();
pInstStandingOn = pUseProbeIsect->GetHitInst();
}
else
{
if(GetWasStanding())
{
// Check we haven't just walked over a tiny crack.
Vector3 vecStart = testMat.d;
Vector3 vecEnd = testMat.d;
Assert(distUnder>0.0f);
u32 nIncludeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_PED_TYPE
| ArchetypeFlags::GTA_OBJECT_TYPE;
WorldProbe::CShapeTestHitPoint testHitPoints[NUM_INTERSECTIONS_FOR_PED_PROBES];
WorldProbe::CShapeTestResults capsuleResults(testHitPoints, NUM_INTERSECTIONS_FOR_PED_PROBES);
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
capsuleDesc.SetCapsule(vecStart, vecEnd, g_CapsuleRadius);
capsuleDesc.SetResultsStructure(&capsuleResults);
capsuleDesc.SetExcludeEntity(this);
capsuleDesc.SetIncludeFlags(nIncludeFlags);
capsuleDesc.SetExcludeTypeFlags(ArchetypeFlags::GTA_PICKUP_TYPE);
capsuleDesc.SetIsDirected(true);
WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc);
WorldProbe::CShapeTestHitPoint* pUseCapsuleIsect = NULL;
for(WorldProbe::ResultIterator it = capsuleResults.begin(); it < capsuleResults.last_result(); ++it)
{
if(it->IsAHit())
{
CEntity* pHitEntity = CPhysics::GetEntityFromInst(it->GetHitInst());
if(pHitEntity && pHitEntity->IsCollisionEnabled())
{
/*
grcDebugDraw::Line(it->GetHitPosition(),it->GetHitPosition()+it->GetHitNormal(),
Color32(1.0f,0.0f,0.0f,1.0f),Color32(0.0f,0.0f,0.0f,1.0f));
grcDebugDraw::Sphere(it->GetHitPosition(),0.05f,Color32(0.0f,0.0f,1.0f,1.0f),false);
*/
// Check we've got the first intersection along the probe.
if(!pUseCapsuleIsect || it->GetHitPosition().z > pUseCapsuleIsect->GetHitPosition().z)
{
pUseCapsuleIsect = &(*it);
}
}
}
else
{
break;
}
}
if(pUseCapsuleIsect)
{
bStanding = true;
if(pUseCapsuleIsect->GetHitInst() && CPhysics::GetEntityFromInst(pUseCapsuleIsect->GetHitInst())
&& CPhysics::GetEntityFromInst(pUseCapsuleIsect->GetHitInst())->GetIsPhysical()
&& static_cast<CPhysical*>(CPhysics::GetEntityFromInst(pUseCapsuleIsect->GetHitInst()))->GetAttachParent()==this)
{
//Assertf(false, "Ped is trying to stand on something he's attached to");
return;
}
floorMin = floorMax = pUseCapsuleIsect->GetHitPosition().z;
floorNormal = pUseCapsuleIsect->GetHitNormal();
ProbeHitMaterial = pUseCapsuleIsect->GetHitMaterialId();
SetStairFlags(pUseCapsuleIsect, NULL, 0, 0);
// Need to grab component ID and instance ptr to enable audio material lookups.
iComponentStoodOn = pUseCapsuleIsect->GetHitComponent();
pInstStandingOn = pUseCapsuleIsect->GetHitInst();
}
}
}
if(bStanding && GetCurrentMotionTask()->ShouldStickToFloor())
{
SetIsStanding(true);
// get intersection along probe a distance centred from default standing height
// float fStandRatio = (testIntersection.GetPosition().z - vecEnd.z) - fGroundColLineOffset;
float fStandRatio = (floorMin - (testMat.d.z-m_pCapsuleInfo->GetGroundToRootOffset()));// - fGroundColLineOffset;
// now scale distance to get a ratio shifted to give ratio of 1.0 at default standing height, and desired spring strength
fStandRatio = (fStandRatio + PED_GROUNDCOLLINE_OFFSET_TIMESTEP/PED_STAND_SPRING_STRENGTH) * (PED_STAND_SPRING_STRENGTH / PED_GROUNDCOLLINE_OFFSET_TIMESTEP);
//the above line amounts to 1+(13.333 * fStandRatio), where fStandRatio is how far under/above the floor the foot of the person would be +ve is below -ve is above
//float fStandRatio = (testIntersection.GetPosition().z - vecEnd.z) / (-1.0f*fGroundColLineOffset);
Vector3 scaledGravity(CPhysics::GetSimulator()->GetGravity());
scaledGravity.Scale(GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor());
ApplyForceCg(-1.0f*fStandRatio*scaledGravity*GetMass());
float fNormalVelocity = DotProduct(floorNormal /*testIntersection.GetNormal()*/, GetVelocity());
Vector3 standDamping(0.0f,0.0f, fNormalVelocity);//*testIntersection.GetNormal().z);
// separate damping coefficients for compression and rebound
fNormalVelocity < 0.0f ? standDamping.z *= PED_STAND_COMPRESSION_DAMP : standDamping.z *= PED_STAND_REBOUND_DAMP;
standDamping.z *= GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor();
if(standDamping.z > 50.0f)
standDamping.z = 50.0f;
else if(standDamping.z < -50.0f)
standDamping.z = -50.0f;
//GetCollider()->ApplyAccel(standDamping);
ApplyForceCg(standDamping*GetMass());
m_vecGroundPos = RCC_VEC3V(testMat.d);//testIntersection.testMat.d;
m_vecGroundPos.SetZf(floorMin);
// move rendered matrix around so that feet touch the ground properly
Vector3 pedPos = testMat.d;
/*
if(m_pMoveBlender->CanFlyThroughAir())
{
float delta=GetGroundPos().z+ m_pCapsuleInfo->GetGroundToRootOffset() - pedPos.z;
// float deltafraction=Min(1.0f,50.0f*fwTimer::GetTimeStep()*0.3f);
// delta*=deltafraction;
if(delta>0.0f) //if we are under the ground then push up, else we are above then float through the air
{
// pedPos.z = GetGroundPos().z + m_pCapsuleInfo->GetGroundToRootOffset(); //move the person position up
pedPos.z += delta;//+0.10f;
}
// grcDebugDraw::Sphere(testIntersection.GetPosition(),0.05f,Color32(255,0,0,255));
Vector3 destNorm=testIntersection.GetNormal();
Vector3 vecStart = testMat.d;
Vector3 vecEnd = testMat.d;
vecEnd -= (m_pCapsuleInfo->GetGroundToRootOffset()+0.6f)*m_vecGroundNormal;
// grcDebugDraw::Line(vecStart,vecEnd,Color32(255,0,0,255),Color32(255,0,0,255));
testSegment.Set(vecStart, vecEnd);
bStanding = CPhysics::GetLevel()->TestProbe(testSegment, &testIntersection, GetCurrentPhysicsInst(), nFlags);
pedPos=testIntersection.testMat.d+m_pCapsuleInfo->GetGroundToRootOffset()*m_vecGroundNormal;
CEntity::SetPosition(pedPos);
destNorm=testIntersection.GetNormal();
m_pMoveBlender->SetActualFloorNormal(destNorm); //the ski physics uses the real normal for some parts
if(destNorm.z>0.9961f||GetVelocity().Mag()<10.0f) //cos of 5 degree slope
{
float blend=(GetVelocity().Mag()/10.0f); // 0 use up vect 1 use surface normal
Vector3 StraightUp=Vector3(0,0,1);
destNorm = (destNorm-StraightUp)*blend+StraightUp;
}
float speed=GetVelocity().Mag();
delta=g_smoothNorm*(MaxTurnSpeed-speed)*1.0f/MaxTurnSpeed;
delta=MIN(delta,1.0f);
delta=MAX(delta,0.0f);
if(destNorm.z>0.05f)
{
//delta=1.0f;
m_vecGroundNormal = (destNorm-m_vecGroundNormal)*delta+m_vecGroundNormal;
m_vecGroundNormal.Normalize();
}
else
{
speed=0.0f;
}
}
else
*/
{
m_vecGroundNormal = floorNormal;//testIntersection.GetNormal();
pedPos.z = m_vecGroundPos.GetZf() + m_pCapsuleInfo->GetGroundToRootOffset();
}
//if(!GetIkManager().IKControlsPedPos())
{
CDynamicEntity::SetPosition(pedPos);
}
// get the material we are standing on
// m_LastMaterialToHaveBeenStandingOn = testIntersection.GetInstance()->GetArchetype()->GetBound()->GetMaterialIndexFromPartIndex(testIntersection.GetPartIndex(), testIntersection.GetComponent());
m_PackedGroundMaterialId = ProbeHitMaterial;//testIntersection.GetMaterialId();//(u8)phInstGta::GetMaterialIdFromIsect(&testIntersection);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
m_secondSurfaceDepth = 0.0f;
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
CEntity* pEntityStandingOn = CPhysics::GetEntityFromInst(pInstStandingOn);
m_PedAudioEntity.GetFootStepAudio().SetStandingMaterial(ProbeHitMaterial, pEntityStandingOn, iComponentStoodOn);
// if we're standing on something physical - store a pointer to it
if(pEntityStandingOn && pEntityStandingOn->GetIsPhysical() && ValidateGroundPhysical(*pInstStandingOn, m_vecGroundPos, iComponentStoodOn))
{
SetGroundPhysical(static_cast<CPhysical*>(pEntityStandingOn));
m_groundPhysicalComponent = iComponentStoodOn;
m_vecGroundPosLocal = UnTransformOrtho(GetGroundPhysical()->GetMatrix(), m_vecGroundPos);
if(GetGroundPhysical() != m_pLastValidGroundPhysical)
{
m_uTimeGroundPhysicalWasSet = fwTimer::GetTimeInMilliseconds();
}
}
}
/*
else
{
if(m_pMoveBlender->CanFlyThroughAir())
{
// really want to turn the skier to be in this direction before he lands
Vector3 destNorm=Vector3(0.0f,0.0f,1.0f);
float delta=0.1f;//g_smoothNorm*(MaxTurnSpeed-GetVelocity().Mag())*1.0f/MaxTurnSpeed;
if(m_vecGroundNormal.z>0.05f)
{
m_vecGroundNormal = (destNorm-m_vecGroundNormal)*delta+m_vecGroundNormal;
m_vecGroundNormal.Normalize();
}
}
}
*/
Assertf(!GetIsAttachedToGround() || GetGroundPhysical(),"Attached to ground without a ground physical.");
}
void CPed::CheckForPlayerBeingSquashedByKinematicPed(const phContactIterator impacts, Vec3V_Ref kinematicPedNormal, Vec3V_Ref nonKinematicNormal)
{
// Helper function for updating the flags used to determine if a player ped should ignore collisions with another ped
// using kinematic physics mode.
phInst* pOtherInst = impacts.GetOtherInstance();
const u32 nOtherInstLevelIndex = pOtherInst->GetLevelIndex();
u32 nNonKinematicPedTypes = ArchetypeFlags::GTA_MAP_TYPE_MOVER;
// Cache some properties from the contact for later testing.
Vec3V vContactNormal;
impacts.GetOtherNormal(vContactNormal);
const int nPlayerHitComp = impacts.GetMyComponent();
// Only count collisions against other geometry when the normals are sufficiently horizontal.
static float sfNormalHorizontalTol = 0.8f;
bool bContactWithKinematicPed = CPhysics::GetLevel()->GetInstanceTypeFlags(nOtherInstLevelIndex)&ArchetypeFlags::GTA_PED_TYPE &&
CPhysics::GetEntityFromInst(pOtherInst) && static_cast<CPed*>(CPhysics::GetEntityFromInst(pOtherInst))->IsUsingKinematicPhysics();
if (bContactWithKinematicPed)
{
// If the kinematic ped in contact with us is entering a vehicle, allow us to consider vehicles as the other thing we are in contact with
CPed* pKinematicPed = static_cast<CPed*>(CPhysics::GetEntityFromInst(pOtherInst));
const bool bInContactWithKinematicPedEnteringVehicle = pKinematicPed && pKinematicPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringVehicle);
Vector3 vKinematicPedVelocity;
CPhysics::GetSimulator()->GetObjectVelocity(*pOtherInst, vKinematicPedVelocity);
const float kfKinematicPedVelThresh2 = 0.05f;
if(vKinematicPedVelocity.Mag2() > kfKinematicPedVelThresh2 || bInContactWithKinematicPedEnteringVehicle)
{
kinematicPedNormal = vContactNormal;
SetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed, true);
m_pKinematicPed = pKinematicPed;
}
return;
}
const bool bInContactWithKinematicPedEnteringVehicle = m_pKinematicPed && m_pKinematicPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringVehicle);
if (bInContactWithKinematicPedEnteringVehicle)
{
aiDebugf1("Frame : %i, Ped %s (%p) in contact with kinematic ped %s (%p) entering vehicle", fwTimer::GetFrameCount(), GetModelName(), this, m_pKinematicPed->GetModelName(), m_pKinematicPed.Get());
nNonKinematicPedTypes |= ArchetypeFlags::GTA_VEHICLE_BVH_TYPE|ArchetypeFlags::GTA_BOX_VEHICLE_TYPE;
}
const u32 uInstanceTypeFlags = CPhysics::GetLevel()->GetInstanceTypeFlags(nOtherInstLevelIndex);
bool bContactHorizontal = fabs(vContactNormal.GetZf())<sfNormalHorizontalTol && nPlayerHitComp==PED_USE_SHAPETEST_CAPSULE;
bool bContactWithSomethingElse = uInstanceTypeFlags&nNonKinematicPedTypes
&& bContactHorizontal;
aiDebugf1("Frame : %i, Ped %s (%p) %s contact with something else, other inst type flags %i, %s contact with kinematic ped", fwTimer::GetFrameCount(), GetModelName(), this, bContactWithSomethingElse ? "IN" : "NOT IN", uInstanceTypeFlags, GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed) ? "IS IN" : "NOT IN");
// also need to include locked doors
if (!bContactWithSomethingElse && bContactHorizontal && (uInstanceTypeFlags&(ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE | ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE)))
{
const CEntity* pEnt = CPhysics::GetEntityFromInst(pOtherInst);
if (pEnt)
{
if (pEnt->GetIsFixedFlagSet())
{
bContactWithSomethingElse = true;
}
else if (pEnt->GetIsPhysical())
{
TUNE_GROUP_FLOAT(KINEMATIC_PHYSICS, fMinMassToConsiderDynamicAsFixed, 5000.0f, 0.0f, 100000000.0f, 0.1f);
if (static_cast<const CPhysical*>(pEnt)->GetMass()>fMinMassToConsiderDynamicAsFixed)
{
bContactWithSomethingElse = true;
}
}
}
}
// We are now guaranteed to have generated the kinematic contact first ( we do the non kinematic ones in a second pass)
// so only do this work if a kinematic contact exists.
if(bContactWithSomethingElse && GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed))
{
if (MagSquared(nonKinematicNormal).Getf()==0.0f)
{
// don't disable collision from a single contact
// The mover capsule should always be able to slide out of the way in that case
nonKinematicNormal = vContactNormal;
}
else
{
// if we've already generated a contact, check if the normal of this one
// opposes the normal of the existing one, relative to the kinematic normal.
Vec3V kinematicRight = RotateAboutZAxis(kinematicPedNormal, ScalarV(PI*0.5f));
ScalarV dotOther = Dot(nonKinematicNormal, kinematicRight);
ScalarV dotThis = Dot(vContactNormal, kinematicRight);
if (Sign(dotOther.Getf())!= Sign(dotThis.Getf()))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithSomethingOtherThanKinematicPed, true);
}
}
}
}
dev_float sfPlayerLegBoundIgnoreMassLimit = 200.0f;
dev_float sfSkiRagdollFriction = 0.8f;
dev_float sfKickedPedRagdollFriction = 0.3f;
dev_float sfKickedMapRagdollFriction = 0.85f;
dev_float sfDefaultMapRagdollFriction = 1.0f;
bool CPed::ValidateGroundPhysical(const phInst &groundInstance, Vec3V_In vGroundPos, int iComponent) const
{
//Do we need to check this?
if (!CPhysics::GetLevel())
{
return false;
}
const CEntity* groundPhysical = CPhysics::GetEntityFromInst(&groundInstance);
if (groundPhysical == NULL)
{
return false;
}
if (groundPhysical->GetIsTypePed())
{
const CPed* pPed = static_cast<const CPed*>(groundPhysical);
// Consider large components on large ped ragdolls as valid ground
if(groundInstance.GetClassType() != PH_INST_FRAG_PED || pPed->GetDeathState() != DeathState_Dead || !pPed->ShouldBeDead() ||
groundInstance.GetArchetype()->GetMass() < ms_fLargePedMass ||
static_cast<const fragInstNMGta&>(groundInstance).GetTypePhysics()->GetChild(iComponent)->GetUndamagedMass() < ms_fLargeComponentMass)
{
return false;
}
}
// Don't allow doors to become ground instances.
if(groundPhysical->GetIsTypeObject())
{
const CObject* groundObject = static_cast<const CObject*>(groundPhysical);
if(groundObject->IsADoor())
{
return false;
}
// Don't allow standing on light floating objects because buoyancy forces might prove unstable.
dev_float minimumFloatingGroundMass = 50.0f;
if(groundObject->GetIsInWater() && groundObject->GetMass() < minimumFloatingGroundMass)
{
return false;
}
}
// Don't allow sinking vehicles to become ground instances.
if(groundPhysical->GetIsTypeVehicle())
{
const CVehicle* groundVehicle = static_cast<const CVehicle*>(groundPhysical);
if(groundVehicle->m_nVehicleFlags.bIsDrowning)
{
return false;
}
if(groundVehicle->IsPropeller(iComponent))
{
return false;
}
}
//Original behavior (accepting all ground entities) is maintained for inactive objects
int iLevelIndex = groundInstance.GetLevelIndex();
if ( !CPhysics::GetLevel()->IsInLevel( iLevelIndex )
|| !CPhysics::GetLevel()->IsActive( iLevelIndex )
)
{
#if PDR_ENABLED
if (!CPhysics::GetLevel()->IsInLevel( iLevelIndex ))
{
PDR_ONLY(debugPlayback::RecordInstLabel(*GetCurrentPhysicsInst(), "GroundInstance", "NotInLevel"));
}
else //!CPhysics::GetLevel()->IsActive( iLevelIndex )
{
PDR_ONLY(debugPlayback::RecordInstLabel(*GetCurrentPhysicsInst(), "GroundInstance", "InActive"));
}
#endif
return true;
}
//TODO: Q. Can we easily get the aaBB for iComponentStoodOn and limit this to subcomponents?
//Test that the object we're standing on has a couple of axis' large enough to support standing / moving contact
const phArchetype *pArchetype = groundInstance.GetArchetype();
if (pArchetype)
{
const phBound *pBound = pArchetype->GetBound();
if (pBound)
{
if ((iComponent >-1) && (pBound->GetType() == phBound::COMPOSITE))
{
//Use the component we're standing on
const phBoundComposite* pBoundComposite = static_cast<const phBoundComposite*>(pBound);
// Protect against crashes like B*743843.
if(iComponent < pBoundComposite->GetNumBounds() && pBoundComposite->GetBound(iComponent))
{
pBound = pBoundComposite->GetBound(iComponent);
}
else
{
// If the ground component has been removed since we started standing on it, don't let it become the ground.
// This happens due to async ragdoll probes.
return false;
}
}
Vec3V vExtents(V_ZERO);
vExtents = pBound->GetBoundingBoxMax() - pBound->GetBoundingBoxMin();
const float fNeededScaleForUse = GetCapsuleInfo()->GetHalfWidth() * ms_MinStandingHalfCapsuleWidthRatio;
int iCount = fNeededScaleForUse < vExtents.GetXf() ? 1 : 0;
iCount += fNeededScaleForUse < vExtents.GetYf() ? 1 : 0;
iCount += fNeededScaleForUse < vExtents.GetZf() ? 1 : 0;
if (iCount >= 2)
{
// If I'm in cover make sure we are moving at about the speed of the proposed ground object.
// B*2078351: Don't do this if the proposed ground entity is also the thing we're using as cover as it can cause the ped to be flung off the back of the vehicle.
if ((GetIsInCover() || GetPedResetFlag(CPED_RESET_FLAG_IsEnteringCover)) && (m_pCoverPoint && m_pCoverPoint->GetEntity() && m_pCoverPoint->GetEntity()->GetCurrentPhysicsInst() != &groundInstance))
{
phCollider *pCollider = PHSIM->GetCollider(&groundInstance);
if (pCollider)
{
//Check velocity difference at point of contact
Vec3V vMyVel = VECTOR3_TO_VEC3V(GetVelocity());
Vec3V vOtherVelAtContact = pCollider->GetLocalVelocity(vGroundPos.GetIntrin128ConstRef());
float fSpeedAllowance = ms_fSizeToSpeedTolForCoverGroundInstance * pBound->GetRadiusAroundCentroid();
ScalarV vVelDiffSqr = DistSquared(vMyVel, vOtherVelAtContact);
if (IsGreaterThanAll(vVelDiffSqr, ScalarV(fSpeedAllowance*fSpeedAllowance)))
{
return false;
}
PDR_ONLY(debugPlayback::RecordPedGroundInstance(GetCurrentPhysicsInst(), &groundInstance, iComponent, vExtents, fNeededScaleForUse, iCount, Sqrt(vVelDiffSqr).Getf(), fSpeedAllowance));
return true;
}
}
PDR_ONLY(debugPlayback::RecordPedGroundInstance(GetCurrentPhysicsInst(), &groundInstance, iComponent, vExtents, fNeededScaleForUse, iCount, -1.0f, -1.0f));
return true;
}
}
}
return false; //TMS: Default to original behavior
}
void CPed::DoHitPed(phContactIterator &impacts)
{
bool bDoHitPed = false;
bool bForceHit = false;
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(impacts.GetMyInstance());
if(pOtherEntity != NULL && pOtherEntity->GetIsTypeObject() && static_cast<CObject*>(pOtherEntity)->IsADoor())
{
CDoor* pDoor = static_cast<CDoor*>(pOtherEntity);
// If the ped is not dead, or if this is the player's corpse in single-player, or if any fixed flags are set on the door, then hit all the time
bForceHit = (GetDeathState() != DeathState_Dead) || !ShouldBeDead() || (!NetworkInterface::IsGameInProgress() && IsPlayer()) || pDoor->GetIsAnyFixedFlagSet();
u32 uMaxContactTime = ms_uMaxRagdollDoorContactTime;
// If the ped hasn't yet been hit by a door this frame and this is our first update after activation (or the door's first update after activation)
// then it means we are inter-penetrating the door (we'd previously deactivated inside the door) and cannot hit them
// (we only disallow hitting because of inter-penetration on dead peds)
if(!bForceHit && !m_bHitByDoor && (GetPedResetFlag(CPED_RESET_FLAG_OnActivationUpdate) || pDoor->IsOnActivationUpdate()))
{
// Ensure that we set that this ped was previously being hit by a door and that they have already reached the max contact time!
m_bPreviouslyHitByDoor = true;
m_uRagdollDoorContactTime = fwTimer::GetTimeInMilliseconds() - uMaxContactTime;
m_bHitByDoor = true;
}
else
{
bDoHitPed = true;
// If the door isn't being forced to hit the ped and the player isn't interacting with the door...
if(!bForceHit && pDoor->GetPlayerDoorState() == CDoor::PDS_NONE)
{
// Go through all the peds colliding with this door and see if there are any (other than ourselves) that are animating
bool bOtherAnimatedPedCollidingWithDoor = false;
for(const CCollisionRecord* pPedCollisionRecord = pDoor->GetFrameCollisionHistory()->GetFirstPedCollisionRecord(); pPedCollisionRecord != NULL; pPedCollisionRecord = pPedCollisionRecord->GetNext())
{
const CPed* pOtherPed = static_cast<const CPed*>(pPedCollisionRecord->m_pRegdCollisionEntity.Get());
if(pOtherPed != NULL && pOtherPed != this && !pOtherPed->GetUsingRagdoll())
{
bOtherAnimatedPedCollidingWithDoor = true;
break;
}
}
// If there are no other animating peds colliding with this door then there is no need to let the door pass through us
if(!bOtherAnimatedPedCollidingWithDoor)
{
bForceHit = true;
}
}
// If a door hadn't previously been hitting the ped and the ped is not dead then hit all the time
if(!m_bPreviouslyHitByDoor && bForceHit)
{
m_uRagdollDoorContactTime = fwTimer::GetTimeInMilliseconds();
}
else
{
// Hit ped should remember that it was being hit by a door this frame
m_bHitByDoor = true;
if(m_bPreviouslyHitByDoor)
{
if(fwTimer::GetTimeInMilliseconds() >= m_uRagdollDoorContactTime + uMaxContactTime)
{
// It's time to stop hitting the ped
bDoHitPed = false;
}
}
else if(fwTimer::GetTimeInMilliseconds() >= m_uRagdollDoorContactTime + uMaxContactTime * 0.5f)
{
// If we haven't touched this ped for half of the maximum hitting time, then reset the timer
m_uRagdollDoorContactTime = fwTimer::GetTimeInMilliseconds();
}
}
}
}
if(!bDoHitPed)
{
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "HitCorpse"));
}
}
void CPed::DoKickPed(phContactIterator &impacts, bool bValidGround)
{
bool bDoKickPed = false;
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
if(pOtherEntity != NULL && pOtherEntity->GetIsTypePed())
{
CPed* pOtherPed = static_cast<CPed*>(pOtherEntity);
int iOtherComponent = impacts.GetOtherComponent();
// Ignore capsule probe contacts with valid ground
if(bValidGround && ((pOtherPed->GetCapsuleInfo()->IsQuadruped() && (iOtherComponent == CBipedCapsuleInfo::BOUND_CAPSULE_PROBE || iOtherComponent == CBipedCapsuleInfo::BOUND_CAPSULE_PROBE_2))
#if PED_USE_CAPSULE_PROBES
|| (!pOtherPed->GetCapsuleInfo()->IsQuadruped() && iOtherComponent == PED_USE_CAPSULE_PROBES)
#endif
))
{
return;
}
// For quadrupeds we only consider the blocker bounds for kicking
// Allowing kicking with a quadruped's main capsule bounds is problematic since their main capsule bounds are often horizontal
// Ensure the probe bounds for quadrupeds are always disabled since they are not suitable for kicking
#if ENABLE_HORSE
if(pOtherPed->GetCapsuleInfo()->IsQuadruped() && GetHorseComponent())
{
const CQuadrupedCapsuleInfo* pQuadrupedCapsuleInfo = pOtherPed->GetCapsuleInfo()->GetQuadrupedCapsuleInfo();
if((!pQuadrupedCapsuleInfo->GetUsePropBlocker() || iOtherComponent != pQuadrupedCapsuleInfo->GetPropBlockerSlot()) &&
(!pQuadrupedCapsuleInfo->GetUseBlocker() || iOtherComponent != pQuadrupedCapsuleInfo->GetBlockerSlot()))
{
if(iOtherComponent == CBipedCapsuleInfo::BOUND_CAPSULE_PROBE || iOtherComponent == CBipedCapsuleInfo::BOUND_CAPSULE_PROBE_2)
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Removed quadruped", "Probe"));
impacts.DisableImpact();
}
return;
}
}
#endif
// Don't kick dead peds when other ped is in the air or when in multi-player matches
if(pOtherPed != NULL && ((GetDeathState() != DeathState_Dead) || !ShouldBeDead() || (!pOtherPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir) && (!NetworkInterface::IsGameInProgress()))))
{
bank_u32* pMaxRagdollContactTime = pOtherPed->IsPlayer() ? ms_uMaxRagdollPlayerLegContactTime : ms_uMaxRagdollAILegContactTime;
float fMoveBlendRatioMag = pOtherPed->GetMotionData()->GetIsStrafing() ? pOtherPed->GetMotionData()->GetCurrentMoveBlendRatio().Mag() : pOtherPed->GetMotionData()->GetCurrentMbrY();
bool bMovingVeryQuickly = false;
if (pOtherPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringCover))
{
bMovingVeryQuickly = true;
}
else
{
const CTaskMotionInCover* pCoverMotionTask = static_cast<CTaskMotionInCover*>(pOtherPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_MOTION_IN_COVER));
if(pCoverMotionTask != NULL && pCoverMotionTask->GetState() == CTaskMotionInCover::State_CoverToCover)
{
bMovingVeryQuickly = true;
}
}
u32 uMaxContactTime = !bMovingVeryQuickly ? (pOtherPed->GetMotionData()->GetIsLessThanSprint(fMoveBlendRatioMag) ? (pOtherPed->GetMotionData()->GetIsLessThanRun(fMoveBlendRatioMag) ? pMaxRagdollContactTime[0] : pMaxRagdollContactTime[1]) : pMaxRagdollContactTime[2]) : pMaxRagdollContactTime[3];
// If kicking ped is the player and is moving in cover then kick all the time
bool bForceKick = pOtherPed->IsPlayer() && pOtherPed->GetIsInCover() && !pOtherPed->GetMotionData()->GetIsStill();
CTaskMeleeActionResult* pTaskMeleeActionResult = pOtherPed->GetPedIntelligence()->GetTaskMeleeActionResult();
if (pTaskMeleeActionResult != NULL && pTaskMeleeActionResult->ShouldProcessMeleeCollisions() && (GetWeaponDamagedTimeByEntity(pOtherPed) + 250) > fwTimer::GetGameTimer().GetTimeInMilliseconds())
{
bForceKick = true;
const CActionResult* pActionResult = pTaskMeleeActionResult->GetActionResult();
if (pActionResult != NULL)
{
const CDamageAndReaction* pDamageAndReaction = pActionResult->GetDamageAndReaction();
if (pDamageAndReaction != NULL && pDamageAndReaction->ShouldUseDefaultUnarmedWeapon())
{
bForceKick = false;
}
}
}
// If this ped hasn't yet been kicked this frame and this is our first update after activation (or the other ped's first update after activation)
// then it means we are inter-penetrating the other ped (we'd previously deactivated inside the other ped or vice versa) and cannot be kicked by them
// (we only disallow kicking because of inter-penetration on dead peds and when not performing a regular melee attack)
if(!bForceKick && !m_bKickedByPlayer && (pOtherPed->GetPedResetFlag(CPED_RESET_FLAG_OnActivationUpdate) || GetPedResetFlag(CPED_RESET_FLAG_OnActivationUpdate)))
{
// Ensure that we set that this ped was previously being kicked and that they have already reached the max contact time!
m_bPreviouslyKickedByPlayer = true;
m_uRagdollPlayerLegContactTime = 0;
m_bKickedByPlayer = true;
}
else
{
bDoKickPed = !GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollOnPedCollisionWhenDead);
// If we hadn't previously been kicked we're not dead or the other ped is the player and is in cover then kick all the time
if(!m_bPreviouslyKickedByPlayer && bForceKick)
{
m_uRagdollPlayerLegContactTime = fwTimer::GetTimeInMilliseconds();
}
else
{
// Remember that we were being kicked this frame
m_bKickedByPlayer = true;
if(m_bPreviouslyKickedByPlayer)
{
// If this is a probe impact and the we're relatively small and enough time has passed, allow stepping through
bool bStopKickingAfterTimeout = (pOtherPed->GetIsInCover() && pOtherPed->GetMotionData()->GetIsStill()) || bMovingVeryQuickly;
#if PED_USE_CAPSULE_PROBES
if (iOtherComponent == PED_USE_CAPSULE_PROBES)
{
bStopKickingAfterTimeout = true;
}
#endif
if(bStopKickingAfterTimeout && fwTimer::GetTimeInMilliseconds() >= m_uRagdollPlayerLegContactTime + uMaxContactTime)
{
// It's time to step over the ped
bDoKickPed = false;
}
}
else if(fwTimer::GetTimeInMilliseconds() >= m_uRagdollPlayerLegContactTime + uMaxContactTime * 0.5f)
{
// If we haven't been kicked for half of the maximum kicking time, then reset the timer
m_uRagdollPlayerLegContactTime = fwTimer::GetTimeInMilliseconds();
}
}
if (bDoKickPed)
{
Vec3V vOffset = impacts.GetOtherPosition() - pOtherPed->GetTransform().GetPosition();
if (vOffset.GetZf() > 0.0f)
{
static dev_float sfAllowedRagdollMoverPenetrationProp = 0.2f;
float fAllowedDepth = sfAllowedRagdollMoverPenetrationProp * Min(pOtherPed->GetCapsuleInfo()->GetHalfWidth(), GetCapsuleInfo()->GetHalfWidth());
if (impacts.GetDepth() >= fAllowedDepth)
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickDepth modified from", impacts.GetDepth()));
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickDepth modified to", impacts.GetDepth() - fAllowedDepth));
impacts.SetDepth(impacts.GetDepth() - fAllowedDepth);
}
else
{
bDoKickPed = false;
}
}
else
{
// Always want to kick corpses away from the cover point so set our normal to an average of the cover point and our current normal
if (pOtherPed->GetIsInCover() && pOtherPed->GetCoverPoint() != NULL)
{
if (ZeroOutImpactZ(impacts))
{
Vec3V vMyNormal;
impacts.GetMyNormal(vMyNormal);
vMyNormal = Normalize(Average(vMyNormal, Negate(pOtherPed->GetCoverPoint()->GetCoverDirectionVector())));
impacts.SetMyNormal(vMyNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickAwayFromCoverNormal", vMyNormal));
}
else
{
bDoKickPed = false;
}
}
// Otherwise always want to kick corpses towards our side to 'plow' them out of the way
else
{
#if PLAYER_USE_LOWER_LEG_BOUND
// If we're kicking a ragdoll that is considered to be on the ground...
// If this is an impact with the player's lower leg bound then allow the player to step over the corpse
// ... Unless the corpse is considered valid ground in which case there is other logic that will allow the player to vault over the corpse
if (!bValidGround && !pOtherPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir) && !pOtherPed->IsUsingKinematicPhysics() && pOtherPed->GetCapsuleInfo()->IsBiped() &&
pOtherPed->GetCapsuleInfo()->GetBipedCapsuleInfo()->GetUseLowerLegBound() && iOtherComponent == PLAYER_USE_LOWER_LEG_BOUND)
{
Vec3V vMyNormal;
impacts.GetMyNormal(vMyNormal);
vMyNormal = Vec3V(0.0f, 0.0f, -1.0f);
impacts.SetMyNormal(vMyNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickLowerLegDownwardNormal", vMyNormal));
}
else
#endif
{
if (ZeroOutImpactZ(impacts))
{
Vec3V vMyNormal;
impacts.GetMyNormal(vMyNormal);
float fAngle = AngleZNormInput(pOtherPed->GetTransform().GetForward(), vMyNormal).Getf();
float fMinimumKickDirectionAngle = ms_fMinimumKickDirectionAngle * DtoR;
if (pOtherPed->GetMotionData()->GetIsSprinting())
{
fMinimumKickDirectionAngle *= ms_fMinimumKickDirectionAngleSprintScaler;
}
// If we're pushing against the ragdolling ped within a certain cone then force the push to the maximum edge of that cone...
if (Abs(fAngle) < fMinimumKickDirectionAngle)
{
//float fMinimumKickDirectionAngle = Selectf(fAngle, fMinimumKickDirectionAngle, -fMinimumKickDirectionAngle);
float fForceKickDirectionAngle = ms_fForceKickDirectionAngle * DtoR;
if (pOtherPed->GetMotionData()->GetIsSprinting())
{
fForceKickDirectionAngle *= ms_fMinimumKickDirectionAngleSprintScaler;
}
// If we're pushing against the ragdolling ped within a certain smaller cone then randomly force the push to always be in one direction or the other
// This is to avoid thrashing between pushing one way on one frame and then the other way on the next frame
if (Abs(fAngle) < fForceKickDirectionAngle)
{
// Use the activation time as the seed so that for this activation we'll always push the ped in the same direction!
if (GetRagdollInst() != NULL && (GetRagdollInst()->GetActivationStartTime() % 2) == 0)
{
fMinimumKickDirectionAngle = -fMinimumKickDirectionAngle;
}
}
else if (fAngle < 0.0f)
{
fMinimumKickDirectionAngle = -fMinimumKickDirectionAngle;
}
vMyNormal = RotateAboutZAxis(vMyNormal, ScalarVFromF32(fMinimumKickDirectionAngle - fAngle));
impacts.SetMyNormal(vMyNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickPlowNormal", vMyNormal));
}
}
else
{
bDoKickPed = false;
}
}
}
if (bDoKickPed)
{
// Resolve impact depth slowly to avoid popping...
static bank_float s_fSlowPushMax = 0.01f;
float fAllowedPenetration = CPhysics::GetSimulator()->GetAllowedPenetration();
const phCollider* pMyCollider = impacts.GetMyCollider();
if (pMyCollider != NULL)
{
fAllowedPenetration += pMyCollider->GetExtraAllowedPenetration();
}
float fExcessPenetration = impacts.GetDepth() - fAllowedPenetration;
if (fExcessPenetration > s_fSlowPushMax)
{
impacts.SetDepth(fAllowedPenetration + s_fSlowPushMax);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickDepthMod(B)", impacts.GetDepth()));
}
}
}
}
}
}
}
if (!bDoKickPed)
{
impacts.DisableImpact();
nmEntityDebugf(this, "Disabling impact with ped as we're not allowed to be kicked");
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "KickDisabled", "KickCorpse"));
}
}
PF_GROUP(PedProcessPreComputeImpacts);
PF_PAGE(PedImpacts, "Ped ProcessPreComputeImpacts");
PF_LINK(PedImpacts, PedProcessPreComputeImpacts);
PF_TIMER(ImpactsTotal, PedProcessPreComputeImpacts);
PF_TIMER(ImpactsForRagdoll, PedProcessPreComputeImpacts);
PF_TIMER(ImpactsForMover, PedProcessPreComputeImpacts);
PF_TIMER(CapsuleProbeImpact, PedProcessPreComputeImpacts);
PF_TIMER(MainCapsuleImpact, PedProcessPreComputeImpacts);
void CPed::ProcessPreComputeImpacts(phContactIterator impacts)
{
#if __BANK
if(!sm_PreComputeImpacts)
return;
#endif
if (GetPedResetFlag(CPED_RESET_FLAG_TaskSkipProcessPreComputeImpacts))
{
return;
}
PF_START(ImpactsTotal);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
// Second surface impacts
if(GetUsingRagdoll())
{
ProcessSecondSurfaceImpacts(impacts,ms_ragdollSecondSurfaceConfig);
}
else if(GetIsSkiing())
{
ProcessSecondSurfaceImpacts(impacts,ms_skiSecondSurfaceConfig);
}
else
{
ProcessSecondSurfaceImpacts(impacts,ms_pedSecondSurfaceConfig);
}
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
if(GetUsingRagdoll())
{
PF_START(ImpactsForRagdoll);
ProcessPreComputeImpactsForRagdoll(impacts);
PF_STOP(ImpactsForRagdoll);
}
else
{
PF_START(ImpactsForMover);
ProcessPreComputeImpactsForMover(impacts);
PF_STOP(ImpactsForMover);
}
// deep surface processing
if (IsDead()==false && GetCurrentPhysicsInst()->IsInLevel() && CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex()))
{
float rangeSqr = VFXRANGE_DEEP_SURFACE_FOOT_SQR;
if(IsPlayer() && IsNetworkClone())
{
rangeSqr = VFXRANGE_DEEP_SURFACE_CLONE_PLAYER_FOOT_SQR;
}
TUNE_BOOL(ForceDeepSurfaceChecks, false); // Mostly temporary, until the scripts set the config flag.
if(ForceDeepSurfaceChecks || GetPedConfigFlag(CPED_CONFIG_FLAG_ForceDeepSurfaceCheck))
{
rangeSqr = (LARGE_FLOAT*LARGE_FLOAT); // Some large enough value to essentially always enable the deep surface checks.
}
m_deepSurfaceInfo.Process(m_vecGroundPos, Vec3V(V_Z_AXIS_WZERO), PGTAMATERIALMGR->UnpackMtlId(GetPackedGroundMaterialId()), rangeSqr);
}
else
{
m_deepSurfaceInfo.Init();
}
PF_STOP(ImpactsTotal);
}
bool IsHighGoundAcceleration(const CPed* pPed)
{
ScalarV scAccelMagSq = MagSquared(pPed->GetGroundAcceleration());
static ScalarV scAccelMagThreshold = ScalarVFromF32(rage::square(100.0f));
if (IsGreaterThanAll(scAccelMagSq, scAccelMagThreshold))
{
return true;
}
return false;
}
bool CanPedBeKnockedOverByVehicle(const CPed* pPed, const CVehicle* pVehicle, phContactIterator& impacts)
{
if (pPed == NULL || pVehicle == NULL)
{
return false;
}
//Keep track of whether the vehicle can knock over the player.
bool bCanKnockOver = true;
//Check if the player is standing on top of the vehicle.
CPhysical* pGroundPhysical = pPed->GetLastValidGroundPhysical() ? pPed->GetLastValidGroundPhysical() : pPed->GetGroundPhysical();
if ((pGroundPhysical == pVehicle) || (pPed->GetClimbPhysical(true) == pVehicle))
{
//Make sure the ped can stand on top of the vehicle.
Assert(pVehicle->GetVehicleModelInfo());
if (pVehicle->CanPedsStandOnTop())
{
//Do not allow the vehicle to knock the player over if the player is standing on top of it.
bCanKnockOver = false;
//Cause the ped to ragdoll when the ground acceleration is extreme.
if (!pVehicle->InheritsFromTrain() && !pVehicle->InheritsFromPlane() && IsHighGoundAcceleration(pPed))
{
//Return immediately. Do not go through the knock-over code, which is tuned for side impacts.
if (pPed->IsDead())
{
if (CTaskNMBehaviour::CanUseRagdoll(const_cast<CPed*>(pPed), RAGDOLL_TRIGGER_DIE))
{
return true;
}
}
else if (CTaskNMBehaviour::CanUseRagdoll(const_cast<CPed*>(pPed), RAGDOLL_TRIGGER_IMPACT_CAR, const_cast<CVehicle*>(pVehicle), 100.0f))
{
return true;
}
}
}
}
if (pPed->GetRagdollTimer() == fwTimer::GetTimeInMilliseconds() && pPed->GetRagdollInst() != NULL && pPed->GetRagdollInst()->GetLastImpactDamageEntity() == pVehicle &&
pPed->GetRagdollInst()->GetTimeSinceImpactDamage() < CCollisionEventScanner::RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT)
{
// If we just switched from using ragdoll this frame and we were damaged by this vehicle recently then don't allow activation
// We need to wait one frame after switching from ragdoll to animated so that our ground physical can be filled in by our capsule probe...
// Should the getup task set our ground physical? It does a bunch of probes looking for ground...
bCanKnockOver = false;
}
//Check if the vehicle is a boat.
if (pVehicle->InheritsFromBoat())
{
//Grab the boat.
const CBoat* pBoat = static_cast<const CBoat*>(pVehicle);
//Check if the in-water states do not match.
if (pBoat->m_BoatHandling.IsInWater() != pPed->GetIsInWater())
{
//Do not allow the boat to knock over the player if one is in the water and the other is not.
bCanKnockOver = false;
}
}
else if (pVehicle->InheritsFromSubmarine())
{
if (pVehicle->GetIsInWater())
{
if (!pPed->IsDead())
{
bCanKnockOver = false;
}
}
}
//Check if the vehicle is a train.
else if (pVehicle->InheritsFromTrain())
{
if(pPed->GetTrainRidingOn())
{
//Grab the train.
const CTrain *pTrainHit = static_cast<const CTrain*>(pVehicle);
if( (pPed->GetTrainRidingOn() == pTrainHit) || (pTrainHit->GetLinkedToBackward() == pPed->GetTrainRidingOn()) || (pTrainHit->GetLinkedToForward() == pPed->GetTrainRidingOn()) )
{
bCanKnockOver = false;
}
}
const fwAttachmentEntityExtension* pPedAttachExtension = pPed->GetAttachmentExtension();
//Check if the attach extension is valid.
if (pPedAttachExtension != NULL)
{
//Check if we are attached to a vehicle.
const CPhysical* pAttachParentPhysical = static_cast<CPhysical*>(pPedAttachExtension->GetAttachParentForced());
if (pAttachParentPhysical && pAttachParentPhysical->GetIsTypeVehicle())
{
//Check if we are attached to a train.
const CVehicle* pAttachParentVehicle = static_cast<const CVehicle*>(pAttachParentPhysical);
if (pAttachParentVehicle->InheritsFromTrain())
{
//Check if the train neighbors the train we collided with.
//We have some collision issues with trains placed very close to each other, due to the way attachments work.
//We want to ignore these collisions.
const CTrain* pAttachParentTrain = static_cast<const CTrain*>(pAttachParentVehicle);
if ((pVehicle == pAttachParentTrain->GetLinkedToBackward()) || pVehicle == pAttachParentTrain->GetLinkedToForward())
{
bCanKnockOver = false;
}
}
}
}
}
// If we're getting up and hit the underside of a vehicle then allow the ped to be knocked over!
else if( ( pVehicle->InheritsFromPlane() || pVehicle->IsTank() ) &&
pVehicle != pGroundPhysical &&
NetworkInterface::IsGameInProgress() &&
impacts.GetMyInstance() == pPed->GetRagdollInst() )
{
CTaskGetUp* pTaskGetUp = static_cast<CTaskGetUp*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_GET_UP));
if(pTaskGetUp != NULL && pTaskGetUp->GetState() != CTaskGetUp::State_Crawl)
{
Vec3V vMyNormal;
impacts.GetMyNormal(vMyNormal);
static float sfUprightThreshold = -0.9f;
static float sfDepthThreshold = 0.1f;
CPed::eDamagedBodyPart eBodyPart = static_cast<CPed::eDamagedBodyPart>(CPed::ConvertBoneToBodyPart(pPed->GetBoneTagFromRagdollComponent(impacts.GetMyComponent())));
if((vMyNormal.GetZf() <= sfUprightThreshold || (impacts.GetDepth() > sfDepthThreshold && (eBodyPart == CPed::DAMAGED_TORSO || eBodyPart == CPed::DAMAGED_HEAD))) &&
CTaskNMBehaviour::CanUseRagdoll(const_cast<CPed*>(pPed), RAGDOLL_TRIGGER_IMPACT_CAR, const_cast<CVehicle*>(pVehicle), 10000.0f))
{
return true;
}
}
}
//Check if the vehicle can knock over the player.
if (bCanKnockOver)
{
//Dead peds can always be run over
if (pPed->IsDead())
{
if (CTaskNMBehaviour::CanUseRagdoll(const_cast<CPed*>(pPed), RAGDOLL_TRIGGER_DIE))
{
return true;
}
}
else
{
Vec3V vMyNormal;
impacts.GetMyNormal(vMyNormal);
Vec3V vOtherPosition = impacts.GetOtherPosition();
int nOtherComponent = impacts.GetOtherComponent();
if(pVehicle->InheritsFromAutomobile())
{
const Vector3 vVel = pVehicle->GetVelocity();
float fVel = vVel.Mag2();
if(fVel < 1.0f)
{
const CCarDoor* pDoor = CCarEnterExit::GetCollidedDoorBonnetBootFromCollisionComponent(pVehicle, nOtherComponent);
if (pDoor)
{
return false;
}
}
}
float fForce = CTaskNMBehaviour::CalculateActivationForce(pPed, RAGDOLL_TRIGGER_IMPACT_CAR, pVehicle, &vOtherPosition, &vMyNormal, nOtherComponent);
if (impacts.GetMyInstance() == pPed->GetRagdollInst())
{
fForce *= pPed->GetRagdollInst()->ScaleImpulseByComponentMass(impacts.GetMyComponent(), 1.0f);
}
if (fForce > 0.0f && CTaskNMBehaviour::CanUseRagdoll(const_cast<CPed*>(pPed), RAGDOLL_TRIGGER_IMPACT_CAR, const_cast<CVehicle*>(pVehicle), fForce))
{
return true;
}
}
}
return false;
}
Vec3V_Out GetCorrectedGroundPosition(Vec3V_In originalPosition, const phInst& instance, int component)
{
Vec3V posV = originalPosition;
const phBound* bound = instance.GetArchetype()->GetBound();
const phBound* finalBound = bound;
if(bound->GetType() == phBound::COMPOSITE)
{
finalBound = static_cast<const phBoundComposite*>(bound)->GetBound(component);
}
if(finalBound && finalBound->GetType() == phBound::BVH)
{
posV.SetZ(Subtract(posV.GetZ(),finalBound->GetMarginV()));
}
return posV;
}
void CPed::ProcessPreComputeImpactsForMover(phContactIterator& impacts)
{
#if __BANK
if(!sm_PreComputeImpactsForMover)
return;
#endif
// Keep track of whether or not the ped is experiencing collisions that are sufficiently opposing (in aggregate)
bool bCoverageFirstImpactFound = false;
bool bPushedByObjectWhilstInCoverThisFrame = false;
Vec3V vCoveredLeft, vCoveredRight;
Vec3V vKinematicPedContactNormal, vNonKinematicContactNormal;
bool rearImpactComputed=false;
bool frontImpactComputed=false;
const bool bIsQuadruped = m_pCapsuleInfo->IsQuadruped();
const bool bIsStanding = GetIsStanding();
const bool bIsPlayer = IsPlayer();
if (bIsPlayer)
{
vKinematicPedContactNormal.ZeroComponents();
vNonKinematicContactNormal.ZeroComponents();
}
const bool bShouldFlattenNormals = m_pCapsuleInfo->ShouldFlattenCollisionNormals();
//Flatten normal limits
const ScalarV svMaxNormalFixed = ScalarVFromF32(m_pCapsuleInfo->GetMaxNormalZForFlatten());
const ScalarV svMaxNormalMoveable = ScalarVFromF32(m_pCapsuleInfo->GetMaxNormalZForFlatten_Movable());
const ScalarV svMinNormal = ScalarVFromF32(m_pCapsuleInfo->GetMinNormalZForFlatten());
// NOTE: Use the current physics inst matrix because the entity matrix isn't updated yet.
float fMinHeightForFlatten = m_pCapsuleInfo->GetMinHeightForFlatten();
if(GetPedResetFlag(CPED_RESET_FLAG_UseInteriorCapsuleSettings))
{
fMinHeightForFlatten += m_pCapsuleInfo->GetMinHeightForFlattenInteriorOffset();
}
const ScalarV svMinHeightForFlatten = ScalarVFromF32(GetCurrentPhysicsInst()->GetMatrix().GetCol3ConstRef().GetZf() - m_pCapsuleInfo->GetGroundToRootOffset() + fMinHeightForFlatten);
// Resetting this here, for lack of a better update function
fragInstNMGta* pRagdollInst = GetRagdollInst();
if (pRagdollInst)
pRagdollInst->SetAllowDeadPedActivationThisFrame(false);
SetPedResetFlag(CPED_RESET_FLAG_TouchingOverhang, false);
SetPedResetFlag(CPED_RESET_FLAG_TooSteepForPlayer, false);
const ScalarV zeroV(V_ZERO);
const ScalarV negHalfV(V_NEGHALF);
if(impacts.GetMyInstance() == GetAnimatedInst())
{
m_vecMaxGroundNormal.ZeroComponents();
m_vecMinOverhangNormal = Vec3V(zeroV, zeroV, negHalfV);
// m_vecMinOverhangNormal.Set(0.0f, 0.0f, -0.5f); //TMS: Make sure there is some tolerance to
}
// We will keep track of whether any of the contacts involve a vehicle, where our
// instance is what GetAnimatedInst() returns.
bool mayHaveVehicleContactWithAnimatedInst = false;
bool bKickCounterImpulseAppliedThisFrame = false;
impacts.ResetToFirstActiveContact();
#if __PPU
phManifold* pManifoldWithRemovedContact = NULL;
phManifold* pRootManifoldWithRemovedContact = NULL;
#endif // __PPU
while(!impacts.AtEnd())
{
PDR_ONLY(debugPlayback::RecordContact(impacts));
// Currently filters out constraints and glass
int otherInstClassType;
if(DoKeepImpactForMover(impacts, otherInstClassType))
{
impacts.NextActiveContact();
continue;
}
if(CanDisableImpactForMover(impacts))
{
if (impacts.GetMyComponent()!=PED_USE_SHAPETEST_CAPSULE)
{
if(bIsPlayer)
{
GetPedAudioEntity()->HandlePreComputeContact(impacts);
}
}
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "DisabledForMover", "true"));
impacts.NextActiveContact();
continue;
}
// Instead of getting these like this:
// phInst* pOtherInstance = impacts.GetOtherInstance();
// const int iComp = impacts.GetMyComponent();
// impacts.GetMyNormal(vMyNormal);
// phMaterialMgr::Id otherMaterialId = impacts.GetOtherMaterialId();
// we try to reduce branching and memory access by being more direct:
phInst* pMyInstance = impacts.GetMyInstance();
phManifold& rCachedManifold = impacts.GetCachedManifold();
phInst* pInstA = rCachedManifold.GetInstanceA();
const phContact& contact = impacts.GetContact();
phInst* pOtherInstance;
int iComp;
Vec3V vMyNormal;
phMaterialMgr::Id otherMaterialId;
if(pMyInstance == pInstA)
{
pOtherInstance = rCachedManifold.GetInstanceB();
iComp = rCachedManifold.GetComponentA();
vMyNormal = contact.GetWorldNormal();
otherMaterialId = contact.GetMaterialId(1);
}
else
{
pOtherInstance = pInstA;
iComp = rCachedManifold.GetComponentB();
vMyNormal = Negate(contact.GetWorldNormal());
otherMaterialId = contact.GetMaterialId(0);
}
// Do these always hold up? If so, we can use that to simplify the code below.
Assert(pOtherInstance);
Assert(!pOtherInstance || pOtherInstance->GetArchetype());
Assert(!pOtherInstance || CPhysics::GetLevel()->LegitLevelIndex(pOtherInstance->GetLevelIndex()));
// now only check for impacts with kinematic peds here, as the non kinematic contacts are handled in the second pass below
if(bIsPlayer)
{
CEntity* otherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
if(otherEntity && otherEntity->GetIsTypePed() && static_cast<CPed*>(otherEntity)->IsUsingKinematicPhysics())
{
CheckForPlayerBeingSquashedByKinematicPed(impacts, vKinematicPedContactNormal, vNonKinematicContactNormal);
}
}
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
CPed* pOtherPed = NULL;
CVehicle* pOtherVehicle = NULL;
if (pOtherEntity)
{
if(pOtherEntity->GetIsTypePed())
{
const bool bIsSwimming = GetIsSwimming();
pOtherPed = static_cast<CPed*>(pOtherEntity);
// Don't push peds out of cover
if (GetIsInCover() || GetPedResetFlag(CPED_RESET_FLAG_IsEnteringCover))
{
// Do not do this if the other ped is stuck, as it would have nowhere to go and probably get jammed through a wall
if(!pOtherPed->GetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions))
{
impacts.SetMassInvScales(0.0f, 1.0f);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(cover::ped)", "true"));
}
}
// We don't want swimming peds to be able to push each other around too much in water.
else if(bIsSwimming)
{
static const float sfSwimmingInvMassScalePedB = 0.7f;
impacts.SetMassInvScales(1.0f, sfSwimmingInvMassScalePedB);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InvMassScale(swimming)", "true"));
}
}
else if (pOtherEntity->GetIsTypeVehicle())
{
pOtherVehicle = static_cast<CVehicle*>(pOtherEntity);
if (impacts.GetMyInstance() == GetAnimatedInst())
{
// If we are potentially touching a vehicle, we may need the second loop over the contacts.
mayHaveVehicleContactWithAnimatedInst = true;
if (!pOtherVehicle->CanPedsStandOnTop() || IsLessThan(vMyNormal.GetZ(), ScalarV(V_HALF)).Getb())
{
Vec3V vVehicleVelocity(VECTOR3_TO_VEC3V(pOtherVehicle->GetLocalSpeed(VEC3V_TO_VECTOR3(impacts.GetOtherPosition()), true, impacts.GetOtherComponent())));
ScalarV fVehicleVelocityMag2 = MagSquared(vVehicleVelocity);
if(IsGreaterThanAll(fVehicleVelocityMag2, ScalarV(V_FLT_SMALL_6)) != 0)
{
// Use the relative velocity to determine impact velocity. The ped could be standing on the vehicle that is hitting them or
// both the ped and vehicle could be on moving ground - need to account for these cases
if(CTaskNMBehaviour::DoesGroundPhysicalMatch(GetGroundPhysical(), pOtherVehicle) || (GetGroundPhysical() != GetLastValidGroundPhysical() && CTaskNMBehaviour::DoesGroundPhysicalMatch(GetLastValidGroundPhysical(), pOtherVehicle) &&
((fwTimer::GetTimeInMilliseconds() - GetLastValidGroundPhysicalTime()) < static_cast<u32>(CTaskNMBehaviour::sm_Tunables.m_DurationRampDownCapsulePushedByVehicle * 1000.0f))))
{
ScalarV fVehicleVelocityMag = Sqrt(fVehicleVelocityMag2);
Vec3V vVehicleDirection(InvScale(vVehicleVelocity, fVehicleVelocityMag));
// Never want to counter more than the velocity of the vehicle itself
ScalarV fDotProduct = Clamp(Dot(GetGroundVelocityIntegrated(), vVehicleDirection), ScalarV(V_ZERO), 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
vVehicleVelocity = SubtractScaled(vVehicleVelocity, vVehicleDirection, fDotProduct);
}
else
{
vVehicleVelocity = Subtract(vVehicleVelocity, VECTOR3_TO_VEC3V(pOtherVehicle->GetReferenceFrameVelocity()));
}
float fDotVel = Dot(vMyNormal, vVehicleVelocity).Getf();
if(impacts.GetDepth() > CTaskNMBehaviour::sm_Tunables.m_MinContactDepthForContinuousPushActivation && fDotVel > CTaskNMBehaviour::sm_Tunables.m_VehicleMinSpeedForContinuousPushActivation)
{
SetPedResetFlag(CPED_RESET_FLAG_CapsuleBeingPushedByVehicle, true);
}
}
}
}
}
else if (pOtherInstance)
{
// When in cover we're going to allow a transition to NM if hit pretty hard
phCollider *pCollider = PHSIM->GetCollider(pOtherInstance);
if (ms_bTriggerNMInCover && pCollider && (GetIsInCover() || GetPedResetFlag(CPED_RESET_FLAG_IsEnteringCover)))
{
//Impulse through contact...
Vec3V vMyVel = VECTOR3_TO_VEC3V(GetVelocity());
Vec3V vOtherVelAtContact = pCollider->GetLocalVelocity(impacts.GetOtherPosition().GetIntrin128ConstRef());
if( ( ms_fMinObjectVelForKnockFromCover * ms_fMinObjectVelForKnockFromCover ) < MagSquared( vOtherVelAtContact - GetGroundVelocity() ).Getf() )
{
ScalarV vDotProduct = Dot(vMyNormal, vOtherVelAtContact - vMyVel);
if (IsGreaterThanAll(vDotProduct, ScalarV(V_ZERO)))
{
float otherColliderMass = pCollider->GetMass();
bool isGateHittingPedInCover = otherColliderMass > 64.0f &&
GetIsInCover() &&
pOtherEntity->GetIsTypeObject() &&
static_cast<CObject*>(pOtherEntity)->IsADoor();
float fImpulse = vDotProduct.Getf() * otherColliderMass;
float fImpulseLimit = GetMass();
if( isGateHittingPedInCover )
{
fImpulseLimit *= ms_fVelLimitForHitByGateKnockFromCover;
}
else
{
fImpulseLimit *= ms_fVelLimitForKnockFromCover;
}
if( fImpulse > fImpulseLimit &&
CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_IMPACT_OBJECT_COVER, pOtherEntity, (Dot(vOtherVelAtContact, vMyNormal)*ScalarV(pCollider->GetMass())).Getf()))
{
bPushedByObjectWhilstInCoverThisFrame = true;
if( m_PushedByObjectInCoverFrameCount > ms_iMinKnockFromCoverFrames ||
isGateHittingPedInCover )
{
//Trigger ragdoll
nmEntityDebugf(this, "Adding nm brace task: Heavy hit in cover. Object: %s(%p), myVelMag: %.3f, otherVelMag: %.3f, vDotProduct: %.3f, vImpulse: %.3f, GroundVelMag: %.3f", pOtherEntity? pOtherEntity->GetModelName() : "none", pOtherEntity, Mag(vMyVel).Getf(), Mag(vOtherVelAtContact).Getf(), vDotProduct.Getf(), fImpulse, Mag(GetGroundVelocity()).Getf());
Vector3 vPedVelocity(GetVelocity());
if (MagSquared(GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2())
{
vPedVelocity = VEC3V_TO_VECTOR3(GetGroundVelocityIntegrated());
}
CEventSwitch2NM event(10000, rage_new CTaskNMBrace(500, 10000, pOtherEntity, CTaskNMBrace::BRACE_DEFAULT, vPedVelocity));
if (GetPedIntelligence()->AddEvent(event) != NULL)
{
//Setting up Custom Idle for after recovery (B* 810427)
//Possibly data drive this in the future if these become more prevalent
static fwMvClipSetId s_HitByCarSet("reaction@shake_it_off",0xC6AE9A3E);
static fwMvClipId s_HitByCarClip("dustoff",0xC9A0759E);
GetMotionData()->RequestCustomIdle(s_HitByCarSet, s_HitByCarClip);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "NMFromCover"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
}
}
}
if(iComp!=PED_USE_CAPSULE_PROBES)
{
if(bIsPlayer)
{
GetPedAudioEntity()->HandlePreComputeContact(impacts);
}
}
if (iComp!=PED_USE_SHAPETEST_CAPSULE)
{
phBound *pBound = pOtherInstance->GetArchetype()->GetBound();
if (pBound->GetType() == phBound::SPHERE)
{
float fRadius = pBound->GetRadiusAroundCentroid();
if (fRadius < 0.3f)
{
//Special case 'kick' code for smallish, lightish balls
if(pCollider)
{
const float kMaxWeightToKick = 20.0f;
float fBallMass = pCollider->GetMass();
if (fBallMass<kMaxWeightToKick)
{
Vec3V vMyVel = RCC_VEC3V( GetVelocity() );
const Vec3V vMultOutZ(1.0f, 1.0f, 0.0f);
ScalarV vVelMag = Mag(vMyVel*vMultOutZ);
Vec3V vKickDir;
//"Kick" ball in direction we are moving, rather than through the offset (might be a better stick direction than velocity...)
//This is to allow it to get out of corners it might get jammed into
if (IsGreaterThanAll(vVelMag, ScalarV(V_ZERO)))
{
vKickDir = vMyVel;
}
else
{
vKickDir = pOtherInstance->GetMatrix().GetCol3() - GetTransform().GetPosition();
}
vKickDir = NormalizeSafe(vKickDir * vMultOutZ, Vec3V(V_ZERO));
const ScalarV vMyVelThroughContact = Dot(vKickDir, vMyVel);
const ScalarV vBallVelThroughContact = Dot(vKickDir, pCollider->GetVelocity());
const ScalarV vMinVelToPushBallOut(0.1f); //This is applied to keep pushing the ball out of contact
ScalarV vKickAheadScale = ScalarV(V_ONE) + Clamp(vVelMag * ScalarV(0.2f), ScalarV(V_ZERO), ScalarV(0.5f));
ScalarV vKickVelocity = ((vMyVelThroughContact*vKickAheadScale) - vBallVelThroughContact) + vMinVelToPushBallOut;
if (IsGreaterThanAll(vKickVelocity, ScalarV(V_ZERO)))
{
vKickVelocity = Min(vKickVelocity, ScalarV(12.0f)); //Quick sanity clamp
Vec3V vForce = vKickDir * vKickVelocity * ScalarV(pCollider->GetMass());
pCollider->ApplyImpulseCenterOfMass(vForce.GetIntrin128ConstRef());
}
//And don't stand on it...
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "!!BallToKick!!"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
}
}
}
}
if (pMyInstance == GetAnimatedInst())
{
if(pOtherEntity && ShouldObjectBeImpossibleToPush(*pOtherEntity,*pOtherInstance, impacts.GetOtherCollider(), impacts.GetOtherComponent()))
{
impacts.SetMassInvScales(1.0f,0.05f);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "SetMassInvScales(1.0f,0.05f)", "Object impossible to push"));
}
if(IsFragInstClassType(otherInstClassType))
{
// If the other object is a fragment, check if the group we're colliding with doesn't allow movement from peds
const fragInst* pOtherFragInst = static_cast<const fragInst*>(pOtherInstance);
const fragPhysicsLOD* pOtherPhysicsLOD = pOtherFragInst->GetTypePhysics();
int otherComponent = impacts.GetOtherComponent();
if(Verifyf(otherComponent < pOtherPhysicsLOD->GetNumChildren(), "Invalid child index %i on manifold with '%s'. Num Children: %i",otherComponent,pOtherFragInst->GetArchetype()->GetFilename(),pOtherPhysicsLOD->GetNumChildren()))
{
const fragTypeChild* pOtherChild = pOtherPhysicsLOD->GetChild(otherComponent);
const fragTypeGroup* pOtherGroup = pOtherPhysicsLOD->GetGroup(pOtherChild->GetOwnerGroupPointerIndex());
float pedInvMassScale = pOtherGroup->GetPedInvMassScale();
if(pedInvMassScale < 1.0f)
{
Assertf(pedInvMassScale >= 0, "Trying to use a negative ped inverse mass scale (%f)", pedInvMassScale);
impacts.SetMassInvScales(1.0f,pedInvMassScale);
#if PDR_ENABLED
if (rCachedManifold.GetInstanceA() == pOtherInstance)
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(boat[A])", pedInvMassScale));
}
else
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(boat[B])", pedInvMassScale));
}
#endif // PDR_ENABLED
}
}
}
//Is this a downwards contact? - Store this as we may want to stop the player getting even more squashed.
if (IsLessThanAll(vMyNormal.GetZ(), m_vecMinOverhangNormal.GetZ()))
{
//Only allow fixed objects, or dynamic objects larger than this ped act as overhangs
bool bDoOverhang = true;
if (!PHLEVEL->IsFixed(pOtherInstance->GetLevelIndex()))
{
float fOtherInstanceRadius = pOtherInstance->GetArchetype()->GetBound()->GetRadiusAroundCentroid();
if (fOtherInstanceRadius*2.0f > GetCurrentPhysicsInst()->GetArchetype()->GetBound()->GetRadiusAroundCentroid())
{
bDoOverhang = false;
}
}
#if PED_USE_CAPSULE_PROBES
// When we swim or crawl the main capsule tilts, allowing the ground probe to pick up ground contacts above the capsule.
// I think the ideal check would be to make sure the contact position is on the bottom of the capsule probe but looking that up is kinda expensive.
if (iComp == PED_USE_CAPSULE_PROBES && m_fCurrentBoundPitch != 0.0f)
{
const Vec3V vecOtherPosition = GetCorrectedGroundPosition(impacts.GetOtherPosition(), *pOtherInstance, impacts.GetOtherComponent());
if (IsGreaterThanAll(vecOtherPosition.GetZ(), GetTransform().GetPosition().GetZ()))
{
bDoOverhang = false;
}
}
#endif
//Also filter out dodgy collisions on the side of the mover
if (bDoOverhang)
{
const float kInsetCollisionsForOverhang = 0.5f;
float fFlatDist = DistXY(impacts.GetMyPosition(), GetMatrix().GetCol3()).Getf();
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), fFlatDist, "OverhangFlatDist"));
const float kfOVerhangFlatDistanceLimit = GetCapsuleInfo()->GetHalfWidth() * kInsetCollisionsForOverhang;
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), kfOVerhangFlatDistanceLimit, "OverhangFlatDistLimit"));
if (fFlatDist < kfOVerhangFlatDistanceLimit)
{
m_vecMinOverhangNormal = vMyNormal;
SetPedResetFlag(CPED_RESET_FLAG_TouchingOverhang, true);
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), 1.0f, "TouchingOverhang"));
}
}
}
if(bShouldFlattenNormals)
{
ScalarV svAdjustedMinNormal = svMinNormal;
if(pOtherVehicle)
{
const bool bIsSwimming = GetIsSwimming();
//! DMKH. Hack. We don't want boats pushing us down as it makes it bloody difficult to climb on them!
//! If normal is pointing down, flatten it.
if(bIsSwimming && pOtherVehicle->GetIsAquatic())
{
CTaskMotionBase* pMotionTask = GetCurrentMotionTask();
if(pMotionTask && !pMotionTask->IsUnderWater())
{
if(IsLessThanAll(vMyNormal.GetZ(), ScalarV(V_ZERO)))
{
vMyNormal.SetZ(ScalarV(V_ZERO));
ScalarV fFlatMag2 = MagSquared(vMyNormal);
if (IsGreaterThanAll(fFlatMag2, ScalarV(V_FLT_SMALL_6)))
{
vMyNormal = Normalize(vMyNormal);
impacts.SetMyNormal(vMyNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "BoatNormalMod", vMyNormal));
}
}
}
}
//! sconde. Hack. We don't want submerged bikes pushing us up as it makes us stand on them which doesn't look very good!
//! If normal is pointing up, flatten it.
else if(pOtherVehicle->InheritsFromBike() && (bIsSwimming || static_cast<CBike*>(pOtherVehicle)->IsUnderwater()))
{
if(IsGreaterThanAll(vMyNormal.GetZ(), ScalarV(V_ZERO)))
{
vMyNormal.SetZ(ScalarV(V_ZERO));
ScalarV fFlatMag2 = MagSquared(vMyNormal);
if (IsGreaterThanAll(fFlatMag2, ScalarV(V_FLT_SMALL_6)))
{
vMyNormal = Normalize(vMyNormal);
impacts.SetMyNormal(vMyNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "BikeNormalMod", vMyNormal));
}
else
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Bike"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
else if(pOtherVehicle->InheritsFromAutomobile()) //B*2509709 For cars with Hydraulics increase the min normal limit so players dont pop through
{
CAutomobile *pAutomobile = static_cast<CAutomobile*>(pOtherVehicle);
if(pAutomobile->HasHydraulicSuspension())
{
static const float sfSAdjustedMinNormalForCarsWithHydraulics = -0.85f;
svAdjustedMinNormal = ScalarV(sfSAdjustedMinNormalForCarsWithHydraulics);
}
}
}
//Move impact to remove torque from the blocker bound
// Do not clamp normals unless we have valid ground
// - Changing the normal by large amounts makes the solver incorrect by enough for peds to fall through the world given sufficient motion
// TODO -- Think about removing clamping entirely or in part. What issues require it? Are there reasonable/better solutions?
// --- We've taken another step and got the main thing we wanted, but still cannot remove flattening yet (See the ped flattening below the quad code)
if (bIsStanding)
{
// Even if we ever remove the ped flattening - my testing indicated that we *need* this quad verticalizing for horses to be able
// to climb stairs without jittering and stutter-stepping horridly. Do NOT delete this section without first testing out horses!
if(IsGreaterThanAll(vMyNormal.GetZ(), ScalarV(-0.2f)) && //TMS: Make sure stairs contacts are somewhat upwards
(PGTAMATERIALMGR->GetPolyFlagStairs(otherMaterialId) || CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInstance->GetLevelIndex(), ArchetypeFlags::GTA_STAIR_SLOPE_TYPE)))
{
//On stairs, verticalize normals for quadrupeds
if(ms_bAllowNormalVerticalizing && bIsQuadruped)
{
//Always force a distinct upwards component! This avoids contacts with stairs that block forwards motion
//TMS: Still not sure why everything is inverted.
Vec3V vOtherNormal;
impacts.GetOtherNormal(vOtherNormal);
if(IsGreaterThanAll(vOtherNormal.GetZ(), ScalarVFromF32(-ms_fNormalVerticalizingFactor)))
{
vOtherNormal.SetZ(ScalarVFromF32(-ms_fNormalVerticalizingFactor));
vOtherNormal = Normalize(vOtherNormal);
impacts.SetOtherNormal(vOtherNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Verticalized", vOtherNormal));
}
}
}
// Still would very much like to remove all the flattening for peds here, but we are able to unnaturally scale active shapes
// like car hoods in a very odd looking way.
// -- Possibly we could do the flattening from the car's side? (This assumes other objects do not have similar odd behavior, but they do...)
// The main thing we wanted was to remove the possibility for tunneling through concavely configured BVH polys via the
// effect of flattening on the solver, and we got that.
else if(ms_bAllowNormalFlattening && (!ms_bOnlyFlattenMovable || PHLEVEL->IsFixed(pOtherInstance->GetLevelIndex())))
{
//[iComp==0 || (bIsQuadruped && iComp>=3)] == Main bound plus extra bounds for quadrupeds...
//not too pretty way of figuring this out though, flags would be neater!
if (iComp==0 || (bIsQuadruped && iComp>=3))
{
//Check normal limits
bool isOtherInstFixed = PHLEVEL->IsFixed(pOtherInstance->GetLevelIndex());
const ScalarV svMaxNormal = isOtherInstFixed ? svMaxNormalFixed : svMaxNormalMoveable;
Vec3V vPos = impacts.GetMyPosition();
const ScalarV myNormalZV = vMyNormal.GetZ();
const BoolV normalLessThanOrEqualV = IsLessThanOrEqual(myNormalZV, svMaxNormal);
const BoolV normalGreaterThanOrEqualV = IsGreaterThanOrEqual(myNormalZV, svAdjustedMinNormal);
const BoolV normalInRangeV = And(normalLessThanOrEqualV, normalGreaterThanOrEqualV);
//Check height offset
const BoolV greaterThanMinHeightForFlatten = IsGreaterThanOrEqual(vPos.GetZ(), svMinHeightForFlatten);
const BoolV shouldFlattenThisNormalV = And(normalInRangeV, greaterThanMinHeightForFlatten);
if (IsTrue(shouldFlattenThisNormalV))
{
#if __ASSERT
ScalarV fOrigNormalMag = Mag(vMyNormal);
#endif //__ASSERT
// Limit how much we change the sine of the normal's angle with the xy-plane. If the depth is under ms_bNormalFlatteningMinDepth
// then fully flatten the normal, otherwise remove between 45-90 degrees from the angle depending on the depth. This ensures that
// small angles get fully flattened regardless of depth.
if(isOtherInstFixed ||
( pOtherVehicle &&
pOtherVehicle->InheritsFromPlane() &&
pOtherVehicle->GetVelocity().Mag2() < 1.0f ) )
{
const ScalarV svMaxDepthForFlatten = ScalarVFromF32(ms_bNormalFlatteningMaxDepth);
const ScalarV svMinDepthForFlatten = ScalarVFromF32(ms_bNormalFlatteningMinDepth);
const ScalarV svSmallestMaxFlattening = ScalarVFromF32(0.707f); // ~45 degrees
const ScalarV maxFlattening = Clamp(Range(impacts.GetDepthV(),svMaxDepthForFlatten,svMinDepthForFlatten),svSmallestMaxFlattening,ScalarV(V_ONE));
const ScalarV newNormalZV = Subtract(myNormalZV,Clamp(myNormalZV,-maxFlattening,maxFlattening));
vMyNormal.SetZ(newNormalZV);
}
else
{
vMyNormal.SetZ(ScalarV(V_ZERO));
}
vMyNormal = NormalizeSafe(vMyNormal, Vec3V(V_X_AXIS_WONE));
#if __ASSERT
// Check the normal mag.
ScalarV fNormalMag = Mag(vMyNormal);
physicsAssertf(fNormalMag.Getf()>=0.97f && fNormalMag.Getf()<=1.03f,
"the contact normal is not normalised (%f) after being changed, it was (%f) before",
fNormalMag.Getf(), fOrigNormalMag.Getf());
#endif //__ASSERT
impacts.SetMyNormal(vMyNormal);
vPos.SetZ(impacts.GetMyInstance()->GetCenterOfMass().GetZ());
impacts.SetMyPositionDoNotUse(vPos);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Flattened(n)", vMyNormal));
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Flattened(p)", vPos, true));
}
}
}
}
}
if (pOtherVehicle && GetPedResetFlag(CPED_RESET_FLAG_DisableVehicleImpacts))
{
// Note whether deep vehicle impacts are occurring
static float fDeepImpact = 0.2f;
if (impacts.GetDepth() > fDeepImpact)
{
SetPedResetFlag( CPED_RESET_FLAG_DeepVehicleImpacts, true );
}
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
// Special handling for animated peds without (some) capsule control
if ((iComp == 0
#if PED_USE_EXTRA_BOUND
|| iComp == PED_USE_EXTRA_BOUND
#endif
) && GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsuleControl)) // Main capsule impact
{
// Decrease the friction with vehicles in an effort to get the peds to slide off
if (pOtherVehicle)
{
static float f1 = 0.0f;
impacts.SetFriction(f1);
}
else
{
// Otherwise increase the capsule friction a bit
impacts.SetFriction(Max(impacts.GetFriction(), 1.0f));
}
}
// If we're kinematic, treat all capsule collisions as if we have infinite mass
if (IsUsingKinematicPhysics())
{
// If we get into a kinematic vs. kinematic collision don't modify the mass scales
if(!pOtherEntity || !pOtherEntity->GetIsPhysical() || !static_cast<const CPhysical*>(pOtherEntity)->IsUsingKinematicPhysics())
{
//currently we disable kinematic behaviour on peds versus vehicles
if (!pOtherVehicle && (!m_pCoverPoint || pOtherEntity != m_pCoverPoint->GetEntity()))
{
impacts.SetMassInvScales(0.0f,1.0f);
#if PDR_ENABLED
if (rCachedManifold.GetInstanceA() == pOtherInstance)
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(kinematic[A])", 0.0f));
}
else
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(kinematic[B])", 0.0f));
}
#endif // PDR_ENABLED
}
}
}
//Check if the main capsule collided.
if(iComp == CBaseCapsuleInfo::BOUND_MAIN_CAPSULE)
{
//Check if the ped is parachuting.
if(GetPedResetFlag(CPED_RESET_FLAG_IsParachuting))
{
//Check if the ped is attached.
if(GetIsAttached())
{
//Check if the task is valid.
CTaskParachute* pTaskParachute = static_cast<CTaskParachute *>(
GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PARACHUTE));
if(pTaskParachute)
{
//Process the impact.
Vec3V vCollisionNormal;
impacts.GetMyNormal(vCollisionNormal);
pTaskParachute->ProcessPedImpact(pOtherEntity, vCollisionNormal);
}
}
}
}
PPU_ONLY(bool bRemovedContact = false;)
switch(iComp)
{
case 0: // Main capsule impact
{
PPU_ONLY(bRemovedContact =) ProcessPreComputeMainCapsuleImpact(impacts, bCoverageFirstImpactFound, vCoveredLeft, vCoveredRight);
break;
}
#if PED_USE_CAPSULE_PROBES
case PED_USE_CAPSULE_PROBES:
PPU_ONLY(bRemovedContact =) ProcessPreComputeCapsuleProbeImpact(impacts, frontImpactComputed);
break;
#endif // PED_USE_CAPSULE_PROBES
case PED_COW_HEAD_BOUND: // Head capsule collision for the cow
{
//Check it's a player, and it's the A_C_Cow model
if (GetPedType() == PEDTYPE_ANIMAL && IsAPlayerPed() && GetModelIndex() == MI_PED_ANIMAL_COW)
{
PPU_ONLY(bRemovedContact = ) ProcessPreComputeMainCapsuleImpact(impacts, bCoverageFirstImpactFound, vCoveredLeft, vCoveredRight);
break;
}
}
default:
{
if (bIsQuadruped)
{
#if ENABLE_HORSE
if(GetHorseComponent())
{
if (iComp == CBipedCapsuleInfo::BOUND_CAPSULE_PROBE_2)
{
ProcessPreComputeHorseLowerLegImpact(impacts);
}
if(iComp == static_cast<const CQuadrupedCapsuleInfo*>(m_pCapsuleInfo)->GetPropBlockerSlot())
{
ProcessPreComputeHorsePropBlockerImpact(impacts);
}
}
else
#endif
{
if (!pOtherInstance || !CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInstance->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE))
{
if (iComp == CBipedCapsuleInfo::BOUND_CAPSULE_PROBE_2)
{
ProcessPreComputeQuadrupedLowerLegImpact(impacts, rearImpactComputed);
}
}
}
}
#if PED_USE_EXTRA_BOUND
else if (iComp == PED_USE_EXTRA_BOUND)
{
FastAssert(impacts.GetMyInstance() != NULL && impacts.GetMyInstance()->GetArchetype() != NULL &&
impacts.GetMyInstance()->GetArchetype()->GetBound() != NULL && impacts.GetMyInstance()->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE &&
static_cast<phBoundComposite*>(impacts.GetMyInstance()->GetArchetype()->GetBound())->GetTypeAndIncludeFlags() != NULL);
// If our extra bound is not using the same include flags as our archetype then it is being used as a boat blocker!
if (static_cast<phBoundComposite*>(impacts.GetMyInstance()->GetArchetype()->GetBound())->GetIncludeFlags(iComp) != impacts.GetMyInstance()->GetArchetype()->GetIncludeFlags() &&
(!GetIsInWater() || pOtherEntity == NULL || !pOtherEntity->GetIsTypeVehicle() || !static_cast<CVehicle*>(pOtherEntity)->GetIsAquatic()))
{
impacts.DisableImpact();
}
}
#endif // PED_USE_EXTRA_BOUND
#if PLAYER_USE_LOWER_LEG_BOUND
else if (iComp == PLAYER_USE_LOWER_LEG_BOUND)
{
if (!pOtherInstance || !CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInstance->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE))
{
ProcessPreComputePlayerLowerLegImpact(impacts);
}
}
#endif // PLAYER_USE_LOWER_LEG_BOUND
}
}
#if __PPU
if (bRemovedContact)
{
phManifold* pNextManifoldWithRemovedContact = &impacts.GetCachedManifold();
if (pManifoldWithRemovedContact && pManifoldWithRemovedContact != pNextManifoldWithRemovedContact)
{
pManifoldWithRemovedContact->RegenerateDmaPlan();
}
pManifoldWithRemovedContact = pNextManifoldWithRemovedContact;
phManifold* pNextRootManifoldWithRemovedContact = &impacts.GetRootManifold();
if (pRootManifoldWithRemovedContact && pRootManifoldWithRemovedContact != pNextRootManifoldWithRemovedContact)
{
pRootManifoldWithRemovedContact->RegenerateDmaPlan();
}
pRootManifoldWithRemovedContact = pNextRootManifoldWithRemovedContact;
}
#endif // __PPU
// !!! ProcessPreComputeMainCapsuleImpact can delete impacts which has the side effect of decrementing the impacts iterator
// !!! DO NOT put any impacts processing between that call and "impacts.NextActiveContact();" below
}
else if (impacts.GetMyInstance() == GetRagdollInst())
{
if (pOtherPed != NULL)
{
bool bOtherPedSyncedScene = false;
if (pOtherPed->GetAnimDirector() != NULL)
{
fwAnimDirectorComponentSyncedScene* pComponentSyncedScene = pOtherPed->GetAnimDirector()->GetComponent<fwAnimDirectorComponentSyncedScene>();
if (pComponentSyncedScene != NULL && pComponentSyncedScene->IsPlayingSyncedScene())
{
bOtherPedSyncedScene = true;
}
}
if (!bOtherPedSyncedScene && !pOtherPed->GetUsingRagdoll() && (pOtherPed->GetDeathState() != DeathState_Dead) && !GetPedConfigFlag(CPED_CONFIG_FLAG_IsBeingDraggedToSafety) && !GetIsSwimming())
{
// Apply a counter-impulse to the player when kicking ragdolls!
// Don't apply while in cover as it causes the player to look jerky
bool bValidGround = pOtherPed->ValidateGroundPhysical(*impacts.GetMyInstance(), impacts.GetMyPosition(), impacts.GetMyComponent());
if(!bValidGround && !bKickCounterImpulseAppliedThisFrame && !(pOtherPed->GetIsInCover() || pOtherPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringCover)))
{
Vector3 vImpulse;
impacts.GetOtherImpulse(vImpulse);
if (vImpulse.XYMag2() > SMALL_FLOAT)
{
Vector3 vVelocityDirection = pOtherPed->GetVelocity();
vVelocityDirection.z = 0.0f;
if (vVelocityDirection.Mag2() > SMALL_FLOAT)
{
vVelocityDirection.Normalize();
float fCounterImpulseStrength = ms_CounterRagdollImpulseStrength;
if (IsDead())
{
fCounterImpulseStrength *= ms_DeadPedCounterRagdollImpulseStrengthModifier;
}
pOtherPed->ApplyImpulse(-vVelocityDirection * fCounterImpulseStrength * vImpulse.XYMag(), VEC3_ZERO, 0);
bKickCounterImpulseAppliedThisFrame = true;
}
}
}
DoKickPed(impacts, bValidGround);
}
// Allow contacts underneath a frozen dead ped from a ragdoll to activate it
if (IsDead() && pOtherPed->GetUsingRagdoll())
{
float fAllowedPenetration = CPhysics::GetSimulator()->GetAllowedPenetration();
const phCollider* pOtherCollider = impacts.GetOtherCollider();
if (pOtherCollider != NULL)
{
fAllowedPenetration += pOtherCollider->GetExtraAllowedPenetration();
}
if (impacts.GetDepth() >= fAllowedPenetration)
{
Vector3 vNormal(Vector3::ZeroType);
impacts.GetOtherNormal(RC_VEC3V(vNormal));
static bank_float sfUnderneathDotThreshold = 0.75f;
if (vNormal.z < -sfUnderneathDotThreshold)
{
GetRagdollInst()->SetAllowDeadPedActivationThisFrame(true);
if (pOtherPed->GetTimeOfFirstBeingUnderAnotherRagdoll() == 0)
{
pOtherPed->SetTimeOfFirstBeingUnderAnotherRagdoll(fwTimer::GetTimeInMilliseconds()); // This info will be used to de-prioritize removal of corpses that have been under another ragdoll
}
}
else if (vNormal.z > sfUnderneathDotThreshold)
{
if (GetTimeOfFirstBeingUnderAnotherRagdoll() == 0)
{
SetTimeOfFirstBeingUnderAnotherRagdoll(fwTimer::GetTimeInMilliseconds()); // This info will be used to de-prioritize removal of corpses that have been under another ragdoll
}
}
}
}
}
// Filter out contacts between a pickup (intended usage is a dropped weapon) and a live ped's arms, head, feet and chest since otherwise bad collision situations can occur
if(GetDeathState() != DeathState_Dead && pOtherInstance && CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInstance->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE))
{
int myComponent = impacts.GetMyComponent();
if (myComponent >= RAGDOLL_SPINE1 || myComponent == RAGDOLL_FOOT_LEFT || myComponent == RAGDOLL_FOOT_RIGHT)
{
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
if( m_forcePedHasNoMassInImpacts &&
( !pOtherEntity ||
!pOtherEntity->GetIsTypePed() ) )
{
if( !pOtherEntity ||
!pOtherEntity->GetIsTypeVehicle() ||
!static_cast< CVehicle* >( pOtherEntity )->GetDriver() ) // if we're hitting a vehicle with a driver don't override mass
{
impacts.SetMassInvScales(1.0f,0.0f);
}
}
impacts.NextActiveContact();
}
#if __PPU
if (pManifoldWithRemovedContact)
{
pManifoldWithRemovedContact->RegenerateDmaPlan();
}
if (pRootManifoldWithRemovedContact)
{
pRootManifoldWithRemovedContact->RegenerateDmaPlan();
}
#endif // __PPU
//Need to set infinite masses after the ground physical is found
const CPhysical *pPhysical = GetGroundPhysical();
// The second pass below basically has three cases when it may do something for one of the contacts:
// A. If the other instance is non-NULL and it's the same as pGroundInst, which implies that pGroundInst must be non-NULL.
// B. If the other entity is a vehicle.
// C. If the ped is a player and it is in contact with a kinematic ped.
// Thus, in the fairly common case of us not having a ground instance, not being in contact with a vehicle and we're not
// a player being pushed by a kinematic ped, as an optimization we don't have to do it since it couldn't possibly do anything.
ScalarV vNMAccumulateThisFrame(V_ZERO);
CVehicle* pNMAccumulateThisFrameVehicle=NULL;
Vec3V vNmAccumulateLastImpactPos(VEC3_ZERO);
if(pPhysical || mayHaveVehicleContactWithAnimatedInst
|| (bIsPlayer && GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed))
|| GetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions))
{
const phInst* pGroundInst = pPhysical ? pPhysical->GetCurrentPhysicsInst() : NULL;
impacts.ResetToFirstActiveContact();
while(!impacts.AtEnd())
{
CEntity* otherEntity = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
if (bIsPlayer && GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed) && !GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithSomethingOtherThanKinematicPed) &&
otherEntity)
{
CheckForPlayerBeingSquashedByKinematicPed(impacts, vKinematicPedContactNormal, vNonKinematicContactNormal);
}
if(GetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions))
{
// If we're constricted, undo any changes to the mass scale of the other object. We need to do this regardless of if we set the scales this frame
// since the mass scales could've been set on a previous frame and they don't get reset.
// Don't mess with the ped's mass inv scale in case it is kinematic.
impacts.SetOtherMassInvScale(1.0f);
}
phInst* pOtherInstance = impacts.GetOtherInstance();
if (pOtherInstance)
{
if (pOtherInstance == pGroundInst)
{
impacts.SetMassInvScales(1.0f,0.0f);
#if PDR_ENABLED
if (impacts.GetCachedManifold().GetInstanceA() == pOtherInstance)
{
//TMS: Can't find a good way to id the contacts...
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(ground)", "A"));
}
else
{
//TMS: Can't find a good way to id the contacts...
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "InfiniteMass(ground)", "B"));
}
#endif // PDR_ENABLED
}
else
{
if (impacts.GetMyInstance() == GetAnimatedInst())
{
if(!impacts.IsConstraint())
{
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
// Activate non-players that are colliding with a vehicle pressing down on them (that isn't their ground instance or driven by them)
if (pOtherEntity && pOtherEntity->GetIsTypeVehicle() && pOtherEntity != GetMyVehicle())
{
bool bEventAdded = false;
if (!IsPlayer())
{
CTaskGetUp* pTaskGetUp = static_cast<CTaskGetUp*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_GET_UP));
// If we're crawling underneath of this vehicle then don't let it crush us...
if (pTaskGetUp == NULL || (NetworkInterface::IsGameInProgress() && pTaskGetUp->GetState() != CTaskGetUp::State_Crawl) || pTaskGetUp->m_pBumpedByEntity != pOtherEntity)
{
Vector3 normal(VEC3_ZERO);
Vector3 up(0.0f, 0.0f, 1.0f);
impacts.GetOtherNormal(RC_VEC3V(normal));
static dev_float s_fUpThreshold = 0.85f;
static dev_float s_fDepthThreshold = 0.06f;
if (normal.Dot(up) > s_fUpThreshold && impacts.GetDepth() > s_fDepthThreshold && CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_IMPACT_CAR, pOtherEntity, 130.0f))
{
nmEntityDebugf(this, "Adding nm balance task: vehicle crushing ped.");
CEventSwitch2NM event(10000, rage_new CTaskNMBalance(500, 10000, NULL, 0));
if (GetPedIntelligence()->AddEvent(event) != NULL)
{
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "CanUseRagdoll0"));
bEventAdded = true;
}
}
}
}
Vec3V normal;
impacts.GetMyNormal(normal);
Vec3V carVel = VECTOR3_TO_VEC3V(((CVehicle*)pOtherEntity)->GetVelocity());
bool bIsClimbingLadder = (GetPedResetFlag(CPED_RESET_FLAG_IsClimbing) && GetPedIntelligence()->GetTaskClimbLadder());
const bool bShouldActivateRagdollDueToCollision = bIsClimbingLadder || !CanDisableImpactForEnterExitVehicle(pOtherEntity, pOtherInstance);
bool bUseSideSwipeActivations = IsFirstPersonShooterModeEnabledForPlayer(false) ? CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_EnableSideSwipeActivationsFirstPerson: CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_EnableSideSwipeActivations;
if (bShouldActivateRagdollDueToCollision && !bEventAdded && CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_EnableActivationsFromCapsuleImpacts)
{
if (Mag(carVel).Getf() < CTaskNMBrace::sm_Tunables.m_LowVelocityReactionThreshold || bIsClimbingLadder)
{
carVel = NormalizeSafe(carVel, Vec3V(V_ZERO));
ScalarV vDotVel = Dot(normal, carVel);
float fDotVel = vDotVel.Getf();
Vec3V vOtherPosition = impacts.GetOtherPosition();
int nOtherComponent = impacts.GetOtherComponent();
float fForce = CTaskNMBehaviour::CalculateActivationForce(this, RAGDOLL_TRIGGER_IMPACT_CAR_CAPSULE, static_cast<CVehicle*>(pOtherEntity), &vOtherPosition, &normal, nOtherComponent);
if ((fDotVel > CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_VehicleVelToImpactNormalMinDot || bIsClimbingLadder) && fForce > 0.0f && CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_IMPACT_CAR_CAPSULE, pOtherEntity, fForce))
{
nmEntityDebugf(this, "Adding nm brace task: Vehicle collided with ped capsule.");
Vector3 vPedVelocity(GetVelocity());
if (MagSquared(GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2())
{
vPedVelocity = VEC3V_TO_VECTOR3(GetGroundVelocityIntegrated());
}
CEventSwitch2NM event(10000, rage_new CTaskNMBrace(500, 10000, pOtherEntity, CTaskNMBrace::BRACE_CAPSULE_HIT, vPedVelocity));
if (GetPedIntelligence()->AddEvent(event) != NULL)
{
bEventAdded = true;
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "CanUseRagdoll1"));
//Setting up Custom Idle for after recovery (B* 810427)
//Possibly data drive this in the future if these become more prevalent
static fwMvClipSetId s_HitByCarSet("reaction@shake_it_off",0xC6AE9A3E);
static fwMvClipId s_HitByCarClip("dustoff",0xC9A0759E);
GetMotionData()->RequestCustomIdle(s_HitByCarSet, s_HitByCarClip);
}
}
}
}
else if (bUseSideSwipeActivations)
{
phCollider *pOtherCollider = CPhysics::GetSimulator()->GetCollider(pOtherInstance);
if(pOtherCollider)
{
//Side forces only, don't want to interfere with the existing bonnet hits etc
const ScalarV vMaxSideNormalForSideNM(CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinSideNormalForSideSwipe);
const ScalarV vVehicleSideCompontentToNormal(Abs(Dot(normal, pOtherCollider->GetMatrix().GetCol0())));
if (IsGreaterThanAll(vVehicleSideCompontentToNormal, vMaxSideNormalForSideNM))
{
//Rough check to see if
Vec3V vMyAnimatedVel = VECTOR3_TO_VEC3V(GetAnimatedVelocity());
// Check relative velocity (in case this vehicle is on a train, for example)
Vec3V vOtherVelAtContact = Subtract(pOtherCollider->GetLocalVelocity(impacts.GetOtherPosition().GetIntrin128ConstRef()), VECTOR3_TO_VEC3V(static_cast<CVehicle*>(pOtherEntity)->GetReferenceFrameVelocity()));
ScalarV vOtherVelAtContactMag = Mag(vOtherVelAtContact);
ScalarV vMinOtherVelMag;
if (NetworkInterface::IsInCopsAndCrooks())
{
vMinOtherVelMag = ScalarV(((CVehicle*)pOtherEntity)->GetVehicleType()==VEHICLE_TYPE_BICYCLE ? CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinVehVelMagForBicycleSideSwipeCNC : CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinVehVelMagForSideSwipeCNC);
}
else
{
vMinOtherVelMag = ScalarV(((CVehicle*)pOtherEntity)->GetVehicleType()==VEHICLE_TYPE_BICYCLE ? CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinVehVelMagForBicycleSideSwipe : CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinVehVelMagForSideSwipe);
}
if (IsGreaterThanAll(vOtherVelAtContactMag, vMinOtherVelMag))
{
ScalarV vDotProduct = Abs(Dot(normal, vOtherVelAtContact - vMyAnimatedVel));
const ScalarV vVelThroughNormalForSideNM(CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinVelThroughNormalForSideSwipe);
if (IsGreaterThanAll(vDotProduct, vVelThroughNormalForSideNM))
{
vDotProduct *= Mag(vOtherVelAtContact) * TIME.GetSecondsV();
vNMAccumulateThisFrame = Max(vNMAccumulateThisFrame,vDotProduct);
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), vNMAccumulateThisFrame.Getf(), "NMAccumulateMax"));
pNMAccumulateThisFrameVehicle = static_cast<CVehicle*>(pOtherEntity);
vNmAccumulateLastImpactPos = impacts.GetMyPosition();
}
}
}
}
}
}
}
}
}
}
impacts.NextActiveContact();
}
}
//Did we hit the side of a car
float fAccumulate = vNMAccumulateThisFrame.Getf();
if (fAccumulate == 0.0f)
{
if(m_fNMAccumulate>0.0f)
{
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), 0.0f, "NMAccumulate"));
}
m_fNMAccumulate = 0.0f;
}
else
{
m_fNMAccumulate += fAccumulate;
float fMinAccumulatedImpactForSideSwipe = NetworkInterface::IsInCopsAndCrooks() ? CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinAccumulatedImpactForSideSwipeCNC : CTaskNMBehaviour::sm_Tunables.m_CapsuleVehicleHitTuning.m_MinAccumulatedImpactForSideSwipe;
if (m_fNMAccumulate >= fMinAccumulatedImpactForSideSwipe && CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_IMPACT_CAR_SIDE_SWIPE))
{
m_fNMAccumulate = 0.0f;
nmEntityDebugf(this, "CPed.ProcessPreComputeImpactsForMover - Adding CTaskNMBrace for accumulated side contacts");
Vector3 vPedVelocity(GetVelocity());
if (MagSquared(GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2())
{
vPedVelocity = VEC3V_TO_VECTOR3(GetGroundVelocityIntegrated());
}
CTaskNMBrace* pBraceTask = rage_new CTaskNMBrace(200, 2000, pNMAccumulateThisFrameVehicle, CTaskNMBrace::BRACE_SIDE_SWIPE, vPedVelocity);
pBraceTask->SetSideSwipeImpulsePos(vNmAccumulateLastImpactPos, this);
CEventSwitch2NM event(10000, pBraceTask);
GetPedIntelligence()->AddEvent(event);
}
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), m_fNMAccumulate, "NMAccumulate"));
}
// Recompute the ground physical using shapetests if we're colliding with a geometry bound
// Collision with geometry bounds looks at the convex hull of the vertices which can make
// it look like we're floating.
if(ms_bRecomputeGroundPhysicalOnVehicles && IsPlayer())
{
if(const CPhysical* groundPhysical = GetGroundPhysical())
{
if(groundPhysical->GetIsTypeVehicle())
{
if(const phInst* groundInst = groundPhysical->GetCurrentPhysicsInst())
{
const phBound* groundBound = groundInst->GetArchetype()->GetBound();
const phBound* collidingBound = groundBound;
if(groundBound->GetType() == phBound::COMPOSITE)
{
collidingBound = static_cast<const phBoundComposite*>(groundBound)->GetBound(GetGroundPhysicalComponent());
}
if(collidingBound && collidingBound->GetType() == phBound::GEOMETRY)
{
RecomputeGroundPhysical();
}
}
}
}
}
// GTAV - b*2429353 - This is a bit hacky.
// It is possible to get through walls by running into a narrowing gap between
// a very heavy vehicle and a wall
static float sf_MaxAllowedPenetrationDepth = 0.1f;
static float sf_VeryHeavyVehicleMass = 7500.0f;
// Only do this for the local player and if they are constricted by opposing collisions
if( sbForceRagDollIfPedForcedThroughWall &&
IsLocalPlayer() &&
GetPedConfigFlag( CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions ) )
{
bool constrictedByStationaryHeavyVehicle = false;
bool constrictedByFixedObject = false;
bool canRagdoll = true;
float maxPenetrationDepth = 0.0f;
// first we need to loop through all the contacts and make sure we are constricted by a heavy vehicle
// and a fixed object.
// If we are then we need to store the highest penetration depth
impacts.ResetToFirstActiveContact();
while(!impacts.AtEnd())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
if( pOtherInstance )
{
CEntity* pOtherEntity = CPhysics::GetEntityFromInst( pOtherInstance );
if( pOtherEntity &&
pOtherEntity->GetIsTypeVehicle() &&
static_cast<CVehicle*>(pOtherEntity)->GetMass() > sf_VeryHeavyVehicleMass ) //->IsTank() )
{
CVehicle* pVehicle = static_cast<CVehicle*>(pOtherEntity);
if( pVehicle->GetVelocity().Mag() < 1.0f )
{
constrictedByStationaryHeavyVehicle = true;
if( impacts.GetDepth() > maxPenetrationDepth )
{
maxPenetrationDepth = impacts.GetDepth();
}
if( pVehicle->GetModelIndex() == MI_PLANE_BOMBUSHKA || pVehicle->GetModelIndex() == MI_PLANE_VOLATOL )
{
canRagdoll = false;
}
}
}
else if( PHLEVEL->IsFixed( pOtherInstance->GetLevelIndex() ) )
{
constrictedByFixedObject = true;
if( impacts.GetDepth() > maxPenetrationDepth )
{
maxPenetrationDepth = impacts.GetDepth();
}
}
}
impacts.NextActiveContact();
}
if( constrictedByStationaryHeavyVehicle &&
constrictedByFixedObject )
{
// if the penetration depth is too high just force the ped to ragdoll
if( maxPenetrationDepth > sf_MaxAllowedPenetrationDepth &&
canRagdoll )
{
if( CTaskNMBehaviour::CanUseRagdoll( this, RAGDOLL_TRIGGER_IMPACT_CAR_SIDE_SWIPE ) )
{
m_fNMAccumulate = 0.0f;
Vector3 vPedVelocity( GetVelocity() );
if( MagSquared( GetGroundVelocityIntegrated() ).Getf() > vPedVelocity.Mag2() )
{
vPedVelocity = VEC3V_TO_VECTOR3( GetGroundVelocityIntegrated() );
}
CTaskNMBrace* pBraceTask = rage_new CTaskNMBrace( 200, 2000, pNMAccumulateThisFrameVehicle, CTaskNMBrace::BRACE_SIDE_SWIPE, vPedVelocity );
pBraceTask->SetSideSwipeImpulsePos( vNmAccumulateLastImpactPos, this );
CEventSwitch2NM event( 10000, pBraceTask );
GetPedIntelligence()->AddEvent(event);
}
}
else if( maxPenetrationDepth > ( sf_MaxAllowedPenetrationDepth * 0.5f ) )
{
Vector3 safeNormal = m_vDesiredVelocity;
safeNormal.NormalizeSafe( VEC3V_TO_VECTOR3( GetTransform().GetForward() ) );
impacts.ResetToFirstActiveContact();
PHSIM->GetContactMgr()->ResetWarmStartAllContactsWithInstance( impacts.GetMyInstance() );
while(!impacts.AtEnd())
{
impacts.SetMyNormal( -safeNormal );
impacts.SetOtherNormal( safeNormal );
impacts.NextActiveContact();
}
}
}
}
#if ENABLE_HORSE
if(!GetHorseComponent())
{
if (frontImpactComputed)
{
m_fGroundZFromImpact += m_fHeightFudgeRecoveryFactor[0];
}
if (rearImpactComputed)
{
m_fRearGroundZFromImpact += m_fHeightFudgeRecoveryFactor[1];
}
}
#endif
// If we didn't exprience a cover push this frame, clear the counter.
if (!bPushedByObjectWhilstInCoverThisFrame)
{
m_PushedByObjectInCoverFrameCount = 0;
}
else
{
m_PushedByObjectInCoverFrameCount++;
}
}
dev_s32 diMinLifetime = 1; // Because we actually move the positions now - this can never be larger doing so would simply allow the contact to be pushed infinitely far
bool CPed::ProcessPreComputeMainCapsuleImpact(phContactIterator &impacts, bool &bCoverageFirstImpactFound, Vec3V_InOut vCoveredLeft, Vec3V_InOut vCoveredRight)
{
#if __BANK
if(!sm_PreComputeMainCapsuleImpact)
return false;
#endif
PF_START(MainCapsuleImpact);
// We definitely shouldn't be dealing with constraints!
physicsAssert(!impacts.IsConstraint());
// !!! GTA V HACK !!!
// B*1918231: Allow high-velocity impacts on the ped capsule to activate the ragdoll directly if the ped is standing on a train and hits the train or a piece of fixed geometry.
// This is a bit of a hack to fix an issue where peds could stand on the front of a moving train, hit the lip of an oncoming tunnel and get pushed through either the train or
// the tunnel geometry all while being completely unharmed. The combination of big, heavy, kinematically moving train + immovable fixed tunnel geometry seems to prevent the solver
// from getting the ped completely out of the way in time so the ped will get stuck in the train slightly. This is further compounded by the various ProcessPreComputeImpacts routines
// (e.g. ped backface culling, disabling impacts for the mover).
if(BANK_ONLY(sm_EnableRagdollForHighVelImpactsWhileOnTrain &&) GetGroundPhysical() && GetGroundPhysical()->GetIsTypeVehicle())
{
CVehicle *pVeh = static_cast<CVehicle *>(GetGroundPhysical());
if(pVeh->InheritsFromTrain())
{
CEntity *pHitEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
if(pHitEnt && (pHitEnt == pVeh || pHitEnt->GetIsTypeBuilding()))
{
// Train is moving slightly faster than this when the bug in question occurs so this seems like a good threshold to test against.
if(impacts.GetRelVelocity().Mag2() > (10 * 10))
{
PHSIM->ActivateObject(m_pRagdollInst, NULL, impacts.GetOtherInstance());
}
}
}
}
// !!! GTA V HACK !!!
// Make small animals ragdoll as soon as they touch the wheel of a vehicle that is moving. Stops their tiny capsule from getting squashed and pushed under the map.
if(IsPlayer() && GetPedType() == PEDTYPE_ANIMAL && GetIsStanding())
{
CEntity *pHitEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
if(pHitEnt && pHitEnt->GetIsTypeVehicle())
{
CVehicle *pHitVeh = static_cast<CVehicle *>(pHitEnt);
if(pHitVeh->GetVelocity().Mag2() > 0.0f)
{
if(impacts.GetDepth() > 0.03f)
{
PHSIM->ActivateObject(m_pRagdollInst, NULL, impacts.GetOtherInstance());
impacts.DisableImpact();
}
}
}
}
// !!! GTA V HACK !!!
// Disable main capsule collision against vehicles that are underneath the vehicle we are entering.
if(GetPedResetFlag(CPED_RESET_FLAG_IsEnteringOrExitingVehicle) && GetIsAttachedInCar())
{
CEntity *pHitEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
if(pHitEnt && pHitEnt->GetIsTypeVehicle())
{
if(GetAttachParent() && ((CPhysical *) GetAttachParent())->GetIsTypeVehicle())
{
CVehicle* pVehicle = (CVehicle*)GetAttachParent();
//Check if the vehicle we are impacting is underneath the vehicle we are getting out of
for(int i = 0; i < pVehicle->GetNumWheels(); i++)
{
CPhysical *pHitPhysical = pVehicle->GetWheel(i)->GetHitPhysical() ? pVehicle->GetWheel(i)->GetHitPhysical() : pVehicle->GetWheel(i)->GetPrevHitPhysical();
if( pHitPhysical == pHitEnt )
{
impacts.DisableImpact();
break;
}
}
}
}
}
bool bIgnoreImpactForOpposingCollisionCheck = false;
// JR - I think it probably makes sense to skip these for all peds, as we wouldn't want to count a small prop as
// something that is constricting the mover (at least in how this config flag is used). But since its late in the project,
// it's probably best to narrow this exclusion to only player animals, which is where the bug with the logic below was found.
if (GetPedType() == PEDTYPE_ANIMAL && IsAPlayerPed())
{
// Check if we are trapped by a "small" object.
// Here, "small" means its not a ped prop or a ped, basically.
CEntity* pHitEnt = CPhysics::GetEntityFromInst(impacts.GetOtherInstance());
if (pHitEnt && (pHitEnt->GetIsTypePed() || (pHitEnt->GetIsTypeObject() && static_cast<CObject*>(pHitEnt)->GetShouldBeIgnoredForConstrictingCollisionChecks())))
{
bIgnoreImpactForOpposingCollisionCheck = true;
}
if( pHitEnt && !ShouldObjectBeImpossibleToPush(*pHitEnt, *impacts.GetOtherInstance(), impacts.GetOtherCollider(), impacts.GetOtherComponent()))
{
bIgnoreImpactForOpposingCollisionCheck = true;
}
}
// Test to see if the impacts are sufficiently opposing (in aggregate). Starting with the first impact keep track of the angle "covered"
// by the impacts. Coverage is defined as the normal of the impact +/- fCoverageHalfAlpha. Keep track of left/right vectors that define
// the extents of this coverage, and with each additional impact widen that range as appropriate. When an impact is received that contains
// both the left/right vectors then the entire area is covered. This approach assumes that coverage alpha is > PI/2 so there are no
// disjoint coverage zones. This is what we want anyways since we want two just-about-opposing vectors to be able to cause the mover to
// be considered constricted. (First submit of this has visualization if debugging or tweaking becomes required).
if(!GetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions) && !bIgnoreImpactForOpposingCollisionCheck)
{
// fCoverageHalfAlpha is the amount of the circle that we want an impact to be considered as "covering". The approach below assumes
// that this is at least PI/2 since that ensures that coverage from distinct impacts is contiguous and that simplifies (and speeds) the approach.
// The angle and cosine of the angle are both used in the approach, so hand calculate it as a constant to avoid runtime calculations or putting it in memory since that might be a pipeline hit.
// const ScalarV fCoverageHalfAlpha(PI*(0.59f));
// const ScalarV fCoverageHalfAlphaCos(-0.278991106f);
// Actually, we don't need the angle now, but instead, we can make good use of
// a full vector of sin/cos values:
const Vec4V coverageConstantsV = Vec4V(Vec::V4VConstant< // fCoverageHalfAlpha = PI*(0.59f)
FLOAT_TO_INT(-0.278991106f), // cos(fCoverageHalfAlpha)
FLOAT_TO_INT(-0.9602936857f), // -sin(fCoverageHalfAlpha);
FLOAT_TO_INT(-0.278991106f), // cos(fCoverageHalfAlpha);
FLOAT_TO_INT(0.9602936857f)>()); // sin(fCoverageHalfAlpha);
Vec3V vThisImpactCenter;
impacts.GetMyNormal(vThisImpactCenter);
vThisImpactCenter.SetZ(ScalarV(V_ZERO));
vThisImpactCenter = Normalize(vThisImpactCenter);
// We used to do this:
// vCoveredRight = RotateAboutZAxis(vThisImpactCenter,-fCoverageHalfAlpha);
// vCoveredLeft = RotateAboutZAxis(vThisImpactCenter,fCoverageHalfAlpha);
// but we can do it much more efficiently by taking advantage of the symmetry
// and the fact that the angle is always the same.
// Basically, we want to compute something like this
// rightX = x*tcos + y*tsin;
// rightY = x*(-tsin) + y*tcos;
// leftX = x*tcos - y*tsin;
// leftY = x*tsin + y*tcos;
// and we do it by putting the factors for the X coordinates in one vector,
// and the factors for the Y coordinates in another, and computing the four
// rotated components by multiply-adds:
const ScalarV impactCenterXV = vThisImpactCenter.GetX();
const ScalarV impactCenterYV = vThisImpactCenter.GetY();
const Vec4V rotXV = coverageConstantsV;
const Vec4V rotYV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(rotXV, rotXV);
const Vec4V mulXV = Scale(rotXV, impactCenterXV);
const Vec4V mulXYV = AddScaled(mulXV, rotYV, impactCenterYV);
// Next, we can rebuild the rotated vectors using cheap permutation instructions
// (note that vThisImpactCenter is flat, so Z = 0).
const Vec4V zeroV(V_ZERO);
const Vec3V vCoveredRightNew = GetFromTwo<Vec::X1, Vec::Y1, Vec::X2, Vec::Y2>(mulXYV, zeroV).GetXYZ();
const Vec3V vCoveredLeftNew = GetFromTwo<Vec::Z1, Vec::W1, Vec::X2, Vec::Y2>(mulXYV, zeroV).GetXYZ();
if(!bCoverageFirstImpactFound)
{
vCoveredRight = vCoveredRightNew;
vCoveredLeft = vCoveredLeftNew;
bCoverageFirstImpactFound = true;
}
else
{
const ScalarV fCoverageHalfAlphaCos = coverageConstantsV.GetX();
// Used to do it like this
// const ScalarV fDotCoveredLeft = Dot(vCoveredLeft,vThisImpactCenter);
// const ScalarV fDotCoveredRight = Dot(vCoveredRight,vThisImpactCenter);
// but should be more efficient to do it with a single multiplication (two 2D vectors in a 4D vector),
// an add, and some permutation:
const Vec4V thisImpactCenterXXYYV = Vec4V(vThisImpactCenter).Get<Vec::X, Vec::X, Vec::Y, Vec::Y>();
const Vec4V coveredLeftXRightXLeftYRightY = GetFromTwo<Vec::X1, Vec::X2, Vec::Y1, Vec::Y2>(vCoveredLeft, vCoveredRight);
const Vec4V mulLeftXRightXLeftYRightYV = Scale(thisImpactCenterXXYYV, coveredLeftXRightXLeftYRightY);
const Vec4V mulLeftYRightYLeftXRightXV = GetFromTwo<Vec::Z1, Vec::W1, Vec::X2, Vec::Y2>(mulLeftXRightXLeftYRightYV, mulLeftXRightXLeftYRightYV);
const Vec4V dotProductsLeftDummyDummyRightV = rage::Add(mulLeftXRightXLeftYRightYV, mulLeftYRightYLeftXRightXV);
const ScalarV fDotCoveredLeft = dotProductsLeftDummyDummyRightV.GetX();
const ScalarV fDotCoveredRight = dotProductsLeftDummyDummyRightV.GetW();
// Instead of this
// const int bCoveredLeftContained = IsLessThanAll(fCoverageHalfAlphaCos,fDotCoveredLeft);
// const int bCoveredRightContained = IsLessThanAll(fCoverageHalfAlphaCos,fDotCoveredRight);
// we use vector masks instead:
const BoolV coveredLeftContainedV = IsLessThan(fCoverageHalfAlphaCos,fDotCoveredLeft);
const BoolV coveredRightContainedV = IsLessThan(fCoverageHalfAlphaCos,fDotCoveredRight);
const BoolV coveredBothV = And(coveredLeftContainedV, coveredRightContainedV);
// B*1848782: Prevent contacts with similar normals from incorrectly triggering the full coverage conditions and setting the CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions flag.
// Contacts with very similar normals can trigger a false positive result as they are almost perfectly centered between the left and right vectors and therefore within fCoverageHalfAlpha of both.
// A correctly opposing impact normal will never be within fCoverageHalfAlpha of both vectors (Assuming fCoverageHalfAlpha is > PI/2) so we can detect if this is the case and prevent it from triggering the false positive.
const BoolV isLeftAndRightAngleRoughlyCoverageHalfAlpha = And(IsClose(fDotCoveredLeft, fCoverageHalfAlphaCos, ScalarV(0.0000001f)), IsClose(fDotCoveredRight, fCoverageHalfAlphaCos, ScalarV(0.0000001f)));
// We still have to do one branch on the vector result, instead of this:
// if(bCoveredLeftContained && bCoveredRightContained)
if(IsTrue(coveredBothV) && !IsTrue(isLeftAndRightAngleRoughlyCoverageHalfAlpha))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions,true);
}
else
{
// Now, instead of branching like this
// if(bCoveredLeftContained)
// {
// vCoveredLeft = RotateAboutZAxis(vThisImpactCenter,fCoverageHalfAlpha);
// }
// else if(bCoveredRightContained)
// {
// vCoveredRight = RotateAboutZAxis(vThisImpactCenter,-fCoverageHalfAlpha);
// }
// we can use vector selection (we know that the masks can't both be true):
vCoveredLeft = SelectFT(coveredLeftContainedV, vCoveredLeft, vCoveredLeftNew);
vCoveredRight = SelectFT(coveredRightContainedV, vCoveredRight, vCoveredRightNew);
}
}
}
#if !USE_NEW_TRIANGLE_BACK_FACE_CULL
// Disable back facing contacts that got past the collision task filtering due to our capsule changing size
//if(m_bMoverCapsuleIsChanging) Always do this now
// This code heavily relies on the last matrix of the ped being correct. If the 'pedCenter' vector is inside collision then it
// will end up disabling the contact. When a ped is attached to the ground it is inactive and doesn't use a last matrix, this
// means if it's attached to a fast moving vehicle it could get inside collision in one frame and always disable contacts. It probably
// should use a last matrix but that's a more dangerous change. The ped only has a ground attachment when standing still on
// an object. As soon as the ped starts moving or capsule changes size it will activate and break its attachment.
bool isLastMatrixValid = !GetIsAttachedToGround();
if(IsPlayer() && isLastMatrixValid)
{
// BUT - Do it from the center of the last safe matrix instead of the current center of mass
Vec3V pedCenter;
const phInst* pMyInst = impacts.GetMyInstance();
const phCollider* pMyCollider = PHSIM->GetCollider(pMyInst);
if(pMyCollider != NULL)
{
pedCenter = pMyCollider->GetLastSafeInstanceMatrix().GetCol3();
}
else
{
pedCenter = pMyInst->GetPosition();
}
// The above shenanigans for doing this always from the last safe matrix are needed because of capsule side shaving
// - Which allows us to continue penetrating objects even after we're done changing size, so we have to continue filtering them
// TODO: We could probably still avoid doing any of this if we're at our base capsule size
Vec3V otherNormal;
impacts.GetOtherNormal(otherNormal);
const Vec3V otherPos = impacts.GetOtherPosition();
// Back up the other contact point by it's velocity to effectively compare with the start of frame position this contact would have
// (Which is somewhat wrong with any angular component but should be good enough in practice)
Vec3V otherLocalVel;
Verifyf( PHSIM->GetObjectLocalVelocity(otherLocalVel, *(impacts.GetOtherInstance()), otherPos, impacts.GetOtherComponent()), "Instance colliding with ped isn't in level." );
const Vec3V otherPosPrev = Subtract( otherPos, Scale(otherLocalVel, ScalarVFromF32(fwTimer::GetRagePhysicsUpdateTimeStep())) );
const Vec3V centerToOther = Subtract(otherPosPrev, pedCenter);
if(IsLessThanAll(Dot(centerToOther, otherNormal), ScalarV(V_ZERO)))
{
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "BackFace"));
}
}
#endif // USE_NEW_TRIANGLE_BACK_FACE_CULL
// All other ped impacts
// Check if we've hit something that should hurt us (e.g. electric fence)
phMaterialMgr::Id nMaterialId = PGTAMATERIALMGR->UnpackMtlId(impacts.GetOtherMaterialId());
// See if there is a weapon associated with this material
u32 uWeaponHash = PGTAMATERIALMGR->GetMtlReactWeaponType(nMaterialId);
phInst* pOtherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
if(!GetPedResetFlag(CPED_RESET_FLAG_DisableMaterialCollisionDamage) && (uWeaponHash > 0) && (uWeaponHash != ms_uIgnoredMaterialCollisionWeaponHash))
{
// Only take damage if we are moving into the collision material
Vec3V vNormal;
impacts.GetOtherNormal(vNormal);
const ScalarV fMovementIntoMaterial = Dot(vNormal, VECTOR3_TO_VEC3V(GetVelocity()));
if(IsGreaterThanAll(fMovementIntoMaterial, ScalarV(V_FLT_SMALL_2))) // 0.01f
{
// Find the weapon info
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uWeaponHash);
#if __ASSERT
char tempBuffer[128];
PGTAMATERIALMGR->GetMaterialName(nMaterialId,tempBuffer,128);
#endif
if(weaponVerifyf(pWeaponInfo,"Couldn't find weapon info for material %s",tempBuffer))
{
float fDamage = pWeaponInfo->GetDamage();
// This material has a weapon associated with it, so cause a damage event
Vec3V impactPos = impacts.GetMyPosition();
CWeaponDamage::GeneratePedDamageEvent(pOtherEntity, this, uWeaponHash, fDamage, VEC3V_TO_VECTOR3(impactPos), NULL, CPedDamageCalculator::DF_None, NULL, -1, pWeaponInfo->GetForceHitPed(), 0, true);
g_vfxWeapon.TriggerPtFxMtlReactWeapon(pWeaponInfo, impactPos);
}
}
}
if(pOtherEntity != NULL)
{
// Even if we zero'd out our z-impact due to mass, we still need to see if this is a corpse we need to
// simply step through instead of kick
if(pOtherEntity->GetIsTypePed())
{
if(!CPhysics::GetLevel()->IsFixed(pOtherInstance->GetLevelIndex()))
{
CPed* pHitPed = static_cast<CPed*>(pOtherEntity);
// Note that this ped is colliding with the main capsule of a ped that should always wake up others.
m_bMainBoundIntersectsPedThatCausesActivations = pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_AlwaysWakeUpPhysicsOfIntersectedPeds);
}
}
#if __ASSERT
else if (pOtherEntity->GetIsTypeVehicle())
{
Assert(!CVehicle::ShouldDisableImpactForPedExitingVehicle(*this, *static_cast<CVehicle*>(pOtherEntity), impacts.GetOtherComponent()));
// Shouldn't need to do this - if this had been true it seems like we should have already discovered
// that in CanDisableImpactForMover(), and that should have prevented us from getting here:
// if (CVehicle::ShouldDisableImpactForPedExitingVehicle(*this, *static_cast<CVehicle*>(pOtherEntity), impacts.GetOtherComponent()))
// {
// impacts.DisableImpact();
// PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "ExitingVehicle"));
// }
}
#endif // __ASSERT
}
// Note that there is some very mild popping when dropping down below a run while there are active side contacts
// This is because we stop updating the depth during the transition back down to normal radius
// -- Could be fixed by running this code at least until the radius got back down to walking size
// Unfortunately it's not as simple as running it any time the radius is larger because there are other ways
// that the radius is enlarged such as crouching and cover - Best fix might be to throw away contacts any time
// the radius is shrinking? (Thinking such logic might be good for the other radius changing cases anyway?..)
// - We've done something cleaner and probably better, if a bit subtle - see @%! below
if(IsLocalPlayer() && !impacts.IsDisabled() && ( m_nPedType != PEDTYPE_ANIMAL ) )
{
// Allow penetration into sides when running
// - The capsule is expanded but we don't want to grow too wide to fit through a door just because we're running/sprinting
const bool bIsRunning = GetMotionData()->GetIsRunning();
const bool bIsSprinting = GetMotionData()->GetIsSprinting();
const bool bIsStealth = IsUsingStealthMode();
const bool bRadiusIsChanging = m_bMoverCapsuleIsChanging;
if((bIsRunning || bIsSprinting || bIsStealth) || bRadiusIsChanging)
{
// If the contact points are old have become separated we have to disable them
// - This is because we don't update the normal when we move the effective collision inward
// So, since the contact points still define planes based on the old normal - motion tangent to
// that old normal can cause responses outside the desired ellipsoid shape that the increased depth
// is meant to emulate. (The reason we don't update the normal is to keep the solver from generating
// any torque on the capsule.)
// TODO: The max distance could probably be informed by how much extra penetration we're allowing
const ScalarV maxDistSqrd(V_FLT_SMALL_4);
if(impacts.GetContact().GetLifetime() > diMinLifetime)
{
// Unfortunately this is kind of useless as a comparison now - It will nearly always occur because we actually move the contacts apart ourselves
ScalarV distSqrd = DistSquared(impacts.GetMyPosition(), impacts.GetOtherPosition());
if( IsGreaterThanAll(distSqrd, maxDistSqrd) != 0 )
{
// Had been disabling here - but because we are now physically moving the contacts rather than just allowing depth
// we should really delete older contacts to avoid building up useless contacts
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Removed", "Separated"));
AddCollisionRecordBeforeDisablingContact(GetCurrentPhysicsInst(), pOtherEntity, impacts);
impacts.RemoveContactWithFinalize();
return true;
}
}
// @%!: We need to keep running this while sizing back down to walking after running but we in general don't want to affect non-running sizing
// So we're making use of our below clamping to positive only delta radius to make this only do anything while down sizing
// (Which WILL affect non-running down sizing, but should be neutral or even beneficial for them anyway.)
const float fBaseCapsuleSize = (bIsRunning || bIsSprinting || bIsStealth) ? PATHSERVER_PED_RADIUS : GetDesiredMainMoverCapsuleRadius();
const float fCurrentCapsuleSize = GetCurrentMainMoverCapsuleRadius();
// We don't ever want to widen the sides if for some reason the current bound is actually smaller than walking radius
const float fExtraRadius = Max(fCurrentCapsuleSize - fBaseCapsuleSize, 0.0f);
const ScalarV extraRadius = ScalarVFromF32(fExtraRadius);
// Peds current facing(side) direction
const Vec3V mySideDir = GetTransform().GetA();
Assert( IsLessThanOrEqualAll(Subtract(ScalarV(V_ONE), MagSquared(mySideDir)), ScalarV(V_FLT_SMALL_2)) );
// Current contact depth and normal
const ScalarV curDepth = impacts.GetDepthV();
Vec3V myNorm;
impacts.GetMyNormal(myNorm);
Assert( IsLessThanOrEqualAll(Subtract(ScalarV(V_ONE), MagSquared(myNorm)), ScalarV(V_FLT_SMALL_2)) );
// Allow penetration along sides by decreasing the reported depth
const ScalarV normDotSide = Dot(myNorm, mySideDir);
Assert( IsLessThanOrEqualAll(Abs(normDotSide), rage::Add(ScalarV(V_ONE), ScalarV(V_FLT_SMALL_2))) );
const ScalarV allowedPenScale = Abs(normDotSide);
// Calculate and set the new depth
const ScalarV depthChange = Scale(extraRadius, allowedPenScale);
const ScalarV newDepth = rage::Add(curDepth, Negate(depthChange));
// Need to actually move contact position and update depth
impacts.SetMyPosition(impacts.GetMyPosition() + Scale(myNorm, depthChange));
impacts.SetDepth(newDepth);
// Finally need to update the positive depth flag otherwise the contact still applies like a planar constraint
impacts.GetContact().SetPositiveDepth( IsGreaterThanOrEqualAll(newDepth, ScalarV(V_ZERO)) != 0 );
}
}
#if PED_USE_EXTRA_BOUND
if (!impacts.IsDisabled())
{
const CBipedCapsuleInfo *pBipedCapsuleInfo = m_pCapsuleInfo->GetBipedCapsuleInfo();
if (pBipedCapsuleInfo)
{
FastAssert(impacts.GetMyInstance() != NULL && impacts.GetMyInstance()->GetArchetype() != NULL &&
impacts.GetMyInstance()->GetArchetype()->GetBound() != NULL && impacts.GetMyInstance()->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE &&
static_cast<phBoundComposite*>(impacts.GetMyInstance()->GetArchetype()->GetBound())->GetTypeAndIncludeFlags() != NULL);
// If our extra bound is using the same include flags as our archetype then it is responsible for Z impacts which means we need to zero out
// Z impacts for the main mover capsule
if (static_cast<phBoundComposite*>(impacts.GetMyInstance()->GetArchetype()->GetBound())->GetIncludeFlags(PED_USE_EXTRA_BOUND) == CPhysics::GetLevel()->GetInstanceIncludeFlags(impacts.GetMyInstance()->GetLevelIndex()))
{
if (ZeroOutImpactZ(impacts, ScalarV(V_FLT_SMALL_2)))
{
impacts.SetDepth(Min(impacts.GetDepth(), 0.06f));
}
}
}
}
#endif
if (GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsuleMapCollision))
{
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(debugPlayback::RecordModificationToContact(impacts, "Disabled", "!Mover"));
}
}
}
PF_STOP(MainCapsuleImpact);
return false;
}
#if PED_USE_CAPSULE_PROBES
bool CPed::ProcessPreComputeCapsuleProbeImpact(phContactIterator &impacts, bool &frontImpactComputed)
{
#if __BANK
if(!sm_PreComputeCapsuleProbeImpact)
return false;
#endif // __BANK
PF_START(CapsuleProbeImpact);
#if __DEV
static bool DEBUG_DRAW_STAND_IMPACTS = false;
if(DEBUG_DRAW_STAND_IMPACTS)
{
CPhysics::ms_debugDrawStore.AddSphere(impacts.GetOtherPosition(), 0.02f, Color_red, fwTimer::GetTimeStepInMilliseconds());
Vec3V vecTempMyNormal;
impacts.GetMyNormal(vecTempMyNormal);
CPhysics::ms_debugDrawStore.AddLine(impacts.GetOtherPosition(), impacts.GetOtherPosition() + Scale(vecTempMyNormal, ScalarVFromF32(0.2f)), Color_red,fwTimer::GetTimeStepInMilliseconds());
CPhysics::ms_debugDrawStore.AddSphere(impacts.GetMyPosition(), 0.02f, Color_green, fwTimer::GetTimeStepInMilliseconds());
}
#endif
// Could do this
// const Vec3V otherPosFromContactV = impacts.GetOtherPosition();
// int groundComponentIndex = GetOtherComponent();
// impacts.GetMyNormal(groundNormal);
// const phPolygon::Index groundPrimitiveIndex = (phPolygon::Index)impacts.GetOtherElement();
// const phMaterialMgr::Id groundMaterialId = impacts.GetOtherMaterialId();
// phInst* pOtherInstance = impacts.GetOtherInstance();
// but we may save some branching by doing them a bit more directly:
const phContact& contact = impacts.GetContact();
const phManifold& manifold = impacts.GetCachedManifold();
Vec3V otherPosFromContactV;
int groundComponentIndex;
Vec3V groundNormal;
phPolygon::Index groundPrimitiveIndex;
phMaterialMgr::Id groundMaterialId;
phInst* pOtherInstance;
if(impacts.GetMyInstance() == impacts.GetInstanceA())
{
otherPosFromContactV = contact.GetWorldPosB();
groundComponentIndex = manifold.GetComponentB();
groundNormal = contact.GetWorldNormal();
groundPrimitiveIndex = (phPolygon::Index)contact.GetElementB();
groundMaterialId = contact.GetMaterialId(1);
pOtherInstance = impacts.GetInstanceB();
}
else
{
otherPosFromContactV = contact.GetWorldPosA();
groundComponentIndex = manifold.GetComponentA();
groundNormal = Negate(contact.GetWorldNormal());
groundPrimitiveIndex = (phPolygon::Index)contact.GetElementA();
groundMaterialId = contact.GetMaterialId(0);
pOtherInstance = impacts.GetInstanceA();
}
const Vec3V vecOtherPosition = GetCorrectedGroundPosition(otherPosFromContactV,*pOtherInstance,groundComponentIndex);
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
if(pOtherEntity && pOtherEntity->GetIsTypeVehicle())
{
CVehicle* pVehicle = static_cast<CVehicle*>(pOtherEntity);
Assert(pOtherInstance->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE);
phBoundComposite* pCompBound = static_cast<phBoundComposite*>(pOtherInstance->GetArchetype()->GetBound());
if(pCompBound->GetTypeAndIncludeFlags() && pCompBound->GetTypeFlags(groundComponentIndex)&ArchetypeFlags::GTA_FORKLIFT_FORKS_TYPE)
{
const static float sfForkMoveThreshold = 0.3f;
if(pVehicle->GetLocalSpeed(VEC3V_TO_VECTOR3(otherPosFromContactV), true, groundComponentIndex).Mag2() > rage::square(sfForkMoveThreshold))
{
SetPedResetFlag(CPED_RESET_FLAG_StandingOnForkliftForks, true);
}
}
}
// Check if the ped touches a pickup
if(CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInstance->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE))
{
m_bPickupInRange = true;
}
if(IsGroundBetterThanCurrent(pOtherEntity,pOtherInstance,vecOtherPosition,groundNormal,groundComponentIndex,m_pGroundPhysical,m_vecGroundPos, m_bValidGroundNormal))
{
frontImpactComputed = true;
SetGround(pOtherEntity,pOtherInstance,vecOtherPosition,groundNormal,groundComponentIndex,groundPrimitiveIndex,groundMaterialId);
}
// underwater ped disturbance effects
if (GetIsSwimming())
{
Vec3V vNormal;
impacts.GetOtherNormal(vNormal);
VfxDisturbanceType_e vfxDisturbanceType = PGTAMATERIALMGR->GetMtlVfxDisturbanceType(impacts.GetOtherMaterialId());
g_vfxPed.UpdatePtFxPedUnderwaterDisturb(this, impacts.GetOtherPosition(), vNormal, vfxDisturbanceType);
}
// Let ragdolls decide if they want to collide with our probe capsule
// Unless the ragdoll is being used as our ground physical - then we must disable the contact!
if(pOtherInstance == NULL || pOtherInstance->GetClassType() != PH_INST_FRAG_PED || GetGroundPhysical() == pOtherEntity)
{
#if __BANK
if(ms_bDeleteProbeCapsuleImpacts)
#endif // __BANK
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Deleted", "Probe"));
impacts.RemoveContactWithFinalize();
PF_STOP(CapsuleProbeImpact);
return true;
}
#if __BANK
else
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Removed", "Probe"));
impacts.DisableImpact();
}
#endif // __BANK
}
PF_STOP(CapsuleProbeImpact);
return false;
}
#endif // PED_USE_CAPSULE_PROBES
void CPed::RecomputeGroundPhysical()
{
CPhysical* groundPhysical = GetGroundPhysical();
if(Verifyf(groundPhysical,"Need to have a ground physical to recompute it."))
{
const phInst* groundInst = groundPhysical->GetCurrentPhysicsInst();
if(groundInst)
{
const CBipedCapsuleInfo *pBipedCapsuleInfo = m_pCapsuleInfo->GetBipedCapsuleInfo();
if (pBipedCapsuleInfo && AssertVerify(GetCurrentPhysicsInst()))
{
const Mat34V& pedMatrix = GetCurrentPhysicsInst()->GetMatrix();
Vec3V probeStart = Transform(pedMatrix,Vec3V(0.0f,pBipedCapsuleInfo->GetProbeYOffset(),pBipedCapsuleInfo->GetHeadHeight()));
Vec3V probeEnd = Transform(pedMatrix,Vec3V(0.0f,pBipedCapsuleInfo->GetProbeYOffset(),(-pBipedCapsuleInfo->GetGroundToRootOffset()) - pBipedCapsuleInfo->GetGroundOffsetExtend()));
WorldProbe::CShapeTestFixedResults<> shapeTestResults;
WorldProbe::CShapeTestCapsuleDesc shapeTestDesc;
shapeTestDesc.SetResultsStructure(&shapeTestResults);
shapeTestDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS);
// Use the same flags as the ped probe capsule
shapeTestDesc.SetTypeFlags(ArchetypeFlags::GTA_PED_TYPE);
shapeTestDesc.SetIncludeFlags((u32)~(ArchetypeFlags::GTA_PED_TYPE|ArchetypeFlags::GTA_HORSE_TYPE));
shapeTestDesc.SetIncludeEntity(groundPhysical, WorldProbe::EIEO_DONT_ADD_VEHICLE | WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS | WorldProbe::EIEO_DONT_ADD_WEAPONS);
shapeTestDesc.SetIsDirected(true);
shapeTestDesc.SetCapsule(RCC_VECTOR3(probeStart),RCC_VECTOR3(probeEnd),pBipedCapsuleInfo->GetProbeRadius());
if(WorldProbe::GetShapeTestManager()->SubmitTest(shapeTestDesc))
{
// Store off the old ground data and clear it so IsGroundBetterThanCurrent prefers these intersections
Vec3V oldVecGroundPos = m_vecGroundPos;
float oldGroundZFromImpact = m_fGroundZFromImpact;
m_fGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_vecGroundPos.ZeroComponents();
m_vecGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
// Find the best non-phys_car_void material to use. This material marks polygons on vehicle chassis that cover
// doors and windows. We want to stand on these polygons but we would prefer a better material for stuff like audio.
const phMaterialMgr::Id physCarVoidMaterialId = PGTAMATERIALMGR->g_idCarVoid;
ScalarV greatestZ = ScalarV(V_NEG_FLT_MAX);
phMaterialMgr::Id bestMaterialId = physCarVoidMaterialId; // fallback material, doesn't have to be phys_car_void
for(int resultIndex = 0; resultIndex < shapeTestResults.GetNumHits(); ++resultIndex)
{
ScalarV resultZ = shapeTestResults[resultIndex].GetPosition().GetZ();
phMaterialMgr::Id resultMaterialId = shapeTestResults[resultIndex].GetMaterialId();
if(phMaterialMgrGta::UnpackMtlId(resultMaterialId) != physCarVoidMaterialId && IsGreaterThanAll(resultZ,greatestZ))
{
bestMaterialId = resultMaterialId;
greatestZ = resultZ;
}
}
// Find the best ground of our results
bool foundValidGround = false;
for(int resultIndex = 0; resultIndex < shapeTestResults.GetNumHits(); ++resultIndex)
{
const WorldProbe::CShapeTestHitPoint& result = shapeTestResults[resultIndex];
Vec3V groundPosition = result.GetPosition();
Vec3V groundNormal = result.GetNormal();
u16 groundComponent = result.GetComponent();
Assertf(result.GetHitEntity() == groundPhysical, "Shapetest against ground instance returned different entity.");
if(IsGroundBetterThanCurrent(groundPhysical,groundInst,groundPosition,groundNormal,groundComponent,m_pGroundPhysical,m_vecGroundPos,m_bValidGroundNormal))
{
if(!foundValidGround)
{
// We found a valid ground, so it's safe to get rid of our old ground
foundValidGround = true;
m_bValidGroundNormal = false;
}
const phPolygon::Index groundPrimitiveIndex = result.GetPartIndex();
SetGround(groundPhysical,groundInst,groundPosition,groundNormal,groundComponent,groundPrimitiveIndex,bestMaterialId);
}
}
// Restore the ground if we didn't find a valid intersection.
if(!foundValidGround)
{
m_vecGroundPos = oldVecGroundPos;
m_fGroundZFromImpact = oldGroundZFromImpact;
}
}
}
}
}
}
bool CPed::IsGroundBetterThanCurrent( const CEntity* groundEntity, const phInst* groundInstance, Vec3V_In groundPosition, Vec3V_In groundNormal, int groundComponentIndex,
const CEntity* currentGroundEntity, Vec3V_In currentGroundPosition, bool currentNormalIsValid)
{
#if __DEV
if(Unlikely(!CPhysics::ms_bDoPedProbesWithImpacts))
{
return false;
}
#endif
if(groundEntity)
{
if(groundEntity->GetIsTypeVehicle())
{
const CVehicle* pVehicle = static_cast<const CVehicle*>(groundEntity);
// Can not stand on top of a propeller, either spinning or stationary
if(pVehicle->IsPropeller(groundComponentIndex))
{
return false;
}
}
else if(groundEntity->GetIsTypePed())
{
const CPed* pPed = static_cast<const CPed*>(groundEntity);
// Consider large components on large ped ragdolls as valid ground
if(groundInstance->GetClassType() != PH_INST_FRAG_PED || pPed->GetDeathState() != DeathState_Dead || !pPed->ShouldBeDead() ||
groundInstance->GetArchetype()->GetMass() < ms_fLargePedMass ||
static_cast<const fragInstNMGta*>(groundInstance)->GetTypePhysics()->GetChild(groundComponentIndex)->GetUndamagedMass() < ms_fLargeComponentMass)
{
// Don't let ped capsules become the ground, this will happen even if the two peds are at the same height as long
// as the capsules intersect enough.
return false;
}
}
else if(groundEntity->GetIsTypeObject())
{
const CObject* pObject = static_cast<const CObject*>(groundEntity);
// We don't want peds walking on the spherical collision bounds around pickups.
if(pObject->IsPickup())
{
return false;
}
// As with pickups above, we don't want AIs walking on dropped ambient props or setting up leg IK with respect to the ambient
// prop's collision bound (since for AIs using partial leg IK mode, the foot placement is inferred from the ground normal).
if(pObject->m_nObjectFlags.bAmbientProp || pObject->m_nObjectFlags.bDetachedPedProp)
{
return false;
}
// Do not allow peds to stand on self-restoring articulated props. Since there is no downward force on the ground physical
// it allows very light articulated links to effortlessly move the ped
if(IsFragInst(groundInstance))
{
const fragInst* pFragInst = static_cast<const fragInst*>(groundInstance);
const fragPhysicsLOD* physicsLOD = pFragInst->GetTypePhysics();
u8 groupIndex = physicsLOD->GetChild(groundComponentIndex)->GetOwnerGroupPointerIndex();
if(physicsLOD->GetGroup(groupIndex)->GetJointRestoringStrength() > 0.1f && pFragInst->GetType()->WillBeArticulated(groupIndex))
{
return false;
}
}
}
// When we swim or crawl the main capsule tilts, allowing the ground probe to pick up ground contacts above the capsule.
// I think the ideal check would be to make sure the contact position is on the bottom of the capsule probe but looking that up is kinda expensive.
if(m_fCurrentBoundPitch != 0.0f && IsGreaterThanAll(groundPosition.GetZ(),rage::Add(GetTransform().GetPosition().GetZ(),ScalarVFromF32(m_vCurrentBoundOffset.GetZ()))))
{
return false;
}
}
else
{
// B*1793907: Do not allow glass shards to be selected as valid ground.
// There was an issue where airbourne shards of glass would be selected as a valid ground in certain situations.
// This could happen for several consecutive frames during which the ped would be catapulted into the air as the spring force in ProcessPedStanding kept trying to adjust the main capsule to sit just above the airbourne and moving "ground".
if((groundInstance->GetArchetype()->GetTypeFlags() & ArchetypeFlags::GTA_GLASS_TYPE) && !(groundInstance->GetArchetype()->GetTypeFlags() & (ArchetypeFlags::GTA_UNSMASHED_TYPE)))
{
return false;
}
}
// Checking CObject::IsPickup is a lot faster. Leaving this here in case we somehow set GTA_PICKUP_TYPE on non CPickups
Assert(!CPhysics::GetLevel()->GetInstanceTypeFlag(groundInstance->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE));
//Keep track of the ground that is pointing most towards up to tune spring damping
//This helps avoid some large drops in damping when moving off small steps
m_vecMaxGroundNormal = SelectFT(IsGreaterThan(groundNormal.GetZ(),m_vecMaxGroundNormal.GetZ()),m_vecMaxGroundNormal,groundNormal);
if(((!currentNormalIsValid) || (IsGreaterThanAll(groundPosition.GetZ(), currentGroundPosition.GetZ()))))
{
// Don't replace a detected ground physical if it has a valid normal and a higher position than the
// contact currently being considered
return !(currentGroundEntity && currentNormalIsValid && IsGreaterThanAll(m_vecGroundPos.GetZ(), groundPosition.GetZ()));
}
return false;
}
void CPed::SetGround(CEntity* groundEntity, const phInst* groundInstance, Vec3V_In groundPosition, Vec3V_In groundNormal, int groundComponentIndex, phPolygon::Index ASSERT_ONLY(groundPrimitiveIndex), phMaterialMgr::Id groundMaterialId)
{
// Prevent L2 cache misses when we check the footstep material
m_PedAudioEntity.GetFootStepAudio().PrefetchLastMaterial();
bool bValidNormal = true;
// The collider position and inst position are the same for animated peds. This is not necessarily true for ragdolls.
Assert(GetCurrentPhysicsInst() == GetAnimatedInst());
const Vec3V colliderPosition = GetCurrentPhysicsInst()->GetMatrix().GetCol3();
bool isGroundFixed = CPhysics::GetLevel()->IsFixed(groundInstance->GetLevelIndex());
const ScalarV svHalf(V_HALF);
ScalarV collisionMaxZ;
// For bipeds we make sure the locations checked are relative to the capsule to cater for peds with offset entity locations.
// For example peds with their mover position at their feet.
const CBaseCapsuleInfo* pCapsuleInfo = GetCapsuleInfo();
if( pCapsuleInfo && pCapsuleInfo->IsBiped() )
{
const CBipedCapsuleInfo* pBipedCapsuleInfo = static_cast<const CBipedCapsuleInfo*>(pCapsuleInfo);
const ScalarV vecCapsuleZOffset = ScalarVFromF32(pBipedCapsuleInfo->GetCapsuleZOffset());
const ScalarV vecCapsuleCentre = Scale(rage::Add(ScalarVFromF32(pBipedCapsuleInfo->GetHeadHeight()),vecCapsuleZOffset),svHalf);
const ScalarV vecFixedCollisionMaxZ = rage::Add(vecCapsuleCentre, colliderPosition.GetZ());
if(isGroundFixed)
{
collisionMaxZ = vecFixedCollisionMaxZ;
}
else
{
const ScalarV svTwoThirds = rage::Add(ScalarV(V_THIRD),ScalarV(V_THIRD));
collisionMaxZ = rage::Add(vecFixedCollisionMaxZ, Scale(Subtract(vecCapsuleZOffset,vecCapsuleCentre),svTwoThirds));
}
//float fStairsAllowance = PGTAMATERIALMGR->GetPolyFlagStairs(impacts.GetOtherMaterialId()) ? 0.25f : 0.0f; //TMS: Allow greater clearance on stairs
//vecDynamicCollisionMaxZ = rage::Add(vecFixedCollisionMaxZ, ScalarV((pBipedCapsuleInfo->GetCapsuleZOffset()-fCapsuleCentre+fStairsAllowance)*0.66f));
}
else
{
// By default reject collisions above the centre of the entity or slightly below the entity if dynamic.
// This is for situations where we have moved into collision before resolving and collisions are detected within the capsule.
// Note the collider is at the entity location and may be offset from the capsule so this could be inaccurate.
if(isGroundFixed)
{
collisionMaxZ = colliderPosition.GetZ();
}
else
{
ScalarV svEntCentreAdjust(V_HALF);
if(pCapsuleInfo && pCapsuleInfo->IsQuadruped())
{
svEntCentreAdjust = Scale(svEntCentreAdjust, ScalarV(GetCurrentMainMoverCapsuleRadius()));
}
collisionMaxZ = Subtract(colliderPosition.GetZ(), svEntCentreAdjust);
}
}
#if __DEV
static bool DEBUG_REJECTION_POSITIONS = false;
if(DEBUG_REJECTION_POSITIONS)
{
CPhysics::ms_debugDrawStore.AddSphere(groundPosition, 0.02f, Color_green, fwTimer::GetTimeStepInMilliseconds());
CPhysics::ms_debugDrawStore.AddSphere(colliderPosition, 0.02f, Color_blue, fwTimer::GetTimeStepInMilliseconds());
CPhysics::ms_debugDrawStore.AddSphere(Vec3V(groundPosition.GetXY(), collisionMaxZ), 0.02f, Color_yellow, fwTimer::GetTimeStepInMilliseconds());
}
#endif
// Ask move blender for this?
ScalarV minNormalZ = svHalf; // 60 deg slope
CVehicle* groundVehicle = NULL;
if(groundEntity && groundEntity->GetIsTypeVehicle())
{
groundVehicle = static_cast<CVehicle*>(groundEntity);
//! DMKH. Accept most normals on trains. This prevents us detaching too easily (as we lose our ground physical).
if(groundVehicle->InheritsFromTrain() || groundVehicle->InheritsFromPlane())
{
//Check if the last valid ground time has expired.
if(fwTimer::GetTimeInMilliseconds() > (m_uTimeGroundPhysicalWasSet + 250) &&
groundVehicle == m_pLastValidGroundPhysical)
{
minNormalZ = ScalarV(V_ZERO);
}
}
else if(groundVehicle->pHandling && groundVehicle->pHandling->hFlags & HF_EXT_WHEEL_BOUNDS_COL) // If we are standing on large exposed wheels( i.e monster truck) disable IK
{
for(int i = 0; i < groundVehicle->GetNumWheels(); i++)
{
if(groundVehicle->GetWheel(i)->GetFragChild(WHEEL_CLIMB_BOUND) == groundComponentIndex)
{
SetDisableLegSolver(true);
break;
}
}
}
}
bValidNormal = And(IsGreaterThanOrEqual(groundNormal.GetZ(), minNormalZ), IsLessThanOrEqual(groundPosition.GetZ(), collisionMaxZ)).Getb();
// Don't let a vehicle impact with an invalid normal qualify as a ground physical
bool validPhysical = true;
if (!bValidNormal)
{
//! Just accept normal is we are climbing on to this.
if(GetClimbPhysical() == groundEntity)
{
bValidNormal = true;
}
else
{
validPhysical = false;
}
}
// Only do this check for the player, since AI peds depend on the RidingTrain flag being set by TaskRideTrain [5/15/2013 mdawe]
if (this->IsPlayer())
{
if((groundVehicle && groundVehicle->InheritsFromTrain()))
{
this->SetRidingOnTrain(true, groundVehicle);
}
else
{
this->SetRidingOnTrain(false, NULL);
}
}
// Set the ground normal and position
// There is other code gating higher positions replacing lower ones
// - If we don't have a valid ground normal, store anything that gets this far
// - If we do have a ground normal, only store new ground contacts that also have valid contacts.
if (!m_bValidGroundNormal || bValidNormal)
{
StoreScalar32FromScalarV(m_fGroundZFromImpact, groundPosition.GetZ());
m_vecGroundNormal = RCC_VECTOR3(groundNormal);
m_vecGroundPos = groundPosition;
m_bValidGroundNormal |= bValidNormal;
}
// if we're standing on something physical - store a pointer to it
//Make sure the prop has a couple of dimensions capable of being stood on.
if(groundEntity && groundEntity->GetIsPhysical())
{
if(validPhysical && ValidateGroundPhysical(*groundInstance, groundPosition, groundComponentIndex))
{
CPhysical* pGroundPhysical = static_cast<CPhysical*>(groundEntity);
SetGroundPhysical(pGroundPhysical);
m_groundPhysicalComponent = groundComponentIndex;
if(GetGroundPhysical() != m_pLastValidGroundPhysical)
{
m_uTimeGroundPhysicalWasSet = fwTimer::GetTimeInMilliseconds();
}
//! DMKH. Only snap Z if we have no IK as it results in poppy movement on vehicles.
u32 legIkMode = m_PedConfigFlags.GetPedLegIkMode();
if(legIkMode == LEG_IK_MODE_OFF)
{
//if we're inside a vehicle then make sure the legs are in the correct place
if(groundVehicle && !groundVehicle->InheritsFromTrain() &&
groundVehicle->CanPedsStandOnTop())
{
// Snap the ped's entity to the correct height above the ground
m_PedResetFlags.SetEntityZFromGround( 50 );
}
}
Mat34V_ConstRef mtrx = groundInstance->GetMatrix();
//Assign the local ground normal.
m_vGroundNormalLocal = UnTransform3x3Ortho(mtrx, RCC_VEC3V(m_vecGroundNormal));
//Convert into local ground physical space, so that it is valid even if the ground physical moves and the ped
//doesn't.
m_vecGroundPosLocal = UnTransformOrtho(mtrx, m_vecGroundPos);
}
// Record that we're standing on a possibly movable object
SetIsStandingOnMovableObject(true);
}
else
{
SetIsStandingOnMovableObject(false);
}
// Set up the ground material
m_PackedGroundMaterialId = groundMaterialId;
SetStairFlags(NULL, groundInstance, (s32)groundComponentIndex, groundMaterialId);
if (PGTAMATERIALMGR->GetPolyFlagTooSteepForPlayer(groundMaterialId))
{
SetPedResetFlag(CPED_RESET_FLAG_TooSteepForPlayer, true);
}
#if __ASSERT // Not sure we need this code anymore but just putting it in to verify answer from impacts is correct
phMaterialMgr::Id nMaterialId;
const phBound* bound = groundInstance->GetArchetype()->GetBound();
int boundType = bound->GetType();
if (boundType == phBound::BVH ||
boundType == phBound::GEOMETRY
USE_GEOMETRY_CURVED_ONLY(|| boundType == phBound::GEOMETRY_CURVED))
{
const phBoundGeometry* boundGeom = static_cast<const phBoundGeometry*>(bound);
int polyIndex = groundPrimitiveIndex;
s32 localMaterialId;
if (polyIndex < boundGeom->GetNumPolygons() && polyIndex >= 0)
{
localMaterialId = boundGeom->GetPolygonMaterialIndex(polyIndex);
}
else
{
// This bound has no vert-edge material, so use its first material.
localMaterialId = 0;
}
#if PH_MATERIAL_ID_64BIT
nMaterialId = (u32)boundGeom->GetMaterialId(localMaterialId); // phContact stores only lower 32bits of materialId
#else
nMaterialId = boundGeom->GetMaterialId(localMaterialId);
#endif
//NOTE: We must compare only the lower 32bits of the material ID for the reason stated above (the contact does not store the other bits.)
physicsAssertf((u32)nMaterialId == (u32)groundMaterialId,"Expected material ids to be the same. nMaterialId: %u groundMaterialId: %u", (u32)nMaterialId, (u32)groundMaterialId);
}
#endif // __ASSERT
audPedFootStepAudio& footStepAudio = m_PedAudioEntity.GetFootStepAudio();
if(footStepAudio.HasStandingMaterialChanged(groundMaterialId, groundEntity, groundComponentIndex))
{
footStepAudio.ChangeStandingMaterial(groundMaterialId, groundEntity, groundComponentIndex);
}
}
void CPed::ProcessPreComputeHorseLowerLegImpact(phContactIterator &impacts)
{
#if __BANK
if(!sm_PreComputeHorseLowerLegImpact)
return;
#endif
const phInst* groundInstance = impacts.GetOtherInstance();
const Vec3V rearGroundPos = GetCorrectedGroundPosition(impacts.GetOtherPosition(),*groundInstance,impacts.GetOtherComponent());
Vec3V rearGroundNormal;
impacts.GetMyNormal(rearGroundNormal);
// Use the same logic as the front contact but assume the current ground normal is valid and that we don't have a ground physical. If we add normal filtering/ground physicals
// for the rear we can just pass that in.
if (IsGroundBetterThanCurrent(CPhysics::GetEntityFromInst(groundInstance), groundInstance, rearGroundPos, rearGroundNormal, impacts.GetOtherComponent(), NULL, m_vecRearGroundPos, true))
{
m_vecRearGroundNormal = rearGroundNormal;
m_vecRearGroundPos = rearGroundPos;
}
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Quadruped"));
}
void CPed::ProcessPreComputeHorsePropBlockerImpact(phContactIterator &impacts)
{
#if __BANK
if(!sm_PreComputeHorsePropBlockerImpact)
return;
#endif
// Don't catch on any unactivated objects. This means we'll clip into the object the first timeslice
// See CPed::ProcessPreComputePlayerLowerLegImpact for similar code.
// We may want make these two functions share some code but for now keep them separate.
if(impacts.GetOtherCollider() == NULL)
{
impacts.DisableImpact();
}
}
void CPed::ProcessPreComputeQuadrupedLowerLegImpact(phContactIterator &impacts, bool &rearImpactComputed)
{
#if __BANK
if(!sm_PreComputeQuadrupedLowerLegImpact)
return;
#endif
float newGroundZ = GetCorrectedGroundPosition(impacts.GetOtherPosition(),*impacts.GetOtherInstance(),impacts.GetOtherComponent()).GetZf();
//only care about the highest impact
if (!rearImpactComputed || m_fRearGroundZFromImpact<newGroundZ) {
impacts.GetMyNormal(m_vecRearGroundNormal);
m_fRearGroundZFromImpact = newGroundZ;
rearImpactComputed=true;
}
//m_vecRearNormal.Average(m_vecGroundNormal); disabled for physics version
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Quadruped"));
}
bool CPed::ShouldObjectBeImpossibleToPush(const CEntity& entity, const phInst& instance, const phCollider* pCollider, int component)
{
if(entity.GetIsTypeVehicle())
{
const CVehicle& vehicle = static_cast<const CVehicle&>(entity);
if( NetworkInterface::IsGameInProgress() && vehicle.GetDriver() )
{
return true;
}
if( (vehicle.InheritsFromQuadBike() || vehicle.InheritsFromAmphibiousQuadBike()) &&
vehicle.GetNumWheels() == 4 )
{
if( vehicle.GetTransform().GetUp().GetZf() < -0.7f )
{
return false;
}
}
if(vehicle.InheritsFromBike() && (pCollider == NULL || IsLessThanAll(MagSquared(pCollider->GetVelocity()),ScalarV(V_FLT_SMALL_1))))
{
// Bikes will fail the volume and mass checks below but we still want them to be immovable
if((static_cast<const CBike&>(vehicle).m_nBikeFlags.bOnSideStand || vehicle.GetDriver()))
{
return true;
}
}
if(( MI_CAR_BRIOSO2.IsValid() && vehicle.GetModelIndex() == MI_CAR_BRIOSO2 ) )
{
return true;
}
}
float mass;
if(pCollider)
{
// Great! the object has a collider so we know exactly what its solver mass is
mass = pCollider->GetMass(component);
}
else
{
// This is a bit tricky without a collider since we might be touching a slept articulated collider.
// We wouldn't want to look at the archetype mass since that includes all parts. Reach into the
// cache entry to figure out the link's mass. Without this, we will treat unlatched doors on slept
// vehicles as impossible to push.
if(IsFragInst(&instance) && static_cast<const fragInst&>(instance).GetCacheEntry())
{
const fragCacheEntry* cacheEntry = static_cast<const fragInst&>(instance).GetCacheEntry();
if(cacheEntry->GetHierInst()->body && cacheEntry->GetHierInst()->articulatedCollider)
{
mass = cacheEntry->GetHierInst()->articulatedCollider->GetMassArt(component);
}
else
{
mass = instance.GetArchetype()->GetMass();
}
}
else
{
mass = instance.GetArchetype()->GetMass();
}
}
// Check that the mass and volume of the object meet the threshold. There might be a better way of determining
// if an object should be immovable. We also might want to not make it a non-boolean return value.
// The issue at the moment is that peds get their velocity set each frame regardless of how much contacts slowed them
// down last frame. It takes a very large mass for a ped to not be able to move it. This means that peds can push
// objects 4x or more their weight with ease. This is an attempt to prevent that unrealistic behavior.
if(mass > ms_fMassOfExpectedHardToPushObjects)
{
const float volume = instance.GetArchetype()->GetBound()->GetBoundingBoxVolume().Getf();
if(volume > ms_fVolumeOfExpectedHardToPushObjects)
{
return true;
}
}
return false;
}
#if PLAYER_USE_LOWER_LEG_BOUND || PED_USE_CAPSULE_PROBES
bool CPed::CanPedMoveObjectWithLowerLegs(const phInst* instance, const phCollider* collider, int component)
{
float mass = FLT_MAX;
// Need to move peds on the first frame otherwise the lower leg bound gets inside of them and we pop when they activate!
// We need to then predict whether this ped will activate because if they aren't going to activate then we want to disable the collision so that the legs don't
// get caught up on whatever they are colliding against. This unfortunately involves duplicating all of the checks that are done in fragInstNMGta::PrepareForActivation
// and all of it's overridden versions.
const CEntity* pOtherEntity = CPhysics::GetEntityFromInst(instance);
if (pOtherEntity != NULL && pOtherEntity->GetIsTypePed())
{
const CPed* pOtherPed = static_cast<const CPed*>(pOtherEntity);
if (instance->GetClassType() == PH_INST_FRAG_PED)
{
// Dont move peds seated in vehicles ( related to small bikes, scooters, gocarts )
if( NetworkInterface::IsGameInProgress() && pOtherPed->GetMyVehicle() && pOtherPed->GetMyVehicle()->GetSeatManager() )
{
const s32 iSeat = pOtherPed->GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(pOtherPed);
if (pOtherPed->GetMyVehicle()->IsSeatIndexValid(iSeat))
{
return false;
}
}
const fragInstNMGta* pOtherFragInst = static_cast<const fragInstNMGta*>(instance);
fragPhysicsLOD* pOtherPhysicsLOD = pOtherFragInst->GetTypePhysics();
if (Verifyf(component < pOtherPhysicsLOD->GetNumChildren(), "Invalid child index %i on manifold with '%s'. Num Children: %i",component,pOtherFragInst->GetArchetype()->GetFilename(),pOtherPhysicsLOD->GetNumChildren()) &&
!pOtherFragInst->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE) && pOtherPhysicsLOD->GetBodyType() != NULL && pOtherFragInst->GetType() != NULL &&
pOtherFragInst->GetBroken() && !pOtherFragInst->GetType()->IsActivationDisabled() && pOtherFragInst->CheckCanActivate(pOtherPed, false) &&
(!pOtherPed->IsDead() || !pOtherPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollOnPedCollisionWhenDead)))
{
mass = pOtherPhysicsLOD->GetChild(component)->GetUndamagedMass();
}
}
else
{
if (!pOtherPed->GetIsAnyFixedFlagSet() && !instance->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE) && !pOtherPed->IsUsingKinematicPhysics())
{
mass = instance->GetArchetype()->GetMass();
}
}
}
else if (pOtherEntity != NULL && collider != NULL && pOtherEntity->GetIsTypeVehicle())
{
// We don't want to allow peds to push doors on vehicles with their lower legs. This makes it much smoother when walking up a ramp onto a plane.
mass = collider->GetMass();
}
// Only try to move non-ped objects if they have a collider. This means that for the first frame we won't try to push inactive objects.
// We do it this way because we don't have to try and predict if the object is going to activate. There are so many things that
// go into determining activation. Not pushing an object for one frame looks better than pushing an object that isn't moving
// consistently.
else if(instance != NULL && collider != NULL)
{
mass = collider->GetMass(component);
}
return mass < ms_fLargePedMass;
}
#endif // PLAYER_USE_LOWER_LEG_BOUND || PED_USE_CAPSULE_PROBES
#if PLAYER_USE_LOWER_LEG_BOUND
void CPed::ProcessPreComputePlayerLowerLegImpact(phContactIterator &impacts)
{
#if __BANK
if(!sm_PreComputePlayerLowerLegImpact)
return;
#endif
Assertf(!impacts.GetOtherInstance() || CPhysics::GetLevel()->LegitLevelIndex(impacts.GetOtherInstance()->GetLevelIndex()),"Non-null phys inst but invalid level index");
if (!CanPedMoveObjectWithLowerLegs(impacts.GetOtherInstance(), impacts.GetOtherCollider(), impacts.GetOtherComponent()))
{
if(IsPlayer())
{
GetPedAudioEntity()->HandlePreComputeContact(impacts);
}
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "LowerLeg cant move"));
}
else
{
// All contacts slide off the lower leg bounds
impacts.SetFriction(0.0f);
// Don't push up on stuff on top of the lower leg bound
Vec3V normal;
impacts.GetMyNormal(normal);
if(normal.GetZf() < 0.0f)
{
ZeroOutImpactZ(impacts);
impacts.SetDepth(Min(impacts.GetDepth(),0.01f));
}
}
}
#endif // PLAYER_USE_LOWER_LEG_BOUND
bool CPed::DoKeepImpactForMover(const phContactIterator &impacts, int& otherInstClassTypeOut) const
{
phInst* pOtherInstance = impacts.GetOtherInstance();
int otherInstClassType = 0;
if(pOtherInstance)
{
otherInstClassType = pOtherInstance->GetClassType();
if(otherInstClassType == phInst::PH_INST_GLASS)
{
return true;
}
}
// We aren't interested in messing with the constraints on the ped's capsule here, but we also don't want to accidentally
// treat them as collision contacts.
if(impacts.IsConstraint())
{
return true;
}
// We return this for performance reasons - phInst::GetClassType() is a virtual call
// so we need to minimize how many times we call it.
otherInstClassTypeOut = otherInstClassType;
return false;
}
bool CPed::CanDisableImpactForMover(phContactIterator &impacts)
{
phInst* pOtherInstance = impacts.GetOtherInstance();
Assert(pOtherInstance);
Assert(pOtherInstance->IsInLevel());
const fragInstNMGta* pRagdollInst = GetRagdollInst();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
const u32 otherInstTypeFlags = CPhysics::GetLevel()->GetInstanceTypeFlags(pOtherInstance->GetLevelIndex());
bool bRagdollImpact = impacts.GetMyInstance() == pRagdollInst;
// Disable impacts against an un-activated ragdoll inst if the other poly is marked to not activate ragdolls
if (bRagdollImpact)
{
if(PGTAMATERIALMGR->GetPolyFlagNoRagdoll(impacts.GetOtherMaterialId()))
{
return true;
}
// If this impact is on the ragdoll inst and the inst is tagged to collide with inactive and fixed collision, we want to signal the a.i. to trigger the running a.i. task to activate the ragdoll next frame
if(m_bShouldActivateRagdollOnCollision)
{
//allow explosions through, they should override the requested a.i. behaviour
if (!(otherInstTypeFlags&ArchetypeFlags::GTA_EXPLOSION_TYPE))
{
if(GetKeepInactiveRagdollContacts())
{
return false;
}
// don't activate on collision with the floor, the anim should be authored to cope with that
bool ragdollActivated = false;
Vec3V up(GetMatrix().c());
Vec3V normal;
impacts.GetOtherNormal(normal);
// Use a more aggressive penetration depth for the right hand when dead or dying and holding a weapon since that is the hand that pickups are dropped from and we don't
// want them dropping inside geometry!
// Also, by default, do not allow any penetration when colliding with other peds' ragdoll instances
float fAllowedPenetrationMultiplier = 1.0f;
if (pOtherInstance->GetClassType() == PH_INST_FRAG_PED)
{
fAllowedPenetrationMultiplier = 0.0f;
}
else if (impacts.GetMyComponent() == RAGDOLL_HAND_RIGHT && GetIsDeadOrDying() && GetWeaponManager() != NULL && GetWeaponManager()->GetEquippedWeaponObject() != NULL)
{
fAllowedPenetrationMultiplier = 0.15f;
}
bool bPassedNormalTest = true;
// No need to check collision normals when jumping out of vehicle, we always want to activate ragdoll
const bool bBeingJackedOnBicycle = (GetPedResetFlag(CPED_RESET_FLAG_BeingJacked) && GetMyVehicle() && GetMyVehicle()->GetVehicleType() == VEHICLE_TYPE_BICYCLE);
const bool bDontCheckNormals = GetPedResetFlag(CPED_RESET_FLAG_JumpingOutOfVehicle) || bBeingJackedOnBicycle;
float upDot = -1.0f;
if (!bDontCheckNormals)
{
upDot = Dot(normal, up).Getf();
bPassedNormalTest = upDot > m_fAllowedRagdollSlope;
}
nmEntityDebugf(this, "ActivateOnCollision: OtherEntity: %s(%p), OtherInstance: %s(%p), NormalTest - %s(upDot=%.3f, m_fAllowedRagdollSlope=%.3f), PartsTest - %s (part:%u, mask %u),PenetrationTest - %s (depth:%.3f, m_fAllowedRagdollPenetration: %.3f, fAllowedPenetrationMultiplier: %.3f), selfCollision: %s, excludedByType: %s",
pOtherEntity ? pOtherEntity->GetModelName() : "none", pOtherEntity,
pOtherInstance->GetArchetype()->GetFilename(), pOtherInstance,
bPassedNormalTest ? "PASSED" : "FAILED", upDot, m_fAllowedRagdollSlope,
((m_iAllowedRagdollPartsMask & BIT(impacts.GetMyComponent())) != 0) ? "PASSED" : "FAILED", BIT(impacts.GetMyComponent()), m_iAllowedRagdollPartsMask,
(impacts.GetDepth() > m_fAllowedRagdollPenetration * fAllowedPenetrationMultiplier) ? "PASSED" : "FAILED", impacts.GetDepth(), m_fAllowedRagdollPenetration, fAllowedPenetrationMultiplier,
pOtherEntity == this ? "YES" : "NO",
(otherInstTypeFlags & (ArchetypeFlags::GTA_PICKUP_TYPE | ArchetypeFlags::GTA_PROJECTILE_TYPE | ArchetypeFlags::GTA_FOLIAGE_TYPE)) ? "YES" : "NO"
);
// If we've got a non-up, non-self-collision impact with an allowed body part with a non-weapon that is sufficiently deep and (if the allowed slope is steeper
// than the default allowed slope) we're not impacting stairs then activate the ragdoll
if ( bPassedNormalTest &&
(m_iAllowedRagdollPartsMask & BIT(impacts.GetMyComponent())) != 0 &&
pOtherEntity != this &&
!(otherInstTypeFlags & (ArchetypeFlags::GTA_PICKUP_TYPE | ArchetypeFlags::GTA_PROJECTILE_TYPE | ArchetypeFlags::GTA_FOLIAGE_TYPE)) &&
impacts.GetDepth() > m_fAllowedRagdollPenetration * fAllowedPenetrationMultiplier &&
(bDontCheckNormals || m_fAllowedRagdollSlope <= ms_fActivateRagdollOnCollisionDefaultAllowedSlope || !PGTAMATERIALMGR->GetPolyFlagStairs(impacts.GetOtherMaterialId())))
{
bool bShouldActivateRagdollDueToCollision;
if(GetPedResetFlag(CPED_RESET_FLAG_IsVaulting))
{
bShouldActivateRagdollDueToCollision = CanActivateRagdollOnCollisionDuringClimb(pOtherEntity);
}
else
{
// don't activate on collision if the other vehicle / ground is almost still, unless we are moving fast (jumping out)
bShouldActivateRagdollDueToCollision = !CanDisableImpactForEnterExitVehicle(pOtherEntity, pOtherInstance);
}
if (GetMyVehicle())
{
const s32 iMySeat = GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(this);
if (GetMyVehicle()->IsSeatIndexValid(iMySeat))
{
const CVehicleSeatAnimInfo* pSeatAnimInfo = GetMyVehicle()->GetSeatAnimationInfo(iMySeat);
if (pSeatAnimInfo && pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle())
{
bool bDontDetachOnWorldOrPedCollision = false;
const CVehicleSeatInfo* pSeatInfo = GetMyVehicle()->GetSeatInfo(iMySeat);
if (pSeatInfo)
{
bDontDetachOnWorldOrPedCollision = pSeatInfo->GetDontDetachOnWorldCollision();
}
bShouldActivateRagdollDueToCollision = CanActivateRagdollOnCollisionWhenOnVehicle(pOtherEntity, bDontDetachOnWorldOrPedCollision);
}
}
}
// activate the ragdoll here
if (bShouldActivateRagdollDueToCollision && CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_ACTIVATE_ON_COLLISION))
{
CEvent* pEvent = m_pActivateRagdollOnCollisionEvent;
if (pEvent)
{
// a ragdoll event to use has been specified. Use it.
GetPedIntelligence()->AddEvent(*pEvent);
}
else
{
//No event provided by the calling task. Generate a default ragdoll event.
nmEntityDebugf(this, "Adding nm balance task: Activate ragdoll on collision: Default ragdoll event.");
CEventSwitch2NM event(500, rage_new CTaskNMBalance(500, 10000, NULL, 0));
GetPedIntelligence()->AddEvent(event);
}
SetActivateRagdollOnCollision(false);
ragdollActivated = true;
}
}
// Disable the impact if we didn't activate the ragdoll
if (!ragdollActivated)
{
return true;
}
}
}
}
// This is where we can hook up event generation for (animated) peds colliding against foliage bounds.
if(otherInstTypeFlags & ArchetypeFlags::GTA_FOLIAGE_TYPE)
{
// If the bound hit was actually a composite, check that the child bound that we hit is really a foliage bound.
bool bReallyFoliageBound = true;
if(pOtherInstance->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pOtherInstance->GetArchetype()->GetBound());
if((pBoundComposite->GetTypeFlags(impacts.GetOtherComponent()) & ArchetypeFlags::GTA_FOLIAGE_TYPE) == 0)
{
bReallyFoliageBound = false;
}
}
if(bReallyFoliageBound)
{
if(pOtherEntity && pOtherEntity->GetBaseModelInfo())
{
SetContactedFoliageHash(pOtherEntity->GetBaseModelInfo()->GetModelNameHash());
}
#if DEBUG_DRAW
if(CPhysics::ms_bShowFoliageImpactsPeds)
{
grcDebugDraw::Sphere(impacts.GetMyPosition(), 0.2f, Color_yellow);
if(pOtherEntity && pOtherEntity->GetBaseModelInfo())
{
char pedDebug[256] = "";
sprintf(pedDebug, "%x = %s", pOtherEntity->GetBaseModelInfo()->GetModelNameHash(), pOtherEntity->GetBaseModelInfo()->GetModelName());
grcDebugDraw::Text(VEC3V_TO_VECTOR3(impacts.GetMyPosition()) + Vector3(0.f,0.f,0.5f), Color_yellow, pedDebug);
}
}
#endif // DEBUG_DRAW
// Set a reset flag which we can use for AI events, audio, etc.
SetPedResetFlag(CPED_RESET_FLAG_InContactWithFoliage, true);
const float fFoliageRadius = CPhysics::GetFoliageRadius(pOtherInstance, impacts.GetOtherComponent(), impacts.GetOtherElement());
if( fFoliageRadius > CPed::ms_fLargeFoliageRadius )
{
SetPedResetFlag(CPED_RESET_FLAG_InContactWithBIGFoliage, true);
}
// Now that we've noted the collision, disable the impact.
return true;
}
else
{
SetContactedFoliageHash(g_NullSoundHash);
}
}
else if(otherInstTypeFlags & ArchetypeFlags::GTA_DEEP_SURFACE_TYPE)
{
// If the bound hit was actually a composite, check that the child bound that we hit is really a deep surface bound.
bool bReallyDeepSurfaceBound = true;
if(pOtherInstance->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pOtherInstance->GetArchetype()->GetBound());
if((pBoundComposite->GetTypeFlags(impacts.GetOtherComponent()) & ArchetypeFlags::GTA_DEEP_SURFACE_TYPE) == 0)
{
bReallyDeepSurfaceBound = false;
}
}
if(bReallyDeepSurfaceBound)
{
// Set a reset flag which we can use for AI events, audio, etc.
SetPedResetFlag(CPED_RESET_FLAG_InContactWithDeepSurface, true);
}
}
#if PED_APPLY_HORSE_PROP_BLOCKER
if (impacts.GetMyComponent() == 4) //TMS: Basic hardcoding - needs flags or similar to fix properly
{
if (!s_bEnableBlockerBound)
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Blocker"));
return true;
}
}
#endif //PED_APPLY_HORSE_PROP_BLOCKER
//! Disable blocker when jumping for horses.
if (m_pCapsuleInfo->IsQuadruped() &&
(impacts.GetMyComponent() == 3 || impacts.GetMyComponent() == 4) &&
GetPedResetFlag(CPED_RESET_FLAG_IsJumping) &&
!GetPedResetFlag(CPED_RESET_FLAG_IsLanding))
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Blocker"));
return true;
}
// Disable collisions against our own vehicle if we're exiting it
if (pOtherEntity)
{
//! don't collide with objects that are attached to our climb physical if we are climbing. fixes being able to physically move trailers
//! etc when climbing on vehicles.
CPhysical *pClimbPhysical = GetClimbPhysical();
if(pClimbPhysical && (pOtherEntity->GetAttachParent()==pClimbPhysical || pClimbPhysical->GetAttachParent()==pOtherEntity))
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Climb Attachment"));
return true;
}
if (pOtherEntity->GetIsTypeVehicle())
{
if (CVehicle::ShouldDisableImpactForPedExitingVehicle(*this, *static_cast<CVehicle*>(pOtherEntity), impacts.GetOtherComponent()))
{
return true;
}
if (CanPedBeKnockedOverByVehicle(this, static_cast<CVehicle*>(pOtherEntity), impacts))
{
if (pOtherEntity && !bRagdollImpact)
{
// B* 1600388 - Avoid peds being pinged up in the air by reactivation of thier capsule collision with the car whilst getting up.
CVehicle* pVeh = static_cast<CVehicle*>(pOtherEntity);
static float s_minVelDiff = 10.0f;
static float s_minContactDepth = 0.40f;
if (pVeh->InheritsFromAutomobile() // only for cars
&& pVeh!=GetGroundPhysical() // ignore vehicles we're resting on
&& pVeh->GetDriver() && pVeh->GetDriver()->IsPlayer() // only for cars driven by players
&& impacts.GetDepth()>s_minContactDepth // must be actually penetrating the capsule
&& (pVeh->GetVelocity() - RCC_VECTOR3(GetGroundVelocity())).Mag2()>s_minVelDiff // make sure the car is moving sufficiently fast
&& GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_GET_UP) // only when the ped is getting up
)
{
// if the entity matrix point is inside the bounding box
spdAABB tempBox;
const spdAABB &bb = pVeh->GetBoundBox(tempBox);
if (bb.ContainsPoint(GetMatrix().d()))
{
nmEntityDebugf(this, "Starting high fall task for deep capsule penetration with speeding player vehicle");
aiTask* pTaskNM = rage_new CTaskNMHighFall(1000, NULL, CTaskNMHighFall::HIGHFALL_FROM_CAR_HIT);
CEventSwitch2NM event(10000, pTaskNM);
GetPedIntelligence()->AddEvent(event);
SetNoCollision(pVeh, NO_COLLISION_RESET_WHEN_NO_IMPACTS);
}
}
}
return !bRagdollImpact;
}
else
{
// Trying to allow a capsule impact so make sure the ped isn't lying down and midway through a car.
// This can happen as capsule impacts may be disabled as a car starts to run over a ped, but will suddenly become re-enabled as the ped decides it doesn't want its ragdoll activated.
CVehicle* pVeh = static_cast<CVehicle*>(pOtherEntity);
if(!bRagdollImpact && pVeh->GetVehicleType() == VEHICLE_TYPE_CAR && !pVeh->IsUpsideDown() && !pVeh->IsOnItsSide() &&
pVeh->GetTransform().GetC().GetZf() > 0 && pVeh->HasContactWheels() && GetPedType() != PEDTYPE_ANIMAL &&
!IsPlayer() && GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_USE_SCENARIO))
{
// See if the root of the ped skeleton is somewhere near the bottom of the vehicle bounding box.
Vector3 vPedPos;
GetBonePosition(vPedPos, BONETAG_ROOT);
Vector3 vPedPosLocal = vPedPos;
Matrix34 matCar = MAT34V_TO_MATRIX34(pVeh->GetMatrix());
matCar.UnTransform(vPedPosLocal);
float fMinZ = pVeh->GetBoundingBoxMin().z;
float fMaxZ = fMinZ + 0.5f;
if(vPedPosLocal.z <= fMaxZ && vPedPosLocal.z >= fMinZ)
{
// See if the ped root is in the vehicle bounding box.
spdAABB tempBox;
const spdAABB &bb = pVeh->GetBoundBox(tempBox);
if (bb.ContainsPoint(VECTOR3_TO_VEC3V(vPedPos)))
{
// Do disable this contact otherwise the collision will force the ped up and into the air.
return true;
}
}
}
return bRagdollImpact;
}
}
else if (pOtherEntity->GetIsTypePed() && ((CPed*)pOtherEntity)->IsPlayer())
{
// collision between ped capsule bounds. If I am going to activate my ragdoll as a result of a collision
// with this guy, don't collide with the ped capsule
if ((impacts.GetMyInstance() == GetAnimatedInst()) && (static_cast<CPed*>(pOtherEntity)->GetAnimatedInst() == pOtherInstance))
{
if ( CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_IMPACT_PLAYER_PED, pOtherEntity) )
{
nmEntityDebugf(this, "Disabling capsule collision with ped %s(%p) for ragdoll activation", pOtherEntity->GetModelName(), pOtherEntity);
return true;
}
else
{
// if we're not going to ragdoll from this contact, note that we're being pushed by the player.
// We'll use this to force activation if continuously pushed for a while.
if (pOtherEntity->GetCollider())
{
Vec3V vel = pOtherEntity->GetCollider()->GetVelocity();
Vec3V normal;
impacts.GetOtherNormal(normal);
float dotVel = Dot(vel, normal).Getf();
if (!IsNetworkClone() && dotVel>=0.0f)
{
SetPedResetFlag(CPED_RESET_FLAG_CapsuleBeingPushedByPlayerCapsule, true);
}
}
}
}
}
}
return false;
}
bool IsHittingSmallOrPushableObject(const CPhysical *pPhysical, const phInst* pHitInst)
{
if(!pPhysical || !pHitInst)
return false;
if(!pPhysical->GetIsAnyFixedFlagSet() && !pPhysical->GetIsTypeVehicle())
{
//! Disable auto-vaulting onto objects that don't weigh much. Note: Non frag objects have a ridiculous mass
//! setup atm, hence the crazy high numbers here.
static dev_float s_sMassCanPush = 200.0f;
static dev_float s_sFragMassCanPush = 100.0f;
static dev_float s_sMaxBoundsXZSize = 2.0f;
fragInst * pFragInst = pPhysical->GetFragInst();
float fMassToCheck = s_sMassCanPush;
if(pFragInst)
{
bool bUprooted = pPhysical->GetIsTypeObject() ? static_cast<const CObject*>(pPhysical)->m_nObjectFlags.bHasBeenUprooted : false;
if(!bUprooted)
fMassToCheck = s_sFragMassCanPush;
}
float fMassOfCollision = pHitInst->GetArchetype()->GetMass();
if (fMassOfCollision < fMassToCheck)
{
Vector3 vSize = VEC3V_TO_VECTOR3(pHitInst->GetArchetype()->GetBound()->GetBoundingBoxSize());
float fBoundXZSize = vSize.x * vSize.z;
if (fBoundXZSize < s_sMaxBoundsXZSize)
{
return true;
}
}
}
return false;
}
bool CPed::CanDisableImpactForEnterExitVehicle(const CEntity* pOtherEntity, phInst* pOtherInstance)
{
const bool bInVehicle = GetIsInVehicle();
const bool bBlockRagdollActivation = (GetPedResetFlag(CPED_RESET_FLAG_BlockRagdollActivationInVehicle) || !GetActivateRagdollOnCollision()) ? true : false;
if (bBlockRagdollActivation && !bInVehicle)
{
return true;
}
bool bInteractingWithVehicle = GetPedResetFlag(CPED_RESET_FLAG_IsEnteringOrExitingVehicle) || GetPedConfigFlag(CPED_CONFIG_FLAG_JustLeftVehicleNeedsReset) || GetIsInVehicle();
if (bInteractingWithVehicle && pOtherEntity && pOtherEntity->GetIsPhysical() &&
IsHittingSmallOrPushableObject(static_cast<const CPhysical*>(pOtherEntity), pOtherInstance))
{
return true;
}
if (bInVehicle)
{
// Dont collide with your own pillion passenger when getting jacked off a bike
if (GetMyVehicle()->GetVehicleType() == VEHICLE_TYPE_BIKE)
{
if (pOtherEntity && pOtherEntity->GetIsTypePed())
{
const CPed* pOtherPed = static_cast<const CPed*>(pOtherEntity);
if (pOtherPed->GetMyVehicle() == GetMyVehicle() && pOtherPed->GetIsInVehicle())
{
return true;
}
}
//If we're getting off the bike in first person, then see if theres anything between camera position and ped's head
const CTaskExitVehicle* pExitVehicleTask = (const CTaskExitVehicle*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE);
if (IsLocalPlayer() && pExitVehicleTask && (IsFirstPersonShooterModeEnabledForPlayer(false) || IsInFirstPersonVehicleCamera()))
{
Vector3 vEnd = camInterface::GetPos();
Vector3 vStart = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
TUNE_GROUP_BOOL(FIRST_PERSON_BIKE_TUNE, bUseCustomLengthProbeTest, true);
if (bUseCustomLengthProbeTest)
{
TUNE_GROUP_BOOL(FIRST_PERSON_BIKE_TUNE, bUseFixedLength, false);
TUNE_GROUP_FLOAT(FIRST_PERSON_BIKE_TUNE, fProbeLength, 2.0f, 0.0f, 100.0f, 0.1f);
TUNE_GROUP_BOOL(FIRST_PERSON_BIKE_TUNE, bUsePercentageIncrease, true);
TUNE_GROUP_FLOAT(FIRST_PERSON_BIKE_TUNE, fProbeLengthIncreasePercentage, 50.0f, 0.0f, 1000.0f, 1.0f);
Vector3 vDirection = vEnd - vStart;
float fLength = vDirection.Mag2();
if (bUseFixedLength)
{
fLength = fProbeLength;
}
else if (bUsePercentageIncrease)
{
fLength += fLength * fProbeLengthIncreasePercentage / 100.0f;
}
vDirection.Normalize();
vEnd = vStart + vDirection * fLength;
}
int nTestTypes = ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_OBJECT_TYPE;
WorldProbe::CShapeTestFixedResults<> probeResults;
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&probeResults);
probeDesc.SetExcludeEntity(GetMyVehicle(), WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS);
probeDesc.SetIncludeFlags(nTestTypes);
probeDesc.SetTypeFlags(ArchetypeFlags::GTA_AI_TEST);
probeDesc.SetContext(WorldProbe::LOS_GeneralAI);
probeDesc.SetStartAndEnd(vStart, vEnd);
#if __BANK
CTask::ms_debugDraw.AddLine(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), Color_cyan, 1000);
#endif //__BANK
if (WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
AI_LOG_WITH_ARGS("Enabling impact for %s as probe test hit something between 1st person camera and ped's head", AILogging::GetDynamicEntityNameSafe(this));
return false;
}
}
}
if (bBlockRagdollActivation)
{
return true;
}
const s32 iMySeat = GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(this);
if (GetMyVehicle()->IsSeatIndexValid(iMySeat))
{
const CVehicleSeatAnimInfo* pSeatAnimInfo = GetMyVehicle()->GetSeatAnimationInfo(iMySeat);
if (pSeatAnimInfo && pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle())
{
return false;
}
}
}
if(GetPedResetFlag(CPED_RESET_FLAG_IsGoingToStandOnExitedVehicle))
{
return false;
}
if (bInteractingWithVehicle)
{
const bool bBeingJackedOnBicycle = (GetPedResetFlag(CPED_RESET_FLAG_BeingJacked) && GetMyVehicle() && GetMyVehicle()->GetVehicleType() == VEHICLE_TYPE_BICYCLE);
bool bTestVelocity = !GetPedResetFlag(CPED_RESET_FLAG_JumpingOutOfVehicle) && !bBeingJackedOnBicycle;
TUNE_GROUP_FLOAT(RAGDOLL_ON_EXIT_TUNE, MIN_PED_SPEED_TO_TRIGGER_RAGDOLL, 3.0f, 0.0f, 10.0f, 0.01f);
if (pOtherEntity)
{
if (pOtherEntity->GetIsPhysical())
{
if (pOtherEntity->GetIsTypePed())
{
const CPed* pOtherPed = static_cast<const CPed*>(pOtherEntity);
if (pOtherPed->IsInjured() || pOtherPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringOrExitingVehicle) || pOtherPed->IsNetworkClone())
{
return true;
}
}
// Maybe this should be force based
else if (bTestVelocity && static_cast<const CPhysical*>(pOtherEntity)->GetVelocity().Mag2() < rage::square(CTaskVehicleFSM::ms_Tunables.m_MinPhysSpeedToActivateRagdoll))
{
return true;
}
else
{
bTestVelocity = false;
}
}
}
if (bTestVelocity)
{
const float fSpeedMultiplier = CAnimSpeedUps::ShouldUseMPAnimRates() ? CAnimSpeedUps::ms_Tunables.m_MultiplayerEnterExitJackVehicleRateModifier : 1.0f;
const float fMinPhysSpeedToActivateRagdoll = fSpeedMultiplier * CTaskVehicleFSM::ms_Tunables.m_MinPedSpeedToActivateRagdoll;
if (GetVelocity().Mag2() < rage::square(fMinPhysSpeedToActivateRagdoll))
{
return true;
}
}
}
if( pOtherEntity &&
pOtherEntity->GetIsTypeVehicle() )
{
const CVehicle* pVehicle = static_cast< const CVehicle* >( pOtherEntity );
if( pVehicle->HasRamp() &&
pVehicle->GetVelocity().Mag2() > 100.0f )
{
return true;
}
}
return false;
}
bool CPed::CanActivateRagdollOnCollisionDuringClimb(const CEntity* pOtherEntity)
{
if (!GetPedResetFlag(CPED_RESET_FLAG_IsVaulting) || !GetActivateRagdollOnCollision())
{
return false;
}
CPhysical *pClimbPhysical = GetClimbPhysical(true);
if(!pClimbPhysical)
{
return false;
}
//! If our entity is sleeping or fixed, don't activate.
if(pClimbPhysical->GetIsAnyFixedFlagSet() || pClimbPhysical->IsAsleep())
{
return false;
}
if(pOtherEntity && pOtherEntity->GetIsPhysical())
{
//! don't ragdoll if we hit something that is attached to our climb physical.
if(pClimbPhysical && (pOtherEntity->GetAttachParent()==pClimbPhysical || pClimbPhysical->GetAttachParent()==pOtherEntity))
{
return false;
}
//! If other entity is a physical, but isn't a vehicle or ped return false.
if(!pOtherEntity->GetIsTypeVehicle() && !pOtherEntity->GetIsTypePed())
{
return false;
}
//! Don't ragdoll against dead peds.
if(pOtherEntity->GetIsTypePed())
{
const CPed *pOtherPed = static_cast<const CPed*>(pOtherEntity);
if(pOtherPed->IsDead())
{
return false;
}
}
}
return true;
}
bool CPed::CanActivateRagdollOnCollisionWhenOnVehicle(const CEntity* pOtherEntity, const bool bDontDetachOnWorldOrPedCollision)
{
if (pOtherEntity)
{
if(bDontDetachOnWorldOrPedCollision && (pOtherEntity->GetIsTypeBuilding() || pOtherEntity->GetIsTypeAnimatedBuilding() || pOtherEntity->GetIsTypePed()))
{
return false;
}
if(m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_NEVER && !IsInjured())
{
return false;
}
// B*3185795 - Ignore collisions with car in the back of Wastelander
if (pOtherEntity->GetIsTypeVehicle() && pOtherEntity->GetIsAttached())
{
CVehicle* pMyVeh = GetVehiclePedInside();
if (pMyVeh && MI_CAR_WASTELANDER.IsValid() && pMyVeh->GetModelIndex() == MI_CAR_WASTELANDER && pOtherEntity->GetAttachParent() == pMyVeh)
{
return false;
}
}
if (!pOtherEntity->GetIsTypePed())
{
return true;
}
const float fVelSqd = static_cast<const CPed*>(pOtherEntity)->GetVelocity().Mag2();
TUNE_GROUP_FLOAT(RAGDOLL_TUNE, MIN_VEL_TO_TRIGGER_RAGDOLL, 5.0f, 0.0f, 20.0f, 0.01f);
if (fVelSqd > square(MIN_VEL_TO_TRIGGER_RAGDOLL))
{
return true;
}
}
return false;
}
bool CPed::ZeroOutImpactZ(phContactIterator &impacts, ScalarV epsilon)
{
Vec3V vMyNormal;
impacts.GetMyNormal(vMyNormal);
// Adjust contact normal so that resultant impulse at this contact point does not cause an upward push to the player.
vMyNormal.SetZ(ScalarV(V_ZERO));
if (IsGreaterThanAll(MagSquared(vMyNormal), epsilon) != 0)
{
vMyNormal = Normalize(vMyNormal);
impacts.SetMyNormal(vMyNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "FlattenedMyNormal", vMyNormal));
return true;
}
else
{
// If a bad normal comes back then disable the impact (implies impact is only upward).
impacts.DisableImpact();
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "BadNormal"));
return false;
}
}
void CPed::ProcessPreComputeImpactsForRagdoll(phContactIterator& impacts)
{
#if __BANK
if(!sm_PreComputeImpactsForRagdoll)
return;
#endif
const bool bValidNMAgentID = GetRagdollInst()->GetNMAgentID() >= 0;
const CObject* pWeaponObj = GetWeaponManager() ? GetWeaponManager()->GetEquippedWeaponObject() : NULL;
const CWeapon *equippedWeapon = GetWeaponManager() ? GetWeaponManager()->GetEquippedWeapon() : NULL;
const bool bValidWeapon = equippedWeapon && equippedWeapon->GetClipComponent() && equippedWeapon->GetClipComponent()->GetDrawable() &&
equippedWeapon->GetClipComponent()->GetDrawable()->GetCurrentPhysicsInst();
const bool bIsSkiing = GetIsSkiing();
const bool bIsSwimming = GetIsSwimming();
const bool bShouldFlattenNormals = m_pCapsuleInfo->ShouldFlattenCollisionNormals();
bool bKickCounterImpulseAppliedThisFrame = false;
bool bRequiresFixStuckInGeometry = false;
// !!!! GTAV HACK !!!!
bool bIsTouchingFiretruckBumper = false;
bool bIsTouchingMapGeom = false;
impacts.ResetToFirstActiveContact();
while(!impacts.AtEnd())
{
PDR_ONLY(debugPlayback::RecordContact(impacts));
phInst *otherInst = impacts.GetOtherInstance();
if(!otherInst || !otherInst->IsInLevel())
{
// Constraints with the world are expected to have a NULL otherInst so don't disable it, but the code below appears to not
// be doing anything with constraints so we might as well skip to the next impact.
if (!impacts.IsConstraint())
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "!IsConstraint"));
impacts.DisableImpact();
}
impacts.NextActiveContact();
continue;
}
CEntity* pHitEntity = CPhysics::GetEntityFromInst(otherInst);
// Disable the injuredOnGround behavior if hit by a car
if (otherInst->GetClassType() == PH_INST_FRAG_VEH)
{
Vec3V vNormal;
impacts.GetMyNormal(vNormal);
Vec3V vecUp(V_Z_AXIS_WZERO);
ScalarV upDot = Dot(vNormal, vecUp);
CVehicle* pVehicle = static_cast<CVehicle*>(otherInst->GetUserData());
static ScalarV s_UpDotForVehicleCrush(-0.8f);
static ScalarV s_UpDotForOnVehicle(0.6f);
if (bValidNMAgentID)
{
GetPedIntelligence()->GetCombatBehaviour().DisableInjuredOnGroundBehaviour();
bool bAllowDownwardLegImpacts = false;
if( pVehicle &&
!pVehicle->GetIsAquatic() ) // Don't do this for boats as it makes them move too much if the ped ragdolls on top of them.
{
Assert(pVehicle->GetIsTypeVehicle());
CTaskNMBrace::ApplyInverseMassScales(impacts, pVehicle);
}
// If we were recently associated with a car, try to eject from the vehicle gracefully
if (pVehicle && GetRagdollInst()->IsAssociatedWithVehicle(pVehicle))
{
GetRagdollInst()->SetRagdollEjectedFromVehicle(pVehicle);
}
// Check if being crushed by a train
if (pVehicle && pVehicle->InheritsFromTrain() && impacts.GetDepth()>=0.0f && GetCollider())
{
// NOTE: Since we don't use last-safe-matrices against non-fixed objects it's possible to get tunneling on extremities.
// This is exaggerated by only-push-on-last-timeslice. Wait for FixStuckInGeometry to run before assuming the ped is
// being crushed.
if ((upDot < s_UpDotForVehicleCrush).Getb() && impacts.GetContact().GetLifetime() > 1)
{
// Check if the ragdoll is close to the bottom of the train
static float sfMaxHeightAboveBottomForCrush = 0.5f;
spdAABB tempBox;
const spdAABB &box = pVehicle->GetLocalSpaceBoundBox(tempBox);
float fTrainBottom = pVehicle->GetBoundCentre().z - 0.5f*(box.GetMaxVector3().z - box.GetMinVector3().z);
float fPedDistAboveTrainBottom = GetCollider()->GetPosition().GetZf() - fTrainBottom;
if (fPedDistAboveTrainBottom < sfMaxHeightAboveBottomForCrush)
{
SetPedResetFlag(CPED_RESET_FLAG_IsTrainCrushingRagdoll, true);
GetRagdollInst()->SetRagdollEjectedFromVehicle(pVehicle);
}
}
}
// Being ejected from deep contact with the chassis of a vehicle
if (pVehicle && GetRagdollInst()->IsEjectingFromVehicle() && GetRagdollInst()->GetEjectingVehicle() == pVehicle)
{
if( CPhysics::ms_bInStuntMode &&
IsNetworkClone() )
{
vehicleDisplayf( "CPed::ProcessPreComputeImpactsForRagdoll: disabling impact between clone ragdoll: %s and vehicle: %s", GetNetworkObject()->GetLogName(), pVehicle->GetNetworkObject() ? pVehicle->GetNetworkObject()->GetLogName() : "none" );
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
bool bIsArticulatedPart = false;
// Don't mess with impact normals on articulated parts of vehicles
if(const phCollider* pOtherCollider = impacts.GetOtherCollider())
{
if(pOtherCollider->IsArticulated())
{
if(static_cast<const phArticulatedCollider*>(pOtherCollider)->GetLinkFromComponent(impacts.GetOtherComponent()) > 0)
{
bIsArticulatedPart = true;
}
}
}
if (!bIsArticulatedPart)
{
// Create a normal towards the side of the car the ped is on
Vec3V vehSide = pVehicle->GetMatrix().GetCol0();
Vec3V vecCarToPed = Subtract(GetTransform().GetPosition(), pVehicle->GetTransform().GetPosition());
vNormal = Scale(vehSide, Dot(vehSide, vecCarToPed));
// Normalize and set
vNormal = NormalizeSafe(vNormal, Vec3V(V_X_AXIS_WONE));
impacts.SetMyNormal(vNormal);
// Scale the depth so that the push is gradual
static float sfDepth = 0.04f;
NOTFINAL_ONLY(nmEntityDebugf(this, "EjectingFromVehicle - scale down contact depth for ejecting vehicle %s(%p) - oldDepth: %.4f, newDepth: %.4f", pVehicle->GetDebugName(), pVehicle, impacts.GetDepth(),Min(impacts.GetDepth(), pVehicle->GetIsSmallDoubleSidedAccessVehicle() ? CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeEjectMaxImpactDepth : sfDepth)));
impacts.SetDepth(Min(impacts.GetDepth(), pVehicle->GetIsSmallDoubleSidedAccessVehicle() ? CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeEjectMaxImpactDepth : sfDepth));
if (pVehicle->GetIsSmallDoubleSidedAccessVehicle() && impacts.GetDepth()>0.0f)
{
NOTFINAL_ONLY(nmEntityDebugf(this, "EjectingFromVehicle - scale up friction for ejecting from bike %s(%p) - oldFriction: %.4f, newFriction: %.4f", pVehicle->GetDebugName(), pVehicle, impacts.GetFriction(),Max(impacts.GetFriction(), CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeEjectImpactFriction)));
impacts.SetFriction(Max(impacts.GetFriction(), CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeEjectImpactFriction));
bAllowDownwardLegImpacts = true;
}
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Scale depth for ejecting from vehicle", "true"));
}
}
else
{
CTask* pTask = GetPedIntelligence()->GetTaskActiveSimplest();
if (pTask && pTask->GetTaskType()==CTaskTypes::TASK_NM_BRACE)
{
CTaskNMBrace* pBraceTask = static_cast<CTaskNMBrace*>(pTask);
bAllowDownwardLegImpacts = pBraceTask->IsUsingUnderVehicleReaction();
//check the other inst isn't valid for walking on
if (otherInst->GetUserData() && !((CVehicle*)otherInst->GetUserData())->CanPedsStandOnTop())
{
float newFriction = impacts.GetFriction()*pBraceTask->GetCollisionFrictionMult();
Assertf(newFriction <= 100.0f, "CPed::ProcessPreComputeImpactsForRagdoll - bad friction from brace task. impacts.GetFriction() is %f, pBraceTask->GetCollisionFrictionMult() is %f",
impacts.GetFriction(), pBraceTask->GetCollisionFrictionMult());
impacts.SetFriction(newFriction);
impacts.SetElasticity(impacts.GetElasticity()*pBraceTask->GetCollisionElasticityMult());
float normalPitch = pBraceTask->GetNormalPitch();
// don't mess with the normals that are in completely the wrong direction
if (
(normalPitch>0.0f && upDot.Getf() > -0.3f) ||
(normalPitch<0.0f && upDot.Getf() < 0.3f)
)
{
//Rotate the impact normal by the specified amount
// make an axis around which to rotate the normal
Vec3V vecVel(V_ZERO);
phCollider *pOtherCollider = impacts.GetOtherCollider();
if(pOtherCollider)
{
vecVel = pOtherCollider->GetLocalVelocity(GetRagdollInst()->GetPosition().GetIntrin128());
}
Vec3V vecAxis = Cross(vecVel, vecUp);
vecAxis = NormalizeSafe(vecAxis,Vec3V(V_ZERO));
if(!IsZeroAll(vecAxis))
{
QuatV rotation = QuatVFromAxisAngle(vecAxis, ScalarV(normalPitch));
Vec3V vNewNormal;
vNewNormal = Transform(rotation, vNormal);
impacts.SetMyNormal(vNewNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "NewNormal", vNewNormal));
}
}
}
}
else if (pTask && pTask->GetTaskType()==CTaskTypes::TASK_NM_THROUGH_WINDSCREEN)
{
// if this is the vehicle we're being ejected from, change the inverse mass scales and disable downward
// contacts on the legs for a while at the beginning of the task.
CTaskNMThroughWindscreen* pBehaviour = static_cast<CTaskNMThroughWindscreen*>(pTask);
if (pBehaviour->GetAllowDisablingContacts() && pVehicle && pVehicle==pBehaviour->GetEjectVehicle()
&& ((fwTimer::GetTimeInMilliseconds() - pBehaviour->GetStartTime()) < CTaskNMThroughWindscreen::sm_Tunables.m_ClearVehicleTimeMS)
)
{
switch (pVehicle->GetVehicleType())
{
case VEHICLE_TYPE_BICYCLE: CTaskNMThroughWindscreen::sm_Tunables.m_BicycleInverseMassScales.Apply(&impacts); break;
case VEHICLE_TYPE_BIKE: CTaskNMThroughWindscreen::sm_Tunables.m_BikeInverseMassScales.Apply(&impacts); break;
default: CTaskNMThroughWindscreen::sm_Tunables.m_DefaultInverseMassScales.Apply(&impacts); break;
}
// Disallow any contacts that are either pointed down or towards the car
Vec3V vNormal;
impacts.GetMyNormal(vNormal);
Vec3V vToCar = pVehicle->GetTransform().GetPosition();
vToCar = Subtract(vToCar, GetTransform().GetPosition());
if(IsLessThanAll(vNormal.GetZ(), ScalarV(V_ZERO)) != 0 || IsLessThanAll(Dot(vNormal, vToCar), ScalarV(V_ZERO)) != 0)
{
// Add a collision record so that we still generate vehicle damage on this ragdoll.
AddCollisionRecordBeforeDisablingContact(GetRagdollInst(), pVehicle, impacts);
NOTFINAL_ONLY(nmEntityDebugf(this, "CTaskNMThroughWindscreen - disable contact downward or into vehicle %s(%p)", pVehicle->GetDebugName(), pVehicle));
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "CTaskNMThroughWindscreen", "disable contact downward or into vehicle"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
bAllowDownwardLegImpacts = true; // Skip the check below since we've already screened those impacts
}
else
{
bAllowDownwardLegImpacts = CTaskNMBrace::ShouldUseUnderVehicleReaction(this, pVehicle);
}
}
else if (pVehicle)
{
bAllowDownwardLegImpacts = CTaskNMBrace::ShouldUseUnderVehicleReaction(this, pVehicle);
}
int pedComponent = impacts.GetMyComponent();
// Hitting a ped at high speeds is picking up unwanted downward contacts due to deep impacts hitting multiple car parts - a problem
// that doesn't happen with a smaller physics timestep.
// To work around that we're disabling such downward contacts to the legs of living humans. The net desired effect is that humans
// hit by a car will tend to roll over the top instead of getting sucked under.
if(pVehicle && GetIsDeadOrDying() && !bAllowDownwardLegImpacts && !GetPedConfigFlag(CPED_CONFIG_FLAG_AttachedToVehicle))
{
static float sfFastRevVel = 0.0f;
float fRevlVelMagSq = (GetVelocity() - pVehicle->GetVelocity()).Mag2();
if (fRevlVelMagSq > sfFastRevVel*sfFastRevVel && pedComponent >= RAGDOLL_THIGH_LEFT && pedComponent <= RAGDOLL_FOOT_RIGHT) // The legs
{
Vec3V vNormal;
impacts.GetMyNormal(vNormal);
if(IsLessThanAll(vNormal.GetZ(), ScalarV(V_ZERO)) != 0)
{
// Add a collision record so that we still generate vehicle damage on this ragdoll.
AddCollisionRecordBeforeDisablingContact(GetRagdollInst(), pVehicle, impacts);
NOTFINAL_ONLY(nmEntityDebugf(this, "disable downward leg contact with vehicle %s(%p)", pVehicle->GetDebugName(), pVehicle));
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "TaskNmBrace", "disable downward leg contact"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
if (!CTaskNMBrace::ExcludeComponentForVehicleTrap(pedComponent) && impacts.GetDepth()>=0.0f)
{
if ((upDot < s_UpDotForVehicleCrush).Getb())
{
SetPedResetFlag(CPED_RESET_FLAG_VehicleCrushingRagdoll, true);
}
else if ((upDot > s_UpDotForOnVehicle).Getb())
{
SetPedResetFlag(CPED_RESET_FLAG_RagdollOnVehicle, true);
}
}
}
}
else // Rage ragdolls
{
CTask* pTask = GetPedIntelligence()->GetTaskActiveSimplest();
if (pTask && pTask->GetTaskType()==CTaskTypes::TASK_RAGE_RAGDOLL)
{
CTaskRageRagdoll* pBehaviour = static_cast<CTaskRageRagdoll*>(pTask);
if (pBehaviour && impacts.GetDepth() > 0.0f)
{
// Inform the rage ragdoll task of the contact normal and entity, so that it can run an appropriate reaction
pBehaviour->SetHitEntityImpactNormal(pHitEntity, vNormal);
}
}
}
// !!!! GTAV HACK !!!!
// Detect when a ped ragdoll is touching fixed map geometry and the front bumper of a firetruck.
// Disable ragdoll fix stuck in geometry and impacts with the bumper in this case to prevent the ragdoll from getting popped through thin map geometry and into inaccessible interiors.
if(pVehicle->GetModelId() == MI_CAR_FIRETRUCK)
{
if(impacts.GetOtherComponent() == pVehicle->GetVehicleFragInst()->GetComponentFromBoneIndex(pVehicle->GetBoneIndex(VEH_BUMPER_F)))
{
bIsTouchingFiretruckBumper = true;
}
}
}
// !!!! GTAV HACK !!!!
if(otherInst->GetArchetype()->GetTypeFlag(ArchetypeFlags::GTA_MAP_TYPE_MOVER))
{
bIsTouchingMapGeom = true;
}
// This is where we can hook up event generation for ragdolls colliding against foliage bounds.
if(otherInst->GetArchetype()->GetTypeFlag(ArchetypeFlags::GTA_FOLIAGE_TYPE))
{
// If the bound hit was actually a composite, check that the child bound that we hit is really a foliage bound.
bool bReallyFoliageBound = true;
if(otherInst->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(otherInst->GetArchetype()->GetBound());
if((pBoundComposite->GetTypeFlags(impacts.GetOtherComponent()) & ArchetypeFlags::GTA_FOLIAGE_TYPE) == 0)
{
bReallyFoliageBound = false;
}
}
if(bReallyFoliageBound)
{
#if DEBUG_DRAW
if(CPhysics::ms_bShowFoliageImpactsRagdolls)
grcDebugDraw::Sphere(impacts.GetMyPosition(), 0.2f, Color_red);
#endif // DEBUG_DRAW
// Set a reset flag which we can use for AI events, audio, etc.
SetPedResetFlag(CPED_RESET_FLAG_InContactWithFoliage, true);
const float fFoliageRadius = CPhysics::GetFoliageRadius(otherInst, impacts.GetOtherComponent(), impacts.GetOtherElement());
if( fFoliageRadius > CPed::ms_fLargeFoliageRadius )
{
SetPedResetFlag(CPED_RESET_FLAG_InContactWithBIGFoliage, true);
}
// Now that we've noted the collision, disable the impact.
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "bReallyFoliageBound"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
else if(otherInst->GetArchetype()->GetTypeFlag(static_cast<u32>(ArchetypeFlags::GTA_DEEP_SURFACE_TYPE)))
{
// If the bound hit was actually a composite, check that the child bound that we hit is really a deep surface bound.
bool bReallyDeepSurfaceBound = true;
if(otherInst->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(otherInst->GetArchetype()->GetBound());
if((pBoundComposite->GetTypeFlags(impacts.GetOtherComponent()) & ArchetypeFlags::GTA_DEEP_SURFACE_TYPE) == 0)
{
bReallyDeepSurfaceBound = false;
}
}
if(bReallyDeepSurfaceBound)
{
if(GetRagdollInst()->GetCached())
{
eAnimBoneTag eBoneTag = GetBoneTagFromBoneIndex(GetRagdollInst()->GetCacheEntry()->GetComponentToBoneIndexMap()[impacts.GetMyComponent()]);
if(eBoneTag != BONETAG_INVALID)
{
// Set a reset flag which we can use for AI events, audio, etc.
SetPedResetFlag(CPED_RESET_FLAG_InContactWithDeepSurface, true);
float fWeight;
crFrameFilter* pFrameFilter = g_FrameFilterDictionaryStore.FindFrameFilter(BONEMASK_UPPERONLY);
if(pFrameFilter == NULL || !pFrameFilter->FilterDof(kTrackBoneRotation, (u16)eBoneTag, fWeight))
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "bReallyDeepSurfaceBound"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
}
}
// Disable impacts between the hands and a held gun
if(pWeaponObj && pWeaponObj->IsCollisionEnabled() && pWeaponObj->GetCurrentPhysicsInst())
{
// Also ignore the weapon clip since that is often located within the grip
phInst *clip = NULL;
if (bValidWeapon)
{
clip = equippedWeapon->GetClipComponent()->GetDrawable()->GetCurrentPhysicsInst();
}
if (otherInst == pWeaponObj->GetCurrentPhysicsInst() || otherInst == clip)
{
int myComponent = impacts.GetMyComponent();
if ((ms_RagdollWeaponDisableCollisionMask & (1 << myComponent)) != 0)
{
// Favor bringing the gun to the hand via inverse mass scales.
// This seems to enforce the constraint a bit better and has the effect of ragdolls holding up two-handed weapons better one-handed
if (impacts.IsConstraint())
{
static float massPed = 0.3f;
impacts.SetMassInvScales(massPed,1.0f-massPed);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "SetMassInvScales", massPed));
}
else
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "HAND+!IsConstraint"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
else if (!impacts.IsConstraint())
{
// Resolve impact depth slowly to avoid popping...
static bank_float s_fSlowPushMax = 0.04f;
float fAllowedPenetration = CPhysics::GetSimulator()->GetAllowedPenetration();
const phCollider* pMyCollider = impacts.GetMyCollider();
if (pMyCollider != NULL)
{
fAllowedPenetration += pMyCollider->GetExtraAllowedPenetration();
}
float fExcessPenetration = impacts.GetDepth() - fAllowedPenetration;
if (fExcessPenetration > s_fSlowPushMax)
{
impacts.SetDepth(fAllowedPenetration + s_fSlowPushMax);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "WeaponRagdollDepthMod(B)", impacts.GetDepth()));
}
}
}
}
// Need a bit of a hack here to increase ragdoll friction when skiing or else the ped slides all the way down
if (bIsSkiing)
{
impacts.SetFriction(sfSkiRagdollFriction);
}
int iClassType = otherInst->GetClassType();
// Friction handling
if (GetDeathState() == DeathState_Dead && m_RagdollCorpseFriction != -1.0f)
{
impacts.SetFriction(m_RagdollCorpseFriction);
}
else if (GetDeathState() == DeathState_Dead && (m_bKickedByPlayer || m_bPreviouslyKickedByPlayer || m_uRagdollPlayerLegContactTime == fwTimer::GetTimeInMilliseconds()))
{
// Enforce a lower minimum friction for dead ragdolls when being kicked.
if (otherInst->GetClassType() == PH_INST_PED)
{
impacts.SetFriction(Max(impacts.GetFriction(), sfKickedPedRagdollFriction));
}
else
{
impacts.SetFriction(Max(impacts.GetFriction(), sfKickedMapRagdollFriction));
}
}
else if (!bValidNMAgentID)
{
if((iClassType == PH_INST_MAPCOL) || (iClassType == PH_INST_FRAG_BUILDING) || (iClassType == PH_INST_BUILDING))
{
// Enforce a minimum friction between dead ragdolls and the map.
// It looks better and any extra sliding from snow, etc. always comes back as a bug.
impacts.SetFriction(Max(impacts.GetFriction(), sfDefaultMapRagdollFriction));
}
}
// We don't really want peds trying to stand back up on bikes/quad-bikes so make them nearly frictionless in the hope that the ped will slide off...
static dev_float sfMinSpeedForFrictionlessRagdollImpact = 1.0f;
if (impacts.GetMyComponent() <= RAGDOLL_SPINE3 && pHitEntity != NULL && pHitEntity->GetIsTypeVehicle() &&
(static_cast<CVehicle*>(pHitEntity)->InheritsFromBike() || static_cast<CVehicle*>(pHitEntity)->InheritsFromQuadBike() || static_cast<CVehicle*>(pHitEntity)->InheritsFromAmphibiousQuadBike() || CVehicle::IsMowerModelId(pHitEntity->GetModelId())) &&
static_cast<CVehicle*>(pHitEntity)->GetVelocityIncludingReferenceFrame().XYMag2() < square(sfMinSpeedForFrictionlessRagdollImpact))
{
static dev_float sfTorsoAndLowerBodyRagdollImpactFriction = 0.05f;
impacts.SetFriction(sfTorsoAndLowerBodyRagdollImpactFriction);
}
// Disable impacts with kinematic peds, and instead apply a force relative to the depth of the impact. We do this because otherwise
// the infinitely massive kinematic ped could force a ragdoll through a wall, etc.
if(pHitEntity != NULL && pHitEntity != this && pHitEntity->GetIsTypePed())
{
CPed* pOtherPed = static_cast<CPed*>(pHitEntity);
bool bOtherPedSyncedScene = false;
if (pOtherPed->GetAnimDirector() != NULL)
{
fwAnimDirectorComponentSyncedScene* pComponentSyncedScene = pOtherPed->GetAnimDirector()->GetComponent<fwAnimDirectorComponentSyncedScene>();
if (pComponentSyncedScene != NULL && pComponentSyncedScene->IsPlayingSyncedScene())
{
bOtherPedSyncedScene = true;
}
}
if(!bOtherPedSyncedScene && !pOtherPed->GetUsingRagdoll() && (pOtherPed->GetDeathState() != DeathState_Dead) && !GetPedConfigFlag(CPED_CONFIG_FLAG_IsBeingDraggedToSafety) && !GetIsSwimming())
{
// Apply a counter-impulse to the player when kicking ragdolls!
// Don't apply while in cover as it causes the player to look jerky
bool bValidGround = pOtherPed->ValidateGroundPhysical(*impacts.GetMyInstance(), impacts.GetMyPosition(), impacts.GetMyComponent());
if(!bValidGround && !bKickCounterImpulseAppliedThisFrame && !(pOtherPed->GetIsInCover() || pOtherPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringCover)))
{
Vector3 vImpulse;
impacts.GetOtherImpulse(vImpulse);
if (vImpulse.XYMag2() > SMALL_FLOAT)
{
Vector3 vVelocityDirection = pOtherPed->GetVelocity();
vVelocityDirection.z = 0.0f;
if (vVelocityDirection.Mag2() > SMALL_FLOAT)
{
vVelocityDirection.Normalize();
float fCounterImpulseStrength = ms_CounterRagdollImpulseStrength;
if (IsDead())
{
fCounterImpulseStrength *= ms_DeadPedCounterRagdollImpulseStrengthModifier;
}
pOtherPed->ApplyImpulse(-vVelocityDirection * fCounterImpulseStrength * vImpulse.XYMag(), VEC3_ZERO, 0);
bKickCounterImpulseAppliedThisFrame = true;
}
}
}
DoKickPed(impacts, bValidGround);
}
if (pOtherPed->IsUsingKinematicPhysics() && !CTaskNMBehaviour::CanUseRagdoll(pOtherPed, IsPlayer() ? RAGDOLL_TRIGGER_IMPACT_PLAYER_PED_RAGDOLL : RAGDOLL_TRIGGER_IMPACT_PED_RAGDOLL, this) && pOtherPed->GetVelocity().Mag2()>0.05f)
{
Vec3V normal;
impacts.GetMyNormal(normal);
Vec3V partVelocity;
partVelocity.ZeroComponents();
phArticulatedBody* pBody = GetRagdollInst() ? GetRagdollInst()->GetArticulatedBody() : NULL;
if ( pBody )
{
partVelocity = pBody->GetLinearVelocity(impacts.GetMyComponent());
}
if (Dot(partVelocity, normal).Getf()>-0.3f)
{
// only disable the contact if we're not moving toward the impact
nmEntityDebugf(pHitEntity, "Disabling contact with ped %s(%p) as the ped is kinematic (replacing with a force instead).", pOtherPed->GetModelName(), pOtherPed);
// Apply a force to replace the contact
static float fMomentum = 150.0f;
ApplyForce(fMomentum * VEC3V_TO_VECTOR3(normal) * impacts.GetDepth(),
VEC3V_TO_VECTOR3(impacts.GetMyPosition()) - VEC3V_TO_VECTOR3(GetTransform().GetPosition()),
impacts.GetMyComponent());
AddCollisionRecordBeforeDisablingContact(GetRagdollInst(), pHitEntity, impacts);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "partVelocity"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
if(bShouldFlattenNormals)
{
if(pHitEntity && pHitEntity->GetIsTypeVehicle())
{
CVehicle* pOtherVehicle = static_cast<CVehicle*>(pHitEntity);
//! sconde. Hack. We don't want submerged bikes pushing us up as it makes us stand on them which doesn't look very good!
//! If normal is pointing up, flatten it and clamp depth.
if(pOtherVehicle->InheritsFromBike() && (bIsSwimming || static_cast<CBike*>(pOtherVehicle)->IsUnderwater()))
{
#if __DEV
static ScalarV s_BikeModDepthThreshold = ScalarV(0.1f);
// static ScalarV s_BikeModNormalThreshold = ScalarV(V_QUARTER);
#else
static const ScalarV s_BikeModDepthThreshold = ScalarV(0.1f);
// static const ScalarV s_BikeModNormalThreshold = ScalarV(V_QUARTER);
#endif
Vec3V vNormal;
impacts.GetMyNormal(vNormal);
if(IsGreaterThanAll(vNormal.GetZ(), ScalarV(V_QUARTER)) != 0)
{
vNormal.SetZ(ScalarV(V_QUARTER));
ScalarV fFlatMag2 = MagSquared(vNormal);
if (IsGreaterThanAll(fFlatMag2, ScalarV(V_FLT_SMALL_6)) != 0)
{
vNormal = Normalize(vNormal);
impacts.SetMyNormal(vNormal);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "BikeNormalMod", vNormal));
if (IsGreaterThanAll(impacts.GetDepthV(), s_BikeModDepthThreshold) != 0)
{
impacts.SetDepth(s_BikeModDepthThreshold);
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "BikeDepthMod", impacts.GetDepth()));
}
}
else
{
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "Bike"));
impacts.DisableImpact();
impacts.NextActiveContact();
continue;
}
}
}
}
}
if (IsPlayer() && !ShouldBeDead() && pHitEntity!=this)
{
Vector3 impulse;
impacts.GetImpulse(impulse);
if (IsPlayer() && impulse.Mag2()>CTaskGetUp::sm_Tunables.m_fInjuredGetupImpulseMag2)
{
SetPedConfigFlag(CPED_CONFIG_FLAG_PreferInjuredGetup, true);
}
}
// Don't call FixStuckInGeometry if we're only deeply penetrating other peds/ragdolls.
if(!bRequiresFixStuckInGeometry && iClassType != PH_INST_PED && iClassType != PH_INST_FRAG_PED)
{
if(impacts.GetDepth() > CPed::ms_fMinDepthForFixStuckInGeometry)
{
bRequiresFixStuckInGeometry = true;
}
}
impacts.NextActiveContact();
}
// !!!! GTAV HACK !!!!
// If the ragdoll is touching both the map geometry and a firetruck's front bumper, disable any impacts with the bumper and prevent fix stuck in geometry from running.
if(bIsTouchingFiretruckBumper && bIsTouchingMapGeom && NetworkInterface::IsGameInProgress())
{
impacts.ResetToFirstActiveContact();
while(!impacts.AtEnd())
{
phInst *otherInst = impacts.GetOtherInstance();
CEntity* pHitEntity = CPhysics::GetEntityFromInst(otherInst);
if(pHitEntity && pHitEntity->GetIsTypeVehicle() && pHitEntity->GetModelId() == MI_CAR_FIRETRUCK)
{
if(impacts.GetOtherComponent() == pHitEntity->GetFragInst()->GetComponentFromBoneIndex(static_cast<CVehicle *>(pHitEntity)->GetBoneIndex(VEH_BUMPER_F)))
{
impacts.DisableImpact();
}
}
impacts.NextActiveContact();
}
bRequiresFixStuckInGeometry = false;
}
// We found a deep enough contact so we need to check that the ragdoll isn't stuck
if((bRequiresFixStuckInGeometry && GetCollider() != NULL) BANK_ONLY(|| CPed::ms_bAlwaysFixStuckInGeometry))
{
impacts.ResetToFirstActiveContact();
GetRagdollInst()->FixStuckInGeometry(impacts,PH_INST_FRAG_PED);
}
}
dev_float PED_GROUND_POS_HISTORY_DIST_SQR = 0.25f*0.25f;
// NOTE: These values (below) assume gravity factor = 1.0 for all peds!
dev_bool sbForceIsOnStairs = false;
//Quadruped specific spring forces
void CPed::ApplyQuadrupedSpring(float fInvTimeStep, float fOffset, const Vector3 &rvSpringPos, const Vector3 &rvForcePos, const Vector3 &rvGroundNormal, float &fOldSpringForceInOut, const CQuadrupedSpringTuning &rTuning)
{
phCollider *pCollider = GetCollider();
phInst *pInst = GetCurrentPhysicsInst();
phArchetype *pArchetype = pInst->GetArchetype();
const float kfMass = pArchetype->GetMass();
const float fFPSSlowDown = Min(fInvTimeStep * (1.0f / 60.0f),1.0f);
//Spring force
fOffset += m_pCapsuleInfo->GetGroundToRootOffset();
float fShallowForcePower = Clamp(fOffset, -rTuning.m_fShallowSpringForceRange, rTuning.m_fShallowSpringForceRange);
const float kVelocityForScaling = Min(pCollider ? VEC3V_TO_VECTOR3(pCollider->GetVelocity()).Mag() : 0.0f, rTuning.m_fMaxVelocityForScaling);
float fVelocityScalingFactor = 1.0f + rTuning.m_fVelocityFactor * kVelocityForScaling * fFPSSlowDown;
// B*2039800: We use a higher force value for player-controlled animals so that they adjust to slopes more quickly.
float fShallowSpringForce = IsPlayer() ? 250.0f : rTuning.m_fShallowSpringForce;
// Calculate the maximum force this spring can apply.
float fTotalSpringForce = (fShallowForcePower * fShallowSpringForce * fVelocityScalingFactor) * kfMass;
// Now scale the maximum allowed force by how far the offset is from zero to get the force to apply.
// So the closer to the desired offset, the larger the force becomes until we reach the desired offset and the spring force completely opposes any further vertical motion.
// A value of 1.0f for the (1.0f + fOffset) term counteracts the bodies mass (0.5 represents this spring's contribution to the total,will need to change if we have more probes...)
fTotalSpringForce = (1.0f + fOffset) * (fTotalSpringForce * -0.5f);
//We can tune a limit for spring force increases to help reduce jitter
//when climbing sharp but short inclines
float kfMaxthisFrame = -1.0f;
if (rTuning.m_fMaxForceIncreasePerSecond > 0.0f)
{
kfMaxthisFrame = fOldSpringForceInOut + rTuning.m_fMaxForceIncreasePerSecond * TIME.GetSeconds() * kfMass;
fTotalSpringForce = Clamp(fTotalSpringForce, -kfMaxthisFrame, kfMaxthisFrame);
fOldSpringForceInOut = Abs(fTotalSpringForce);
}
Vector3 force(CPhysics::GetSimulator()->GetGravity());
force.Scale(pArchetype->GetGravityFactor());
force.Scale(fTotalSpringForce);
//Damping
Vector3 vecGroundVelocity = pCollider ? VEC3V_TO_VECTOR3(pCollider->GetLocalVelocity(rvSpringPos, 0)) : GetVelocity();
// might want separate damping coefficients for rebound (as opposed to compression, only do rebound damping if we want to stick to the floor)
float fDamping = rvGroundNormal.Dot(vecGroundVelocity);
float fDampingScale = 1.0f;
if (rTuning.m_fDampingDropOffRange > 0.0f)
{
fDampingScale = 1.0f - Min(Abs(fOffset), rTuning.m_fDampingDropOffRange) * 1.0f / rTuning.m_fDampingDropOffRange;
}
fDamping *= fDampingScale * rTuning.m_fSpringDamping * pArchetype->GetGravityFactor() * fFPSSlowDown;
Vector3 vDamping = Vector3(0.0f, 0.0f, fDamping) * GetMass();
DR_ONLY(debugPlayback::RecordQuadrupedSpringForce(pInst, RCC_VEC3V(rvForcePos), RCC_VEC3V(force), RCC_VEC3V(vDamping), kfMaxthisFrame, fOffset));
force += vDamping;
//Application
ApplyForce(force, rvForcePos, 0, false);
}
//Quadruped specific spring forces
void CPed::ApplyHorseSpring(float fInvTimeStep, float fOffset, const Vector3 &rvSpringPos, const Vector3 &rvForcePos, const Vector3 &rvGroundNormal, float &fOldSpringForceInOut, const CQuadrupedSpringTuning &rTuning)
{
phCollider *pCollider = GetCollider();
phInst *pInst = GetCurrentPhysicsInst();
phArchetype *pArchetype = pInst->GetArchetype();
const float kfMass = pArchetype->GetMass();
const float fFPSSlowDown = Min(fInvTimeStep * (1.0f / 60.0f),1.0f);
//Spring force
float fTotalSpringForce = Clamp(fOffset, -rTuning.m_fShallowSpringForceRange, rTuning.m_fShallowSpringForceRange);
const float kVelocityForScaling = Min(pCollider ? VEC3V_TO_VECTOR3(pCollider->GetVelocity()).Mag() : 0.0f, rTuning.m_fMaxVelocityForScaling);
float fVelocityScalingFactor = 1.0f + rTuning.m_fVelocityFactor * kVelocityForScaling * fFPSSlowDown;
fTotalSpringForce *= rTuning.m_fShallowSpringForce * fVelocityScalingFactor;
fTotalSpringForce = (1.0f + fTotalSpringForce) * -0.5f * kfMass; //1.0f counteracts the bodies mass (0.5 will need to change if we have more probes...)
//We can tune a limit for spring force increases to help reduce jitter
//when climbing sharp but short inclines
float kfMaxthisFrame = -1.0f;
if (rTuning.m_fMaxForceIncreasePerSecond > 0.0f)
{
kfMaxthisFrame = fOldSpringForceInOut + rTuning.m_fMaxForceIncreasePerSecond * TIME.GetSeconds() * kfMass;
fTotalSpringForce = Clamp(fTotalSpringForce, -kfMaxthisFrame, kfMaxthisFrame);
fOldSpringForceInOut = Abs(fTotalSpringForce);
}
Vector3 force(CPhysics::GetSimulator()->GetGravity());
force.Scale(pArchetype->GetGravityFactor());
force.Scale(fTotalSpringForce);
//Damping
Vector3 vecGroundVelocity = pCollider ? VEC3V_TO_VECTOR3(pCollider->GetLocalVelocity(rvSpringPos, 0)) : GetVelocity();
// might want separate damping coefficients for rebound (as opposed to compression, only do rebound damping if we want to stick to the floor)
float fDamping = rvGroundNormal.Dot(vecGroundVelocity);
float fDampingScale = 1.0f;
if (rTuning.m_fDampingDropOffRange > 0.0f)
{
fDampingScale = 1.0f - Min(Abs(fOffset), rTuning.m_fDampingDropOffRange) * 1.0f / rTuning.m_fDampingDropOffRange;
}
fDamping *= fDampingScale * rTuning.m_fSpringDamping * pArchetype->GetGravityFactor() * fFPSSlowDown;
Vector3 vDamping = Vector3(0.0f, 0.0f, fDamping) * GetMass();
DR_ONLY(debugPlayback::RecordQuadrupedSpringForce(pInst, RCC_VEC3V(rvForcePos), RCC_VEC3V(force), RCC_VEC3V(vDamping), kfMaxthisFrame, fOffset));
force += vDamping;
//Application
pCollider->ApplyForce(force,rage::Add(pCollider->GetPosition(),RCC_VEC3V(rvForcePos)).GetIntrin128ConstRef(),Invert(ScalarVFromF32(fInvTimeStep)).GetIntrin128ConstRef(),0);
}
ScalarV_Out ComputePedSpringOffset(Vec3V_In pointOnGround, Vec3V_In groundNormal, Vec3V_In springEnd)
{
return InvScaleSafe(Dot(Subtract(pointOnGround,springEnd),groundNormal),groundNormal.GetZ(),ScalarV(V_ZERO));
}
void CPed::ProcessPedStanding(float fTimeStep, phCollider* pCollider)
{
#if PED_USE_CAPSULE_PROBES
#if PDR_ENABLED
debugPlayback::SetTaggedDataCollectionEvent *pPDRInfo=0;
{
debugPlayback::FilterToEvent<debugPlayback::PedCapsuleInfoEvent> filter;
if (filter.IsSet())
{
pPDRInfo= debugPlayback::RecordDataCollection( GetCurrentPhysicsInst(), "StandingInfo" );
if (pPDRInfo)
{
pPDRInfo->AddBool("ProcessProbes", GetPedResetFlag(CPED_RESET_FLAG_ProcessProbesWhenExtractingZ));
pPDRInfo->AddBool("IsStanding(Start)", GetIsStanding());
pPDRInfo->AddBool("ValidGroundNormal", m_bValidGroundNormal);
pPDRInfo->AddBool("IsRagdoll", GetUsingRagdoll());
pPDRInfo->AddBool("IsLanding", GetPedResetFlag(CPED_RESET_FLAG_IsLanding));
pPDRInfo->AddFloat("GroundOffset", m_fGroundOffsetForPhysics);
pPDRInfo->AddBool("UseExtractedZ", GetUseExtractedZ());
}
}
}
#endif
if(GetUsingRagdoll())
{
return;
}
phInst* pInst = GetCurrentPhysicsInst();
if(!pInst->IsInLevel())
{
return;
}
if(!pCollider)
{
return;
}
Vec3V forceToApplyV(V_ZERO);
bool mayHaveForceToApply = false;
bool bCountPedAsStanding = GetIsStanding() || GetPedResetFlag(CPED_RESET_FLAG_IsLanding) || GetPedResetFlag(CPED_RESET_FLAG_ProcessProbesWhenExtractingZ);
if (m_pCapsuleInfo->IsQuadruped() && (!GetUseExtractedZ() || GetPedResetFlag(CPED_RESET_FLAG_ProcessProbesWhenExtractingZ) || GetPedResetFlag(CPED_RESET_FLAG_IsLanding)))
{
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_DisableQuadrupedSpring) && !GetIsSwimming())
{
// TODO: This part could still use optimizations.
int iProbesOnGround = 0;
#if ENABLE_HORSE
if(GetHorseComponent())
{
const Mat34V& pedMatrix = pInst->GetMatrix();
const Vec3V groundToRootVector = Negate(Scale(pedMatrix.GetCol2(),ScalarVFromF32(m_pCapsuleInfo->GetGroundToRootOffsetRef())));
const ScalarV probeLocalYOffset = ScalarVFromF32(m_pCapsuleInfo->GetProbeYOffset());
//Added torque has a much greater effect at lower framerates so the slower the framerate the
const float kInvTimeStep = 1.0f / fTimeStep;
const float fFPSSlowDown = Min(kInvTimeStep * (1.0f / 60.0f),1.0f);
float fCentreOffset = m_pCapsuleInfo->GetProbeYOffset()*s_QuadrupedSpringTuning.m_fExtraOffsetForTorque * fFPSSlowDown;
const ScalarV maxGroundAllowance = ScalarV(V_TWO);
//SPRING FORCE
//Front legs
const Vec3V frontPosStart = AddScaled(pedMatrix.GetCol3(),pedMatrix.GetCol1(),probeLocalYOffset);
const ScalarV frontOffset = ComputePedSpringOffset(m_vecGroundPos,RCC_VEC3V(m_vecGroundNormal),rage::Add(frontPosStart,groundToRootVector));
if (IsLessThanAll(Abs(frontOffset),maxGroundAllowance))
{
Vector3 forceOffsetFront(0.0f, fCentreOffset, 0.0f);
forceOffsetFront.RotateZ(GetCurrentHeading());
ApplyHorseSpring(kInvTimeStep, frontOffset.Getf(), RCC_VECTOR3(frontPosStart), forceOffsetFront, m_vecGroundNormal, m_fSpringForceFront, s_QuadrupedSpringTuning);
++iProbesOnGround;
}
//Back legs
const Vec3V rearPosStart = SubtractScaled(pedMatrix.GetCol3(),pedMatrix.GetCol1(),probeLocalYOffset);
const ScalarV rearOffset = ComputePedSpringOffset(m_vecRearGroundPos,m_vecRearGroundNormal,rage::Add(rearPosStart,groundToRootVector));
if (IsLessThanAll(Abs(rearOffset),maxGroundAllowance))
{
Vector3 forceOffsetBack(0.0f, -fCentreOffset, 0.0f);
forceOffsetBack.RotateZ(GetCurrentHeading());
ApplyHorseSpring(kInvTimeStep, rearOffset.Getf(), RCC_VECTOR3(rearPosStart), forceOffsetBack, RCC_VECTOR3(m_vecRearGroundNormal), m_fSpringForceBack, s_QuadrupedSpringTuning);
++iProbesOnGround;
}
}
else
#endif
{
const float kfGroundToRootOffset = m_pCapsuleInfo->GetGroundToRootOffset();
//SPRING FORCE
//Front legs
const Matrix34& capsuleMtx = RCC_MATRIX34(pInst->GetMatrix()); //MAT34V_TO_MATRIX34(GetMatrix());
Vector3 frontPos = capsuleMtx.d + (capsuleMtx.b * m_pCapsuleInfo->GetProbeYOffset());
float fOffset = m_fGroundZFromImpact - frontPos.z;
//Added torque has a much greater effect at lower framerates so the slower the framerate the
const float kInvTimeStep = 1.0f / fTimeStep;
const float fFPSSlowDown = Min(kInvTimeStep * (1.0f / 60.0f),1.0f);
float fCentreOffset = m_pCapsuleInfo->GetProbeYOffset()*s_QuadrupedSpringTuning.m_fExtraOffsetForTorque * fFPSSlowDown;
const float kfMaxGroundAllowance = 2.0f;
const float kfFrontSep = kfGroundToRootOffset + fOffset;
if (Abs(kfFrontSep) < kfMaxGroundAllowance)
{
Vector3 forceOffsetFront(0.0f, fCentreOffset, 0.0f);
forceOffsetFront.RotateZ(GetCurrentHeading());
ApplyQuadrupedSpring(kInvTimeStep, fOffset, frontPos, forceOffsetFront, m_vecGroundNormal, m_fSpringForceFront, s_QuadrupedSpringTuning);
++iProbesOnGround;
}
//Back legs
Vector3 backPos = capsuleMtx.d - (capsuleMtx.b * m_pCapsuleInfo->GetProbeYOffset());
fOffset = m_fRearGroundZFromImpact - backPos.z;
const float kfRearSep = kfGroundToRootOffset + fOffset;
if (Abs(kfRearSep) < kfMaxGroundAllowance)
{
Vector3 forceOffsetBack(0.0f, -fCentreOffset, 0.0f);
forceOffsetBack.RotateZ(GetCurrentHeading());
ApplyQuadrupedSpring(kInvTimeStep, fOffset, backPos, forceOffsetBack, RCC_VECTOR3(m_vecRearGroundNormal), m_fSpringForceBack, s_QuadrupedSpringTuning);
++iProbesOnGround;
}
}
//Check to see if we're hanging in space, only do this once we've detected the horse on the ground correctly
if (iProbesOnGround == 2)// || GetPedResetFlag(CPED_RESET_FLAG_IsJumping))
{
m_iFailedQPedRagdollTests = 0;
}
else if (!GetPedResetFlag(CPED_RESET_FLAG_IsJumping))
{
if (m_iFailedQPedRagdollTests > -1)
{
++m_iFailedQPedRagdollTests;
if (m_iFailedQPedRagdollTests == kNumFramesBeforeQPedRagdoll)
{
m_iFailedQPedRagdollTests = 0;
if(CTaskNMRelax::CanUseRagdoll(this, RAGDOLL_TRIGGER_FALL))
{
const CSeatManager *pSeatManager = GetSeatManager();
if(pSeatManager)
{
for (int i=0 ; i<pSeatManager->GetMaxSeats() ; i++)
{
CPed *pRider = pSeatManager->GetPedInSeat(i);
if (pRider)
{
CEventSwitch2NM event(10000, rage_new CTaskNMRelax(1000, 10000));
pRider->SwitchToRagdoll(event);
}
}
}
//Set in NM
CEventSwitch2NM event(10000, rage_new CTaskNMRelax(1000, 10000));
SwitchToRagdoll(event);
// fix an issue with player controlled quadrupeds aborting the ragdoll
// incorrectly when this event fires.
if (IsAPlayerPed())
{
CTaskFall* pFallTask = static_cast<CTaskFall*>(GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_FALL));
if (pFallTask)
{
pFallTask->SetDontAbortRagdollOnCleanUp();
}
}
}
}
}
}
}
else
{
PDR_ONLY(debugPlayback::RecordInstLabel(*pInst, "Jumping", "CPED_RESET_FLAG_IsJumping set true"));
}
}
else if(bCountPedAsStanding && (m_fGroundOffsetForPhysics > -2.0f) && DEV_ONLY(CPhysics::ms_bDoPedProbesWithImpacts &&) (!GetUseExtractedZ() || GetPedResetFlag(CPED_RESET_FLAG_ProcessProbesWhenExtractingZ)))
{
CTaskMotionBase* pTask = GetCurrentMotionTask();
const phArchetype& arch = *pInst->GetArchetype();
// Note: would benefit from being able to load more of these straight into a vector registers:
const ScalarV groundOffsetForPhysicsV = LoadScalar32IntoScalarV(m_fGroundOffsetForPhysics);
const ScalarV groundToRootOffsetV = LoadScalar32IntoScalarV(m_pCapsuleInfo->GetGroundToRootOffsetRef());
const ScalarV standingSpringStrengthV = ScalarVFromF32(pTask->GetStandingSpringStrength());
const ScalarV gravityFactorV = ScalarV(arch.GetGravityFactor());
const ScalarV massV = ScalarV(arch.GetMass());
const Vec3V gravityV = RCC_VEC3V(CPhysics::GetSimulator()->GetGravity());
const ScalarV oneV(V_ONE);
const ScalarV zeroV(V_ZERO);
const ScalarV maxStandRatioV(V_NINE); // 9.0f - was 10.0f when it included gravity
// get intersection along probe a distance centered from default standing height
// float fStandRatio = m_fGroundOffsetForPhysics + m_pCapsuleInfo->GetGroundToRootOffset();
// fStandRatio = 1.0f + fStandRatio * pTask->GetStandingSpringStrength();
// if(fStandRatio > 10.0f)
// fStandRatio = 10.0f;
// Vector3 scaledGravity(CPhysics::GetSimulator()->GetGravity());
// scaledGravity.Scale(GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor());
// scaledGravity.Scale(-1.0f * GetMass() * fStandRatio);
const ScalarV groundOffsetSumV = rage::Add(groundOffsetForPhysicsV, groundToRootOffsetV);
const ScalarV standRatioBeforeClampV = Scale(groundOffsetSumV, standingSpringStrengthV);
const ScalarV standRatioV = Min(standRatioBeforeClampV, maxStandRatioV);
const ScalarV gravityFactorTimesMassV = Scale(gravityFactorV, massV);
const Vec3V gravityWithFactorTimesMassV = Scale(gravityV, gravityFactorTimesMassV);
const Vec3V centeringForceV = SubtractScaled(Vec3V(zeroV), gravityWithFactorTimesMassV, standRatioV);
Vec3V groundNormalV = m_vecMaxGroundNormal;
Vec3V pedVelocityV = VECTOR3_TO_VEC3V(GetVelocity());
Vec3V groundVelocityV = GetGroundVelocity();
Vec3V relativeVelocityV = Subtract(pedVelocityV,groundVelocityV);
if((GetPedConfigFlag(CPED_CONFIG_FLAG_OnStairs) || sbForceIsOnStairs) && m_vecGroundPosHistory[0].IsNonZero())
{
// TODO: This part could still use optimizations.
Vector3 vGroundPos = GetGroundPos();
Vector3 vecGroundDirn;
vecGroundDirn.Set(vGroundPos);
vecGroundDirn.Subtract(m_vecGroundPosHistory[0]);
if(vecGroundDirn.Mag2() < PED_GROUND_POS_HISTORY_DIST_SQR && m_vecGroundPosHistory[1].IsNonZero())
{
vecGroundDirn.Set(vGroundPos);
vecGroundDirn.Subtract(m_vecGroundPosHistory[1]);
}
if(vecGroundDirn.XYMag2() > 0.05f)
{
Vector3 vecTempRight;
vecTempRight.Cross(vecGroundDirn, ZAXIS);
RC_VECTOR3(groundNormalV).Cross(vecTempRight, vecGroundDirn);
RC_VECTOR3(groundNormalV).Normalize();
}
}
const Vec4V constantsV(Vec::V4VConstant<
FLOAT_TO_INT(50.0f), // s_fMaxStandDamping
FLOAT_TO_INT(0.0f), // unused
FLOAT_TO_INT(0.0f), // unused
FLOAT_TO_INT(0.0f)>()); // unused
// static dev_float s_fMaxStandDamping = 50.0f;
// float fStandDamping = vecGroundNormal.Dot(vecGroundVelocity);
Vec3V forceBeyondGravityV;
const ScalarV normalDotVelocityV = Dot(groundNormalV, relativeVelocityV);
if(IsLessThanAll(normalDotVelocityV, ScalarV(V_ZERO)) || pTask->ShouldStickToFloor())
{
// if(vecGroundNormal.z > 0.1f)
// fStandDamping /= vecGroundNormal.z;
//
// separate damping coefficients for rebound (as opposed to compression, only do rebound damping if we want to stick to the floor)
// if(fStandDamping < 0.0f)
// {
// fStandDamping *= pTask->GetStandingSpringDamping();
// }
// else
// {
// fStandDamping *= pTask->ShouldStickToFloor() ? pTask->GetStandingSpringDamping() : 0.0f;
// }
// fStandDamping *= GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor();
const ScalarV standingSpringDampingV = ScalarV(pTask->GetStandingSpringDamping());
const ScalarV groundNormalZV = groundNormalV.GetZ();
// Note: using InvScaleFast() instead of InvScale() is probably OK here, I don't expect
// this dampening force to have to be all that accurate.
const ScalarV standDampingDivByGroundNormalZV = InvScaleFast(normalDotVelocityV, groundNormalZV);
const ScalarV divThresholdV(V_FLT_SMALL_1); // 0.1f
// NOTE: I do not think dividing by Z is correct. We need a velocity along the direction of the spring (z axis) but we don't want to include
// any velocity that is parallel to the ground (running up a hill shouldn't cause damping).
// Velocity projected onto ground normal = (velocity.groundNormal)*groundNormal
// Velocity projected onto ground normal projected onto spring direction = (((velocity.groundNormal)*groundNormal).ZAxis)*ZAxis = (velocity.groundNormal)*groundNormalZ
// Velocity we're computing = (velocity.groundNormal)/groundNormalZ
// This means our ground damping is scaled by 1/(groundNormalZ^2) more than it should be so it is significantly increased on slopes.
// I'm hesitant to fix this so late in GTAV since I haven't found any cases it improves.
ScalarV standDampingV = SelectFT(IsGreaterThan(groundNormalZV, divThresholdV), normalDotVelocityV, standDampingDivByGroundNormalZV);
standDampingV = Scale(standDampingV, standingSpringDampingV);
standDampingV = Scale(standDampingV, gravityFactorV);
//! DMKH. If jumping/falling, increase damping to keep ped out of ground.
ScalarV maxDampingV;
if((GetPedResetFlag(CPED_RESET_FLAG_IsFalling) || GetPedResetFlag(CPED_RESET_FLAG_IsLanding)) && IsLessThanAll(relativeVelocityV.GetZ(), ScalarV(V_ZERO)))
{
// static dev_float s_fMaxStandDampingJumpFall = 150.0f;
// static dev_float s_fStandDampingJumpFallMinVelZ = 2.5f;
// static dev_float s_fStandDampingJumpFallMaxVelZ = 6.0f;
// static dev_float s_fStandDampingJumpFallFactor = 2.0f;
const Vec4V jumpFallConstantsV(Vec::V4VConstant<
FLOAT_TO_INT(150.0f), // s_fMaxStandDampingJumpFall
FLOAT_TO_INT(2.5f), // s_fStandDampingJumpFallMinVelZ
FLOAT_TO_INT(2.0f - 1.0f), // s_fStandDampingJumpFallFactor - 1.0f
FLOAT_TO_INT(1.0f/(6.0f - 2.5f))>()); // 1.0f/(s_fStandDampingJumpFallMaxVelZ - s_fStandDampingJumpFallMinVelZ)
maxDampingV = jumpFallConstantsV.GetX();
const ScalarV standDampingJumpFallMinVelZV = jumpFallConstantsV.GetY();
const ScalarV standDampingJumpFallFactorMinusOneV = jumpFallConstantsV.GetZ();
const ScalarV standDampingJumpFallMaxMinusMinVelZInvV = jumpFallConstantsV.GetW();
//! Interp damping the faster we are going.
// float fInterp = ClampRange(abs(vecGroundVelocity.z), s_fStandDampingJumpFallMinVelZ, s_fStandDampingJumpFallMaxVelZ);
// float fDampingMod = (1.0f + ((s_fStandDampingJumpFallFactor-1.0f) * fInterp));
// fStandDamping *= fDampingMod;
const ScalarV absGroundVelocityZV = Abs(relativeVelocityV.GetZ());
const ScalarV interpBeforeClampV = Scale((absGroundVelocityZV - standDampingJumpFallMinVelZV), standDampingJumpFallMaxMinusMinVelZInvV);
const ScalarV interpV = Clamp(interpBeforeClampV, ScalarV(V_ZERO), oneV);
const ScalarV dampingModV = AddScaled(oneV, standDampingJumpFallFactorMinusOneV, interpV);
standDampingV = Scale(standDampingV, dampingModV);
}
else
{
maxDampingV = constantsV.GetX(); // s_fMaxStandDamping;
}
// if(fStandDamping > fMaxDamping)
// fStandDamping = fMaxDamping;
// else if(fStandDamping < -fMaxDamping)
// fStandDamping = -fMaxDamping;
const ScalarV negMaxDampingV = Negate(maxDampingV);
standDampingV = rage::Clamp(standDampingV, negMaxDampingV, maxDampingV);
Vec3V standDampingVecV(V_ZERO);
standDampingVecV.SetZ(standDampingV);
forceBeyondGravityV = AddScaled(centeringForceV, standDampingVecV, massV);
DR_ONLY(debugPlayback::RecordPedSpringForce(pInst, SubtractScaled(centeringForceV, gravityV, gravityFactorTimesMassV), groundNormalV, relativeVelocityV, standRatioV.Getf(), standDampingV.Getf()));
}
else
{
forceBeyondGravityV = centeringForceV;
}
forceToApplyV = SubtractScaled(forceBeyondGravityV, gravityV, gravityFactorTimesMassV);
mayHaveForceToApply = true;
// If we are trying to single step the ProcessPedStanding() calls, check if the force exceeds
// a threshold (not including the force to counteract gravity). If so, set a reset flag
// to make ProcessPedStanding() be called for the second step, to avoid instability problems.
if(CPedAILodManager::IsPedProcessPedStandingSingleStepActive()
&& !GetPedResetFlag(CPED_RESET_FLAG_ForceProcessPedStandingUpdateEachSimStep))
{
if(IsGreaterThanAll(Abs(forceBeyondGravityV.GetZ()), ScalarV(V_FLT_LARGE_2 /* 100.0f */)))
{
SetPedResetFlag(CPED_RESET_FLAG_ForceProcessPedStandingUpdateEachSimStep, true);
}
}
}
// If the ped spring has a force it always includes negative gravity, don't apply counter gravity twice.
if(!mayHaveForceToApply && GetPedResetFlag( CPED_RESET_FLAG_FirstPhysicsUpdate ) && !GetPedResetFlag(CPED_RESET_FLAG_ProcessProbesWhenExtractingZ) && !GetPedResetFlag(CPED_RESET_FLAG_DisableProcessProbes))
{
// Vector3 scaledGravity(CPhysics::GetSimulator()->GetGravity());
// scaledGravity.Scale(GetCurrentPhysicsInst()->GetArchetype()->GetGravityFactor());
// scaledGravity.Scale(-1.0f * GetMass());
// ApplyForceCg(scaledGravity);
const phArchetype& arch = *pInst->GetArchetype();
const ScalarV massV = ScalarV(arch.GetMass());
const ScalarV gravityFactorV = ScalarV(arch.GetGravityFactor());
const Vec3V gravityV = RCC_VEC3V(CPhysics::GetSimulator()->GetGravity());
const Vec3V gravityWithFactorV = Scale(gravityV, gravityFactorV);
forceToApplyV = rage::SubtractScaled(forceToApplyV, gravityWithFactorV, massV);
mayHaveForceToApply = true;
}
// if ped capsule control is disabled, there won't be any friction to slow us down on landing. Damp the peds velocity whilst it is on the ground to avoid sliding
if (GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsuleControl) && GetPedConfigFlag(CPED_CONFIG_FLAG_IsStanding) && GetPedConfigFlag(CPED_CONFIG_FLAG_WasStanding))
{
TUNE_GROUP_FLOAT(DISABLE_PED_CAPSULE_CONTROL, fVelocityDamping, 1.75f, 0.0f, 10.0f, 0.001f);
Vec3V pedVelocityV = VECTOR3_TO_VEC3V(GetVelocity());
Vec3V groundVelocityV = GetGroundVelocity();
Vec3V relativeVelocityV = Subtract(pedVelocityV,groundVelocityV);
relativeVelocityV*=ScalarV(Max(0.0f, 1.0f - (fVelocityDamping*fwTimer::GetTimeStep())));
SetVelocity(VEC3V_TO_VECTOR3(rage::Add(groundVelocityV, relativeVelocityV)));
Vec3V pedAngVelocityV = VECTOR3_TO_VEC3V(GetAngVelocity());
Vec3V groundAngVelocityV = GetGroundAngularVelocity();
Vec3V relativeAngVelocityV = Subtract(pedAngVelocityV,groundAngVelocityV);
relativeAngVelocityV*=ScalarV(Max(0.0f, 1.0f - (fVelocityDamping*fwTimer::GetTimeStep())));
SetAngVelocity(VEC3V_TO_VECTOR3(rage::Add(groundAngVelocityV, relativeAngVelocityV)));
}
Assert(MagSquared(Vec2V(forceToApplyV.GetX(), forceToApplyV.GetY())).Getf() <= 0.00001f);
StoreScalar32FromScalarV(m_CurrentSpringForceZ, forceToApplyV.GetZ());
// Adapted from ApplyForceCg():
if(mayHaveForceToApply)
{
FORCE_CHECK("[CPhysical::ApplyForceCg()]", VEC3V_TO_VECTOR3(forceToApplyV), DEFAULT_ACCEL_LIMIT);
Assert(IsFiniteAll(forceToApplyV));
ScalarV timeStepV = ScalarV(fTimeStep);
pCollider->ApplyForceCenterOfMass(forceToApplyV.GetIntrin128(), timeStepV.GetIntrin128());
#if DR_ENABLED && __ASSERT
static bool sbOnceOnly;
if (!sbOnceOnly)
{
if (!CheckForceInRange(VEC3V_TO_VECTOR3(forceToApplyV), DEFAULT_ACCEL_LIMIT))
{
debugPlayback::RecordTaggedVectorValue(*GetCurrentPhysicsInst(), forceToApplyV, "FlaggedForce", debugPlayback::eVectorTypeVelocity);
debugPlayback::SaveRecordingForInst(GetCurrentPhysicsInst());
sbOnceOnly = true;
}
}
#endif // DR_ENABLED && __ASSERT
}
#endif // PED_USE_CAPSULE_PROBES
}
void CPed::ProcessLowLodPhysics()
{
if (GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsule))
{
phInst* pInst = GetAnimatedInst();
if (pInst && pInst->IsInLevel())
{
phLevelNew* pLevel = CPhysics::GetLevel();
u16 nLevelIndex = pInst->GetLevelIndex();
if (pLevel->IsActive(nLevelIndex))
{
//Warningf("Deactivating ped capsule for CPED_RESET_FLAG_DisablePedCapsule. Check the logs for any activation asserts.");
//pLevel->DeactivateObject(nLevelIndex);
CPhysics::GetSimulator()->DeactivateObject(nLevelIndex);
}
pLevel->SetInstanceIncludeFlags(nLevelIndex, 0);
pInst->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, true);
return;
}
}
if (IsUsingKinematicPhysics())
{
return;
}
bool bUsingLowLodPhysics = this->IsUsingLowLodPhysics();
bool bOverridePhysics = GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics);
// Check all of our flags are set up correctly
if(bUsingLowLodPhysics || bOverridePhysics)
{
if(!GetUsingRagdoll())
{
DeActivatePhysics();
}
SetUseExtractedZ(true);
if(IsProtectedBaseFlagSet(fwEntity::USES_COLLISION))
{
DisableCollision();
}
// Deactivate the animated inst
if(GetAnimatedInst())
{
GetAnimatedInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, bUsingLowLodPhysics || bOverridePhysics);
if(GetAnimatedInst()->IsInLevel())
{
if(CPhysics::GetLevel()->IsActive(GetAnimatedInst()->GetLevelIndex()))
{
CPhysics::GetSimulator()->DeactivateObject(GetAnimatedInst()->GetLevelIndex());
}
}
}
}
// Don't un set these flags if we're not using low lod physics because it could override other bits of code
}
void CPed::ProcessOverridePhysics()
{
// GSALES 07/12/2011 : Fix to stop low-lod physics processing from
// reactivating collision when peds are in vehicles. Maybe we need multiple
// flags for disabling collisions?
bool bUseCollision = !GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics);
bool bWasCollision = !GetPedResetFlag(CPED_RESET_FLAG_WasPhysicsOverridden);
if (bUseCollision!=bWasCollision)
{
bool bUseExtractedZ = !bUseCollision;
if (GetIsInVehicle())
{
bUseCollision = false;
}
// url:bugstar:516623
// If script has explicitly disabled collision on a ped, don't let AI lod reactivate it
if(GetPedConfigFlag(CPED_CONFIG_FLAG_ScriptHasDisabledCollision))
{
bUseCollision = false;
}
bUseCollision ? EnableCollision() : DisableCollision(NULL, GetPedConfigFlag(CPED_CONFIG_FLAG_ScriptHasCompletelyDisabledCollision));
SetUseExtractedZ(bUseExtractedZ);
// Deactivate the animated inst if necessary
phInst* pInst = GetAnimatedInst();
if(pInst)
{
fwAttachmentEntityExtension *extension = GetAttachmentExtension();
bool bBasicallyAttached = extension && (extension->IsAttachStateBasicDerived() || extension->GetAttachState()==ATTACH_STATE_WORLD);
//When the ped is basically attached it shouldn't be allowed to activate
pInst->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, !bUseCollision || bBasicallyAttached);
if(pInst->IsInLevel())
{
if((!bUseCollision || bBasicallyAttached))
{
if(CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()))
{
CPhysics::GetSimulator()->DeactivateObject(pInst->GetLevelIndex());
}
}
}
}
}
SetPedResetFlag(CPED_RESET_FLAG_WasPhysicsOverridden, GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics));
}
bool CPed::IsFireResistant() const
{
const bool bPlayerWearingFireSuit = IsPlayer() && false; //TODO: detect this
//if we're scorched, assume that means our fire resistance was already used up by whatever scorched us
return (GetPedType() == PEDTYPE_FIRE || bPlayerWearingFireSuit) && !m_nPhysicalFlags.bRenderScorched;
}
void CPed::DecayAccumulatedFire(float fTimeStep)
{
static dev_float s_fDecay = 0.15f;
if(m_nFlags.bIsOnFire)
{
m_fAccumulatedFire = 1.0f;
return;
}
//use GetTimeStep() here because we expect 1 update per tick
//of the decay
m_fAccumulatedFire -= s_fDecay * fTimeStep;
m_fAccumulatedFire = Clamp(m_fAccumulatedFire, 0.0f, 1.0f);
}
bool CPed::ProcessFireResistance(float fPenetrationDepth, float fTimeStep)
{
// if (IsLocalPlayer())
// {
// Displayf("ProcessFireResistance: fPenetrationDepth = %f, m_fAccumulatedFire = %f", fPenetrationDepth, m_fAccumulatedFire);
// }
//if the ped is fireproof, don't accumulate any fire
if (m_nPhysicalFlags.bNotDamagedByFlames
|| (m_nPhysicalFlags.bNotDamagedByAnything BANK_ONLY(&& !CPedVariationDebug::ms_bLetInvunerablePedsBeAffectedByFire))
|| (GetPlayerInfo() && GetPlayerInfo()->bFireProof))
{
m_fAccumulatedFire = 0.0f;
return false;
}
//@@: range CPED_PROCESSFIRERESISTANCE {
#if TAMPERACTIONS_ENABLED && TAMPERACTIONS_INVINCIBLECOP
if(TamperActions::IsInvincibleCop(this))
{
m_fAccumulatedFire = 0.0f;
return false;
}
#endif
//@@: } CPED_PROCESSFIRERESISTANCE
//if we're already on fire, make sure the accumulated fire is set to 1.0 and return
if (m_nFlags.bIsOnFire)
{
m_fAccumulatedFire = 1.0f;
return true;
}
Assert(fPenetrationDepth > 0.0f);
if (IsFireResistant() && ms_fOnFireDelayTimeResistant > 0.0f)
{
const float fNormalizedPenetration = Clamp(fPenetrationDepth / (GetCapsuleInfo()->GetHalfWidth()), 0.0f, 1.0f);
const float fStepInSeconds = fTimeStep / 1000.0f;
m_fAccumulatedFire += fStepInSeconds / ms_fOnFireDelayTimeResistant * fNormalizedPenetration*fNormalizedPenetration;
}
else if(ms_fOnFireDelayTime > 0.0f)
{
// Peds need to be in contact with fire for a period of time before being set alight .
const float fNormalizedPenetration = Clamp(fPenetrationDepth / (GetCapsuleInfo()->GetHalfWidth()), 0.0f, 1.0f);
const float fStepInSeconds = fTimeStep / 1000.0f;
m_fAccumulatedFire += fStepInSeconds / ms_fOnFireDelayTime * fNormalizedPenetration*fNormalizedPenetration;
}
else
{
m_fAccumulatedFire = 1.0f;
return true;
}
m_fAccumulatedFire = Clamp(m_fAccumulatedFire, 0.0f, 1.0f);
if (m_fAccumulatedFire >= 1.0f)
{
return true;
}
return false;
}
void CPed::ComputeMatrixFromHeadingAndPitch(Mat34V_Ref mtrxOut, const float& heading, const float& pitch, Vec3V_In posV)
{
// This whole thing should be equivalent to this:
// Matrix34& m = RC_MATRIX34(mtrxOut);
// m.MakeRotateZ(heading);
// m.RotateLocalX(pitch);
// m.d = VEC3V_TO_VECTOR3(posV);
// Load the input angles.
const ScalarV headingV = LoadScalar32IntoScalarV(heading);
const ScalarV pitchV = LoadScalar32IntoScalarV(pitch);
// Set up a few constants that we will need.
const Vec4V constantsV(Vec::V4VConstant<
FLOAT_TO_INT(0.0f), // Unused
FLOAT_TO_INT(PI),
FLOAT_TO_INT(2.0f*PI), // Only used in SinFast() case
FLOAT_TO_INT(0.5f*PI)>());
// const ScalarV piV = constantsV.GetY();
const ScalarV halfPiV = constantsV.GetW();
const VecBoolV ttffV(V_T_T_F_F);
const Vec4V zeroV(V_ZERO);
// Set up a single vector of angles to compute the sine of, giving us the
// sine and cosine of both the heading and pitch.
const Vec4V headingPitchV = SelectFT(ttffV, Vec4V(pitchV), Vec4V(headingV));
const Vec4V anglesCosSinCosSinV = MergeXY(Vec4V(halfPiV), zeroV);
const Vec4V anglesV = rage::Add(headingPitchV, anglesCosSinCosSinV);
// Do the actual sine/cosine computation. We could potentially use this approximation
// const ScalarV twoPiV = constantsV.GetZ();
// const Vec4V anglesMinusTwoPiV = rage::Subtract(anglesV, Vec4V(twoPiV));
// const Vec4V normalizedAnglesV = SelectFT(IsGreaterThan(anglesV, Vec4V(piV)), anglesV, anglesMinusTwoPiV);
// const Vec4V cos1Sin1Cos2Sin2V = SinFast(normalizedAnglesV);
// but that feels just a little bit too risky for now, so use the regular Sin() (which
// still is relatively fast).
const Vec4V cos1Sin1Cos2Sin2V = Sin(anglesV);
// Negate and shuffle around a bit, and multiply together. This takes care
// of all multiplication we need to do.
const Vec4V negCos1Sin1Cos2Sin2V = Negate(cos1Sin1Cos2Sin2V);
const Vec4V negSin2NegCos2Cos1Sin1V = GetFromTwo<Vec::W2, Vec::Z2, Vec::X1, Vec::Y1>(cos1Sin1Cos2Sin2V, negCos1Sin1Cos2Sin2V);
const Vec4V productsV = Scale(cos1Sin1Cos2Sin2V, negSin2NegCos2Cos1Sin1V);
// Assemble the matrix columns.
const Vec3V mtrxAV = And(cos1Sin1Cos2Sin2V, Vec4V(ttffV)).GetXYZ();
const Vec3V mtrxBV(productsV.GetY(), productsV.GetZ(), cos1Sin1Cos2Sin2V.GetW());
const Vec3V mtrxCV(productsV.GetW(), productsV.GetX(), cos1Sin1Cos2Sin2V.GetZ());
// Store it.
mtrxOut.SetCol0(mtrxAV);
mtrxOut.SetCol1(mtrxBV);
mtrxOut.SetCol2(mtrxCV);
mtrxOut.SetCol3(posV);
}
// Name : ApplyMovementRequests
// Purpose : Calculates new current heading and velocity based on control inputs and extracted velocity from animation
// Parameters : None
// Returns : Nothing
//
void CPed::ApplyMovementRequests(float fTimestep, bool bUpdateVelocity, bool bUpdateAngularVelocity)
{
#if GTA_REPLAY
// Do not apply movement in replay as replay controls the movement.
if(CReplayMgr::IsEditModeActive())
{
return;
}
#endif // GTA_REPLAY
//*************************************************
// Orientate ped's matrix by new heading & pitch
Mat34V updatedPedMatrix;
const Vec3V posV = m_MatrixTransformPtr->GetCol3ConstRef();
ComputeMatrixFromHeadingAndPitch(updatedPedMatrix, m_MotionData.GetCurrentHeadingRef(), m_MotionData.GetCurrentPitchRef(), posV);
//****************************************************
CTaskMotionBase* pTask = GetCurrentMotionTask();
taskAssertf(pTask, "Movement task is NULL");
//*************************************************
// Calculate the worldspace velocity of the ped
//Let the task calculate the desired angular velocity.
if(bUpdateAngularVelocity)
{
Vec3V desiredAngularVelocityV = pTask->CalcDesiredAngularVelocity(updatedPedMatrix, fTimestep);
Assertf(IsFiniteAll(desiredAngularVelocityV), "Task desired angular velocity is invalid (1st test).");
//Check if the ground is valid.
if(GetGroundPhysical())
{
//Add the ground angular velocity.
desiredAngularVelocityV = rage::Add(desiredAngularVelocityV, m_vGroundAngularVelocity);
//Update the desired heading.
{
//Grab the current and desired heading.
float fCurrentHeading = m_MotionData.GetCurrentHeading();
float fDesiredHeading = m_MotionData.GetDesiredHeading();
//Calculate the heading change from the ground.
float fGroundHeadingChange = DotProduct(VEC3V_TO_VECTOR3(GetTransform().GetC()), GetGroundPhysical()->GetAngVelocity()) * fTimestep;
//Add the heading change from the ground.
fDesiredHeading += fGroundHeadingChange;
//Limit the angles.
fCurrentHeading = fwAngle::LimitRadianAngleSafe(fCurrentHeading);
fDesiredHeading = fwAngle::LimitRadianAngleSafe(fDesiredHeading);
//Set the new heading values.
m_MotionData.SetCurrentHeading(fCurrentHeading);
m_MotionData.SetDesiredHeading(fDesiredHeading);
}
}
//Set the desired angular velocity.
Assertf(IsFiniteAll(desiredAngularVelocityV),
"Task desired angular velocity is invalid (2nd test). m_MotionData: currentHeading=%5.3f, desiredHeading=%5.3f. Ground ang vel=%5.3f.",
m_MotionData.GetCurrentHeading(), m_MotionData.GetDesiredHeading(), GetGroundPhysical() ? GetGroundPhysical()->GetAngVelocity().Mag() : -1.f);
SetDesiredAngularVelocity(VEC3V_TO_VECTOR3(desiredAngularVelocityV));
}
//Let the task calculate the desired velocity.
if(bUpdateVelocity)
{
const Vec3V desiredVelocityV = pTask->CalcDesiredVelocity(updatedPedMatrix, fTimestep);
Assertf(IsFiniteAll(desiredVelocityV), "Task %s 's desired velocity is invalid. desiredVelocityV: %f,%f,%f.", pTask->GetTaskName(), desiredVelocityV.GetXf(), desiredVelocityV.GetYf(), desiredVelocityV.GetZf());
//Set the desired velocity.
Assertf(MagSquared(desiredVelocityV).Getf() < ( DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT + 0.0001f), "Task %s's desired velocity magnitude2 was invalid (%f).\n", pTask->GetTaskName(), MagSquared(desiredVelocityV).Getf());
SetDesiredVelocity(RCC_VECTOR3(desiredVelocityV));
}
//*******************************************************
// Debug code for printing out extracted velocity, etc
#if __DEV
static bool bTestPrintExtractedVel = false;
if(bTestPrintExtractedVel)
{
// This is debugging only, so it should be OK to just create a component here at a whim.
fwDynamicEntityComponent *dynComp = CreateDynamicComponentIfMissing();
Vector3 vAnimatedVelocity = dynComp->GetAnimatedVelocity();
Displayf("Frame %d: exX: %2.5f exY: %2.5f exZ: %2.5f [exAng: %2.5f fThetaZ: %2.5f] sec: %1.5f\n",
fwTimer::GetSystemFrameCount(), vAnimatedVelocity.x, vAnimatedVelocity.y, vAnimatedVelocity.z,
dynComp->GetAnimatedAngularVelocity(), dynComp->GetAnimatedAngularVelocity(), fwTimer::GetTimeStep());
printf("Frame %d: ExAng : %.2f, AdditionalAng : %2.f, EstimatedAng : %2.f\n",
fwTimer::GetSystemFrameCount(), dynComp->GetAnimatedAngularVelocity(),
GetMotionData()->GetExtraHeadingChangeThisFrame(), 0.0f);
}
#endif
}
// Name : UpdatePosition
// Purpose : Updates a peds position
// Parameters : None
// Returns : Nothing
//
//
void CPed::ApplyDesiredVelocity(float ENABLE_HORSE_ONLY(fTimeStep))
{
// Desired velocity doesn't make sense for peds whose capsule is being "freed"
if (GetPedResetFlag( CPED_RESET_FLAG_DisablePedCapsuleControl ))
return;
// if the player is attached to a vehicle their position will be updated elsewhere
if(GetUsingRagdoll())
return;
// Don't apply desired velocity to frozen corpses in water
bool bCorpseInWater = IsDead() && GetIsInWater() && GetAngVelocity().IsZero();
if (bCorpseInWater)
return;
const Vector3& vDesiredVelocity = GetDesiredVelocity();
Assertf(vDesiredVelocity == vDesiredVelocity, "Desired velocity is invalid.");
Assertf(vDesiredVelocity.Mag2() < ( DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT + 0.0001f), "Desired velocity Mag2 (%f) > %f * %f", vDesiredVelocity.Mag2(), DEFAULT_VELOCITY_LIMIT, DEFAULT_VELOCITY_LIMIT);
const Vector3& vDesiredAngVelocity = GetDesiredAngularVelocity();
Assertf(vDesiredAngVelocity == vDesiredAngVelocity, "Desired angular velocity is invalid.");
PDR_ONLY(debugPlayback::RecordPedDesiredVelocity(GetCurrentPhysicsInst(), RCC_VEC3V(vDesiredVelocity), RCC_VEC3V(vDesiredAngVelocity)));
// Update max ang speed for collider
phCollider *pCollider = GetCollider();
if (pCollider)
{
// Increase max ang speed if an independent mover transition is taking place
float fMaxAngSpeed = (GetMotionData()->GetIndependentMoverTransition() == 0 FPS_MODE_SUPPORTED_ONLY(&& !IsFirstPersonShooterModeEnabledForPlayer(false))) ? PED_MAX_ANG_SPEED : FLT_MAX;
pCollider->SetMaxAngSpeed(fMaxAngSpeed);
}
// This function is no longer expected to be called under these conditions -
// SimulatePhysicsForLowLod() does this work now, for performance reasons.
Assert(!GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics));
Assert(!GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics));
if (IsUsingKinematicPhysics() && pCollider)
{
Assertf(IsLessThanAll(MagSquared(Vec3V(vDesiredVelocity)),ScalarV(DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT + 0.0001f)), "Setting invalid collider velocity (%f, %f, %f)",VEC3V_ARGS(Vec3V(vDesiredVelocity)));
pCollider->SetVelocity(vDesiredVelocity);
pCollider->SetAngVelocity(vDesiredAngVelocity);
return;
}
const Vector3& vMoveSpeed = GetVelocity();
Assertf(vMoveSpeed == vMoveSpeed, "Velocity is invalid.");
Assertf(vMoveSpeed.Mag2() <= DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT, "Current Move Speed Mag2 (%f) > %f * %f", vMoveSpeed.Mag2(), DEFAULT_VELOCITY_LIMIT, DEFAULT_VELOCITY_LIMIT);
const Vector3& vTurnSpeed = GetAngVelocity();
Assertf(vTurnSpeed == vTurnSpeed, "Angular velocity is invalid.");
Vector3 vecChange = vDesiredVelocity - vMoveSpeed;
//! DMKH. Removing this clamp (Phil says it's ok). It basically breaks anyone setting the velocity outside of the movement code. i.e.
//! vaulting, ladders etc. Also, it's breaks down in low frame rate conditions (e.g. where we need to apply a larger velocity, due to
//! increased dT).
/*float fChangeMag = vecChange.Mag();
static float sfMagMax = 1.0f;
if (fChangeMag > sfMagMax)
{
vecChange *= sfMagMax / fChangeMag;
}*/
// Horse sliding
// TODO:
// -Add rotation
// -Orient the quadruped so the forward faces uphill or downhill, whichever is closer
// -Limit how much we can turn into a steep slope to prevent turning on a dime when riding alongside a slope
// -Find a more stable normal
// -Look at front/back normals
// -Try to only store the z-component of the interpolated normal so we don't need 16 bytes
TUNE_GROUP_BOOL(PED_SLIDE, PED_SLIDE_ENABLED,true);
m_SlideSpeed = 0.0f;
#if ENABLE_HORSE
if(PED_SLIDE_ENABLED && IsOnGround() && m_pCapsuleInfo->IsQuadruped() && GetHorseComponent())
{
const ScalarV timeStep = ScalarVFromF32(fTimeStep);
// Compute a ground normal based on the previous interpolated normal and the current max normal. OLD_NORMAL_WEIGHT = 0 would just be using the current normal.
// This will help reduce the impact of unavoidable single frame normal jumps and somewhat smooths out the whole process.
TUNE_GROUP_FLOAT(PED_SLIDE, OLD_NORMAL_WEIGHT, 1.0f, 0.0f, 2.0f, 0.1f);
const Vec3V groundNormal = m_vecInterpMaxGroundNormal = NormalizeSafe(AddScaled(GetMaxGroundNormal(),m_vecInterpMaxGroundNormal,ScalarVFromF32(OLD_NORMAL_WEIGHT)),GetMaxGroundNormal());
// Don't adjust anything if we're not on a significant slope
TUNE_GROUP_FLOAT(PED_SLIDE, LIMITED_ACCEL_STEEP_COSINE, 0.866f, 0.0f, 1.0f, 0.01f);
if(IsLessThanAll(groundNormal.GetZ(),ScalarVFromF32(LIMITED_ACCEL_STEEP_COSINE)))
{
// Find the uphill slope direction and tangent. These are orthogonal to the ground normal and each other, forming a basis.
// The tangent should point to the right if looking uphill
const Vec3V up(V_Z_AXIS_WZERO);
const Vec3V uphillSlopeDir = NormalizeSafe(Cross(groundNormal,Cross(up,groundNormal)),Vec3V(V_ZERO));
const Vec3V slopeTangent = Cross(uphillSlopeDir,groundNormal);
// Determine if we're on a very steep slope or not.
// On very steep slopes completely disable uphill acceleration and force downhill acceleration
// On semi-steep slopes just limit uphill acceleration but don't force the ped to slide
// NOTE: We could make this a single acceleration factor but the desired uphill acceleration needs to be projected onto the ground
// We also need to ensure that we don't reach equilibrium where the ped is trying to accelerate uphill but remains stationary.
// It's preferable to walk up a distance and then slide down.
ScalarV maxDesiredUphillAccel;
ScalarV slideAccel;
TUNE_GROUP_FLOAT(PED_SLIDE, NO_ACCEL_STEEP_COSINE, 0.75f, 0.0f, 1.0f, 0.01f);
if(IsLessThanAll(groundNormal.GetZ(),ScalarVFromF32(NO_ACCEL_STEEP_COSINE)))
{
TUNE_GROUP_FLOAT(PED_SLIDE, GRAVITY_ACCEL_MULT, 1.0f, 0.0f, 20.0f, 0.1f);
slideAccel = Scale(Dot(CPhysics::GetSimulator()->GetGravityV(),uphillSlopeDir),ScalarVFromF32(GRAVITY_ACCEL_MULT));
maxDesiredUphillAccel = ScalarV(V_ZERO);
}
else
{
TUNE_GROUP_FLOAT(PED_SLIDE, MAX_ACCEL_COSINE_MULT, 7.0f, 0.0f, 20.0f, 0.1f);
TUNE_GROUP_FLOAT(PED_SLIDE, MAX_ACCEL_BASE, 0.0f, -10.0f, 10.0f, 0.1f);
slideAccel = ScalarV(V_ZERO);
maxDesiredUphillAccel = AddScaled(ScalarVFromF32(MAX_ACCEL_BASE),groundNormal.GetZ(),ScalarVFromF32(MAX_ACCEL_COSINE_MULT));
}
// If we're on any slope, limit how much we can accelerate along the slopes tangent
TUNE_GROUP_FLOAT(PED_SLIDE, MAX_TANGENT_ACCEL, 5.0f, -20.0f, 20.0f, 0.1f);
const ScalarV maxTangentAccel = ScalarVFromF32(MAX_TANGENT_ACCEL);
// Compute a new acceleration based on the inputs
const Vec3V desiredAccel = InvScale(RCC_VEC3V(vecChange),timeStep);
const Vec3V currentVelocity = RCC_VEC3V(vMoveSpeed);
// Limit how much we can accelerate along the tangent. 'maxTangentAccel' refers to acceleration in the direction of our velocity along the tangent
const ScalarV desiredTangentAccel = Dot(slopeTangent,desiredAccel);
const ScalarV clampedTangentAccel = SelectFT(IsGreaterThan(Dot(slopeTangent,currentVelocity),ScalarV(V_ZERO)),rage::Max(desiredTangentAccel,Negate(maxTangentAccel)),rage::Min(desiredTangentAccel,maxTangentAccel));
// Clamp uphill acceleration. To do this we can't just clamp the acceleration projected onto the slope. If we do that then the velocity that
// directs us into the slope will be converted into uphill acceleration. Instead z-project the acceleration onto the slope to figure out how much
// uphill slope acceleration is necessary to get the desired acceleration. Clamp the projected velocity and then clamp the actual desired velocity
// by the same amount.
// This is necessary if we want to maintain the direction of the non-tangent desired velocity. I'm not sure if maintaining that is necessary but it seems
// safer. This is also why we can't bundle up gravity and the desired uphill acceleration.
const Vec3V desiredTangentPlaneAccel = SubtractScaled(desiredAccel,slopeTangent,desiredTangentAccel);
const Vec2V desiredTangentPlaneAccelXY = desiredTangentPlaneAccel.GetXY();
const ScalarV desiredTangentPlaneAccelZ = InvScaleSafe(Dot(desiredTangentPlaneAccelXY,groundNormal.GetXY()),Negate(groundNormal.GetZ()),ScalarV(V_ZERO));
const ScalarV desiredUphillAccel = Dot(Vec3V(desiredTangentPlaneAccelXY,desiredTangentPlaneAccelZ),uphillSlopeDir);
const ScalarV clampedUphillAccel = rage::Min(desiredUphillAccel,maxDesiredUphillAccel);
const Vec3V clampedTangentPlaneAccel = Scale(desiredTangentPlaneAccel,InvScaleSafe(clampedUphillAccel,desiredUphillAccel,ScalarV(V_ZERO)));
// Compute the final acceleration from the tangent acceleration, tangent plane acceleration, and the sliding acceleration
const Vec3V finalAccel = clampedTangentPlaneAccel + Scale(slopeTangent,clampedTangentAccel) + Scale(uphillSlopeDir,slideAccel);
const Vec3V newVelChange = Scale(finalAccel,timeStep);
// The slide velocity is the difference between how fast the ped wants to go and how fast it is going after sliding.
m_SlideSpeed = Dist(newVelChange,RCC_VEC3V(vecChange)).Getf();
RC_VEC3V(vecChange) = newVelChange;
}
}
#endif
Assertf(vecChange == vecChange, "Velocity change is invalid.");
Vector3 vecAngChange = vDesiredAngVelocity - vTurnSpeed;
Assertf(vecAngChange == vecAngChange, "Angular velocity change is invalid.");
//////////////////////////
// apply speed change to movement speed
static dev_bool sbApplyVelChangeDirectly = false;
if((GetPedResetFlag( CPED_RESET_FLAG_ApplyVelocityDirectly ) || sbApplyVelChangeDirectly) && pCollider)
{
Assertf(IsLessThanAll(MagSquared(VECTOR3_TO_VEC3V(vecChange+vMoveSpeed)),ScalarV(DEFAULT_IMPULSE_LIMIT*DEFAULT_IMPULSE_LIMIT + 0.0001f)), "Setting invalid collider velocity (%f, %f, %f)",VEC3V_ARGS(VECTOR3_TO_VEC3V(vecChange+vMoveSpeed)));
pCollider->SetVelocity(vecChange+vMoveSpeed);
}
else if(GetCurrentPhysicsInst()->IsInLevel())
{
physicsAssertf(vecChange.Mag2() < (DEFAULT_IMPULSE_LIMIT*DEFAULT_IMPULSE_LIMIT + 0.0001f ),
"Request for large desired velocity change (%5.3f) for ped at (%5.3f,%5.3f,%5.3f). ", vecChange.Mag(),
GetTransform().GetPosition().GetXf(), GetTransform().GetPosition().GetYf(), GetTransform().GetPosition().GetZf());
ApplyImpulseCg(vecChange*GetMass());
}
// Need to apply desired velocity directly because frame delay really screws up torque application
// Figure out a inertia indep torque
if(vecAngChange.IsNonZero())
{
if(pCollider)
{
float fMinPitch = 0.0f;
float fMaxPitch = 0.0f;
CTaskMotionBase* pTask = GetCurrentMotionTask();
if(pTask)
{
pTask->GetPitchConstraintLimits(fMinPitch,fMaxPitch);
}
if (fMinPitch == 0.0f && fMaxPitch == 0.0f)
{
// Can't rotate around X or Z if we're not allowed to rotate by or task
vecAngChange.And(VEC3_MASKZ);
}
pCollider->SetAngVelocity(vecAngChange + vTurnSpeed);
}
else if(GetCurrentPhysicsInst()->IsInLevel())
{
vecAngChange = VEC3V_TO_VECTOR3(GetTransform().UnTransform3x3(VECTOR3_TO_VEC3V(vecAngChange)));
vecAngChange.Multiply(GetAngInertia());
vecAngChange = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(VECTOR3_TO_VEC3V(vecAngChange)));
ApplyTorque(vecAngChange);
}
}
}
// Name : ProcessBuoyancy
// Purpose : Work out forces for ped in water
// Parameters : ----None
// Returns : Nothing
void CPed::ProcessBuoyancy(float fTimeStep, bool lastTimeSlice)
{
bool bUsingArticulatedBody = false;
if(GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
{
// We don't want to actually process the buoyancy for this ped since it is in a vehicle, but we do need to check
// if the ped should be considered as drowning.
CVehicle* pVehicle = GetMyVehicle();
Assert(pVehicle);
if(pVehicle && pVehicle->m_nFlags.bPossiblyTouchesWater && pVehicle->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate
&& pVehicle->m_Buoyancy.GetWaterLevelOnSample(0)>0.0f)
{
if(GetSkeleton())
{
Matrix34 headMtx;
GetSkeleton()->GetGlobalMtx(GetBoneIndexFromBoneTag(BONETAG_HEAD), RC_MAT34V(headMtx));
const float fPedHeadHeight = headMtx.d.z;
// If we're in a vehicle, we need the water level from the vehicle's buoyancy.
if(fPedHeadHeight < pVehicle->m_Buoyancy.GetAbsWaterLevel())
{
// Don't need to check if the vehicle is drowning unless it's a submarine.
if((pVehicle->InheritsFromSubmarine() || pVehicle->InheritsFromSubmarineCar()) && pVehicle->GetStatus()!=STATUS_WRECKED)
{
SetPedResetFlag(CPED_RESET_FLAG_IsDrowning, false);
}
else
{
SetPedResetFlag(CPED_RESET_FLAG_IsDrowning, true);
}
}
else
{
SetPedResetFlag(CPED_RESET_FLAG_IsDrowning, false);
}
}
}
return;
}
// Don't want ragdolls processing buoyancy when lying in the bottom of a boat.
if(GetPedConfigFlag(CPED_CONFIG_FLAG_RagdollingOnBoat) && GetUsingRagdoll())
return;
if (m_Buoyancy.GetStatus()!=FULLY_IN_WATER && (CanGroundPhysicalDisableBuoyancy() || GetIsAttached())) //can't swim when standing on something (boats) or are attached.
return;
if(m_nRagdollState==RAGDOLL_STATE_PHYS)
{
bUsingArticulatedBody = true;
}
if ( GetIsNotBuoyant() )
{
return;
}
if( IsNetworkClone() )
{
// network specific buoyancy - greatly simplified as tasks and events managed on the machine owning the ped
if(m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate && !m_nFlags.bPossiblyTouchesWater)
{
m_Buoyancy.ResetBuoyancy();
}
else
{
float fBuoyancyAccel = 0.0f;
if(m_Buoyancy.Process(this, fTimeStep, bUsingArticulatedBody, lastTimeSlice, &fBuoyancyAccel))
{
static dev_float s_fPositionOffset = 0.2f;
static dev_float s_fJumpingOffset = 0.3f;
float fPosOffset = GetPedResetFlag(CPED_RESET_FLAG_IsJumping) ? s_fJumpingOffset : s_fPositionOffset;
bool bSwimTasksRunning = GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning);
CNetObjPed* netObjPed = static_cast<CNetObjPed*>(GetNetworkObject());
if(netObjPed)
{
// if the owner is using a swimming state...
if( netObjPed->GetIsOwnerSwimming() || netObjPed->GetIsDiving() || netObjPed->GetIsOwnerDiveToSwimming())
{
// make it a little bit easier to swim so they stay in sync...
TUNE_GROUP_FLOAT(PED_SWIMMING, fCloneBuoyancyToSwimMultiplier, 1.4f, 0.0f, 2.0f, 0.01f);
fBuoyancyAccel *= fCloneBuoyancyToSwimMultiplier;
TUNE_GROUP_FLOAT(PED_SWIMMING, fCloneHeightOffset, -0.3f, -0.5f, 0.5f, 0.01f);
fPosOffset += fCloneHeightOffset;
}
else
{
// make it a little bit harder to swim so they stay in sync...
TUNE_GROUP_FLOAT(PED_SWIMMING, fCloneBuoyancyToOnFootMultiplier, 1.45f, 0.0f, 2.0f, 0.01f);
fBuoyancyAccel /= fCloneBuoyancyToOnFootMultiplier;
}
}
if(fBuoyancyAccel > 0.75f * CPhysics::GetGravitationalAcceleration())
{
if(bSwimTasksRunning || m_Buoyancy.GetAbsWaterLevel() > (GetTransform().GetPosition().GetZf()+fPosOffset))
{
SetIsStanding(false);
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, true );
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsHandCuffed)) { //kill 4 leggers in deep water for now
if (m_nPhysicalFlags.bNotDamagedByAnything)
m_nPhysicalFlags.bNotDamagedByAnything = false; //no invincibility allowed B* 267499
CEntity* inflictor = GetInflictorForDrowningHandCuffedInVehicle();
CEventDamage tempDamageEvent(inflictor, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_DROWNING);
CPedDamageCalculator damageCalculator(inflictor, 10.0f*GetHealth(), WEAPONTYPE_DROWNING, 0, false);
damageCalculator.ApplyDamageAndComputeResponse(this, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
}
}
}
}
}
return;
}
// If bPossiblyTouchesWater is false we are either well above the surface of the water or in a tunnel.
if ( m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate && !m_nFlags.bPossiblyTouchesWater)
{
m_Buoyancy.ResetBuoyancy();
}
else
{
float fBuoyancyAccel = 0.0f;
if(m_Buoyancy.Process(this, fTimeStep, bUsingArticulatedBody, lastTimeSlice, &fBuoyancyAccel))
{
// setting before fall damage for the damage calculator
SetIsInWater( true );
// we have gone from being 'not in water' to being 'in water'
if (m_nPhysicalFlags.bWasInWater==false REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive()))
{
// Apply some damage if high fall into water.
const CPedIntelligence* pPedIntelligence = GetPedIntelligence();
if(pPedIntelligence && pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_HIGH_FALL))
{
float fFallingSpeed = GetVelocity().z * -1.0f;
const float RAGDOLL_DAMAGE_MIN_SPEED_FALL_INTO_WATER = 15.0f; // about falling from 15m height.
const float RAGDOLL_DAMAGE_MULTIPLIER_FALL_INTO_WATER = 1.25f;
if(fFallingSpeed > RAGDOLL_DAMAGE_MIN_SPEED_FALL_INTO_WATER)
{
u32 weaponHash = WEAPONTYPE_FALL;
int nComponent = RAGDOLL_SPINE0;
float fDamage = fFallingSpeed * RAGDOLL_DAMAGE_MULTIPLIER_FALL_INTO_WATER;
//Apply the damage.
CEventDamage tempDamageEvent(NULL, fwTimer::GetTimeInMilliseconds(), weaponHash);
CPedDamageCalculator damageCalculator(NULL, fDamage, weaponHash, nComponent, false);
damageCalculator.ApplyDamageAndComputeResponse(this, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
}
}
bool isPlayerFx = false;
CPlayerInfo* pPlayerInfo = CGameWorld::GetMainPlayerInfo();
if (pPlayerInfo && pPlayerInfo->GetPlayerPed()==this)
{
isPlayerFx = true;
}
float downwardSpeed = -GetVelocity().z;
if (m_pPedVfx)
{
// get the downward speed from a couple of frames ago as it is more reliable
// when parachuting into water (and maybe other cases) the splash can be triggered a few frames later
// by which time the ped has been moving upwards for a few frames due to buoyancy
downwardSpeed = m_pPedVfx->GetPrevDownwardSpeed(2);
}
g_vfxWater.TriggerPtFxSplashPedLod(this, downwardSpeed, isPlayerFx);
g_vfxWater.TriggerPtFxSplashPedEntry(this, downwardSpeed, isPlayerFx);
}
static dev_float s_fSwimmingWaterWeightRatio = 0.75f;//B* 1254337. Separating these ratios has been causing more bugs than it's worth
static dev_float s_fDefaultWaterWeightRatio = 0.75f;
static dev_float s_fPositionOffset = 0.2f;
static dev_float s_fJumpingOffset = 0.3f;
float fPosOffset = GetPedResetFlag(CPED_RESET_FLAG_IsJumping) ? s_fJumpingOffset : s_fPositionOffset;
bool bFPSSwim = IsFirstPersonShooterModeEnabledForPlayer(false) && !IsOnGround() && GetIsInWater();
if(bFPSSwim && !GetPedResetFlag(CPED_RESET_FLAG_IsJumping))
{
static dev_float s_fFirstPersonSwimmingOffset = 0.1f;
fPosOffset = s_fFirstPersonSwimmingOffset;
}
else if (IsFirstPersonShooterModeEnabledForPlayer(false) && IsOnGround() && GetIsInWater())
{
static dev_float s_fFirstPersonSwimmingOffsetGrounded = 0.4f;
fPosOffset = s_fFirstPersonSwimmingOffsetGrounded;
}
bool bSwimTasksRunning = GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning);
float fWaterWeightRatio = bSwimTasksRunning ? s_fSwimmingWaterWeightRatio : s_fDefaultWaterWeightRatio;
//Keep us flagged as swimming if we are in a SwimIdle state (ie aiming harpoon underwater)
bool bInSwimIdleState = false;
CTask* pMotionTask = GetCurrentMotionTask(false);
if (pMotionTask && pMotionTask->GetTaskType()==CTaskTypes::TASK_MOTION_AIMING)
{
CTaskMotionAiming* pTaskMotionAiming = static_cast<CTaskMotionAiming*>(pMotionTask);
if (pTaskMotionAiming)
{
bInSwimIdleState = (pTaskMotionAiming->GetState() == CTaskMotionAiming::State_SwimIdleIntro || pTaskMotionAiming->GetState() == CTaskMotionAiming::State_SwimIdle
|| pTaskMotionAiming->GetState() == CTaskMotionAiming::State_SwimIdleOutro || pTaskMotionAiming->GetState() == CTaskMotionAiming::State_SwimStrafe);
}
}
bool bIsAimingAndSwimming = GetIsSwimming() && (GetPedResetFlag(CPED_RESET_FLAG_IsAiming) || bInSwimIdleState);
bool bIsDivingDownInFPSMode = false;
#if FPS_MODE_SUPPORTED
// B*1999572: Ensure we set the drowning flag if we're diving down in FPS Swim mode.
// Drowning/Swimming flags were being cleared for a couple of frames causing issues with surface reflections/intersections (B*1966157).
if (IsFirstPersonShooterModeEnabledForPlayer(false) && GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_MOTION_DIVING))
{
CTaskMotionDiving* pTaskMotionDiving = static_cast<CTaskMotionDiving*>(GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_MOTION_DIVING));
if (pTaskMotionDiving && pTaskMotionDiving->IsDivingDown())
{
bIsDivingDownInFPSMode = true;
}
}
#endif //FPS_MODE_SUPPORTED
// B*2001443: Don't unflag us as swimming if we're not on the ground but have a low buoyancy accel within a set time frame
// Fixes issues where ped sometimes drops out of State_Swimming in TaskMotionPed due to large waves causing
// the buoyancy value to fall off drastically for a couple of frames
bool bStaySwimming = false;
if (GetWasSwimming() && fBuoyancyAccel <= fWaterWeightRatio * CPhysics::GetGravitationalAcceleration() && !IsOnGround())
{
m_bStartOutOfWaterTimer = true;
}
if (m_bStartOutOfWaterTimer)
{
static dev_float fMaxTimeOutOfWater = 0.1f;
m_fTimeOutOfWater += fwTimer::GetTimeStep();
if (m_fTimeOutOfWater <= fMaxTimeOutOfWater)
{
bStaySwimming = true;
}
else
{
bStaySwimming = false;
m_fTimeOutOfWater = 0.0f;
m_bStartOutOfWaterTimer = false;
}
}
if(fBuoyancyAccel > fWaterWeightRatio * CPhysics::GetGravitationalAcceleration() || bStaySwimming || bIsAimingAndSwimming || bIsDivingDownInFPSMode)// Is water supporting 75% of ped weight? or is ped aiming while swimming?
//|| (m_Buoyancy.GetShouldStickToWaterSurface() && !GetIsStanding())
{
Vector3 vWaterLevelTestPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
float fWaterLevel = m_Buoyancy.GetAbsWaterLevel();
// FPS: Get high detail water level at camera x/y position for depth test.
if (IsFirstPersonShooterModeEnabledForPlayer(false))
{
float fTempWaterLevel = 0.0f;
// Use X/Y position of FPS camera (only if it's the dominant camera).
const camBaseCamera* pDominantRenderedCamera = camInterface::GetDominantRenderedCamera();
if(pDominantRenderedCamera && pDominantRenderedCamera->GetIsClassId(camFirstPersonShooterCamera::GetStaticClassId()))
{
Vector3 vFPSCamPos = camInterface::GetPos();
vWaterLevelTestPosition.x = camInterface::GetPos().x;
vWaterLevelTestPosition.y = camInterface::GetPos().y;
}
if (m_Buoyancy.GetWaterLevelIncludingRivers(vWaterLevelTestPosition, &fTempWaterLevel, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL, this, GetCanUseHighDetailWaterLevel()))
{
fWaterLevel = fTempWaterLevel;
}
}
bool bIsFPSGrounded = IsFirstPersonShooterModeEnabledForPlayer(false) && IsOnGround();
if ((bSwimTasksRunning && !bIsFPSGrounded) || fWaterLevel > (vWaterLevelTestPosition.z+fPosOffset)) //make sure I'm actually in a swimmable depth of water
{
SetIsStanding(false);
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, true );
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsHandCuffed)) { //kill 4 leggers in deep water for now
if (m_nPhysicalFlags.bNotDamagedByAnything)
m_nPhysicalFlags.bNotDamagedByAnything = false; //no invincibility allowed B* 267499
CEntity* inflictor = GetInflictorForDrowningHandCuffedInVehicle();
CEventDamage tempDamageEvent(inflictor, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_DROWNING);
CPedDamageCalculator damageCalculator(inflictor, 10.0f*GetHealth(), WEAPONTYPE_DROWNING, 0, false);
damageCalculator.ApplyDamageAndComputeResponse(this, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
}
if (!GetIsSwimming())
{
static dev_float s_DiveTriggerSpeed = 5.0f;
static dev_float s_DiveTriggerSpeedVaulting = 10.0f;
const float fTriggerSpeed = GetPedResetFlag(CPED_RESET_FLAG_IsVaulting) ? s_DiveTriggerSpeedVaulting : s_DiveTriggerSpeed;
float downwardSpeed = -GetVelocity().z;
if (downwardSpeed > fTriggerSpeed || GetPedResetFlag(CPED_RESET_FLAG_IsDiving)) { // high dive
GetMotionData()->SetForcedMotionStateThisFrame(CPedMotionStates::MotionState_Diving_Idle );
CTaskMotionBase* pPrimaryTask = GetPrimaryMotionTask();
if (pPrimaryTask && pPrimaryTask->GetTaskType()==CTaskTypes::TASK_MOTION_PED)
{
CTaskMotionPed* pTask = static_cast<CTaskMotionPed*>(pPrimaryTask);
pTask->SetDiveImpactSpeed(downwardSpeed);
}
}
}
}
} // End if supported by 75% of ped weight
else
{
//Maybe we don't want to leave water just yet, check for choppy seas?
if (bSwimTasksRunning || bFPSSwim)
{
float fLastWaveDelta = 0.0f;
if (bSwimTasksRunning)
{
CTask* pMotionTask = GetCurrentMotionTask(false);
if (pMotionTask && pMotionTask->GetTaskType()==CTaskTypes::TASK_MOTION_SWIMMING)
{
fLastWaveDelta = static_cast<CTaskMotionSwimming*>(pMotionTask)->GetLastWaveDelta();
}
}
else if (bFPSSwim)
{
CTask* pMotionTask = GetCurrentMotionTask(false);
if (pMotionTask && pMotionTask->GetTaskType()==CTaskTypes::TASK_MOTION_AIMING)
{
fLastWaveDelta = static_cast<CTaskMotionAiming*>(pMotionTask)->GetLastWaveDelta();
}
}
static dev_float sf_MinWaveTolerance = 1.0f;
static dev_float sf_MinWaveToleranceFPS = 0.0125f;
float fWaveTolerance = bFPSSwim ? sf_MinWaveToleranceFPS : sf_MinWaveTolerance;
Displayf("fLastWaveDelta: %f", fLastWaveDelta);
if (fabs(fLastWaveDelta) > fWaveTolerance)
{
//check ground pos
float fGroundDist = GetTransform().GetPosition().GetZf() - GetGroundPos().z;
static dev_float sf_GroundDelta = 2.0f;
if (fGroundDist > sf_GroundDelta)
{
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, true );
if (IsFirstPersonShooterModeEnabledForPlayer(false))
{
SetIsStanding(false);
}
}
}
}
}
} // End if full buoyancy check returned true
else
{
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, false );
}
}
}// CPed::ProcessBuoyancy()...
bool CPed::CanGroundPhysicalDisableBuoyancy() const
{
if(GetGroundPhysical())
{
// DMKH. If our ground physical is in water and it pulls us under, then we have to run buoyancy code.
if(!IsGroundPhysicalPullingPedUnderWater())
{
return true;
}
}
return false;
}
bool CPed::IsGroundPhysicalPullingPedUnderWater() const
{
pedFatalAssertf(GetGroundPhysical(), "Ground Physical is invalid!");
// B*2996778: Don't stay attached to fixed props when walking underwater.
if (GetGroundPhysical()->GetIsTypeObject() && GetGroundPhysical()->GetIsFixedFlagSet())
{
return true;
}
if((GetGroundPhysical()->m_Buoyancy.GetStatus() != NOT_IN_WATER) &&
GetGroundPhysical()->m_Buoyancy.GetAbsWaterLevel() > GetTransform().GetPosition().GetZf())
{
return true;
}
return false;
}
CEntity* CPed::GetInflictorForDrowningHandCuffedInVehicle()
{
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_IsHandCuffed))
return 0;
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_DrownsInSinkingVehicle))
return 0;
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_DrownsInWater))
return 0;
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_HasJustLeftCar))
return 0;
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_WasSwimming))
return 0;
if (!GetPedConfigFlag(CPED_CONFIG_FLAG_IsSwimming))
return 0;
CEntity* inflictor = 0;
if (GetMyVehicle())
{
inflictor = GetMyVehicle()->GetDriver();
if (!inflictor)
{
inflictor = GetMyVehicle()->GetSeatManager()->GetLastPedInSeat(0);
}
}
return inflictor;
}
void CPed::SetWindyClothingScale(float fWindyClothingScale)
{
Assert(fWindyClothingScale >=0.0f && fWindyClothingScale<= 1.0f);
fWindyClothingScale = Clamp(fWindyClothingScale, 0.0f, 1.0f);
m_fWindyClothingScale = fWindyClothingScale;
}
// Name : ProcessWindyClothing
// Purpose : Decide how much to jitter the global matrices to mimic windy clothing
// Parameters : None
// Returns : Nothing
void CPed::ProcessWindyClothing()
{
// Are we wearing clothes
bool bIsWearingClothes = m_PedAudioEntity.GetFootStepAudio().IsWearingClothes();
if (!bIsWearingClothes)
{
m_fWindyClothingScale = 0.0f;
return;
}
// Blend out the windy clothing effect
static float fBlendOutScale = 1.0f;
m_fWindyClothingScale -= fwTimer::GetTimeStep() * fBlendOutScale;
if(m_fWindyClothingScale < 0.0f)
{
m_fWindyClothingScale = 0.0f;
}
float fSpeed = 0.0f;
float fMinSpeed = 0.0f, fMaxSpeed = 0.0f;
// Am I on a motorcycle or boat?
if( GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle &&
( m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE ||
m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_QUADBIKE ||
m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE ||
m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_BOAT )
)
{
fSpeed = m_pMyVehicle->GetVelocity().Mag();
fMinSpeed = ms_fWindyClothingMinVehicleSpeed;
fMaxSpeed = ms_fWindyClothingMaxVehicleSpeed;
}
// On top of a fast moving vehicle?
else if(GetGroundPhysical() && GetGroundPhysical()->GetIsTypeVehicle())
{
fSpeed = GetGroundPhysical()->GetVelocity().Mag();
fMinSpeed = ms_fWindyClothingMinInAirSpeed;
fMaxSpeed = ms_fWindyClothingMaxInAirSpeed;
}
//! Falling?
else if(GetPedResetFlag(CPED_RESET_FLAG_IsFalling) || GetPedResetFlag(CPED_RESET_FLAG_IsParachuting) || GetPedResetFlag(CPED_RESET_FLAG_IsUsingJetpack))
{
//! If attached (i.e. parachuting), then get attach parents velocity.
CEntity *pEntity = GetIsAttached() ? static_cast<CEntity*>(GetAttachParent()) : NULL;
if(pEntity && pEntity->GetIsPhysical())
{
fSpeed = abs(static_cast<CPhysical*>(pEntity)->GetVelocity().z);
}
else
{
fSpeed = abs(GetVelocity().z);
}
fMinSpeed = ms_fWindyClothingMinFallSpeed;
fMaxSpeed = ms_fWindyClothingMaxFallSpeed;
}
// In mid air, but not falling (jumping etc).
else if(GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir))
{
fSpeed = GetVelocity().Mag();
fMinSpeed = ms_fWindyClothingMinInAirSpeed;
fMaxSpeed = ms_fWindyClothingMaxInAirSpeed;
}
if(fSpeed > 0.0f)
{
// Work out how much to blow the clothing by
Assert(fMaxSpeed > fMinSpeed);
if (fSpeed > fMinSpeed)
{
fSpeed = MIN(fSpeed, fMaxSpeed);
m_fWindyClothingScale = (fSpeed - fMinSpeed)/(fMaxSpeed - fMinSpeed);
}
}
// Am I in an open top car?
if (m_fWindyClothingScale > 0.0f)
{
// Factor in camera distance
// Get the camera position
Vector3 vCameraPosition= camInterface::GetPos();
// Get the peds position
Vector3 vPedPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 vDirectionFromPedToCamera(vCameraPosition - vPedPosition);
float distanceFromPedToCamera = vDirectionFromPedToCamera.Mag();
static float fCullDistance = 30.0f;
if (distanceFromPedToCamera > fCullDistance)
{
m_fWindyClothingScale = 0.0f;
return;
}
Assert(ms_fWindyClothingMaxDistance > ms_fWindyClothingMinDistance);
// fDistanceFactor is between 1.0 at ms_fMaxDistance and 0.0 at ms_fMinDistance
float fDistanceFactor = 0.0f;
if (distanceFromPedToCamera > ms_fWindyClothingMinDistance)
{
distanceFromPedToCamera = MIN(distanceFromPedToCamera, ms_fWindyClothingMaxDistance );
fDistanceFactor = (distanceFromPedToCamera - ms_fWindyClothingMinDistance)/(ms_fWindyClothingMaxDistance - ms_fWindyClothingMinDistance);
}
// Map fDistanceFactor to fDistanceScale
// fDistanceScale is between ms_fScaleAtMaxDistance at max distance and ms_fScaleAtMinDistance at minimum distance
Assert(ms_fWindyClothingScaleAtMaxDistance > ms_fWindyClothingScaleAtMinDistance);
float fDistanceScale = ms_fWindyClothingScaleAtMinDistance + fDistanceFactor * (ms_fWindyClothingScaleAtMaxDistance - ms_fWindyClothingScaleAtMinDistance);
m_fWindyClothingScale *= fDistanceScale;
//Printf("distanceToCamera %f \t m_fWindyClothingScale %f \n", distanceFromPedToCamera, m_fWindyClothingScale);
}
#if __DEV
if (ms_bWindyClothingOverride)
{
m_fWindyClothingScale = ms_fWindyClothingOverride;
}
#endif //__DEV
m_fWindyClothingScale = Clamp(m_fWindyClothingScale, 0.0f, 1.0f);
}
// Name : ClearWetClothing
// Purpose : clear the wet clothing flags and reset the wet amounts
// Parameters : None
// Returns : Nothing
void CPed::ClearWetClothing()
{
m_uWetClothingFlags = 0;
SetWetClothingLowerHeight(-2.0f);
SetWetClothingUpperHeight(-2.0f);
SetWetClothingLowerWetness(0.0f);
SetWetClothingUpperWetness(0.0f);
}
// Name : SetWetClothingHeight
// Purpose : script callable function to set the wet clothes height
// Parameters : height
// Returns : Nothing
void CPed::SetWetClothingHeight(float height)
{
height = Min(height,1.99f); // clamp this so old resources don't get wet.
// we're wet and in the water.
m_uWetClothingFlags |= kClothesAreWet|kClothesAreInWater;
SetWetClothingLowerHeight(height);
SetWetClothingUpperHeight(height);
SetClothPinRadiusScale(0.3f);
}
// Name : SetWetClothingLevel
// Purpose : script callable function to set the wet clothes level
// Parameters : level
// Returns : Nothing
void CPed::SetWetClothingLevel(float level)
{
float saturatedLevel = Saturate(level);
SetWetClothingLowerWetness(saturatedLevel);
SetWetClothingUpperWetness(saturatedLevel);
}
// Name : ProcessWetClothingInWater
// Purpose :
// Parameters : Nothing
// Returns : Nothing
void CPed::ProcessWetClothingInWater()
{
bool inWater = false;
float height = -10000.0f;
// check if we're in or on a vehicle.
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
if (pAttachParent && pAttachParent->GetIsTypeVehicle() && GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
{
CVehicle* veh = (CVehicle*)static_cast<CVehicle*>(pAttachParent);
if (!veh->GetIsAquatic()) // don't get wet when in a boat or sub
{
// we need to use the water level the vehicle detected.
if (pAttachParent->m_nFlags.bPossiblyTouchesWater && pAttachParent->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate && pAttachParent->m_Buoyancy.GetWaterLevelOnSample(0)>0.0f)
{
const float fThisZ = GetTransform().GetPosition().GetZf();
height = pAttachParent->m_Buoyancy.GetAbsWaterLevel() - fThisZ; // if we're in a vehicle, we need the water level of the vehicle's buoyancy
inWater = height > -1.0f;
// HACK: when in a vehicle, our legs are frequently bent, so we scale a water height to compensate when water is below the waist
if (height<0 && GetSkeleton())
{
Matrix34 mFootMtx;
GetSkeleton()->GetGlobalMtx(GetBoneIndexFromBoneTag(BONETAG_L_TOE), RC_MAT34V(mFootMtx));
float distToToe = fThisZ - mFootMtx.d.z;
if (distToToe>0)
height *= 1.5f/distToToe;
}
}
}
}
else
{
// check if the ped buoyancy data is valid (if the ped is dead and not active it won't be)
bool buoyancyTestsValid = true;
if (GetIsDeadOrDying())
{
phInst* pInst = GetCurrentPhysicsInst();
int levelIndex = pInst->GetLevelIndex();
bool isActive = levelIndex!=phInst::INVALID_INDEX && CPhysics::GetLevel()->IsActive(levelIndex);
if (!isActive)
{
buoyancyTestsValid = false;
}
}
Vec3V vPedPos = GetTransform().GetPosition();
if (buoyancyTestsValid)
{
if (GetIsSwimming() || GetIsInWater() || (m_nFlags.bPossiblyTouchesWater && m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate))
{
if (GetRagdollState()<=RAGDOLL_STATE_ANIM)
{
// not in ragdoll - use buoyancy sample zero
inWater = m_Buoyancy.GetWaterLevelOnSample(0)>0.0f;
// if we're doing a combat roll we need to get fully wet
CTask* pTask = GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_COMBAT_ROLL);
if (inWater && pTask)
{
if (static_cast<CTaskCombatRoll*>(pTask)->GetIsLanding())
{
height = 2.0f;
}
}
else
{
float waterZ = m_Buoyancy.GetAbsWaterLevel();
height = waterZ - vPedPos.GetZf();
}
}
else
{
if (GetSkeleton())
{
Vec3V vFootPos = m_vecOffsets[kOffsetLeftFoot];
Vec3V vHeadPos = m_vecOffsets[kOffsetHead];
vFootPos = Transform(GetTransform().GetMatrix(), vFootPos);
vHeadPos = Transform(GetTransform().GetMatrix(), vHeadPos);
float footZ = vFootPos.GetZf();
float headZ = vHeadPos.GetZf();
float minPedZ = Min(footZ, headZ);
float maxPedZ = Max(footZ, headZ);
float waterZ;
CVfxHelper::GetWaterZ(vPedPos, waterZ, this);
if (waterZ>=maxPedZ)
{
height = 2.0f;
inWater = true;
}
else if (waterZ<minPedZ)
{
height = -2.0f;
}
else
{
const float pedHeight = 1.8f;
float interp = CVfxHelper::GetInterpValue(waterZ, minPedZ, maxPedZ);
height = -(pedHeight*0.5f) + (interp*pedHeight);
inWater = true;
}
}
}
}
}
else
{
float oceanWaterZ;
if (CVfxHelper::GetOceanWaterZ(vPedPos, oceanWaterZ))
{
if (oceanWaterZ>=vPedPos.GetZf()+0.2f)
{
inWater = true;
height = 2.0f;
}
}
}
}
if(inWater)
{
height = Min(height,1.99f); // clamp this so old resources don't get wet.
// we're wet and in the water.
m_uWetClothingFlags |= kClothesAreWet|kClothesAreInWater;
SetClothPinRadiusScale(0.3f);
// if the water height is above the lower height
if (GetWetClothingLowerHeight() <= height)
{
// increase the lower height to here and make fully wet
SetWetClothingLowerHeight(height);
SetWetClothingLowerWetness(1.0f);
}
// if the water height is above the upper height
if (GetWetClothingUpperHeight() <= height)
{
// increase the upper height to here and make fully wet
SetWetClothingUpperHeight(height);
SetWetClothingUpperWetness(1.0f);
}
// timers only get reset when we get deeper in water so if we're at a static height or coming to the surface they don't get reset
// we solve this by resetting if we're swimming
if (GetIsSwimming())
{
SetWetClothingLowerWetness(1.0f);
SetWetClothingUpperWetness(1.0f);
SetClothIsProne(true);
}
else
{
SetClothIsProne(false);
}
}
else
{
// we're not currently in the water
m_uWetClothingFlags &= ~kClothesAreInWater;
}
}
// Name : IncWetClothing
// Purpose : function to increase a peds wet clothing at a specified rate
// Parameters : wetnessRate, heightRate
// Returns : Nothing
void CPed::IncWetClothing(float rate)
{
float elapsed = TIME.GetSeconds();
// deal with both upper and lower sections being active
if (GetWetClothingLowerWetness()>0.0f && GetWetClothingUpperWetness()>0.0f)
{
// increase the upper wetness
TargetWetClothingUpperWetness(1.0f, rate, elapsed);
// check if the upper section is at max height
if (GetWetClothingUpperHeight()>=1.0f)
{
// upper section is already at max - increase the lower wetness as well
TargetWetClothingLowerWetness(1.0f, rate, elapsed);
// check if the upper section is now as wet as the lower
if (GetWetClothingUpperWetness()>=GetWetClothingLowerWetness())
{
// clear the lower section
SetWetClothingLowerHeight(-2.0f);
SetWetClothingLowerWetness(0.0f);
}
}
else
{
// check if the upper section is now as wet as the lower
if (GetWetClothingUpperWetness()>=GetWetClothingLowerWetness())
{
// set the lower section to that of the upper
SetWetClothingLowerHeight(GetWetClothingUpperHeight());
SetWetClothingLowerWetness(GetWetClothingUpperWetness());
// activate the upper section to max height
SetWetClothingUpperHeight(1.0f);
SetWetClothingUpperWetness(0.0f);
TargetWetClothingUpperWetness(1.0f, rate, elapsed);
}
}
}
// deal with only the upper section being active
else if (GetWetClothingUpperWetness()>0.0f)
{
// check if the upper height isn't at max
if (GetWetClothingUpperHeight()<1.0f)
{
// set the lower section to that of the upper
SetWetClothingLowerHeight(GetWetClothingUpperHeight());
SetWetClothingLowerWetness(GetWetClothingUpperWetness());
// activate the upper section to max height
SetWetClothingUpperHeight(1.0f);
SetWetClothingUpperWetness(0.0f);
TargetWetClothingUpperWetness(1.0f, rate, elapsed);
}
else
{
// increase the upper wetness
TargetWetClothingUpperWetness(1.0f, rate, elapsed);
}
}
// deal with only the lower section being active or neither section being active
else
{
// activate the upper section to max height
SetWetClothingUpperHeight(1.0f);
SetWetClothingUpperWetness(0.0f);
TargetWetClothingUpperWetness(1.0f, rate, elapsed);
}
// set as being wet
m_uWetClothingFlags |= kClothesAreWet | kClothesGotWetterThisFrame;
SetClothPinRadiusScale(0.3f);
}
// Name : DecWetClothing
// Purpose : function to decrease a peds wet clothing at a specified rate
// Parameters : rate
// Returns : Nothing
void CPed::DecWetClothing(float rate)
{
float elapsed = TIME.GetSeconds();
// dry the upper section
TargetWetClothingUpperWetness(0.0f, rate, elapsed);
// check if we are out of water
if ((m_uWetClothingFlags & kClothesAreInWater) == 0)
{
// we are - dry the lower section
TargetWetClothingLowerWetness(0.0f, rate, elapsed);
// check if the lower height is above or level with the upper
if (GetWetClothingLowerHeight() >= GetWetClothingUpperHeight())
{
// it is - reset so we can measure the low level when we go back in
// but keep the wetter value, since we've been seeing that, not the higher one that was drying
SetWetClothingUpperWetness(GetWetClothingLowerWetness());
SetWetClothingLowerHeight(-2.0f);
SetWetClothingLowerWetness(0.0f);
}
}
// check if the upper section is dry
if (GetWetClothingUpperWetness() <= 0.0f)
{
// it is - the lower section becomes the new upper section
SetWetClothingUpperHeight(GetWetClothingLowerHeight());
SetWetClothingUpperWetness(GetWetClothingLowerWetness());
// reset the lower section so it tracks the new low water line
SetWetClothingLowerHeight(-2.0f);
SetWetClothingLowerWetness(0.0f);
// if the upper section is also dry we're no longer wet at all
if (GetWetClothingUpperWetness() <= 0.0f)
{
m_uWetClothingFlags ^= kClothesAreWet;
SetClothPinRadiusScale(1.0f);
}
}
}
// Name : ProcessWetClothing
// Purpose : track how high up the character the wet clothes effect should go. we need to handle the cases of the ped walking in then out and back into the water
// Parameters : None
// Returns : Nothing
void CPed::ProcessWetClothing()
{
ProcessWetClothingInWater();
// check if the ped should be getting wet
float pedWetnessApproachRate = 0.0f;
float currRain = g_weather.GetTimeCycleAdjustedRain();
if (currRain>0.0f)
{
static float s_RainWetnessApproachRateLo = 0.005f;
static float s_RainWetnessApproachRateHi = 0.015f;
pedWetnessApproachRate = s_RainWetnessApproachRateLo + (currRain*(s_RainWetnessApproachRateHi-s_RainWetnessApproachRateLo));
if (GetIsShelteredFromRain())
{
pedWetnessApproachRate = 0.0f;
}
}
// is the ped getting wet?
if (pedWetnessApproachRate>0.0f)
{
IncWetClothing(pedWetnessApproachRate);
}
// otherwise we need to dry off slowly
else if ((m_uWetClothingFlags&kClothesAreWet) && (m_uWetClothingFlags&kClothesGotWetterThisFrame)==false)
{
static float s_DryApproachRate = 0.025f;
DecWetClothing(s_DryApproachRate);
}
m_uWetClothingFlags ^= kClothesGotWetterThisFrame;
}
bool CPed::GetIsShelteredInVehicle()
{
#if GTA_REPLAY
//on replay we need to store the value of this as we dont record
//any attachment information.
if( CReplayMgr::IsEditModeActive())
{
ReplayPedExtension *extension = ReplayPedExtension::GetExtension(this);
if(extension)
return extension->GetPedShelteredInVehicle();
}
#endif
// in a vehicle with a roof or windows down?
CVehicle* pVehicle = GetMyVehicle();
if (pVehicle)
{
if (GetAttachState()==ATTACH_STATE_PED_IN_CAR)
{
if (pVehicle)
{
VehicleType vehType = pVehicle->GetVehicleType();
if (vehType==VEHICLE_TYPE_CAR)
{
bool hasRoof = !pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_NO_ROOF);
if (hasRoof)
{
if (pVehicle->DoesVehicleHaveAConvertibleRoofAnimation())
{
if (pVehicle->GetConvertibleRoofState()==CTaskVehicleConvertibleRoof::STATE_RAISED)
{
return true;
}
}
else
{
return true;
}
}
}
else if (vehType==VEHICLE_TYPE_PLANE || vehType==VEHICLE_TYPE_TRAILER || vehType==VEHICLE_TYPE_HELI ||
vehType==VEHICLE_TYPE_BLIMP || vehType==VEHICLE_TYPE_AUTOGYRO || vehType==VEHICLE_TYPE_TRAIN ||
vehType==VEHICLE_TYPE_SUBMARINE)
{
return true;
}
}
}
}
return false;
}
bool CPed::GetIsShelteredFromRain()
{
// check if undercover
if (GetPedAudioEntity())
{
// use the audio data if it exists
if (GetPedAudioEntity()->GetIsUnderCover())
{
return true;
}
}
#if __TRACK_PEDS_IN_NAVMESH
else
{
// otherwise query the navmesh
if (GetNavMeshTracker().GetIsValid())
{
if (GetNavMeshTracker().GetNavPolyData().m_bSheltered)
{
return true;
}
}
}
#endif
if (GetIsShelteredInVehicle())
{
return true;
}
// or in an interior
if (GetIsInInterior())
{
return true;
}
return false;
}
CEntity * CPed::GetAttacker()
{
if (m_Attacker && m_Attacker->GetIsTypePed())
return static_cast<CPed*>(m_Attacker.Get());
else
return NULL;
}
bool CPed::AddWoundData(const WoundData &wound, CEntity *pShotBy, bool forcePrimary)
{
m_Attacker = pShotBy;
if (forcePrimary || !(GetPrimaryWoundData()->valid && m_WoundPrimary.component == RAGDOLL_HEAD))
{
// Bump the wounds down
m_WoundSecondary = m_WoundPrimary;
m_WoundPrimary = wound;
}
// don't run injuredOnGround if shot in the head
if (wound.component == RAGDOLL_HEAD)
GetPedIntelligence()->GetCombatBehaviour().DisableInjuredOnGroundBehaviour();
GetPrimaryWoundData()->numHits++;
return true;
}
void CPed::SetRenderDelayFlag(u8 uFlag)
{
#if __ASSERT
if(m_uRenderDelayFlags.GetAllFlags() == 0)
{
m_uRenderFlagFrameCount = fwTimer::GetFrameCount();
}
#endif
m_uRenderDelayFlags.SetFlag(uFlag);
}
void CPed::ClearRenderDelayFlag(u8 uFlag)
{
ASSERT_ONLY(bool bWasOn = m_uRenderDelayFlags.GetAllFlags() > 0);
m_uRenderDelayFlags.ClearFlag(uFlag);
#if __ASSERT
if(bWasOn && m_uRenderDelayFlags.GetAllFlags() == 0)
{
/*u32 nNumFramesInvisible = fwTimer::GetFrameCount() - m_uRenderFlagFrameCount;
if(nNumFramesInvisible > 0)
{
Displayf("Ped invisible for %d frames", nNumFramesInvisible);
}*/
m_uRenderFlagFrameCount = 0;
}
#endif
}
void CPed::SetStubbleGrowth( float growth )
{
if ( growth < 0.f){
m_ShaveTimeInSeconds=-1;
return;
}
growth = Clamp( growth,0.f,1.f);
int timeSecs = fwTimer::GetTimeInMilliseconds() /1000;
m_ShaveTimeInSeconds = timeSecs - (int)(growth* 3.f * (float)SECONDS_IN_DAY );
}
float CPed::GetCurrentStubbleGrowth() const
{
int timeSecs = fwTimer::GetTimeInMilliseconds() /1000;
float growth=0.f;
if ( this->m_ShaveTimeInSeconds != -1 )
{
growth= (float)(timeSecs-this->m_ShaveTimeInSeconds) /( 3.f*(float)SECONDS_IN_DAY );
growth = Clamp( growth,0.f,1.f);
}
return growth;
}
#if __DEV
bool CPed::ProcessControl_Debug()
{
Assert(!IsNetworkClone());
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessControlOfFocusEntity(), this );
DEV_BREAK_ON_PROXIMITY( (CDebugScene::ShouldDebugBreakOnProximityOfProcessControlCallingEntity() || CDebugScene::ShouldDebugBreakOnProximityOfProcessControlCallingPed()), VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()) );
#if NAVMESH_EXPORT // Don't do ProcessControl() or update AI for navmesh export process, want to keep things basic as poss
if(CNavMeshDataExporter::IsExportingCollision())
return true;
#endif
#if __ASSERT
taskAssertf(!GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || m_pMyVehicle, "Vehicle deleted without ped being removed!");
if(GetRagdollInst() && GetRagdollInst()->IsInLevel())
{
if(CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()) && GetRagdollState()!=RAGDOLL_STATE_ANIM_DRIVEN)
{
if (GetCurrentPhysicsInst()!=GetRagdollInst())
SpewRagdollTaskInfo();
Assert(GetCurrentPhysicsInst()==GetRagdollInst());
if (GetAnimatedInst()->IsInLevel())
{
// If its in the level check its not collide-able
Assert(CPhysics::GetLevel()->GetInstanceIncludeFlags(GetAnimatedInst()->GetLevelIndex())==0);
}
Assert(GetRagdollState() > RAGDOLL_STATE_ANIM);
}
}
#endif
#if USE_PHYSICAL_HISTORY
Color32 colour = Color_green;
if( GetMotionData()->GetIsWalking() )
{
colour = Color_orange;
}
else if( GetMotionData()->GetIsRunning() || GetMotionData()->GetIsSprinting() )
{
colour = Color_red;
}
m_physicalHistory.UpdateHistory(GetMatrix(), this, colour);
#endif
return false;
}
#endif // __DEV
void CPed::ProcessControl_Turret()
{
const bool fullUpdate = CPedAILodManager::ShouldDoFullUpdate(*this);
ProcessControl_ResetVariables();
ProcessControl_WeaponsAndAccessoriesPreIntelligence();
ProcessControl_Intelligence(fullUpdate);
ProcessControl_WeaponsAndAccessoriesPostIntelligence(fullUpdate);
}
// ProcessFrozen: Called when the entity is frozen instead of process control
void CPed::ProcessFrozen()
{
PF_FUNC(ProcessFrozen);
//Increment the accumulate time of each recorded event.
GetPedIntelligence()->TickEvents();
// Remove any invalid events
GetPedIntelligence()->RemoveInvalidEvents(true);
if(!GetPedConfigFlag(CPED_CONFIG_FLAG_StoppedSpeechUponFreezing))
{
if(GetSpeechAudioEntity())
GetSpeechAudioEntity()->StopSpeech();
SetPedConfigFlag(CPED_CONFIG_FLAG_StoppedSpeechUponFreezing, true);
}
//Ensure this is called, even when frozen
ProcessSpecialNetworkLeave();
}
bool CPed::CanBeFrozen() const
{
//! Peds awaiting death need to be unfrozen so that they can run task & animation updates to play an appropriate death
//! animation.
if(IsFatallyInjured() && GetDeathState() != DeathState_Dead)
{
return false;
}
return true;
}
void CPed::ProcessBuoyancyProbe(bool bProcess)
{
if(bProcess && !GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics) && !GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) && !GetIsAttached())
{
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(IsPlayer() && !GetIsInInterior())
{
m_Buoyancy.m_buoyancyFlags.bUseWidestRiverBoundTolerance = 1;
}
else
{
m_Buoyancy.m_buoyancyFlags.bUseWidestRiverBoundTolerance = 0;
}
m_Buoyancy.SubmitRiverBoundProbe(vPos);
}
else
{
m_Buoyancy.InvalidateCachedRiverBoundProbeResult();
}
}
void SetProtectedBaseFlagAndFPVExtensionOnChildAttachments(CPed* pPed, fwEntity* pEnt, fwEntity::protectedFlags baseFlag, bool newValue)
{
// Update attachments
fwAttachmentEntityExtension* attachExt = pEnt->GetAttachmentExtension();
if(attachExt REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()))
{
fwEntity* pCurChild = attachExt->GetChildAttachment();
while(pCurChild)
{
SetProtectedBaseFlagAndFPVExtensionOnChildAttachments(pPed, pCurChild, baseFlag, newValue);
pCurChild->AssignProtectedBaseFlag(baseFlag, newValue);
if(pCurChild->GetBaseFlags() & fwEntity::IS_DYNAMIC)
{
static_cast<CDynamicEntity*>(pCurChild)->m_nDEflags.bUseExtendedBoundingBox = newValue;
}
// Don't create alt skeleton extensions on vehicles; they can be cleaned up from scheduler threads
if(newValue && pCurChild->GetType() != ENTITY_TYPE_VEHICLE)
{
fwAttachmentEntityExtension* childAttachExt = pCurChild->GetAttachmentExtension();
if(childAttachExt)
{
fwAltSkeletonExtension* pExtension = fwAltSkeletonExtension::GetOrAddExtension(*pCurChild);
if(pExtension)
{
Mat34V offsetMtx(V_IDENTITY);
childAttachExt->GetOffsetFromParent(pPed, offsetMtx, pPed == pEnt);
rage::Transform(pExtension->m_offset, pPed->GetMatrix(), offsetMtx);
}
}
}
else if(pCurChild->GetExtension<fwAltSkeletonExtension>())
{
fwAltSkeletonExtension::RemoveExtension(*pCurChild);
}
pCurChild = pCurChild->GetAttachmentExtensionRef().GetSiblingAttachment();
}
}
}
// UpdateFirstPersonFlags: Enable / disable the first person view render flags on the ped and all attachments
void CPed::UpdateFirstPersonFlags(bool isFps)
{
#if __BANK
isFps = isFps && g_bEnableFPV;
#endif
// Update ped
AssignProtectedBaseFlag(HAS_FPV, isFps);
SetProtectedBaseFlagAndFPVExtensionOnChildAttachments(this, this, HAS_FPV, isFps);
}
//
// Name : ProcessControl
// Purpose : Work out forces for ped standing on ground
// Parameters : None
// Returns : Nothing
//
bool CPed::ProcessControl()
{
PF_FUNC(ProcessControlFunc);
if(m_NeedToInitAudio)
{
// Process deferred audio initialisation as early as possible
InitAudio();
}
// Contains any process control per frame debug code
DEV_ONLY(if(ProcessControl_Debug()) return true;)
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool) || m_nDEflags.bFrozen)
{
ProcessSpecialNetworkLeave(); //ensure this is called - even during early out
return true;
}
#if __BANK
utimer_t debugStartTime = sysTimer::GetTicks();
#endif
bool fullUpdate = CPedAILodManager::ShouldDoFullUpdate(*this);
TUNE_BOOL(TimesliceProcessControl, true);
bool fullUpdateForIntelligence = fullUpdate;
fullUpdate = TimesliceProcessControl ? fullUpdate : true;
// Block if necessary and wait for the anim state to be updated
ProcessControl_AnimStateUpdate();
if(fullUpdate)
{
//Process the component reservations.
ProcessControl_ComponentReservations();
// Fire off an asynchronous probe to test if this physical is in a river and how deep it
// is submerged for the buoyancy code. Don't do this if the ped is in a vehicle or if the
// ped is using low lod physics.
ProcessBuoyancyProbe();
// check for HD resource requests from any peds which may support HD
if (m_pedLodState != PLS_HD_NA)
{
// pgDictionaryBase::SetListOwnerThread(sysIpcGetCurrentThreadId());
Update_HD_Models();
// pgDictionaryBase::SetListOwnerThread(sysIpcCurrentThreadIdInvalid);
}
// Any function calls relating to ped population
ProcessControl_Population();
// Update any audio related members, variables
ProcessControl_Audio();
}
if(m_pedLodState == PLS_HD_AVAILABLE)
{
perfClearingHouse::Increment(perfClearingHouse::HIGH_LOD_PEDS);
}
else
{
perfClearingHouse::Increment(perfClearingHouse::LOW_LOD_PEDS);
}
// Call process on any related ped data
ProcessControl_Data();
// Reset a load of flags before the main intelligence update
ProcessControl_ResetVariables();
// Update the peds inventory, weapons and any other accessories that need to be done before the AI update
ProcessControl_WeaponsAndAccessoriesPreIntelligence();
//ProcessJetpack();
// Main per frame update of the AI
if (!GetPedResetFlag(CPED_RESET_FLAG_SkipAiUpdateProcessControl))
{
//@TEMP
ProcessControl_Intelligence(fullUpdateForIntelligence);
//ProcessControl_Intelligence(fullUpdate);
}
// Update the peds inventory, weapons and any other accessories that need to be done after the AI update
ProcessControl_WeaponsAndAccessoriesPostIntelligence(fullUpdate REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()));
// Release any coverpoint we have if there are no tasks that want it
if (!GetPedResetFlag(CPED_RESET_FLAG_KeepCoverPoint))
{
ReleaseCoverPoint();
}
if( !GetPedResetFlag(CPED_RESET_FLAG_KeepDesiredCoverPoint) ||
(m_pDesiredCoverPoint && !m_pDesiredCoverPoint->CanAccomodateAnotherPed()) )
{
ReleaseDesiredCoverPoint();
}
// Process control animation update
ProcessControl_Animation();
// Graphics update including damage mapping and vfx
ProcessControl_Graphics(fullUpdate);
// Any functions, variables related to physics that are updated during process control
ProcessControl_Physics();
ProcessControl_SpecialAbilityChargeEvents();
ProcessControl_SpecialAbilities();
#if __BANK
// B*2343564: Debug tunables to modify cop perception parameters.
TUNE_GROUP_BOOL(COP_PERCEPTION_OVERRIDE, bEnableOverrides, false);
if (bEnableOverrides)
{
CPedPerception::ms_bCopOverridesSet = bEnableOverrides;
TUNE_GROUP_FLOAT(COP_PERCEPTION_OVERRIDE, HearingRange, CPedPerception::ms_fSenseRange, -360.0f, 360.0f, 0.01f);
TUNE_GROUP_FLOAT(COP_PERCEPTION_OVERRIDE, SeeingRange, CPedPerception::ms_fSenseRange, -360.0f, 360.0f, 0.01f);
TUNE_GROUP_FLOAT(COP_PERCEPTION_OVERRIDE, SeeingRangePeripheral, CPedPerception::ms_fSenseRangePeripheral, -360.0f, 360.0f, 0.01f);
TUNE_GROUP_FLOAT(COP_PERCEPTION_OVERRIDE, MinAzimuthAngle, CPedPerception::ms_fVisualFieldMinAzimuthAngle, -360.0f, 360.0f, 0.01f);
TUNE_GROUP_FLOAT(COP_PERCEPTION_OVERRIDE, MaxAzimuthAngle, CPedPerception::ms_fVisualFieldMaxAzimuthAngle, -360.0f, 360.0f, 0.01f);
TUNE_GROUP_FLOAT(COP_PERCEPTION_OVERRIDE, CentreOfGazeMaxAngle, CPedPerception::ms_fCentreOfGazeMaxAngle, 0.0f, 360.0f, 0.01f);
CPedPerception::ms_fCopHearingRangeOverride = HearingRange;
CPedPerception::ms_fCopSeeingRangeOverride = SeeingRange;
CPedPerception::ms_fCopSeeingRangePeripheralOverride = SeeingRangePeripheral;
CPedPerception::ms_fCopVisualFieldMinAzimuthAngleOverride = MinAzimuthAngle;
CPedPerception::ms_fCopVisualFieldMaxAzimuthAngleOverride = MaxAzimuthAngle;
CPedPerception::ms_fCopCentreOfGazeMaxAngleOverride = CentreOfGazeMaxAngle;
}
#endif // __BANK
#if __BANK
m_processControlTime = sysTimer::GetTicksToMicroseconds() * (sysTimer::GetTicks() - debugStartTime);
#endif
if(GetIsVisibleInSomeViewportThisFrame())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_PedHasBeenSeen, true);
}
#if ENABLE_JETPACK
ProcessJetpack();
#endif
if (IsLocalPlayer() && GetVehiclePedEntering() REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()))
{
if ((GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) || GetPedResetFlag(CPED_RESET_FLAG_IsEnteringVehicle)) && !CVehicle::DialsOverriddenByScript())
GetVehiclePedEntering()->RequestDials();
//make sure dials render in synced scenes as well, when the player is removed from vehicle
else if (this->GetAnimDirector() && !CVehicle::DialsOverriddenByScript())
{
// are we in a synchronized scene?
fwAnimDirectorComponentSyncedScene* pSceneComponent = static_cast<fwAnimDirectorComponentSyncedScene*>(this->GetAnimDirector()->GetComponentByType(fwAnimDirectorComponent::kComponentTypeSyncedScene));
if (pSceneComponent && pSceneComponent->IsPlayingSyncedScene())
{
fwSyncedSceneId sceneId = pSceneComponent->GetSyncedSceneId();
fwEntity* sceneAttachParent = fwAnimDirectorComponentSyncedScene::GetSyncedSceneAttachEntity(sceneId);
if(sceneAttachParent && static_cast<CEntity*>(sceneAttachParent)->GetIsTypeVehicle())
{
static_cast<CVehicle*>(sceneAttachParent)->RequestDials();
}
}
}
}
// Call the inherited CPhysical process control.
return CPhysical::ProcessControl();
}
#if __BANK
void CPed::ProcessHeadBlendDebug()
{
if ((PARAM_headblendcheck.Get() || MeshBlendManager::ms_showNonFinalizedPeds) && HasHeadBlend())
{
CPedHeadBlendData* blendData = GetExtensionList().GetExtension<CPedHeadBlendData>();
if (blendData && !MESHBLENDMANAGER.IsBlendFinalized(blendData->m_blendHandle))
{
Vector3 pos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) + Vector3(0,0,1.1f);
char buf[64];
formatf(buf, "NOT FINALIZED (%p %d)", this, blendData->m_blendHandle);
grcDebugDraw::Text(pos, Color_NavyBlue, 0, grcDebugDraw::GetScreenSpaceTextHeight(), buf);
}
}
}
void CPed::ProcessStreamingDebug()
{
if (PARAM_pedstreamingcheck.Get() || CPedVariationDebug::displayStreamingRequests)
{
CPedStreamRequestGfx* req = GetExtensionList().GetExtension<CPedStreamRequestGfx>();
if (req)
{
Vector3 pos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()) + Vector3(0,0,0.5f);
char line[256];
int numLines = 0;
bool firstLine = true;
for (u32 i = 0; i < PV_MAX_COMP; ++i)
{
char buf[64];
formatf(line, "%s: ", varSlotNames[i]);
bool firstOnLine = true;
if(req->IsDwdSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetDwdNames().size() > i)
formatf(buf, "dwd %s", req->GetDwdNames()[i].TryGetCStr());
else
#endif
formatf(buf, "dwd");
if(!firstOnLine)
strcat(line, ", ");
strcat(line, buf);
firstOnLine = false;
}
if(req->IsTxdSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetTxdNames().size() > i)
formatf(buf, "txd %s", req->GetTxdNames()[i].TryGetCStr());
else
#endif
formatf(buf, "txd");
if(!firstOnLine)
strcat(line, ", ");
strcat(line, buf);
firstOnLine = false;
}
if(req->IsCldSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetCldNames().size() > i)
formatf(buf, "cld %s", req->GetCldNames()[i].TryGetCStr());
else
#endif
formatf(buf, "cld");
if(!firstOnLine)
strcat(line, ", ");
strcat(line, buf);
firstOnLine = false;
}
if(req->IsFpAltSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetFpAltNames().size() > i)
formatf(buf, "FPalt (%s)", req->GetFpAltNames()[i].TryGetCStr());
else
#endif
formatf(buf, "FPalt");
if(!firstOnLine)
strcat(line, ", ");
strcat(line, buf);
firstOnLine = false;
}
if(!firstOnLine)
{
if(firstLine)
{
pedDebugf1("Ped %p is loading variations:", this);
grcDebugDraw::Text(pos, Color_NavyBlue, 0, 0, "Variations loading:");
++numLines;
firstLine = false;
}
grcDebugDraw::Text(pos, Color_NavyBlue, 0, numLines*grcDebugDraw::GetScreenSpaceTextHeight(), line);
pedDebugf1("%s", line);
++numLines;
}
}
for (u32 i = 0; i < HBS_MAX; ++i)
{
line[0] = 0;
if(req->IsHeadBlendSlotPending(i))
{
#if GTA_REPLAY && !USE_HASH_ONLY_FOR_OBJECT_NAMES
if(req->GetHeadBlendNames().size() > i)
formatf(line, "head blend slot %d dwd %s", i, req->GetHeadBlendNames()[i].TryGetCStr());
else
#endif
formatf(line, "head blend slot %d dwd", i);
}
if(line[0])
{
if(firstLine)
{
pedDebugf1("Ped %p is loading variations:", this);
grcDebugDraw::Text(pos, Color_NavyBlue, 0, 0, "Variations loading:");
++numLines;
firstLine = false;
}
grcDebugDraw::Text(pos, Color_NavyBlue, 0, numLines*grcDebugDraw::GetScreenSpaceTextHeight(), line);
pedDebugf1("%s", line);
++numLines;
}
}
}
}
}
#endif // __BANK
void CPed::ProcessCollisionVfx(VfxCollisionInfo_s& vfxColnInfo)
{
if (GetPedVfx() && GetPedVfx()->ProcessCollisionVfx(vfxColnInfo))
{
CEntity::ProcessCollisionVfx(vfxColnInfo);
}
}
#if ENABLE_JETPACK
void CPed::ProcessJetpack()
{
CPedInventory* pPedInventory = GetInventory();
//! Remove jetpack from inventory if dead.
if(m_pJetpack && m_pJetpack->GetObject() && IsDead())
{
pPedInventory->RemoveWeapon(GADGETTYPE_JETPACK);
}
if(GetPedConfigFlag(CPED_CONFIG_FLAG_EquipJetpack) && pPedInventory && pPedInventory->GetWeapon(GADGETTYPE_JETPACK))
{
if(!m_pJetpack)
{
m_pJetpack = CJetpackManager::CreateJetpackContainer();
}
if(Verifyf(m_pJetpack, "Failed to create jetpack - increase pool size?"))
{
bool bAttach = false;
//! Stream in jetpack model.
m_pJetpack->Request(CTaskJetpack::sm_Tunables.m_JetpackModelData.m_JetpackModelName);
if(!m_pJetpack->GetObject() && m_pJetpack->HasLoaded())
{
CreateJetpack(m_pJetpack);
bAttach = true;
}
if(m_pJetpack->GetObject())
{
if(bAttach)
{
AttachJetpackToPed();
}
if(GetIsInVehicle())
{
m_pJetpack->GetObject()->SetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY, false);
}
else
{
m_pJetpack->GetObject()->SetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY, true);
}
phInst* pInst = GetAnimatedInst();
if(pInst && pInst->IsInLevel())
{
//Grab the level index.
u16 uLevelIndex = pInst->GetLevelIndex();
//Set inactive collisions.
PHLEVEL->SetInactiveCollidesAgainstInactive(uLevelIndex, true);
PHLEVEL->SetInactiveCollidesAgainstFixed(uLevelIndex, true);
}
}
//! Auto update attach offset so that we can tune this via RAG.
#if __BANK
if(m_pJetpack->GetObject())
{
fwAttachmentEntityExtension *extension = m_pJetpack->GetObject()->GetAttachmentExtension();
if(extension)
{
extension->SetAttachOffset(CTaskJetpack::sm_Tunables.m_JetpackModelData.m_JetpackAttachOffset);
}
}
#endif
}
}
else
{
if(m_pJetpack)
{
//! remove as a pickup if it's not in our inventory.
if(!pPedInventory->GetWeapon(GADGETTYPE_JETPACK) && !GetPedConfigFlag( CPED_CONFIG_FLAG_DontDropJetpackOnDeath ))
{
DropJetpackObject();
}
else
{
//! otherwise hide and destroy.
DestroyJetpack();
}
}
}
}
bool CPed::CreateJetpack(CJetpack *pJetpack)
{
pedFatalAssertf(pJetpack, "Calling CreateJetpack() before initialising jetpack container!");
if(!taskVerifyf(pJetpack->CreateObject(), "Couldn't create jetpack!"))
{
return false;
}
//! put jetpack in same place as ped for now.
pJetpack->GetObject()->SetMatrix(MAT34V_TO_MATRIX34(GetMatrix()));
return true;
}
void CPed::AttachJetpackToPed()
{
if(!m_pJetpack || !m_pJetpack->GetObject())
{
return;
}
//Calculate the attach offset.
Vector3 vAttachOffset = CTaskJetpack::sm_Tunables.m_JetpackModelData.m_JetpackAttachOffset;
const crSkeletonData &skeletonData = GetSkeletonData();
int spineIndex = -1;
skeletonData.ConvertBoneIdToIndex((u16)BONETAG_SPINE3, spineIndex);
Quaternion qAttach;
//! Spine bone doesn't have z up :(
static Vector3 vRotateAxis = YAXIS;
static dev_float fRotateAngle = HALF_PI;
qAttach.FromRotation(vRotateAxis,fRotateAngle);
//Ensure the parachute is not attached.
if(m_pJetpack->GetObject()->GetIsAttached())
{
m_pJetpack->GetObject()->DetachFromParent(0);
}
//Attach the ped to the jetpack.
m_pJetpack->GetObject()->AttachToPhysicalBasic(this, (s16)spineIndex, ATTACH_STATE_BASIC | ATTACH_FLAG_INITIAL_WARP, &vAttachOffset, &qAttach);
//! Immediately disable collision ( can't specify physics to be off anymore in CreateObject( ) )
//! We disable after attaching to the ped to avoid overriding collision flags.
m_pJetpack->GetObject()->DisableCollision(NULL, true);
}
void CPed::DetachJetpackFromPed()
{
//Ensure the parachute is not attached.
if(m_pJetpack && m_pJetpack->GetObject())
{
if(m_pJetpack->GetObject()->GetIsAttached())
{
m_pJetpack->GetObject()->DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_APPLY_VELOCITY|DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR);
}
}
}
void CPed::DestroyJetpack()
{
DetachJetpackFromPed();
if(m_pJetpack)
{
if(m_pJetpack->GetObject())
m_pJetpack->GetObject()->SetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY, false);
m_pJetpack->DestroyObject();
phInst* pInst = GetAnimatedInst();
if(pInst && pInst->IsInLevel())
{
//Grab the level index.
u16 uLevelIndex = pInst->GetLevelIndex();
//Set inactive collisions.
PHLEVEL->SetInactiveCollidesAgainstInactive(uLevelIndex, false);
PHLEVEL->SetInactiveCollidesAgainstFixed(uLevelIndex, false);
}
//! delete jetpack now that we are not using it.
delete m_pJetpack;
m_pJetpack = NULL;
}
}
void CPed::PickupJetpackObject(CPickup *UNUSED_PARAM(pObject))
{
//! TO DO - use existing jetpack instead of re-creating one?
}
void CPed::DropJetpackObject()
{
if(m_pJetpack)
{
//! Remove jetpack from container.
CPedInventory* pPedInventory = GetInventory();
pPedInventory->RemoveWeapon(GADGETTYPE_JETPACK);
//! Create a new pickup from jetpack object.
if(m_pJetpack->GetObject())
{
Matrix34 m;
m = MAT34V_TO_MATRIX34(m_pJetpack->GetObject()->GetTransform().GetMatrix());
CJetpackManager::CreateJetpackPickup(m, this);
}
DestroyJetpack();
}
}
#endif
void sComputeMatrixRotatedAroundUnitAxis(Mat34V_Ref mtrxOutV, Mat34V_ConstRef mtrxInV, Vec3V_In unitAxisV, ScalarV_In rotAmountV)
{
// What we will be doing here is the same as this, but should be faster by doing all
// the work in the vector pipeline with no floats or memory usage.
// mtrxOutV = mtrxInV;
// RC_MATRIX34(mtrxOutV).RotateUnitAxis(VEC3V_TO_VECTOR3(unitAxisV), rotAmountV.Getf());
const Vec3V pAV = mtrxInV.GetCol0();
const Vec3V pBV = mtrxInV.GetCol1();
const Vec3V pCV = mtrxInV.GetCol2();
// Set up some constants. Unfortunately there is no V_HALF_PI.
const ScalarV halfPiV(Vec::V4VConstantSplat<FLOAT_TO_INT(0.5f*PI)>());
const Vec4V zeroV(V_ZERO);
// Prepare for sine/cosine computation.
const Vec4V anglesCosSinV = MergeXY(Vec4V(halfPiV), zeroV);
const Vec4V anglesV = rage::Add(Vec4V(rotAmountV), anglesCosSinV);
// Compute the sine and cosine.
const Vec4V cosSinCosSinV = Sin(anglesV);
const ScalarV cosV = cosSinCosSinV.GetX();
const ScalarV sinV = cosSinCosSinV.GetY();
// What we are doing here is basically computing the axes a, b, and c
// of a matrix for rotating around the axis, adapted from Matrix34::MakeRotateUnitAxis():
// float omc = 1.0f - cos;
// Vector3 vscaled = omc*v;
// Vector3 vsin = sin*v;
// a.x=vscaled.x*v.x + cos;
// a.y=vscaled.x*v.y + vsin.z;
// a.z=vscaled.x*v.z - vsin.y;
// b.x=vscaled.x*v.y - vsin.z;
// b.y=vscaled.y*v.y + cos;
// b.z=vscaled.y*v.z + vsin.x;
// c.x=vscaled.x*v.z + vsin.y;
// c.y=vscaled.y*v.z - vsin.x;
// c.z=vscaled.z*v.z + cos;
const Vec3V axisTimesOneMinusCosV = SubtractScaled(unitAxisV, unitAxisV, cosV);
const Vec3V axisTimesSinV = Scale(unitAxisV, sinV);
const Vec3V negAxisTimesSinV = SubtractScaled(Vec3V(V_ZERO), unitAxisV, sinV);
// Note: the stuff below is probably not optimal, could probably be smarter about
// how we build the vectors. Probably not worth the effort, though.
const Vec3V pV(axisTimesOneMinusCosV.GetY(), axisTimesOneMinusCosV.GetY(), axisTimesOneMinusCosV.GetZ());
const Vec3V qV(unitAxisV.GetY(), unitAxisV.GetZ(), unitAxisV.GetZ());
const Vec3V pqV = Scale(pV, qV);
const Vec3V a1V = Scale(axisTimesOneMinusCosV.GetX(), unitAxisV);
const Vec3V b1V(a1V.GetY(), pqV.GetX(), pqV.GetY());
const Vec3V c1V(a1V.GetZ(), pqV.GetY(), pqV.GetZ());
const Vec3V a2V(cosV, axisTimesSinV.GetZ(), negAxisTimesSinV.GetY());
const Vec3V b2V(negAxisTimesSinV.GetZ(), cosV, axisTimesSinV.GetX());
const Vec3V c2V(axisTimesSinV.GetY(), negAxisTimesSinV.GetX(), cosV);
const Vec3V aV = Add(a1V, a2V);
const Vec3V bV = Add(b1V, b2V);
const Vec3V cV = Add(c1V, c2V);
const ScalarV pAXV = pAV.GetX();
const ScalarV pAYV = pAV.GetY();
const ScalarV pAZV = pAV.GetZ();
const ScalarV pBXV = pBV.GetX();
const ScalarV pBYV = pBV.GetY();
const ScalarV pBZV = pBV.GetZ();
const ScalarV pCXV = pCV.GetX();
const ScalarV pCYV = pCV.GetY();
const ScalarV pCZV = pCV.GetZ();
const Vec3V da1V = Scale(aV, pAXV);
const Vec3V db1V = Scale(aV, pBXV);
const Vec3V dc1V = Scale(aV, pCXV);
const Vec3V da2V = AddScaled(da1V, bV, pAYV);
const Vec3V db2V = AddScaled(db1V, bV, pBYV);
const Vec3V dc2V = AddScaled(dc1V, bV, pCYV);
const Vec3V daV = AddScaled(da2V, cV, pAZV);
const Vec3V dbV = AddScaled(db2V, cV, pBZV);
const Vec3V dcV = AddScaled(dc2V, cV, pCZV);
mtrxOutV.SetCol0(daV);
mtrxOutV.SetCol1(dbV);
mtrxOutV.SetCol2(dcV);
}
// New more optimized version:
void CPed::SimulatePhysicsForLowLod()
{
PF_FUNC(SimulatePhysicsForLowLod);
Assert(IsUsingLowLodPhysics() || GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics));
const float fTimeStep = fwTimer::GetTimeStep();
// 8/9/12 - cthomas - We now timeslice the velocity updates for low lod peds.
const int cMaskToTimesliceVelocityUpdateIfVisibile = 3;
const int cMaskToTimesliceVelocityUpdateIfNotVisibile = 7;
const bool bIsVisible = GetIsVisibleInSomeViewportThisFrame();
const int maskToTimesliceVelocityUpdateForLowLodPeds = bIsVisible ? cMaskToTimesliceVelocityUpdateIfVisibile : cMaskToTimesliceVelocityUpdateIfNotVisibile;
const int pedPoolIndex = CPed::GetPool()->GetJustIndex(this);
const int iCurrentFrame = fwTimer::GetFrameCount();
const bool bUpdateVelocitiesThisFrame = (pedPoolIndex & maskToTimesliceVelocityUpdateForLowLodPeds) == (iCurrentFrame & maskToTimesliceVelocityUpdateForLowLodPeds);
if (GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics))
{
if (GetPedResetFlag(CPED_RESET_FLAG_UsingMoverExtraction))
{
SetDesiredVelocity(VEC3_ZERO);
SetDesiredAngularVelocity(VEC3_ZERO);
}
else
{
// use the velocity provided by animation.
SetDesiredVelocity(VEC3V_TO_VECTOR3(Transform3x3(GetMatrix(), VECTOR3_TO_VEC3V(GetDynamicComponent()->GetAnimatedVelocity()))));
SetDesiredAngularVelocity(VEC3V_TO_VECTOR3(GetMatrix().c()*ScalarV(GetDynamicComponent()->GetAnimatedAngularVelocity())));
}
// inform the network blender we have calculate the desired velocity
NetworkInterface::OnDesiredVelocityCalculated(*this);
}
else
{
if(bUpdateVelocitiesThisFrame)
{
ApplyMovementRequests(fwTimer::GetTimeStep(), true, true);
// When updating angular velocity, we need to be careful so we don't overshoot the desired heading.
float fInvTimestepsUntilNextUpdate;
if(bIsVisible)
{
fInvTimestepsUntilNextUpdate = 0.25f; // (float)(cMaskToTimesliceVelocityUpdateIfVisibile + 1)
}
else
{
fInvTimestepsUntilNextUpdate = 0.125f; // (float)(cMaskToTimesliceVelocityUpdateIfNotVisibile + 1)
}
// Approximate the time step between timesliced angular velocity updates by multiplying by the update period in frames.
const float fInvOvershootTimeStep = fwTimer::GetInvTimeStep()*fInvTimestepsUntilNextUpdate;
// Get the desired angular velocity around the Z axis.
const float fOriginalHeadingVel = m_vDesiredAngularVelocity.z;
// Compute the delta between the current heading and the desired heading.
const float fAngDiff = fwAngle::LimitRadianAngle(GetDesiredHeading() - GetCurrentHeading());
// First, generate a value that's either 1, if fAngDiff and fTotalHeadingVel have the same sign, or -1.
const float fSameDir = Selectf(Selectf(fAngDiff, fOriginalHeadingVel, -fOriginalHeadingVel), 1.0f, -1.0f);
// Compute how fast we could possibly go without risking overshooting before our next update.
const float fMaxVel = Abs(fAngDiff*fInvOvershootTimeStep);
// Clamp the angular velocity against the limit we computed.
const float fClampedHeadingVel = Clamp(fOriginalHeadingVel, -fMaxVel, fMaxVel);
// Make use of this clamped velocity, except for if it would turn us away from the desired
// heading, in which case we make it zero.
const float fNewHeadingVel = Selectf(fSameDir, fClampedHeadingVel, 0.0f);
// Store it.
m_vDesiredAngularVelocity.z = fNewHeadingVel;
}
}
// update the network blender to allow it to adjust the ped's velocity to
// move it faster towards it's target position if possible
if(IsNetworkClone())
{
netObject *networkObject = GetNetworkObject();
if(networkObject->GetNetBlender())
{
networkObject->GetNetBlender()->ProcessPrePhysics();
if (networkObject->CanBlend())
{
networkObject->GetNetBlender()->Update();
}
}
}
bool usingRagdoll = GetUsingRagdoll();
CTask::BeginProcessPhysicsUpdates(1);
bool bNeedsToBeAwake = GetPedIntelligence()->ProcessPhysics(fTimeStep, 0, usingRagdoll);
CTask::EndProcessPhysicsUpdate();
// We can probably make the assumption that we don't want to do any of the stuff below if using ragdoll.
if(Unlikely(usingRagdoll))
{
return;
}
// skip default physics update
// if we're fixed, it doesn't matter what wants to wake us up because we're not gonna
if(Unlikely(GetIsAnyFixedFlagSet()))
{
return;
}
phInst* pCurrentPhysInst = GetCurrentPhysicsInst();
if(Unlikely(!pCurrentPhysInst->IsInLevel()))
{
return;
}
const fwAttachmentEntityExtension *attachment = GetAttachmentExtension();
bool attached = false;
if(attachment && attachment->GetIsAttached())
{
// Not sure, do we need to support this?
Assert(!attachment->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION));
const int attachState = attachment->GetAttachState();
if(attachState == ATTACH_STATE_BASIC || attachState == ATTACH_STATE_WORLD)
{
return;
}
attached = true;
}
const Vec3V desiredAngVelV = VECTOR3_TO_VEC3V(GetDesiredAngularVelocity());
Assertf(IsFiniteAll(desiredAngVelV), "Desired angular velocity is invalid.");
const ScalarV desiredAngVelMagSqV = MagSquared(desiredAngVelV);
// Special cases under which we don't want to miss an update
if(!bNeedsToBeAwake)
{
if(GetIsStanding() || GetPedResetFlag(CPED_RESET_FLAG_NoCollisionMovementMode))
{
// If this ped is trying to turn or to move, then we can't go to sleep.
ScalarV scThreshold(V_FLT_SMALL_2); // 0.01f
if(IsLessThanOrEqualAll(desiredAngVelMagSqV, scThreshold))
{
const Vec3V vRelVel = VECTOR3_TO_VEC3V(m_vDesiredVelocity);
if(IsLessThanOrEqualAll(MagSquared(vRelVel), scThreshold))
{
return;
}
}
}
}
// Update/approximate ped's in-water state
// Try using the navmesh polygon under the ped for an early out
// If there is no water poly underfoot, then we don't need to do any further tests
// JB: Note that as of CL 3975033 peds are no longer allowed to be in low physics LOD when in water
//const bool bIsInWater = false;
const Mat34V& mtrxRefV = GetMatrixRef();
const Vec3V oldPosV = mtrxRefV.GetCol3();
/*
ScalarV waterZV; // Will get initialized if bIsInWater.
if( (m_NavMeshTracker.GetIsValid() && m_NavMeshTracker.GetNavPolyData().m_bIsWater) || GetPedResetFlag(CPED_RESET_FLAG_ForceLowLodWaterCheck) )
{
Vector3 vWaterCheckPos = VEC3V_TO_VECTOR3(oldPosV);
vWaterCheckPos.z -= GetCapsuleInfo()->GetGroundToRootOffset();
// We are possibly in water.
// Now do the more expensive tests to determine the water's depth, etc.
float fWaterZ = 0.0f;
if(CWaterTestHelper::GetWaterHeightAtPositionIncludingRivers(vWaterCheckPos, &fWaterZ))
{
const Vec3V bndBoxMinV = VECTOR3_TO_VEC3V(GetBoundingBoxMin());
const ScalarV thresholdV = rage::Add(oldPosV.GetZ(), bndBoxMinV.GetZ());
waterZV = LoadScalar32IntoScalarV(fWaterZ);
if(IsGreaterThanAll(waterZV, thresholdV))
{
bIsInWater = true;
}
}
}
*/
const bool wasStanding = GetIsStanding();
const bool wasSwimming = GetIsSwimming();
SetWasStanding( wasStanding );
SetWasSwimming( wasSwimming );
/*
if(bIsInWater)
{
SetIsStanding( false );
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, true );
SetIsInWater( true );
SetIsSwimming( true );
}
else */ if(!GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir))
{
SetIsStanding( true);
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, false );
SetIsInWater( GetTaskData().GetIsFlagSet(CTaskFlags::ConsiderAsInWaterInLowLodPhysics) );
SetIsSwimming( false );
}
else
{
SetIsStanding( false);
SetPedResetFlag( CPED_RESET_FLAG_IsDrowning, false );
SetIsInWater( false );
SetIsSwimming( false );
}
if(attached)
{
if (GetPedResetFlag(CPED_RESET_FLAG_ApplyAnimatedVelocityWhilstAttached))
{
ApplyAnimatedVelocityWhilstAttached(fTimeStep);
}
return;
}
// Determine if we should make a position update this frame or not.
float fUpdatePosTimeStep = fTimeStep;
const CPedAILod& lod = GetPedAiLod();
if(CPedAILodManager::MayPedSimulatePhysicsForLowLodSkipPosUpdates() && lod.IsLodFlagSet(CPedAILod::AL_LodPhysics))
{
// If this ped is not visible or if we are already timeslicing the animations,
// consider skipping the position update this frame.
bool allowSkipUpdate = false;
if(!bIsVisible)
{
// If not visible, we can skip position updates.
allowSkipUpdate = true;
}
else
{
// If timesliced, we can skip position updates, but only if we are far enough away, otherwise
// it can look quite bad in combination with the skipped animation frames.
if(lod.IsLodFlagSet(CPedAILod::AL_LodTimesliceAnimUpdate))
{
allowSkipUpdate = lod.MaySkipVisiblePositionUpdatesInLowLod();
}
}
if(allowSkipUpdate)
{
// If we are either not visible, or if we are using timeslicing with a period of 2, synchronize
// the position updates with the animation updates - in the period 2 case, it looks considerably
// worse if the position and animation updates happen on alternating frames.
if(!bIsVisible || (lod.IsLodFlagSet(CPedAILod::AL_LodTimesliceAnimUpdate) && lod.GetAnimUpdatePeriod() == 2))
{
// If not visible, tie the position updates to the animation updates.
if(!lod.ShouldUpdateAnimsThisFrame())
{
return;
}
fUpdatePosTimeStep = lod.GetUpdateAnimsTimeStep();
}
else
{
// If visible, update every other frame, with twice the time step.
// Skipping more than that tends to look quite bad, whether in sync
// with the animation updates or not.
if(!(pedPoolIndex & 1) == (iCurrentFrame & 1))
{
return;
}
fUpdatePosTimeStep *= 2.0f;
}
}
}
if (GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsuleControl))
return;
const Vec3V desiredVelV = VECTOR3_TO_VEC3V(GetDesiredVelocity());
Assertf(IsFiniteAll(desiredVelV), "Desired velocity is invalid.");
Assertf(MagSquared(desiredVelV).Getf() < (DEFAULT_IMPULSE_LIMIT * DEFAULT_IMPULSE_LIMIT + 0.0001f), "Desired velocity Mag2 (%f) > %f * %f", MagSquared(desiredVelV).Getf(), DEFAULT_IMPULSE_LIMIT, DEFAULT_IMPULSE_LIMIT);
PDR_ONLY(debugPlayback::RecordPedDesiredVelocity(pCurrentPhysInst, desiredVelV, desiredAngVelV));
const ScalarV timeStepV(fUpdatePosTimeStep);
Mat34V matNewV;
const ScalarV turnAmountSqV = desiredAngVelMagSqV;
const ScalarV turnThresholdSqV(Vec::V4VConstantSplat<FLOAT_TO_INT(1e-8f)>()); // square(0.0001f);
if(IsGreaterThanAll(turnAmountSqV, turnThresholdSqV))
{
const ScalarV turnAmountV = Sqrt(turnAmountSqV);
const Vec3V turnAxisV = InvScale(desiredAngVelV, turnAmountV);
const ScalarV turnAmountThisFrameV = Scale(turnAmountV, timeStepV);
sComputeMatrixRotatedAroundUnitAxis(matNewV, GetMatrixRef(), turnAxisV, turnAmountThisFrameV);
}
else
{
matNewV.Set3x3(mtrxRefV);
}
// Since we keep multiplying matrices together, round-off errors could accumulate
// to the point of making the matrix not orthonormal. Every 128 frames,
// we re-orthonormalize. The random seed value is used to distribute this across
// peds, it's in a place where it's likely to be in the caches already.
// JB: I've upped the frequency to every 32 frames, see (url:bugstar:1704614)
// FF: I added an actual update counter, too, so we always reorthonormalize after a while.
// There are some early returns above which could bypass the other mechanism.
static const int kMaxUpdatesWithoutReOrtho = 64;
CompileTimeAssert(kMaxUpdatesWithoutReOrtho <= 255); // We use a u8 for the counter.
if(((unsigned int)(fwTimer::GetFrameCount() & 0x1f) == (unsigned int)(m_randomSeed & 0x1f))
|| (m_SimPhysForLowLodMtrxMulCtr >= kMaxUpdatesWithoutReOrtho))
{
ReOrthonormalize3x3(matNewV, matNewV);
m_SimPhysForLowLodMtrxMulCtr = 0;
}
else
{
m_SimPhysForLowLodMtrxMulCtr++;
}
Vec3V newPosV = AddScaled(oldPosV, desiredVelV, timeStepV);
// Note: the position in matNewV is still not initialized yet - we will write newPosV in there after the adjustments below.
bool updatePhysics = true;
if(GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics))
{
// Correct the z component with the position sampled directly from navmesh
if( GetPedConfigFlag(CPED_CONFIG_FLAG_LowPhysicsLodMayPlaceOnNavMesh) && m_NavMeshTracker.GetIsValid() && !GetIsInVehicle() )
{
// Only allow snapping to the navmesh if not in the water (or in the air?)
//if(!bIsInWater)
{
const ScalarV maxDeltaZV(V_TWO); // 0.5f
const ScalarV groundToRootOffsetV = LoadScalar32IntoScalarV(GetCapsuleInfo()->GetGroundToRootOffsetRef());
const ScalarV lastNavMeshIsectZV = RCC_VEC3V(m_NavMeshTracker.GetLastNavMeshIntersection()).GetZ();
const ScalarV navMeshZV = rage::Add(groundToRootOffsetV, lastNavMeshIsectZV);
const ScalarV newPosZBeforeV = newPosV.GetZ();
// Set the Z position to navMeshZV if we are close enough already.
const BoolV closeV = IsClose(newPosZBeforeV, navMeshZV, maxDeltaZV);
newPosV.SetZ(SelectFT(closeV, newPosZBeforeV, navMeshZV));
// Special logic to align qpeds to slope in low lod mode.
if (m_pCapsuleInfo->IsQuadruped())
{
// Pretend the ground normal is the same as the navmesh poly normal.
Vec3V vFakeGroundNormal = VECTOR3_TO_VEC3V(m_NavMeshTracker.GetPolyNormal());
// Extract the current pitch/heading.
const Matrix34 mMat = MAT34V_TO_MATRIX34(matNewV);
const Vector3 vRotation = mMat.GetEulers("xyz");
const float fHeading = vRotation.z;
float fCurrentPitch = vRotation.x;
float fDesiredPitch = 0.0f;
// No point bothering with the math below if the ped is on flat ground. Also avoids normalizing a zero vector.
if (vFakeGroundNormal.GetZf() < 0.999f && !IsCloseAll(vFakeGroundNormal, Vec3V(V_ZERO), ScalarV(SMALL_FLOAT)) && IsFiniteAll(vFakeGroundNormal))
{
// Grab a copy of the ped's forward vector.
Vec3V vForward = matNewV.GetCol1();
// Slope is the angle rotated away from WORLD_UP.
const float fGroundSlope = Angle(vFakeGroundNormal, VECTOR3_TO_VEC3V(ZAXIS)).Getf();
const ScalarV zeroV(V_ZERO);
vForward.SetZ(zeroV);
vFakeGroundNormal.SetZ(zeroV);
vForward = Normalize(vForward);
vFakeGroundNormal = Normalize(vFakeGroundNormal);
// Dot it with the forward vector (ignoring z) to match up with the angle that the qped is approaching the slope.
fDesiredPitch = fGroundSlope * Dot(vForward, vFakeGroundNormal).Getf();
fDesiredPitch = -fDesiredPitch;
}
// Smoothly approach the desired pitch to avoid pops.
Approach(fCurrentPitch, fDesiredPitch, 1.0f, fUpdatePosTimeStep);
// Rebuild the ped matrix.
Matrix34 matNew(Matrix34::IdentityType);
matNew.RotateLocalZ(fHeading);
matNew.RotateLocalX(fCurrentPitch);
matNewV = MATRIX34_TO_MAT34V(matNew);
// Sanity checks.
Assertf(IsFiniteAll(matNewV), "New low lod qped matrix is not finite. Navmesh normal: (%f, %f, %f)", m_NavMeshTracker.GetPolyNormal().x, m_NavMeshTracker.GetPolyNormal().y, m_NavMeshTracker.GetPolyNormal().z);
Assertf(matNew.IsOrthonormal(), "New low lod animal ped matrix is not orthonormal.");
}
}
// If the ped is in water, then set height directly from water
// Maybe this should be outside of the above 'CPED_CONFIG_FLAG_LowPhysicsLodMayPlaceOnNavMesh' conditional block?
/*
else
{
const ScalarV offsetTweakV(-0.4f);
newPosV.SetZ(rage::Add(waterZV, offsetTweakV));
}
*/
}
}
else
{
Assert(GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics));
// Don't update the physics for peds playing a synced scene anim since this will get updated later in
// fwAnimDirector::UpdateSyncedSceneAnimMover() once the new position is set. This is a fix for the last matrices
// of these types of peds getting set at a time (here) such that the last and current inst matrix would be identical
// once ragdoll simulation starts.
fwAnimDirector* pAnimDirector = GetAnimDirector();
if(pAnimDirector)
{
fwAnimDirectorComponentSyncedScene* pComp = pAnimDirector->GetComponent<fwAnimDirectorComponentSyncedScene>();
if(pComp)
{
if(pComp->IsPlayingSyncedScene())
{
updatePhysics = false;
}
}
}
}
matNewV.SetCol3(newPosV);
Assert(IsFiniteAll(matNewV));
#if __ASSERT
bool bOrthonormal = RCC_MATRIX34(matNewV).IsOrthonormal();
if( !bOrthonormal )
{
const Matrix34& mat = RCC_MATRIX34(matNewV);
Matrix34 matTrans(mat);
matTrans.Transpose3x4();
Matrix34 I;
I.Dot(matTrans, mat);
Assertf(bOrthonormal, "Matrix wasn't orthonormal: \n a (%.4f, %.4f, %.4f) \n b (%.4f, %.4f, %.4f) \n c (%.4f, %.4f, %.4f) \n d (%.4f, %.4f, %.4f)\n"
"Matrix times matrix-transpose - I: \n a (%.5f, %.5f, %.5f) \n b (%.5f, %.5f, %.5f) \n c (%.5f, %.5f, %.5f)",
mat.a.x, mat.a.y, mat.a.z, mat.b.x, mat.b.y, mat.b.z, mat.c.x, mat.c.y, mat.c.z, mat.d.x, mat.d.y, mat.d.z,
I.a.x - 1.0f, I.a.y, I.a.z, I.b.x, I.b.y - 1.0f, I.b.z, I.c.x, I.c.y, I.c.z - 1.0f);
}
#endif
// Not sure, but would like to avoid this if possible:
// ResetWaterStatus();
const bool bPhysicsActive = CPhysics::GetLevel()->IsActive(pCurrentPhysInst->GetLevelIndex());
//Assert(!bPhysicsActive);
if(bPhysicsActive)
{
CPhysics::GetSimulator()->DeactivateObject(pCurrentPhysInst->GetLevelIndex());
}
// Remember that we are changing our position through low LOD movement. Because of this,
// we may have to activate later if we stop moving and switch back to high LOD, so the
// capsule and ground position get properly set.
SetPedConfigFlag(CPED_CONFIG_FLAG_MovedUsingLowLodPhysicsSinceLastActive, true);
if(updatePhysics)
{
// Don't bother with the GetCollider() call if we weren't active when we checked
// a moment ago - there shouldn't be one.
if(bPhysicsActive)
{
phCollider* pCollider = GetCollider();
if(pCollider)
{
pCollider->SetLastInstanceMatrix(mtrxRefV);
pCollider->SetMatrix(matNewV);
}
}
else
{
Assert(!GetCollider());
}
fwDynamicEntityComponent* pDynComp = GetDynamicComponent();
pDynComp->SetPreviousPosition(VEC3V_TO_VECTOR3(newPosV));
pCurrentPhysInst->SetMatrix(matNewV);
CPhysics::GetLevel()->UpdateObjectLocation(pCurrentPhysInst->GetLevelIndex());
// if we're using a fragInst need to let it know we've moved as well
Assert(pCurrentPhysInst->GetClassType() < PH_INST_FRAG_GTA);
// if(pCurrentPhysInst->GetClassType() >= PH_INST_FRAG_GTA)
// {
// ((fragInst *)pCurrentPhysInst)->ReportMovedBySim();
// }
Assert(!IsInPathServer());
// Update the entity's bounds in the pathserver system.
// if(IsInPathServer())
// {
// CPathServerGta::UpdateDynamicObject(this);
// }
// TODO: Try to optimize this part, may be able to get away with not doing it
// every frame, at least off screen. The UpdateRagdollMatrix() call is fairly expensive.
phInst* pRagdollInst = GetRagdollInst();
if(pRagdollInst)
{
// Not sure if we really have to cover the warp case here.
const ScalarV pedMaxDistToWarpSqV(PED_MAX_DIST_TO_WARP_SQR);
const Vec3V ragdollInstPosV = pRagdollInst->GetMatrix().GetCol3();
const ScalarV distMovedSqV = DistSquared(newPosV, ragdollInstPosV);
bool bWarp = (IsGreaterThanAll(distMovedSqV, pedMaxDistToWarpSqV) != 0);
UpdateRagdollMatrix(bWarp);
}
}
TUNE_BOOL(SkipHasMovedForPeds, false);
if(!SkipHasMovedForPeds)
m_nDEflags.bHasMovedSinceLastPreRender = true;
CEntity::SetMatrix(RCC_MATRIX34(matNewV), false, updatePhysics, false);
UpdateWorldFromEntity();
m_MotionData.SetCurrentHeadingAndPitchFromMatrix(matNewV);
// Not sure, hopefully we can get away without this?
// if(updatePhysics && GetAnimatedInst())
// ProcessRotationalConstraint();
}
void CPed::ProcessPrePhysics()
{
#if __ASSERT
m_CanUseRagdollSuccessfull = false;
#endif //__ASSERT
ProcessOverridePhysics();
this->ProcessLowLodPhysics();
const bool bIsUsingLowLodPhysics = this->IsUsingLowLodPhysics();
if(bIsUsingLowLodPhysics || GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics))
{
this->SimulatePhysicsForLowLod();
// If this is a low lod ped, then we're done updating their physics for this frame.
// Make sure this ped has preSimUpdate removed from its scene bits to save time.
u32 flagsToRemove = CGameWorld::SU_PRESIM_PHYSICS_UPDATE;
if(m_CachedSceneUpdateFlags & flagsToRemove)
{
fwSceneUpdate::RemoveFromSceneUpdate(*this, flagsToRemove);
UpdateCachedSceneUpdateFlags();
}
Assert((GetExtension<fwSceneUpdateExtension>()->m_sceneUpdateFlags & CGameWorld::SU_PRESIM_PHYSICS_UPDATE) == 0);
// To save some update time, we don't forward the call to CPhysical::ProcessPrePhysics()
// in this low LOD case. The code below was adapted from there, the rest of that isn't necessary.
m_nPhysicalFlags.bWasInWater = GetIsInWater();
if(IsProtectedBaseFlagSet(fwEntity::IS_FIXED_UNTIL_COLLISION))
{
UpdateFixedWaitingForCollision(false);
}
CNetObjGame* pNetObj = GetNetworkObject();
if(pNetObj)
{
static_cast<CNetObjEntity *>(pNetObj)->UpdateBeforePhysics();
}
return;
}
else
{
// Else, make sure this ped has preSimUpdate added back to its scene bits.
u32 flagsToAdd = CGameWorld::SU_PRESIM_PHYSICS_UPDATE;
if((m_CachedSceneUpdateFlags & flagsToAdd) == 0)
{
fwSceneUpdate::AddToSceneUpdate(*this, flagsToAdd);
UpdateCachedSceneUpdateFlags();
}
Assert((GetExtension<fwSceneUpdateExtension>()->m_sceneUpdateFlags & CGameWorld::SU_PRESIM_PHYSICS_UPDATE) != 0);
}
// Update the slipstream timer for the vehicle if this is the player's car.
// NOTE:
// This was moved from CVehicle::PreProcessPhysics because we only want to do this for the player.
// Getting the driver of each vehicle and determining if it's a player gets expensive.
if(CVehicle::sm_bSlipstreamingEnabled && IsPlayer())
{
if(CVehicle* myVehicle = GetMyVehicle())
{
if(myVehicle->GetDriver() == this)
{
myVehicle->UpdateSlipStreamTimer(fwTimer::GetTimeStep());
}
}
}
CPhysical::ProcessPrePhysics();
}
dev_float dfPlayerCarMaxPushDepth = 0.9f; // changed this to be the percentage of the current capsule radius
ePhysicsResult CPed::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
Assertf(!IsUsingLowLodPhysics(), "CPed::ProcessPhysics - low lod peds should be processed already with SimulatePhysicsForLowLod()! "
"Ped is %s, at (%.1f, %.1f, %.1f), clone: %d, attached: %d, in vehicle: %d.", GetDebugName(),
GetMatrixRef().GetCol3ConstRef().GetXf(), GetMatrixRef().GetCol3ConstRef().GetYf(), GetMatrixRef().GetCol3ConstRef().GetZf(),
IsNetworkClone(), GetIsAttached(), GetIsInVehicle());
int numTimeSlices = CPhysics::GetNumTimeSlices();
float fTimeStepThisSlice = fTimeStep;
#if __ASSERT
CBuoyancyInfo* pBuoyancyInfo = m_Buoyancy.GetBuoyancyInfo(this);
Assertf(pBuoyancyInfo->m_WaterSamples && pBuoyancyInfo->m_nNumWaterSamples>0,
"[CPed::ProcessPhysics()] Ped found with invalid water samples: '%s' m_WaterSamples=%p, m_nNumWaterSamples=%d.",
GetModelName(), pBuoyancyInfo->m_WaterSamples, pBuoyancyInfo->m_nNumWaterSamples);
#endif // __ASSERT
bool bIsLastTimeSlice = false;
if(CPedAILodManager::IsPedProcessPhysicsSingleStepActive() && !GetPedResetFlag(CPED_RESET_FLAG_ForceProcessPhysicsUpdateEachSimStep))
{
if(!CPhysics::GetIsFirstTimeSlice(nTimeSlice))
{
phInst* pInst = GetCurrentPhysicsInst();
if(pInst && pInst->IsInLevel() && CPhysics::GetLevel()->IsActive(pInst->GetLevelIndex()))
{
// If we are allowed to just do the ProcessPedStanding() call on the first simulation step,
// just apply the force we stored.
if(CPedAILodManager::IsPedProcessPedStandingSingleStepActive()
&& m_pCapsuleInfo->IsBiped() && !GetPedResetFlag(CPED_RESET_FLAG_ForceProcessPedStandingUpdateEachSimStep))
{
if((*(const u32*)&m_CurrentSpringForceZ) != 0)
{
Vec3V forceToApplyV(V_ZERO);
ScalarV forceToApplyZV = LoadScalar32IntoScalarV(m_CurrentSpringForceZ);
forceToApplyV.SetZ(forceToApplyZV);
ApplyForceCg(RCC_VECTOR3(forceToApplyV));
}
}
else
{
phCollider* pCollider = GetCollider();
ProcessPedStanding(fTimeStepThisSlice, pCollider);
}
// These things need to be done in order for the ground normal to register correctly
// through ProcessPreComputeImpactsForMover().
m_fGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_fRearGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_vecRearGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_vecGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_bValidGroundNormal = false;
}
return PHYSICS_DONE;
}
fTimeStep *= numTimeSlices;
numTimeSlices = 1;
bIsLastTimeSlice = true;
}
else
{
bIsLastTimeSlice = CPhysics::GetIsLastTimeSlice(nTimeSlice);
}
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnProcessPhysicsOfFocusEntity(), this );
// Handle general ragdoll update items here before prePhysics flags get reset
if(GetUsingRagdoll() && Verifyf(GetCollider(), "Ped using ragdoll but ragdoll instance has no collider."))
{
GetRagdollInst()->ProcessRagdoll(fTimeStep);
}
m_PedResetFlags.ResetPrePhysics(this);
// If standing on something physical then postpone to allow it to get processed
if(GetIsStanding() && GetGroundPhysical() && bCanPostpone)
{
//! DMKH. If we are on top of a clone, we need to defer physics until the ground physical has updated its net blender (which can happen as late
//! as after ProcessPhysics2)
if(GetGroundPhysical()->IsNetworkClone())
{
return PHYSICS_POSTPONE2;
}
else
{
return PHYSICS_POSTPONE;
}
}
// To avoid bad penetration issues which can lead to a player being pushed through a wall, disable collision with
// any kinematic ped which is pushing the player against other geometry from opposing sides.
if(IsPlayer())
{
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithSomethingOtherThanKinematicPed)
&& GetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed))
{
SetNoCollision(m_pKinematicPed, NO_COLLISION_RESET_WHEN_NO_IMPACTS);
}
// Reset the flags used to determine if the ped is being squashed now that we have used them.
SetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithSomethingOtherThanKinematicPed, false);
SetPedConfigFlag(CPED_CONFIG_FLAG_PlayerInContactWithKinematicPed, false);
// Need to apply push for all updates for these cases
if(IsLocalPlayer())
{
if( m_nPedType == PEDTYPE_ANIMAL ||
( GetTrainRidingOn() && GetTrainRidingOn()->GetVelocity().IsNonZero() ))
{
CPhysics::DisableOnlyPushOnLastUpdateForOneFrame();
}
else
{
float fSinglePushUpdateSpeedLimit = ( dfPlayerCarMaxPushDepth * m_fCurrentMainMoverCapsuleRadius ) / (fTimeStep * numTimeSlices);
float fSinglePushUpdateSpeedLimitSqrd = fSinglePushUpdateSpeedLimit * fSinglePushUpdateSpeedLimit;
CVehicle *pVehicle = GetVehiclePedInside();
if(pVehicle && pVehicle->GetVehicleType() == VEHICLE_TYPE_CAR)
{
if( static_cast<CAutomobile *>(pVehicle)->IsInAir() ||
pVehicle->GetVelocity().Mag2() > fSinglePushUpdateSpeedLimitSqrd )
{
CPhysics::DisableOnlyPushOnLastUpdateForOneFrame();
}
}
// B*2015708: Apply pushes for all updates when ped is standing on a moving vehicle.
// This is to prevent issues where peds could tunnel through thin collision geometry (e.g. On the benson it was possible to move through from the top of the cabin into the usually inaccesible back area).
if(GetGroundPhysical() && GetGroundPhysical()->GetIsTypeVehicle())
{
CVehicle *pVehicle = static_cast<CVehicle *>(GetGroundPhysical());
if( pVehicle->GetVelocity().Mag2() > fSinglePushUpdateSpeedLimitSqrd )
{
CPhysics::DisableOnlyPushOnLastUpdateForOneFrame();
}
}
}
}
}
ProcessGroundMovement(fTimeStep);
// B*1955849: Update the collider reference frame velocities straight after processing ground movement and before processing ped task physics.
// This prevents a bug where the melee combat task would detect the ped standing a moving vehicle at the end of one frame (When entering the PlayClip state),
// but the same task thinks the vehicle isn't moving during ProcessPhysics during the next frame because the reference frame velocities get zeroed out during ProcessPrePhysics.
// This caused an issue where the task ProcessPhysics would try to perform homing even though the homing data hadn't been initialised correctly because the PlayClip state hadn't performed due to the ped standing on a moving vehicle.
if(GetCollider())
{
// Ragdoll peds are handled differently here since they aren't getting their groundPhysical set like animated peds.
// Instead they use an asynch probe every few frames so they can't have their ref frame velocities getting cleared each frame.
if (!GetUsingRagdoll())
{
//Set the reference frame velocity.
GetCollider()->SetReferenceFrameVelocity(VEC3V_TO_VECTOR3(m_vGroundVelocityIntegrated));
//Set the reference frame angular velocity.
GetCollider()->SetReferenceFrameAngularVelocity(VEC3V_TO_VECTOR3(m_vGroundAngularVelocity));
}
}
// If we are doing an independent mover expression, apply it fully on the first timeslice, rather than over multiple time slices
if(GetMotionData()->GetIndependentMoverTransition() == 1)
{
if(nTimeSlice > 0)
{
// Reset so that the heading delta is not applied on further timeslices
GetMotionData()->SetIndependentMoverHeadingDelta(0.0f);
}
}
ApplyMovementRequests(fTimeStep);
// update the network blender to allow it to adjust the ped's velocity to
// move it faster towards it's target position if possible
if(IsNetworkClone())
{
GetNetworkObject()->GetNetBlender()->ProcessPrePhysics();
}
// Process bounds must happen before the rotational constraints are processed
unsigned int cnt = m_ProcessBoundsCountdown;
if(cnt == 0)
{
// Need to allow processing of bounds while 'dying' but not while 'dead'
if(GetDeathState() != DeathState_Dead)
{
if(!ProcessBounds(fTimeStep) && bIsLastTimeSlice && !IsLocalPlayer())
{
// Run this code every four frames, for AI, if nothing is changing.
static const int kProcessBoundsCountdownPeriod = 4;
// Nothing changed, go to sleep for a few frames unless we are the player.
// Only do this on the last time slice otherwise we won't have received a full frame's worth of update.
m_ProcessBoundsCountdown = (kProcessBoundsCountdownPeriod - 1);
}
}
}
else
{
m_ProcessBoundsCountdown = (u8)(cnt - 1);
}
SetClothIsSkydiving(false);
//
CTask::BeginProcessPhysicsUpdates(numTimeSlices);
bool bNeedsToBeAwake = GetPedIntelligence()->ProcessPhysics(fTimeStep, nTimeSlice, GetUsingRagdoll());
CTask::EndProcessPhysicsUpdate();
/*static dev_bool s_bTestSleepParams = false;
if(s_bTestSleepParams)
{
phSleep *pSleep = GetCollider() ? GetCollider()->GetSleep() : NULL;
if(pSleep)
{
TUNE_GROUP_FLOAT(PED_ATTACH, fDefaultVelTolerance2, 0.01f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_INT(PED_ATTACH, fDefaultMotionlessTicks, 99999, 0, 999999, 1);
TUNE_GROUP_INT(PED_ATTACH, fDefaultAsleepTicks, 99999, 0, 999999, 1);
pSleep->SetVelTolerance2(fDefaultVelTolerance2);
pSleep->SetMaxMotionlessTicks(fDefaultMotionlessTicks);
pSleep->SetMaxAsleepTicks(fDefaultAsleepTicks);
}
}
}*/
// skip default physics update
if (ProcessIsAsleep(bNeedsToBeAwake))
{
Assertf(!GetIsAttachedToGround() || GetGroundPhysical() != NULL,"Slept ped is attached but doesn't have a ground physical.");
if(GetPedResetFlag(CPED_RESET_FLAG_ForceBuoyancyProcessingIfAsleep))
{
ProcessBuoyancy(fTimeStep, bIsLastTimeSlice);
bool isSwimming = GetPedResetFlag( CPED_RESET_FLAG_IsDrowning );
bool wasSwimming = GetIsSwimming();
SetWasSwimming(wasSwimming);
SetIsSwimming(isSwimming);
}
return PHYSICS_DONE;
}
//Ensure the collider is valid.
phCollider* pCollider = GetCollider();
Assertf(!GetIsAttachedToGround() || GetGroundPhysical() != NULL,"Active ped is attached but doesn't have a ground physical. Collider: 0x%p",pCollider);
// Only reset if not asleep
SetPedResetFlag(CPED_RESET_FLAG_InContactWithFoliage, false);
SetPedResetFlag(CPED_RESET_FLAG_InContactWithBIGFoliage, false);
SetPedResetFlag(CPED_RESET_FLAG_InContactWithDeepSurface, false);
// B*1856370: Only zero out this value if the ped is not asleep to ensure there is a force to apply during the second timeslice after a ped has been activated in the first timeslice.
// The spring force previously got zeroed every timeslice, but ProcessPedStanding is only called for the first. If the ped is asleep, ProcessPedStanding is never called and the spring force remained at zero.
// If the ped was then activated during the first timeslice, the capsule would sink slightly during the second timeslice due to there being no counter-force to gravity.
// If peds stood close together, this could happen in a never-ending cycle of activate, sink, correct, sleep, activate...
m_CurrentSpringForceZ = 0.0f;
// Note: we intentionally use the time step of just this physics simulation step.
// It's currently being used for the call to phCollider::ApplyForceCenterOfMass(),
// and even if we only call ProcessPedStanding() for one of the steps, we will apply
// the same force during the second step.
ProcessPedStanding(fTimeStepThisSlice, pCollider);
ProcessBuoyancy(fTimeStep, bIsLastTimeSlice);
//Update swim state based on isDrowning flag updated in ProcessBuoyancy and backup previous state.
bool isSwimming = GetPedResetFlag( CPED_RESET_FLAG_IsDrowning );
bool wasSwimming = GetIsSwimming();
SetWasSwimming(wasSwimming); //Backup previous state.
SetIsSwimming(isSwimming);
if (isSwimming && !wasSwimming)
{
SetIsCrouching(false,-1,false);
}
if (!GetIsAttached())
{
ApplyDesiredVelocity(fTimeStep);
}
else
{
//always reset the stored last matrix on kinematic peds if it exists
if (IsUsingKinematicPhysics() && GetAnimatedInst() && GetAnimatedInst()->HasLastMatrix())
{
PHLEVEL->SetLastInstanceMatrix(GetAnimatedInst(), GetMatrix());
}
if (GetIsAttachedInCar())
{
//Ensure the collider is valid.
if(pCollider)
{
CPhysical *attachParent = (CPhysical *) GetAttachParentForced();
if(attachParent && attachParent->GetIsTypeVehicle())
{
CVehicle* pAttachVehicle = static_cast<CVehicle*>(attachParent);
Vector3 vDesiredVelocity = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(VECTOR3_TO_VEC3V(GetAnimatedVelocity())));
vDesiredVelocity += pAttachVehicle->GetVelocity();
const Vector3 vDesiredAngVelocity = GetDesiredAngularVelocity();
pCollider->SetVelocity(vDesiredVelocity);
pCollider->SetAngVelocity(vDesiredAngVelocity);
}
}
}
if (GetPedResetFlag(CPED_RESET_FLAG_ApplyAnimatedVelocityWhilstAttached))
{
ApplyAnimatedVelocityWhilstAttached(fTimeStep);
}
}
// Now when we are active, forget anything about previous movement in low LOD.
SetPedConfigFlag(CPED_CONFIG_FLAG_MovedUsingLowLodPhysicsSinceLastActive, false);
// Reset the raise velocity change limit flag, it is applied above in the solve friction call
SetPedResetFlag( CPED_RESET_FLAG_RaiseVelocityChangeLimit, false );
if(GetRagdollState()==RAGDOLL_STATE_ANIM_DRIVEN)
{
if(CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()) && GetRagdollInst()->GetNMAgentID()!=-1)
{
// setup TM's from current skeleton pose
GetRagdollInst()->SetComponentTMsFromSkeleton(*GetSkeleton());
// post message to tell NM to grab TM's as pose to drive to
ART::MessageParams msg;
msg.addBool(NMSTR_PARAM(NM_START), true);
GetRagdollInst()->PostARTMessage(NMSTR_MSG(NM_ACTIVE_POSE_MSG), &msg);
}
}
// if we're standing see if we've moved far enough to store history
// make sure we were standing last frame
Vector3 vGroundPos = GetGroundPos();
if(GetIsStanding() && GetWasStanding() && !GetPedConfigFlag( CPED_CONFIG_FLAG_IsInTheAir ) && vGroundPos.z > PED_GROUNDPOS_RESET_Z + 1.0f)
{
if(m_vecGroundPosHistory[0].IsZero())
{
m_vecGroundPosHistory[0] = vGroundPos;
m_vecGroundPosHistory[1] = vGroundPos;
}
else if((vGroundPos - m_vecGroundPosHistory[0]).Mag2() > PED_GROUND_POS_HISTORY_DIST_SQR)
{
m_vecGroundPosHistory[1] = m_vecGroundPosHistory[0];
m_vecGroundPosHistory[0] = vGroundPos;
}
}
// if not standing, reset history
else
{
m_vecGroundPosHistory[0].Zero();
m_vecGroundPosHistory[1].Zero();
}
m_fGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_fRearGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_vecRearGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_vecGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_bValidGroundNormal = false;
//Check to see if we're going to damage the ground instance
float fGroundInstanceDamage = GetCapsuleInfo()->GetGroundInstanceDamage();
if (fGroundInstanceDamage > 0.0f)
{
//We only do this when we change props or components rather than accumulating the damage every frame.
//This isn't technically very correct but we wanted to limit the damage applied by walking on things.
if (GetGroundPhysical())
{
if ((GetGroundPhysical() != m_pLastValidGroundPhysical) || (m_groundPhysicalComponent != m_lastGroundPhysicalComponent))
{
fragInst *pFragInst = GetGroundPhysical()->GetFragInst();
if (pFragInst)
{
// Ensure that the component we're standing on hasn't been removed
const phBoundComposite* pFragBound = static_cast<const phBoundComposite*>(pFragInst->GetArchetype()->GetBound());
if(m_groundPhysicalComponent < pFragBound->GetNumBounds() && pFragBound->GetBound(m_groundPhysicalComponent) != NULL)
{
float healthPrior = 1.0f;
float healthLeft = 1.0f;
pFragInst->ApplyDamage(m_groundPhysicalComponent, fGroundInstanceDamage, m_vecGroundPos, m_vecMaxGroundNormal, healthPrior, healthLeft);
#if GTA_REPLAY
if(GetGroundPhysical()->GetType() == ENTITY_TYPE_OBJECT)
{
if(CReplayMgr::IsEditModeActive() == false)
{
// During normal gameplay this object has been activated so let the replay
// know so it can hide the equivalent on playback
CReplayMgr::RecordMapObject((CObject*)GetGroundPhysical());
}
}
#endif // GTA_REPLAY
}
}
}
}
}
//Check if the ground is valid.
if(GetGroundPhysical())
{
//Assign the last valid ground.
m_pLastValidGroundPhysical = GetGroundPhysical();
m_lastGroundPhysicalComponent = m_groundPhysicalComponent;
//Assign the last valid ground time.
m_uLastValidGroundPhysicalTime = fwTimer::GetTimeInMilliseconds();
}
//Check if the last valid ground is valid.
else if(m_pLastValidGroundPhysical)
{
//Check if the last valid ground time has expired.
if((m_uLastValidGroundPhysicalTime + 500) < fwTimer::GetTimeInMilliseconds())
{
//Clear the last valid ground.
m_pLastValidGroundPhysical = NULL;
m_uTimeGroundPhysicalWasSet = 0xFFFFFFFF;
}
}
// reset ground physical pointer
Assertf(GetCollider() || GetGroundPhysical() == NULL, "Clearing Ped ground when it doesn't have a collider.");
SetGroundPhysical(NULL);
SetIsStandingOnMovableObject(false);
m_groundPhysicalComponent = 0;
m_lastGroundPhysicalComponent = 0;
m_vecGroundPosLocal.ZeroComponents();
// Clear bWasStanding flag. This will be updated in ProcessProbes after sim has updated
SetWasStanding(false);
// Make sure we can't push any deeper into overhangs
Vector3 vExitDirn;
bool bNeedsConstraint = false;
if (GetPedResetFlag(CPED_RESET_FLAG_TouchingOverhang))
{
//Do we need a constraint to prevent further movement?
vExitDirn = VEC3V_TO_VECTOR3(rage::Add(GetMinOverhangNormal(), GetMaxGroundNormal()));
vExitDirn.z = 0.0f;
const float kMinExitDirMag = 0.01f;
if (vExitDirn.Mag2() > kMinExitDirMag*kMinExitDirMag)
{
bNeedsConstraint = true;
}
}
if (bNeedsConstraint && GetCurrentPhysicsInst())
{
vExitDirn.Normalize();
if (!m_OverhangConstraint.IsValid())
{
phConstraintHalfSpace::Params params;
params.instanceA = GetCurrentPhysicsInst();
params.instanceB = NULL;
params.worldAnchorA = GetCurrentPhysicsInst()->GetCenterOfMass();
params.worldAnchorB = params.worldAnchorA;
params.worldNormal = RCC_VEC3V(vExitDirn);
m_OverhangConstraint = PHCONSTRAINT->Insert( params );
}
else if(phConstraintHalfSpace* pConstraint = static_cast<phConstraintHalfSpace*>(PHCONSTRAINT->GetTemporaryPointer(m_OverhangConstraint)))
{
Vec3V vCOG = GetCurrentPhysicsInst()->GetCenterOfMass();
pConstraint->SetWorldPosA(vCOG);
pConstraint->SetWorldPosB(vCOG);
pConstraint->SetWorldNormal(RCC_VEC3V(vExitDirn));
}
const u8 kHoldConstraint = 2;
m_OverhangConstraintCountdown = kHoldConstraint;
}
else if (m_OverhangConstraint.IsValid())
{
--m_OverhangConstraintCountdown;
if (m_OverhangConstraintCountdown==0)
{
RemoveOverhangConstraint();
}
}
PDR_ONLY(debugPlayback::RecordTaggedFloatValue(*GetCurrentPhysicsInst(), float(m_OverhangConstraintCountdown), "OCCountdown"));
// Update rotation constraint last so it can take into account any changes to the collider made by this function.
ProcessRotationalConstraint();
return PHYSICS_DONE;
}
dev_float sfShoveSleepingPed = 0.2f;
dev_bool sbKeepPlayerAwake = false;
//
bool CPed::ProcessIsAsleep(bool bNeedsToBeAwake)
{
bool bSkipPhysics = true;
// if we're fixed, it doesn't matter what wants to wake us up because we're not gonna
if(GetIsAnyFixedFlagSet())
{
if(GetIsAttachedToGround() && GetGroundPhysical() == NULL)
{
DetachFromGround();
}
return true;
}
if(GetRagdollState() > RAGDOLL_STATE_ANIM)
return false;
if(!GetCurrentPhysicsInst()->IsInLevel())
return true;
// Likewise if we have certain attachment types.
if(GetIsAttached() && !GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION)
&& (GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_BASIC||GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD))
{
return true;
}
bool bInNoCollisionMovementMode = GetPedResetFlag(CPED_RESET_FLAG_NoCollisionMovementMode);
//Threshold after which we consider the animated velocity is moving.
static const float sfAnimatedVelocityConsideredMoving2 = 0.0025f;
// Special cases under which we don't want to miss an update
if(GetPedResetFlag(CPED_RESET_FLAG_ForceProcessPhysicsUpdateEachSimStep))
bSkipPhysics = false;
else if(m_Buoyancy.GetStatus()==PARTIALLY_IN_WATER || (m_nFlags.bPossiblyTouchesWater && m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate)) // B*469447
bSkipPhysics = false;
else if(GetAnimatedVelocity().IsNonZero() && !bInNoCollisionMovementMode && (GetAnimatedVelocity().Mag2() > sfAnimatedVelocityConsideredMoving2)) //Consider less than a viable threshold the ped is remaining still
bSkipPhysics = false;
else if(!GetIsStanding() && !bInNoCollisionMovementMode)
bSkipPhysics = false;
else if(bNeedsToBeAwake)
bSkipPhysics = false;
else if (IsLocalPlayer() && sbKeepPlayerAwake)
bSkipPhysics = false;
else if (m_bPickupInRange && IsPlayer()) // Don't sleep if there is a pickup in range.
bSkipPhysics = false;
else if (m_bMainBoundIntersectsPedThatCausesActivations)
bSkipPhysics = false;
else if(GetNoCollisionFlags() & (NO_COLLISION_RESET_WHEN_NO_BBOX | NO_COLLISION_RESET_WHEN_NO_IMPACTS))
{
// If we're avoiding collision with an entity until we separate, one of the objects needs to be awake or else
// we won't run collision and it will look like we've separated.
// NOTE: I think this should be solved at a much lower level by enabling inactive-inactive collision on objects with
// no collision entities. That is a pretty big change at this point though.
if(GetNoCollisionEntity())
{
bSkipPhysics = false;
}
}
// Reset the flags.
m_bPickupInRange = false;
m_bMainBoundIntersectsPedThatCausesActivations = false;
//Generate the relative velocity and relative angular velocity.
Vec3V vRelVel = VECTOR3_TO_VEC3V(m_vDesiredVelocity);
Vec3V vRelAngVel = VECTOR3_TO_VEC3V(m_vDesiredAngularVelocity);
//Check if the ground is valid.
if(GetGroundPhysical())
{
//Subtract the ground velocity integrated.
vRelVel -= m_vGroundVelocityIntegrated;
//Subtract the ground angular velocity.
vRelAngVel -= m_vGroundAngularVelocity;
}
// if this ped is trying to turn or to move, then we can't go to sleep
ScalarV scThreshold = ScalarVFromF32(0.01f);
if((IsGreaterThanAll(MagSquared(vRelVel), scThreshold) != 0) || (IsGreaterThanAll(MagSquared(vRelAngVel), scThreshold)) != 0)
bSkipPhysics = false;
// if this is a clone ped on a moving ground physical, then we can't go to sleep
if( ( IsNetworkClone() || IsLocalPlayer() ) &&
!GetIsAttached() && GetGroundPhysical() && MagSquared(GetGroundVelocity()).Getf() > 0.01f)
{
bSkipPhysics = false;
}
//Check if physics are going to be skipped.
if(bSkipPhysics)
{
//Check if the ped is attached to the ground.
if(GetIsAttachedToGround())
{
if(GetGroundPhysical())
{
//Transform the ground normal back to world space.
Vec3V vGroundNormal = Transform3x3(GetGroundPhysical()->GetMatrixRef(), m_vGroundNormalLocal);
//Compare the up-vectors of the ped and the ground.
ScalarV scDot = Dot(GetTransform().GetC(), vGroundNormal);
static ScalarV scDotThreshold = ScalarVFromF32(0.75f);
if(IsLessThanAll(scDot, scDotThreshold))
{
//This will activate the ped and cause the ground attachment to break.
bSkipPhysics = false;
}
//! For clone vehicles that we can stand on, do not break off due to large acceleration values. This can happen if we warp the vehicle or
//! interpolate quickly to target position.
bool bDoAccelerationBreakoff = bSkipPhysics;
if(bDoAccelerationBreakoff && GetGroundPhysical()->IsNetworkClone() && GetGroundPhysical()->GetIsTypeVehicle())
{
bDoAccelerationBreakoff = (((CVehicle*)GetGroundPhysical())->CanPedsStandOnTop()) == false;
}
if(bDoAccelerationBreakoff)
{
//Check if the ground acceleration has surpassed the threshold.
ScalarV scAccelMagSq = MagSquared(m_vGroundAcceleration);
static ScalarV scAccelMagSqThreshold = ScalarVFromF32(rage::square(100.0f));
if(IsGreaterThanAll(scAccelMagSq, scAccelMagSqThreshold))
{
//This will activate the ped and cause the ground attachment to break.
bSkipPhysics = false;
}
}
if(bSkipPhysics)
{
//Ensure the ped is a reasonable distance away from the ground.
//It was requested that if the ground warps, the ped does not warp with it.
spdAABB tempBox;
const spdAABB& box = GetGroundPhysical()->GetBoundBox(tempBox);
ScalarV scBoxDistSq = box.DistanceToPointSquared(GetTransform().GetPosition());
static ScalarV scBoxDistSqThreshold = ScalarVFromF32(rage::square(10.0f));
if(IsGreaterThanAll(scBoxDistSq, scBoxDistSqThreshold))
{
//This will activate the ped and cause the ground attachment to break.
bSkipPhysics = false;
}
}
//! Activate ped if our ground physical tries to pull us under water (as we won't
//! start to do buoyancy processing if still attached).
if(IsGroundPhysicalPullingPedUnderWater())
{
bSkipPhysics = false;
}
}
else
{
//The ground has become invalid.
//This will activate the ped and cause the ground attachment to break.
//Forcibly detach from ground incase we don't activate
bSkipPhysics = false;
}
}
// If we're standing on any physical object that we cant attach to, keep the ped awake so that the object can't
// slide/delete/teleport out from under them.
else if(GetIsStandingOnMovableObject() && !IsGroundEligibleForAttachment())
{
// Players are always kept awake, as an optimization don't bother keeping other peds
// awake unless their ground is also awake or using external velocity
if( IsPlayer() ||
(GetGroundPhysical() &&
(!GetGroundPhysical()->IsAsleep() ||
(GetGroundPhysical()->GetCurrentPhysicsInst() && GetGroundPhysical()->GetCurrentPhysicsInst()->GetInstFlag(phInst::FLAG_QUERY_EXTERN_VEL)))))
{
bSkipPhysics = false;
}
}
}
// check if this vehicle is asleep, and wake up when necessary
bool bWokePed = false;
if(CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex()))
{
// We are inactive: if we are about to skip physics (remain inactive),
// wake up if we moved in low LOD earlier, since we may not be in an accurate position.
if(bSkipPhysics)
{
if(GetPedConfigFlag(CPED_CONFIG_FLAG_MovedUsingLowLodPhysicsSinceLastActive))
{
if(!IsDead())
{
bSkipPhysics = false;
}
else
{
SetPedConfigFlag(CPED_CONFIG_FLAG_MovedUsingLowLodPhysicsSinceLastActive, false);
}
}
}
if(bSkipPhysics==false)
{
if(GetIsAttachedToGround() && GetGroundPhysical() == NULL)
{
// Always detach from the ground if we're inactive and trying to wake up, regardless of if we wake.
// This should prevent us from leaving this function attached without a ground physical.
DetachFromGround();
}
ActivatePhysics();
if(GetCollider())
{
bWokePed = true;
}
else
{
// If we tried to wake up and failed, clear this flag so we don't keep trying.
// We may be attached, or something like that.
SetPedConfigFlag(CPED_CONFIG_FLAG_MovedUsingLowLodPhysicsSinceLastActive, false);
bSkipPhysics = true;
}
}
}
else
{
phCollider* pCollider = GetCollider();
if(pCollider && pCollider->GetSleep() && pCollider->GetSleep()->IsAsleep())
{
if(bSkipPhysics==false)
{
// Don't let the collider sleep for a frame (incase we're timesliced)
pCollider->GetSleep()->BlockSleepFinishing(CPhysics::GetNumTimeSlices());
bWokePed = true;
}
bSkipPhysics = false;
}
else
bSkipPhysics = false;
}
// if this ped went to sleep without his feet on the ground, he's probably stuck
// give a little shove to push him off
if(sfShoveSleepingPed > 0.0 && bWokePed && !GetIsStanding() && !GetUsingRagdoll() && !GetUseExtractedZ() && !GetPedResetFlag( CPED_RESET_FLAG_DisableAsleepImpulse ) && !GetPedResetFlag( CPED_RESET_FLAG_FirstPhysicsUpdate ) )
{
float fDirn = fwRandom::GetRandomNumberInRange(-PI, PI);
ApplyImpulseCg(sfShoveSleepingPed * GetMass() * Vector3(-rage::Sinf(fDirn), rage::Cosf(fDirn), 0.5f));
}
return bSkipPhysics;
}
bool CPed::ProcessBounds(float fTimeStep, bool bForceUpdate)
{
static dev_float ms_fCapsuleRate = 1.0f;
bool bNeedToRecalcRagdollExtents = false;
#if FPS_MODE_SUPPORTED
if(IsLocalPlayer() && GetRagdollInst() != NULL && GetRagdollInst()->GetCached())
{
phBoundComposite* pCompositeBounds = GetRagdollInst()->GetCacheEntry()->GetBound();
s32 iHeadComponent = GetRagdollInst()->GetComponentFromBoneIndex(GetBoneIndexFromBoneTag(BONETAG_HEAD));
if(pCompositeBounds != NULL && iHeadComponent != -1)
{
static ScalarV s_fFirstPersonShooterModeHeadScale(1.2f);
ScalarV fHeadScale = IsFirstPersonShooterModeEnabledForPlayer(false) ? s_fFirstPersonShooterModeHeadScale : ScalarV(V_ONE);
Vec3V vCurrentSize(V_ZERO);
Vec3V vDesiredSize(V_ZERO);
// It looks like bounds within the composite are shared between fragTypes so we need to create a custom head bound to be able to adjust its size
GetRagdollInst()->GetCacheEntry()->CloneBoundParts();
phBound* pCurrentBound = pCompositeBounds->GetBound(iHeadComponent);
phBound* pDefaultBound = GetRagdollInst()->GetTypePhysics()->GetCompositeBounds()->GetBound(iHeadComponent);
if(Verifyf(pCurrentBound != pDefaultBound, "CPed::ProcessBounds: Current and default bounds must be different") &&
Verifyf(pCurrentBound->GetType() == pDefaultBound->GetType(), "CPed::ProcessBounds: Current and default bound must be the same type"))
{
switch(pCurrentBound->GetType())
{
case phBound::SPHERE:
{
vCurrentSize = Vec3V(static_cast<phBoundSphere*>(pCurrentBound)->GetRadiusV());
vDesiredSize = Vec3V(Scale(static_cast<phBoundSphere*>(pDefaultBound)->GetRadiusV(), fHeadScale));
break;
}
case phBound::CAPSULE:
{
vCurrentSize = Vec3V(static_cast<phBoundCapsule*>(pCurrentBound)->GetRadiusV());
vDesiredSize = Vec3V(Scale(static_cast<phBoundCapsule*>(pDefaultBound)->GetRadiusV(), fHeadScale));
break;
}
case phBound::BOX:
{
vCurrentSize = static_cast<phBoundBox*>(pCurrentBound)->GetBoxSize();
vDesiredSize = Scale(static_cast<phBoundBox*>(pDefaultBound)->GetBoxSize(), fHeadScale);
break;
}
default:
{
Assertf(false, "CPed::ProcessBounds: Invalid head bound type: %s", pCurrentBound->GetTypeString());
break;
}
}
if(!IsCloseAll(vCurrentSize, vDesiredSize, Vec3V(V_FLT_SMALL_3)))
{
bNeedToRecalcRagdollExtents = true;
RC_VECTOR3(vCurrentSize).ApproachStraight(RCC_VECTOR3(vDesiredSize), ms_fCapsuleRate, fTimeStep);
switch(pCurrentBound->GetType())
{
case phBound::SPHERE:
{
static_cast<phBoundSphere*>(pCurrentBound)->SetSphereRadius(vCurrentSize.GetX());
break;
}
case phBound::CAPSULE:
{
static_cast<phBoundCapsule*>(pCurrentBound)->SetCapsuleRadius(vCurrentSize.GetX());
break;
}
case phBound::BOX:
{
static_cast<phBoundBox*>(pCurrentBound)->SetBoxSize(vCurrentSize);
break;
}
}
pCompositeBounds->CalculateCompositeExtents();
if(GetRagdollInst()->IsInLevel())
{
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetRagdollInst()->GetLevelIndex(), (Mat34V_Ptr)(NULL));
if(pCompositeBounds->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetRagdollInst()->GetLevelIndex());
}
}
else
{
pCompositeBounds->UpdateBvh(true);
}
}
}
}
}
#endif
// Note: Intersection checks are done inside SetIsCrouching to prevent peds standing up inside the map
if(GetUsingRagdoll())
{
return bNeedToRecalcRagdollExtents;
}
bool bNeedToRecalcExtents = false;
const CBipedCapsuleInfo *pBipedCapsuleInfo = m_pCapsuleInfo->GetBipedCapsuleInfo();
if (pBipedCapsuleInfo)
{
bool bInternalMotionOnly = true;
// Keep the crouching info here
bool bIsCrouching = GetIsCrouching();
float fNewHeadHeight = pBipedCapsuleInfo->GetHeadHeight();
float fNewHeightOffset = 0;
if (!GetPedResetFlag(CPED_RESET_FLAG_DisableDynamicCapsuleRadius) && !GetMyMount())
{
const bool bIsStealth = GetMotionData()->GetUsingStealth();
if (GetPedResetFlag(CPED_RESET_FLAG_IsClimbing))
{
static float MaxCapsuleWhileClimbing = 0.2f;
SetDesiredMainMoverCapsuleRadius(rage::Min(MaxCapsuleWhileClimbing, pBipedCapsuleInfo->GetRadius()), true);
}
else if (GetPedResetFlag(CPED_RESET_FLAG_IsNearLaddder))
{
//Keep the bound narrow on stairs or near ladders
SetDesiredMainMoverCapsuleRadius(pBipedCapsuleInfo->GetRadius(), true);
}
else if (m_fScriptCapsuleRadius > 0.0f)
{
SetDesiredMainMoverCapsuleRadius(m_fScriptCapsuleRadius, true);
}
else if (m_fTaskCapsuleRadius > 0.0f)
{
SetDesiredMainMoverCapsuleRadius(m_fTaskCapsuleRadius, true);
}
else if(bIsCrouching || bIsStealth)
{
SetDesiredMainMoverCapsuleRadius( GetIsInCover() ? pBipedCapsuleInfo->GetRadiusCrouchedInCover() : pBipedCapsuleInfo->GetRadiusCrouched() );
fNewHeadHeight = bIsStealth ? pBipedCapsuleInfo->GetHeadHeight() : pBipedCapsuleInfo->GetHeadHeightCrouched();
fNewHeightOffset = pBipedCapsuleInfo->GetCrouchedCapsuleOffsetZ();
}
else if (GetIsInCover())
{
SetDesiredMainMoverCapsuleRadius(pBipedCapsuleInfo->GetRadiusInCover(), true);
}
else if (GetPedConfigFlag(CPED_CONFIG_FLAG_StairsDetected))
{
//Keep the bound narrow on stairs
SetDesiredMainMoverCapsuleRadius(pBipedCapsuleInfo->GetRadius(), true);
}
else if (GetGroundPhysical() && GetGroundPhysical()->GetIsTypeVehicle() && ((CVehicle*)GetGroundPhysical())->InheritsFromBoat())
{
//Keep the bound narrow on boats
SetDesiredMainMoverCapsuleRadius(pBipedCapsuleInfo->GetRadius(), true);
}
else if (GetPedResetFlag(CPED_RESET_FLAG_UsingMobilePhone) && ((GetMotionData()->GetDesiredMbrY() > MOVEBLENDRATIO_STILL) || IsNearMirror(ScalarV(pBipedCapsuleInfo->GetRadiusMobilePhone()))))
{
SetDesiredMainMoverCapsuleRadius(pBipedCapsuleInfo->GetRadiusMobilePhone());
}
else if (GetPedResetFlag(CPED_RESET_FLAG_WeaponBlockedInFPSMode) && ((GetMotionData()->GetDesiredMbrY() > MOVEBLENDRATIO_STILL) || IsNearMirror(ScalarV(pBipedCapsuleInfo->GetRadiusWeaponBlocked()))))
{
SetDesiredMainMoverCapsuleRadius(pBipedCapsuleInfo->GetRadiusWeaponBlocked());
}
else if(IsPlayer() && m_BlockCapsuleResizeTime<fwTimer::GetTimeInMilliseconds())
{
bool bIsNearDoor = GetPedResetFlag(CPED_RESET_FLAG_IsNearDoor);
if (bIsNearDoor)
{
CDoor* pDoor = GetPedIntelligence()->GetClosestDoorInRange();
if (pDoor && CDoor::DoorTypeAutoOpens(pDoor->GetDoorType())) //ignore auto doors B* 705615
bIsNearDoor = false;
}
if(!bIsNearDoor) //not near doors!
{
const bool bIsRunning = GetMotionData()->GetIsRunning();
const bool bIsSprinting = GetMotionData()->GetIsSprinting();
const bool bClonePlayerPed = IsAPlayerPed() && IsNetworkClone();
if (bIsRunning || bIsSprinting || GetPedResetFlag(CPED_RESET_FLAG_IsJumping))
{
float fDesiredCapsuleSize = pBipedCapsuleInfo->GetRadiusRunning();
if (!GetMotionData()->GetPlayerHasControlOfPedThisFrame() && !bClonePlayerPed)
fDesiredCapsuleSize = rage::Min(PATHSERVER_PED_RADIUS, fDesiredCapsuleSize);
SetDesiredMainMoverCapsuleRadius(fDesiredCapsuleSize);
m_bPedCapsuleFullStop = false;
}
else if ((!m_bPedCapsuleFullStop && (GetPreviousAnimatedVelocity().Mag() > 0.01f || GetMotionData()->GetGaitReduction()!=0.0f)) ||
(m_bPedCapsuleFullStop && GetMotionData()->GetIsMbrInRange(MOVEBLENDRATIO_WALK, MOVEBLENDRATIO_RUN))) //walking
{
float fDesiredCapsuleSize = (pBipedCapsuleInfo->GetRadiusRunning() + pBipedCapsuleInfo->GetRadius())*0.5f;
if (!GetMotionData()->GetPlayerHasControlOfPedThisFrame() && !bClonePlayerPed)
fDesiredCapsuleSize = rage::Min(PATHSERVER_PED_RADIUS, fDesiredCapsuleSize);
SetDesiredMainMoverCapsuleRadius(fDesiredCapsuleSize);
m_bPedCapsuleFullStop = false;
}
else if (GetPedResetFlag(CPED_RESET_FLAG_IsAiming))
{
CTask* pMotionTask = GetCurrentMotionTask(false);
if (pMotionTask && pMotionTask->GetTaskType()==CTaskTypes::TASK_MOTION_AIMING)
{
float fPitch = static_cast<CTaskMotionAiming*>(pMotionTask)->GetCurrentPitch();
float fDesiredCapsuleSize = Lerp(Max(fPitch-0.5f, 0.0f)*2.0f, pBipedCapsuleInfo->GetRadius(), pBipedCapsuleInfo->GetRadiusRunning());
SetDesiredMainMoverCapsuleRadius(fDesiredCapsuleSize);
}
}
else
{
m_bPedCapsuleFullStop = true;
}
}
else
{
//trying walk near doors in action mode B* 999425
if (IsUsingActionMode())
{
float fDesiredCapsuleSize = (pBipedCapsuleInfo->GetRadiusRunning() + pBipedCapsuleInfo->GetRadius())*0.5f;
SetDesiredMainMoverCapsuleRadius(fDesiredCapsuleSize);
}
}
}
//else
// We want to use the cached version of fDesiredMainMoverCapsuleRadius therefore
// there is no need to set it here.
//raise the height on stairs B* 584908
if (GetPedConfigFlag(CPED_CONFIG_FLAG_StairsDetected))
{
static dev_float STAIR_HEIGHT_OFFSET = 0.2f;
fNewHeightOffset = STAIR_HEIGHT_OFFSET;
}
}
else if (GetIsFPSSwimming())
{
fNewHeadHeight = pBipedCapsuleInfo->GetHeadHeight();
TUNE_GROUP_FLOAT(FPS_SWIMMING, fSwimCapsuleHeightOffset, -0.25f, -5.0f, 5.0f, 0.01f);
fNewHeightOffset = fSwimCapsuleHeightOffset;
if (m_fTaskCapsuleRadius > 0.0f)
{
SetDesiredMainMoverCapsuleRadius(m_fTaskCapsuleRadius, true);
}
}
else
{
fNewHeadHeight = pBipedCapsuleInfo->GetHeadHeight();
fNewHeightOffset = 0.0f;
}
if(GetPedResetFlag(CPED_RESET_FLAG_ExpandPedCapsuleFromSkeleton))
{
crSkeleton *pSkeleton = GetSkeleton();
if(pSkeleton)
{
float radius = 0.0f;
const crSkeletonData &skeletonData = GetSkeletonData();
int boneIdx = 0;
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_HEAD, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_L_CALF, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_L_FOOT, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_L_FOREARM, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_L_HAND, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_R_CALF, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_R_FOOT, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_R_FOREARM, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_R_HAND, boneIdx))
{
radius = Max(radius, VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(boneIdx).GetCol3ConstRef()).GetHorizontalMag());
}
radius = MIN(radius, (fNewHeadHeight - pBipedCapsuleInfo->GetCapsuleZOffset() + fNewHeightOffset) / 2.0f);
SetDesiredMainMoverCapsuleRadius(radius);
}
}
if(PARAM_disableMoverCapsuleRadiusChanges.Get())
{
ResetDesiredMainMoverCapsuleData();
bIsCrouching = false;
}
SetPedConfigFlag(CPED_CONFIG_FLAG_UsingCrouchedPedCapsule,bIsCrouching);
// Do we need to transition or grow the mover bound?
const float fCurrentMainMoverCapsuleRadius = GetCurrentMainMoverCapsuleRadius();
float fDesiredMainMoverCapsuleRadius = GetDesiredMainMoverCapsuleRadius();
bool changing = (fCurrentMainMoverCapsuleRadius != fDesiredMainMoverCapsuleRadius) || (m_fCurrentCapsultHeightOffset!=fNewHeightOffset);
m_bMoverCapsuleIsChanging = changing;
if(changing)
{
if(fDesiredMainMoverCapsuleRadius > fCurrentMainMoverCapsuleRadius)
{
// Restrict the desired size if opposing collisions are happening.
if(GetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions))
{
static float restrictedByOpposingCollisionsShrinkSpeed = 0.01f;
fDesiredMainMoverCapsuleRadius = Max(fCurrentMainMoverCapsuleRadius-restrictedByOpposingCollisionsShrinkSpeed,pBipedCapsuleInfo->GetRadius());
}
}
//ensure capsules don't penetrate the ground
static dev_float s_fMaxCapsuleRadiusbeforeHeightOffset = 0.75f;
if (fDesiredMainMoverCapsuleRadius > s_fMaxCapsuleRadiusbeforeHeightOffset)
{
fNewHeightOffset += fDesiredMainMoverCapsuleRadius-s_fMaxCapsuleRadiusbeforeHeightOffset;
}
SetBoundSize( pBipedCapsuleInfo, fNewHeadHeight, fNewHeightOffset, fDesiredMainMoverCapsuleRadius, fTimeStep);
bNeedToRecalcExtents = true;
bInternalMotionOnly = false;
//Include ATTACH_STATE_PED_ON_GROUND, otherwise we can activate incorrectly.
if(!GetIsAttached() || GetAttachmentExtension()->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION)
|| !(GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_BASIC || GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_WORLD || GetAttachmentExtension()->GetAttachState()==ATTACH_STATE_PED_ON_GROUND) )
{
ActivatePhysics();
}
}
// Reset the constricted flag now that we have used it.
SetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions,false);
float fSteerBias = GetMotionData()->GetSteerBias();
Approach(fSteerBias, GetMotionData()->GetDesiredSteerBias(), ms_fCapsuleRate, fTimeStep);
GetMotionData()->SetSteerBias(fSteerBias);
// Pitch/heading and offset the capsules correctly. Allow rotation of the ped's bounds for when
// anims play at a rotated angle, e.g. swimming or falling down. Maybe a better solution is
// extracting pitch velocity and applying it as a torque?
Vector3 vDesiredBoundEulers(GetMotionData()->GetDesiredBoundPitch(), 0.0f, GetMotionData()->GetDesiredBoundHeading());
Vector3 vBoundEulers(GetBoundPitch(), 0.0f, GetBoundHeading());
//Update bound pitch/yaw
if (!vDesiredBoundEulers.IsClose(vBoundEulers, SMALL_FLOAT)) //smoothed for camera
{
vBoundEulers.ApproachStraight(vDesiredBoundEulers, ms_fCapsuleRate, fTimeStep);
// FPS Swim: If we're running the swimming/diving task while swimming, set the bounds pitch to the desired bounds pitch (no lerping)
if (GetIsFPSSwimming() && GetPedResetFlag(CPED_RESET_FLAG_FPSSwimUseSwimMotionTask))
{
vBoundEulers.x = vDesiredBoundEulers.x;
}
SetBoundPitch(vBoundEulers.x);
SetBoundHeading(vBoundEulers.z);
}
Vector3 vDesiredBoundOffset = GetMotionData()->GetDesiredBoundOffset();
Vector3 vBoundOffset = GetBoundOffset();
//Update bound offset
if (!vDesiredBoundOffset.IsClose(vBoundOffset, SMALL_FLOAT)) //smoothed for camera
{
vBoundOffset.ApproachStraight(vDesiredBoundOffset, ms_fCapsuleRate, fTimeStep);
SetBoundOffset(vBoundOffset);
}
const float fMainCapsuleHeight = bIsCrouching ? pBipedCapsuleInfo->GetHeadHeightCrouched() : pBipedCapsuleInfo->GetHeadHeight();
const float fCrouchHeightOffset = bIsCrouching ? pBipedCapsuleInfo->GetCrouchedCapsuleOffsetZ() : 0.0f;
phArchetype * pPhysArch = GetPhysArch();
Assert(pPhysArch->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pPhysArch->GetBound());
if(m_fCurrentBoundPitch != vBoundEulers.x || m_fCurrentBoundHeading != vBoundEulers.z || !m_vCurrentBoundOffset.IsClose(vBoundOffset, SMALL_FLOAT) || bForceUpdate)
{
Matrix34 capsuleMat;
#if PED_CURVEDGEOM_FOR_MAIN_CAPSULE
capsuleMat.Identity();
capsuleMat.d.z = 0.5f * (pBipedCapsuleInfo->GetCapsuleZOffset() + fMainCapsuleHeight + fCrouchHeightOffset);
capsuleMat.RotateFullX(vBoundEulers.x);
capsuleMat.RotateFullZ(vBoundEulers.z);
capsuleMat.Translate(vBoundOffset);
pBoundComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE,capsuleMat);
pBoundComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE,capsuleMat);
#if PED_USE_SHAPETEST_CAPSULE > 0
Assert(pBoundComposite->GetNumBounds() > PED_USE_SHAPETEST_CAPSULE);
capsuleMat.Identity();
capsuleMat.RotateX(HALF_PI);
capsuleMat.d.z = 0.5f * (pBipedCapsuleInfo->GetHeadHeight() + pBipedCapsuleInfo->GetCapsuleZOffset() + fCrouchHeightOffset);
capsuleMat.RotateFullX(vBoundEulers.x);
capsuleMat.RotateFullZ(vBoundEulers.z);
capsuleMat.Translate(vBoundOffset);
pBoundComposite->SetCurrentMatrix(PED_USE_SHAPETEST_CAPSULE,capsuleMat);
pBoundComposite->SetLastMatrix(PED_USE_SHAPETEST_CAPSULE,capsuleMat);
#endif // PED_USE_SHAPETEST_CAPSULE
#else // PED_CURVEGEOM_FOR_MAIN_CAPSULE
capsuleMat.Identity();
capsuleMat.d.z = 0.5f * (fMainCapsuleHeight + pBipedCapsuleInfo->GetCapsuleZOffset() + fCrouchHeightOffset);
capsuleMat.RotateX(HALF_PI);
capsuleMat.RotateFullX(vBoundEulers.x);
capsuleMat.RotateFullZ(vBoundEulers.z);
capsuleMat.Translate(vBoundOffset);
pBoundComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE,RCC_MAT34V(capsuleMat));
pBoundComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE,RCC_MAT34V(capsuleMat));
#endif // PED_CURVEGEOM_FOR_MAIN_CAPSULE
#if PED_USE_CAPSULE_PROBES
// Only need to currently do this if there's a new offset
if (!m_vCurrentBoundOffset.IsClose(vBoundOffset, SMALL_FLOAT))
{
const float fPedProbeTop = pBipedCapsuleInfo->GetKneeHeight() + 2.0f * pBipedCapsuleInfo->GetProbeRadius();
const float fPedProbeBottom = (-pBipedCapsuleInfo->GetGroundToRootOffset()) - pBipedCapsuleInfo->GetGroundOffsetExtend();
capsuleMat.Identity();
capsuleMat.d.z = 0.5f * (fPedProbeTop + fPedProbeBottom);
capsuleMat.d.y += pBipedCapsuleInfo->GetProbeYOffset();
capsuleMat.RotateX(HALF_PI);
capsuleMat.Translate(vBoundOffset);
pBoundComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE,RCC_MAT34V(capsuleMat));
capsuleMat.d.z = 0.5f * (fPedProbeTop + fPedProbeBottom) + (pBipedCapsuleInfo->GetHeadHeight() - fPedProbeTop);
pBoundComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE,RCC_MAT34V(capsuleMat));
}
#endif // PED_USE_CAPSULE_PROBES
#if PLAYER_USE_LOWER_LEG_BOUND
if(pBipedCapsuleInfo->GetUseLowerLegBound())
{
Assert(pBoundComposite->GetNumBounds() > PLAYER_USE_LOWER_LEG_BOUND);
#if !PLAYER_USE_CAPSULE_LEG_BOUND
capsuleMat.Identity();
#else // !PLAYER_USE_CAPSULE_LEG_BOUND
capsuleMat.Set(Vector3(1,0,0),Vector3(0,0,1),Vector3(0,-1,0),Vector3(0,0,0));
#endif // !PLAYER_USE_CAPSULE_LEG_BOUND
capsuleMat.d.z = 0.5f * (PLAYER_LEGS_TOP + PLAYER_LEGS_BOTTOM);
capsuleMat.RotateFullX(vBoundEulers.x);
capsuleMat.RotateFullZ(vBoundEulers.z);
capsuleMat.Translate(vBoundOffset);
pBoundComposite->SetCurrentMatrix(PLAYER_USE_LOWER_LEG_BOUND,RCC_MAT34V(capsuleMat));
pBoundComposite->SetLastMatrix(PLAYER_USE_LOWER_LEG_BOUND,RCC_MAT34V(capsuleMat));
}
#endif // PLAYER_USE_LOWER_LEG_BOUND
m_fCurrentBoundPitch = vBoundEulers.x;
m_fCurrentBoundHeading = vBoundEulers.z;
m_vCurrentBoundOffset = vBoundOffset;
bNeedToRecalcExtents = true;
}
#if PED_USE_EXTRA_BOUND
Assertf(pBoundComposite->GetBound(PED_USE_EXTRA_BOUND)->GetType() == phBound::CAPSULE, "Ped %s has a non-capsule extra bound.", GetDebugName());
if(pBoundComposite->GetBound(PED_USE_EXTRA_BOUND)->GetType() == phBound::CAPSULE)
{
phBoundCapsule* pExtraBoundCapsule = static_cast<phBoundCapsule*>(pBoundComposite->GetBound(PED_USE_EXTRA_BOUND));
bool bUseBoatBlocker = false;
if(pBipedCapsuleInfo->GetUseBoatBlocker())
{
CTaskMotionBase* pMotionTask = GetCurrentMotionTask();
bool bUnderWater = pMotionTask && pMotionTask->IsUnderWater();
bool bSwimming = (pMotionTask && pMotionTask->IsInWater()) || GetIsSwimming() || GetWasSwimming();
bool bRecentlySubmerged = false;
if(bUnderWater && GetPedIntelligence()->GetEventScanner())
{
bRecentlySubmerged = bUnderWater && (GetPedIntelligence()->GetEventScanner()->GetInWaterEventScanner().GetTimeSubmerged() < 2.0f);
}
bUseBoatBlocker = bSwimming || bRecentlySubmerged;
if(bUseBoatBlocker || bNeedToRecalcExtents)
{
float fRadius = pExtraBoundCapsule->GetRadius();
float fScale = fRadius/pBipedCapsuleInfo->GetRadius();
static float fGrowSpeed = 0.25f;
static float fMinSize = 0.25f;
if(!bSwimming || (bUnderWater && !bRecentlySubmerged))
{
fScale = fMinSize;
}
else
{
Approach(fScale, 1.0f, fGrowSpeed, fwTimer::GetTimeStep());
}
float fNewRadius = pBipedCapsuleInfo->GetRadius() * fScale;
float fNewLength = (pBipedCapsuleInfo->GetBoatBlockerHeadHeight() - pBipedCapsuleInfo->GetBoatBlockerZOffset() - 2.0f*pBipedCapsuleInfo->GetRadius()) * fScale;
pExtraBoundCapsule->SetCapsuleSize(fNewRadius,fNewLength);
Matrix34 capsuleMat;
capsuleMat.Identity();
capsuleMat.d.z = 0.5f * (pBipedCapsuleInfo->GetBoatBlockerHeadHeight() + pBipedCapsuleInfo->GetBoatBlockerZOffset()) * fScale;
capsuleMat.RotateX(HALF_PI);
//! We want to keep boat blocker upright, so need to pre-emptively multiply by inverse ped
//! matrix as the current bound matrix is in local space.
Matrix34 pedMatrix = MAT34V_TO_MATRIX34( GetCurrentPhysicsInst()->GetMatrix() );
capsuleMat.Dot3x3Transpose(pedMatrix);
pBoundComposite->SetCurrentMatrix(PED_USE_EXTRA_BOUND,RCC_MAT34V(capsuleMat));
pBoundComposite->SetLastMatrix(PED_USE_EXTRA_BOUND,RCC_MAT34V(capsuleMat));
//! Set include flags appropriately.
if (!pBoundComposite->GetTypeAndIncludeFlags())
{
pBoundComposite->AllocateTypeAndIncludeFlags();
}
pBoundComposite->SetIncludeFlags(PED_USE_EXTRA_BOUND, GetIsSwimming() ? ArchetypeFlags::GTA_VEHICLE_TYPE : 0);
bNeedToRecalcExtents = true;
}
}
if (!pBipedCapsuleInfo->GetUseBoatBlocker() || !bUseBoatBlocker)
{
if (pBoundComposite->GetTypeAndIncludeFlags() == NULL)
{
pBoundComposite->AllocateTypeAndIncludeFlags();
}
if (m_fCurrentBoundPitch != 0.0f && m_fCurrentBoundHeading != 0.0f)
{
static dev_float s_fExtraBoundScaleMultiplier = 0.5f;
float fNewCapsuleLen = (fNewHeadHeight - pBipedCapsuleInfo->GetCapsuleZOffset() - 2.0f * m_fCurrentMainMoverCapsuleRadius - fNewHeightOffset) * s_fExtraBoundScaleMultiplier;
// Check that the capsule radius isn't too large for head height
if (fNewCapsuleLen < 0.0f)
{
fNewCapsuleLen = 0.0f;
}
pExtraBoundCapsule->SetCapsuleSize(m_fCurrentMainMoverCapsuleRadius, fNewCapsuleLen);
pBoundComposite->SetCurrentMatrix(PED_USE_EXTRA_BOUND, pBoundComposite->GetCurrentMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE));
pBoundComposite->SetLastMatrix(PED_USE_EXTRA_BOUND, pBoundComposite->GetLastMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE));
// Set include flags appropriately.
if (GetCurrentPhysicsInst() != NULL && GetCurrentPhysicsInst()->IsInLevel())
{
pBoundComposite->SetIncludeFlags(PED_USE_EXTRA_BOUND, CPhysics::GetLevel()->GetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex()));
}
else
{
pBoundComposite->SetIncludeFlags(PED_USE_EXTRA_BOUND, 0);
}
bNeedToRecalcExtents = true;
}
else
{
// Clear include flags
pBoundComposite->SetIncludeFlags(PED_USE_EXTRA_BOUND, 0);
}
}
}
#endif
#if PLAYER_USE_FPS_HEAD_BOUND
if(pBipedCapsuleInfo->GetUseFPSHeadBlocker())
{
Assert(pBoundComposite->GetBound(PLAYER_USE_FPS_HEAD_BOUND)->GetType() == phBound::SPHERE);
phBoundSphere* pFPSHeadSphereInfo = static_cast<phBoundSphere*>(pBoundComposite->GetBound(PLAYER_USE_FPS_HEAD_BOUND));
float fHeadBlockerRadius = pBipedCapsuleInfo->GetRadiusFPSHeadBlocker();
const CTaskVault* pVaultTask = GetPedIntelligence()->IsPedVaulting() ? static_cast<const CTaskVault*>(GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_VAULT)) : NULL;
bool bCanClimbUseFPSBound = pVaultTask && pVaultTask->CanUseFPSHeadBound();
TUNE_GROUP_FLOAT(VAULT_FPS, fVaultScale, 4.0f, 1.0f, 10.0f, 0.01f)
if(bCanClimbUseFPSBound)
fHeadBlockerRadius *= fVaultScale;
float fRadius = pFPSHeadSphereInfo->GetRadius();
float fScale = fRadius/fHeadBlockerRadius;
static float fGrowSpeed = 0.25f;
static float fMinSize = 0.25f;
bool bUseHeadBound = GetIsFPSSwimming() FPS_MODE_SUPPORTED_ONLY(|| (IsFirstPersonShooterModeEnabledForPlayer(false) && bCanClimbUseFPSBound));
if(!bUseHeadBound)
{
#if FPS_MODE_SUPPORTED
if(IsFirstPersonShooterModeEnabledForPlayer(false))
{
Approach(fScale, fMinSize, fGrowSpeed, fTimeStep);
}
else
#endif
{
fScale = fMinSize;
}
}
else
{
Approach(fScale, 1.0f, fGrowSpeed, fTimeStep);
}
float fNewRadius = fHeadBlockerRadius * fScale;
pFPSHeadSphereInfo->SetSphereRadius(fNewRadius);
// Position sphere around ped head bone
Vector3 vHeadPosition(Vector3::ZeroType);
s32 headBoneIndex;
const bool hasValidHeadBone = GetSkeletonData().ConvertBoneIdToIndex((u16)BONETAG_HEAD, headBoneIndex);
if (hasValidHeadBone)
{
Matrix34 localBoneMatrix = GetObjectMtx(headBoneIndex);
vHeadPosition = localBoneMatrix.d;
}
Matrix34 capsuleMat;
capsuleMat.Identity();
capsuleMat.d = vHeadPosition;
capsuleMat.RotateX(HALF_PI);
pBoundComposite->SetCurrentMatrix(PLAYER_USE_FPS_HEAD_BOUND, RCC_MAT34V(capsuleMat));
pBoundComposite->SetLastMatrix(PLAYER_USE_FPS_HEAD_BOUND, RCC_MAT34V(capsuleMat));
//! Set include flags appropriately.
if (!pBoundComposite->GetTypeAndIncludeFlags())
{
pBoundComposite->AllocateTypeAndIncludeFlags();
}
if (!bUseHeadBound && fScale <= fMinSize)
{
pBoundComposite->SetIncludeFlags(PLAYER_USE_FPS_HEAD_BOUND, 0);
}
else if (GetCurrentPhysicsInst() != NULL && GetCurrentPhysicsInst()->IsInLevel())
{
pBoundComposite->SetIncludeFlags(PLAYER_USE_FPS_HEAD_BOUND, CPhysics::GetLevel()->GetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex()));
}
bNeedToRecalcExtents = true;
}
#endif //PLAYER_USE_FPS_HEAD_BOUND
if(bNeedToRecalcExtents)
{
phArchetype * pPhysArch = GetPhysArch();
Assert(pPhysArch->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pPhysArch->GetBound());
const float fOldCompositeRadius = pBoundComposite->GetRadiusAroundCentroid();
pBoundComposite->CalculateCompositeExtents(bInternalMotionOnly);
if(GetCurrentPhysicsInst()->IsInLevel())
{
const float fNewCompositeRadius = pBoundComposite->GetRadiusAroundCentroid();
if(fNewCompositeRadius != fOldCompositeRadius)
{
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetCurrentPhysicsInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
}
if(pBoundComposite->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetCurrentPhysicsInst()->GetLevelIndex());
}
}
else
{
pBoundComposite->UpdateBvh(true);
}
}
}
//Horse capsule pitch
#if PED_USE_CAPSULE_PROBES
if (m_pCapsuleInfo->IsQuadruped()) {
phArchetype * pPhysArch = GetPhysArch();
//rotate capsule
Assert(pPhysArch->GetBound()->GetType() == phBound::COMPOSITE);
const float fOldRadius = pPhysArch->GetBound()->GetRadiusAroundCentroid();
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pPhysArch->GetBound());
Matrix34 capsuleMat;
//main capsule
capsuleMat = RCC_MATRIX34(pBoundComposite->GetCurrentMatrix(0));
capsuleMat.Identity3x3();
capsuleMat.RotateX(GetBoundPitch());
pBoundComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE,RCC_MAT34V(capsuleMat));
pBoundComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE,RCC_MAT34V(capsuleMat));
//fix up capsule probe locations. We've cached the top location - this is where we want to start
//our sweep from (last matrix) and we want to probe downwards. As the capsule matrices are in local space
//this needs to be taken into account
capsuleMat = MAT34V_TO_MATRIX34( GetCurrentPhysicsInst()->GetMatrix() );
CPedModelInfo *pModelInfo = GetPedModelInfo();
const CQuadrupedCapsuleInfo* pQuadCapsuleInfo = static_cast<const CQuadrupedCapsuleInfo*>(m_pCapsuleInfo);
//We're going to do some extra work to try and avoid feedback loops cause by changes in the X axis rotation
//moving the probe positions. Stairs cause this to happen. A possible side effect of this is that the probes will
//not match the feet positions (if needed we could use bands, which would still have a possible feedback effect
//but it could be limited with hysteresis).
Vector3 vYAxis = capsuleMat.b;
Vector3 vTmp = vYAxis;
vTmp.z = 0.0f;
float fFlatMagY = vTmp.Mag();
float fPushOutRatio = 1.0f / Max(0.5f, fFlatMagY); //Limit to 60 degree slopes
// We need to determine how far along the local y-axis of the capsule we can extend the probes, otherwise they might go outside the capsule and fully compressn
// Conceptually we're creating two world space planes perpendicular to the up (z) axis and the quadruped's forward. We cannot let the probe sphere go outside the
// bounds of those two planes.
TUNE_FLOAT(QUAD_PED_COLLISION_BUFFER,0.03f,0.0f,1.0f,0.01f);
const ScalarV collisionBufferRequired = rage::Add(ScalarVFromF32(m_pCapsuleInfo->GetProbeRadius()),ScalarVFromF32(QUAD_PED_COLLISION_BUFFER));
const ScalarV mainCapsuleRadius = ScalarVFromF32(pQuadCapsuleInfo->GetMainBoundRadius());
const ScalarV mainCapsuleHalfLength = Scale(ScalarVFromF32(pQuadCapsuleInfo->GetMainBoundLength()),ScalarV(V_HALF));
const Vec3V coverageDirectionWorld = NormalizeSafe(And(RCC_MAT34V(capsuleMat).GetCol1(),Vec3V(V_MASKXY)),Vec3V(V_Y_AXIS_WZERO), Vec3V(V_ZERO));
// Find the plane normal in local space
const Vec3V coverageDirectionLocal = UnTransform3x3Ortho(RCC_MAT34V(capsuleMat),coverageDirectionWorld);
// Find how much of the y-axis we cover at z = 0
const ScalarV coverageAlongNormalFromOrigin = Subtract(rage::Add(Scale(mainCapsuleHalfLength,coverageDirectionLocal.GetY()),mainCapsuleRadius),collisionBufferRequired);
const ScalarV coverageAlongYFromOrigin = InvScaleSafe(coverageAlongNormalFromOrigin,coverageDirectionLocal.GetY(),ScalarV(V_ZERO));
// Find how much we need to shift our coverage depending on the z value of the probe position
const ScalarV zShiftToYShiftRatio = InvScaleSafe(Negate(coverageDirectionLocal.GetZ()),coverageDirectionLocal.GetY(),ScalarV(V_ZERO));
//Invert our inst matrix to get world->local transformation
capsuleMat.Inverse3x3();
//Probe depth
float fZDist = pModelInfo->GetQuadrupedProbeDepth();
//Need to take into account rotation of object (working out local coords)
Vector3 vBottomDelta = capsuleMat.c * (-fZDist);
//We also project back into the capsule a little bit to make sure the sweep
//covers all needed space
Vector3 vTopDelta = capsuleMat.c * (s_QuadrupedSpringTuning.m_fTopSpringExtension);
//-------------
//First probe (front?)
//Grab the top location (start of sweep or last frame's matrix)
Vector3 vTop = pModelInfo->GetQuadrupedProbeTopPosition(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE);
float coverageShift = vTop.z*zShiftToYShiftRatio.Getf();
float fNewY = Clamp(vTop.y * fPushOutRatio,-coverageAlongYFromOrigin.Getf() + coverageShift, coverageAlongYFromOrigin.Getf() + coverageShift);
const float kfRatioToHeight = vYAxis.z * fPushOutRatio;
m_fHeightFudgeRecoveryFactor[0] = (vTop.y - fNewY) * kfRatioToHeight;
vTop.y = fNewY;
//Offset for bottom with calculated local space delta
Vector3 vBottom = vTop + vBottomDelta;
//SwapEm(capsuleMat.b, capsuleMat.c);
//capsuleMat.c = -capsuleMat.c;
capsuleMat.Identity3x3();
capsuleMat.d = vBottom;
pBoundComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE, RCC_MAT34V(capsuleMat));
//Move last matrix to stored top position
capsuleMat.d = vTop + vTopDelta;
pBoundComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE, RCC_MAT34V(capsuleMat));
//-------------
//Other probe (back?)
//get new top location
vTop = pModelInfo->GetQuadrupedProbeTopPosition(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE_2);
coverageShift = vTop.z*zShiftToYShiftRatio.Getf();
fNewY = Clamp(vTop.y * fPushOutRatio,-coverageAlongYFromOrigin.Getf() + coverageShift, coverageAlongYFromOrigin.Getf() + coverageShift);
m_fHeightFudgeRecoveryFactor[1] = (vTop.y - fNewY) * kfRatioToHeight;
vTop.y = fNewY;
//Offset for bottom
vBottom = vTop + vBottomDelta;
capsuleMat.d = vBottom;
pBoundComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE_2, RCC_MAT34V(capsuleMat));
//Move last matrix to stored top position
capsuleMat.d = vTop+vTopDelta;
pBoundComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_CAPSULE_PROBE_2, RCC_MAT34V(capsuleMat));
//Update neck bound
if (pQuadCapsuleInfo->GetUseNeckBound())
{
capsuleMat = RCC_MATRIX34(pBoundComposite->GetCurrentMatrix(pQuadCapsuleInfo->GetNeckBoundSlot()));
pBoundComposite->SetLastMatrix(pQuadCapsuleInfo->GetNeckBoundSlot(), RCC_MAT34V(capsuleMat));
crSkeleton *pSkeleton = GetSkeleton();
if(pSkeleton)
{
const crSkeletonData &skeletonData = GetSkeletonData();
int boneIdx = 0;
if(skeletonData.ConvertBoneIdToIndex((u16)BONETAG_SPINE3, boneIdx))
{
int headBoneIdx = 0;
if (skeletonData.ConvertBoneIdToIndex((u16)BONETAG_HEAD, headBoneIdx))
{
Matrix34 globalMtx;
pSkeleton->GetGlobalMtx(headBoneIdx, RC_MAT34V(globalMtx));
Vector3 neckVec = globalMtx.d;
pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(globalMtx));
neckVec.Subtract(globalMtx.d);
const float fHeadAngle = fwAngle::GetRadianAngleBetweenPoints(neckVec.x, neckVec.y, 0.0f, 0.0f);
const float fAngle = SubtractAngleShorter(fHeadAngle, GetCurrentHeading());
capsuleMat.Identity3x3();
capsuleMat.FromEulersXYZ(Vector3(pQuadCapsuleInfo->GetNeckBoundRotation(),0,fAngle));
float t = (fAngle)/HALF_PI;
capsuleMat.d.x = -t*pQuadCapsuleInfo->GetNeckBoundLength();
}
}
}
pBoundComposite->SetCurrentMatrix(pQuadCapsuleInfo->GetNeckBoundSlot(), RCC_MAT34V(capsuleMat));
}
//Update size and in level if needed
pBoundComposite->CalculateCompositeExtents(true);
const float fNewRadius = pPhysArch->GetBound()->GetRadiusAroundCentroid();
if(GetCurrentPhysicsInst()->IsInLevel())
{
if(fNewRadius != fOldRadius)
{
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetCurrentPhysicsInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
}
if(pBoundComposite->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetCurrentPhysicsInst()->GetLevelIndex());
}
}
else
{
pBoundComposite->UpdateBvh(true);
}
m_fCurrentMainMoverCapsuleRadius = pQuadCapsuleInfo->GetMainBoundRadius();
}
#endif
//Store bounds information
#if DR_ENABLED
phArchetype * pPhysArch = GetPhysArch();
if (Verifyf(pPhysArch->GetBound()->GetType() == phBound::COMPOSITE, "Unexpected bound type"))
{
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pPhysArch->GetBound());
Mat34V boundMat;
Transform(boundMat, GetMatrix(), pBoundComposite->GetCurrentMatrix(0));
//Get start and end positions
Assert(pBoundComposite->GetBound(0)->GetType() == phBound::CAPSULE);
const phBoundCapsule *pBoundCapsule = (const phBoundCapsule *)pBoundComposite->GetBound(0);
Vec3V startPos = pBoundCapsule->GetEndPointA();
Vec3V endPos = pBoundCapsule->GetEndPointB();
startPos = Transform(boundMat, startPos);
endPos = Transform(boundMat, endPos);
Vec3V groundPos(endPos);
groundPos.SetZf(m_fGroundZFromImpact);
debugPlayback::RecordPedCapsuleInfo(GetCurrentPhysicsInst(), startPos, endPos, groundPos, pBoundCapsule->GetRadius(), RCC_VEC3V(m_vCurrentBoundOffset));
}
#endif
// Need to clear this flag for qpeds, too - it is checked and set to false only under the IsBiped() logic above.
if (m_pCapsuleInfo->IsQuadruped())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_MoverConstrictedByOpposingCollisions, false);
}
return bNeedToRecalcExtents || bNeedToRecalcRagdollExtents;
}
void CPed::InstantResetCloneDesiredMainMoverCapsuleData()
{
//If current capsule radius is greater than desired then instantly reduced it to desired
if( taskVerifyf(IsNetworkClone(),"only expected to be used on clones") &&
GetCurrentMainMoverCapsuleRadius() > GetDesiredMainMoverCapsuleRadius())
{
ResetDesiredMainMoverCapsuleData(true);
}
}
void CPed::InstantResetDesiredMainMoverCapsuleDataForCutscene()
{
if(CutSceneManager::GetInstance() && CutSceneManager::GetInstance()->IsRunning())
{
ResetDesiredMainMoverCapsuleData(true);
}
}
void CPed::ResetDesiredMainMoverCapsuleData(bool bInstant)
{
phArchetype * pPhysArch = GetPhysArch();
if(!Verifyf(pPhysArch,"Archetype missing."))
{
return;
}
if(!Verifyf(pPhysArch->GetBound(),"Bound missing."))
{
return;
}
const CBipedCapsuleInfo *pBipedCapsuleInfo = m_pCapsuleInfo->GetBipedCapsuleInfo();
if(pBipedCapsuleInfo)
{
m_fDesiredMainMoverCapsuleRadius = pBipedCapsuleInfo->GetRadius();
if (bInstant)
{
SetBoundSize(pBipedCapsuleInfo, pBipedCapsuleInfo->GetHeadHeight(), 0.0f, m_fDesiredMainMoverCapsuleRadius, -1.0f);
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(GetPhysArch()->GetBound());
pBoundComposite->CalculateCompositeExtents(false);
if(GetCurrentPhysicsInst()->IsInLevel())
{
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetCurrentPhysicsInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
if(pBoundComposite->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetCurrentPhysicsInst()->GetLevelIndex());
}
}
else
{
pBoundComposite->UpdateBvh(true);
}
}
}
// Reset this here each frame
SetScriptCapsuleRadius(0.0f);
}
void CPed::SetBoundSize( const CBipedCapsuleInfo* pBipedCapsuleInfo, float fNewHeadHeight, float fNewHeightOffset, float fDesiredBoundRadius, float fTimeStep )
{
// This resizes the main bound of the ped for use when the ped crouches etc.
// We have to deal with different configurations of ped bounds as defined in pedmodelinfo.h
// If the ped bound setup gets changed in the modelinfo make sure this function is updated too
if(!Verifyf(pBipedCapsuleInfo, "Function only supported on bipeds!"))
{
return;
}
Assert(GetPhysArch()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* pComposite = static_cast<phBoundComposite*>(GetPhysArch()->GetBound());
#if PED_CURVEDGEOM_FOR_MAIN_CAPSULE
const float fCurvedGeomRadius = pBipedCapsuleInfo->GetRadius() - PED_CURVEDGEOM_FOR_MAIN_MARGIN;
const float fCurvedGeomHeight = fNewHeadHeight - pBipedCapsuleInfo->GetCapsuleZOffset() - 2.0f*PED_CURVEDGEOM_FOR_MAIN_MARGIN;
const float fCurvedGeomMargin = PED_CURVEDGEOM_FOR_MAIN_MARGIN;
CBipedCapsuleInfo::InitPhysCreateCurvedGeomentryBound(fCurvedGeomRadius,fCurvedGeomHeight,fCurvedGeomMargin,pGeomBound);
Matrix34 geomMat;
geomMat.Identity();
geomMat.d.z = 0.5f * (pBipedCapsuleInfo->GetCapsuleZOffset() + fNewHeadHeight + fNewHeightOffset);
pComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE, geomMat);
pComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE, geomMat);
#if PED_USE_SHAPETEST_CAPSULE
// Lets not resize this capsule so we can use it for AI probes against the position we would be
// if we stood up
// stops feedback loop when ped crouch to avoid getting shot
/*
Assert(pComposite->GetBound(PED_USE_SHAPETEST_CAPSULE)->GetType() == phBound::CAPSULE);
phBoundCapsule* pCapsuleBound = static_cast<phBoundCapsule*>(pComposite->GetBound(PED_USE_SHAPETEST_CAPSULE));
pCapsuleBound->SetCapsuleSize(m_pCapsuleInfo->GetRadius(), fNewHeadHeight - m_pCapsuleInfo->GetCapsuleZOffset() - 2.0f*m_pCapsuleInfo->GetRadius());
Matrix34 capsuleMat;
capsuleMat.Identity();
capsuleMat.d.z = 0.5f * (fNewHeadHeight + m_pCapsuleInfo->GetCapsuleZOffset());
capsuleMat.RotateX(HALF_PI);
pComposite->SetCurrentMatrix(PED_USE_SHAPETEST_CAPSULE, capsuleMat);
pComposite->SetLastMatrix(PED_USE_SHAPETEST_CAPSULE, capsuleMat);
*/
#endif // PED_USE_SHAPETEST_CAPSULE
#else // PED_CURVEDGEOM_FOR_MAIN_CAPSULE
Assert(pComposite->GetBound(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE)->GetType() == phBound::CAPSULE);
phBoundCapsule* pMainCapsule = static_cast<phBoundCapsule*>(pComposite->GetBound(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE));
m_fCurrentMainMoverCapsuleRadius = pMainCapsule->GetRadius();
// Determine if we want to grow or shrink the bound radius
float fNewCapsuleRadius = m_fCurrentMainMoverCapsuleRadius;
float fRadiusGrowSpeed = m_fOverrideCapsuleRadiusGrowSpeed!=0.0f ? m_fOverrideCapsuleRadiusGrowSpeed : pBipedCapsuleInfo->GetRadiusGrowSpeed();
m_fOverrideCapsuleRadiusGrowSpeed = 0.0f;
if (fTimeStep < 0.0f)
{
fNewCapsuleRadius = fDesiredBoundRadius;
}
else
{
Approach(fNewCapsuleRadius, fDesiredBoundRadius, fRadiusGrowSpeed, fTimeStep);
}
float fNewCapsuleLen = fNewHeadHeight - pBipedCapsuleInfo->GetCapsuleZOffset() - 2.0f * fNewCapsuleRadius - fNewHeightOffset;
if(fNewCapsuleLen<0.0f) //"Capsule radius too large for head height."
{
fNewCapsuleLen = 0.0f;
}
pMainCapsule->SetCapsuleSize(fNewCapsuleRadius,fNewCapsuleLen);
#if PLAYER_USE_LOWER_LEG_BOUND
if(pBipedCapsuleInfo->GetUseLowerLegBound())
{
Assert(pComposite->GetNumBounds() > PLAYER_USE_LOWER_LEG_BOUND);
Assert(pComposite->GetBound(PLAYER_USE_LOWER_LEG_BOUND)->GetType() == phBound::CYLINDER);
phBoundCylinder* pLegBoundCylinder = static_cast<phBoundCylinder*>(pComposite->GetBound(PLAYER_USE_LOWER_LEG_BOUND));
if(m_bResizePlayerLegBound)
{
pLegBoundCylinder->SetCylinderRadiusAndHalfHeight(fNewCapsuleRadius, pLegBoundCylinder->GetHalfHeight());
}
else
{
pLegBoundCylinder->SetCylinderRadiusAndHalfHeight(pBipedCapsuleInfo->GetRadius(), pLegBoundCylinder->GetHalfHeight());
}
}
#endif
m_fCurrentMainMoverCapsuleRadius = pMainCapsule->GetRadius();
m_fCurrentCapsultHeightOffset = fNewHeightOffset;
pMainCapsule->SetMaterial(PGTAMATERIALMGR->g_idPhysPedCapsule);
pComposite->SetBound(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE, pMainCapsule);
Matrix34 capsuleMat;
capsuleMat.Identity();
capsuleMat.d.z = 0.5f * (fNewHeadHeight + pBipedCapsuleInfo->GetCapsuleZOffset() + fNewHeightOffset);
capsuleMat.RotateX(HALF_PI);
capsuleMat.RotateFullX(m_fCurrentBoundPitch);
capsuleMat.RotateFullZ(m_fCurrentBoundHeading);
capsuleMat.Translate(m_vCurrentBoundOffset);
pComposite->SetCurrentMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE, RCC_MAT34V(capsuleMat));
pComposite->SetLastMatrix(CBaseCapsuleInfo::BOUND_MAIN_CAPSULE, RCC_MAT34V(capsuleMat));
#endif
}
void CPed::SetDynamicEntityFlagsForWeapon(CObject* pWeaponObject, bool value)
{
if(pWeaponObject)
{
pWeaponObject->m_nDEflags.bUseExtendedBoundingBox = value;
CWeapon* pWeapon = pWeaponObject->GetWeapon();
if (pWeapon)
{
const CWeapon::Components& rWeaponComponents = pWeapon->GetComponents();
for(s32 i = 0; i < rWeaponComponents.GetCount(); i++)
{
CDynamicEntity* pEnt = const_cast<CDynamicEntity*>(rWeaponComponents[i]->GetDrawable());
if (pEnt)
{
pEnt->m_nDEflags.bUseExtendedBoundingBox = value;
}
}
}
}
}
void CPed::SetDynamicEntityFlagsForWeapon(bool value)
{
if (GetWeaponManager())
{
// When running IK, the weapon may not always render since visibility scanning uses the pre-IK position for the weapon.
// To workaround this, the bounding box for the weapon is stretched so that it never gets culled.
// Restricting this workaround to the local player for now.
if(IsLocalPlayer())
{
// Set the ped to always be rendered as they can be animated away from the mover when in cover
if (GetIsInCover() && IsFirstPersonShooterModeEnabledForPlayer(false))
{
m_nDEflags.bUseExtendedBoundingBox = true;
}
CObject* pEquippedWeaponObject = GetWeaponManager()->GetEquippedWeaponObject();
if(pEquippedWeaponObject)
{
SetDynamicEntityFlagsForWeapon(pEquippedWeaponObject, value);
}
if (GetPedIntelligence())
{
CTaskReloadGun* pTaskReloadGun = static_cast<CTaskReloadGun*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_RELOAD_GUN));
if (pTaskReloadGun)
{
CObject* pHeldObject = pTaskReloadGun->GetHeldObject();
if (pHeldObject)
{
pHeldObject->m_nDEflags.bUseExtendedBoundingBox = value;
}
}
// We need to explicitly set the flags for our weapon when doing a quick grenade throw, because this changes our equipped weapon
CTaskAimGunOnFoot* pAimGunOnFootTask = static_cast<CTaskAimGunOnFoot*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_ON_FOOT));
if (pAimGunOnFootTask)
{
CObject* pWeaponObject = pAimGunOnFootTask->GetWeaponObject();
if (pWeaponObject && pWeaponObject != pEquippedWeaponObject)
{
SetDynamicEntityFlagsForWeapon(pWeaponObject, value);
}
}
}
}
}
}
void CPed::ProcessFallCollision()
{
const float fFallHeight = m_fFallingHeight - GetTransform().GetPosition().GetZf();
// TODO: Tweak the sonar blip size depending on audio volume.
float fSonarBlipSize = fFallHeight;
static float s_fMaxBlipSize = 50.0f;
fSonarBlipSize = rage::Clamp(fSonarBlipSize, 0.0f, s_fMaxBlipSize);
}
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
void CPed::ProcessNMFootCollision(bool isLeft, phMaterialMgr::Id collisionMtlId, float mtlDepth)
#else
void CPed::ProcessNMFootCollision(bool isLeft, phMaterialMgr::Id collisionMtlId, float)
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
{
if (isLeft)
{
// left foot collision
if (GetPedConfigFlag( CPED_CONFIG_FLAG_PrevLeftFootCollNM ) || GetPedConfigFlag( CPED_CONFIG_FLAG_CurrLeftFootCollNM ))
{
// has had left foot collision this frame or last - don't process again
SetPedConfigFlag( CPED_CONFIG_FLAG_CurrLeftFootCollNM, true );
return;
}
SetPedConfigFlag( CPED_CONFIG_FLAG_CurrLeftFootCollNM, true );
}
if (!isLeft)
{
// right foot collision
if (GetPedConfigFlag( CPED_CONFIG_FLAG_PrevRightFootCollNM ) || GetPedConfigFlag( CPED_CONFIG_FLAG_CurrRightFootCollNM ))
{
// has had left foot collision this frame or last - don't process again
SetPedConfigFlag( CPED_CONFIG_FLAG_CurrRightFootCollNM, true );
return;
}
SetPedConfigFlag( CPED_CONFIG_FLAG_CurrRightFootCollNM, true );
}
m_PackedGroundMaterialId = collisionMtlId;
SetStairFlags(NULL, NULL, 0, m_PackedGroundMaterialId);
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
m_secondSurfaceDepth = mtlDepth;
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
// GetPedVfx()->ProcessVfxFootStep(isLeft);
// sound effects
//bool disableFootAudio = false;
//if(GetPedIntelligence()->GetQueriableInterface2()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SIMPLE_JUMP_IN_AIR) ||
// GetPedIntelligence()->GetQueriableInterface2()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SIMPLE_CLIMB) ||
// GetPedIntelligence()->GetQueriableInterface2()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SIMPLE_JUMP_LAUNCH) ||
// GetPedIntelligence()->GetQueriableInterface2()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SIMPLE_JUMP_LAND) ||
// GetPedIntelligence()->GetQueriableInterface2()->IsTaskCurrentlyRunning(CTaskTypes::TASK_MELEE) || // melee
// GetPedIntelligence()->GetQueriableInterface2()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_CONTROL))
//{
// // cheap hack to stop footstep sounds being triggered when jumping
// disableFootAudio = true;
//}
//if(!disableFootAudio)
//{
// if (isLeft)
// {
// if(!g_UseProceduralFootsteps)
// m_PedAudioEntity.PlayFootstepEvent(AUD_FOOTSTEP_WALK_L);
// }
// else
// {
// if(!g_UseProceduralFootsteps)
// m_PedAudioEntity.PlayFootstepEvent(AUD_FOOTSTEP_WALK_R);
// }
//}
}
//////////////////////////////////////////////////////////////////////////////
// Name : CreateDeadPedMoney
// Purpose : When a ped dies he leaves some money and/or health snacks on the map.
//////////////////////////////////////////////////////////////////////////////
void CPed::CreateDeadPedPickups()
{
Assert(!IsNetworkClone());
// the local player never drops stuff in SP
if (IsAPlayerPed() && !NetworkInterface::IsGameInProgress())
{
return;
}
// B*6340485: Disable these pickups in CNC mode.
if (NetworkInterface::IsInCopsAndCrooks())
{
return;
}
bool bCreateMoney = (m_MoneyCarried >= 10); // don't bother for small amounts
bool bCreateHealthSnack = (ms_ProbabilityPedsWillDropHealthSnacks > 0.0f && ms_HealthInSnackCarriedByAllNewPeds > 0) && !GetPedConfigFlag(CPED_CONFIG_FLAG_BlockDroppingHealthSnacksOnDeath);
Vector3 pedPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
if (bCreateMoney && !GetPedConfigFlag( CPED_CONFIG_FLAG_MoneyHasBeenGivenByScript )) // If the money has been awarded by script it needs to be created no matter what the localisation settings.
{
if (!CLocalisation::StealFromDeadPed())
{
bCreateMoney = false;
}
// Some peds don't carry money. We don't want to encourage violence now.
if (GetPedType() == PEDTYPE_COP)
{
bCreateMoney = false;
}
}
// Mission peds don't carry money. That would look a bit silly
if (bCreateMoney && PopTypeIsMission() && !GetPedConfigFlag( CPED_CONFIG_FLAG_MoneyHasBeenGivenByScript ))
{
bCreateMoney = false;
}
// Peds in cars have their pickups burnt with the car
if (this->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
{
bCreateMoney = false;
bCreateHealthSnack = false;
}
// Are random peds not to drop money
if (bCreateMoney && !GetRandomPedsDropMoney() && !IsAPlayerPed() && !PopTypeIsMission())
{
bCreateMoney = false;
}
// Don't let ambient swat/cops/army create money
if (bCreateMoney && PopTypeIsRandom() && IsLawEnforcementPed())
{
bCreateMoney = false;
}
// don't let the local player drop health snacks
if (IsAPlayerPed())
{
bCreateHealthSnack = false;
}
if (bCreateMoney)
{
const u32 stackSize = (m_MoneyCarried > 40) ? 100 : 20; // Random peds only have only a little money so make sure the drop a couple of stacks for peds with more make sure we dont get loads.
u32 maxNumPickups = MIN(1 + m_MoneyCarried/stackSize, 8);
// in MP only one money pickup is created
if (NetworkInterface::IsGameInProgress())
{
maxNumPickups = 1;
}
CPickupManager::CreateSomeMoney(pedPos, m_MoneyCarried, maxNumPickups, fwModelId::MI_INVALID, this);
m_MoneyCarried = 0; // Just to make sure.
}
// peds can also drop a health snack pickup. This is specified by script.
if (bCreateHealthSnack)
{
float probabilityOfSnack = fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
if (probabilityOfSnack <= ms_ProbabilityPedsWillDropHealthSnacks)
{
CPickupManager::CPickupPositionInfo pos( pedPos );
pos.m_MinDist = 1.5f;
pos.m_MaxDist = 1.55f;
CPickupManager::CalculateDroppedPickupPosition( pos, true );
if (pos.m_bOnGround)
{
Matrix34 pickupM;
pickupM.Identity();
pickupM.d = pos.m_Pos;
pickupM.d.z += 0.05f;
CPickup* pPickup = CPickupManager::CreatePickup(PICKUP_HEALTH_SNACK, pickupM, NULL, true);
if (pPickup)
{
pPickup->SetAmount(ms_HealthInSnackCarriedByAllNewPeds);
pPickup->SetPlaceOnGround();
if (PopTypeIsMission() || (GetNetworkObject() && GetNetworkObject()->IsGlobalFlagSet(CNetObjGame::GLOBALFLAG_WAS_SCRIPTOBJECT)))
{
pPickup->SetDroppedByPed();
}
}
}
}
}
}
//#define PEDANIM_FOOTDOWNFRAME (2.0f/30.0f)
//
//
// Name : PlayFootSteps
// Purpose : play footstep samples
//
void CPed::PlayFootSteps()
{
if( GetPedResetFlag(CPED_RESET_FLAG_IsVaulting) )
{
return;
}
if ( GetPedVfx() )
{
GetPedVfx()->UpdateFootWetness();
}
m_PedAudioEntity.GetFootStepAudio().DoFootstepStealth();
}// end of CPed::PlayFootSteps()...
//
// GetLocalDirection: Returns the local direction. If vector is coming from the back, front,left,right
// etc(0 = front, 1 = left, 2 = back, 3 = right
//
s32 CPed::GetLocalDirection(const Vector2& dir)
{
const float theta = rage::Atan2f(-dir.x, dir.y);
float angle = theta - m_MotionData.GetCurrentHeading();
angle += PI/4.0f;
while(angle < 0)
angle += 2.0f*PI;
s32 quadrant = s32(angle / (PI/2.0f));
while(quadrant > 3)
quadrant -= 4;
return quadrant;
}
// name: CPed::UpdatePortalTracker
// description: ensure that this ped is tracked into and through any interiors (and interior collisions
// are loaded etc.)
void CPed::UpdatePortalTracker()
{
// Keep track of whether we switched using the entity position to a ragdoll based position or vice versa
bool bPortalTrackerUsingRagdollorRagdollFrame = false;
// By default use the entity position for portal tracking
Vector3 vPosition(VEC3V_TO_VECTOR3(GetTransform().GetPosition()));
// If we are in active ragdoll or the static ragdoll frame (i.e. settled and dead)
// Use the spine 0 bound position as it is guaranteed to be within the ragdoll bounds the entity
// position can be offset and go under floors etc making the ped leave the room and go invisible
if ((GetRagdollInst() && GetRagdollInst()->GetCacheEntry()) &&
(GetUsingRagdoll() || (GetMovePed().GetState()==CMovePed::kStateStaticFrame)) )
{
phBoundComposite* pBound = GetRagdollInst()->GetCacheEntry()->GetBound();
if(pBound && pBound->GetNumBounds() > 0)
{
bPortalTrackerUsingRagdollorRagdollFrame = true;
Mat34V boundMat;
Transform(boundMat, GetRagdollInst()->GetMatrix(), pBound->GetCurrentMatrix(RAGDOLL_SPINE0));
vPosition = VEC3V_TO_VECTOR3(boundMat.GetCol3());
}
}
// Update the portal tracker using our calculated position
GetPortalTracker()->Update(vPosition);
// If we switched from using the entity position to using the ragdoll spine0 position or vice versa
// force a portal tracker rescan
if (m_bPortalTrackerUsingRagdollorRagdolFrame != bPortalTrackerUsingRagdollorRagdollFrame)
{
if (NetworkInterface::IsGameInProgress() && GetInteriorLocation().IsValid() && IsPlayer())
{
GetPortalTracker()->ScanUntilProbeTrue();
}
else
{
GetPortalTracker()->RequestRescanNextUpdate();
}
}
m_bPortalTrackerUsingRagdollorRagdolFrame = bPortalTrackerUsingRagdollorRagdollFrame;
}
//
//
// Name : SetupMissionState
// Purpose : Converts a random entity to a script entity.
//
void CPed::SetupMissionState()
{
CPhysical::SetupMissionState();
SetDefaultDecisionMaker();
SetCharParamsBasedOnManagerType();
GetPedIntelligence()->SetDefaultRelationshipGroup();
m_pPedIntelligence->GetCombatBehaviour().SetTargetLossResponse(CCombatData::TLR_NeverLoseTarget);
m_pPedIntelligence->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanCommandeerVehicles);
m_pPedIntelligence->GetCombatBehaviour().ClearFlag(CCombatData::BF_UseEnemyAccuracyScaling);
m_pPedIntelligence->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanCharge);
m_pPedIntelligence->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanThrowSmokeGrenade);
m_pPedIntelligence->GetCombatBehaviour().SetFlag(CCombatData::BF_DisableSeekDueToLineOfSight);
// Set charge timers to mission defaults
// These should match the corresponding "init" values in //depot/gta5/src/dev/game/Peds/CombatInfo.psc
const float kAttribFloatTriggerChargeTime_Far_DEFAULT = 6.0f;
const float kAttribFloatTriggerChargeTime_Near_DEFAULT = 2.25f;
m_pPedIntelligence->GetCombatBehaviour().SetCombatFloat(kAttribFloatTriggerChargeTime_Far, kAttribFloatTriggerChargeTime_Far_DEFAULT);
m_pPedIntelligence->GetCombatBehaviour().SetCombatFloat(kAttribFloatTriggerChargeTime_Near, kAttribFloatTriggerChargeTime_Near_DEFAULT);
//Set up the flee behavior.
m_pPedIntelligence->GetFleeBehaviour().GetFleeFlags().SetFlag(CFleeBehaviour::BF_DisableCower);
if(IsSecurityPed())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_CanAttackNonWantedPlayerAsLaw, true);
}
SetPedConfigFlag( CPED_CONFIG_FLAG_AllowMedicsToReviveMe, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_DontStoreAsPersistent, true );
SetPedConfigFlag( CPED_CONFIG_FLAG_CanActivateRagdollWhenVehicleUpsideDown, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_DisableTalkTo, true );
SetCanPlayInCarIdles(false);
GetPedIntelligence()->AddTaskDefault(ComputeDefaultTask(*this));
// Init ped health to default value
float fHealthLost = GetHealthLost();
static dev_float DEFAULT_HEALTH = 200.f;
SetMaxHealth(DEFAULT_HEALTH);
if(fHealthLost < SMALL_FLOAT)
{
SetHealth(DEFAULT_HEALTH);
#if RSG_PC
// We dont' need real links for all the peds; just the local player
if(IsLocalPlayer())
{
for(int i = 0; i < fwRandom::GetRandomNumberInRange(MIN_NUM_SYSOBF_LINKS, MAX_NUM_SYSOBF_LINKS); i++)
{
m_fHealth.AddLink(rage_new sysLinkedData<float, ReportHealthMismatch>(DEFAULT_HEALTH));
}
}
#endif //RSG_PC
}
if(IsAPlayerPed())
{
}
else
{
//Nasty hack to keep mission peds at 60% accuracy (we dropped the default significantly).
GetPedIntelligence()->GetCombatBehaviour().SetCombatFloat(kAttribFloatWeaponAccuracy, 0.6f);
}
}
//
//
// Name : CleanupMissionState
// Purpose : Converts a script entity to a random entity.
//
void CPed::CleanupMissionState()
{
SetPedConfigFlag( CPED_CONFIG_FLAG_CanActivateRagdollWhenVehicleUpsideDown, true );
SetPedConfigFlag( CPED_CONFIG_FLAG_DisablePedAvoidance, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_DisableTalkTo, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_DisableEventInteriorStatusCheck, false );
DeleteVehicleEntryConfig();
// Reset water flags
if(!GetPedIntelligence() || GetPedIntelligence()->FindTaskActiveMotionByType(CTaskTypes::TASK_ON_FOOT_FISH) == NULL)
{
SetPedConfigFlag( CPED_CONFIG_FLAG_DrownsInWater, true );
SetPedConfigFlag( CPED_CONFIG_FLAG_DiesInstantlyWhenSwimming, false );
ResetWaterTimers();
}
if (IsAPlayerPed())
{
//
// Player Ped Cleanup (mjc - here or CPlayerInfo)
//
SetAutoConversationLookAts(false);
if (GetInventory())
{
GetInventory()->GetAmmoRepository().SetUsingInfiniteClips(false);
}
SetPedConfigFlag( CPED_CONFIG_FLAG_UseKinematicModeWhenStationary, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_NotAllowedToJackAnyPlayers, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_ForcedAim, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_DrownsInWater, true);
SetPedConfigFlag( CPED_CONFIG_FLAG_ForcedToStayInCover, false);
SetPedConfigFlag( CPED_CONFIG_FLAG_ForcedToStayInCoverDueToPlayerSwitch, false);
SetPedConfigFlag( CPED_CONFIG_FLAG_AllowLockonToFriendlyPlayers, false );
//SetPedConfigFlag( CPED_CONFIG_FLAG_DisableLockonToRandomPeds, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_BlockWeaponSwitching, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_WillFlyThroughWindscreen, true );
SetPedConfigFlag( CPED_CONFIG_FLAG_ForceDirectEntry, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_WillTakeDamageWhenVehicleCrashes, true );
SetPedConfigFlag( CPED_CONFIG_FLAG_WaitForDirectEntryPointToBeFreeWhenExiting, false );
eRagdollBlockingFlagsBitSet set = GetRagdollBlockingFlags();
ClearRagdollBlockingFlags(set);
#if __DEV
scriptDisplayf("Ped %s set to fly through windscreen at frame %i, via code CPed::CleanupMissionState", GetDebugName() ? GetDebugName() : "NULL", fwTimer::GetFrameCount());
scrThread::PrePrintStackTrace();
#endif // __DEV
if (m_PedConfigFlags.GetCantBeKnockedOffVehicle() != KNOCKOFFVEHICLE_DEFAULT)
{
ped_commands::SetPedCanBeKnockedOffVehicle(this, KNOCKOFFVEHICLE_DEFAULT);
}
if( GetHelmetComponent() )
{
GetHelmetComponent()->SetPropFlag(PV_FLAG_NONE);
}
// Reset the player relationship group (don't do in network game)
if(!NetworkInterface::IsGameInProgress())
{
const CRelationshipGroup* pRelationshipGroup = GetPedIntelligence()->GetRelationshipGroup();
if(pRelationshipGroup && pRelationshipGroup->GetType() == RT_mission && pRelationshipGroup != CRelationshipManager::s_pPlayerGroup)
{
GetPedIntelligence()->SetDefaultRelationshipGroup();
}
}
// Set the player's group back to its default values
int PlayersGroup = GetPlayerInfo()->GetPlayerDataPlayerGroup();
CPedGroups::ms_groups[PlayersGroup].GetGroupMembership()->SetMaxSeparation(CPedGroupMembership::ms_fPlayerGroupMaxSeparation);
g_ScriptAudioEntity.StopPedSpeaking(this, false);
GetSpeechAudioEntity()->SetPedIsBlindRaging(false);
if (AssertVerify(m_pPlayerInfo))
{
m_pPlayerInfo->m_fForceAirDragMult = 0.0f;
m_pPlayerInfo->bCanDoDriveBy = true;
m_pPlayerInfo->bCanUseCover = true;
m_pPlayerInfo->bEnableControlOnDeath = true;
m_pPlayerInfo->GetWanted().m_DontDispatchCopsForThisPlayer = false;
m_pPlayerInfo->GetWanted().m_fMultiplier = 1.0f;
m_pPlayerInfo->GetWanted().m_Overrides.Reset();
}
// If the player is using a mobile phone then end the phonecall now
CTask *pTaskPrimary = GetPedIntelligence()->FindTaskPrimaryByType(CTaskTypes::TASK_COMPLEX_USE_MOBILE_PHONE);
if(pTaskPrimary && pTaskPrimary->GetTaskType()==CTaskTypes::TASK_COMPLEX_USE_MOBILE_PHONE)
{
CTaskComplexUseMobilePhone *pTaskPhone=(CTaskComplexUseMobilePhone*)pTaskPrimary;
pTaskPhone->Stop(this);
}
// Clear script and combat action mode. Battle awareness is reset as this can bleed into post mission as it's timing
// based.
SetUsingActionMode(false, AME_Script);
SetUsingActionMode(false, AME_Combat);
SetMovementModeOverrideHash(0);
GetPedIntelligence()->ResetBattleAwareness();
}
else
{
//
// Standard ped cleanup
//
CPhysical::CleanupMissionState();
SetDefaultDecisionMaker();
SetCharParamsBasedOnManagerType();
if(!GetPedConfigFlag( CPED_CONFIG_FLAG_KeepRelationshipGroupAfterCleanUp ))
{
GetPedIntelligence()->SetDefaultRelationshipGroup();
}
if(!GetPedConfigFlag( CPED_CONFIG_FLAG_KeepTargetLossResponseOnCleanup ))
{
m_pPedIntelligence->GetCombatBehaviour().SetTargetLossResponse(CCombatData::TLR_ExitTask);
}
//Set up the combat behavior.
m_pPedIntelligence->GetCombatBehaviour().ClearFlag(CCombatData::BF_DisableSeekDueToLineOfSight);
//Set up the flee behavior.
m_pPedIntelligence->GetFleeBehaviour().GetFleeFlags().ClearFlag(CFleeBehaviour::BF_DisableCower);
//Restore the default weapon accuracy and damage.
float fDefaultCombatFloat = 0.0f;
if(CCombatBehaviour::GetDefaultCombatFloat(*this, kAttribFloatWeaponAccuracy, fDefaultCombatFloat))
{
GetPedIntelligence()->GetCombatBehaviour().SetCombatFloat(kAttribFloatWeaponAccuracy, fDefaultCombatFloat);
}
if(CCombatBehaviour::GetDefaultCombatFloat(*this, kAttribFloatWeaponDamageModifier, fDefaultCombatFloat))
{
GetPedIntelligence()->GetCombatBehaviour().SetCombatFloat(kAttribFloatWeaponDamageModifier, fDefaultCombatFloat);
}
SetPedConfigFlag( CPED_CONFIG_FLAG_ForcePedLoadCover, false );
SetAutoConversationLookAts(false);
if(!GetPedConfigFlag( CPED_CONFIG_FLAG_KeepTasksAfterCleanUp ))
{
CPedGroup *pPedGroup = GetPedsGroup();
if (pPedGroup)
{
if (pPedGroup->GetGroupMembership()->IsFollower(this) && pPedGroup->IsLocallyControlled()) // Not sure about the group leader
{
pPedGroup->GetGroupMembership()->RemoveMember(this);
}
}
}
TUNE_GROUP_BOOL(CAR_AI, EnableDontResetIfOnRecording, true);
const bool bIsInRecordingPlaybackVehicle = m_pMyVehicle && CVehicleRecordingMgr::IsPlaybackGoingOnForCar(m_pMyVehicle);
if (!ShouldBeDead() && !IsNetworkClone() && (!bIsInRecordingPlaybackVehicle || !EnableDontResetIfOnRecording))
{
GetPedIntelligence()->AddTaskDefault(ComputeDefaultTask(*this));
bool bTellPedToWander = true;
if(!GetPedConfigFlag( CPED_CONFIG_FLAG_KeepTasksAfterCleanUp ))
{
if(GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle && !m_pMyVehicle->IsNetworkClone())
{
if(this == m_pMyVehicle->GetSeatManager()->GetDriver())
{
bTellPedToWander = false;
if (m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_HELI)
{
// Fly high up into the sky
CHeli *pHeli = (CHeli *)GetMyVehicle();
Vector3 vTarget;
vTarget.x = WORLDLIMITS_XMAX - 1.0f;
vTarget.y = WORLDLIMITS_YMIN + 1.0f;//-10000.0f;
vTarget.z = WORLDLIMITS_ZMAX - 1.0f;//10000.0f;
sVehicleMissionParams params;
params.SetTargetPosition(vTarget);
CTask *pTask = rage_new CTaskVehicleGoToHelicopter( params,
0,
-1.0f,
1000);
pHeli->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
}
else if (m_pMyVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE)
{
// Fly high up into the sky
CPlane *pPlane = (CPlane *)GetMyVehicle();
Vector3 vTarget;
vTarget.x = WORLDLIMITS_XMAX - 1.0f;
vTarget.y = WORLDLIMITS_YMAX - 1.0f;//10000.0f;
vTarget.z = rage::Min(1000.0f, WORLDLIMITS_ZMAX - 1.0f);
sVehicleMissionParams params;
params.SetTargetPosition(vTarget);
params.m_fCruiseSpeed = pPlane->pHandling->m_fEstimatedMaxFlatVel * .9f;
CTask *pTask = rage_new CTaskVehicleGoToPlane( params,
1000,
1000);
pPlane->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
}
else if (m_pMyVehicle->InheritsFromAutomobile() || m_pMyVehicle->InheritsFromBike())
{
// do nothing, drive away normally
sVehicleMissionParams params;
params.m_fCruiseSpeed = 8.0f;
CTask *pTask = CVehicleIntelligence::CreateCruiseTask(*m_pMyVehicle, params);
m_pMyVehicle->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
}
}
}
#if ENABLE_HORSE
if (m_pSeatManager && m_pSeatManager->GetDriver()!=NULL) //already have a driver, don't wander.
bTellPedToWander=false;
#endif
if (bTellPedToWander && !IsInjured())
{
if(GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle)
{
bool bAlreadyAdded=false;
if(!bAlreadyAdded)
{
CEvent* pEvent=GetPedIntelligence()->GetEventOfType(EVENT_SCRIPT_COMMAND);
if(pEvent)
{
CEventScriptCommand* pEventScript=(CEventScriptCommand*)pEvent;
aiTask* pTask=pEventScript->GetTask();
if(pTask && pTask->GetTaskType()==CTaskTypes::TASK_USE_SEQUENCE)
{
bAlreadyAdded=true;
}
}
}
if(!bAlreadyAdded)
{
CTask* pTask=GetPedIntelligence()->GetTaskAtPriority(PED_TASK_PRIORITY_PRIMARY);
if(pTask && pTask->GetTaskType()==CTaskTypes::TASK_USE_SEQUENCE)
{
bAlreadyAdded=true;
}
}
if(!bAlreadyAdded)
{
CTaskSequenceList* pTaskSequence=rage_new CTaskSequenceList();
CTask* pTaskWander=ComputeWanderTask(*this);
if(pTaskWander && pTaskWander->GetTaskType() != CTaskTypes::TASK_POLICE)
{
CTaskLeaveAnyCar* pTaskLeaveAnyCar=rage_new CTaskLeaveAnyCar();
pTaskSequence->AddTask(pTaskLeaveAnyCar);
}
pTaskSequence->AddTask(pTaskWander);
CEventScriptCommand event(PED_TASK_PRIORITY_PRIMARY,rage_new CTaskUseSequence(*pTaskSequence));
GetPedIntelligence()->AddEvent(event);
}
}
else
{
bool bAlreadyAdded=false;
if(!bAlreadyAdded)
{
CEvent* pEvent=GetPedIntelligence()->GetEventOfType(EVENT_SCRIPT_COMMAND);
if(pEvent)
{
CEventScriptCommand* pEventScript=(CEventScriptCommand*)pEvent;
aiTask* pTask=pEventScript->GetTask();
if(pTask && pTask->GetTaskType()==CTaskTypes::TASK_WANDER)
{
bAlreadyAdded=true;
}
}
}
if(!bAlreadyAdded)
{
CTask* pTask=GetPedIntelligence()->GetTaskAtPriority(PED_TASK_PRIORITY_PRIMARY);
if(pTask && pTask->GetTaskType()==CTaskTypes::TASK_WANDER)
{
bAlreadyAdded=true;
}
}
if(!bAlreadyAdded)
{
CTask* pTaskWander=ComputeWanderTask(*this);
CEventScriptCommand event(PED_TASK_PRIORITY_PRIMARY,pTaskWander);
GetPedIntelligence()->AddEvent(event);
}
}
}
GetPedIntelligence()->BuildQueriableState();
}
}
}
m_PedConfigFlags.SetPedLegIkMode( CIkManager::GetDefaultLegIkMode(this) );
}
//
// IsPedInControl: Returns if ped is control of it own movement
//
bool CPed::IsPedInControl()
{
if(GetPedConfigFlag( CPED_CONFIG_FLAG_IsInTheAir ) ||
IsDead() ||
(IsAPlayerPed() && GetIsArrested()))
return false;
if (IsNetworkClone())
return false;
return true;
}
// Name : CanBeDeleted
// Purpose : Checks whether the ped can be removed from the map or converted
// into a dummy ped. Currently, Mission-created peds cannot be
// removed or converted, but randomly-created peds can be.
// Parameters : flag to specify whether peds in cars can be deleted or not
// Returns : TRUE if the ped can be deleted or converted
bool CPed::CanBeDeleted(bool bPedsInCarsCanBeDeleted, bool bDoNetworkChecks, bool bMissionPedsCanBeDeleted) const
{
if (bDoNetworkChecks)
{
if (IsNetworkClone()) // this object is being controlled by a remote machine
{
return FALSE;
}
if (GetNetworkObject() && !GetNetworkObject()->CanDelete())
{
return FALSE;
}
}
if(!bMissionPedsCanBeDeleted && !PopTypeIsRandomNonPermanent())
{
return FALSE;
}
if (GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && !bPedsInCarsCanBeDeleted)
{
return FALSE;
}
// Peds in the players' gang should not be deleted.
if(FindPlayerPed() && CPedGroups::ms_groups[FindPlayerPed()->GetPlayerInfo()->GetPlayerDataPlayerGroup()].GetGroupMembership()->IsFollower(this))
{
return FALSE;
}
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedIsInReusePool))
{
return FALSE; // Don't delete peds in the reuse pool, that is handled in CPedPopulation::UpdateReusePool()
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : UpdatePedCountTracking
// PURPOSE : This makes sure the the population ped counts are properly
// updated in case the ped gets into or out of vehicles, etc.
// PARAMETERS : None
// RETURNS : Nothing
/////////////////////////////////////////////////////////////////////////////////
void CPed::UpdatePedCountTracking()
{
// As an optimization, rather than always removing the ped from both lists
// (if in them) and then re-adding, check if we are already in the right one.
if(GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle))
{
if(IsInInVehPedCount())
{
// We are already in the vehicle list, shouldn't have to do something.
// It should hopefully be impossible for us to also be in the on-foot list,
// so we just check that with an assert.
Assert(!IsInOnFootPedCount());
return;
}
}
else
{
if(IsInOnFootPedCount())
{
// We are already in the on-foot list, shouldn't have to do something.
// It should hopefully be impossible for us to also be in the vehicle list,
// so we just check that with an assert.
Assert(!IsInInVehPedCount());
return;
}
}
// removes ped from onFoot or inVeh list
CPedPopulation::RemovePedFromPopulationCount(this);
// adds ped to the appropriate list, depending on whether it is in a vehicle
CPedPopulation::AddPedToPopulationCount(this);
}
extern float MAX_DISTANCE_PED_SHADOWS_SQR;
//
void CPed::PreRenderShaders()
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
if (pModelInfo && pModelInfo->GetIsStreamedGfx())
{
const CPedStreamRenderGfx* pGfx = GetPedDrawHandler().GetConstPedRenderGfx();
if (pGfx && pGfx->m_pShaderEffect){
pGfx->m_pShaderEffect->Update(this);
}
/*
#if __BANK
g_wrinkleWeights[0]=g_wrinkleStrength[0];
g_wrinkleWeights[1]=g_wrinkleStrength[1];
g_wrinkleWeights[2]=g_wrinkleStrength[2];
g_wrinkleWeights[3]=g_wrinkleStrength[3];
g_wrinkleWeights[4]=g_wrinkleStrength[4];
g_wrinkleWeights[5]=g_wrinkleStrength[5];
#endif // __BANK
*/
}
}
void CPed::UpdateRagdollBoundsFromAnimatedSkel(bool updateBounds)
{
#if LAZY_RAGDOLL_BOUNDS_UPDATE
if (!IsRagdollBoundsUpdateRequested() && !GetUsingRagdoll())
{
SetRagdollBoundsUpToDate(false);
if(GetRagdollState()==RAGDOLL_STATE_ANIM_PRESETUP)
{
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
SetRagdollState(RAGDOLL_STATE_ANIM, true);
}
}
else if(GetPedAiLod().ShouldUpdateAnimsThisFrame() && GetRagdollInst() && GetRagdollInst()->IsInLevel() && !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()))
#else
if(GetPedAiLod().ShouldUpdateAnimsThisFrame() && GetRagdollInst() && GetRagdollInst()->IsInLevel() && !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()))
#endif
{
#if __ASSERT
if (GetRagdollState()==RAGDOLL_STATE_PHYS || GetRagdollState()==RAGDOLL_STATE_ANIM_DRIVEN)
{
SpewRagdollTaskInfo();
Assertf(GetRagdollState()!=RAGDOLL_STATE_PHYS && GetRagdollState()!=RAGDOLL_STATE_ANIM_DRIVEN, "Ped ragdoll state is out of sync with the current inst in UpdateRagdollBoundsFromAnimatedSkel()" );
}
#endif //__ASSERT
// // If close to viewport, set the LOD high
// static float lim = 5.0f;
// if(CPlayerInfo::ms_cachedMainPlayerPos.Dist(GetPosition()) < lim || GetDangerTimer())
// GetRagdollInst()->SetCurrentPhysicsLOD(fragInst::HIGH);
// else
// GetRagdollInst()->SetCurrentPhysicsLOD(fragInst::LOW);
//}
// If a human is selected, send the pose to bSpy
#if ART_ENABLE_BSPY && NM_ANIM_MATRICES
if (GetRagdollInst() && GetRagdollInst()->IsInLevel() && !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()) &&
GetRagdollInst()->GetType()->GetARTAssetID() >= 0)
{
CPed* pFocusPed = NULL;
for(int i = 0; i < CDebugScene::FOCUS_ENTITIES_MAX && !pFocusPed; ++i)
{
CEntity* pEnt = CDebugScene::FocusEntities_Get(i);
if( pEnt && pEnt->GetIsTypePed() )
pFocusPed = static_cast<CPed*>(pEnt);
}
if (pFocusPed && pFocusPed == this)
{
// Check that this ped has an articulated body to send in
phArticulatedBody *body = NULL;
if (GetRagdollInst()->GetCacheEntry())
{
if (!(GetRagdollInst()->GetCacheEntry()->GetHierInst() && GetRagdollInst()->GetCacheEntry()->GetHierInst()->body))
{
GetRagdollInst()->GetCacheEntry()->LazyArticulatedInit();
}
body = GetRagdollInst()->GetCacheEntry()->GetHierInst()->body;
}
GetRagdollInst()->SetComponentTMsFromSkeleton(*GetSkeleton(), ART::kITSourceAnimation, body);
}
}
#endif
if(updateBounds)
{
GetRagdollInst()->PoseBoundsFromSkeleton(true, !GetPedResetFlag(CPED_RESET_FLAG_SetLastBoundMatricesDone));
}
else
{
GetRagdollInst()->GrabRootFrame();
}
SetPedResetFlag(CPED_RESET_FLAG_SetLastBoundMatricesDone, true);
if(GetRagdollState()==RAGDOLL_STATE_ANIM_PRESETUP)
{
if(updateBounds)
{
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
}
SetRagdollState(RAGDOLL_STATE_ANIM, true);
}
#if LAZY_RAGDOLL_BOUNDS_UPDATE
// If bounds weren't updated last frame, set the last bounds mats to the new
if (!AreRagdollBoundsUpToDate())
{
fragCacheEntry* entry = GetRagdollInst()->GetCacheEntry();
if (entry)
{
entry->GetBound()->UpdateLastMatricesFromCurrent();
}
}
// Decrement bounds update frames and indicate that bounds are up to date
m_bRagdollBoundsUpdateRequestFrames--;
SetRagdollBoundsUpToDate(true);
#endif
#if __ASSERT
static bool bAssertedAlready = false;
if (!bAssertedAlready REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()))
{
phBoundComposite* pRagdollBound = (phBoundComposite*)GetRagdollInst()->GetArchetype()->GetBound();
for(int iBound=0; iBound<pRagdollBound->GetNumBounds(); iBound++)
{
Vec3V boundPos = pRagdollBound->GetCurrentMatrix(iBound).GetCol3();
Vector3 boundOffset = RCC_VECTOR3(boundPos) - GetObjectMtx(0).d;
if(boundOffset.Mag2() > 20.0f*20.0f)
{
bAssertedAlready = true;
Printf("\n****************** Begin debug spew for CPed::UpdateRagdollBoundsFromAnimatedSkel() ******************\n");
Printf("Animated Inst Matrix:");
Printf(" %f %f %f %f", GetAnimatedInst()->GetMatrix().GetCol0().GetXf(), GetAnimatedInst()->GetMatrix().GetCol1().GetXf(), GetAnimatedInst()->GetMatrix().GetCol2().GetXf(), GetAnimatedInst()->GetMatrix().GetCol3().GetXf());
Printf(" %f %f %f %f", GetAnimatedInst()->GetMatrix().GetCol0().GetYf(), GetAnimatedInst()->GetMatrix().GetCol1().GetYf(), GetAnimatedInst()->GetMatrix().GetCol2().GetYf(), GetAnimatedInst()->GetMatrix().GetCol3().GetYf());
Printf(" %f %f %f %f", GetAnimatedInst()->GetMatrix().GetCol0().GetZf(), GetAnimatedInst()->GetMatrix().GetCol1().GetZf(), GetAnimatedInst()->GetMatrix().GetCol2().GetZf(), GetAnimatedInst()->GetMatrix().GetCol3().GetZf());
Printf("Ragdoll Inst Matrix:");
Printf(" %f %f %f %f", GetRagdollInst()->GetMatrix().GetCol0().GetXf(), GetRagdollInst()->GetMatrix().GetCol1().GetXf(), GetRagdollInst()->GetMatrix().GetCol2().GetXf(), GetRagdollInst()->GetMatrix().GetCol3().GetXf());
Printf(" %f %f %f %f", GetRagdollInst()->GetMatrix().GetCol0().GetYf(), GetRagdollInst()->GetMatrix().GetCol1().GetYf(), GetRagdollInst()->GetMatrix().GetCol2().GetYf(), GetRagdollInst()->GetMatrix().GetCol3().GetYf());
Printf(" %f %f %f %f", GetRagdollInst()->GetMatrix().GetCol0().GetZf(), GetRagdollInst()->GetMatrix().GetCol1().GetZf(), GetRagdollInst()->GetMatrix().GetCol2().GetZf(), GetRagdollInst()->GetMatrix().GetCol3().GetZf());
Printf("Entity Transform:");
Printf(" %f %f %f %f", GetTransform().GetMatrix().GetCol0().GetXf(), GetTransform().GetMatrix().GetCol1().GetXf(), GetTransform().GetMatrix().GetCol2().GetXf(), GetTransform().GetMatrix().GetCol3().GetXf());
Printf(" %f %f %f %f", GetTransform().GetMatrix().GetCol0().GetYf(), GetTransform().GetMatrix().GetCol1().GetYf(), GetTransform().GetMatrix().GetCol2().GetYf(), GetTransform().GetMatrix().GetCol3().GetYf());
Printf(" %f %f %f %f", GetTransform().GetMatrix().GetCol0().GetZf(), GetTransform().GetMatrix().GetCol1().GetZf(), GetTransform().GetMatrix().GetCol2().GetZf(), GetTransform().GetMatrix().GetCol3().GetZf());
for (int ibone = 0; ibone < GetSkeleton()->GetBoneCount(); ibone++)
{
Vector3 vObjectOffset = GetObjectMtx(ibone).d - GetObjectMtx(0).d;
Vector3 vLocalOffset = GetLocalMtx(ibone).d;
Printf("Offset between root bone position and %s bone = %f,%f,%f (locals %f,%f,%f)\n", GetSkeleton()->GetSkeletonData().GetBoneData(ibone)->GetName(), vObjectOffset.x, vObjectOffset.y, vObjectOffset.z, vLocalOffset.x, vLocalOffset.y, vLocalOffset.z);
}
Printf("\n****************** SpewRagdollTaskInfo ******************\n");
SpewRagdollTaskInfo();
Assertf(false, "%s ragdoll component %d offset too big (%f,%f,%f)", GetModelName(), iBound, pRagdollBound->GetCurrentMatrix(iBound).GetCol3().GetXf(), pRagdollBound->GetCurrentMatrix(iBound).GetCol3().GetYf(), pRagdollBound->GetCurrentMatrix(iBound).GetCol3().GetZf());
Printf("\n****************** End debug spew for CPed::UpdateRagdollBoundsFromAnimatedSkel() ******************\n");
break;
}
}
}
#endif
}
}
void CPed::UpdateRagdollRootTransformFromAnimatedSkel()
{
// Moved into UpdateEntityFromPhysics() - but might not always get called there
// Moved this code to PreRender2 so that the physics inst move is done at the same point as the UpdateObjectLocationAndRadius().
// If it isn't done here then physics level complains because all the peds have been moved but the physics level hasn't been
// told about it.
if(GetRagdollInst() && GetRagdollInst()->IsInLevel() && !CPhysics::GetLevel()->IsActive(GetRagdollInst()->GetLevelIndex()))
{
if(!GetPedResetFlag( CPED_RESET_FLAG_SetLastMatrixDone ))
{
PHSIM->SetLastInstanceMatrix(GetRagdollInst(), GetRagdollInst()->GetMatrix());
SetPedResetFlag( CPED_RESET_FLAG_SetLastMatrixDone, true );
}
GetRagdollInst()->SetMatrixNoZeroAssert(GetMatrix());
}
}
CDynamicEntity* CPed::GetProcessControlOrderParent() const
{
if(CTaskParachute::IsPedDependentOnParachute(*this))
{
return CTaskParachute::GetParachuteForPed(*this);
}
return NULL;
}
u32 CPed::GetStartAnimSceneUpdateFlag() const
{
if(CTaskParachute::IsPedDependentOnParachute(*this))
{
return CGameWorld::SU_START_ANIM_UPDATE_PRE_PHYSICS_PASS2;
}
return CPhysical::GetStartAnimSceneUpdateFlag();
}
void CPed::SetDamageSetID(u8 ID)
{
m_damageSetID = ID;
// In CGameWorld::ProcessAfterPreRender(), weapons are processed after the peds. I
// if a weapons adds damage that causes this peds damage to be recycled, we need to
// update it customShaderEffect data or it will render crap for one frame
if (ID==kInvalidPedDamageSet)
UpdateDamageCustomShaderVars();
}
void CPed::UpdateDamageCustomShaderVars()
{
if (const CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo()))
{
CCustomShaderEffectPed * pCustomShaderPedEffect = NULL;
if (pModelInfo->GetIsStreamedGfx())
{
if (const CPedStreamRenderGfx* pGfx = GetPedDrawHandler().GetConstPedRenderGfx())
pCustomShaderPedEffect = pGfx->m_pShaderEffect;
}
else
{
pCustomShaderPedEffect = static_cast<CCustomShaderEffectPed*>(GetDrawHandler().GetShaderEffect());
}
if (pCustomShaderPedEffect)
pCustomShaderPedEffect->UpdatePedDamage(this);
}
}
void CPed::UpdateDamagePriority(bool bIsVisibleInMainViewport)
{
bool bIsVis = bIsVisibleInMainViewport;
if (!bIsVis && !GetCanUseCachedVisibilityThisFrame()) // can't count of bIsVisibleInMainViewport after a camera cut., if it's false, make sure it really is not vis
{
float radius = GetBoundRadius();
Vector3 centre; GetBoundCentre(centre);
bIsVis = camInterface::IsSphereVisibleInGameViewport(centre, radius);
}
CPedDamageManager::GetInstance().UpdatePriority(m_damageSetID,camInterface::GetPos().Dist(VEC3V_TO_VECTOR3(GetTransform().GetPosition())), bIsVis, IsDead());
}
void CPed::ReleaseDamageSet()
{
if (m_damageSetID != kInvalidPedDamageSet) // we have a damage target associated with us, we need to release it.
CPedDamageManager::GetInstance().ReleaseDamageSet(m_damageSetID);
if (m_compressedDamageSetID != kInvalidPedDamageSet)
CPedDamageManager::GetInstance().ReleaseCompressedDamageSet(m_compressedDamageSetID);
}
void CPed::ClearDamage()
{
if (m_damageSetID != kInvalidPedDamageSet)
{
CPedDamageManager::GetInstance().ClearPedBlood(m_damageSetID);
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, kDamageZoneInvalid, ATSTRINGHASH("ArmorBullet",0x5e3ff621)); // armour bullets are decals that are really damage
if (NetworkInterface::IsGameInProgress() && GetNetworkObject() && !IsNetworkClone())
{
CNetObjPed* pNetObjPed = static_cast<CNetObjPed*>(GetNetworkObject());
if (pNetObjPed)
pNetObjPed->OnPedClearDamage();
}
}
m_dirtColScaleCustom = 0.0f;
}
void CPed::ClearDamageAndScars()
{
if (m_damageSetID != kInvalidPedDamageSet)
{
CPedDamageManager::GetInstance().ClearPedBlood(m_damageSetID);
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, kDamageZoneInvalid, ATSTRINGHASH("ArmorBullet",0x5e3ff621)); // armour bullets are decals that are really damage
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, kDamageZoneInvalid, ATSTRINGHASH("scar",0xda0a88e8));
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, kDamageZoneInvalid, ATSTRINGHASH("bruise",0xe838afb1));
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, kDamageZoneInvalid, ATSTRINGHASH("bruise_large",0xa1db9d43));
if (NetworkInterface::IsGameInProgress() && GetNetworkObject() && !IsNetworkClone())
{
CNetObjPed* pNetObjPed = static_cast<CNetObjPed*>(GetNetworkObject());
if (pNetObjPed)
pNetObjPed->OnPedClearDamage();
}
}
m_dirtColScaleCustom = 0.0f;
}
void CPed::ClearDamage(int zone)
{
if (m_damageSetID != kInvalidPedDamageSet)
{
CPedDamageManager::GetInstance().ClearPedBlood(m_damageSetID,(ePedDamageZones)zone);
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, (ePedDamageZones)zone, ATSTRINGHASH("ArmorBullet",0x5e3ff621)); // armour bullets are decals that are really damage
}
}
void CPed::HideBloodDamage(int zone, bool enable)
{
if (m_damageSetID != kInvalidPedDamageSet)
{
CPedDamageManager::GetInstance().LimitZoneBlood(m_damageSetID, (ePedDamageZones)zone, (enable) ? 0 : -1); // 'hide' is just a 0 limit
if (enable)
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, (ePedDamageZones)zone, ATSTRINGHASH("ArmorBullet",0x5e3ff621)); // armour bullets are decals that are really damage
}
}
void CPed::LimitBloodDamage(int zone, int limit)
{
if (m_damageSetID != kInvalidPedDamageSet)
{
CPedDamageManager::GetInstance().LimitZoneBlood(m_damageSetID, (ePedDamageZones)zone, limit);
if (limit==0) // we don't have a limit for the damage decals, but if they are limiting to 0 we can kill them all
CPedDamageManager::GetInstance().ClearDamageDecals(m_damageSetID, (ePedDamageZones)zone, ATSTRINGHASH("ArmorBullet",0x5e3ff621));// armour bullets are decals that are really damage
}
}
void CPed::SetBloodDamageClearInfo(float redIntensity, float alphaIntensity)
{
if (m_damageSetID != kInvalidPedDamageSet)
CPedDamageManager::GetInstance().SetPedBloodClearInfo(m_damageSetID,redIntensity,alphaIntensity);
}
void CPed::ClearDecorations()
{
if (m_damageSetID != kInvalidPedDamageSet)
CPedDamageManager::GetInstance().ClearPedDecorations(m_damageSetID);
if (m_compressedDamageSetID != kInvalidPedDamageSet)
CPedDamageManager::GetInstance().ClearCompressedPedDecorations(m_compressedDamageSetID);
#if GTA_REPLAY
if(ReplayPedExtension::HasExtension(this))
{
ReplayPedExtension::ClearPedDecorationData(this);
}
#endif //GTA_REPLAY
}
void CPed::PromoteBloodToScars()
{
if (m_damageSetID != kInvalidPedDamageSet)
{
if (NetworkInterface::IsGameInProgress())
{
if (IsPlayer() && IsNetworkClone())
{
CNetObjPlayer* pNetObjPlayer = static_cast<CNetObjPlayer*>(GetNetworkObject());
if (pNetObjPlayer && pNetObjPlayer->HasRemoteScarData())
{
float u = 0.f, v = 0.f, scale = 0.f;
pNetObjPlayer->GetRemoteScarData(u,v,scale);
CPedDamageManager::GetInstance().PromoteBloodToScars(m_damageSetID, true, u, v, scale);
return;
}
}
}
CPedDamageManager::GetInstance().PromoteBloodToScars(m_damageSetID);
}
}
void CPed::ClearCustomDirt()
{
// modify the ped dirt color based on the popzone values
Color32 dirtCol = CPopCycle::GetCurrentZoneDirtCol();
if (dirtCol.GetColor() != 0)
{
// modify dirt tint based on weather modifier
float dirtWeatherMod = g_timeCycle.GetStaleDirtModifier();
m_dirtColRedf = dirtCol.GetRedf() * dirtWeatherMod;
m_dirtColGreenf = dirtCol.GetGreenf() * dirtWeatherMod;
m_dirtColBluef = dirtCol.GetBluef() * dirtWeatherMod;
m_dirtColAlphaf = dirtCol.GetAlphaf() * dirtWeatherMod;
}
m_dirtColScale = CPopCycle::GetCurrentZonePedDirtMax();
m_dirtColScaleCustom = 0.0f;
}
void CPed::CloneDamage(const CPed * source, bool bCloneCompressedDamage)
{
CPedDamageManager::GetInstance().ClonePedDamage(this, source, bCloneCompressedDamage);
}
void CPed::CalculateDynamicAmbientScalesWithVehicleScaler()
{
if (GetUseDynamicAmbientScale())
{
// Darken peds if in a car.
float incaredNess = 0.0f;
bool checkRoof = false;
bool outsideVehicle = false;
int attachState = GetAttachState();
CVehicle *veh = GetMyVehicle();
if (attachState != ATTACH_STATE_NONE)
{
if (veh)
{
s32 iMySeat = -1;
if (attachState == ATTACH_STATE_PED_ENTER_CAR)
{
CTaskEnterVehicle *enterVehTask = (CTaskEnterVehicle*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE);
if (enterVehTask)
{
iMySeat = enterVehTask->GetTargetSeat();
}
}
else
{
iMySeat = veh->GetSeatManager()->GetPedsSeatIndex(this);
}
if (veh->IsSeatIndexValid(iMySeat))
{
const CVehicleSeatAnimInfo* pSeatAnimInfo = veh->GetSeatAnimationInfo(iMySeat);
bool bIsAATrailer = MI_TRAILER_TRAILERSMALL2.IsValid() && veh->GetModelIndex() == MI_TRAILER_TRAILERSMALL2;
if (pSeatAnimInfo && (pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle() || pSeatAnimInfo->IsStandingTurretSeat() || bIsAATrailer))
{
outsideVehicle = true;
}
}
}
}
if( attachState == ATTACH_STATE_PED_IN_CAR )
{
if( veh )
{ // Attached to a non existent vehicle ?
VehicleType vehType = veh->GetVehicleType();
bool hasRoof = !veh->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_NO_ROOF);
if( vehType == VEHICLE_TYPE_CAR && hasRoof && !outsideVehicle)
{
incaredNess = 1.0f;
checkRoof = true;
}
}
}
else if ( attachState == ATTACH_STATE_PED_ENTER_CAR )
{
bool enteringVeh = GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE);
CTaskEnterVehicle *cloneEnterVehTask = NULL;
if(IsNetworkClone() && !enteringVeh)
{
cloneEnterVehTask = (CTaskEnterVehicle*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE);
enteringVeh = cloneEnterVehTask != NULL;
}
if( enteringVeh )
{
CTaskVehicleFSM *task = NULL;
CTaskEnterVehicle *enterVehTask = cloneEnterVehTask?cloneEnterVehTask:(CTaskEnterVehicle*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE);
CTaskVehicleFSM *cloneEnterVehSeatTask = NULL;
if(IsNetworkClone())
{
cloneEnterVehSeatTask = (CTaskVehicleFSM *)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE_SEAT);
}
if( enterVehTask->IsConsideredGettingInVehicleForAmbientScale() )
{
task = enterVehTask;
}
else if( GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE_SEAT) || cloneEnterVehSeatTask!=NULL)
{
task = cloneEnterVehSeatTask?cloneEnterVehSeatTask:(CTaskVehicleFSM *)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE_SEAT);
}
if( task )
{
veh = task->GetVehicle();
VehicleType vehType = veh->GetVehicleType();
bool hasRoof = !veh->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_NO_ROOF);
int entryPoint = task->GetTargetEntryPoint();
if( vehType == VEHICLE_TYPE_CAR && hasRoof && !outsideVehicle && entryPoint != -1 )
{
Quaternion seatOrientation(0.0f,0.0f,0.0f,1.0f);
Vector3 startPos(Vector3::ZeroType);
Vector3 endPos(Vector3::ZeroType);
Vec3V pedPos = GetTransform().GetPosition();
CModelSeatInfo::CalculateEntrySituation(veh, this, startPos, seatOrientation, entryPoint);
CModelSeatInfo::CalculateSeatSituation(veh, endPos, seatOrientation, entryPoint);
const float mag2ToGo = (endPos - startPos).Mag2();
const float mag2Done = (VEC3V_TO_VECTOR3(pedPos) - startPos).Mag2();
const float ratio = Clamp(mag2Done / mag2ToGo,0.0f,1.0f);
incaredNess = ratio;
checkRoof = true;
}
}
}
}
else if ( attachState == ATTACH_STATE_PED_EXIT_CAR )
{
if( veh )
{
VehicleType vehType = veh->GetVehicleType();
bool hasRoof = !veh->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_NO_ROOF);
if( vehType == VEHICLE_TYPE_CAR && hasRoof && !outsideVehicle)
{
bool exitingVeh = GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE_SEAT);
CTaskExitVehicleSeat *cloneExitVehSeatTask = NULL;
if(IsNetworkClone() && !exitingVeh)
{
cloneExitVehSeatTask = (CTaskExitVehicleSeat*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE_SEAT);
exitingVeh = cloneExitVehSeatTask!=NULL;
}
if( exitingVeh && hasRoof)
{
float phase = 0.0f;
CTaskExitVehicleSeat *task = cloneExitVehSeatTask?cloneExitVehSeatTask:(CTaskExitVehicleSeat*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE_SEAT);
if (task)
{
task->GetClipAndPhaseForState(phase);
}
incaredNess = 1.0f - phase;
checkRoof = true;
}
}
}
}
else if (CTaskSynchronizedScene *pTaskSync = (CTaskSynchronizedScene*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_SYNCHRONIZED_SCENE))
{
incaredNess = pTaskSync->GetVehicleLightingScalar();
}
else if (CTaskCutScene *pTaskCuts = (CTaskCutScene*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_CUTSCENE))
{
incaredNess = pTaskCuts->GetVehicleLightingScalar();
}
if(checkRoof)
{ // Take Roof into account.
if( veh && veh->DoesVehicleHaveAConvertibleRoofAnimation() )
{
incaredNess *= 1.0f - rage::Clamp(veh->GetConvertibleRoofProgress(),0.0f,1.0f);
}
}
Vector2 ambScale(0.0f, 0.0f);
// If the "incaredness" is a full 1.0, then we can assume the ped is fully inside the car,
// and simply inherit the vehicle's ambient scales. We'll play it safe and only do this for
// peds that are set to AL_LodRagdollInVehicle, which should be peds in vehicles >= 30m
// from the camera.
bool inheritAmbientScalesFromVehicle = (veh != NULL) && (incaredNess == 1.0f) && GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodRagdollInVehicle);
if(inheritAmbientScalesFromVehicle)
{
ambScale.Set(veh->GetNaturalAmbientScale() / 255.0f, veh->GetArtificialAmbientScale() / 255.0f);
ambScale *= Lerp(incaredNess, 1.0f, gPedInCarAmbientScale);
Assertf(ambScale.x >= 0.0f && ambScale.x <= 1.0f,"Dynamic Natural Ambient is out of range (%f, should be between 0.0f and 1.0f)",ambScale.x);
Assertf(ambScale.y >= 0.0f && ambScale.y <= 1.0f,"Dynamic Artificial Ambient is out of range (%f, should be between 0.0f and 1.0f)",ambScale.y);
SetNaturalAmbientScale((ambScale.x == 1.0f) ? 254 : u8(ambScale.x * 255.0f));
SetArtificialAmbientScale(u8(ambScale.y * 255.0f));
}
else
{
CTimeCycleAsyncQueryMgr::AddPed(this, incaredNess);
}
}
}
void CPed::CalculateDynamicAmbientScalesWithVehicleScaler(float incaredNess)
{
// Stolen from CEntity::CalculateDynamicAmbientScales()
fwInteriorLocation interiorLocation = this->GetInteriorLocation();
Vec2V vAmbientScale = g_timeCycle.CalcAmbientScale(GetTransform().GetPosition(), interiorLocation);
Vector2 ambScale = RCC_VECTOR2(vAmbientScale);
ambScale *= Lerp(incaredNess, 1.0f, gPedInCarAmbientScale);
Assertf(ambScale.x >= 0.0f && ambScale.x <= 1.0f,"Dynamic Natural Ambient is out of range (%f, should be between 0.0f and 1.0f)",ambScale.x);
Assertf(ambScale.y >= 0.0f && ambScale.y <= 1.0f,"Dynamic Artificial Ambient is out of range (%f, should be between 0.0f and 1.0f)",ambScale.y);
SetNaturalAmbientScale((ambScale.x == 1.0f) ? 254 : u8(ambScale.x * 255.0f));
SetArtificialAmbientScale(u8(ambScale.y * 255.0f));
}
float CPed::GetCarInsideAmount( const CEntity*& vehicle) const
{
float incaredNess=0.f;
int attachState = GetAttachState();
CVehicle *veh = GetMyVehicle();
vehicle = NULL;
if( attachState == ATTACH_STATE_PED_IN_CAR )
{
if( veh )
{ // Attached to a non existent vehicle ?
VehicleType vehType = veh->GetVehicleType();
if( vehType == VEHICLE_TYPE_CAR){
incaredNess = 1.0f;
vehicle = veh;
}
}
}
else if ( attachState == ATTACH_STATE_PED_ENTER_CAR )
{
bool enteringVeh = GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE);
if( enteringVeh )
{
CTaskVehicleFSM *task = NULL;
CTaskEnterVehicle *enterVehTask = (CTaskEnterVehicle*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE);
if( enterVehTask->IsConsideredGettingInVehicleForAmbientScale() )
{
task = enterVehTask;
}
else if( GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE_SEAT) )
{
task = (CTaskVehicleFSM *)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE_SEAT);
}
if( task )
{
veh = task->GetVehicle();
VehicleType vehType = veh->GetVehicleType();
int entryPoint = task->GetTargetEntryPoint();
if( vehType == VEHICLE_TYPE_CAR && entryPoint != -1 )
{
Quaternion seatOrientation(0.0f,0.0f,0.0f,1.0f);
Vector3 startPos(Vector3::ZeroType);
Vector3 endPos(Vector3::ZeroType);
Vec3V pedPos = GetTransform().GetPosition();
CModelSeatInfo::CalculateEntrySituation(veh, this, startPos, seatOrientation, entryPoint);
CModelSeatInfo::CalculateSeatSituation(veh, endPos, seatOrientation, entryPoint);
const float mag2ToGo = (endPos - startPos).Mag2();
const float mag2Done = (VEC3V_TO_VECTOR3(pedPos) - startPos).Mag2();
const float ratio = Clamp(mag2Done / mag2ToGo,0.0f,1.0f);
incaredNess = ratio;
vehicle = veh;
}
}
}
}
else if ( attachState == ATTACH_STATE_PED_EXIT_CAR )
{
VehicleType vehType = veh->GetVehicleType();
if( vehType == VEHICLE_TYPE_CAR)
{
bool exitingVeh = GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE_SEAT);
if( exitingVeh)
{
float phase = 0.0f;
CTaskExitVehicleSeat *task = (CTaskExitVehicleSeat*)GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE_SEAT);
if (task)
{
task->GetClipAndPhaseForState(phase);
}
incaredNess = 1.0f - phase;
}
vehicle = veh;
}
}
return incaredNess;
}
#if FPS_MODE_SUPPORTED
void CPed::UpdateFpsCameraRelativeMatrix()
{
bool bHasFetchedCameraMatrix = false;
bool bHasRelativeMatrix = false;
Matrix34 camRelativeMatrix(M34_IDENTITY);
const camBaseCamera* pCam = camInterface::GetDominantRenderedCamera();
const bool bIncludeExplosions = GetIsInVehicle();
if (pCam && IsLocalPlayer() && IsFirstPersonCameraOrFirstPersonSniper() && !camInterface::IsCameraShaking(bIncludeExplosions))
{
TUNE_GROUP_BOOL(FPS_CAM_RELATIVE, bForceUseBlendedCameras, false);
if(GetPedResetFlag(CPED_RESET_FLAG_UseBlendedCamerasOnUpdateFpsCameraRelativeMatrix) || bForceUseBlendedCameras)
{
bHasFetchedCameraMatrix = camInterface::GetObjectSpaceCameraMatrix(this, camRelativeMatrix);
}
else
{
bHasFetchedCameraMatrix = camInterface::GetRenderedCameras().GetCount()==1 && pCam->GetObjectSpaceCameraMatrix(this, camRelativeMatrix);
}
}
if(bHasFetchedCameraMatrix)
{
fwCameraRelativeExtension* pExtension = fwCameraRelativeExtension::GetOrAddExtension(*this);
if (pExtension)
{
pExtension->m_cameraOffset = RC_MAT34V(camRelativeMatrix);
bHasRelativeMatrix = true;
}
}
if (!bHasRelativeMatrix)
{
fwCameraRelativeExtension* pExtension = GetExtension<fwCameraRelativeExtension>();
if (pExtension)
{
fwCameraRelativeExtension::RemoveExtension(*this);
}
}
}
#endif // FPS_MODE_SUPPORTED
ePrerenderStatus CPed::PreRender(const bool bIsVisibleInMainViewport)
{
Assert(!m_PreRenderLock);
ASSERT_ONLY(m_PreRenderLock = true;)
DEV_BREAK_IF_FOCUS( CDebugScene::ShouldDebugBreakOnPreRenderOfFocusEntity(), this );
UpdateLodState();
if (fwTimer::IsGamePaused())
{
if (m_damageSetID!=kInvalidPedDamageSet)
UpdateDamagePriority(bIsVisibleInMainViewport); // make sure this is updated, in case damage is changed during pause
#if GTA_REPLAY
if(CReplayMgr::IsReplayInControlOfWorld() && bIsVisibleInMainViewport)
{
CalculateDynamicAmbientScalesWithVehicleScaler();
}
#endif // GTA_REPLAY
ASSERT_ONLY(m_PreRenderLock = false;)
return PRERENDER_DONE;
}
if(bIsVisibleInMainViewport)
{
CalculateDynamicAmbientScalesWithVehicleScaler();
}
// If we just left cover (through CTaskInCover::CleanUp()), we may need to cancel
// left hand grip IK. Doing it here rather than directly from CTaskInCover::CleanUp()
// means that we should avoid stalling on accessing the bone matrices of the weapon,
// and it happens right before we call ProcessPreRender2() on the tasks, to avoid glitches
// in case any task wants to enable it again.
if(GetPedResetFlag(CPED_RESET_FLAG_CancelLeftHandGripIk))
{
// Use the current Ik blend out settings
float fOverrideBlendOutDuration = -1.f;
CArmIkSolver* pArmSolver = static_cast<CArmIkSolver*>(GetIkManager().GetSolver(IKManagerSolverTypes::ikSolverTypeArm));
if(pArmSolver && (pArmSolver->GetArmActive(CArmIkSolver::LEFT_ARM) || (pArmSolver->GetArmBlend(CArmIkSolver::LEFT_ARM) > 0.0f)))
{
float fSolverBlendOutDuration = pArmSolver->GetBlendOutRate(CArmIkSolver::LEFT_ARM);
if(fSolverBlendOutDuration > 0.001f)
{
fOverrideBlendOutDuration = 1.f/fSolverBlendOutDuration;
}
ProcessLeftHandGripIk(false, false, -1.f, fOverrideBlendOutDuration);
}
else
{
SetPedResetFlag(CPED_RESET_FLAG_CancelLeftHandGripIk, false);
}
// Should have been cleared in ProcessLeftHandGripIk().
Assert(!GetPedResetFlag(CPED_RESET_FLAG_CancelLeftHandGripIk));
}
if(GetPedResetFlag( CPED_RESET_FLAG_ProcessPreRender2 ))
{
// This gets called after the skeleton update, but before any Ik
GetPedIntelligence()->ProcessPreRender2();
}
#if FPS_MODE_SUPPORTED
const bool bNeedFpsUpdatePass = IsFirstPersonShooterModeEnabledForPlayer(false, false, false, true, GetExtension<fwCameraRelativeExtension>()==NULL);
#endif // FPS_MODE_SUPPORTED
if(GetPedAiLod().ShouldUpdateAnimsThisFrame())
{
#if FPS_MODE_SUPPORTED
TUNE_GROUP_BOOL(FPS_CAM_RELATIVE, bUpdateCameraRelativeExtensionPreRender, true);
if(bUpdateCameraRelativeExtensionPreRender)
{
UpdateFpsCameraRelativeMatrix();
}
if (bNeedFpsUpdatePass)
{
GetIkManager().CacheInputFrameForFpsUpdatePass(*this);
}
#endif // FPS_MODE_SUPPORTED
REPLAY_ONLY( if( CReplayMgr::IsReplayInControlOfWorld() == false) )
{
GetIkManager().PreIkUpdate(fwTimer::GetTimeStep());
}
}
if (m_damageSetID!=kInvalidPedDamageSet)
UpdateDamagePriority(bIsVisibleInMainViewport);
#if __DEV
ApplyBoneOverrides();
#endif //__DEV
#if FPS_MODE_SUPPORTED
if (bNeedFpsUpdatePass)
{
crSkeleton* pSkeleton = GetSkeleton();
if (pSkeleton)
{
camInterface::ApplyPedBoneOverridesForFirstPerson(*this, *pSkeleton);
}
}
#endif // FPS_MODE_SUPPORTED
#if GTA_REPLAY
bool UpdateAnim = true;
if(CReplayMgr::IsReplayInControlOfWorld())
{
UpdateAnim = false;
}
if(UpdateAnim)
#endif // GTA_REPLAY
{
if (GetAnimDirector()->IsUpdated())
{
crmtMotionTree* parent = NULL;
if(m_pMyVehicle ENABLE_FRAG_OPTIMIZATION_ONLY(&& m_pMyVehicle->GetHasFragCacheEntry()))
{
fwAnimDirectorComponentMotionTree* comp = static_cast<fwAnimDirectorComponentMotionTree*>(m_pMyVehicle->GetAnimDirector()->GetComponentByTypeAndPhase(fwAnimDirectorComponent::kComponentTypeMotionTree, fwAnimDirectorComponent::kPhasePreRender));
if(comp)
{
parent = &comp->GetMotionTree();
}
}
GetAnimDirector()->StartUpdatePreRender(fwTimer::GetTimeStep(), parent);
}
}
// modify the ped dirt color based on the popzone values
Color32 dirtCol = CPopCycle::GetCurrentZoneDirtCol();
float dirtScale = CPopCycle::GetCurrentZonePedDirtMax();
if (dirtCol.GetColor() != 0)
{
// modify dirt tint based on weather modifier
float colorInterpRate = 0.001f;
float dirtWeatherMod = g_timeCycle.GetStaleDirtModifier();
float newRed = dirtCol.GetRedf() * dirtWeatherMod;
float newGreen = dirtCol.GetGreenf() * dirtWeatherMod;
float newBlue = dirtCol.GetBluef() * dirtWeatherMod;
float newAlpha = dirtCol.GetAlphaf() * dirtWeatherMod;
// possibly add blood color to env effect layer if dead
if( IsDead() )
{
newRed = m_dirtColScaleCustom * 0.235f; // ~60/255 red
newGreen = 0.0f;
newBlue = 0.0f;
colorInterpRate = 0.03f;
}
m_dirtColRedf = Lerp(colorInterpRate, m_dirtColRedf, newRed);
m_dirtColGreenf = Lerp(colorInterpRate, m_dirtColGreenf, newGreen);
m_dirtColBluef = Lerp(colorInterpRate, m_dirtColBluef, newBlue);
m_dirtColAlphaf = Lerp(colorInterpRate, m_dirtColAlphaf, newAlpha);
if (dirtScale < 0.0f)
dirtScale = 1.0f;
}
else
{
dirtScale = 0.0f;
}
// Add on custom dirt and clamp
dirtScale += Clamp(m_dirtColScaleCustom, 0.0f, 0.5f); // limit custom to 0.5
dirtScale = Clamp(dirtScale, 0.0f, 1.0f); // limit total to 1.0
m_dirtColScale = Lerp(0.01f, m_dirtColScale, dirtScale);
// Sweatscale
if( IsPlayer() )
{
// Calculate the perceived heat, using Australian Apparent Temperature,
// which factors in temperature, humidity, and wind
float heat = g_weather.GetTemperature(GetTransform().GetPosition());
float wind = g_weather.GetWindSpeed();
float humidity = 0.7f; // assuming 70% humidity for LA, until tracked in Weather system
float vaporPressureFactor = humidity * 6.105f * exp((17.27f*heat)/(237.7f+heat));
float apparentTemperature = heat + 0.33f*vaporPressureFactor - 0.7f * wind - 4.0f;
// Normalize between no-sweat and full-sweat temperatures and apply
// These could be customized per-character if necessary
const float ROOM_TEMPERATURE = 15.0f;
const float FULL_SWEAT_TEMP = 30.0f;
float sweatiness = Clamp((apparentTemperature - ROOM_TEMPERATURE)/(FULL_SWEAT_TEMP - ROOM_TEMPERATURE), 0.0f, 1.0f);
if( GetMotionData()->GetIsSprinting() )
{
const float SPRINT_SWEAT_INCREASE = 0.5f;
sweatiness = Min(sweatiness + SPRINT_SWEAT_INCREASE, 1.0f);
}
const float INV_SWEAT_ADJUST_TIME = 1.0f/20.0f;
Approach(m_sweatScale, sweatiness, INV_SWEAT_ADJUST_TIME, fwTimer::GetTimeStep());
}
#if STENCIL_VEHICLE_INTERIOR
//if this ped is inside a vehicle and we're using interior cam in this vehicle, make sure it sets up the stencil bit while rendering
SetUseVehicleInteriorMaterial(CVfxHelper::IsEntityInCurrentVehicle(this) BANK_ONLY(&& CVehicle::ms_StencilOutInterior));
#endif // STENCIL_VEHICLE_INTERIOR
m_PedFootStepHelper.UpdateAudioLOD();
if (!IsLocalPlayer())
{
m_nFlags.bAddtoMotionBlurMask = false;
if (GetIsInVehicle())
{
CVehicle* pVehicle = GetVehiclePedInside();
if (pVehicle->ContainsLocalPlayer())
{
m_nFlags.bAddtoMotionBlurMask = true;
}
}
}
ASSERT_ONLY(m_PreRenderLock = false;)
#if FPS_MODE_SUPPORTED
if (IsFirstPersonShooterModeEnabledForPlayer(false, false, false, true, GetExtension<fwCameraRelativeExtension>()==NULL))
{
return PRERENDER_NEED_SECOND_PASS;
}
#endif // FPS_MODE_SUPPORTED
return PRERENDER_DONE;
}
#define USE_LIGHT_ON_PHONE 1
void CPed::PostPreRender()
{
//! if we updated anims this frame, unset PRDF_DontRenderUntilNextAnimUpdate.
if(m_uRenderDelayFlags.IsFlagSet(PRDF_DontRenderUntilNextAnimUpdate))
{
fwAnimDirector* pAnimDirector = GetAnimDirector();
if(pAnimDirector && pAnimDirector->HasBeenUpdatedThisFrame())
{
ClearRenderDelayFlag(PRDF_DontRenderUntilNextAnimUpdate);
}
}
//! If we have render delay flags set, extend don't render period.
if(m_uRenderDelayFlags.GetAllFlags() > 0)
{
#if __ASSERT
if(m_nDEflags.bFrozen)
{
m_uRenderFlagFrameCount=fwTimer::GetFrameCount(); //reset frame count if frozen as we are probably not going to be able to reset flags in this case.
}
Assertf( (fwTimer::GetFrameCount() - m_uRenderFlagFrameCount) < 150, "Ped delaying initial invisibility for more than 150 frames! This is probably bad");
#endif
SetPedResetFlag(CPED_RESET_FLAG_DontRenderThisFrame, true);
}
if (fwTimer::IsGamePaused())
{
//Check with the debug recorder to see if we need to apply a stored motion tree event to this entity
DR_ONLY(debugPlayback::SetVisualSkeletonIfPossible( this ));
UpdateDamageCustomShaderVars(); // we can skip most of the code below, but you can get some weird results if someone messes with ped damage during pause.
return;
}
const CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
Assert(pModelInfo);
// Update visibility flags for this ped;
CPedAILodManager::UpdatePedVisibility(this);
#if __DEV
ApplyBoneOverrides();
#endif //__DEV
if(GetPedAiLod().ShouldUpdateAnimsThisFrame())
{
GetIkManager().PostIkUpdate(fwTimer::GetTimeStep());
}
//Check with the debug recorder to see if we need to apply a stored motion tree event to this entity
DR_ONLY(debugPlayback::SetVisualSkeletonIfPossible( this ));
UpdateRagdollRootTransformFromAnimatedSkel();
bool UpdateBounds = !ENABLE_PRERENDER_BOUNDS_UPDATE;
#if GTA_REPLAY
if(CReplayMgr::IsReplayInControlOfWorld())
{
// If this ped is controlled by replay and it was visible when it was recorded
// then we need to update the ragdoll bounds since the animation director
// wasn't allowed to update!
ReplayPedExtension* pExtension = ReplayPedExtension::GetExtension(this);
if(pExtension)
{
UpdateBounds = pExtension->GetIsRecordedVisible();
}
if( !CReplayMgr::IsPlaying() )
UpdateBounds = true;
}
#endif // GTA_REPLAY
UpdateRagdollBoundsFromAnimatedSkel(UpdateBounds);
#if __BANK
if(CAnimViewer::m_pDynamicEntity == this)
{
CAnimViewer::OnDynamicEntityPreRender();
}
#endif // __BANK
// Cache the offsets of interesting bones
if (GetSkeleton())
{
m_PedFootStepHelper.Update();
RecomputeCachedBoneOffsets();
}
if (GetIsVisibleInSomeViewportThisFrame())
{
// process ped vfx (now that the skeleton data is all calculated for this frame)
if (GetPedVfx())
{
GetPedVfx()->ProcessVfxPed();
}
float dist = (VEC3V_TO_VECTOR3(GetTransform().GetPosition())-(camInterface::GetPos())).Mag();
float distfade = 1.0f-rage::Clamp((dist - gPedAmbientVolumeFadeStartSetting)/(gPedAmbientVolumeFadeEndSetting - gPedAmbientVolumeFadeStartSetting),0.0f,1.0f);
float fade=gPedAmbientVolumeMaxStrength*distfade;
if (fade>=0.01f && m_bRenderAoBlobs && !GetCapsuleInfo()->IsQuadruped())
{
Matrix34 leftFoot;
GetBoneMatrix(leftFoot, BONETAG_L_FOOT);
Matrix34 rightFoot;
GetBoneMatrix(rightFoot, BONETAG_R_FOOT);
Matrix34 leftCalf;
GetBoneMatrix(leftCalf, BONETAG_L_CALF);
Matrix34 rightCalf;
GetBoneMatrix(rightCalf, BONETAG_R_CALF);
const float distanceBetweenFeet = (leftFoot.d - rightFoot.d).Mag();
const float ellipseRadius = (leftFoot.d - leftCalf.d).Mag();
// Work out direction / position of foot so we can work out the fade
const float fadeL = fade * Saturate(leftFoot.b.z * 2.0f);
const float fadeR = fade * Saturate(rightFoot.b.z * 2.0f);
// Shift inwards
static dev_float pedAOOffset = 0.15f;
Vector3 adjustedLeftFootPos = leftFoot.d + leftFoot.c * distanceBetweenFeet * pedAOOffset;
Vector3 adjustedRightFootPos = rightFoot.d - rightFoot.c * distanceBetweenFeet * pedAOOffset;
if (fadeL >= 0.001f)
{
CLightSource volumeLeft(LIGHT_TYPE_AO_VOLUME, 0, adjustedLeftFootPos, Vector3(0,0,0), fadeL, LIGHT_ALWAYS_ON);
volumeLeft.SetDirTangent(leftFoot.b, leftFoot.a);
volumeLeft.SetAOVolume(0.25f, 0.25f, 0.25f);
volumeLeft.SetAOVolumeBaseIntensity(gPedAmbientVolumeBaseIntensity);
volumeLeft.SetRadius(ellipseRadius);
Lights::AddSceneLight(volumeLeft);
}
if (fadeR >= 0.001f)
{
CLightSource volumeRight(LIGHT_TYPE_AO_VOLUME, 0, adjustedRightFootPos, Vector3(0,0,0), fadeR, LIGHT_ALWAYS_ON);
volumeRight.SetDirTangent(leftFoot.b, leftFoot.a);
volumeRight.SetAOVolume(0.25f, 0.25f, 0.25f);
volumeRight.SetAOVolumeBaseIntensity(gPedAmbientVolumeBaseIntensity);
volumeRight.SetRadius(ellipseRadius);
Lights::AddSceneLight(volumeRight);
}
#if __BANK
if (ms_bShowAOVolumeDebug)
{
// Left
grcDebugDraw::Axis(leftFoot, ellipseRadius, true);
grcDebugDraw::Sphere(adjustedLeftFootPos, ellipseRadius, Color32(fadeL, fadeL, 0.0f, 1.0f), false, 1, 20);
// Right
grcDebugDraw::Axis(rightFoot, ellipseRadius, true);
grcDebugDraw::Sphere(adjustedRightFootPos, ellipseRadius, Color32(fadeR, fadeR, 0.0f, 1.0f), false, 1, 20);
}
#endif
}
if (m_pDrawHandler)
{
PreRenderShaders();
m_pDrawHandler->Update(this);
}
}//if (GetIsVisibleInSomeViewportThisFrame())..
RenderPedLight(false);
RenderPedFeetLights(m_bEnableFootLight);
if( GetWeaponManager() && GetWeaponManager()->GetEquippedWeaponObjectHash() == OBJECTTYPE_OBJECT &&
GetWeaponManager()->GetEquippedWeaponObject())
{
CWeaponItem *phone = GetInventory()->GetWeapon(OBJECTTYPE_OBJECT);
if( phone != NULL && phone->GetModelHash() == CPedPhoneComponent::GetPhoneModelHashSafe(GetPhoneComponent()))
{
// Update phone tint...
int phonePalIdx = GetPedPhonePaletteIdx();
if( phonePalIdx != 0xff )
{
CWeapon* weapon = GetWeaponManager()->GetEquippedWeapon();
Assert(GetPedPhonePaletteIdx() < (u8)~0);
weapon->UpdateShaderVariables((u8)GetPedPhonePaletteIdx());
}
const CEntity* entity = GetWeaponManager()->GetEquippedWeaponObject();
RenderLightOnPhone(entity);
}
}
if(GetPedResetFlag(CPED_RESET_FLAG_ProcessPostPreRender))
{
GetPedIntelligence()->ProcessPostPreRender();
}
GetPedIntelligence()->RemoveExpiredEvents();
m_PedResetFlags.ResetPostPreRender(this);
CPlayerInfo *playerinfo = GetPlayerInfo();
if( NetworkInterface::IsGameInProgress() &&
playerinfo && playerinfo->GetEnableBlueToothHeadset() &&
IsBaseFlagSet(fwEntity::IS_VISIBLE) &&
GetDrawHandlerPtr() != NULL
)
{
const CPedPropData& propData = GetPedDrawHandler().GetPropData();
for(int i=0;i<MAX_PROPS_PER_PED;i++)
{
const CPedPropData::SinglePropData& singlePropData = propData.GetPedPropData(i);
if (singlePropData.m_anchorID == ANCHOR_EARS && ( singlePropData.m_propModelHash == headSet0NameHash.GetHash() ||
singlePropData.m_propModelHash == headSet1NameHash.GetHash() ||
singlePropData.m_propModelHash == headSet2NameHash.GetHash() ))
{
s32 boneIdx = pModelInfo->GetVarInfo()->GetAnchorBoneIdx(ANCHOR_EARS);
if (boneIdx != -1)
{
Matrix34 genMat;
crSkeleton* pSkel = GetSkeleton();
Matrix34 boneMat;
pSkel->GetGlobalMtx(boneIdx, RC_MAT34V(boneMat));
Matrix34 offsetMat;
Vector3 posOffset = singlePropData.m_posOffset + headSetLightOffset;
Quaternion rotOffset = singlePropData.m_rotOffset;
offsetMat.Identity();
offsetMat.FromQuaternion(rotOffset);
offsetMat.d = posOffset;
offsetMat.Dot(boneMat);
genMat = offsetMat;
float intensity = 0;
if( NetworkInterface::GetVoice().IsGamerTalking(playerinfo->m_GamerInfo.GetGamerId()) )
{
float loudness = NetworkInterface::GetVoice().GetPlayerLoudness(playerinfo->m_GamerInfo.GetLocalIndex());
intensity = Lerp(loudness, 0.0f, headSetLightIntensity);
}
unsigned int flags = CORONA_DONT_REFLECT;
if( headSetLightHasDirection )
flags |= CORONA_HAS_DIRECTION;
if( headSetLightDontRenderFlare )
flags |= CORONA_DONT_RENDER_FLARE;
if( headSetLightFadeOffDirMult )
flags |= CORONA_FADEOFF_DIR_MULT;
Vector3 lightDir = -genMat.c;
g_coronas.Register( RCC_VEC3V(genMat.d),
headSetLightSize,
headSetLightCol,
headSetLightIntensity * intensity,
headSetLightZBias,
RCC_VEC3V(lightDir),
headSetLightDirViewThreshold,
headSetLightDirLightConeAngle,
headSetLightDirLightConeAngle,
(u8)(flags & 0xff));
break;
}
}
}
}
}
void CPed::PostPreRenderAfterAttachments()
{
if(GetPedResetFlag(CPED_RESET_FLAG_ProcessPostPreRenderAfterAttachments))
{
GetPedIntelligence()->ProcessPostPreRenderAfterAttachments();
}
}
bool CPed::ShouldFixIfNoCollisionLoadedAroundPosition()
{
return ((PopTypeIsMission() || GetPedConfigFlag(CPED_CONFIG_FLAG_ShouldFixIfNoCollision)) && !IsPlayer() && !GetPedResetFlag( CPED_RESET_FLAG_AllowUpdateIfNoCollisionLoaded )) && GetAllowFreezeWaitingOnCollision();
}
void CPed::OnFixIfNoCollisionLoadedAroundPosition(bool bFix)
{
if( bFix )
{
DeactivatePhysicsAndDealWithRagdoll();
}
else
{
Assert(GetCurrentPhysicsInst());
ActivatePhysics();
}
}
void CPed::RenderLightOnPhone(const CEntity* entity)
{
#if USE_LIGHT_ON_PHONE
if(Verifyf(entity, "Invalid phone entity!"))
{
CLightSource phoneLight = gPedPhoneLight;
//we want the flip of this as it points away from the screen
Vec3V phoneLightDir = -entity->GetTransform().GetForward();
//bias this forward slight to prevent it clipping through the back og the phone
Vec3V phoneLightPos = entity->GetTransform().GetPosition() + phoneLightDir * ScalarV(0.01f);
Vec3V phoneLightTan = entity->GetTransform().GetRight();
phoneLight.SetPosition(VEC3V_TO_VECTOR3(phoneLightPos));
phoneLight.SetDirTangent(VEC3V_TO_VECTOR3(phoneLightDir), VEC3V_TO_VECTOR3(phoneLightTan));
BANK_ONLY(DebugLighting::OverridePedPhoneLight(&phoneLight);)
//blend off the radius of the light if we are near to the head or
//pelvis to prevent the light casting through the body of the ped
Matrix34 globalMatrix;
Vector3 headBonePos;
GetGlobalMtx(GetBoneIndexFromBoneTag(BONETAG_HEAD), globalMatrix);
headBonePos = globalMatrix.GetVector(3);
Vector3 pelvisBonePos;
GetGlobalMtx(GetBoneIndexFromBoneTag(BONETAG_PELVIS), globalMatrix);
pelvisBonePos = globalMatrix.GetVector(3);
float distanceToHead = (VEC3V_TO_VECTOR3(phoneLightPos) - headBonePos).Mag();
float distanceToPelvis = (VEC3V_TO_VECTOR3(phoneLightPos) - pelvisBonePos).Mag();
float blendOffValue = distanceToPelvis;
if(distanceToHead < distanceToPelvis)
{
blendOffValue = distanceToHead;
}
phoneLight.SetRadius(phoneLight.GetRadius() * blendOffValue);
Lights::AddSceneLight(phoneLight);
#if GTA_REPLAY
if(CReplayMgr::ShouldRecord())
{
CReplayMgr::RecordFx<CPacketPhoneLight>(
CPacketPhoneLight(), this, entity);
}
#endif // GTA_REPLAY
}
#endif //USE_LIGHT_ON_PHONE
}
void CPed::RenderPedLight(bool forceOn)
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
Assert(pModelInfo);
static dev_bool sm_ligthEnabledTest = false;
bool bLightEnabled = false;
s32 boneIdx = -1;
bool bMpBone = false;
bool bOffsetLightPos = false;
Vector3 lightOffset(0.0f, 0.0f, 0.0f);
crSkeleton *pSkeleton = GetSkeleton();
if(pSkeleton)
{
// standard FX light:
if((pModelInfo->GetLightFxBoneIdx()!=0xffff) && (pModelInfo->GetLightFxBoneSwitchIdx()!=0xffff))
{
const s32 boneSwitchIdx = pModelInfo->GetLightFxBoneSwitchIdx();
if(boneSwitchIdx > -1)
{
Matrix34 mBoneSwitchMtx;
pSkeleton->GetGlobalMtx(boneSwitchIdx, RC_MAT34V(mBoneSwitchMtx));
bLightEnabled = (mBoneSwitchMtx.a.x==0.0f) || forceOn; // enabled if expression driven scale is zero or we are forcing it on
}
boneIdx = pModelInfo->GetLightFxBoneIdx();
}
enum { MP_F_FREEMODE_01 = 0x9C9EFFD8 }; //"mp_f_freemode_01"
enum { MP_M_FREEMODE_01 = 0x705E61F2 }; //"mp_m_freemode_01"
// MP-only light:
if(pModelInfo->GetLightMpBoneIdx()!=0xffff)
{
bLightEnabled = m_bMpLightEnabled;
boneIdx = pModelInfo->GetLightMpBoneIdx();
const u32 modelNameHash = pModelInfo->GetModelNameHash();
if(modelNameHash == MP_M_FREEMODE_01)
{
bMpBone = true;
bOffsetLightPos = true;
lightOffset.Set(0.164f, 0.202f, -0.144f);
}
else if (modelNameHash == MP_F_FREEMODE_01)
{
bMpBone = true;
bOffsetLightPos = true;
lightOffset.Set(0.156f, 0.249f, -0.128f);
}
}
}
const pedLight *curLight = &sm_mainLight;
bool bFirstPerson = IsControlledByLocalPlayer() && ( camInterface::IsRenderingFirstPersonShooterCamera());
if( bFirstPerson )
{
curLight = &sm_fpsLight;
}
if(bLightEnabled || sm_ligthEnabledTest)
{
if(boneIdx>-1)
{
Matrix34 mBoneMtx;
pSkeleton->GetGlobalMtx(boneIdx, RC_MAT34V(mBoneMtx));
Vector3 lightPos = mBoneMtx.d;
Vector3 worldDir;
Vector3 worldTan;
if(bMpBone)
{ // MP light:
static dev_bool bAxisX=false;
if(bAxisX)
mBoneMtx.Transform3x3(VEC3V_TO_VECTOR3(Vec3V(V_X_AXIS_WZERO)), worldDir);
else
mBoneMtx.Transform3x3(VEC3V_TO_VECTOR3(Vec3V(V_Y_AXIS_WZERO)), worldDir);
mBoneMtx.Transform3x3(VEC3V_TO_VECTOR3(Vec3V(V_Z_AXIS_WZERO)), worldTan);
}
else
{ // SP light:
static dev_bool bPointingDown = false;
if(bPointingDown)
{
worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(-Vec3V(V_Z_AXIS_WZERO)));
worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO)));
}
else
{
static dev_bool bAxisX=false;
if(bAxisX)
worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_X_AXIS_WZERO)));
else
worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO)));
worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Z_AXIS_WZERO)));
}
}
if(bOffsetLightPos)
{
Vector3 offset;
mBoneMtx.Transform3x3(lightOffset, offset);
lightPos += offset;
}
static int texId = ATSTRINGHASH("torch", 0x008b461b5);
CLightSource light(
LIGHT_TYPE_SPOT,
LIGHTFLAG_FX
| LIGHTFLAG_BOTH_INTERIOR_AND_EXTERIOR
| LIGHTFLAG_CAST_STATIC_GEOM_SHADOWS | LIGHTFLAG_CAST_DYNAMIC_GEOM_SHADOWS
| LIGHTFLAG_MOVING_LIGHT_SOURCE
| LIGHTFLAG_DRAW_VOLUME | LIGHTFLAG_USE_VOLUME_OUTER_COLOUR
| LIGHTFLAG_TEXTURE_PROJECTION,
lightPos,
RCC_VECTOR3(curLight->m_lightSettings.colour),
curLight->m_lightSettings.intensity,
LIGHT_ALWAYS_ON);
light.SetDirTangent(worldDir, worldTan);
light.SetInInterior(GetInteriorLocation());
light.SetShadowTrackingId(fwIdKeyGenerator::Get(this));
light.SetTexture(texId,CShaderLib::GetTxdId());
light.SetRadius(curLight->m_lightSettings.radius);
light.SetFalloffExponent(curLight->m_lightSettings.falloffExp);
light.SetSpotlight(curLight->m_lightSettings.innerConeAngle, curLight->m_lightSettings.outerConeAngle);
light.SetLightVolumeIntensity(curLight->m_lightVolumeIntensity, curLight->m_lightVolumeSize, curLight->m_lightVolumeColor, curLight->m_lightVolumeExponent);
light.AddToShadowExclusionList(this);
light.SetShadowFadeDistance((u8)curLight->m_lightShadowFade);
light.SetSpecularFadeDistance((u8)curLight->m_lightSpecularFade);
light.SetVolumetricFadeDistance((u8)curLight->m_lightSpecularFade);
light.SetLightFadeDistance((u8)curLight->m_lightFade);
light.SetExtraFlags(EXTRA_LIGHTFLAG_CAUSTIC);
Lights::AddSceneLight(light);
static dev_bool bRenderCorona=true;
if(bRenderCorona)
{
u16 flags = CORONA_DONT_REFLECT | CORONA_HAS_DIRECTION;
float size = curLight->m_lightSettings.coronaSize;
float intensity = curLight->m_lightSettings.coronaHDR;
if (!GetInteriorLocation().IsValid() BANK_ONLY(|| g_coronas.GetForceExterior()))
{
size *= g_timeCycle.GetCurrUpdateKeyframe().GetVar(TCVAR_SPRITE_SIZE);
intensity *= g_timeCycle.GetCurrUpdateKeyframe().GetVar(TCVAR_SPRITE_BRIGHTNESS);
}
g_coronas.Register( RCC_VEC3V(lightPos),
size,
Color32(curLight->m_lightSettings.colour),
intensity,
0.02f, // zbias
RCC_VEC3V(worldDir),
1.0f,
curLight->m_lightSettings.innerConeAngle,
curLight->m_lightSettings.outerConeAngle,
flags);
}
#if GTA_REPLAY
if(CReplayMgr::ShouldRecord())
{
CReplayMgr::RecordFx<CPacketPedLight>(
CPacketPedLight(), this);
}
#endif // GTA_REPLAY
}
}
}
void CPed::RenderPedFeetLights(bool forceOn)
{
static dev_bool sm_ligthEnabledTest = false;
if (forceOn || sm_ligthEnabledTest)
{
const pedLight *curLight = &sm_footLight;
s32 boneIdx[2] = { -1, -1 };
const bool bOffsetLightPos = true;
Vector3 lightOffset(0.0f, 0.0f, 0.0f);
crSkeleton *pSkeleton = GetSkeleton();
if (pSkeleton)
{
boneIdx[0] = GetBoneIndexFromBoneTag(BONETAG_R_FOOT);
boneIdx[1] = GetBoneIndexFromBoneTag(BONETAG_L_FOOT);
}
if( (boneIdx[0] > -1) && (boneIdx[1] > -1))
{
Matrix34 mBoneMtx[2];
pSkeleton->GetGlobalMtx(boneIdx[0], RC_MAT34V(mBoneMtx[0]));
pSkeleton->GetGlobalMtx(boneIdx[1], RC_MAT34V(mBoneMtx[1]));
Vector3 lightPos[2];
lightPos[0] = mBoneMtx[0].d;
lightPos[1] = mBoneMtx[1].d;
Vector3 worldDir;
Vector3 worldTan;
static dev_bool bPointingDown = false;
if (bPointingDown)
{
worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(-Vec3V(V_Z_AXIS_WZERO)));
worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO)));
}
else
{
static dev_bool bAxisX = false;
if (bAxisX)
worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_X_AXIS_WZERO)));
else
worldDir = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Y_AXIS_WZERO)));
worldTan = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(Vec3V(V_Z_AXIS_WZERO)));
}
static dev_bool dbgForceCapsuleLight = true; //0=point, 1=capsule
//static Vector3 dbgLightOffsetPoint(0.05f, -0.075f, 0.0f);
//static Vector3 dbgLightOffsetCapsule(0.15f, -0.075f, 0.0f);
//lightOffset.Set(dbgForceCapsuleLight? dbgLightOffsetCapsule : dbgLightOffsetPoint);
lightOffset.Set(curLight->m_lightOffset.GetXf(), curLight->m_lightOffset.GetYf(), curLight->m_lightOffset.GetZf());
if (bOffsetLightPos)
{
Vector3 offset[2];
mBoneMtx[0].Transform3x3(lightOffset, offset[0]);
mBoneMtx[1].Transform3x3(lightOffset, offset[1]);
lightPos[0] += offset[0];
lightPos[1] += offset[1];
}
CLightSource light0(
dbgForceCapsuleLight? LIGHT_TYPE_CAPSULE : LIGHT_TYPE_POINT,
LIGHTFLAG_FX
| LIGHTFLAG_BOTH_INTERIOR_AND_EXTERIOR
| LIGHTFLAG_MOVING_LIGHT_SOURCE,
lightPos[0],
RCC_VECTOR3(curLight->m_lightSettings.colour),
curLight->m_lightSettings.intensity,
LIGHT_ALWAYS_ON);
if(dbgForceCapsuleLight)
{
light0.SetDirTangent(worldDir, worldTan);
light0.SetCapsule(curLight->m_lightSettings.capsuleLength);
}
light0.SetInInterior(GetInteriorLocation());
light0.SetRadius(curLight->m_lightSettings.radius);
light0.SetFalloffExponent(curLight->m_lightSettings.falloffExp);
light0.SetSpecularFadeDistance((u8)curLight->m_lightSpecularFade);
light0.SetLightFadeDistance((u8)curLight->m_lightFade);
Lights::AddSceneLight(light0);
CLightSource light1(
dbgForceCapsuleLight ? LIGHT_TYPE_CAPSULE : LIGHT_TYPE_POINT,
LIGHTFLAG_FX
| LIGHTFLAG_BOTH_INTERIOR_AND_EXTERIOR
| LIGHTFLAG_MOVING_LIGHT_SOURCE,
lightPos[1],
RCC_VECTOR3(curLight->m_lightSettings.colour),
curLight->m_lightSettings.intensity,
LIGHT_ALWAYS_ON);
if (dbgForceCapsuleLight)
{
light1.SetDirTangent(worldDir, worldTan);
light1.SetCapsule(curLight->m_lightSettings.capsuleLength);
}
light1.SetInInterior(GetInteriorLocation());
light1.SetRadius(curLight->m_lightSettings.radius);
light1.SetFalloffExponent(curLight->m_lightSettings.falloffExp);
light1.SetSpecularFadeDistance((u8)curLight->m_lightSpecularFade);
light1.SetLightFadeDistance((u8)curLight->m_lightFade);
Lights::AddSceneLight(light1);
#if 0 && GTA_REPLAY // TODO
if (CReplayMgr::ShouldRecord())
{
CReplayMgr::RecordFx<CPacketPedLight>(
CPacketPedLight(), this);
}
#endif // GTA_REPLAY
}
}
}
void CPed::ForceRagdollPreSetup()
{
if(GetRagdollState()==RAGDOLL_STATE_ANIM_PRESETUP)
{
Assert(GetSkeleton());
GetRagdollInst()->SetMatrix(GetMatrix());
GetSkeleton()->Update();
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
SetRagdollState(RAGDOLL_STATE_ANIM, true);
Assert(GetRagdollInst()->GetArchetype());
Assert(GetRagdollInst()->GetArchetype()->GetBound());
Assert(GetRagdollInst()->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* bound = static_cast<phBoundComposite*>(GetRagdollInst()->GetArchetype()->GetBound());
float oldRadius = bound->GetRadiusAroundCentroid();
bound->CalculateCompositeExtents(true);
if(bound->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetRagdollInst()->GetLevelIndex());
}
if (oldRadius != bound->GetRadiusAroundCentroid())
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetRagdollInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
else
CPhysics::GetLevel()->UpdateObjectLocation(GetRagdollInst()->GetLevelIndex());
}
}
fwDrawData* CPed::AllocateDrawHandler(rmcDrawable* pDrawable)
{
return rage_new CPedDrawHandler(this, pDrawable);
}
/*
// Name : SetShootTimer
// Purpose : Sets the amount of time a ped attacks for
// Parameters :
// Returns : Nothing
void CPed::SetShootTimer(u32 nTime)
{
if(fwTimer::GetTimeInMilliseconds() > m_nShootTimer)
m_nShootTimer = (fwTimer::GetTimeInMilliseconds() + nTime);
} // end - CPed::SetShootTimer
*/
/*
// Name : ClearLook
// Purpose : Clears ped look state
// Parameters : None
// Returns : Nothing
void CPed::ClearLook()
{
//RestorePreviousState();
ClearLookFlag();
} // end - CPed::ClearLook
*/
/*
// Name : Look
// Purpose : Turn Ped to face something
// Parameters : None
// Returns : Nothing
void CPed::Look()
{
TurnBody();
}
*/
///////////////////////////////////////////////////////////////////////////
// FUNCTION: Teleport
// DOES: Moves this thing to different coordinates doing the things
// that need to be done to stop the game from crashing.
///////////////////////////////////////////////////////////////////////////
// bClearOrientation is only used in the vehicle Teleport functions
void CPed::Teleport(const Vector3& vecSetCoors, float fSetHeading, bool bKeepTasks, bool bTriggerPortalRescan, bool bKeepIK, bool bWarp, bool bKeepRagdoll, bool bResetPlants)
{
Assertf(!IsCloseAll(RCC_VEC3V(vecSetCoors), Vec3V(V_ZERO), Vec3V(V_FLT_SMALL_1)),
"Ped %p (%s) is being teleported very close to the origin: (%.f, %f, %f)", this, this->GetModelName(),
vecSetCoors.x, vecSetCoors.y, vecSetCoors.z);
pedDebugf3("CPed::Teleport ped[%p][%s][%s] vecSetCoors[%f %f %f] fSetHeading[%f] bKeepTasks[%d] bTriggerPortalRescan[%d] bKeepIK[%d] bWarp[%d] bKeepRagdoll[%d] bResetPlants[%d]",this,GetModelName(),GetNetworkObject() ? GetNetworkObject()->GetLogName() : "",vecSetCoors.x,vecSetCoors.y,vecSetCoors.z,fSetHeading,bKeepTasks,bTriggerPortalRescan,bKeepIK,bWarp,bKeepRagdoll,bResetPlants);
DEV_BREAK_ON_PROXIMITY( CDebugScene::ShouldDebugBreakOnProximityOfTeleportCallingPed(), vecSetCoors );
// Reset the stealth noise on Teleport.
CPlayerInfo* pPlayerInfo = GetPlayerInfo();
if (pPlayerInfo)
{
pPlayerInfo->SetStealthNoise(0.f);
}
#if GTA_REPLAY
CReplayMgr::RecordWarpedEntity(this);
#endif
Vector3 prevPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 newPos = vecSetCoors;
if(GetPedAudioEntity())
{
GetPedAudioEntity()->GetFootStepAudio().StopLadderSlide();
GetPedAudioEntity()->GetFootStepAudio().ResetWetFeet();
}
GetFootstepHelper().ResetAirFlags();
GetFootstepHelper().ResetFlags();
// need to know if we were using ragdoll when we got teleported,
// need to reset some extra stuff if we were, but flushing the tasks
const bool bWasUsingRagdoll = GetUsingRagdoll();
const bool bIsPlayer = IsPlayer();
#if !__FINAL
if (bIsPlayer)
{
#if __BANK
if(PARAM_trackPlayerTeleport.Get())
{
// Some TTY to track where the teleport comes from (to aid with B*1772492)
const char *origin = CTheScripts::GetCurrentScriptName();
if (!origin || !origin[0])
{
origin = "Code";
}
Displayf("CPed::Teleport() - From %s: Teleporting player to %.2f, %.2f %.2f", origin, vecSetCoors.x, vecSetCoors.y, vecSetCoors.z);
sysStack::PrintStackTrace();
}
#endif
if (CScriptDebug::GetDisableDebugCamAndPlayerWarping()) // For build for Image Metrics
{
return;
}
#if STREAMING_VISUALIZE
if (strStreamingVisualize::IsInstantiated())
{
char markerText[256];
const char *origin = CTheScripts::GetCurrentScriptName();
if (!origin || !origin[0])
{
origin = "Code";
STRVIS_ADD_CALLSTACK_TO_MARKER();
}
formatf(markerText, "From %s: Teleporting player to %.2f, %.2f %.2f", origin, vecSetCoors.x, vecSetCoors.y, vecSetCoors.z);
STRVIS_SET_MARKER_TEXT(markerText);
STRVIS_ADD_MARKER(strStreamingVisualize::MARKER);
STRVIS_ADD_CALLSTACK_TO_MARKER();
}
#endif // STREAMING_VISUALIZE
}
#endif
//#if __BANK
// if(CNetwork::OverrideSpawnPoint())
// {
// newPos = GetPosition();
// }
//#endif
if (bKeepTasks == false)
{ // can't flush this ped's tasks if this function was called by the task code
//Clear certain water flags.
//This is done before tasks are cleared because clearing the tasks will attempt to
//restore the ped's backup weapon if they are in the water.
//The weapon selection code relies on the drowning flag to determine if the ped is
//allowed to equip a particular weapon or not. If the drowning flag is not cleared here,
//the ped will not be able to restore their backup weapon unless it was able to be equipped underwater.
SetPedResetFlag(CPED_RESET_FLAG_IsDrowning, false);
SetIsSwimming(false); //re-evaluate
if(bIsPlayer)
{
// remove all tasks and give back default task
if(!IsNetworkClone())
{
if(GetNetworkObject())
{
if(!IsNetworkBot())
{
GetPedIntelligence()->FlushImmediately(true);
}
else
{
GetPedIntelligence()->FlushImmediately(false);
GetPedIntelligence()->AddTaskDefault(rage_new CTaskDoNothing(-1));
}
}
else
{
// No network object, safe to flush
GetPedIntelligence()->FlushImmediately(true);
}
}
}
else
{
// remove all tasks if exiting a car and give back default task
/*RAGE if(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_COMPLEX_LEAVE_CAR))
{
GetPedIntelligence()->FlushImmediately(true);
}*/
}
}
if (bIsPlayer)
{
#if !__FINAL
// We need to reset the plants manager here otherwise it'll crash...
if(gbPlantMgrActive)
#endif
{
// flat distance
if(bResetPlants && IsLocalPlayer() && (MagSquared((RCC_VEC3V(newPos) - GetTransform().GetPosition()).Get<Vec::X, Vec::Y>()).Getf() > 25.0f))
{
CPlantMgr::Shutdown(SHUTDOWN_WITH_MAP_LOADED);
CPlantMgr::PreUpdateOnceForNewCameraPos(newPos);
}
}
}
const bool bLegIKActive = GetIkManager().HasSolver(IKManagerSolverTypes::ikSolverTypeLeg);
const bool bIgnoreLegIKRestrictions = GetPedConfigFlag(CPED_CONFIG_FLAG_IgnoreLegIkRestrictions);
if (!bKeepIK)
{
if (bIsPlayer || bIgnoreLegIKRestrictions)
{
GetIkManager().ResetAllNonLegSolvers();
}
else
{
GetIkManager().ResetAllSolvers();
if (bLegIKActive)
{
GetIkManager().SetFlag(PEDIK_LEGS_AND_PELVIS_OFF);
GetIkManager().SetFlag(PEDIK_LEGS_AND_PELVIS_FADE_OFF);
}
}
}
bool bPreserveRagdoll = NetworkInterface::IsGameInProgress() && GetUsingRagdoll() && bKeepRagdoll;
// if ped is using ragdoll then go back to animation because we can't teleport the ragdoll
if(GetUsingRagdoll() && !bPreserveRagdoll)
{
nmEntityDebugf(this, "CPed::Teleport switching to animated");
SwitchToAnimated();
}
// need to make sure we re-set up animation, skeleton / bound matrices, otherwise we'll get crazy velocities when we activate
if(bWasUsingRagdoll && !bPreserveRagdoll)
{
GetAnimDirector()->SetPosed(false);
SetRagdollState(RAGDOLL_STATE_ANIM_PRESETUP);
}
ResetStandData();
if (!bPreserveRagdoll)
{
ResetDesiredMainMoverCapsuleData(true);
}
SetPosition(newPos, true, !bPreserveRagdoll, bWarp);
#if RSG_PC
if (bIsPlayer)
{
CRenderPhaseCascadeShadows::UpdateRasterUpdateId();
}
#endif
if (bPreserveRagdoll && GetRagdollInst())
{
Matrix34 newMat(RCC_MATRIX34(GetRagdollInst()->GetMatrix()));
newMat.d=newPos;
GetRagdollInst()->SetMatrix(RCC_MAT34V(newMat));
if (GetCollider())
GetCollider()->SetMatrix(RCC_MAT34V(newMat));
PHSIM->SetLastInstanceMatrix(GetRagdollInst(), RCC_MAT34V(newMat));
if (bWarp)
{
CPhysics::ClearManifoldsForInst(GetRagdollInst());
GetFrameCollisionHistory()->Reset();
}
GetRagdollInst()->ReportMovedBySim();
CPhysics::GetLevel()->UpdateObjectLocation(GetRagdollInst()->GetLevelIndex());
}
else
{
SetHeading(fwAngle::LimitRadianAngleSafe(fSetHeading));
SetCurrentHeading(fwAngle::LimitRadianAngleSafe(fSetHeading));
SetDesiredHeading(fwAngle::LimitRadianAngleSafe(fSetHeading));
}
if (bKeepIK || bIsPlayer || bIgnoreLegIKRestrictions)
{
if (bLegIKActive)
{
// Make sure flags are cleared after the position has been warped above
GetIkManager().ClearFlag(PEDIK_LEGS_AND_PELVIS_OFF);
GetIkManager().ClearFlag(PEDIK_LEGS_AND_PELVIS_FADE_OFF);
}
}
if (bLegIKActive)
{
// Force an instant setup in one frame
GetIkManager().SetFlag(PEDIK_LEGS_INSTANT_SETUP);
}
//Orient the qpeds to the ground
if (m_pCapsuleInfo && m_pCapsuleInfo->IsQuadruped() && !bPreserveRagdoll)
{
Vector3 vNormal;
bool bDunno;
float fGroundZ = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, vecSetCoors, &bDunno, &vNormal);
if (Abs(fGroundZ - vecSetCoors.z) < 4.0f) //Teleported near ground, align
{
Matrix34 mat;
mat.Identity();
mat.b.Cross(vNormal, VEC3V_TO_VECTOR3(GetMatrix().GetCol0()));
//TMS: Going to ignore really steep stuff!
if (mat.b.Mag2() > 0.1f*0.1f)
{
Vector3 vZ;
mat.a = VEC3V_TO_VECTOR3(GetMatrix().GetCol0());
mat.c.Cross(mat.a, mat.b);
mat.Normalize();
mat.d = vecSetCoors;
//Set the matrix from this
SetMatrix(mat);
}
}
}
// force portal tracker to update from new position (should eventually get data out of collisions under ped... hopefully...)
if (bTriggerPortalRescan)
{
GetPortalTracker()->SetProbeType(CPortalTracker::PROBE_TYPE_NEAR);
GetPortalTracker()->ScanUntilProbeTrue();
}
if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel() && CPhysics::GetLevel()->IsInactive(GetCurrentPhysicsInst()->GetLevelIndex())
&& !GetIsAttached())
{
ActivatePhysics();
}
if(GetIsAttached() && GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_TELEPORT))
{
DetachFromParent(DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK);
}
// Reset in case left stale if the activation doesn't trigger
SetPedResetFlag(CPED_RESET_FLAG_InContactWithFoliage, false);
SetPedResetFlag(CPED_RESET_FLAG_InContactWithBIGFoliage, false);
SetPedResetFlag(CPED_RESET_FLAG_InContactWithDeepSurface, false);
// update collider in physics world
if(GetCollider() && !bPreserveRagdoll)// && GetCollider()->GetLevelIndex()!=phInst::INVALID_INDEX)
{
GetCollider()->SetMatrix(GetMatrix());
GetCollider()->SetLastInstanceMatrix(GetMatrix());
GetCollider()->Reset();
if(GetCollider()->GetSleep())
GetCollider()->GetSleep()->Reset();
}
// need to update its ragdoll now or it'll crash if it trys to activate before prerender gets called.
if(GetRagdollInst() && GetRagdollInst()->IsInLevel() && GetRagdollState()!=RAGDOLL_STATE_PHYS && GetRagdollState()!=RAGDOLL_STATE_ANIM_DRIVEN)
{
// call update lastMatrix and poseBoundsFromSkeleton twice deliberately because we're warping and all old matricies are now invalid
GetRagdollInst()->SetMatrix(GetMatrix());
PHSIM->SetLastInstanceMatrix(GetRagdollInst(), GetMatrix());
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
GetRagdollInst()->PoseBoundsFromSkeleton(true, true);
Assert(GetRagdollInst()->GetArchetype());
Assert(GetRagdollInst()->GetArchetype()->GetBound());
Assert(GetRagdollInst()->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* bound = static_cast<phBoundComposite*>(GetRagdollInst()->GetArchetype()->GetBound());
float oldRadius = bound->GetRadiusAroundCentroid();
bound->CalculateCompositeExtents(true);
if(bound->HasBVHStructure())
{
CPhysics::GetLevel()->RebuildCompositeBvh(GetRagdollInst()->GetLevelIndex());
}
if (oldRadius != bound->GetRadiusAroundCentroid())
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(GetRagdollInst()->GetLevelIndex(),(Mat34V_Ptr)(NULL));
else
CPhysics::GetLevel()->UpdateObjectLocation(GetRagdollInst()->GetLevelIndex());
}
//Make sure we wait until we're on the ground properly before we test for a ragdolling QPed
m_iFailedQPedRagdollTests = -1;
m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = false; // Force the m_nFlags.bPossiblyTouchesWater to be updated
// Set this to be the new position, incase we have been teleported into the air
m_fFallingHeight = GetTransform().GetPosition().GetZf();
g_ptFxManager.RelocateEntityPtFx(this);
#if __TRACK_PEDS_IN_NAVMESH
GetNavMeshTracker().Teleport(vecSetCoors);
#endif
GetPedIntelligence()->SetLastMainNavMeshPosition(VEC3_ZERO);
bool bCanPlayerRetainParachute = GetPlayerInfo() && GetPlayerInfo()->CanRetainParachuteAfterTeleport();
if (!NetworkInterface::IsGameInProgress() || !bCanPlayerRetainParachute )
{
//Check if we should not keep the parachute pack on - if we're not a clone wipe it...
if((!GetPedResetFlag(CPED_RESET_FLAG_KeepParachutePackOnAfterTeleport)) && (!IsNetworkClone()))
{
//Clear the parachute pack variation.
CTaskParachute::ClearParachutePackVariationForPed(*this);
}
}
//Check if we are a player.
if(IsLocalPlayer() && GetPlayerInfo())
{
// If the camera is following this player, then we need to ensure the next Portal Tracker
// update for the camera uses UpdateUsingTarget in order to pick up the correct interior
if( g_SceneToGBufferPhaseNew && g_SceneToGBufferPhaseNew->GetPortalVisTracker() )
{
CViewport *pVp = g_SceneToGBufferPhaseNew->GetViewport();
if(pVp && pVp->GetAttachEntity() == this)
{
CPortalVisTracker *pVisPortalTracker = g_SceneToGBufferPhaseNew->GetPortalVisTracker();
pVisPortalTracker->SetForceUpdateUsingTarget(true);
//We need to ensure the ped's portal tracker is updated before we update the vis portal tracker,
//so let's do it now. It would normally be updated in CGameWorld::ProcessAfterAllMovement() but
//that will be skipped if the game is paused, such as on some respawn cases.
UpdatePortalTracker();
}
}
//Disable agitation.
GetPlayerInfo()->DisableAgitation();
if (NetworkInterface::IsGameInProgress())
{
static const float sfMaxDeltaTeleportResetProximityControlTimer = rage::square(10.f);
float fDelta = (prevPos - newPos).Mag2();
if (fDelta > sfMaxDeltaTeleportResetProximityControlTimer)
{
pedDebugf3("CPed::Teleport--IsLocalPlayer-->invoke ForAllLocalObjectsResetProximityControlTimer");
NetworkInterface::GetObjectManager().ForAllLocalObjectsResetProximityControlTimer();
}
}
}
}
void CPed::ResetStandData()
{
if(GetIsAttachedToGround())
{
DetachFromGround();
}
SetIsStanding(false);
m_vecGroundPosHistory[0].Zero();
m_vecGroundPosHistory[1].Zero();
m_vecSteepSlopePos.Zero();
m_vecGroundPos.ZeroComponents();
m_vecGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_fGroundZFromImpact = PED_GROUNDPOS_RESET_Z;
m_fGroundOffsetForPhysics = PED_GROUNDPOS_RESET_Z;
m_vecRearGroundPos.ZeroComponents();
m_vecRearGroundPos.SetZf(PED_GROUNDPOS_RESET_Z);
m_bValidGroundNormal = false;
m_bKickedByPlayer = false;
m_bPreviouslyKickedByPlayer = false;
m_bHitByDoor = false;
m_bPreviouslyHitByDoor = false;
m_uRagdollPlayerLegContactTime = 0;
m_uRagdollDoorContactTime = 0;
SetGroundPhysical(NULL);
m_vecGroundPosLocal.ZeroComponents();
m_groundPhysicalComponent = 0;
m_lastGroundPhysicalComponent = 0;
SetIsStandingOnMovableObject(false);
SetPedResetFlag(CPED_RESET_FLAG_FirstPhysicsUpdate, true);
SetPedResetFlag(CPED_RESET_FLAG_ForceProcessPhysicsUpdateEachSimStep, true);
}
void CPed::RestoreHeadingChangeRate()
{
m_MotionData.SetHeadingChangeRate( CPedType::ms_fMaxHeadingChange );
}
//void CPed::RestoreHeadingRateCB(CAnimBlendAssociation* pAnim, void* pData)
//{
// ((CPed *)pData)->m_fHeadingChangeRate = ((CPed *)pData)->m_pPedStats->ms_fMaxHeadingChange;
//}
void CPed::FlagToDestroyWhenNextProcessed()
{
#if !__NO_OUTPUT
if(m_LogDeletions)
{
EntityDebugfWithCallStack(this, "FlagToDestroyWhenNextProcessed");
}
#endif
FlagToDestroyWhenNextProcessed(true);
}
void CPed::FlagToDestroyWhenNextProcessed(bool bChangeCarStatus)
{
#if !__NO_OUTPUT
if(m_LogDeletions)
{
EntityDebugfWithCallStack(this, "FlagToDestroyWhenNextProcessed");
}
#endif
Assertf(!this->IsAPlayerPed(), "Trying to delete player ped");
#if GTA_REPLAY
replayFatalAssertf(CReplayMgr::IsOkayToDelete() || GetOwnedBy() != ENTITY_OWNEDBY_REPLAY, "Entity being deleted during playback...but NOT by the replay system!");
#endif
Assert(!IsNetworkClone());
if (GetNetworkObject())
{
Assert(GetNetworkObject()->CanDelete());
}
SetBaseFlag(fwEntity::REMOVE_FROM_WORLD);;
#if __BANK
CPedFactory::LogDestroyedPed(this, "CPed::FlagToDestroyWhenNextProcessed()", grcDebugDraw::GetScreenSpaceTextHeight(), Color_orange);
#endif
// If the character is in a vehicle then check whether the character
// is the driver or a passenger and remove the relevant pointer to this character
// in the vehicle class
if (GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle)
{
Assert(m_pMyVehicle);
SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, FALSE );
m_pMyVehicle->RemovePedFromSeat(this, bChangeCarStatus);
// Register in the spatial array that we are no longer considered using a vehicle.
UpdateSpatialArrayTypeFlags();
m_pMyVehicle = NULL;
}
}
ASSERT_ONLY(bool CPed::ASSERT_IF_DETACHED_FROM_VEHICLE = true;)
#if __BANK
void CPed::DebugAttachPedToPhysical(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, const Quaternion* pQuatOffset, float fDefaultHeading, float fHeadingLimit)
#else
void CPed::AttachPedToPhysical(CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, const Quaternion* pQuatOffset, float fDefaultHeading, float fHeadingLimit)
#endif
{
m_fAttachHeading = fDefaultHeading;
m_fAttachHeadingLimit = fHeadingLimit;
#if __BANK
DebugAttachToPhysicalBasic(strCodeFile, strCodeFunction, nCodeLine, pPhysical, nEntBone, nAttachFlags, pVecOffset, pQuatOffset);
#else
AttachToPhysicalBasic(pPhysical, nEntBone, nAttachFlags, pVecOffset, pQuatOffset);
#endif
Assertf(GetAttachState()==ATTACH_STATE_PED || GetAttachState()==ATTACH_STATE_PED_WITH_ROT, "Unexpected attachment state %s.", fwAttachmentEntityExtension::GetAttachStateString(GetAttachState()));
if(nAttachFlags & ATTACH_FLAG_INITIAL_WARP)
{
ProcessAttachment();
SetDesiredHeading( GetCurrentHeading() );
}
}
#if __BANK
void CPed::DebugAttachPedToGround(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, float fDefaultHeading, float fHeadingLimit)
#else
void CPed::AttachPedToGround(CPhysical* pPhysical, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, float fDefaultHeading, float fHeadingLimit)
#endif
{
m_fAttachHeading = fDefaultHeading;
m_fAttachHeadingLimit = fHeadingLimit;
#if __BANK
DebugAttachToPhysicalBasic(strCodeFile, strCodeFunction, nCodeLine, pPhysical, nEntBone, nAttachFlags, pVecOffset, NULL);
#else
AttachToPhysicalBasic(pPhysical, nEntBone, nAttachFlags, pVecOffset, NULL);
#endif
if(nAttachFlags & ATTACH_FLAG_INITIAL_WARP)
{
ProcessAttachment();
SetDesiredHeading( GetCurrentHeading() );
}
ASSERT_ONLY( bool bDetached = ((nAttachFlags & ATTACH_FLAG_AUTODETACH_ON_DEATH) && IsInjured()) || ((nAttachFlags & ATTACH_FLAG_AUTODETACH_ON_RAGDOLL) && GetUsingRagdoll()); )
Assertf(bDetached || GetAttachState()==ATTACH_STATE_PED_ON_GROUND, "Unexpected attachment state %d.", GetAttachState());
}
#if __BANK
void CPed::DebugAttachPedToWorld(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, u32 nAttachFlags, const Vector3 &vecWorldPos, float fDefaultHeading, float fHeadingLimit)
#else
void CPed::AttachPedToWorld(u32 nAttachFlags, const Vector3 &vecWorldPos, float fDefaultHeading, float fHeadingLimit)
#endif
{
m_fAttachHeading = fDefaultHeading;
m_fAttachHeadingLimit = fHeadingLimit;
#if __BANK
DebugAttachToWorldBasic(strCodeFile, strCodeFunction, nCodeLine, nAttachFlags, vecWorldPos, NULL);
#else
AttachToWorldBasic(nAttachFlags, vecWorldPos, NULL);
#endif
Assertf(GetAttachState()==ATTACH_STATE_PED, "Unexpected attachment state %d.", GetAttachState());
}
#if __BANK
void CPed::DebugAttachPedToEnterCar(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPhysical* pPhysical, u32 nAttachFlags, int nSeatIndex, int nEntryExitPoint, const Vector3* pVecOffset, const Quaternion* pRotOffset)
#else
void CPed::AttachPedToEnterCar(CPhysical* pPhysical, u32 nAttachFlags, int nSeatIndex, int nEntryExitPoint, const Vector3* pVecOffset, const Quaternion* pRotOffset)
#endif
{
Vector3 vecSeatOffset(VEC3_ZERO);
if(pVecOffset)
{
vecSeatOffset.Set(*pVecOffset);
}
m_nAttachCarSeatIndex = (s16) nSeatIndex;
if(nEntryExitPoint >= 0)
m_nAttachCarEntryExitPoint = (s16) nEntryExitPoint;
Assert(pPhysical->GetIsTypeVehicle());
s16 boneIndex = -1;
if(pPhysical->GetIsTypeVehicle() && nSeatIndex != -1)
{
CVehicle* pVehicle = static_cast<CVehicle*>(pPhysical);
// Look up seat anim info
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(nSeatIndex);
aiAssert(pVehicle->GetVehicleModelInfo());
// let peds auto detach from the vehicle when they want to ragdoll (sometimes)
bool bWillAutoDetachOnRagdoll = pVehicle->GetVehicleModelInfo()->GetCanPassengersBeKnockedOff() || pSeatAnimInfo->GetCanDetachViaRagdoll();
if(bWillAutoDetachOnRagdoll)
{
if(m_PedConfigFlags.GetCantBeKnockedOffVehicle()!=KNOCKOFFVEHICLE_NEVER )
{
nAttachFlags |= ATTACH_FLAG_AUTODETACH_ON_RAGDOLL;
nAttachFlags |= ATTACH_FLAG_AUTODETACH_ON_DEATH;
}
}
boneIndex = static_cast<s16>(pVehicle->GetVehicleModelInfo()->GetModelSeatInfo()->GetBoneIndexFromSeat(nSeatIndex));
}
REPLAY_ONLY(if(CReplayMgr::IsEditModeActive() == false))
{
#if __BANK
DebugAttachToPhysicalBasic(strCodeFile, strCodeFunction, nCodeLine, pPhysical, boneIndex, nAttachFlags, &vecSeatOffset, pRotOffset);
#else
AttachToPhysicalBasic(pPhysical, boneIndex, nAttachFlags, &vecSeatOffset, pRotOffset);
#endif
}
}
#if __BANK
void CPed::DebugAttachPedToMountAnimal(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CPed* pMount, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, float fDefaultHeading, float fHeadingLimit)
#else
void CPed::AttachPedToMountAnimal(CPed* pMount, s16 nEntBone, u32 nAttachFlags, const Vector3* pVecOffset, float fDefaultHeading, float fHeadingLimit)
#endif
{
m_fAttachHeading = fDefaultHeading;
m_fAttachHeadingLimit = fHeadingLimit;
//Quaternion orient; orient.FromRotation(Vector3(1.0f, 0,0), -PI/2.0f);
#if __BANK
//DebugAttachToPhysicalBasic(strCodeFile, strCodeFunction, nCodeLine, pMount, nEntBone, nAttachFlags, pVecOffset, &orient);
DebugAttachToPhysicalBasic(strCodeFile, strCodeFunction, nCodeLine, pMount, nEntBone, nAttachFlags, pVecOffset, NULL);
#else
//AttachToPhysicalBasic(pMount, nEntBone, nAttachFlags, pVecOffset, &orient);
AttachToPhysicalBasic(pMount, nEntBone, nAttachFlags, pVecOffset, NULL);
#endif
if(nAttachFlags & ATTACH_FLAG_INITIAL_WARP)
{
ProcessAttachment();
SetDesiredHeading( GetCurrentHeading() );
}
}
void CPed::DetachFromParent(u16 nDetachFlags)
{
#if DR_ENABLED
if (GetAttachParent() && static_cast<const CEntity*>(GetAttachParent())->GetIsTypeVehicle())
{
debugPlayback::RecordPedDetachFromVehicle(*this, static_cast<const CVehicle*>(GetAttachParent()), nDetachFlags);
}
#endif // DR_ENABLED
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedBeingDeleted))
{
// If we are about to delete this ped, as an optimization, never activate its physics.
nDetachFlags &= ~DETACH_FLAG_ACTIVATE_PHYSICS;
// To stop an assert below firing spuriously.
#if __ASSERT
nDetachFlags |= DETACH_FLAG_DONT_ASSERT_IF_IN_VEHICLE;
#endif
}
fwAttachmentEntityExtension *extension = GetAttachmentExtension();
if(!extension) return;
if( nDetachFlags & DETACH_FLAG_DONT_REMOVE_BASIC_ATTACHMENTS )
{
bool bPhysicalAttachment = extension->GetAttachState() == ATTACH_STATE_WORLD_PHYSICAL || extension->GetAttachState() == ATTACH_STATE_PHYSICAL || extension->GetAttachState() == ATTACH_STATE_RAGDOLL;
if(!bPhysicalAttachment) return;
}
physicsAssertf(!extension->GetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION), "DetachFromParent called recursively");
extension->SetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION, true);
#if __ASSERT
bool bDontAssert = (nDetachFlags & DETACH_FLAG_DONT_ASSERT_IF_IN_VEHICLE) != 0u;
bDontAssert = bDontAssert || NetworkInterface::IsNetworkOpen() || NetworkInterface::IsNetworkClosing();
#if GTA_REPLAY
bDontAssert = bDontAssert || CReplayMgr::IsEditModeActive();
#endif
const bool bInVehicle = GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle );
const bool bOnVehicle = GetPedConfigFlag( CPED_CONFIG_FLAG_AttachedToVehicle );
physicsAssertf(bDontAssert
|| !ASSERT_IF_DETACHED_FROM_VEHICLE
|| GetAttachParent() == NULL
|| !static_cast<CPhysical*>(GetAttachParent())->GetIsTypeVehicle()
|| (!bInVehicle || bOnVehicle)
|| m_nRagdollState >= RAGDOLL_STATE_PHYS_ACTIVATE,
"bDontAssert=%s, ASSERT_IF_DETACHED_FROM_VEHICLE=%s, attachParent=0x%p, typeVehicle?=%s, bInVehicle=%s, bOnVehicle=%s, ragdollState=%d, clone?=%s",
bDontAssert?"true":"false", ASSERT_IF_DETACHED_FROM_VEHICLE?"true":"false", GetAttachParent(),
static_cast<CPhysical*>(GetAttachParent())->GetIsTypeVehicle()?"true":"false", bInVehicle?"true":"false",
bOnVehicle?"true":"false", m_nRagdollState, IsNetworkClone()?"yes":"no");
#endif
// Not sure our heading will be correct when using ragdoll due to inconsistency with ragdoll matrices and animated matrices
if(!GetUsingRagdoll())
{
// Need to to do this or ped will pop back to original heading it had when first attached
SetDesiredHeading(GetTransform().GetHeading());
SetCurrentHeading(GetTransform().GetHeading());
}
m_fAttachHeading = 0.0f;
m_fAttachHeadingLimit = 0.0f;
if((GetAttachState()==ATTACH_STATE_PED_ENTER_CAR || GetAttachState()==ATTACH_STATE_PED_IN_CAR || GetAttachState()==ATTACH_STATE_PED_EXIT_CAR || GetAttachState()==ATTACH_STATE_PED_WITH_ROT)
&& !(nDetachFlags &DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK))
DetachFromCarInSafePosition(nDetachFlags);
m_nAttachCarSeatIndex = -1;
m_nAttachCarEntryExitPoint = -1;
//Check if the ped is detaching from the ground.
if(GetAttachState() == ATTACH_STATE_PED_ON_GROUND)
{
//Note that the ped detached from the ground.
OnDetachFromGround();
}
extension->SetAttachFlag(ATTACH_FLAG_IN_DETACH_FUNCTION, false);
// If the ped is detached from a train, set that train to be the ground entity.
if(GetAttachParent() && static_cast<const CEntity*>(GetAttachParent())->GetIsTypeVehicle())
{
if(static_cast<CVehicle*>(GetAttachParent())->InheritsFromTrain())
{
SetGroundPhysical(static_cast<CPhysical*>(GetAttachParent()));
m_groundPhysicalComponent = 0; // Reset the ground physical component so it doesn't go stale.
}
}
CPhysical::DetachFromParent(nDetachFlags);
GetAnimatedInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false);
if(IsNetworkClone())
{
NetworkInterface::OnClonePedDetached(GetNetworkObject());
}
}
#if __BANK
void CPed::DebugProcessAttachment(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine)
#else
void CPed::ProcessAttachment()
#endif
{
fwAttachmentEntityExtension *extension = GetAttachmentExtension();
if(!extension)
{
return;
}
if(extension->GetAttachState()==ATTACH_STATE_PED_IN_CAR)
{
//B* 1567121 "While in voltic bj anim did not line up"
if(NetworkInterface::IsGameInProgress())
{
CTaskSynchronizedScene *pTaskSynchronizedScene = static_cast<CTaskSynchronizedScene *>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_SYNCHRONIZED_SCENE));
if(pTaskSynchronizedScene && pTaskSynchronizedScene->GetNetworkSceneID()!=-1)
{
return;
}
}
}
if(extension->GetAttachState()==ATTACH_STATE_PED || extension->GetAttachState()==ATTACH_STATE_PED_WITH_ROT || extension->GetAttachState()==ATTACH_STATE_PED_ON_GROUND)
{
CPhysical *attachParent = (CPhysical *) extension->GetAttachParentForced();
// Ignore this assert if not a mission ped
Assertf(!PopTypeIsMission() || attachParent || extension->GetAttachFlag(ATTACH_FLAG_DONT_ASSERT_ON_NULL_ENTITY), "%s:the thing script attached this ped to has been deleted", GetModelName());
Assertf(attachParent, "A ped attachment has lost its parent");
if(extension->GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_DEATH) && IsInjured())
{
DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_APPLY_VELOCITY);
return;
}
if (extension->GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL) && GetUsingRagdoll())
{
DetachFromParent(DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_APPLY_VELOCITY);
return;
}
// shouldn't be ragdoll'ing when attached in a non-physical way
if(GetUsingRagdoll())
{
// in a network game I believe it could happen though, so deal with it
if(NetworkInterface::IsGameInProgress())
{
DeactivatePhysicsAndDealWithRagdoll();
}
else
{
Assertf(false, "Attached ped has active ragdoll");
}
}
if(extension->GetAttachState()!=ATTACH_STATE_PED_ON_GROUND)
{
if(GetCurrentPhysicsInst() && GetCurrentPhysicsInst()->IsInLevel())
{
if(!AssertVerify(GetCurrentPhysicsInst()->GetInstFlag(phInst::FLAG_NEVER_ACTIVATE) != 0u))
{
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE,true);
}
if(!AssertVerify(!CPhysics::GetLevel()->IsActive(GetCurrentPhysicsInst()->GetLevelIndex())))
{
PHSIM->DeactivateObject(GetCurrentPhysicsInst());
}
if( !extension->GetAttachFlag(ATTACH_FLAG_COL_ON) )
{
if(!Verifyf(CPhysics::GetLevel()->GetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex()) == u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES)
|| CPhysics::GetLevel()->GetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex()) == 0u,
"Instance include flags not equal to GTA_WEAPON_AND_PROJECTILE_INCLUDE_TYPES or 0: 0x%X", CPhysics::GetLevel()->GetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex())))
{
CPhysics::GetLevel()->SetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES));
}
}
}
}
Matrix34 matParent = MAT34V_TO_MATRIX34(attachParent->GetMatrix());
if(extension->GetAttachBone() > -1 && attachParent->GetIsPhysical() && ((CPhysical*)extension->GetAttachParentForced())->GetSkeleton())
attachParent->GetGlobalMtx(extension->GetAttachBone(), matParent);
// move parent matrix by m_vecAttachOffset
Vector3 vecFinalPosition;
matParent.Transform3x3(extension->GetAttachOffset(), vecFinalPosition);
vecFinalPosition += matParent.d;
matParent.MakeTranslate(vecFinalPosition);
//Check if the ped is attached to the ground.
if(extension->GetAttachState() == ATTACH_STATE_PED_ON_GROUND)
{
//Apply the offset from the feet to the origin.
matParent.d.z += m_pCapsuleInfo->GetGroundToRootOffset();
}
// can't do GetTransform().GetHeading() because we're using the bone matrix
float fEntHeading = rage::Atan2f(-matParent.b.x, matParent.b.y);
float fAttachedHeading = fEntHeading + m_fAttachHeading;
fAttachedHeading = fwAngle::LimitRadianAngleSafe(fAttachedHeading);
m_MotionData.SetCurrentHeading( fwAngle::LimitRadianAngle(m_MotionData.GetCurrentHeading()) );
float fHeadingDiff = m_MotionData.GetCurrentHeading() - fAttachedHeading;
fHeadingDiff = fwAngle::LimitRadianAngleFast(fHeadingDiff);
// limit heading within limits relative to parent
if(fHeadingDiff > m_fAttachHeadingLimit)
m_MotionData.SetCurrentHeading( fAttachedHeading + m_fAttachHeadingLimit );
else if(fHeadingDiff < -m_fAttachHeadingLimit)
m_MotionData.SetCurrentHeading( fAttachedHeading - m_fAttachHeadingLimit );
// set current heading
m_MotionData.SetCurrentHeading( fwAngle::LimitRadianAngle(m_MotionData.GetCurrentHeading()) );
m_MotionData.SetDesiredHeading( m_MotionData.GetCurrentHeading() );
Matrix34 newMatrix;
newMatrix.Identity();
//Check if the ped is not attached to the ground.
if(extension->GetAttachState() != ATTACH_STATE_PED_ON_GROUND)
{
// gonna dot newMatrix with parent bone matrix, so heading is diff of ped heading and bone heading
newMatrix.MakeRotateZ(m_MotionData.GetCurrentHeading() - fEntHeading);
// option to attach using a quat for peds after all, overriding heading stuff
if(extension->GetAttachState()==ATTACH_STATE_PED_WITH_ROT)
{
newMatrix.FromQuaternion(extension->GetAttachQuat());
}
newMatrix.Dot(matParent);
}
else
{
//Rotate to the ped's current heading.
newMatrix.MakeRotateZ(m_MotionData.GetCurrentHeading());
//Copy the position data.
newMatrix.d = matParent.d;
}
bool bWarp = false;
// do warp if moved more than 3m
if(GetRagdollInst())
bWarp = (newMatrix.d - VEC3V_TO_VECTOR3(GetRagdollInst()->GetMatrix().GetCol3())).Mag2() > PED_MAX_DIST_TO_WARP_SQR;
SetMatrix(newMatrix, true, true, bWarp);
if(!bWarp)
UpdateRagdollMatrix(false);
SetIsStanding(true);
SetVelocity(ORIGIN);
}
else
{
#if __BANK
CPhysical::DebugProcessAttachment(strCodeFile, strCodeFunction, nCodeLine);
#else
CPhysical::ProcessAttachment();
#endif
}
}
bool CPed::CheckPositionNearEntityIsValid( Matrix34& mat, CEntity* pEntity, const bool bIncludeEntity ) const
{
static const int nTestTypes = ArchetypeFlags::GTA_MAP_TYPE_MOVER|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE;
const CEntity* exclusionList[MAX_NUM_ENTITIES_ATTACHED_TO_PED+2];
int nExclusions = 0;
CEntity* pExcludeEntity = NULL;
if( bIncludeEntity )
pExcludeEntity = pEntity;
GeneratePhysExclusionList(exclusionList, nExclusions, MAX_NUM_ENTITIES_ATTACHED_TO_PED, nTestTypes,TYPE_FLAGS_ALL, pExcludeEntity);
// exclude ourselves
exclusionList[nExclusions++] = this;
// exclude the entity we are testing
if(bIncludeEntity)
exclusionList[nExclusions++] = pEntity;
// const CEntity* testPedExcludeList[2];
// testPedExcludeList[0] = this; // exclude ourselves and our attachments
// testPedExcludeList[1] = pEntity; // exclude the entity we are testing
// if( WorldProbe::TestPed(&mat, testPedExcludeList, 2, nTestTypes)
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()), mat.d);
probeDesc.SetIncludeFlags(nTestTypes);
probeDesc.SetExcludeEntities(exclusionList, nExclusions);
probeDesc.SetContext(WorldProbe::LOS_Weapon);
#if DEBUG_DRAW
static bool sbDrawDebug = false;
if (sbDrawDebug)
{
grcDebugDraw::Axis(RCC_MAT34V(mat), 0.2f, true);
grcDebugDraw::Sphere(RCC_VEC3V(mat.d), 0.2f, Color_red, false);
}
#endif
if( CPedGeometryAnalyser::TestPedCapsule(this, &mat, exclusionList, nExclusions, nTestTypes)
|| WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc) )
{
return false;
}
return true;
}
bool CPed::CalculateDetachPosition( DetachPosition detachPos, const Matrix34& mEnterCarMat, Matrix34& mDetachPos )
{
CVehicle* pVehicle = (CVehicle*)GetAttachParentForced();
const float fSafePedRadius = m_pCapsuleInfo->GetHalfWidth() + 0.2f;
const Vector3 vecVehiclePosition(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition()));
Vector3 vXOffsetFromCenter = mEnterCarMat.d - vecVehiclePosition;
Vector3 VehRightVector(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetA()));
float fDot = DotProduct(VehRightVector, vXOffsetFromCenter);
float fMult = 1.0f;
if( fDot < 0.0f )
{
fMult = -1.0f;
}
vXOffsetFromCenter = (fMult * ( pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().x+fSafePedRadius) ) * VehRightVector;
mDetachPos = mEnterCarMat;
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_BACK_LOWER_Z_HEIGHT, 0.25f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_FRONT_LOWER_Z_HEIGHT, 0.25f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_BACKWARDS_DIST, 2.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_BACKWARDS_DIST_INC, 1.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_BACKWARDS_LOWER_Z_HEIGHT, 0.25f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_FORWARDS_DIST, 2.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_FORWARDS_DIST_INC, 1.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_FORWARDS_LOWER_Z_HEIGHT, 0.25f, 0.0f, 1.0f, 0.01f);
switch(detachPos)
{
case DP_ByDoor:
// Matrix already set as enter car mat
break;
case DP_Side1:
if (pVehicle->GetLayoutInfo()->GetClimbUpAfterOpenDoor())
{
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_SIDE_1_SHAMAL_SIDE_OFFSET, 1.0f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(VEHICLE_TUNE, DETACH_SIDE_1_SHAMAL_UP_OFFSET, 0.25f, 0.0f, 1.0f, 0.01f);
mDetachPos.d = mEnterCarMat.d;
mDetachPos.d -= DETACH_SIDE_1_SHAMAL_SIDE_OFFSET * VehRightVector;
mDetachPos.d.z += DETACH_SIDE_1_SHAMAL_UP_OFFSET;
}
else
{
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += ( pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y * 0.5f ) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d += vXOffsetFromCenter;
}
break;
case DP_Side2:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().y * 0.5f ) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d += vXOffsetFromCenter;
break;
case DP_Side3:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y * 1.0f ) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d += vXOffsetFromCenter;
break;
case DP_Side4:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().y * 1.0f ) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d += vXOffsetFromCenter;
break;
case DP_OtherDoor:
mDetachPos.d -= 2.0f * vXOffsetFromCenter;
break;
case DP_Front:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().y + fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
break;
case DP_Back:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y - fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
break;
case DP_FrontCorner:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().y + fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d -= vXOffsetFromCenter;
break;
case DP_BackCorner:
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y - fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d -= vXOffsetFromCenter;
break;
case DP_Roof:
mDetachPos.d.Zero();
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().z + 1.0f) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC());
mDetachPos.d = pVehicle->TransformIntoWorldSpace(mDetachPos.d);
break;
case DP_SafePosNavmesh:
{
const Vector3 vStartPos = mEnterCarMat.d + Vector3(0.0f,0.0f,2.0f);
Vector3 vNewPos;
if( CPathServer::GetClosestPositionForPed(vStartPos,vNewPos,40.0f,GetClosestPos_ClearOfObjects|GetClosestPos_ExtraThorough) != ENoPositionFound)
{
mDetachPos.d = vNewPos;
return true;
}
return false;
}
case DP_SafePosNavmeshLargeRadis:
{
const Vector3 vStartPos = mEnterCarMat.d + Vector3(0.0f,0.0f,2.0f);
Vector3 vNewPos;
if( CPathServer::GetClosestPositionForPed(vStartPos,vNewPos,100.0f,GetClosestPos_ClearOfObjects|GetClosestPos_ExtraThorough) != ENoPositionFound)
{
mDetachPos.d = vNewPos;
return true;
}
return false;
}
case DP_SafePosCarNodes:
{
Vector3 vNewPos;
CNodeAddress NearestNode = ThePaths.FindNodeClosestToCoors(mDetachPos.d, 99999.9f); //vDestination);
if (!NearestNode.IsEmpty())
{
Vector3 nearestCoors;
ThePaths.FindNodePointer(NearestNode)->GetCoors(nearestCoors);
mDetachPos.d = nearestCoors;
return true;
}
return false;
}
case DP_BackLower:
if (!NetworkInterface::IsGameInProgress())
{
return false;
}
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z - DETACH_BACK_LOWER_Z_HEIGHT;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y - fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
break;
case DP_ForwardLower:
if (!NetworkInterface::IsGameInProgress())
{
return false;
}
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z - DETACH_FRONT_LOWER_Z_HEIGHT;
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMax().y + fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
break;
case DP_Back1:
case DP_Back2:
case DP_Back3:
{
if (!pVehicle->InheritsFromQuadBike() && !pVehicle->InheritsFromAmphibiousQuadBike())
{
return false;
}
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z - DETACH_BACK_LOWER_Z_HEIGHT;
s32 iBackIncrement = detachPos - DP_Back1;
float fBackwardsScale = DETACH_BACKWARDS_DIST + static_cast<float>(iBackIncrement) * DETACH_BACKWARDS_DIST_INC;
mDetachPos.d -= fBackwardsScale * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y - fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
}
break;
case DP_Forward1:
case DP_Forward2:
case DP_Forward3:
{
if (!pVehicle->InheritsFromQuadBike() && !pVehicle->InheritsFromAmphibiousQuadBike())
{
return false;
}
mDetachPos.d = vecVehiclePosition;
mDetachPos.d.z = mEnterCarMat.d.z - DETACH_FORWARDS_LOWER_Z_HEIGHT;
s32 iForwardsIncrement = detachPos - DP_Forward1;
float fForwardsScale = DETACH_FORWARDS_DIST + static_cast<float>(iForwardsIncrement) * DETACH_FORWARDS_DIST_INC;
mDetachPos.d += fForwardsScale * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
mDetachPos.d += (pVehicle->GetVehicleModelInfo()->GetBoundingBoxMin().y - fSafePedRadius) * VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB());
}
break;
default:
Assert(0);
break;
}
return true;
}
dev_float EXTRA_Z_TO_AVOID_GROUND_INTERSECTIONS = 0.5f;
dev_float EXTRA_Z_FOR_BOAT = 2.0f;
dev_float MAX_DIST_FOR_GROUND_REPOSITION = 2.0f;
dev_float MAX_DIST_FOR_GROUND_REPOSITION_PLANE = 30.0f;
dev_float PROBE_LENGTH_FOR_GROUND_CHECK = 2.5f;
dev_float PROBE_LENGTH_FOR_GROUND_CHECK_PLANE = 12.0f;
void CPed::DetachFromCarInSafePosition(u16 nDetachFlags)
{
// Dont bother with all this stuff if a ped is being deleted
if( GetPedConfigFlag( CPED_CONFIG_FLAG_PedBeingDeleted ) )
return;
// or if the vehicle is being removed
if(GetPedConfigFlag(CPED_CONFIG_FLAG_ParentCarIsBeingRemoved))
{
// clear this status
SetPedConfigFlag(CPED_CONFIG_FLAG_ParentCarIsBeingRemoved, false);
return;
}
if(GetAttachParentForced() && ((CPhysical *) GetAttachParentForced())->GetIsTypeVehicle())
{
CVehicle* pVehicle = (CVehicle*)GetAttachParentForced();
bool bUpsideDownBoat = pVehicle->GetVehicleType() == VEHICLE_TYPE_BOAT && pVehicle->GetTransform().GetC().GetZf() < 0.0f;
const CEntryExitPoint* pEntryExitPoint = NULL;
// Some vehicles don't have valid entry points (rear seats of low vehicles) so just use the dirvers entrypoint
if(!pVehicle->IsEntryIndexValid(m_nAttachCarEntryExitPoint))
m_nAttachCarEntryExitPoint = 0;
pEntryExitPoint = ((CVehicleModelInfo*)pVehicle->GetBaseModelInfo())->GetModelSeatInfo()->GetEntryExitPoint(m_nAttachCarEntryExitPoint);
Matrix34 newMatrix = MAT34V_TO_MATRIX34(GetMatrix());
if(!(nDetachFlags & DETACH_FLAG_DONT_SNAP_MATRIX_UPRIGHT))
{
// make the peds's matrix vertical again.
newMatrix.MakeRotateZ(GetTransform().GetHeading());
}
const float fExtraZToAvoidGroundIntersections = pVehicle->GetVehicleType() == VEHICLE_TYPE_SUBMARINE && pVehicle->GetIsInWater() ? 0.0f : EXTRA_Z_TO_AVOID_GROUND_INTERSECTIONS;
newMatrix.d.z += fExtraZToAvoidGroundIntersections;
const bool bDoCurrentPositionCheck = !(nDetachFlags&DETACH_FLAG_SKIP_CURRENT_POSITION_CHECK);
bool bIsCurrentPositionClear = false;
if (bDoCurrentPositionCheck)
{
const bool bIncludeVehicle = !(nDetachFlags&DETACH_FLAG_EXCLUDE_VEHICLE);
bIsCurrentPositionClear = CModelSeatInfo::IsPositionClearForPed(this, (CPhysical*)GetAttachParentForced(), newMatrix, !bIncludeVehicle, -fExtraZToAvoidGroundIntersections);
}
#if __BANK
CVehicleDebug::RenderTestCapsuleAtPosition(this, RCC_VEC3V(newMatrix.d), RCC_VEC3V(newMatrix.c), bIsCurrentPositionClear ? Color_green : Color_red, 1000, *pVehicle, -fExtraZToAvoidGroundIntersections);
CTask::ms_debugDraw.AddLine(RCC_VEC3V(newMatrix.d), pVehicle->GetTransform().GetPosition(), bIsCurrentPositionClear ? Color_green : Color_red, 1000);
#endif // __BANK
// Test to see if the root bone position has given a safe result
if(pVehicle->GetSkeleton() != NULL &&
(bUpsideDownBoat || !bIsCurrentPositionClear) &&
taskVerifyf(m_nAttachCarEntryExitPoint >= 0, "Trying to detach a ped from vehicle (%s) without a valid exit/entry point attachment!", pVehicle->GetDebugName()))
{
DetachPosition startPosition = DP_Side1;
// Yuck, needs reworking when entrypoint evaulation gets redone
Vector3 vEntryPos(Vector3::ZeroType);
Quaternion qEntryRot(0.0f, 0.0f, 0.0f, 1.0f);
bool bComputedDesiredDetachPosition = false;
if (nDetachFlags & DETACH_FLAG_USE_UPSIDE_DOWN_POINT)
{
bComputedDesiredDetachPosition = CTaskVehicleFSM::ComputeTargetTransformInWorldSpace(*pVehicle, vEntryPos, qEntryRot, m_nAttachCarEntryExitPoint, CExtraVehiclePoint::UPSIDE_DOWN_EXIT);
}
if (!bComputedDesiredDetachPosition)
{
CModelSeatInfo::CalculateEntrySituation(pVehicle, this, vEntryPos, qEntryRot, m_nAttachCarEntryExitPoint);
}
// If pVehicle is getting destroyed, we cannot make the virtual call to IsInAir
if (!(nDetachFlags & DETACH_FLAG_IN_PHYSICAL_DESTRUCTOR))
{
const CVehicleEntryPointAnimInfo* pAnimEntryInfo = pVehicle->GetEntryAnimInfo(m_nAttachCarEntryExitPoint);
if(pAnimEntryInfo && pVehicle->GetIsAircraft() && pVehicle->IsInAir() && !bDoCurrentPositionCheck)
{
vEntryPos.z += pAnimEntryInfo->GetExtraZForMPPlaneWarp();
}
}
if(bUpsideDownBoat)
{
vEntryPos.z -= EXTRA_Z_FOR_BOAT;
}
newMatrix.d = vEntryPos;
Vector3 vEulers(Vector3::ZeroType);
qEntryRot.ToEulers(vEulers, "xyz");
taskAssertf(rage::FPIsFinite(vEulers.z), "Heading computed is invalid");
float fAttachedHeading = fwAngle::LimitRadianAngle(vEulers.z);
if( pEntryExitPoint )
{
int iSeatIndex = -1;
if (pEntryExitPoint->GetSeatAccessor().GetSeatAccessType() == CSeatAccessor::eSeatAccessTypeMultiple)
{
// Hack for tourbus, for now use the first seat
iSeatIndex = pEntryExitPoint->GetSeatAccessor().GetSeatByIndex(0);
}
else
{
iSeatIndex = pEntryExitPoint->GetSeat(SA_directAccessSeat);
}
Assert(iSeatIndex != -1);
int iBone = pVehicle->GetVehicleModelInfo()->GetModelSeatInfo()->GetBoneIndexFromSeat(iSeatIndex);
if(iBone!=-1)
{
startPosition = DP_ByDoor;
Matrix34 localSeatMatrix = pVehicle->GetLocalMtx(iBone);
Vector3 vPos = localSeatMatrix.d;
vPos = pVehicle->TransformIntoWorldSpace(vPos);
if( newMatrix.d.z > (vPos.z+1.0f) )
newMatrix.d.z = (vPos.z+1.0f);
}
if (PopTypeGet() == POPTYPE_MISSION || GetPedConfigFlag(CPED_CONFIG_FLAG_TeleportToLeaderVehicle))
{
// If target entry point is blocked then we're safety warping
if(!CModelSeatInfo::IsPositionClearForPed(this, (CPhysical*)GetAttachParentForced(), newMatrix))
{
// To get mission peds warping out slightly closer to where their seats are, skip some DPs depending on entry point side
CVehicleEntryPointInfo::eSide eVehicleSide = pVehicle->GetEntryInfo(m_nAttachCarEntryExitPoint) ? pVehicle->GetEntryInfo(m_nAttachCarEntryExitPoint)->GetVehicleSide() : CVehicleEntryPointInfo::SIDE_LEFT;
if (eVehicleSide == CVehicleEntryPointInfo::SIDE_RIGHT)
{
// Start at front right
startPosition = DP_Side2;
}
else if (eVehicleSide == CVehicleEntryPointInfo::SIDE_REAR)
{
// Start at rear left
startPosition = DP_Side3;
}
}
}
}
// Raise the matrix up slightly to avoid collision intersecting with the ground on a slope
newMatrix.d.z += fExtraZToAvoidGroundIntersections;
// Titan/Shamal Are huge, if they land on their side, don't want to warp into air, B*1187243
const float fMaxDistForGroundReposition = pVehicle->InheritsFromPlane() ? MAX_DIST_FOR_GROUND_REPOSITION_PLANE : MAX_DIST_FOR_GROUND_REPOSITION;
const float fGroundProbeLength = pVehicle->InheritsFromPlane() ? PROBE_LENGTH_FOR_GROUND_CHECK_PLANE : PROBE_LENGTH_FOR_GROUND_CHECK;
bool bSafePlaceFound = false;
for( s32 i = startPosition; i < DP_Max; i++ )
{
Matrix34 mSafePos;
if( CalculateDetachPosition( (DetachPosition)i, newMatrix, mSafePos) )
{
static dev_bool RENDER_POSITIONS = false;
if( i >= DP_SafePosNavmesh || ( ( i != DP_ByDoor || !bUpsideDownBoat)) )
{
const bool bIsClear = CModelSeatInfo::IsPositionClearForPed(this, (CPhysical*)GetAttachParentForced(), mSafePos);
#if __BANK
CVehicleDebug::RenderTestCapsuleAtPosition(this, RCC_VEC3V(mSafePos.d), RCC_VEC3V(mSafePos.c), bIsClear ? Color_green : Color_red, 1000, *pVehicle);
CTask::ms_debugDraw.AddLine(RCC_VEC3V(mSafePos.d), pVehicle->GetTransform().GetPosition(), bIsClear ? Color_green : Color_red, 1000);
#endif // __BANK
if(bIsClear && !bSafePlaceFound )
{
m_MotionData.SetCurrentHeading(fAttachedHeading);
m_MotionData.SetDesiredHeading(fAttachedHeading);
Vector3 vTestCoords = mSafePos.d;
float fGroundZ = vTestCoords.z;
Vector3 vGroundPos(Vector3::ZeroType);
if (CTaskVehicleFSM::FindGroundPos(pVehicle, this, vTestCoords, fGroundProbeLength, vGroundPos, false, true))
{
fGroundZ = vGroundPos.z - PED_HUMAN_GROUNDTOROOTOFFSET;
}
// Try to find the ground at this position, if its relatively close reposition so the ped is on the ground
const CBaseCapsuleInfo* pBaseCapsuleInfo = GetCapsuleInfo();
if (aiVerifyf(pBaseCapsuleInfo, "Couldn't find capsule info for ped %s", GetDebugName()))
{
const CBipedCapsuleInfo* pCapsuleInfo = pBaseCapsuleInfo->GetBipedCapsuleInfo();
if (pCapsuleInfo)
{
const float fGroundToRootOffset = pCapsuleInfo->GetGroundToRootOffset();
#if __DEV
Vector3 vOriginalPos = mSafePos.d;
Vector3 vGroundPos = Vector3(mSafePos.d.x, mSafePos.d.y, fGroundZ);
Vector3 vAdjustedPos = Vector3(mSafePos.d.x, mSafePos.d.y, fGroundZ + fGroundToRootOffset);
CTask::ms_debugDraw.AddSphere(RCC_VEC3V(vOriginalPos), 0.025f, Color_red, 2000);
CTask::ms_debugDraw.AddSphere(RCC_VEC3V(vGroundPos), 0.025f, Color_blue, 2000);
CTask::ms_debugDraw.AddSphere(RCC_VEC3V(vAdjustedPos), 0.025f, Color_green, 2000);
#endif // __DEV
if (Abs(mSafePos.d.z - fGroundToRootOffset - fGroundZ) < fMaxDistForGroundReposition)
{
mSafePos.d.z = fGroundZ + fGroundToRootOffset;
}
}
}
// finally set the desired matrix
SetMatrix(mSafePos, true, true, true);
GetPortalTracker()->RequestRescanNextUpdate();
bSafePlaceFound = true;
if( !RENDER_POSITIONS ){break;}
}
}
}
}
#if __ASSERT
if (!bSafePlaceFound)
aiWarningf("Couldn't find a safe place to place ped out of vehicle");
#endif
}
else
{
// need to use the new vertical matrix, or we may be leaving the ped in a non-vertical orientation
// if distance to new matrix is too great, also use warp
newMatrix.d.z -= fExtraZToAvoidGroundIntersections;
if(GetTransform().GetC().GetZf() < 0.0f || (newMatrix.d - VEC3V_TO_VECTOR3(GetTransform().GetPosition())).Mag2() > 10.0f)
SetMatrix(newMatrix, true, true, true);
else
SetMatrix(newMatrix);
}
}
}
atHashString CPed::GetPedAudioID(u8 slotId)
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
Assert(pModelInfo);
u32 compVar = CPedVariationPack::GetCompVar(this, (ePedVarComp)slotId);
if (!pModelInfo->GetVarInfo())
return atHashString::Null();
return(pModelInfo->GetVarInfo()->LookupCompInfoAudioID((ePedVarComp)slotId, compVar, false));
}
atHashString CPed::GetSecondPedAudioID(u8 slotId)
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
Assert(pModelInfo);
u32 compVar = CPedVariationPack::GetCompVar(this, (ePedVarComp)slotId);
if (!pModelInfo->GetVarInfo())
return atHashString::Null();
return(pModelInfo->GetVarInfo()->LookupCompInfoAudioID((ePedVarComp)slotId, compVar, true));
}
bool CPed::GetBonePosition(Vector3& posn, eAnimBoneTag boneTag) const
{
// This function uses either this frames global bone matrix or last frames global bone matrix
// depending on when in the frame it is called
// Global matrices aren't guaranteed to have been updated last frame either!
bool rv = false;
const crSkeleton* pSkeleton = GetSkeleton();
Assertf(pSkeleton, "CPed::GetBonePosition - Invalid skeleton");
if(pSkeleton)
{
int boneIndex = GetBoneIndexFromBoneTag(boneTag);
//Assertf(boneIndex!=-1), "CPed::GetBonePosition - BoneTag does not exist for this skeleton";
if (boneIndex!=-1)
{
Matrix34 globalBoneMat;
GetGlobalMtx(boneIndex, globalBoneMat);
posn = globalBoneMat.d;
rv = true;
}
else
{
posn = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
}
}
else
{
posn = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
}
return rv;
}
bool CPed::GetBonePositionVec3V(Vec3V_InOut posn, eAnimBoneTag boneTag) const
{
// This function uses either this frames global bone matrix or last frames global bone matrix
// depending on when in the frame it is called
// Global matrices aren't guaranteed to have been updated last frame either!
bool rv = false;
const crSkeleton* pSkeleton = GetSkeleton();
Assertf(pSkeleton, "CPed::GetBonePosition - Invalid skeleton");
if(pSkeleton)
{
int boneIndex = GetBoneIndexFromBoneTag(boneTag);
//Assertf(boneIndex!=-1), "CPed::GetBonePosition - BoneTag does not exist for this skeleton";
if (boneIndex!=-1)
{
Matrix34 globalBoneMat;
GetGlobalMtx(boneIndex, globalBoneMat);
posn = VECTOR3_TO_VEC3V( globalBoneMat.d );
rv = true;
}
else
{
posn = GetTransform().GetPosition();
}
}
else
{
posn = GetTransform().GetPosition();
}
return rv;
}
bool CPed::GetBoneMatrix(Matrix34& matResult, eAnimBoneTag boneTag) const
{
// This function uses either this frames global bone matrix or last frames global bone matrix
// depending on when in the frame it is called
// Global matrices aren't guaranteed to have been updated last frame either!
bool rv = false;
const crSkeleton* pSkeleton = GetSkeleton();
Assertf(pSkeleton, "CPed::GetBoneMatrix - Invalid skeleton");
if(pSkeleton)
{
int boneIndex = GetBoneIndexFromBoneTag(boneTag);
//Assert(boneIndex!=-1);
if (boneIndex!=-1)
{
Matrix34 globalBoneMat;
GetGlobalMtx(boneIndex, globalBoneMat);
matResult = globalBoneMat;
rv = true;
}
else
{
GetMatrixCopy(matResult);
}
}
else
{
GetMatrixCopy(matResult);
}
return rv;
}
Vec3V_Out CPed::GetBonePositionCachedV(eAnimBoneTag boneTag) const
{
Vec3V vOffset;
switch(boneTag)
{
case BONETAG_L_FOOT:
vOffset = m_vecOffsets[kOffsetLeftFoot];
break;
case BONETAG_R_FOOT:
vOffset = m_vecOffsets[kOffsetRightFoot];
break;
case BONETAG_HEAD:
vOffset = m_vecOffsets[kOffsetHead];
break;
case BONETAG_L_HAND:
vOffset = m_vecOffsets[kOffsetLeftHand];
break;
case BONETAG_R_HAND:
vOffset = m_vecOffsets[kOffsetRightHand];
break;
case BONETAG_NECK:
vOffset = m_vecOffsets[kOffsetNeck];
break;
case BONETAG_SPINE1:
vOffset = m_vecOffsets[kOffsetSpine1];
break;
case BONETAG_L_PH_FOOT:
vOffset = m_vecOffsets[kOffsetPHLeftFoot];
break;
case BONETAG_R_PH_FOOT:
vOffset = m_vecOffsets[kOffsetPHRightFoot];
break;
default:
pedAssertf(false, "Bone position not cached.");
vOffset = Vec3V(V_ZERO);
}
return Transform(*m_MatrixTransformPtr, vOffset);
}
bool CPed::GetBoneMatrixCached(Matrix34& matResult, eAnimBoneTag boneTag)const
{
switch(boneTag)
{
case BONETAG_HEAD:
matResult.d = RCC_VECTOR3(m_vecOffsets[kOffsetHead]);
matResult.FromQuaternion(RCC_QUATERNION(m_rotOffsets[kRotOffsetHead]));
break;
#if GTA_REPLAY
case BONETAG_L_PH_FOOT:
matResult.d = RCC_VECTOR3(m_vecOffsets[kOffsetPHLeftFoot]);
matResult.FromQuaternion(RCC_QUATERNION(m_rotOffsets[kRotOffsetPHLeftFoot]));
break;
case BONETAG_R_PH_FOOT:
matResult.d = RCC_VECTOR3(m_vecOffsets[kOffsetPHRightFoot]);
matResult.FromQuaternion(RCC_QUATERNION(m_rotOffsets[kRotOffsetPHRightFoot]));
break;
#endif
default:
pedAssertf(false, "Bone matrix not cached.");
matResult.Identity();
}
const Matrix34& mPedMat = RCC_MATRIX34(GetMatrixRef());
matResult.Dot(mPedMat);
return true;
}
void CPed::GetBoneMatrixFromRagdollComponent(Matrix34& matResult, int nComponent) const
{
if(GetRagdollInst() && GetRagdollInst()->GetCached())
{
fragTypeChild* child = GetRagdollInst()->GetTypePhysics()->GetAllChildren()[nComponent];
s16 boneIndex = (s16) GetRagdollInst()->GetType()->GetBoneIndexFromID(child->GetBoneID());
Assert(boneIndex!=-1);
GetGlobalMtx(boneIndex, matResult);
}
else
GetMatrixCopy(matResult);
}
bool CPed::GetRagdollComponentMatrix(Matrix34& matResult, int nComponent) const
{
if(GetRagdollInst() && GetRagdollInst()->GetCached())
{
//if(GetRagdollInst()->GetPosition().Mag2() > 1.0f)
if(IsGreaterThanAll(MagSquared(GetRagdollInst()->GetMatrix().GetCol3()), ScalarV(V_ONE)))
{
#if __DEV && __ASSERT && LAZY_RAGDOLL_BOUNDS_UPDATE
if (!AreRagdollBoundsUpToDate())
{
Assertf(AreRagdollBoundsUpToDate(), "CPed::GetRagdollComponentMatrix requested a ped bound position without the bounds being up to date. Call CPed::RequestRagdollBoundsUpdate() a frame prior to needing up to query bound positions for animated AI peds.");
SpewRagdollTaskInfo();
}
#endif
phBoundComposite* pRagdollBound = (phBoundComposite*)GetRagdollInst()->GetArchetype()->GetBound();
Assertf(nComponent >= 0 && nComponent < pRagdollBound->GetNumActiveBounds(), "CPed::GetRagdollComponentMatrix - bad component. input component is %d. Num active bounds is %d.", nComponent, pRagdollBound->GetNumActiveBounds());
matResult = RCC_MATRIX34(pRagdollBound->GetCurrentMatrix(nComponent));
matResult.Dot(RCC_MATRIX34(GetRagdollInst()->GetMatrix()));
return true;
}
else
{
#if __ASSERT
const char *modelName = GetBaseModelInfo()->GetModelName();
modelName = modelName ? modelName : "Unknown";
char pedName[256];
safecpy(pedName, modelName, 256);
if(GetNetworkObject())
{
safecpy(pedName, GetNetworkObject()->GetLogName(), 256);
}
Assertf(false, "Ragdoll inst matrix is near zero (%s: %f, %f, %f)", pedName, GetRagdollInst()->GetMatrix().GetM03f(), GetRagdollInst()->GetMatrix().GetM13f(), GetRagdollInst()->GetMatrix().GetM23f());
#endif
}
}
GetMatrixCopy(matResult);
return false;
}
#if USE_TARGET_SEQUENCES
CTargetSequenceHelper* CPed::FindActiveTargetSequence() const
{
if (GetPlayerInfo())
{
CTargetSequenceHelper& helper = GetPlayerInfo()->GetTargeting().GetTargetSequenceHelper();
if (helper.HasSequence())
{
return &helper;
}
}
return NULL;
}
#endif // USE_TARGET_SEQUENCES
void CPed::HandleFacialAnimEvent( const crProperty* pProp )
{
static const crProperty::Key facialKey = crProperty::CalcKey("Facial",0x511E56C2);
static const crProperty::Key nameIdKey = crProperty::CalcKey("NameId",0x804A1E4A);
static const crProperty::Key isClipNameKey = crProperty::CalcKey("IsClipName",0x0C905639);
CFacialDataComponent* pFacialData = GetFacialData();
if ( pFacialData && pProp && pProp->GetKey() == facialKey && GetAnimDirector())
{
crPropertyAttributeHashStringAccessor nameAccessor(pProp->GetAttribute(nameIdKey));
crPropertyAttributeBoolAccessor isClipNameAccessor(pProp->GetAttribute(isClipNameKey));
if(nameAccessor.Valid())
{
int nameHashKey = nameAccessor.GetPtr()->GetHash();
bool bIsClipName;
isClipNameAccessor.Get(bIsClipName);
if (bIsClipName)
{
// Clip
fwMvClipId clipToPlay(nameHashKey);
pFacialData->PlayFacialAnim(this, clipToPlay);
}
else
{
// ClipVariationSet
fwMvClipVariationSetId clipVarSetId(nameHashKey);
fwClipVariationSet* pClipVarSet = fwClipSetManager::GetClipVariationSet(clipVarSetId);
if (pClipVarSet)
{
fwMvClipId randomClipId;
if (pClipVarSet->PickRandomClip(randomClipId))
{
pFacialData->PlayFacialAnim(this, randomClipId);
}
else
{
Assertf(0, "Failed to pick a random clip from clip variation set - '%s'", clipVarSetId.TryGetCStr());
}
}
else
{
Assertf(0, "Could not find clip variation set - '%s'", clipVarSetId.TryGetCStr());
}
}
}
}
}
dev_bool TRAIL_RETICULE_BEHIND_ALL_TARGETS = false;
dev_bool TRAIL_RETICULE_BEHIND_PLAYER_TARGETS = false;
dev_float MAX_TRAIL_RETICULE_DISTANCE = -0.5f;
dev_float TRAIL_MININUM_SPEED = 1.0f;
dev_float TRAIL_MAXIMUM_SPEED = 3.0f;
dev_float MIN_DISTANCE_TO_OFFSET = 5.0f;
dev_float MAX_DISTANCE_TO_OFFSET = 15.0f;
void CPed::GetPlayerLockOnTargetVelocityOffset(Vector3& aimAtPos, const Vector3& vPlayerPos) const
{
aimAtPos.Zero();
// Add on a distance behind the ped to trail bullets, depending on the speed
if( TRAIL_RETICULE_BEHIND_ALL_TARGETS || (IsAPlayerPed() && TRAIL_RETICULE_BEHIND_PLAYER_TARGETS) )
{
Vector3 vDiff = aimAtPos-vPlayerPos;
float fDist = vDiff.Mag();
if( fDist < MIN_DISTANCE_TO_OFFSET )
return;
float fDistanceScale = MIN((fDist-MIN_DISTANCE_TO_OFFSET)/(MAX_DISTANCE_TO_OFFSET-MIN_DISTANCE_TO_OFFSET), 1.0f);
Vector3 vSpeed = GetVelocity();
float fSpeedMag = vSpeed.Mag();
if( fSpeedMag > TRAIL_MININUM_SPEED )
{
float fTrailDist = MIN((fSpeedMag-TRAIL_MININUM_SPEED)/(TRAIL_MAXIMUM_SPEED-TRAIL_MININUM_SPEED), 1.0f);
vSpeed.Normalize();
aimAtPos = vSpeed * (fTrailDist * fDistanceScale* MAX_TRAIL_RETICULE_DISTANCE);
}
}
}
void CPed::GetLockOnTargetAimAtPos(Vector3& aimAtPos) const
{
// In order to avoid passing in a new param through the virtual interface we decided to check against
// the local player ability.
// NOTE: If we decide that AI is allowed to use this ability, passing in a flag or setting a flag on
// the targeted ped would be a better solution.
CPed* pLocalPlayer = CGameWorld::FindLocalPlayer();
CPlayerSpecialAbility* pSpecialAbility = pLocalPlayer ? pLocalPlayer->GetSpecialAbility() : NULL;
if( GetPedResetFlag( CPED_RESET_FLAG_ForceAimAtHead ) || ( pSpecialAbility && pSpecialAbility->GetType() == SAT_SNAPSHOT && pSpecialAbility->IsActive() ) )
{
aimAtPos = VEC3V_TO_VECTOR3(GetBonePositionCachedV(BONETAG_HEAD));
}
else
{
// Aim at the position of the dummy ped between the neck and the spine 1
if (GetUsingRagdoll())
{
Matrix34 mat(M34_IDENTITY);
GetRagdollComponentMatrix(mat, RAGDOLL_SPINE2);
aimAtPos = mat.d;
}
else
{
// Get the cached bone offsets directly rather than going through GetBonePositionCachedV().
// It's more efficient to transform the result after doing the lerp than to transform the
// two bone positions.
const Vec3V localPositionNeckBoneV = m_vecOffsets[kOffsetNeck];
const Vec3V localPositionSpine1BoneV = m_vecOffsets[kOffsetSpine1];
// For reference, we used to have these
// TUNE_GROUP_FLOAT(PLAYER_TARGETING, fInVehicleTargetingNeckWeight, 0.9f, 0.0f, 1.0f, 0.01f);
// TUNE_GROUP_FLOAT(PLAYER_TARGETING, fInVehicleTargetingSpineWeight, 0.1f, 0.0f, 1.0f, 0.01f);
// TUNE_GROUP_FLOAT(PLAYER_TARGETING, fTargetingNeckWeight, 0.6f, 0.0f, 1.0f, 0.01f);
// TUNE_GROUP_FLOAT(PLAYER_TARGETING, fTargetingSpineWeight, 0.4f, 0.0f, 1.0f, 0.01f);
// but now we load all the constants into a vector registers in a single operation,
// for improved performance:
const Vec4V weightConstantsV = Vec4VConstant<
FLOAT_TO_INT(0.9f),
FLOAT_TO_INT(0.1f),
FLOAT_TO_INT(0.6f),
FLOAT_TO_INT(0.4f)>();
#if FPS_MODE_SUPPORTED
bool bPlayerUsingFirstPersonCameraMelee = false;
if( pLocalPlayer && pLocalPlayer->GetPlayerInfo() && pLocalPlayer->GetPlayerInfo()->GetTargeting().GetLockOnTarget()==this &&
pLocalPlayer->GetWeaponManager() && pLocalPlayer->GetWeaponManager()->GetEquippedWeaponInfo() )
{
const camFirstPersonShooterCamera* fpsCamera = camInterface::GetGameplayDirector().GetFirstPersonShooterCamera();
if(fpsCamera)
{
const CWeaponInfo* pWeaponInfo = pLocalPlayer->GetWeaponManager()->GetEquippedWeaponInfo();
bPlayerUsingFirstPersonCameraMelee = pWeaponInfo->GetIsMelee();
}
}
#endif
ScalarV neckWeightV, spineWeightV;
if(GetIsInVehicle() && !GetIsEnteringVehicle() && !IsExitingVehicle())
{
neckWeightV = weightConstantsV.GetX();
spineWeightV = weightConstantsV.GetY();
}
#if FPS_MODE_SUPPORTED
else if(bPlayerUsingFirstPersonCameraMelee)
{
neckWeightV = weightConstantsV.GetX();
spineWeightV = weightConstantsV.GetY();
}
#endif
else
{
neckWeightV = weightConstantsV.GetZ();
spineWeightV = weightConstantsV.GetW();
}
// Perform the lerp, used to be this:
// (fNeckWeight * globalPositionNeckBone) + (fSpineWeight * globalPositionSpine1Bone);
const Vec3V localAimAtPosV = AddScaled(Scale(localPositionNeckBoneV, neckWeightV), localPositionSpine1BoneV, spineWeightV);
// Transform to world space.
aimAtPos = VEC3V_TO_VECTOR3(Transform(*m_MatrixTransformPtr, localAimAtPosV));
}
}
}
#if DR_RECORD_PED_FLAGS
extern const char* parser_ePedConfigFlags_Strings[];
extern const char* parser_ePedResetFlags_Strings[];
void CPed::SetPedConfigFlag(const enum ePedConfigFlags flag, bool value)
{
debugPlayback::RecordPedFlag(GetCurrentPhysicsInst(), parser_ePedConfigFlags_Strings[ flag ], value);
m_PedConfigFlags.SetFlag( flag, value );
}
void CPed::SetPedResetFlag(const enum ePedResetFlags flag, bool value)
{
debugPlayback::RecordPedFlag(GetCurrentPhysicsInst(), parser_ePedResetFlags_Strings[ flag ], value);
m_PedResetFlags.SetFlag( flag, value );
}
#endif
bool CPed::GetBlockingOfNonTemporaryEventsThisFrame() const
{
//Check if the flag is set.
if(ms_bRandomPedsBlockingNonTempEventsThisFrame)
{
//Check if the ped is random (and not law-enforcement, as requested by script).
if(PopTypeIsRandom() && !IsLawEnforcementPed())
{
return true;
}
}
return false;
}
////////////////////////////////////////////////////
// FUNCTION: ReleaseCoverPoint
// DOES: If this ped has a cover point 'reserved' we clear it here
////////////////////////////////////////////////////
void CPed::ReleaseCoverPoint()
{
if (m_pCoverPoint)
{
m_pCoverPoint->ReleaseCoverPointForPed(this);
m_pCoverPoint = NULL;
m_vLocalOffsetToCoverPoint.Zero();
}
}
void CPed::ReleaseDesiredCoverPoint()
{
m_pDesiredCoverPoint = NULL;
}
void CPed::ForceDeath(bool bRagdollDeath, CEntity* pForceInflictor, u32 uForceDamageWeapon)
{
CEntity* pInflictor = pForceInflictor != NULL ? pForceInflictor : GetWeaponDamageEntity();
if(pInflictor == NULL)
{
pInflictor = this;
}
u32 uDamageWeapon = uForceDamageWeapon != 0 ? uForceDamageWeapon : GetWeaponDamageHash();
if(uDamageWeapon == 0)
{
uDamageWeapon = WEAPONTYPE_UNARMED.GetHash();
}
// Let audio know this death is due to entering ragdoll so we don't play a long drawn out death, as this comes across as a late
// trigger and just doesn't work.
if(bRagdollDeath && m_pSpeechAudioEntity)
{
m_pSpeechAudioEntity->SetDeathIsFromEnteringRagdoll();
}
CEventDamage tempDamageEvent(pInflictor, fwTimer::GetTimeInMilliseconds(), uDamageWeapon);
CPedDamageCalculator damageCalculator(pInflictor, GetHealth()+GetArmour(), uDamageWeapon, GetWeaponDamageComponent(), false);
u32 flags = CPedDamageCalculator::DF_ForceInstantKill | CPedDamageCalculator::DF_IgnoreStatModifiers | CPedDamageCalculator::DF_IgnoreArmor | CPedDamageCalculator::DF_IgnorePedFlags;
if(CPedDamageCalculator::WasDamageFromEntityBeforeWantedLevelCleared(pInflictor, this))
{
flags |= CPedDamageCalculator::DF_KillPriorToClearedWantedLevel;
}
damageCalculator.ApplyDamageAndComputeResponse(this, tempDamageEvent.GetDamageResponseData(), flags);
Assertf(IsFatallyInjured(), "Ragdoll force death FAILED for: %s, inflictor == this: %u, WeaponDamageHash: %u, Clone: %d, Migrating: %d, Force Instant Kill: %d", GetModelName(), pInflictor == this, GetWeaponDamageHash(),
IsNetworkClone(), GetNetworkObject() != NULL && GetNetworkObject()->IsPendingOwnerChange(), tempDamageEvent.GetDamageResponseData().m_bForceInstantKill);
}
// SetDeathState
void CPed::SetDeathState( eDeathState deathState )
{
if (deathState == DeathState_Dying || deathState == DeathState_Dead)
{
ReleaseCoverPoint();
if (GetPedConfigFlag( CPED_CONFIG_FLAG_ClearRadarBlipOnDeath ))
{
RemoveBlip(BLIP_TYPE_CHAR);
}
// To prevent peds being removed "instantly" after death if we didn't see them die
TouchInFovTime(5000);
if(GetPlayerInfo())
{
GetPlayerInfo()->ResetAllPlayerLOS();
}
#if ENABLE_HORSE
if (m_pSeatManager && m_pSeatManager->GetDriver()) //If I'm mounted pop them off.
m_pSeatManager->GetDriver()->SetPedOffMount();
#endif
if(!m_bDeathTimeHasSet)
{
m_deathTime = fwTimer::GetTimeInMilliseconds();
m_bDeathTimeHasSet = true;
}
}
m_deathState = deathState;
}
bool CPed::ShouldUseInjuredMovement() const
{
if (
!IsPlayer()
&& (!PopTypeIsMission() || GetPedConfigFlag(CPED_CONFIG_FLAG_AllowMissionPedToUseInjuredMovement))
&& !GetPedConfigFlag(CPED_CONFIG_FLAG_IsHandCuffed)
#if ENABLE_DRUNK
&& !GetPedConfigFlag(CPED_CONFIG_FLAG_IsDrunk)
#endif // ENABLE_DRUNK
&& (!GetWeaponManager() || !GetWeaponManager()->GetIsArmed())
&& (!GetPedResetFlag(CPED_RESET_FLAG_MoveBlend_bMeleeTaskRunning))
&& (!GetPedResetFlag(CPED_RESET_FLAG_IsInCombat))
&& GetHealth()<m_fFatiguedHealthThreshold
)
{
return true;
}
return false;
}
void CPed::SetArrestState(eArrestState arrestState)
{
if(m_arrestState != arrestState)
{
m_arrestState = arrestState;
switch(m_arrestState)
{
case(ArrestState_Arrested):
//! Local player should disable special ability when arrested.
if(IsLocalPlayer())
{
for (int i = 0; i < PSAS_MAX; ++i)
{
if (m_specialAbilities[i] && IsValidSpecialAbilitySlot(ePlayerSpecialAbilitySlot(i)))
{
GetSpecialAbility()->Deactivate();
}
}
}
break;
default:
break;
}
}
}
bool CPed::IsPedBleeding() const
{
if (IsPlayer())
{
if (GetPlayerInfo() && GetPlayerInfo()->GetPlayerDataSprintReplenishing())
return false; // B*1044637
}
else
{
if (IsBodyPartDamaged(CPed::DAMAGED_STUN))
return false;
if (!IsBodyPartDamaged(CPed::DAMAGED_ANY_VALID))
return false;
// To allow civilians show bleed effect
if ((GetWeaponManager() == NULL || !GetWeaponManager()->GetIsArmed()) && GetHurtEndTime() == 0 && GetHealth() < GetHurtHealthThreshold())
return true;
return HasHurtStarted();
}
// Only bleed for some time
return (GetHealth() < GetHurtHealthThreshold() && GetHurtEndTime() > fwTimer::GetTimeInMilliseconds());
}
// Fairly expensive and only to be used to trigger hurt, use HasHurtStarted() to see if we are in hurtmode or not!
bool CPed::IsHurtHealthThreshold() const
{
if (!CanBeInHurt())
return false;
if( IsBodyPartDamaged(CPed::DAMAGED_LUNGS) )
{
return true;
}
// Assert(GetHealth() > GetHurtHealthThreshold());
return ((GetHealth() + m_nHealthLostScript) < GetHurtHealthThreshold());
}
bool CPed::CanBeInHurt() const
{
// Break out into can do hurt
#if !__FINAL
if (!ms_bUseHurtCombat)
return false;
#endif
if (!IsBodyPartDamaged(CPed::DAMAGED_HAS_BEEN))
return false;
if (!GetWeaponDamageEntity())
return false;
if (GetPedConfigFlag(CPED_CONFIG_FLAG_DisableHurt))
return false;
if (!ms_bAllowHurtForAllMissionPeds && PopTypeIsMission())
return false;
const CPedModelInfo* pModelInfo = GetPedModelInfo();
if (!pModelInfo ||
pModelInfo->GetIsPlayerModel() || // Player never gets hurt!
pModelInfo->GetInjuredStrafeClipSet() == CLIP_SET_ID_INVALID)
{
return false;
}
if (IsPlayer())
return false;
return true;
}
bool CPed::CanDoHurtTransition() const
{
// IsOnGround does not do as advertised so FUCK IT!
// if (!IsOnGround())
// return false;
// So this is an estimation if we are on the ground or not
if (abs(GetVelocity().z) > 0.5f)
return false;
if (GetGroundPhysical())
return false;
if (IsInOrAttachedToCar())
return false;
return true;
}
bool CPed::WillPedEnterWater(const float fMinSurvivalTime) const
{
if(GetPedConfigFlag( CPED_CONFIG_FLAG_DrownsInWater ) && m_fMaxTimeInWater <= fMinSurvivalTime)
{
return false;
}
return true;
}
void CPed::UpdatePossiblyTouchesWaterFlag()
{
if (GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
{
CVehicle *pVehicle = m_pMyVehicle;
if (pVehicle)
{
this->m_nFlags.bPossiblyTouchesWater = pVehicle->m_nFlags.bPossiblyTouchesWater;
this->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate = pVehicle->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate;
return;
}
}
CPhysical::UpdatePossiblyTouchesWaterFlag();
}
//-------------------------------------------------------------------------
// Query function for estimating the ped's pose.
//-------------------------------------------------------------------------
EstimatedPose CPed::EstimatePose(void) const
{
// Use bone position for our estimates because this will work for
// ragdolls as well as simple animation driven peds.
// Note: The matrix could be out of date so an update might be
// required (thus we tell it to force a pre-render update if necessary).
Matrix34 spineMat;
GetBoneMatrix(spineMat, BONETAG_SPINE0);
// Make sure our spine isn't in some weird orientation such as facing
// too downwards or upwards (which would mess up our simplistic
// approximations).
if(rage::Abs(spineMat.b.z) > 0.6f)
{
// Use the spines orientation to estimate the pose.
return EstimatePoseFromSpineMatrix(&spineMat);
}
// Get the height of the spine so we can compare it to the root height
// for an approximation of standing or not.
const float spineHeight = spineMat.d.z;
if(GetUsingRagdoll())
{
Matrix34 tempMat;
GetBoneMatrix(tempMat, BONETAG_L_FOOT);
const float leftFootHeight = tempMat.d.z;
GetBoneMatrix(tempMat, BONETAG_R_FOOT);
const float rightFootHeight = tempMat.d.z;
// This is a ragdoll ped
// compare the spine height to the height of each of the feet
if(leftFootHeight < spineHeight - 0.5f || rightFootHeight < spineHeight - 0.5f)
{
// We're probably just standing.
return POSE_STANDING;
}
}
else if(!IsDead())
{
// For a standing ped the default location of the mover depends on whether
// the ped is in ragdoll mode or not.
const float moverHeight = GetTransform().GetPosition().GetZf();
const float heightDiff = spineHeight - moverHeight;
// This is just a normal animated ped:
// For a normal sized standing ped the spine bone should be at about
// the same height as the root of the ped.
if(rage::Abs(heightDiff) < 0.35f)
{
// We're probably just standing.
return POSE_STANDING;
}
}
// Use the spines orientation to estimate the pose.
return EstimatePoseFromSpineMatrix(&spineMat);
}
/* -------------------------------------------------------------------------
// Types and query function for estimating the peds pose from the spine matrix
// \ R /
// ^ \ /
// A FR X BK
// | / \
// L \
//
// -B->
// ------------------------------------------------------------------------- */
EstimatedPose CPed::EstimatePoseFromSpineMatrix( const Matrix34* pSpineMatrix ) const
{
// Are we on either the Left or right hand side
if(fabsf(pSpineMatrix->a.z) > fabsf(pSpineMatrix->b.z))
{
// Which side?
if(pSpineMatrix->a.z < 0.0f)
{
return POSE_ON_RHS;
}
else
{
return POSE_ON_LHS;
}
}
// Otherwise must be on the front or back
else
{
// Front or back
if(pSpineMatrix->b.z < 0.0f)
{
return POSE_ON_FRONT;
}
else
{
return POSE_ON_BACK;
}
}
}
//-------------------------------------------------------------------------
// Estimates a bounding box from the peds final global bone matrices.
//-------------------------------------------------------------------------
spdAABB CPed::EstimateLocalBBoxFromPose( void ) const
{
if( GetRagdollInst() &&
GetRagdollInst()->GetArchetype() &&
GetRagdollInst()->GetArchetype()->GetBound() )
{
Vec3V boundBoxMin = GetRagdollInst()->GetArchetype()->GetBound()->GetBoundingBoxMin();
Vec3V boundBoxMax = GetRagdollInst()->GetArchetype()->GetBound()->GetBoundingBoxMax();
return spdAABB(boundBoxMin, boundBoxMax);
}
else
{
spdAABB tempBox;
return CEntity::GetBoundBox(tempBox);
}
}
void CPed::OrientBoundToSpine(float fBlendInDelta, float fExtraZOffset)
{
s32 iBoneIndexLClavicle = GetBoneIndexFromBoneTag(BONETAG_L_CLAVICLE);
s32 iBoneIndexRClavicle = GetBoneIndexFromBoneTag(BONETAG_R_CLAVICLE);
s32 iBoneIndexLThigh = GetBoneIndexFromBoneTag(BONETAG_L_THIGH);
s32 iBoneIndexRThigh = GetBoneIndexFromBoneTag(BONETAG_R_THIGH);
const crSkeleton* pSkeleton = GetSkeleton();
if (pSkeleton != NULL && iBoneIndexLClavicle >= 0 && iBoneIndexRClavicle >= 0 && iBoneIndexLThigh >= 0 && iBoneIndexRThigh >= 0)
{
Mat34V_ConstRef xParentMatrix = *pSkeleton->GetParentMtx();
Vec3V vClavicleAveragePosition = Transform(xParentMatrix, Average(pSkeleton->GetObjectMtx((u32)iBoneIndexLClavicle).d(), pSkeleton->GetObjectMtx((u32)iBoneIndexRClavicle).d()));
Vec3V vThighAveragePosition = Transform(xParentMatrix, Average(pSkeleton->GetObjectMtx((u32)iBoneIndexLThigh).d(), pSkeleton->GetObjectMtx((u32)iBoneIndexRThigh).d()));
Vec3V vThighToClavicleDirection = Normalize(Subtract(vClavicleAveragePosition, vThighAveragePosition));
float fBoundPitch = GetBoundPitch();
Approach(fBoundPitch, -AngleNormInput(vThighToClavicleDirection, GetTransform().GetC()).Getf(), fBlendInDelta, fwTimer::GetTimeStep());
SetBoundPitch(fBoundPitch);
SetDesiredBoundPitch(GetBoundPitch());
if (GetCapsuleInfo()->GetBipedCapsuleInfo() != NULL)
{
float fCapsuleZOffset = GetCapsuleInfo()->GetBipedCapsuleInfo()->GetCapsuleZOffset();
float fRootZOffset = fCapsuleZOffset;
s32 iBoneIndexRoot = GetBoneIndexFromBoneTag(BONETAG_ROOT);
if (iBoneIndexRoot >= 0)
{
fRootZOffset = pSkeleton->GetObjectMtx((u32)iBoneIndexRoot).d().GetZf() + fExtraZOffset;
}
// We often catch ourselves on our hands and knees - so see if the average height of those bones is lower than the root
// and, if so, use that height instead
s32 iBoneIndexLHand = GetBoneIndexFromBoneTag(BONETAG_L_HAND);
s32 iBoneIndexRHand = GetBoneIndexFromBoneTag(BONETAG_R_HAND);
s32 iBoneIndexLCalf = GetBoneIndexFromBoneTag(BONETAG_L_CALF);
s32 iBoneIndexRCalf = GetBoneIndexFromBoneTag(BONETAG_R_CALF);
if (iBoneIndexLHand >= 0 && iBoneIndexRHand >= 0 && iBoneIndexLCalf >= 0 && iBoneIndexRCalf >= 0)
{
Vec3V vHandAveragePosition = Average(pSkeleton->GetObjectMtx((u32)iBoneIndexLHand).d(), pSkeleton->GetObjectMtx((u32)iBoneIndexRHand).d());
Vec3V vCalfAveragePosition = Average(pSkeleton->GetObjectMtx((u32)iBoneIndexLCalf).d(), pSkeleton->GetObjectMtx((u32)iBoneIndexRCalf).d());
float fAverageHandCalfHeight = (vHandAveragePosition.GetZf() + vCalfAveragePosition.GetZf()) * 0.5f;
if ((fAverageHandCalfHeight + fExtraZOffset) < fRootZOffset)
{
fRootZOffset = fAverageHandCalfHeight + fExtraZOffset;
}
}
Vector3 vBoundOffset = GetBoundOffset();
// The bound height is based on the bound pitch which is already interpolated so no need to interpolate bound height...
vBoundOffset.z = Max(fRootZOffset, fCapsuleZOffset);
SetBoundOffset(vBoundOffset);
SetDesiredBoundOffset(GetBoundOffset());
}
// We intentionally don't interpolate heading and we need to keep setting heading until the ped no longer has any pitch (may as well just keep setting it until
// the task ends)
SetBoundHeading(-AngleZNormInput(vThighToClavicleDirection, GetTransform().GetB()).Getf());
SetDesiredBoundHeading(GetBoundHeading());
ResetProcessBoundsCountdown();
}
}
void CPed::OffsetBoundToFurthestBoneInXYDirection(Vec2V_In vDirection)
{
const crSkeleton* pSkeleton = GetSkeleton();
if (pSkeleton != NULL)
{
int iFurthestBoneIndex = -1;
const fragInstNMGta* pRagdollInst = GetRagdollInst();
if(pRagdollInst != NULL && pRagdollInst->GetCached())
{
fragTypeChild** ppChildren = pRagdollInst->GetTypePhysics()->GetAllChildren();
// Go through the ragdoll components' bones and find the position (in the X/Y plane) furthest (in object space) in
// the direction of the ped's velocity
float fFurthestDistance = 0.0f;
for (int i = 0; i < RAGDOLL_NUM_COMPONENTS; i++)
{
int iBoneIndex = pRagdollInst->GetType()->GetBoneIndexFromID(ppChildren[i]->GetBoneID());
if (iBoneIndex != -1)
{
float fDistanceInVelocityDirection = Dot(vDirection, pSkeleton->GetObjectMtx(iBoneIndex).d().GetXY()).Getf();
if (fDistanceInVelocityDirection > fFurthestDistance)
{
fFurthestDistance = fDistanceInVelocityDirection;
iFurthestBoneIndex = iBoneIndex;
}
}
}
}
// No point in doing anything if the root is somehow furthest
if (iFurthestBoneIndex > 0)
{
Vector3 vFurthestObjectPosition = VEC3V_TO_VECTOR3(pSkeleton->GetObjectMtx(iFurthestBoneIndex).d());
vFurthestObjectPosition.z = 0.0f;
Vector3 vBoundOffset = GetBoundOffset();
vBoundOffset.ApproachStraight(vFurthestObjectPosition, SLOW_BLEND_IN_DELTA, fwTimer::GetTimeStep());
SetBoundOffset(vBoundOffset);
SetDesiredBoundOffset(vBoundOffset);
}
ResetProcessBoundsCountdown();
}
}
void CPed::ClearBound()
{
SetDesiredBoundPitch(0.0f);
SetDesiredBoundHeading(0.0f);
SetDesiredBoundOffset(VEC3_ZERO);
SetTaskCapsuleRadius(0.0f);
ResetProcessBoundsCountdown();
}
void CPed::CalcCameraAttachPositionForRagdoll(Vector3& pos) const
{
if (!GetRagdollInst())
return;
Vec3V_Ref vPos = RC_VEC3V(pos);
phBoundComposite *bound = GetRagdollInst()->GetCacheEntry()->GetBound();
nmAssert(bound);
float totalWeight = 0.0f;
float boundWeight;
pos = VEC3_ZERO;
atArray<CTaskNMBehaviour::Tunables::BoundWeight>& boundWeights = CTaskNMBehaviour::sm_Tunables.m_CamAttachPositionWeights;
for (s32 i=0; i<boundWeights.GetCount(); i++)
{
if (boundWeights[i].m_Bound>=0 && boundWeights[i].m_Bound<bound->GetNumBounds())
{
boundWeight = boundWeights[i].m_Weight;
vPos += (bound->GetCurrentMatrix(boundWeights[i].m_Bound).GetCol3()*ScalarV(boundWeight));
totalWeight+=boundWeight;
}
}
if (totalWeight!=0.0f)
vPos = vPos / ScalarV(totalWeight);
vPos = Transform(GetRagdollInst()->GetMatrix(), vPos);
}
bool CPed::GetIsFPSSwimming() const
{
#if FPS_MODE_SUPPORTED
return IsFirstPersonShooterModeEnabledForPlayer(false, true) && GetIsSwimming();
#else //FPS_MODE_SUPPORTED
return false;
#endif
}
bool CPed::GetIsFPSSwimmingUnderwater() const
{
#if FPS_MODE_SUPPORTED
if (GetIsFPSSwimming())
{
CTaskMotionBase* pPrimaryTask = GetPrimaryMotionTask();
if(pPrimaryTask && pPrimaryTask->GetTaskType() == CTaskTypes::TASK_MOTION_PED)
{
CTaskMotionPed* pTask = static_cast<CTaskMotionPed*>(pPrimaryTask);
if (pTask)
{
return pTask->CheckForDiving();
}
}
}
#endif //FPS_MODE_SUPPORTED
return false;
}
bool CPed::GetCanUseHighDetailWaterLevel() const
{
// B*2255289: Don't use high detail water calculations for clone peds further than 10m away from ped.
// Was returning QNaN value in CTaskMotionSwimming/Diving::CalcDesiredVelocity if outside of the water level grid that surrounds the local player.
if(!IsLocalPlayer())
{
CPed *pLocalPlayer = CPedFactory::GetFactory()->GetLocalPlayer();
if (pLocalPlayer)
{
const float fMaxDistFromLocalPlayer = 10.0f;
float fDistFromPlayer = VEC3V_TO_VECTOR3(pLocalPlayer->GetTransform().GetPosition() - GetTransform().GetPosition()).Mag();
if (fDistFromPlayer > fMaxDistFromLocalPlayer)
{
return false;
}
}
}
return true;
}
bool CPed::GetHasJetpackEquipped() const
{
#if ENABLE_JETPACK
return m_pJetpack && m_pJetpack->GetObject() != NULL;
#else
return false;
#endif
}
bool CPed::AddPedInSeat(CPed *ENABLE_HORSE_ONLY(pPed), s32 ENABLE_HORSE_ONLY(iSeat))
{
#if ENABLE_HORSE
if (m_pSeatManager)
{
return m_pSeatManager->AddPedInSeat(pPed, iSeat);
}
#endif
return false;
}
void CPed::SetMyVehicle(CVehicle* pVehicle)
{
// The ped's vehicle changed, so these flags need to be cleared.
if (m_pMyVehicle != pVehicle)
{
SetPedConfigFlag(CPED_CONFIG_FLAG_JackedOutOfMyVehicle, false);
SetPedConfigFlag(CPED_CONFIG_FLAG_WentIntoCombatAfterBeingJacked, false);
}
#if __ASSERT
if (pVehicle)
Assertf(!pVehicle->GetIsInReusePool(), "Setting ped %s (%p) in vehicle %s (%p) that's currently in the reuse pool!", GetModelName(), this, pVehicle->GetModelName(), pVehicle);
#endif // _ASSERT
#if __BANK
TUNE_GROUP_BOOL(VEHICLE_DEBUG, bLogSetMyVehicleCalls, false);
TUNE_GROUP_BOOL(VEHICLE_DEBUG, bLogSetMyVehicleResetCalls, false);
if (bLogSetMyVehicleCalls)
{
if (bLogSetMyVehicleResetCalls || pVehicle)
{
aiDisplayf("CPed::SetMyVehicle - Setting vehicle %s [%p] for ped %s [%p]", AILogging::GetDynamicEntityNameSafe(pVehicle), pVehicle, AILogging::GetDynamicEntityNameSafe(this), this);
sysStack::PrintStackTrace();
}
}
#endif
m_pMyVehicle = pVehicle;
}
bool CPed::WantsToMoveQuickly()
{
if (IsAPlayerPed())
{
const float fCurrentMbrY = GetMotionData()->GetCurrentMbrY();
if (fCurrentMbrY > MOVEBLENDRATIO_WALK)
{
return true;
}
if (IsLocalPlayer())
{
if (GetPlayerWanted()->GetWantedLevel() > WANTED_CLEAN)
{
return true;
}
}
if (CPlayerInfo::AreCNCResponsivenessChangesEnabled(this))
{
return true;
}
}
return false;
}
void CPed::CreateVehicleEntryConfigIfRequired()
{
if (!m_pVehicleEntryScriptConfig)
{
m_pVehicleEntryScriptConfig = rage_new CSyncedPedVehicleEntryScriptConfig();
#if __BANK
AI_LOG_WITH_ARGS("[CPed::CreateVehicleEntryConfigIfRequired] - Creating vehicle entry config [%p] for %s ped %s [%p]", m_pVehicleEntryScriptConfig, AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), this);
AI_LOG_STACK_TRACE(6);
#endif
}
Assertf(m_pVehicleEntryScriptConfig, "Failed to create vehicle entry config");
}
void CPed::CopyVehicleEntryConfigData(const CSyncedPedVehicleEntryScriptConfig& rOther)
{
if (rOther.HasDefaultState())
{
DeleteVehicleEntryConfig();
return;
}
CreateVehicleEntryConfigIfRequired();
(*m_pVehicleEntryScriptConfig) = rOther;
}
void CPed::SetForcedSeatUsage(s32 iSlot, s32 iFlags, CVehicle* pVehicle, s32 iSeatIndex)
{
SetForcedSeatUsageCommon(iSlot, iFlags, iSeatIndex);
m_pVehicleEntryScriptConfig->SetVehicle(iSlot, pVehicle);
}
void CPed::SetForcedSeatUsage(s32 iSlot, s32 iFlags, ObjectId vehicleId, s32 iSeatIndex)
{
SetForcedSeatUsageCommon(iSlot, iFlags, iSeatIndex);
m_pVehicleEntryScriptConfig->SetVehicleId(iSlot, vehicleId);
}
void CPed::SetForcedSeatUsageCommon(s32 iSlot, s32 iFlags, s32 iSeatIndex)
{
Assert(iSlot >= 0 && iSlot < CSyncedPedVehicleEntryScriptConfig::MAX_VEHICLE_ENTRY_DATAS);
CreateVehicleEntryConfigIfRequired();
m_pVehicleEntryScriptConfig->SetFlags(iSlot, (u8)iFlags);
if (iSeatIndex >= 0)
m_pVehicleEntryScriptConfig->SetSeat(iSlot, iSeatIndex);
}
void CPed::DeleteVehicleEntryConfig()
{
if (m_pVehicleEntryScriptConfig)
{
delete m_pVehicleEntryScriptConfig;
m_pVehicleEntryScriptConfig = NULL;
#if __BANK
AI_LOG_WITH_ARGS("[CPed::DeleteVehicleEntryConfig] - Deleting vehicle entry config for %s ped %s [%p]", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), this);
AI_LOG_STACK_TRACE(6);
#endif
}
}
void CPed::ClearForcedSeatUsage()
{
if (!m_pVehicleEntryScriptConfig)
return;
m_pVehicleEntryScriptConfig->ClearForcedSeatUsage();
if (m_pVehicleEntryScriptConfig->HasDefaultState())
{
DeleteVehicleEntryConfig();
}
}
bool CPed::IsInFirstPersonDriverCamera() const
{
CTaskMotionBase *pCurrentMotionTask = GetCurrentMotionTask();
if (pCurrentMotionTask && pCurrentMotionTask->GetTaskType() == CTaskTypes::TASK_MOTION_IN_AUTOMOBILE)
{
const CTaskMotionInAutomobile* pAutoMobileTask = static_cast<const CTaskMotionInAutomobile*>(pCurrentMotionTask);
if(pAutoMobileTask->IsUsingFirstPersonSteeringAnims())
{
return true;
}
}
return false;
}
bool CPed::IsInFirstPersonVehicleCamera(bool bCheckIsDominant, bool bIncludeClones) const
{
if (bIncludeClones && IsAPlayerPed() && IsNetworkClone())
{
return static_cast<CNetObjPlayer*>(GetNetworkObject())->IsUsingFirstPersonVehicleCamera();
}
if(camInterface::FindFollowPed() == this)
{
const camCinematicMountedCamera* pCamera = camInterface::GetGameplayDirector().GetFirstPersonVehicleCamera();
if(pCamera)
{
const camBaseCamera* pDominantCamera = camInterface::GetDominantRenderedCamera();
if(!bCheckIsDominant || (pDominantCamera == pCamera))
{
return true;
}
}
}
return false;
}
CPed* CPed::GetCustodian() const
{
if(m_pCustodianPed)
{
//! return cached custodian ped if valid.
return m_pCustodianPed;
}
else
{
#if ENABLE_TASKS_ARREST_CUFFED
//! Find custodian.
if (CTaskInCustody* pInCustody = static_cast<CTaskInCustody*>(m_pPedIntelligence->FindTaskByType(CTaskTypes::TASK_IN_CUSTODY)))
{
return pInCustody->GetCustodian();
}
else
{
if(GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_IN_CUSTODY))
{
CTaskInfo *pTaskInfo = GetPedIntelligence()->GetQueriableInterface()->GetTaskInfoForTaskType(CTaskTypes::TASK_IN_CUSTODY, PED_TASK_PRIORITY_MAX);
if(pTaskInfo)
{
Assert(pTaskInfo->GetTaskInfoType() == CTaskInfo::INFO_TYPE_IN_CUSTODY);
CClonedTaskInCustodyInfo* pInCustodyInfo = static_cast<CClonedTaskInCustodyInfo*>(pTaskInfo);
return pInCustodyInfo->GetCustodianPed();
}
}
}
#endif // ENABLE_TASKS_ARREST_CUFFED
}
//! return cached custodian ped.
return NULL;
}
void CPed::SetCustodian(CPed *pPed)
{
#if __BANK
if(m_pCustodianPed != pPed)
{
if(pPed)
{
taskDisplayf("Arrest: Setting Ped (%s) Custodian to %s", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "No Name",
pPed->GetNetworkObject() ? pPed->GetNetworkObject()->GetLogName() : "No Name");
}
else
{
taskDisplayf("Arrest: Setting Ped (%s) Custodian to NULL", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "No Name");
}
}
#endif
m_pCustodianPed = pPed;
}
void CPed::SetInCustody(bool bInCustody, CPed *pCustodian)
{
bool bInCustodyAlready = GetPedConfigFlag(CPED_CONFIG_FLAG_IsInCustody);
if(bInCustody)
{
if(!bInCustodyAlready)
{
#if __BANK
taskDisplayf("Arrest: Setting Ped (%s) to in custody!", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "No Name");
#endif
GetEventScriptNetworkGroup()->Add(CEventNetworkPlayerArrest(pCustodian, this, CArrestHelpers::EVENT_TAKENCUSTODY));
}
}
#if __BANK
else
{
if(bInCustodyAlready)
{
taskDisplayf("Arrest: Setting Ped (%s) out of custody!", GetNetworkObject() ? GetNetworkObject()->GetLogName() : "No Name");
}
}
#endif
if(bInCustody && !bInCustodyAlready)
{
GetEventScriptNetworkGroup()->Add(CEventNetworkPlayerArrest(pCustodian, this, CArrestHelpers::EVENT_TAKENCUSTODY));
}
SetPedConfigFlag(CPED_CONFIG_FLAG_IsInCustody, bInCustody);
SetCustodian(pCustodian);
}
CPed *CPed::GetArrestTarget() const
{
CTaskPlayerOnFoot* pTaskPlayer = static_cast<CTaskPlayerOnFoot*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_ON_FOOT));
if(pTaskPlayer)
{
return pTaskPlayer->GetTargetArrestPed();
}
return NULL;
}
CPed *CPed::GetUncuffTarget() const
{
CTaskPlayerOnFoot* pTaskPlayer = static_cast<CTaskPlayerOnFoot*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_ON_FOOT));
if(pTaskPlayer)
{
return pTaskPlayer->GetTargetUncuffPed();
}
return NULL;
}
void CPed::SetIsAHighPriorityTarget(bool bHighPriority)
{
#if __ASSERT
if(GetPedConfigFlag(CPED_CONFIG_FLAG_ThisPedIsATargetPriority) != bHighPriority)
{
#if !__FINAL
scrThread::PrePrintStackTrace();
#endif
Displayf("%s [Frame=%d] SET_ENTITY_IS_TARGET_PRIORITY. %s:%s", CTheScripts::GetCurrentScriptNameAndProgramCounter(), fwTimer::GetSystemFrameCount(), GetDebugName(), bHighPriority ? "true" : "false");
}
#endif
SetPedConfigFlag( CPED_CONFIG_FLAG_ThisPedIsATargetPriority, bHighPriority );
}
CPedGroup* CPed::GetPedsGroup() const
{
return CPedGroups::GetPedsGroup(this);
}
bool CPed::IsValidSpecialAbilitySlot(ePlayerSpecialAbilitySlot abilitySlot)
{
if (NetworkInterface::IsGameInProgress())
{
switch (abilitySlot)
{
case PSAS_PRIMARY:
case PSAS_SECONDARY:
return true;
default:
return false;
}
}
else
{
pedAssertf(abilitySlot == PSAS_PRIMARY, "Special ability slot %d is not valid for single player", (int)abilitySlot);
return true;
}
}
const CPlayerSpecialAbility* CPed::GetSpecialAbility(ePlayerSpecialAbilitySlot abilitySlot /* = PSAS_PRIMARY*/) const
{
if (IsValidSpecialAbilitySlot(abilitySlot))
{
return m_specialAbilities[abilitySlot];
}
else
{
return NULL;
}
}
CPlayerSpecialAbility* CPed::GetSpecialAbility(ePlayerSpecialAbilitySlot abilitySlot /* = PSAS_PRIMARY*/)
{
if (IsValidSpecialAbilitySlot(abilitySlot))
{
return m_specialAbilities[abilitySlot];
}
else
{
return NULL;
}
}
SpecialAbilityType CPed::GetSpecialAbilityType(ePlayerSpecialAbilitySlot abilitySlot /* = PSAS_PRIMARY*/) const
{
SpecialAbilityType abilityType = SAT_NONE;
if (IsValidSpecialAbilitySlot(abilitySlot))
{
if (NetworkInterface::IsGameInProgress())
{
if (const CPlayerInfo* pPlayerInfo = GetPlayerInfo())
{
abilityType = pPlayerInfo->GetMPSpecialAbility(abilitySlot);
}
}
else
{
const CPedModelInfo* pModelInfo = (CPedModelInfo*)GetBaseModelInfo();
pedAssert(pModelInfo);
abilityType = pModelInfo->GetSpecialAbilityType();
}
}
return abilityType;
}
void CPed::InitSpecialAbility(ePlayerSpecialAbilitySlot abilitySlot /* = PSAS_PRIMARY*/)
{
if (!IsValidSpecialAbilitySlot(abilitySlot))
return;
if (m_specialAbilities[abilitySlot])
{
m_specialAbilities[abilitySlot]->Deactivate();
CPlayerSpecialAbilityManager::Destroy(m_specialAbilities[abilitySlot]);
m_specialAbilities[abilitySlot] = NULL;
}
SpecialAbilityType abilityType = GetSpecialAbilityType(abilitySlot);
if (abilityType != SAT_NONE)
{
m_specialAbilities[abilitySlot] = CPlayerSpecialAbilityManager::Create(abilityType);
}
}
const bool CPed::HasSpecialAbility() const
{
if (NetworkInterface::IsGameInProgress())
{
for (int i = 0; i < PSAS_MAX; ++i)
{
if (m_specialAbilities[i])
{
return true;
}
}
return false;
}
else
{
return m_specialAbilities[PSAS_PRIMARY] != NULL;
}
}
bool CPed::SetSelectedAbilitySlot(ePlayerSpecialAbilitySlot newSlot)
{
pedDebugf1("CPed::SetSelectedAbilitySlot: %d", (int)newSlot);
if (IsValidSpecialAbilitySlot(newSlot))
{
m_selectedAbilitySlot = newSlot;
return true;
}
return false;
}
ePlayerSpecialAbilitySlot CPed::GetDesiredAbilitySlot()
{
int currentIndex = (int)m_selectedAbilitySlot;
if (currentIndex + 1 >= PSAS_MAX)
{
return PSAS_PRIMARY;
}
else
{
return ePlayerSpecialAbilitySlot(currentIndex + 1);
}
}
//
// Name : CountNearbyCops
// Purpose : Counts the number of nearby cops
//
s32 CPed::CountNearbyHostilePedsTryingToEnterMyVehicle(float fDistance)
{
float fDistanceSq = rage::square(fDistance);
s32 iCount = 0;
const CEntityScannerIterator entityList = GetPedIntelligence()->GetNearbyPeds();
for( const CEntity* pEnt = entityList.GetFirst(); pEnt; pEnt = entityList.GetNext() )
{
const CPed* pNearbyPed=(const CPed*)pEnt;
if( DistSquared(pNearbyPed->GetTransform().GetPosition(), GetTransform().GetPosition()).Getf() < fDistanceSq )
{
if( pNearbyPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() == (CEntity*)this )
{
if( pNearbyPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_ENTER_VEHICLE ) )
{
CVehicle* pVehicle = (CVehicle*)( pNearbyPed->GetPedIntelligence()->GetQueriableInterface()->GetTargetForTaskType( CTaskTypes::TASK_ENTER_VEHICLE));
if( pVehicle == m_pMyVehicle )
{
++iCount;
}
}
}
}
}
return iCount;
}
bool CPed::IsGoonPed() const
{
atHashWithStringBank decKey("Player_Goon",0x668B57C2);
if (fwDecoratorExtension::ExistsOn(*(const_cast<CPed*>(this)), decKey))
{
return true;
}
return false;
}
bool CPed::IsBossPed() const
{
atHashWithStringBank decKey("Player_Boss",0x60CE00F9);
if (fwDecoratorExtension::ExistsOn(*(const_cast<CPed*>(this)), decKey))
{
return true;
}
return false;
}
int CPed::GetBossID() const
{
int iID = -1;
atHashWithStringBank decKeyBoss("Player_Boss",0x60CE00F9);
atHashWithStringBank decKeyGoon("Player_Goon",0x668B57C2);
CPed& rPed = *(const_cast<CPed*>(this));
if (fwDecoratorExtension::ExistsOn(rPed,decKeyBoss))
{
fwDecoratorExtension::Get(rPed,decKeyBoss,iID);
}
if (fwDecoratorExtension::ExistsOn(rPed,decKeyGoon))
{
fwDecoratorExtension::Get(rPed,decKeyGoon,iID);
}
return iID;
}
bool CPed::IsCivilianPed() const
{
//Ensure the ped type is civilian.
if(!CPedType::IsCivilianType(GetPedType()))
{
return false;
}
//Ensure the ped is not security.
if(IsSecurityPed())
{
return false;
}
//Ensure the ped is not gang.
if(IsGangPed())
{
return false;
}
return true;
}
// Is this ped a gang ped
bool CPed::IsGangPed( void ) const
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
if(pModelInfo && pModelInfo->GetPersonalitySettings().GetIsGang() )
{
return true;
}
return false;
}
bool CPed::IsSecurityPed() const
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
if(pModelInfo && pModelInfo->GetPersonalitySettings().GetIsSecurity() )
{
return true;
}
return false;
}
bool CPed::ShouldBehaveLikeLaw() const
{
if(!IsSecurityPed() && !IsLawEnforcementPed())
{
return false;
}
if(GetPedConfigFlag(CPED_CONFIG_FLAG_DontBehaveLikeLaw))
{
return false;
}
return true;
}
CEntity *CPed::GetOQBoundingBox(Vec3V_InOut min, Vec3V_InOut max)
{
CEntity *boundSource = this;
CVehicle *vehicle = GetVehiclePedInside();
min = Vec3V(V_ZERO);
max = Vec3V(V_ZERO);
if( vehicle && GetUseVehicleBoundingBox())
{
boundSource = vehicle;
if( GetUseRestrictedVehicleBoundingBox() )
{
Vec3V vehMin = VECTOR3_TO_VEC3V(vehicle->GetBoundingBoxMin());
Vec3V vehMax = VECTOR3_TO_VEC3V(vehicle->GetBoundingBoxMax());
if( vehicle->GetIsAircraft())
{
int componentIdx = vehicle->GetFragmentComponentIndex(VEH_CHASSIS);
const fragInst* fragInst = vehicle->GetVehicleFragInst();
const phBoundComposite* pBoundComp = fragInst->GetTypePhysics()->GetCompositeBounds();
vehMin = pBoundComp->GetBound(componentIdx)->GetBoundingBoxMin();
vehMax = pBoundComp->GetBound(componentIdx)->GetBoundingBoxMax();
if( componentIdx > -1 )
{
const int iGroupIndex = fragInst->GetGroupFromBoneIndex(componentIdx);
if(iGroupIndex!=-1)
{
const fragTypeGroup *pGroup = fragInst->GetTypePhysics()->GetAllGroups()[iGroupIndex];
const int iFirstChildIndex = pGroup->GetChildFragmentIndex();
const int iNumChildren = pGroup->GetNumChildren();
for(int iChild = iFirstChildIndex ; iChild < iFirstChildIndex+iNumChildren; iChild++)
{
const phBound * pSubBound = pBoundComp->GetBound(iChild);
if(pSubBound)
{
Vec3V min = pSubBound->GetBoundingBoxMin();
Vec3V max = pSubBound->GetBoundingBoxMax();
if( IsGreaterThanAll(MagSquared(max-min), MagSquared(vehMax-vehMin)) )
{
vehMax = max;
vehMin = min;
}
}
}
}
}
}
Vec3V pedMin = VECTOR3_TO_VEC3V(GetBoundingBoxMin());
Vec3V pedMax = VECTOR3_TO_VEC3V(GetBoundingBoxMax());
const ScalarV lengthMin = Max(pedMin.GetY(),vehMin.GetY());
const ScalarV lengthMax = Min(pedMax.GetY(),vehMax.GetY());
const ScalarV delta = (lengthMax - lengthMin) * ScalarV(0.05f);
Vec3V pedPos = GetTransform().GetPosition();
Vec3V vehPos = vehicle->GetTransform().GetPosition();
Vec3V worldSpaceOffset = pedPos - vehPos;
Vec3V localOffset = vehicle->GetTransform().UnTransform3x3(worldSpaceOffset);
min = vehMin;
min.SetY(lengthMin - delta + localOffset.GetY());
max = vehMax;
max.SetY(lengthMax + delta + localOffset.GetY());
}
else if( vehicle->GetIsHeli() )
{
int componentIdx = vehicle->GetFragmentComponentIndex(VEH_CHASSIS);
if( componentIdx > -1 )
{
const phBoundComposite* pBoundComp = vehicle->GetVehicleFragInst()->GetTypePhysics()->GetCompositeBounds();
max = pBoundComp->GetBound(componentIdx)->GetBoundingBoxMax();
min = pBoundComp->GetBound(componentIdx)->GetBoundingBoxMin();
}
else
{
min = VECTOR3_TO_VEC3V(vehicle->GetBoundingBoxMin());
max = VECTOR3_TO_VEC3V(vehicle->GetBoundingBoxMax());
}
}
else
{
min = VECTOR3_TO_VEC3V(vehicle->GetBoundingBoxMin());
max = VECTOR3_TO_VEC3V(vehicle->GetBoundingBoxMax());
}
}
else if ( GetUseHnSBoundingBox() )
{
min = VECTOR3_TO_VEC3V(GetBoundingBoxMin());
max = VECTOR3_TO_VEC3V(GetBoundingBoxMax());
const crSkeleton *pSkeleton = GetSkeleton();
Assert(pSkeleton);
const crSkeletonData &skeletonData = GetSkeletonData();
int spineIdx;
int rUpperArmIdx;
int lUpperArmIdx;
bool hasSpine = skeletonData.ConvertBoneIdToIndex((u16)BONETAG_SPINE2, spineIdx);
bool hasrUpperArm = skeletonData.ConvertBoneIdToIndex((u16)BONETAG_R_UPPERARM, rUpperArmIdx);
bool haslUpperArm = skeletonData.ConvertBoneIdToIndex((u16)BONETAG_L_UPPERARM, lUpperArmIdx);
if( hasSpine && hasrUpperArm && haslUpperArm )
{
// Matrix
Mat34V globalSpineMtx;
pSkeleton->GetGlobalMtx(spineIdx,globalSpineMtx);
Mat34V matrix = boundSource->GetMatrix();
Vec3V mtxPos = matrix.d();
matrix = globalSpineMtx;
Mat34VRotateLocalY(matrix,ScalarV(HALF_PI));
matrix.Setd(mtxPos);
// Box : this is a god awfull blend between the actual bound, and made up bits and pieces.
const Mat34V spineMtx = pSkeleton->GetObjectMtx(spineIdx);
const Mat34V rUpperArmMtx = pSkeleton->GetObjectMtx(rUpperArmIdx);
const Mat34V lUpperArmMtx = pSkeleton->GetObjectMtx(lUpperArmIdx);
const Vec3V spinePos = spineMtx.d();
const Vec3V rUpperArmPos = rUpperArmMtx.d();
const Vec3V lUpperArmPos = lUpperArmMtx.d();
const ScalarV zMin = Max(min.GetZ(),spinePos.GetZ());
const ScalarV boxWidthMin = Min(rUpperArmPos.GetX(),lUpperArmPos.GetX());
const ScalarV boxWidthMax = Max(rUpperArmPos.GetX(),lUpperArmPos.GetX());
const float restrictedBoxLength = 0.20f;
min.SetX(boxWidthMin);
min.SetY(ScalarV(-restrictedBoxLength));
min.SetZ(zMin);
max.SetX(boxWidthMax);
max.SetY(ScalarV(restrictedBoxLength));
}
}
else
{
min = VECTOR3_TO_VEC3V(GetBoundingBoxMin());
max = VECTOR3_TO_VEC3V(GetBoundingBoxMax());
}
#if __BANK
if( bShowPedsVisibilityBoundingBox )
{
grcDebugDraw::BoxOriented(VECTOR3_TO_VEC3V(GetBoundingBoxMin()),VECTOR3_TO_VEC3V(GetBoundingBoxMax()),GetMatrix(),Color32(0x7f7f7f7f),false);
grcDebugDraw::BoxOriented(min,max,boundSource->GetMatrix(),Color32(0xffffffff),false);
}
#endif
return boundSource;
}
bool CPed::IsPedVisible()
{
fwDrawData *drawData = GetDrawHandlerPtr();
if( drawData )
{
unsigned int queryId = drawData->GetOcclusionQueryId();
if( queryId )
{
int drawCount = OcclusionQueries::OQGetQueryResult(queryId);
if( drawCount != -1 )
{
m_lastValidResult = drawCount > 100;
}
}
}
if( GetIsVisibleInSomeViewportThisFrame() == false )
{ // No results, so just pretend it wasn't there.
m_lastValidResult = false;
}
return m_lastValidResult;
}
int CPed::GetPedPixelCount()
{
int pixelCount = -1;
fwDrawData *drawData = GetDrawHandlerPtr();
if( drawData )
{
unsigned int queryId = drawData->GetOcclusionQueryId();
if( queryId )
{
pixelCount = OcclusionQueries::OQGetQueryResult(queryId);
}
}
if( GetIsVisibleInSomeViewportThisFrame() == false )
{ // No results, so just pretend it wasn't there.
pixelCount = 0;
}
return pixelCount;
}
bool CPed::HaveAllStreamingReqsCompleted()
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return true;
CPedStreamRequestGfx* req = GetExtensionList().GetExtension<CPedStreamRequestGfx>();
if (req)
return false;
return true;
}
void AddAssetStreamingIndexToList(strIndex idx, atArray<strIndex>& deps, const strIndex* ignoreList, s32 numIgnores)
{
// make sure it's not already in the list
bool found = false;
for (s32 i = 0; i < deps.GetCount(); ++i)
{
if (deps[i] == idx)
{
found = true;
break;
}
}
if (found)
return;
// make sure we ignore the indices in the ignore list
for (s32 i = 0; i < numIgnores; ++i)
{
if (ignoreList[i] == idx)
{
found = true;
break;
}
}
if (found)
return;
deps.PushAndGrow(idx);
}
void CPed::AppendAssetStreamingIndices(atArray<strIndex>& deps, const strIndex* ignoreList, s32 numIgnores)
{
if (!GetPedModelInfo()->GetIsStreamedGfx())
return;
if (!GetPedModelInfo()->GetVarInfo())
return;
const CPedVariationData* varData = &GetPedDrawHandler().GetVarData();
CPedVariationInfoCollection* varInfo = GetPedModelInfo()->GetVarInfo();
char diffMap[64] = {0};
char drawablName[64] = {0};
const char* pedFolder = GetPedModelInfo()->GetStreamFolder();
for (u32 i = 0; i < PV_MAX_COMP; ++i)
{
// generate the file names of the files we require
u32 drawblIdx = varData->m_aDrawblId[i]; // current drawable for each component
u8 drawblAltIdx = varData->m_aDrawblAltId[i]; // current variation of drawable
u8 texIdx = varData->m_aTexId[i];
if (drawblIdx == PV_NULL_DRAWBL)
{
Assert(i != PV_COMP_HEAD);
continue;
}
const CPVDrawblData* pDrawblData = varInfo->GetCollectionDrawable(i, drawblIdx);
if (!pDrawblData)
continue;
if (drawblAltIdx > 0) {
// use a variation of the drawable
if (pDrawblData->IsUsingRaceTex() == true) {
sprintf(drawablName, "%s/%s_%03d_r_%d", pedFolder, varSlotNames[i], drawblIdx, drawblAltIdx);
} else {
sprintf(drawablName, "%s/%s_%03d_u_%d", pedFolder, varSlotNames[i], drawblIdx, drawblAltIdx);
}
} else {
if (pDrawblData->IsUsingRaceTex() == true) {
sprintf(drawablName, "%s/%s_%03d_r", pedFolder, varSlotNames[i], drawblIdx);
} else {
sprintf(drawablName, "%s/%s_%03d_u", pedFolder, varSlotNames[i], drawblIdx);
}
}
u8 raceIdx = pDrawblData->GetTexData(texIdx);
if (!Verifyf(raceIdx != (u8)PV_RACE_INVALID, "Bad race index for drawable %s with texIdx %d on ped %s", drawablName, texIdx, GetModelName()))
continue;
// generate the name of the textures required
sprintf(diffMap, "%s/%s_diff_%03d_%c_%s", pedFolder, varSlotNames[i], drawblIdx, (texIdx+'a'),
raceTypeNames[raceIdx]); // diffuse map
strLocalIndex txd = g_TxdStore.FindSlot(diffMap);
if (txd != -1)
AddAssetStreamingIndexToList(g_TxdStore.GetStreamingIndex(txd), deps, ignoreList, numIgnores);
strLocalIndex dwd = strLocalIndex(g_DwdStore.FindSlot(drawablName));
if (dwd != -1)
AddAssetStreamingIndexToList(g_DwdStore.GetStreamingIndex(dwd), deps, ignoreList, numIgnores);
if (pDrawblData->m_clothData.HasCloth())
{
strLocalIndex cld = g_ClothStore.FindSlot(drawablName);
if (cld != -1)
AddAssetStreamingIndexToList(g_ClothStore.GetStreamingIndex(cld), deps, ignoreList, numIgnores);
}
}
// head blend data
{
if (varData->HasHeadBlendData(this))
{
const CPedHeadBlendData* blendData = GetExtensionList().GetExtension<CPedHeadBlendData>();
const CPVDrawblData* drawable0 = varInfo->GetCollectionDrawable(PV_COMP_HEAD, blendData->m_head0);
if (drawable0)
{
if (drawable0->IsUsingRaceTex())
{
sprintf(drawablName, "%s/head_%03d_r", pedFolder, blendData->m_head0);
} else {
sprintf(drawablName, "%s/head_%03d_u", pedFolder, blendData->m_head0);
}
strLocalIndex dwd = strLocalIndex(g_DwdStore.FindSlot(drawablName));
if (dwd != -1)
AddAssetStreamingIndexToList(g_DwdStore.GetStreamingIndex(dwd), deps, ignoreList, numIgnores);
}
const CPVDrawblData* drawable1 = varInfo->GetCollectionDrawable(PV_COMP_HEAD, blendData->m_head1);
if (drawable1)
{
if (drawable1->IsUsingRaceTex())
{
sprintf(drawablName, "%s/head_%03d_r", pedFolder, blendData->m_head1);
} else {
sprintf(drawablName, "%s/head_%03d_u", pedFolder, blendData->m_head1);
}
strLocalIndex dwd = strLocalIndex(g_DwdStore.FindSlot(drawablName));
if (dwd != -1)
AddAssetStreamingIndexToList(g_DwdStore.GetStreamingIndex(dwd), deps, ignoreList, numIgnores);
}
const CPVDrawblData* drawable2 = varInfo->GetCollectionDrawable(PV_COMP_HEAD, blendData->m_head2);
if (drawable2)
{
if (drawable2->IsUsingRaceTex())
{
sprintf(drawablName, "%s/head_%03d_r", pedFolder, blendData->m_head2);
} else {
sprintf(drawablName, "%s/head_%03d_u", pedFolder, blendData->m_head2);
}
strLocalIndex dwd = strLocalIndex(g_DwdStore.FindSlot(drawablName));
if (dwd != -1)
AddAssetStreamingIndexToList(g_DwdStore.GetStreamingIndex(dwd), deps, ignoreList, numIgnores);
}
u8 tex0 = 0;
const CPVDrawblData* texDrawable0 = varInfo->GetCollectionDrawable(PV_COMP_HEAD, blendData->m_tex0);
if (texDrawable0 && tex0 < texDrawable0->GetNumTex())
{
u8 raceIdx = texDrawable0->GetTexData(tex0);
if (raceIdx != (u8)PV_RACE_INVALID)
{
sprintf(diffMap, "%s/head_diff_%03d_%c_%s", pedFolder, blendData->m_tex0, (tex0 + 'a'), raceTypeNames[raceIdx]);
strLocalIndex txd = g_TxdStore.FindSlot(diffMap);
if (txd != -1)
AddAssetStreamingIndexToList(g_TxdStore.GetStreamingIndex(txd), deps, ignoreList, numIgnores);
}
}
u8 tex1 = 0;
const CPVDrawblData* texDrawable1 = varInfo->GetCollectionDrawable(PV_COMP_HEAD, blendData->m_tex1);
if (texDrawable1 && tex1 < drawable1->GetNumTex())
{
u8 raceIdx = texDrawable1->GetTexData(tex1);
if (raceIdx != (u8)PV_RACE_INVALID)
{
sprintf(diffMap, "%s/head_diff_%03d_%c_%s", pedFolder, blendData->m_tex1, (tex1 + 'a'), raceTypeNames[raceIdx]);
strLocalIndex txd = g_TxdStore.FindSlot(diffMap);
if (txd != -1)
AddAssetStreamingIndexToList(g_TxdStore.GetStreamingIndex(txd), deps, ignoreList, numIgnores);
}
}
u8 tex2 = 0;
const CPVDrawblData* texDrawable2 = varInfo->GetCollectionDrawable(PV_COMP_HEAD, blendData->m_tex2);
if (texDrawable2 && tex2 < drawable2->GetNumTex())
{
u8 raceIdx = texDrawable2->GetTexData(tex2);
if (raceIdx != (u8)PV_RACE_INVALID)
{
sprintf(diffMap, "%s/head_diff_%03d_%c_%s", pedFolder, blendData->m_tex2, (tex2 + 'a'), raceTypeNames[raceIdx]);
strLocalIndex txd = g_TxdStore.FindSlot(diffMap);
if (txd != -1)
AddAssetStreamingIndexToList(g_TxdStore.GetStreamingIndex(txd), deps, ignoreList, numIgnores);
}
}
// overlays
for (s32 i = 0; i < HOS_MAX; ++i)
{
const atArray<atHashString>* arr = NULL;
switch (i)
{
case HOS_BLEMISHES: arr = &CPedVariationStream::GetFacialOverlays().m_blemishes; break;
case HOS_FACIAL_HAIR: arr = &CPedVariationStream::GetFacialOverlays().m_facialHair; break;
case HOS_EYEBROW: arr = &CPedVariationStream::GetFacialOverlays().m_eyebrow; break;
case HOS_AGING: arr = &CPedVariationStream::GetFacialOverlays().m_aging; break;
case HOS_MAKEUP: arr = &CPedVariationStream::GetFacialOverlays().m_makeup; break;
case HOS_BLUSHER: arr = &CPedVariationStream::GetFacialOverlays().m_blusher; break;
case HOS_DAMAGE: arr = &CPedVariationStream::GetFacialOverlays().m_damage; break;
case HOS_BASE_DETAIL: arr = &CPedVariationStream::GetFacialOverlays().m_baseDetail; break;
case HOS_SKIN_DETAIL_1: arr = &CPedVariationStream::GetFacialOverlays().m_skinDetail1; break;
case HOS_SKIN_DETAIL_2: arr = &CPedVariationStream::GetFacialOverlays().m_skinDetail2; break;
case HOS_BODY_1: arr = &CPedVariationStream::GetFacialOverlays().m_bodyOverlay1; break;
case HOS_BODY_2: arr = &CPedVariationStream::GetFacialOverlays().m_bodyOverlay2; break;
case HOS_BODY_3: arr = &CPedVariationStream::GetFacialOverlays().m_bodyOverlay3; break;
}
Assertf(arr, "New entry added to eBodyOverlaySlots but not the above case block");
if (blendData->m_overlayTex[i] < arr->GetCount())
{
atHashString overlay = (*arr)[blendData->m_overlayTex[i]];
strLocalIndex txd = g_TxdStore.FindSlot(overlay);
if (txd != -1)
AddAssetStreamingIndexToList(g_TxdStore.GetStreamingIndex(txd), deps, ignoreList, numIgnores);
}
}
}
}
}
#define RAMMED_BY_CAR_BLEND_VELOCITY_MULTIPLIER (0.16f)
#define RUNOVER_BY_CAR_BLEND_VELOCITY_MULTIPLIER (0.24f)
#define RUNOVER_BY_CAR_PLAYBACK_VELOCITY_MULTIPLIER (0.32f)
float gArse1 = 0.35f;
float gOldArse = 300.0f;
float gArse2 = 30.0f;
void CPed::KillPedWithCar(CVehicle *pVehicle, float fImpulse, const CCollisionRecord* pColRecord)
{
// if ped's already doing no collisions with this car, skip all this stuff
// (must've been already done earlier in loop - like in ProcessCollision)
int nActiveTaskType = CTaskTypes::MAX_NUM_TASK_TYPES;
if(GetPedIntelligence()->GetTaskActiveSimplest())
nActiveTaskType = GetPedIntelligence()->GetTaskActiveSimplest()->GetTaskType();
CPhysical *pGroundPhysical = GetGroundPhysical();
if(!pGroundPhysical && GetPedConfigFlag(CPED_CONFIG_FLAG_RidingTrain))
{
if(GetTrainRidingOn() &&
pVehicle->InheritsFromTrain() )
{
const CTrain *pTrainHit = static_cast<const CTrain*>(pVehicle);
if( (GetTrainRidingOn() == pTrainHit) || (pTrainHit->GetLinkedToBackward() == GetTrainRidingOn()) || (pTrainHit->GetLinkedToForward() == GetTrainRidingOn()) )
{
return;
}
}
pGroundPhysical = m_pLastValidGroundPhysical;
}
if(nActiveTaskType==CTaskTypes::TASK_FALL_OVER || nActiveTaskType==CTaskTypes::TASK_DYING_DEAD)
{
return;
}
// don't want player to get hit by vehicles they can stand on.
else if(pGroundPhysical && pGroundPhysical->GetIsTypeVehicle())
{
if(((CVehicle*)pGroundPhysical)->CanPedsStandOnTop())
return;
else if(IsPlayer())
return;
// Allow peds to stand on vehicles they've been recently damaged by (this allows a ped some time to escape from a vehicle they might be trapped on)
else if (GetRagdollInst() != NULL && GetRagdollInst()->GetLastImpactDamageEntity() == pGroundPhysical && GetRagdollInst()->GetTimeSinceImpactDamage() < CCollisionEventScanner::RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT)
return;
}
// don't want peds to get hit by boats (or planes) they just tried to get out of
else if(m_pMyVehicle && m_pMyVehicle==pVehicle && (pVehicle->InheritsFromBoat() || pVehicle->GetVehicleType()==VEHICLE_TYPE_PLANE))
{
return;
}
// We would end up getting hit by a cargobob that we'd just been standing on resulting in an animated fall. The animated fall would prohibit CEventInAir events from
// being responded to and stop the ped from ever going into a high fall behavior. We just ignore impacts with a cargobob that we'd recently been standing on here.
// The ped-vehicle ragdoll impact logic already checks that the impacting vehicle isn't the last valid ground physical so perhaps this animated impact logic should also
// use the last valid ground physical regardless of whether it is a cargobob or not...
else if(m_pLastValidGroundPhysical == pVehicle && m_pLastValidGroundPhysical->GetIsTypeVehicle() &&
static_cast<CVehicle*>(m_pLastValidGroundPhysical.Get())->GetIsHeli() && static_cast<CHeli*>(m_pLastValidGroundPhysical.Get())->GetIsCargobob())
{
return;
}
// B*2846564 - same issue jumping off a moving TUG boat, but m_pLastValidGroundPhysical can be null (if timer has run out)
else if (MI_BOAT_TUG.IsValid() && pVehicle->GetModelIndex() == MI_BOAT_TUG)
{
return;
}
CBaseModelInfo *pVehModelInfo = pVehicle->GetBaseModelInfo();
Vector3 vecDelta = VEC3V_TO_VECTOR3(GetTransform().GetPosition() - pVehicle->GetTransform().GetPosition());
bool bApplyForceToVehicle = false;
static const float RAMMED_BY_CAR_DAMAGE_LOW = 30.0f;
// static const float RAMMED_BY_TRAIN_DAMAGE = 150.0f;
bool bSkipPlayers = IsPlayer();
// don't skip players if we're in a network game
bSkipPlayers &= !NetworkInterface::IsGameInProgress();
if (CTaskNMBehaviour::ShouldReactToVehicleHit(this, pVehicle, pColRecord))
{
u8 nUDLR = 0;
u8 nForceUD = u8(fwRandom::GetRandomNumber()&3);
u32 nHitType = WEAPONTYPE_RAMMEDBYVEHICLE;
// Add little jolt.
if (pVehicle == FindPlayerVehicle())
{
dev_float fIntenistyModifier = 0.0001f;
const float fIntensity = fImpulse * fIntenistyModifier;
CControlMgr::StartPlayerPadShakeByIntensity(400,fIntensity);
}
SetIsStanding(false);
Vector2 dir2d(-pVehicle->GetVelocity().x, -pVehicle->GetVelocity().y);
s32 dirOffset = GetLocalDirection(dir2d);
float fCarRightSize = pVehModelInfo->GetBoundingBoxMax().x;
float fPedSidePos = DotProduct(vecDelta, VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetA()));
float fPedRelativeHeight = DotProduct(vecDelta, VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC()));// + pVehicle->m_vecCOM.z;
if(pVehicle->GetVehicleType()==VEHICLE_TYPE_TRAIN)
{
nUDLR = 2; // down
nHitType = WEAPONTYPE_RUNOVERBYVEHICLE;
Vector3 vecSetSpeed = pVehicle->GetVelocity() * 0.9f;
vecSetSpeed.z = 0.0f;
SetVelocity(vecSetSpeed);
// only want to use front and back knockdown anims for running over ped
if(dirOffset==1 || dirOffset==3)
dirOffset = 2;
}
else if(DotProduct(pVehicle->GetVelocity(), VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB())) < 0.0f)
{
}
// Collision knocks ped off to the side
else if(ABS(fPedSidePos) > 0.99f*fCarRightSize)
{
if(fPedSidePos>0.0f)
nUDLR = 4; // right
else
nUDLR = 3; // left
// if ped has been sideswiped by car, knock them under
if( rage::Abs(DotProduct(vecDelta, VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()))) < 0.85f*pVehModelInfo->GetBoundingBoxMax().y)
{
nHitType = WEAPONTYPE_RUNOVERBYVEHICLE;
Vector3 vecSetSpeed = pVehicle->GetVelocity() * 0.9f;
vecSetSpeed.z = 0.0f;
SetVelocity(vecSetSpeed);
// only want to use front and back knockdown anims for running over ped
if(dirOffset==1 || dirOffset==3)
dirOffset = 2;
}
}
// Collision pushes ped up and over car (not big vehicles)
// (if nForceUD==1 force knock down , if nForceUD==0 force knock up, else use relative height)
else if((nForceUD==0 || (fPedRelativeHeight > 0.1f && nForceUD>1)) && !(pVehicle->pHandling->mFlags &MF_IS_BIG) && !pVehicle->InheritsFromBoat())
{
nUDLR = 1; // up
// first we need to calculate the height of the vehicle the ped must pass over
float fVehicleFront = pVehModelInfo->GetBoundingBoxMax().y;
float fVehicleRear = pVehModelInfo->GetBoundingBoxMin().y;
float fVehicleTop = pVehModelInfo->GetBoundingBoxMax().z;
// if vehicle tilted down use height of boundbox at rear of vehicle
if(pVehicle->GetTransform().GetB().GetZf() < -0.2f){
fVehicleTop = ( VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition()) + fVehicleTop*(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC())) +
fVehicleRear*(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB())) ).z;
// fVehicleTop += 0.3f;
// now save dist from front of veh to height cross point - longer if using the rear
fVehicleRear = -fVehicleRear + fVehicleFront;
}
// else if vehicle going up hill
else if(pVehicle->GetTransform().GetB().GetZf() > 0.1f){
fVehicleTop = ( VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition()) + fVehicleTop*(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC())) +
fVehicleFront*(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB())) ).z;
// now save dist from front of veh to height cross point - longer if using the rear
fVehicleRear = fVehicleFront;
// NOW VERY DODGY STEP -> directly move ped 1/2 dist required to clear bonnet to avoid high speeds
const float fThisPosZ = GetTransform().GetPosition().GetZf();
if( (fVehicleTop - fThisPosZ) > 0.0f){
Matrix34 mat = MAT34V_TO_MATRIX34(GetMatrix());
mat.d.z += 0.5f*(fVehicleTop - fThisPosZ);
SetMatrix(mat, false, false, false);
fVehicleTop += 0.25f*(fVehicleTop - GetTransform().GetPosition().GetZf());
}
}
// otherwise height of boundbox at vehicle centre
else{
fVehicleTop = ( VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition()) + fVehicleTop*(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC())) ).z;
fVehicleRear = pVehModelInfo->GetBoundingBoxMax().y;
}
float fRiseTime = fVehicleRear / pVehicle->GetVelocity().Mag();
float fReqRiseSpeed = (fVehicleTop - GetTransform().GetPosition().GetZf()) / fRiseTime;
fReqRiseSpeed = (1.5f+0.002f*(fwRandom::GetRandomNumber()&255))*fReqRiseSpeed;
vecDelta = pVehicle->GetVelocity();
vecDelta.Normalize();
vecDelta = 0.2f*fReqRiseSpeed*vecDelta;
vecDelta.z += fReqRiseSpeed;
// just set move speed directly now
SetVelocity(vecDelta);
// reverse hit direction
if( (dirOffset+=2) > 3 ) dirOffset-=4;
vecDelta = VEC3V_TO_VECTOR3(GetTransform().GetPosition() - pVehicle->GetTransform().GetPosition());
}
// collision pushes ped under car
else
{
nUDLR = 2; // down
nHitType = WEAPONTYPE_RUNOVERBYVEHICLE;
Vector3 vecSetSpeed = pVehicle->GetVelocity() * 0.9f;
vecSetSpeed.z = 0.0f;
SetVelocity(vecSetSpeed);
// only want to use front and back knockdown anims for running over ped
if(dirOffset==1 || dirOffset==3)
dirOffset = 2;
}
CPed* pDriver = pVehicle->GetSeatManager()->GetDriver();
switch (CCrime::sm_eReportCrimeMethod)
{
case CCrime::REPORT_CRIME_DEFAULT:
if(pDriver)
{
if(IsLawEnforcementPed())
{
CCrime::ReportCrime(CRIME_RUNOVER_COP, (CPed*)this, (CPed*)pDriver);
CCrime::ReportCrime(CRIME_KILL_COP, (CPed*)this, (CPed*)pDriver);
}
else
{
CCrime::ReportCrime(CRIME_RUNOVER_PED, (CPed*)this, (CPed*)pDriver);
CCrime::ReportCrime(CRIME_KILL_PED, (CPed*)this, (CPed*)pDriver);
}
}
break;
case CCrime::REPORT_CRIME_ARCADE_CNC:
// NFA - ped and cop deaths are handled in script for CnC
break;
}
float fDamage = RAMMED_BY_CAR_DAMAGE_LOW;
if(IsPlayer())
{
if(NetworkInterface::IsGameInProgress())
{
if(pDriver && pDriver->IsPlayer())
{
fDamage = fImpulse / 2.0f;//RAMMED_BY_CAR_DAMAGE_HIGH;
if(!NetworkInterface::FriendlyFireAllowed() && !NetworkInterface::IsPedAllowedToDamagePed(*this, *pDriver))
{
fDamage = 0.0f;
}
}
}
}
// Reduce damage given to underwater animals/peds rammed by submarines
if (GetIsInWater() && pVehicle->InheritsFromSubmarine())
{
fDamage *= 0.1f;
}
CPedDamageCalculator damageCalculator(pVehicle, fDamage, nHitType, 0, false);
CEventDamage event(pVehicle, fwTimer::GetTimeInMilliseconds(), nHitType);
fwMvClipSetId clipSetId = CLIP_SET_ID_INVALID;
fwMvClipId clipId = CLIP_ID_INVALID;
CTaskFallOver::eFallDirection dir = CTaskFallOver::kDirBack;
int nRand = int(fwRandom::GetRandomNumber()&3);
// if (this->m_nFlags.bNotDamagedByCollisions) return;
switch(dirOffset){
case 0:
if((nUDLR==3 && nRand>1))
dir = CTaskFallOver::kDirRight;
else if((nUDLR==4 && nRand>1))
dir = CTaskFallOver::kDirLeft;
else
dir = CTaskFallOver::kDirBack;
break;
case 1:
dir = CTaskFallOver::kDirRight;
break;
case 2:
if((nUDLR==3 && nRand>1))
dir = CTaskFallOver::kDirRight;
else if((nUDLR==4 && nRand>1))
dir = CTaskFallOver::kDirLeft;
else
dir = CTaskFallOver::kDirFront;
break;
case 3:
dir = CTaskFallOver::kDirLeft;
break;
}
damageCalculator.ApplyDamageAndComputeResponse(this, event.GetDamageResponseData(), CPedDamageCalculator::DF_None);
if(event.AffectsPed(this))
{
CEvent* pEventAdded = GetPedIntelligence()->AddEvent(event);
if(pEventAdded && pEventAdded != &event
&& pEventAdded->GetEventType()==EVENT_DAMAGE)
{
CTask* pResponseTask = NULL;
// Don't disable collisions here, since we want the car to collide with the capsule while the ped is off the ground.
// Contacts filtering for peds on the ground is happening in PreComputeImpcats
if(event.HasKilledPed() || event.HasInjuredPed())
{
CDeathSourceInfo info(pVehicle, WEAPONTYPE_RAMMEDBYVEHICLE, CTaskFallOver::kContextVehicleHit, dir);
pResponseTask = rage_new CTaskDyingDead(&info);
}
else
{
if (!GetIsInWater() && !GetIsSwimming() && !GetCapsuleInfo()->IsFish())
{
CTaskFallOver::PickFallAnimation(this, CTaskFallOver::kContextVehicleHit, dir, clipSetId, clipId);
pResponseTask = rage_new CTaskFallAndGetUp(clipSetId, clipId, 2.0f);
if (GetRagdollInst()->GetARTAssetID() >= 0)
{
static_cast<CTaskFallAndGetUp*>(pResponseTask)->SetCapsuleRestrictionsDisabled(true);
CTaskFallOver::ApplyInitialVehicleImpact(pVehicle, this);
}
}
}
((CEventDamage*)pEventAdded)->SetPhysicalResponseTask(pResponseTask, false);
}
// set flag to store whether ped was knocked up into the air or down onto the ground
if(nUDLR == 1)
SetPedConfigFlag( CPED_CONFIG_FLAG_KnockedUpIntoAir, true );
else
SetPedConfigFlag( CPED_CONFIG_FLAG_KnockedUpIntoAir, false );
GetPedIntelligence()->GetEventScanner()->GetCollisionEventScanner().SetHitByCar();
}
else
{
SetPedConfigFlag( CPED_CONFIG_FLAG_KnockedUpIntoAir, false );
}
// apply a hit force to the vehicle in question
bApplyForceToVehicle = true;
}
else if (pColRecord)
{
// the hit is not hard enough for an animated knockdown. Do a low impact bump reaction
const int iVehicleComponent = pColRecord->m_OtherCollisionComponent;
Assert((unsigned)iVehicleComponent < 65536);
CEventVehicleCollision event(
pVehicle,
pColRecord->m_MyCollisionNormal,
pColRecord->m_MyCollisionPos,
pColRecord->m_fCollisionImpulseMag,
(u16)iVehicleComponent,
GetPedIntelligence()->GetMoveBlendRatioFromGoToTask());
GetPedIntelligence()->AddEvent(event);
}
// apply a force to the vehicle
if (bApplyForceToVehicle)
{
static float fScaleMass = 1600.0f;
static float fScaleImpact = -0.5f;
static float fScaleImpactBikes = -0.75f;
static float fScaleOffset = 0.25f;
Vector3 VehUpVector(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetC()));
vecDelta -= DotProduct(vecDelta, VehUpVector)*VehUpVector;
Vector3 vecImpact = vecDelta;
vecImpact.Normalize();
float fImpactSpeed = DotProduct(vecImpact, pVehicle->GetVelocity());
if(!GetPedConfigFlag( CPED_CONFIG_FLAG_KnockedUpIntoAir ))
vecImpact.z -= 0.2f;
if(pVehicle->InheritsFromBike())
fImpactSpeed *= fScaleImpactBikes;
else
fImpactSpeed *= fScaleImpact;
pVehicle->ApplyForce(rage::Min(pVehicle->GetMass(), fScaleMass)*fImpactSpeed*rage::Min(1.0f, pVehicle->GetMass()/fScaleMass)*vecImpact, fScaleOffset*vecDelta);
// apply an upwards force also to make it feel like the vehicle is bouncing over the body
// pVehicle->ApplyForce(Vector3(0.0f, 0.0f, 1.0f) * MIN(1.0f, pVehicle->m_fMass/1400.0f) * 40.0f, vecDelta);
}
return;
}
CVehicle* CPed::GetVehiclePedEntering() const
{
if (m_pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE))
{
if (IsNetworkClone())
{
CClonedEnterVehicleInfo* pTaskInfo = static_cast<CClonedEnterVehicleInfo*>(m_pPedIntelligence->GetQueriableInterface()->GetTaskInfoForTaskType(CTaskTypes::TASK_ENTER_VEHICLE, PED_TASK_PRIORITY_MAX));
if (pTaskInfo)
{
return pTaskInfo->GetVehicle();
}
}
else
{
CTaskEnterVehicle* pEnterTask = static_cast<CTaskEnterVehicle*>(m_pPedIntelligence->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE));
if (pEnterTask)
{
return pEnterTask->GetVehicle();
}
}
}
return m_pMyVehicle;
}
bool CPed::GetIsEnteringVehicle(bool bJustTestForEnterTask) const
{
const bool bEnterTaskRunning = m_pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE, true);
if (bJustTestForEnterTask)
{
return bEnterTaskRunning;
}
s32 iTaskState = -1;
if (IsNetworkClone())
{
if (bEnterTaskRunning)
{
iTaskState = m_pPedIntelligence->GetQueriableInterface()->GetStateForTaskType(CTaskTypes::TASK_ENTER_VEHICLE, PED_TASK_PRIORITY_MAX);
}
}
else
{
CTask* pTask = m_pPedIntelligence->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE);
if (pTask)
{
iTaskState = pTask->GetState();
}
}
if (iTaskState >= CTaskEnterVehicle::State_Align)
{
return true;
}
return false;
}
bool CPed::GetIsDrivingVehicle() const
{
if( GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle && !m_pMyVehicle->InheritsFromTrailer())
{
return m_pMyVehicle->IsDriver(this);
}
return false;
}
bool CPed::IsBeingJackedFromVehicle() const
{
if (m_pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE))
{
if (IsNetworkClone())
{
CClonedExitVehicleInfo* pTaskInfo = static_cast<CClonedExitVehicleInfo*>(m_pPedIntelligence->GetQueriableInterface()->GetTaskInfoForTaskType(CTaskTypes::TASK_EXIT_VEHICLE, PED_TASK_PRIORITY_MAX));
if (pTaskInfo)
{
return pTaskInfo->GetFlags().BitSet().IsSet(CVehicleEnterExitFlags::BeJacked);
}
}
else
{
CTaskExitVehicle* pExitTask = static_cast<CTaskExitVehicle*>(m_pPedIntelligence->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE));
if (pExitTask)
{
return pExitTask->GetRunningFlags().BitSet().IsSet(CVehicleEnterExitFlags::BeJacked);
}
}
}
return false;
}
bool CPed::IsExitingVehicle() const
{
return (GetAttachState() == ATTACH_STATE_PED_EXIT_CAR);
}
bool CPed::CanShovePed(CPed* pPedToShove) const
{
Assert(pPedToShove);
if(GetMotionData()->GetUsingStealth())
return false;
if( GetPedResetFlag( CPED_RESET_FLAG_ScriptDisableSecondaryAnimationTasks ) )
return false;
if( GetPedConfigFlag( CPED_CONFIG_FLAG_IsHandCuffed ) )
return false;
if ( !pPedToShove->GetTaskData().GetIsFlagSet(CTaskFlags::CanBeShoved) )
return false;
if ( !GetTaskData().GetIsFlagSet(CTaskFlags::CanShove) )
return false;
if( pPedToShove->GetIsSitting() )
return false;
if( this->GetPedResetFlag( CPED_RESET_FLAG_MoveBlend_bMeleeTaskRunning) ||
pPedToShove->GetPedResetFlag( CPED_RESET_FLAG_MoveBlend_bMeleeTaskRunning))
return false;
TUNE_GROUP_INT(PED_SHOVING, nShoveTimeout, 2500, 0, 20000, 100);
if( (pPedToShove == GetLastPedShoved()) && fwTimer::GetTimeInMilliseconds() < (m_nLastPedShoveTime + nShoveTimeout ))
return false;
#if __BANK
if(!CTaskShovePed::IsTesting())
#endif
{
if( GetPedIntelligence()->IsFriendlyWith(*pPedToShove) )
return false;
}
if( pPedToShove->IsInjured() )
return false;
return true;
}
void CPed::SetLastPedShoved(CPed* pPedShoved)
{
//now record new shoved ped
m_pLastPedShoved = pPedShoved;
m_nLastPedShoveTime = fwTimer::GetTimeInMilliseconds();
}
bool CPed::NewSay(const char* context, u32 voiceNameHash, bool forcePlay, bool allowRepeat, s32 delay,
CPed* replyingPed, u32 replyingContext, s32 replyingDelay, f32 replyProbability, u32 requestedVolume, bool preloadOnly, u32 preloadTimeoutMS, bool fromScript)
{
audSpeechInitParams speechParams;
speechParams.forcePlay = forcePlay;
speechParams.allowRecentRepeat = allowRepeat;
speechParams.requestedVolume = requestedVolume;
speechParams.preloadOnly = preloadOnly;
speechParams.preloadTimeoutInMilliseconds = preloadTimeoutMS;
speechParams.fromScript = fromScript;
if(m_pSpeechAudioEntity &&
m_pSpeechAudioEntity->Say(context, speechParams, voiceNameHash, delay, replyingPed, replyingContext, replyingDelay, replyProbability) != AUD_SPEECH_SAY_FAILED)
{
#if !__FINAL
LogSpeech(context);
#endif
// Launch the line on the clone....
if (GetNetworkObject() && !IsNetworkClone())
{
CPedConversationLineEvent::Trigger(this, voiceNameHash, context, 0);
}
return true;
}
return false;
}
bool CPed::NewSayWithParams(const char* context, const char* speechParams, u32 voiceNameHash, s32 delay, CPed* replyingPed, u32 replyingContext, s32 replyingDelay, f32 replyProbability, bool fromScript)
{
if(m_pSpeechAudioEntity &&
m_pSpeechAudioEntity->Say(context, speechParams, voiceNameHash, delay, replyingPed, replyingContext, replyingDelay, replyProbability, fromScript) != AUD_SPEECH_SAY_FAILED )
{
#if !__FINAL
LogSpeech(context);
#endif
return true;
}
return false;
}
bool CPed::RandomlySay( const char* context, float fRandom, bool forcePlay, bool allowRepeat )
{
if( fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < fRandom )
return NewSay(context, 0, forcePlay, allowRepeat);
return false;
}
bool CPed::NewSay(u32 contextHash, u32 voiceNameHash, bool forcePlay, bool allowRepeat, s32 delay,
CPed* replyingPed, u32 replyingContext, s32 replyingDelay, f32 replyProbability, u32 requestedVolume, bool preloadOnly, u32 preloadTimeoutMS, bool fromScript)
{
bool returningDefault = false;
SpeechContext* contextObj = g_SpeechManager.GetSpeechContext(contextHash, this, &returningDefault);
if(returningDefault)
Warningf("Default speechcontext object being used for hash %u in NewSay. Ask audio to add one if you want this to work.", contextHash);
#if __ASSERT
if(PARAM_assertOnMissingSpeechContext.Get())
Assertf(!returningDefault, "NewSay called with object-less context. Hash: %u", contextHash);
#endif
#if !__FINAL
const char* debugName = "";
if(contextObj)
debugName = audNorthAudioEngine::GetMetadataManager().GetNameFromNTO_Debug(contextObj->NameTableOffset);
else
{
AudBaseObject *obj = static_cast<AudBaseObject*>(audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr(contextHash));
debugName = (obj && obj->ClassId==TriggeredSpeechContext::TYPE_ID) ?
audNorthAudioEngine::GetMetadataManager().GetNameFromNTO_Debug(obj->NameTableOffset) :
"";
}
#endif
audSpeechInitParams speechParams;
speechParams.forcePlay = forcePlay;
speechParams.allowRecentRepeat = allowRepeat;
speechParams.requestedVolume = requestedVolume;
speechParams.preloadOnly = preloadOnly;
speechParams.preloadTimeoutInMilliseconds = preloadTimeoutMS;
speechParams.fromScript = fromScript;
//contextObj will be NULL for TriggeredSpeechContexts (which, once we're all set up, should be most of the time)
if(m_pSpeechAudioEntity && m_pSpeechAudioEntity->Say(contextObj && !returningDefault ? contextObj->ContextNamePHash : contextHash,
speechParams, voiceNameHash, delay, replyingPed, replyingContext, replyingDelay, replyProbability, 0, ORIGIN
#if __BANK
, debugName
#endif
) != AUD_SPEECH_SAY_FAILED )
{
#if !__FINAL
LogSpeech(debugName);
#endif
return true;
}
return false;
}
bool CPed::NewSayWithParams(u32 contextHash, const char* speechParams, u32 voiceNameHash, s32 delay, CPed* replyingPed, u32 replyingContext, s32 replyingDelay, f32 replyProbability, bool fromScript)
{
SpeechContext* contextObj = g_SpeechManager.GetSpeechContext(contextHash, this, replyingPed);
#if !__FINAL
const char* debugName = "";
if(contextObj)
debugName = audNorthAudioEngine::GetMetadataManager().GetNameFromNTO_Debug(contextObj->NameTableOffset);
else
{
AudBaseObject *obj = static_cast<AudBaseObject*>(audNorthAudioEngine::GetMetadataManager().GetObjectMetadataPtr(contextHash));
debugName = (obj && obj->ClassId==TriggeredSpeechContext::TYPE_ID) ?
audNorthAudioEngine::GetMetadataManager().GetNameFromNTO_Debug(obj->NameTableOffset) :
"";
}
#endif
//contextObj will be NULL for TriggeredSpeechContexts (which, once we're all set up, should be most of the time)
if(m_pSpeechAudioEntity && m_pSpeechAudioEntity->Say(contextObj ? contextObj->ContextNamePHash : contextHash,
speechParams, voiceNameHash, delay, replyingPed, replyingContext, replyingDelay, replyProbability, fromScript, 0, ORIGIN
#if __BANK
, debugName
#endif
) != AUD_SPEECH_SAY_FAILED )
{
#if !__FINAL
LogSpeech(debugName);
#endif
return true;
}
return false;
}
bool CPed::RandomlySay(u32 contextHash, float fRandom, bool forcePlay, bool allowRepeat )
{
SpeechContext* contextObj = g_SpeechManager.GetSpeechContext(contextHash, this, NULL, NULL);
if( fwRandom::GetRandomNumberInRange(0.0f, 1.0f) < fRandom )
{
return NewSay(contextObj ? contextObj->ContextNamePHash : contextHash, 0, forcePlay, allowRepeat);
}
return false;
}
bool CPed::IsNearMirror(ScalarV_In vThreshold) const
{
if (CMirrors::GetIsMirrorVisible(true))
{
ScalarV vDistance(PlaneDistanceTo(CMirrors::GetMirrorPlane(), GetMatrix().GetCol3()));
return IsLessThanAll(vDistance, vThreshold) != 0;
}
return false;
}
bool CPed::CanPutOnHelmet() const
{
if(ms_bBikeHelmetsDisabledForAllPeds || !GetPedConfigFlag( CPED_CONFIG_FLAG_UseHelmet ))
{
return false;
}
else if(IsAPlayerPed() || PopTypeIsMission())
{
return true;
}
else
{
CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
if(pModelInfo && pModelInfo->GetPersonalitySettings().DrivesVehicleType(PED_DRIVES_MOTORCYCLE))
{
return true;
}
}
return false;
}
void CPed::GetBikeHelmetOffsets(Vector3& vPosOffset, Quaternion& qRotOffset) const
{
int boneIndexRightHand = GetBoneIndexFromBoneTag(BONETAG_R_HAND);
Assert(boneIndexRightHand!=BONETAG_INVALID);
Matrix34 matRightHand;
GetGlobalMtx(boneIndexRightHand, matRightHand);
int boneIndexLeftHand = GetBoneIndexFromBoneTag(BONETAG_L_HAND);
Assert(boneIndexLeftHand!=BONETAG_INVALID);
Matrix34 matLeftHand;
GetGlobalMtx(boneIndexLeftHand, matLeftHand);
int boneIndexHead = GetBoneIndexFromBoneTag(BONETAG_HEAD);
Assert(boneIndexHead!=BONETAG_INVALID);
Matrix34 matHead;
GetGlobalMtx(boneIndexHead, matHead);
// Pos offset starts off as in between both hands
vPosOffset = (matLeftHand.d - matRightHand.d) * 0.5f;
// I was having trouble getting a generic offset to work for the helmet.
// It was in the right position at the start of the animation, but needed to be offset at the end.
// Due to time constraints, I have decided to lerp the position during the animation, so it looks ok.
const Vector3 PUT_ON_OFFSET_FROM_HEAD(0.0f, -0.16f, 0.0f);
const Vector3 TAKE_OFF_OFFSET_FROM_HEAD(0.0f, -0.1f, 0.0f);
Vector3 vOffsetFromHead(PUT_ON_OFFSET_FROM_HEAD);
CTask* pTask = GetPedIntelligence()->GetTaskActiveSimplest();
if(pTask)
{
CClipHelper* pClipHelper = pTask->GetClipHelper();
if(pClipHelper)
{
if (pClipHelper->GetClip())
{
float fCreatePhase = 0.0f;
if(CClipEventTags::FindEventPhase<crPropertyAttributeBool, bool>(pClipHelper->GetClip(), CClipEventTags::Object, CClipEventTags::Create, true, fCreatePhase, 0.0f, 1.0f))
{
float fReleasePhase = 1.0f;
if(CClipEventTags::FindEventPhase<crPropertyAttributeBool, bool>(pClipHelper->GetClip(), CClipEventTags::Object, CClipEventTags::Release, true, fReleasePhase, 0.0f, 1.0f))
{
if(fReleasePhase > fCreatePhase)
{
// Interpolate to the offset based on the animation events
float t = (pClipHelper->GetPhase() - fCreatePhase) / (fReleasePhase - fCreatePhase);
if(pTask->GetTaskType() == CTaskTypes::TASK_PUT_ON_HELMET)
{
vOffsetFromHead = Lerp(t, VEC3_ZERO, PUT_ON_OFFSET_FROM_HEAD);
}
else
{
// Do the lerp in reverse, as we are taking the helmet off the head
vOffsetFromHead = Lerp(t, TAKE_OFF_OFFSET_FROM_HEAD, VEC3_ZERO);
}
}
}
}
}
}
}
// Transform the offset into "head space"
matHead.Transform3x3(vOffsetFromHead);
// Add onto our world space hand position
vPosOffset += vOffsetFromHead;
// Convert to local space of the right hand (as this is what we are effectively attached to)
matRightHand.UnTransform3x3(vPosOffset);
// Calculate rot offset, so the helmet matches the orientation of the head
Matrix34 mat = matRightHand;
mat.Inverse();
mat.DotFromLeft(matHead);
qRotOffset.FromMatrix34(mat);
}
void CPed::KnockPedOffVehicle(bool bForce, float fOptionalDamage, u32 iMinTime)
{
// If we aren't forcing the knock off behaviour, check if someone wants this ped to stay on regardless.
if(!bForce)
{
if(m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_NEVER && !IsInjured())
return;
}
// I've just been thrown off my bike audio.
if(m_pSpeechAudioEntity)
m_pSpeechAudioEntity->SayWhenSafe("OVER_HANDLEBARS");
nmEntityDebugf(this, "KnockPedOffVehicle: Added through windscreen task (ped knocked from vehicle)");
if(IsNetworkClone())
{
return;
}
const Mat34V savedLastMat = PHSIM->GetLastInstanceMatrix(GetRagdollInst());
CVehicle* pVehicle = GetMyVehicle();
if(pVehicle)
{
Mat34V lastMat = PHSIM->GetLastInstanceMatrix(GetRagdollInst());
Mat34V differenceMat;
UnTransformOrtho(differenceMat, GetRagdollInst()->GetMatrix(), lastMat);
// Do an explicit attachment update here to make sure the ped is in the correct position.
if(GetIsAttached())
{
ProcessAllAttachments();
}
// Ensure that we maintain the same difference between current/last matrices after processing attachments since we don't want to mess up
// the initial velocity of the ragdoll
Transform(lastMat, GetRagdollInst()->GetMatrix(), differenceMat);
PHSIM->SetLastInstanceMatrix(GetRagdollInst(), lastMat);
SetPedOutOfVehicle(CPed::PVF_Warp|CPed::PVF_IgnoreSafetyPositionCheck);
// Switch the ped to ragdoll.
if(CTaskNMBehaviour::CanUseRagdoll(this, RAGDOLL_TRIGGER_VEHICLE_JUMPOUT))
{
nmEntityDebugf(this, "CPed::KnockPedOffVehicle - Adding CTaskNMThroughWindscreen");
aiTask* pTaskNM = rage_new CTaskNMThroughWindscreen(iMinTime, 30000, false, pVehicle);
CEventSwitch2NM event(30000, pTaskNM);
pTaskNM = NULL;
SwitchToRagdoll(event);
// Remove some of this ped's health if we have been given a non-zero damage amount.
if(fOptionalDamage > 0.0f)
{
CEventDamage tempDamageEvent(pVehicle, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE);
CPedDamageCalculator damageCalculator(pVehicle, fOptionalDamage, WEAPONTYPE_RAMMEDBYVEHICLE, 0, true);
damageCalculator.ApplyDamageAndComputeResponse(this, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None, 0);
}
}
}
}
void CPed::SetPedInVehicle(CVehicle* pVehicle, const s32 iTargetSeat, u32 iFlags)
{
#if __ASSERT
if (pVehicle->IsSeatIndexValid(iTargetSeat) && pVehicle->GetComponentReservationMgr() && !PopTypeIsRandom())
{
CComponentReservation* pComponentReservation = pVehicle->GetComponentReservationMgr()->GetSeatReservationFromSeat(iTargetSeat);
if (pComponentReservation && pComponentReservation->GetPedUsingComponent() != this)
{
CPed* pPedInSeat = pVehicle->GetSeatManager()->GetPedInSeat(iTargetSeat);
aiDisplayf("Frame : %i, Setting ped %s(%p) into vehicle %s(%p) without seat reservation, it is occupied by ped %s(%p)", fwTimer::GetFrameCount(), GetDebugName(), this, pVehicle->GetDebugName(), pVehicle, pPedInSeat ? pPedInSeat->GetDebugName() : "NULL", pPedInSeat);
sysStack::PrintStackTrace();
}
}
const bool bIsTrailerLarge = MI_TRAILER_TRAILERLARGE.IsValid() && pVehicle->GetModelIndex() == MI_TRAILER_TRAILERLARGE;
if (bIsTrailerLarge)
{
AI_LOG_WITH_ARGS("%s Ped %s is being placed into the trailerlarge", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this));
AI_LOG_STACK_TRACE(8);
}
#endif // __ASSERT
#if DR_ENABLED
debugPlayback::RecordPedSetIntoVehicle(*this, *pVehicle, iFlags);
#endif // DR_ENABLED
#if __BANK
if (IsAPlayerPed() || PopTypeIsMission())
{
AI_LOG_WITH_ARGS("[VehicleEntryExit][CPed::SetPedInVehicle] - Setting %s ped %s into vehicle %s, seat %i\n", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityNameSafe(pVehicle), iTargetSeat);
if (IsAPlayerPed())
{
sysStack::PrintStackTrace();
}
}
#endif // __BANK
// We should never try to force entry if theres a ped inside.
if (pVehicle->GetCarDoorLocks() == CARLOCK_LOCKED_INITIALLY)
{
pVehicle->SetCarDoorLocks(CARLOCK_UNLOCKED);
}
ResetStandData();
// Don't want peds which have been warped into a vehicle on land to start drowning or to still be dripping wet
// when they get out of the car after a long period of time has passed.
ResetWaterStatus();
m_Buoyancy.SetStatusHighNDry();
#if __DEV
if (!Verifyf(GetPedConfigFlag(CPED_CONFIG_FLAG_AllowPedInVehiclesOverrideTaskFlags) || !GetTaskData().GetIsFlagSet(CTaskFlags::DisableVehicleUsage),
"Something is setting an incompatible ped %s into a vehicle %s", GetModelName(), pVehicle->GetModelName()))
{
printf("Task history:\n");
GetPedIntelligence()->PrintTasks();
printf("Script task history:\n");
GetPedIntelligence()->PrintScriptTaskHistory();
}
#endif
bool bShuffling = false;
u32 uEquippedWeapon = 0;
s32 iEquippedVehicleWeapon = -1;
if( GetWeaponManager() )
{
uEquippedWeapon = GetWeaponManager()->GetEquippedWeaponHash();
iEquippedVehicleWeapon = GetWeaponManager()->GetEquippedVehicleWeaponIndex();
}
if ((iFlags & CPed::PVF_Warp) && !pVehicle->IsNetworkClone())
{
if (pVehicle->IsTurretSeat(iTargetSeat))
{
const s32 iDirectEntryPointIndex = pVehicle->GetVehicleModelInfo()->GetModelSeatInfo()->GetEntryPointIndexForSeat(iTargetSeat, pVehicle);
CCarDoor* pDoor = CTaskVehicleFSM::GetDoorForEntryPointIndex(pVehicle, iDirectEntryPointIndex);
if (pDoor && pDoor->GetIsIntact(pVehicle))
{
pDoor->ClearFlag(CCarDoor::DRIVEN_MASK);
pDoor->SetFlag(CCarDoor::PROCESS_FORCE_AWAKE | CCarDoor::SET_TO_FORCE_OPEN);
}
}
}
// Set the ped out of any car it is currently in
if( GetMyVehicle() && GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) )
{
bShuffling = pVehicle == GetMyVehicle();
if (iFlags&CPed::PVF_KeepInVehicleMoveBlender)
{
iFlags = CPed::PVF_KeepInVehicleMoveBlender;
// Ignore safety position check if being put into a seat in a vehicle we're already in
if (bShuffling)
{
iFlags |= CPed::PVF_IgnoreSafetyPositionCheck;
}
SetPedOutOfVehicle(iFlags);
}
else
{
iFlags = CPed::PVF_Warp;
// Ignore safety position check if being put into a seat in a vehicle we're already in
if (pVehicle == GetMyVehicle())
{
iFlags |= CPed::PVF_IgnoreSafetyPositionCheck;
}
SetPedOutOfVehicle(iFlags);
}
}
// Set the vehicle pointer and in vehicle flag
SetMyVehicle(pVehicle);
SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, true );
if (GetMyVehicle())
{
if(GetNetworkObject())
{
Assertf(GetMyVehicle()->m_nPhysicalFlags.bNotToBeNetworked || GetMyVehicle()->GetNetworkObject(), "Trying to put a network ped (%s) in a non-networked vehicle! (0x%p)", GetNetworkObject()->GetLogName(), GetMyVehicle());
}
else
{
Assertf(m_nPhysicalFlags.bNotToBeNetworked || !GetMyVehicle()->GetNetworkObject(), "Trying to put a non-networked ped (0x%p) in a networked vehicle (%s)!", this, GetMyVehicle()->GetNetworkObject()->GetLogName());
}
#if KEYBOARD_MOUSE_SUPPORT
#if __BANK
// in bank mode we can disable pc input to better work with rag.
if(!PARAM_noKeyboardMouseControls.Get())
#endif // __BANK
{
if(IsLocalPlayer())
{
CControlMgr::GetPlayerMappingControl().ResetKeyboardMouseSettings();
}
}
#endif //KEYBOARD_MOUSE_SUPPORT
}
const bool bApplyForce = ((iFlags&PVF_Warp) == 0) & ((iFlags&PVF_DontApplyEnterVehicleForce) == 0);
bool bAddedToCar = false;
if( iTargetSeat == -1 )
{
for(int i=0; i< pVehicle->GetSeatManager()->GetMaxSeats(); i++)
{
if(!pVehicle->GetSeatManager()->GetPedInSeat(i) && i!=pVehicle->GetDriverSeat())
{
bAddedToCar=pVehicle->AddPedInSeat(this, i, (iFlags&PVF_IfForcedTestVehConversionCollision) != 0, bApplyForce);
}
}
}
else
{
bAddedToCar=pVehicle->AddPedInSeat(this, iTargetSeat, (iFlags&PVF_IfForcedTestVehConversionCollision) != 0, bApplyForce);
}
// Failed to add this ped into the car, quit.
if(!bAddedToCar)
{
#if !__FINAL
const char* pedName = GetModelName();
#if __ASSERT
const char* vehicleName = pVehicle->GetModelName();
#endif
if(GetNetworkObject())
{
pedName = GetNetworkObject()->GetLogName();
}
CPed* pOccupier = pVehicle->GetSeatManager()->GetPedInSeat(iTargetSeat);
if (pOccupier)
{
const char* occupierName = pOccupier->GetModelName();
if(GetNetworkObject())
{
occupierName = pOccupier->GetNetworkObject()->GetLogName();
}
#if __ASSERT
taskWarningf("Failed to add ped (%s) to vehicle (%s) in seat %d because it's occupied by ped (%s)", pedName, vehicleName, iTargetSeat, occupierName);
#endif // __ASSERT
}
else
{
#if __ASSERT
taskWarningf("Failed to add ped (%s) to vehicle (%s) in seat %d", pedName, vehicleName, iTargetSeat);
#endif // __ASSERT
}
#endif
SetMyVehicle(NULL);
SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, false );
UpdateSpatialArrayTypeFlags();
return;
}
// Position offset
Vector3 vAttachOffset(Vector3::ZeroType);
// Rotation offset
Quaternion q(0.0f,0.0f,0.0f,1.0f);
// See if we want to keep collision on
s32 iAttachFlags = ATTACH_STATE_PED_IN_CAR;
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iTargetSeat);
if (pSeatAnimInfo && pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle())
{
iAttachFlags |= ATTACH_FLAG_COL_ON;
}
// If we're not already attached, we must be warping the ped into the vehicle
if (!GetIsAttached())
{
AttachPedToEnterCar(pVehicle, iAttachFlags, iTargetSeat, pVehicle->GetVehicleModelInfo()->GetModelSeatInfo()->GetEntryPointIndexForSeat(iTargetSeat, pVehicle), &vAttachOffset, &q);
ProcessAllAttachments();
}
else // Otherwise just set the attach state to be basic now
{
SetRagdollOnCollisionIgnorePhysical(NULL);
SetActivateRagdollOnCollision(false);
ClearActivateRagdollOnCollisionEvent();
AttachPedToEnterCar(pVehicle, iAttachFlags, iTargetSeat, pVehicle->GetVehicleModelInfo()->GetModelSeatInfo()->GetEntryPointIndexForSeat(iTargetSeat, pVehicle), &vAttachOffset, &q);
}
// re-pose the character before the render to get rid of the driving anims completely
if( iFlags&PVF_Warp )
{
GetIkManager().ResetAllSolvers();
SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAIUpdate, true );
SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAnimUpdate, true );
ForceMotionStateThisFrame(CPedMotionStates::MotionState_InVehicle);
}
GetMotionData()->SetDesiredMoveBlendRatio(MOVEBLENDRATIO_STILL);
GetMotionData()->SetUsingStealth(false);
// Player enter vehicle stuff
if( IsPlayer() )
{
// If the ped is a player mark that he is an unknown vehicle.
// If he is currently being watched by an attacking AI they will reset this flag.
GetPlayerWanted()->PlayerEnteredVehicle( GetMyVehicle() );
CNetObjGame* pNetObject = this->GetNetworkObject();
if (pNetObject)
{
CNetGamePlayer* pNetPlayer = pNetObject->GetPlayerOwner();
if (pNetPlayer)
{
GetEventScriptNetworkGroup()->Add(CEventNetworkPlayerEnteredVehicle(*pNetPlayer, *pVehicle));
}
}
// CNC: Report vehicle as stolen if player is a Crook entering a vehicle owned by a Cop player.
bool bShouldReportStolenEvenIfOwnedByPlayer = false;
if (NetworkInterface::IsInCopsAndCrooks() && pVehicle->m_nVehicleFlags.bHasBeenOwnedByPlayer)
{
const CPlayerInfo* pPlayerInfo = GetPlayerInfo();
if (pPlayerInfo && pPlayerInfo->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_CROOK)
{
if (pVehicle->IsPersonalVehicle())
{
int iVehicleOwnerId = pVehicle->GetPersonalVehicleOwnerId();
if (iVehicleOwnerId != 0)
{
// Don't go any further if this is this peds personal vehicle.
// Get Player Id (same logic as CommandNetworkHashFromPlayerHandle which is used to set the Player_Vehicle decorator).
char playerId[RL_MAX_USERID_BUF_LENGTH];
pPlayerInfo->m_GamerInfo.GetGamerHandle().ToUserId(playerId);
int iPlayerId = static_cast<int>(atDataHash(playerId, strlen(playerId)));
if (iPlayerId != iVehicleOwnerId)
{
const netPlayer * const *allPhysicalPlayers = netInterface::GetAllPhysicalPlayers();
for (unsigned index = 0; index < netInterface::GetNumPhysicalPlayers(); index++)
{
const CNetGamePlayer* pNetGamePlayer = SafeCast(const CNetGamePlayer, allPhysicalPlayers[index]);
const CPed* pTargetPlayer = pNetGamePlayer->GetPlayerPed();
if (pTargetPlayer && pTargetPlayer != this)
{
const CPlayerInfo* pTargetPlayerInfo = pTargetPlayer->GetPlayerInfo();
if (pTargetPlayerInfo)
{
// Found the vehicle's owner.
// Get Target Player Id (same logic as CommandNetworkHashFromPlayerHandle which is used to set the Player_Vehicle decorator).
char targetPlayerId[RL_MAX_USERID_BUF_LENGTH];
pTargetPlayerInfo->m_GamerInfo.GetGamerHandle().ToUserId(targetPlayerId);
int iTargetPlayerId = static_cast<int>(atDataHash(targetPlayerId, strlen(targetPlayerId)));
if (iTargetPlayerId == iVehicleOwnerId)
{
if (pTargetPlayerInfo->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_COP)
{
bShouldReportStolenEvenIfOwnedByPlayer = true;
}
}
}
}
}
}
}
}
}
}
// BAD_SEAT_USE
if( !pVehicle->m_nVehicleFlags.bHasBeenOwnedByPlayer || bShouldReportStolenEvenIfOwnedByPlayer)
{ // Player is stealing this car (for the first time)
pVehicle->m_nVehicleFlags.bHasBeenOwnedByPlayer = true;
// don't report as stolen if there is already another player driving it
if (pVehicle->m_nVehicleFlags.bCanBeStolen && !(pVehicle->GetSeatManager()->GetDriver() && pVehicle->GetSeatManager()->GetDriver()->IsPlayer() && pVehicle->GetSeatManager()->GetDriver() != this))
{
if(NetworkInterface::IsGameInProgress())
{
pVehicle->m_nVehicleFlags.bIsStolen = true;
}
if(pVehicle->GetVehicleType() == VEHICLE_TYPE_CAR)
{
CCrime::ReportCrime(CRIME_STEAL_CAR, pVehicle, this);
}
else
{
CCrime::ReportCrime(CRIME_STEAL_VEHICLE, pVehicle, this);
}
if(IsLocalPlayer())
{
CStatsMgr::UpdateVehiclesStolen(pVehicle);
}
}
}
// Reset any player vehicle control modifiers.
if( GetPlayerInfo() )
{
GetPlayerInfo()->ResetVehicleControls();
}
#if USE_SIXAXIS_GESTURES
if(IsLocalPlayer()
&& pVehicle)
{
if((bool)CPauseMenu::GetMenuPreference(PREF_SIXAXIS_CALIBRATION))
{
//const float fMinZeroPoint = CHeli::MOTION_CONTROL_PITCH_MAX -1.0f;
//const float fMaxZeroPoint = 1.0f + CHeli::MOTION_CONTROL_PITCH_MIN;
CPadGestureMgr::CalibratePlayerPitchFromCurrent();
}
else
{
CPadGestureMgr::ResetPlayerPitchCalibration();
}
if(CControlMgr::GetPlayerPad() && CControlMgr::GetPlayerPad()->GetPadGesture())
{
CControlMgr::GetPlayerPad()->GetPadGesture()->ResetPadYaw();
}
}
#endif // USE_SIXAXIS_GESTURES
//If ped is player and the target vehicle is law enforcement vehicle then set player's wanted
//level to a minimum of 1 and broadcast to the vehicle occupants
//that the player is trying to steal their cop car.
// Only generate stolen cop car events if the player isn't using a cop model
const CPedModelInfo* pModelInfo = static_cast<CPedModelInfo*>(GetBaseModelInfo());
Assert(pModelInfo);
Assert(pModelInfo->GetModelType() == MI_TYPE_PED);
Assertf(pModelInfo->GetDrawable() || pModelInfo->GetFragType(), "%s:Ped model is not loaded", pModelInfo->GetModelName());
const ePedType pedType = pModelInfo->GetDefaultPedType();
if(IsPlayer() && pVehicle->IsLawEnforcementVehicle() && pedType != PEDTYPE_COP)
{
CEventCopCarBeingStolen event(this,pVehicle);
GetEventGlobalGroup()->Add(event);
}
if (IsLocalPlayer())
{
CStatsMgr::PlayerEnteredVehicle(pVehicle);
if(pVehicle->GetVehicleType() == VEHICLE_TYPE_HELI || pVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE)
g_ScriptAudioEntity.SetAircraftWarningSpeechVoice(pVehicle);
// Reset homing toggle when we get into a vehicle
GetPlayerInfo()->GetTargeting().SetVehicleHomingEnabled(true);
}
if(pVehicle && pVehicle == CGameWorld::FindLocalPlayerVehicle())
{
NetworkInterface::ResolveWaypoint();
}
}
if( iTargetSeat == pVehicle->GetDriverSeat() )
{
if(IsPlayer())
{
if (pVehicle->GetStatus()!=STATUS_WRECKED)
{
pVehicle->SetStatus(STATUS_PLAYER);
}
//Turn on the special ability bar
if( IsLocalPlayer() &&
!HasSpecialAbility() && !CTheScripts::GetIsInDirectorMode() &&
( ( pVehicle->pHandling->hFlags & HF_HAS_KERS && pVehicle->m_Transmission.GetKERSAllowed() ) ||
pVehicle->HasJump() ||
pVehicle->HasRocketBoost() ||
pVehicle->HasNitrousBoost() ||
pVehicle->HasSideShunt() ) )
{
CNewHud::SetToggleAbilityBar(true);
uiDebugf3("CPed::SetPedOutOfVehicle::SetToggleAbilityBar true");
}
#if __BANK
else
{
uiDebugf3("CPed::SetPedOutOfVehicle::SetToggleAbilityBar not called");
bool bSpecialAbility = HasSpecialAbility();
bool bIsInDirectorsMode = CTheScripts::GetIsInDirectorMode();
bool bHasKernsFlag = (pVehicle->pHandling->hFlags & HF_HAS_KERS) == 1;
bool bKernsAllowed = pVehicle->m_Transmission.GetKERSAllowed();
bool bHasJump = pVehicle->HasJump();
bool bHasRocketBoost = pVehicle->HasRocketBoost();
uiDebugf3("CPed:: bSpecialAbility: %d, bDirectorMode: %d, bHasKernsFlag: %d, bKernsAllowed: %d, bHasJump: %d, bHasRocketBoost: %d ", bSpecialAbility, bIsInDirectorsMode, bHasKernsFlag, bKernsAllowed, bHasJump, bHasRocketBoost);
}
#endif
}
else
{
if (pVehicle->GetStatus()!=STATUS_WRECKED)
pVehicle->SetStatus(STATUS_PHYSICS);
}
//reset vehicle flags for driver type based on this driver's personality
const float fDriverAbility = CDriverPersonality::FindDriverAbility(this, pVehicle);
const float fDriverAggressiveness = CDriverPersonality::FindDriverAggressiveness(this, pVehicle);
if (fDriverAbility <= SMALL_FLOAT && fDriverAggressiveness >= 1.0f - SMALL_FLOAT )
{
pVehicle->m_nVehicleFlags.bMadDriver = true;
}
else
{
pVehicle->m_nVehicleFlags.bMadDriver = false;
}
if (fDriverAggressiveness <= SMALL_FLOAT && fDriverAbility <= SMALL_FLOAT)
{
pVehicle->m_nVehicleFlags.bSlowChillinDriver = true;
}
else
{
pVehicle->m_nVehicleFlags.bSlowChillinDriver = false;
}
}
// remove weapons
CPedWeaponManager* pWeaponMgr = GetWeaponManager();
if( pWeaponMgr )
{
pWeaponMgr->BackupEquippedWeapon();
CWeapon* pWeapon = pWeaponMgr->GetEquippedWeapon();
if (!pWeapon || !pWeapon->GetIsCooking()) //don't destroy cooking projectiles, you made a terrible mistake! B* 1226206
pWeaponMgr->DestroyEquippedWeaponObject(false OUTPUT_ONLY(,"CPed::SetPedInVehicle")); // Don't allow drop the current weapon, so the player can reequip it after getting out.
if( bShuffling )
{
//When shuffling to a new seat need to equip the vehicle weapon in that seat if there is any.
Assert(pVehicle);
if (pVehicle->GetVehicleWeaponMgr())
{
iEquippedVehicleWeapon = -1; //reset the equiped index cause shuffling to new seat
//BEGIN Code taken from CPedWeaponSelector::GetBestVehicleWeapon
atArray<const CVehicleWeapon*> weapons;
pVehicle->GetVehicleWeaponMgr()->GetWeaponsForSeat(iTargetSeat, weapons);
if (weapons.GetCount() > 0)
{
iEquippedVehicleWeapon = 0;
uEquippedWeapon = 0; //reset so the vehicle weapon is selected
}
//END Code taken from CPedWeaponSelector::GetBestVehicleWeapon
}
pWeaponMgr->EquipWeapon(uEquippedWeapon, iEquippedVehicleWeapon);
}
else
{
// Reset the driveby equip or fire time
pWeaponMgr->SetLastEquipOrFireTime(0);
if (pVehicle->GetVehicleModelInfo() && pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EQUIP_UNARMED_ON_ENTER))
{
pWeaponMgr->EquipWeapon(GetDefaultUnarmedWeaponHash());
}
else if( GetPedConfigFlag( CPED_CONFIG_FLAG_BlockWeaponSwitching ) )
{
if( !pWeaponMgr->GetWeaponSelector()->GetIsWeaponValid(this, GetWeaponManager()->GetEquippedWeaponInfo(), false, false) )
{
pWeaponMgr->EquipWeapon(GetDefaultUnarmedWeaponHash());
}
}
// Only change weapon if the selected weapon is not valid
else if( pWeaponMgr->GetEquippedWeaponHash() == GetDefaultUnarmedWeaponHash() ||
!pWeaponMgr->GetWeaponSelector()->GetIsWeaponValid(this, GetWeaponManager()->GetEquippedWeaponInfo(), false, false) )
{
GetWeaponManager()->EquipBestWeapon();
}
}
}
if (pSeatAnimInfo && pSeatAnimInfo->GetWeaponRemainsVisible() && GetWeaponManager())
{
bool bIsWeaponInLeftHand = pSeatAnimInfo->GetWeaponAttachedToLeftHand();
const CVehicleDriveByInfo* pDrivebyInfo = pSeatAnimInfo->GetDriveByInfo();
if (pDrivebyInfo && pDrivebyInfo->GetWeaponAttachedToLeftHand1HOnly())
{
CPedWeaponManager* pWeaponMgr = GetWeaponManager();
const CWeaponInfo* pWeaponInfo = pWeaponMgr ? pWeaponMgr->GetEquippedWeaponInfo() : NULL;
bIsWeaponInLeftHand = pWeaponInfo ? !pWeaponInfo->GetIsGun2Handed() : false;
}
bIsWeaponInLeftHand = bIsWeaponInLeftHand FPS_MODE_SUPPORTED_ONLY(|| ((IsInFirstPersonVehicleCamera() || IsFirstPersonShooterModeEnabledForPlayer(false)) && pDrivebyInfo->GetLeftHandedFirstPersonAnims()));
CPedEquippedWeapon::eAttachPoint attachPoint = bIsWeaponInLeftHand ? CPedEquippedWeapon::AP_LeftHand : CPedEquippedWeapon::AP_RightHand;
GetWeaponManager()->CreateEquippedWeaponObject(attachPoint);
}
if((iFlags & PVF_DontResetDefaultTasks) == 0)
{
GetPedIntelligence()->AddTaskDefault(ComputeDefaultDrivingTask(*this,pVehicle,iFlags&PVF_UseExistingNodes));
}
CTaskMotionBase* pPrimaryTask = GetPrimaryMotionTask();
if (((iFlags & PVF_DontRestartVehicleMotionState) == 0 ) && pPrimaryTask && pPrimaryTask->GetTaskType()==CTaskTypes::TASK_MOTION_PED)
{
// Restart the in vehicle state if we're in it (This will trigger a transition in the in vehicle state to another in vehicle state)
// otherwise let the transition from on foot to in vehicle happen
if (pPrimaryTask->GetState() == CTaskMotionPed::State_InVehicle)
{
static_cast<CTaskMotionPed*>(pPrimaryTask)->SetRestartCurrentStateThisFrame(true);
}
}
if(GetIsSkiing() && GetWeaponManager())
{
// Toggle skis off
GetWeaponManager()->EquipWeapon(GADGETTYPE_SKIS);
}
// Remove scuba variation in MP whenever we enter a vehicle
if (NetworkInterface::IsGameInProgress() && CTaskMotionSwimming::IsScubaGearVariationActiveForPed(*this))
{
CTaskMotionSwimming::ClearScubaGearVariationForPed(*this);
}
const CVehicleSeatInfo* pSeatInfo = pVehicle->GetSeatInfo(iTargetSeat);
// HACK: FLAG_PEDS_INSIDE_CAN_BE_SET_ON_FIRE_MP is tagged on vehicles that have open tops (caddy, dune, mower, etc.), only flag we have to identify these.
bool KeepHatOnInOpenTopVehicles = pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_PEDS_INSIDE_CAN_BE_SET_ON_FIRE_MP);
bool KeepHeadProp = (pSeatInfo && pSeatInfo->GetKeepOnHeadProp()) || KeepHatOnInOpenTopVehicles;
if (KeepHatOnInOpenTopVehicles)
{
// HACK: Only remove hats on the BIFTA if we have a roof mod.
static const u32 biftaHash = ATSTRINGHASH("bifta", 0xeb298297);
const u32 vehModelHash = pVehicle->GetVehicleModelInfo()->GetModelNameHash();
if (vehModelHash == biftaHash)
{
const CVehicleVariationInstance& variation = pVehicle->GetVariationInstance();
if (variation.GetModIndex(VMT_ROOF) != INVALID_MOD)
KeepHeadProp = false;
}
}
// Keep pilot mask on (Flight School DLC) if in a plane or heli
if ((pVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE || pVehicle->GetVehicleType() == VEHICLE_TYPE_HELI) && CPedPropsMgr::CheckPropFlags(this, ANCHOR_HEAD, PV_FLAG_PILOT_HELMET))
{
KeepHeadProp = true;
}
// B*2043514: Don't remove hat if vehicle is tagged to have no roof and is tagged to allow hats (can't just check for no roof because some vehicles such as the cheetah are mistagged, bit scary to change this now).
bool bRemoveHat = true;
if (pVehicle->CarHasRoof() && pVehicle->GetVehicleModelInfo() && pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_NO_ROOF)
&& pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_ALLOW_HATS_NO_ROOF))
{
bRemoveHat = false;
}
// B*3170483: Want to hide hat even though car has no roof, don't want to edit CarHasRoof() to not affect other areas where it's used
bool bTreatAsHasRoof = false;
if (MI_CAR_VOLTIC2.IsValid() && pVehicle->GetModelIndex() == MI_CAR_VOLTIC2)
{
bTreatAsHasRoof = true;
}
s32 iHeadPropIndex = CPedPropsMgr::GetPedPropIdx(this, ANCHOR_HEAD);
if (KeepHeadProp && iHeadPropIndex != -1)
{
m_bKeepingCurrentHelmetInVehicle = true;
}
// Remove hats for specific vehicle types
switch(pVehicle->GetVehicleType())
{
case VEHICLE_TYPE_CAR:
case VEHICLE_TYPE_HELI:
case VEHICLE_TYPE_PLANE:
case VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE:
case VEHICLE_TYPE_SUBMARINECAR:
if((bTreatAsHasRoof || pVehicle->CarHasRoof()) && bRemoveHat)
{
if (!KeepHeadProp && pSeatInfo->ShouldRemoveHeadProp(*this))
{
// Backup any hat (not helmets)
if (GetHelmetComponent())
GetHelmetComponent()->SetStoredHatIndices(CPedPropsMgr::GetPedPropIdx(this, ANCHOR_HEAD), CPedPropsMgr::GetPedPropTexIdx(this, ANCHOR_HEAD));
CPedPropsMgr::ClearPedProp(this, ANCHOR_HEAD);
if (GetPedConfigFlag(CPED_CONFIG_FLAG_HasHelmet))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_HasHelmet, false);
m_bHidingHelmetInVehicle = true;
}
}
}
break;
default:
// Remove helmet if not getting on a bike
if(!KeepHeadProp && pVehicle->GetVehicleType() != VEHICLE_TYPE_BIKE && pVehicle->GetVehicleType() != VEHICLE_TYPE_QUADBIKE && pVehicle->GetVehicleType() != VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE && pVehicle->GetVehicleType() != VEHICLE_TYPE_BICYCLE && GetHelmetComponent() && GetHelmetComponent()->IsHelmetEnabled())
{
// Disable bike helmet
GetHelmetComponent()->DisableHelmet();
}
break;
}
// If entering an RC car, make player invisible
if(pVehicle->pHandling->mFlags & MF_IS_RC)
{
SetIsVisibleForModule(SETISVISIBLE_MODULE_WORLD, false, true);
DisableCollision(0, true);
}
if (GetNetworkObject() && !IsNetworkClone() && GetNetworkObject()->GetSyncData())
{
// make sure that the in vehicle state is sent immediately in the next update for this ped, this prevents problems where the state is sent
// out later after the ped is doing something interesting (like dying)
CNetObjPed *netObjPed = static_cast<CNetObjPed *>(GetNetworkObject());
netObjPed->RequestForceSendOfGameState();
}
// Give player a parachute if the flag is set
bool bVehicleValidForParachute = pVehicle->GetVehicleType() == VEHICLE_TYPE_HELI || pVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE ||
pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_PARACHUTE) || pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_GLIDER) ||
(MI_BIKE_OPPRESSOR2.IsValid() && pVehicle->GetModelIndex() == MI_BIKE_OPPRESSOR2) || (MI_BLIMP_3.IsValid() && pVehicle->GetModelIndex() == MI_BLIMP_3);
if(GetPlayerInfo() && GetPlayerInfo()->GetAutoGiveParachuteWhenEnterPlane() && GetInventory() && bVehicleValidForParachute)
{
CTaskParachute::GivePedParachute(this);
}
// Register in the spatial array that we are now using a vehicle.
UpdateSpatialArrayTypeFlags();
// remove any NOT_IN_CAR items from the ped
// FA: do we want to tag them as culled and just not render them? this way the item will be visible
// again once the ped exits the car, but the pop when the item appears might not be very nice.
if (!IsPlayer() && !PopTypeIsMission() && !GetPedModelInfo()->GetIsStreamedGfx() REPLAY_ONLY(&& !CReplayMgr::IsReplayInControlOfWorld()))
{
const bool bIsOnBike = m_pMyVehicle && (m_pMyVehicle->InheritsFromBike() || m_pMyVehicle->InheritsFromQuadBike() || m_pMyVehicle->InheritsFromAmphibiousQuadBike() || m_pMyVehicle->GetIsJetSki());
const bool bDisableInVehicleBlocking = bIsOnBike || GetPedResetFlag(CPED_RESET_FLAG_DisableInVehiclePedVariationBlocking);
CPedVariationInfoCollection* varInfo = GetPedModelInfo()->GetVarInfo();
if (!bDisableInVehicleBlocking && varInfo)
{
CPedVariationData& varData = GetPedDrawHandler().GetVarData();
for (u32 i = 0; i < PV_MAX_COMP; ++i)
{
u32 drawable = varData.GetPedCompIdx((ePedVarComp)i);
if (drawable == PV_NULL_DRAWBL)
continue;
if (varInfo->HasComponentFlagsSet((ePedVarComp)i, drawable, PV_FLAG_NOT_IN_CAR))
{
varData.SetPedVariation((ePedVarComp)i, PV_NULL_DRAWBL, 0, 0, 0, NULL, false);
#if !__FINAL
pedDisplayf("Remove NOT_IN_CAR component (%d) from ped %s",i, GetModelName());
#endif //!__FINAL
}
}
for (u32 i = 0; i < NUM_ANCHORS; ++i)
{
if (CPedPropsMgr::CheckPropFlags(this, (eAnchorPoints)i, PV_FLAG_NOT_IN_CAR))
{
CPedPropsMgr::ClearPedProp(this, (eAnchorPoints)i);
#if !__FINAL
pedDisplayf("Remove NOT_IN_CAR prop (anchor %d) from ped %s",i, GetModelName());
#endif //!__FINAL
}
}
}
}
}
void CPed::SetPedOutOfVehicle(u32 iFlags, s32 nNumFramesJustLeftOverride)
{
#if DR_ENABLED
debugPlayback::RecordPedSetOutOfVehicle(*this, GetMyVehicle(), iFlags);
#endif // DR_ENABLED
#if __BANK
if (GetIsInVehicle())
{
TUNE_GROUP_BOOL(SET_PED_OUT_OF_VEHICLE, bLogCallstacksForAllPeds, false);
if (IsAPlayerPed() || PopTypeIsMission())
{
AI_LOG_WITH_ARGS("[VehicleEntryExit][CPed::SetPedOutOfVehicle] - Setting %s ped %s out of vehicle %s, seat %i\n", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityNameSafe(GetMyVehicle()), GetMyVehicle() ? GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(this) : -1);
if (IsAPlayerPed() || bLogCallstacksForAllPeds)
{
AI_LOG_STACK_TRACE(6);
aiDebugf1("[VehicleEntryExit][CPed::SetPedOutOfVehicle] - Setting %s ped %s out of vehicle %s, seat %i\n", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), AILogging::GetDynamicEntityNameSafe(GetMyVehicle()), GetMyVehicle() ? GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(this) : -1);
sysStack::PrintStackTrace();
}
}
}
#endif // __BANK
if( GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) )
{
CVehicle* pVehicle = GetMyVehicle();
if (!(iFlags & PVF_IgnoreSettingJustLeftVehicle))
{
// The default set here used to be 10, but it was being put into a 2-bit field so the actual default value being used was 2.
// I've increased the number of bits for that field to four for B*1814957, but I'm keeping the default value at 2 just so as not to mess with anything else that worked with the default of 2.
TUNE_GROUP_INT(SET_PED_OUT_OF_VEHICLE, NUM_FRAMES_TO_CONSIDER_JUST_LEFT_DEFAULT, 2, 0, 15, 1);
// B*1814957: The ped is set to not collide with the VOLTIC in CTaskExitVehicleSeat::ResetRagdollOnCollision.
// To make sure that the ped has plenty of time to get itself free of the problem areas, set the number of frames before resetting the no collision entity to ten; the default two frames wasn't quite enough in some cases.
if(ShouldBeDead() && pVehicle->GetModelIndex() == MI_CAR_VOLTIC)
nNumFramesJustLeftOverride = 15;
SetHasJustLeftVehicle(nNumFramesJustLeftOverride >= 0 ? nNumFramesJustLeftOverride : NUM_FRAMES_TO_CONSIDER_JUST_LEFT_DEFAULT);
}
//Note that we left a vehicle.
GetPedIntelligence()->LeftVehicle();
// should we resolve waypoint
bool bResolveWaypoint = false;
if(pVehicle && pVehicle == CGameWorld::FindLocalPlayerVehicle())
{
bResolveWaypoint = true;
}
bool bIsTula = MI_PLANE_TULA.IsValid() && pVehicle && pVehicle->GetModelIndex() == MI_PLANE_TULA;
if (!bIsTula && pVehicle && pVehicle->InheritsFromAutomobile())
{
const s32 iSeatIndex = pVehicle->GetSeatManager()->GetPedsSeatIndex(this);
if (pVehicle->IsSeatIndexValid(iSeatIndex))
{
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iSeatIndex);
if (pSeatAnimInfo && pSeatAnimInfo->IsTurretSeat() && !pSeatAnimInfo->IsStandingTurretSeat())
{
TUNE_GROUP_INT(TURRET_TUNE, CLOSE_DOOR_TIME, 500, 0, 2000, 100);
static_cast<CAutomobile*>(pVehicle)->SetAutoDoorTimer(CLOSE_DOOR_TIME, CAutomobile::CLOSE_ONLY);
}
}
}
if (IsNetworkClone())
{
static_cast<CNetObjPed*>(GetNetworkObject())->SetClonePedOutOfVehicle(false, iFlags);
CObject* pWeapon = GetWeaponManager() ? GetWeaponManager()->GetEquippedWeaponObject() : NULL;
if(pWeapon && !pWeapon->GetIsVisible())
{
pWeapon->SetIsVisibleForModule( SETISVISIBLE_MODULE_GAMEPLAY, true, true );
AI_LOG_WITH_ARGS("[WEAPON_VISIBILITY] - %s ped %s has restored weapon %s visibility on cleanup in CPed::SetPedOutOfVehicle\n", AILogging::GetDynamicEntityIsCloneStringSafe(this), AILogging::GetDynamicEntityNameSafe(this), pWeapon->GetWeapon() ? (pWeapon->GetWeapon()->GetWeaponInfo() ? pWeapon->GetWeapon()->GetWeaponInfo()->GetName() : "NULL") : "NULL");
}
}
else
{
Vector3 vecNewPosition;
EnableCollision();
SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, false );
//Replay records detach states, this isn't needed
REPLAY_ONLY(if(!CReplayMgr::IsEditModeActive() || !(iFlags & PVF_DontAllowDetach)))
{
u16 nDetachFlags = DETACH_FLAG_ACTIVATE_PHYSICS;
if(iFlags&PVF_IgnoreSafetyPositionCheck || GetPedResetFlag(CPED_RESET_FLAG_IgnoreDetachSafePositionCheck))
nDetachFlags |= DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK;
if(iFlags&PVF_Warp)
nDetachFlags |= DETACH_FLAG_SKIP_CURRENT_POSITION_CHECK;
if(iFlags&PVF_NoCollisionUntilClear)
nDetachFlags |= DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR;
if(iFlags&PVF_ExcludeVehicleFromSafetyCheck)
nDetachFlags |= DETACH_FLAG_EXCLUDE_VEHICLE;
if(iFlags&PVF_DontSnapMatrixUpright)
nDetachFlags |= DETACH_FLAG_DONT_SNAP_MATRIX_UPRIGHT;
if(iFlags&PVF_IsUpsideDownExit)
nDetachFlags |= DETACH_FLAG_USE_UPSIDE_DOWN_POINT;
DetachFromParent(nDetachFlags);
}
// re-pose the character before the render to get rid of the driving anims completely
if( iFlags&PVF_Warp )
{
// Remove any lingering task networks immediately if none are owned
if ((iFlags&PVF_ClearTaskNetwork) && !GetMovePed().GetTaskNetwork())
{
GetMovePed().SetTaskNetwork(NULL);
}
GetIkManager().ResetAllSolvers();
SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAnimUpdate, true );
}
if(pVehicle) // calback function, car could be destroyed
{
pVehicle->m_fPreviousSpeedForDash = 0.0f;
if(!(iFlags&PVF_IgnoreSafetyPositionCheck) && !(iFlags&PVF_DontNullVelocity) && pVehicle->GetVehicleType()!=VEHICLE_TYPE_BOAT)
{
SetVelocity(Vector3(0.0f, 0.0f, 0.0f));
}
if( (iFlags&PVF_InheritVehicleVelocity) )
{
Vector3 vDesiredVelocity = VEC3V_TO_VECTOR3(GetTransform().Transform3x3(VECTOR3_TO_VEC3V(GetAnimatedVelocity())));
vDesiredVelocity += pVehicle->GetVelocity();
const Vector3 vDesiredAngVelocity = GetDesiredAngularVelocity();
SetVelocity(vDesiredVelocity);
SetAngVelocity(vDesiredAngVelocity);
}
if(pVehicle->IsDriver(this)) // need this check because there may be a new driver already
{
//if the driver was the player, also clear out the vehicle's backup followroute
if (IsPlayer())
{
pVehicle->GetIntelligence()->m_BackupFollowRoute.Invalidate();
}
if (!pVehicle->GetIsUsingScriptAutoPilot())
{
if (pVehicle->GetCarDoorLocks() == CARLOCK_LOCKED_INITIALLY)
pVehicle->SetCarDoorLocks(CARLOCK_UNLOCKED);
if(!(iFlags&PVF_DontResetDefaultTasks) && !pVehicle->IsNetworkClone() && !pVehicle->m_nVehicleFlags.bHasParentVehicle)
{
//only set an initial vehicle task if we are the driver.
CTask *pTask = rage_new CTaskVehicleStop(CTaskVehicleStop::SF_SupressBrakeLight);
pVehicle->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pTask, VEHICLE_TASK_PRIORITY_PRIMARY, false);
}
}
}
SetPedResetFlag(CPED_RESET_FLAG_PedExitedVehicleThisFrame, true);
pVehicle->RemovePedFromSeat(this);
// If we are in a garage we switch off the lights (to stop them going through door)
Vector3 TempoVect = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
if (CGarages::IsPointWithinAnyGarage(TempoVect))
{
pVehicle->m_OverrideLights = NO_CAR_LIGHT_OVERRIDE;
pVehicle->m_nVehicleFlags.bLightsOn = false;
}
}
// not sure if always want to do this here 'cause ped might've been knocked off?/
// it's ok - will get cleared again in knock off bike event response
if(pVehicle && pVehicle->InheritsFromBike()
&& rage::Abs(pVehicle->GetVelocity().x)<0.1
&& rage::Abs(pVehicle->GetVelocity().y)<0.1 )
{
if (!pVehicle->IsNetworkClone())
{
((CBike *)pVehicle)->m_nBikeFlags.bOnSideStand = true;
}
}
if(!(iFlags&PVF_DontResetDefaultTasks))
{
CTask* pNewDefaultTask = ComputeDefaultTask(*this);
CTask* pCurrentDefaultTask = GetPedIntelligence()->GetTaskDefault();
const bool bRandomOnABike = PopTypeIsRandom() && pVehicle && pVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE;
if( pNewDefaultTask && ( pCurrentDefaultTask == NULL || pNewDefaultTask->GetTaskType() != pCurrentDefaultTask->GetTaskType() ) && !bRandomOnABike )
{
GetPedIntelligence()->AddTaskDefault(pNewDefaultTask);
}
else if( pNewDefaultTask )
{
delete pNewDefaultTask;
pNewDefaultTask = NULL;
}
}
CPedWeaponManager* pWeaponMgr = GetWeaponManager();
if( pWeaponMgr )
{
if(!(iFlags&PVF_DontDestroyWeaponObject))
{
// First we want to try and destroy the equipped weapon object
// since we could have simply toggled the visibility for drive by reasons
// We want to destroy the weapon object now so we can do the unholster anims
CWeapon* pWeapon = pWeaponMgr->GetEquippedWeapon();
if (pWeaponMgr->GetEquippedWeaponObject() && (!pWeapon || !pWeapon->GetIsCooking())) //don't destroy cooking projectiles, you made a terrible mistake! B* 1226206
{
pWeaponMgr->DestroyEquippedWeaponObject(true OUTPUT_ONLY(,"CPed::SetPedOutOfVehicle"));
}
if (NetworkInterface::IsInCopsAndCrooks() && IsPlayer())
{
bool bEquipSuccess = false;
const CPlayerInfo* pPlayerInfo = GetPlayerInfo();
const eArcadeTeam arcadeTeam = pPlayerInfo->GetArcadeInformation().GetTeam();
if (arcadeTeam == eArcadeTeam::AT_CNC_COP)
{
bEquipSuccess = pWeaponMgr->EquipBestNonLethalWeapon();
}
else if (arcadeTeam == eArcadeTeam::AT_CNC_CROOK)
{
bEquipSuccess = pWeaponMgr->EquipBestWeapon();
}
if (bEquipSuccess)
{
pWeaponMgr->ClearBackupWeapon();
}
}
else
{
// If we have equipped or fired a driveby weapon in the last DRIVEBY_WEAPON_EQUIP_TIME, keep that as the weapon, don't restore the backup
TUNE_GROUP_INT(WEAPON_TUNE, DRIVEBY_WEAPON_EQUIP_TIME, 20000, 0, 999999, 1);
if(!IsLocalPlayer() || pWeaponMgr->GetLastEquipOrFireTime() == 0 || (pWeaponMgr->GetLastEquipOrFireTime() + DRIVEBY_WEAPON_EQUIP_TIME < fwTimer::GetTimeInMilliseconds()))
{
pWeaponMgr->RestoreBackupWeapon();
}
else
{
pWeaponMgr->ClearBackupWeapon();
}
}
}
// Special case for when a player leaves a vehicle with a vehicle weapon and has no backup weapon
if( pWeaponMgr->GetEquippedWeaponHash() == 0 )
pWeaponMgr->EquipWeapon(GetDefaultUnarmedWeaponHash(), -1, true);
}
SetPedConfigFlag( CPED_CONFIG_FLAG_HasJustLeftCar, true );
// make sure the respot visibility module is reset (the ped may be ejected from a car while it is respotting)
SetIsVisibleForModule( SETISVISIBLE_MODULE_VEHICLE_RESPOT, true, true );
// do networky stuff
if (GetNetworkObject())
{
NetworkInterface::NetworkedPedPlacedOutOfVehicle(*this, pVehicle);
}
if ( IsLocalPlayer() )
{
// If we're the player and we're about to be thrown out of our current vehicle release the dials.
pVehicle->GetVehicleModelInfo()->ReleaseDials();
CStatsMgr::PlayerLeftVehicle(pVehicle);
//Turn off the special ability bar
if( !HasSpecialAbility() &&
( (pVehicle->pHandling->hFlags & HF_HAS_KERS) || pVehicle->HasJump() || pVehicle->HasRocketBoost() || pVehicle->HasNitrousBoost() ) &&
(CTheScripts::GetIsInDirectorMode() || NetworkInterface::IsGameInProgress() ) )
{
CNewHud::SetToggleAbilityBar(false);
uiDebugf3("CPed::SetPedOutOfVehicle::SetToggleAbilityBar false");
}
}
// Remove helmet (headset) for helicopters and planes
bool bIsKeepingOwnedHelmetInVehicle = NetworkInterface::IsGameInProgress() && m_bKeepingCurrentHelmetInVehicle;
if (!bIsKeepingOwnedHelmetInVehicle && GetHelmetComponent() && GetHelmetComponent()->GetPropFlag() == PV_FLAG_FLIGHT_HELMET && (pVehicle->GetVehicleType() == VEHICLE_TYPE_HELI || pVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE))
{
//HACK: A different helmet is used for DUSTER and STUNT for single player characters only, make sure it gets removed correctly
const CPedModelInfo* pPedModelInfo = GetPedModelInfo();
const CVehicleModelInfo* pVehicleModelInfo = pVehicle->GetVehicleModelInfo();
s32 iHeadPropIndex = CPedPropsMgr::GetPedPropIdx(this, ANCHOR_HEAD);
if(pVehicleModelInfo && pVehicleModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_PLANE_WEAR_ALTERNATIVE_HELMET) && iHeadPropIndex != -1)
{
//Reset the override indexs
if( (pPedModelInfo->GetModelNameHash() == MI_PLAYERPED_PLAYER_TWO.GetName().GetHash() && iHeadPropIndex == ALTERNATIVE_FLIGHT_HELMET_PLAYER_TWO) ||
(pPedModelInfo->GetModelNameHash() == MI_PLAYERPED_PLAYER_ONE.GetName().GetHash() && iHeadPropIndex == ALTERNATIVE_FLIGHT_HELMET_PLAYER_ONE) ||
(pPedModelInfo->GetModelNameHash() == MI_PLAYERPED_PLAYER_ZERO.GetName().GetHash() && iHeadPropIndex == ALTERNATIVE_FLIGHT_HELMET_PLAYER_ZERO) )
{
GetHelmetComponent()->SetHelmetPropIndex(-1);
GetHelmetComponent()->SetHelmetTexIndex(-1);
}
}
GetHelmetComponent()->DisableHelmet();
}
if(!(iFlags&PVF_IsBeingJacked))
{
RestoreHatPropOnVehicleExit();
}
// If entering an RC car, make player invisible
if(pVehicle && pVehicle->pHandling->mFlags & MF_IS_RC)
{
SetIsVisibleForModule(SETISVISIBLE_MODULE_WORLD, true, true);
SetIsVisibleForModule(SETISVISIBLE_MODULE_FIRST_PERSON, true, true);
EnableCollision();
}
if (GetNetworkObject() && GetNetworkObject()->GetSyncData())
{
// make sure that the out of vehicle state is sent immediately in the next update for this ped, this prevents problems where the state is sent
// out later after the ped is doing something interesting (like dying)
CNetObjPed *netObjPed = static_cast<CNetObjPed *>(GetNetworkObject());
netObjPed->RequestForceSendOfGameState();
NetworkInterface::ForceTaskStateUpdate(*this);
netObjPed->RequestForceSendOfTaskState();
netObjPed->RequestForceSendOfEntityScriptGameState();
}
}
//Check if the ped should be given scuba gear.
if(GetPlayerInfo() && GetPlayerInfo()->GetAutoGiveScubaGearWhenExitVehicle() && pVehicle &&
pVehicle->GetVehicleModelInfo()->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_GIVE_SCUBA_GEAR_ON_EXIT) &&
(pVehicle->m_Buoyancy.GetStatus() != NOT_IN_WATER) &&
!(iFlags & CPed::PVF_KeepInVehicleMoveBlender))
{
CTaskMotionSwimming::SetScubaGearVariationForPed(*this);
}
// Register in the spatial array that we are no longer considered using a vehicle.
UpdateSpatialArrayTypeFlags();
// resolve waypoint
if(bResolveWaypoint)
{
NetworkInterface::ResolveWaypoint();
}
if (GetPedConfigFlag(CPED_CONFIG_FLAG_ResetLastVehicleOnVehicleExit))
{
SetPedConfigFlag(CPED_CONFIG_FLAG_ResetLastVehicleOnVehicleExit, false);
SetMyVehicle(NULL);
}
}
}
void CPed::RestoreHatPropOnVehicleExit()
{
static dev_float MIN_DISTANCE_SQ = 100.0f;
if (GetHelmetComponent())
{
GetHelmetComponent()->RestoreStoredHatIfSafe(MIN_DISTANCE_SQ);
if (m_bHidingHelmetInVehicle)
{
m_bHidingHelmetInVehicle = false;
SetPedConfigFlag(CPED_CONFIG_FLAG_HasHelmet, true);
}
m_bKeepingCurrentHelmetInVehicle = false;
}
}
void CPed::SetPedOnMount(CPed* ENABLE_HORSE_ONLY(pMount), const s32 ENABLE_HORSE_ONLY(iTargetSeat), bool ENABLE_HORSE_ONLY(bAnimated), bool ENABLE_HORSE_ONLY(bSwitchMotionTask))
{
#if ENABLE_HORSE
//Ensure the mount is changing.
if(pMount == GetMyMount())
return;
if (GetUsingRagdoll())
return;
if (!GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_MOUNT_ANIMAL))
GetPedIntelligence()->FlushImmediately(true);
// Set the mount pointer and on mount flag
SetMyMount(pMount);
SetPedConfigFlag(CPED_CONFIG_FLAG_OnMount, true);
bool bSeated = false;
s32 iResultSeat = iTargetSeat;
if (pMount->m_pSeatManager)
{
if (iTargetSeat==-1)
{
for(int i=0; i< pMount->GetSeatManager()->GetMaxSeats() && !bSeated; i++)
{
if(!pMount->GetSeatManager()->GetPedInSeat(i))
{
bSeated = pMount->m_pSeatManager->AddPedInSeat(this, i);
if (bSeated)
iResultSeat = i;
}
}
}
else
bSeated = pMount->m_pSeatManager->AddPedInSeat(this, iTargetSeat);
pMount->GetHorseComponent()->UpdateMountState();
}
if (!bSeated)
{ //abort!
SetMyMount(NULL);
SetPedConfigFlag(CPED_CONFIG_FLAG_OnMount, false);
return;
}
//pMount->AddRider();
GetMotionData()->SetDesiredMoveBlendRatio(MOVEBLENDRATIO_STILL);
//Start the primary motion task.
StartPrimaryMotionTask();
//Start the primary motion task.
if (bSwitchMotionTask)
StartPrimaryMotionTask();
if (!GetIsAttached())
{
//Attach the ped to the mount.
Vector3 attachOffset(Vector3::ZeroType);
s16 boneIndex = static_cast<s16>(pMount->GetPedModelInfo()->GetModelSeatInfo()->GetBoneIndexFromSeat(iResultSeat));
AttachPedToMountAnimal(pMount, (u16)(boneIndex!=-1 ? boneIndex : 0), ATTACH_STATE_PED_ON_MOUNT|ATTACH_FLAG_INITIAL_WARP, &attachOffset, 0.0f, 0.0f);
}
//Check if the horse is owned locally.
if(!pMount->IsNetworkClone())
{
//Clear the horse tasks. B*94564
if (!bAnimated)
{
pMount->GetPedIntelligence()->ClearTasks(true, true);
pMount->GetPedIntelligence()->GetTaskManager()->SetTask(PED_TASK_TREE_PRIMARY, rage_new CTaskDoNothing(-1), PED_TASK_PRIORITY_PRIMARY);
}
pMount->GetPedIntelligence()->GetTaskManager()->SetTask(PED_TASK_TREE_MOVEMENT, rage_new CTaskMoveMounted(), PED_TASK_MOVEMENT_DEFAULT);
}
// remove weapons
if( GetWeaponManager() )
{
GetWeaponManager()->BackupEquippedWeapon();
GetWeaponManager()->DestroyEquippedWeaponObject(true OUTPUT_ONLY(,"CPed::SetPedOnMount"));
GetWeaponManager()->EquipBestWeapon();
}
if(!bAnimated)
{
GetPedIntelligence()->AddTaskDefault(ComputeDefaultTask(*this));
if (pedVerifyf(GetPedIntelligence()->GetMotionTaskActiveSimplest()->GetTaskType() == CTaskTypes::TASK_MOTION_RIDE_HORSE, "Rider missing TASK_MOTION_RIDE_HORSE!!"))
{
pMount->GetHorseComponent()->SetStirrupEnable(true, true);
pMount->GetHorseComponent()->SetStirrupEnable(false, true);
}
}
//Check if this ped is the local player.
if(IsLocalPlayer())
{
//Check if the mount is a horse.
if(pedVerifyf(pMount->GetHorseComponent(), "The mount is not a horse."))
{
//Check if the player has not owned the horse.
if(!pMount->GetHorseComponent()->GetHasBeenOwnedByPlayer())
{
//The player owns the horse now.
pMount->GetHorseComponent()->SetHasBeenOwnedByPlayer(true);
}
}
}
else
{
CPed* myMount = GetMyMount();
//Assert( myMount );
if( myMount && iResultSeat == 0 )
{
CHorseComponent* horseComponent = myMount->GetHorseComponent();
if( horseComponent )
{
CReins* pReins = horseComponent->GetReins();
if( pReins )
{
pReins->SetHasRider( true );
pReins->AttachOneHand( this );
}
}
}
}
#else
pedAssert(0);
#endif
}
void CPed::SetPedOffMount(bool ENABLE_HORSE_ONLY(bClearHorseTasks))
{
#if ENABLE_HORSE
if( GetPedConfigFlag( CPED_CONFIG_FLAG_OnMount ))
{
if( !GetPedConfigFlag( CPED_CONFIG_FLAG_OnMount ) )
return;
SetPedConfigFlag( CPED_CONFIG_FLAG_OnMount, false );
u8 nDetachFlags = DETACH_FLAG_ACTIVATE_PHYSICS;
DetachFromParent(nDetachFlags);
if(m_pMyMount )
{
//Ensure the horse is not a network clone.
if (bClearHorseTasks && !m_pMyMount->IsNetworkClone())
{
//Clear the horse tasks.
CEventGivePedTask primaryTaskEvent(PED_TASK_PRIORITY_PRIMARY, rage_new CTaskDoNothing(-1));
m_pMyMount->GetPedIntelligence()->AddEvent(primaryTaskEvent, true, true);
m_pMyMount->GetPedIntelligence()->GetTaskManager()->SetTask(PED_TASK_TREE_MOVEMENT, rage_new CTaskMoveStandStill(), PED_TASK_MOVEMENT_DEFAULT);
}
if (m_pMyMount->m_pSeatManager) {
m_pMyMount->m_pSeatManager->RemovePedFromSeat(this);
m_pMyMount->GetHorseComponent()->UpdateMountState();
}
CHorseComponent* pHorseComponent = m_pMyMount->GetHorseComponent();
if( pHorseComponent )
{
CReins* pReins = pHorseComponent->GetReins();
if( pReins )
{
pReins->Detach( m_pMyMount );
pReins->SetHasRider( false );
}
}
SetMyMount(NULL);
CPedWeaponManager* pWeaponMgr = GetWeaponManager();
if(pWeaponMgr)
{
// First we want to try and destroy the equipped weapon object
// since we could have simply toggled the visibility for drive by reasons
// We want to destroy the weapon object now so we can do the unholster anims
if( pWeaponMgr->GetEquippedWeaponObject() )
pWeaponMgr->DestroyEquippedWeaponObject(true OUTPUT_ONLY(,"CPed::SetPedOffMount"));
pWeaponMgr->RestoreBackupWeapon();
}
}
StartPrimaryMotionTask();
}
#endif
}
void CPed::GetPedRadioCategory(s32 &firstRadioCategory, s32 &secondRadioCategory) const
{
CPedModelInfo *modelInfo = ((CPedModelInfo*)GetBaseModelInfo());
Assert(modelInfo);
firstRadioCategory = modelInfo->GetFirstRadioStation();
secondRadioCategory = modelInfo->GetSecondRadioStation();
}
dev_float CHANCE_OF_COWERING_IN_INTERIORS = 0.66f;
bool CPed::WillCowerInInteriors()
{
return (float)( GetRandomSeed()/(float)RAND_MAX_16 ) <= CHANCE_OF_COWERING_IN_INTERIORS;
}
bool CPed::CanPutHandsUp( int responseFleeFlags ) const
{
// From script
if ( GetPedIntelligence()->GetFleeBehaviour().GetFleeFlags().IsFlagSet(CFleeBehaviour::BF_DisableHandsUp) )
{
return false;
}
// From settors (usually event response)
if ( responseFleeFlags & ( 1<<DisableHandsUp ) )
{
return false;
}
// From ped properties
if ( GetTaskData().GetIsFlagSet(CTaskFlags::DisableHandsUp) )
{
return false;
}
return true;
}
bool CPed::CanUseCover( int responseFleeFlags ) const
{
// From script
if ( !GetPedIntelligence()->GetFleeBehaviour().GetFleeFlags().IsFlagSet(CFleeBehaviour::BF_CanUseCover) )
{
return false;
}
// From settors (usually event response)
if ( responseFleeFlags & ( 1<<DisableCover ) )
{
return false;
}
// From ped properties
if ( GetTaskData().GetIsFlagSet(CTaskFlags::DisableCover) )
{
return false;
}
return true;
}
bool CPed::WillReturnToPositionPostFlee( int responseFleeFlags ) const
{
// From script
if ( GetPedIntelligence()->GetFleeBehaviour().GetFleeFlags().IsFlagSet(CFleeBehaviour::BF_WillReturnToOriginalPositionAfterFlee) )
{
return true;
}
// From settors (usually event response)
if ( responseFleeFlags & ( 1<<IsShortTerm ) )
{
return true;
}
// From ped properties
// if (ped...)
// TODO: To come from Neil D.
return false;
}
bool CPed::GetIsPanicking() const
{
return GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_THREAT_RESPONSE) != NULL;
}
// dev_bool APPLY_PICKUP_OFFSET = true;
// dev_bool APPLY_PULLUP_OFFSET = true;
// dev_float PICKUP_OFFSET_SCALE = 2.0f;
// dev_float PULLUP_OFFSET_SCALE = 1.0f;
void CPed::SetCoverPoint( CCoverPoint * val )
{
Assert( !val || val->GetType() != CCoverPoint::COVTYPE_NONE );
if (AssertVerify(!IsNetworkClone()))
{
if( m_pCoverPoint )
{
ReleaseCoverPoint();
}
m_pCoverPoint = val;
}
}
void CPed::SetDesiredCoverPoint( CCoverPoint * val)
{
Assert( !val || val->GetType() != CCoverPoint::COVTYPE_NONE );
if (AssertVerify(!IsNetworkClone()))
{
m_pDesiredCoverPoint = val;
}
}
#if FPS_MODE_SUPPORTED
float CPed::ComputeLowCoverHeightOffsetFromMover(float fInitialZOffset, float fFinalZOffset, float fIncrementZOffset, float fProbeLength)
{
float fCoverDistanceXY = -1.0f;
if (IsFirstPersonShooterModeEnabledForPlayer(false) && GetCoverPoint() && GetCoverPoint()->GetHeight() != CCoverPoint::COVHEIGHT_TOOHIGH)
{
Vector3 vPedPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
Vector3 vProbeDir = VEC3V_TO_VECTOR3(GetCoverPoint()->GetCoverDirectionVector()) * fProbeLength;
m_fLowCoverHeightOffsetFromMover = fInitialZOffset;
Vector3 vStartPos = vPedPos;
Vector3 vEndPos = vStartPos + vProbeDir;
Vector3 vIntersectionPos(Vector3::ZeroType);
// Limit to a maximum of 10 probes for sanity
const int iMaxNumProbes = 10;
int iNumProbesRemaining = iMaxNumProbes;
while (iNumProbesRemaining > 0)
{
vStartPos.z = vPedPos.z + m_fLowCoverHeightOffsetFromMover;
vEndPos.z = vStartPos.z;
if (CDynamicCoverHelper::DoLineProbeTest(vStartPos, vEndPos, vIntersectionPos) == NULL)
{
aiDebugf3("m_fLowCoverHeightOffsetFromMover = %.2f", m_fLowCoverHeightOffsetFromMover);
break;
}
if (Approach(m_fLowCoverHeightOffsetFromMover, fFinalZOffset, fIncrementZOffset, 1.0f))
{
break;
}
iNumProbesRemaining--;
}
// If we didn't break out of the loop right away that means we found a valid cover intersection to calculate the distance
if (iNumProbesRemaining < 10)
{
fCoverDistanceXY = (vIntersectionPos - vStartPos).XYMag();
}
#if __ASSERT
if (iNumProbesRemaining == 0)
{
Warningf("CPed::ComputeLowCoverHeightOffsetFromMover: Reached maximum number of probes allowed (%d) before finding the final height of cover", iMaxNumProbes);
}
#endif
}
return fCoverDistanceXY;
}
#endif
#if !__FINAL
void CPed::LogSpeech( const char* DEBUG_DRAW_ONLY(context) )
{
#if DEBUG_DRAW && !__PROFILE
if( CPedDebugVisualiserMenu::ShouldLogPedSpeech() )
Displayf("NewSay(%s)\n",context);
#endif
}
#endif
void CPed::CheckComponentCloth()
{
/* // This is not needed anymore ( SetClothPackageIndex ). I will keep it around for just in case something need to be doublechecked - Svetli
if( m_pComponentClothInfo )
{
int drawbId;
CPedStreamRenderGfx* pGfxData = GetPedDrawHandler().GetPedRenderGfx();
if( !pGfxData )
{
CPedVariationData& pedVarData = GetPedDrawHandler().GetVarData();
drawbId = (int)pedVarData.GetPedCompIdx( (ePedVarComp)m_pComponentClothInfo->GetComponentID() );
}
else
{
drawbId = (int)pGfxData->m_dwdIdx[ m_pComponentClothInfo->GetComponentID() ];
}
if( drawbId == m_pComponentClothInfo->GetDrawableID() )
{
// TODO: use this in the future
//const int componentTargetID = m_pComponentClothInfo->GetComponentTargetID();
//if( m_CClothController[ componentTargetID ] )
SetClothPackageIndex( (u8)m_pComponentClothInfo->GetClothSetID(), 0 );
}
else
{
SetClothPackageIndex( 0, 0 );
}
}
*/
}
void CPed::SetClothPackageIndex(u8 packageIndex, u8 transitionFrames)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetPackageIndex(packageIndex, transitionFrames);
}
}
void CPed::SetClothPoseIndex(u8 poseIndex)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
{
characterCloth* pCharCloth = (characterCloth*)m_CClothController[i]->GetOwner();
Assert( pCharCloth );
pCharCloth->SetPose( poseIndex );
}
}
}
void CPed::QueueClothPoseIndex(u8 poseIndex )
{
m_PoseIndex = poseIndex;
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
{
m_CClothController[i]->SetFlag( characterClothController::enIsQueuedPose, true );
}
}
}
void CPed::QueueClothPackageIndex(u8 packageIndex)
{
m_PackageIndex = packageIndex;
}
void CPed::SetClothForcePin(u8 framesToPin)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetForcePin(framesToPin);
}
}
void CPed::SetClothLockCounter(u8 count)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
{
characterCloth* pCharCloth = (characterCloth*)m_CClothController[i]->GetOwner();
Assert( pCharCloth );
pCharCloth->SetLockCounter(count);
}
}
}
void CPed::SetClothWindScale(float windScale)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetWindScale(windScale);
}
}
void CPed::SetClothPinRadiusScale(float radiusScale)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetPinningRadiusScale(radiusScale);
}
}
void CPed::SetClothIsProne(bool bIsProne)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetFlag( characterClothController::enIsProneFlipped, bIsProne);
}
}
void CPed::SetClothIsSkydiving(bool bIsSkydiving)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetFlag( characterClothController::enIsSkydiving, bIsSkydiving);
}
}
void CPed::SetClothForceSkin(bool bForceSkin)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
{
characterCloth* pCharCloth = (characterCloth*)m_CClothController[i]->GetOwner();
Assert( pCharCloth );
if( pCharCloth->GetLockCounter() == 0 )
{
m_CClothController[i]->SetFlag( characterClothController::enIsForceSkin, bForceSkin);
}
}
}
}
void CPed::SetClothIsFalling(bool bIsFalling)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
m_CClothController[i]->SetFlag( characterClothController::enIsFalling, bIsFalling);
}
}
void CPed::SetClothNegateForce(bool bNegateForce)
{
for( int i = 0; i < PV_MAX_COMP; ++i)
{
if( m_CClothController[i] )
{
characterCloth* pCharCloth = (characterCloth*) m_CClothController[i]->GetOwner();
Assert( pCharCloth );
pCharCloth->SetForceNegate(bNegateForce);
}
}
}
bool CPed::IsProne() const
{
Assert(m_pRagdollInst);
if (!m_pRagdollInst)
return false;
// Frozen corpses are prone
if (GetDeathState() == DeathState_Dead && !GetUsingRagdoll())
{
return true;
}
// The calculations below need a collider
if (!GetCollider())
{
return false;
}
// Peds with a fast relative velocity aren't prone
static float sfSlowVel = 0.3f;
Vec3V relativeVelocity = Subtract(GetCollider()->GetVelocity(), GetCollider()->GetReferenceFrameVelocity());
float fCOMvelSq = MagSquared(relativeVelocity).Getf();
if (fCOMvelSq > sfSlowVel*sfSlowVel)
{
return false;
}
// Use NM's IsOnGround() fucntion for NM agents
if (m_nRagdollState >= RAGDOLL_STATE_PHYS_ACTIVATE && m_pRagdollInst->GetNMAgentID() >= 0 && FRAGNMASSETMGR->IsOnGround(m_pRagdollInst->GetNMAgentID()))
{
return true;
}
// A detailed check for high LOD rage ragdolls
if (m_pRagdollInst->GetCurrentPhysicsLOD() == fragInst::RAGDOLL_LOD_HIGH)
{
if(GetRagdollInst() && GetRagdollInst()->GetCacheEntry())
{
// Check that the pelvis and spine3 have similar heights
phBoundComposite *bound = GetRagdollInst()->GetCacheEntry()->GetBound();
if(bound && bound->GetNumBounds() > 0)
{
Mat34V boundMat;
Transform(boundMat, GetRagdollInst()->GetMatrix(), bound->GetCurrentMatrix(RAGDOLL_BUTTOCKS));
float pelvisHeight = boundMat.GetCol3().GetZf();
//! Some ped variations (e.g. hens, fish etc) don't have some bounds, so check beforehand.
if( (RAGDOLL_NECK < bound->GetNumBounds()) && (RAGDOLL_FOOT_LEFT < bound->GetNumBounds()) && (RAGDOLL_FOOT_RIGHT < bound->GetNumBounds()))
{
Transform(boundMat, GetRagdollInst()->GetMatrix(), bound->GetCurrentMatrix(RAGDOLL_NECK));
float neckHeight = boundMat.GetCol3().GetZf();
Transform(boundMat, GetRagdollInst()->GetMatrix(), bound->GetCurrentMatrix(RAGDOLL_FOOT_LEFT));
float leftFootHeight = boundMat.GetCol3().GetZf();
Transform(boundMat, GetRagdollInst()->GetMatrix(), bound->GetCurrentMatrix(RAGDOLL_FOOT_RIGHT));
float rightFootHeight = boundMat.GetCol3().GetZf();
// This check is only really good for slopes less than ~40 degrees. Would like to get more precise, but without a probe.
static float upperBodyRange = 0.12f;
static float lowerBodyRange = 0.14f;
if (Abs(pelvisHeight-neckHeight) < upperBodyRange && (Abs(pelvisHeight-leftFootHeight) < lowerBodyRange || Abs(pelvisHeight-rightFootHeight) < lowerBodyRange))
{
return true;
}
}
}
}
}
else
{
// For lower LOD ragdolls, return true since we already know that the relative velocity is very small
return true;
}
return false;
}
bool CPed::WillAttackInjuredPeds() const
{
if( GetPedIntelligence()->GetCombatBehaviour().GetTargetInjuredReaction() == CCombatData::TIR_TreatAsDead )
return false;
return true;
}
bool CPed::IsAllowedToDamageEntity(const CWeaponInfo* pWeaponInfo, const CEntity* pTarget NOTFINAL_ONLY(, u32* uRejectionReason)) const
{
if(IsPlayer())
{
if(pTarget && pTarget->GetIsTypePed())
{
const CPed* pTargetPed = static_cast<const CPed*>(pTarget);
//! Don't let friendly players shoot other players during 1st person driveby.
if(pTargetPed->IsPlayer())
{
if(IsInFirstPersonVehicleCamera() && GetVehiclePedInside() && (GetVehiclePedInside() == pTargetPed->GetVehiclePedInside()))
{
bool bFriendlyWithPlayer = GetPedIntelligence()->IsFriendlyWith(*pTargetPed) && pTargetPed->GetPedIntelligence()->IsFriendlyWith(*this);
if(bFriendlyWithPlayer)
{
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_Friendly; })
return false;
}
}
}
// Check not damaged by rel group.
if(pTargetPed->m_nPhysicalFlags.bNotDamagedByRelGroup && GetPedIntelligence()->GetRelationshipGroup()->GetName().GetHash() == pTargetPed->m_specificRelGroupHash)
{
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagedByRelGroup; })
return false;
}
if (pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_TreatAsFriendlyForTargetingAndDamage) || pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_TreatAsFriendlyForTargetingAndDamageNonSynced))
{
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_Friendly; })
return false;
}
if (NetworkInterface::AreBulletsImpactsDisabledInMP(*this, *pTargetPed))
{
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_ImpactsDisabledInMP; })
return false;
}
if (NetworkInterface::IsGameInProgress() && pTargetPed->IsPlayer() && GetPlayerInfo())
{
CNetObjPed* pTargetPedObj = SafeCast(CNetObjPed, pTargetPed->GetNetworkObject());
if (pTargetPedObj && !pTargetPedObj->GetIsDamagableByPlayer(GetPlayerInfo()->GetPhysicalPlayerIndex()))
{
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NotDamagableByThisPlayer; })
return false;
}
}
if(!pTargetPed->IsDead() && GetPedIntelligence()->IsFriendlyWith(*pTargetPed) && (!GetPedConfigFlag(CPED_CONFIG_FLAG_CanAttackFriendly) || (pWeaponInfo && pWeaponInfo->GetNoFriendlyFireDamage())))
{
// Check players in a network game and revert if set to mutually antagonistic
if(NetworkInterface::IsGameInProgress())
{
if (pTargetPed->IsPlayer() && !NetworkInterface::IsFriendlyFireAllowed(pTargetPed,this))
{
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_NoFriendlyFire; })
return false;
}
if(!pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IgnoreNetSessionFriendlyFireCheckForAllowDamage) && NetworkInterface::FriendlyFireAllowed() )
{
return true;
}
if (pTargetPed->IsPlayer())
{
if (NetworkInterface::IsPedAllowedToDamagePed(*this, *pTargetPed))
{
return true;
}
}
}
NOTFINAL_ONLY(if (uRejectionReason) { *uRejectionReason = CPedDamageCalculator::DRR_Friendly; })
return false;
}
}
}
return true;
}
u32 CPed::GetPedVoiceGroup( bool bCreateIfUnassigned )
{
// If we've already selected a voice, return it
if( m_bVoiceGroupSelected )
return m_selectedVoiceGroup;
// If not, just return 0, we haven't yet assigned a voice and have been requested not to create one.
if( !bCreateIfUnassigned )
return 0;
CPedModelInfo* pModelInfo = (CPedModelInfo*)GetBaseModelInfo();
Assert(pModelInfo);
if (pModelInfo)
{
u32 headComponent = CPedVariationPack::GetCompVar(this, PV_COMP_HEAD);
u32 PedVoiceGroupHash;
// If we find a specific group for this component, return it
if( pModelInfo->GetAudioGroupForHeadComponent(headComponent, PedVoiceGroupHash) )
{
m_selectedVoiceGroup = PedVoiceGroupHash;
}
// Return the main group hash
else
{
m_selectedVoiceGroup = pModelInfo->GetPedVoiceGroup();
}
m_bVoiceGroupSelected = true;
}
return m_selectedVoiceGroup;
}
float CPed::GetWaterLevelOnPed() const
{
if(m_Buoyancy.GetStatus() == NOT_IN_WATER)
{
return 0.0f;
}
else if (m_Buoyancy.GetStatus() == FULLY_IN_WATER)
{
return 1.0f;
}
else
{
CBuoyancyInfo* pBuoyancyInfo = m_Buoyancy.GetBuoyancyInfo(this);
if (pBuoyancyInfo != NULL)
{
// Should probably give larger components higher weight...
float fSubmergedLevel = 0.0f;
for (int iSample = 0; iSample < pBuoyancyInfo->m_nNumWaterSamples; iSample++)
{
fSubmergedLevel += m_Buoyancy.GetWaterLevelOnSample(iSample) / pBuoyancyInfo->m_WaterSamples[iSample].m_fSize;
}
fSubmergedLevel /= pBuoyancyInfo->m_nNumWaterSamples;
return fSubmergedLevel;
}
}
return 0.0f;
}
float CPed::GetMaxTimeUnderwater() const
{
//! If no time is set, get defaults.
if(m_fMaxTimeUnderwater < 0.0f)
{
if(IsPlayer())
{
return CPlayerInfo::GetMaxTimeUnderWaterForPed(*this);
}
else
{
return PED_MAX_TIME_UNDERWATER;
}
}
return m_fMaxTimeUnderwater;
}
void CPed::SetMaxTimeUnderwater(const float f)
{
//! If < 0.0f, indicates we should reset.
if(f < 0.0f)
{
m_fMaxTimeUnderwater = -1.0f;
}
else
{
m_fMaxTimeUnderwater = f;
}
}
void CPed::ResetWaterTimers()
{
if(IsPlayer())
{
m_fMaxTimeInWater = PLAYER_MAX_TIME_IN_WATER;
}
else
{
m_fMaxTimeInWater = PED_MAX_TIME_IN_WATER;
}
m_fMaxTimeUnderwater = -1.0f;
m_OverrideMaxTimeInWater = -1;
}
void CPed::SetDesiredVelocityClamped(const Vector3 & vVel, float fAccelLimit )
{
aiAssertf(fAccelLimit < 150.0f, "accel limit is too high in SetDesiredVelocityClamped");
Assert(vVel == vVel);
Assert(fabs(vVel.Mag2()) < 100000.0f);
Assert(GetVelocity() == GetVelocity());
Assert(fabs(GetVelocity().Mag2()) < 100000.0f);
if (vVel != vVel || GetVelocity() != GetVelocity())
return;
Vector3 vVelChange = vVel - GetVelocity();
if(fAccelLimit > 0.0f)
{
//Limit the rate of change so we don't apply a crazy force to the ped.
if(vVelChange.Mag2() > fAccelLimit * fAccelLimit)
{
vVelChange *= fAccelLimit / vVelChange.Mag();
}
}
Assert(vVelChange == vVelChange);
Assert(fabs(vVelChange.Mag2()) < 100000.0f);
if (vVelChange != vVelChange)
return;
SetDesiredVelocity(GetVelocity() + vVelChange);
}
void CPed::ProcessControl_AnimStateUpdate()
{
PF_FUNC(ProcessControl_AnimStateUpdate);
TUNE_GROUP_BOOL(LEG_IK, FORCE_LEG_IK_OFF, false);
if (FORCE_LEG_IK_OFF)
{
GetIkManager().SetFlag(PEDIK_LEGS_AND_PELVIS_OFF);
}
TUNE_GROUP_BOOL(LEG_IK, FORCE_USE_ANIM_ALLOW_TAGS, false);
if (FORCE_USE_ANIM_ALLOW_TAGS)
{
GetIkManager().SetFlag(PEDIK_LEGS_USE_ANIM_ALLOW_TAGS);
}
TUNE_GROUP_BOOL(LEG_IK, FORCE_USE_ANIM_BLOCK_TAGS, false);
if (FORCE_USE_ANIM_BLOCK_TAGS)
{
GetIkManager().SetFlag(PEDIK_LEGS_USE_ANIM_BLOCK_TAGS);
}
if (GetAnimDirector())
{
GetAnimDirector()->WaitForPrePhysicsStateUpdateToComplete();
GetAnimDirector()->SwapMoveOutputBuffers();
}
}
void CPed::ProcessControl_ComponentReservations()
{
#if ENABLE_HORSE
if(m_pComponentReservationMgr)
m_pComponentReservationMgr->Update();
#endif
}
void CPed::ProcessControl_Data()
{
PF_FUNC(ProcessControl_Data);
if (m_pPlayerInfo)
{
m_pPlayerInfo->ProcessControl();
m_pPlayerInfo->ProcessLightEffects();
#if FPS_MODE_SUPPORTED
m_pPlayerInfo->ProcessFPSState();
#endif // FPS_MODE_SUPPORTED
}
}
void CPed::ProcessControl_Audio()
{
PF_FUNC(ProcessControl_Audio);
// Early assert to try and catch B* 1747160
Assertf(this == m_PedAudioEntity.GetOwningPed(), "PedAudioEntity's owning ped ( %p ) does not match the current ped ( %p ), probably the cause of B* 1747160. Please contact the audio team.",m_PedAudioEntity.GetOwningPed(),this);
m_PedAudioEntity.Update();
if (m_pSpeechAudioEntity)
m_pSpeechAudioEntity->Update();
SetPedConfigFlag(CPED_CONFIG_FLAG_StoppedSpeechUponFreezing, false);
// foot effects etc - must be after the SetMoveAnim call
if( !GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEventScanning) )
{
PlayFootSteps();
}
}
void CPed::ProcessControl_Animation()
{
PF_FUNC(ProcessControl_Animation);
// Early out if a low lod ped
if( GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEventScanning) )
return;
// Audio assets can be tagged to trigger gestures and/or facial gestures
ProcessBodyAndFacialGesturesEmbeddedInAudioAssets();
// Process voice driven mouth movement
ProcessVoiceDrivenMouthMovement();
// Do look at for action mode.
ProcessActionModeLookAt();
// Anim assets can be tagged to trigger gestures and/or facial animations
//ProcessAnimDataEmbeddedInAnimAssets();
ControlSecondaryTaskBlend();
ProcessExternallyDrivenDOFs();
ProcessInjuredClipSetRequestHelper();
}
void CPed::ProcessInjuredClipSetRequestHelper()
{
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsInjured))
{
if (!m_injuredRequestHelper.IsLoaded())
{
m_injuredRequestHelper.Request(IsMale() ? CLIP_SET_MOVE_INJURED : CLIP_SET_MOVE_INJURED_FEMALE);
}
else
{
CTaskMotionBase* pMotionTask = GetPrimaryMotionTask();
if (pMotionTask && !pMotionTask->IsUsingInjuredMovementOverride())
{
pMotionTask->ResetOnFootClipSet(true);
}
}
m_injuredGetupRequestHelper.Request(NMBS_INJURED_GETUPS);
}
else
{
if (m_injuredRequestHelper.IsLoaded())
{
m_injuredRequestHelper.Release();
}
if (m_injuredGetupRequestHelper.IsLoaded())
{
m_injuredGetupRequestHelper.Release();
}
}
}
void CPed::ProcessControl_ResetVariables()
{
PF_FUNC(ProcessControl_ResetVariables);
// prefetch the ragdoll inst to avoid a cache miss in m_PedResetFlags.Reset
PrefetchDC(GetRagdollInst());
//Next anim update it is safe to start accumulating a new velocity
m_bResetPreviousAnimatedVelocity = true;
// Reset the no collision entity/events when we've exited
if (!GetIsAttachedInCar() && GetPedConfigFlag(CPED_CONFIG_FLAG_JustLeftVehicleNeedsReset) && !GetHasJustLeftVehicle())
{
SetPedConfigFlag(CPED_CONFIG_FLAG_JustLeftVehicleNeedsReset, false);
if (GetRagdollOnCollisionIgnorePhysical() || GetNoCollisionEntity())
{
SetNoCollisionEntity(NULL);
SetActivateRagdollOnCollision(false);
SetRagdollOnCollisionIgnorePhysical(NULL);
ClearActivateRagdollOnCollisionEvent();
}
}
m_PedResetFlags.Reset(this);
if(((GetIsStanding() || GetWasStanding() ) && !GetPedConfigFlag( CPED_CONFIG_FLAG_IsInTheAir )) || GetIsAttached())
{
// this is either the current ped height, or the last known standing ped height
m_fFallingHeight = GetTransform().GetPosition().GetZf();
}
// Count up the time since the ped was last in water
if( GetIsInWater() )
m_fTimeSincePedInWater = 0.0f;
else
m_fTimeSincePedInWater = MIN( m_fTimeSincePedInWater + fwTimer::GetTimeStep(), 60.0f );
// Keep note of the last time a shot was fired (up to a maximum of 10 secs for now)
if( m_fTimeSinceLastShotFired < 10.0f )
m_fTimeSinceLastShotFired += fwTimer::GetTimeStep();
if(GetWeaponManager())
{
// Reset the accuracy variables
GetWeaponManager()->GetAccuracy().GetResetVars().Reset();
}
}
void CPed::ProcessSpecialNetworkLeave()
{
if (NetworkInterface::IsGameInProgress())
{
if (m_specialNetworkLeaveWhenDead)
{
if (IsDead())
{
m_specialNetworkLeaveTimeRemaining -= static_cast<s16>(fwTimer::GetSystemTimeStepInMilliseconds());
m_specialNetworkLeaveTimeRemaining = MAX(m_specialNetworkLeaveTimeRemaining, 0);
if (m_specialNetworkLeaveTimeRemaining <= 0)
{
SetSpecialNetworkLeave();
}
}
return;
}
if (m_specialNetworkLeaveTimeRemaining > 0)
{
u32 timeStep = MIN(fwTimer::GetSystemTimeStepInMilliseconds(), 100);
m_specialNetworkLeaveTimeRemaining -= static_cast<s16>(timeStep);
m_specialNetworkLeaveTimeRemaining = MAX(m_specialNetworkLeaveTimeRemaining, 0);
if (m_specialNetworkLeaveTimeRemaining > 0)
{
s16 finalAlpha;
// fade out dead peds
if (GetIsDeadOrDying())
{
finalAlpha = (s16)(((float)m_specialNetworkLeaveTimeRemaining / (float) NetworkUtils::GetSpecialAlphaRampingFadeOutTime()) * 255.0f);
}
else
{
finalAlpha = NetworkUtils::GetSpecialAlphaRampingValue(m_iAlphaOverride, m_bAlphaIncreasing, NetworkUtils::GetSpecialAlphaRampingFadeOutTime(), m_specialNetworkLeaveTimeRemaining, true); //ramp up
}
entity_commands::CommandSetEntityAlphaEntity(this, finalAlpha, false BANK_PARAM(entity_commands::CMD_OVERRIDE_ALPHA_BY_NETCODE));
}
else
{
//ZERO - SET THE PED OUT OF THE VEHICLE - SET PED TO BE DESTROYED
if (GetIsInVehicle())
{
SetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle, false );
UpdateSpatialArrayTypeFlags();
DetachFromParent(0);
GetMyVehicle()->RemovePedFromSeat(this);
}
SetIsVisibleForModule( SETISVISIBLE_MODULE_RESPAWN, false, true );
if (GetNetworkObject())
{
CNetObjEntity* pNetObjEntity = static_cast<CNetObjEntity*>(GetNetworkObject());
if (pNetObjEntity)
{
pNetObjEntity->FlagForDeletion(); //will immediately remove because as soon as this is set it will get processed in Update and removed because of check there...
m_specialNetworkLeaveTimeRemaining = 0;
}
}
else
{
FlagToDestroyWhenNextProcessed();
m_specialNetworkLeaveTimeRemaining = 0;
}
}
}
}
}
void CPed::ProcessControl_Graphics(bool fullUpdate)
{
PF_FUNC(ProcessControl_Graphics);
//Do this upfront in this method before early outs
ProcessSpecialNetworkLeave();
// Early out if a low lod ped
if( GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEventScanning) )
return;
if(GetUsingRagdoll())
{
DoCollisionEffects();
}
ProcessControl_Cloth();
if(fullUpdate)
{
// Calculate how much (if any) we should ruffle the peds clothing
ProcessWindyClothing();
// Adjust wet clothing values when getting in/out of the water.
ProcessWetClothing();
}
// Update any pending requests to kill off a helmet and restore a hat...
if(GetHelmetComponent())
{
GetHelmetComponent()->Update();
}
}
void CPed::ProcessControl_Cloth()
{
bool shouldStopClothSound = true;
CPedStreamRenderGfx* pGfxData = GetPedDrawHandler().GetPedRenderGfx();
u32 buildBufferIndex = clothManager::GetBufferIndex();
if( !pGfxData )
{
CPedVariationData& pedVarData = GetPedDrawHandler().GetVarData();
for(int i = 0; i < PV_MAX_COMP; ++i )
{
clothVariationData* clothVarData = (clothVariationData*)pedVarData.GetClothData((ePedVarComp)i);
if( clothVarData )
clothVarData->GetCloth()->SetEntityVerticesPtr( clothVarData->GetClothVertices(buildBufferIndex) );
}
}
else
{
for(int i = 0; i < PV_MAX_COMP; ++i )
{
clothVariationData* clothVarData = pGfxData->GetClothData(i);
if( clothVarData )
{
clothVarData->GetCloth()->SetEntityVerticesPtr( clothVarData->GetClothVertices(buildBufferIndex) );
if( IsLocalPlayer() )
{
m_PedAudioEntity.GetFootStepAudio().PlayWindClothSound();
shouldStopClothSound = false;
}
}
}
}
if( GetPedModelInfo() && GetPedModelInfo()->GetPersonalitySettings().GetIsHuman() )
{
if( IsLocalPlayer() )
{
if( !m_ClothCollision )
{
m_ClothCollision = CPedClothCollision::GetNextPedClothCollision( GetSkeleton() );
}
if(shouldStopClothSound)
{
m_PedAudioEntity.GetFootStepAudio().StopWindClothSound();
}
}
else if( !IsPlayer() )
{
if( GetPedConfigFlag( CPED_CONFIG_FLAG_HasClothCollisionBounds ) )
{
if( !m_ClothCollision )
{
m_ClothCollision = CPedClothCollision::GetNextPedClothCollision( GetSkeleton() );
}
}
}
}
else
{
if( m_ClothCollision )
{
m_ClothCollision->Shutdown();
m_ClothCollision = NULL;
}
}
if(m_ClothCollision)
{
m_ClothCollision->Update();
float calsuleLen = 0.01f, capsuleRadius = 0.01f;
Mat34V boundMatrix(V_IDENTITY);
CalculateWeaponBound(calsuleLen, capsuleRadius, boundMatrix);
m_ClothCollision->UpdateWeaponBound(calsuleLen, capsuleRadius, boundMatrix);
}
const bool isFalling = ( m_pPedIntelligence && m_pPedIntelligence->IsPedFalling() ) ? true: false;
SetClothIsFalling(isFalling);
if( !g_PlayerSwitch.IsActive() && m_pMyVehicle && m_pMyVehicle->GetVehicleModelInfo() && m_pMyVehicle->GetVehicleModelInfo()->GetIsAutomobile() )
{
bool bPedIsSeat = false;
for (s32 i=0; i<m_pMyVehicle->GetSeatManager()->GetMaxSeats(); i++)
{
if( this == m_pMyVehicle->GetPedInSeat(i) )
{
bPedIsSeat = true;
break;
}
}
SetClothForceSkin(bPedIsSeat);
}
}
bool CPed::CalculateWeaponBound(float& capsuleLen, float& capsuleRadius, Mat34V_InOut boundMatrix)
{
CPedWeaponManager* pPedWeaponManager = GetWeaponManager();
if( pPedWeaponManager )
{
CWeapon* pEquippedWeapon = pPedWeaponManager->GetEquippedWeapon();
CObject* pObject = pPedWeaponManager->GetEquippedWeaponObject();
if( pEquippedWeapon && pObject )
{
s32 iPedBoneIndex = GetBoneIndexFromBoneTag( (!0/*pEquippedWeapon->GetAttachPoint()*/) ? BONETAG_R_PH_HAND: BONETAG_L_PH_HAND );
if( iPedBoneIndex != -1 )
{
Assert( GetSkeleton() );
Matrix34 boneMat;
GetSkeleton()->GetGlobalMtx( iPedBoneIndex, RC_MAT34V(boneMat) );
s32 iObjectBoneIndex = -1;
const CWeaponModelInfo* pWeaponModelInfo = pEquippedWeapon->GetWeaponModelInfo();
if(pWeaponModelInfo)
{
iObjectBoneIndex = pWeaponModelInfo->GetBoneIndex(WEAPON_GRIP_R);
}
// Get the offsets
Matrix34 mOffsetBone(Matrix34::IdentityType);
if(iObjectBoneIndex != -1)
{
mOffsetBone = MAT34V_TO_MATRIX34(GetSkeleton()->GetObjectMtx( iObjectBoneIndex ));
if(pObject->GetSkeleton())
{
Mat34V mOffset;
pObject->GetSkeletonData().ComputeGlobalTransform(iObjectBoneIndex, pObject->GetMatrix(), mOffset);
mOffsetBone = MAT34V_TO_MATRIX34(mOffset);
}
else
{
// In case the child has no skeleton, make sure the position offset is correct when we transform this matrix into model space.
mOffsetBone.d = VEC3V_TO_VECTOR3(pObject->GetMatrix().d());
}
Matrix34 m = MAT34V_TO_MATRIX34(pObject->GetMatrix());
mOffsetBone.DotTranspose(m);
mOffsetBone.FastInverse();
}
const Vector3 boundingBoxMax = pObject->GetBoundingBoxMax();
const Vector3 boundingBoxMin = pObject->GetBoundingBoxMin();
mOffsetBone.Dot( boneMat );
// grcDebugDraw::BoxOriented( VECTOR3_TO_VEC3V(boundingBoxMin), VECTOR3_TO_VEC3V(boundingBoxMax), MATRIX34_TO_MAT34V(mOffsetBone), Color_white, false, 1 );
Vector3 bbMinW, bbMaxW; // BB min and max in world space
mOffsetBone.Transform( boundingBoxMin, bbMinW );
mOffsetBone.Transform( boundingBoxMax, bbMaxW );
Vector3 bbCenterW = (bbMaxW+bbMinW)*0.5f;
mOffsetBone.d = bbCenterW;
Matrix34 parentMat = MAT34V_TO_MATRIX34(*(GetSkeleton()->GetParentMtx()));
parentMat.FastInverse();
mOffsetBone.Dot( parentMat );
// NOTE: the transformed matrix rely on the fact that original box was AABB in local space
Vector3 tempV = mOffsetBone.a;
mOffsetBone.a = mOffsetBone.b;
mOffsetBone.b = tempV;
Vector3 center = (boundingBoxMax + boundingBoxMin) * 0.5f;
Vector3 extends = boundingBoxMax - center;
float halfCapsuleLength = extends.x > extends.y ? extends.x : extends.y;
capsuleRadius = extends.x > extends.y ? extends.y : extends.x;
halfCapsuleLength = halfCapsuleLength > extends.z ? halfCapsuleLength: extends.z;
capsuleRadius = capsuleRadius > extends.z ? capsuleRadius: extends.z;
capsuleLen = halfCapsuleLength*2.0f;
boundMatrix = MATRIX34_TO_MAT34V(mOffsetBone);
return true;
}
}
}
return false;
}
void CPed::ProcessControl_Population()
{
PF_FUNC(ProcessControl_Population);
// Make sure interesting vehicles are tracked.
if((CGameWorld::FindFollowPlayer() == this) && GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pMyVehicle)
{
CVehiclePopulation::RegisterInterestingVehicle(m_pMyVehicle);
}
// Make sure the ped counts are up to date.
UpdatePedCountTracking();
}
void CPed::ProcessControl_Physics()
{
PF_FUNC(ProcessControl_Physics);
// mark previously kicked by player before resetting
m_bPreviouslyKickedByPlayer = m_bKickedByPlayer;
m_bKickedByPlayer = false;
m_bPreviouslyHitByDoor = m_bHitByDoor;
m_bHitByDoor = false;
// check ragdoll state
ProcessRagdollState();
// natural motion footstep control
SetPedConfigFlag( CPED_CONFIG_FLAG_PrevLeftFootCollNM, GetPedConfigFlag( CPED_CONFIG_FLAG_CurrLeftFootCollNM ) );
SetPedConfigFlag( CPED_CONFIG_FLAG_CurrLeftFootCollNM, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_PrevRightFootCollNM, GetPedConfigFlag( CPED_CONFIG_FLAG_CurrRightFootCollNM ) );
SetPedConfigFlag( CPED_CONFIG_FLAG_CurrRightFootCollNM, false );
ProcessGlassCollisionTest();
// Detect whether we are colliding with a door which the player is also in collision with.
// If so then suppress kinematic mode when stationary, to fix issues such as url:bugstar:1564883
if( m_PedResetFlags.GetKnockedByDoor() )
{
const CCollisionRecord * pObjCollision = GetFrameCollisionHistory() ? GetFrameCollisionHistory()->GetFirstObjectCollisionRecord() : NULL;
while(pObjCollision)
{
CObject * pObj = (CObject*)pObjCollision->m_pRegdCollisionEntity.Get();
if(pObj && pObj->IsADoor())
{
CDoor * pDoor = (CDoor*)pObj;
if(pDoor->GetPlayerDoorState()!=CDoor::PDS_NONE)
{
m_uSuppressKinematicModeTimer = 30;
break;
}
}
pObjCollision = pObjCollision->GetNext();
}
}
// If this config flag is set on a particular ped and the ped isn't moving currently, we want to make sure we set the use
// kinematic reset flag just before it is checked and used to enable kinematic mode below.
if(GetPedConfigFlag(CPED_CONFIG_FLAG_UseKinematicModeWhenStationary) && !IsInMotion())
{
SetShouldUseKinematicPhysics(true);
}
if(GetPedResetFlag(CPED_RESET_FLAG_UseKinematicPhysics) || GetPedResetFlag(CPED_RESET_FLAG_TaskUseKinematicPhysics))
{
SetShouldUseKinematicPhysics(true);
}
if(m_uSuppressKinematicModeTimer > 0)
m_uSuppressKinematicModeTimer--;
// if the follow vehicle has been fixed by network we need to clear this
if(GetPlayerInfo() && NetworkInterface::IsInSpectatorMode())
{
CVehicle *pFollowVehicle = CGameWorld::FindFollowPlayerVehicle();
if(pFollowVehicle && pFollowVehicle->IsBaseFlagSet(fwEntity::IS_FIXED_BY_NETWORK))
{
pFollowVehicle->SetFixedPhysics(false, true);
}
}
#if ENABLE_HORSE
if( m_pComponentSetInfo->GetIsRidable() && m_pHorseComponent )
m_pHorseComponent->Update();
#endif
}
void CPed::ProcessControl_SpecialAbilityChargeEvents()
{
// Process charge events for ALL abilities
for (int i = 0; i < PSAS_MAX; ++i)
{
if (m_specialAbilities[i] && IsValidSpecialAbilitySlot((ePlayerSpecialAbilitySlot)i))
{
// when driving within the top 10% of the current vehicle's velocity we add a continuous special ability charge
// (assuming we drive a somewhat fast vehicle)
if (GetIsDrivingVehicle() && m_pMyVehicle && !m_pMyVehicle->InheritsFromBoat())
{
if (m_pMyVehicle->pHandling)
{
const float minThresholdSqr = 20.f * 20.f;
const float maxVelSqr = (m_pMyVehicle->pHandling->m_fEstimatedMaxFlatVel * 0.9f) * (m_pMyVehicle->pHandling->m_fEstimatedMaxFlatVel * 0.9f);
const float curVel = m_pMyVehicle->GetVelocity().Mag2();
if (curVel > maxVelSqr && curVel > minThresholdSqr)
CPlayerSpecialAbilityManager::ChargeEvent(ACET_TOP_SPEED, this);
}
bool inAir = true;
s32 numWheels = m_pMyVehicle->GetNumWheels();
for (s32 i = 0; i < numWheels; ++i)
{
CWheel* wheel = m_pMyVehicle->GetWheel(i);
if (wheel && wheel->GetIsTouching())
{
inAir = false;
break;
}
}
if (inAir)
{
CPlayerSpecialAbilityManager::ChargeEvent(ACET_VEHICLE_JUMP, this, 0.f, 0, m_pMyVehicle);
}
}
}
}
}
void CPed::ProcessControl_SpecialAbilities()
{
// check for input re selected ability
if (NetworkInterface::IsInCopsAndCrooks())
{
if (!IsDead())
{
CControl *pControl = GetControlFromPlayer();
CPlayerSpecialAbility* pAbility = GetSpecialAbility(m_selectedAbilitySlot);
ePlayerSpecialAbilitySlot m_newAbilitySlot = GetDesiredAbilitySlot();
CPlayerSpecialAbility* pNewAbility = GetSpecialAbility(m_newAbilitySlot);
if (pAbility == NULL || pNewAbility == NULL)
{
return;
}
bool bControlPressed = false;
if (pControl)
{
const ioValue& cycleSelectedAbilityInput = pControl->GetPedContextAction();
bControlPressed = cycleSelectedAbilityInput.IsDown() && !cycleSelectedAbilityInput.WasDown();
pedDebugf1("Pressed cycle ability, current selected ability is %d", m_selectedAbilitySlot);
}
bool bIsNextAbilitySelectable = pNewAbility->CanBeSelectedInArcadeMode();
if (bControlPressed || !pAbility->CanBeSelectedInArcadeMode())
{
if (bIsNextAbilitySelectable)
{
SetSelectedAbilitySlot(m_newAbilitySlot);
pedDebugf1("New selected ability is %d", m_newAbilitySlot);
pedDebugf1("Selected ability type is %d", (int)pAbility->GetType());
}
else
{
pedDebugf1("Ability cannot be selected in arcade mode, switch failed.");
}
}
}
}
// called outside of the for loop as we only want to process control for the selected ability
CPlayerSpecialAbilityManager::ProcessControl(this);
// process ALL special abilities
for (int i = 0; i < PSAS_MAX; ++i)
{
if (m_specialAbilities[i] && IsValidSpecialAbilitySlot((ePlayerSpecialAbilitySlot)i))
{
m_specialAbilities[i]->Process(this);
}
}
}
void CPed::ProcessControl_WeaponsAndAccessoriesPreIntelligence()
{
PF_FUNC(ProcessControl_WeaponsAndAccessoriesPreIntelligence);
if(GetWeaponManager())
GetWeaponManager()->ProcessPreAI();
// Reset variables after the weapon and accessories update but before the ai update
m_PedResetFlags.ResetPreAIPostInventory();
}
void CPed::ProcessControl_WeaponsAndAccessoriesPostIntelligence(bool fullUpdate)
{
PF_FUNC(ProcessControl_WeaponsAndAccessoriesPostIntelligence);
// only update if both repositories are not streamed in
if(fullUpdate && GetInventory() && !GetInventory()->GetIsStreamedIn())
{
GetInventory()->Process();
}
if(GetWeaponManager())
{
if(IsPlayer())
{
GetWeaponManager()->GetAccuracy().ProcessPlayerAccuracy(*this);
}
if(fullUpdate)
{
GetWeaponManager()->ProcessPostAI();
}
SetDynamicEntityFlagsForWeapon(true);
}
// process helmet for AI peds
if(fullUpdate && GetHelmetComponent() && GetHelmetComponent()->IsHelmetEnabled())
{
if(!IsPlayer() && !GetPedConfigFlag( CPED_CONFIG_FLAG_DontTakeOffHelmet ) && !GetVehiclePedInside() && !GetVehiclePedEntering() && !GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE))
{
// ped is wearing helmet but doesn't HAVE to - look to remove after a certain amount of time or once it is off screen
GetHelmetComponent()->UpdateInactive(!GetIsVisibleInSomeViewportThisFrame());
}
}
//decay this ped's fire resistance
DecayAccumulatedFire(fwTimer::GetTimeStep());
}
void CPed::ProcessControl_Intelligence(bool fullUpdate, float fOverrideTimeStep)
{
PF_FUNC(ProcessControl_Intelligence);
// Update the action mode first
UpdateMovementMode();
GetMotionData()->ProcessPreTaskTrees();
// All of ped AI processing done here now!!
GetPedIntelligence()->Process(fullUpdate, fOverrideTimeStep);
GetMotionData()->ProcessPostTaskTrees();
// MOVED from CGameWorld: reset the queriable interface scripted task info, ready for being recalculated in CPedScriptedTaskRecord::Process()
GetPedIntelligence()->GetQueriableInterface()->ResetScriptTaskInfo();
if(GetPlayerInfo())
{
GetPlayerInfo()->GetPlayerResetFlags().ResetPostAI();
}
}
bool CPed::CanUseKinematicPhysics() const
{
return GetAnimatedInst() && !GetUsingRagdoll() && (m_uSuppressKinematicModeTimer==0);
}
bool CPed::InstantAnimUpdateStart()
{
PF_AUTO_PUSH_TIMEBAR("InstantAnimUpdateStart");
const bool bUseZeroTimestep = GetPedResetFlag( CPED_RESET_FLAG_ePostCameraAnimUpdateUseZeroTimestep );
if (bUseZeroTimestep && GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodTimesliceAnimUpdate))
{
// Check if AL_LodTimesliceAnimUpdate is set. If so, it means we are in low animation update LOD,
// and don't update animations every frame. If we do a zero time step update here, we seem to also
// end up with a zero velocity, and that means we won't translate properly on the frames
// when we skip animation updates. If us skipping the instant animation update causes problems for a
// user, they have the option to block timeslicing.
return false;
}
// make sure any active updates have completed before forcing an instant update
if(GetAnimDirector())
{
GetAnimDirector()->WaitForAnyActiveUpdateToComplete(false);
}
float timestep = bUseZeroTimestep ? 0.0f : fwTimer::GetTimeStep();
StartAnimUpdate(timestep);
return true;
}
void CPed::InstantAnimUpdateEnd()
{
PF_AUTO_PUSH_TIMEBAR("InstantAnimUpdateEnd");
GetAnimDirector()->WaitForPrePhysicsUpdateToComplete();
// Not entirely sure, but hopefully we wouldn't have to do the things below
// if we didn't actually update animations above.
const CPedAILod& lod = GetPedAiLod();
if(lod.ShouldUpdateAnimsThisFrame())
{
UpdateRagdollBoundsFromAnimatedSkel();
UpdateVelocityAndAngularVelocity(lod.GetUpdateAnimsTimeStep());
// Since the instant anim update is effectively re-doing the PrePhysics animation,
// we need to call StartUpdateMidPhysics() here when the instant anim update is done,
// so that the right things happen. If we don't call StartUpdateMidPhysics() here,
// then mid-physics animations like facial animations won't happen for this ped.
const bool bUseZeroTimestep = GetPedResetFlag( CPED_RESET_FLAG_ePostCameraAnimUpdateUseZeroTimestep );
float timestep = bUseZeroTimestep ? 0.0f : lod.GetUpdateAnimsTimeStep();
GetAnimDirector()->StartUpdateMidPhysics(timestep);
}
}
void CPed::InstantAIUpdate(bool bForceFullUpdate)
{
if(IsNetworkClone())
{
if( GetPedResetFlag( CPED_RESET_FLAG_AllowCloneForcePostCameraAIUpdate ) )
{
CNetObjPed* pNetworkObject = static_cast<CNetObjPed*>(GetNetworkObject());
if (pNetworkObject)
{
pNetworkObject->HandleUnmanagedCloneRagdoll();
}
GetMotionData()->ProcessPreTaskTrees();
GetPedIntelligence()->InstantTaskUpdate(bForceFullUpdate);
GetMotionData()->ProcessPostTaskTrees();
}
}
else
{
PF_FUNC(ProcessControl_Intelligence);
#if FPS_MODE_SUPPORTED
if (m_pPlayerInfo)
{
m_pPlayerInfo->ProcessFPSState();
}
#endif // FPS_MODE_SUPPORTED
ProcessControl_Intelligence(bForceFullUpdate || CPedAILodManager::ShouldDoFullUpdate(*this), 0.0f);
}
}
void CPed::ProcessPreCamera()
{
if (m_pPlayerInfo)
{
m_pPlayerInfo->ProcessPreCamera();
}
//B*2525396 If we are in first person and we dont update the suspension mods here we will get a one frame pop when chaning suspension height in first person view.
if(IsInFirstPersonVehicleCamera())
{
CVehicle *pVehiclePedInside = GetVehiclePedInside();
if(pVehiclePedInside && pVehiclePedInside->InheritsFromAutomobile())
{
CAutomobile *pAutomobile = static_cast<CAutomobile*>(pVehiclePedInside);
float fFakeSuspensionLoweringAmount = pAutomobile->GetFakeSuspensionLoweringAmount();
if(fFakeSuspensionLoweringAmount != pAutomobile->GetFakeSuspensionLoweringAmountApplied())
{
pAutomobile->PreRenderSuspensionMod((fFakeSuspensionLoweringAmount - pAutomobile->GetFakeSuspensionLoweringAmountApplied()));
pAutomobile->ProcessAllAttachments();
}
}
}
// B*2343564: Script have set overriden perception parameters for cops using SET_COP_PERCEPTION_OVERRIDES. Set these on the ped now.
// These get reset in CScriptPeds::Process.
if (IsCopPed() && CPedPerception::ms_bCopOverridesSet && GetPedIntelligence())
{
GetPedIntelligence()->SetCopPedOverridenPerception(this);
}
if ( GetPedResetFlag( CPED_RESET_FLAG_ForcePreCameraProcessExternallyDrivenDOFs ) || GetPedResetFlag(CPED_RESET_FLAG_DisableHighHeels))
{
ProcessExternallyDrivenDOFs();
}
// Avoid flooding the move network with signals when frozen
if ( !m_nDEflags.bFrozen REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive()) )
{
bool doEarlyUpdateForFirstPerson = IsLocalPlayer() && camControlHelperMetadataViewMode::FIRST_PERSON == camControlHelper::GetViewModeForContext(camControlHelperMetadataViewMode::ON_FOOT) && GetPedResetFlag(CPED_RESET_FLAG_ForcePreCameraAiAnimUpdateIfFirstPerson);
if( GetPedResetFlag( CPED_RESET_FLAG_ForcePreCameraAIUpdate ) || doEarlyUpdateForFirstPerson )
{
InstantAIUpdate();
}
if( GetPedResetFlag( CPED_RESET_FLAG_ForcePreCameraAnimUpdate ) || doEarlyUpdateForFirstPerson )
{
fwAnimDirectorComponentMotionTree::SetLockedGlobal(true, fwAnimDirectorComponent::kPhasePrePhysics);
InstantAnimUpdateStart();
InstantAnimUpdateEnd();
fwAnimDirectorComponentMotionTree::SetLockedGlobal(false, fwAnimDirectorComponent::kPhasePrePhysics);
}
}
}
#if __DEV
dev_bool ENABLE_INSTANT_UPDATES = true;
#endif
#if FPS_MODE_SUPPORTED
float CPed::ComputeExternallyDrivenPelvisOffsetForFPSStealth() const
{
float fPelvisOffset = GetIkManager().GetExternallyDrivenPelvisOffset();
if ((fPelvisOffset == 0.0f) && IsFirstPersonShooterModeEnabledForPlayer(false))
{
const bool bMeleeActionResult = GetPedIntelligence() && (GetPedIntelligence()->GetTaskMeleeActionResult() != NULL);
if (!GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_COVER) && !GetIsInCover() && !bMeleeActionResult && GetMotionData()->GetUsingStealth())
{
TUNE_GROUP_BOOL(LEG_IK, ExternallyDrivenPelvisOffsetStealthEnable, true);
TUNE_GROUP_FLOAT(LEG_IK, ExternallyDrivenPelvisOffsetStealthMale, -0.20f, -0.5f, 0.5f, 0.01f);
TUNE_GROUP_FLOAT(LEG_IK, ExternallyDrivenPelvisOffsetStealthFemale, -0.16f, -0.5f, 0.5f, 0.01f);
if (ExternallyDrivenPelvisOffsetStealthEnable)
{
fPelvisOffset = IsMale() ? ExternallyDrivenPelvisOffsetStealthMale : ExternallyDrivenPelvisOffsetStealthFemale;
}
}
}
return fPelvisOffset;
}
#endif // FPS_MODE_SUPPORTED
bool CPed::ProcessPostCamera()
{
bool bInstantAnimUpdateStarted = false;
if (m_PedConfigFlags.GetPedLegIkMode() != LEG_IK_MODE_OFF)
{
CIkRequestLeg legRequest;
GetIkManager().Request(legRequest FPS_MODE_SUPPORTED_ONLY(, CIkManager::IK_PASS_MAIN));
#if FPS_MODE_SUPPORTED
float fPelvisOffset = ComputeExternallyDrivenPelvisOffsetForFPSStealth();
legRequest.SetPelvisOffset(fPelvisOffset);
GetIkManager().Request(legRequest, CIkManager::IK_PASS_FPS);
GetIkManager().Request(legRequest, CIkManager::IK_PASS_MAIN);
TUNE_GROUP_BOOL(FPS_CAM_RELATIVE, bUpdateCameraRelativeExtensionPostCamera, false);
if(bUpdateCameraRelativeExtensionPostCamera)
{
UpdateFpsCameraRelativeMatrix();
}
#endif // FPS_MODE_SUPPORTED
}
SetDynamicEntityFlagsForWeapon(false);
if (GetPedResetFlag(CPED_RESET_FLAG_EnableMoverAnimationWhileAttached) && GetAttachParent())
{
float dt = fwTimer::GetTimeStep();
Vector3 extraOffset = GetAnimatedVelocity() * dt;
SetAttachOffset(GetAttachOffset() + extraOffset);
}
if(GetPedResetFlag(CPED_RESET_FLAG_ProcessPostCamera))
{
GetPedIntelligence()->ProcessPostCamera();
}
// Avoid flooding the move network with signals when frozen
const bool bDoInstantUpdates = !m_nDEflags.bFrozen DEV_ONLY(&& ENABLE_INSTANT_UPDATES) REPLAY_ONLY(&& !CReplayMgr::IsEditModeActive());
if ( bDoInstantUpdates && GetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAIUpdate ) )
{
InstantAIUpdate();
}
// Store away the reset flags we'll possibly need for doing an instant anim update, so we can always
// just reset the flags right now, whether we do an instant anim update or not.
const bool bForcePostCameraAnimUpdate = GetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAnimUpdate );
m_PedResetFlags.ResetPostCamera(this);
if ( bDoInstantUpdates && bForcePostCameraAnimUpdate )
{
bInstantAnimUpdateStarted = InstantAnimUpdateStart();
}
return bInstantAnimUpdateStarted;
}
void CPed::ProcessPostPhysics()
{
if (GetPedResetFlag(CPED_RESET_FLAG_CapsuleBeingPushedByVehicle))
{
m_TimeCapsulePushedByVehicle+=fwTimer::GetTimeStep();
m_TimeRampDownCapsulePushedByVehicle=m_TimeCapsulePushedByVehicle;
}
else
{
// Need to give the vehicle time to actually hit the ragdoll bounds once impacts with the capsule bounds are being ignored!
if (CTaskNMBehaviour::sm_Tunables.m_DurationRampDownCapsulePushedByVehicle <= 0.0f ||
Approach(m_TimeRampDownCapsulePushedByVehicle, 0.0f, m_TimeCapsulePushedByVehicle / CTaskNMBehaviour::sm_Tunables.m_DurationRampDownCapsulePushedByVehicle, fwTimer::GetTimeStep()))
{
m_TimeCapsulePushedByVehicle = 0.0f;
}
}
if (GetPedResetFlag(CPED_RESET_FLAG_CapsuleBeingPushedByPlayerCapsule))
{
if (m_TimeCapsuleFirstPushedByPlayerCapsule==0)
{
m_TimeCapsuleFirstPushedByPlayerCapsule=fwTimer::GetTimeInMilliseconds();
}
}
else
{
if (m_TimeCapsuleFirstPushedByPlayerCapsule>0)
{
m_TimeCapsuleFirstPushedByPlayerCapsule=0;
}
}
#if GTA_REPLAY
// B*2218297 - Look into disabling recording prostitute encounters in multiplayer (by other players who're in range)
if( GetPedResetFlag(CPED_RESET_FLAG_BlockRemotePlayerRecording))
{
CPed *pLocalPlayerPed = CGameWorld::FindLocalPlayer();
if( pLocalPlayerPed )
{
// Flag is set, if we're in rage, disable recording
const float dist = camInterface::GetReplayDirector().GetMaxDistanceAllowedFromPlayer() + CReplayMgr::GetExtraBlockRemotePlayerRecordingDistance();
const float distToCheck2 = dist*dist;
Vector3 playerPos = VEC3V_TO_VECTOR3(pLocalPlayerPed->GetTransform().GetPosition());
Vector3 pedPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition());
if( (pedPos-playerPos).Mag2() < distToCheck2 )
{
ReplayRecordingScriptInterface::PreventRecordingThisFrame();
}
}
}
#endif //GTA_REPLAY
GetPedIntelligence()->ProcessPostPhysics();
// Reset the scripted max move blend ratio
m_MotionData.SetScriptedMaxMoveBlendRatio(MOVEBLENDRATIO_SPRINT);
m_MotionData.SetScriptedMinMoveBlendRatio(MOVEBLENDRATIO_REVERSE);
// Reset the scripted desired run speed override
m_MotionData.SetDesiredMoveRateOverride( 1.0f );
m_MotionData.SetDesiredMoveRateInWaterOverride( 1.0f );
// Reset desired bound size back to original value
// This gives all outside systems an equal opportunity to resize
ResetDesiredMainMoverCapsuleData();
// Reset the stealth multipliers
if(GetPlayerInfo())
{
GetPlayerInfo()->SetNormalStealthMultiplier(1.0f);
GetPlayerInfo()->SetSneakingStealthMultiplier(1.0f);
GetPlayerInfo()->SetStealthPerceptionModifier(1.0f);
}
if(ms_bEnableGroundPhysicalCounterForce)
{
// Apply the opposite of the ped spring force to the ground physical
if(CPhysical* groundPhysical = GetGroundPhysical())
{
// Only players peds and vehicles for now.
if(IsPlayer() && groundPhysical->GetIsTypeVehicle() && !((CVehicle *)groundPhysical)->InheritsFromPlane())
{
// We only want to apply forces if we're on an articulated part, but we don't want to activate the
// object just to figure that out so just look at the articulated collider on the cache entry.
if(fragInst* groundFragInst = groundPhysical->GetFragInst())
{
if(groundFragInst == groundPhysical->GetCurrentPhysicsInst())
{
if(fragCacheEntry* cacheEntry = groundFragInst->GetCacheEntry())
{
if(phArticulatedCollider* articulatedCollider = cacheEntry->GetHierInst()->articulatedCollider)
{
if(articulatedCollider->IsArticulated())
{
int linkIndex = articulatedCollider->GetLinkFromComponent(m_groundPhysicalComponent);
if(linkIndex > 0)
{
phArticulatedBody* body = articulatedCollider->GetBody();
// It's only necessary to apply forces to non-driven joints
if(body && body->GetNumJoints() > linkIndex-1 && articulatedCollider->GetBody()->GetJoint(linkIndex-1).GetDriveState() == phJoint::DRIVE_STATE_FREE)
{
// Make sure the physical is active and apply the negated ped spring force.
groundPhysical->ActivatePhysics();
if(groundPhysical->GetCollider() == articulatedCollider)
{
const ScalarV timestep = ScalarVFromF32(fwTimer::GetTimeStep());
const ScalarV currentSpringForce = ScalarVFromF32(m_CurrentSpringForceZ);
const Vec3V springForce = Scale(Clamp(currentSpringForce,ScalarV(V_ZERO),ScalarVFromF32(25.0f)),Vec3V(V_Z_AXIS_WZERO));
articulatedCollider->ApplyForce(Negate(springForce).GetIntrin128(),VECTOR3_TO_VEC3V(GetGroundPos()).GetIntrin128(),timestep.GetIntrin128(),m_groundPhysicalComponent);
}
}
}
}
}
}
}
}
}
}
}
CPhysical::ProcessPostPhysics();
}
eAnimBoneTag CPed::GetBoneTagFromRagdollComponent(const int component) const
{
eAnimBoneTag boneTag = BONETAG_ROOT;
int boneIndex = GetBoneIndexFromRagdollComponent(component);
if(boneIndex > -1)
{
boneTag = GetBoneTagFromBoneIndex(boneIndex);
}
return boneTag;
}
s32 CPed::GetBoneIndexFromRagdollComponent(const int component) const
{
s32 boneIndex = -1;
const fragInstNMGta* pRagdollInst = GetRagdollInst();
if(pRagdollInst && pRagdollInst->GetCached())
{
int mappedComponent = pRagdollInst->MapRagdollLODComponentHighToCurrent(component);
if(pedVerifyf(mappedComponent != -1 && mappedComponent < pRagdollInst->GetTypePhysics()->GetNumChildren(), "Ped (%s). Mapped ragdoll component [%d] is out of range 0..[%d]. Original component [%d]", GetModelName(), mappedComponent, pRagdollInst->GetTypePhysics()->GetNumChildren()-1, component))
{
fragTypeChild* child = pRagdollInst->GetTypePhysics()->GetChild(mappedComponent);
boneIndex = pRagdollInst->GetType()->GetBoneIndexFromID(child->GetBoneID());
}
}
return boneIndex;
}
s32 CPed::GetRagdollComponentFromBoneTag(const eAnimBoneTag boneTag) const
{
if(GetRagdollInst() && GetRagdollInst()->GetCached())
{
s32 boneIndex = GetBoneIndexFromBoneTag(boneTag);
for(int i=0; i<GetRagdollInst()->GetTypePhysics()->GetNumChildren(); i++)
{
if(GetRagdollInst()->GetType()->GetBoneIndexFromID(GetRagdollInst()->GetTypePhysics()->GetAllChildren()[i]->GetBoneID()) == boneIndex)
return i;
}
}
return 0;
}
void CPed::SetPropRequestGfx(CPedPropRequestGfx* newGfx)
{
if (m_propRequestGfx)
{
m_propRequestGfx->ClearWaitForGfx();
delete m_propRequestGfx;
}
m_propRequestGfx = newGfx;
if (m_propRequestGfx)
{
m_propRequestGfx->SetTargetEntity(this);
// if we request both props and assets, sync them so they display at the same time (e.g. clothes shops)
CPedStreamRequestGfx* streamGfx = GetExtensionList().GetExtension<CPedStreamRequestGfx>();
if (streamGfx)
{
streamGfx->SetWaitForGfx(m_propRequestGfx);
m_propRequestGfx->SetWaitForGfx(streamGfx);
}
}
}
void CPed::ProcessLeftHandGripIk(const bool bAllow1HandedAttachToRightGrip, const bool bAllow2HandedAttachToStockGrip, float fOverrideBlendInDuration, float fOverrideBlendOutDuration)
{
// Clear this flag, since whatever intention we had to cancel IK will no longer be relevant after this call.
SetPedResetFlag(CPED_RESET_FLAG_CancelLeftHandGripIk, false);
if(GetWeaponManager())
{
bool bFPSAttachWeaponLeftIKToRight = false;
#if FPS_MODE_SUPPORTED
if(IsFirstPersonShooterModeEnabledForPlayer(false) && GetWeaponManager())
{
const CWeaponInfo* pWeaponInfo = (GetWeaponManager()->GetEquippedWeapon()) ? GetWeaponManager()->GetEquippedWeapon()->GetWeaponInfo() : NULL;
if(pWeaponInfo)
{
bFPSAttachWeaponLeftIKToRight = pWeaponInfo->GetAttachFPSLeftHandIKToRight();
if(pWeaponInfo->GetIsThrownWeapon())
{
bFPSAttachWeaponLeftIKToRight = bFPSAttachWeaponLeftIKToRight && GetPedResetFlag(CPED_RESET_FLAG_FPSAllowAimIKForThrownProjectile);
}
}
}
#endif
CObject* pWeaponObject = GetWeaponManager()->GetEquippedWeaponObject();
if (pWeaponObject || bFPSAttachWeaponLeftIKToRight)
{
CWeapon* pWeapon = GetWeaponManager()->GetEquippedWeapon();
if (pWeapon)
{
const CWeaponModelInfo* pModelInfo = pWeapon->GetWeaponModelInfo();
const CWeaponInfo* pWeaponInfo = pWeapon->GetWeaponInfo();
taskAssert(pWeaponInfo);
if (!pWeaponInfo->GetNoLeftHandIK() && (bFPSAttachWeaponLeftIKToRight || (pModelInfo && taskVerifyf(pWeaponObject, "Ped doesn't have a weapon object"))))
{
#if FPS_MODE_SUPPORTED
if (!pWeaponInfo->GetNoLeftHandIKWhenBlocked() || !GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_WEAPON_BLOCKED) || IsFirstPersonShooterModeEnabledForPlayer(false))
#else
if (!pWeaponInfo->GetNoLeftHandIKWhenBlocked() || !GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_WEAPON_BLOCKED))
#endif // FPS_MODE_SUPPORTED
{
if (CTaskAimGun::ms_bUseLeftHandIk)
{
const bool bAttachToRightGrip = bAllow1HandedAttachToRightGrip && (pWeaponInfo->GetIsGun1Handed() || bFPSAttachWeaponLeftIKToRight);
const bool bAttachToStockGrip = bAllow2HandedAttachToStockGrip && pWeaponInfo->GetIsGun2Handed();
s32 boneIdxL = -1;
s32 boneIdxR = -1;
if(pModelInfo)
{
boneIdxL = pModelInfo->GetBoneIndex(!bAttachToStockGrip ? WEAPON_GRIP_L : WEAPON_NM_BUTT_MARKER);
boneIdxR = pModelInfo->GetBoneIndex(WEAPON_GRIP_R);
}
if(((boneIdxL >= 0) || bAttachToRightGrip) && ((boneIdxR >= 0) || bFPSAttachWeaponLeftIKToRight))
{
Matrix34 m(M34_IDENTITY);
if (!bAttachToRightGrip && (boneIdxR >= 0) && (boneIdxL >= 0))
{
crSkeleton* pWeaponSkeleton = pWeapon->GetEntity()->GetSkeleton();
Matrix34 mLeftGripBone(MAT34V_TO_MATRIX34(pWeaponSkeleton->GetObjectMtx(boneIdxL)));
static const atHashWithStringNotFinal HEAVYRIFLE_HASH("WEAPON_HEAVYRIFLE", 0xC78D71B4);
static const atHashWithStringNotFinal PRECISIONRIFLE_HASH("WEAPON_PRECISIONRIFLE", 0x6E7DDDEC);
// Don't apply offset hacks during reloads
aiTask* pSecondaryTask = GetPedIntelligence()->GetTaskSecondaryActive();
bool bSecondaryTaskIsReload = pSecondaryTask ? pSecondaryTask->GetTaskType() == CTaskTypes::TASK_RELOAD_GUN : false;
if(!GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_RELOAD_GUN) && !bSecondaryTaskIsReload)
{
// Weapon-Specific Hacks (sorry...)
static const atHashWithStringNotFinal GUSENBERG_HASH("WEAPON_GUSENBERG", 0x61012683);
static const atHashWithStringNotFinal AUTOSHOTGUN_HASH("WEAPON_AUTOSHOTGUN", 0x12e82d3d);
static const atHashWithStringNotFinal HEAVYSNIPER_HASH("WEAPON_HEAVYSNIPER", 0xc472fe2);
static const atHashWithStringNotFinal ASSAULTRIFLE_MK2_HASH("WEAPON_ASSAULTRIFLE_MK2", 0x394f415c);
static const atHashWithStringNotFinal CARBINERIFLE_MK2_HASH("WEAPON_CARBINERIFLE_MK2", 0xfad1f1c9);
static const atHashWithStringNotFinal COMBATMG_MK2_HASH("WEAPON_COMBATMG_MK2", 0xdbbd7280);
static const atHashWithStringNotFinal HEAVYSNIPER_MK2_HASH("WEAPON_HEAVYSNIPER_MK2", 0xa914799);
static const atHashWithStringNotFinal SMG_MK2_HASH("WEAPON_SMG_MK2", 0x78a97cd0);
static const atHashWithStringNotFinal BULLPUPRIFLE_MK2_HASH("WEAPON_BULLPUPRIFLE_MK2", 0x84d6fafd);
static const atHashWithStringNotFinal SPECIALCARBINE_MK2_HASH("WEAPON_SPECIALCARBINE_MK2", 0x969c3d67);
static const atHashWithStringNotFinal PUMPSHOTGUN_MK2_HASH("WEAPON_PUMPSHOTGUN_MK2", 0x555af99a);
static const atHashWithStringNotFinal MARKSMANRIFLE_MK2_HASH("WEAPON_MARKSMANRIFLE_MK2", 0x6a6c02e0);
static const atHashWithStringNotFinal MILITARYRIFLE_HASH("WEAPON_MILITARYRIFLE", 0x9D1F17E6);
TUNE_GROUP_BOOL(WEAPON_ATTACH_OFFSET, bDebugGripOffsetLeftHand, false);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fDebugGripOffsetLeftHandX, 0.0f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fDebugGripOffsetLeftHandY, 0.0f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fDebugGripOffsetLeftHandZ, 0.0f, -1.0f, 1.0f, 0.01f);
if (bDebugGripOffsetLeftHand && GetEquippedWeaponInfo() && GetEquippedWeaponInfo()->GetIsGun())
{
mLeftGripBone.d.x += fDebugGripOffsetLeftHandX;
mLeftGripBone.d.y += fDebugGripOffsetLeftHandY;
mLeftGripBone.d.z += fDebugGripOffsetLeftHandZ;
}
else if (pWeaponInfo->GetHash() == GUSENBERG_HASH)
{
if (GetPedResetFlag(CPED_RESET_FLAG_InCoverFacingLeft))
{
CTaskAimGunBlindFire* pBlindFireTask = static_cast<CTaskAimGunBlindFire*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AIM_GUN_BLIND_FIRE));
if (pBlindFireTask)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fGusenbergGripOffsetBlindFire, -0.1f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fGusenbergGripOffsetBlindFireLerpDuration, 0.1f, 0.0f, 1.0f, 0.01f);
float fBlendRatio = pBlindFireTask->GetState() < CTaskAimGunBlindFire::State_OutroNew ? 1.0f : 1.0f - rage::Clamp(pBlindFireTask->GetTimeInState() / fGusenbergGripOffsetBlindFireLerpDuration, 0.0f, 1.0f);
mLeftGripBone.d += mLeftGripBone.c * fGusenbergGripOffsetBlindFire * fBlendRatio;
}
}
}
else if (pWeaponInfo->GetHash() == AUTOSHOTGUN_HASH)
{
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fAutoShotgunGripOffsetLeftHandZ, -0.03f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.z += fAutoShotgunGripOffsetLeftHandZ;
}
}
else if (pWeaponInfo->GetHash() == ASSAULTRIFLE_MK2_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fAssaultRifleMk2GripOffsetX, -0.05f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fAssaultRifleMk2GripOffsetY, -0.002f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fAssaultRifleMk2GripOffsetZ, -0.006f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fAssaultRifleMk2GripOffsetX;
mLeftGripBone.d.y += fAssaultRifleMk2GripOffsetY;
mLeftGripBone.d.z += fAssaultRifleMk2GripOffsetZ;
// MP females need adjusting slightly, as they're playing a different idle anim
if (IsPlayer() && !IsMale() && !GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fAssaultRifleMk2GripOffsetXFemaleAddition, 0.07f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fAssaultRifleMk2GripOffsetXFemaleAddition;
}
}
else if (pWeaponInfo->GetHash() == CARBINERIFLE_MK2_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCarbineRifleMk2GripOffsetX, -0.02f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCarbineRifleMk2GripOffsetY, 0.003f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fCarbineRifleMk2GripOffsetX;
mLeftGripBone.d.y += fCarbineRifleMk2GripOffsetY;
// MP females need adjusting slightly, as they're playing a different idle anim
if (IsPlayer() && !IsMale() && !GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCarbineRifleMk2GripOffsetYFemaleAddition, -0.003f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.y += fCarbineRifleMk2GripOffsetYFemaleAddition;
}
}
else if (pWeaponInfo->GetHash() == COMBATMG_MK2_HASH)
{
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCombatMGMk2GripOffsetAimingX, 0.015f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCombatMGMk2GripOffsetAimingY, 0.007f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCombatMGMk2GripOffsetAimingZ, -0.005f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fCombatMGMk2GripOffsetAimingX;
mLeftGripBone.d.y += fCombatMGMk2GripOffsetAimingY;
mLeftGripBone.d.z += fCombatMGMk2GripOffsetAimingZ;
}
else
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCombatMGMk2GripOffsetIdleX, 0.04f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fCombatMGMk2GripOffsetIdleY, 0.006f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fCombatMGMk2GripOffsetIdleX;
mLeftGripBone.d.y += fCombatMGMk2GripOffsetIdleY;
}
}
else if (pWeaponInfo->GetHash() == HEAVYSNIPER_HASH || pWeaponInfo->GetHash() == HEAVYSNIPER_MK2_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fHeavySniperGripOffsetX, 0.020f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fHeavySniperGripOffsetY, 0.003f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fHeavySniperGripOffsetZ, -0.005f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fHeavySniperGripOffsetX;
mLeftGripBone.d.y += fHeavySniperGripOffsetY;
mLeftGripBone.d.z += fHeavySniperGripOffsetZ;
}
else if (pWeaponInfo->GetHash() == SMG_MK2_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSMGMk2GripOffsetX, -0.025f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSMGMk2GripOffsetY, -0.002f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSMGMk2GripOffsetZ, -0.008f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fSMGMk2GripOffsetX;
mLeftGripBone.d.y += fSMGMk2GripOffsetY;
mLeftGripBone.d.z += fSMGMk2GripOffsetZ;
}
else if (pWeaponInfo->GetHash() == BULLPUPRIFLE_MK2_HASH && !IsFirstPersonShooterModeEnabledForPlayer(false))
{
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBullpupRifleMk2GripOffsetAimingX, -0.05f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBullpupRifleMk2GripOffsetAimingY, 0.004f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBullpupRifleMk2GripOffsetAimingZ, -0.004f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fBullpupRifleMk2GripOffsetAimingX;
mLeftGripBone.d.y += fBullpupRifleMk2GripOffsetAimingY;
mLeftGripBone.d.z += fBullpupRifleMk2GripOffsetAimingZ;
}
else
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBullpupRifleMk2GripOffsetIdleX, 0.000f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBullpupRifleMk2GripOffsetIdleY, -0.006f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBullpupRifleMk2GripOffsetIdleZ, 0.015f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fBullpupRifleMk2GripOffsetIdleX;
mLeftGripBone.d.y += fBullpupRifleMk2GripOffsetIdleY;
mLeftGripBone.d.z += fBullpupRifleMk2GripOffsetIdleZ;
}
}
else if (pWeaponInfo->GetHash() == SPECIALCARBINE_MK2_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSpecialCarbineMk2GripOffsetX, -0.013f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSpecialCarbineMk2GripOffsetFirstPersonY, -0.007f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSpecialCarbineMk2GripOffsetThirdPersonY, 0.000f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fSpecialCarbineMk2GripOffsetZ, -0.010f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fSpecialCarbineMk2GripOffsetX;
mLeftGripBone.d.y += IsFirstPersonShooterModeEnabledForPlayer(false) ? fSpecialCarbineMk2GripOffsetFirstPersonY : fSpecialCarbineMk2GripOffsetThirdPersonY;
mLeftGripBone.d.z += fSpecialCarbineMk2GripOffsetZ;
}
else if (pWeaponInfo->GetHash() == PUMPSHOTGUN_MK2_HASH)
{
if (GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fPumpShotgunMk2GripOffsetAimingY, -0.004f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.y += fPumpShotgunMk2GripOffsetAimingY;
}
}
else if (pWeaponInfo->GetHash() == MARKSMANRIFLE_MK2_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fMarksmanRifleMk2GripOffsetX, -0.02f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fMarksmanRifleMk2GripOffsetY, -0.004f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fMarksmanRifleMk2GripOffsetZ, -0.005f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fMarksmanRifleMk2GripOffsetX;
mLeftGripBone.d.y += fMarksmanRifleMk2GripOffsetY;
mLeftGripBone.d.z += fMarksmanRifleMk2GripOffsetZ;
}
else if (pWeaponInfo->GetHash() == MILITARYRIFLE_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fMilitartrideGripOffsetX, -0.025f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fMilitartrideGripOffsetY, -0.004f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fMilitartrideGripOffsetZ, -0.005f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fMilitartrideGripOffsetX;
mLeftGripBone.d.y += fMilitartrideGripOffsetY;
mLeftGripBone.d.z += fMilitartrideGripOffsetZ;
}
else if (pWeaponInfo->GetHash() == HEAVYRIFLE_HASH)
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fHeavyRifleGripOffsetX, 0.045f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fHeavyRifleGripOffsetY, 0.000f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fHeavyRifleGripOffsetZ, 0.014f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fHeavyRifleGripOffsetX;
mLeftGripBone.d.y += fHeavyRifleGripOffsetY;
mLeftGripBone.d.z += fHeavyRifleGripOffsetZ;
}
else if (pWeaponInfo->GetHash() == PRECISIONRIFLE_HASH && IsFirstPersonShooterModeEnabledForPlayer(false))
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fPrecisionRifleGripOffsetX, -0.100f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fPrecisionRifleGripOffsetY, 0.000f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fPrecisionRifleGripOffsetZ, 0.000f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fPrecisionRifleGripOffsetX;
mLeftGripBone.d.y += fPrecisionRifleGripOffsetY;
mLeftGripBone.d.z += fPrecisionRifleGripOffsetZ;
}
// B*2346868: Offset female skeleton peds left hand grip bone. Same offset as set in CPedEquippedWeapon::AttachObjects to keep aligned properly.
if (!IsMale() && GetPedModelInfo() && GetPedModelInfo()->IsUsingFemaleSkeleton() && GetEquippedWeaponInfo() && GetEquippedWeaponInfo()->GetIsGun())
{
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fFemaleGripOffsetLeftHandX, -0.015f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fFemaleGripOffsetLeftHandY, -0.010f, -1.0f, 1.0f, 0.01f);
mLeftGripBone.d.x += fFemaleGripOffsetLeftHandX;
mLeftGripBone.d.y += fFemaleGripOffsetLeftHandY;
}
// Reset blend out values when we are not reloading
m_mCurrentWeaponLeftGripBoneOffset.d = mLeftGripBone.d;
m_fElapsedBlendOutTime = 0.0f;
}
else if(pWeaponInfo->GetHash() == HEAVYRIFLE_HASH)
{
// Lerp off any left grip IK offset when we reload the weapon
TUNE_GROUP_FLOAT(WEAPON_ATTACH_OFFSET, fBlendOutTime, 0.3f, 0.0f, 100.0f, 0.1f);
if(m_fElapsedBlendOutTime < fBlendOutTime)
{
float fDelta = Clamp(m_fElapsedBlendOutTime / fBlendOutTime, 0.0f, 1.0f);
Matrix34 mLeftHandGripBonePosition(MAT34V_TO_MATRIX34(pWeaponSkeleton->GetObjectMtx(boneIdxL)));
mLeftGripBone.d = Lerp(fDelta, m_mCurrentWeaponLeftGripBoneOffset.d, mLeftHandGripBonePosition.d);
m_fElapsedBlendOutTime += fwTimer::GetGameTimer().GetTimeStep();
}
}
Matrix34 mRightGripBone(MAT34V_TO_MATRIX34(pWeaponSkeleton->GetObjectMtx(boneIdxR)));
m.DotTranspose(mLeftGripBone, mRightGripBone);
}
const bool bBlendQuick = GetIkManager().IsActive(IKManagerSolverTypes::ikSolverTypeTorsoReact);
float fBlendInDuration = fOverrideBlendInDuration;
float fBlendOutDuration = fOverrideBlendOutDuration;
if (fBlendInDuration < 0.0f)
{
fBlendInDuration = bBlendQuick ? PEDIK_ARMS_FADEIN_TIME_QUICK : PEDIK_ARMS_FADEIN_TIME;
}
if (fBlendOutDuration < 0.0f)
{
fBlendOutDuration = bBlendQuick ? PEDIK_ARMS_FADEOUT_TIME_QUICK : PEDIK_ARMS_FADEOUT_TIME;
}
if(GetPlayerInfo())
{
// Instant blend in if we are going to switch modes this frame
if(GetPlayerInfo()->GetSwitchedToOrFromFirstPersonCount() == 1 || (GetMotionData() && !GetMotionData()->GetWasUsingFPSMode() && GetMotionData()->GetUsingFPSMode()))
{
fBlendInDuration = 0.f;
}
// If we will switch next frame, allow instant blend outs
if(GetPlayerInfo()->GetSwitchedToOrFromFirstPersonCount() == 2)
{
fBlendOutDuration = 0.f;
}
}
s32 controlFlags = AIK_TARGET_WRT_IKHELPER | AIK_TARGET_WEAPON_GRIP | AIK_USE_FULL_REACH;
if(pWeaponInfo->GetUseLeftHandIKAllowTags(*this))
{
controlFlags |= AIK_USE_ANIM_ALLOW_TAGS;
}
else
{
if (bAttachToStockGrip)
{
controlFlags |= AIK_USE_ANIM_BLOCK_TAGS;
}
if (NetworkInterface::IsGameInProgress() && IsPlayer() && GetPedResetFlag(CPED_RESET_FLAG_IsAiming))
{
CTask* pScriptedAnimTask = GetPedIntelligence()->FindTaskActiveByTreeAndType(PED_TASK_TREE_SECONDARY, CTaskTypes::TASK_SCRIPTED_ANIMATION);
if (pScriptedAnimTask)
{
controlFlags |= AIK_USE_ANIM_BLOCK_TAGS;
}
}
}
GetIkManager().SetRelativeArmIKTarget(crIKSolverArms::LEFT_ARM,
this,
GetBoneIndexFromBoneTag(BONETAG_R_PH_HAND),
m.d,
controlFlags,
fBlendInDuration,
fBlendOutDuration);
#if FPS_MODE_SUPPORTED
if (IsFirstPersonShooterModeEnabledForPlayer(false, false, false, true, false))
{
Quaternion rotation;
m.ToQuaternion(rotation);
controlFlags |= AIK_USE_ORIENTATION;
if (GetPedResetFlag(CPED_RESET_FLAG_PlayFPSIdleFidgets) && GetMotionData()->GetIsFPSIdle())
{
controlFlags |= AIK_USE_ANIM_BLOCK_TAGS;
}
CIkRequestArm armRequest;
armRequest.SetArm(IK_ARM_LEFT);
armRequest.SetTargetEntity(this);
armRequest.SetTargetBoneTag(BONETAG_R_PH_HAND);
armRequest.SetTargetOffset(RC_VEC3V(m.d));
armRequest.SetTargetRotation(RC_QUATV(rotation));
armRequest.SetFlags(controlFlags);
armRequest.SetBlendInRate(fBlendInDuration != 0.0f ? 1.0f / fBlendInDuration : INSTANT_BLEND_IN_DELTA);
armRequest.SetBlendOutRate(fBlendOutDuration != 0.0f ? 1.0f / fBlendOutDuration : INSTANT_BLEND_IN_DELTA);
GetIkManager().Request(armRequest, CIkManager::IK_PASS_FPS);
GetIkManager().Request(armRequest, CIkManager::IK_PASS_MAIN);
}
#endif // FPS_MODE_SUPPORTED
}
}
}
}
}
}
}
}
const CPedIKSettingsInfo & CPed::GetIKSettings()
{
if (!m_pIKSettings)
{
const CPedModelInfo *pModelInfo = GetPedModelInfo();
Assert(pModelInfo);
m_pIKSettings = CPedIKSettingsInfoManager::GetInfo(pModelInfo->GetPedIKSettingsHash().GetHash());
Assertf(m_pIKSettings, "Failed to get IK settings %s for ped model %s", pModelInfo->GetPedIKSettingsHash().TryGetCStr(), GetModelName());
}
return *m_pIKSettings;
}
const CTaskDataInfo & CPed::GetTaskData() const
{
if ( !m_pTaskData )
{
const CPedModelInfo *pModelInfo = GetPedModelInfo();
Assert(pModelInfo);
m_pTaskData = CTaskDataInfoManager::GetInfo(pModelInfo->GetTaskDataHash().GetHash());
}
Assert(m_pTaskData);
return *m_pTaskData;
}
// Name : SetInitialState
// Purpose : Processes control for player ped object
// mjc - moved from CPedPlayer
// Parameters : None
// Returns : Nothing
void CPed::SetInitialState()
{
fwTimer::SetTimeWarp(1.0f);
EnableCollision();
SetArrestState(ArrestState_None);
SetDeathState(DeathState_Alive);
m_bDeathTimeHasSet = false;
GetMotionData()->SetDesiredMoveBlendRatio(MOVEBLENDRATIO_STILL);
SetIsCrouching(false);
if(GetIsAttached())
DetachFromParent(0);
EnableCollision();
if(GetPlayerInfo())
GetPlayerInfo()->SetInitialState();
}
// Name : Resurrect
// Purpose : Resurrects the ped (player?) at the given coords
// Parameters : NewCoors - the coords the ped has to be resurrected at, on the ground.
// fNewHeading - the ped's heading after resurrection
// Returns : Nothing
void CPed::Resurrect(const Vector3& NewCoors, float fNewHeading, const bool triggerNetworkRespawnEvent, const bool bDoTeleport)
{
respawnDebugf3("[%d] CPed::Resurrect ped[%p][%s][%s]", fwTimer::GetFrameCount(), this, GetModelName(), GetNetworkObject() ? GetNetworkObject()->GetLogName() : "");
// Only reactivate the peds physics if currently inactive
if( !GetCurrentPhysicsInst()->IsInLevel() )
{
AddPhysics();
}
Assert(GetCurrentPhysicsInst()->IsInLevel());
// Make sure that player's animated inst is allowed to activate. It might have been
// told not to do so as part of the dead ragdoll shenanigans.
physicsAssert(GetAnimatedInst());
GetAnimatedInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false);
//Ensure that we will ignore any UpdateVehiclePed requests to put the ped back into the vehicle as this was happening incorrectly
if (NetworkInterface::IsGameInProgress() && IsNetworkClone())
{
NetworkInterface::IgnoreVehicleUpdates(this,1000);
}
//
// DETACH PLAYER FROM VEHICLE, ETC:
//
// If we will be teleporting the player after this, don't bother doing any safety position checks
const u32 uSetPedOutFlags = bDoTeleport ? CPed::PVF_Warp|CPed::PVF_IgnoreSafetyPositionCheck : CPed::PVF_Warp;
SetPedOutOfVehicle(uSetPedOutFlags);
if(GetIsAttached())
{
const u16 uDetachFlags = bDoTeleport ? DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK : 0;
DetachFromParent(uDetachFlags);
}
Assert(GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) == false);
Assert(!GetIsAttached());
CPickupManager::DetachAllPortablePickupsFromPed(this);
//
// CLEANUP ANY RENDERING / VISUAL EFFECTS:
//
if(IsPlayer())
{
if (IsPlayer())
{
bool bProcessPromoteBloodToScars = true;
if (NetworkInterface::IsGameInProgress() && GetNetworkObject())
{
CNetObjPlayer* pNetObjPlayer = static_cast<CNetObjPlayer*>(GetNetworkObject());
if (pNetObjPlayer)
{
bProcessPromoteBloodToScars = pNetObjPlayer->HasMoney();
if (bProcessPromoteBloodToScars)
pNetObjPlayer->ClearNetworkedDamagePack();
}
}
if (bProcessPromoteBloodToScars)
PromoteBloodToScars(); // do remote peds even resurect? if they do , they will deal with their own blood/scar conversions
}
ClearCustomDirt();
SetSweatScale(0.0f);
if(HasSpecialAbility())
{
for (int i = 0; i < PSAS_MAX; ++i)
{
if (m_specialAbilities[i] && IsValidSpecialAbilitySlot(ePlayerSpecialAbilitySlot(i)))
{
m_specialAbilities[i]->Deactivate();
}
}
}
}
else
{
ClearDamage();
}
GetPedDrawHandler().GetPropData().SetSkipRender(false);
m_nPhysicalFlags.bRenderScorched = false;
// clean up wet effect
ClearWetClothing();
if (GetPedVfx())
{
GetPedVfx()->Init(this);
}
g_ptFxManager.RemovePtFxFromEntity(this, true); // pass true so that certain effects don't get cleared when respawning
g_fireMan.ExtinguishEntityFires(this, true);
//
// CLEANUP ANIMATION:
//
// Remove any higher priority animation
GetMovePed().SetTaskNetwork(NULL);
GetMovePed().SetAdditiveNetwork(NULL);
// get rid of ragdoll frame
GetMovePed().SwitchToAnimated(0.f);
// get rid of any root slope fixup
GetIkManager().RemoveSolver(IKManagerSolverTypes::ikSolverTypeRootSlopeFixup);
// get rid of any bound re-orientation
ClearBound();
SetBoundPitch(0.0f);
SetBoundHeading(0.0f);
SetBoundOffset(VEC3_ZERO);
// The player may have been posed in a different animation state, set posed to false so the current animation state is forced after the camera update
GetAnimDirector()->SetPosed(false);
RestoreHeadingChangeRate();
//
// CLEANUP AUDIO:
//
// Reset speech flags so we stop screaming if falling or on fire
if(GetSpeechAudioEntity())
{
GetSpeechAudioEntity()->Resurrect();
}
if(GetPedAudioEntity())
{
GetPedAudioEntity()->Resurrect();
}
//
// TELEPORT
//
pedDisplayf("[%d] DEADPED: Ped is being resurrected! [Ped=%s]", CNetwork::GetNetworkTime(),
GetNetworkObject() ? GetNetworkObject()->GetLogName() : "No Name");
if (bDoTeleport)
{
// If the player doesn't actually exist in the world (because he died in a car) we'd have to add him in again.
CGameWorld::Remove(this);
CGameWorld::Add(this, CGameWorld::OUTSIDE );
// we have to add on ped's root offset because the spawn point is on the ground
respawnDebugf3("[%d] CPed::Resurrect-- Teleport pos %.2f:%.2f:%.2f", fwTimer::GetFrameCount(), NewCoors.x, NewCoors.y, NewCoors.z);
Teleport(NewCoors+Vector3(0.0f, 0.0f, m_pCapsuleInfo->GetGroundToRootOffset()), fNewHeading);
respawnDebugf3("[%d] CPed::Resurrect-- Ped position after teleport %.2f:%.2f:%.2f", fwTimer::GetFrameCount(), GetTransform().GetPosition().GetXf(), GetTransform().GetPosition().GetYf(), GetTransform().GetPosition().GetZf());
}
// prevent the player sinking into the ground for one frame:
SetIsStanding(true);
SetWasStanding(true);
SetGroundOffsetForPhysics( -m_pCapsuleInfo->GetGroundToRootOffset() );
if (NetworkInterface::IsGameInProgress())
{
if (IsNetworkClone())
{
// ensure the player is upright
Matrix34 newMatrix = MAT34V_TO_MATRIX34(GetMatrix());
// make the peds's matrix vertical again.
newMatrix.MakeRotateZ(GetTransform().GetHeading());
// set the matrix to the newMatrix to enforce the uprightness
SetMatrix(newMatrix);
// ensure the desired bound pitch is set to zero also
SetDesiredBoundPitch(0.0f);
}
}
if(IsLocalPlayer())
{
if(NetworkInterface::IsGameInProgress())
{
CVehiclePopulation::RemoveEmptyWreckedVehsAndPedsOnRespawn_MP(this);
CPedPopulation::RemovePedsOnRespawn_MP(this);
}
CVehiclePopulation::ResetDesertedStreetsMultiplier();
}
//mjc m_fTimeBeenSprinting = 0.0f;
//mjc m_fSprintStaminaUpdatePeriod = 0.0f;
//mjc m_fSprintStaminaDurationMultiplier = 1.0f;
//mjc m_fTimeBeenSwimming = 0.0f;
//mjc m_fSwimLungCapacityUpdatePeriod = 0.0f;
//
// RESET PED STATE
//
CPlayerInfo* pPlayerInfo = GetPlayerInfo();
if (IsNetworkClone())
{
respawnDebugf3("CPed::Resurrect--IsNetworkClone--call ResurrectClone");
static_cast<CNetObjPlayer *>(GetNetworkObject())->ResurrectClone();
}
else
{
#if __DEV
m_nDEflags.bCheckedForDead = true;
#endif
SetIsVisibleForModule( SETISVISIBLE_MODULE_GAMEPLAY, true );
SetIsVisibleForModule( SETISVISIBLE_MODULE_FIRST_PERSON, true );
ClearDeathInfo();
if (pPlayerInfo)
{
if(GetInjuredHealthThreshold() > GetPlayerInfo()->MaxHealth)
{
pedAssertf(0, "m_fInjuredHealthThreshold > GetPlayerInfo()->MaxHealth. Something went wrong, so correcting health values! Health: %.2f MaxHealth: %u, InjuredThresh: %f", GetHealth(), GetPlayerInfo()->MaxHealth, GetInjuredHealthThreshold());
GetPlayerInfo()->MaxHealth = static_cast<u16>(GetInjuredHealthThreshold()) + 1;
}
SetHealth(GetPlayerInfo()->MaxHealth);
pPlayerInfo->SetStealthNoise(0.f);
respawnDebugf3("CPed::Resurrect-- GetHealth:%.2f Max Health:%u", GetHealth(), GetPlayerInfo()->MaxHealth);
}
else
{
if(GetInjuredHealthThreshold() > GetMaxHealth())
{
pedAssertf(0, "m_fInjuredHealthThreshold > GetPlayerInfo()->MaxHealth. Something went wrong, so correcting health values! Health: %.2f MaxHealth: %.2f, InjuredThresh: %f", GetHealth(), GetMaxHealth(), GetInjuredHealthThreshold());
SetMaxHealth(GetInjuredHealthThreshold() + 1.0f);
}
SetHealth(GetMaxHealth());
respawnDebugf3("CPed::Resurrect-- GetHealth:%.2f Max Health:%.2f", GetHealth(), GetMaxHealth());
}
SetDeathState(DeathState_Alive);
pedAssertf(!IsInjured(), "Ped has been resurrected but is still injured. Something went wrong! Health: %f MaxHealth: %f, InjuredThresh: %f", GetHealth(), GetMaxHealth(), GetInjuredHealthThreshold());
SetArmour(0.0f);
SetEndurance(GetMaxEndurance());
respawnDebugf3("CPed::Resurrect-- GetEndurance:%.2f GetMaxEndurance:%.2f", GetEndurance(), GetMaxEndurance());
#if RSG_PC
// We dont' need real links for all the peds; just the local player
if(IsLocalPlayer())
{
for(int i = 0; i < fwRandom::GetRandomNumberInRange(MIN_NUM_SYSOBF_LINKS, MAX_NUM_SYSOBF_LINKS); i++)
{
m_nArmour.AddLink(rage_new sysLinkedData<float, ReportArmorMismatch>(0.0f));
}
}
#endif //RSG_PC
ResetWeaponDamageInfo();
SetWeaponDamageComponent(-1);
if (GetPlayerWanted())
{
GetPlayerWanted()->Reset();
}
//mjc-moved ResetSprintEnergy();
if (GetHelmetComponent() && (!NetworkInterface::IsGameInProgress() || !GetPedConfigFlag(CPED_CONFIG_FLAG_DontTakeOffHelmet)))
GetHelmetComponent()->DisableHelmet();
ClearBaseFlag(fwEntity::REMOVE_FROM_WORLD);
SetPedConfigFlag( CPED_CONFIG_FLAG_BlockWeaponSwitching, false );
//mjc-moved - GetTargeting().ClearLockOnTarget();
//mjc-moved SetInitialState();
SetVelocity(ORIGIN);
// Clear all gadgets
CPedWeaponManager* pWeaponManager = GetWeaponManager();
if (pWeaponManager)
{
pWeaponManager->DestroyEquippedGadgets();
}
if (GetNetworkObject())
{
//mjc - do we want to move this down below the PlayerInfo resurrect call?
respawnDebugf3("CPed::Resurrect--!IsNetworkClone--call Resurrect");
static_cast<CNetObjPlayer *>(GetNetworkObject())->Resurrect(NewCoors + Vector3(0.0f, 0.0f, m_pCapsuleInfo->GetGroundToRootOffset()), triggerNetworkRespawnEvent);
}
// Clear script and combat action mode. Battle awareness is reset as this can bleed into post mission as it's timing
// based.
SetUsingActionMode(false, AME_Combat);
GetPedIntelligence()->ResetBattleAwareness();
}
// Clear all melee flags
SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_Knockedout, false );
SetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStandardMelee, false );
//! Reset flag for local player when we are resurrected (code sets this when melee executed, so it needs cleared here).
if(IsLocalPlayer())
{
SetPedConfigFlag( CPED_CONFIG_FLAG_DoesntDropWeaponsWhenDead, false );
}
//
// RESET PLAYER INFO STATE:
//
if (pPlayerInfo)
{
pPlayerInfo->ResurrectPlayer();
}
// Reset character cloth stuff
SetClothIsFalling(false);
SetClothPinRadiusScale(1.0f);
SetClothNegateForce(false);
SetClothForceSkin(false);
SetPedConfigFlag( (ePedConfigFlags)CPED_CONFIG_FLAG_ForceSkinCharacterCloth, false );
// B*2004318/1949041: Reset the helmet shot flag and number of hits it's taken
SetPedConfigFlag(CPED_CONFIG_FLAG_HelmetHasBeenShot, false);
ResetNumArmouredHelmetHits();
// Clear stealth and crouch mode
if(GetIsCrouching())
{
SetIsCrouching(false);
}
if(GetMotionData()->GetUsingStealth())
{
GetMotionData()->SetUsingStealth(false);
}
}
const Vector3& CPed::GetVelocity() const
{
//Check if the ped is attached to the ground.
if(GetIsAttachedToGround() || (GetPedResetFlag(CPED_RESET_FLAG_IsClimbing) && GetCollider() == NULL))
{
Assertf(IsLessThanAll(MagSquared(m_vGroundVelocity), ScalarV(DEFAULT_IMPULSE_LIMIT * DEFAULT_IMPULSE_LIMIT + 0.0001f)), "Ground velocity was invalid: (%f, %f, %f).", VEC3V_ARGS(m_vGroundVelocity));
Assertf(IsFiniteAll(m_vGroundVelocity), "Ground velocity was invalid: (%f, %f, %f).", VEC3V_ARGS(m_vGroundVelocity));
return RCC_VECTOR3(m_vGroundVelocity);
}
if(IsUsingLowLodPhysics())
{
Assertf(IsLessThanAll(MagSquared(VECTOR3_TO_VEC3V(m_vDesiredVelocity)), ScalarV(DEFAULT_IMPULSE_LIMIT * DEFAULT_IMPULSE_LIMIT + 0.0001f)), "Desired velocity was invalid: (%f, %f, %f).", m_vDesiredVelocity.x, m_vDesiredVelocity.y, m_vDesiredVelocity.z);
Assertf(IsFiniteAll(VECTOR3_TO_VEC3V(m_vDesiredVelocity)), "Desired velocity was invalid: (%f, %f, %f).", m_vDesiredVelocity.x, m_vDesiredVelocity.y, m_vDesiredVelocity.z);
return m_vDesiredVelocity;
}
return CPhysical::GetVelocity();
}
void CPed::SetVelocity(const Vector3& vecMoveSpeed)
{
Assertf(vecMoveSpeed.Mag2() < DEFAULT_VELOCITY_LIMIT * DEFAULT_VELOCITY_LIMIT, "Externally set velocity was invalid: (%f, %f, %f).",
vecMoveSpeed.x, vecMoveSpeed.y, vecMoveSpeed.z);
CPhysical::SetVelocity(vecMoveSpeed);
}
const Vector3& CPed::GetAngVelocity() const
{
//Check if the ped is attached to the ground.
if(GetIsAttachedToGround() || (GetPedResetFlag(CPED_RESET_FLAG_IsClimbing) && GetCollider() == NULL))
{
return RCC_VECTOR3(m_vGroundAngularVelocity);
}
return CPhysical::GetAngVelocity();
}
//
// name: GetControlFromPlayer
// description: Returns a pointer to the pad class that is used by this player (or NULL if not a player)
CControl* CPed::GetControlFromPlayer()
{
if (IsControlledByLocalPlayer())
{
return &CControlMgr::GetMainPlayerControl();
}
else
{
return NULL; // Don't take controls into account if this player is controlled from another machine or just not a player
}
}
const CControl* CPed::GetControlFromPlayer() const
{
if (IsControlledByLocalPlayer())
{
return &CControlMgr::GetMainPlayerControl();
}
else
{
return NULL; // Don't take controls into account if this player is controlled from another machine or just not a player
}
}
#if !__FINAL
atString CPed::GetBodyPartDamageDbgStr() const
{
static const int nDamageStr = 6;
static const char DamageStr[nDamageStr][24] =
{
"DAMAGED_LEFT_LEG ",
"DAMAGED_RIGHT_LEG ",
"DAMAGED_LEFT_ARM ",
"DAMAGED_RIGHT_ARM ",
"DAMAGED_TORSO ",
"DAMAGED_HEAD ",
};
atString DbgStr("");
for (int i = 0; i < nDamageStr; ++i)
{
if (IsBodyPartDamaged(1 << i))
DbgStr += DamageStr[i];
}
return DbgStr;
}
#endif
u16 CPed::ConvertBoneToBodyPart(s32 nHitBoneTag)
{
switch(nHitBoneTag)
{
case BONETAG_L_FOOT:
case BONETAG_L_CALF:
case BONETAG_L_THIGH:
return DAMAGED_LEFT_LEG;
case BONETAG_R_FOOT:
case BONETAG_R_CALF:
case BONETAG_R_THIGH:
return DAMAGED_RIGHT_LEG;
case BONETAG_L_HAND:
case BONETAG_L_FOREARM:
case BONETAG_L_UPPERARM:
return DAMAGED_LEFT_ARM;
case BONETAG_R_HAND:
case BONETAG_R_FOREARM:
case BONETAG_R_UPPERARM:
return DAMAGED_RIGHT_ARM;
};
if (IsHeadShot(nHitBoneTag))
return DAMAGED_HEAD;
if (IsTorsoShot(nHitBoneTag))
return DAMAGED_TORSO;
return DAMAGED_UNKNOWN;
}
void CPed::InitHealth()
{
const CHealthConfigInfo *pHealthInfo = GetPedHealthInfo();
Assert(pHealthInfo);
m_fMaxHealth = pHealthInfo->GetDefaultHealth();
m_nHealthLostScript = 0.f;
m_fHealth = m_fMaxHealth;
m_nArmour = pHealthInfo->GetDefaultArmour();
m_nArmourLost = 0.f;
m_fMaxEndurance = pHealthInfo->GetDefaultEndurance();
m_fEndurance = m_fMaxEndurance;
m_DamagedBodyParts = DAMAGED_NONE;
m_fFatiguedHealthThreshold = pHealthInfo->GetFatiguedHealthThreshold();
m_fInjuredHealthThreshold = pHealthInfo->GetInjuredHealthThreshold();
m_fDyingHealthThreshold = pHealthInfo->GetDyingHealthThreshold();
m_fHurtHealthThreshold = pHealthInfo->GetHurtHealthThreshold();
m_fNMAccumulate = 0.f;
if (pHealthInfo->GetInvincible())
{
m_nPhysicalFlags.bNotDamagedByAnything = true;
}
#if RSG_PC
if(IsLocalPlayer())
{
for(int i = 0; i < fwRandom::GetRandomNumberInRange(MIN_NUM_SYSOBF_LINKS, MAX_NUM_SYSOBF_LINKS); i++)
{
m_fHealth.AddLink(rage_new sysLinkedData<float, ReportHealthMismatch>(m_fMaxHealth));
}
for(int i = 0; i < fwRandom::GetRandomNumberInRange(MIN_NUM_SYSOBF_LINKS, MAX_NUM_SYSOBF_LINKS); i++)
{
m_nArmour.AddLink(rage_new sysLinkedData<float, ReportArmorMismatch>(pHealthInfo->GetDefaultArmour()));
}
}
#endif //RSG_PC
}
void CPed::SetEndurance(float fNewEndurance, bool ASSERT_ONLY(bNetCall))
{
// can't change endurance on objects controlled by another machine
#if __ASSERT
if (!bNetCall && GetNetworkObject())
{
Assertf(!GetNetworkObject()->IsClone(), "Clone %s is having endurance altered illegally", GetNetworkObject()->GetLogName());
Assertf(!GetNetworkObject()->IsPendingOwnerChange() || m_fEndurance == fNewEndurance, "%s is having endurance altered illegally while migrating", GetNetworkObject()->GetLogName());
}
#endif
if (fNewEndurance <= SMALL_FLOAT)
{
fNewEndurance = 0.0f;
}
else
{
fNewEndurance = MIN(fNewEndurance, GetMaxEndurance());
}
if (fNewEndurance != m_fEndurance)
{
#if __BANK
Displayf("%s: Endurance changed from %f to %f", GetDebugName() ? GetDebugName() : "NULL", m_fEndurance, fNewEndurance);
#endif
m_fEndurance = fNewEndurance;
}
}
void CPed::SetMaxEndurance(float fNewEndurance, bool ASSERT_ONLY(bNetCall))
{
// can't change endurance on objects controlled by another machine
#if __ASSERT
if (!bNetCall && GetNetworkObject())
{
Assertf(!GetNetworkObject()->IsClone(), "Clone %s is having endurance altered illegally", GetNetworkObject()->GetLogName());
Assertf(!GetNetworkObject()->IsPendingOwnerChange() || m_fEndurance == fNewEndurance, "%s is having endurance altered illegally while migrating", GetNetworkObject()->GetLogName());
}
#endif
if (fNewEndurance <= SMALL_FLOAT)
{
fNewEndurance = 0.0f;
}
if (fNewEndurance != m_fMaxEndurance)
{
#if __BANK
Displayf("%s: Max Endurance changed from %f to %f", GetDebugName() ? GetDebugName() : "NULL", m_fMaxEndurance, fNewEndurance);
#endif
m_fMaxEndurance = fNewEndurance;
m_fEndurance = MIN(m_fEndurance, m_fMaxEndurance);
}
}
#if !__FINAL
bool CPed::IsDebugInvincible() const
{
if(IsPlayer())
{
if(IsLocalPlayer())
{
return CPlayerInfo::IsPlayerInvincible();
}
else
{
CNetObjPlayer *netObjPlayer = static_cast< CNetObjPlayer * >(GetNetworkObject());
if(netObjPlayer)
{
return netObjPlayer->IsDebugInvincible();
}
}
}
return false;
}
#endif
bool CPed::IsNetworkBot() const
{
if(GetNetworkObject() && GetNetworkObject()->GetPlayerOwner())
{
if(GetNetworkObject()->GetPlayerOwner()->GetPlayerType() == CNetGamePlayer::NETWORK_BOT)
{
return true;
}
}
return false;
}
#if !__SPU
fwMove *CPed::CreateMoveObject()
{
return rage_new CMovePedPooledObject(*this);
}
const fwMvNetworkDefId &CPed::GetAnimNetworkMoveInfo() const
{
return CClipNetworkMoveInfo::ms_NetworkPed;
}
void CPed::SetActivePoseFromSkel()
{
GetRagdollInst()->SetActivePoseFromSkel(GetSkeleton());
}
float CPed::GetUpdateFrequencyThreshold() const
{
u32 lod = GetPedDrawHandler().GetVarData().GetLODFadeValue();
static const float highLodDelta = 0.03f; // 30Hz (well, slightly over 30Hz deliberately)
static const float medLodDelta = 0.066f; // 15Hz
static const float lowLodDelta = 0.1f; // 10Hz
if(IsVisible())
{
if(lod <= 128)
{
return Lerp(Clamp(float(lod)*(1.f/128.f), 0.f, 1.f), lowLodDelta, medLodDelta);
}
else
{
return Lerp(Clamp(float(lod-128)*(1.f/127.f), 0.f, 1.f), medLodDelta, highLodDelta);
}
}
return lowLodDelta;
}
u8 CPed::GetMotionTreePriority(fwAnimDirectorComponent::ePhase FPS_MODE_SUPPORTED_ONLY(phase)) const
{
// 5/13/13 - cthomas - change this from IsPlayer() to IsLocalPlayer(). With IsPlayer(), all MP
// player peds (local and remote) have the same high priority, which would cause clashes with
// the priority of their recycle jobs, thus causing the main thread to starve and go idle waiting
// for animations to finish.
#if FPS_MODE_SUPPORTED
if (IsLocalPlayer())
{
return (phase == fwAnimDirectorComponent::kPhasePreRender && IsFirstPersonShooterModeEnabledForPlayer(false)) ? 3 : 2;
}
return 1;
#else
return IsLocalPlayer()?2:1;
#endif // FPS_MODE_SUPPORTED
}
bool CPed::InitIkRequest(crmtRequest &request, crmtRequestIk &ikRequest)
{
ikRequest.Init(request, GetIkManager().GetKinematics());
return true;
}
bool CPed::InitExpressions(crExpressionsDictionary& exprDict)
{
// attempt to add face rig translator expression - this MUST precede any per-component face expressions
const crExpressions* exprFaceInit = exprDict.Lookup("faceinit");
if(exprFaceInit)
{
GetAnimDirector()->AddExpression(*exprFaceInit, true);
}
// attempt to add any per-component expressions
int n=0;
for(int i=0; i<PV_MAX_COMP; ++i)
{
ePedVarComp component = varSlotEnums[i];
for (s32 f = 0; f < 2; ++f)
{
if(const crExpressions* expr = CPedVariationPack::GetExpressionForComponent(this,component,exprDict,f))
{
bool mid = false;
switch(component)
{
case PV_COMP_HEAD:
case PV_COMP_TEEF:
case PV_COMP_BERD:
mid = true;
break;
default:
break;
}
GetAnimDirector()->AddExpression(*expr, mid);
n++;
}
}
}
return n>0;
}
#endif // !__SPU
//
// Network object creation
// Does not use the 'standard' macros since we need to determine whether to create a player or ped network object
netObject* CPed::CreateNetworkObject( const ObjectId objectID,
const PhysicalPlayerIndex playerIndex,
const NetObjFlags localFlags,
const NetObjFlags globalFlags,
const unsigned numPlayers)
{
Assert(m_networkObject.m_NetObject == 0);
if (IsAPlayerPed())
m_networkObject.m_NetObject = rage_new CNetObjPlayer(this, NET_OBJ_TYPE_PLAYER, objectID, playerIndex, localFlags, globalFlags);
else
m_networkObject.m_NetObject = rage_new CNetObjPed(this, NET_OBJ_TYPE_PED, objectID, playerIndex, localFlags, globalFlags);
m_networkObject.m_NetObject->Init(numPlayers);
return m_networkObject.m_NetObject;
}
netObject* CPed::StaticCreateNetworkObject( const ObjectId objectID,
const PhysicalPlayerIndex playerIndex,
const NetObjFlags globalFlags,
const unsigned numPlayers)
{
netObject* pNetObj = (netObject*) rage_new CNetObjPed(NULL, NET_OBJ_TYPE_PED, objectID, playerIndex, 0, globalFlags);
pNetObj->Init(numPlayers);
return pNetObj;
}
netObject* CPed::StaticCreatePlayerNetworkObject( const ObjectId objectID,
const PhysicalPlayerIndex playerIndex,
const NetObjFlags globalFlags,
const unsigned numPlayers)
{
netObject* pNetObj = (netObject*) rage_new CNetObjPlayer(NULL, NET_OBJ_TYPE_PLAYER, objectID, playerIndex, 0, globalFlags);
pNetObj->Init(numPlayers);
return pNetObj;
}
unsigned CPed::GetNetworkObjectType()
{
if (IsAPlayerPed())
return NET_OBJ_TYPE_PLAYER;
else
return NET_OBJ_TYPE_PED;
}
#if !__FINAL
void CPed::AddDamageRecord(const sDamageRecord& dr)
{
if(dr.fHealthLost <= 0.0f && dr.fArmourLost <= 0.0f && dr.fEnduranceLost < 0.0f && !dr.bInvincible)
{
// Don't record damage when the peds dead
return;
}
if(m_damageRecords.IsFull())
{
m_damageRecords.Delete(0);
}
m_damageRecords.Push(dr);
if(m_damageRecords.Top().pInflictor)
{
m_damageRecords.Top().fDistance = Dist(GetTransform().GetPosition(), m_damageRecords.Top().pInflictor->GetTransform().GetPosition()).Getf();
}
}
#endif // !__FINAL
//////////////////////////////////////////////////////////////////////////
// CPedVfx
//////////////////////////////////////////////////////////////////////////
#define CAM_OUT_OF_WATER_LENS_EFFECT_TIME (1.0f)
CPedVfx::CPedVfx()
: m_breathTimer(0.0f)
, m_breathRate(0.0f)
, m_isWadeSpineIntersecting(false)
, m_pPed(NULL)
, m_pOutOfWaterBoneTimers(NULL)
, m_numOutOfWaterBoneTimers(0)
, m_camOutOfWaterTimer(CAM_OUT_OF_WATER_LENS_EFFECT_TIME)
{
m_footLiquidAttachInfo[0].Init();
m_footLiquidAttachInfo[1].Init();
};
CPedVfx::~CPedVfx()
{
if (m_pOutOfWaterBoneTimers)
{
delete[] m_pOutOfWaterBoneTimers;
m_pOutOfWaterBoneTimers = NULL;
m_numOutOfWaterBoneTimers = 0;
}
}
void CPedVfx::Init(CPed* pPed)
{
m_pPed = pPed;
SetBreathRate(VFXPED_BREATH_RATE_BASE);
m_isWadeSpineIntersecting = false;
m_footLiquidAttachInfo[0].Init();
m_footLiquidAttachInfo[1].Init();
for (int i=0; i<PEDVFX_NUM_PREV_DOWNWARD_SPEEDS; i++)
{
m_prevDownwardSpeed[i] = 0;
}
m_camOutOfWaterTimer = CAM_OUT_OF_WATER_LENS_EFFECT_TIME;
// check if this is the player
if (pPed->IsPlayer())
{
// find out how many water drip infos this ped has
int numWaterDripPtFxInfos = 0;
CVfxPedInfo* pVfxPedInfo = g_vfxPedInfoMgr.GetInfo(pPed);
if (pVfxPedInfo)
{
numWaterDripPtFxInfos = pVfxPedInfo->GetWaterDripPtFxNumInfos();
}
// create the out of water bone timers if we need to
if (m_pOutOfWaterBoneTimers==NULL)
{
Assertf(m_numOutOfWaterBoneTimers==0, "ped has no out of water bone timers but thinks it has");
if (numWaterDripPtFxInfos>0)
{
m_numOutOfWaterBoneTimers = numWaterDripPtFxInfos;
m_pOutOfWaterBoneTimers = rage_new float[m_numOutOfWaterBoneTimers];
}
}
// reset the out of water bone timers
if (m_pOutOfWaterBoneTimers)
{
Assertf(numWaterDripPtFxInfos==m_numOutOfWaterBoneTimers, "mismatch between the number of ped water drip infos and out of water bone timers");
for (int i=0; i<m_numOutOfWaterBoneTimers; i++)
{
m_pOutOfWaterBoneTimers[i] = 0.0f;
}
}
}
else
{
// delete any out of water bone timers that a
// a ped that was the player may no longer be
if (m_pOutOfWaterBoneTimers)
{
delete[] m_pOutOfWaterBoneTimers;
m_pOutOfWaterBoneTimers = NULL;
m_numOutOfWaterBoneTimers = 0;
}
}
}
void CPedVfx::ResetOutOfWaterBoneTimers()
{
if (m_pOutOfWaterBoneTimers)
{
for (int i=0; i<m_numOutOfWaterBoneTimers; i++)
{
m_pOutOfWaterBoneTimers[i] = 0.0f;
}
}
}
void CPedVfx::SetOutOfWaterBoneTimer(int vfxSkeletonBoneIdx, float timer)
{
// get the vfx ped info
CVfxPedInfo* pVfxPedInfo = g_vfxPedInfoMgr.GetInfo(m_pPed);
if (pVfxPedInfo && m_pOutOfWaterBoneTimers)
{
int waterDripPtFxId = pVfxPedInfo->GetWaterDripPtFxFromSkeletonBoneId(vfxSkeletonBoneIdx);
if (waterDripPtFxId>=0)
{
m_pOutOfWaterBoneTimers[waterDripPtFxId] = timer;
}
}
}
void CPedVfx::ProcessVfxPed()
{
#if __BANK
g_vfxPed.RenderDebug();
if (gPedWetnessLevelOverrideEnabled)
{
m_pPed->SetWetClothingHeight(1.0f);
m_pPed->SetWetClothingLevel(gPedWetnessLevelOverrideAmount);
}
#endif
if (fwTimer::IsGamePaused())
{
return;
}
// get the vfx ped info
CVfxPedInfo* pVfxPedInfo = g_vfxPedInfoMgr.GetInfo(m_pPed);
if (pVfxPedInfo==NULL)
{
return;
}
phMaterialMgr::Id pedFootMtlId = CVfxHelper::GetPedFootMtlId(m_pPed);
// check if footstep detection is required
float pedFootVfxLodRangeScaleSqr = g_vfxPed.GetFootVfxLodRangeScale();
pedFootVfxLodRangeScaleSqr *= pedFootVfxLodRangeScaleSqr;
if (CVfxHelper::GetDistSqrToCamera(m_pPed->GetTransform().GetPosition())<=pVfxPedInfo->GetFootVfxRangeSqr()*pedFootVfxLodRangeScaleSqr)
{
m_pPed->GetFootstepHelper().SetVfxWantsToUpdate(true);
}
else
{
m_pPed->GetFootstepHelper().SetVfxWantsToUpdate(false);
}
// deep surface wade vfx
if (m_pPed->GetDeepSurfaceInfo().IsActive())
{
g_vfxPed.ProcessVfxPedSurfaceWade(m_pPed, PGTAMATERIALMGR->GetMtlVfxGroup(pedFootMtlId));
}
// breath effect
if (!m_pPed->IsDead())
{
// update the breath rate
if (m_pPed->GetMotionData()->GetIsSprinting())
{
if (m_breathRate>VFXPED_BREATH_RATE_SPRINT)
{
m_breathRate -= fwTimer::GetTimeStep()*VFXPED_BREATH_RATE_SPEED_SPRINT;
}
}
else if (m_pPed->GetMotionData()->GetIsRunning())
{
if (m_breathRate>VFXPED_BREATH_RATE_RUN)
{
m_breathRate -= fwTimer::GetTimeStep()*VFXPED_BREATH_RATE_SPEED_RUN;
}
else if (m_breathRate<VFXPED_BREATH_RATE_RUN)
{
m_breathRate += fwTimer::GetTimeStep()*VFXPED_BREATH_RATE_SPEED_SLOW_DOWN;
}
}
else
{
if (m_breathRate<VFXPED_BREATH_RATE_BASE)
{
m_breathRate += fwTimer::GetTimeStep()*VFXPED_BREATH_RATE_SPEED_SLOW_DOWN;
}
}
//local player breath effects are controlled by audio.
if( CNetwork::IsGameInProgress() || (m_pPed->GetSpeechAudioEntity() && !m_pPed->GetSpeechAudioEntity()->AudioHandlingBreathingVfx()))
{
// update the breath timer
m_breathTimer += fwTimer::GetTimeStep();
if (m_breathTimer>m_breathRate)
{
m_pPed->m_PedAudioEntity.TriggerBreathSound(m_breathRate);
bool isCutscene = CutSceneManager::GetInstance()->IsCutscenePlayingBack();
bool isScriptedCutscene = false;
CPlayerInfo *pPlayerInfo = m_pPed->GetPlayerInfo();
if (pPlayerInfo && pPlayerInfo->IsControlsScriptDisabled())
{
isScriptedCutscene = true;
}
bool isFirstPersonCameraActive = false;
#if FPS_MODE_SUPPORTED
const camBaseCamera* pCamera = camInterface::GetDominantRenderedCamera();
isFirstPersonCameraActive = pCamera && pCamera->GetIsClassId(camFirstPersonShooterCamera::GetStaticClassId()) && m_pPed->IsLocalPlayer();
#endif
// trigger a breath effect
if (m_pPed->IsAPlayerPed() &&
!m_pPed->GetCurrentMotionTask()->IsInWater() &&
g_weather.GetTemperature(m_pPed->GetTransform().GetPosition())<pVfxPedInfo->GetBreathPtFxTempEvoMax() &&
!m_pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) &&
!camInterface::GetGameplayDirector().IsFirstPersonAiming(m_pPed) &&
!isFirstPersonCameraActive &&
!isScriptedCutscene &&
!isCutscene)
{
int boneIndex = m_pPed->GetBoneIndexFromBoneTag(BONETAG_HEAD);
if (boneIndex!=-1)
{
g_vfxPed.TriggerPtFxPedBreath(m_pPed, boneIndex);
}
}
m_breathTimer = 0.0f;
}
//You should blow bubbles in all vehicle types that are submerged, except subs (unless they are wrecked i.e. cracked window(s))
bool canDrown = m_pPed->GetPedIntelligence()->GetEventScanner()->GetInWaterEventScanner().CanDrown(*m_pPed);
// when swimming we want emit bubbles over half of the breath rate
if (m_pPed->GetCurrentMotionTask()->IsInWater() || canDrown)
{
if (m_breathTimer<m_breathRate*0.5f)
{
int boneIndex = m_pPed->GetBoneIndexFromBoneTag(BONETAG_HEAD);
if (boneIndex!=BONETAG_INVALID)
{
g_vfxPed.UpdatePtFxPedBreathWater(m_pPed, boneIndex);
}
}
}
}
}
// camera out of water timer
if (m_pPed->IsLocalPlayer())
{
if (Water::IsCameraUnderwater())
{
m_camOutOfWaterTimer = 0.0f;
}
else
{
m_camOutOfWaterTimer += fwTimer::GetTimeStep();
}
}
if (m_camOutOfWaterTimer>0.0f && m_camOutOfWaterTimer<CAM_OUT_OF_WATER_LENS_EFFECT_TIME && m_pPed->GetIsSwimming())
{
g_vfxLens.Register(VFXLENSTYPE_RUNNING_WATER, 1.0f, 0.0f, 0.0f, 1.0f);
}
// water drip effects
if (m_pPed->GetIsVisible() && m_pPed->GetIsSwimming()==false && m_pPed->GetIsDeadOrDying()==false)
{
// check if this ped has out of water bone timers enabled
if (m_pOutOfWaterBoneTimers)
{
for (int i=0; i<pVfxPedInfo->GetWaterDripPtFxNumInfos(); i++)
{
if (m_pOutOfWaterBoneTimers[i]>0.0f)
{
g_vfxPed.UpdatePtFxPedWetness(m_pPed, pVfxPedInfo->GetWaterDripPtFxToSkeletonBoneId(i), m_pOutOfWaterBoneTimers[i]);
m_pOutOfWaterBoneTimers[i] = Max(0.0f, m_pOutOfWaterBoneTimers[i]-fwTimer::GetTimeStep());
}
}
}
}
// special ability VFX (update selected only)
ePlayerSpecialAbilitySlot eSelectedSlot = m_pPed->GetSelectedAbilitySlot();
CPlayerSpecialAbility* pSpecialAbility = m_pPed->GetSpecialAbility(eSelectedSlot);
if (pSpecialAbility)
{
pSpecialAbility->ProcessVfx();
}
// bleeding peds
if (m_pPed->IsPedBleeding())
{
g_vfxBlood.UpdatePtFxBloodDrips(m_pPed);
}
// previous velocity
for (int i=PEDVFX_NUM_PREV_DOWNWARD_SPEEDS-1; i>0; i--)
{
m_prevDownwardSpeed[i] = m_prevDownwardSpeed[i-1];
}
SetPrevDownwardSpeed(0, -m_pPed->GetVelocity().z);
const CPedIntelligence* pPedIntelligence = m_pPed->GetPedIntelligence();
if(pPedIntelligence && pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_PARACHUTE, true))
{
if (m_pPed->GetIsAttached())
{
fwAttachmentEntityExtension* pAttachEntity = m_pPed->GetAttachmentExtension();
fwEntity* pAttachParent = pAttachEntity->GetAttachParent();
if (pAttachParent)
{
CEntity* pEntity = static_cast<CEntity*>(pAttachParent);
if (pEntity && pEntity->GetIsPhysical())
{
CPhysical* pPhysical = static_cast<CPhysical*>(pEntity);
SetPrevDownwardSpeed(0, -pPhysical->GetVelocity().z);
}
}
}
}
}
void CPedVfx::ProcessVfxFootStep(bool isLeft, bool isHind, Mat34V_In vFootMtx, const u32 foot, Vec3V_In vGroundNormal)
{
// get the vfx ped info
CVfxPedInfo* pVfxPedInfo = g_vfxPedInfoMgr.GetInfo(m_pPed);
if (pVfxPedInfo==NULL)
{
return;
}
// return if foot vfx aren't enabled
if (pVfxPedInfo->GetFootPtFxEnabled()==false && pVfxPedInfo->GetFootDecalsEnabled()==false)
{
return;
}
float pedFootVfxLodRangeScaleSqr = g_vfxPed.GetFootVfxLodRangeScale();
pedFootVfxLodRangeScaleSqr *= pedFootVfxLodRangeScaleSqr;
if (CVfxHelper::GetDistSqrToCamera(m_pPed->GetTransform().GetPosition())>pVfxPedInfo->GetFootVfxRangeSqr()*pedFootVfxLodRangeScaleSqr)
{
return;
}
phMaterialMgr::Id pedFootMtlId = CVfxHelper::GetPedFootMtlId(m_pPed);
// reject any glass collisions instantly - they're bad news!
if (PGTAMATERIALMGR->GetIsGlass(pedFootMtlId))
{
return;
}
// store the collision vfx group
VfxGroup_e colnVfxGroup = PGTAMATERIALMGR->GetMtlVfxGroup(pedFootMtlId);
// check if snow is active
if (g_vfxPed.GetUseSnowFootVfxWhenUnsheltered() && !m_pPed->GetIsShelteredFromRain())
{
colnVfxGroup = VFXGROUP_SNOW_COMPACT;
}
// check if we're in a puddle
if (m_pPed->GetFootstepHelper().IsWalkingOnPuddle())
{
colnVfxGroup = VFXGROUP_PUDDLE;
}
bool isInPuddle = colnVfxGroup==VFXGROUP_PUDDLE;
// check for being in any 'liquid' materials
VfxLiquidType_e liquidType = VFXLIQUID_TYPE_NONE;
if (colnVfxGroup==VFXGROUP_MUD_DEEP)
{
liquidType = VFXLIQUID_TYPE_MUD;
SetFootWetnessInfo(isLeft, liquidType);
}
else if (colnVfxGroup==VFXGROUP_LIQUID_WATER || isInPuddle)
{
liquidType = VFXLIQUID_TYPE_WATER;
SetFootWetnessInfo(isLeft, liquidType);
}
else
{
// check the 'liquid' decals
u32 foundDecalTypeFlag = 0;
s8 decalLiquidType = -1;
if (g_decalMan.IsPointInLiquid(1<<DECALTYPE_POOL_LIQUID | 1<<DECALTYPE_TRAIL_LIQUID, decalLiquidType, vFootMtx.GetCol3(), 0.2f, false, false, DECAL_FADE_OUT_TIME, foundDecalTypeFlag))
{
liquidType = (VfxLiquidType_e)decalLiquidType;
SetFootWetnessInfo(isLeft, liquidType);
}
}
// default the decal and ptfx vfx groups
VfxGroup_e decalVfxGroup = colnVfxGroup;
VfxGroup_e ptfxVfxGroup = colnVfxGroup;
// default the decal info based on the default decal vfx group
const VfxPedFootInfo* pVfxPedFootInfo_DecalVfxGroup = pVfxPedInfo->GetFootVfxInfo(decalVfxGroup);
const VfxPedFootDecalInfo* pVfxPedFootDecalInfo = pVfxPedFootInfo_DecalVfxGroup->GetDecalInfo();
float decalLife = pVfxPedFootDecalInfo->m_decalLife;
float decalAlpha = 1.0f;
// deal with being in a liquid
if (liquidType!=VFXLIQUID_TYPE_NONE)
{
// override the ptfx vfx group with the liquid vfx group
// unless we're in a puddle - in this case we just want to use the puddle vfx group settings instead
if (!isInPuddle)
{
VfxLiquidInfo_s& liquidInfo = g_vfxLiquid.GetLiquidInfo(liquidType);
ptfxVfxGroup = liquidInfo.vfxGroup;
}
}
// otherwise deal with being wet from a liquid
else if (m_footLiquidAttachInfo[isLeft].GetTimer()>0.0f)
{
// on a non-porous material we want to override the decal vfx group with the liquid vfx group
if (PGTAMATERIALMGR->GetMtlFlagIsPorous(pedFootMtlId)==false)
{
VfxLiquidInfo_s& liquidInfo = g_vfxLiquid.GetLiquidInfo(m_footLiquidAttachInfo[isLeft].GetLiquidType());
decalLife = liquidInfo.decalLifeFoot;
decalAlpha = m_footLiquidAttachInfo[isLeft].GetCurrAlpha();
decalVfxGroup = liquidInfo.vfxGroup;
pVfxPedFootInfo_DecalVfxGroup = pVfxPedInfo->GetFootVfxInfo(decalVfxGroup);
pVfxPedFootDecalInfo = pVfxPedFootInfo_DecalVfxGroup->GetDecalInfo();
}
}
Vec3V vHeelPos = vFootMtx.GetCol3();
Vec3V vFootForward = -vFootMtx.GetCol1();
// calculate the footstep matrix using the heel position and the ground normal (and aligning to the forward vector)
Mat34V vFootStepMtx;
CVfxHelper::CreateMatFromVecZAlign(vFootStepMtx, vHeelPos, vGroundNormal, vFootForward);
//grcDebugDraw::Axis(vFootStepMtx, 0.5f, false, -200);
// process footstep decals and particles
g_vfxPed.ProcessVfxPedFootStep(m_pPed, pedFootMtlId, decalVfxGroup, ptfxVfxGroup, vFootStepMtx, foot, decalLife, decalAlpha, isLeft, isHind);
}
void CPedVfx::UpdateFootWetness()
{
if (m_pPed->IsDead())
{
return;
}
// update right and left wet footstep
m_footLiquidAttachInfo[0].Update(fwTimer::GetTimeStep());
m_footLiquidAttachInfo[1].Update(fwTimer::GetTimeStep());
}
bool CPedVfx::GetBloodVfxDisabled() const
{
// no blood in certain language build
if (CLocalisation::Blood()==false)
{
return true;
}
// check if the marketing tools allow blood
return (CMarketingTools::ShouldDisableBloodForPed(*m_pPed));
}
bool CPedVfx::ProcessCollisionVfx(VfxCollisionInfo_s& vfxColnInfo)
{
if (GetBloodVfxDisabled())
{
return false;
}
// ignore collisions between different parts of the same ped
if (m_pPed==vfxColnInfo.pEntityB)
{
return false;
}
// check for ped footsteps (if the ped is using NM then they will come through this route)
if (vfxColnInfo.mtlIdA == PGTAMATERIALMGR->g_idFootLeft || vfxColnInfo.mtlIdA == PGTAMATERIALMGR->g_idFootRight)
{
m_pPed->ProcessNMFootCollision(vfxColnInfo.mtlIdA==PGTAMATERIALMGR->g_idFootLeft, vfxColnInfo.mtlIdB, 0.0f);
return false;
}
return true;
}
void CPedVfx::SetPrevDownwardSpeed(int idx, float actualSpeed)
{
if (Verifyf(idx>=0 && idx<PEDVFX_NUM_PREV_DOWNWARD_SPEEDS, "out of range index passed to CPedVfx::SetPrevDownwardSpeed"))
{
float clampedSpeed = Clamp(actualSpeed, 0.0f, 50.0f);
u8 storedSpeed = (u8)(clampedSpeed*5.0f);
m_prevDownwardSpeed[idx] = storedSpeed;
}
}
float CPedVfx::GetPrevDownwardSpeed(int idx)
{
if (Verifyf(idx>=0 && idx<PEDVFX_NUM_PREV_DOWNWARD_SPEEDS, "out of range index passed to CPedVfx::GetPrevDownwardSpeed"))
{
u8 storedSpeed = m_prevDownwardSpeed[idx];
return (float)storedSpeed*0.2f;
}
return 0.0f;
}
void CPed::ClearPedsThatCanGetWetThisFrame()
{
ms_pedsThatCanGetWetThisFrame.Reset();
}
void CPed::AddPedThatCanGetWetThisFrame(CPed* pPed)
{
for (int i=0; i<ms_pedsThatCanGetWetThisFrame.GetCount(); i++)
{
if (pPed == ms_pedsThatCanGetWetThisFrame[i])
{
return;
}
}
if (pedVerifyf(ms_pedsThatCanGetWetThisFrame.IsFull()==false, "Trying to add too many peds that can get wet this frame"))
{
ms_pedsThatCanGetWetThisFrame.Push(pPed);
}
}
s32 CPed::GetNumPedsThatCanGetWetThisFrame()
{
return ms_pedsThatCanGetWetThisFrame.GetCount();
}
CPed* CPed::GetPedThatCanGetWetThisFrame(s32 idx)
{
return ms_pedsThatCanGetWetThisFrame[idx];
}
void CPed::SetPlayerInfo(CPlayerInfo* pInfo)
{
#if __BANK
if(m_pPlayerInfo != pInfo)
{
gnetDebug2("CPed::SetPlayerInfo - Changing player info (%p) for ped %s", pInfo, GetDebugName());
if(pInfo)
{
// ensure no other peds are using this player info
CPed::Pool *pedPool = CPed::GetPool();
for(int index = 0; index < pedPool->GetSize(); index++)
{
CPed *pPed = pedPool->GetSlot(index);
if(pPed && (pPed != this))
{
if(pPed->GetPlayerInfo() == pInfo)
{
sysStack::PrintStackTrace();
gnetAssertf(0, "Assigning a player info to %s that is already used by %s! This is bad and will probably crash!", GetDebugName(), pPed->GetDebugName());
gnetError("Assigning a player info to %s that is already used by %s! This is bad and will probably crash!", GetDebugName(), pPed->GetDebugName());
}
}
}
}
}
#endif
if (pInfo)
{
// B*1649000
SetPedConfigFlag(CPED_CONFIG_FLAG_PreventUsingLowerPrioritySeats, false);
// B*2983081
// We also set the clanId in the ped to be used if the player leaves the session
NetworkClan& clanMgr = CLiveManager::GetNetworkClan();
rlGamerHandle gamerHandle = pInfo->m_GamerInfo.GetGamerHandle();
const rlClanDesc* clan = clanMgr.GetPrimaryClan(gamerHandle);
if (clan && clan->m_Id)
{
m_SavedClanId = clan->m_Id;
gnetDebug1("Setting saved ClanId for Ped ped=0x%p to %d", (void*)this, (s32)m_SavedClanId);
}
else
{
m_SavedClanId = 0;
CNetGamePlayer *pPlayer = NetworkInterface::GetPlayerFromGamerHandle(gamerHandle);
if (pPlayer)
{
m_SavedClanId = pPlayer->GetCrewId();
gnetDebug1("Setting saved ClanId for Ped ped=0x%p to %d", (void*)this, (s32)m_SavedClanId);
}
}
}
m_pPlayerInfo = pInfo;
BANK_ONLY(InitDebugName());
}
#if __DEV
void CPed::PoolFullCallback(void* pItem)
{
if (!pItem)
{
Printf("ERROR - Ped Pool FULL!\n");
}
else
{
CPed* pPed = static_cast<CPed*>(pItem);
const char *pDebugName = pPed->GetDebugName();
const char *pName = (pDebugName && pDebugName[0] != '\0') ? pDebugName : pPed->GetModelName();
Displayf("Model Name - %s, Model Index - %u, Memory Address %p", pName, pPed->GetModelIndex(), pPed);
}
}
#endif
bool CPed::IsBeingForcedToRun() const
{
// Force those crazy lefties to run if in southpaw mode.
if(IsLocalPlayer() && GetControlFromPlayer())
{
switch(GetControlFromPlayer()->GetActiveLayout())
{
case(SOUTHPAW_TPS_LAYOUT):
case(SOUTHPAW_TRIGGER_SWAP_TPS_LAYOUT):
//case(SOUTHPAW_FPS_LAYOUT):
//case(SOUTHPAW_TRIGGER_SWAP_FPS_LAYOUT):
//case(SOUTHPAW_FPS_ALTERNATE_LAYOUT):
//case(SOUTHPAW_TRIGGER_SWAP_FPS_ALTERNATE_LAYOUT):
return CControlMgr::GetMainPlayerControl().GetValue(INPUT_SPRINT).IsEnabled();
default:
break;
}
}
// No forced run with heavy guns, e.g., minigun.
if(GetWeaponManager())
{
const CWeapon* pWeapon = GetWeaponManager()->GetEquippedWeapon();
if(pWeapon && pWeapon->GetWeaponInfo()->GetIsHeavy())
{
return false;
}
}
if(IsUsingActionMode() &&
!m_MovementModeStreamAnimsThenExit &&
CPed::IsActionModeJogTestEnabled() &&
(!GetIsInInterior() || GetPortalTracker()->IsAllowedToRunInInterior() || GetPedConfigFlag(CPED_CONFIG_FLAG_IgnoreInteriorCheckForSprinting)))
{
//! If we are trying to delay run, then only run if battle awareness is forcing it.
if(fwTimer::GetTimeInMilliseconds() < m_uForcedRunDelayTime)
{
return GetPedIntelligence()->IsBattleAware() && GetPedIntelligence()->IsBattleAwareForcingRun();
}
//! Otherwise, run if we are being told to.
else if(fwTimer::GetTimeInMilliseconds() < m_nActionModeForcingRunExpiryTime)
{
return true;
}
}
return false;
}
void CPed::SetForcedRunDelay(u32 uDelayMS)
{
u32 nDelayTime = fwTimer::GetTimeInMilliseconds() + uDelayMS;
if(nDelayTime > m_uForcedRunDelayTime)
{
m_uForcedRunDelayTime = nDelayTime;
}
}
bool CPed::IsActionModeJogTestEnabled()
{
return true;
}
bool CPed::CanStraddlePortals() const
{
return (this == CGameWorld::FindLocalPlayer());
}
void CPed::SetEnableCrewEmblem(bool enable)
{
m_bEnableCrewEmblem = enable;
}
bool CPed::GetEnableCrewEmblem() const
{
return m_bEnableCrewEmblem;
}
void CPed::SetUsingActionMode(const bool b, const ActionModeEnabled reason, const float fTime, bool bForcingRun, bool bStreamAnimsThenExit )
{
#if !__NO_OUTPUT
if(IsActionModeReasonActive(reason) != b)
pedDebugf1("CPed::SetUsingActionMode: Ped: 0x%p [%s], UsingActionMode: %d, Reason: %d, ActionModeTime: %.2f", this, GetModelName(), b, reason, fTime);
#endif // !__NO_OUTPUT
bool bWasActionModeApplied = IsUsingActionMode();
if(b)
{
if(fTime < 0.0f)
{
m_nActionModeTimes[reason] = 0xFFFFFFFF; //infinite.
}
else
{
m_nActionModeTimes[reason] = fwTimer::GetTimeInMilliseconds() + (u32)(fTime * 1000.0f);
}
m_MovementModeStreamAnimsThenExit = false;
m_uActionModeForcingRun.ChangeFlag(1<<reason, bForcingRun);
}
else
{
m_nActionModeTimes[reason] = 0;
m_uActionModeForcingRun.ClearFlag(1<<reason);
}
if(!m_pMovementModes)
{
CachePersonalityMovementMode();
}
//! Get latest expiry time.
m_nActionModeExpiryTime = m_nActionModeForcingRunExpiryTime = m_nActionModeTimes[0];
for(s32 i = 1; i < AME_Max; i++)
{
if(m_nActionModeTimes[i] > m_nActionModeExpiryTime)
{
m_nActionModeExpiryTime = m_nActionModeTimes[i];
}
if(m_uActionModeForcingRun.IsFlagSet(1<<i) && m_nActionModeTimes[i] > m_nActionModeForcingRunExpiryTime)
{
m_nActionModeForcingRunExpiryTime = m_nActionModeTimes[i];
}
}
//! If we have turned action mode off, and have switched weapons, then we need to stream in new weapons transition anims.
if(bWasActionModeApplied && (fwTimer::GetTimeInMilliseconds() > m_nActionModeExpiryTime))
{
u32 uCurrentWeaponHash = GetMovementModeWeaponHash();
if(uCurrentWeaponHash != m_uMovementModeWeaponHash || bStreamAnimsThenExit)
{
m_MovementModeStreamAnimsThenExit = true;
//! Force us to recalc action mode.
m_uMovementModeWeaponHash = 0;
m_iMovementModeType = CPedModelInfo::PersonalityMovementModes::MM_Invalid;
UpdateMovementMode();
}
}
if(m_pSpeechAudioEntity && (reason != CPed::AME_Wanted || !b))
m_pSpeechAudioEntity->SetCodeSetIsAngry(b);
}
bool CPed::IsActionModeReasonActive(const ActionModeEnabled reason) const
{
return fwTimer::GetTimeInMilliseconds() <= m_nActionModeTimes[reason];
}
#if __BANK
const char *CPed::GetActionModeReasonString(const CPed::ActionModeEnabled reason) const
{
switch(reason)
{
case(AME_Wanted):
return "AME_Wanted";
case(AME_Script):
return "AME_Script";
case(AME_Combat):
return "AME_Combat";
case(AME_RespondingToOrder):
return "AME_RespondingToOrder";
case(AME_Network):
return "AME_Network";
case(AME_Investigate):
return "AME_Investigate";
case(AME_Equipment):
return "AME_Equipment";
case(AME_Melee):
return "AME_Melee";
case(AME_MeleeTransition):
return "AME_MeleeTransition";
case(AME_VehicleEntry):
return "AME_VehicleEntry";
case(AME_CopSeenCrookCNC):
return "AME_CopSeenCrookCNC";
#if __BANK
case(AME_Debug):
return "AME_Debug";
#endif // __BANK
default:
pedAssert(0);
}
return "";
}
#endif
bool CPed::IsAnyActionModeReasonActive() const
{
return fwTimer::GetTimeInMilliseconds() <= m_nActionModeExpiryTime;
}
bool CPed::IsUsingStealthMode() const
{
return m_iMovementModeType == CPedModelInfo::PersonalityMovementModes::MM_Stealth && m_iMovementModeIndex != -1;
}
bool CPed::HasStreamedStealthModeClips() const
{
return IsUsingStealthMode() && m_bMovementModeApplied;
}
void CPed::SetMovementModeOverrideHash(u32 iHash)
{
if(IsMovementModeEnabled() && (iHash != m_iMovementModeOverrideHash))
{
#if !__FINAL
scrThread::PrePrintStackTrace();
Displayf("%s [Frame=%d] SetMovementModeOverrideHash. %s:%s", CTheScripts::GetCurrentScriptNameAndProgramCounter(), fwTimer::GetSystemFrameCount(), GetDebugName(), atNonFinalHashString(iHash).TryGetCStr());
#endif
m_iMovementModeOverrideHash = iHash;
CachePersonalityMovementMode();
}
}
bool CPed::IsMovementModeEnabled() const
{
return m_iMovementModeOverrideHash != s_nInvalidMovementModeHash;
}
bool CPed::HasValidMovementModeForWeapon(u32 uWeaponHash) const
{
if(m_pMovementModes)
{
CPedModelInfo::PersonalityMovementModes::MovementModes iMovementModeType = GetMovementModeType();
if(iMovementModeType != CPedModelInfo::PersonalityMovementModes::MM_Invalid)
{
s32 iIndex;
return m_pMovementModes->FindMovementMode(this, iMovementModeType, uWeaponHash, iIndex) != NULL;
}
}
return false;
}
const CPedModelInfo::PersonalityMovementModes::MovementMode::ClipSets& CPed::GetMovementModeDataForWeaponHash(u32 uWeaponHash) const
{
if(m_pMovementModes)
{
CPedModelInfo::PersonalityMovementModes::MovementModes iMovementModeType = GetMovementModeType();
if(iMovementModeType != CPedModelInfo::PersonalityMovementModes::MM_Invalid)
{
s32 iIndex;
const CPedModelInfo::PersonalityMovementModes::MovementMode* pMode = m_pMovementModes->FindMovementMode(this, iMovementModeType, uWeaponHash, iIndex);
if (pMode)
{
return pMode->GetRandomClipSets();
}
}
}
//If no data exists for the weapon (for example, swapping to Grenade Launcher whilst in stealth mode), return the current movement mode data (for weapon currently equipped)
return m_MovementModeClipSets;
}
void CPed::EnableMovementMode(bool bEnable)
{
if(bEnable)
{
m_iMovementModeOverrideHash = 0;
}
else
{
m_iMovementModeOverrideHash = s_nInvalidMovementModeHash;
}
CachePersonalityMovementMode();
}
bool CPed::IsUsingActionMode() const
{
return m_iMovementModeType == CPedModelInfo::PersonalityMovementModes::MM_Action && m_iMovementModeIndex != -1 && m_bMovementModeApplied &&
!GetPedResetFlag(CPED_RESET_FLAG_DisableActionMode);
}
bool CPed::WantsToUseActionMode(const bool FPS_MODE_SUPPORTED_ONLY(bCheckFPS)) const
{
if( ((fwTimer::GetTimeInMilliseconds() <= m_nActionModeExpiryTime) || m_MovementModeStreamAnimsThenExit || GetPedResetFlag(CPED_RESET_FLAG_ForceActionMode) || m_bActionModeIdleActive) &&
(!GetPedResetFlag(CPED_RESET_FLAG_DisableActionMode) || GetPedResetFlag(CPED_RESET_FLAG_StreamActionModeAnimsIfDisabled)) &&
!IsFatallyInjured() &&
FPS_MODE_SUPPORTED_ONLY( (!IsFirstPersonShooterModeEnabledForPlayer(false, false, true) || !bCheckFPS) && )
!GetMotionData()->GetUsingStealth())
{
return true;
}
return false;
}
bool CPed::IsStreamingActionModeClips() const
{
return m_iMovementModeType == CPedModelInfo::PersonalityMovementModes::MM_Action && m_iMovementModeIndex != -1 && !m_bMovementModeApplied;
}
const fwMvClipSetId CPed::GetMovementModeClipSet() const
{
return m_MovementModeClipSets.m_MovementClipSetId;
}
const fwMvClipSetId CPed::GetMovementModeIdleTransitionClipSet() const
{
return m_IdleTransitionClipSetID;
}
float CPed::GetActionModeTimer() const
{
if(m_nActionModeExpiryTime == 0xFFFFFFFF)
return -1.0f;
float fExpiryTime = (float)m_nActionModeExpiryTime * 0.001f;
float fCurrentTime = (float)fwTimer::GetTimeInMilliseconds() * 0.001f;
return Max(0.0f, fExpiryTime - fCurrentTime);
}
void CPed::CachePersonalityMovementMode()
{
const CPedModelInfo::PersonalityMovementModes* pMovementModes = GetPedModelInfo()->GetPersonalitySettings().GetMovementModes();
u32 iOverrideHash = m_iMovementModeOverrideHash;
//! Always set in MP. Note: This is tricky to set up in data as the ped types are shared across everyone.
if(iOverrideHash == 0 && NetworkInterface::IsGameInProgress() && IsPlayer() && !pMovementModes)
{
iOverrideHash = ATSTRINGHASH("DEFAULT_ACTION", 0x076f1bf8d);
}
if(iOverrideHash != 0)
{
pMovementModes = CPedModelInfo::FindPersonalityMovementModes(iOverrideHash);
}
m_pMovementModes = pMovementModes;
}
bool CPed::IsHighEnergyMovementMode() const
{
#if __DEV
TUNE_GROUP_BOOL(ACTION_MODE, FORCE_HIGH_ENERGY, false);
if(FORCE_HIGH_ENERGY)
{
return true;
}
#endif // __DEV
u32 nLastBattleEventTime = GetPedIntelligence()->GetLastBattleEventTime();
if(m_pMovementModes && (nLastBattleEventTime > 0))
{
if(fwTimer::GetTimeInMilliseconds() < (GetPedIntelligence()->GetLastBattleEventTime() + m_pMovementModes->GetLastBattleEventHighEnergyEndTimeMS()))
{
return true;
}
}
return false;
}
bool CPed::IsBattleEventBlockingActionModeIdle() const
{
u32 nLastBattleEventTime = GetPedIntelligence()->GetLastBattleEventTime();
if(m_pMovementModes && (nLastBattleEventTime > 0))
{
if(fwTimer::GetTimeInMilliseconds() < (GetPedIntelligence()->GetLastBattleEventTime() + m_pMovementModes->GetLastBattleEventHighEnergyStartTimeMS()))
{
return true;
}
}
return false;
}
void CPed::UpdateMovementMode(const bool bCheckFps)
{
PF_FUNC(UpdateMovementMode);
if(GetPedConfigFlag(CPED_CONFIG_FLAG_PedBeingDeleted))
{
return;
}
// Whether or not we want to clear the movement mode settings
bool bClearMovementMode = false;
if(m_pMovementModes)
{
CPedModelInfo::PersonalityMovementModes::MovementModes iMovementModeType = GetMovementModeType(bCheckFps);
if(iMovementModeType != CPedModelInfo::PersonalityMovementModes::MM_Invalid)
{
u32 uCurrentWeaponHash = GetMovementModeWeaponHash();
bool bMovementModeChanged = false;
if(m_iMovementModeType != iMovementModeType)
{
// Reset any active index
m_iMovementModeIndex = -1;
bMovementModeChanged = true;
}
bool bCheckCurrentMovementModeIsValid = uCurrentWeaponHash != m_uMovementModeWeaponHash || bMovementModeChanged;
if(bCheckCurrentMovementModeIsValid)
{
// If we have no movement mode selected, or our conditions are invalid, pick a new one
if(m_iMovementModeIndex == -1 || !m_pMovementModes->GetMovementMode(m_iMovementModeType, m_iMovementModeIndex).IsValid(this, uCurrentWeaponHash))
{
s32 iMovementModeIndex;
const CPedModelInfo::PersonalityMovementModes::MovementMode* pMode = m_pMovementModes->FindMovementMode(this, iMovementModeType, uCurrentWeaponHash, iMovementModeIndex);
if(pMode)
{
// Kill current movement mode if we are switching to a new one.
if((m_iMovementModeIndex != -1 && m_iMovementModeIndex != iMovementModeIndex) || bMovementModeChanged)
{
ResetMovementMode();
}
// Choose the clip set
const CPedModelInfo::PersonalityMovementModes::MovementMode::ClipSets& clipSets = pMode->GetRandomClipSets();
if(pedVerifyf(clipSets.IsValid(), "Movement clip not set for movement mode: %s", m_pMovementModes->GetName()))
{
m_iMovementModeIndex = iMovementModeIndex;
m_MovementModeClipSets = clipSets;
m_IdleTransitionClipSetID = m_MovementModeClipSets.GetIdleTransitionClipSet();
m_MovementModeMovementClipRequestHelper.Request(m_MovementModeClipSets.m_MovementClipSetId);
if(fwClipSetManager::GetClipSet(m_MovementModeClipSets.m_WeaponClipSetId))
{
m_MovementModeWeaponClipRequestHelper.Request(m_MovementModeClipSets.m_WeaponClipSetId);
}
else
{
pedAssertf(m_MovementModeClipSets.m_WeaponClipSetId == CLIP_SET_ID_INVALID, "Weapon Clipset ID not set for movement mode: %s", m_pMovementModes->GetName());
}
if(fwClipSetManager::GetClipSet(m_IdleTransitionClipSetID))
{
m_MovementModeIdleTransitionsClipRequestHelper.Request(m_IdleTransitionClipSetID);
}
else
{
pedAssertf(m_IdleTransitionClipSetID == CLIP_SET_ID_INVALID, "Idle Transition Clipset ID not set for movement mode: %s", m_pMovementModes->GetName());
}
m_MovementModeUnholsterClipRequestHelper.Request(m_MovementModeClipSets.m_UnholsterClipSetId);
}
}
else
{
// No valid mode.
bClearMovementMode = true;
// Disable stealth if the current weapon that doesn't support stealth anims.
if(GetMotionData()->GetUsingStealth())
{
GetMotionData()->SetUsingStealth(false);
iMovementModeType = m_iMovementModeType;
}
m_IdleTransitionClipSetID = CLIP_SET_ID_INVALID;
}
}
}
// Update the variables we have checked
m_uMovementModeWeaponHash = uCurrentWeaponHash;
m_iMovementModeType = iMovementModeType;
if((m_iMovementModeIndex != -1))
{
if(!m_bMovementModeApplied)
{
//! Action mode only. Otherwise, exiting stealth can trigger action mode if we bail before anims are streamed.
if(m_iMovementModeType == CPedModelInfo::PersonalityMovementModes::MM_Action)
{
m_MovementModeStreamAnimsThenExit = true;
}
// Ensure streamed in
bool bLoaded = m_MovementModeMovementClipRequestHelper.Request();
bLoaded &= m_MovementModeWeaponClipRequestHelper.IsInvalid() ? true : m_MovementModeWeaponClipRequestHelper.Request();
bLoaded &= m_MovementModeIdleTransitionsClipRequestHelper.IsInvalid() ? true : m_MovementModeIdleTransitionsClipRequestHelper.Request();
bLoaded &= m_MovementModeUnholsterClipRequestHelper.IsInvalid() ? true : m_MovementModeUnholsterClipRequestHelper.Request();
if(bLoaded)
{
// Apply settings
m_bMovementModeApplied = true;
m_MovementModeStreamAnimsThenExit = false;
}
}
}
else
{
m_MovementModeStreamAnimsThenExit = false;
}
if(m_iMovementModeType == CPedModelInfo::PersonalityMovementModes::MM_Action && IsLocalPlayer())
{
const CTaskAmbientClips* pAmbientTask = static_cast<CTaskAmbientClips*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AMBIENT_CLIPS));
if(pAmbientTask)
{
m_bActionModeIdleActive = !pAmbientTask->IsIdleFinished();
}
else
{
m_bActionModeIdleActive = false;
}
}
else
{
m_bActionModeIdleActive = false;
}
}
else
{
// Turned off
bClearMovementMode = true;
#if FPS_MODE_SUPPORTED
if(WantsToUseActionMode(false))
{
// Stream in the action mode anims when in first person
u32 uCurrentWeaponHash = GetMovementModeWeaponHash();
s32 iMovementModeIndex;
const CPedModelInfo::PersonalityMovementModes::MovementMode* pMode = m_pMovementModes->FindMovementMode(this, CPedModelInfo::PersonalityMovementModes::MM_Action, uCurrentWeaponHash, iMovementModeIndex);
if(pMode)
{
// Choose the clip set
const CPedModelInfo::PersonalityMovementModes::MovementMode::ClipSets& clipSets = pMode->GetRandomClipSets();
if(pedVerifyf(clipSets.IsValid(), "Movement clip not set for movement mode: %s", m_pMovementModes->GetName()))
{
m_MovementModeMovementClipRequestHelper.Request(clipSets.m_MovementClipSetId);
if(fwClipSetManager::GetClipSet(clipSets.m_WeaponClipSetId))
{
m_MovementModeWeaponClipRequestHelper.Request(clipSets.m_WeaponClipSetId);
}
else
{
pedAssertf(clipSets.m_WeaponClipSetId == CLIP_SET_ID_INVALID, "Weapon Clipset ID not set for movement mode: %s", m_pMovementModes->GetName());
}
if(fwClipSetManager::GetClipSet(clipSets.GetIdleTransitionClipSet()))
{
m_MovementModeIdleTransitionsClipRequestHelper.Request(clipSets.GetIdleTransitionClipSet());
}
else
{
pedAssertf(clipSets.GetIdleTransitionClipSet() == CLIP_SET_ID_INVALID, "Idle Transition Clipset ID not set for movement mode: %s", m_pMovementModes->GetName());
}
m_MovementModeUnholsterClipRequestHelper.Request(clipSets.m_UnholsterClipSetId);
}
}
}
#endif // FPS_MODE_SUPPORTED
}
}
else
{
bClearMovementMode = true;
}
if(bClearMovementMode && m_iMovementModeIndex != -1)
{
ResetMovementMode();
}
}
void CPed::ResetMovementMode()
{
CTaskMotionBase* pMotionTask = GetPrimaryMotionTask();
Assert(pMotionTask);
// Clear settings from motion task
if(pMotionTask->GetOverrideWeaponClipSet() == m_MovementModeClipSets.m_WeaponClipSetId)
{
pMotionTask->ClearOverrideWeaponClipSet();
}
m_iMovementModeType = CPedModelInfo::PersonalityMovementModes::MM_Invalid;
m_iMovementModeIndex = -1;
m_MovementModeClipSets.Clear();
m_bMovementModeApplied = false;
m_MovementModeMovementClipRequestHelper.Release();
m_MovementModeWeaponClipRequestHelper.Release();
m_MovementModeIdleTransitionsClipRequestHelper.Release();
m_MovementModeUnholsterClipRequestHelper.Release();
m_bActionModeIdleActive = false;
u32 uCurrentWeaponHash = GetMovementModeWeaponHash();
//! Note: We don't reset m_IdleTransitionClipSetID as we need this to play out transition animation from movement mode -> on foot.
//! However, we can't play transition if our weapon changes.
if(uCurrentWeaponHash != m_uMovementModeWeaponHash)
{
m_IdleTransitionClipSetID = CLIP_SET_ID_INVALID;
}
m_uMovementModeWeaponHash = 0;
// Force update conditional anim.
CTaskAmbientClips* pAmbientClips = static_cast<CTaskAmbientClips*>(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_AMBIENT_CLIPS));
if (pAmbientClips)
{
pAmbientClips->ForceReevaluateConditionalAnim();
}
}
CPedModelInfo::PersonalityMovementModes::MovementModes CPed::GetMovementModeType(const bool bCheckFps) const
{
CPedModelInfo::PersonalityMovementModes::MovementModes iMovementModeType;
if(GetMotionData()->GetUsingStealth())
{
iMovementModeType = CPedModelInfo::PersonalityMovementModes::MM_Stealth;
}
else if(WantsToUseActionMode(bCheckFps))
{
iMovementModeType = CPedModelInfo::PersonalityMovementModes::MM_Action;
}
else
{
iMovementModeType = CPedModelInfo::PersonalityMovementModes::MM_Invalid;
}
return iMovementModeType;
}
void CPed::ProcessActionModeLookAt()
{
if(!IsLocalPlayer())
return;
if(!WantsToUseActionMode())
return;
TUNE_GROUP_INT(ACTION_MODE, fHeadIkHoldTimeMs, 1250, 0, 10000, 1);
TUNE_GROUP_INT(ACTION_MODE, fHeadIkBlendInTime, 500, 0, 10000, 1);
TUNE_GROUP_INT(ACTION_MODE, fHeadIkBlendOutTime, 500, 0, 10000, 1);
TUNE_GROUP_INT(ACTION_MODE, fHeadIkTimeToRetest, 1000, 0, 10000, 1);
if(PARAM_enableActionModeHeadLookAt.Get())
{
CEntity *pEntity = GetPlayerInfo()->GetTargeting().GetTarget();
if(!pEntity)
{
//! This is expensive, so don't do it too often. May need to optimise this further
//! (i.e. don't do LOS tests or something).
if( (fwTimer::GetTimeInMilliseconds() - fHeadIkTimeToRetest) > m_iLastActionModeHeadIKTest )
{
m_iLastActionModeHeadIKTest = fwTimer::GetTimeInMilliseconds();
pEntity = GetPlayerInfo()->GetTargeting().FindLockOnTarget();
}
}
if(pEntity && pEntity->GetIsTypePed())
{
GetIkManager().LookAt(
0,
pEntity,
fHeadIkHoldTimeMs,
BONETAG_HEAD,
NULL,
false,
fHeadIkBlendInTime,
fHeadIkBlendOutTime,
CIkManager::IK_LOOKAT_LOW);
}
}
}
u32 CPed::GetMovementModeWeaponHash() const
{
u32 uCurrentWeaponHash = 0;
if(GetWeaponManager())
{
if(GetPedConfigFlag(CPED_CONFIG_FLAG_IsSwitchingWeapon))
{
// If swapping weapon, use the weapon we are swapping to
uCurrentWeaponHash = GetWeaponManager()->GetEquippedWeaponHash();
}
else
{
const CWeapon* pWeapon = GetWeaponManager()->GetEquippedWeapon();
uCurrentWeaponHash = pWeapon ? pWeapon->GetWeaponHash() : CWeaponManager::GetWeaponUnarmed()->GetWeaponHash();
}
}
if(IsPlayer())
{
const bool bForcingUnarmedForMobilePhone = (uCurrentWeaponHash == OBJECTTYPE_OBJECT && CTaskMobilePhone::IsRunningMobilePhoneTask(*this));
const bool bForcingUnarmedForEnterVehicle = GetPedResetFlag(CPED_RESET_FLAG_ForceUnarmedActionMode);
if (bForcingUnarmedForMobilePhone || bForcingUnarmedForEnterVehicle)
{
uCurrentWeaponHash = WEAPONTYPE_UNARMED;
}
}
return uCurrentWeaponHash;
}
void CPed::SetControlledByInfo(const CControlledByInfo& controlledBy)
{
// Is this ped turning into a player or vice versa?
CControlledByInfo ccontrol(m_isControlledByNetwork, m_isControlledByPlayer);
bool bWasPlayer = ccontrol.IsControlledByLocalOrNetworkPlayer();
bool bBecomingPlayer = controlledBy.IsControlledByLocalOrNetworkPlayer();
if(bWasPlayer != bBecomingPlayer && m_pMyVehicle)
{
m_pMyVehicle->ChangePedsPlayerStatus(*this,bBecomingPlayer);
}
// Remove the player movement tasks after becoming non-player.
if(bWasPlayer && !bBecomingPlayer)
{
CTask* pTask= GetPedIntelligence()->FindMovementTaskByType(CTaskTypes::TASK_MOVE_PLAYER);
if(pTask)
{
pTask->MakeAbortable(aiTask::ABORT_PRIORITY_IMMEDIATE, NULL);
}
GetPedIntelligence()->GetTaskManager()->AbortTasksAtTreeIndex(PED_TASK_TREE_MOVEMENT);
}
// Update m_controlledBy
m_isControlledByNetwork = controlledBy.IsControlledByNetwork();
m_isControlledByPlayer = controlledBy.IsControlledByLocalOrNetworkPlayer();
}
void CPed::SetupExternallyDrivenDOFs()
{
if (m_pExternallyDrivenDOFFrame == NULL)
{
CPedModelInfo *pPedModelInfo = GetPedModelInfo();
if (pPedModelInfo)
{
atArray< u8 > tracks; tracks.Reserve(16);
atArray< u16 > ids; ids.Reserve(16);
atArray< u8 > types; types.Reserve(16);
atFixedArray<SPedDLCMetaFileQueryData, STREAMING_MAX_DEPENDENCIES> creatureIndicesToLoad;
EXTRAMETADATAMGR.GetCreatureMetaDataIndices(pPedModelInfo, creatureIndicesToLoad);
for (u32 cInfoIdx = 0; cInfoIdx < creatureIndicesToLoad.GetCount(); cInfoIdx++)
{
CCreatureMetaData *pCreatureMetadata = NULL;
SPedDLCMetaFileQueryData& currCreatureMetaFileInfo = creatureIndicesToLoad[cInfoIdx];
if (g_fwMetaDataStore.IsValidSlot(strLocalIndex(currCreatureMetaFileInfo.m_storeIndex)))
{
pCreatureMetadata = g_fwMetaDataStore.Get(strLocalIndex(currCreatureMetaFileInfo.m_storeIndex))->GetObject< CCreatureMetaData >();
if (pCreatureMetadata)
{
atArray< u8 > propTracks; propTracks.Reserve(16);
atArray< u16 > propIds; propIds.Reserve(16);
atArray< u8 > propTypes; propTypes.Reserve(16);
for(int i = 0; i < pCreatureMetadata->m_pedPropExpressions.GetCount(); i ++)
{
const CPedPropExpressionData *pPedPropExpressionData = &pCreatureMetadata->m_pedPropExpressions[i];
for(int j = 0; j < pPedPropExpressionData->m_tracks.GetCount(); j ++)
{
u8 propTrack = pPedPropExpressionData->m_tracks[j];
u16 propId = pPedPropExpressionData->m_ids[j];
u8 propType = pPedPropExpressionData->m_types[j];
bool trackIdExists = (propTracks.Find(propTrack) != -1) && (propIds.Find(propId) != -1);
if(!trackIdExists)
{
propTracks.PushAndGrow(propTrack);
propIds.PushAndGrow(propId);
propTypes.PushAndGrow(propType);
}
}
}
// merge prop dofs into externally driven dofs
for(int i = 0; i < propTracks.GetCount(); i ++)
{
bool trackIdExists = (tracks.Find(propTracks[i]) != -1) && (ids.Find(propIds[i]) != -1);
if (!trackIdExists)
{
tracks.PushAndGrow(propTracks[i]);
ids.PushAndGrow(propIds[i]);
types.PushAndGrow(propTypes[i]);
}
}
atArray< u8 > compTracks; compTracks.Reserve(16);
atArray< u16 > compIds; compIds.Reserve(16);
atArray< u8 > compTypes; compTypes.Reserve(16);
for(int i = 0; i < pCreatureMetadata->m_pedCompExpressions.GetCount(); i ++)
{
const CPedCompExpressionData *pPedCompExpressionData = &pCreatureMetadata->m_pedCompExpressions[i];
for(int j = 0; j < pPedCompExpressionData->m_tracks.GetCount(); j ++)
{
u8 compTrack = pPedCompExpressionData->m_tracks[j];
u16 compId = pPedCompExpressionData->m_ids[j];
u8 compType = pPedCompExpressionData->m_types[j];
bool compTrackIdExists = (compTracks.Find(compTrack) != -1) && (compIds.Find(compId) != -1);
if(!compTrackIdExists)
{
compTracks.PushAndGrow(compTrack);
compIds.PushAndGrow(compId);
compTypes.PushAndGrow(compType);
}
}
}
// merge comp dofs into externally driven dofs
for(int i = 0; i < compTracks.GetCount(); i ++)
{
bool trackIdExists = (tracks.Find(compTracks[i]) != -1) && (ids.Find(compIds[i]) != -1);
if (!trackIdExists)
{
tracks.PushAndGrow(compTracks[i]);
ids.PushAndGrow(compIds[i]);
types.PushAndGrow(compTypes[i]);
}
}
}
}
}
u8 externallyDrivenDOFs = pPedModelInfo->GetExternallyDrivenDOFs();
if (externallyDrivenDOFs & HIGH_HEELS)
{
atArray< u8 > highHeelTracks; highHeelTracks.Reserve(16);
atArray< u16 > highHeelIds; highHeelIds.Reserve(16);
atArray< u8 > highHeelTypes; highHeelTypes.Reserve(16);
bool trackIdExists = (highHeelTracks.Find(kTrackGenericControl) != -1) && (highHeelIds.Find(BONETAG_HIGH_HEELS) != -1);
if(!trackIdExists)
{
highHeelTracks.PushAndGrow(kTrackGenericControl);
highHeelIds.PushAndGrow(BONETAG_HIGH_HEELS);
highHeelTypes.PushAndGrow(kFormatTypeFloat);
}
// merge high heel dofs into externally driven dofs
for(int i = 0; i < highHeelTracks.GetCount(); i ++)
{
bool trackIdExists = (tracks.Find(highHeelTracks[i]) != -1) && (ids.Find(highHeelIds[i]) != -1);
if(!trackIdExists)
{
tracks.PushAndGrow(highHeelTracks[i]);
ids.PushAndGrow(highHeelIds[i]);
types.PushAndGrow(highHeelTypes[i]);
}
}
}
if(tracks.GetCount() > 0)
{
// Create frame
m_pExternallyDrivenDOFFrame = rage_new crFrame();
Assertf(m_pExternallyDrivenDOFFrame, "Failed to create ExternallyDrivenDOFFrame");
// Create DOFs
m_pExternallyDrivenDOFFrame->InitCreateDofs(tracks.GetCount(), tracks.GetElements(), ids.GetElements(), types.GetElements(), false, NULL);
// Set DOF initial value
if (externallyDrivenDOFs & HIGH_HEELS)
{
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS, 1.0f);
m_pExternallyDrivenDOFFrame->InvalidateDof(kTrackGenericControl, BONETAG_HIGH_HEELS);
}
for (u32 cInfoIdx = 0; cInfoIdx < creatureIndicesToLoad.GetCount(); cInfoIdx++)
{
CCreatureMetaData *pCreatureMetadata = NULL;
SPedDLCMetaFileQueryData& currCreatureMetaFileInfo = creatureIndicesToLoad[cInfoIdx];
if (g_fwMetaDataStore.IsValidSlot(strLocalIndex(currCreatureMetaFileInfo.m_storeIndex)))
{
pCreatureMetadata = g_fwMetaDataStore.Get(strLocalIndex(currCreatureMetaFileInfo.m_storeIndex))->GetObject< CCreatureMetaData >();
if (pCreatureMetadata)
{
for(int i = 0; i < pCreatureMetadata->m_pedPropExpressions.GetCount(); i ++)
{
const CPedPropExpressionData *pPedPropExpressionData = &pCreatureMetadata->m_pedPropExpressions[i];
eAnchorPoints anchorPoint = static_cast< eAnchorPoints >(pPedPropExpressionData->m_pedPropID);
s32 currPropVarIndex = -1;
for(u32 idx = 0; idx < MAX_PROPS_PER_PED; idx ++)
{
if(anchorPoint == GetPedDrawHandler().GetPropData().GetAnchor(idx))
{
if(GetPedDrawHandler().GetPedPropRenderGfx())
{
if(GetPedDrawHandler().GetPedPropRenderGfx()->GetDrawable(idx))
{
currPropVarIndex = GetPedDrawHandler().GetPropData().GetPropId(idx);
}
}
else
{
currPropVarIndex = GetPedDrawHandler().GetPropData().GetPropId(idx);
}
}
}
s32 globalPropIndex = pPedPropExpressionData->m_pedPropVarIndex;
if (currCreatureMetaFileInfo.m_dlcNameHash != 0)
{
globalPropIndex = CPedPropsMgr::GetGlobalPropData(this, anchorPoint, currCreatureMetaFileInfo.m_dlcNameHash, pPedPropExpressionData->m_pedPropVarIndex);
}
if (currPropVarIndex == globalPropIndex)
{
// Ped is using the selected prop
if (pPedPropExpressionData->m_pedPropExpressionIndex == -1)
{
// Reset the associated dofs
for(int j = 0; j < pPedPropExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedPropExpressionData->m_tracks[j];
u16 id = pPedPropExpressionData->m_ids[j];
u8 type = pPedPropExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = 0.0f;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
else
{
// Apply the expression values to the associated dofs
const CPedVariationInfoCollection *pPedVariationInfoCollection = pPedModelInfo->GetVarInfo();
const float *pExpressionMods = pPedVariationInfoCollection->GetExpressionMods(pPedPropExpressionData->m_pedPropID, currPropVarIndex);
if(Verifyf(pExpressionMods, "Ped %p %s - Could not find expression mods for the prop! anchorId = %u propId = %u", this, GetModelName(), pPedPropExpressionData->m_pedPropID, currPropVarIndex))
{
float expressionVal = pExpressionMods[pPedPropExpressionData->m_pedPropExpressionIndex];
for(int j = 0; j < pPedPropExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedPropExpressionData->m_tracks[j];
u16 id = pPedPropExpressionData->m_ids[j];
u8 type = pPedPropExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO); value.SetElemf(pPedPropExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO); value.SetElemf(pPedPropExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = expressionVal;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
}
}
}
for(int i = 0; i < pCreatureMetadata->m_pedCompExpressions.GetCount(); i ++)
{
const CPedCompExpressionData *pPedCompExpressionData = &pCreatureMetadata->m_pedCompExpressions[i];
const CPedVariationData &pedVarData = GetPedDrawHandler().GetVarData();
ePedVarComp componentType = static_cast< ePedVarComp >(pPedCompExpressionData->m_pedCompID);
s32 currCompVarIndex = pedVarData.GetPedCompIdx(componentType);
s32 globalCompIndex = pPedCompExpressionData->m_pedCompVarIndex;
if (currCreatureMetaFileInfo.m_dlcNameHash != 0)
{
globalCompIndex = CPedVariationPack::GetGlobalCompData(this, componentType, currCreatureMetaFileInfo.m_dlcNameHash, pPedCompExpressionData->m_pedCompVarIndex);
}
if (currCompVarIndex == globalCompIndex)
{
// Ped is using the selected component
if (pPedCompExpressionData->m_pedCompExpressionIndex == -1)
{
// Reset the associated dofs
for(int j = 0; j < pPedCompExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedCompExpressionData->m_tracks[j];
u16 id = pPedCompExpressionData->m_ids[j];
u8 type = pPedCompExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = 0.0f;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
else
{
// Apply the expression values to the associated dofs
CPedVariationInfoCollection *pPedVariationInfoCollection = pPedModelInfo->GetVarInfo();
const float *pExpressionMods = pPedVariationInfoCollection->GetCompExpressionsMods(static_cast< u8 >(pPedCompExpressionData->m_pedCompID), static_cast< u8 >(currCompVarIndex));
if(Verifyf(pExpressionMods, "Ped %p %s - Could not find expression mods for the component! compIdx = %u drawblIdx = %u", this, GetModelName(), pPedCompExpressionData->m_pedCompID, currCompVarIndex))
{
float expressionVal = pExpressionMods[pPedCompExpressionData->m_pedCompExpressionIndex];
for(int j = 0; j < pPedCompExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedCompExpressionData->m_tracks[j];
u16 id = pPedCompExpressionData->m_ids[j];
u8 type = pPedCompExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO); value.SetElemf(pPedCompExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO); value.SetElemf(pPedCompExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = expressionVal;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
}
}
}
}
}
}
if (GetAnimDirector())
{
GetAnimDirector()->GetMove().SetFrame(ms_ExternallyDrivenDOFFrameId, m_pExternallyDrivenDOFFrame);
}
}
}
}
}
void CPed::DeleteExternallyDrivenDOFs()
{
if (m_pExternallyDrivenDOFFrame)
{
m_pExternallyDrivenDOFFrame->Release();
}
m_pExternallyDrivenDOFFrame = NULL;
}
void CPed::ProcessExternallyDrivenDOFs()
{
SetPedConfigFlag(CPED_CONFIG_FLAG_HasHighHeels, false);
if (m_pExternallyDrivenDOFFrame)
{
CPedModelInfo *pPedModelInfo = GetPedModelInfo();
if (pPedModelInfo)
{
u8 externallyDrivenDOFs = pPedModelInfo->GetExternallyDrivenDOFs();
if (externallyDrivenDOFs & HIGH_HEELS)
{
TUNE_GROUP_BOOL(HIGH_HEELS_BOOL, bCodeDrivenHighHeelsExpressionBlend, true);
if (bCodeDrivenHighHeelsExpressionBlend)
{
// High heels
TUNE_GROUP_BOOL(HIGH_HEELS_BOOL, bOverrideHighHeelsExpressionBlend, false);
TUNE_GROUP_FLOAT(HIGH_HEELS_SLIDER, fOverrideHighHeelsExpressionBlend, 0.0f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(HIGH_HEELS_RATE, fOverrideHighHeelsExpressionRate, 5.0f, 0.0f, 100.0f, 0.01f);
bool bBlendOutHighHeels = false;
if (GetUsingRagdoll())
{
if (GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_CONTROL) && !GetIsDeadOrDying())
{
CTaskNMControl *task = smart_cast< CTaskNMControl * >(GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL));
if ((task && task->IsFeedbackFlagSet(CTaskNMControl::BALANCE_FAILURE)) || IsProne())
{
// Blend out the high heel expression
bBlendOutHighHeels = true;
}
}
else
{
// Blend out the high heel expression
bBlendOutHighHeels = true;
}
}
else if (GetMovePed().GetState()==CMovePed::kStateStaticFrame)
{
// don't apply high heels in the ragdoll frame
bBlendOutHighHeels = true;
}
if (bBlendOutHighHeels)
{
m_fHighHeelExpressionBlend = rage::Clamp(m_fHighHeelExpressionBlend - (fOverrideHighHeelsExpressionRate * fwTimer::GetTimeStep()), 0.0f, 1.0f);
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS, m_fHighHeelExpressionBlend);
}
else
{
// ALways starts fully blended in
m_fHighHeelExpressionBlend = 1.0f;
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS, m_fHighHeelExpressionBlend);
m_pExternallyDrivenDOFFrame->InvalidateDof(kTrackGenericControl, BONETAG_HIGH_HEELS);
}
if (GetPedResetFlag(CPED_RESET_FLAG_DisableHighHeels))
{
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS, 0.0f);
}
if (bOverrideHighHeelsExpressionBlend)
{
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS, fOverrideHighHeelsExpressionBlend);
}
}
}
atFixedArray<SPedDLCMetaFileQueryData, STREAMING_MAX_DEPENDENCIES> creatureIndicesToLoad;
EXTRAMETADATAMGR.GetCreatureMetaDataIndices(pPedModelInfo, creatureIndicesToLoad);
for (u32 cInfoIdx = 0; cInfoIdx < creatureIndicesToLoad.GetCount(); cInfoIdx++)
{
SPedDLCMetaFileQueryData& currCreatureMetaFileInfo = creatureIndicesToLoad[cInfoIdx];
if (g_fwMetaDataStore.IsValidSlot(strLocalIndex(currCreatureMetaFileInfo.m_storeIndex)))
{
CCreatureMetaData *pCreatureMetadata = g_fwMetaDataStore.Get(strLocalIndex(currCreatureMetaFileInfo.m_storeIndex))->GetObject< CCreatureMetaData >();
if (pCreatureMetadata)
{
for(int i = 0; i < pCreatureMetadata->m_pedPropExpressions.GetCount(); i ++)
{
const CPedPropExpressionData *pPedPropExpressionData = &pCreatureMetadata->m_pedPropExpressions[i];
eAnchorPoints anchorPoint = static_cast< eAnchorPoints >(pPedPropExpressionData->m_pedPropID);
s32 currPropVarIndex = -1;
for(u32 idx = 0; idx < MAX_PROPS_PER_PED; idx ++)
{
if(anchorPoint == GetPedDrawHandler().GetPropData().GetAnchor(idx))
{
if(GetPedDrawHandler().GetPedPropRenderGfx())
{
if(GetPedDrawHandler().GetPedPropRenderGfx()->GetDrawable(idx))
{
currPropVarIndex = GetPedDrawHandler().GetPropData().GetPropId(idx);
}
}
else
{
currPropVarIndex = GetPedDrawHandler().GetPropData().GetPropId(idx);
}
}
}
s32 globalPropIndex = pPedPropExpressionData->m_pedPropVarIndex;
if (currCreatureMetaFileInfo.m_dlcNameHash != 0)
{
globalPropIndex = CPedPropsMgr::GetGlobalPropData(this, anchorPoint, currCreatureMetaFileInfo.m_dlcNameHash, pPedPropExpressionData->m_pedPropVarIndex);
}
if (currPropVarIndex == globalPropIndex)
{
// Ped is using the selected prop
if (pPedPropExpressionData->m_pedPropExpressionIndex == -1)
{
// Reset the associated dofs
for(int j = 0; j < pPedPropExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedPropExpressionData->m_tracks[j];
u16 id = pPedPropExpressionData->m_ids[j];
u8 type = pPedPropExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = 0.0f;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
else
{
// Apply the expression values to the associated dofs
const CPedVariationInfoCollection *pPedVariationInfoCollection = pPedModelInfo->GetVarInfo();
const float *pExpressionMods = pPedVariationInfoCollection->GetExpressionMods(pPedPropExpressionData->m_pedPropID, currPropVarIndex);
if(Verifyf(pExpressionMods, "Ped %p %s - Could not find expression mods for the prop! anchorId = %u propId = %u", this, GetModelName(), pPedPropExpressionData->m_pedPropID, currPropVarIndex))
{
float expressionVal = pExpressionMods[pPedPropExpressionData->m_pedPropExpressionIndex];
for(int j = 0; j < pPedPropExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedPropExpressionData->m_tracks[j];
u16 id = pPedPropExpressionData->m_ids[j];
u8 type = pPedPropExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO); value.SetElemf(pPedPropExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO); value.SetElemf(pPedPropExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = expressionVal;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
}
}
}
for(int i = 0; i < pCreatureMetadata->m_pedCompExpressions.GetCount(); i ++)
{
const CPedCompExpressionData *pPedCompExpressionData = &pCreatureMetadata->m_pedCompExpressions[i];
const CPedVariationData &pedVarData = GetPedDrawHandler().GetVarData();
ePedVarComp componentType = static_cast< ePedVarComp >(pPedCompExpressionData->m_pedCompID);
s32 currCompVarIndex = pedVarData.GetPedCompIdx(componentType);
s32 globalCompIndex = pPedCompExpressionData->m_pedCompVarIndex;
if (currCreatureMetaFileInfo.m_dlcNameHash != 0)
{
globalCompIndex = CPedVariationPack::GetGlobalCompData(this, componentType, currCreatureMetaFileInfo.m_dlcNameHash, pPedCompExpressionData->m_pedCompVarIndex);
}
if (currCompVarIndex == globalCompIndex)
{
// Ped is using the selected component
if (pPedCompExpressionData->m_pedCompExpressionIndex == -1)
{
// Reset the associated dofs
for(int j = 0; j < pPedCompExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedCompExpressionData->m_tracks[j];
u16 id = pPedCompExpressionData->m_ids[j];
u8 type = pPedCompExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = 0.0f;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
else
{
// Apply the expression values to the associated dofs
CPedVariationInfoCollection *pPedVariationInfoCollection = pPedModelInfo->GetVarInfo();
const float *pExpressionMods = pPedVariationInfoCollection->GetCompExpressionsMods(static_cast< u8 >(pPedCompExpressionData->m_pedCompID), static_cast< u8 >(currCompVarIndex));
if(Verifyf(pExpressionMods, "Ped %p %s - Could not find expression mods for the component! compIdx = %u drawblIdx = %u", this, GetModelName(), pPedCompExpressionData->m_pedCompID, currCompVarIndex))
{
float expressionVal = pExpressionMods[pPedCompExpressionData->m_pedCompExpressionIndex];
for(int j = 0; j < pPedCompExpressionData->m_tracks.GetCount(); j ++)
{
u8 track = pPedCompExpressionData->m_tracks[j];
u16 id = pPedCompExpressionData->m_ids[j];
u8 type = pPedCompExpressionData->m_types[j];
switch(type)
{
case kFormatTypeVector3:
{
Vec3V value = Vec3V(V_ZERO); value.SetElemf(pPedCompExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetVector3(track, id, value);
} break;
case kFormatTypeQuaternion:
{
QuatV value = QuatV(V_ZERO); value.SetElemf(pPedCompExpressionData->m_components[j], expressionVal);
m_pExternallyDrivenDOFFrame->SetQuaternion(track, id, value);
} break;
case kFormatTypeFloat:
{
float value = expressionVal;
m_pExternallyDrivenDOFFrame->SetFloat(track, id, value);
} break;
}
}
}
}
}
}
}
}
}
if(externallyDrivenDOFs & HIGH_HEELS)
{
if(m_pExternallyDrivenDOFFrame->HasDof(kTrackGenericControl, BONETAG_HIGH_HEELS_META))
{
float fHighHeelsMeta = 0.0f;
m_pExternallyDrivenDOFFrame->GetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS_META, fHighHeelsMeta);
if(fHighHeelsMeta > 0.0f)
{
SetPedConfigFlag(CPED_CONFIG_FLAG_HasHighHeels, true);
}
}
else
{
SetPedConfigFlag(CPED_CONFIG_FLAG_HasHighHeels, true);
}
}
if(m_pExternallyDrivenDOFFrame->HasDof(kTrackGenericControl, BONETAG_HAIR_SCALE))
{
/* We have a hair scale dof */
TUNE_GROUP_BOOL(HAIR_SCALE, bTuneHairScaleOverride, false);
TUNE_GROUP_FLOAT(HAIR_SCALE, fTuneHairScale, 0.0f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(HAIR_SCALE, fTuneHairScaleRate, 5.0f, 0.0f, 100.0f, 0.01f);
/* Get target hair scale */
float fTargetHairScale = 0.0f;
m_pExternallyDrivenDOFFrame->GetFloat(kTrackGenericControl, BONETAG_HAIR_SCALE, fTargetHairScale);
if(GetPedResetFlag(CPED_RESET_FLAG_OverrideHairScale))
{
/* We're overriding the hair scale, but only if we're going to make it smaller */
fTargetHairScale = rage::Min(fTargetHairScale, m_fTargetHairScale);
}
else if(GetPedResetFlag(CPED_RESET_FLAG_OverrideHairScaleLarger))
{
/* We're overriding the hair scale, but only if we're going to make it smaller */
fTargetHairScale = rage::Max(fTargetHairScale, m_fTargetHairScale);
}
if(bTuneHairScaleOverride)
{
/* Override target hair scale */
fTargetHairScale = fTuneHairScale;
}
if(m_pExternallyDrivenDOFFrame->HasDof(kTrackGenericControl, BONETAG_HAIR_HEIGHT))
{
/* Get hair height */
m_pExternallyDrivenDOFFrame->GetFloat(kTrackGenericControl, BONETAG_HAIR_HEIGHT, m_fHairHeight);
}
else
{
/* Reset hair height */
m_fHairHeight = -1.0f;
}
/* Scale towards target hair scale */
if(m_bHairScaleLerp)
{
if(m_fCurrentHairScale < fTargetHairScale)
{
/* Going up! */
m_fCurrentHairScale += rage::Min(fTargetHairScale - m_fCurrentHairScale, fTuneHairScaleRate * fwTimer::GetTimeStep());
}
else
{
/* Going down! */
m_fCurrentHairScale -= rage::Min(m_fCurrentHairScale - fTargetHairScale, fTuneHairScaleRate * fwTimer::GetTimeStep());
}
}
else
{
m_fCurrentHairScale = fTargetHairScale;
/* Check head prop has updated! */
s32 propMgrPropVarIndex = CPedPropsMgr::GetPedPropIdx(this, ANCHOR_HEAD);
s32 currPropVarIndex = -1;
for(u32 idx = 0; idx < MAX_PROPS_PER_PED; idx ++)
{
if(ANCHOR_HEAD == GetPedDrawHandler().GetPropData().GetAnchor(idx))
{
if(GetPedDrawHandler().GetPedPropRenderGfx())
{
if(GetPedDrawHandler().GetPedPropRenderGfx()->GetDrawable(idx))
{
currPropVarIndex = GetPedDrawHandler().GetPropData().GetPropId(idx);
}
}
}
}
if(propMgrPropVarIndex == currPropVarIndex)
{
SetPedResetFlag(CPED_RESET_FLAG_ForcePostCameraAnimUpdate, true);
SetPedResetFlag(CPED_RESET_FLAG_ePostCameraAnimUpdateUseZeroTimestep, true);
m_bHairScaleLerp = true;
}
}
/* Set hair scale dof */
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HAIR_SCALE, m_fCurrentHairScale);
}
}
}
}
void CPed::InvalidateExternallyDrivenDOFs()
{
if(m_pExternallyDrivenDOFFrame)
{
m_pExternallyDrivenDOFFrame->Invalidate();
}
}
void CPed::ProcessColarDOFs()
{
if (m_pExternallyDrivenDOFFrame)
{
CPedModelInfo *pPedModelInfo = GetPedModelInfo();
if (pPedModelInfo)
{
u8 externallyDrivenDOFs = pPedModelInfo->GetExternallyDrivenDOFs();
if (externallyDrivenDOFs & COLLAR)
{
crSkeleton& skel = *GetSkeleton();
const crSkeletonData& skelData = skel.GetSkeletonData();
if(Unlikely(skelData.GetSignature() != m_cachedBoneSignature))
{
for(u32 i=0; i < kOffsetCount; i++)
{
if(Unlikely(!skelData.ConvertBoneIdToIndex(ms_cachedBoneTags[i], m_cachedBoneIndices[i])))
{
m_cachedBoneIndices[i] = -1;
}
}
m_cachedBoneSignature = skelData.GetSignature();
}
Mat34V_ConstRef headLocalMtx = skel.GetLocalMtx(m_cachedBoneIndices[0]); //BONETAG_HEAD
Vec3V_ConstRef headLocalEulerXYZ = Mat34VToEulersXYZ(headLocalMtx);
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_HEAD_X, RtoD * headLocalEulerXYZ.GetXf());
Mat34V_ConstRef neckLocalMtx = skel.GetLocalMtx(m_cachedBoneIndices[7]); //BONETAG_NECK
Vec3V_ConstRef neckLocalEulerXYZ = Mat34VToEulersXYZ(neckLocalMtx);
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_NECK_X, RtoD * neckLocalEulerXYZ.GetXf());
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_NECK_Y, RtoD * neckLocalEulerXYZ.GetYf());
m_pExternallyDrivenDOFFrame->SetFloat(kTrackGenericControl, BONETAG_NECK_Z, RtoD * neckLocalEulerXYZ.GetZf());
}
}
}
}
void CRewardedVehicleExtension::GivePedRewards(CPed* pPed, const CVehicle* pVehicle)
{
bool bAlreadyGotRewardFromThisVehicle = false;
//! Don't give out vehicle rewards if player has flag set.
if(pPed->IsLocalPlayer() &&
pPed->GetPlayerInfo() &&
pPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_DISABLE_VEHICLE_REWARDS))
{
return;
}
// Remove NULL vehicles in the array.
for(int i = 0; i < GetNumRewardedVehicles(); i++)
{
if(GetRewardedVehicle(i) == NULL)
{
RemoveRewardedVehicle(i);
}
}
// Check if the ped already got the rewards from this vehicle
for(int i = 0; i < GetNumRewardedVehicles(); i++)
{
if(GetRewardedVehicle(i) == pVehicle)
{
bAlreadyGotRewardFromThisVehicle = true;
break;
}
}
if(!bAlreadyGotRewardFromThisVehicle)
{
const CVehicleModelInfo* pVehicleModelInfo = pVehicle->GetVehicleModelInfo();
u32 numRewards = pVehicleModelInfo->GetNumRewards();
for (u32 i=0; i<numRewards; i++)
{
const CPickupRewardData* pReward = CPickupDataManager::GetPickupRewardData(pVehicleModelInfo->GetRewardHash(i));
if (AssertVerify(pReward) &&
!CPickupManager::IsSuppressionFlagSet( pReward->GetType() ) &&
pReward->CanGive(NULL, pPed))
{
pReward->Give(NULL, pPed);
bAlreadyGotRewardFromThisVehicle = true;
}
}
// Record the vehicle if rewards are given.
if(bAlreadyGotRewardFromThisVehicle)
{
AddRewardedVehicle(pVehicle);
}
}
}
crFrame* CPed::GetIndependentMoverFrame() {
static crFrameDataSingleDof s_moverFrameData(kTrackIndependentMover, 0, kFormatTypeQuaternion);
if (!m_pIndependentMoverFrame)
{
m_pIndependentMoverFrame = rage_new crFrameSingleDof(s_moverFrameData);
}
return m_pIndependentMoverFrame;
}
float CPed::GetHighHeelExpressionDOF()
{
float fHighHeelsDof(0.0f);
CPedModelInfo *pPedModelInfo = GetPedModelInfo();
if (pPedModelInfo)
{
u8 externallyDrivenDOFs = pPedModelInfo->GetExternallyDrivenDOFs();
if (externallyDrivenDOFs & HIGH_HEELS)
{
const crCreatureComponentExtraDofs* pExtraDofs = GetAnimDirector()->GetCreature()->FindComponent<crCreatureComponentExtraDofs>();
if(pExtraDofs)
{
const crFrame& poseFrame = pExtraDofs->GetPoseFrame();
poseFrame.GetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS, fHighHeelsDof);
}
}
}
return fHighHeelsDof;
}
float CPed::GetHighHeelExpressionMetaDOF()
{
float fHighHeelsMetaDof(0.0f);
CPedModelInfo *pPedModelInfo = GetPedModelInfo();
if (pPedModelInfo)
{
u8 externallyDrivenDOFs = pPedModelInfo->GetExternallyDrivenDOFs();
if (externallyDrivenDOFs & HIGH_HEELS)
{
const crCreatureComponentExtraDofs* pExtraDofs = GetAnimDirector()->GetCreature()->FindComponent<crCreatureComponentExtraDofs>();
if(pExtraDofs)
{
const crFrame& poseFrame = pExtraDofs->GetPoseFrame();
poseFrame.GetFloat(kTrackGenericControl, BONETAG_HIGH_HEELS_META, fHighHeelsMetaDof);
}
}
}
return fHighHeelsMetaDof;
}
bool CPed::GetFirstPersonCameraDOFs(Vec3V_InOut vTrans, QuatV_InOut qRot, float &fMinX, float &fMaxX, float &fMinY, float &fMaxY, float &fMinZ, float &fMaxZ, float &fWeight, float &fov) const
{
bool bFoundAnyDOFs = false;
crCreature *pCreature = GetCreature();
if(taskVerifyf(pCreature, "Ped %p %s has no creature!", this, GetModelName()))
{
crCreatureComponentExtraDofs *pCreatureComponentExtraDofs = pCreature->FindComponent< crCreatureComponentExtraDofs >();
if(taskVerifyf(pCreatureComponentExtraDofs, "Ped %p %s has no creature component extra dofs!", this, GetModelName()))
{
crFrame &poseFrame = pCreatureComponentExtraDofs->GetPoseFrame();
Vec3V vValue(V_ZERO);
QuatV qValue(V_IDENTITY);
float fValue = 0.0f;
if(poseFrame.GetVector3(kTrackCameraTranslation, BONETAG_FIRSTPERSONCAM, vValue))
{
vTrans = vValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetQuaternion(kTrackCameraRotation, BONETAG_FIRSTPERSONCAM, qValue))
{
qRot = qValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_MINX, fValue))
{
fMinX = fValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_MINY, fValue))
{
fMinY = fValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_MINZ, fValue))
{
fMinZ = fValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_MAXX, fValue))
{
fMaxX = fValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_MAXY, fValue))
{
fMaxY = fValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_MAXZ, fValue))
{
fMaxZ = fValue;
bFoundAnyDOFs = true;
}
if(poseFrame.GetFloat(kTrackCameraLimit, BONETAG_FIRSTPERSONCAM_FOV, fValue))
{
fov = fValue;
bFoundAnyDOFs = true;
}
poseFrame.GetFloat(kTrackFirstPersonCameraWeight, 0, fWeight);
}
}
return bFoundAnyDOFs;
}
bool CPed::GetConstraintHelperDOFs(bool rightHand, float& weight, Vec3V_InOut translation, QuatV_InOut rotation)
{
bool bValid = false;
weight = 0.0f;
translation = Vec3V(V_ZERO);
rotation = QuatV(V_IDENTITY);
crCreature* pCreature = GetCreature();
if (pCreature)
{
crCreatureComponentExtraDofs* pExtraDofs = pCreature->FindComponent<crCreatureComponentExtraDofs>();
if (pExtraDofs)
{
crFrame& poseFrame = pExtraDofs->GetPoseFrame();
bValid = true;
u8 track = (u8)(rightHand ? kTrackConstraintHelperRightHandWeight : kTrackConstraintHelperLeftHandWeight);
u16 id = (u16)(rightHand ? BONETAG_CH_R_HAND : BONETAG_CH_L_HAND);
bValid &= poseFrame.GetFloat(track, 0, weight);
bValid &= poseFrame.GetVector3(kTrackBoneTranslation, id, translation);
bValid &= poseFrame.GetQuaternion(kTrackBoneRotation, id, rotation);
}
}
return bValid;
}
void CPed::InvalidateUpperBodyFixupDOFs()
{
crCreature* pCreature = GetCreature();
if (pCreature)
{
crCreatureComponentExtraDofs* pExtraDofs = pCreature->FindComponent<crCreatureComponentExtraDofs>();
if (pExtraDofs)
{
crFrame& poseFrame = pExtraDofs->GetPoseFrame();
if (poseFrame.HasDof(kTrackUpperBodyFixupShadow, BONETAG_ROOT))
{
// Invalidate shadow DOFs since the upperbodyfixup expression doesn't guarantee their validity across updates.
poseFrame.InvalidateDof(kTrackUpperBodyFixupShadow, BONETAG_ROOT);
poseFrame.InvalidateDof(kTrackUpperBodyFixupShadow, BONETAG_SPINE0);
poseFrame.InvalidateDof(kTrackUpperBodyFixupShadow, BONETAG_SPINE1);
poseFrame.InvalidateDof(kTrackUpperBodyFixupShadow, BONETAG_SPINE2);
poseFrame.InvalidateDof(kTrackUpperBodyFixupShadow, BONETAG_SPINE3);
poseFrame.InvalidateDof(kTrackUpperBodyFixupShadow, BONETAG_SPINE_ROOT);
}
}
}
}
#if __ASSERT
bool CPed::IsPedAllowedInMultiplayer(void) const
{
// Now that players can be animals in MP, allow everything
return true;
}
#endif /* __ASSERT */
#if __BANK
void CPed::InitDebugName()
{
u32 modelNameHash = GetPedModelInfo() ? GetPedModelInfo()->GetModelNameHash() : 0;
//! Ignore ID's for players so that we have consistent naming.
if( modelNameHash == MI_PLAYERPED_PLAYER_ZERO.GetName().GetHash() )
{
sprintf(m_DebugLogName, "PLAYER_0");
}
else if( modelNameHash == MI_PLAYERPED_PLAYER_ONE.GetName().GetHash() )
{
sprintf(m_DebugLogName, "PLAYER_1");
}
else if( modelNameHash == MI_PLAYERPED_PLAYER_TWO.GetName().GetHash() )
{
sprintf(m_DebugLogName, "PLAYER_2");
}
else if(IsPlayer())
{
sprintf(m_DebugLogName, "PLAYER_1"); //default ped. e.g. we are in testbed.
}
else
{
sprintf(m_DebugLogName, "%s%d", PopTypeIsMission() ? "SCRIPT_PED_" : "PED_", GetDebugObjectID() );
}
}
void CPed::SetLastPlayerTargetingRejectionString(const char *pString)
{
if(!m_pDebugPlayerTargetingRejectionString)
{
m_pDebugPlayerTargetingRejectionString = rage_new char[100]; //assume we never need more than 100.
}
strcpy(m_pDebugPlayerTargetingRejectionString, pString);
m_nPlayerTargetingLastRejectedFrame = fwTimer::GetFrameCount();
}
#endif
void CPed::LoadVisualSettings()
{
// check the visual settings are loaded
tcAssertf(g_visualSettings.GetIsLoaded(), "visual settings aren't loaded");
sm_mainLight.m_lightSettings.Set( g_visualSettings, "pedlight");
sm_mainLight.m_lightVolumeIntensity = g_visualSettings.Get("pedlight.volumeIntensity");
sm_mainLight.m_lightVolumeSize = g_visualSettings.Get("pedlight.volumeSize");
sm_mainLight.m_lightVolumeExponent = g_visualSettings.Get("pedlight.volumeExponent");
sm_mainLight.m_lightVolumeColor = g_visualSettings.GetVec4V("pedLight.volumeColor");
sm_mainLight.m_lightFade = g_visualSettings.Get("pedlight.fade");
sm_mainLight.m_lightShadowFade = g_visualSettings.Get("pedlight.shadowFade");
sm_mainLight.m_lightSpecularFade = g_visualSettings.Get("pedlight.SpecularFade");
sm_fpsLight.m_lightSettings.Set( g_visualSettings, "pedFpsLight");
sm_fpsLight.m_lightVolumeIntensity = g_visualSettings.Get("pedFpsLight.volumeIntensity");
sm_fpsLight.m_lightVolumeSize = g_visualSettings.Get("pedFpsLight.volumeSize");
sm_fpsLight.m_lightVolumeExponent = g_visualSettings.Get("pedFpsLight.volumeExponent");
sm_fpsLight.m_lightVolumeColor = g_visualSettings.GetVec4V("pedFpsLight.volumeColor");
sm_fpsLight.m_lightFade = g_visualSettings.Get("pedFpsLight.fade");
sm_fpsLight.m_lightShadowFade = g_visualSettings.Get("pedFpsLight.shadowFade");
sm_fpsLight.m_lightSpecularFade = g_visualSettings.Get("pedFpsLight.SpecularFade");
sm_footLight.m_lightSettings.Set(g_visualSettings, "pedFootLight");
sm_footLight.m_lightVolumeIntensity = 0.0f;
sm_footLight.m_lightVolumeSize = 0.0f;
sm_footLight.m_lightVolumeExponent = 0.0f;
sm_footLight.m_lightOffset = g_visualSettings.GetVec4V("pedFootLight.offset");
sm_footLight.m_lightFade = g_visualSettings.Get("pedFootLight.fade");
sm_footLight.m_lightShadowFade = 0.0f;
sm_footLight.m_lightSpecularFade = g_visualSettings.Get("pedFootLight.SpecularFade");
}
#if FPS_MODE_SUPPORTED
void CPed::PreRender2(const bool)
{
GetIkManager().PerformFpsUpdatePass(*this);
}
bool CPed::IsFirstPersonShooterModeEnabledForPlayer(const bool bCheckStrafe, const bool bIncludeClone, const bool bDisableForDominantScriptedCams, const bool bDisableForDominantCutsceneCams, const bool bCheckFlags) const
{
if (IsAPlayerPed())
{
if (IsNetworkClone())
{
if (bIncludeClone)
{
return static_cast<CNetObjPlayer*>(GetNetworkObject())->IsUsingFirstPersonCamera();
}
}
else if (IsLocalPlayer() && camInterface::GetGameplayDirector().IsFirstPersonModeEnabled())
{
if(bCheckFlags && GetPlayerResetFlag(CPlayerResetFlags::PRF_CAMERA_VIEW_MODE_SWITCHED_TO_OR_FROM_FIRST_PERSON))
{
if(GetPlayerResetFlag(CPlayerResetFlags::PRF_CAMERA_VIEW_MODE_SWITCHED_TO_FIRST_PERSON))
{
return true;
}
else
{
return false;
}
}
camFirstPersonShooterCamera* pFpsCamera = camInterface::GetGameplayDirector().GetFirstPersonShooterCamera();
const bool bDisableDueToDominantScriptedOrCutscene = (bDisableForDominantScriptedCams && (camInterface::IsDominantRenderedDirector(camInterface::GetScriptDirector()) || camInterface::GetScriptDirector().IsRendering())) ||
(bDisableForDominantCutsceneCams && camInterface::IsDominantRenderedDirector(camInterface::GetCutsceneDirector()));
if (bDisableDueToDominantScriptedOrCutscene)
{
// If we want to disable when scripted/cutscene camera is rendering, treat as if fps camera does not exist.
pFpsCamera = NULL;
}
if (!bDisableDueToDominantScriptedOrCutscene && camInterface::GetGameplayDirector().GetFirstPersonPedAimCamera())
{
return camInterface::GetGameplayDirector().IsFirstPersonModeEnabled() && camInterface::GetGameplayDirector().IsFirstPersonModeAllowed();
}
return ( (pFpsCamera && (!bCheckStrafe || pFpsCamera->IsStrafeEnabled())) ||
camInterface::GetGameplayDirector().ShouldUseThirdPersonCameraForFirstPersonMode() );
}
}
return false;
}
bool CPed::IsFirstPersonCameraOrFirstPersonSniper() const
{
// Phone active?
if(CPhoneMgr::CamGetState())
{
return false;
}
// Standard first person cam?
if(camInterface::IsDominantRenderedCameraAnyFirstPersonCamera())
{
return true;
}
// Sniper cam, and using first person anims?
if(camInterface::GetGameplayDirector().IsFirstPersonAiming() && IsFirstPersonShooterModeEnabledForPlayer(false))
{
return true;
}
return false;
}
bool CPed::IsFirstPersonShooterModeStickWithinStrafeAngleThreshold() const
{
if(IsLocalPlayer())
{
const camFirstPersonShooterCamera* pFPSCam = camInterface::GetGameplayDirector().GetFirstPersonShooterCamera();
if(pFPSCam && pFPSCam->IsStickWithinStrafeAngleThreshold())
{
return true;
}
}
return false;
}
bool CPed::UseFirstPersonUpperBodyAnims(bool bCheckStrafe) const
{
if( IsNetworkClone() )
{
// We never want to use clone 1st person anims when the ped is running the locomotion task
CTask* pMotionTask = GetCurrentMotionTask(false);
if( pMotionTask && pMotionTask->GetTaskType() == CTaskTypes::TASK_HUMAN_LOCOMOTION )
{
return false;
}
bool bInFirstPersonIdle(false), bStickWithinStrafeAngle(false);
if( NetworkInterface::IsRemotePlayerInFirstPersonMode(*this, &bInFirstPersonIdle, &bStickWithinStrafeAngle) )
{
bool bPedIsUnarmed(false), bUsingThrownWeapon(false), bIsUsingPetrolCan(false), bIsUsingMeleeFistWeapon(false);
const CPedWeaponManager* pWeaponMgr = GetWeaponManager();
// Check if the ped is unarmed or has a thrown weapon
if( pWeaponMgr != NULL )
{
const CWeaponInfo* pEquippedWeaponInfo = pWeaponMgr->GetEquippedWeaponInfo();
if(pEquippedWeaponInfo)
{
bPedIsUnarmed = pEquippedWeaponInfo->GetIsUnarmed();
bUsingThrownWeapon = pEquippedWeaponInfo->GetIsThrownWeapon();
bIsUsingPetrolCan = pEquippedWeaponInfo->GetGroup() == WEAPONGROUP_PETROLCAN;
bIsUsingMeleeFistWeapon = pEquippedWeaponInfo->GetIsMeleeFist();
}
}
// Are we withing the strafing angle?
if( !PARAM_useFPIdleMovementOnClones.Get() && !CNetObjPlayer::ms_bUseFPIdleMovementOnClones )
{
bStickWithinStrafeAngle = true;
}
// Only use first person anims on clones that are idling or clones that are using the petrolcan
return ( bIsUsingPetrolCan || (((bInFirstPersonIdle && bStickWithinStrafeAngle) || bPedIsUnarmed || bIsUsingMeleeFistWeapon) && !bUsingThrownWeapon) );
}
}
else
{
return IsFirstPersonShooterModeEnabledForPlayer(bCheckStrafe);
}
return false;
}
const Matrix34& CPed::GetThirdPersonSkeletonObjectMtx(int boneIdx) const
{
extern const crSkeleton* GetSkeletonForDraw(const CEntity *entity);
return RCC_MATRIX34(GetSkeletonForDraw(this)->GetObjectMtx(boneIdx));
}
void ReportArmorMismatch(u32 /*expected*/, u32 /*tampered*/)
{
//LinkDataReporterPlugin_Report(LinkDataReporterCategories::LDRC_ARMOR);
return;
}
#endif // FPS_MODE_SUPPORTED