4666 lines
161 KiB
C++
4666 lines
161 KiB
C++
//
|
|
// physics/gtaInst.cpp
|
|
//
|
|
|
|
// Game headers
|
|
#include "ai/debug/types/AnimationDebugInfo.h"
|
|
#include "audio/collisionaudioentity.h"
|
|
#include "event/events.h"
|
|
#include "event/EventGroup.h"
|
|
#include "event/EventMovement.h"
|
|
#include "event/EventScript.h"
|
|
#include "Ik/IkManager.h"
|
|
#include "modelinfo/PedModelInfo.h"
|
|
#include "modelInfo/vehicleModelInfo.h"
|
|
#include "network/General/NetworkUtil.h"
|
|
#include "Objects/Door.h"
|
|
#include "objects/object.h"
|
|
#include "pathserver/PathServer.h"
|
|
#include "pathserver/ExportCollision.h"
|
|
#include "peds/ped.h"
|
|
#include "peds/PedIntelligence.h"
|
|
#include "physics/gtaInst.h"
|
|
#include "physics/gtaMaterialManager.h"
|
|
#include "physics/physics.h"
|
|
#include "physics/sleep.h"
|
|
#include "physics/VehicleFragImpulseFunction.h"
|
|
#include "physics/WorldProbe/worldprobe.h"
|
|
#include "pickups/Pickup.h"
|
|
#include "Task/Motion/Locomotion/TaskMotionPed.h"
|
|
#include "Task/physics/NmDebug.h"
|
|
#include "Task/Physics/TaskNM.h"
|
|
#include "Task/Physics/TaskNMBalance.h"
|
|
#include "Task/Physics/TaskNMSlungOverShoulder.h"
|
|
#include "Task/Physics/TaskRageRagdoll.h"
|
|
#include "Task/Combat/TaskDamageDeath.h"
|
|
#include "Vehicles/Boat.h"
|
|
#include "Vehicles/Planes.h"
|
|
#include "Vehicles/Train.h"
|
|
#include "Vehicles/Vehicle.h"
|
|
#include "Vehicles/Submarine.h"
|
|
#include "Vehicles/AmphibiousAutomobile.h"
|
|
#include "Vehicles/Metadata/VehicleSeatInfo.h"
|
|
#include "vfx/vehicleglass/VehicleGlassManager.h"
|
|
#include "Vfx/VisualEffects.h"
|
|
#include "Vfx/Decals/DecalManager.h"
|
|
#include "Vfx/Systems/VfxTrail.h"
|
|
#include "Vfx/Systems/VfxMaterial.h"
|
|
#include "Weapons/Projectiles/Projectile.h"
|
|
#include "Weapons/Weapon.h"
|
|
#include "game/ModelIndices.h"
|
|
|
|
// Rage headers
|
|
#include "crskeleton/skeleton.h"
|
|
#include "crskeleton/skeletondata.h"
|
|
#include "fragmentnm/manager.h"
|
|
#include "fragmentnm/nm_channel.h "
|
|
#include "fragment/typeChild.h"
|
|
#include "fragment/typeGroup.h"
|
|
#include "fwanimation/animmanager.h"
|
|
#include "fwanimation/animdirector.h"
|
|
#include "fwanimation/directorcomponentsyncedscene.h"
|
|
#include "fwanimation/directorcomponentragdoll.h"
|
|
#include "parser/manager.h"
|
|
#include "physics/collider.h"
|
|
#include "physics/debugcontacts.h"
|
|
#include "physics/intersection.h"
|
|
#include "phbound/boundcapsule.h"
|
|
#include "phbound/boundGeomSecondSurface.h"
|
|
#include "phcore/materialmgr.h"
|
|
#include "pharticulated/articulatedcollider.h"
|
|
#include "phsolver/contactmgr.h"
|
|
#include "vectormath/classfreefuncsv.h"
|
|
|
|
PHYSICS_OPTIMISATIONS()
|
|
AI_VEHICLE_OPTIMISATIONS()
|
|
FRAGMENT_OPTIMISATIONS()
|
|
|
|
// The size of these pools will be set in the InitPool() calls, because
|
|
// the default size depends on the ped and/or vehicle pools, unknown until
|
|
// we've loaded the configuration file.
|
|
FW_INSTANTIATE_CLASS_POOL_SPILLOVER(phInstGta, 0, 0.56f, atHashString("phInstGta",0xeb9564e1));
|
|
FW_INSTANTIATE_CLASS_POOL_SPILLOVER(fragInstGta, 0, 0.15f, atHashString("fragInstGta",0x6851786c));
|
|
FW_INSTANTIATE_CLASS_POOL_SPILLOVER(fragInstNMGta, 0, 0.26f, atHashString("fragInstNMGta",0x2fead7a6));
|
|
|
|
PARAM(logusedragdolls, "Logs the maximum number of NM agents, rage ragdolls and anim fallbacks the get used.");
|
|
|
|
#if __DEV
|
|
phInst* spDebugBreakCar = NULL;
|
|
phInst* spDebugBreakRagdoll = NULL;
|
|
phInst* spDebugBreakGround = NULL;
|
|
#endif
|
|
|
|
#if __DEV
|
|
void InvalidStateDumpHelper(const phInst* pInst)
|
|
{
|
|
Displayf("InvalidStateDumpHelper - 0x%p - '%s'", pInst, pInst->GetArchetype() ? pInst->GetArchetype()->GetFilename() : NULL);
|
|
if(const CEntity* pEntity = CPhysics::GetEntityFromInst(pInst))
|
|
{
|
|
Displayf("\tEntity - 0x%p", pEntity);
|
|
Displayf("\tCEntity::GetType - %i",pEntity->GetType());
|
|
Displayf("\tCEntity::GetDebugName - '%s'",pEntity->GetDebugName());
|
|
Displayf("\tCEntity::GetModelName - '%s'",pEntity->GetModelName());
|
|
Displayf("\tCEntity::IsCollisionEnabled - %s",pEntity->IsCollisionEnabled() ? "T" : "F");
|
|
Displayf("\tCEntity::GetIsAnyFixedFlagSet - %s",pEntity->GetIsAnyFixedFlagSet() ? "T" : "F");
|
|
Displayf("\tCEntity::IsStatic - %s",pEntity->GetIsStatic() ? "T" : "F");
|
|
Vec3V position = pEntity->GetTransform().GetPosition();
|
|
Displayf("\tCEntity::GetTransform::GetPosition - <%f, %f, %f>",VEC3V_ARGS(position));
|
|
if(pEntity->GetIsPhysical())
|
|
{
|
|
const CPhysical* pPhysical = static_cast<const CPhysical*>(pEntity);
|
|
Vec3V velocity = RCC_VEC3V(pPhysical->GetVelocity());
|
|
Vec3V angVelocity = RCC_VEC3V(pPhysical->GetAngVelocity());
|
|
Displayf("\tCPhysical::GetVelocity - <%f, %f, %f>",VEC3V_ARGS(velocity));
|
|
Displayf("\tCPhysical::GetAngVelocity - <%f, %f, %f>",VEC3V_ARGS(angVelocity));
|
|
if(pPhysical->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pVehicle = static_cast<const CVehicle*>(pPhysical);
|
|
Displayf("\tVehicle Info:");
|
|
Displayf("\t\tCVehicle::IsDummy: %s", pVehicle->IsDummy() ? "T" : "F");
|
|
Displayf("\t\tCVehicle::IsSuperDummy: %s", pVehicle->IsSuperDummy() ? "T" : "F");
|
|
Displayf("\t\tCVehicle::IsClone: %s", pVehicle->IsNetworkClone() ? "T" : "F");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Displayf("\tNon-Physical Entity");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Displayf("\tNo Entity");
|
|
}
|
|
}
|
|
#endif // __DEV
|
|
|
|
atFixedArray<RegdPed, 40> g_SettledPeds;
|
|
|
|
/////////////////////////////////////////////////////
|
|
// phInstGta
|
|
/////////////////////////////////////////////////////
|
|
|
|
phInstGta::phInstGta (s32 nInstType) : phfwInst(nInstType)
|
|
{
|
|
}
|
|
|
|
phInstGta::~phInstGta()
|
|
{
|
|
}
|
|
|
|
//
|
|
// this function lets us skip out of collision tests
|
|
//
|
|
bool phInstGta::ShouldFindImpacts(const phInst* pOtherInst) const
|
|
{
|
|
FastAssert(pOtherInst);
|
|
#if __DEV
|
|
if(pOtherInst==spDebugBreakCar && this==spDebugBreakGround)
|
|
Printf("Found combo");
|
|
#endif
|
|
|
|
#if ENABLE_NETWORK_LOGGING
|
|
// pickups log the reasons why ShouldFindImpacts() may fail, as there are some bugs with ProcessPreComputeImpacts never getting called on pickups,
|
|
// making them uncollectable. This is a mission breaker.
|
|
CPickup *pPickup = NULL;
|
|
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
if (GetUserData() && pOtherInst->GetUserData())
|
|
{
|
|
if (GetClassType()==PH_INST_OBJECT && pOtherInst->GetClassType()==PH_INST_PED)
|
|
{
|
|
CObject *pObject = (CObject *)GetUserData();
|
|
CPed *pPed = (CPed *)pOtherInst->GetUserData();
|
|
|
|
if (pObject->m_nObjectFlags.bIsPickUp && pObject->GetNetworkObject() && pPed->IsLocalPlayer())
|
|
{
|
|
pPickup = (CPickup*)pObject;
|
|
}
|
|
}
|
|
else if (GetClassType()==PH_INST_PED && pOtherInst->GetClassType()==PH_INST_OBJECT)
|
|
{
|
|
CObject *pObject = (CObject *)pOtherInst->GetUserData();
|
|
CPed *pPed = (CPed *)GetUserData();
|
|
|
|
if (pObject->m_nObjectFlags.bIsPickUp && pObject->GetNetworkObject() && pPed->IsLocalPlayer())
|
|
{
|
|
pPickup = (CPickup*)pObject;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
if(GetInstFlag(FLAG_NO_COLLISION))
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("FLAG_NO_COLLISION set"));
|
|
return false;
|
|
}
|
|
|
|
if(CEntity* pEntity = (CEntity*)GetUserData())
|
|
{
|
|
if(pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = static_cast<CPhysical*>(pEntity);
|
|
CEntity* pOtherEntity = (CEntity*)pOtherInst->GetUserData();
|
|
const bool isOtherEntityPhysical = pOtherEntity && pOtherEntity->GetIsPhysical();
|
|
if(pEntity->GetIsTypePed())
|
|
{
|
|
CPed *pPed = static_cast<CPed*>(pEntity);
|
|
|
|
if(pEntity == pOtherEntity)
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("User datas the same"));
|
|
return false;
|
|
}
|
|
|
|
if(pPed->GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle))
|
|
{
|
|
if (pOtherEntity && pOtherEntity->GetIsTypeVehicle() && pOtherEntity == pPed->GetMyVehicle())
|
|
{
|
|
if(!pPed->GetPedResetFlag(CPED_RESET_FLAG_IsGoingToStandOnExitedVehicle))
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("CPED_RESET_FLAG_IsExitingVehicle set on player"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ))
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("CPED_CONFIG_FLAG_InVehicle set on player"));
|
|
return false;
|
|
}
|
|
|
|
// Optimization Sanity Check: FLAG_NO_COLLISION should be set in this case
|
|
Assert(pPed->GetRagdollState() <= RAGDOLL_STATE_ANIM_DRIVEN);
|
|
|
|
if(isOtherEntityPhysical)
|
|
{
|
|
if(pPed->GetRagdollOnCollisionIgnorePhysical() &&
|
|
pPed->GetRagdollOnCollisionIgnorePhysical() == pOtherEntity)
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("RagdollOnCollisionIgnoreEntity"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(pPhysical->GetIsTypeObject())
|
|
{
|
|
// don't let doors collide with other doors
|
|
if(GetInstFlag(FLAG_IS_DOOR) && !ShouldFindImpactsDoorHelper(pOtherInst))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// in MP collisions between certain vehicles can be disabled (eg ghost vehicles in a race)
|
|
if (NetworkInterface::IsGameInProgress() && pOtherEntity && NetworkInterface::AreInteractionsDisabledInMP(*pPhysical, *pOtherEntity))
|
|
{
|
|
NetworkInterface::RegisterDisabledCollisionInMP(*pPhysical, *pOtherEntity);
|
|
return false;
|
|
}
|
|
|
|
// Optimization Sanity Check: FLAG_IS_DOOR shouldn't be set on non CObjects
|
|
Assert(pPhysical->GetIsTypeObject() || !GetInstFlag(FLAG_IS_DOOR));
|
|
|
|
if (pPhysical->GetAnimDirector())
|
|
{
|
|
// are we in a synchronized scene?
|
|
fwAnimDirectorComponentSyncedScene* pSceneComponent = static_cast<fwAnimDirectorComponentSyncedScene*>(pPhysical->GetAnimDirector()->GetComponentByType(fwAnimDirectorComponent::kComponentTypeSyncedScene));
|
|
if (pSceneComponent && pSceneComponent->IsPlayingSyncedScene())
|
|
{
|
|
fwSyncedSceneId sceneId = pSceneComponent->GetSyncedSceneId();
|
|
fwEntity* sceneAttachParent = fwAnimDirectorComponentSyncedScene::GetSyncedSceneAttachEntity(sceneId);
|
|
// don't collide with the entity our scene is attached to
|
|
if (sceneAttachParent && sceneAttachParent==pOtherEntity)
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("In synced scene"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Going to eat a cache miss anyways for pOtherEntity->GetIsPhysical when the mirrored ShouldFindImpacts happens, might
|
|
// as well early out on ground.
|
|
if(isOtherEntityPhysical)
|
|
{
|
|
if (pPhysical->IsUsingKinematicPhysics())
|
|
{
|
|
CPhysical* pOtherPhys = static_cast<CPhysical*>(pOtherEntity);
|
|
if (pOtherPhys->IsUsingKinematicPhysics())
|
|
{
|
|
// Allow kinematic ped vs. kinematic ped collisions
|
|
if(!pPhysical->GetIsTypePed() || !pOtherPhys->GetIsTypePed())
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("Ped using kinematic physics"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if we are colliding with our attachment
|
|
// Do not use getAttachEntity since this will return NULL if we are detaching
|
|
fwAttachmentEntityExtension *attachExt = pPhysical->GetAttachmentExtension();
|
|
|
|
if(attachExt && attachExt->GetAttachParentForced())
|
|
{
|
|
if(attachExt->GetAttachParentForced()->GetCurrentPhysicsInst()==pOtherInst)
|
|
{
|
|
CVehicle *pParentVehicle = NULL;
|
|
if( ((CPhysical*)attachExt->GetAttachParentForced())->GetIsTypeVehicle())
|
|
{
|
|
pParentVehicle = (CVehicle *) attachExt->GetAttachParentForced();
|
|
}
|
|
|
|
// physically attached entities will collide together ONLY if flag is set
|
|
if(attachExt->GetAttachState()==ATTACH_STATE_PHYSICAL
|
|
|| attachExt->GetAttachState()==ATTACH_STATE_RAGDOLL)
|
|
{
|
|
if(!attachExt->GetAttachFlag(ATTACH_FLAG_DO_PAIRED_COLL))
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("Attached (physical/ragdoll)"));
|
|
return false;
|
|
}
|
|
}
|
|
else if((attachExt->GetAttachState() == ATTACH_STATE_DETACHING) && pParentVehicle && pParentVehicle->InheritsFromPlane()
|
|
&& pParentVehicle->GetStatus() == STATUS_WRECKED)
|
|
{
|
|
// Allow collision between wrecked plane and its pilot while he is detaching from the plane.
|
|
}
|
|
// all other attachment types don't collide with each other
|
|
// in most case GetUsesCollision() should return false so won't get this far anyway
|
|
else
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("Attached"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// do this check last to allow a chance to skip out earlier and let NoCollision reset
|
|
if(pPhysical->GetNoCollisionEntity() && pPhysical->TestNoCollision(pOtherInst))
|
|
{
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsFailed("TestNoCollision returned true"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOGGING_ONLY(if (pPickup) pPickup->ShouldFindImpactsSuccess());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool phInstGta::ShouldFindImpactsDoorHelper(const phInst* pOtherInst)
|
|
{
|
|
if(pOtherInst && pOtherInst->IsInLevel() && !CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInst->GetLevelIndex(), ArchetypeFlags::GTA_EXPLOSION_TYPE))
|
|
{
|
|
if(pOtherInst->GetInstFlag(FLAG_IS_DOOR))
|
|
{
|
|
return false;
|
|
}
|
|
else if(CPhysics::GetLevel()->IsFixed(pOtherInst->GetLevelIndex()))
|
|
{
|
|
return false;
|
|
}
|
|
else if(pOtherInst->GetUserData() && ((CEntity*)pOtherInst->GetUserData())->GetIsAnyFixedFlagSet())
|
|
{
|
|
return false;
|
|
}
|
|
else if(IsFragInst(pOtherInst) && !static_cast<const fragInst*>(pOtherInst)->GetBroken())
|
|
{
|
|
// If the other fragment is attached to the world don't collide with it. This should only be possible
|
|
// if art/script place these two objects together and we make the assumption that nothing should be
|
|
// blocking the doors path by default, only if it gets moved into its way.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE // Disable now that we've disabled second surface and its replacement.
|
|
#if !HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
void ModifyImpactsForSecondSurface(phInst* thisInst, phContactIterator impacts, float fInterp)
|
|
#else
|
|
void ModifyImpactsForSecondSurface(phInst*, phContactIterator, float )
|
|
#endif
|
|
{
|
|
#if !HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
if(fInterp != 0.0f)
|
|
{
|
|
impacts.Reset();
|
|
|
|
for( ; !impacts.AtEnd(); ++impacts)
|
|
{
|
|
// Eh? What purpose was this line serving?
|
|
//if (&impacts.GetManifold() && &impacts.GetContact())
|
|
{
|
|
if (phInst* otherInst = impacts.GetOtherInstance())
|
|
{
|
|
phBound* otherBound = otherInst->GetArchetype()->GetBound();
|
|
int otherBoundType = otherBound->GetType();
|
|
if (otherBoundType == phBound::BVH)
|
|
{
|
|
phBoundBVH* otherBvhBound = reinterpret_cast<phBoundBVH*>(otherBound);
|
|
|
|
if (otherBvhBound->GetHasSecondSurface())
|
|
{
|
|
int polyIndex = impacts.GetOtherElement();
|
|
const phPolygon& poly = otherBvhBound->GetPolygon(polyIndex);
|
|
|
|
phBoundGeometrySecondSurfacePolygonCalculator secondSurfaceCalculator;
|
|
Vector3 avSecondSurfacePolyVertices[POLY_MAX_VERTICES];
|
|
Vector3 vSecondSurfacePolyNormal;
|
|
float fSecondSurfacePolyArea;
|
|
secondSurfaceCalculator.SetSecondSurfaceInterp(fInterp);
|
|
secondSurfaceCalculator.ComputeSecondSurfacePolyVertsAndNormal(*otherBvhBound,poly,avSecondSurfacePolyVertices,vSecondSurfacePolyNormal,fSecondSurfacePolyArea);
|
|
const Vec3V topSurfacePolyUnitNormal=VECTOR3_TO_VEC3V(otherBvhBound->GetPolygonUnitNormal(polyIndex));
|
|
const Vec3V polyUnitNormal = RCC_VEC3V(vSecondSurfacePolyNormal);
|
|
|
|
Vec3V vertex0 = VECTOR3_TO_VEC3V(otherBvhBound->GetCompressedVertex(poly.GetVertexIndex(0)));
|
|
|
|
Mat34V lastMatrix = PHSIM->GetLastInstanceMatrix(thisInst);
|
|
phBound* thisBound = thisInst->GetArchetype()->GetBound();
|
|
if (thisBound->GetType() == phBound::COMPOSITE)
|
|
{
|
|
phBoundComposite* thisComposite = static_cast<phBoundComposite*>(thisBound);
|
|
if (phBound* thisPartBound = thisComposite->GetBound(impacts.GetMyComponent()))
|
|
{
|
|
thisBound = thisPartBound;
|
|
}
|
|
|
|
Mat34V partLast = thisComposite->GetLastMatrix(impacts.GetMyComponent());
|
|
Transform(lastMatrix, partLast);
|
|
}
|
|
|
|
Vec3V myLocal;
|
|
if (thisInst == impacts.GetInstanceA())
|
|
{
|
|
myLocal = impacts.GetContact().GetLocalPosA();
|
|
}
|
|
else
|
|
{
|
|
myLocal = impacts.GetContact().GetLocalPosB();
|
|
}
|
|
Vec3V contactAtLast = Transform(lastMatrix, myLocal);
|
|
Vec3V sweepDelta = contactAtLast - VECTOR3_TO_VEC3V(impacts.GetOtherPosition());
|
|
ScalarV depthAtLast = Dot(sweepDelta, polyUnitNormal);
|
|
Vec3V sweepTangent = depthAtLast * polyUnitNormal - sweepDelta;
|
|
|
|
ScalarV distToBottomPlane=Dot(polyUnitNormal,VECTOR3_TO_VEC3V(impacts.GetOtherPosition()-avSecondSurfacePolyVertices[0]));
|
|
Vec3V worldPosB=Add(VECTOR3_TO_VEC3V(impacts.GetOtherPosition()),Scale(polyUnitNormal,-distToBottomPlane));
|
|
|
|
// Adjust along tangent so that wheels are above the correct point
|
|
worldPosB += sweepTangent * distToBottomPlane * InvertSafe(depthAtLast, ScalarV(V_ZERO));
|
|
|
|
Vec3V extents = thisBound->GetBoundingBoxMax() - thisBound->GetBoundingBoxMin();
|
|
ScalarV maxSecondSurfaceDepth = Min(Min(SplatX(extents), SplatY(extents)), SplatZ(extents));
|
|
|
|
ScalarV secondSurfaceDepthB=-Dot(topSurfacePolyUnitNormal,worldPosB-vertex0);
|
|
secondSurfaceDepthB = Min(maxSecondSurfaceDepth, secondSurfaceDepthB);
|
|
ScalarV zero(V_ZERO);
|
|
BoolV cond=IsGreaterThan(secondSurfaceDepthB,zero);
|
|
secondSurfaceDepthB=SelectFT(cond, zero, secondSurfaceDepthB);
|
|
impacts.SetDepth(impacts.GetDepthV() - secondSurfaceDepthB);
|
|
impacts.SetMyNormal(vSecondSurfacePolyNormal);
|
|
impacts.SetOtherPositionDoNotUse(worldPosB);
|
|
impacts.SetUserData(secondSurfaceDepthB);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // !HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
}
|
|
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
|
|
void phInstGta::PreComputeImpacts(phContactIterator impacts)
|
|
{
|
|
if(!impacts.GetMyInstance() || !PHLEVEL->LegitLevelIndex(impacts.GetMyInstance()->GetLevelIndex()))
|
|
{
|
|
// If our instance is now invalid, don't do anything
|
|
return;
|
|
}
|
|
|
|
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
ModifyImpactsForSecondSurface(this, impacts, GetBoundGeomIntersectionSecondSurfaceInterpolation());
|
|
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
|
|
phInst::PreComputeImpacts(impacts);
|
|
|
|
if(GetUserData() && ((CEntity *)GetUserData())->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)GetUserData();
|
|
if(pPhysical->DoPreComputeImpactsTest())
|
|
{
|
|
impacts.Reset();
|
|
while(!impacts.AtEnd())
|
|
{
|
|
if(pPhysical->TestNoCollision(impacts.GetOtherInstance()))
|
|
{
|
|
impacts.DisableImpact();
|
|
}
|
|
impacts++;
|
|
}
|
|
impacts.Reset();
|
|
}
|
|
|
|
pPhysical->ProcessPreComputeImpacts(impacts);
|
|
}
|
|
}
|
|
|
|
|
|
Vec3V_Out phInstGta::GetExternallyControlledVelocity () const
|
|
{
|
|
Vec3V velocity(V_ZERO);
|
|
|
|
CEntity* pEntity = (CEntity*)GetUserData();
|
|
if(pEntity)
|
|
{
|
|
phInst* pInst = pEntity->GetCurrentPhysicsInst();
|
|
phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(pInst);
|
|
if(pInst && pInst->IsInLevel() && pInst != this && pCollider)
|
|
{
|
|
velocity = pCollider->GetVelocity();
|
|
}
|
|
else
|
|
{
|
|
bool bInstActive = PHLEVEL->LegitLevelIndex(GetLevelIndex()) && PHLEVEL->IsActive(GetLevelIndex());
|
|
if (!bInstActive && pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)pEntity;
|
|
// ***HACK***
|
|
// This logic should probably be contained within CPhysical::GetVelocity but it too late in the project to make such sweeping changes.
|
|
// Here (and the equivalent function in fragInstGta and fragInstNmGta) seemed to be the only place where this logic was required (although there are probably
|
|
// other areas that would benefit) so the change was made here.
|
|
if(pPhysical->GetIsAttached() && !pPhysical->GetIsAttachedToGround() && pPhysical->GetAttachParent() != NULL && static_cast<CEntity*>(pPhysical->GetAttachParent())->GetIsPhysical() &&
|
|
(pPhysical->GetIsTypePed() || (pPhysical->GetIsTypeObject() && static_cast<CEntity*>(pPhysical->GetAttachParent())->GetIsTypeVehicle() && static_cast<CVehicle*>(pPhysical->GetAttachParent())->InheritsFromTrain())))
|
|
{
|
|
CPhysical* pAttachParent = static_cast<CPhysical*>(pPhysical->GetAttachParent());
|
|
pPhysical->CalculateGroundVelocity(pAttachParent, pPhysical->GetTransform().GetPosition(), 0, velocity);
|
|
|
|
// For peds climbing out of certain vehicles (such as the Titan) we need to set a valid velocity while they are attached
|
|
// and inactive so that they will activate any peds standing waiting at the exit point.
|
|
if(MagSquared(velocity).Getf() < SMALL_FLOAT && pAttachParent->GetIsTypeVehicle()
|
|
&& static_cast<CVehicle*>(pAttachParent)->GetLayoutInfo()->GetClimbUpAfterOpenDoor()
|
|
&& pPhysical->GetAttachState()==ATTACH_STATE_PED_EXIT_CAR)
|
|
{
|
|
velocity = VECTOR3_TO_VEC3V(pPhysical->GetAnimatedVelocity());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
velocity = RCC_VEC3V(pPhysical->GetVelocity());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return velocity;
|
|
}
|
|
|
|
|
|
Vec3V_Out phInstGta::GetExternallyControlledAngVelocity () const
|
|
{
|
|
Vector3 angVelocity(ORIGIN);
|
|
|
|
CEntity* pEntity = (CEntity*)GetUserData();
|
|
if(pEntity)
|
|
{
|
|
phInst* pInst = pEntity->GetCurrentPhysicsInst();
|
|
phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(pInst);
|
|
if(pInst && pInst->IsInLevel() && pInst != this && pCollider)
|
|
{
|
|
angVelocity = RCC_VECTOR3(pCollider->GetAngVelocity());
|
|
}
|
|
else
|
|
{
|
|
bool bInstActive = PHLEVEL->LegitLevelIndex(GetLevelIndex()) && PHLEVEL->IsActive(GetLevelIndex());
|
|
if (!bInstActive && pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)pEntity;
|
|
angVelocity.Set(pPhysical->GetAngVelocity());
|
|
}
|
|
}
|
|
}
|
|
|
|
return RCC_VEC3V(angVelocity);
|
|
}
|
|
|
|
|
|
phInst *phInstGta::PrepareForActivation(phCollider **collider, phInst* otherInst, const phConstraintBase * constraint)
|
|
{
|
|
CEntity* pThisEntity = CPhysics::GetEntityFromInst(this);
|
|
// // FA: temp asserts to cache vehicle cache issues
|
|
//#if __ASSERT
|
|
// if (pThisEntity && pThisEntity->GetIsTypeVehicle())
|
|
// {
|
|
// CVehicle* veh = (CVehicle*)pThisEntity;
|
|
// Assert(!veh->GetIsInPopulationCache());
|
|
// }
|
|
//
|
|
// CEntity* other = CPhysics::GetEntityFromInst(otherInst);
|
|
// if (other && other->GetIsTypeVehicle())
|
|
// {
|
|
// CVehicle* veh = (CVehicle*)other;
|
|
// Assert(!veh->GetIsInPopulationCache());
|
|
// }
|
|
//#endif
|
|
|
|
#if NAVMESH_EXPORT
|
|
const bool bExportingCollision = CNavMeshDataExporter::IsExportingCollision();
|
|
#else
|
|
const bool bExportingCollision = false;
|
|
#endif
|
|
|
|
if(pThisEntity)
|
|
{
|
|
// Allow the object to activate and check for fixed collision next frame
|
|
/*if(pThisEntity->GetIsPhysical())
|
|
{
|
|
// If we aren't fixed, check for nearby collision
|
|
if(!pThisEntity->GetIsFixedUntilCollisionFlagSet())
|
|
{
|
|
static_cast<CPhysical*>(pThisEntity)->UpdateFixedWaitingForCollision(true);
|
|
Assert(CPhysics::GetSimulator()->GetCollider(this)==NULL);
|
|
}
|
|
}*/
|
|
// don't let this instance activate if the fixed flag is set
|
|
if(pThisEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// flag can also be set on the phInst to not activate
|
|
if(GetInstFlag(FLAG_NEVER_ACTIVATE))
|
|
return NULL;
|
|
|
|
// Need to know if someone is enabling the ped capsule after we've specifically disabled it.
|
|
// Assertf(!pThisEntity->GetIsTypePed() || !((CPed*)pThisEntity)->GetPedResetFlag(CPED_RESET_FLAG_DisablePedCapsule), "Should not enable the ped capsule whilst the DisablePedCapsule reset flag is set!");
|
|
|
|
CEntity* pOtherEntity = NULL;
|
|
|
|
if(otherInst && otherInst->GetUserData())
|
|
{
|
|
pOtherEntity = CPhysics::GetEntityFromInst(otherInst);
|
|
|
|
if( pOtherEntity )
|
|
{
|
|
// Kinematic peds shouldn't activate when bumped by the player.
|
|
if(pThisEntity->GetIsTypePed() && static_cast<CPed*>(pThisEntity)->IsUsingKinematicPhysics()
|
|
&& pOtherEntity->GetIsTypePed() && static_cast<CPed*>(pOtherEntity)->IsPlayer())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if((pThisEntity->GetIsTypeVehicle() || pThisEntity->GetIsTypeObject()) && pOtherEntity->GetIsTypePed())
|
|
{
|
|
// we don't want to activate non-doors that pathfinding is trying to avoid
|
|
if(!static_cast<CObject*>(pThisEntity)->IsADoor())
|
|
{
|
|
CPed* pPed = static_cast<CPed*>(pOtherEntity);
|
|
|
|
// Don't activate if we're being hit by a ped that can't push us
|
|
if (pThisEntity->GetIsTypeObject() && pPed->ShouldObjectBeImpossibleToPush(*pThisEntity, *this, PHSIM->GetCollider(this), 0))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Don't allow animated NPCs to activate vehicles or objects.
|
|
if(!pPed->IsPlayer() && pPed->GetRagdollState()<RAGDOLL_STATE_ANIM_DRIVEN)
|
|
{
|
|
// If we are stuck and running into an object then allow it to be activated after a short amount of time
|
|
if(pThisEntity->GetIsTypeObject() && pPed->GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().GetStaticCounter() >= CStaticMovementScanner::ms_iStaticActivateObjectLimit)
|
|
{
|
|
|
|
}
|
|
else if (CPathServerGta::ShouldPedAvoidObject(pPed, pThisEntity))
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
phInst *pReturn = phInst::PrepareForActivation(collider, otherInst, constraint);
|
|
|
|
if(pReturn)
|
|
{
|
|
CEntity* pEntity = CPhysics::GetEntityFromInst(this);
|
|
|
|
if(pEntity)
|
|
{
|
|
if(pEntity->GetIsPhysical())
|
|
{
|
|
if(!((CDynamicEntity *) pEntity)->GetIsOnSceneUpdate())
|
|
((CDynamicEntity *) pEntity)->AddToSceneUpdate();
|
|
}
|
|
|
|
if(pEntity->GetIsTypeObject())
|
|
{
|
|
CEntity* pOtherEntity = NULL;
|
|
|
|
if( otherInst && otherInst->GetUserData() )
|
|
{
|
|
pOtherEntity = CPhysics::GetEntityFromInst(otherInst);
|
|
}
|
|
|
|
if(!((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted)
|
|
{
|
|
if(!bExportingCollision)
|
|
{
|
|
g_CollisionAudioEntity.ReportUproot(pEntity);
|
|
}
|
|
|
|
// Climbable objects; need deactivating when uprooted
|
|
if(((CObject*)pEntity)->IsADoor()==false && pEntity->GetBaseModelInfo()->GetIsClimbableByAI())
|
|
{
|
|
Vector3 vCenter;
|
|
((CObject*)pEntity)->GetBoundCentre(vCenter);
|
|
CPathServer::DeferredDisableClimbAtPosition(vCenter, false, true);
|
|
}
|
|
}
|
|
|
|
((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted = true;
|
|
|
|
// need to store what woke this entity up if it is a world object
|
|
((CObject*)pEntity)->ObjectHasBeenMoved(pOtherEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pReturn;
|
|
}
|
|
|
|
void phInstGta::OnActivate(phInst* otherInst)
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->OnActivate(this,otherInst);
|
|
}
|
|
|
|
if(CExplosionManager::InsideExplosionUpdate())
|
|
{
|
|
CExplosionManager::RegisterExplosionActivation(GetLevelIndex());
|
|
}
|
|
}
|
|
|
|
bool phInstGta::PrepareForDeactivation(bool UNUSED_PARAM(colliderManagedBySim), bool UNUSED_PARAM(forceDeactivate))
|
|
{
|
|
// Don't allow peds to deactivate when standing near water unless they are attaching to something or are in
|
|
// low LOD buoyancy mode.
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
// Don't prevent deactivation if we're fixed
|
|
if(!pEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
// Don't prevent deactivation of peds who are in low physics lod based upon proximity to water.
|
|
if(pEntity->GetIsTypePed() && (((CPed*)pEntity)->GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics)) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = static_cast<CPhysical*>(pEntity);
|
|
|
|
// If we are attached, we want to allow deactivation.
|
|
fwAttachmentEntityExtension* pAttachExt = pEntity->GetAttachmentExtension();
|
|
bool bIsAttaching = pAttachExt && pAttachExt->GetIsAttached() &&
|
|
(pAttachExt->IsAttachStateBasicDerived() || pAttachExt->GetAttachState()==ATTACH_STATE_WORLD);
|
|
|
|
if(!bIsAttaching && pPhysical->m_nPhysicalFlags.bPossiblyTouchesWaterIsUpToDate && pEntity->m_nFlags.bPossiblyTouchesWater
|
|
&& !pPhysical->m_Buoyancy.m_buoyancyFlags.bLowLodBuoyancyMode)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bReturn = true;//phInst::PrepareForDeactivation(colliderManagedBySim);
|
|
/*
|
|
if(GetUserData() && bReturn)
|
|
{
|
|
if(((CEntity *)GetUserData())->GetIsPhysical())
|
|
{
|
|
// if we've just gone static, test if we still require ProcessControl
|
|
if(!((CEntity *)GetUserData())->RequiresProcessControl())
|
|
{
|
|
((CPhysical *)GetUserData())->RemoveFromProcessControlList();
|
|
}
|
|
}
|
|
|
|
#if __DEV
|
|
if(((CEntity *)GetUserData())->GetIsTypeVehicle())
|
|
((CVehicle *)GetUserData())->m_pDebugColliderDO_NOT_USE = NULL;
|
|
#endif
|
|
}
|
|
*/
|
|
return bReturn;
|
|
}
|
|
|
|
void phInstGta::OnDeactivate()
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->OnDeactivate(this);
|
|
}
|
|
}
|
|
|
|
|
|
#if __DEV
|
|
void phInstGta::InvalidStateDump() const
|
|
{
|
|
phInst::InvalidStateDump();
|
|
InvalidStateDumpHelper(this);
|
|
}
|
|
#endif // __DEV
|
|
|
|
#if __BANK
|
|
extern bool gbVehicleImpulseModification;
|
|
#endif
|
|
|
|
// The directory in which xml data for clip sets is stored
|
|
const char * g_VehicleFragImpulseFunctionFile = "common:/data/VehicleFragImpulseFunction.xml";
|
|
|
|
VehicleFragImpulseFunction g_VehicleFragImpulseFunction;
|
|
|
|
void fragInstGta::LoadVehicleFragImpulseFunction()
|
|
{
|
|
PARSER.LoadObject(g_VehicleFragImpulseFunctionFile, "xml", g_VehicleFragImpulseFunction);
|
|
}
|
|
|
|
#if __BANK
|
|
void fragInstGta::SaveVehicleFragImpulseFunction()
|
|
{
|
|
PARSER.SaveObject(g_VehicleFragImpulseFunctionFile, "xml", &g_VehicleFragImpulseFunction);
|
|
}
|
|
#endif // __BANK
|
|
|
|
void fragInstGta::ReleaseVehicleFragImpulseFunction()
|
|
{
|
|
g_VehicleFragImpulseFunction.m_Ranges.Reset();
|
|
}
|
|
|
|
void fragInstGta::AddVehicleFragImpulseFunctionWidgets(bkBank& BANK_ONLY(bank))
|
|
{
|
|
#if __BANK
|
|
bank.PushGroup("Vehicle Frag Impulse Function");
|
|
PARSER.AddWidgets(bank, &g_VehicleFragImpulseFunction);
|
|
bank.AddButton("Load", datCallback(CFA(fragInstGta::LoadVehicleFragImpulseFunction)));
|
|
bank.AddButton("Save", datCallback(CFA(fragInstGta::SaveVehicleFragImpulseFunction)));
|
|
bank.PopGroup();
|
|
#endif
|
|
}
|
|
|
|
ScalarV_Out fragInstGta::ModifyImpulseMag (int iMyComponent, int iOtherComponent, int iNumComponentImpulses, ScalarV_In fImpulseMagSquared, const phInst* pOtherInst) const
|
|
{
|
|
//Set the default breaking impulse value to -1
|
|
//(this forces the impulse used to determine if the frag breaks to be equal to the contact impulse).
|
|
ScalarV breakingImpulse(V_NEGONE);
|
|
|
|
//If we've got no frag object then just return and use the raw impulse as the impulse for the breaking test.
|
|
//We might still have a frag in the contact pair but if we do have a frag then it must be a vehicle or a
|
|
//ped in ragdoll mode. We can ignore ragdolling peds because they don't break so are unaffected by
|
|
//frag tuning. We can ignore vehicles too because their breaking strengths are tuned in code and we can
|
|
//happily rely on the contact impulse to determine if the vehicle breaks apart.
|
|
//Non-frags can obviously be ignored because logically they can never break apart.
|
|
if(!pOtherInst)
|
|
{
|
|
return breakingImpulse;
|
|
}
|
|
|
|
//Get the flags that describe the types of object that can break/damage the frag.
|
|
const int iOtherClassType = pOtherInst->GetClassType();
|
|
|
|
fragPhysicsLOD& rMyPhysics = *GetTypePhysics();
|
|
int iMyGroupIndex = rMyPhysics.GetAllChildren()[iMyComponent]->GetOwnerGroupPointerIndex();
|
|
fragTypeGroup* pMyGroup = rMyPhysics.GetAllGroups()[iMyGroupIndex];
|
|
|
|
//We've determined that we've got a frag object that will break apart if a breaking impulse
|
|
//exceeds a strength set by frag tuning. We want to compute a breaking impulse value
|
|
//to help the prop artists more intuitively set breaking strengths.
|
|
//Take a look at what the frag object has hit and work out a breaking impulse accordingly.
|
|
switch(iOtherClassType)
|
|
{
|
|
case PH_INST_PED:
|
|
{
|
|
//Our frag object has hit a bound of an animated ped.
|
|
//We could end up with multiple ped bounds colliding with the same frag component
|
|
//so only consider the main bound of the ped because we don't want to count the
|
|
//ped multiple times when summing all the breaking impulses.
|
|
const CEntity* pEntity=static_cast<const CEntity*>(pOtherInst->GetUserData());
|
|
if(pEntity && 0==iOtherComponent)
|
|
{
|
|
//Our frag object has hit the main bound of an animated ped.
|
|
//We're going to work out an approximate ped momentum based on the move speed of the ped.
|
|
//Based on the move state of the ped and a ped mass of 50kg then we can write down
|
|
//ped momenta based on a walk speed of 3.5mph, a run speed of 5mph, a sprint speed of 10mph.
|
|
//In kgms^1 that equates to 75kgms^-1 for walking, 100kgms^-1 for running, 200kgms^-1 for sprinting.
|
|
//We're going to ignore the contact normals and the speed of the frag object and see how we get on.
|
|
Assertf(pEntity->GetIsTypePed(), "Non-frag inst should be ped");
|
|
const CPed* pPed=static_cast<const CPed*>(pEntity);
|
|
|
|
//float sprintingSpeed = 4.4704f; // 10.0 mph = 4.4704 mps
|
|
float runningSpeed = 2.2352f; // 5.0 mph = 2.2352 mps
|
|
float walkingSpeed = 1.5646f; // 3.5 mph = 1.5646 mps
|
|
float idleSpeed = 0.0f; // in case we need an epsilon
|
|
|
|
float velocitySquared = pPed->GetVelocity().Mag2();
|
|
if( pPed->GetPedResetFlag(CPED_RESET_FLAG_ForceRunningSpeedForFragSmashing) )
|
|
{
|
|
// Higher impulse for custom breaking
|
|
breakingImpulse=ScalarVFromF32(250);
|
|
}
|
|
else if (velocitySquared > runningSpeed*runningSpeed)
|
|
{
|
|
// Equivalent of sprinting
|
|
breakingImpulse=ScalarVFromF32(200);
|
|
}
|
|
else if (velocitySquared > walkingSpeed*walkingSpeed)
|
|
{
|
|
// Equivalent of running
|
|
breakingImpulse=ScalarVFromF32(100);
|
|
}
|
|
else if (velocitySquared > idleSpeed*idleSpeed)
|
|
{
|
|
// Equivalent of walking
|
|
breakingImpulse=ScalarVFromF32(75);
|
|
}
|
|
else
|
|
{
|
|
breakingImpulse=ScalarV(V_ZERO);
|
|
}
|
|
|
|
#if __BANK
|
|
if(CPhysics::ms_bPrintBreakingImpulsesForPeds)
|
|
{
|
|
Displayf("Ped contact breakingImpulse %f", breakingImpulse.Getf());
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
//Frag hit a bound other the than the main animated ped bound, or we have no entity.
|
|
//Just ignore this collision.
|
|
breakingImpulse=ScalarV(V_ZERO);
|
|
}
|
|
// Since the out-impulse isn't based on the in-impulse at all we need to make sure the impulse is distributed between the contacts with impulses.
|
|
// Otherwise the total impulse is dependent on the arbitrary number of contacts.
|
|
breakingImpulse = InvScaleSafe(breakingImpulse,ScalarVFromF32((float)iNumComponentImpulses),ScalarV(V_ZERO));
|
|
|
|
breakingImpulse *= ScalarV(pMyGroup->GetPedScale());
|
|
|
|
break;
|
|
}
|
|
|
|
case PH_INST_VEH:
|
|
case PH_INST_FRAG_VEH:
|
|
{
|
|
// Only clamp breaking impulses for fragments still attached to the world. Since fixed root props are articulated they're marked as broken always.
|
|
bool isFixedRoot = GetCacheEntry() && GetCacheEntry()->GetHierInst()->body && GetCacheEntry()->GetHierInst()->body->RootIsFixed();
|
|
if ((!GetBroken() || isFixedRoot) BANK_ONLY(&& gbVehicleImpulseModification))
|
|
{
|
|
atArray<VehicleFragImpulseRange>& impulseTable = g_VehicleFragImpulseFunction.m_Ranges;
|
|
|
|
breakingImpulse=ScalarV(V_ZERO);
|
|
|
|
ScalarV priorThreshold(V_ZERO);
|
|
int index;
|
|
for (index = 0; index < impulseTable.GetCount(); ++index)
|
|
{
|
|
ScalarV inputThreshold(impulseTable[index].m_InputThreshold);
|
|
if (IsTrue(fImpulseMagSquared < inputThreshold * inputThreshold))
|
|
{
|
|
breakingImpulse = Ramp(Sqrt(fImpulseMagSquared),
|
|
priorThreshold, inputThreshold,
|
|
ScalarVFromF32(impulseTable[index].m_OutputMin), ScalarVFromF32(impulseTable[index].m_OutputMax));
|
|
break;
|
|
}
|
|
priorThreshold = inputThreshold;
|
|
}
|
|
|
|
const CEntity* pEntity=static_cast<const CEntity*>(pOtherInst->GetUserData());
|
|
Assertf(pEntity && pEntity->GetIsTypeVehicle(), "Non-frag inst should be vehicle");
|
|
const CVehicle* pVehicle=static_cast<const CVehicle*>(pEntity);
|
|
|
|
//also allow us to set the max value if the bActAsIfHighSpeedForFragSmashing vehicle reset flag has been set
|
|
if (index == impulseTable.GetCount() && index > 0)
|
|
{
|
|
breakingImpulse = ScalarVFromF32(impulseTable[index - 1].m_OutputMax);
|
|
}
|
|
else if (pVehicle && pVehicle->m_nVehicleFlags.bActAsIfHighSpeedForFragSmashing && impulseTable.GetCount() > 0)
|
|
{
|
|
breakingImpulse = ScalarVFromF32(impulseTable[impulseTable.GetCount()-1].m_OutputMax);
|
|
}
|
|
|
|
#if __BANK
|
|
if(CPhysics::ms_bPrintBreakingImpulsesForVehicles && IsTrue(fImpulseMagSquared > ScalarV(V_ZERO)))
|
|
{
|
|
Displayf("Vehicle contact impulse %f : breakingImpulse %f", sqrtf(fImpulseMagSquared.Getf()), breakingImpulse.Getf());
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
breakingImpulse = Sqrt(fImpulseMagSquared);
|
|
}
|
|
|
|
breakingImpulse *= ScalarV(pMyGroup->GetVehicleScale());
|
|
|
|
break;
|
|
}
|
|
|
|
case PH_INST_FRAG_PED:
|
|
{
|
|
breakingImpulse = Scale(Sqrt(fImpulseMagSquared), ScalarV(pMyGroup->GetRagdollScale()));
|
|
|
|
break;
|
|
}
|
|
|
|
case PH_INST_PROJECTILE:
|
|
{
|
|
breakingImpulse = Scale(Sqrt(fImpulseMagSquared), ScalarV(pMyGroup->GetWeaponScale()));
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
breakingImpulse = Scale(Sqrt(fImpulseMagSquared), ScalarV(pMyGroup->GetObjectScale()));
|
|
}
|
|
}
|
|
//Need to extend this as we see fit.
|
|
|
|
return breakingImpulse;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
phInstGtaBoat::phInstGtaBoat (s32 nInstType)
|
|
: phInstGta(nInstType)
|
|
{
|
|
m_LastMatrix.Identity();
|
|
}
|
|
|
|
phInstGtaBoat::phInstGtaBoat (datResource & rsc)
|
|
: phInstGta(rsc)
|
|
{
|
|
m_LastMatrix.Identity();
|
|
}
|
|
|
|
|
|
const Matrix34& phInstGtaBoat::GetLastMatrix() const
|
|
{
|
|
return m_LastMatrix;
|
|
}
|
|
|
|
void phInstGtaBoat::SetLastMatrix (const Matrix34& last)
|
|
{
|
|
m_LastMatrix = last;
|
|
}
|
|
*/
|
|
|
|
/////////////////////////////////////////////////////
|
|
// fragInstGta
|
|
/////////////////////////////////////////////////////
|
|
|
|
|
|
fragInstGta::fragInstGta(s32 instType, const fragType* type, const Matrix34& matrix, u32 guid) : phfwFragInst(type, matrix, guid), m_classType(instType)
|
|
{
|
|
m_nDontBreakCompFlags = 0;
|
|
}
|
|
|
|
fragInstGta::fragInstGta () : phfwFragInst(), m_classType(PH_INST_FRAG_GTA)
|
|
{
|
|
// assume anything created with the default constructor is an object (from the fragment cache probably)
|
|
m_nDontBreakCompFlags = 0;
|
|
}
|
|
|
|
fragInstGta::~fragInstGta()
|
|
{
|
|
}
|
|
|
|
|
|
void fragInstGta::SetDontBreakFlagAllChildren(s64 nChild)
|
|
{
|
|
Assert(nChild >= 0 && nChild < GetTypePhysics()->GetNumChildren());
|
|
|
|
const int MAX_NUM_GROUPS_TO_VISIT = 256;
|
|
int groupsToVisit[MAX_NUM_GROUPS_TO_VISIT];
|
|
int numGroupsToVisit = 1;
|
|
groupsToVisit[0] = GetTypePhysics()->GetAllChildren()[nChild]->GetOwnerGroupPointerIndex();
|
|
|
|
while (numGroupsToVisit > 0)
|
|
{
|
|
const fragTypeGroup* pGroup = GetTypePhysics()->GetAllGroups()[groupsToVisit[--numGroupsToVisit]];
|
|
|
|
for (int childIndex = 0; childIndex < pGroup->GetNumChildren(); ++childIndex)
|
|
{
|
|
// set flag so this can't break off until we want it to.
|
|
SetDontBreakFlag(BIT(static_cast<s64>(childIndex + pGroup->GetChildFragmentIndex())));
|
|
}
|
|
|
|
for (int groupIndex = 0; numGroupsToVisit < MAX_NUM_GROUPS_TO_VISIT && groupIndex < pGroup->GetNumChildGroups(); ++groupIndex)
|
|
{
|
|
// push this group onto the stack for visitation
|
|
groupsToVisit[numGroupsToVisit++] = groupIndex + pGroup->GetChildGroupsPointersIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void fragInstGta::ClearDontBreakFlagAllChildren(s64 nChild)
|
|
{
|
|
Assert(nChild >= 0 && nChild < GetTypePhysics()->GetNumChildren());
|
|
|
|
const int MAX_NUM_GROUPS_TO_VISIT = 256;
|
|
int groupsToVisit[MAX_NUM_GROUPS_TO_VISIT];
|
|
int numGroupsToVisit = 1;
|
|
groupsToVisit[0] = GetTypePhysics()->GetAllChildren()[nChild]->GetOwnerGroupPointerIndex();
|
|
|
|
while (numGroupsToVisit > 0)
|
|
{
|
|
const fragTypeGroup* pGroup = GetTypePhysics()->GetAllGroups()[groupsToVisit[--numGroupsToVisit]];
|
|
|
|
for (int childIndex = 0; childIndex < pGroup->GetNumChildren(); ++childIndex)
|
|
{
|
|
// set flag so this can't break off until we want it to.
|
|
ClearDontBreakFlag(BIT(static_cast<s64>(childIndex + pGroup->GetChildFragmentIndex())));
|
|
}
|
|
|
|
for (int groupIndex = 0; numGroupsToVisit < MAX_NUM_GROUPS_TO_VISIT && groupIndex < pGroup->GetNumChildGroups(); ++groupIndex)
|
|
{
|
|
// push this group onto the stack for visitation
|
|
groupsToVisit[numGroupsToVisit++] = groupIndex + pGroup->GetChildGroupsPointersIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// this function lets us skip out of collision tests
|
|
//
|
|
bool fragInstGta::ShouldFindImpacts(const phInst* pOtherInst) const
|
|
{
|
|
#if __DEV
|
|
if(this==spDebugBreakCar && pOtherInst==spDebugBreakGround)
|
|
Printf("Found combo");
|
|
if(this==spDebugBreakCar && pOtherInst==spDebugBreakRagdoll)
|
|
Printf("Found combo");
|
|
#endif
|
|
|
|
if (!fragInst::ShouldFindImpacts(pOtherInst))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CEntity* pEntity = (CEntity*)GetUserData();
|
|
if(pEntity && pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = static_cast<CPhysical*>(pEntity);
|
|
|
|
const CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInst);
|
|
if(pOtherInst && GetUserData() == pOtherInst->GetUserData())
|
|
return false;
|
|
|
|
const bool isOtherEntityPhysical = pOtherEntity && pOtherEntity->GetIsPhysical();
|
|
if(isOtherEntityPhysical)
|
|
{
|
|
const CPhysical* pOtherPhys = static_cast<const CPhysical*>(pOtherEntity);
|
|
// Check if we are colliding with our attachment
|
|
// Do not use getAttachEntity since this will return NULL if we are detaching
|
|
fwAttachmentEntityExtension *attachExt = pPhysical->GetAttachmentExtension();
|
|
|
|
if(attachExt && attachExt->GetAttachParentForced())
|
|
{
|
|
if(attachExt->GetAttachParentForced()->GetCurrentPhysicsInst()==pOtherInst)
|
|
{
|
|
// physically attached entities will collide together ONLY if flag is set
|
|
if(attachExt->GetAttachState()==ATTACH_STATE_PHYSICAL
|
|
|| attachExt->GetAttachState()==ATTACH_STATE_RAGDOLL)
|
|
{
|
|
if(!attachExt->GetAttachFlag(ATTACH_FLAG_DO_PAIRED_COLL))
|
|
return false;
|
|
}
|
|
// all other attachment types don't collide with each other
|
|
// in most case GetUsesCollision() should return false so won't get this far anyway
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (pPhysical->IsUsingKinematicPhysics())
|
|
{
|
|
if (pOtherPhys->IsUsingKinematicPhysics())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(pPhysical->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pPhysical);
|
|
if(!pVehicle->ProcessShouldFindImpactsWithPhysical(pOtherInst))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(pVehicle->GetVehicleType()==VEHICLE_TYPE_TRAIN)
|
|
{
|
|
// Don't let trains collide with other trains unless at least one of them is active (we might be
|
|
// dropping a carriage onto another as in the "Big Score" heist mission in GTA5.
|
|
if(pOtherInst && pOtherPhys && pOtherPhys->GetIsTypeVehicle() &&
|
|
static_cast<const CVehicle*>(pOtherPhys)->GetVehicleType()==VEHICLE_TYPE_TRAIN)
|
|
{
|
|
u32 nLevelIndexThis = GetLevelIndex();
|
|
u32 nLevelIndexOther = pOtherInst->GetLevelIndex();
|
|
if((CPhysics::GetLevel()->IsInLevel(nLevelIndexThis) && CPhysics::GetLevel()->IsActive(nLevelIndexThis))
|
|
|| (CPhysics::GetLevel()->IsInLevel(nLevelIndexOther) && CPhysics::GetLevel()->IsActive(nLevelIndexOther)) )
|
|
{
|
|
// Do nothing in this case in case some later check should force us to return false.
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(pPhysical->GetIsTypeObject())
|
|
{
|
|
// Don't let doors collide with other doors.
|
|
// Note: calls into phInstGta to share code.
|
|
if(GetInstFlag(phInstGta::FLAG_IS_DOOR) && !phInstGta::ShouldFindImpactsDoorHelper(pOtherInst))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// in MP collisions between certain vehicles can be disabled (eg ghost vehicles in a race)
|
|
if (NetworkInterface::IsGameInProgress() && pOtherEntity && NetworkInterface::AreInteractionsDisabledInMP(*pPhysical, *pOtherEntity))
|
|
{
|
|
NetworkInterface::RegisterDisabledCollisionInMP(*pPhysical, *pOtherEntity);
|
|
return false;
|
|
}
|
|
|
|
Assert(pPhysical->GetIsTypeObject() || !GetInstFlag(phInstGta::FLAG_IS_DOOR));
|
|
|
|
// do this last to give a chance to skip out, and let NoCollision be reset
|
|
if(const fwEntity* pNoCollisionEntity = pPhysical->GetNoCollisionEntity())
|
|
{
|
|
if(pPhysical->TestNoCollision(pOtherInst))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Optimization for vehicle explosions: If both objects aren't colliding with the parent vehicle then disable collision between them
|
|
// As soon as one of them clears the vehicle they'll start colliding again
|
|
if( pPhysical->GetIsTypeObject() && pOtherEntity && pOtherEntity->GetIsTypeObject())
|
|
{
|
|
const CObject* pObject = static_cast<const CObject*>(pPhysical);
|
|
const CObject* pOtherObject = static_cast<const CObject*>(pOtherEntity);
|
|
if(pObject->m_nObjectFlags.bVehiclePart &&
|
|
pOtherObject->GetFragParent() == pNoCollisionEntity &&
|
|
pOtherObject->GetNoCollisionEntity() == pNoCollisionEntity)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void fragInstGta::PreComputeImpacts(phContactIterator impacts)
|
|
{
|
|
#if __DEV
|
|
if(this==spDebugBreakCar)
|
|
Printf("Found combo");
|
|
#endif
|
|
|
|
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
ModifyImpactsForSecondSurface(this, impacts, GetBoundGeomIntersectionSecondSurfaceInterpolation());
|
|
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
|
|
fragInst::PreComputeImpacts(impacts);
|
|
|
|
if(!impacts.GetMyInstance() || !PHLEVEL->LegitLevelIndex(impacts.GetMyInstance()->GetLevelIndex()))
|
|
{
|
|
// If our instance is now invalid, don't do anything
|
|
return;
|
|
}
|
|
|
|
if(GetUserData() && ((CEntity *)GetUserData())->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)GetUserData();
|
|
if(pPhysical->DoPreComputeImpactsTest())
|
|
{
|
|
while(!impacts.AtEnd())
|
|
{
|
|
if(pPhysical->TestNoCollision(impacts.GetOtherInstance()))
|
|
{
|
|
impacts.DisableImpact();
|
|
}
|
|
impacts++;
|
|
}
|
|
}
|
|
|
|
pPhysical->ProcessPreComputeImpacts(impacts);
|
|
}
|
|
}
|
|
|
|
phInst *fragInstGta::PrepareForActivation(phCollider **collider, phInst* otherInst, const phConstraintBase * constraint)
|
|
{
|
|
// // FA: temp asserts to cache vehicle cache issues
|
|
//#if __ASSERT
|
|
// CEntity* pThisEntity = CPhysics::GetEntityFromInst(this);
|
|
// if (pThisEntity && pThisEntity->GetIsTypeVehicle())
|
|
// {
|
|
// CVehicle* veh = (CVehicle*)pThisEntity;
|
|
// Assert(!veh->GetIsInPopulationCache());
|
|
// }
|
|
//
|
|
// CEntity* other = CPhysics::GetEntityFromInst(otherInst);
|
|
// if (other && other->GetIsTypeVehicle())
|
|
// {
|
|
// CVehicle* veh = (CVehicle*)other;
|
|
// Assert(!veh->GetIsInPopulationCache());
|
|
// }
|
|
//#endif
|
|
|
|
CEntity* pEntity = CPhysics::GetEntityFromInst(this);
|
|
|
|
// flag can also be set on the phInst to not activate
|
|
if(GetInstFlag(FLAG_NEVER_ACTIVATE))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if(pEntity)
|
|
{
|
|
// Allow the instance to activate and become properly fixed next frame
|
|
/*if(pEntity->GetIsPhysical())
|
|
{
|
|
// If we aren't fixed, check for nearby collision
|
|
if(!pEntity->GetIsFixedUntilCollisionFlagSet())
|
|
{
|
|
static_cast<CPhysical*>(pEntity)->UpdateFixedWaitingForCollision(true);
|
|
Assert(CPhysics::GetSimulator()->GetCollider(this)==NULL);
|
|
}
|
|
}*/
|
|
|
|
// We still need to update the FixedWaitingForCollision flag for parked supper dummies, as they can be possibly push into the ground by other vehicles in one physics update
|
|
if(pEntity->GetIsTypeVehicle() && static_cast<CVehicle*>(pEntity)->IsParkedSuperDummy())
|
|
{
|
|
// If we aren't fixed, check for nearby collision
|
|
if(!pEntity->GetIsFixedUntilCollisionFlagSet())
|
|
{
|
|
static_cast<CPhysical*>(pEntity)->UpdateFixedWaitingForCollision(true);
|
|
Assert(CPhysics::GetSimulator()->GetCollider(this)==NULL);
|
|
}
|
|
}
|
|
|
|
// don't let this instance activate if the fixed flag is set
|
|
if(pEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// if this isn't the phInst that's controlling the object at this time, don't activate this one
|
|
if(pEntity->GetCurrentPhysicsInst()!=this)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if(pEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pEntity);
|
|
if(pVehicle->IsRunningCarRecording() && pVehicle->CanBeInactiveDuringRecording()
|
|
&& (CVehicle::sm_bForceRecordedVehicleToBeInactive || pVehicle->m_nVehicleFlags.bForceInactiveDuringPlayback)
|
|
&& !pVehicle->m_nVehicleFlags.bSwitchToAiRecordingThisFrame)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#if __ASSERT
|
|
if(CVehicleAILodManager::ms_bFreezeParkedSuperDummyWhenCollisionsNotLoaded && pVehicle->IsParkedSuperDummy())
|
|
{
|
|
Assertf(pVehicle->IsCollisionLoadedAroundPosition(), "Trying to activate a parked supper dummy where gound collision is not loaded, activation aborted. vehicle 0x%p, fixedUntilCollisionFlag %x, bShouldFixIfNoCollision %x, AllowFreezeIfNoCollision %x, ShouldFixIfNoCollisionLoadedAroundPosition %x",
|
|
pVehicle, pVehicle->GetIsFixedUntilCollisionFlagSet(), pVehicle->m_nVehicleFlags.bShouldFixIfNoCollision, pVehicle->m_nPhysicalFlags.bAllowFreezeIfNoCollision, pVehicle->ShouldFixIfNoCollisionLoadedAroundPosition());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(otherInst && otherInst->GetUserData())
|
|
{
|
|
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(otherInst);
|
|
|
|
if(pOtherEntity->GetIsTypePed())
|
|
{
|
|
if(pEntity->GetIsTypeVehicle())
|
|
{
|
|
CPed* pPed = static_cast<CPed*>(pOtherEntity);
|
|
// Don't allow animated NPCs to activate vehicles or frag objects.
|
|
if(!pPed->IsPlayer() && pPed->GetRagdollState()<RAGDOLL_STATE_ANIM_DRIVEN)
|
|
{
|
|
// Check if the ped is anywhere near an open car door.
|
|
if(pEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pEntity);
|
|
bool bPedNearOpenDoor = false;
|
|
for(int i = 0; i < pVehicle->GetNumDoors(); ++i)
|
|
{
|
|
CCarDoor* pDoor = pVehicle->GetDoor(i);
|
|
if(!pDoor->GetIsClosed())
|
|
{
|
|
Assert(pVehicle->GetVehicleFragInst());
|
|
Assert(pVehicle->GetVehicleFragInst()->GetArchetype());
|
|
Assert(pVehicle->GetVehicleFragInst()->GetArchetype()->GetBound());
|
|
Assert(pVehicle->GetVehicleFragInst()->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE);
|
|
int nBoneIndex = pVehicle->GetBoneIndex(pDoor->GetHierarchyId());
|
|
int nBoundIndex = -1;
|
|
if(pVehicle->GetVehicleFragInst() && nBoneIndex>-1)
|
|
{
|
|
nBoundIndex = pVehicle->GetVehicleFragInst()->GetComponentFromBoneIndex(nBoneIndex);
|
|
}
|
|
if(nBoundIndex!=-1)
|
|
{
|
|
phBoundComposite* pCompBound = static_cast<phBoundComposite*>(pVehicle->GetVehicleFragInst()->GetArchetype()->GetBound());
|
|
Mat34V matDoor = pVehicle->GetMatrix();
|
|
Transform(matDoor, pCompBound->GetCurrentMatrix(nBoundIndex));
|
|
phBound* pDoorBound = pCompBound->GetBound(nBoundIndex);
|
|
if(pDoorBound)
|
|
{
|
|
Vec3V vDoorCentroidPos = pDoorBound->GetWorldCentroid(matDoor);
|
|
float fDoorCentroidRadius = pDoorBound->GetRadiusAroundCentroid();
|
|
float fPedCapsuleRadius = pPed->GetCapsuleInfo()->GetHalfWidth();
|
|
//grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(vDoorCentroidPos), fDoorCentroidRadius, Color_yellow, false);
|
|
if(MagSquared(pPed->GetTransform().GetPosition()-vDoorCentroidPos).Getf()
|
|
< rage::square(fDoorCentroidRadius+fPedCapsuleRadius))
|
|
{
|
|
bPedNearOpenDoor = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!bPedNearOpenDoor)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(pEntity->GetIsTypeObject())
|
|
{
|
|
// Allow peds to still activate doors.
|
|
if(!static_cast<CObject*>(pEntity)->IsADoor())
|
|
{
|
|
CPed* pPed = static_cast<CPed*>(pOtherEntity);
|
|
|
|
// Don't activate if we're being hit by a ped that can't push us
|
|
if (!GetType()->GetHasAnyArticulatedParts() && pPed->ShouldObjectBeImpossibleToPush(*pEntity, *this, PHSIM->GetCollider(this), 0))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Don't allow animated NPCs to activate vehicles or frag objects.
|
|
if(!pPed->IsPlayer() && pPed->GetRagdollState()<RAGDOLL_STATE_ANIM_DRIVEN)
|
|
{
|
|
// If we are stuck and running into an object then allow it to be activated after a short amount of time
|
|
if(pPed->GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().GetStaticCounter() >= CStaticMovementScanner::ms_iStaticActivateObjectLimit)
|
|
{
|
|
|
|
}
|
|
else if (CPathServerGta::ShouldPedAvoidObject(pPed, pEntity))
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // If(other object == ped).
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if(impactList && GetType()->GetMinMoveForce() > 0.0f)
|
|
{
|
|
phInst *pOtherInst = impactList->GetInstanceA();
|
|
if(pOtherInst==this || pOtherInst==NULL)
|
|
pOtherInst = impactList->GetInstanceB();
|
|
|
|
if(pOtherInst && pOtherInst->GetClassType()==PH_INST_PED)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// ok to go ahead and activate
|
|
phInst *pReturn = fragInst::PrepareForActivation(collider, otherInst, constraint);
|
|
|
|
if(pReturn && pEntity)
|
|
{
|
|
if(pEntity->GetIsPhysical())
|
|
{
|
|
if(!((CDynamicEntity *) pEntity)->GetIsOnSceneUpdate())
|
|
((CDynamicEntity *) pEntity)->AddToSceneUpdate();
|
|
}
|
|
|
|
if(pEntity->GetIsTypeObject())
|
|
{
|
|
|
|
CEntity * pOtherEntity = NULL;
|
|
if(otherInst)
|
|
{
|
|
pOtherEntity = CPhysics::GetEntityFromInst(otherInst);
|
|
}
|
|
|
|
if(!((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted)
|
|
{
|
|
g_CollisionAudioEntity.ReportUproot(pEntity);
|
|
|
|
// Climbable objects; need deactivating when uprooted
|
|
if (((CObject*)pEntity)->IsADoor()==false && pEntity->GetBaseModelInfo()->GetIsClimbableByAI())
|
|
{
|
|
Vector3 vCenter;
|
|
((CObject*)pEntity)->GetBoundCentre(vCenter);
|
|
CPathServer::DeferredDisableClimbAtPosition(vCenter, false, true);
|
|
}
|
|
}
|
|
|
|
((CObject*)pEntity)->m_nObjectFlags.bHasBeenUprooted = true;
|
|
|
|
// need to store what woke this entity up if it is a world object
|
|
((CObject*)pEntity)->ObjectHasBeenMoved(pOtherEntity);
|
|
|
|
if (*collider && (*collider)->IsArticulated())
|
|
{
|
|
// Give a smaller than default internal velocity limit
|
|
(*collider)->GetSleep()->SetInternalMotionTolerance2Sum(0.002f);
|
|
}
|
|
}
|
|
|
|
if(pEntity->GetIsTypeVehicle())
|
|
{
|
|
if (*collider && (*collider)->IsArticulated())
|
|
{
|
|
static bool sbTestArticulatedLargeRoot = true;
|
|
static_cast<phArticulatedCollider*>(*collider)->SetArticulatedLargeRoot(sbTestArticulatedLargeRoot);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pReturn;
|
|
|
|
}
|
|
|
|
|
|
bool fragInstGta::PrepareForDeactivation(bool colliderManagedBySim, bool forceDeactivate)
|
|
{
|
|
bool bReturn = fragInst::PrepareForDeactivation(colliderManagedBySim, forceDeactivate);
|
|
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
if(!pEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
if(pEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pEntity);
|
|
// B* 1089571: Don't refuse to deactivate vehicles owned by cutscenes, even if they are touching water.
|
|
if(pVehicle->GetOwnedBy()!=ENTITY_OWNEDBY_CUTSCENE)
|
|
{
|
|
// Vehicles in water need to be kept awake (other than low-lod boats).
|
|
bool bUsingLowLodAnchorMode = false;
|
|
if(pVehicle->InheritsFromBoat())
|
|
{
|
|
bUsingLowLodAnchorMode = static_cast<CBoat*>(pVehicle)->GetAnchorHelper().UsingLowLodMode();
|
|
}
|
|
else if(pVehicle->InheritsFromSubmarine())
|
|
{
|
|
CSubmarineHandling* pSubHandling = pVehicle->GetSubHandling();
|
|
bUsingLowLodAnchorMode = pSubHandling->GetAnchorHelper().UsingLowLodMode();
|
|
}
|
|
else if(pVehicle->InheritsFromAmphibiousAutomobile())
|
|
{
|
|
bUsingLowLodAnchorMode = static_cast<CAmphibiousAutomobile*>(pVehicle)->GetAnchorHelper().UsingLowLodMode();
|
|
}
|
|
|
|
if(!pVehicle->GetIsAquatic() || !bUsingLowLodAnchorMode)
|
|
{
|
|
// Keep awake if partially submerged in water...
|
|
if(pVehicle->m_Buoyancy.GetStatus()==PARTIALLY_IN_WATER)
|
|
{
|
|
return false;
|
|
}
|
|
// ... or fully submerged and above the submerge sleep threshold.
|
|
else if(pVehicle->m_Buoyancy.GetStatus()==FULLY_IN_WATER)
|
|
{
|
|
if(pVehicle->GetTransform().GetPosition().GetZf() > CVehicle::ms_fVehicleSubmergeSleepGlobalZ)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(pEntity->GetIsTypeObject())
|
|
{
|
|
// If this is an object, reset the flag which indicates it is attached to the handler vehicle.
|
|
static_cast<CObject*>(pEntity)->SetAttachedToHandler(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
void fragInstGta::OnActivate(phInst* otherInst)
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->OnActivate(this,otherInst);
|
|
}
|
|
|
|
if(CExplosionManager::InsideExplosionUpdate())
|
|
{
|
|
CExplosionManager::RegisterExplosionActivation(GetLevelIndex());
|
|
}
|
|
}
|
|
|
|
void fragInstGta::OnDeactivate()
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->OnDeactivate(this);
|
|
}
|
|
}
|
|
|
|
void fragInstGta::PrepareBrokenInst(fragInst *brokenInst) const
|
|
{
|
|
// temporarily set user data so inst knows it's parent when it's inserted
|
|
brokenInst->SetUserData((void*)this);
|
|
brokenInst->SetInstFlag(phInstGta::FLAG_USERDATA_PARENT_INST, true);
|
|
}
|
|
|
|
void fragInstGta::PartsBrokeOff(const ComponentBits& brokenGroups, phInst* newInst)
|
|
{
|
|
// Safely get and cast child/parent pointers. I don't think these can ever fail.
|
|
CEntity* pParentEntity = CPhysics::GetEntityFromInst(this);
|
|
if(!AssertVerify(pParentEntity && pParentEntity->GetIsPhysical()))
|
|
{
|
|
return;
|
|
}
|
|
CPhysical* pParentPhysical = static_cast<CPhysical*>(pParentEntity);
|
|
|
|
CPhysical* pNewPhysical = NULL;
|
|
fragInst* newFragInst = NULL;
|
|
if(newInst)
|
|
{
|
|
CEntity* pNewEntity = CPhysics::GetEntityFromInst(newInst);
|
|
if(!AssertVerify(pNewEntity && pNewEntity->GetIsPhysical() && IsFragInst(newInst)))
|
|
{
|
|
return;
|
|
}
|
|
pNewPhysical = static_cast<CPhysical*>(pNewEntity);
|
|
newFragInst = static_cast<fragInst*>(newInst);
|
|
}
|
|
|
|
// If any part breaks off a frag which has a dynamic cover bound, just disable this bound.
|
|
if(pParentEntity->GetBaseModelInfo() && pParentEntity->GetBaseModelInfo()->TestAttribute(MODEL_ATTRIBUTE_HAS_DYNAMIC_COVER_BOUND))
|
|
{
|
|
if(!pParentPhysical->CoverBoundsAlreadyRemoved())
|
|
{
|
|
pParentEntity->GetBaseModelInfo()->ClearDynamicCoverCollisionBounds(pParentEntity->GetPhysArch());
|
|
pParentPhysical->SetCoverBoundsAlreadyRemoved(true);
|
|
}
|
|
|
|
if(pNewPhysical && !pNewPhysical->CoverBoundsAlreadyRemoved())
|
|
{
|
|
pNewPhysical->GetBaseModelInfo()->ClearDynamicCoverCollisionBounds(pParentEntity->GetPhysArch());
|
|
pNewPhysical->SetCoverBoundsAlreadyRemoved(true);
|
|
}
|
|
}
|
|
|
|
// Iterate over all breaking fragGroups and fragChildren/components
|
|
const fragPhysicsLOD* physicsLOD = GetTypePhysics();
|
|
u8 firstBrokenGroupIndex = 0xFF;
|
|
for(u8 groupIndex=0; groupIndex < physicsLOD->GetNumChildGroups(); groupIndex++)
|
|
{
|
|
if(brokenGroups.IsSet(groupIndex))
|
|
{
|
|
const fragTypeGroup& group = *physicsLOD->GetGroup(groupIndex);
|
|
if(firstBrokenGroupIndex == 0xFF)
|
|
{
|
|
// Cache off the first broken group
|
|
firstBrokenGroupIndex = groupIndex;
|
|
}
|
|
|
|
// Iterate over all the fragChildren/Components in the group
|
|
for (int groupChildIndex = 0; groupChildIndex < group.GetNumChildren(); groupChildIndex++)
|
|
{
|
|
int childIndex = groupChildIndex + group.GetChildFragmentIndex();
|
|
|
|
if(pParentEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pParentVehicle = static_cast<CVehicle*>(pParentEntity);
|
|
|
|
// Mark doors as broken off
|
|
for(int doorIndex = 0; doorIndex < pParentVehicle->GetNumDoors(); ++doorIndex)
|
|
{
|
|
CCarDoor* pDoor = pParentVehicle->GetDoor(doorIndex);
|
|
if(pDoor->GetFragChild() == childIndex)
|
|
{
|
|
#if __BANK
|
|
vehicledoorDebugf3("fragInstGta::PartsBrokeOff() CCarDoor::IS_BROKEN_OFF [%s][%s][%p]: Scriptindex - %i", AILogging::GetDynamicEntityNameSafe(pParentVehicle), pParentVehicle->GetModelName(), pParentVehicle,
|
|
pDoor->GetHierarchyId());
|
|
if (Channel_vehicledoor.FileLevel >= DIAG_SEVERITY_DEBUG3)
|
|
{
|
|
sysStack::PrintStackTrace();
|
|
}
|
|
#endif
|
|
pDoor->SetFlag(CCarDoor::IS_BROKEN_OFF);
|
|
}
|
|
}
|
|
|
|
for(int wheelIndex = 0; wheelIndex < pParentVehicle->GetNumWheels(); ++wheelIndex)
|
|
{
|
|
CWheel* pWheel = pParentVehicle->GetWheel(wheelIndex);
|
|
if( pWheel->GetFragChild() == childIndex &&
|
|
pParentVehicle->GetVehicleModelInfo()->GetVehicleClass() != VC_OPEN_WHEEL )
|
|
{
|
|
// Additionally, to stop brake calipers from drawing we have to zero the hub matrix
|
|
pWheel->GetConfigFlags().SetFlag(WCF_DONT_RENDER_HUB);
|
|
}
|
|
}
|
|
|
|
if(pNewPhysical)
|
|
{
|
|
// Update type/include flags of this component
|
|
pParentVehicle->UpdateCollisionOnPartBrokenOff(childIndex, newFragInst);
|
|
|
|
// Let glass know that this part broke off
|
|
g_vehicleGlassMan.UpdateBrokenPart(pParentEntity, pNewPhysical, childIndex);
|
|
|
|
if(pParentVehicle->InheritsFromPlane())
|
|
{
|
|
// Iterate over children of any parts which break off planes and reduce their buoyancy force so that
|
|
// we don't get wings, rudders, etc. which float in weird ways.
|
|
CPlane* pPlane = static_cast<CPlane*>(pParentEntity);
|
|
pPlane->GetAircraftDamage().UpdateBuoyancyOnPartBreakingOff(pPlane, childIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(pNewPhysical)
|
|
{
|
|
// Transfer attachments
|
|
CPhysical* pAttachChild = static_cast<CPhysical*>(pParentPhysical->GetChildAttachment());
|
|
while(pAttachChild)
|
|
{
|
|
// Get the next physical attached to this object now since we might transfer the current one
|
|
CPhysical* pNextAttachChild = static_cast<CPhysical*>(pAttachChild->GetSiblingAttachment());
|
|
if(GetControllingComponentFromBoneIndex(pAttachChild->GetAttachBone()) == childIndex)
|
|
{
|
|
// If the component controlling this attachment's bone got broken, move the attachment.
|
|
pAttachChild->AttachParentPartBrokeOff(pNewPhysical);
|
|
}
|
|
pAttachChild = pNextAttachChild;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assert(firstBrokenGroupIndex != 0xFF);
|
|
|
|
|
|
// this group (group i) broke off its parent - do audio and ptfx
|
|
|
|
// find the bone tag (we're looking at groups here so need to get first child of group to get to bone)
|
|
const fragTypeGroup& firstBrokenGroup = *physicsLOD->GetGroup(firstBrokenGroupIndex);
|
|
u8 firstBreakingChildIndex = firstBrokenGroup.GetChildFragmentIndex();
|
|
u16 childBoneID = physicsLOD->GetChild(firstBreakingChildIndex)->GetBoneID();
|
|
if (pParentEntity->GetIsTypeVehicle())
|
|
{
|
|
// NOTE: I think this might need to be called per-component but I'm not sure. Keeping old behavior for now.
|
|
CVehicle* pParentVehicle = static_cast<CVehicle*>(pParentEntity);
|
|
pParentVehicle->GetVehicleDamage()->UpdateLightsOnBreakOff(firstBreakingChildIndex);
|
|
}
|
|
|
|
if(pNewPhysical)
|
|
{
|
|
if(CExplosionManager::InsideExplosionUpdate())
|
|
{
|
|
CExplosionManager::RegisterExplosionBreak();
|
|
}
|
|
|
|
// If the entity associated with the current frag is a CPhysical (likely), then we need to check
|
|
// if it is maintaining any attachments on this group and let it know that its attachment parent will now have
|
|
// a different physics inst.
|
|
if(pParentEntity && pParentEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pParentPhysical = static_cast<CPhysical*>(pParentEntity);
|
|
physicsAssertf(pParentPhysical, "pParentEntity: 0x%p.", pParentEntity);
|
|
|
|
// If the object which is breaking is constrained to a forklift, just break the constraint.
|
|
if(CPhysics::GetConstraintManager()->IsConstrained(this))
|
|
{
|
|
CPhysical* pAttachParent = static_cast<CPhysical*>(pParentPhysical->GetAttachParentForced());
|
|
if(pAttachParent && pAttachParent->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pAttachParent);
|
|
|
|
// See if we have a pair of forks.
|
|
for(int i = 0; i < pVehicle->GetNumberOfVehicleGadgets(); ++i)
|
|
{
|
|
CVehicleGadget* pVehicleGadget = pVehicle->GetVehicleGadget(i);
|
|
|
|
if(pVehicleGadget->GetType() == VGT_FORKS)
|
|
{
|
|
CVehicleGadgetForks* pForks = static_cast<CVehicleGadgetForks*>(pVehicleGadget);
|
|
|
|
pForks->DetachPalletImmediatelyAndCleanUp(pVehicle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the parent boneID as well
|
|
u8 firstParent = firstBrokenGroup.GetParentGroupPointerIndex();
|
|
if(firstParent != 0xFF)
|
|
{
|
|
u16 parentBoneID = GetTypePhysics()->GetChild(GetTypePhysics()->GetGroup(firstParent)->GetChildFragmentIndex())->GetBoneID();
|
|
|
|
// do any vfx
|
|
pParentEntity->ProcessFxFragmentBreak(childBoneID, false, parentBoneID);
|
|
}
|
|
|
|
// do any vfx
|
|
pNewPhysical->ProcessFxFragmentBreak(childBoneID, true);
|
|
|
|
g_CollisionAudioEntity.ReportFragmentBreak(newInst, firstBreakingChildIndex);
|
|
}
|
|
|
|
// Update the parent object as it is likely smaller or even removed
|
|
if(pParentEntity->GetIsTypeObject())
|
|
{
|
|
CObject* pObject = static_cast<CObject*>(pParentEntity);
|
|
if(pObject->m_nObjectFlags.bBoundsNeedRecalculatingForNavigation)
|
|
{
|
|
if(pObject->IsInPathServer())
|
|
{
|
|
CPathServerGta::UpdateDynamicObject(pObject, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// B*1760493: Force the TACO van roof sign to have a greater mass so it cannot be pushed around by peds.
|
|
if(pParentEntity->GetIsTypeVehicle() && pNewPhysical)
|
|
{
|
|
if(pNewPhysical->GetModelIndex() == MI_CAR_TACO)
|
|
{
|
|
pNewPhysical->SetMass(200.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstGta::PartsRestored(const ComponentBits& restoredGroups)
|
|
{
|
|
CEntity* pEntity = CPhysics::GetEntityFromInst(this);
|
|
if(!AssertVerify(pEntity && pEntity->GetIsPhysical()))
|
|
{
|
|
return;
|
|
}
|
|
CPhysical* pPhysical = static_cast<CPhysical*>(pEntity);
|
|
|
|
// Loop over each restored group
|
|
const fragPhysicsLOD* physicsLOD = GetTypePhysics();
|
|
for(int groupIndex = 0; groupIndex < physicsLOD->GetNumChildGroups(); ++groupIndex)
|
|
{
|
|
if(restoredGroups.IsSet(groupIndex))
|
|
{
|
|
if(pPhysical->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pPhysical);
|
|
|
|
// Update the cached broken off flag of the door
|
|
for(int doorIndex = 0; doorIndex < pVehicle->GetNumDoors(); ++doorIndex)
|
|
{
|
|
CCarDoor* pDoor = pVehicle->GetDoor(doorIndex);
|
|
if(pDoor->GetFragGroup() == groupIndex)
|
|
{
|
|
pDoor->ClearFlag(CCarDoor::IS_BROKEN_OFF);
|
|
}
|
|
}
|
|
|
|
// Fix any wheels that are being restored
|
|
for(int wheelIndex = 0; wheelIndex < pVehicle->GetNumWheels(); ++wheelIndex)
|
|
{
|
|
CWheel* pWheel = pVehicle->GetWheel(wheelIndex);
|
|
int wheelChildIndex = pWheel->GetFragChild();
|
|
if(wheelChildIndex >= 0 && (int)physicsLOD->GetChild(wheelChildIndex)->GetOwnerGroupPointerIndex() == groupIndex)
|
|
{
|
|
pWheel->ResetDamage();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstGta::CollisionDamage(const Collision& msg, phContactIterator *impacts)
|
|
{
|
|
CEntity* pEntity = CPhysics::GetEntityFromInst(this);
|
|
// can't break or damage frozen/fixed fragments in any way
|
|
if(!pEntity)
|
|
return;
|
|
|
|
if(!IsInLevel())
|
|
return;
|
|
|
|
int component = msg.component;
|
|
Assert(component < GetTypePhysics()->GetNumChildren());
|
|
// this results in a fatal crash, try and avoid it by jumping out
|
|
if(component >= GetTypePhysics()->GetNumChildren())
|
|
return;
|
|
|
|
fragTypeChild* child = GetTypePhysics()->GetAllChildren()[component];
|
|
int groupIndex = child->GetOwnerGroupPointerIndex();
|
|
fragTypeGroup* breakGroup = GetTypePhysics()->GetAllGroups()[groupIndex];
|
|
|
|
float fOldHealth = breakGroup->GetDamageHealth();
|
|
bool bWasBroken = false;
|
|
|
|
Matrix34 matChild;
|
|
u32 boneIndex = GetType()->GetBoneIndexFromID(child->GetBoneID());
|
|
bool bGotMatrix = false;
|
|
|
|
if(GetCached())
|
|
{
|
|
fOldHealth = GetCacheEntry()->GetHierInst()->groupInsts[groupIndex].damageHealth;
|
|
bWasBroken = GetCacheEntry()->GetHierInst()->groupBroken->IsSet(groupIndex);
|
|
GetSkeleton()->GetGlobalMtx(boneIndex, RC_MAT34V(matChild));
|
|
bGotMatrix = true;
|
|
}
|
|
|
|
fragInst::CollisionDamage(msg, impacts);
|
|
|
|
// if it's not cached then it can't have broken!
|
|
if(GetCached())
|
|
{
|
|
float fHealth = GetCacheEntry()->GetHierInst()->groupInsts[groupIndex].damageHealth;
|
|
bool bIsBroken = GetCacheEntry()->GetHierInst()->groupBroken->IsSet(groupIndex);
|
|
|
|
// didn't get the original matrix, because it wasn't cached before, so safe to calculate it using the shared skeleton
|
|
if(!bGotMatrix && ((bIsBroken && !bWasBroken) || (fHealth < 0.0f && fOldHealth >= 0.0f)))
|
|
{
|
|
GetType()->GetSkeletonData().ComputeGlobalTransform(boneIndex, pEntity->GetTransform().GetMatrix(), RC_MAT34V(matChild));
|
|
}
|
|
|
|
if(bIsBroken)
|
|
{
|
|
if(!bWasBroken)
|
|
{
|
|
// component just disappeared - do audio and ptfx
|
|
|
|
// set damaged flag so scripts can tell this object has been damaged
|
|
if(pEntity->GetIsTypeObject())
|
|
{
|
|
pEntity->m_nFlags.bRenderDamaged = true;
|
|
|
|
// If we have just broken a pane of glass, notify the pathserver which can then
|
|
// remove the dynamic object for this entity - allowing peds to navigate through.
|
|
if(((CObject*)pEntity)->m_nDEflags.bIsBreakableGlass && pEntity->IsInPathServer())
|
|
{
|
|
if( !CPathServerGta::SpecialCaseDontRemoveSmashedGlass(((CObject*)pEntity)))
|
|
{
|
|
CPathServerGta::MaybeRemoveDynamicObject(pEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
// find the bonetag
|
|
fragTypeChild** ppChildren = GetTypePhysics()->GetAllChildren();
|
|
Assert(component>=0);
|
|
Assert(component<GetTypePhysics()->GetNumChildren());
|
|
u32 boneTag = ppChildren[component]->GetBoneID();
|
|
|
|
// stop any current vfx on this entity as it's now going to destroy
|
|
// g_ptFx.RemoveEntityPtFx(pEntity);
|
|
|
|
// do any vfx
|
|
pEntity->ProcessFxFragmentDestroy(boneTag, matChild);
|
|
|
|
g_CollisionAudioEntity.ReportFragmentBreak(this, component);
|
|
|
|
// Object might not exist in physics world anymore -
|
|
// check for this case and destroy the object if necessary
|
|
// (fragInst::CollisionDamage may have set all the bounds to NULL)
|
|
if(pEntity)
|
|
{
|
|
fragCacheEntry* pCacheEntry = GetCacheEntry();
|
|
const phBoundComposite* pCompositeBound = static_cast<phBoundComposite*>(pCacheEntry->GetBound());
|
|
bool bFoundValidBound = false;
|
|
for(int boundIndex = 0; boundIndex < pCompositeBound->GetNumBounds() && !bFoundValidBound; boundIndex++)
|
|
{
|
|
if(pCompositeBound->GetBound(boundIndex))
|
|
bFoundValidBound = true;
|
|
}
|
|
|
|
bool bHasGlass = false;
|
|
if(fragHierarchyInst *pHierInst = pCacheEntry->GetHierInst())
|
|
{
|
|
bHasGlass = pHierInst->numGlassInfos > 0;
|
|
}
|
|
|
|
|
|
CObject *pObject = pEntity->GetIsTypeObject() ? static_cast<CObject *>(pEntity) : NULL;
|
|
bool bOkayToRemove = true;
|
|
|
|
if (pObject)
|
|
{
|
|
if (ENTITY_OWNEDBY_SCRIPT == pObject->GetOwnedBy())
|
|
{
|
|
bOkayToRemove = false; // Only scripts should remove mission peds, cars, objects
|
|
}
|
|
}
|
|
|
|
if (bOkayToRemove)
|
|
{
|
|
netObject *pNetObj = NetworkUtils::GetNetworkObjectFromEntity(pEntity);
|
|
|
|
if (pNetObj && (pNetObj->IsClone() || !pNetObj->CanDelete()))
|
|
{
|
|
//ambient props can be deleted as clones as their network object persists
|
|
if (!pObject || !CNetObjObject::IsAmbientObjectType(pObject->GetOwnedBy()))
|
|
{
|
|
bOkayToRemove = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Add damage entity event
|
|
if (pEntity->GetIsPhysical() && TriggerEventEntityDestroyed(static_cast<CPhysical*>(pEntity)))
|
|
{
|
|
GetEventGlobalGroup()->Add(CEventEntityDestroyed(static_cast<CPhysical*>(pEntity)));
|
|
}
|
|
|
|
if(!bFoundValidBound
|
|
&& !bHasGlass
|
|
&& !pEntity->GetIsAnyManagedProcFlagSet() // Procedural objects will clean up themselves
|
|
&& bOkayToRemove)
|
|
{
|
|
pEntity->FlagToDestroyWhenNextProcessed();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(fHealth < 0.0f && fOldHealth >= 0.0f)
|
|
{
|
|
// component just switched to damage model - do audio and ptfx
|
|
|
|
//Add damage entity event
|
|
if (pEntity->GetIsPhysical() && TriggerEventEntityDestroyed(static_cast<CPhysical*>(pEntity)))
|
|
{
|
|
GetEventGlobalGroup()->Add(CEventEntityDestroyed(static_cast<CPhysical*>(pEntity)));
|
|
}
|
|
|
|
// set damaged flag so scripts can tell this object has been damaged
|
|
if(pEntity->GetIsTypeObject())
|
|
{
|
|
pEntity->m_nFlags.bRenderDamaged = true;
|
|
}
|
|
|
|
// find the bonetag
|
|
fragTypeChild** ppChildren = GetTypePhysics()->GetAllChildren();
|
|
Assert(component>=0);
|
|
Assert(component<GetTypePhysics()->GetNumChildren());
|
|
u32 boneTag = ppChildren[component]->GetBoneID();
|
|
|
|
// stop any current vfx on this entity as it's now going to destroy
|
|
// g_ptFx.RemoveEntityPtFx(pEntity);
|
|
|
|
// do any vfx
|
|
pEntity->ProcessFxFragmentDestroy(boneTag, matChild);
|
|
|
|
// remove any decals from this entity
|
|
g_decalMan.Remove(pEntity, boneIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Called locally on a frag to detect if the destroy event should be triggered.
|
|
bool fragInstGta::TriggerEventEntityDestroyed(CPhysical* entity)
|
|
{
|
|
bool addDestroyEvent = true;
|
|
|
|
if (entity && NetworkInterface::IsGameInProgress())
|
|
{
|
|
bool addDestroyEvent = false;
|
|
|
|
//If it has a weapon damage entity then the event should be triggered.
|
|
// - Child Object won't need this and wont have a valid Weapon Damage Entity. And this avoids
|
|
// triggering the event for the child object's.
|
|
CEntity* damageEntity = entity->GetWeaponDamageEntity();
|
|
|
|
if (damageEntity)
|
|
{
|
|
//Last Damage Entity is Available.
|
|
CPed* ped = NULL;
|
|
|
|
if (damageEntity->GetIsTypePed())
|
|
{
|
|
ped = static_cast<CPed *>(damageEntity);
|
|
}
|
|
else if (damageEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* vehicle = static_cast<CVehicle *>(damageEntity);
|
|
ped = vehicle->GetDriver();
|
|
}
|
|
|
|
//Only trigger the event if it was damaged by the local player.
|
|
if (ped && ped->IsLocalPlayer())
|
|
{
|
|
addDestroyEvent = true;
|
|
ASSERT_ONLY( gnetDebug1("Add destroy event for %s", entity->GetDebugName()); )
|
|
}
|
|
}
|
|
}
|
|
|
|
return addDestroyEvent;
|
|
}
|
|
|
|
bool fragInstGta::IsBreakable(phInst* otherInst) const
|
|
{
|
|
const CEntity* pEntity = CPhysics::GetEntityFromInst(this);
|
|
|
|
// B*2040313 - Shooting range rail gun targets no longer fragging.
|
|
// This should probably be done but we'll disable it for the time being
|
|
// as it is no worse than last gen and it is causing a bug.
|
|
//// Don't let the instance be broken apart if the instance has been flagged to never activated.
|
|
//if(GetInstFlag(FLAG_NEVER_ACTIVATE))
|
|
//{
|
|
// return false;
|
|
//}
|
|
|
|
if(pEntity)
|
|
{
|
|
// Don't let this instance be broken apart if any fixed flags are set.
|
|
if(pEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return fragInst::IsBreakable(otherInst);
|
|
}
|
|
|
|
bool fragInstGta::FindBreakStrength(const Vector3* componentImpulses, const Vector4* componentPositions, float* breakStrength, phBreakData* breakData) const
|
|
{
|
|
// want the option to override breaking on specific components (car doors)
|
|
if(m_nDontBreakCompFlags)
|
|
{
|
|
static Vector3 breakingImpulses[phInstBreakable::MAX_NUM_BREAKABLE_COMPONENTS];
|
|
for(s64 i=0; i<GetTypePhysics()->GetNumChildren(); i++)
|
|
{
|
|
if(m_nDontBreakCompFlags & static_cast<s64>(BIT(i)))
|
|
breakingImpulses[i].Zero();
|
|
else
|
|
breakingImpulses[i].Set(componentImpulses[i]);
|
|
}
|
|
|
|
return fragInst::FindBreakStrength(breakingImpulses, componentPositions, breakStrength, breakData);
|
|
}
|
|
|
|
return fragInst::FindBreakStrength(componentImpulses, componentPositions, breakStrength, breakData);
|
|
}
|
|
|
|
bool fragInstGta::IsGroupBreakable(int nGroupIndex) const
|
|
{
|
|
if(m_nDontBreakCompFlags)
|
|
{
|
|
Assert(nGroupIndex > -1 && nGroupIndex < GetTypePhysics()->GetNumChildGroups());
|
|
fragTypeGroup* pGroup = GetTypePhysics()->GetAllGroups()[nGroupIndex];
|
|
for(int i = 0; i < pGroup->GetNumChildren(); i++)
|
|
{
|
|
int iChild = pGroup->GetChildFragmentIndex() + i;
|
|
if(m_nDontBreakCompFlags & static_cast<s64>(BIT(iChild)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool fragInstGta::ShouldBeInCacheToDraw() const
|
|
{
|
|
if(fragInst::ShouldBeInCacheToDraw())
|
|
return true;
|
|
|
|
if(GetClassType()==PH_INST_FRAG_VEH || GetClassType()==PH_INST_FRAG_PED)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void fragInstGta::LosingCacheEntry()
|
|
{
|
|
Assertf(CSystem::IsThisThreadId(SYS_THREAD_UPDATE), "Fragment cache entry being lost from outside the main thread (callstack please)!");
|
|
// Assertf(GetInstFlag(FLAG_BEING_DELETED) || !(GetUserData() && ((CEntity*)GetUserData())->GetIsTypeVehicle()), "%s:Vehicle lost cache entry", CModelInfo::GetModelInfo (((CEntity*)GetUserData())->GetModelIndex()));
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->LosingFragCacheEntry();
|
|
}
|
|
|
|
fragInst::LosingCacheEntry();
|
|
}
|
|
|
|
fragCacheEntry* fragInstGta::PutIntoCache()
|
|
{
|
|
fragCacheEntry* newEntry = fragInst::PutIntoCache();
|
|
if(newEntry)
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->GainingFragCacheEntry(*newEntry);
|
|
}
|
|
}
|
|
return newEntry;
|
|
}
|
|
|
|
|
|
Mat34V_ConstRef fragInstGta::GetLastMatrix() const
|
|
{
|
|
return PHLEVEL->GetLastInstanceMatrix(this);
|
|
}
|
|
|
|
|
|
Vec3V_Out fragInstGta::GetExternallyControlledVelocity () const
|
|
{
|
|
Vec3V velocity(V_ZERO);
|
|
|
|
CEntity* pEntity = (CEntity*)GetUserData();
|
|
if(pEntity)
|
|
{
|
|
phInst* pInst = pEntity->GetCurrentPhysicsInst();
|
|
phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(pInst);
|
|
if(pInst && pInst->IsInLevel() && pInst != this && pCollider)
|
|
{
|
|
velocity = pCollider->GetVelocity();
|
|
}
|
|
else
|
|
{
|
|
bool bInstActive = PHLEVEL->LegitLevelIndex(GetLevelIndex()) && PHLEVEL->IsActive(GetLevelIndex());
|
|
if (!bInstActive && pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)pEntity;
|
|
// ***HACK***
|
|
// This logic should probably be contained within CPhysical::GetVelocity but it too late in the project to make such sweeping changes.
|
|
// Here (and the equivalent function in phInstGta) seemed to be the only place where this logic was required (although there are probably
|
|
// other areas that would benefit) so the change was made here.
|
|
if(pPhysical->GetIsAttached() && pPhysical->GetAttachParent() != NULL && static_cast<CEntity*>(pPhysical->GetAttachParent())->GetIsTypeVehicle() && static_cast<CVehicle*>(pPhysical->GetAttachParent())->InheritsFromTrain())
|
|
{
|
|
CPhysical* pAttachParent = static_cast<CPhysical*>(pPhysical->GetAttachParent());
|
|
pPhysical->CalculateGroundVelocity(pAttachParent, pPhysical->GetTransform().GetPosition(), 0, velocity);
|
|
}
|
|
else
|
|
{
|
|
velocity = RCC_VEC3V(pPhysical->GetVelocity());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return velocity;
|
|
}
|
|
|
|
Vec3V_Out fragInstGta::GetExternallyControlledAngVelocity () const
|
|
{
|
|
Vector3 angVelocity(ORIGIN);
|
|
|
|
CEntity* pEntity = (CEntity*)GetUserData();
|
|
if(pEntity)
|
|
{
|
|
phInst* pInst = pEntity->GetCurrentPhysicsInst();
|
|
phCollider* pCollider = CPhysics::GetSimulator()->GetCollider(pInst);
|
|
if(pInst && pInst->IsInLevel() && pInst != this && pCollider)
|
|
{
|
|
angVelocity = RCC_VECTOR3(pCollider->GetAngVelocity());
|
|
}
|
|
else
|
|
{
|
|
bool bInstActive = PHLEVEL->LegitLevelIndex(GetLevelIndex()) && PHLEVEL->IsActive(GetLevelIndex());
|
|
if (!bInstActive && pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)pEntity;
|
|
angVelocity.Set(pPhysical->GetAngVelocity());
|
|
}
|
|
}
|
|
}
|
|
|
|
return RCC_VEC3V(angVelocity);
|
|
}
|
|
|
|
ScalarV_Out fragInstGta::GetInvTimeStepForComponentExternalVelocity () const
|
|
{
|
|
if(const CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
// Only use component external velocity on inactive animating ambient fragments.
|
|
// It shouldn't cause any issues with peds/vehicles but it is a bit slow and doesn't solve any known issues with peds/vehicles.
|
|
if(pEntity->GetIsTypeObject() && pEntity->GetAnimDirector() && pEntity->GetIsStatic())
|
|
{
|
|
return Invert(ScalarVFromF32(fwTimer::GetTimeStep()));
|
|
}
|
|
}
|
|
|
|
return ScalarV(V_ZERO);
|
|
}
|
|
|
|
#if __DEV
|
|
void fragInstGta::InvalidStateDump() const
|
|
{
|
|
fragInst::InvalidStateDump();
|
|
InvalidStateDumpHelper(this);
|
|
}
|
|
#endif // __DEV
|
|
|
|
/////////////////////////////////////////////////////
|
|
// fragInstNMGta (Natural Motion - but used for all peds even if NM is not defined)
|
|
/////////////////////////////////////////////////////
|
|
//
|
|
dev_float fragInstNMGta::ms_fVehiclePedActivationSpeed = 0.5f;
|
|
dev_float fragInstNMGta::ms_fVehiclePlayerActivationSpeed = 2.0f;
|
|
dev_float fragInstNMGta::ms_fPlayerPedActivationSpeed = 1.0f;
|
|
dev_float fragInstNMGta::ms_fPlayerGroupPedActivationSpeed = 2.0f;
|
|
|
|
float fragInstNMGta::m_ArmedPedBumpResistance = 5.0f;
|
|
|
|
#if __BANK
|
|
bool fragInstNMGta::ms_bLogUsedRagdolls = false;
|
|
int fragInstNMGta::ms_MaxNMAgentsUsedCurrentLevel = 0;
|
|
int fragInstNMGta::ms_MaxRageRagdollsUsedCurrentLevel = 0;
|
|
int fragInstNMGta::ms_MaxNMAgentsUsedInComboCurrentLevel = 0;
|
|
int fragInstNMGta::ms_MaxRageRagdollsUsedInComboCurrentLevel = 0;
|
|
int fragInstNMGta::ms_MaxTotalRagdollsUsedCurrentLevel = 0;
|
|
int fragInstNMGta::ms_NumFallbackAnimsUsedCurrentLevel = 0;
|
|
|
|
int fragInstNMGta::ms_MaxNMAgentsUsedGlobally = 0;
|
|
int fragInstNMGta::ms_MaxRageRagdollsUsedGlobally = 0;
|
|
int fragInstNMGta::ms_MaxNMAgentsUsedInComboGlobally = 0;
|
|
int fragInstNMGta::ms_MaxRageRagdollsUsedInComboGlobally = 0;
|
|
int fragInstNMGta::ms_MaxTotalRagdollsUsedGlobally = 0;
|
|
int fragInstNMGta::ms_NumFallbackAnimsUsedGlobally = 0;
|
|
#endif
|
|
|
|
//bool fragInstNMGta::ms_bUseBulletForces = true;
|
|
bool fragInstNMGta::ms_bLogRagdollForces = false;
|
|
|
|
|
|
fragInstNMGta::fragInstNMGta(s32 nInstType, const fragType* type, const Matrix34& matrix, u32 guid)
|
|
: fragInstNM(type, matrix, guid)
|
|
{
|
|
m_nType = nInstType;
|
|
SetEntity(NULL);
|
|
m_feedbackInterfaceGta.SetParentInst(this);
|
|
m_bDontZeroMatricesOnActivation = false;
|
|
m_iDisableCollisionMask = 0;
|
|
m_ImpulseModifier = CTaskRageRagdoll::GetMaxImpulseModifier();
|
|
m_PendingImpulseModifier = -1.0f;
|
|
m_CounterImpulseCount = -1;
|
|
m_IncomingAnimVelocityScaleReset = -1.0f;
|
|
m_AllowDeadPedActivationThisFrame = false;
|
|
m_fActivationStartTime = 0;
|
|
m_SuppressHighRagdollLODOnActivation = false;
|
|
m_RequestedPhysicsLODOnActivation = -1;
|
|
m_EjectedFromVehicleFrames = -1;
|
|
m_VehicleBeingEjectedFrom = NULL;
|
|
m_RootBonePosAtBoundsUpdate = Vec3V(V_ZERO);
|
|
m_pLastImpactDamageEntity = NULL;
|
|
m_uTimeSinceImpactDamage = 0;
|
|
m_fAccumulatedImpactDamage = 0.0f;
|
|
m_LastRRApplyBulletTime = 0;
|
|
m_bBulletLoosenessActive = false;
|
|
m_WheelTwitchStarted = false;
|
|
m_ContactedWheel = false;
|
|
m_uTimeTireImpulseWasApplied = 0;
|
|
m_fTireImpulseAppliedRatio = 0.0f;
|
|
#if __BANK
|
|
ms_bLogUsedRagdolls = PARAM_logusedragdolls.Get();
|
|
#endif
|
|
}
|
|
|
|
fragInstNMGta::fragInstNMGta(datResource & rsc)
|
|
: fragInstNM(rsc)
|
|
{
|
|
Assertf(false, "fragInstNMGta::fragInstNMGta(datResource & rsc) - wanted to see if we hit this");
|
|
m_nType = PH_INST_FRAG_GTA;
|
|
SetEntity(NULL);
|
|
m_feedbackInterfaceGta.SetParentInst(this);
|
|
m_bDontZeroMatricesOnActivation = false;
|
|
m_iDisableCollisionMask = 0;
|
|
m_ImpulseModifier = CTaskRageRagdoll::GetMaxImpulseModifier();
|
|
m_PendingImpulseModifier = -1.0f;
|
|
m_CounterImpulseCount = -1;
|
|
m_AllowDeadPedActivationThisFrame = false;
|
|
m_fActivationStartTime = 0;
|
|
m_SuppressHighRagdollLODOnActivation = false;
|
|
m_RequestedPhysicsLODOnActivation = -1;
|
|
m_EjectedFromVehicleFrames = -1;
|
|
m_VehicleBeingEjectedFrom = NULL;
|
|
m_RootBonePosAtBoundsUpdate = Vec3V(V_ZERO);
|
|
m_pLastImpactDamageEntity = NULL;
|
|
m_uTimeSinceImpactDamage = 0;
|
|
m_fAccumulatedImpactDamage = 0.0f;
|
|
m_LastRRApplyBulletTime = 0;
|
|
m_bBulletLoosenessActive = false;
|
|
m_WheelTwitchStarted = false;
|
|
m_ContactedWheel = false;
|
|
m_uTimeTireImpulseWasApplied = 0;
|
|
m_fTireImpulseAppliedRatio = 0.0f;
|
|
}
|
|
|
|
|
|
fragInstNMGta::~fragInstNMGta()
|
|
{
|
|
Assert(!IsInLevel());
|
|
}
|
|
|
|
//
|
|
// this function lets us skip out of collision tests
|
|
//
|
|
bool fragInstNMGta::ShouldFindImpacts(const phInst* pOtherInst) const
|
|
{
|
|
#if __DEV
|
|
if(this==spDebugBreakRagdoll && pOtherInst==spDebugBreakCar)
|
|
Printf("Found combo");
|
|
#endif
|
|
if (this == pOtherInst)
|
|
{
|
|
if (CPhysics::GetLevel()->IsActive(this->GetLevelIndex()))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "Already active"));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Assertf(0, "I'm activating myself!");
|
|
}
|
|
}
|
|
|
|
if(GetUserData()==NULL)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "No user data"));
|
|
return false;
|
|
}
|
|
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
|
|
if(!pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_AttachedToVehicle ))
|
|
{
|
|
if(pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && !pPed->GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle))
|
|
{
|
|
bool bShouldFindImpacts = false;
|
|
const CVehicle* pVehicle = pPed->GetMyVehicle();
|
|
if (pVehicle)
|
|
{
|
|
const s32 iMySeat = pVehicle->GetSeatManager()->GetPedsSeatIndex(pPed);
|
|
if (pVehicle->IsSeatIndexValid(iMySeat))
|
|
{
|
|
CEntity* pOtherEntity = static_cast<CEntity*>(pOtherInst->GetUserData());
|
|
bool bProjectile = false;
|
|
if(pOtherEntity && pOtherEntity->GetIsTypeObject())
|
|
{
|
|
bProjectile = static_cast<CObject*>(pOtherEntity)->GetAsProjectile() ? true : false;
|
|
}
|
|
|
|
if(bProjectile && (pVehicle->InheritsFromQuadBike() || pVehicle->InheritsFromBike() || pVehicle->InheritsFromAmphibiousQuadBike()))
|
|
{
|
|
bShouldFindImpacts = true;
|
|
}
|
|
|
|
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iMySeat);
|
|
if (pSeatAnimInfo && pSeatAnimInfo->GetKeepCollisionOnWhenInVehicle())
|
|
{
|
|
bShouldFindImpacts = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bShouldFindImpacts)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "In vehicle"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// else
|
|
// {
|
|
// CEntity* pEnt = (CEntity*)(pOtherInst->GetUserData());
|
|
// if (pEnt && pEnt->GetIsTypeVehicle() && pEnt == pPed->GetVehiclePedInside())
|
|
// {
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
|
|
/*** We shouldn't get in here if collision is disabled for this ped as the include flags should have been set correctly.
|
|
if(!pPed->IsCollisionEnabled())
|
|
{
|
|
if(pedAttachExtension && pedAttachExtension->GetIsAttached() && pedAttachExtension->GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL) )
|
|
{
|
|
// Prevent ragdolling when getting onto the seat
|
|
if (pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE_SEAT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Continue and allow ragdoll activation
|
|
}
|
|
else if(pOtherInst && (pOtherInst->GetClassType() == PH_INST_EXPLOSION) && pPed->GetPedResetFlag(CPED_RESET_FLAG_ForceExplosionCollisions))
|
|
{
|
|
// Continue and allow ragdoll activation
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}**
|
|
*/
|
|
|
|
// check against other entity
|
|
CEntity* pOtherEntity = static_cast<CEntity*>(pOtherInst->GetUserData());
|
|
if(pOtherEntity && pOtherEntity->GetIsPhysical())
|
|
{
|
|
if( pPed->GetRagdollOnCollisionIgnorePhysical() &&
|
|
pPed->GetRagdollOnCollisionIgnorePhysical() == pOtherEntity)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "Ignore entity"));
|
|
return false;
|
|
}
|
|
|
|
const fwAttachmentEntityExtension *pedAttachExtension = pPed->GetAttachmentExtension();
|
|
// Check if we are colliding with our attachment
|
|
// Do not use getAttachEntity since this will return NULL if we are detaching
|
|
if(pedAttachExtension && pedAttachExtension->GetAttachParentForced())
|
|
{
|
|
if(pedAttachExtension->GetAttachParentForced()->GetCurrentPhysicsInst()==pOtherInst)
|
|
{
|
|
// physically attached entities will collide together ONLY if flag is set
|
|
if(pedAttachExtension->GetAttachState()==ATTACH_STATE_PHYSICAL
|
|
|| pedAttachExtension->GetAttachState()==ATTACH_STATE_RAGDOLL)
|
|
{
|
|
if(!pedAttachExtension->GetAttachFlag(ATTACH_FLAG_DO_PAIRED_COLL))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "ATTACH_FLAG_DO_PAIRED_COLL"));
|
|
return false;
|
|
}
|
|
}
|
|
// all other attachment types don't collide with each other
|
|
// in most case GetUsesCollision() should return false so won't get this far anyway
|
|
else
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "other attachment type"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// all the fixed-type reasons for a ragdoll not activating
|
|
if( !CPhysics::CanUseRagdolls() ||
|
|
pPed->GetIsAnyFixedFlagSet() ||
|
|
GetInstFlag(FLAG_NEVER_ACTIVATE) ||
|
|
pPed->GetRagdollState()==RAGDOLL_STATE_ANIM_PRESETUP ||
|
|
( pPed->GetRagdollState()==RAGDOLL_STATE_ANIM_LOCKED && !pPed->IsUsingJetpack() ) ) // B*1919511 - Ignore the ragdoll state if they're using a jetpack
|
|
{
|
|
#if __ASSERT
|
|
if (CPhysics::GetLevel()->IsActive(this->GetLevelIndex()))
|
|
pPed->SpewRagdollTaskInfo();
|
|
Assertf(!CPhysics::GetLevel()->IsActive(this->GetLevelIndex()),//
|
|
"This peds ragdoll shouldn't have activated (If statement BitFlags %d%d%d%d%d%d%d%d)!",
|
|
!CPhysics::CanUseRagdolls() ? 1 : 0,
|
|
pPed->GetIsAnyFixedFlagSet() ? 1 : 0,
|
|
pPed->GetIsFixedFlagSet() ? 1 : 0,
|
|
pPed->GetIsFixedByNetworkFlagSet() ? 1 : 0,
|
|
pPed->GetIsFixedUntilCollisionFlagSet() ? 1 : 0,
|
|
GetInstFlag(FLAG_NEVER_ACTIVATE) ? 1 : 0,
|
|
pPed->GetRagdollState()==RAGDOLL_STATE_ANIM_PRESETUP ? 1 : 0,
|
|
pPed->GetRagdollState()==RAGDOLL_STATE_ANIM_LOCKED ? 1 : 0);
|
|
#endif
|
|
|
|
// explosion behaviours are still allowed to find impacts, since it's going to deal with the results differently
|
|
if(!pOtherInst || pOtherInst->GetClassType() != PH_INST_EXPLOSION)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "!PH_INST_EXPLOSION"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// if inst's both have the same parent, don't collide
|
|
if(pOtherInst && pOtherInst->GetUserData() == GetUserData())
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "Same parent"));
|
|
return false;
|
|
}
|
|
|
|
// do this check last to allow chance to skip out earlier and let NoCollision be reset
|
|
if(pPed->GetNoCollisionEntity() && pPed->TestNoCollision(pOtherInst))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "GetNoCollisionEntity"));
|
|
return false;
|
|
}
|
|
|
|
if(pPed->GetRagdollState()==RAGDOLL_STATE_ANIM)
|
|
{
|
|
if(pOtherInst && pOtherInst->IsInLevel())
|
|
{
|
|
// Always consider hits with explosions
|
|
if( pOtherInst->GetClassType() == PH_INST_EXPLOSION )
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "Other == PH_INST_EXPLOSION"));
|
|
return true;
|
|
}
|
|
|
|
if(pOtherEntity)
|
|
{
|
|
if (pPed->GetAnimDirector())
|
|
{
|
|
// are we in a synchronized scene?
|
|
fwAnimDirectorComponentSyncedScene* pSceneComponent = static_cast<fwAnimDirectorComponentSyncedScene*>(pPed->GetAnimDirector()->GetComponentByType(fwAnimDirectorComponent::kComponentTypeSyncedScene));
|
|
if (pSceneComponent && pSceneComponent->IsPlayingSyncedScene())
|
|
{
|
|
fwSyncedSceneId sceneId = pSceneComponent->GetSyncedSceneId();
|
|
fwEntity* sceneAttachParent = fwAnimDirectorComponentSyncedScene::GetSyncedSceneAttachEntity(sceneId);
|
|
// don't collide with the entity our scene is attached to
|
|
if (sceneAttachParent && sceneAttachParent==pOtherEntity)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "SyncedSceneAttachParent"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// kinematic entities don't collide with each other
|
|
if (pOtherEntity && pOtherEntity->GetIsPhysical() && pPed->IsUsingKinematicPhysics())
|
|
{
|
|
CPhysical* pOtherPhys = static_cast<CPhysical*>(pOtherEntity);
|
|
if (pOtherPhys->IsUsingKinematicPhysics())
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "IsUsingKinematicPhysics"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// always allow collision between dead peds and an active ragdoll (note - this does not necessarily result in activating
|
|
// the dead ped (see fragInstNMGTA::PrepareForActivation), but we don't want ragdolling peds falling through corpses.
|
|
if ( pOtherEntity->GetIsTypePed() && ((CPed*)pOtherEntity)->GetUsingRagdoll() && pPed->IsDead())
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "ragdoll vs dead ragdoll"));
|
|
return true;
|
|
}
|
|
// the player ped can knock peds over
|
|
else if((!GetInstFlag(phInstGta::FLAG_NM_NO_PLAYER_PED_ACTIVATE) || pPed->IsDead()) && pOtherEntity->GetIsTypePed() && ((CPed*)pOtherEntity)->IsPlayer())
|
|
{
|
|
// Players can collide with dead ragdolls
|
|
if (pPed->IsDead() && CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_DIE))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "player vs dead ragdoll"));
|
|
return true;
|
|
}
|
|
|
|
if( CTaskNMBehaviour::CanUseRagdoll(pPed, ((CPed*)pOtherEntity)->GetUsingRagdoll() ? RAGDOLL_TRIGGER_IMPACT_PLAYER_PED_RAGDOLL : RAGDOLL_TRIGGER_IMPACT_PLAYER_PED, pOtherEntity))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "fVel > fThreshold"));
|
|
return true;
|
|
}
|
|
}
|
|
else if(pOtherEntity->GetIsTypeObject())
|
|
{
|
|
// Allow the ragdoll bounds of animated peds to collide with pickups (with caveats that are handled in PreComputeImpacts and PrepareForActivation)
|
|
if(pOtherInst->GetArchetype() && CPhysics::GetLevel()->GetInstanceTypeFlag(pOtherInst->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE))
|
|
{
|
|
return true;
|
|
}
|
|
else if(((CObject*)pOtherEntity)->GetAsProjectile())
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "GetAsProjectile"));
|
|
return true;
|
|
}
|
|
else if (pPed->IsDead() && ((CObject*)pOtherEntity)->IsADoor())
|
|
{
|
|
// Allow through if dead animating and contacting a door
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "Frozen corpse touching door"));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Allow objects to cause peds to ragdoll only if they exceed a certain momentum threshold, or if they're frozen corpses.
|
|
const float kfObjectMomentumThreshold = 500.0f;
|
|
|
|
float fObjMass = static_cast<CObject*>(pOtherEntity)->GetMass();
|
|
Vector3 vObjVel = static_cast<CObject*>(pOtherEntity)->GetVelocity();
|
|
Vector3 vOffset = VEC3V_TO_VECTOR3(GetPosition()) - VEC3V_TO_VECTOR3(pOtherInst->GetPosition());
|
|
vOffset.NormalizeSafe();
|
|
float fDotVel = vOffset.Dot(vObjVel);
|
|
float fForce = fDotVel*fObjMass;
|
|
float fObjMomentumSq = fForce * fForce;
|
|
|
|
if((fObjMomentumSq > kfObjectMomentumThreshold*kfObjectMomentumThreshold || pPed->IsDead())
|
|
&& CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_IMPACT_OBJECT, pOtherEntity, fForce))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (pOtherEntity->GetIsTypePed())
|
|
{
|
|
CPed* pOtherPed = (CPed*) pOtherInst->GetUserData();
|
|
if (pOtherPed)
|
|
{
|
|
if (pOtherPed->GetUsingRagdoll())
|
|
{
|
|
// Let other ragdolls at least collide with frozen corpses (won't actually wake up unless underneath the frozen corpse)
|
|
if (pPed->IsDead() && CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_DIE))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "wake up frozen corpses"));
|
|
return true;
|
|
}
|
|
|
|
// Let collision through with carried ped ragdoll if this is the carrier
|
|
if (CTaskNMSlungOverShoulder* pTask = static_cast<CTaskNMSlungOverShoulder*>(
|
|
pOtherPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_SLUNG_OVER_SHOULDER)))
|
|
{
|
|
if (pTask->GetCarrierPed() == pPed)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "GetCarrierPed() == pPed"));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Allow moving ragdolls to activate live non-player peds
|
|
if (CTaskNMBehaviour::CanUseRagdoll(pPed, pOtherPed->IsPlayer() ? RAGDOLL_TRIGGER_IMPACT_PLAYER_PED_RAGDOLL : RAGDOLL_TRIGGER_IMPACT_PED_RAGDOLL, pOtherPed))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "moving ragdoll activation"));
|
|
return true;
|
|
}
|
|
}
|
|
else // Both peds are animated
|
|
{
|
|
// If this a frozen corpse colliding with an animated ped that can activate based on collisions, allow the contact through
|
|
if (pPed->IsDead() && pOtherPed->GetActivateRagdollOnCollision() &&
|
|
CTaskNMBehaviour::CanUseRagdoll(pOtherPed, RAGDOLL_TRIGGER_ACTIVATE_ON_COLLISION))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "frozen corpse vs. bActivateRagdollOnCollision"));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (pOtherEntity->GetIsTypeVehicle())
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "ped vs. vehicle"));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bActivateRagdollOnCollision = pPed->GetActivateRagdollOnCollision();
|
|
// if we want to activate the ragdoll on hitting an object, we need to continue on to precomputeimpacts
|
|
if (bActivateRagdollOnCollision && (pPed->GetAllowedRagdollFixed() || !CPhysics::GetLevel()->IsFixed(pOtherInst->GetLevelIndex())) && CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_ACTIVATE_ON_COLLISION))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "bActivateRagdollOnCollision"));
|
|
return true;
|
|
}
|
|
|
|
/**************************************************/
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, false, "RAGDOLL_STATE_ANIM passed"));
|
|
return false;
|
|
}
|
|
|
|
// Ensure that injuredOnGround is disabled if possibly colliding with another corpse
|
|
if (pOtherEntity && pOtherEntity->GetIsTypePed() && pPed->GetUsingRagdoll() && pPed->IsDead())
|
|
{
|
|
CPed* pOtherPed = (CPed*) pOtherInst->GetUserData();
|
|
if (pOtherPed && pOtherPed->IsDead())
|
|
{
|
|
pPed->GetPedIntelligence()->GetCombatBehaviour().SetFlag(CCombatData::BF_DisableInjuredOnGround);
|
|
}
|
|
}
|
|
|
|
|
|
PDR_ONLY(debugPlayback::RecordNMFindImpacts(this, pOtherInst, true, "All tests passed"));
|
|
return true;
|
|
}
|
|
|
|
void fragInstNMGta::PreComputeImpacts(phContactIterator impacts)
|
|
{
|
|
#if __DEV
|
|
if(this==spDebugBreakRagdoll)
|
|
Printf("Found combo");
|
|
#endif
|
|
|
|
#if HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
ModifyImpactsForSecondSurface(this, impacts, GetBoundGeomIntersectionSecondSurfaceInterpolation());
|
|
#endif // HACK_GTA4_BOUND_GEOM_SECOND_SURFACE
|
|
|
|
fragInstNM::PreComputeImpacts(impacts);
|
|
|
|
if(!impacts.GetMyInstance() || !PHLEVEL->LegitLevelIndex(impacts.GetMyInstance()->GetLevelIndex()))
|
|
{
|
|
// If our instance is now invalid, don't do anything
|
|
return;
|
|
}
|
|
|
|
if(GetUserData() && ((CEntity *)GetUserData())->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)GetUserData();
|
|
const bool bDoPreComputeImpactsTest = pPhysical->DoPreComputeImpactsTest();
|
|
const bool bDoDisableCollisionMaskTest = m_iDisableCollisionMask > 0;
|
|
if(bDoPreComputeImpactsTest || bDoDisableCollisionMaskTest)
|
|
{
|
|
while(!impacts.AtEnd())
|
|
{
|
|
if(bDoPreComputeImpactsTest && pPhysical->TestNoCollision(impacts.GetOtherInstance()))
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordContact(impacts));
|
|
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "TestNoCollision"));
|
|
impacts.DisableImpact();
|
|
}
|
|
else if(bDoDisableCollisionMaskTest &&
|
|
BIT(impacts.GetMyComponent()) & m_iDisableCollisionMask
|
|
&& !impacts.IsConstraint()
|
|
)
|
|
{
|
|
PDR_ONLY(debugPlayback::RecordContact(impacts));
|
|
PDR_ONLY(debugPlayback::RecordModificationToContact(impacts, "Disabled", "DisableCollisionMask"));
|
|
impacts.DisableImpact();
|
|
}
|
|
impacts++;
|
|
}
|
|
}
|
|
|
|
if(pPhysical->GetIsTypePed())
|
|
{
|
|
CPed* pPed=static_cast<CPed*>(pPhysical);
|
|
pPed->ProcessPreComputeImpacts(impacts);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool fragInstNMGta::CheckCanActivate(const CPed* pPed, bool ASSERT_ONLY(bAssert)) const
|
|
{
|
|
// don't let this instance activate if the fixed flag is set
|
|
if(pPed->GetIsAnyFixedFlagSet())
|
|
{
|
|
#if __ASSERT
|
|
if(bAssert)
|
|
Assertf(false, "%s:Ragdoll failed to activate because of AnyFixedFlagSet, Fixed: %d, FixedByNetwork: %d, FixedForCollision: %d", pPed->GetModelName(), pPed->IsBaseFlagSet(fwEntity::IS_FIXED), pPed->IsBaseFlagSet(fwEntity::IS_FIXED_BY_NETWORK), pPed->IsProtectedBaseFlagSet(fwEntity::IS_FIXED_UNTIL_COLLISION));
|
|
#endif // __ASSERT
|
|
return false;
|
|
}
|
|
|
|
// flag can also be set on the phInst to not activate
|
|
if(GetInstFlag(FLAG_NEVER_ACTIVATE))
|
|
{
|
|
#if __ASSERT
|
|
if(bAssert)
|
|
Assertf(false, "%s:Ragdoll failed to activate because of DONT_ACTIVATE flag", pPed->GetModelName());
|
|
#endif // __ASSERT
|
|
return false;
|
|
}
|
|
|
|
// Make sure Ragdolls don't get activated physically if they're not active due to animation being in control
|
|
if(pPed->GetRagdollState() < RAGDOLL_STATE_ANIM || pPed->GetRagdollState() > RAGDOLL_STATE_PHYS)
|
|
{
|
|
#if __ASSERT
|
|
if(bAssert)
|
|
Assertf(false, "%s:Ragdoll failed to activate because of ragdoll state", pPed->GetModelName());
|
|
#endif // __ASSERT
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
dev_bool sbForceZeroLastMatricesA = true; // zero last matrices if std activation
|
|
dev_bool sbForceZeroLastMatricesB = false; // higher level code can request not to zero matrices on activation
|
|
dev_bool sbForceZeroLastMatricesC = false; // zero last matrices if physical activation or ped is seated
|
|
//
|
|
|
|
void fragInstNMGta::SetImpactDamageEntity(CEntity *entity)
|
|
{
|
|
m_pLastImpactDamageEntity = entity;
|
|
m_uTimeSinceImpactDamage = fwTimer::GetTimeInMilliseconds();
|
|
}
|
|
|
|
u32 fragInstNMGta::GetTimeSinceImpactDamage() const
|
|
{
|
|
return fwTimer::GetTimeInMilliseconds() - m_uTimeSinceImpactDamage;
|
|
}
|
|
|
|
Vec3V_Out fragInstNMGta::SendAnimationAverageVelToNM(float fTimeStep)
|
|
{
|
|
Vec3V vecVelFromLastMatrix = Vec3V(V_ZERO);
|
|
ScalarV timeStepV;
|
|
timeStepV.Setf(fTimeStep);
|
|
if (IsGreaterThanAll(timeStepV, ScalarV(V_ZERO)))
|
|
{
|
|
Assertf(IsFiniteStable(GetMatrix().GetCol3()), "GetMatrix().GetCol3() is invalid");
|
|
Assertf(IsFiniteStable(PHSIM->GetLastInstanceMatrix(this).GetCol3()), "PHSIM->GetLastInstanceMatrix(this).GetCol3() is invalid");
|
|
vecVelFromLastMatrix = GetMatrix().GetCol3() - PHSIM->GetLastInstanceMatrix(this).GetCol3();
|
|
vecVelFromLastMatrix /= timeStepV;
|
|
if(!physicsVerifyf(IsFiniteStable(vecVelFromLastMatrix), "vecVelFromLastMatrix is invalid. timeStepV is %f", timeStepV.Getf()))
|
|
{
|
|
vecVelFromLastMatrix.ZeroComponents();
|
|
}
|
|
}
|
|
FRAGNMASSETMGR->SetFromAnimationAverageVel(RCC_VECTOR3(vecVelFromLastMatrix));
|
|
return vecVelFromLastMatrix;
|
|
}
|
|
|
|
void fragInstNMGta::OnActivate(phInst* otherInst)
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->OnActivate(this, otherInst);
|
|
}
|
|
|
|
if(CExplosionManager::InsideExplosionUpdate())
|
|
{
|
|
CExplosionManager::RegisterExplosionActivation(GetLevelIndex());
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::OnDeactivate()
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->OnDeactivate(this);
|
|
}
|
|
}
|
|
|
|
phInst *fragInstNMGta::PrepareForActivation(phCollider **collider, phInst* otherInst, const phConstraintBase * constraint)
|
|
{
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
Assert(pPed);
|
|
|
|
if(GetInstFlag(FLAG_NEVER_ACTIVATE))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. FLAG_NEVER_ACTIVATE is set.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
|
|
if(GetUserData()==NULL)
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. GetUserData()==NULL.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
|
|
// *See CTaskNMBehaviour::CanUseRagdoll* fragInstNMGta::PrepareForActivation will no longer call UpdateFixedWaitingForCollision which
|
|
// allows ragdolls to activate until the next physics update where it will get properly fixed. This should be
|
|
// no different than an active ragdoll losing collision and deactivating. The benefit is that we don't have to
|
|
// call UpdateFixedWaitingForCollision in CTaskNMBehaviour::CanUseRagdoll.
|
|
/*
|
|
// If we aren't fixed, check for nearby collision
|
|
if(!pPed->GetIsFixedUntilCollisionFlagSet())
|
|
{
|
|
pPed->UpdateFixedWaitingForCollision(true);
|
|
Assert(CPhysics::GetSimulator()->GetCollider(this)==NULL);
|
|
}
|
|
*/
|
|
CEntity* pOtherEntity = otherInst ? CPhysics::GetEntityFromInst(otherInst) : NULL;
|
|
|
|
if(otherInst && otherInst->GetUserData() && pPed->IsDead())
|
|
{
|
|
// Disable corpse activations from non-players and ragdolling players unless the specifically instructed to do so
|
|
if(pOtherEntity->GetIsTypePed())
|
|
{
|
|
CPed* pOtherPed = static_cast<CPed*>(pOtherEntity);
|
|
if(((!pOtherPed->IsPlayer() || pOtherPed->GetUsingRagdoll()) && !m_AllowDeadPedActivationThisFrame) || pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollOnPedCollisionWhenDead))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Disable corpse activations from non-players and ragdolling players unless the specifically instructed to do so", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Disable reactivations from another vehicle when our guy is dead and we are allowed to (determined by a ped config flag
|
|
// which can be set from script).
|
|
if(pOtherEntity->GetIsTypeVehicle() && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollOnVehicleCollisionWhenDead))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Disable reactivations from another vehicle when our guy is dead and we are allowed to.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#if __ASSERT && __BANK
|
|
if(CPed::sm_deadRagdollDebug && pPed->IsDead())
|
|
{
|
|
Displayf("----==== [B*1860478] ====----");
|
|
|
|
if(otherInst && pOtherEntity)
|
|
{
|
|
CNetObjGame *netObj = 0;
|
|
if(pOtherEntity->GetIsDynamic())
|
|
netObj = static_cast<CDynamicEntity *>(pOtherEntity)->GetNetworkObject();
|
|
|
|
Displayf("[%u] Dead ped ragdoll just got activated. Other Entity: %s [%s][Type %i] | otherInst: %p | Other Level Index: %i", fwTimer::GetFrameCount(), pOtherEntity->GetDebugName(), netObj ? netObj->GetLogName() : "", pOtherEntity->GetType(), otherInst, otherInst->GetLevelIndex());
|
|
}
|
|
else
|
|
Displayf("[%u] Dead ped ragdoll just got activated. otherInst: %p | Other Level Index: %i", fwTimer::GetFrameCount(), otherInst, otherInst ? otherInst->GetLevelIndex() : -1);
|
|
|
|
sysStack::PrintStackTrace();
|
|
Displayf("----==== [END B*1860478] ====----");
|
|
}
|
|
#endif
|
|
|
|
// Don't allow collisions with pickups to activate a ragdoll
|
|
if(otherInst && otherInst->GetArchetype() && CPhysics::GetLevel()->GetInstanceTypeFlag(otherInst->GetLevelIndex(), ArchetypeFlags::GTA_PICKUP_TYPE))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Don't allow collisions with pickups to activate a ragdoll.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
|
|
// Disable activations from a ground physical vehicle that supports standing
|
|
|
|
CPhysical *pGroundPhysical = pPed->GetGroundPhysical();
|
|
if(!pGroundPhysical && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_RidingTrain))
|
|
{
|
|
pGroundPhysical = pPed->GetLastValidGroundPhysical();
|
|
}
|
|
|
|
if ( (pGroundPhysical || pPed->GetClimbPhysical(true)) && otherInst)
|
|
{
|
|
if(pOtherEntity && pOtherEntity->GetIsTypeVehicle())
|
|
{
|
|
//! Walking between carriages on a train.
|
|
bool bTrainLink = false;
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pOtherEntity);
|
|
if(pVehicle->InheritsFromTrain() && pGroundPhysical)
|
|
{
|
|
CTrain *pTrain = static_cast<CTrain*>(pVehicle);
|
|
if(pTrain->GetLinkedToBackward() == pGroundPhysical || pTrain->GetLinkedToForward() == pGroundPhysical)
|
|
{
|
|
bTrainLink = true;
|
|
}
|
|
}
|
|
|
|
if((pOtherEntity == pGroundPhysical) || (pOtherEntity == pPed->GetClimbPhysical(true)) || bTrainLink)
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pOtherEntity);
|
|
Assert(pVehicle->GetVehicleModelInfo());
|
|
if(pVehicle->CanPedsStandOnTop())
|
|
{
|
|
// Don't allow the ped to ragdoll unless the ground acceleration is extreme.
|
|
// This check should match the one in ShouldFindImpacts - it's needed here as well since if this is the first
|
|
// frame that the ground physical was detected, ShouldFindImpacts wouldn't have had a chance to screen the contacts.
|
|
//if (!IsHighGoundAcceleration(pPed))
|
|
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Disable activations from a ground physical vehicle that supports standing.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// standard checks for ragdoll being able to activate (e.g. not fixed, not ragdoll state locked)
|
|
if(!CheckCanActivate(pPed, false))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. standard checks for ragdoll being able to activate (e.g. not fixed, not ragdoll state locked).", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
|
|
// Dead peds can be activated by sleep islands, so we have to check its ok to ragdoll them here.
|
|
if (pPed->IsDead())
|
|
{
|
|
if (!CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_DIE))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Dead peds can be activated by sleep islands, so we have to check its ok to ragdoll them here..", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// This path gets hit from within the InsertAgent below, we don't want to interfere at that time
|
|
if(m_AgentId != -1)
|
|
return this;
|
|
|
|
if (otherInst)
|
|
{
|
|
CEntity* pOtherEnt = (CEntity*)(otherInst->GetUserData());
|
|
if (pOtherEnt)
|
|
{
|
|
if(pOtherEnt->GetIsTypePed())
|
|
{
|
|
CPed* pOtherPed = (CPed*) otherInst->GetUserData();
|
|
if (pOtherPed && pOtherPed->GetRagdollState() >= RAGDOLL_STATE_PHYS_ACTIVATE)
|
|
{
|
|
// Don't activate due to carrying a ragdoll over your shoulder
|
|
if (CTaskNMSlungOverShoulder* pTask = static_cast<CTaskNMSlungOverShoulder*>(
|
|
pOtherPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_SLUNG_OVER_SHOULDER)))
|
|
{
|
|
if (pTask->GetCarrierPed() == pPed)
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Don't activate due to carrying a ragdoll over your shoulder.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(pOtherEnt->GetIsTypeObject())
|
|
{
|
|
const CObject* pObject = static_cast<CObject *>(pOtherEnt);
|
|
|
|
const CProjectile* pProjectile = pObject->GetAsProjectile();
|
|
if(pProjectile)
|
|
{
|
|
//Don't activate due to a projectile hitting you at less than a certain speed.
|
|
//This was added to prevent peds from tripping over active grenades rolling on the ground.
|
|
if(pProjectile->GetVelocity().Mag2() < square(2.0f))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Don't activate due to a projectile hitting you at less than a certain speed..", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
|
|
const CEntity* pProjectileOwner = pProjectile->GetOwner();
|
|
if (pProjectileOwner && pProjectileOwner->GetIsTypePed() && static_cast<const CPed*>(pProjectileOwner)->GetMyMount()==pPed)
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. if this projectile was tossed by my rider, ignore itL.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL; //if this was tossed by my rider, ignore it
|
|
}
|
|
|
|
// check whether we should activate for this projectile
|
|
// (have to do this here rather than in shouldfindimpacts because
|
|
// we need the projectile to bounce of the ragdoll bounds, but not activate the ragdoll
|
|
// if it is too slow or small)
|
|
Vec3V vecVel = VECTOR3_TO_VEC3V(pProjectile->GetVelocity());
|
|
const float fVel = Mag(vecVel).Getf();
|
|
Vec3V vecOffset = GetPosition() - otherInst->GetPosition();
|
|
ScalarV vDotVel = Dot(vecOffset, vecVel);
|
|
float fDotVel = vDotVel.Getf();
|
|
|
|
float fThreshold = ms_fPlayerPedActivationSpeed;
|
|
|
|
if(pPed->GetIsCrouching()) // crouching peds are generally more stable (also they are less able to balance effectively)
|
|
{
|
|
fThreshold *= 2.0f;
|
|
}
|
|
|
|
float fForce = fDotVel*((CObject*)pOtherEnt)->GetMass();
|
|
|
|
if(fVel < fThreshold || !CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_IMPACT_OBJECT, pOtherEnt, fForce))
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. check whether we should activate for this projectile.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(pPed->GetKeepInactiveRagdollContacts())
|
|
{
|
|
nmDebugf2("[%u] PrepareForActivation:%s(%p) - ABORTED. Keeping inactive ragdoll contacts.", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (PHSIM->IsUpdateRunning())
|
|
{
|
|
// Update the ragdoll's inst matrices. These matrices should only be different
|
|
// if activated in CollisionActivations or due to a constraint. At that point the animated inst matrix has been updated via the collider position integration
|
|
// but the ragdoll's inst doesn't get set until PostPhysics in CPed::UpdateEntityFromPhysics();
|
|
if(HasLastMatrix())
|
|
{
|
|
// Since we activating in the middle of physics update, the ragdoll matrix and time step are mismatching
|
|
// Recompute the last matrix from the mover instance matrix and last time step stored in NM engine
|
|
Mat34V matLast = GetMatrix();
|
|
Vec3V vDisplacement = Scale(VECTOR3_TO_VEC3V(pPed->GetVelocity()), ScalarVFromF32(fwTimer::GetTimeStep()));
|
|
matLast.SetCol3(pPed->GetAnimatedInst()->GetMatrix().d() - vDisplacement);
|
|
CPhysics::GetLevel()->SetLastInstanceMatrix(this, matLast);
|
|
}
|
|
pPed->SetPedResetFlag( CPED_RESET_FLAG_SetLastMatrixDone, true );
|
|
SetMatrix(pPed->GetAnimatedInst()->GetMatrix());
|
|
}
|
|
|
|
nmDebugf2("[%u] Activating ragdoll: %s(%p)", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
|
|
// Set the ragdoll LOD
|
|
if (pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsHandCuffed))
|
|
{
|
|
SetCurrentPhysicsLOD(fragInst::RAGDOLL_LOD_HIGH);
|
|
}
|
|
else if (m_RequestedPhysicsLODOnActivation > -1)
|
|
{
|
|
SetCurrentPhysicsLOD((ePhysicsLOD) m_RequestedPhysicsLODOnActivation);
|
|
}
|
|
else if (!GetSuppressHighRagdollLODOnActivation())
|
|
{
|
|
SetCurrentPhysicsLOD(fragInst::RAGDOLL_LOD_HIGH);
|
|
}
|
|
SetSuppressHighRagdollLODOnActivation(false);
|
|
m_RequestedPhysicsLODOnActivation = -1;
|
|
|
|
#if __ASSERT
|
|
pPed->CollectRagdollStack(CPed::kStackPrepareForActivation);
|
|
#endif //___ASSERT
|
|
|
|
if(pPed->GetRagdollState() < RAGDOLL_STATE_PHYS_ACTIVATE)
|
|
pPed->SetRagdollState(RAGDOLL_STATE_PHYS_ACTIVATE);
|
|
|
|
#if LAZY_RAGDOLL_BOUNDS_UPDATE
|
|
// Update bounds now if they haven't been getting updated
|
|
if (!pPed->AreRagdollBoundsUpToDate())
|
|
{
|
|
PoseBoundsFromSkeleton(true, false);
|
|
Assert(GetCacheEntry());
|
|
GetCacheEntry()->GetBound()->UpdateLastMatricesFromCurrent();
|
|
}
|
|
#endif
|
|
|
|
phBoundComposite* pOriginalBound = (phBoundComposite*)GetArchetype()->GetBound();
|
|
for(int i=0; i<pOriginalBound->GetNumBounds(); i++)
|
|
{
|
|
// check current and last matrices for NAN's
|
|
Assert(RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).a==RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).a
|
|
&& RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).b==RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).b
|
|
&& RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).c==RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).c
|
|
&& RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).d==RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i)).d);
|
|
Assert(RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).a==RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).a
|
|
&& RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).b==RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).b
|
|
&& RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).c==RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).c
|
|
&& RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).d==RCC_MATRIX34(pOriginalBound->GetLastMatrix(i)).d);
|
|
|
|
// check for orthonormal
|
|
if(!pOriginalBound->GetCurrentMatrix(i).IsOrthonormal3x3(ScalarV(V_FLT_SMALL_2)))
|
|
{
|
|
Warningf("Current bound matrix %d is not orthonormal (%f,%f,%f)\n", i, Mag(pOriginalBound->GetCurrentMatrix(i).GetCol0()).Getf(), Mag(pOriginalBound->GetCurrentMatrix(i).GetCol1()).Getf(), Mag(pOriginalBound->GetCurrentMatrix(i).GetCol2()).Getf());
|
|
// in the unlikely event that this happens, normalise the matrix so we don't hit lots of other potential problems inside the physics
|
|
Matrix34 matNormalised = RCC_MATRIX34(pOriginalBound->GetCurrentMatrix(i));
|
|
matNormalised.NormalizeSafe();
|
|
pOriginalBound->SetCurrentMatrix(i, RCC_MAT34V(matNormalised));
|
|
}
|
|
if(!pOriginalBound->GetLastMatrix(i).IsOrthonormal3x3(ScalarV(V_FLT_SMALL_2)))
|
|
{
|
|
Warningf("Last bound matrix %d is not orthonormal (%f,%f,%f)\n", i, Mag(pOriginalBound->GetLastMatrix(i).GetCol0()).Getf(), Mag(pOriginalBound->GetLastMatrix(i).GetCol1()).Getf(), Mag(pOriginalBound->GetLastMatrix(i).GetCol2()).Getf());
|
|
// in the unlikely event that this happens, normalise the matrix so we don't hit lots of other potential problems inside the physics
|
|
Matrix34 matNormalised = RCC_MATRIX34(pOriginalBound->GetLastMatrix(i));
|
|
matNormalised.NormalizeSafe();
|
|
pOriginalBound->SetLastMatrix(i, RCC_MAT34V(matNormalised));
|
|
}
|
|
|
|
// check the positional offset isn't to great
|
|
#if __ASSERT
|
|
bool bValidOffset = true;
|
|
if (!Verifyf(MagSquared(pOriginalBound->GetCurrentMatrix(i).GetCol3()).Getf() < 200.0f,
|
|
"Current bound matrix %d offset is %f - please check ped's animations to ensure correct mover/root bone DOFs (see TTY/log for animation list)",
|
|
i, Mag(pOriginalBound->GetCurrentMatrix(i).GetCol3()).Getf()))
|
|
{
|
|
bValidOffset = false;
|
|
}
|
|
if (!Verifyf(MagSquared(pOriginalBound->GetLastMatrix(i).GetCol3()).Getf() < 200.0f,
|
|
"Last bound matrix %d offset is %f - please check ped's animations to ensure correct mover/root bone DOFs (see TTY/log for animation list)",
|
|
i, Mag(pOriginalBound->GetLastMatrix(i).GetCol3()).Getf()))
|
|
{
|
|
bValidOffset = false;
|
|
}
|
|
if (!bValidOffset)
|
|
{
|
|
CAIDebugInfo::DebugInfoPrintParams printParams;
|
|
CAnimationDebugInfo tempDebugInfo(pPed, printParams);
|
|
tempDebugInfo.Print();
|
|
tempDebugInfo.SetLogType(AILogging::TTY);
|
|
tempDebugInfo.Print();
|
|
}
|
|
#endif // __ASSERT
|
|
}
|
|
|
|
#if __DEV
|
|
// shouldn't need to set these every activation, only in CPhysics::Init, but for now allows us to easily modify them
|
|
FRAGNMASSETMGR->SetFromAnimationMaxSpeed(CPhysics::GetRagdollActivationAnimVelClamp());
|
|
FRAGNMASSETMGR->SetFromAnimationMaxAngSpeed(CPhysics::GetRagdollActivationAnimAngVelClamp());
|
|
#endif
|
|
|
|
// Check if NM agents are disabled
|
|
bool bWasNMActivationBlocked = IsNMActivationBlocked();
|
|
if (CTaskNMBehaviour::sm_OnlyUseRageRagdolls)
|
|
SetBlockNMActivation(true);
|
|
|
|
// Reset the counter impulse counter
|
|
m_CounterImpulseCount = -1;
|
|
|
|
// Determine if the last matrix positons need to be set to zero
|
|
bool bZeroLastMatrices = sbForceZeroLastMatricesA;
|
|
if(m_bDontZeroMatricesOnActivation)
|
|
bZeroLastMatrices = sbForceZeroLastMatricesB;
|
|
else if(pPed->GetIsAttached() && pPed->GetAttachFlag(ATTACH_FLAG_COL_ON))
|
|
bZeroLastMatrices = sbForceZeroLastMatricesC;
|
|
SetZeroLastMatricesOnActivation(bZeroLastMatrices);
|
|
|
|
//Check if there is an override for the velocity scale.
|
|
float fVelocityScale = 1.0f / CPhysics::GetNumTimeSlices();
|
|
if(m_IncomingAnimVelocityScaleReset != -1.0f)
|
|
{
|
|
//Set the velocity scale.
|
|
fVelocityScale *= m_IncomingAnimVelocityScaleReset;
|
|
|
|
//The velocity scale is a reset value, it is only good once.
|
|
m_IncomingAnimVelocityScaleReset = -1.0f;
|
|
}
|
|
|
|
//Set the velocity scale.
|
|
FRAGNMASSETMGR->SetIncomingAnimVelocityScale(fVelocityScale);
|
|
|
|
// Determine the average animation velocity, about which we'll clamp velocities
|
|
if (fVelocityScale == 0.0f)
|
|
{
|
|
// If the scale is 0.0, then we also set the average animation velocity to 0 in order to have the ragdoll
|
|
// start with zero velocity, as that seems to be the desired useage.
|
|
FRAGNMASSETMGR->SetFromAnimationAverageVel(Vector3::ZeroType);
|
|
}
|
|
else
|
|
{
|
|
// This will set the animation velocity based on the last and current matrices if CPed::SwitchToRagdollInternal
|
|
// was not called prior to this.
|
|
SendAnimationAverageVelToNM(fwTimer::GetTimeStep());
|
|
}
|
|
|
|
// need to detach the ped if it's currently attached to something
|
|
if (pPed->GetIsAttached())
|
|
{
|
|
u16 iDetachFlags = 0;
|
|
bool bDetachInPlace = false;
|
|
if (pPed->GetAttachState() == ATTACH_STATE_PED_ENTER_CAR || pPed->GetAttachState() == ATTACH_STATE_PED_EXIT_CAR)
|
|
{
|
|
bDetachInPlace = true;
|
|
}
|
|
else if (pPed->GetAttachState() == ATTACH_STATE_PED_IN_CAR)
|
|
{
|
|
if (pPed->GetAttachParent() && static_cast<CEntity*>(pPed->GetAttachParent())->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pVeh = static_cast<const CVehicle*>(pPed->GetAttachParent());
|
|
const s32 iSeatIndex = pVeh->GetSeatManager()->GetPedsSeatIndex(pPed);
|
|
if (pVeh->IsSeatIndexValid(iSeatIndex))
|
|
{
|
|
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVeh->GetSeatAnimationInfo(iSeatIndex);
|
|
if (pSeatAnimInfo->GetCanDetachViaRagdoll())
|
|
{
|
|
bDetachInPlace = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If we're dead and not using the ATTACH_STATE_PED_IN_CAR state but we're attached to a vehicle then allow us to be detached in-place
|
|
else if (pPed->IsDead() && pPed->GetAttachParent() != NULL && static_cast<CEntity*>(pPed->GetAttachParent())->GetIsTypeVehicle())
|
|
{
|
|
const CVehicle* pVehicle = static_cast<const CVehicle*>(pPed->GetAttachParent());
|
|
if(pVehicle->GetVehicleType() == VEHICLE_TYPE_BOAT || pVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE || pVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
pPed->ProcessAllAttachments();
|
|
bDetachInPlace = true;
|
|
}
|
|
}
|
|
|
|
if (bDetachInPlace)
|
|
{
|
|
iDetachFlags |= DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_EXCLUDE_VEHICLE|DETACH_FLAG_IGNORE_SAFE_POSITION_CHECK;
|
|
}
|
|
pPed->DetachFromParent(iDetachFlags);
|
|
}
|
|
|
|
pPed->GetSkeleton()->GetObjectMtx(0).GetCol3Ref() = pPed->GetSkeleton()->GetLocalMtx(0).GetCol3Ref() = m_RootBonePosAtBoundsUpdate;
|
|
|
|
// ok to go ahead and activate
|
|
phInst *pReturn = fragInstNM::PrepareForActivation(collider, otherInst, constraint);
|
|
|
|
// Reset the average velocity
|
|
FRAGNMASSETMGR->SetFromAnimationAverageVel(Vector3(Vector3::ZeroType), true);
|
|
|
|
//Restore the default velocity scale.
|
|
FRAGNMASSETMGR->SetIncomingAnimVelocityScale(1.0f);
|
|
|
|
// Reset NM disabled state
|
|
if (CTaskNMBehaviour::sm_OnlyUseRageRagdolls && !bWasNMActivationBlocked)
|
|
SetBlockNMActivation(false);
|
|
|
|
if(pReturn)
|
|
{
|
|
// Set the ragdoll's damping parameters (accessed via rag tuning in rag->Physics->Simulator->Ragdoll tuning)
|
|
if (pPed->GetRagdollInst()->GetArchetype())
|
|
{
|
|
phArchetypeDamp* pArch = static_cast<phArchetypeDamp*>(pPed->GetRagdollInst()->GetArchetype());
|
|
pArch->ActivateDamping(phArchetypeDamp::LINEAR_C, Vector3(phArticulatedBody::sm_RagdollDampingLinC, phArticulatedBody::sm_RagdollDampingLinC, phArticulatedBody::sm_RagdollDampingLinC));
|
|
pArch->ActivateDamping(phArchetypeDamp::LINEAR_V, Vector3(phArticulatedBody::sm_RagdollDampingLinV, phArticulatedBody::sm_RagdollDampingLinV, phArticulatedBody::sm_RagdollDampingLinV));
|
|
pArch->ActivateDamping(phArchetypeDamp::LINEAR_V2, Vector3(phArticulatedBody::sm_RagdollDampingLinV2, phArticulatedBody::sm_RagdollDampingLinV2, phArticulatedBody::sm_RagdollDampingLinV2));
|
|
pArch->ActivateDamping(phArchetypeDamp::ANGULAR_C, Vector3(phArticulatedBody::sm_RagdollDampingAngC, phArticulatedBody::sm_RagdollDampingAngC, phArticulatedBody::sm_RagdollDampingAngC));
|
|
pArch->ActivateDamping(phArchetypeDamp::ANGULAR_V, Vector3(phArticulatedBody::sm_RagdollDampingAngV, phArticulatedBody::sm_RagdollDampingAngV, phArticulatedBody::sm_RagdollDampingAngV));
|
|
pArch->ActivateDamping(phArchetypeDamp::ANGULAR_V2, Vector3(phArticulatedBody::sm_RagdollDampingAngV2, phArticulatedBody::sm_RagdollDampingAngV2, phArticulatedBody::sm_RagdollDampingAngV2));
|
|
}
|
|
|
|
// Note the activation time
|
|
m_fActivationStartTime = fwTimer::GetTimeInMilliseconds();
|
|
|
|
#if LAZY_RAGDOLL_BOUNDS_UPDATE
|
|
// Indicate that the bounds are up to date (since physics is updating them each frame)
|
|
pPed->SetRagdollBoundsUpToDate(true);
|
|
pPed->RequestRagdollBoundsUpdate(0); // Resetting the animated update request frames
|
|
#endif
|
|
|
|
// Not really sure if it should be guaranteed that the instance is already in the level. If this assert fires and we get bugs about it,
|
|
// the verify can be removed and an else block should be added to set the archetype type/include flags instead.
|
|
if(AssertVerify(pReturn->IsInLevel()))
|
|
{
|
|
CPhysics::GetLevel()->SetInstanceTypeFlags(pReturn->GetLevelIndex(), GetTypePhysics()->GetArchetype()->GetTypeFlags());
|
|
CPhysics::GetLevel()->SetInstanceIncludeFlags(pReturn->GetLevelIndex(), GetTypePhysics()->GetArchetype()->GetIncludeFlags());
|
|
}
|
|
|
|
phBoundComposite* pNewBound = static_cast<phBoundComposite*>(pReturn->GetArchetype()->GetBound());
|
|
phBoundComposite* pOrigBound = GetTypePhysics()->GetCompositeBounds();
|
|
|
|
Assertf(pOrigBound->GetNumBounds() == pNewBound->GetNumBounds(), "Bound count in pOrigBound (%d) does not match bound count in pNewBound (%d)", pOrigBound->GetNumBounds(), pNewBound->GetNumBounds());
|
|
|
|
for(int i=0; i<pOrigBound->GetNumBounds(); i++)
|
|
{
|
|
if(pNewBound->GetBound(i) && pNewBound->GetBound(i)->GetType()==phBound::CAPSULE
|
|
&& pOrigBound->GetBound(i) && pOrigBound->GetBound(i)->GetType()==phBound::CAPSULE)
|
|
{
|
|
float fOrigRadius = static_cast<phBoundCapsule*>(pOrigBound->GetBound(i))->GetRadius();
|
|
static_cast<phBoundCapsule*>(pNewBound->GetBound(i))->SetCapsuleRadius(fOrigRadius);
|
|
}
|
|
}
|
|
|
|
// Inform the vehicle ejection code of any vehicle association
|
|
CheckForVehicleAssociation();
|
|
|
|
if(GetNMAgentID()!=-1)
|
|
{
|
|
// Disabling this 'fix' by default now because certain behaviors (pointGun namely) use ITMs even after configureCharacter has been called below so
|
|
// we can't put the ITMs back to how they were...
|
|
#if ART_ENABLE_BSPY && 0
|
|
// Looks like there is a bug in bSpy where it is using the current transforms sent for the character configuration as the current transforms for the actual bounds
|
|
// To get around this we resend the current bound transforms after doing the character configuration
|
|
int numChildren = GetTypePhysics()->GetNumChildren();
|
|
|
|
// Store the current transforms prior to character configuration (which overwrites the current transforms)
|
|
Matrix34* storedMatrices = Alloca(Matrix34, numChildren);
|
|
Matrix34* currentMatrices = FRAGNMASSETMGR->GetWorldCurrentMatrices(GetNMAgentID());
|
|
memcpy(storedMatrices, currentMatrices, sizeof(Matrix34) * numChildren);
|
|
#endif
|
|
|
|
bool bWeaponInLeftHand = false;
|
|
bool bWeaponInRightHand = false;
|
|
|
|
crSkeletonData *pSkeletonData = pPed->GetDrawable()->GetSkeletonData();
|
|
if (pSkeletonData)
|
|
{
|
|
crSkeleton tempSkeleton;
|
|
tempSkeleton.Init(*pSkeletonData, NULL);
|
|
|
|
// ensure matrix is in memory all the time
|
|
Mat34V tempMatrix = this->GetMatrix();
|
|
tempSkeleton.SetParentMtx(&tempMatrix);
|
|
|
|
// Grab the target pose for nm to aim for (only need to fill in the local matrices)
|
|
pPed->GetZeroPoseForNM(tempSkeleton, bWeaponInLeftHand, bWeaponInRightHand);
|
|
|
|
// Update the object space matrices
|
|
tempSkeleton.Update();
|
|
|
|
#if __BANK
|
|
// Allow incoming transforms to be debugged in-game.
|
|
if(CNmDebug::ms_bDrawTransforms)
|
|
{
|
|
CNmDebug::SetComponentTMsFromSkeleton(tempSkeleton);
|
|
}
|
|
#endif // __BANK
|
|
|
|
fragInstNM::SetComponentTMsFromSkeleton(tempSkeleton);//name change fragInstNM::ActivePose(*pTempSkeleton);
|
|
}
|
|
|
|
// Bias the COM down and widen the stance for armed peds
|
|
static float stanceBiasSWAT = 0.1f;
|
|
static float COMBiasSWAT = -0.05f;
|
|
static float stanceBiasOther = 0.05f;
|
|
static float COMBiasOther = -0.025f;
|
|
float stanceBias = 0.0f;
|
|
float COMBias = 0.0f;
|
|
|
|
if (bWeaponInRightHand || bWeaponInLeftHand)
|
|
{
|
|
if (pPed->GetPedType() == PEDTYPE_SWAT)
|
|
{
|
|
stanceBias = stanceBiasSWAT;
|
|
COMBias = COMBiasSWAT;
|
|
}
|
|
else
|
|
{
|
|
stanceBias = stanceBiasOther;
|
|
COMBias = COMBiasOther;
|
|
}
|
|
}
|
|
|
|
ConfigureCharacter(GetNMAgentID(), true, !bWeaponInLeftHand, !bWeaponInRightHand, stanceBias, COMBias);
|
|
|
|
#if ART_ENABLE_BSPY && 0
|
|
memcpy(currentMatrices, storedMatrices, sizeof(Matrix34) * numChildren);
|
|
|
|
ART::gRockstarARTInstance->setComponentTMs(ART::gRockstarARTInstance->getCharacterFromAgentID(GetNMAgentID()),
|
|
currentMatrices,
|
|
numChildren,
|
|
ART::kITSNone,
|
|
ART::kITSourceCurrent);
|
|
#endif
|
|
|
|
SetARTFeedbackInterfaceGta();
|
|
setProbeTypeIncludeFlags(GetNMAgentID(), ArchetypeFlags::GTA_WEAPON_TYPES);
|
|
|
|
//GetBulletForce()->Reset();
|
|
}
|
|
}
|
|
|
|
if (*collider)
|
|
{
|
|
// Stop live peds from sleeping
|
|
if((*collider)->GetSleep())
|
|
{
|
|
if (pPed->GetDeathState() != DeathState_Dead)
|
|
{
|
|
(*collider)->GetSleep()->SetActiveSleeper(true);
|
|
(*collider)->GetSleep()->SetVelTolerance2(0.0f);
|
|
(*collider)->GetSleep()->SetAngVelTolerance2(0.0f);
|
|
(*collider)->GetSleep()->SetInternalMotionTolerance2Sum(0.0f);
|
|
}
|
|
}
|
|
|
|
if ((*collider)->IsArticulated())
|
|
{
|
|
static int tempDisable = 1;
|
|
static_cast<phArticulatedCollider*>(*collider)->TemporarilyDisableSelfCollision(tempDisable);
|
|
|
|
// Initialize the collider velocity to the root link velocity, in case anyone queries it before the collider update runs
|
|
(*collider)->SetVelocityOnly(((phArticulatedCollider*)(*collider))->GetBody()->GetLinearVelocityNoProp(0).GetIntrin128ConstRef());
|
|
(*collider)->SetAngVelocityOnly(((phArticulatedCollider*)(*collider))->GetBody()->GetAngularVelocityNoProp(0).GetIntrin128ConstRef());
|
|
}
|
|
}
|
|
|
|
pPed->GetAnimDirector()->PoseRagdollFrameFromCreature(*pPed->GetAnimDirector()->GetCreature());
|
|
|
|
// Set the skeleton back to the stored ragdoll frame root
|
|
// we need to do this as the root bone may have been changed
|
|
// since the last call to PoseBoundsFromSkeleton
|
|
if (pPed->GetAnimDirector() && pPed->GetCreature() && pPed->GetSkeleton())
|
|
{
|
|
fwAnimDirectorComponentRagDoll* comp = static_cast<fwAnimDirectorComponentRagDoll*>(pPed->GetAnimDirector()->GetComponentByType(fwAnimDirectorComponent::kComponentTypeRagDoll));
|
|
if(comp)
|
|
{
|
|
comp->PoseSkelRootFromRagdollRootFrame(*pPed->GetCreature());
|
|
pPed->GetSkeleton()->Update();
|
|
}
|
|
}
|
|
|
|
// Reset the corpse friction override
|
|
pPed->SetCorpseRagdollFriction(-1.0f);
|
|
|
|
#if __BANK
|
|
// Collect data regarding how many ragdolls are used
|
|
if (ms_bLogUsedRagdolls)
|
|
{
|
|
CheckRagdollsUsedDebug();
|
|
}
|
|
#endif
|
|
|
|
return pReturn;
|
|
|
|
}
|
|
|
|
bool fragInstNMGta::PrepareForDeactivation(bool colliderManagedBySim, bool forceDeactivate)
|
|
{
|
|
//Displayf("Ragdoll Deactivated (frame:%d)\n", fwTimer::GetFrameCount());
|
|
bool bReturn = fragInstNM::PrepareForDeactivation(colliderManagedBySim, forceDeactivate);
|
|
|
|
// Reset the activation time
|
|
m_fActivationStartTime = 0;
|
|
|
|
// If a ped is being deactivated due to being asleep, add it to a queue so that it can be switched to animation in post-physics
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
nmEntityDebugf(pPed, "fragInstNMGta::PrepareForDeactivation - Deactivating ragdoll %s. %s", bReturn ? "SUCCESSFUL" : "FAILED", pPed && pPed->GetCollider() && pPed->GetCollider()->GetSleep()->IsAsleep() ? "Ragdoll is asleep" : "Not sleeping");
|
|
if (pPed && pPed->GetCollider() && pPed->GetCollider()->GetSleep()->IsAsleep())
|
|
{
|
|
Assert(g_SettledPeds.GetCount() < g_SettledPeds.GetMaxCount());
|
|
if (g_SettledPeds.GetCount() < g_SettledPeds.GetMaxCount())
|
|
g_SettledPeds.Push(RegdPed(pPed));
|
|
}
|
|
|
|
if (GetCached())
|
|
{
|
|
GetCacheEntry()->ActivateInstabilityPrevention(-1);
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
void fragInstNMGta::PoseBoundsFromSkeleton(bool onlyAdjustForInternalMotion, bool updateLastBoundsMatrices, bool, s8, const s8*)
|
|
{
|
|
GrabRootFrame();
|
|
|
|
if (fragCacheEntry* entry = GetCacheEntry())
|
|
{
|
|
if (updateLastBoundsMatrices)
|
|
entry->GetBound()->UpdateLastMatricesFromCurrent();
|
|
|
|
const fragPhysicsLOD* physicsLOD = GetTypePhysics();
|
|
const u32* componentToBoneIndex = reinterpret_cast<const u32*>(entry->GetComponentToBoneIndexMap());
|
|
u32 numChildren = physicsLOD->GetNumChildren();
|
|
fragTypeChild** child = physicsLOD->GetAllChildren();
|
|
const Mat34V* objectMtxs = GetSkeleton()->GetObjectMtxs();
|
|
Mat34V* currentBound = const_cast<Mat34V*>(entry->GetBound()->GetCurrentMatrices());
|
|
|
|
u32 count4 = numChildren >> 2;
|
|
for (; count4; count4--)
|
|
{
|
|
Mat34V instanceFromBone0 = objectMtxs[componentToBoneIndex[0]];
|
|
Mat34V instanceFromBone1 = objectMtxs[componentToBoneIndex[1]];
|
|
Mat34V instanceFromBone2 = objectMtxs[componentToBoneIndex[2]];
|
|
Mat34V instanceFromBone3 = objectMtxs[componentToBoneIndex[3]];
|
|
|
|
// TODO - please give me an array of bound matrices
|
|
Mat34V boneFromComponent0 = RCC_MAT34V((child[0])->GetUndamagedEntity()->GetBoundMatrix());
|
|
Mat34V boneFromComponent1 = RCC_MAT34V((child[1])->GetUndamagedEntity()->GetBoundMatrix());
|
|
Mat34V boneFromComponent2 = RCC_MAT34V((child[2])->GetUndamagedEntity()->GetBoundMatrix());
|
|
Mat34V boneFromComponent3 = RCC_MAT34V((child[3])->GetUndamagedEntity()->GetBoundMatrix());
|
|
|
|
Transform(currentBound[0], instanceFromBone0, boneFromComponent0);
|
|
Transform(currentBound[1], instanceFromBone1, boneFromComponent1);
|
|
Transform(currentBound[2], instanceFromBone2, boneFromComponent2);
|
|
Transform(currentBound[3], instanceFromBone3, boneFromComponent3);
|
|
|
|
componentToBoneIndex += 4;
|
|
currentBound += 4;
|
|
child += 4;
|
|
}
|
|
|
|
u32 count1 = numChildren & 3;
|
|
for (; count1; count1--)
|
|
{
|
|
Mat34V instanceFromBone = objectMtxs[*componentToBoneIndex];
|
|
Mat34V boneFromComponent = RCC_MAT34V((*child)->GetUndamagedEntity()->GetBoundMatrix());
|
|
Transform(*currentBound, instanceFromBone, boneFromComponent);
|
|
|
|
componentToBoneIndex += 1;
|
|
currentBound += 1;
|
|
child += 1;
|
|
}
|
|
|
|
// MIGRATE_FIXME - the fixed true value passed into this can now be drived by the argument
|
|
phBoundComposite *bnd = entry->GetBound();
|
|
bnd->CalculateCompositeExtents(onlyAdjustForInternalMotion, true);
|
|
|
|
//Revisit this later
|
|
//Updating the physics level should really NOT be done from here -- instead the calling code should handle it itself
|
|
//since not every call to PoseBoundsFromSkeelton would require a level update
|
|
Assert(IsInLevel());
|
|
const phArchetype* pArch = GetArchetype();
|
|
if(pArch)
|
|
{
|
|
const phBound* pInstBound = pArch->GetBound();
|
|
if(pInstBound == bnd)
|
|
{
|
|
const int levelIndex = GetLevelIndex();
|
|
PHLEVEL->UpdateObjectLocationAndRadius(levelIndex, (Mat34V_Ptr)(NULL));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::GrabRootFrame()
|
|
{
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
if (pPed)
|
|
{
|
|
m_RootBonePosAtBoundsUpdate = GetSkeleton()->GetLocalMtx(0).GetCol3();
|
|
|
|
// pose the ragdoll frame root. We use this to keep the root bone posed correctly
|
|
// when we switch to ragdoll (as it is not mapped directly to a ragdoll bound).
|
|
if (pPed->GetAnimDirector() && pPed->GetCreature())
|
|
{
|
|
fwAnimDirectorComponentRagDoll* comp = static_cast<fwAnimDirectorComponentRagDoll*>(pPed->GetAnimDirector()->GetComponentByType(fwAnimDirectorComponent::kComponentTypeRagDoll));
|
|
if(comp)
|
|
{
|
|
comp->PoseRagdollRootFrame(*pPed->GetCreature());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void fragInstNMGta::SetNMAssetSize(int newSize)
|
|
{
|
|
int currentAssetID = GetARTAssetID();
|
|
if (currentAssetID < 0)
|
|
{
|
|
Assertf(0, "fragInstNMGta::SwitchNMAssetSize() called on a non-NM agent");
|
|
return;
|
|
}
|
|
|
|
// Determine what the new asset ID should be (based on gender and current size)
|
|
int currentSize = currentAssetID <= 1 ? 0 : 1;
|
|
if (currentSize == newSize)
|
|
{
|
|
Warningf("fragInstNMGta::SwitchNMAssetSize() - Input size is already the current size");
|
|
return;
|
|
}
|
|
|
|
// Determine what the new asset ID should be (based on gender and current size)
|
|
int newID = 0;
|
|
if (currentAssetID == 0 || currentAssetID == 2)
|
|
newID = currentAssetID == 0 ? 2 : 0;
|
|
else
|
|
newID = currentAssetID == 1 ? 3 : 1;
|
|
|
|
// Set the new art asset ID, physics type data, and ragdoll LOD
|
|
Displayf("Switching NM art asset ID to %d.", newID);
|
|
((fragType*) GetType())->SetARTAssetID(newID);
|
|
((fragType*) GetType())->SetPhysicsLODGroup(CPhysics::GetManagedPhysicsLODGroup(GetType()->GetARTAssetID()).GetPhysicsLODGroup());
|
|
m_CurrentLOD = RAGDOLL_LOD_HIGH;
|
|
|
|
PHSIM->GetContactMgr()->RemoveAllContactsWithInstance(this);
|
|
|
|
// Need to do more if there's a cache entry
|
|
fragCacheEntry* cacheEntry = GetCacheEntry();
|
|
if (cacheEntry)
|
|
{
|
|
cacheEntry->ReloadPhysicsData();
|
|
ASSERT_ONLY(cacheEntry->GetBound()->CheckCachedMinMaxConsistency());
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::SetCurrentPhysicsLOD(ePhysicsLOD newLOD)
|
|
{
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
if (pPed)
|
|
{
|
|
pPed->RemoveConstraints(this);
|
|
}
|
|
|
|
fragInst::SetCurrentPhysicsLOD(newLOD);
|
|
}
|
|
|
|
void fragInstNMGta::CheckForVehicleAssociation()
|
|
{
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
if (pPed)
|
|
{
|
|
CVehicle *pVehicle = pPed->GetMyVehicle();
|
|
// Only allow associating with a NULL vehicle if we just left - otherwise peds that are running to a car with CPED_RESET_FLAG_IsEnteringOrExitingVehicle set will get
|
|
// an association. Once SetAssociatedVehicle(NULL) gets called we will become "ejected" from the next vehicle we touch since IsAssociatedWithVehicle will return true
|
|
// always.
|
|
if(pVehicle || pPed->GetHasJustLeftVehicle())
|
|
{
|
|
bool isInvalidVehicleForEjection = pVehicle && (pVehicle->GetVehicleType() != VEHICLE_TYPE_CAR);
|
|
|
|
if (pVehicle && !isInvalidVehicleForEjection)
|
|
{
|
|
for( s32 iSeat = 0; iSeat < pVehicle->GetSeatManager()->GetMaxSeats(); iSeat++ )
|
|
{
|
|
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iSeat);
|
|
Assert(pSeatAnimInfo);
|
|
if(pSeatAnimInfo && pSeatAnimInfo->GetCanDetachViaRagdoll())
|
|
{
|
|
isInvalidVehicleForEjection = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isInvalidVehicleForEjection && (pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || pPed->GetHasJustLeftVehicle() || pPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringOrExitingVehicle)))
|
|
{
|
|
SetAssociatedWithVehicle(pVehicle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::SetAssociatedWithVehicle(CVehicle *pVehicle)
|
|
{
|
|
m_VehicleBeingEjectedFrom = pVehicle;
|
|
if (m_EjectedFromVehicleFrames < 0)
|
|
{
|
|
m_EjectedFromVehicleFrames = -3;
|
|
}
|
|
};
|
|
|
|
bool fragInstNMGta::IsAssociatedWithVehicle(CVehicle *pVehicle) const
|
|
{
|
|
bool bIsAssociated = false;
|
|
|
|
if (!m_VehicleBeingEjectedFrom || (m_VehicleBeingEjectedFrom && m_VehicleBeingEjectedFrom == pVehicle))
|
|
{
|
|
bIsAssociated = m_EjectedFromVehicleFrames < -1;
|
|
}
|
|
|
|
return bIsAssociated;
|
|
}
|
|
|
|
void fragInstNMGta::SetRagdollEjectedFromVehicle(CVehicle *vehicle)
|
|
{
|
|
m_VehicleBeingEjectedFrom = vehicle;
|
|
m_EjectedFromVehicleFrames = 30;
|
|
};
|
|
|
|
void fragInstNMGta::ProcessVehicleEjection(float fTimeStep)
|
|
{
|
|
// Check if we're associated with a vehicle, and if so increment - this ensures that we get a couple frames of association
|
|
if (m_EjectedFromVehicleFrames < -1)
|
|
{
|
|
m_EjectedFromVehicleFrames++;
|
|
}
|
|
else if (m_EjectedFromVehicleFrames > -1) // other wise decrement
|
|
{
|
|
m_EjectedFromVehicleFrames--;
|
|
}
|
|
|
|
// If the vehicle you're ejecting from is a train - push to the side
|
|
if (IsEjectingFromVehicle() && GetEjectingVehicle() && GetEjectingVehicle()->InheritsFromTrain())
|
|
{
|
|
// Update general ragdoll related items here
|
|
CPed *pPed = (CPed *)GetUserData();
|
|
|
|
Vec3V vPushDir;
|
|
vPushDir = Cross(RCC_VEC3V(GetEjectingVehicle()->GetVelocity()), Vec3V(V_UP_AXIS_WONE));
|
|
vPushDir = NormalizeSafe(vPushDir, Vec3V(V_ZERO));
|
|
Vec3V vecCarToPed = Subtract(pPed->GetTransform().GetPosition(), GetEjectingVehicle()->GetTransform().GetPosition());
|
|
ScalarV vDot = Dot(vecCarToPed, vPushDir);
|
|
static float sfPushMult = 10.0f;
|
|
float integratedPushMult = fTimeStep * sfPushMult;
|
|
if (vDot.Getf() < 0.0f)
|
|
{
|
|
integratedPushMult *= -1.0f;
|
|
}
|
|
vPushDir = Scale(vPushDir, ScalarVFromF32(integratedPushMult));
|
|
|
|
phArticulatedBody *body = pPed->GetRagdollInst()->GetArticulatedBody();
|
|
for (int iLink = 0; iLink < body->GetNumBodyParts(); iLink++)
|
|
{
|
|
if (GetBodyPartGroup(iLink) == fragInstNMGta::kSpine)
|
|
{
|
|
body->ApplyImpulse(iLink, Scale(vPushDir, body->GetMass(iLink)), Vec3V(V_ZERO));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::ResetVehicleEjectionMembers()
|
|
{
|
|
m_EjectedFromVehicleFrames = -1;
|
|
m_VehicleBeingEjectedFrom = NULL;
|
|
}
|
|
|
|
|
|
#if __BANK
|
|
void fragInstNMGta::CheckRagdollsUsedDebug()
|
|
{
|
|
// Collect data regarding how many NM agents are used
|
|
int numActiveAgents = FRAGNMASSETMGR->GetAgentCapacity(0) - FRAGNMASSETMGR->GetAgentCount(0);
|
|
int numActiveRageRagdolls = FRAGNMASSETMGR->GetNumActiveNonNMRagdolls();
|
|
if (CTheScripts::GetPlayerIsOnAMission() && numActiveAgents > ms_MaxNMAgentsUsedCurrentLevel)
|
|
{
|
|
Displayf("[RAGDOLL WATERMARK] [%u] The maximum number of NM AGENTS used during [%s] has increased to %d.",
|
|
fwTimer::GetTimeInMilliseconds(), CDebug::GetCurrentMissionName(), numActiveAgents);
|
|
ms_MaxNMAgentsUsedCurrentLevel = numActiveAgents;
|
|
}
|
|
if (numActiveAgents > ms_MaxNMAgentsUsedGlobally)
|
|
{
|
|
Displayf("[RAGDOLL WATERMARK] [%u] The maximum number of NM AGENTS used GLOBALLY has increased to %d.",
|
|
fwTimer::GetTimeInMilliseconds(), numActiveAgents);
|
|
ms_MaxNMAgentsUsedGlobally = numActiveAgents;
|
|
}
|
|
if (CTheScripts::GetPlayerIsOnAMission() && numActiveRageRagdolls > ms_MaxRageRagdollsUsedCurrentLevel)
|
|
{
|
|
Displayf("[RAGDOLL WATERMARK] [%u] The maximum number of RAGE RAGDOLLS used during [%s] has increased to %d.",
|
|
fwTimer::GetTimeInMilliseconds(), CDebug::GetCurrentMissionName(), numActiveRageRagdolls);
|
|
ms_MaxRageRagdollsUsedCurrentLevel = numActiveRageRagdolls;
|
|
}
|
|
if (numActiveRageRagdolls > ms_MaxRageRagdollsUsedGlobally)
|
|
{
|
|
Displayf("[RAGDOLL WATERMARK] [%u] The maximum number of RAGE RAGDOLLS used GLOBALLY has increased to %d.",
|
|
fwTimer::GetTimeInMilliseconds(), numActiveRageRagdolls);
|
|
ms_MaxRageRagdollsUsedGlobally = numActiveRageRagdolls;
|
|
}
|
|
if (CTheScripts::GetPlayerIsOnAMission() && numActiveAgents + numActiveRageRagdolls > ms_MaxTotalRagdollsUsedCurrentLevel)
|
|
{
|
|
Displayf("[RAGDOLL WATERMARK] [%u] The peak usage number of ragdolls used simultaneously during [%s] has increased to %d. %d of those were NM agents and %d were rage ragdolls.",
|
|
fwTimer::GetTimeInMilliseconds(), CDebug::GetCurrentMissionName(), numActiveAgents + numActiveRageRagdolls, numActiveAgents, numActiveRageRagdolls);
|
|
ms_MaxTotalRagdollsUsedCurrentLevel = numActiveAgents + numActiveRageRagdolls;
|
|
ms_MaxNMAgentsUsedInComboCurrentLevel = numActiveAgents;
|
|
ms_MaxRageRagdollsUsedInComboCurrentLevel = numActiveRageRagdolls;
|
|
}
|
|
if (numActiveAgents + numActiveRageRagdolls > ms_MaxTotalRagdollsUsedGlobally)
|
|
{
|
|
Displayf("[RAGDOLL WATERMARK] [%u] The peak usage number of ragdolls used simultaneously GLOBALLY has increased to %d. %d of those were NM agents and %d were rage ragdolls.",
|
|
fwTimer::GetTimeInMilliseconds(), numActiveAgents + numActiveRageRagdolls, numActiveAgents, numActiveRageRagdolls);
|
|
ms_MaxTotalRagdollsUsedGlobally = numActiveAgents + numActiveRageRagdolls;
|
|
ms_MaxNMAgentsUsedInComboGlobally = numActiveAgents;
|
|
ms_MaxRageRagdollsUsedInComboGlobally = numActiveRageRagdolls;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool fragInstNMGta::ShouldBeInCacheToDraw() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
void fragInstNMGta::LosingCacheEntry()
|
|
{
|
|
Assertf(GetInstFlag(phInstGta::FLAG_BEING_DELETED) || !GetUserData(), "%s:fragInstNM - lost cache entry", ((CEntity*)GetUserData())->GetModelName());
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->LosingFragCacheEntry();
|
|
}
|
|
fragInstNM::LosingCacheEntry();
|
|
}
|
|
|
|
fragCacheEntry* fragInstNMGta::PutIntoCache()
|
|
{
|
|
fragCacheEntry* newEntry = fragInstNM::PutIntoCache();
|
|
if(newEntry)
|
|
{
|
|
if(CEntity* pEntity = CPhysics::GetEntityFromInst(this))
|
|
{
|
|
pEntity->GainingFragCacheEntry(*newEntry);
|
|
}
|
|
}
|
|
return newEntry;
|
|
}
|
|
|
|
// ragdolls never break apart
|
|
bool fragInstNMGta::FindBreakStrength(const Vector3* UNUSED_PARAM(componentImpulses), const Vector4* UNUSED_PARAM(componentPositions), float* UNUSED_PARAM(breakStrength), phBreakData* UNUSED_PARAM(breakData)) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool fragInstNMGta::IsBreakable(phInst* ) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
void fragInstNMGta::SetActivePoseFromClip(const crClip* pClip, const float fPhase, bool UNUSED_PARAM(bUseWorldCoords))
|
|
{
|
|
// If bUseWorldCoords is set to true (default is false), transform the matrices by the ped's current
|
|
// position and orientation before passing to NM.
|
|
|
|
Matrix34 parentMatrix;
|
|
parentMatrix.Identity();
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
if(pPed && pClip)
|
|
{
|
|
// Create skeleton
|
|
crSkeletonData *pSkeletonData = pPed->GetDrawable()->GetSkeletonData();
|
|
if (pSkeletonData)
|
|
{
|
|
crSkeleton* pTempSkeleton = rage_new crSkeleton();
|
|
pTempSkeleton->Init(*pSkeletonData, NULL);
|
|
|
|
// ensure matrix is in memory all the time
|
|
Mat34V tempMatrix = this->GetMatrix();
|
|
pTempSkeleton->SetParentMtx(&tempMatrix);
|
|
|
|
pPed->GetAnimDirector()->PoseSkeletonUsingRagdollFrame(*pTempSkeleton, pClip, fPhase);
|
|
pTempSkeleton->Update();
|
|
|
|
#if __BANK
|
|
// Allow incoming transforms to be debugged in-game.
|
|
if(CNmDebug::ms_bDrawTransforms)
|
|
{
|
|
CNmDebug::SetComponentTMsFromSkeleton(*pTempSkeleton);
|
|
}
|
|
#endif // __BANK
|
|
|
|
fragInstNM::SetComponentTMsFromSkeleton(*pTempSkeleton);//name change fragInstNM::ActivePose(*pTempSkeleton);
|
|
|
|
delete pTempSkeleton;
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::SetIncomingTransformsFromAnim(const crClip* pClip, const float fPhase)
|
|
{
|
|
fragInstNM::SetLastComponentTMsFromCurrent();
|
|
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
if(pPed && pClip)
|
|
{
|
|
// Create skeleton
|
|
crSkeletonData *pSkeletonData = pPed->GetDrawable()->GetSkeletonData();
|
|
if (pSkeletonData)
|
|
{
|
|
crSkeleton* pTempSkeleton = rage_new crSkeleton();
|
|
pTempSkeleton->Init(*pSkeletonData, NULL);
|
|
|
|
// ensure matrix is in memory all the time
|
|
Mat34V tempMatrix = this->GetMatrix();
|
|
pTempSkeleton->SetParentMtx(&tempMatrix);
|
|
|
|
pPed->GetAnimDirector()->PoseSkeletonUsingRagdollFrame(*pTempSkeleton, pClip, fPhase);
|
|
pTempSkeleton->Update();
|
|
|
|
fragInstNM::SetComponentTMsFromSkeleton(*pTempSkeleton);//name change fragInstNM::ActivePose(*pTempSkeleton);
|
|
|
|
delete pTempSkeleton;
|
|
}
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::SetActivePoseFromSkel(const crSkeleton* pSkel)
|
|
{
|
|
CPed* pPed = (CPed*)GetUserData();
|
|
if(pPed && pSkel)
|
|
{
|
|
// Create a temp skeleton
|
|
crSkeleton* pTempSkeleton = rage_new crSkeleton();
|
|
if (pTempSkeleton)
|
|
{
|
|
pTempSkeleton->Init(pSkel->GetSkeletonData(), NULL);
|
|
pTempSkeleton->CopyFrom(*pSkel);
|
|
pTempSkeleton->Update();
|
|
|
|
// ensure matrix is in memory all the time
|
|
Mat34V tempMatrix(V_IDENTITY);
|
|
pTempSkeleton->SetParentMtx(&tempMatrix);
|
|
|
|
fragInstNM::SetComponentTMsFromSkeleton(*pTempSkeleton);
|
|
delete pTempSkeleton;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Vec3V_Out fragInstNMGta::GetExternallyControlledVelocity () const
|
|
{
|
|
Vec3V velocity(V_ZERO);
|
|
|
|
CEntity* pEntity = (CEntity*)GetUserData();
|
|
if(pEntity)
|
|
{
|
|
phInst* pInst = pEntity->GetCurrentPhysicsInst();
|
|
phCollider* pCollider = (pInst && pInst->IsInLevel() && pInst != this) ? CPhysics::GetSimulator()->GetCollider(pInst) : nullptr;
|
|
if(pCollider)
|
|
{
|
|
velocity = pCollider->GetVelocity();
|
|
}
|
|
else
|
|
{
|
|
bool bInstActive = PHLEVEL->LegitLevelIndex(GetLevelIndex()) && PHLEVEL->IsActive(GetLevelIndex());
|
|
if (!bInstActive && pEntity->GetIsPhysical())
|
|
{
|
|
CPhysical* pPhysical = (CPhysical*)pEntity;
|
|
// ***HACK***
|
|
// This logic should probably be contained within CPhysical::GetVelocity but it too late in the project to make such sweeping changes.
|
|
// Here (and the equivalent function in phInstGta) seemed to be the only place where this logic was required (although there are probably
|
|
// other areas that would benefit) so the change was made here.
|
|
if(pPhysical->GetIsTypePed() && pPhysical->GetIsAttached() && !pPhysical->GetIsAttachedToGround() && pPhysical->GetAttachParent() != NULL && static_cast<CEntity*>(pPhysical->GetAttachParent())->GetIsPhysical())
|
|
{
|
|
pPhysical->CalculateGroundVelocity(static_cast<CPhysical*>(pPhysical->GetAttachParent()), pPhysical->GetTransform().GetPosition(), 0, velocity);
|
|
}
|
|
else
|
|
{
|
|
velocity = RCC_VEC3V(pPhysical->GetVelocity());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return velocity;
|
|
}
|
|
|
|
void fragInstNMGta::SetARTFeedbackInterfaceGta()
|
|
{
|
|
fragInstNM::SetARTFeedbackInterface(&m_feedbackInterfaceGta);
|
|
}
|
|
|
|
// transform magnitude of impulse to equalize the effect over the body
|
|
// nComponent is the ragdoll component the force will be applied to
|
|
// fMassInfluence is how much the mass of the component will influence the magnitude (0.0 == original force, 1.0f == completely scaled by mass)
|
|
void fragInstNMGta::ScaleImpulseByComponentMass(Vector3& vecImpulse, int nComponent, float fMassInfluence)
|
|
{
|
|
vecImpulse *= ScaleImpulseByComponentMass(nComponent, fMassInfluence);
|
|
}
|
|
|
|
float fragInstNMGta::ScaleImpulseByComponentMass(int nComponent, float fMassInfluence)
|
|
{
|
|
float fAverageMass = GetTypePhysics()->GetTotalUndamagedMass() / GetTypePhysics()->GetNumChildren();
|
|
float fMassProportion = GetTypePhysics()->GetAllChildren()[nComponent]->GetUndamagedMass() / fAverageMass;
|
|
fMassProportion = rage::Clamp(fMassProportion, 0.2f, 1.5f);
|
|
|
|
return (fMassProportion*fMassInfluence + 1.0f*(1.0f-fMassInfluence));
|
|
}
|
|
|
|
void fragInstNMGta::ApplyBulletForce(CPed* pPed, float fMomentum, const Vector3& vecWorldNormal, const Vector3& vecWorldPos, int nComponent, u32 nWeaponHash)
|
|
{
|
|
// If the ped isn't dead, don't allow the impulse modifier to exceed 1.0
|
|
if (pPed->GetDeathState() != DeathState_Dead)
|
|
m_ImpulseModifier = Min(1.0f, m_ImpulseModifier);
|
|
|
|
// The impulse of rapid firing should diminish over a short time (except for shotgun blasts)
|
|
static float initialHitThreshold = 0.98f;
|
|
bool bIsInitialHit = m_ImpulseModifier >= CTaskRageRagdoll::GetMaxImpulseModifier()*initialHitThreshold;
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
if (!(pWeaponInfo && pWeaponInfo->GetGroup() == WEAPONGROUP_SHOTGUN))
|
|
{
|
|
if (m_PendingImpulseModifier != -1.0f)
|
|
{
|
|
m_ImpulseModifier = m_PendingImpulseModifier;
|
|
m_PendingImpulseModifier = -1.0f;
|
|
}
|
|
fMomentum *= m_ImpulseModifier;
|
|
m_ImpulseModifier = Max(CTaskRageRagdoll::GetMinImpulseModifier(), m_ImpulseModifier - CTaskRageRagdoll::GetImpulseReductionPerShot());
|
|
}
|
|
|
|
// Clamp the bullet force (actually an impulse) we are going to apply so that we don't send non-human animals
|
|
// like chickens flying across the map. The dev_float below defines the maximum velocity change that the
|
|
// computed impulse is allowed to generate.
|
|
static float s_smallMass = 50.0f;
|
|
if (GetARTAssetID() == -1 && pPed->GetMass() <= s_smallMass)
|
|
{
|
|
static dev_float fMaxImpVel = 2.0;
|
|
float fMass = rage::Max(1.0f, pPed->GetMass());
|
|
if(fMomentum/fMass > fMaxImpVel)
|
|
fMomentum = fMaxImpVel*fMass;
|
|
}
|
|
|
|
// Modify impulses to animals
|
|
if (GetARTAssetID() == -1)
|
|
{
|
|
phArticulatedBody *body = GetArticulatedBody();
|
|
float fMass = body->GetMass(nComponent).Getf();
|
|
fMomentum *= Min(Max(CTaskRageRagdoll::GetAnimalImpulseMultMin(), CTaskRageRagdoll::GetAnimalMassMult() * fMass), CTaskRageRagdoll::GetAnimalImpulseMultMax());
|
|
}
|
|
|
|
#if __DEV
|
|
if (CTaskNMBehaviour::sm_DoOverrideBulletImpulses)
|
|
fMomentum = CTaskNMBehaviour::sm_OverrideImpulse;
|
|
#endif
|
|
|
|
// First check for a pending impulse and apply it
|
|
if (m_CounterImpulseCount >= 0)
|
|
{
|
|
m_CounterImpulseCount = 1;
|
|
UpdateCounterImpulse(pPed, true);
|
|
}
|
|
|
|
// Get a safe component (can become invalidated when ragdoll lods change)
|
|
phArticulatedBody *body = GetArticulatedBody();
|
|
if (body)
|
|
nComponent = Clamp(nComponent, 0, body->GetNumBodyParts()-1);
|
|
|
|
// Store wound data in the ped
|
|
CPed::WoundData wound;
|
|
wound.valid = true;
|
|
wound.component = nComponent;
|
|
wound.localHitLocation = vecWorldPos;
|
|
wound.localHitNormal = vecWorldNormal;
|
|
wound.impulse = fMomentum;
|
|
|
|
bool localHitPos = false;
|
|
|
|
// Get the position and normal in local space
|
|
Matrix34 ragdollComponentMatrix;
|
|
if(pPed && pPed->GetRagdollComponentMatrix(ragdollComponentMatrix, nComponent))
|
|
{
|
|
ragdollComponentMatrix.UnTransform(wound.localHitLocation);
|
|
Assert(wound.localHitLocation.Mag2() < 5.0f);
|
|
ragdollComponentMatrix.UnTransform3x3(wound.localHitNormal);
|
|
wound.localHitNormal.Normalize();
|
|
localHitPos = true;
|
|
}
|
|
|
|
pPed->AddWoundData(wound, NULL, true);
|
|
|
|
m_LastRRApplyBulletTime = fwTimer::GetTimeInMilliseconds();
|
|
|
|
// Lower joint stiffnesses for half a second each time a bullet comes it
|
|
if (body)
|
|
{
|
|
body->SetStiffness(CTaskRageRagdoll::GetTempInitialStiffnessWhenShot());
|
|
m_bBulletLoosenessActive = true;
|
|
}
|
|
|
|
// Apply half the impulse as a counter impulse
|
|
pPed->ApplyImpulse(fMomentum*vecWorldNormal*(-1.0f * CTaskRageRagdoll::GetCounterImpulseRatio()), localHitPos ? wound.localHitLocation : vecWorldPos - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), nComponent, localHitPos);
|
|
m_CounterImpulseCount = 2;
|
|
|
|
// If this is an initial hit, double the impulse
|
|
if (bIsInitialHit)
|
|
{
|
|
pPed->GetPrimaryWoundData()->impulse *= CTaskRageRagdoll::GetInitialHitImpulseMult();
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::UpdateCounterImpulse(CPed* pPed, bool UNUSED_PARAM(bHurriedCounterImpulse))
|
|
{
|
|
m_CounterImpulseCount = Max(-1, m_CounterImpulseCount-1);
|
|
if (m_CounterImpulseCount == 0)
|
|
{
|
|
// Get a safe component (can become invalidated when ragdoll lods change)
|
|
int nComponent = pPed->GetPrimaryWoundData()->component;
|
|
phArticulatedBody *body = GetArticulatedBody();
|
|
if (body)
|
|
nComponent = Clamp(nComponent, 0, body->GetNumBodyParts()-1);
|
|
|
|
// Get the hit position and normal in world space
|
|
Vector3 vecWorldHitPos, vecWorldHitNorm;
|
|
Matrix34 ragdollComponentMatrix;
|
|
bool localHitPos = false;
|
|
if(pPed && pPed->GetRagdollComponentMatrix(ragdollComponentMatrix, nComponent))
|
|
{
|
|
ragdollComponentMatrix.Transform(pPed->GetPrimaryWoundData()->localHitLocation, vecWorldHitPos);
|
|
ragdollComponentMatrix.Transform3x3(pPed->GetPrimaryWoundData()->localHitNormal, vecWorldHitNorm);
|
|
vecWorldHitNorm.Normalize();
|
|
localHitPos = true;
|
|
}
|
|
|
|
pPed->ApplyImpulse(pPed->GetPrimaryWoundData()->impulse * vecWorldHitNorm,
|
|
localHitPos ? pPed->GetPrimaryWoundData()->localHitLocation : vecWorldHitPos - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()),
|
|
nComponent, localHitPos);
|
|
|
|
m_CounterImpulseCount = -1;
|
|
}
|
|
}
|
|
|
|
void fragInstNMGta::ProcessRagdoll(float fTimeStep)
|
|
{
|
|
// Update general ragdoll related items here
|
|
CPed *pPed = (CPed *)GetUserData();
|
|
|
|
// Reduce linear damping if falling for a more realistic falling speed
|
|
static float fallingSpeedLim = -10.0f;
|
|
static float terminalVelocity = -55.0f;
|
|
float fallingSpeed = pPed->GetCollider()->GetVelocity().GetZf();
|
|
if (fallingSpeed <= fallingSpeedLim && fallingSpeed >= terminalVelocity)
|
|
{
|
|
static_cast<phArticulatedCollider*>(pPed->GetCollider())->SetReducedLinearDampingFrames();
|
|
}
|
|
|
|
pPed->GetRagdollInst()->ProcessVehicleEjection(fTimeStep);
|
|
|
|
// Update params related to contacting a wheel
|
|
if (!m_ContactedWheel)
|
|
{
|
|
if(m_WheelTwitchStarted)
|
|
{
|
|
static_cast<phArticulatedCollider*>(pPed->GetCollider())->GetBody()->GetJoint(RAGDOLL_KNEE_LEFT_JOINT).SetMinStiffness(0.0f);
|
|
static_cast<phArticulatedCollider*>(pPed->GetCollider())->GetBody()->GetJoint(RAGDOLL_KNEE_RIGHT_JOINT).SetMinStiffness(0.0f);
|
|
}
|
|
|
|
m_WheelTwitchStarted = false;
|
|
m_uTimeTireImpulseWasApplied = 0;
|
|
m_fTireImpulseAppliedRatio = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
if(m_WheelTwitchStarted && pPed->GetDeathState() == DeathState_Dead)
|
|
{
|
|
static dev_float sfKneeWheelTwitchStiffness = 0.95f;
|
|
static_cast<phArticulatedCollider*>(pPed->GetCollider())->GetBody()->GetJoint(RAGDOLL_KNEE_LEFT_JOINT).SetMinStiffness(sfKneeWheelTwitchStiffness);
|
|
static_cast<phArticulatedCollider*>(pPed->GetCollider())->GetBody()->GetJoint(RAGDOLL_KNEE_RIGHT_JOINT).SetMinStiffness(sfKneeWheelTwitchStiffness);
|
|
}
|
|
}
|
|
m_ContactedWheel = false;
|
|
|
|
// If being pressed down by a train, prevent instability
|
|
if (pPed->GetPedResetFlag(CPED_RESET_FLAG_IsTrainCrushingRagdoll))
|
|
{
|
|
pPed->GetRagdollInst()->GetCacheEntry()->ActivateInstabilityPrevention(60);
|
|
}
|
|
|
|
// Rage ragdoll processing
|
|
if (GetNMAgentID() == -1)
|
|
{
|
|
phArticulatedBody *body = GetArticulatedBody();
|
|
|
|
// Restore normal joint stiffness if enough time has passed since getting shot
|
|
static u32 loosenessTime = 500u;
|
|
if (m_bBulletLoosenessActive && (fwTimer::GetTimeInMilliseconds() - m_LastRRApplyBulletTime > loosenessTime) && body)
|
|
{
|
|
static float stiff = 0.5f;
|
|
body->SetStiffness(stiff);
|
|
m_bBulletLoosenessActive = false;
|
|
pPed->SetCorpseRagdollFriction(-1.0f);
|
|
}
|
|
|
|
// Update allowed impulse recovery
|
|
m_ImpulseModifier = Min(CTaskRageRagdoll::GetMaxImpulseModifier(), m_ImpulseModifier + fTimeStep * CTaskRageRagdoll::GetImpulseRecoveryPerSecond());
|
|
|
|
UpdateCounterImpulse(pPed);
|
|
|
|
// Assign non-NM agent ragdolls to the rage ragdoll pool if there's room (and they're not already assigned there)
|
|
if (pPed->GetUsingRagdoll() && pPed->GetRagdollInst()->GetNMAgentID() == -1 && pPed->GetCurrentRagdollPool() != CTaskNMBehaviour::kRagdollPoolRageRagdoll &&
|
|
CTaskNMBehaviour::GetRagdollPool(CTaskNMBehaviour::kRagdollPoolRageRagdoll).GetFreeSlots()>0)
|
|
{
|
|
CTaskNMBehaviour::AddToRagdollPool(CTaskNMBehaviour::kRagdollPoolRageRagdoll, *pPed);
|
|
}
|
|
|
|
// Handle corpse friction
|
|
if (m_bBulletLoosenessActive)
|
|
{
|
|
static float f1 = 2.0f;
|
|
pPed->SetCorpseRagdollFriction(f1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ARTFeedbackInterfaceGta::ARTFeedbackInterfaceGta()
|
|
: ART::ARTFeedbackInterface(),
|
|
m_pParentInst(NULL)
|
|
{
|
|
}
|
|
|
|
ARTFeedbackInterfaceGta::~ARTFeedbackInterfaceGta()
|
|
{
|
|
}
|
|
|
|
int ARTFeedbackInterfaceGta::onBehaviourFailure()
|
|
{
|
|
if(GetParentInst() && GetParentInst()->GetUserData())
|
|
{
|
|
CPed* pPed = (CPed*)GetParentInst()->GetUserData();
|
|
CTaskNMBehaviour* pTaskNM = pPed->GetPedIntelligence()->GetLowestLevelNMTask(pPed);
|
|
|
|
if(pTaskNM)
|
|
{
|
|
pTaskNM->BehaviourFailure(pPed, this);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
Displayf("\nbehaviour failure - unknown ped\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ARTFeedbackInterfaceGta::onBehaviourSuccess()
|
|
{
|
|
if(GetParentInst() && GetParentInst()->GetUserData())
|
|
{
|
|
CPed* pPed = (CPed*)GetParentInst()->GetUserData();
|
|
CTaskNMBehaviour* pTaskNM = pPed->GetPedIntelligence()->GetLowestLevelNMTask(pPed);
|
|
|
|
if(pTaskNM)
|
|
{
|
|
pTaskNM->BehaviourSuccess(pPed, this);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
Displayf("\nbehaviour success - unknown ped\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ARTFeedbackInterfaceGta::onBehaviourStart()
|
|
{
|
|
if(GetParentInst() && GetParentInst()->GetUserData())
|
|
{
|
|
CPed* pPed = (CPed*)GetParentInst()->GetUserData();
|
|
CTaskNMBehaviour* pTaskNM = pPed->GetPedIntelligence()->GetLowestLevelNMTask(pPed);
|
|
|
|
if(pTaskNM)
|
|
{
|
|
pTaskNM->BehaviourStart(pPed, this);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
Displayf("\nbehaviour start - unknown ped\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ARTFeedbackInterfaceGta::onBehaviourFinish()
|
|
{
|
|
if(GetParentInst() && GetParentInst()->GetUserData())
|
|
{
|
|
CPed* pPed = (CPed*)GetParentInst()->GetUserData();
|
|
CTaskNMBehaviour* pTaskNM = pPed->GetPedIntelligence()->GetLowestLevelNMTask(pPed);
|
|
|
|
if(pTaskNM)
|
|
{
|
|
pTaskNM->BehaviourFinish(pPed, this);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
Displayf("\nbehaviour finish - unknown ped\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ARTFeedbackInterfaceGta::onBehaviourEvent()
|
|
{
|
|
if(GetParentInst() && GetParentInst()->GetUserData())
|
|
{
|
|
CPed* pPed = (CPed*)GetParentInst()->GetUserData();
|
|
CTaskNMBehaviour* pTaskNM = pPed->GetPedIntelligence()->GetLowestLevelNMTask(pPed);
|
|
|
|
if(pTaskNM)
|
|
{
|
|
pTaskNM->BehaviourEvent(pPed, this);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
Displayf("\nbehaviour event - unknown ped\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
fragInstNMGta::eBodyPartGroup fragInstNMGta::GetBodyPartGroup(int component)
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
{
|
|
CPed *pPed = (CPed *)GetUserData();
|
|
if (pPed)
|
|
{
|
|
eAnimBoneTag boneTag = pPed->GetBoneTagFromRagdollComponent(component);
|
|
|
|
// Check for neck
|
|
if (boneTag == BONETAG_NECK || boneTag == BONETAG_NECK2 || boneTag == BONETAG_HEAD)
|
|
{
|
|
return fragInstNMGta::kNeck;
|
|
}
|
|
// Check for the spine
|
|
else if (boneTag == BONETAG_ROOT || boneTag == BONETAG_PELVISROOT || boneTag == BONETAG_PELVIS || boneTag == BONETAG_PELVIS1 || boneTag == BONETAG_SPINE_ROOT ||
|
|
boneTag == BONETAG_SPINE0 || boneTag == BONETAG_SPINE1 || boneTag == BONETAG_SPINE2 || boneTag == BONETAG_SPINE3)
|
|
{
|
|
return fragInstNMGta::kSpine;
|
|
}
|
|
|
|
// otherwise it's part of a limb
|
|
return kLimb;
|
|
}
|
|
|
|
return fragInstNMGta::kInvalidPartGroup;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void fragInstNMGta::ProcessTwoStageRamp(fragInstNMGta::eRampType type, fragInstNMGta::eBodyPartGroup partGroup, float startStifnessIn, float midStifnessIn,
|
|
float endStiffnessIn, float durationStartToMid, float durationMidToEnd, float timeElapsed)
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
{
|
|
phArticulatedBody *body = GetArticulatedBody();
|
|
if (body && partGroup != fragInstNMGta::kInvalidPartGroup)
|
|
{
|
|
// Bail if time has elapsed
|
|
if (timeElapsed > durationStartToMid + durationMidToEnd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Match the duration and stiffnesses to the stage
|
|
float duration = durationStartToMid;
|
|
float startStiffness = startStifnessIn;
|
|
float endStiffness = midStifnessIn;
|
|
float timePassedRatio = ClampRange(timeElapsed, 0.0f, durationStartToMid);
|
|
if (timeElapsed > durationStartToMid)
|
|
{
|
|
duration = durationMidToEnd;
|
|
startStiffness = midStifnessIn;
|
|
endStiffness = endStiffnessIn;
|
|
timePassedRatio = ClampRange(timeElapsed-durationStartToMid, 0.0f, durationMidToEnd);
|
|
}
|
|
|
|
// Compute the stiffness to apply
|
|
float stiffness = Lerp(timePassedRatio, startStiffness, endStiffness);
|
|
|
|
// Apply stiffness
|
|
for (int iJoint = 0; iJoint < body->GetNumJoints(); iJoint++)
|
|
{
|
|
phJoint &joint = body->GetJoint(iJoint);
|
|
if (GetBodyPartGroup(joint.GetChildLinkIndex()) == partGroup)
|
|
{
|
|
if (type == kStiffness)
|
|
{
|
|
joint.SetStiffness(stiffness);
|
|
}
|
|
else if (type == kProportionalGain)
|
|
{
|
|
switch (joint.GetJointType())
|
|
{
|
|
case phJoint::JNT_1DOF:
|
|
{
|
|
phJoint1Dof& joint1Dof = *static_cast<phJoint1Dof*>(&body->GetJoint(iJoint));
|
|
joint1Dof.SetMuscleAngleStrength(stiffness);
|
|
break;
|
|
}
|
|
|
|
case phJoint::JNT_3DOF:
|
|
{
|
|
phJoint3Dof& joint3Dof = *static_cast<phJoint3Dof*>(&body->GetJoint(iJoint));
|
|
joint3Dof.SetMuscleAngleStrength(stiffness);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if __DEV
|
|
void fragInstNMGta::InvalidStateDump() const
|
|
{
|
|
fragInstNM::InvalidStateDump();
|
|
InvalidStateDumpHelper(this);
|
|
}
|
|
#endif // __DEV
|
|
|
|
//float CBulletForce::ms_fPedBulletApplyTime = 0.30f;
|
|
//float CBulletForce::ms_fPedMeleeApplyTime = 0.15f;
|
|
//float CBulletForce::ms_fPedBulletMassMult = 1.0f;
|
|
//float CBulletForce::ms_fPedMeleeMassMult = 1.5f;
|
|
//
|
|
//CBulletForce::CBulletForce()
|
|
//{
|
|
// m_pTarget = NULL;
|
|
// m_vecForce.Zero();
|
|
// m_vecPos.Zero();
|
|
// m_fTime = 0.0f;
|
|
//
|
|
// m_nComponent = 0;
|
|
// m_nFlags = 0;
|
|
//}
|
|
//
|
|
//void CBulletForce::InitPedImpact(CPed* pPed, float fMomentum, const Vector3& vecDir, const Vector3& vecWorldPos, int nComponent, u32 nWeaponHash)
|
|
//{
|
|
// Assert(pPed);
|
|
// if(pPed==NULL)
|
|
// return;
|
|
//
|
|
// if(pPed!=m_pTarget)
|
|
// {
|
|
// m_pTarget = pPed;
|
|
// }
|
|
//
|
|
// // reset flags
|
|
// m_nFlags = 0;
|
|
//
|
|
// const CWeaponInfo* pInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(nWeaponHash);
|
|
// if(pInfo && pInfo->GetIsMelee())
|
|
// {
|
|
// m_fMassMult = ms_fPedMeleeMassMult;
|
|
// if(m_fTime <= 0.0f)
|
|
// m_fTime = ms_fPedMeleeApplyTime;
|
|
//// else
|
|
//// Assert(false);
|
|
// }
|
|
// else
|
|
// {
|
|
// m_fMassMult = ms_fPedBulletMassMult;
|
|
// m_fTime = ms_fPedBulletApplyTime;
|
|
// }
|
|
//
|
|
// fMomentum *= 1.0f / m_fTime;
|
|
// m_vecForce = vecDir * fMomentum;
|
|
//
|
|
// Matrix34 ragdollRootMatrix;
|
|
// if(pPed->GetRagdollComponentMatrix(ragdollRootMatrix, 0))
|
|
// {
|
|
// Assertf(ragdollRootMatrix.d.Dist2(vecWorldPos) < 9.0f, "hitPos is more then 3 meters away from ped position!");
|
|
// m_vecPos.Subtract(vecWorldPos, ragdollRootMatrix.d);
|
|
// }
|
|
// else
|
|
// {
|
|
// m_vecPos.Zero();
|
|
// }
|
|
//
|
|
// Assert((unsigned)nComponent < 65536);
|
|
// m_nComponent = (u16)nComponent;
|
|
//}
|
|
//
|
|
//void CBulletForce::ProcessPhysics(float fTimeStep)
|
|
//{
|
|
// Assert(m_pTarget && m_fTime > 0.0f);
|
|
// fragInstNMGta* pRagdoll = NULL;
|
|
// if(m_pTarget && m_pTarget->GetIsTypePed())
|
|
// pRagdoll = ((CPed*)m_pTarget.Get())->GetRagdollInst();
|
|
//
|
|
// float fApplyFrac = 1.0f;
|
|
// if(m_fTime > fTimeStep)
|
|
// m_fTime -= fTimeStep;
|
|
// else
|
|
// {
|
|
// fApplyFrac = m_fTime / fTimeStep;
|
|
// m_fTime = 0.0f;
|
|
// }
|
|
//
|
|
// float fMassScale = m_fMassMult;
|
|
// float fMassScaleRemaining = m_fMassMult - fMassScale;
|
|
// if(pRagdoll)
|
|
// pRagdoll->ScaleImpulseByComponentMass(m_nComponent, 0.8f);
|
|
//
|
|
// const Vector3 vTargetPosition = VEC3V_TO_VECTOR3(m_pTarget->GetTransform().GetPosition());
|
|
// Vector3 vecApplyWorldPos(m_vecPos);
|
|
// if(m_pTarget->GetIsTypePed())
|
|
// {
|
|
// Matrix34 ragdollRootMatrix;
|
|
// if(((CPed*)m_pTarget.Get())->GetRagdollComponentMatrix(ragdollRootMatrix, 0))
|
|
// vecApplyWorldPos.Add(ragdollRootMatrix.d);
|
|
// else
|
|
// vecApplyWorldPos.Set(vTargetPosition);
|
|
// }
|
|
//
|
|
// m_pTarget->ApplyForce(fApplyFrac * fMassScale * m_vecForce, vecApplyWorldPos - vTargetPosition, m_nComponent);
|
|
//
|
|
// int nGroup = pRagdoll->GetTypePhysics()->GetAllChildren()[m_nComponent]->GetOwnerGroupPointerIndex();
|
|
// int nParentGroup = pRagdoll->GetTypePhysics()->GetAllGroups()[nGroup]->GetParentGroupPointerIndex();
|
|
//
|
|
// while(pRagdoll && fMassScaleRemaining > 0.0f && nParentGroup > 0)
|
|
// {
|
|
// int nParentFirstChild = pRagdoll->GetTypePhysics()->GetAllGroups()[nParentGroup]->GetChildFragmentIndex();
|
|
// fMassScale = pRagdoll->ScaleImpulseByComponentMass(nParentFirstChild, 0.8f);
|
|
// if(fMassScale > fMassScaleRemaining)
|
|
// {
|
|
// fMassScale = fMassScaleRemaining;
|
|
// fMassScaleRemaining = -1.0f;
|
|
// }
|
|
// else
|
|
// fMassScaleRemaining -= fMassScale;
|
|
//
|
|
// m_pTarget->ApplyForce(fApplyFrac * fMassScale * m_vecForce, vecApplyWorldPos - VEC3V_TO_VECTOR3(m_pTarget->GetTransform().GetPosition()), nParentFirstChild);
|
|
//
|
|
// nParentGroup = pRagdoll->GetTypePhysics()->GetAllGroups()[nParentGroup]->GetParentGroupPointerIndex();
|
|
// }
|
|
//
|
|
// if(m_fTime <= 0.0f)
|
|
// {
|
|
// m_pTarget = NULL;
|
|
// }
|
|
//}
|
|
//
|
|
//void CBulletForce::Reset()
|
|
//{
|
|
// m_pTarget = NULL;
|
|
//}
|
|
|
|
|
|
|
|
|