8814 lines
312 KiB
C++
8814 lines
312 KiB
C++
// C++ headers
|
|
#include <limits>
|
|
|
|
// rage headers
|
|
#include "Profile/timebars.h"
|
|
#include "phbound/BoundSphere.h"
|
|
#include "phcore/Segment.h"
|
|
#include "fragmentnm/nm_channel.h"
|
|
|
|
// Framework headers
|
|
#include "fwmaths/Angle.h"
|
|
#include "fwscene/search/SearchVolumes.h"
|
|
#include "grcore/debugdraw.h"
|
|
#include "fwmaths/geomutil.h"
|
|
|
|
// Game headers
|
|
#include "animation/FacialData.h"
|
|
#include "animation/MovePed.h"
|
|
#include "camera/CamInterface.h"
|
|
#include "Control/Record.h"
|
|
#include "Cutscene/CutSceneManagerNew.h"
|
|
#include "debug/vectormap.h"
|
|
#include "Event/EventDamage.h"
|
|
#include "Event/EventLeader.h"
|
|
#include "Event/EventMovement.h"
|
|
#include "Event/EventScript.h"
|
|
#include "Event/EventShocking.h"
|
|
#include "event/EventSound.h"
|
|
#include "Event/Events.h"
|
|
#include "Event/Decision/EventDecisionMaker.h"
|
|
#include "Event/ShockingEvents.h"
|
|
#include "Event/system/EventData.h"
|
|
#include "Event/system/EventDataManager.h"
|
|
#include "Game/Cheat.h"
|
|
#include "Game/localisation.h"
|
|
#include "Game/ModelIndices.h"
|
|
#include "game/Riots.h"
|
|
#include "ModelInfo/VehicleModelInfo.h"
|
|
#include "Network/Events/NetworkEventTypes.h"
|
|
#include "Network/NetworkInterface.h"
|
|
#include "Objects/Door.h"
|
|
#include "PedGroup/PedGroup.h"
|
|
#include "Peds/ped_channel.h"
|
|
#include "Peds/Ped.h"
|
|
#include "Peds/PedCapsule.h"
|
|
#include "Peds/PedDebugVisualiser.h"
|
|
#include "Peds/PedEventScanner.h"
|
|
#include "Peds/PedGeometryAnalyser.h"
|
|
#include "Peds/PedIntelligence.h"
|
|
#include "Peds/PedTaskRecord.h"
|
|
#include "Peds/PedWeapons/PedTargetEvaluator.h"
|
|
#include "Peds/PedMoveBlend/PedMoveBlendOnFoot.h"
|
|
#include "Peds/PedMoveBlend/PedMoveBlendOnSkis.h"
|
|
#include "Physics/Floater.h"
|
|
#include "Physics/GtaInst.h"
|
|
#include "Physics/Physics.h"
|
|
#include "physics/Tunable.h"
|
|
#include "physics/WorldProbe/worldprobe.h"
|
|
//#include "Renderer/Font.h"
|
|
#include "renderer/River.h"
|
|
#include "renderer/Water.h"
|
|
#include "scene/world/GameWorld.h"
|
|
#include "Stats/StatsInterface.h"
|
|
#include "Task/Combat/Cover/TaskCover.h"
|
|
#include "Task/Combat/TaskInvestigate.h"
|
|
#include "Task/Scenario/ScenarioManager.h"
|
|
#include "Task/Vehicle/TaskEnterVehicle.h"
|
|
#include "Task/Combat/TaskCombat.h"
|
|
#include "Task/Combat/TaskDamageDeath.h"
|
|
#include "Task/Combat/TaskThreatResponse.h"
|
|
#include "Task/Combat/TaskWrithe.h"
|
|
#include "Task/Combat/Subtasks/TaskVehicleCombat.h"
|
|
#include "Task/General/TaskBasic.h"
|
|
#include "Task/Default/TaskIncapacitated.h"
|
|
#include "Task/Default/TaskPlayer.h"
|
|
#include "Task/General/TaskBasic.h"
|
|
#include "Task/Motion/Locomotion/TaskMotionPed.h"
|
|
#include "Task/Motion/Locomotion/TaskInWater.h"
|
|
#include "Task/Motion/Locomotion/TaskHumanLocomotion.h"
|
|
#include "task/Movement/TaskFall.h"
|
|
#include "task/Movement/TaskGetUp.h"
|
|
#include "Task/Movement/TaskMoveWander.h"
|
|
#include "Task/Movement/TaskParachute.h"
|
|
#include "Task/Movement/TaskSeekEntity.h"
|
|
#include "Task/Physics/BlendFromNmData.h"
|
|
#include "Task/Physics/TaskBlendFromNM.h"
|
|
#include "Task/Physics/TaskNMBalance.h"
|
|
#include "Task/Physics/TaskNMBrace.h"
|
|
#include "Task/Physics/TaskNMHighFall.h"
|
|
#include "Task/Physics/TaskNMJumpRollFromRoadVehicle.h"
|
|
#include "Task/Physics/TaskNMRelax.h"
|
|
#include "Task/Physics/TaskNMRiverRapids.h"
|
|
#include "Task/Physics/TaskNMRollUpAndRelax.h"
|
|
#include "Task/Physics/TaskNMSimple.h"
|
|
#include "Task/Physics/TaskNMSit.h"
|
|
#include "Task/Physics/TaskNMThroughWindscreen.h"
|
|
#include "Task/Response/TaskAgitated.h"
|
|
#include "Task/Response/Info/AgitatedManager.h"
|
|
#include "Task/Response/Info/AgitatedTrigger.h"
|
|
#include "Task/Scenario/Info/ScenarioInfo.h"
|
|
#include "Task/Scenario/TaskScenario.h"
|
|
#include "Task/Scenario/Types/TaskUseScenario.h"
|
|
#include "Task/System/MotionTaskData.h"
|
|
#include "Task/Vehicle/TaskCarAccessories.h"
|
|
#include "Task/Vehicle/TaskCarUtils.h"
|
|
#include "Task/Vehicle/TaskExitVehicle.h"
|
|
#include "Vehicles/Automobile.h"
|
|
#include "Vehicles/Bike.h"
|
|
#include "Vehicles/Boat.h"
|
|
#include "Vfx/Misc/Fire.h"
|
|
#include "Vfx/Systems/VfxBlood.h"
|
|
#include "Vehicles/Metadata/VehicleSeatInfo.h"
|
|
#include "vehicles/Submarine.h"
|
|
#include "vehicleAi/vehicleintelligence.h"
|
|
#include "weapons/Weapon.h"
|
|
#include "Weapons/WeaponDamage.h"
|
|
#include "stats/StatsMgr.h"
|
|
#include "peds/HealthConfig.h"
|
|
|
|
AI_OPTIMISATIONS()
|
|
AI_EVENT_OPTIMISATIONS()
|
|
PHYSICS_OPTIMISATIONS()
|
|
AI_VEHICLE_OPTIMISATIONS()
|
|
|
|
// RAGDOLL DAMAGE PARAMETERS //////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Define the mass categories for different damage bands (minimum mass for each category).
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_MASS_HEAVY = 5000.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_MASS_MEDIUM = 500.0f;
|
|
// Define the base damage inflicted in each of the different velocity bands for vehicle damage.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_CRAZY = 100.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_FAST = 50.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_MEDIUM = 17.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_SLOW = 12.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_CRAWLING = 0.1f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_SPEED_VEHICLE_CRAZY = 30.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_SPEED_VEHICLE_FAST = 20.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_SPEED_VEHICLE_MEDIUM = 10.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_SPEED_VEHICLE_SLOW = 5.0f;
|
|
// Angle impact normal makes with horizontal to decide when to apply vehicle damage.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_VEHICLE_IMPACT_ANGLE_PARAM = 0.2f;
|
|
// Dot product value to decide when a ped is under a vehicle.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_UNDER_VEHICLE_IMPACT_ANGLE_PARAM = -0.9f;
|
|
// Minimum velocity of train before damage is applied.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_TRAIN_VEL_THRESHOLD = 4.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_TRAIN_BB_ADJUST_Y = 1.0f;
|
|
// Modifiers for the various damage inflictor types. This is related to the mass and is multiplied by the magnitude of the relative
|
|
// velocity to compute a damage value.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_TRAIN = 5.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_PLANE = 2.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_BIKE = 5.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_LIGHT_VEHICLE = 3.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_MEDIUM_VEHICLE = 7.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_HEAVY_VEHICLE = 10.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_UNDER_VEHICLE = 10.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_FALL = 7.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_FALL_LAND_FOOT = 3.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_GENERAL_COLLISION = 5.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MIN_SPEED_SLIDING_DAMAGE = 2.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_SLIDING_COLLISION = 0.05f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_PED = 3.0f;
|
|
// Minimum amount to fall before ped will take any fall damage.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_PED_FALL_HEIGHT_THRESHOLD = 3.0f;
|
|
// Minimum relative speed at which a non-zero damage event will be created for each inflictor type.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MIN_SPEED_FALL = 3.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MIN_SPEED_PED = 3.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE = 2.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MIN_SPEED_WRECKED_BIKE = 6.0f;
|
|
|
|
// Minimum mass of object which causes damage to ped.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MIN_OBJECT_MASS = 100.0f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_OBJECT_LIGHT = 0.5f;
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_OBJECT_HEAVY = 10.0f;
|
|
// Scaling factors to modify various damage types when they happen to a player ped.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_PLAYER_VEHICLE_SCALE = 0.8f;
|
|
// Scaling factors to modify various damage types when they happen to an animal.
|
|
bank_float CCollisionEventScanner::RAGDOLL_DAMAGE_MULTIPLIER_ANIMAL_SCALE = 10.0f;
|
|
// The amount of time before an impact with the same vehicle will cause more damage.
|
|
bank_u32 CCollisionEventScanner::RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT = 1500;
|
|
// Per-component weightings for those that want them (see enumerated list on pedDefines.h).
|
|
bank_float CCollisionEventScanner::RAGDOLL_COMPONENT_SCALES[RAGDOLL_NUM_COMPONENTS] =
|
|
{
|
|
1.0f, // RAGDOLL_BUTTOCKS
|
|
0.9f, // RAGDOLL_THIGH_LEFT
|
|
0.7f, // RAGDOLL_SHIN_LEFT
|
|
0.1f, // RAGDOLL_FOOT_LEFT
|
|
0.9f, // RAGDOLL_THIGH_RIGHT
|
|
0.7f, // RAGDOLL_SHIN_RIGHT
|
|
0.1f, // RAGDOLL_FOOT_RIGHT
|
|
1.0f, // RAGDOLL_SPINE0
|
|
1.0f, // RAGDOLL_SPINE1
|
|
1.0f, // RAGDOLL_SPINE2
|
|
1.0f, // RAGDOLL_SPINE3
|
|
1.0f, // RAGDOLL_CLAVICLE_LEFT
|
|
0.9f, // RAGDOLL_UPPER_ARM_LEFT
|
|
0.7f, // RAGDOLL_LOWER_ARM_LEFT
|
|
0.1f, // RAGDOLL_HAND_LEFT
|
|
1.0f, // RAGDOLL_CLAVICLE_RIGHT
|
|
0.9f, // RAGDOLL_UPPER_ARM_RIGHT
|
|
0.7f, // RAGDOLL_LOWER_ARM_RIGHT
|
|
0.1f, // RAGDOLL_HAND_RIGHT
|
|
1.0f, // RAGDOLL_NECK
|
|
1.0f // RAGDOLL_HEAD
|
|
};
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// KNOCK OFF VEHICLE PARAMETERS ///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bank_float CCollisionEventScanner::sfBikeKO_PlayerMult = 0.5f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_PlayerMultMP = 0.5f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_VehicleMultPlayer = 2.0f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_VehicleMultAI = 4.0f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_TrainMult = 0.50f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_BikeMult = 2.0f;
|
|
|
|
bank_float CCollisionEventScanner::sfBikeKO_Mag = 7.0f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_EasyMag = 3.0f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_HardMag = 10.0f;
|
|
bank_float CCollisionEventScanner::sfPushBikeKO_Mag = 6.0f;
|
|
bank_float CCollisionEventScanner::sfPushBikeKO_EasyMag = 1.25f;
|
|
bank_float CCollisionEventScanner::sfPushBikeKO_HardMag = 8.0f;
|
|
|
|
|
|
bank_float CCollisionEventScanner::sfBikeKO_HeadOnDot = 0.85f;
|
|
|
|
bank_float CCollisionEventScanner::sfBikeKO_FrontMult = 0.80f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_FrontZMult = 0.60f;
|
|
|
|
bank_float CCollisionEventScanner::sfBikeKO_TopMult = 1.5f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_TopUpsideDownMult = 10.0f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_TopUpsideDownMultAI = 50.0f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_BottomMult = 0.05f;
|
|
bank_float CCollisionEventScanner::sfBikeKO_SideMult = 0.40f;
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
CPedStuckChecker::CPedStuckChecker()
|
|
{
|
|
m_fStuckCounter = 0.0f;
|
|
m_nStuck = PED_NOT_STUCK;
|
|
m_nSkipStuckCheck = 0;
|
|
}
|
|
|
|
|
|
dev_float sfStuckTimeForReset = 5.0f;
|
|
dev_float sfStuckTimeForDist = 1.0f;
|
|
dev_float sfStuckSpeedForDist = 0.2f;
|
|
dev_float sfStuckTimeForZ = 2.0f;
|
|
dev_float sfStuckSpeedForZ = 0.1f;
|
|
//
|
|
bool CPedStuckChecker::TestPedStuck(CPed *pPed)
|
|
{
|
|
if(!pPed->IsCollisionEnabled() || pPed->GetIsAttached()
|
|
|| pPed->GetIsDeadOrDying()
|
|
|| pPed->GetUsingRagdoll())
|
|
{
|
|
m_fStuckCounter = 0.0f;
|
|
m_nStuck = PED_NOT_STUCK;
|
|
return false;
|
|
}
|
|
|
|
// certain tasks will want to skip the stuck checker (swimming, climbing, ladder)
|
|
if(m_nSkipStuckCheck > 0)
|
|
{
|
|
// && !pPed->GetPedIntelligence()->GetTaskSwim()
|
|
// && !pPed->GetPedIntelligence()->IsPedClimbing()
|
|
// && !pPed->GetPedIntelligence()->GetTaskClimbLadder())
|
|
|
|
m_nSkipStuckCheck--;
|
|
return false;
|
|
}
|
|
|
|
// Displayf("Stuck Counter %d/n", m_nStuckCounter);
|
|
|
|
if(!pPed->GetIsStanding() && !pPed->GetWasStanding() && pPed->GetFrameCollisionHistory()->GetNumCollidedEntities() > 0
|
|
&& !pPed->GetPedResetFlag(CPED_RESET_FLAG_IsJumping) && !pPed->GetPedResetFlag(CPED_RESET_FLAG_IsLanding))
|
|
{
|
|
const Vector3 vecPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
|
|
Vector3 vecMoveDelta;
|
|
vecMoveDelta.Subtract(vecPedPos, m_vecPreviousPos);
|
|
|
|
if(m_fStuckCounter > sfStuckTimeForReset || m_fStuckCounter <= 0.0f)
|
|
{
|
|
m_fStuckCounter = fwTimer::GetTimeStep();
|
|
m_nStuck = PED_NOT_STUCK;
|
|
m_vecPreviousPos = vecPedPos;
|
|
vecMoveDelta.Zero();
|
|
}
|
|
else
|
|
{
|
|
m_fStuckCounter += fwTimer::GetTimeStep();
|
|
}
|
|
|
|
// first check if we're not moving, so we think we're stuck
|
|
if((m_fStuckCounter > sfStuckTimeForDist && vecMoveDelta.Mag2() < square(sfStuckSpeedForDist*m_fStuckCounter))
|
|
|| (m_fStuckCounter > sfStuckTimeForZ && rage::Abs(vecMoveDelta.z) < sfStuckSpeedForZ*m_fStuckCounter) )
|
|
{
|
|
m_nStuck = PED_STUCK_STAND_FOR_JUMP;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fStuckCounter = 0.0f;
|
|
m_nStuck = PED_NOT_STUCK;
|
|
}
|
|
|
|
if(m_nStuck != PED_NOT_STUCK)
|
|
{
|
|
// stop creating stuck events if scanningPed is now underwater, so that InWater event can get processed
|
|
bool bUnderWater = false;
|
|
if(pPed->GetPedIntelligence()->GetEventOfType(EVENT_IN_WATER)
|
|
&& ((CEventInWater *)pPed->GetPedIntelligence()->GetEventOfType(EVENT_IN_WATER))->GetBuoyancyFraction() > 1.0f)
|
|
bUnderWater = true;
|
|
|
|
if(bUnderWater)
|
|
{
|
|
pPed->SetIsStanding(false);
|
|
}
|
|
else
|
|
{
|
|
pPed->SetIsStanding(true);
|
|
pPed->SetWasStanding(true);
|
|
pPed->SetGroundPos(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()) - pPed->GetCapsuleInfo()->GetGroundToRootOffset() * ZAXIS);
|
|
pPed->SetGroundNormal(ZAXIS);
|
|
|
|
CEventStuckInAir event(pPed);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//////////////////////////////
|
|
//PED POTENTIAL COLLISION SCANNER
|
|
//////////////////////////////
|
|
|
|
EXT_PF_TIMER(PotentialCollisionScanner);
|
|
|
|
const float CPlayerToPedWalkIntoScanner::ms_fPedAvoidDistance=3.5f; // was 2.5f;
|
|
|
|
void CPlayerToPedWalkIntoScanner::Scan(CPed & ped, CPed* pClosestPed)
|
|
{
|
|
PF_FUNC(PotentialCollisionScanner);
|
|
|
|
if( !ped.IsAPlayerPed() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(0==pClosestPed)
|
|
{
|
|
//No ped to avoid.
|
|
return;
|
|
}
|
|
|
|
if(!ped.IsCollisionEnabled())
|
|
{
|
|
//Ped doesn't use collision.
|
|
return;
|
|
}
|
|
|
|
bool bShouldScan = true;
|
|
|
|
if( IsRegistered() )
|
|
{
|
|
bShouldScan = ShouldBeProcessedThisFrame();
|
|
}
|
|
|
|
if( bShouldScan )
|
|
{
|
|
StartProcess();
|
|
|
|
Vec3V vPedPos = ped.GetTransform().GetPosition();
|
|
Vec3V vTarget;
|
|
|
|
bool bThisPedIsPlayer = ped.IsAPlayerPed();
|
|
|
|
if(!bThisPedIsPlayer)// && !ped.IsNetworkClone())
|
|
{
|
|
//Don't want AI pushing people out the way
|
|
StopProcess();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// If this ped is the player, then extrapolate current direction
|
|
TUNE_GROUP_FLOAT(STATIONARYPEDAVOIDANCE, fPlayerOrNetworkCloneLookAheadDist, 0.75f, 0.0f, 5.0f, 0.1f);
|
|
vTarget = vPedPos + VECTOR3_TO_VEC3V(ped.GetVelocity()) * ScalarV(fPlayerOrNetworkCloneLookAheadDist);
|
|
}
|
|
|
|
// If movement vector is nearly zero in XY, then don't bother with this scan
|
|
if( (DistXY(vPedPos, vTarget) <= ScalarV(0.1f) ).Getb() )
|
|
{
|
|
StopProcess();
|
|
return;
|
|
}
|
|
|
|
// Display our look-ahead vector
|
|
#if __DEV
|
|
if(CPedDebugVisualiser::GetDebugDisplay()==CPedDebugVisualiser::eMovementDebugging)
|
|
{
|
|
grcDebugDraw::Line(vPedPos, vTarget, Color_CadetBlue, Color_BlueViolet);
|
|
}
|
|
#endif
|
|
|
|
const CEntityScannerIterator entityList = ped.GetPedIntelligence()->GetNearbyPeds();
|
|
const CEntity* pEntity = entityList.GetFirst();
|
|
while(pEntity)
|
|
{
|
|
Assert(pEntity->GetType()==ENTITY_TYPE_PED);
|
|
CPed* pClosePed=(CPed*)pEntity;
|
|
|
|
if ( !pClosePed->IsPlayer() )
|
|
{
|
|
Vec3V vClosePedPos = pClosePed->GetTransform().GetPosition();
|
|
|
|
//Check that close ped is collidable.
|
|
if(!pClosePed->IsDead() && pClosePed->IsCollisionEnabled())
|
|
{
|
|
TUNE_GROUP_FLOAT(STATIONARYPEDAVOIDANCE, s_CollisionPadding, 0.25f, 0.0f, 5.0f, 0.1f);
|
|
|
|
ScalarV vPlayerHalfWidth = ScalarV(ped.GetCapsuleInfo()->GetHalfWidth());
|
|
ScalarV vOtherHalfWidth = ScalarV(pClosePed->GetCapsuleInfo()->GetHalfWidth());
|
|
ScalarV vCollisionPadding = ScalarV(s_CollisionPadding);
|
|
ScalarV vRadius = vPlayerHalfWidth + vOtherHalfWidth + vCollisionPadding;
|
|
|
|
#if __DEV
|
|
if(CPedDebugVisualiser::GetDebugDisplay()==CPedDebugVisualiser::eMovementDebugging)
|
|
{
|
|
grcDebugDraw::Line(vPedPos, vTarget, Color_CadetBlue, Color_BlueViolet);
|
|
|
|
Vector3 drawTestLocation(vClosePedPos.GetXf(), vClosePedPos.GetYf(), vClosePedPos.GetZf());
|
|
grcDebugDraw::Circle(drawTestLocation, vRadius.Getf(), Color_red, XAXIS, YAXIS );
|
|
|
|
}
|
|
#endif
|
|
|
|
#if __BANK
|
|
//! Hard to test ped shoving, when peds dive out of the way all the time.
|
|
if(!CTaskShovePed::IsTesting())
|
|
#endif
|
|
{
|
|
if(pClosePed->GetMotionData()->GetIsStill() || pClosePed->GetMotionData()->GetIsWalking())
|
|
{
|
|
if ( rage::geomSpheres::TestSphereToSeg(vClosePedPos, vRadius, vPedPos, vTarget) )
|
|
{
|
|
CEventPotentialBeWalkedInto beWalkedIntoEvent(
|
|
&ped,
|
|
VEC3V_TO_VECTOR3(vTarget),
|
|
pClosePed->GetMotionData()->GetCurrentMbrY()
|
|
);
|
|
|
|
pClosePed->GetPedIntelligence()->AddEvent(beWalkedIntoEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! Can Ped shove?
|
|
if(CTaskShovePed::ScanForShoves() &&
|
|
!CPedMotionData::GetIsStill(ped.GetMotionData()->GetCurrentMbrY()) &&
|
|
CTaskShovePed::ShouldAllowTask( &ped, pClosePed))
|
|
{
|
|
CEventShovePed event( pClosePed );
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
pEntity = entityList.GetNext();
|
|
}
|
|
|
|
StopProcess();
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////
|
|
//VEHICLE POTENTIAL COLLISION SCANNER
|
|
//////////////////////////////
|
|
|
|
EXT_PF_TIMER(VehicleCollisionScanner);
|
|
|
|
const float CVehiclePotentialCollisionScanner::ms_fVehiclePotentialRunOverDistance=4.0f;//16.0f;
|
|
const float CVehiclePotentialCollisionScanner::ms_fVehicleThreatMultiplier=2.0f;
|
|
const float CVehiclePotentialCollisionScanner::ms_fMinAvoidSpeed = 0.05f;//0.01f; //0.1f;
|
|
const float CVehiclePotentialCollisionScanner::ms_fMinAvoidScaredSpeed=0.3f;
|
|
const float CVehiclePotentialCollisionScanner::ms_fSlowDiveDist=-0.5f;
|
|
const float CVehiclePotentialCollisionScanner::ms_fFastDiveDist=0.1f;
|
|
const float CVehiclePotentialCollisionScanner::ms_fVehicleAvoidDistance=7.5f;
|
|
dev_float CVehiclePotentialCollisionScanner::ms_fMinIntersectionLength=0.01f; //0.5f;
|
|
const int CVehiclePotentialCollisionScanner::ms_iPeriod=500; //1;
|
|
|
|
const float g_HalfPedHeight = 0.5f;
|
|
|
|
float CVehiclePotentialCollisionScanner::GetVehicleAvoidDistance(CVehicle * pVehicle)
|
|
{
|
|
return (pVehicle->GetVehicleType()==VEHICLE_TYPE_TRAIN || pVehicle->GetVehicleType()==VEHICLE_TYPE_PLANE || pVehicle->GetVehicleType()==VEHICLE_TYPE_HELI) ? ms_fVehicleAvoidDistance*2.0f : ms_fVehicleAvoidDistance;
|
|
}
|
|
|
|
void CVehiclePotentialCollisionScanner::Scan(const CPed& ped, bool bForce /* = false */)
|
|
{
|
|
PF_FUNC(VehicleCollisionScanner);
|
|
|
|
bool bShouldScan = false;
|
|
|
|
if( IsRegistered() )
|
|
{
|
|
bShouldScan = ShouldBeProcessedThisFrame();
|
|
}
|
|
else
|
|
{
|
|
if(!m_timer.IsSet())
|
|
{
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),ms_iPeriod);
|
|
}
|
|
|
|
if(m_timer.IsOutOfTime())
|
|
{
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),ms_iPeriod);
|
|
bShouldScan = true;
|
|
}
|
|
}
|
|
|
|
|
|
if( bShouldScan || bForce)
|
|
{
|
|
StartProcess();
|
|
|
|
//------------------------------------------------------------------------------------------------------------------
|
|
// Test if ped is wandering
|
|
// This scanner is now only used for wandering peds in order to navigate them past cars parked up on the sidewalk,
|
|
// by creation of the event 'CEventPotentialWalkIntoVehicle'.
|
|
|
|
CTaskMoveWander * pMoveWander = (CTaskMoveWander*)ped.GetPedIntelligence()->FindTaskActiveMovementByType(CTaskTypes::TASK_MOVE_WANDER);
|
|
if(pMoveWander && pMoveWander->GetState() == CTaskNavBase::NavBaseState_FollowingPath)
|
|
{
|
|
//Test if the ped is walking to a target.
|
|
const CTask* pTaskActiveSimplest=ped.GetPedIntelligence()->GetActiveSimplestMovementTask();
|
|
if( pTaskActiveSimplest )
|
|
{
|
|
const CTaskMoveInterface* pMoveInterface = pTaskActiveSimplest->GetMoveInterface();
|
|
|
|
if( !CPedMotionData::GetIsStill(pMoveInterface->GetMoveBlendRatio()) && pMoveInterface->HasTarget() )
|
|
{
|
|
//Get the closest vehicle.
|
|
CVehicle* pVehicle = ped.GetPedIntelligence()->GetClosestVehicleInRange(bForce);
|
|
|
|
// If we just forced an update, clear the scanners. [11/5/2012 mdawe]
|
|
if (bForce && ped.GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEntityScanning))
|
|
{
|
|
ped.GetPedIntelligence()->ClearScanners();
|
|
}
|
|
|
|
if(pVehicle)
|
|
{
|
|
//Test if the ped is near the vehicle or if the ped has just
|
|
//left a vehicle and should be forced to test the closest vehicle.
|
|
const Vector3 vPedPos = VEC3V_TO_VECTOR3(ped.GetTransform().GetPosition());
|
|
Vector3 vDiff = vPedPos - VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition());
|
|
const phBound* pBound=pVehicle->GetCurrentPhysicsInst()->GetArchetype()->GetBound();
|
|
const Matrix34 mat = MAT34V_TO_MATRIX34(pVehicle->GetMatrix());
|
|
|
|
Vector3 wldmin = VEC3V_TO_VECTOR3(pBound->GetBoundingBoxMin()) + mat.d;
|
|
Vector3 wldmax = VEC3V_TO_VECTOR3(pBound->GetBoundingBoxMax()) + mat.d;
|
|
|
|
Vector3 up_vec = mat.c;
|
|
float up_planedist = - DotProduct(wldmax, up_vec);
|
|
Vector3 down_vec = mat.c * -1.0f;
|
|
float down_planedist = - DotProduct(wldmin, down_vec);
|
|
|
|
// DPs to determine if this vehicle is a threat to ped in Z
|
|
bool isZThreat = ((DotProduct(up_vec, vPedPos) + up_planedist) < g_HalfPedHeight) &&
|
|
((DotProduct(down_vec, vPedPos) + down_planedist) < g_HalfPedHeight);
|
|
|
|
// For some larger vehicles, comparing distance between origins against ms_fVehicleAvoidDistance is no good.
|
|
// The vehicles are large enough to still be potential obstacles when outside this range.
|
|
const float fVehicleAvoidDistance = GetVehicleAvoidDistance(pVehicle);
|
|
|
|
if(isZThreat && (ped.GetPedConfigFlag( CPED_CONFIG_FLAG_HasJustLeftCar ) || vDiff.Mag2() < (fVehicleAvoidDistance*fVehicleAvoidDistance)))
|
|
{
|
|
CEntityBoundAI bound(*pVehicle,vPedPos.z,ped.GetCapsuleInfo()->GetHalfWidth());
|
|
|
|
// Try and catch bad shape test data before we pass it through to ComputeIntersectionLength.
|
|
Assert(ped.GetCurrentPhysicsInst()->IsInLevel());
|
|
Assert(pVehicle->GetCurrentPhysicsInst()->IsInLevel());
|
|
|
|
Assertf(vPedPos.Mag2()<1e8*1e8,"CVehiclePotentialCollisionScanner::Scan() - impractical segment starting point: (%f, %f, %f) movetask: %s", vPedPos.x, vPedPos.y, vPedPos.z, pTaskActiveSimplest->GetTaskName());
|
|
|
|
static dev_float fExtend = 3.5f;
|
|
|
|
Vector3 v1,v2;
|
|
ScalarV distToIsectV;
|
|
float fTotalRouteDist = 0.f;
|
|
Vector3 vPrevRoutePt = vPedPos;
|
|
int nProgress = pMoveWander->GetCurrentRouteProgress();
|
|
int nRouteLength = pMoveWander->GetNumRoutePoints();
|
|
|
|
for (int i = nProgress; i < nRouteLength; ++i)
|
|
{
|
|
Vector3 vRouteTarget = pMoveWander->GetRoutePoint(i);
|
|
Assertf(vRouteTarget.Mag2()<1e8*1e8,"CVehiclePotentialCollisionScanner::Scan() - impractical segment ending point: (%f, %f, %f) movetask: %s", vRouteTarget.x, vRouteTarget.y, vRouteTarget.z, pTaskActiveSimplest->GetTaskName());
|
|
|
|
Vector3 vToRouteTarget = vRouteTarget - vPrevRoutePt;
|
|
vToRouteTarget.Normalize();
|
|
|
|
// Target point must be somewhat in front of us, we might not be following the path precicely
|
|
if ((i > nProgress || vToRouteTarget.Dot(VEC3V_TO_VECTOR3(ped.GetTransform().GetForward())) > 0.5f) &&
|
|
!bound.TestLineOfSightReturnDistance(VECTOR3_TO_VEC3V(vPrevRoutePt), VECTOR3_TO_VEC3V(vRouteTarget), distToIsectV))
|
|
{
|
|
//Vector3 vToRouteTarget = vRouteTarget - vPrevRoutePt;
|
|
//vToRouteTarget.Normalize();
|
|
vRouteTarget = vPrevRoutePt + (vToRouteTarget * fVehicleAvoidDistance);
|
|
|
|
if ((bound.ComputeHitSideByPosition(vPrevRoutePt) != bound.ComputeHitSideByPosition(vRouteTarget)) &&
|
|
bound.ComputeCrossingPoints(vPrevRoutePt, vToRouteTarget, v2, v1))
|
|
{
|
|
// This might happen if we are inside the vehicle, low lod peds can be that
|
|
if ((v1-vRouteTarget).Mag2() >= (v2-vRouteTarget).Mag2())
|
|
{
|
|
vRouteTarget = v2 + (vToRouteTarget * (fExtend+ped.GetCapsuleInfo()->GetHalfWidth()));
|
|
if(IsGreaterThanAll(distToIsectV, LoadScalar32IntoScalarV(ms_fMinIntersectionLength)))
|
|
{
|
|
Vector3 vDirToTarget = vRouteTarget - vPedPos;
|
|
vDirToTarget.Normalize();
|
|
if(!CPedGeometryAnalyser::IsBrushingContact(ped, *pVehicle, vDirToTarget))
|
|
{
|
|
CEventPotentialWalkIntoVehicle event(pVehicle, ped.GetPedIntelligence()->GetMoveBlendRatioFromGoToTask(), vRouteTarget);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (fTotalRouteDist > fVehicleAvoidDistance)
|
|
{
|
|
break;
|
|
}
|
|
|
|
fTotalRouteDist += (vRouteTarget - vPrevRoutePt).XYMag();
|
|
vPrevRoutePt = vRouteTarget;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
StopProcess();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//BUILDING COLLISION SCANNER
|
|
//////////////////////////////
|
|
|
|
const float CBuildingPotentialCollisionScanner::ms_fLookAheadDistanceWalking=4.0f;
|
|
const float CBuildingPotentialCollisionScanner::ms_fLookAheadDistanceRunning=8.0f;
|
|
const float CBuildingPotentialCollisionScanner::ms_fNormalZThreshold=0.707f;
|
|
const int CBuildingPotentialCollisionScanner::ms_iPeriod=500;
|
|
|
|
void CBuildingPotentialCollisionScanner::ScanForBuildingPotentialCollisionEvents(const CPed& UNUSED_PARAM(ped))
|
|
{
|
|
|
|
}
|
|
|
|
////////////////////////////
|
|
//PED ACQUAINTANCE SCANNER
|
|
////////////////////////////
|
|
|
|
float CPedAcquaintanceScanner::ms_fThresholdDotProduct=0.0f;
|
|
bool CPedAcquaintanceScanner::ms_bDoPedAcquaintanceScannerLosAsync = true;
|
|
|
|
// PURPOSE: true => Apply fix for LOSForSoundEvents flag (B*2105791) (Just for testing purposes)
|
|
bank_bool CPedAcquaintanceScanner::ms_bFixCheckLoSForSoundEvents = false;
|
|
|
|
EXT_PF_TIMER(AcquaintanceScanner);
|
|
|
|
//PURPOSE: Check to see that in SP player wanted level is less than 'wanted', in MP all players wanted level less than 'wanted'
|
|
bool CheckPlayerWantedLevelLessThan(eWantedLevel wanted)
|
|
{
|
|
if (NetworkInterface::IsGameInProgress())
|
|
{
|
|
//MP - the first player found with a wanted level greater than or equal to the passed in wanted level will return false
|
|
for(ActivePlayerIndex i=0; i<MAX_NUM_ACTIVE_PLAYERS; ++i)
|
|
{
|
|
CNetGamePlayer* pPlayer = NetworkInterface::GetActivePlayerFromIndex(i);
|
|
CPed* pPed = pPlayer ? pPlayer->GetPlayerPed() : NULL;
|
|
if(pPed && pPed->GetPlayerWanted()->GetWantedLevel() >= wanted)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//SP - evaluate the local player only
|
|
return (FindPlayerPed()->GetPlayerWanted()->GetWantedLevel() < wanted);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CPedAcquaintanceScanner::Scan(CPed& ped, CEntityScannerIterator& entityList)
|
|
{
|
|
PF_FUNC(AcquaintanceScanner);
|
|
|
|
Assert(IsRegistered());
|
|
|
|
// Should this event be processed this frame
|
|
bool bShouldScan = ShouldBeProcessedThisFrame();
|
|
|
|
if( bShouldScan )
|
|
{
|
|
StartProcess();
|
|
// Is the ped in a good state for scanning, not dead, injured etc...
|
|
if(PedShouldBeScanning(ped))
|
|
{
|
|
// Scan nearby acquaintances
|
|
ScanNearbyAcquaintances(ped, entityList);
|
|
|
|
// clear check time
|
|
m_uNextShouldBeScanningCheckTimeMS = 0;
|
|
}
|
|
else
|
|
{
|
|
// If check time has not been set
|
|
// and this is not a mission ped
|
|
// and this is not a law ped with wanted player
|
|
if( m_uNextShouldBeScanningCheckTimeMS == 0 && !ped.PopTypeIsMission() && (!ped.IsLawEnforcementPed() || CheckPlayerWantedLevelLessThan(WANTED_LEVEL1)) )
|
|
{
|
|
// store a time that we may scan again
|
|
// and in the meantime keep returning false for performance
|
|
static dev_u32 delayDurationMS = 500;
|
|
m_uNextShouldBeScanningCheckTimeMS = fwTimer::GetTimeInMilliseconds() + delayDurationMS;
|
|
}
|
|
}
|
|
StopProcess();
|
|
}
|
|
}
|
|
|
|
// PURPOSE: Check our acquaintance type between ped and pOtherPed and add them to the array of peds if our acquaintance types have a response
|
|
// PARAM NOTES: ped - the local ped doing the scans, pOtherPed - the ped we are checking against
|
|
// aPedsToScan - an array of ped pointers which we've decided we are allowed to scan, we end up picking one of these peds
|
|
// aAcquaintanceTypes - the acquaintance types of the peds we decide to scan, numPedsToScan - the number of peds we've decided we can scan
|
|
bool CPedAcquaintanceScanner::AddPedToScan(CPed& ped, CPed* pOtherPed, CPed** aPedsToScan, s32* aAcquaintanceTypes, int& numPedsToScan)
|
|
{
|
|
bool bPedAdded = false;
|
|
|
|
// Work out the highest priority acquaintance type this ped has with the scanned ped.
|
|
s32 iAcquaintanceType = GetHighestAcquintanceTypeBetweenPeds(ped, *pOtherPed);
|
|
|
|
// Some checks to see if we should force add this ped
|
|
const bool bAllowAddForDeadPed = (iAcquaintanceType == ACQUAINTANCE_TYPE_PED_DEAD) && ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillGenerateDeadPedSeenScriptEvents);
|
|
bool bForceAddIfAlive = iAcquaintanceType == ACQUAINTANCE_TYPE_PED_DEAD || (iAcquaintanceType == ACQUAINTANCE_TYPE_PED_HATE && ped.GetPedResetFlag(CPED_RESET_FLAG_CanPedSeeHatedPedBeingUsed));
|
|
if(!bAllowAddForDeadPed && !bForceAddIfAlive && ped.GetBlockingOfNonTemporaryEvents())
|
|
{
|
|
// When blocking events we ONLY force add hate or wanted peds, otherwise just early out
|
|
bForceAddIfAlive = (iAcquaintanceType == ACQUAINTANCE_TYPE_PED_HATE || iAcquaintanceType == ACQUAINTANCE_TYPE_PED_WANTED);
|
|
if(!bForceAddIfAlive)
|
|
{
|
|
return bPedAdded;
|
|
}
|
|
}
|
|
|
|
// Check to see if this ped has a response for the given acquaintance type
|
|
// This fn can handle being passed ACQUAINTANCE_TYPE_INVALID
|
|
bool isLawEnforcementPed = ped.IsLawEnforcementPed();
|
|
|
|
// url:bugstar:2557284 - eTEAM_COP isn't used any more, script use teams 0-3 freely with assuming certain team types. Removing below code.
|
|
// || (ped.IsPlayer() && ped.GetNetworkObject() && ped.GetNetworkObject()->GetPlayerOwner() && ped.GetNetworkObject()->GetPlayerOwner()->GetTeam() == eTEAM_COP);
|
|
|
|
if(NetworkInterface::IsGameInProgress() && isLawEnforcementPed && !pOtherPed->IsDead() &&
|
|
pOtherPed->GetPlayerWanted() && pOtherPed->GetPlayerWanted()->GetWantedLevel() > WANTED_CLEAN)
|
|
{
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::AddPedToScan ACQUAINTANCE_TYPE_PED_WANTED [%s]",pOtherPed->GetNetworkObject() ? pOtherPed->GetNetworkObject()->GetLogName() : pOtherPed->GetModelName());)
|
|
aAcquaintanceTypes[numPedsToScan] = ACQUAINTANCE_TYPE_PED_WANTED;
|
|
aPedsToScan[numPedsToScan++] = pOtherPed;
|
|
bPedAdded = true;
|
|
}
|
|
else if( !pOtherPed->IsDead() &&
|
|
(bForceAddIfAlive || PedHasResponseForAcquaintanceType(ped, *pOtherPed, iAcquaintanceType)) )
|
|
{
|
|
// In MP the wanted relationship is only valid if the player has a wanted level
|
|
if( NetworkInterface::IsGameInProgress() && iAcquaintanceType == ACQUAINTANCE_TYPE_PED_WANTED && pOtherPed->GetPlayerWanted() && pOtherPed->GetPlayerWanted()->GetWantedLevel() == WANTED_CLEAN )
|
|
{
|
|
// Reject, the target doesn't have a wanted level
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::AddPedToScan Reject, the target doesn't have a wanted level [%s]",pOtherPed->GetNetworkObject() ? pOtherPed->GetNetworkObject()->GetLogName() : pOtherPed->GetModelName());)
|
|
}
|
|
else
|
|
{
|
|
aAcquaintanceTypes[numPedsToScan] = iAcquaintanceType;
|
|
aPedsToScan[numPedsToScan++] = pOtherPed;
|
|
bPedAdded = true;
|
|
}
|
|
}
|
|
// Code to get dead ped investigation stuff working
|
|
else if (pOtherPed->IsDead() &&
|
|
(ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillScanForDeadPeds)
|
|
|| ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillGenerateDeadPedSeenScriptEvents)))
|
|
{
|
|
aAcquaintanceTypes[numPedsToScan] = ACQUAINTANCE_TYPE_PED_DEAD;
|
|
aPedsToScan[numPedsToScan++] = pOtherPed;
|
|
bPedAdded = true;
|
|
}
|
|
|
|
return bPedAdded;
|
|
}
|
|
|
|
void CPedAcquaintanceScanner::ScanNearbyAcquaintances(CPed& ped, CEntityScannerIterator& entityList)
|
|
{
|
|
CPed * aPedsToScan[CEntityScanner::MAX_NUM_ENTITIES];
|
|
s32 aAcquaintanceTypes[CEntityScanner::MAX_NUM_ENTITIES];
|
|
int numPedsToScan = 0;
|
|
|
|
bool bIsPedLocalPlayer = ped.IsLocalPlayer();
|
|
CPlayerInfo* pLocalPlayerInfo = ped.GetPlayerInfo();
|
|
|
|
// If we are a local player then scan through our player peds
|
|
if(pLocalPlayerInfo && bIsPedLocalPlayer && NetworkInterface::IsGameInProgress())
|
|
{
|
|
float fRange = MAX(ped.GetPedIntelligence()->GetPedPerception().GetSeeingRange(), CPedPerception::ms_fSenseRange);
|
|
float fRangeSq = square(fRange);
|
|
|
|
// Verify the indices of network players and if they don't exist then mark that index as not seen
|
|
for(u8 playerIndex = 0; playerIndex < MAX_NUM_PHYSICAL_PLAYERS; playerIndex++)
|
|
{
|
|
CNetGamePlayer* pPlayer = NetworkInterface::GetPhysicalPlayerFromIndex(static_cast<PhysicalPlayerIndex>(playerIndex));
|
|
if (!pPlayer || !pPlayer->IsPhysical() || !pPlayer->GetPlayerPed())
|
|
{
|
|
pLocalPlayerInfo->UpdatePlayerLOS(playerIndex, false);
|
|
pLocalPlayerInfo->UpdatePlayerTimeLastScanned(playerIndex, 0);
|
|
}
|
|
}
|
|
|
|
// iterate through remote network players
|
|
CNetGamePlayer *localPlayer = NetworkInterface::GetLocalPlayer();
|
|
|
|
if(localPlayer)
|
|
{
|
|
CVehicle* pMyVehicle = ped.GetVehiclePedInside();
|
|
bool bInHeli = pMyVehicle && pMyVehicle->InheritsFromHeli();
|
|
|
|
unsigned numRemotePhysicalPlayers = netInterface::GetNumRemotePhysicalPlayers();
|
|
const netPlayer * const *remotePhysicalPlayers = netInterface::GetRemotePhysicalPlayers();
|
|
|
|
for(unsigned index = 0; index < numRemotePhysicalPlayers; index++)
|
|
{
|
|
const CNetGamePlayer *pPlayer = SafeCast(const CNetGamePlayer, remotePhysicalPlayers[index]);
|
|
|
|
if(localPlayer->GetTeam() != pPlayer->GetTeam())
|
|
{
|
|
CPed* pOtherPed = pPlayer->GetPlayerPed();
|
|
if(pOtherPed)
|
|
{
|
|
// If we should scan this ped and they are within our range then we add them to the list, otherwise we say they aren't visible
|
|
if( ShouldScanThisPed(pOtherPed, ped) )
|
|
{
|
|
// if the other ped is in a heli then we should increase our range
|
|
CVehicle* pOtherPedVeh = pOtherPed->GetVehiclePedInside();
|
|
TUNE_GROUP_FLOAT(PED_ACQUAINTANCE_SCANNER, fPlayerInHeliRangeMultiplier, 2.5f, 0.0f, 50.0f, 0.1f);
|
|
float fMaxDistSq = (bInHeli || (pOtherPedVeh && pOtherPedVeh->InheritsFromHeli())) ? square(fRange * fPlayerInHeliRangeMultiplier) : fRangeSq;
|
|
if( DistSquared(ped.GetTransform().GetPosition(), pOtherPed->GetTransform().GetPosition()).Getf() < fMaxDistSq )
|
|
{
|
|
AddPedToScan(ped, pOtherPed, &aPedsToScan[0], &aAcquaintanceTypes[0], numPedsToScan);
|
|
}
|
|
else
|
|
{
|
|
pLocalPlayerInfo->UpdatePlayerLOS(pPlayer->GetPhysicalPlayerIndex(), false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pLocalPlayerInfo->UpdatePlayerLOS(pPlayer->GetPhysicalPlayerIndex(), false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( !bIsPedLocalPlayer )
|
|
{
|
|
// If have pending requests, we process just those this tick
|
|
for (s32 iRequestIdx = 0; !IsScanRequestQueueEmpty() && iRequestIdx < uMAX_SCAN_REQUESTS; ++iRequestIdx)
|
|
{
|
|
CScanRequest& rRequest = m_aScanRequests[iRequestIdx];
|
|
if (rRequest.IsValid())
|
|
{
|
|
bool bPedAdded = false;
|
|
|
|
if (ShouldScanThisPed(rRequest.GetPed(), ped))
|
|
{
|
|
bPedAdded = AddPedToScan(ped, rRequest.GetPed(), &aPedsToScan[0], &aAcquaintanceTypes[0], numPedsToScan);
|
|
}
|
|
|
|
if (!bPedAdded)
|
|
{
|
|
ClearScanRequest(iRequestIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't have any request (or the request peds could not be added to the scan), check the entity list
|
|
if (!numPedsToScan)
|
|
{
|
|
// Scan through all the nearby peds and make a list of those with whom this ped has a valid relationship
|
|
CEntity* pEntity = entityList.GetFirst();
|
|
while( pEntity )
|
|
{
|
|
//Get the ith nearby scanningEntity from the nearby ped list.
|
|
Assert(!pEntity || pEntity->GetType()==ENTITY_TYPE_PED);
|
|
CPed* pOtherPed=static_cast<CPed*>(pEntity);
|
|
|
|
//Test if we should scan this ped, also checks the pointer is valid.
|
|
if(ShouldScanThisPed(pOtherPed, ped))
|
|
{
|
|
AddPedToScan(ped, pOtherPed, &aPedsToScan[0], &aAcquaintanceTypes[0], numPedsToScan);
|
|
}
|
|
|
|
pEntity = entityList.GetNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If some valid peds to scan, pick a random one from the list
|
|
if( numPedsToScan > 0 )
|
|
{
|
|
CPed* pOtherPed = NULL;
|
|
if(!m_bWaitingOnAsyncTest)
|
|
{
|
|
// Not waiting on a previous async targeting query so we can pick a new target ped.
|
|
s32 iPedToScan = fwRandom::GetRandomNumberInRange(0, numPedsToScan);
|
|
|
|
// If our ped is a local player and has a valid info
|
|
if( pLocalPlayerInfo && (bIsPedLocalPlayer || NetworkInterface::IsGameInProgress()) )
|
|
{
|
|
// Look through our peds to scan
|
|
u32 oldestScannedTime = MAX_UINT32;
|
|
for(int i = 0; i < numPedsToScan; i++)
|
|
{
|
|
// Verify the other ped has a player info
|
|
CPlayerInfo* pOtherPlayerInfo = aPedsToScan[i]->GetPlayerInfo();
|
|
if(pOtherPlayerInfo)
|
|
{
|
|
// Get the last scan timed and if its older than our stored value store this as the ped to scan
|
|
// unless of course another ped in the array has an older scanned time
|
|
u32 uTimeLastScanned = pLocalPlayerInfo->GetTimeLastScannedPlayer(pOtherPlayerInfo->GetPhysicalPlayerIndex());
|
|
if(uTimeLastScanned < oldestScannedTime)
|
|
{
|
|
oldestScannedTime = uTimeLastScanned;
|
|
iPedToScan = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pOtherPed = aPedsToScan[iPedToScan];
|
|
m_pPedBeingScanned = pOtherPed;
|
|
}
|
|
else
|
|
{
|
|
pOtherPed = m_pPedBeingScanned;
|
|
}
|
|
|
|
// If the ped we have been scanning gets deleted, the Regd ptr will be NULL. In that case, we just reset in order to pick a
|
|
// new ped next time and quit. The cache entry will get cleaned up in CPedGeometryAnalyser::Process() when we call ClearExpiredEntries()
|
|
// on the CanTargetCache.
|
|
ECanTargetResult eLosResult = ENotSureYet;
|
|
if(pOtherPed)
|
|
{
|
|
// If we have a local player info stored and the other ped has a player info
|
|
if(pLocalPlayerInfo)
|
|
{
|
|
CPlayerInfo* pOtherPedInfo = pOtherPed->GetPlayerInfo();
|
|
if(pOtherPedInfo)
|
|
{
|
|
// Get their physical player index, reset the LOS (to false) and update the time we last scanned them to now
|
|
NOTFINAL_ONLY(wantedDebugf3("ResetPlayerLOS() and UpdatePlayerTimeLastScanned by ped[%s] for player[%s]",ped.GetNetworkObject() ? ped.GetNetworkObject()->GetLogName() : ped.GetModelName(),pOtherPed->GetNetworkObject() ? pOtherPed->GetNetworkObject()->GetLogName() : pOtherPed->GetModelName());)
|
|
PhysicalPlayerIndex otherPedIndex = pOtherPedInfo->GetPhysicalPlayerIndex();
|
|
pLocalPlayerInfo->ResetPlayerLOS(otherPedIndex);
|
|
pLocalPlayerInfo->UpdatePlayerTimeLastScanned(otherPedIndex, fwTimer::GetTimeInMilliseconds());
|
|
}
|
|
}
|
|
|
|
//Set flag to ignore vehicles
|
|
const bool ignoreVehiclesFlag = (bIsPedLocalPlayer
|
|
&& ped.GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle)
|
|
&& pOtherPed
|
|
&& pOtherPed->IsAPlayerPed()
|
|
&& pOtherPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle));
|
|
// Check visibility to the other ped from this ped.
|
|
m_bWaitingOnAsyncTest = true;
|
|
eLosResult = CanPedSeePed(ped, *pOtherPed);
|
|
if(eLosResult==ECanTarget || eLosResult==ECanNotTarget)
|
|
{
|
|
bool bMustPedBeAbleToSeeOtherPed = MustPedBeAbleToSeeOtherPed(ped, *pOtherPed);
|
|
bool bPedCanSeePed = (eLosResult == ECanTarget);
|
|
|
|
NOTFINAL_ONLY(wantedDebugf3("(eLosResult==ECanTarget[%d] || eLosResult==ECanNotTarget[%d]) bMustPedBeAbleToSeeOtherPed[%d] bPedCanSeePed[%d]",eLosResult==ECanTarget,eLosResult==ECanNotTarget,bMustPedBeAbleToSeeOtherPed,bPedCanSeePed);)
|
|
|
|
if( (!bIsPedLocalPlayer && (!bMustPedBeAbleToSeeOtherPed || bPedCanSeePed)) ||
|
|
(bIsPedLocalPlayer && pOtherPed->GetIsVisibleInSomeViewportThisFrame() && !CPedTargetEvaluator::GetIsLineOfSightBlockedBetweenPeds(&ped, pOtherPed, false, false, false, ignoreVehiclesFlag, false)) )
|
|
{
|
|
// Because relationship groups can change while waiting on the test results, we need to regrab the acquaintance type to make sure it's up to date
|
|
s32 iAcquaintanceType = GetHighestAcquintanceTypeBetweenPeds(ped, *pOtherPed);
|
|
|
|
NOTFINAL_ONLY(wantedDebugf3("next call ShouldAddAcquaintanceEvent");)
|
|
|
|
if(ShouldAddAcquaintanceEvent(ped, iAcquaintanceType, *pOtherPed))
|
|
{
|
|
// This isn't in AddAcquaintance event due to ped being passed in as const
|
|
if (ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillGenerateDeadPedSeenScriptEvents)
|
|
&& iAcquaintanceType == ACQUAINTANCE_TYPE_PED_DEAD)
|
|
{
|
|
if (!ped.GetPedIntelligence()->HasPedSeenDeadPedRecently())
|
|
{
|
|
ped.GetPedIntelligence()->RecordPedSeenDeadPed();
|
|
if(NetworkInterface::IsGameInProgress())
|
|
{
|
|
CNetworkPedSeenDeadPedEvent::Trigger(pOtherPed, &ped);
|
|
}
|
|
|
|
aiDebugf2("Frame %i: Adding CEventPedSeenDeadPed to script ai group ped %p has found dead ped %p", fwTimer::GetFrameCount(), &ped, pOtherPed);
|
|
GetEventScriptAIGroup()->Add(CEventPedSeenDeadPed(*pOtherPed, ped));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This ped can see the other ped, generate an acquaintance event
|
|
AddAcquaintanceEvent( ped, iAcquaintanceType, pOtherPed );
|
|
}
|
|
}
|
|
}
|
|
|
|
eLosResult = EReadyForNewPedPair;
|
|
}
|
|
}
|
|
|
|
if(eLosResult==EReadyForNewPedPair || !pOtherPed)
|
|
{
|
|
// Either our async probe has returned or timed out and we've done a synchronous probe or the ped we were scanning
|
|
// has been deleted. Either way, we can now select a new random ped.
|
|
m_bWaitingOnAsyncTest = false;
|
|
|
|
if (m_pPedBeingScanned)
|
|
{
|
|
ClearPedScanRequest(*m_pPedBeingScanned);
|
|
m_pPedBeingScanned = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE[egarcia]: We clear the scan request queue just to be sure we are not leaving anyone hanging around
|
|
if (!IsScanRequestQueueEmpty())
|
|
{
|
|
//aiWarningf("CPedAcquaintanceScanner::ScanNearbyAcquaintances: Scan Requests pending...");
|
|
ClearAllScanRequests();
|
|
}
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::ArePedsInSameVehicle(const CPed& rPed, const CPed& rOtherPed) const
|
|
{
|
|
//Ensure the ped is in a vehicle.
|
|
const CVehicle* pVehicle = rPed.GetVehiclePedInside();
|
|
if(!pVehicle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the other ped is in a vehicle.
|
|
const CVehicle* pOtherVehicle = rOtherPed.GetVehiclePedInside();
|
|
if(!pOtherVehicle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the vehicles match.
|
|
if(pVehicle != pOtherVehicle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::IsPedInFastMovingVehicle(const CPed& rPed) const
|
|
{
|
|
//Ensure the ped is in a vehicle.
|
|
const CVehicle* pVehicle = rPed.GetVehiclePedInside();
|
|
if(!pVehicle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the vehicle is moving fast.
|
|
TUNE_GROUP_FLOAT(PED_ACQUAINTANCE_SCANNER, fMinSpeedForVehicleToBeConsideredMovingFast, 5.0f, 0.0f, 50.0f, 1.0f);
|
|
float fMinSpeedForVehicleToBeConsideredMovingFastSq = square(fMinSpeedForVehicleToBeConsideredMovingFast);
|
|
float fSmoothedSpeedSq = pVehicle->GetIntelligence()->GetSmoothedSpeedSq();
|
|
if(fSmoothedSpeedSq < fMinSpeedForVehicleToBeConsideredMovingFastSq)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::MustPedBeAbleToSeeOtherPed(const CPed& rPed, const CPed& rOtherPed) const
|
|
{
|
|
//There are some situations where we want acquaintance events to fire, even if the ped can't see the other ped.
|
|
|
|
//Check if we are in a vehicle.
|
|
const CVehicle* pVehicle = rPed.GetVehiclePedInside();
|
|
if(pVehicle)
|
|
{
|
|
//Check if the other ped is standing on our vehicle.
|
|
if(rOtherPed.GetGroundPhysical() == pVehicle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Check if the other ped is driving on our vehicle.
|
|
const CVehicle* pOtherVehicle = rOtherPed.GetVehiclePedInside();
|
|
if(pOtherVehicle && (pOtherVehicle->GetVehicleDrivingOn() == pVehicle))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Grab the current time.
|
|
u32 uCurrentTime = fwTimer::GetTimeInMilliseconds();
|
|
|
|
//Check if we have been knocked to the ground recently by another ped.
|
|
u32 uLastTimeWeWereKnockedToTheGround = rPed.GetPedIntelligence()->GetLastTimeWeWereKnockedToTheGround();
|
|
static u32 s_uMaxTimeSinceWeWereLastKnockedToTheGround = 10000;
|
|
if((uLastTimeWeWereKnockedToTheGround + s_uMaxTimeSinceWeWereLastKnockedToTheGround) > uCurrentTime)
|
|
{
|
|
//Check if the entity that knocked us to the ground was the other ped.
|
|
const CEntity* pLastEntityThatKnockedUsToTheGround = rPed.GetPedIntelligence()->GetLastEntityThatKnockedUsToTheGround();
|
|
if(pLastEntityThatKnockedUsToTheGround == &rOtherPed)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we have been rammed in a vehicle recently by another ped.
|
|
u32 uLastTimeWeWereRammedInVehicle = rPed.GetPedIntelligence()->GetLastTimeWeWereRammedInVehicle();
|
|
static u32 s_uMaxTimeSinceWeWereLastRammedInVehicle = 10000;
|
|
if((uLastTimeWeWereRammedInVehicle + s_uMaxTimeSinceWeWereLastRammedInVehicle) > uCurrentTime)
|
|
{
|
|
//Check if the ped that rammed us in vehicle was the other ped.
|
|
const CPed* pLastPedThatRammedUsInVehicle = rPed.GetPedIntelligence()->GetLastPedThatRammedUsInVehicle();
|
|
if(pLastPedThatRammedUsInVehicle == &rOtherPed)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::ShouldAddAcquaintanceEvent(const CPed& rPed, const int iAcquaintanceType, const CPed& rOtherPed) const
|
|
{
|
|
// Don't add acquaintance type if it's invalid
|
|
if(iAcquaintanceType == ACQUAINTANCE_TYPE_INVALID)
|
|
{
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::ShouldAddAcquaintanceEvent--ACQUAINTANCE_TYPE_INVALID-->return false");)
|
|
return false;
|
|
}
|
|
|
|
//Check if the ped should ignore hated peds in fast moving vehicles.
|
|
if((iAcquaintanceType == ACQUAINTANCE_TYPE_PED_HATE) &&
|
|
rPed.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_IgnoreHatedPedsInFastMovingVehicles))
|
|
{
|
|
//Check if the other ped is in a fast moving vehicle, and the peds are not in the same vehicle.
|
|
if(IsPedInFastMovingVehicle(rOtherPed) && !ArePedsInSameVehicle(rPed, rOtherPed))
|
|
{
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::ShouldAddAcquaintanceEvent--(IsPedInFastMovingVehicle(rOtherPed) && !ArePedsInSameVehicle(rPed, rOtherPed))-->return false");)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if the ped should ignore the other ped, when they are wanted.
|
|
if((iAcquaintanceType == ACQUAINTANCE_TYPE_PED_WANTED) &&
|
|
rOtherPed.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_IgnoredByOtherPedsWhenWanted))
|
|
{
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::ShouldAddAcquaintanceEvent--ACQUAINTANCE_TYPE_PED_WANTED && BF_IgnoredByOtherPedsWhenWanted-->return false");)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::AddAcquaintanceEvent(const CPed& ped, const int iAcquaintanceType, CPed* pAcquaintancePed)
|
|
{
|
|
bool bAdded=false;
|
|
|
|
switch(iAcquaintanceType)
|
|
{
|
|
//Create the appropriate event.
|
|
case ACQUAINTANCE_TYPE_PED_RESPECT:
|
|
{
|
|
// Chat stuff
|
|
CTask* pTask = CChatHelper::GetPedsTask(const_cast<CPed*>(&ped));
|
|
if (pTask)
|
|
{
|
|
CChatHelper* pChatHelper = pTask->GetChatHelper();
|
|
if (pChatHelper)
|
|
{
|
|
pChatHelper->SetBestChatPed(pAcquaintancePed);
|
|
}
|
|
}
|
|
|
|
//CEventAcquaintancePedRespect event(pAcquaintancePed);
|
|
//bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false); // Gets the chat stuff working
|
|
}
|
|
break;
|
|
case ACQUAINTANCE_TYPE_PED_LIKE:
|
|
{
|
|
CEventAcquaintancePedLike event(pAcquaintancePed);
|
|
bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false);
|
|
}
|
|
break;
|
|
case ACQUAINTANCE_TYPE_PED_IGNORE:
|
|
break;
|
|
case ACQUAINTANCE_TYPE_PED_DISLIKE:
|
|
{
|
|
CEventAcquaintancePedDislike event(pAcquaintancePed);
|
|
bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false);
|
|
}
|
|
break;
|
|
case ACQUAINTANCE_TYPE_PED_WANTED:
|
|
{
|
|
// If we have a net game in progress, the other ped is a player we need
|
|
// to do some additional stuff (as we forced our way through previous checks to accomplish this)
|
|
if(NetworkInterface::IsGameInProgress() && pAcquaintancePed->IsPlayer())
|
|
{
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::AddAcquaintanceEvent--ACQUAINTANCE_TYPE_PED_WANTED");)
|
|
|
|
// Get and verify both player infos
|
|
CPed* localPlayer = CPedFactory::GetFactory()->GetLocalPlayer();
|
|
if (localPlayer)
|
|
{
|
|
CPlayerInfo* pLocalPlayerInfo = localPlayer->GetPlayerInfo();
|
|
CPlayerInfo* pNetPlayerInfo = pAcquaintancePed->GetPlayerInfo();
|
|
|
|
//Only valid if this isn't targetting the local player as this is for cop peds targetting other players and to fix that situation.
|
|
if(pLocalPlayerInfo && pNetPlayerInfo && pLocalPlayerInfo != pNetPlayerInfo)
|
|
{
|
|
// Here we are updating the LOS to this net player to be true, meaning our local player sees this player
|
|
NOTFINAL_ONLY(wantedDebugf3("CPedAcquaintanceScanner::AddAcquaintanceEvent--ACQUAINTANCE_TYPE_PED_WANTED--UpdatePlayerLOS(true)--UpdatePlayerTimeLastScanned");)
|
|
pLocalPlayerInfo->UpdatePlayerLOS(pNetPlayerInfo->GetPhysicalPlayerIndex(), true);
|
|
pLocalPlayerInfo->UpdatePlayerTimeLastScanned(pNetPlayerInfo->GetPhysicalPlayerIndex(), fwTimer::GetTimeInMilliseconds());
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!ped.GetBlockingOfNonTemporaryEvents())
|
|
{
|
|
CEventAcquaintancePedWanted event(pAcquaintancePed);
|
|
bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false);
|
|
}
|
|
else if(ped.GetPedIntelligence()->IsThreatenedBy(*pAcquaintancePed) && ped.GetPedIntelligence()->CanAttackPed(pAcquaintancePed))
|
|
{
|
|
#if __BANK
|
|
CAILogManager::GetLog().Log("CPedAcquaintanceScanner::AddAcquaintanceEvent, ACQUAINTANCE_TYPE_PED_WANTED, NonTemporaryEvents blocked. Registering threat without adding the event! Ped %s, AcquaintancePed %s.\n", AILogging::GetEntityDescription(&ped).c_str(), AILogging::GetEntityDescription(pAcquaintancePed).c_str());
|
|
#endif // __BANK
|
|
CPedTargetting* pPedTargetting = ped.GetPedIntelligence()->GetTargetting(true);
|
|
if (pPedTargetting)
|
|
{
|
|
pPedTargetting->RegisterThreat(pAcquaintancePed);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ACQUAINTANCE_TYPE_PED_HATE:
|
|
{
|
|
bool bAddEvent = (!ped.GetBlockingOfNonTemporaryEvents() || ped.GetPedResetFlag(CPED_RESET_FLAG_CanPedSeeHatedPedBeingUsed));
|
|
ped.GetPedIntelligence()->SetAlertnessState(CPedIntelligence::AS_Alert);
|
|
#if __BANK
|
|
if (!bAddEvent)
|
|
{
|
|
CAILogManager::GetLog().Log("CPedAcquaintanceScanner::AddAcquaintanceEvent, ACQUAINTANCE_TYPE_PED_HATE, NonTemporaryEvents blocked. Increasing alertness state without adding the event! Ped %s, AcquaintancePed %s.\n", AILogging::GetEntityDescription(&ped).c_str(), AILogging::GetEntityDescription(pAcquaintancePed).c_str());
|
|
}
|
|
#endif // __BANK
|
|
|
|
|
|
// Check to see if the target ped is actually at a distance where they aren't properly identified
|
|
if( DistSquared(ped.GetTransform().GetPosition(), pAcquaintancePed->GetTransform().GetPosition()).Getf() > rage::square(ped.GetPedIntelligence()->GetPedPerception().GetIdentificationRange()) &&
|
|
ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanInvestigate) &&
|
|
pAcquaintancePed->GetPedIntelligence()->GetPedStealth().GetFlags().IsFlagSet(CPedStealth::SF_TreatedAsActingSuspiciously))
|
|
{
|
|
if(bAddEvent)
|
|
{
|
|
CEventUnidentifiedPed event(pAcquaintancePed);
|
|
bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPedTargetting* pPedTargetting = ped.GetPedIntelligence()->GetTargetting(true);
|
|
if (pPedTargetting)
|
|
{
|
|
pPedTargetting->RegisterThreat(pAcquaintancePed);
|
|
}
|
|
|
|
#if __BANK
|
|
if(!bAddEvent)
|
|
{
|
|
CAILogManager::GetLog().Log("CPedAcquaintanceScanner::AddAcquaintanceEvent, ACQUAINTANCE_TYPE_PED_HATE, NonTemporaryEvents blocked. Registering threat without adding the event! Ped %s, AcquaintancePed %s.\n", AILogging::GetEntityDescription(&ped).c_str(), AILogging::GetEntityDescription(pAcquaintancePed).c_str());
|
|
}
|
|
#endif // __BANK
|
|
|
|
if(bAddEvent)
|
|
{
|
|
CEventAcquaintancePedHate event(pAcquaintancePed);
|
|
bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ACQUAINTANCE_TYPE_PED_DEAD:
|
|
{
|
|
// If the dead ped has not already been reported
|
|
if (taskVerifyf(pAcquaintancePed->IsDead(),"Ped Isn't Dead, something has gone wrong with the aquaintance scanning") && !pAcquaintancePed->GetPedConfigFlag( CPED_CONFIG_FLAG_HasDeadPedBeenReported ))
|
|
{
|
|
CPedIntelligence* pPedIntell = ped.GetPedIntelligence();
|
|
CTask* pTask = pPedIntell->FindTaskActiveByType(CTaskTypes::TASK_INVESTIGATE);
|
|
|
|
if (pTask)
|
|
{
|
|
static_cast<CTaskInvestigate*>(pTask)->QuitForDeadPedInvestigation(pAcquaintancePed);
|
|
}
|
|
else
|
|
{
|
|
CEventAcquaintancePedDead event(pAcquaintancePed, true);
|
|
bAdded=(ped.GetPedIntelligence()->AddEvent(event) ? true : false);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
Assert(false);
|
|
bAdded=false;
|
|
break;
|
|
}
|
|
|
|
return bAdded;
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::PedShouldBeScanning(const CPed& ped) const
|
|
{
|
|
if(ped.IsDead())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we returned false before, continue to do so for a short time
|
|
if( m_uNextShouldBeScanningCheckTimeMS > 0 && fwTimer::GetTimeInMilliseconds() < m_uNextShouldBeScanningCheckTimeMS )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If this is a mission ped
|
|
if(ped.PopTypeIsMission())
|
|
{
|
|
//Test if the scanner is activated everywhere.
|
|
if(!m_bActivatedEverywhere)
|
|
{
|
|
//Scanner isn't activated everywhere.
|
|
//Test if the scanner is activated under specific conditions.
|
|
|
|
//Test if the ped is in a car.
|
|
if(!ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || !m_bActivatedInVehicle)
|
|
{
|
|
//Ped is in a car and the scanner is activated for cars
|
|
return false;
|
|
}
|
|
|
|
//Test if the ped is executing a script command.
|
|
if(!CPedScriptedTaskRecord::GetStatus(&ped) || !m_bActivatedDuringScriptCommands)
|
|
{
|
|
//Ped is on script command and scanner is activated for script commands.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check if the ped is already responding to an acquaintance event.
|
|
CEvent* pEvent=ped.GetPedIntelligence()->GetCurrentEvent();
|
|
if(pEvent && pEvent->GetEventType()==EVENT_ACQUAINTANCE_PED_HATE)
|
|
{
|
|
bool bForceScan = (ped.ShouldBehaveLikeLaw() && ped.PopTypeIsMission());
|
|
if(!bForceScan)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillScanForDeadPeds) ||
|
|
ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillGenerateDeadPedSeenScriptEvents))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Only bother scanning at all if the ped has any hate relationships
|
|
if( !PedHasRelationshipsToScan(ped) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CPedAcquaintanceScanner::PedHasRelationshipsToScan( const CPed &ped ) const
|
|
{
|
|
// If the ped is in a group then always scan
|
|
if( ped.GetPedGroupIndex() != PEDGROUP_INDEX_NONE )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Is the ped a random type?
|
|
bool bIsPopTypeRandom = ped.PopTypeIsRandom();
|
|
|
|
// If the ped is a random type and there is a riot, always scan
|
|
if( bIsPopTypeRandom && CRiots::GetInstance().GetEnabled() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// By default the ped does not need to scan
|
|
bool bHasRelationshipsToScan = false;
|
|
|
|
// Get the ped's relationship group
|
|
const CRelationshipGroup* pRelGroup = ped.GetPedIntelligence()->GetRelationshipGroup();
|
|
if( pRelGroup )
|
|
{
|
|
// If the ped is a random type
|
|
if( bIsPopTypeRandom )
|
|
{
|
|
// Random peds scan if they have any relationships they hate
|
|
// OR if they are law enforcement and have any that are wanted
|
|
if( pRelGroup->HasAnyRelationShipOfType( ACQUAINTANCE_TYPE_PED_HATE ) ||
|
|
(ped.ShouldBehaveLikeLaw() && pRelGroup->HasAnyRelationShipOfType( ACQUAINTANCE_TYPE_PED_WANTED ) ) )
|
|
{
|
|
bHasRelationshipsToScan = true;
|
|
}
|
|
}
|
|
else // ped is not random
|
|
{
|
|
// Mission peds scan if they have any relationships of certain types
|
|
if( pRelGroup->HasAnyRelationShipOfType( ACQUAINTANCE_TYPE_PED_HATE ) ||
|
|
pRelGroup->HasAnyRelationShipOfType( ACQUAINTANCE_TYPE_PED_DISLIKE) ||
|
|
pRelGroup->HasAnyRelationShipOfType( ACQUAINTANCE_TYPE_PED_WANTED ) )
|
|
{
|
|
bHasRelationshipsToScan = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bHasRelationshipsToScan;
|
|
}
|
|
|
|
bool CPedAcquaintanceScanner::ShouldScanThisPed( const CPed* pOtherPed, CPed& ped )
|
|
{
|
|
// Invalid ped, quit
|
|
if( pOtherPed == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if !__FINAL
|
|
// Debug test for ignoring an invisible player ped
|
|
if( pOtherPed->IsPlayer() && CPlayerInfo::ms_bDebugPlayerInvisible )
|
|
{
|
|
return false;
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
// SP only - Don't scan peds who are in the targeting list already
|
|
// MP need to rescan these as they need to get wanted times updated - including local player
|
|
if ( !NetworkInterface::IsGameInProgress() )
|
|
{
|
|
CPedTargetting* pPedTargetting = ped.GetPedIntelligence()->GetTargetting( false );
|
|
if( pPedTargetting && pPedTargetting->FindTarget(pOtherPed) )
|
|
{
|
|
if( !ped.GetPedResetFlag(CPED_RESET_FLAG_CanPedSeeHatedPedBeingUsed) ||
|
|
!ped.GetBlockingOfNonTemporaryEvents() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we can't scan for dead peds and the ped is dead, do not scan this ped
|
|
if (pOtherPed->IsDead() && !ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillScanForDeadPeds))
|
|
{
|
|
if (ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillGenerateDeadPedSeenScriptEvents))
|
|
{
|
|
return true;
|
|
}
|
|
else if (pOtherPed->GetPedConfigFlag( CPED_CONFIG_FLAG_HasDeadPedBeenReported ) == true)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int CPedAcquaintanceScanner::GetHighestAcquintanceTypeBetweenPeds( const CPed& ped, const CPed& otherPed )
|
|
{
|
|
if (ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_WillGenerateDeadPedSeenScriptEvents) && otherPed.IsDead())
|
|
{
|
|
return ACQUAINTANCE_TYPE_PED_DEAD;
|
|
}
|
|
|
|
const CRelationshipGroup* pRelGroup = ped.GetPedIntelligence()->GetRelationshipGroup();
|
|
if( pRelGroup )
|
|
{
|
|
const CPedIntelligence* pOtherIntelligence = otherPed.GetPedIntelligence();
|
|
pedAssert(pOtherIntelligence->GetRelationshipGroup());
|
|
eRelationType relationType = pRelGroup->GetRelationship(pOtherIntelligence->GetRelationshipGroupIndex());
|
|
|
|
if(relationType == ACQUAINTANCE_TYPE_PED_DISLIKE && ped.GetPedConfigFlag(CPED_CONFIG_FLAG_TreatDislikeAsHateWhenInCombat) &&
|
|
ped.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT) )
|
|
{
|
|
relationType = ACQUAINTANCE_TYPE_PED_HATE;
|
|
}
|
|
|
|
if( relationType < ACQUAINTANCE_TYPE_PED_WANTED || relationType == ACQUAINTANCE_TYPE_INVALID )
|
|
{
|
|
// Check the players effective wanted level directly as wanted level acquaintances aren't synced
|
|
// during network games
|
|
if(otherPed.IsPlayer())
|
|
{
|
|
if( otherPed.GetPlayerWanted() && otherPed.GetPlayerWanted()->GetWantedLevel() > WANTED_CLEAN )
|
|
{
|
|
#if __ASSERT
|
|
if (ped.GetPedIntelligence()->IsFriendlyWith(otherPed))
|
|
{
|
|
aiDisplayf("Setting acquaintance type to ACQUAINTANCE_TYPE_PED_WANTED Ped %s in relationship group %s (ped group %p, ped type %i) is friendly with ped %s in relationship group %s (ped group %p, ped type %i)",
|
|
ped.GetModelName(), ped.GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr(), ped.GetPedsGroup(), ped.GetPedType(),
|
|
otherPed.GetModelName(), otherPed.GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr(), otherPed.GetPedsGroup(), otherPed.GetPedType());
|
|
}
|
|
#endif // __ASSERT
|
|
relationType = ACQUAINTANCE_TYPE_PED_WANTED;
|
|
}
|
|
}
|
|
|
|
// If the relationship to the enemy ped is neutral but they are aggressive to this ped,
|
|
// or a ped in this peds group, treat them as a threat.
|
|
// Only do this if in a group and a mission ped, swat/cops should automatically do this
|
|
if( ped.PopTypeIsMission() && ped.GetPedsGroup() && !ped.GetPedIntelligence()->IsFriendlyWith(otherPed) &&
|
|
( ( otherPed.GetWeaponManager() && otherPed.GetWeaponManager()->GetIsArmed() ) || !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) ) )
|
|
{
|
|
CEntity* pEntity = otherPed.GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget();
|
|
CPed* pPedsTarget = NULL;
|
|
if( pEntity && pEntity->GetIsTypePed() )
|
|
{
|
|
pPedsTarget = static_cast<CPed*>(pEntity);
|
|
}
|
|
else if( pEntity && pEntity->GetIsTypeVehicle() )
|
|
{
|
|
pPedsTarget = static_cast<CVehicle*>(pEntity)->GetDriver();
|
|
}
|
|
if( pPedsTarget )
|
|
{
|
|
if( ped.GetPedsGroup() &&
|
|
(ped.GetPedIntelligence()->IsFriendlyWith( *pPedsTarget) ||
|
|
ped.GetPedsGroup() == pPedsTarget->GetPedsGroup() ))
|
|
{
|
|
#if __ASSERT
|
|
if (ped.GetPedIntelligence()->IsFriendlyWith(otherPed))
|
|
{
|
|
aiDisplayf("Setting acquaintance type to ACQUAINTANCE_TYPE_PED_HATE Ped %s in relationship group %s (ped group %p, ped type %i) is friendly with ped %s in relationship group %s (ped group %p, ped type %i)",
|
|
ped.GetModelName(), ped.GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr(), ped.GetPedsGroup(), ped.GetPedType(),
|
|
otherPed.GetModelName(), otherPed.GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr(), otherPed.GetPedsGroup(), otherPed.GetPedType());
|
|
aiAssertf(0,"Please add a bug and attach logs");
|
|
}
|
|
#endif // __ASSERT
|
|
relationType = ACQUAINTANCE_TYPE_PED_HATE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ped.PopTypeIsRandom() && CRiots::GetInstance().GetEnabled() && !ped.GetPedIntelligence()->IsFriendlyWith(otherPed))
|
|
{
|
|
relationType = ACQUAINTANCE_TYPE_PED_HATE;
|
|
}
|
|
|
|
return relationType;
|
|
}
|
|
return ACQUAINTANCE_TYPE_INVALID;
|
|
}
|
|
|
|
bool CPedAcquaintanceScanner::PedHasResponseForAcquaintanceType( CPed& ped, CPed& otherPed, s32 iAcquaintanceScanType )
|
|
{
|
|
//Check if ped has a response set up to any acquaintance event.
|
|
switch(iAcquaintanceScanType)
|
|
{
|
|
case ACQUAINTANCE_TYPE_PED_HATE:
|
|
{
|
|
CEventAcquaintancePedHate tempEvent(&otherPed);
|
|
return HasEventResponse(ped, &tempEvent);
|
|
}
|
|
case ACQUAINTANCE_TYPE_PED_WANTED:
|
|
{
|
|
// url:bugstar:2557284 - eTEAM_COP isn't used any more, script use teams 0-3 freely with assuming certain team types. Removing below code.
|
|
|
|
// If this is a network game, our ped is a local player and we are checking against another player we want
|
|
// to assume we have a response (we don't, but we use this assumption for visibility storage for other players)
|
|
//if(NetworkInterface::IsGameInProgress() && ped.IsLocalPlayer() && otherPed.IsPlayer() &&
|
|
// ped.GetPlayerInfo() && ped.GetPlayerInfo()->Team == eTEAM_COP)
|
|
//{
|
|
// return true;
|
|
//}
|
|
//else
|
|
|
|
CEventAcquaintancePedWanted tempEvent(&otherPed);
|
|
return HasEventResponse(ped, &tempEvent);
|
|
|
|
}
|
|
case ACQUAINTANCE_TYPE_PED_DISLIKE:
|
|
{
|
|
CEventAcquaintancePedDislike tempEvent(&otherPed);
|
|
return HasEventResponse(ped, &tempEvent);
|
|
}
|
|
case ACQUAINTANCE_TYPE_PED_IGNORE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CPedAcquaintanceScanner::HasEventResponse(CPed& ped, CEventEditableResponse* pEvent)
|
|
{
|
|
CPedEventDecisionMaker& rPedDecisionMaker = ped.GetPedIntelligence()->GetPedDecisionMaker();
|
|
|
|
CEventResponseTheDecision decision;
|
|
rPedDecisionMaker.MakeDecision(&ped, pEvent, decision);
|
|
if(decision.HasDecision())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ECanTargetResult CPedAcquaintanceScanner::CanPedSeePed( CPed& ped, CPed& otherPed )
|
|
{
|
|
ECanTargetResult returnValue = ENotSureYet;
|
|
s32 iTargetFlags = !(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || otherPed.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle )) ? TargetOption_doDirectionalTest : 0;
|
|
u32 uCacheTimeOut = CAN_TARGET_CACHE_TIMEOUT;
|
|
|
|
CPedTargetting* pPedTargetting = ped.GetPedIntelligence()->GetTargetting(false);
|
|
bool bHasTargetInfo = false;
|
|
if( pPedTargetting == NULL || (!pPedTargetting->AreTargetsWhereaboutsKnown(NULL, &otherPed, NULL, &bHasTargetInfo) || !bHasTargetInfo ) )
|
|
{
|
|
iTargetFlags |= TargetOption_IgnoreTargetsCover;
|
|
iTargetFlags |= TargetOption_UseFOVPerception;
|
|
iTargetFlags &= ~TargetOption_doDirectionalTest;
|
|
}
|
|
|
|
if(ped.GetPedResetFlag(CPED_RESET_FLAG_IgnoreTargetsCoverForLOS))
|
|
{
|
|
iTargetFlags |= TargetOption_IgnoreTargetsCover;
|
|
}
|
|
|
|
if (ms_bFixCheckLoSForSoundEvents)
|
|
{
|
|
// If we have a pending scan request for this ped, check the flags
|
|
const CScanRequest* pScanRequest = GetPedScanRequest(otherPed);
|
|
if (pScanRequest)
|
|
{
|
|
iTargetFlags |= pScanRequest->GetAddTargetFlags();
|
|
iTargetFlags &= ~pScanRequest->GetRemoveTargetFlags();
|
|
uCacheTimeOut = 0; // Force the test
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If this ped can hear sound events, check to see if we're within sound range of the other ped.
|
|
// Unless CPED_CONFIG_FLAG_CheckLoSForSoundEvents is set, we can skip doing FOV perception if we're within sound range. [5/14/2013 mdawe]
|
|
if ( (iTargetFlags & TargetOption_UseFOVPerception) &&
|
|
ped.GetPedConfigFlag(CPED_CONFIG_FLAG_ListensToSoundEvents) &&
|
|
!ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanInvestigate) &&
|
|
!ped.GetPedConfigFlag(CPED_CONFIG_FLAG_CheckLoSForSoundEvents) )
|
|
{
|
|
CPlayerInfo *pPlayerInfo = otherPed.GetPlayerInfo();
|
|
if (pPlayerInfo)
|
|
{
|
|
ScalarV vPlayerSoundRangeSq = ScalarVFromF32(rage::square(pPlayerInfo->GetStealthNoise()));
|
|
ScalarV vDistToTargetSq = DistSquared(ped.GetTransform().GetPosition(), otherPed.GetTransform().GetPosition());
|
|
if (IsLessThanAll(vDistToTargetSq, vPlayerSoundRangeSq))
|
|
{
|
|
// Script can set a distance beyond which sounds should not be heard.
|
|
if (ped.GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat(kAttribFloatMaxDistanceToHearEvents) >= 0)
|
|
{
|
|
ScalarV vMaxDistanceSq = ScalarVFromF32(rage::square(ped.GetPedIntelligence()->GetCombatBehaviour().GetCombatFloat(kAttribFloatMaxDistanceToHearEvents)));
|
|
if (IsLessThanAll(vDistToTargetSq, vMaxDistanceSq))
|
|
{
|
|
iTargetFlags &= ~TargetOption_UseFOVPerception;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iTargetFlags &= ~TargetOption_UseFOVPerception;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// IF the peds targeting system is active and scanning this target, use its LOS
|
|
bool bScanTarget = true;
|
|
if( pPedTargetting )
|
|
{
|
|
CSingleTargetInfo* pTargetInfo = pPedTargetting->FindTarget(&otherPed);
|
|
if( pTargetInfo )
|
|
{
|
|
LosStatus losStatus = pTargetInfo->GetLosStatus();
|
|
if( losStatus == Los_clear || losStatus == Los_blockedByFriendly)
|
|
{
|
|
//SP - Allow as before - keep the same.
|
|
//MP - Might have target but might no longer be valid - check to see if the last seen timer is valid before using
|
|
if (!NetworkInterface::IsGameInProgress() || !pTargetInfo->HasLastSeenTimerExpired())
|
|
{
|
|
BANK_ONLY(wantedDebugf3("CPedAcquaintanceScanner::CanPedSeePed--losStatus[%d] ( losStatus == Los_clear || losStatus == Los_blockedByFriendly) note: GetTimeUntilLastSeenTimerExpiresMs[%u]",losStatus,pTargetInfo->GetTimeUntilLastSeenTimerExpiresMs());)
|
|
returnValue = ECanTarget;
|
|
bScanTarget = false;
|
|
}
|
|
}
|
|
else if( losStatus == Los_blocked )
|
|
{
|
|
returnValue = ECanNotTarget;
|
|
bScanTarget = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bScanTarget )
|
|
{
|
|
if(ms_bDoPedAcquaintanceScannerLosAsync)
|
|
{
|
|
// The following call will check if there is a valid entry for these two peds in the targeting cache and
|
|
// either return that result or, if there is no valid cache entry, try and do an asynchronous probe if there
|
|
// is free space to store the result within the cache. If there is no space in the cache, a synchronous probe
|
|
// will be done instead.
|
|
ECanTargetResult eLosRet = CPedGeometryAnalyser::CanPedTargetPedAsync(ped,otherPed,iTargetFlags,false,CPedGeometryAnalyser::ms_fCanTargetCacheMaxPosChangeSqr, uCacheTimeOut);
|
|
|
|
bool bAsyncTimedOut = false;
|
|
|
|
// If we still have a valid result for the target query in the cache, just use this.
|
|
if(eLosRet==ECanTarget || eLosRet==ECanNotTarget)
|
|
{
|
|
BANK_ONLY(wantedDebugf3("CPedAcquaintanceScanner::CanPedSeePed--(eLosRet==ECanTarget[%d] || eLosRet==ECanNotTarget[%d])",eLosRet==ECanTarget,eLosRet==ECanNotTarget);)
|
|
returnValue = eLosRet;
|
|
}
|
|
else if(eLosRet==ENotSureYet)
|
|
{
|
|
// There is an asynchronous probe in flight for this query. Check if we can wait for it.
|
|
|
|
static const u32 knTimeToWaitOnAsyncTargetProbes = 500; // Time in milliseconds.
|
|
if(fwTimer::GetTimeInMilliseconds() - CPedGeometryAnalyser::m_nAsyncProbeTimeout > knTimeToWaitOnAsyncTargetProbes)
|
|
{
|
|
// We have waited too long for the async probe between these two peds to complete. We are now going
|
|
// to cancel it and do a synchronous probe later in CanPedTargetPed().
|
|
CPedGeometryAnalyser::CancelAsyncProbe(&ped, &otherPed);
|
|
CPedGeometryAnalyser::ClearCachedResult(&ped, &otherPed);
|
|
|
|
bAsyncTimedOut = true;
|
|
|
|
// Signal the scanner that we can move on to another random ped.
|
|
returnValue = EReadyForNewPedPair;
|
|
}
|
|
else
|
|
{
|
|
// We are ok to continue waiting on this async request.
|
|
returnValue = ENotSureYet;
|
|
}
|
|
}
|
|
|
|
if(bAsyncTimedOut)
|
|
{
|
|
// If we get here, we have to do a synchronous test.
|
|
#if !__FINAL
|
|
CPedGeometryAnalyser::ms_iNumLineTestsNotProcessedInTime++;
|
|
CPedGeometryAnalyser::ms_iNumLineTestsNotAsync++;
|
|
#endif
|
|
// If we still don't have a result at this point, we're going to have to perform a synchronous probe.
|
|
// (This call is forced into synchronous mode by the default NULL value for pEmptyCacheEntry.)
|
|
BANK_ONLY(wantedDebugf3("CPedAcquaintanceScanner::CanPedSeePed--bAsyncTimedOut--invoke CPedGeometryAnalyser::CanPedTargetPed");)
|
|
returnValue = CPedGeometryAnalyser::CanPedTargetPed(ped,otherPed,iTargetFlags) ? ECanTarget : ECanNotTarget;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BANK_ONLY(wantedDebugf3("CPedAcquaintanceScanner::CanPedSeePed--else--invoke CPedGeometryAnalyser::CanPedTargetPed");)
|
|
returnValue = CPedGeometryAnalyser::CanPedTargetPed(ped,otherPed,iTargetFlags) ? ECanTarget : ECanNotTarget;
|
|
}
|
|
}
|
|
|
|
|
|
// Field of view perception, adjust the distances to take into account the peds alertness this time
|
|
// If this fails, mark the target as spotted
|
|
// if( bCanPedTargetPed && iTargetFlags & TargetOption_UseFOVPerception )
|
|
// {
|
|
// if( !ped.GetPedIntelligence()->GetPedPerception().ComputeFOVVisibility(&otherPed, &otherPed.GetPosition(), CPedPerception::FOV_AdjustPerceptionForAlertness|CPedPerception::FOV_AdjustPerceptionForLighting) )
|
|
// {
|
|
// otherPed.GetPedIntelligence()->GetPedStealth().SpottedThisFrame();
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
#if DEBUG_DRAW
|
|
if( CPedTargetting::DebugAcquaintanceScanner )
|
|
{
|
|
Color32 colour = (returnValue==ECanTarget) ? Color_green : Color_red;
|
|
grcDebugDraw::Line( VEC3V_TO_VECTOR3(ped.GetTransform().GetPosition()) + Vector3(0.0f, 0.0f, 1.0f), VEC3V_TO_VECTOR3(otherPed.GetTransform().GetPosition()), colour);
|
|
}
|
|
#endif
|
|
|
|
if(returnValue == ECanTarget)
|
|
{
|
|
// If cops are hunting player in an unknown vehicle, the range is much tighter
|
|
if( ped.PopTypeIsRandom() && ped.IsLawEnforcementPed() &&
|
|
CPedGeometryAnalyser::IsPedInUnknownCar(otherPed) && !CPedGeometryAnalyser::CanPedSeePedInUnknownCar( ped, otherPed ))
|
|
{
|
|
returnValue = ECanNotTarget;
|
|
}
|
|
|
|
// If peds are hunting player in a bush, the range is much tighter
|
|
if( !ped.PopTypeIsMission() && (iTargetFlags&TargetOption_UseFOVPerception) &&
|
|
CPedGeometryAnalyser::IsPedInBush(otherPed) && !CPedGeometryAnalyser::CanPedSeePedInBush(ped, otherPed) )
|
|
{
|
|
returnValue = ECanNotTarget;
|
|
}
|
|
|
|
if( !ped.GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanSeeUnderwaterPeds) &&
|
|
CPedGeometryAnalyser::IsPedInWaterAtDepth(otherPed, CPedGeometryAnalyser::ms_MaxPedWaterDepthForVisibility) )
|
|
{
|
|
returnValue = ECanNotTarget;
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
if (returnValue == ECanTarget)
|
|
{
|
|
wantedDebugf3("CPedAcquaintanceScanner::CanPedSeePed ped[%s] otherPed[%s]",ped.GetDebugName(),otherPed.GetDebugName());
|
|
}
|
|
#endif
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Scan Requests
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void CPedAcquaintanceScanner::RegisterScanRequest(CPed& rPed, int iAddTargetFlags, int iRemoveTargetFlags)
|
|
{
|
|
const CScanRequest* pPedScanRequest = GetPedScanRequest(rPed);
|
|
if (pPedScanRequest)
|
|
{
|
|
if (iAddTargetFlags != pPedScanRequest->GetAddTargetFlags() || iRemoveTargetFlags != pPedScanRequest->GetRemoveTargetFlags())
|
|
{
|
|
aiWarningf("PedAcquaintanceScanner already had a scan request for this ped and the new one was discarded. The new request had different flags though.");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (aiVerifyf(!IsScanRequestQueueFull(), "Not enough scan requests!"))
|
|
{
|
|
s32 iFreeRequestIdx = FindFreeScanRequestIdx();
|
|
if (aiVerify(iFreeRequestIdx >= 0))
|
|
{
|
|
m_aScanRequests[iFreeRequestIdx].Set(rPed, iAddTargetFlags, iRemoveTargetFlags);
|
|
++m_uNumScanRequests;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void CPedAcquaintanceScanner::ClearAllScanRequests()
|
|
{
|
|
if (m_uNumScanRequests == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int iRequestIdx = 0; iRequestIdx < uMAX_SCAN_REQUESTS; ++iRequestIdx)
|
|
{
|
|
m_aScanRequests[iRequestIdx].Clear();
|
|
}
|
|
|
|
m_uNumScanRequests = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void CPedAcquaintanceScanner::ClearScanRequest(int iIdx)
|
|
{
|
|
CScanRequest& rRequest = m_aScanRequests[iIdx];
|
|
if (aiVerify(rRequest.IsValid()))
|
|
{
|
|
rRequest.Clear();
|
|
|
|
aiAssert(m_uNumScanRequests > 0);
|
|
--m_uNumScanRequests;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int CPedAcquaintanceScanner::FindFreeScanRequestIdx() const
|
|
{
|
|
for (int iRequestIdx = 0; iRequestIdx < uMAX_SCAN_REQUESTS; ++iRequestIdx)
|
|
{
|
|
if (!m_aScanRequests[iRequestIdx].IsValid())
|
|
{
|
|
return iRequestIdx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int CPedAcquaintanceScanner::FindPedScanRequestIdx(const CPed& rPed) const
|
|
{
|
|
for (int iRequestIdx = 0; iRequestIdx < uMAX_SCAN_REQUESTS; ++iRequestIdx)
|
|
{
|
|
if (m_aScanRequests[iRequestIdx].GetPed() == &rPed)
|
|
{
|
|
return iRequestIdx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#if __BANK
|
|
void CPedAcquaintanceScanner::AddWidgets(rage::bkBank& bank)
|
|
{
|
|
bank.PushGroup("Acquaintance Scanner");
|
|
bank.AddToggle("Fix CheckLosForSoundEvents (ScanRequests) (B*2105791)", &CPedAcquaintanceScanner::ms_bFixCheckLoSForSoundEvents);
|
|
bank.PopGroup(); // "Acquaintance Scanner"
|
|
}
|
|
#endif // __BANK
|
|
|
|
|
|
////////////////////////////
|
|
//PED AGITATION SCANNER
|
|
////////////////////////////
|
|
|
|
CPedAgitationScanner::CPedAgitationScanner()
|
|
: CExpensiveProcess(PPT_AgitationScanner)
|
|
, m_aTriggers()
|
|
, m_bBumped(false)
|
|
, m_bBumpedByVehicle(false)
|
|
, m_bDodged(false)
|
|
, m_bDodgedVehicle(false)
|
|
, m_bAmbientFriendBumped(false)
|
|
, m_bAmbientFriendBumpedByVehicle(false)
|
|
{
|
|
//Register a slot for the expensive process.
|
|
RegisterSlot();
|
|
}
|
|
|
|
CPedAgitationScanner::~CPedAgitationScanner()
|
|
{
|
|
//Unregister the slot for the expensive process.
|
|
UnregisterSlot();
|
|
|
|
//Deactivate the triggers.
|
|
DeactivateTriggers();
|
|
}
|
|
|
|
void CPedAgitationScanner::Scan(CPed& rPed, CEntityScannerIterator& UNUSED_PARAM(entityList))
|
|
{
|
|
//No need to scan on the local player.
|
|
if(rPed.IsLocalPlayer())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Grab the player.
|
|
CPed* pPlayer = CGameWorld::FindLocalPlayer();
|
|
if(!pPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Update the flags.
|
|
UpdateFlags(rPed, *pPlayer);
|
|
|
|
//Update the triggers.
|
|
UpdateTriggers(rPed, *pPlayer);
|
|
|
|
#if __BANK
|
|
RenderDebug(rPed);
|
|
#endif
|
|
}
|
|
|
|
void CPedAgitationScanner::ActivateTrigger(const CPed& rPed, const CPed& UNUSED_PARAM(rPlayer), const CAgitatedTrigger& rTrigger)
|
|
{
|
|
//Find an inactive trigger.
|
|
int iIndex = FindFirstInactiveTrigger();
|
|
if(!aiVerify(iIndex >= 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the reaction is valid.
|
|
atHashWithStringNotFinal hReaction = rTrigger.GetReaction();
|
|
CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(hReaction);
|
|
if(!aiVerifyf(pReaction, "The reaction: %s is invalid.", hReaction.GetCStr()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Add a reaction.
|
|
pReaction->AddReaction();
|
|
|
|
//Grab the trigger.
|
|
Trigger& rNewTrigger = m_aTriggers[iIndex];
|
|
|
|
//Set the reaction.
|
|
rNewTrigger.m_hReaction = hReaction;
|
|
|
|
//Set the state.
|
|
rNewTrigger.m_nState = Trigger::Start;
|
|
|
|
//Set the chances.
|
|
rNewTrigger.m_fChances = rTrigger.GetChances();
|
|
|
|
//Set the distance.
|
|
rNewTrigger.m_fDistance = GetDistance(rPed, rTrigger, *pReaction);
|
|
|
|
//Set the original position.
|
|
rNewTrigger.SetOriginalPosition(rPed.GetTransform().GetPosition());
|
|
|
|
//Set the time in state.
|
|
rNewTrigger.m_fTimeInState = 0.0f;
|
|
|
|
//Clear the timers.
|
|
rNewTrigger.m_fTimeBeforeReaction = 0.0f;
|
|
rNewTrigger.m_fTimeBeforeFinish = 0.0f;
|
|
|
|
//Clear the flags.
|
|
rNewTrigger.m_uFlags.ClearAllFlags();
|
|
}
|
|
|
|
void CPedAgitationScanner::ActivateTriggers(CPed& rPed, CPed& rPlayer)
|
|
{
|
|
//Ensure we can check the triggers.
|
|
if(!CanActivateTriggers(rPed, rPlayer))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Load the reactions.
|
|
static const int s_iMaxReactions = 8;
|
|
atHashWithStringNotFinal aReactions[s_iMaxReactions];
|
|
int iNumReactions = CAgitatedManager::GetInstance().GetTriggers().LoadReactions(aReactions, s_iMaxReactions);
|
|
|
|
//Iterate over the reactions.
|
|
for(int i = 0; i < iNumReactions; ++i)
|
|
{
|
|
//Ensure the trigger is valid.
|
|
const CAgitatedTrigger* pTrigger = CAgitatedManager::GetInstance().GetTriggers().GetTrigger(rPed, aReactions[i]);
|
|
if(!pTrigger)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Ensure the trigger can be activated.
|
|
if(!CanActivateTrigger(rPed, rPlayer, *pTrigger))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Activate the trigger.
|
|
ActivateTrigger(rPed, rPlayer, *pTrigger);
|
|
}
|
|
}
|
|
|
|
bool CPedAgitationScanner::AreExpensiveFlagsValid(const CPed& rPed, const CPed& rPlayer, const CAgitatedTriggerReaction& rReaction, bool bIsActive) const
|
|
{
|
|
//Check if a line of sight is needed.
|
|
bool bNoLineOfSightNeeded = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::NoLineOfSightNeeded);
|
|
bool bLineOfSightNeeded = !bNoLineOfSightNeeded;
|
|
if(bLineOfSightNeeded)
|
|
{
|
|
//Ensure the ped can see the player.
|
|
s32 iTargetFlags = TargetOption_UseFOVPerception;
|
|
if(!bIsActive)
|
|
{
|
|
if(!CPedGeometryAnalyser::CanPedTargetPed(rPed, rPlayer, iTargetFlags))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(CPedGeometryAnalyser::CanPedTargetPedAsync(const_cast<CPed &>(rPed), const_cast<CPed &>(rPlayer), iTargetFlags) == ECanNotTarget)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::AreFlagsValid(const CPed& rPed, const CPed& rPlayer, const CAgitatedTriggerReaction& rReaction, bool UNUSED_PARAM(bIsActive)) const
|
|
{
|
|
//Check if we should be disabled when agitated.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::DisableWhenAgitated))
|
|
{
|
|
//Ensure the ped is not agitated,
|
|
if(rPed.GetPedConfigFlag(CPED_CONFIG_FLAG_IsAgitated))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if the trigger reaction should be disabled if a friendly is agitated.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::DisableWhenFriendlyIsAgitated))
|
|
{
|
|
//Ensure a friendly is not agitated.
|
|
if(IsFriendlyAgitated(rPed, rPlayer))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we must be using a scenario.
|
|
bool bMustBeUsingScenario = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeUsingScenario);
|
|
bool bMustBeUsingScenarioBeforeInitialReaction = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeUsingScenarioBeforeInitialReaction);
|
|
bool bMustBeUsingATerritorialScenario = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeUsingATerritorialScenario);
|
|
bMustBeUsingScenario = (bMustBeUsingScenario || (bMustBeUsingScenarioBeforeInitialReaction && IsBeforeInitialReaction(rReaction)));
|
|
if(bMustBeUsingScenario)
|
|
{
|
|
//Ensure we are using a scenario.
|
|
if(!(rPed.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_USE_SCENARIO, true) ||
|
|
rPed.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_WANDERING_SCENARIO, true) ||
|
|
rPed.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_USE_VEHICLE_SCENARIO, true)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if the ped must be using a scenario point marked as "Territorial" to be a valid trigger.
|
|
if (bMustBeUsingATerritorialScenario)
|
|
{
|
|
//Grab the point off the ped.
|
|
CTask* pActiveTask = rPed.GetPedIntelligence()->GetTaskActive();
|
|
|
|
if (!pActiveTask)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CScenarioPoint* pPoint = pActiveTask->GetScenarioPoint();
|
|
|
|
//Check the flag.
|
|
if (!pPoint || !pPoint->IsFlagSet(CScenarioPointFlags::TerritorialScenario))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we must be wandering.
|
|
bool bMustBeWandering = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeWandering);
|
|
bool bMustBeWanderingBeforeInitialReaction = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeWanderingBeforeInitialReaction);
|
|
bMustBeWandering = (bMustBeWandering || (bMustBeWanderingBeforeInitialReaction && IsBeforeInitialReaction(rReaction)));
|
|
if(bMustBeWandering)
|
|
{
|
|
//Ensure we are wandering.
|
|
if(!rPed.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_WANDER, true))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we should be disabled when wandering.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::DisableWhenWandering))
|
|
{
|
|
//Ensure we are not wandering.
|
|
if(rPed.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_WANDER, true))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we should be disabled when still.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::DisableWhenStill))
|
|
{
|
|
//Ensure the ped is not still.
|
|
if(rPed.GetMotionData()->GetIsStill())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we should be disabled if neither ped is on foot.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::DisableIfNeitherPedIsOnFoot))
|
|
{
|
|
//Ensure one of the peds are on foot.
|
|
if(!rPed.GetIsOnFoot() && !rPlayer.GetIsOnFoot())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we must be on foot.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeOnFoot))
|
|
{
|
|
//Ensure we are on foot.
|
|
if(!rPed.GetIsOnFoot())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if the target must be on foot.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::TargetMustBeOnFoot))
|
|
{
|
|
//Ensure the target is on foot.
|
|
if(!rPlayer.GetIsOnFoot())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we must be in a vehicle.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeInVehicle))
|
|
{
|
|
//Ensure we are in a vehicle.
|
|
if(!rPed.GetIsInVehicle())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we must be stationary.
|
|
if(rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::MustBeStationary))
|
|
{
|
|
//Ensure we are stationary.
|
|
float fSpeedSq = rPed.GetVelocity().XYMag2();
|
|
static dev_float s_fMaxSpeed = 0.15f;
|
|
float fMaxSpeedSq = square(s_fMaxSpeed);
|
|
if(fSpeedSq > fMaxSpeedSq)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
float CPedAgitationScanner::CalculateDistanceSqBetweenPedAndVehicle(const CPed& rPed, const CVehicle& rVehicle) const
|
|
{
|
|
//Transform the ped's position into the local space of the vehicle.
|
|
Vec3V vLocalPedPosition = rVehicle.GetTransform().UnTransform(rPed.GetTransform().GetPosition());
|
|
|
|
return rVehicle.GetBaseModelInfo()->GetBoundingBox().DistanceToPointSquared(vLocalPedPosition).Getf();
|
|
}
|
|
|
|
float CPedAgitationScanner::CalculateDistanceSqBetweenPeds(const CPed& rPed, const CPed& rPlayer) const
|
|
{
|
|
//Transform the player's position into the local space of the ped.
|
|
Vec3V vLocalPlayerPosition = rPed.GetTransform().UnTransform(rPlayer.GetTransform().GetPosition());
|
|
|
|
return (rPed.GetBaseModelInfo()->GetBoundingBox().DistanceToPointSquared(vLocalPlayerPosition).Getf());
|
|
}
|
|
|
|
bool CPedAgitationScanner::CanActivateTrigger(const CPed& rPed, const CPed& rPlayer, const CAgitatedTrigger& rTrigger) const
|
|
{
|
|
//Ensure we have an inactive trigger.
|
|
if(!HasInactiveTrigger())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the trigger is not active.
|
|
if(IsTriggerActive(rTrigger))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the ped type is valid.
|
|
if(!IsPedTypeValid(rPlayer, rTrigger))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the reaction is valid.
|
|
CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(rTrigger.GetReaction());
|
|
if(!pReaction)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure we have not reached the max reactions.
|
|
if(pReaction->HasReachedMaxReactions())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the flags are valid.
|
|
if(!AreFlagsValid(rPed, rPlayer, *pReaction, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the distance is valid.
|
|
if(!IsDistanceValid(rPed, rPlayer, rTrigger, *pReaction))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the expensive flags are valid.
|
|
if(!AreExpensiveFlagsValid(rPed, rPlayer, *pReaction, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the dot is valid.
|
|
if(!IsDotValid(rPed, rPlayer, pReaction->GetMinDotToTarget(), pReaction->GetMaxDotToTarget()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the target speed is valid.
|
|
if(!IsSpeedValid(rPlayer, pReaction->GetMinTargetSpeed(), pReaction->GetMaxTargetSpeed()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the type is valid.
|
|
if(!IsTypeValid(rPed, pReaction->GetType()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::CanActivateTriggers(const CPed& rPed, const CPed& rPlayer)
|
|
{
|
|
//Check if we should not ignore time slicing.
|
|
if(!ShouldIgnoreTimeSlicing(rPed, rPlayer))
|
|
{
|
|
//Ensure the processing should happen this frame.
|
|
if(!ShouldBeProcessedThisFrame())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Ensure the ped is ambient.
|
|
if(!rPed.PopTypeIsRandom())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure that either the player is not wanted or the scanning ped is an animal.
|
|
if(rPlayer.GetPlayerWanted()->GetWantedLevel() > WANTED_CLEAN && rPed.GetPedType() != PEDTYPE_ANIMAL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the ped is not friendly with the player.
|
|
if(rPed.GetPedIntelligence()->IsFriendlyWith(rPlayer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the ped does not ignore the player.
|
|
if(rPed.GetPedIntelligence()->Ignores(rPlayer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the player info is valid.
|
|
CPlayerInfo* pPlayerInfo = rPlayer.GetPlayerInfo();
|
|
if(!pPlayerInfo)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the player's controls are enabled.
|
|
if(pPlayerInfo->AreControlsDisabled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure a cutscene is not running.
|
|
if(CutSceneManager::GetInstance() && CutSceneManager::GetInstance()->IsRunning())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure we have an inactive trigger.
|
|
if(!HasInactiveTrigger())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::CanUpdateTriggers(const CPed& rPed, const CPed& rPlayer) const
|
|
{
|
|
//Ensure the ped is not dead.
|
|
if(rPed.IsInjured())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the player is not dead.
|
|
if(rPlayer.IsInjured())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the ped has not disabled agitation.
|
|
if(rPed.GetPedResetFlag(CPED_RESET_FLAG_DisableAgitation))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the ped has not disabled agitation triggers.
|
|
if(rPed.GetPedResetFlag(CPED_RESET_FLAG_DisableAgitationTriggers))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the player has not disabled agitation.
|
|
if(rPlayer.GetPedResetFlag(CPED_RESET_FLAG_DisableAgitation))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the player has not disabled agitation triggers.
|
|
if(rPlayer.GetPedResetFlag(CPED_RESET_FLAG_DisableAgitationTriggers))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the player has not disabled agitation.
|
|
if(rPlayer.GetPlayerInfo() && rPlayer.GetPlayerInfo()->GetDisableAgitation())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the ped should not flee the player.
|
|
if(ShouldPedFleePlayer(rPed, rPlayer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CPedAgitationScanner::ClearAgitation(CPed& rPed, ePedConfigFlags nFlag, bool& bStorage)
|
|
{
|
|
//Clear the flag.
|
|
rPed.SetPedConfigFlag(nFlag, false);
|
|
|
|
//Clear the storage.
|
|
bStorage = false;
|
|
}
|
|
|
|
void CPedAgitationScanner::DeactivateTrigger(Trigger& rTrigger)
|
|
{
|
|
//Ensure the trigger is active.
|
|
if(!aiVerify(rTrigger.IsActive()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the reaction is valid.
|
|
CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(rTrigger.m_hReaction);
|
|
if(!pReaction)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Remove a reaction.
|
|
pReaction->RemoveReaction();
|
|
|
|
//Clear the reaction.
|
|
rTrigger.m_hReaction.Clear();
|
|
|
|
//Clear the state.
|
|
rTrigger.m_nState = Trigger::None;
|
|
|
|
//Clear the chances.
|
|
rTrigger.m_fChances = 0.0f;
|
|
|
|
//Clear the distance.
|
|
rTrigger.m_fDistance = 0.0f;
|
|
|
|
//Clear the original position.
|
|
rTrigger.SetOriginalPosition(Vec3V(V_ZERO));
|
|
|
|
//Clear the time in state.
|
|
rTrigger.m_fTimeInState = 0.0f;
|
|
|
|
//Clear the timers.
|
|
rTrigger.m_fTimeBeforeReaction = 0.0f;
|
|
rTrigger.m_fTimeBeforeFinish = 0.0f;
|
|
|
|
//Clear the flags.
|
|
rTrigger.m_uFlags.ClearAllFlags();
|
|
}
|
|
|
|
void CPedAgitationScanner::DeactivateTriggers()
|
|
{
|
|
//Iterate over the triggers.
|
|
int iCount = m_aTriggers.GetMaxCount();
|
|
for(int i = 0; i < iCount; ++i)
|
|
{
|
|
//Ensure the trigger is active.
|
|
Trigger& rTrigger = m_aTriggers[i];
|
|
if(!rTrigger.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Deactivate the trigger.
|
|
DeactivateTrigger(rTrigger);
|
|
}
|
|
}
|
|
|
|
int CPedAgitationScanner::FindFirstActiveTriggerWithType(AgitatedType nType) const
|
|
{
|
|
//Iterate over the triggers.
|
|
int iCount = m_aTriggers.GetMaxCount();
|
|
for(int i = 0; i < iCount; ++i)
|
|
{
|
|
//Ensure the trigger is active.
|
|
const Trigger& rTrigger = m_aTriggers[i];
|
|
if(!rTrigger.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Ensure the reaction is valid.
|
|
const CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(rTrigger.m_hReaction);
|
|
if(!pReaction)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Ensure the type matches.
|
|
if(nType != pReaction->GetType())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int CPedAgitationScanner::FindFirstInactiveTrigger() const
|
|
{
|
|
//Iterate over the triggers.
|
|
int iCount = m_aTriggers.GetMaxCount();
|
|
for(int i = 0; i < iCount; ++i)
|
|
{
|
|
//Ensure the trigger is inactive.
|
|
if(m_aTriggers[i].IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
float CPedAgitationScanner::GetDistance(const CPed& rPed, const CAgitatedTrigger& rTrigger, const CAgitatedTriggerReaction& rReaction) const
|
|
{
|
|
//Grab the distance.
|
|
float fDistance = rTrigger.GetDistance();
|
|
|
|
//Check if we should use the distance from the scenario.
|
|
bool bUseDistanceFromScenario = rReaction.GetFlags().IsFlagSet(CAgitatedTriggerReaction::UseDistanceFromScenario);
|
|
if(bUseDistanceFromScenario)
|
|
{
|
|
//Check if the scenario point is valid.
|
|
const CScenarioPoint* pScenarioPoint = rPed.GetScenarioPoint(rPed, true);
|
|
if(pScenarioPoint)
|
|
{
|
|
//Set the distance.
|
|
fDistance = pScenarioPoint->GetRadius();
|
|
}
|
|
}
|
|
|
|
return fDistance;
|
|
}
|
|
|
|
bool CPedAgitationScanner::HasInactiveTrigger() const
|
|
{
|
|
//Ensure we have an inactive trigger.
|
|
int iIndex = FindFirstInactiveTrigger();
|
|
if(iIndex < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsBeforeInitialReaction(const CAgitatedTriggerReaction& rReaction) const
|
|
{
|
|
//Check if there is no trigger active for the type.
|
|
int iIndex = FindFirstActiveTriggerWithType(rReaction.GetType());
|
|
if(iIndex < 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//Check if the trigger has not reacted.
|
|
if(!m_aTriggers[iIndex].m_uFlags.IsFlagSet(Trigger::HasReacted))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsDistanceValid(const CPed& rPed, const CPed& rPlayer, const CAgitatedTrigger& rTrigger, const CAgitatedTriggerReaction& rReaction) const
|
|
{
|
|
//Get the distance.
|
|
float fDistance = GetDistance(rPed, rTrigger, rReaction);
|
|
|
|
return IsDistanceValid(rPed, rPlayer, fDistance);
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsDistanceValid(const CPed& rPed, const CPed& rPlayer, float fDistance) const
|
|
{
|
|
//Ensure the distance is valid.
|
|
if(fDistance <= 0.0f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Comparing against distance squared.
|
|
fDistance *= fDistance;
|
|
|
|
//Calculate the distance between the peds.
|
|
float fDistanceBetweenPeds = FLT_MAX;
|
|
const CVehicle* pPedVehicle = rPed.GetVehiclePedInside();
|
|
const CVehicle* pPlayerVehicle = rPlayer.GetVehiclePedInside();
|
|
if(pPedVehicle && !pPlayerVehicle)
|
|
{
|
|
fDistanceBetweenPeds = CalculateDistanceSqBetweenPedAndVehicle(rPlayer, *pPedVehicle);
|
|
}
|
|
else if(!pPedVehicle && pPlayerVehicle)
|
|
{
|
|
fDistanceBetweenPeds = CalculateDistanceSqBetweenPedAndVehicle(rPed, *pPlayerVehicle);
|
|
}
|
|
else
|
|
{
|
|
fDistanceBetweenPeds = CalculateDistanceSqBetweenPeds(rPed, rPlayer);
|
|
}
|
|
|
|
return (fDistanceBetweenPeds <= fDistance);
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsDotValid(const CPed& rPed, const CPed& rPlayer, float fMin, float fMax) const
|
|
{
|
|
//Check if we care about the dot to the target.
|
|
float fMinDotToTarget = fMin;
|
|
float fMaxDotToTarget = fMax;
|
|
bool bHasMinDotToTarget = (fMinDotToTarget > -1.0f);
|
|
bool bHasMaxDotToTarget = (fMaxDotToTarget < 1.0f);
|
|
if(bHasMinDotToTarget || bHasMaxDotToTarget)
|
|
{
|
|
//Grab the ped forward.
|
|
Vec3V vPedForward = rPed.GetTransform().GetForward();
|
|
|
|
//Grab the direction from the ped to the target.
|
|
Vec3V vPedToTarget = Subtract(rPlayer.GetTransform().GetPosition(), rPed.GetTransform().GetPosition());
|
|
// Vec3V vPedToTargetDirection = NormalizeFastSafe(vPedToTarget, vPedForward);
|
|
|
|
//Calculate the dot to the target.
|
|
ScalarV scDotToTarget = Dot(vPedForward, vPedToTarget);
|
|
scDotToTarget = Clamp(scDotToTarget, ScalarV(V_NEGONE), ScalarV(V_ONE));
|
|
|
|
//Check if we have a min dot to the target.
|
|
if(bHasMinDotToTarget)
|
|
{
|
|
//Ensure the dot is valid.
|
|
if(IsLessThanAll(scDotToTarget, ScalarVFromF32(fMinDotToTarget)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we have a max dot to the target.
|
|
if(bHasMaxDotToTarget)
|
|
{
|
|
//Ensure the dot is valid.
|
|
if(IsGreaterThanAll(scDotToTarget, ScalarVFromF32(fMaxDotToTarget)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsFriendlyAgitated(const CPed& rPed, const CPed& UNUSED_PARAM(rPlayer)) const
|
|
{
|
|
//Scan the nearby peds.
|
|
const CEntityScannerIterator entityList = rPed.GetPedIntelligence()->GetNearbyPeds();
|
|
for(const CEntity* pOtherEntity = entityList.GetFirst(); pOtherEntity; pOtherEntity = entityList.GetNext())
|
|
{
|
|
//Grab the other ped.
|
|
const CPed* pOtherPed = static_cast<const CPed *>(pOtherEntity);
|
|
|
|
//Ensure the other ped is not the ped.
|
|
if(pOtherPed == &rPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Ensure the peds are friendly.
|
|
if(!rPed.GetPedIntelligence()->IsFriendlyWith(*pOtherPed))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Ensure the other ped is agitated.
|
|
const CTaskAgitated* pTask = CTaskAgitated::FindAgitatedTaskForPed(*pOtherPed);
|
|
if(!pTask)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Ensure the ped is not just reacting to our own agitation.
|
|
if(&rPed == pTask->GetLeader())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsPedTypeValid(const CPed& rPlayer, const CAgitatedTrigger& rTrigger) const
|
|
{
|
|
//Ensure the ped type matches.
|
|
if(!rTrigger.HasPedType(rPlayer.GetPedType()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsSpeedValid(const CPed& rPed, float fMin, float fMax) const
|
|
{
|
|
//Check if we care about the speed.
|
|
bool bHasMin = (fMin > 0.0f);
|
|
bool bHasMax = (fMax > 0.0f);
|
|
if(bHasMin || bHasMax)
|
|
{
|
|
//Grab the speed.
|
|
float fSpeedSq = rPed.GetVelocity().XYMag2();
|
|
|
|
//Check if we have a min speed.
|
|
if(bHasMin)
|
|
{
|
|
//Ensure the speed is valid.
|
|
if(fSpeedSq < square(fMin))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Check if we have a max speed.
|
|
if(bHasMax)
|
|
{
|
|
//Ensure the speed is valid.
|
|
if(fSpeedSq > square(fMax))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsTriggerActive(const CAgitatedTrigger& rTrigger) const
|
|
{
|
|
//Ensure the reaction is valid.
|
|
const CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(rTrigger.GetReaction());
|
|
if(!pReaction)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the trigger is valid.
|
|
int iIndex = FindFirstActiveTriggerWithType(pReaction->GetType());
|
|
if(iIndex < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsTriggerStillValid(const CPed& rPed, const CPed& rPlayer, const Trigger& rTrigger) const
|
|
{
|
|
//Assert that the trigger is active.
|
|
aiAssert(rTrigger.IsActive());
|
|
|
|
//Ensure the reaction is valid.
|
|
const CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(rTrigger.m_hReaction);
|
|
if(!pReaction)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the flags are valid.
|
|
if(!AreFlagsValid(rPed, rPlayer, *pReaction, true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the distance is valid.
|
|
if(!IsDistanceValid(rPed, rPlayer, rTrigger.m_fDistance))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the expensive flags are valid.
|
|
if(!AreExpensiveFlagsValid(rPed, rPlayer, *pReaction, true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the dot is valid.
|
|
if(!IsDotValid(rPed, rPlayer, pReaction->GetMinDotToTarget(), pReaction->GetMaxDotToTarget()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the target speed is valid.
|
|
if(!IsSpeedValid(rPlayer, pReaction->GetMinTargetSpeed(), pReaction->GetMaxTargetSpeed()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::IsTypeValid(const CPed& rPed, AgitatedType nType) const
|
|
{
|
|
//Check the type.
|
|
switch(nType)
|
|
{
|
|
case AT_Loitering:
|
|
{
|
|
//Ensure the active task is valid.
|
|
CTask* pTask = rPed.GetPedIntelligence()->GetTaskActive();
|
|
if(!pTask)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//Ensure the point is valid.
|
|
const CScenarioPoint* pPoint = pTask->GetScenarioPoint();
|
|
if(!pPoint)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//Check if the point ignores loitering.
|
|
if(pPoint->IsFlagSet(CScenarioPointFlags::IgnoreLoitering))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPedAgitationScanner::ShouldIgnoreTimeSlicing(const CPed& rPed, const CPed& rPlayer) const
|
|
{
|
|
//Check if the distance is within the threshold.
|
|
ScalarV scDistSq = DistSquared(rPed.GetTransform().GetPosition(), rPlayer.GetTransform().GetPosition());
|
|
static dev_float s_fMaxDistance = 3.0f;
|
|
ScalarV scMaxDistSq = ScalarVFromF32(square(s_fMaxDistance));
|
|
if(IsLessThanAll(scDistSq, scMaxDistSq))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CPedAgitationScanner::ShouldPedFleePlayer(const CPed& rPed, const CPed& rPlayer) const
|
|
{
|
|
//Check if the ped is random.
|
|
if(rPed.PopTypeIsRandom())
|
|
{
|
|
//Check if the player has a wanted structure.
|
|
const CWanted* pWanted = rPlayer.GetPlayerWanted();
|
|
if(pWanted)
|
|
{
|
|
//Check if all randoms should flee the player.
|
|
if(pWanted->m_AllRandomsFlee)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//Check if all neutral randoms should flee the player.
|
|
if(pWanted->m_AllNeutralRandomsFlee)
|
|
{
|
|
//Check if the ped and the player have a neutral (or better) relationship.
|
|
if(!rPed.GetPedIntelligence()->IsThreatenedBy(rPlayer))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(pWanted->m_AllRandomsOnlyAttackWithGuns)
|
|
{
|
|
const CPedWeaponManager* pWeaponManager = rPed.GetWeaponManager();
|
|
const CWeaponInfo* pBestWeaponInfo = pWeaponManager ? pWeaponManager->GetBestWeaponInfo() : NULL;
|
|
if(!pBestWeaponInfo || !pBestWeaponInfo->GetIsGun())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CPedAgitationScanner::UpdateAgitation(CPed& ped, CPed& player, ePedConfigFlags nFlag, AgitatedType nType, bool& bStorage) const
|
|
{
|
|
//Check if the flag is set.
|
|
if(ped.GetPedConfigFlag(nFlag))
|
|
{
|
|
//Clear the flag.
|
|
ped.SetPedConfigFlag(nFlag, false);
|
|
|
|
//Set the storage.
|
|
bStorage = true;
|
|
|
|
//Delay a frame (to allow ragdoll, etc to kick in).
|
|
return;
|
|
}
|
|
else if(!bStorage)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the ped is not using a ragdoll.
|
|
if(ped.GetUsingRagdoll())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure we are not falling over.
|
|
if(ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_FALL_OVER))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the current event type is valid.
|
|
const s32 iCurrentEventType = ped.GetPedIntelligence()->GetCurrentEventType();
|
|
switch(iCurrentEventType)
|
|
{
|
|
case EVENT_CLIMB_LADDER_ON_ROUTE:
|
|
case EVENT_CLIMB_NAVMESH_ON_ROUTE:
|
|
case EVENT_GET_OUT_OF_WATER:
|
|
case EVENT_SWITCH_2_NM_TASK:
|
|
{
|
|
return;
|
|
}
|
|
case EVENT_PED_COLLISION_WITH_PED:
|
|
case EVENT_PED_COLLISION_WITH_PLAYER:
|
|
case EVENT_POTENTIAL_BE_WALKED_INTO:
|
|
case EVENT_POTENTIAL_GET_RUN_OVER:
|
|
case EVENT_VEHICLE_COLLISION:
|
|
{
|
|
//Allow the event through when we start to get up (if ever).
|
|
//Make sure get up has been running for a bit of time, since
|
|
//it's sometimes used when we don't actually need to get up.
|
|
CTask* pTask = ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_GET_UP);
|
|
if(pTask && (pTask->GetTimeRunning() > 0.25f))
|
|
{
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Ensure we are not in the water, in the air, or climbing.
|
|
if(ped.GetPedConfigFlag(CPED_CONFIG_FLAG_IsSwimming) ||
|
|
ped.GetIsInWater() || ped.GetWasInWater() ||
|
|
ped.GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir) ||
|
|
ped.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_CLIMB_LADDER, true) ||
|
|
ped.GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Clear the storage.
|
|
bStorage = false;
|
|
|
|
//Add the agitation event.
|
|
CEventAgitated event(&player, nType);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
|
|
void CPedAgitationScanner::UpdateFlags(CPed& rPed, CPed& rPlayer)
|
|
{
|
|
//Ensure the ped is not dead.
|
|
if(rPed.IsInjured())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Ensure the player is not dead.
|
|
if(rPlayer.IsInjured())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//If the player has been bumped by a ped, disregard the fact that they tried to dodge it.
|
|
if(rPed.GetPedConfigFlag(CPED_CONFIG_FLAG_BumpedByPlayer) || m_bBumped)
|
|
{
|
|
ClearAgitation(rPed, CPED_CONFIG_FLAG_DodgedPlayer, m_bDodged);
|
|
}
|
|
|
|
//If the player has been bumped by a vehicle, disregard the fact that they tried to dodge it.
|
|
if(rPed.GetPedConfigFlag(CPED_CONFIG_FLAG_BumpedByPlayerVehicle) || m_bBumpedByVehicle)
|
|
{
|
|
ClearAgitation(rPed, CPED_CONFIG_FLAG_DodgedPlayerVehicle, m_bDodgedVehicle);
|
|
}
|
|
|
|
//If the player is entering a vehicle, ignore bumped & dodged.
|
|
if(rPlayer.GetIsEnteringVehicle())
|
|
{
|
|
ClearAgitation(rPed, CPED_CONFIG_FLAG_BumpedByPlayer, m_bBumped);
|
|
ClearAgitation(rPed, CPED_CONFIG_FLAG_DodgedPlayer, m_bDodged);
|
|
ClearAgitation(rPed, CPED_CONFIG_FLAG_AmbientFriendBumpedByPlayer, m_bAmbientFriendBumped);
|
|
}
|
|
|
|
//Update the agitation.
|
|
UpdateAgitation(rPed, rPlayer, CPED_CONFIG_FLAG_BumpedByPlayer, AT_Bumped, m_bBumped);
|
|
UpdateAgitation(rPed, rPlayer, CPED_CONFIG_FLAG_BumpedByPlayerVehicle, AT_BumpedByVehicle, m_bBumpedByVehicle);
|
|
UpdateAgitation(rPed, rPlayer, CPED_CONFIG_FLAG_DodgedPlayer, AT_Dodged, m_bDodged);
|
|
UpdateAgitation(rPed, rPlayer, CPED_CONFIG_FLAG_DodgedPlayerVehicle, AT_DodgedVehicle, m_bDodgedVehicle);
|
|
UpdateAgitation(rPed, rPlayer, CPED_CONFIG_FLAG_AmbientFriendBumpedByPlayer, AT_Bumped, m_bAmbientFriendBumped);
|
|
UpdateAgitation(rPed, rPlayer, CPED_CONFIG_FLAG_AmbientFriendBumpedByPlayerVehicle, AT_BumpedByVehicle, m_bAmbientFriendBumpedByVehicle);
|
|
}
|
|
|
|
void CPedAgitationScanner::UpdateTrigger(CPed& rPed, CPed& rPlayer, Trigger& rTrigger)
|
|
{
|
|
//Assert that the trigger is active.
|
|
aiAssert(rTrigger.IsActive());
|
|
|
|
//Ensure the reaction is valid.
|
|
const CAgitatedTriggerReaction* pReaction = CAgitatedManager::GetInstance().GetTriggers().GetReaction(rTrigger.m_hReaction);
|
|
if(!pReaction)
|
|
{
|
|
//Deactivate the trigger.
|
|
DeactivateTrigger(rTrigger);
|
|
return;
|
|
}
|
|
|
|
//Keep track of the states.
|
|
Trigger::State nCurrentState = rTrigger.m_nState;
|
|
Trigger::State nNextState = nCurrentState;
|
|
|
|
//Check if the trigger is still valid.
|
|
bool bDoWeCareIfTriggerIsStillValid = (nCurrentState != Trigger::WaitBeforeFinish);
|
|
bool bIsTriggerStillValid = bDoWeCareIfTriggerIsStillValid && IsTriggerStillValid(rPed, rPlayer, rTrigger);
|
|
|
|
//Check the current state.
|
|
switch(nCurrentState)
|
|
{
|
|
case Trigger::Start:
|
|
{
|
|
//Check the chances to react initially.
|
|
float fChancesToReactInitially = rTrigger.m_fChances;
|
|
if(fChancesToReactInitially > 0.0f)
|
|
{
|
|
//Check if we should react.
|
|
float fRandom = fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
|
|
if(fRandom <= fChancesToReactInitially)
|
|
{
|
|
//Calculate the time before the initial reaction.
|
|
float fMinTimeBeforeInitialReaction = pReaction->GetTimeBeforeInitialReaction().m_Min;
|
|
float fMaxTimeBeforeInitialReaction = pReaction->GetTimeBeforeInitialReaction().m_Max;
|
|
float fTimeBeforeInitialReaction = (fMaxTimeBeforeInitialReaction > 0.0f) ? fwRandom::GetRandomNumberInRange(fMinTimeBeforeInitialReaction, fMaxTimeBeforeInitialReaction) : 0.0f;
|
|
|
|
//Check if the time before initial reaction is valid.
|
|
if(fTimeBeforeInitialReaction > 0.0f)
|
|
{
|
|
//Set the time before reaction.
|
|
rTrigger.m_fTimeBeforeReaction = fTimeBeforeInitialReaction;
|
|
|
|
//Wait before the reaction.
|
|
nNextState = Trigger::WaitBeforeReaction;
|
|
}
|
|
else
|
|
{
|
|
//React.
|
|
nNextState = Trigger::React;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Calculate the time after an initial reaction failure.
|
|
float fMinTimeAfterInitialReactionFailure = pReaction->GetTimeAfterInitialReactionFailure().m_Min;
|
|
float fMaxTimeAfterInitialReactionFailure = pReaction->GetTimeAfterInitialReactionFailure().m_Max;
|
|
float fTimeAfterInitialReactionFailure = (fMaxTimeAfterInitialReactionFailure > 0.0f) ? fwRandom::GetRandomNumberInRange(fMinTimeAfterInitialReactionFailure, fMaxTimeAfterInitialReactionFailure) : 0.0f;
|
|
|
|
//Check if the time after an initial reaction failure is valid.
|
|
if(fTimeAfterInitialReactionFailure > 0.0f)
|
|
{
|
|
//Set the time to wait before finish.
|
|
rTrigger.m_fTimeBeforeFinish = fTimeAfterInitialReactionFailure;
|
|
|
|
//Wait before we finish.
|
|
nNextState = Trigger::WaitBeforeFinish;
|
|
}
|
|
else
|
|
{
|
|
//Move to the finish state.
|
|
nNextState = Trigger::Finish;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case Trigger::WaitBeforeReaction:
|
|
{
|
|
//Check if the trigger is still valid.
|
|
if(bIsTriggerStillValid)
|
|
{
|
|
//Check if we are agitated.
|
|
const CTaskAgitated* pTask = CTaskAgitated::FindAgitatedTaskForPed(rPed);
|
|
if(pTask)
|
|
{
|
|
//Sync the time in state to the time since the audio ended.
|
|
//This allows us to have more consistent timing in between reactions.
|
|
rTrigger.m_fTimeInState = pTask->GetTimeSinceAudioEndedForState();
|
|
}
|
|
|
|
//Check if the time in state has exceeded the threshold.
|
|
float fTimeBeforeReaction = rTrigger.m_fTimeBeforeReaction;
|
|
if(rTrigger.m_fTimeInState > fTimeBeforeReaction)
|
|
{
|
|
//React.
|
|
nNextState = Trigger::React;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Check if we have reacted.
|
|
if(rTrigger.m_uFlags.IsFlagSet(Trigger::HasReacted))
|
|
{
|
|
//Calculate the time after the last successful reaction.
|
|
float fMinTimeAfterLastSuccessfulReaction = pReaction->GetTimeAfterLastSuccessfulReaction().m_Min;
|
|
float fMaxTimeAfterLastSuccessfulReaction = pReaction->GetTimeAfterLastSuccessfulReaction().m_Max;
|
|
float fTimeAfterLastSuccessfulReaction = (fMaxTimeAfterLastSuccessfulReaction > 0.0f) ? fwRandom::GetRandomNumberInRange(fMinTimeAfterLastSuccessfulReaction, fMaxTimeAfterLastSuccessfulReaction) : 0.0f;
|
|
|
|
//Check if the time after the last successful reaction is valid.
|
|
if(fTimeAfterLastSuccessfulReaction > 0.0f)
|
|
{
|
|
//Set the time before finish.
|
|
rTrigger.m_fTimeBeforeFinish = fTimeAfterLastSuccessfulReaction;
|
|
|
|
//Wait before finish.
|
|
nNextState = Trigger::WaitBeforeFinish;
|
|
}
|
|
else
|
|
{
|
|
//Move to the finish state.
|
|
nNextState = Trigger::Finish;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Move to the finish state.
|
|
nNextState = Trigger::Finish;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case Trigger::React:
|
|
{
|
|
//Check if the trigger is still valid.
|
|
if(bIsTriggerStillValid)
|
|
{
|
|
//Note that we have reacted.
|
|
rTrigger.m_uFlags.SetFlag(Trigger::HasReacted);
|
|
|
|
//Add the agitation event.
|
|
CEventAgitated event(&rPlayer, pReaction->GetType());
|
|
rPed.GetPedIntelligence()->AddEvent(event);
|
|
|
|
//Calculate the time between escalating reactions.
|
|
float fMinTimeBetweenEscalatingReactions = pReaction->GetTimeBetweenEscalatingReactions().m_Min;
|
|
float fMaxTimeBetweenEscalatingReactions = pReaction->GetTimeBetweenEscalatingReactions().m_Max;
|
|
float fTimeBetweenEscalatingReactions = (fMaxTimeBetweenEscalatingReactions > 0.0f) ? fwRandom::GetRandomNumberInRange(fMinTimeBetweenEscalatingReactions, fMaxTimeBetweenEscalatingReactions) : 0.0f;
|
|
|
|
//Check if the time between escalating reactions is valid.
|
|
if(fTimeBetweenEscalatingReactions > 0.0f)
|
|
{
|
|
//Set the time before reaction.
|
|
rTrigger.m_fTimeBeforeReaction = fTimeBetweenEscalatingReactions;
|
|
|
|
//Wait before the reaction.
|
|
nNextState = Trigger::WaitBeforeReaction;
|
|
}
|
|
else
|
|
{
|
|
//Calculate the time after the last successful reaction.
|
|
float fMinTimeAfterLastSuccessfulReaction = pReaction->GetTimeAfterLastSuccessfulReaction().m_Min;
|
|
float fMaxTimeAfterLastSuccessfulReaction = pReaction->GetTimeAfterLastSuccessfulReaction().m_Max;
|
|
float fTimeAfterLastSuccessfulReaction = (fMaxTimeAfterLastSuccessfulReaction > 0.0f) ? fwRandom::GetRandomNumberInRange(fMinTimeAfterLastSuccessfulReaction, fMaxTimeAfterLastSuccessfulReaction) : 0.0f;
|
|
|
|
//Check if the time after the last successful reaction is valid.
|
|
if(fTimeAfterLastSuccessfulReaction > 0.0f)
|
|
{
|
|
//Set the time before finish.
|
|
rTrigger.m_fTimeBeforeFinish = fTimeAfterLastSuccessfulReaction;
|
|
|
|
//Wait before finish.
|
|
nNextState = Trigger::WaitBeforeFinish;
|
|
}
|
|
else
|
|
{
|
|
//Move to the finish state.
|
|
nNextState = Trigger::Finish;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Move to the finish state.
|
|
nNextState = Trigger::Finish;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case Trigger::WaitBeforeFinish:
|
|
{
|
|
//Check if the time in state has exceeded the threshold.
|
|
float fTimeBeforeFinish = rTrigger.m_fTimeBeforeFinish;
|
|
if(rTrigger.m_fTimeInState > fTimeBeforeFinish)
|
|
{
|
|
//Move to the finish state.
|
|
nNextState = Trigger::Finish;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case Trigger::Finish:
|
|
{
|
|
//Deactivate the trigger.
|
|
DeactivateTrigger(rTrigger);
|
|
return;
|
|
}
|
|
case Trigger::None:
|
|
default:
|
|
{
|
|
aiAssertf(false, "The state is invalid: %d.", rTrigger.m_nState);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Check if we are in the same state.
|
|
if(nCurrentState == nNextState)
|
|
{
|
|
//Increase the time in state.
|
|
rTrigger.m_fTimeInState += fwTimer::GetTimeStep();
|
|
}
|
|
else
|
|
{
|
|
//Set the state.
|
|
rTrigger.m_nState = nNextState;
|
|
|
|
//Clear the time in state.
|
|
rTrigger.m_fTimeInState = 0.0f;
|
|
}
|
|
}
|
|
|
|
void CPedAgitationScanner::UpdateTriggers(CPed& rPed, CPed& rPlayer)
|
|
{
|
|
//Ensure we can update triggers.
|
|
if(!CanUpdateTriggers(rPed, rPlayer))
|
|
{
|
|
//Deactivate the triggers.
|
|
DeactivateTriggers();
|
|
return;
|
|
}
|
|
|
|
//Activate the triggers.
|
|
ActivateTriggers(rPed, rPlayer);
|
|
|
|
//Iterate over the triggers.
|
|
int iCount = m_aTriggers.GetMaxCount();
|
|
for(int i = 0; i < iCount; ++i)
|
|
{
|
|
//Ensure the trigger is active.
|
|
Trigger& rTrigger = m_aTriggers[i];
|
|
if(!rTrigger.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Update the trigger.
|
|
UpdateTrigger(rPed, rPlayer, rTrigger);
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
void CPedAgitationScanner::RenderDebug(const CPed& rPed) const
|
|
{
|
|
//Ensure rendering is enabled.
|
|
TUNE_GROUP_BOOL(AGITATION_SCANNER, bRenderingEnabled, false);
|
|
if(!bRenderingEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Render the active triggers.
|
|
TUNE_GROUP_BOOL(AGITATION_SCANNER, bRenderActiveTriggers, true);
|
|
if(bRenderActiveTriggers)
|
|
{
|
|
char debugText[128];
|
|
int iLine = 0;
|
|
|
|
for(int i = 0; i < m_aTriggers.GetMaxCount(); ++i)
|
|
{
|
|
const Trigger& rTrigger = m_aTriggers[i];
|
|
if(!rTrigger.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sprintf(debugText, "Reaction: %s, State: %d, Time in state: %.2f", rTrigger.m_hReaction.GetCStr(), rTrigger.m_nState, rTrigger.m_fTimeInState);
|
|
grcDebugDraw::Text(VEC3V_TO_VECTOR3(rPed.GetTransform().GetPosition()) + ZAXIS, Color_red, 0, iLine++*grcDebugDraw::GetScreenSpaceTextHeight(), debugText);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////
|
|
//Ped encroachment scanner
|
|
//////////////////////////////
|
|
|
|
EXT_PF_TIMER(EncroachmentScanner);
|
|
|
|
const float CPedEncroachmentScanner::ms_fPedMinimumSpeedSq = (0.5f * 0.5f);
|
|
const float CPedEncroachmentScanner::ms_fSneakyPedMinimumSpeedSq = (3.0f * 3.0f);
|
|
|
|
void CPedEncroachmentScanner::Scan(CPed & ped, CEntityScannerIterator& entityList)
|
|
{
|
|
PF_FUNC(EncroachmentScanner);
|
|
|
|
//shortcut to only check our decisionmaker once rather than every scan
|
|
if (!m_bCheckedForRelevance)
|
|
{
|
|
if ( ped.GetPedIntelligence()->HasResponseToEvent(EVENT_ENCROACHING_PED) )
|
|
{
|
|
RegisterSlot();
|
|
}
|
|
m_bCheckedForRelevance = true;
|
|
}
|
|
|
|
if( IsRegistered() && (ShouldBeProcessedThisFrame() || ShouldForceEncroachmentScanThisFrame()))
|
|
{
|
|
StartProcess();
|
|
|
|
const Vector3 vPedPos = VEC3V_TO_VECTOR3(ped.GetTransform().GetPosition());
|
|
|
|
|
|
// Note: the following code ASSUMES that this entity list is sorted by order of distance from the focus ped (nearest first).
|
|
// If this ceases to be the case, then this scanner is going to become pretty useless!
|
|
const CEntity* pEntity = entityList.GetFirst();
|
|
while (pEntity != NULL)
|
|
{
|
|
Assert(pEntity->GetType()==ENTITY_TYPE_PED);
|
|
CPed* pClosePed=(CPed*)pEntity;
|
|
Vector3 vPedVelocity = !pClosePed->GetIsInVehicle() ? pClosePed->GetVelocity() : pClosePed->GetMyVehicle()->GetVelocity();
|
|
float fPedVelocityMagSq = vPedVelocity.Mag2();
|
|
const CPedPerception& perception = ped.GetPedIntelligence()->GetPedPerception();
|
|
|
|
bool bNoisyPlayer = pClosePed->GetPlayerInfo() ? pClosePed->GetPlayerInfo()->GetStealthNoise() > SOUNDRANGE_MOSTLY_AUDIBLE : false;
|
|
bool bAutoSense = pClosePed->GetPlayerInfo() && perception.GetCanAlwaysSenseEncroachingPlayers();
|
|
bool bDo3DCheck = perception.GetPerformsEncroachmentChecksIn3D();
|
|
|
|
//only have encroachment from alive things that are moving fast or making noise.
|
|
if(!pClosePed->IsDead() && (pClosePed->GetMotionData()->GetCurrentMbrY() > MOVEBLENDRATIO_WALK || fPedVelocityMagSq >= ms_fPedMinimumSpeedSq || bNoisyPlayer || bAutoSense))
|
|
{
|
|
const Vector3 vClosePedPos = VEC3V_TO_VECTOR3(pClosePed->GetTransform().GetPosition());
|
|
if ( ped.GetPedIntelligence()->IsThreatenedBy(*pClosePed, true ) )
|
|
{
|
|
//Check that close ped is in front of ped.
|
|
Vector3 vDiff = vClosePedPos - vPedPos;
|
|
|
|
//Calculate the personal space.
|
|
float fPedPersonalSpaceDistanceSq = rage::square(perception.GetEncroachmentRange());
|
|
|
|
//Check if the ped is using a scenario.
|
|
const CTaskUseScenario* pScenarioTask = static_cast<CTaskUseScenario *>(ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_USE_SCENARIO));
|
|
if(pScenarioTask)
|
|
{
|
|
//Check if the scenario point is valid.
|
|
const CScenarioPoint* pScenarioPoint = pScenarioTask->GetScenarioPoint();
|
|
if(pScenarioPoint && pScenarioPoint->GetRadius() > 0.0f)
|
|
{
|
|
//Set the encroachment radius to be the point's radius.
|
|
fPedPersonalSpaceDistanceSq = square(pScenarioPoint->GetRadius());
|
|
}
|
|
}
|
|
|
|
float fDiff = bDo3DCheck ? vDiff.Mag2() : vDiff.XYMag2();
|
|
if (fDiff <= fPedPersonalSpaceDistanceSq)
|
|
{
|
|
// If the ped is sneaking, then don't startle unless super close.
|
|
bool bAddEncroachmentEvent = false;
|
|
if (!bAutoSense && !bNoisyPlayer && pClosePed->GetMotionData()->GetCurrentMbrY() <= MOVEBLENDRATIO_WALK && fPedVelocityMagSq <= ms_fSneakyPedMinimumSpeedSq)
|
|
{
|
|
if (fDiff <= rage::square(perception.GetEncroachmentCloseRange()))
|
|
{
|
|
// Even though the ped was sneaking he got too close.
|
|
bAddEncroachmentEvent = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The ped isn't sneaking so just add the event.
|
|
bAddEncroachmentEvent = true;
|
|
}
|
|
if (bAddEncroachmentEvent)
|
|
{
|
|
CEventEncroachingPed event(pClosePed);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//we can safely bail out because the nearest ped was not near enough to startle
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
pEntity = entityList.GetNext();
|
|
}
|
|
|
|
StopProcess();
|
|
}
|
|
}
|
|
|
|
bool CPedEncroachmentScanner::ShouldForceEncroachmentScanThisFrame() const
|
|
{
|
|
const CPed* pPlayer = CGameWorld::FindLocalPlayer();
|
|
if (pPlayer)
|
|
{
|
|
const CVehicle* pVehicle = pPlayer->GetVehiclePedInside();
|
|
if (pVehicle)
|
|
{
|
|
if (IsGreaterThanAll(MagSquared(pVehicle->GetAiVelocityConstRef()), ScalarV(CPedIntelligence::ms_fFrequentScanVehicleVelocitySquaredThreshold)))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
EXT_PF_TIMER(FireScanner);
|
|
|
|
const int CNearbyFireScanner::ms_iLatencyPeriod=100;
|
|
const float CNearbyFireScanner::ms_fNearbyFireRange=20.0f;
|
|
const float CNearbyFireScanner::ms_fPotentialWalkIntoFireRange=4.0f;
|
|
|
|
void CNearbyFireScanner::Scan(CPed& ped, const bool bForce)
|
|
{
|
|
PF_FUNC(FireScanner);
|
|
|
|
//Test if its time to do another check.
|
|
bool bShouldScan = false;
|
|
if( IsRegistered() )
|
|
{
|
|
bShouldScan = ShouldBeProcessedThisFrame();
|
|
}
|
|
else
|
|
{
|
|
//Set the timer if it isn't already set.
|
|
if(!m_timer.IsSet())
|
|
{
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),0);
|
|
}
|
|
|
|
//Test if the scanner is ready to do another threat scan.
|
|
if(m_timer.IsOutOfTime())
|
|
{
|
|
//Ready to to a threat scan.
|
|
//Reset the timer.
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),ms_iLatencyPeriod);
|
|
bShouldScan = true;
|
|
}
|
|
}
|
|
|
|
if( bShouldScan || bForce)
|
|
{
|
|
StartProcess();
|
|
//Time do do another check.
|
|
//Test if the ped isn't on fire.
|
|
|
|
//Reset the timer.
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),ms_iLatencyPeriod);
|
|
|
|
//const float fMoveBlendRatio = ped.GetPedIntelligence()->GetMoveBlendRatioFromGoToTask();
|
|
|
|
//Ped isn't on fire so he still might want to avoid fire.
|
|
//Test that the ped isn't already walking around a fire.
|
|
//CTask* pTask = ped.GetPedIntelligence()->GetTaskActive();
|
|
|
|
CFire* pNearestFire = NULL;
|
|
|
|
// Perform a full scan once every ten scan periods;
|
|
// Since scan interval is 100ms, this equals once per second.
|
|
m_iFullScanCounter++;
|
|
|
|
if(m_iFullScanCounter >= 5 || bForce)
|
|
{
|
|
m_iFullScanCounter = 0;
|
|
|
|
struct TNearbyFire
|
|
{
|
|
Vec3V vPositionAndRadius;
|
|
ScalarV fDistance;
|
|
};
|
|
TNearbyFire fires[NUM_NEARBY_FIRES+1];
|
|
s32 iNumFires = 0;
|
|
|
|
//-----------------------------------------------------------
|
|
// Obtain a list of the nearest fires to this ped
|
|
// Find the closest fire in the XY plane as 'pNearestFire'
|
|
|
|
const ScalarV fScanRange( ms_fNearbyFireRange*ms_fNearbyFireRange );
|
|
ScalarV fNearestFireXY = fScanRange;
|
|
|
|
s32 i,f;
|
|
|
|
for (i=0; i<g_fireMan.GetNumActiveFires(); i++)
|
|
{
|
|
CFire * pFire = g_fireMan.GetActiveFire(i);
|
|
Vec3V vVec = pFire->GetPositionWorld() - ped.GetTransform().GetPosition();
|
|
ScalarV fDistSqr = MagSquared(vVec);
|
|
if( (fDistSqr < fScanRange).Getb())
|
|
{
|
|
for(f=0; f<iNumFires; f++)
|
|
{
|
|
if( (fDistSqr < fires[f].fDistance).Getb() )
|
|
{
|
|
for(s32 g=iNumFires-1; g>f; g--)
|
|
{
|
|
fires[g+1] = fires[g];
|
|
}
|
|
fires[f].vPositionAndRadius = pFire->GetPositionWorld();
|
|
fires[f].vPositionAndRadius.SetW(pFire->GetMaxRadius());
|
|
fires[f].fDistance = fDistSqr;
|
|
break;
|
|
}
|
|
}
|
|
if(f == iNumFires && f < NUM_NEARBY_FIRES)
|
|
{
|
|
fires[f].vPositionAndRadius = pFire->GetPositionWorld();
|
|
fires[f].vPositionAndRadius.SetW(pFire->GetMaxRadius());
|
|
fires[f].fDistance = fDistSqr;
|
|
iNumFires++;
|
|
}
|
|
|
|
// Maintain the previous behaviour which determines the nearst fire
|
|
// ranking nearby fires only on their XY distance from the ped.
|
|
|
|
Vec2V vVecXY( vVec.GetIntrin128ConstRef() );
|
|
ScalarV fDistSqrXY = MagSquared(vVecXY);
|
|
if( (fDistSqrXY < fNearestFireXY).Getb() )
|
|
{
|
|
fNearestFireXY = fDistSqrXY;
|
|
pNearestFire = pFire;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(i=0; i<iNumFires; i++)
|
|
{
|
|
m_vNearbyFires[i] = RCC_VECTOR3(fires[i].vPositionAndRadius);
|
|
}
|
|
if(i < NUM_NEARBY_FIRES)
|
|
{
|
|
m_vNearbyFires[i].Zero();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Get the nearest fire
|
|
|
|
float nearestFireDistSqr = ms_fNearbyFireRange*ms_fNearbyFireRange;
|
|
|
|
for (int i=0; i<g_fireMan.GetNumActiveFires(); i++)
|
|
{
|
|
CFire * pFire = g_fireMan.GetActiveFire(i);
|
|
|
|
Vec3V vVec = pFire->GetPositionWorld() - ped.GetTransform().GetPosition();
|
|
vVec.SetZ(ScalarV(V_ZERO));
|
|
|
|
float distSqr = MagSquared(vVec).Getf();
|
|
if (distSqr<nearestFireDistSqr)
|
|
{
|
|
nearestFireDistSqr = distSqr;
|
|
pNearestFire = pFire;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Trigger a CEventFireNearby if required.
|
|
if(pNearestFire)
|
|
{
|
|
Vec3V vFirePos = pNearestFire->GetPositionWorld();
|
|
|
|
m_vNearbyFires[0] = RCC_VECTOR3(vFirePos);
|
|
m_vNearbyFires[0].SetW(pNearestFire->GetMaxRadius());
|
|
|
|
float zDiff = Abs((vFirePos - ped.GetTransform().GetPosition()).GetZf());
|
|
if(zDiff < 2.0f)
|
|
{
|
|
CEventFireNearby event(RCC_VECTOR3(vFirePos));
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
|
|
////Trigger a CEventPotentialWalkIntoFire if required.
|
|
|
|
//CTaskMove * pMoveTask = ped.GetPedIntelligence()->GetActiveSimplestMovementTask();
|
|
//if(pMoveTask && pMoveTask->IsMoveTask() && pMoveTask->IsTaskMoving() )
|
|
//{
|
|
// //There is a nearest fire.
|
|
// //Test if the nearest fire is in the danger range.
|
|
// if(nearestFireDistSqr < (ms_fPotentialWalkIntoFireRange*ms_fPotentialWalkIntoFireRange))
|
|
// {
|
|
// if(zDiff<2.0f && pMoveTask->HasTarget())
|
|
// {
|
|
// Vector3 vMoveTarget2d = pMoveTask->GetTarget();
|
|
// Vector3 vPedPos2d = VEC3V_TO_VECTOR3(ped.GetTransform().GetPosition());
|
|
// vMoveTarget2d.z = vFirePos.GetZf();
|
|
// vPedPos2d.z = vFirePos.GetZf();
|
|
|
|
// const spdSphere sphere( vFirePos, ScalarV(pNearestFire->GetMaxRadius() + ped.GetCapsuleInfo()->GetHalfWidth()) );
|
|
// Vector3 vIsect1, vIsect2;
|
|
|
|
// const bool bIntersectsFire = fwGeom::IntersectSphereEdge(sphere, vPedPos2d, vMoveTarget2d, vIsect1, vIsect2);
|
|
|
|
// //Nearest fire is in the danger range.
|
|
// //Give the ped an event
|
|
// if(bIntersectsFire)
|
|
// {
|
|
// CEventPotentialWalkIntoFire event(RCC_VECTOR3(vFirePos), pNearestFire->GetMaxRadius(), fMoveBlendRatio);
|
|
// ped.GetPedIntelligence()->AddEvent(event);
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
if( ped.GetPedConfigFlag( CPED_CONFIG_FLAG_RunFromFiresAndExplosions ) )
|
|
{
|
|
//Find the nearest fire that can cause a blast.
|
|
float nearestFireDistSqr = ms_fNearbyFireRange*ms_fNearbyFireRange;
|
|
CFire* pNearestBlastFire = NULL;
|
|
for (int i=0; i<g_fireMan.GetNumActiveFires(); i++)
|
|
{
|
|
CFire* pFire = g_fireMan.GetActiveFire(i);
|
|
if (pFire->GetFireType()==FIRETYPE_REGD_VEH_PETROL_TANK || pFire->GetFireType()==FIRETYPE_TIMED_PETROL_POOL || pFire->GetFireType()==FIRETYPE_TIMED_PETROL_TRAIL)
|
|
{
|
|
Vec3V vVec = pFire->GetPositionWorld()-ped.GetTransform().GetPosition();
|
|
vVec.SetZ(ScalarV(V_ZERO));
|
|
|
|
float distSqr = MagSquared(vVec).Getf();
|
|
if (distSqr<nearestFireDistSqr)
|
|
{
|
|
nearestFireDistSqr = distSqr;
|
|
pNearestBlastFire = pFire;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Trigger a CEventPotentialBlast if required.
|
|
if(pNearestBlastFire)
|
|
{
|
|
CEntity* pEntity = pNearestBlastFire->GetEntity();
|
|
|
|
float blastRadius = 0.0f;
|
|
if(pNearestBlastFire->GetFireType() == FIRETYPE_REGD_VEH_PETROL_TANK)
|
|
{
|
|
if(pEntity)
|
|
{
|
|
blastRadius = pEntity->GetBoundRadius();
|
|
}
|
|
}
|
|
else if(pNearestBlastFire->GetFireType() == FIRETYPE_TIMED_PETROL_TRAIL || pNearestBlastFire->GetFireType() == FIRETYPE_TIMED_PETROL_POOL)
|
|
{
|
|
// Consider petrol trails and pools explosive to make peds react, as fires
|
|
// there tend to spread and may connect up to vehicles or other explosive objects.
|
|
// We may want to actually add some more sophisticated checks for that stuff later,
|
|
// if needed. The request to escape from petrol trails came from
|
|
// B* 191538: "Shocking event: if there is a fire trail near them peds should see it and react and run away - see video".
|
|
blastRadius = 3.0f; // MAGIC!
|
|
}
|
|
else
|
|
{
|
|
Assertf(0, "found unsupported fire type");
|
|
}
|
|
|
|
//Ensure the entity is valid.
|
|
if(pEntity)
|
|
{
|
|
//Calculate the time of explosion.
|
|
float fTimeUntilExplosion;
|
|
u32 uTimeOfExplosion = CExplosionHelper::CalculateTimeUntilExplosion(*pEntity, fTimeUntilExplosion) ?
|
|
(fwTimer::GetTimeInMilliseconds() + (u32)(fTimeUntilExplosion * 1000.0f)) : 0;
|
|
|
|
//Add the event.
|
|
CEventPotentialBlast event(CAITarget(pEntity), blastRadius, uTimeOfExplosion);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
|
|
//Add the shocking event.
|
|
CEventShockingPotentialBlast shockingEvent(*pEntity);
|
|
CShockingEventsManager::Add(shockingEvent);
|
|
}
|
|
else
|
|
{
|
|
//Add the event.
|
|
CEventPotentialBlast event(CAITarget(VEC3V_TO_VECTOR3(pNearestBlastFire->GetPositionWorld())), blastRadius, 0);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
|
|
//Add the shocking event.
|
|
CEventShockingPotentialBlast shockingEvent(pNearestBlastFire->GetPositionWorld());
|
|
CShockingEventsManager::Add(shockingEvent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
StopProcess();
|
|
}
|
|
|
|
//Process the IsEntityOnFire section every time this method is called, regardless of bShouldScan.
|
|
//If this is a MP game then the ApplyDamangeAndComputeResponse needs to be called every time otherwise the application of damage from fire will differ from SP.
|
|
//When this is a SP game the throttle of bShouldScan is still applied to peds and players in SP, so the addevent calls occur at the same rate as prior.
|
|
//lavalley
|
|
|
|
//Check if ped is on fire.
|
|
if(g_fireMan.IsEntityOnFire(&ped))
|
|
{
|
|
bool bPlayerInNetworkGame = false;
|
|
if( ped.IsAPlayerPed() && NetworkInterface::IsGameInProgress() )
|
|
bPlayerInNetworkGame = true;
|
|
if(!bPlayerInNetworkGame)
|
|
{
|
|
if (bShouldScan) //only call addevent at the same rate as if this was in the bShouldScan section (throttled) <works at the same rate as before this change for peds and non-MP players> lavalley.
|
|
{
|
|
u32 uWeaponHash = WEAPONTYPE_FIRE;
|
|
CFire* pFire = g_fireMan.GetEntityFire(&ped);
|
|
if (pFire)
|
|
{
|
|
uWeaponHash = pFire->GetWeaponHash();
|
|
}
|
|
|
|
CEventOnFire event(uWeaponHash);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFire* pFire = g_fireMan.GetEntityFire(&ped);
|
|
|
|
//Allow the player to take damage when they pause the game (even though controls are disabled).
|
|
//If this was not here, players could pause the game inside of a blazing inferno and take no damage while waiting for the fire to dissipate.
|
|
//Allow the player to take damage if SPC_AllowPlayerToBeDamaged is set to true
|
|
bool bInflictDamage = (CGameWorld::GetMainPlayerInfo() && (!CGameWorld::GetMainPlayerInfo()->AreAnyControlsOtherThanFrontendDisabled() || CGameWorld::GetMainPlayerInfo()->GetPlayerDataCanBeDamaged()));
|
|
|
|
if( bInflictDamage )
|
|
{
|
|
if(pFire)
|
|
{
|
|
float ftimestep = fwTimer::GetTimeStep();
|
|
u32 uWeaponHash = pFire->GetWeaponHash();
|
|
if(uWeaponHash == 0)
|
|
{
|
|
uWeaponHash = WEAPONTYPE_FIRE;
|
|
}
|
|
|
|
CEventDamage event(NULL, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_FIRE);
|
|
CPedDamageCalculator damageCalculator(pFire->GetCulprit(), CTaskComplexOnFire::ms_fHealthRate*ftimestep, uWeaponHash, false);
|
|
damageCalculator.ApplyDamageAndComputeResponse(&ped, event.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
if (CLocalisation::PedsOnFire() && ped.GetSpeechAudioEntity())
|
|
{
|
|
audDamageStats damageStats;
|
|
damageStats.Fatal = false;
|
|
damageStats.RawDamage = 20.0f;
|
|
damageStats.DamageReason = AUD_DAMAGE_REASON_ON_FIRE;
|
|
damageStats.PedWasAlreadyDead = ped.ShouldBeDead();
|
|
ped.GetSpeechAudioEntity()->InflictPain(damageStats);
|
|
}
|
|
|
|
if( (event.GetDamageResponseData().m_bKilled || event.GetDamageResponseData().m_bInjured) )
|
|
{
|
|
Vec3V vFirePos = pFire->GetPositionWorld();
|
|
CWeaponDamage::GeneratePedDamageEvent(pFire->GetCulprit(), &ped, uWeaponHash, CTaskComplexOnFire::ms_fHealthRate, RCC_VECTOR3(vFirePos), NULL, CPedDamageCalculator::DF_None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////
|
|
// PASSENGER EVENT SCANNER
|
|
//////////////////////////
|
|
|
|
EXT_PF_TIMER(PassengerEventScanner);
|
|
|
|
const int CPassengerEventScanner::ms_iLatencyPeriod=300;
|
|
|
|
void CPassengerEventScanner::Scan(CPed& ped)
|
|
{
|
|
PF_FUNC(PassengerEventScanner);
|
|
|
|
//Test if its time to do another check.
|
|
bool bShouldScan = false;
|
|
if( IsRegistered() )
|
|
{
|
|
bShouldScan = ShouldBeProcessedThisFrame();
|
|
}
|
|
else
|
|
{
|
|
//Test if the scanner is ready to do another scan.
|
|
if(!m_timer.IsSet() || m_timer.IsOutOfTime())
|
|
{
|
|
bShouldScan = true;
|
|
}
|
|
}
|
|
|
|
if( bShouldScan )
|
|
{
|
|
StartProcess();
|
|
|
|
//Reset the timer.
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),ms_iLatencyPeriod);
|
|
|
|
//Scan for player standing on my boat
|
|
if(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && ped.GetMyVehicle()
|
|
&& ped.GetMyVehicle()->InheritsFromBoat()
|
|
&& !ped.IsPlayer() && FindPlayerPed()->GetGroundPhysical()==ped.GetMyVehicle())
|
|
{
|
|
//Player is standing on ped's boat.
|
|
CEventPedEnteredMyVehicle event(FindPlayerPed(),ped.GetMyVehicle());
|
|
event.SetResponseTaskType(CTaskTypes::TASK_COMPLEX_LEAVE_CAR_AND_FLEE);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
|
|
// If we're in a car thats on fire, generate the event
|
|
if( ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && ped.GetMyVehicle() && ped.GetPedConfigFlag( CPED_CONFIG_FLAG_GetOutBurningVehicle ) )
|
|
{
|
|
if( ped.GetMyVehicle()->IsOnFire() )
|
|
{
|
|
CEventVehicleOnFire fireEvent(ped.GetMyVehicle());
|
|
ped.GetPedIntelligence()->AddEvent(fireEvent);
|
|
}
|
|
}
|
|
|
|
// If we are allowed to react to a ped on the roof
|
|
if(!ped.GetPedConfigFlag(CPED_CONFIG_FLAG_NeverReactToPedOnRoof))
|
|
{
|
|
//Scan for player on my car roof and I am the driver
|
|
CPed * pPlayerPed = FindPlayerPed();
|
|
if(pPlayerPed && pPlayerPed->GetGroundPhysical() && ped.GetMyVehicle() &&
|
|
pPlayerPed->GetGroundPhysical()==ped.GetMyVehicle() &&
|
|
!ped.PopTypeIsMission() && !ped.IsAPlayerPed() )
|
|
{
|
|
if( !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_AlreadyReactedToPedOnRoof ) || !ped.GetMyVehicle()->IsHornOn() )
|
|
{
|
|
CEventPedOnCarRoof event(pPlayerPed,(CVehicle*)pPlayerPed->GetGroundPhysical());
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
ped.SetPedConfigFlag( CPED_CONFIG_FLAG_AlreadyReactedToPedOnRoof, true );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ped.SetPedConfigFlag( CPED_CONFIG_FLAG_AlreadyReactedToPedOnRoof, false );
|
|
}
|
|
}
|
|
|
|
StopProcess();
|
|
}
|
|
}
|
|
|
|
//////////////////////////
|
|
//COLLISION EVENT SCANNER
|
|
//////////////////////////
|
|
|
|
const float CCollisionEventScanner::KILL_FALL_HEIGHT = 10.0f;
|
|
const float CCollisionEventScanner::PLAYER_KILL_FALL_HEIGHT = 15.0f;
|
|
|
|
void CCollisionEventScanner::ScanForCollisionEvents(CPed* pPed)
|
|
{
|
|
if(pPed->GetRagdollState()==RAGDOLL_STATE_PHYS)
|
|
return;
|
|
|
|
const CCollisionHistory* pCollisionHistory = pPed->GetFrameCollisionHistory();
|
|
if(pCollisionHistory->GetNumCollidedEntities() == 0)
|
|
return;
|
|
|
|
const float fMoveBlendRatio = pPed->GetPedIntelligence()->GetMoveBlendRatioFromGoToTask();
|
|
const CCollisionRecord* pColRecord;
|
|
|
|
//Vehicle records
|
|
for(pColRecord = pCollisionHistory->GetFirstVehicleCollisionRecord();
|
|
pColRecord != NULL ; pColRecord = pColRecord->GetNext())
|
|
{
|
|
CVehicle * pVehicle = static_cast<CVehicle*>(pColRecord->m_pRegdCollisionEntity.Get());
|
|
|
|
if( pVehicle == NULL ) continue;
|
|
if( m_bAlreadyHitByCar ) continue;
|
|
if( pVehicle->GetIsAttached() && pVehicle->GetAttachParent() == pPed ) continue;
|
|
|
|
// enforce a minimum absolute vehicle speed and minimum speed relative to (And in the direction of) the ped
|
|
const float fVehicleSpeedSqr = pVehicle->GetVelocity().XYMag2();
|
|
const float fMinVelSqr = rage::square(CEventVehicleCollision::ms_fDamageThresholdSpeed);
|
|
bool bOverSpeedThreshold = fVehicleSpeedSqr > fMinVelSqr;
|
|
bool bOverRelSpeedInDirectionThreshold = false;
|
|
|
|
if (bOverSpeedThreshold)
|
|
{
|
|
const float fVehicleRelSpeedAlongContact = Dot(VECTOR3_TO_VEC3V(pVehicle->GetVelocity() - pPed->GetVelocity()), RCC_VEC3V(pColRecord->m_MyCollisionNormal)).Getf();
|
|
const float fMinRelSpeedAlongContact = CEventVehicleCollision::ms_fDamageThresholdSpeedAlongContact;
|
|
|
|
bOverRelSpeedInDirectionThreshold = fVehicleRelSpeedAlongContact > fMinRelSpeedAlongContact;
|
|
|
|
//animEntityDebugf(pPed, "VehicleCollisionRecord: speed check: %s(AbsSpeed: %.3f, min: %.3f) relSpeedAlongContact: %s (dot: %.3f, min:%.3f)(relSpeed: %.3f, %.3f, %.3f)(normal:%.3f, %.3f, %.3f)",
|
|
// bOverSpeedThreshold ? "PASSED" : "FAILED", fVehicleSpeedSqr, fMinVelSqr,
|
|
// bOverRelSpeedInDirectionThreshold ? "PASSED" : "FAILED", fVehicleRelSpeedAlongContact, fMinRelSpeedAlongContact,
|
|
// (pVehicle->GetVelocity() - pPed->GetVelocity()).x, (pVehicle->GetVelocity() - pPed->GetVelocity()).y, (pVehicle->GetVelocity() - pPed->GetVelocity()).z,
|
|
// pColRecord->m_MyCollisionNormal.x, pColRecord->m_MyCollisionNormal.y, pColRecord->m_MyCollisionNormal.z);
|
|
}
|
|
|
|
if(bOverSpeedThreshold && bOverRelSpeedInDirectionThreshold)
|
|
{
|
|
float fImpMag = pColRecord->m_fCollisionImpulseMag;
|
|
if(pPed->GetIsStanding())
|
|
{
|
|
float fPedVelAlongCol = DotProduct(pPed->GetVelocity(), pColRecord->m_MyCollisionNormal);
|
|
if(fPedVelAlongCol < 0.0f)
|
|
fImpMag = MAX(0.0f, fImpMag + fPedVelAlongCol*pPed->GetMass());
|
|
}
|
|
|
|
//bool bLimitedCollisionImpact = pPed->IsPlayer();
|
|
// don't limit the collision impact for cars and players
|
|
// if we're playing a network game
|
|
//bLimitedCollisionImpact &= !NetworkInterface::IsGameInProgress();
|
|
if(pPed->IsPlayer() && !NetworkInterface::IsGameInProgress())
|
|
{
|
|
if(fImpMag > 1000.0f) // limited to this anyway
|
|
fImpMag = 1000.0f;
|
|
|
|
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
|
|
const float fVehicleHeading = pVehicle->GetTransform().GetHeading();
|
|
const Vector3 bBoxVMin = pVehicle->GetBaseModelInfo()->GetBoundingBoxMin();
|
|
const Vector3 bBoxVMax = pVehicle->GetBaseModelInfo()->GetBoundingBoxMax();
|
|
Vector3 bBoxCentre = (bBoxVMin + bBoxVMax) / 2.0f;
|
|
bBoxCentre = pVehicle->TransformIntoWorldSpace(bBoxCentre);
|
|
bBoxCentre = bBoxCentre - vPedPos;
|
|
|
|
const float theta = rage::Atan2f(-bBoxCentre.x, bBoxCentre.y);
|
|
const float theta2 = fwAngle::LimitRadianAngle(fVehicleHeading - theta);
|
|
const float cornerAngle = rage::Atan2f(bBoxVMax.x - bBoxVMin.x, bBoxVMax.y - bBoxVMin.y);
|
|
|
|
Vector3 vecHitOffset = vPedPos - VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetPosition());
|
|
vecHitOffset.Normalize();
|
|
float fHitSpeed;
|
|
|
|
static const float fHitSpdThreshMax = 5.0f;
|
|
|
|
// hit by front or back of car (just do normal hit)
|
|
if(rage::Abs(theta2) < cornerAngle || rage::Abs(theta2) > (PI - cornerAngle))
|
|
{
|
|
fHitSpeed = DotProduct(vecHitOffset, pVehicle->GetVelocity());
|
|
}
|
|
// hit by left side of car
|
|
else if(theta2 > 0.0f)
|
|
{
|
|
fHitSpeed = DotProduct(-1.0f*VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetA()), pVehicle->GetVelocity());
|
|
}
|
|
// hit by right side of car
|
|
else
|
|
{
|
|
fHitSpeed = DotProduct(VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetA()), pVehicle->GetVelocity());
|
|
}
|
|
|
|
if(fHitSpeed > fHitSpdThreshMax)
|
|
{
|
|
pPed->KillPedWithCar(pVehicle, fImpMag, pColRecord);
|
|
}
|
|
else
|
|
{
|
|
//Player shouldn't do anything if he isn't going to take any damage.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!pPed->GetAttachParent() || pPed->GetAttachParent()->GetType()!=ENTITY_TYPE_VEHICLE)
|
|
{
|
|
// Ped not attached to a car.
|
|
// Depending on the impulse magnitude either make him fall over or kill him
|
|
pPed->KillPedWithCar(pVehicle, fImpMag, pColRecord);
|
|
}
|
|
else
|
|
{
|
|
// Ped attached to a car, just decrement the health
|
|
CPedDamageCalculator damageCalculator(pVehicle, fImpMag, WEAPONTYPE_RAMMEDBYVEHICLE, 0, false);
|
|
CEventDamage event(pVehicle, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE);
|
|
damageCalculator.ApplyDamageAndComputeResponse(pPed, event.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(0==pPed->GetAttachParent() || pPed->GetAttachParent()->GetType()!=ENTITY_TYPE_VEHICLE)
|
|
{
|
|
bool bShouldBeEnteringVehicleAsPartOfGroup = false;
|
|
if (NetworkInterface::IsGameInProgress() && pPed->GetPedsGroup()
|
|
&& pPed->GetPedsGroup()->GetGroupMembership()->GetLeader() && pPed->GetPedsGroup()->GetGroupMembership()->GetLeader()->GetIsInVehicle()
|
|
&& pPed->GetPedsGroup()->GetGroupMembership()->GetLeader()->GetMyVehicle() == pVehicle)
|
|
{
|
|
bShouldBeEnteringVehicleAsPartOfGroup = true;
|
|
}
|
|
|
|
// We want the player to react to running into car doors so he can navigate round them
|
|
CTaskMove * pSimpleMove = pPed->GetPedIntelligence()->GetActiveSimplestMovementTask();
|
|
const bool bPlayerInControl = pSimpleMove && pSimpleMove->GetTaskType()==CTaskTypes::TASK_MOVE_PLAYER;
|
|
if(!bPlayerInControl && !bShouldBeEnteringVehicleAsPartOfGroup)
|
|
{
|
|
// Ped not attached to a car.
|
|
// Just make him react to the collision.
|
|
const int iVehicleComponent = pColRecord->m_OtherCollisionComponent;
|
|
Assert((unsigned)iVehicleComponent < 65536);
|
|
|
|
CEventVehicleCollision event(
|
|
pVehicle,
|
|
pColRecord->m_MyCollisionNormal,
|
|
pColRecord->m_MyCollisionPos,
|
|
pColRecord->m_fCollisionImpulseMag,
|
|
(u16)iVehicleComponent,
|
|
fMoveBlendRatio);
|
|
event.SetVehicleStationary(true);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(pVehicle!=pPed->GetAttachParent())
|
|
{
|
|
bool bApplyDamage = true;
|
|
if(pVehicle->GetVelocity().Mag2() < CTaskVehicleFSM::ms_Tunables.m_MinPhysSpeedToActivateRagdoll)
|
|
{
|
|
bApplyDamage = false;
|
|
}
|
|
|
|
if(bApplyDamage)
|
|
{
|
|
// Ped attached to a car, compute reduction in health from impact with other car.
|
|
|
|
// Work out the relative speed difference between this ped and pEntity from the impulse.
|
|
float fRelSpeed = 0.0f;
|
|
float fPedEntRelVelMag = 0.0f;
|
|
float fPedEntTangVelMagSq = 0.0f;
|
|
ComputeRelativeSpeedFromImpulse(pPed, pVehicle, pColRecord->m_fCollisionImpulseMag, pColRecord->m_MyCollisionNormal, fRelSpeed, fPedEntRelVelMag, fPedEntTangVelMagSq);
|
|
|
|
float fDamage = ComputeDamageFromVehicle(pPed, pVehicle, pColRecord->m_MyCollisionComponent, fPedEntRelVelMag, pColRecord->m_MyCollisionNormal, true, fRelSpeed);
|
|
|
|
static float DAMAGE_MULTIPLIER_VEHICLE_IN_WATER = 0.1f;
|
|
if(pVehicle->GetIsInWater())
|
|
{
|
|
fDamage *= DAMAGE_MULTIPLIER_VEHICLE_IN_WATER;
|
|
}
|
|
|
|
CPedDamageCalculator damageCalculator(pVehicle, fDamage, WEAPONTYPE_RAMMEDBYVEHICLE, PED_SPHERE_CHEST, false);
|
|
CEventDamage event(pVehicle, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE);
|
|
damageCalculator.ApplyDamageAndComputeResponse(pPed, event.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Ped records
|
|
for(pColRecord = pCollisionHistory->GetFirstPedCollisionRecord();
|
|
pColRecord != NULL ; pColRecord = pColRecord->GetNext())
|
|
{
|
|
CPed* pOtherPed=(CPed*)pColRecord->m_pRegdCollisionEntity.Get();
|
|
|
|
if(pOtherPed == NULL) continue;
|
|
|
|
// Collision events not generated for swimming peds until NM behaviours can be switched off
|
|
if(pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsSwimming ) || pOtherPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsSwimming )) continue;
|
|
|
|
const float fOtherPedMoveBlendRatio = pOtherPed->GetMotionData()->GetCurrentMbrY();
|
|
|
|
if(pPed->IsPlayer())
|
|
{
|
|
CEventPlayerCollisionWithPed event(
|
|
(u16)pColRecord->m_MyCollisionComponent,
|
|
pColRecord->m_fCollisionImpulseMag,
|
|
pOtherPed,
|
|
pColRecord->m_MyCollisionNormal,
|
|
pColRecord->m_MyCollisionPos,
|
|
fMoveBlendRatio,
|
|
fOtherPedMoveBlendRatio);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
else
|
|
{
|
|
if(pOtherPed->IsPlayer())
|
|
{
|
|
CEventPedCollisionWithPlayer event(
|
|
(u16)pColRecord->m_MyCollisionComponent,
|
|
pColRecord->m_fCollisionImpulseMag,
|
|
pOtherPed,
|
|
pColRecord->m_MyCollisionNormal,
|
|
pColRecord->m_MyCollisionPos,
|
|
fMoveBlendRatio,
|
|
fOtherPedMoveBlendRatio);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
const CRelationshipGroup* pRelGroup = pPed->GetPedIntelligence()->GetRelationshipGroup();
|
|
|
|
// consider bumping into the player as the same as seeing them
|
|
if( pRelGroup )
|
|
{
|
|
if(pRelGroup->CheckRelationship( ACQUAINTANCE_TYPE_PED_HATE, pOtherPed->GetPedIntelligence()->GetRelationshipGroupIndex() ))
|
|
{
|
|
CEventAcquaintancePedHate eventHate(pOtherPed);
|
|
pPed->GetPedIntelligence()->AddEvent(eventHate);
|
|
}
|
|
else if(pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_TreatDislikeAsHateWhenInCombat) &&
|
|
pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT) &&
|
|
pRelGroup->CheckRelationship( ACQUAINTANCE_TYPE_PED_DISLIKE, pOtherPed->GetPedIntelligence()->GetRelationshipGroupIndex() ))
|
|
{
|
|
CEventAcquaintancePedHate eventHate(pOtherPed);
|
|
pPed->GetPedIntelligence()->AddEvent(eventHate);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEventPedCollisionWithPed event(
|
|
(u16)pColRecord->m_MyCollisionComponent,
|
|
pColRecord->m_fCollisionImpulseMag,
|
|
pOtherPed,
|
|
pColRecord->m_MyCollisionNormal,
|
|
pColRecord->m_MyCollisionPos,
|
|
fMoveBlendRatio,
|
|
fOtherPedMoveBlendRatio);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Object records (this is a mess)
|
|
for(pColRecord = pCollisionHistory->GetFirstObjectCollisionRecord();
|
|
pColRecord != NULL ; pColRecord = pColRecord->GetNext())
|
|
{
|
|
CObject *pDamageObj = (CObject*)pColRecord->m_pRegdCollisionEntity.Get();
|
|
|
|
if(pDamageObj == NULL) continue;
|
|
|
|
// subtract damage caused by the ped running into the object
|
|
// so we know how hard the object hit the ped
|
|
float fTempDamageImpulse = pColRecord->m_fCollisionImpulseMag;
|
|
fTempDamageImpulse *= Clamp(1.0f - pColRecord->m_MyCollisionNormal.z, 0.0f, 1.0f);
|
|
|
|
if(pPed->GetIsStanding() && !pDamageObj->GetIsStatic())
|
|
{
|
|
float fPedVelAlongCol = DotProduct(pPed->GetVelocity(), pColRecord->m_MyCollisionNormal);
|
|
if(fPedVelAlongCol < 0.0f)
|
|
fTempDamageImpulse = rage::Max(0.0f, fTempDamageImpulse + fPedVelAlongCol*pPed->GetMass());
|
|
}
|
|
|
|
static float PED_OBJECT_COLLISION_DAMAGE_THRESHOLD = 50.0f;
|
|
#if 0 // CS
|
|
static float PED_HIT_BY_WEAPON_OBJECT_COLLISION_DAMAGE_THRESHOLD = 10.0f;
|
|
#endif // 0
|
|
static float PLAYER_OBJECT_COLLISION_DAMAGE_THRESHOLD = 100.0f;
|
|
float fCollisionDamageThreshold = PED_OBJECT_COLLISION_DAMAGE_THRESHOLD;
|
|
#if 0 // CS
|
|
if(pDamageObj->GetWeapon() && pDamageObj->GetWeapon()->GetWeaponType() == WEAPONTYPE_OBJECT)
|
|
fCollisionDamageThreshold = PED_HIT_BY_WEAPON_OBJECT_COLLISION_DAMAGE_THRESHOLD;
|
|
else
|
|
#endif // 0
|
|
if(pPed->GetPlayerInfo())
|
|
fCollisionDamageThreshold = PLAYER_OBJECT_COLLISION_DAMAGE_THRESHOLD;
|
|
|
|
if(fTempDamageImpulse > fCollisionDamageThreshold && !pDamageObj->GetIsStatic() && pPed->GetIsStanding()
|
|
&& !pDamageObj->IsADoor()
|
|
&& pDamageObj!=pPed->GetGroundPhysical() && !(pDamageObj->GetAttachParent() && pDamageObj==pDamageObj->GetAttachParent()))
|
|
{
|
|
|
|
//If ever re-enabled this will need fixing to use the latest type of collision records
|
|
#if 0 // CS
|
|
static float OBJECT_COLLISION_DAMAGE_MULTIPLIER = 10.0f;
|
|
fTempDamageImpulse *= OBJECT_COLLISION_DAMAGE_MULTIPLIER/fCollisionDamageThreshold;
|
|
|
|
Vector3 vecTempStart = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
|
|
phIntersection tempIntersection;
|
|
tempIntersection.Set(pDamageObj->GetCurrentPhysicsInst(), RCC_VEC3V(pColRecord->m_MyCollisionPos),
|
|
RCC_VEC3V(pColRecord->m_MyCollisionNormal), 0.0f, 1.0f, 0, 0, (u16)pColRecord->m_MyCollisionComponent());
|
|
|
|
if(pDamageObj->GetWeapon() && pDamageObj->GetWeapon()->GetWeaponType() == WEAPONTYPE_OBJECT)
|
|
{
|
|
CEntity* pParent = pPed->m_pCollisionEntity;
|
|
|
|
// look up the original owner of the projectile in the projectile manager
|
|
CProjectileData* pProj = CProjectileInfo::GetProjectile(pDamageObj);
|
|
if(pProj && pProj->m_pEntProjectileOwner)
|
|
pParent = pProj->m_pEntProjectileOwner;
|
|
|
|
float fObjectDamage;
|
|
CWeaponInfo* pWeaponInfo = pDamageObj->GetWeapon()->GetWeaponInfo();
|
|
if(pWeaponInfo)
|
|
{
|
|
// Use the constant weapon damage from the CWeaponInfo
|
|
fObjectDamage = static_cast<float>(pWeaponInfo->GetWeaponDamage(pPed));
|
|
}
|
|
else
|
|
{
|
|
fObjectDamage = fTempDamageImpulse;
|
|
}
|
|
|
|
CWeaponDamage::GeneratePedDamageEvent(pParent, pPed, WEAPONTYPE_OBJECT, fObjectDamage, &vecTempStart, &tempIntersection);
|
|
}
|
|
else
|
|
{
|
|
CWeaponDamage::GeneratePedDamageEvent(pPed->m_pCollisionEntity, pPed, WEAPONTYPE_FALL, fTempDamageImpulse, &vecTempStart, &tempIntersection);
|
|
}
|
|
#endif // 0
|
|
}
|
|
else
|
|
{
|
|
u32 frameCount = fwTimer::GetSystemFrameCount();
|
|
|
|
// Do not allow object collision event to trigger consecutively
|
|
bool bGenerateEvent = ( m_CollisionEventFrame + 1 ) < frameCount;
|
|
if( bGenerateEvent )
|
|
{
|
|
m_CollisionEventFrame = frameCount;
|
|
|
|
CEventObjectCollision event(
|
|
(u16)pColRecord->m_MyCollisionComponent,
|
|
pDamageObj,
|
|
pColRecord->m_MyCollisionNormal,
|
|
pColRecord->m_MyCollisionPos,
|
|
fMoveBlendRatio);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Audio
|
|
if( pPed->GetPlayerInfo() && pCollisionHistory->HasCollidedWithAnyOfTypes(ENTITY_TYPE_MASK_BUILDING | ENTITY_TYPE_MASK_OBJECT) )
|
|
{
|
|
static float PLAYER_BUMP_SOUND_THRESHOLD_MED = 3.0f;
|
|
static float PLAYER_BUMP_SOUND_THRESHOLD_LOW = 1.0f;
|
|
static u32 nLastBumpSoundEvent = 0;
|
|
|
|
float fRange = 0.0f; // sound event range
|
|
float fMaxImpulse = pCollisionHistory->GetMaxCollisionImpulseMagLastFrameOfTypes(ENTITY_TYPE_MASK_BUILDING | ENTITY_TYPE_MASK_OBJECT);
|
|
|
|
if(fMaxImpulse > PLAYER_BUMP_SOUND_THRESHOLD_LOW && fwTimer::GetTimeInMilliseconds() > nLastBumpSoundEvent + 1000)
|
|
{
|
|
if(fMaxImpulse > PLAYER_BUMP_SOUND_THRESHOLD_MED)
|
|
fRange = SOUNDRANGE_CLEARLY_AUDIBLE;
|
|
else
|
|
fRange = SOUNDRANGE_BARELY_AUDIBLE;
|
|
|
|
nLastBumpSoundEvent = fwTimer::GetTimeInMilliseconds();
|
|
}
|
|
}
|
|
|
|
// reset flag
|
|
m_bAlreadyHitByCar = false;
|
|
}
|
|
|
|
dev_float BLEEDER_DAMAGE_INTERVAL = 5.0f;
|
|
dev_float BLEEDER_DAMAGE_AMOUNT = 6.0f;
|
|
//dev_u32 BLEEDER_MIN_TIME = 5000;
|
|
|
|
CPedHealthScanner::CPedHealthScanner()
|
|
: m_DamageEntity(NETWORK_INVALID_OBJECT_ID)
|
|
, m_fApplyDamageTimer(BLEEDER_DAMAGE_INTERVAL)
|
|
, m_fTimeInValidHunchedTransition(0.f)
|
|
, m_nFramesSinceLastUpdate(1)
|
|
, m_bIsFallingOver(false)
|
|
{
|
|
|
|
}
|
|
|
|
void CPedHealthScanner::Scan(CPed& ped)
|
|
{
|
|
if (ped.IsPlayer())
|
|
{
|
|
if (ped.GetHealth() < ped.GetHurtHealthThreshold())
|
|
{
|
|
if (ped.GetHurtEndTime() == 0 && !ped.GetIsSwimming())
|
|
{
|
|
static const int iBleedDuration = 15000;
|
|
ped.SetHurtEndTime(fwTimer::GetTimeInMilliseconds() + iBleedDuration);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ped.SetHurtEndTime(0); // Allow more bleed once we reach threshold again
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (ped.GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) || !ped.CanBeInHurt() || ped.IsDead()) //
|
|
return;
|
|
|
|
// We don't need to check this every frame really, but beware of the timeslice also
|
|
if (m_nFramesSinceLastUpdate++ < 1) // ugly prevent LHS
|
|
return;
|
|
|
|
// Keep caching the last damager as this bugger might not always be valid
|
|
CEntity* pEntity = ped.GetWeaponDamageEntityPedSafe();
|
|
if (pEntity && pEntity->GetIsTypePed())
|
|
{
|
|
if (((CPed*)pEntity)->GetNetworkObject())
|
|
{
|
|
m_DamageEntity = ((CPed*)pEntity)->GetNetworkObject()->GetObjectID();
|
|
}
|
|
}
|
|
|
|
m_nFramesSinceLastUpdate = 0;
|
|
const float fFrameTimeConsideringFrameSkip = fwTimer::GetTimeStep() * 2.f;
|
|
|
|
// Well... These tunes are "expensive" in non BANK and we probably don't need them anymore
|
|
static const int MinTimeInHunched = 5000;
|
|
static const int MaxTimeInHunched = 10000;
|
|
static const float fBleedDamageAmount = 15.f;
|
|
static const float fBleedValidTransitionTimeThreshold = 2.f;
|
|
static const float fLungsValidTransitionTimeThreshold = 0.75f;
|
|
static const float fBleedMaxVelocityThreshold = 0.1f;
|
|
static const float fLungsMaxVelocityThreshold = 0.3f;
|
|
//TUNE_GROUP_INT(WRITHE_TWEAK, MinTimeInHunched, 5000, 0, 10000, 100);
|
|
//TUNE_GROUP_INT(WRITHE_TWEAK, MaxTimeInHunched, 10000, 0, 25000, 100);
|
|
//TUNE_GROUP_FLOAT(BLEEDING_TWEAK, BleedDamageAmount, 15.f, 0.f, 500.f, 100.f);
|
|
//TUNE_GROUP_FLOAT(BLEEDING_TWEAK, BleedValidTransitionThreshold, 2.f, 0.f, 10.f, 100.f);
|
|
|
|
float fTimeInValidHunchedTransition = m_fTimeInValidHunchedTransition; // Local variable to prevent LHS when comparing since we write to member variable
|
|
bool bIsInValidHunchTransitionState = (ped.GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun) && ped.CanDoHurtTransition());
|
|
if (bIsInValidHunchTransitionState)
|
|
fTimeInValidHunchedTransition += fFrameTimeConsideringFrameSkip;
|
|
else
|
|
fTimeInValidHunchedTransition = 0.f;
|
|
|
|
m_fTimeInValidHunchedTransition = fTimeInValidHunchedTransition;
|
|
Assertf(ped.GetPedIntelligence(), "Invalid PedIntelligence in CPedHealthScanner::Scan()");
|
|
|
|
if (!ped.IsHurtHealthThreshold())
|
|
{
|
|
// Bleeding when damaged
|
|
if (ped.GetHurtEndTime() == 0)
|
|
{
|
|
if (*(int*)&m_fApplyDamageTimer > 0) // Yes super neat but >= cannot be tested just like this, as, -0.0f != 0.0f
|
|
{
|
|
if (bIsInValidHunchTransitionState)
|
|
m_fApplyDamageTimer -= fFrameTimeConsideringFrameSkip;
|
|
}
|
|
else
|
|
{
|
|
m_fApplyDamageTimer += BLEEDER_DAMAGE_INTERVAL;
|
|
|
|
// IsHurtHealthThreshold is dependent on not having scripts fiddling around with the health
|
|
// Therefor we might actually bleed to death unless we check for that here, maybe death is fine
|
|
// but it was never the intention according to the design
|
|
if (ped.GetHealth() - fBleedDamageAmount > ped.GetInjuredHealthThreshold())
|
|
{
|
|
CEntity* pCurrentTarget = ped.GetWeaponDamageEntityPedSafe();
|
|
if (!pCurrentTarget || !pCurrentTarget->GetIsTypePed())
|
|
{
|
|
if (NetworkInterface::IsGameInProgress() && m_DamageEntity != NETWORK_INVALID_OBJECT_ID)
|
|
pCurrentTarget = NetworkUtils::GetPedFromNetworkObject(NetworkInterface::GetNetworkObject(m_DamageEntity));
|
|
}
|
|
|
|
CPedDamageCalculator damageCalculator(pCurrentTarget, fBleedDamageAmount, WEAPONTYPE_BLEEDING, 0, false);
|
|
CEventDamage event(pCurrentTarget, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_BLEEDING);
|
|
|
|
// Don't apply the bleeding if we already have a damage event, as it will throw out valid response tasks
|
|
if (!ped.GetPedIntelligence()->HasEventOfType(&event))
|
|
{
|
|
damageCalculator.ApplyDamageAndComputeResponse(&ped, event.GetDamageResponseData(), false);
|
|
if ((event.GetDamageResponseData().m_bKilled || event.GetDamageResponseData().m_bInjured))
|
|
{
|
|
Assertf(0, "Bleeder damage killed the ped, this shouldn't happend in this code path");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ped.IsBodyPartDamaged(CPed::DAMAGED_GOTOWRITHE) && !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_DisableGoToWritheWhenInjured ) && !ped.GetPedConfigFlag(CPED_CONFIG_FLAG_CanBeArrested)) // To end up here we are already shot somewhere so this is safe
|
|
{
|
|
bool bAllowGoToWrithe = false;
|
|
|
|
//! If OnlyWritheFromWeaponDamage is set & we have been damaged by a weapon in the last 5 secs, allow writhe.
|
|
if( ped.GetPedConfigFlag( CPED_CONFIG_FLAG_OnlyWritheFromWeaponDamage ) )
|
|
{
|
|
float fWritheThreshold = ped.GetPedHealthInfo() ? ped.GetPedHealthInfo()->GetWritheFromBulletDamageTheshold() : 200.0f;
|
|
if(ped.GetHealth() < fWritheThreshold )
|
|
{
|
|
for(int i = 0; i < ped.GetWeaponDamageInfoCount() && !bAllowGoToWrithe; ++i)
|
|
{
|
|
const CPhysical::WeaponDamageInfo &weaponDamageInfo = ped.GetWeaponDamageInfo(i);
|
|
if(fwTimer::GetTimeInMilliseconds() < (weaponDamageInfo.weaponDamageTime + 5000))
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo< CWeaponInfo >(weaponDamageInfo.weaponDamageHash);
|
|
if(pWeaponInfo)
|
|
{
|
|
switch(pWeaponInfo->GetDamageType())
|
|
{
|
|
case DAMAGE_TYPE_BULLET:
|
|
case DAMAGE_TYPE_BULLET_RUBBER:
|
|
case DAMAGE_TYPE_EXPLOSIVE:
|
|
case DAMAGE_TYPE_FIRE:
|
|
bAllowGoToWrithe = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bAllowGoToWrithe = true;
|
|
}
|
|
|
|
|
|
if(bAllowGoToWrithe)
|
|
{
|
|
// Make sure we roll the dice if peds are falling over but not hurt enough
|
|
CTaskNMControl* pNMTask = smart_cast<CTaskNMControl*>(ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL));
|
|
if (pNMTask)
|
|
{
|
|
bool bWasFallingOver = m_bIsFallingOver;
|
|
if (pNMTask->IsFeedbackFlagSet(CTaskNMControl::BALANCE_FAILURE))
|
|
{
|
|
static dev_float WRITHE_CHANCE = 0.5f;
|
|
if (!bWasFallingOver && fwRandom::GetRandomNumberInRange(0.f, 1.f) < WRITHE_CHANCE)
|
|
ped.ReportBodyPartDamage(CPed::DAMAGED_GOTOWRITHE);
|
|
|
|
m_bIsFallingOver = true;
|
|
}
|
|
else
|
|
{
|
|
m_bIsFallingOver = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // ped health is hurt and they can go to hurt mode
|
|
{
|
|
// Set default check values
|
|
float fMinTime = fBleedValidTransitionTimeThreshold;
|
|
float fMaxVelocity = fBleedMaxVelocityThreshold;
|
|
|
|
// If lungs are damaged
|
|
if( ped.IsBodyPartDamaged(CPed::DAMAGED_LUNGS) )
|
|
{
|
|
// ped can no longer use cover effectively
|
|
// NOTE: without this the peds may choose to go right back to cover they bail from
|
|
ped.GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanUseCover);
|
|
|
|
// Make the ped cough due to lung damage
|
|
const audSpeechAudioEntity* pAudioEnt = ped.GetSpeechAudioEntity();
|
|
if( pAudioEnt && !pAudioEnt->IsPainPlaying() && pAudioEnt->ShouldPlayCoughForDamagedLungs() )
|
|
{
|
|
audDamageStats damageStats;
|
|
damageStats.DamageReason = AUD_DAMAGE_REASON_COUGH;
|
|
ped.GetSpeechAudioEntity()->InflictPain(damageStats);
|
|
}
|
|
|
|
// adjust check settings
|
|
fMinTime = fLungsValidTransitionTimeThreshold;
|
|
fMaxVelocity = fLungsMaxVelocityThreshold;
|
|
}
|
|
|
|
// Alright see if we should trigger the transition to hunched
|
|
if (!ped.HasHurtStarted() && fTimeInValidHunchedTransition > fMinTime)
|
|
{
|
|
if (ped.GetVelocity().Mag2() > fMaxVelocity && fBleedValidTransitionTimeThreshold < fMinTime * 4.f)
|
|
{
|
|
// This stuff does not seem to help, either add a flag or something else
|
|
// ped.GetMotionData()->SetDesiredMoveBlendRatio(0.f);
|
|
// ped.GetMotionData()->StopAllMotion();
|
|
}
|
|
else // Possibly check this one also IsBodyPartDamage(CPed::DAMAGED_GOTOWRITHE), we might be in getup task mode
|
|
{
|
|
CTask* pTaskActive = ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL);
|
|
if (!pTaskActive)
|
|
{
|
|
// We might have waited for this transition for quite a while so we set the timer again
|
|
ped.SetHurtEndTime(fwRandom::GetRandomNumberInRange(0, MaxTimeInHunched - MinTimeInHunched) + MinTimeInHunched + fwTimer::GetTimeInMilliseconds());
|
|
|
|
CEntity* pTarget = ped.GetWeaponDamageEntityPedSafe();
|
|
if (!pTarget || !pTarget->GetIsTypePed())
|
|
{
|
|
if (NetworkInterface::IsGameInProgress() && m_DamageEntity != NETWORK_INVALID_OBJECT_ID)
|
|
pTarget = NetworkUtils::GetPedFromNetworkObject(NetworkInterface::GetNetworkObject(m_DamageEntity));
|
|
}
|
|
|
|
CEventHurtTransition hurtEvent(pTarget);
|
|
ped.GetPedIntelligence()->AddEvent(hurtEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if we should go into writhe or trigger the signal for NM to start the transition next time damaged
|
|
if (!ped.IsInjured()) // Can't let stunned enemies to bleed out and stuff
|
|
{
|
|
if (ped.GetHurtEndTime() == 0 &&
|
|
!ped.GetPedIntelligence()->GetTaskMelee() &&
|
|
!ped.GetPedIntelligence()->GetTaskWrithe())
|
|
{
|
|
CTask* pTask = FindControllingTask(&ped);
|
|
if ((pTask && pTask->IsConsideredInCombat()) || ped.GetPedIntelligence()->GetTaskCombat() || ped.GetPedIntelligence()->GetTaskGun())
|
|
{
|
|
ped.SetHurtEndTime(fwRandom::GetRandomNumberInRange(0, MaxTimeInHunched - MinTimeInHunched) + MinTimeInHunched + fwTimer::GetTimeInMilliseconds());
|
|
ped.SetPedResetFlag(CPED_RESET_FLAG_HurtThisFrame, true);
|
|
}
|
|
}
|
|
|
|
if (ped.HasHurtStarted())
|
|
{
|
|
// Ped can no longer use cover while in hurt state
|
|
ped.GetPedIntelligence()->GetCombatBehaviour().ClearFlag(CCombatData::BF_CanUseCover);
|
|
ped.GetInventory()->GetAmmoRepository().SetUsingInfiniteAmmo(true);
|
|
ped.GetInventory()->GetAmmoRepository().SetUsingInfiniteClips(true);
|
|
|
|
if (ped.GetHurtEndTime() > 0 && ped.GetHurtEndTime() <= fwTimer::GetTimeInMilliseconds() &&
|
|
!ped.GetPedIntelligence()->GetTaskMelee() &&
|
|
!ped.GetPedIntelligence()->GetTaskWrithe())
|
|
{
|
|
CTask* pTaskActive = ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL);
|
|
if (!pTaskActive && CTaskWrithe::StreamReqResourcesInOldAndBad(&ped, CTaskWrithe::NEXT_STATE_COLLAPSE))
|
|
{
|
|
CEntity* pTarget = ped.GetWeaponDamageEntityPedSafe();
|
|
if (!pTarget || !pTarget->GetIsTypePed())
|
|
{
|
|
if (NetworkInterface::IsGameInProgress() && m_DamageEntity != NETWORK_INVALID_OBJECT_ID)
|
|
pTarget = NetworkUtils::GetPedFromNetworkObject(NetworkInterface::GetNetworkObject(m_DamageEntity));
|
|
}
|
|
|
|
//! Don't set target unless we have one as it'll assert otherwise.
|
|
CWeaponTarget Target;
|
|
if(pTarget)
|
|
{
|
|
Target.SetEntity(pTarget);
|
|
}
|
|
|
|
CEventWrithe event(Target);
|
|
if (!ped.GetPedIntelligence()->HasEventOfType(&event))
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CTask* CPedHealthScanner::FindControllingTask(CPed* pPed)
|
|
{
|
|
CTask* pTask = NULL;
|
|
if (pPed)
|
|
{
|
|
pTask = pPed->GetPedIntelligence()->GetTaskAtPriority(PED_TASK_PRIORITY_EVENT_RESPONSE_NONTEMP);
|
|
|
|
if (!pTask)
|
|
pTask = pPed->GetPedIntelligence()->GetTaskAtPriority(PED_TASK_PRIORITY_PRIMARY);
|
|
if (!pTask)
|
|
pTask = pPed->GetPedIntelligence()->GetTaskAtPriority(PED_TASK_PRIORITY_DEFAULT);
|
|
}
|
|
|
|
return pTask;
|
|
}
|
|
|
|
bool IsPlayerOrPlayersVehicle(const CEntity* const pEntity)
|
|
{
|
|
bool bIsPlayerOrPlayersVehicle = false;
|
|
|
|
if(pEntity)
|
|
{
|
|
if(pEntity->GetIsTypeVehicle())
|
|
{
|
|
const CPed* const pDriver = static_cast<const CVehicle*>(pEntity)->GetDriver();
|
|
bIsPlayerOrPlayersVehicle = (pDriver && pDriver->IsPlayer());
|
|
}
|
|
else if(pEntity->GetIsTypePed())
|
|
{
|
|
bIsPlayerOrPlayersVehicle = static_cast<const CPed*>(pEntity)->IsPlayer();
|
|
}
|
|
}
|
|
|
|
return bIsPlayerOrPlayersVehicle;
|
|
}
|
|
|
|
#if __BANK
|
|
void CCollisionEventScanner::AddRagdollDamageWidgets(rage::bkBank& bank)
|
|
{
|
|
bank.PushGroup("Ragdoll Damage");
|
|
// Helps to just see the health of a ped without any other debug info sometimes.
|
|
bank.AddToggle("Display ped health", &CPhysics::ms_bDisplayPedHealth);
|
|
// Useful to figure out if the fall height is being updated correctly.
|
|
bank.AddToggle("Display fall height", &CPhysics::ms_bDisplayPedFallHeight);
|
|
// Define the mass categories for different damage bands (minimum mass for each category).
|
|
bank.AddSlider("Vehicle mass heavy", &RAGDOLL_DAMAGE_VEHICLE_MASS_HEAVY, 0.0f, 1000000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle mass medium", &RAGDOLL_DAMAGE_VEHICLE_MASS_MEDIUM, 0.0f, 1000000.0f, 1.0f);
|
|
// Define the base damage inflicted in each of the different velocity bands for vehicle damage.
|
|
bank.AddSlider("Vehicle damage crazy fast", &RAGDOLL_DAMAGE_VEHICLE_CRAZY, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle damage fast", &RAGDOLL_DAMAGE_VEHICLE_FAST, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle damage medium", &RAGDOLL_DAMAGE_VEHICLE_MEDIUM, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle damage slow", &RAGDOLL_DAMAGE_VEHICLE_SLOW, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle damage crawling", &RAGDOLL_DAMAGE_VEHICLE_CRAWLING, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle speed crazy fast", &RAGDOLL_SPEED_VEHICLE_CRAZY, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle speed fast", &RAGDOLL_SPEED_VEHICLE_FAST, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle speed medium", &RAGDOLL_SPEED_VEHICLE_MEDIUM, 0.0f, 1000.0f, 1.0f);
|
|
bank.AddSlider("Vehicle speed slow", &RAGDOLL_SPEED_VEHICLE_SLOW, 0.0f, 1000.0f, 1.0f);
|
|
// Angle impact normal makes with horizontal to decide when to apply vehicle damage.
|
|
bank.AddSlider("Vehicle impact angle param", &RAGDOLL_DAMAGE_VEHICLE_IMPACT_ANGLE_PARAM, 0.0f, 1.0f, 0.01f);
|
|
// Dot product value to decide when a ped is under a vehicle.
|
|
bank.AddSlider("Under vehicle impact angle param", &RAGDOLL_DAMAGE_UNDER_VEHICLE_IMPACT_ANGLE_PARAM, -1.0f, 1.0f, 0.01f);
|
|
// Minimum velocity of train before damage is applied.
|
|
bank.AddSlider("Train vel threshold", &RAGDOLL_DAMAGE_TRAIN_VEL_THRESHOLD, 0.0f, 100.0f, 0.1f);
|
|
// If player is within this range at end of bounding box during impact then we count it as a front on impact.
|
|
bank.AddSlider("Train bounding box 'y' adjust", &RAGDOLL_DAMAGE_TRAIN_BB_ADJUST_Y, 0.0f, 10.0f, 0.01f);
|
|
// Modifiers for the various damage inflictor types. This is related to the mass and is multiplied by the magnitude of the relative
|
|
// velocity to compute a damage value.
|
|
bank.AddSlider("Damage mult train", &RAGDOLL_DAMAGE_MULTIPLIER_TRAIN, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult plane", &RAGDOLL_DAMAGE_MULTIPLIER_PLANE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult bike", &RAGDOLL_DAMAGE_MULTIPLIER_BIKE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult light vehicle", &RAGDOLL_DAMAGE_MULTIPLIER_LIGHT_VEHICLE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult medium vehicle", &RAGDOLL_DAMAGE_MULTIPLIER_MEDIUM_VEHICLE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage multiplier heavy vehicle", &RAGDOLL_DAMAGE_MULTIPLIER_HEAVY_VEHICLE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult under vehicle", &RAGDOLL_DAMAGE_MULTIPLIER_UNDER_VEHICLE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult fall", &RAGDOLL_DAMAGE_MULTIPLIER_FALL, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult fall land foot", &RAGDOLL_DAMAGE_MULTIPLIER_FALL_LAND_FOOT, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult general collision", &RAGDOLL_DAMAGE_MULTIPLIER_GENERAL_COLLISION, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult sliding damage", &RAGDOLL_DAMAGE_MULTIPLIER_SLIDING_COLLISION, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult ped", &RAGDOLL_DAMAGE_MULTIPLIER_PED, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult light object", &RAGDOLL_DAMAGE_MULTIPLIER_OBJECT_LIGHT, 0.0, 100.0f, 0.1f);
|
|
bank.AddSlider("Damage mult heavy object", &RAGDOLL_DAMAGE_MULTIPLIER_OBJECT_HEAVY, 0.0, 100.0f, 0.1f);
|
|
// Minimum amount to fall before ped will take any fall damage.
|
|
bank.AddSlider("Ped fall height threshold", &RAGDOLL_DAMAGE_PED_FALL_HEIGHT_THRESHOLD, 0.0f, 1000.0f, 0.01f);
|
|
// Minimum tangential velocity before inflicting sliding damage.
|
|
bank.AddSlider("Min speed for sliding damage", &RAGDOLL_DAMAGE_MIN_SPEED_SLIDING_DAMAGE, 0.0f, 100.0f, 0.1f);
|
|
// Minimum relative speed at which a non-zero damage event will be created for each inflictor type.
|
|
bank.AddSlider("Min speed fall", &RAGDOLL_DAMAGE_MIN_SPEED_FALL, 0.0f, 100.0f, 0.01f);
|
|
bank.AddSlider("Min speed ped", &RAGDOLL_DAMAGE_MIN_SPEED_PED, 0.0f, 100.0f, 0.01f);
|
|
bank.AddSlider("Min speed vehicle", &RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE, 0.0f, 100.0f, 0.01f);
|
|
// Minimum mass of object which causes damage to ped.
|
|
bank.AddSlider("Min object mass", &RAGDOLL_DAMAGE_MIN_OBJECT_MASS, 0.0f, 10000.0f, 0.1f);
|
|
// Scaling factors to modify various damage types when they happen to a player ped.
|
|
bank.AddSlider("Player ped scale vehicle damage", &RAGDOLL_DAMAGE_MULTIPLIER_PLAYER_VEHICLE_SCALE, 0.0f, 100.0f, 0.1f);
|
|
// Scaling factors to modify various damage types when they happen to an animal.
|
|
bank.AddSlider("Scale animal damage", &RAGDOLL_DAMAGE_MULTIPLIER_ANIMAL_SCALE, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Vehicle impact damage time limit", &RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT, 0, 100000, 1);
|
|
bank.PopGroup(); // "Ragdoll Damage"
|
|
}
|
|
#endif // __BANK
|
|
|
|
void CCollisionEventScanner::ComputeRelativeSpeedFromImpulse(const CPed* pPed, const CEntity* pEntity, const float fImpulseMag, const Vector3& vPedNormal,
|
|
float& fRelSpeed, float& fPedEntRelVelMag, float& fPedEntTangVelMagSq)
|
|
{
|
|
fRelSpeed = fImpulseMag * pPed->GetRagdollInst()->GetArchetype()->GetInvMass();
|
|
// Also work out the magnitude of the velocity difference between this ped and the entity doing the damage which will
|
|
// likely be different (NB: this is the difference in the linear velocities of the ped and the inflictor entity, not just
|
|
// the velocity along the collision normal which is given by fRelSpeed).
|
|
fPedEntRelVelMag = 0.0f;
|
|
fPedEntTangVelMagSq = 0.0f; // Tangential / sliding component of relative velocity.
|
|
Vector3 vPedEntRelVel(VEC3_ZERO);
|
|
if(pEntity && pEntity->GetIsPhysical())
|
|
{
|
|
const CPhysical* pPhysical = static_cast<const CPhysical*>(pEntity);
|
|
vPedEntRelVel = pPed->GetVelocity() - pPhysical->GetVelocity();
|
|
}
|
|
else
|
|
{
|
|
vPedEntRelVel = pPed->GetVelocity();
|
|
}
|
|
fPedEntRelVelMag = vPedEntRelVel.Dot(-vPedNormal);
|
|
fPedEntTangVelMagSq = vPedEntRelVel.Mag2() - fPedEntRelVelMag*fPedEntRelVelMag;
|
|
|
|
// We can get valid contacts with zero impulse applied due to the velocity solve happening at the start of each physics update. If
|
|
// this happens, just estimate the impulse from the velocity difference.
|
|
if(fImpulseMag < SMALL_FLOAT)
|
|
{
|
|
fRelSpeed = fPedEntRelVelMag;
|
|
}
|
|
}
|
|
|
|
float CCollisionEventScanner::ComputeDamageFromVehicle(const CPed* pPed, const CVehicle* pVehicle, const int nComponent, const float fPedEntRelVelMag,
|
|
const Vector3& vPedNormal, const bool bPedStanding, const float fRelSpeed)
|
|
{
|
|
float fDamage = 0.0f;
|
|
|
|
// Base damage is calculated from the relative velocity of the inflictor vehicle. The vehicle's velocity
|
|
// range is divided into 5 equal regions.
|
|
if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_CRAZY)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_CRAZY;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_FAST)
|
|
{
|
|
const float maxSpeedDiff = RAGDOLL_SPEED_VEHICLE_CRAZY - RAGDOLL_SPEED_VEHICLE_FAST;
|
|
const float maxDamageDiff = RAGDOLL_DAMAGE_VEHICLE_CRAZY - RAGDOLL_DAMAGE_VEHICLE_FAST;
|
|
|
|
float speedFactor = ( fPedEntRelVelMag - RAGDOLL_SPEED_VEHICLE_FAST ) / maxSpeedDiff;
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_FAST + ( speedFactor * maxDamageDiff );
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_MEDIUM)
|
|
{
|
|
const float maxSpeedDiff = RAGDOLL_SPEED_VEHICLE_FAST - RAGDOLL_SPEED_VEHICLE_MEDIUM;
|
|
const float maxDamageDiff = RAGDOLL_DAMAGE_VEHICLE_FAST - RAGDOLL_DAMAGE_VEHICLE_MEDIUM;
|
|
|
|
float speedFactor = ( fPedEntRelVelMag - RAGDOLL_DAMAGE_VEHICLE_MEDIUM ) / maxSpeedDiff;
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_MEDIUM + ( speedFactor * maxDamageDiff );
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_SLOW)
|
|
{
|
|
const float maxSpeedDiff = RAGDOLL_SPEED_VEHICLE_MEDIUM - RAGDOLL_SPEED_VEHICLE_SLOW;
|
|
const float maxDamageDiff = RAGDOLL_DAMAGE_VEHICLE_MEDIUM - RAGDOLL_DAMAGE_VEHICLE_SLOW;
|
|
|
|
float speedFactor = ( fPedEntRelVelMag - RAGDOLL_DAMAGE_VEHICLE_SLOW ) / maxSpeedDiff;
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_SLOW + ( speedFactor * maxDamageDiff );
|
|
}
|
|
else
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_CRAWLING;
|
|
}
|
|
|
|
// Scale the damage based on which of three different weight classes the inflictor vehicle is in if the
|
|
// vehicle isn't moving very slowly.
|
|
float fVehicleMassDamageMult = RAGDOLL_DAMAGE_MULTIPLIER_LIGHT_VEHICLE;
|
|
if(pVehicle->GetVelocity().Mag2() >= RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE*RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE)
|
|
{
|
|
float fVehMass = pVehicle->GetMass();
|
|
if(fVehMass > RAGDOLL_DAMAGE_VEHICLE_MASS_HEAVY)
|
|
{
|
|
fVehicleMassDamageMult = RAGDOLL_DAMAGE_MULTIPLIER_HEAVY_VEHICLE;
|
|
}
|
|
else if(fVehMass > RAGDOLL_DAMAGE_VEHICLE_MASS_MEDIUM)
|
|
{
|
|
fVehicleMassDamageMult = RAGDOLL_DAMAGE_MULTIPLIER_MEDIUM_VEHICLE;
|
|
}
|
|
}
|
|
fDamage *= fVehicleMassDamageMult;
|
|
|
|
// Don't apply damage to peds which are standing when the closing speed between the vehicle and ped
|
|
// is less than some threshold or when the vehicle is hardly moving unless the ped is under the vehicle.
|
|
float fScalarProd = vPedNormal.Dot(-ZAXIS);
|
|
bool bConsiderThisComponentUnderVeh = nComponent == RAGDOLL_BUTTOCKS
|
|
|| (nComponent >= RAGDOLL_SPINE0 && nComponent <= RAGDOLL_SPINE3)
|
|
|| nComponent == RAGDOLL_CLAVICLE_LEFT
|
|
|| nComponent == RAGDOLL_CLAVICLE_RIGHT
|
|
|| nComponent == RAGDOLL_NECK
|
|
|| nComponent == RAGDOLL_HEAD;
|
|
if(fScalarProd>RAGDOLL_DAMAGE_UNDER_VEHICLE_IMPACT_ANGLE_PARAM || !bConsiderThisComponentUnderVeh)
|
|
{
|
|
if( bPedStanding &&
|
|
( fRelSpeed < RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE ||
|
|
( pVehicle->GetVelocity().Mag2() < RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE * RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE &&
|
|
fRelSpeed < RAGDOLL_DAMAGE_VEHICLE_SLOW ) ) )
|
|
{
|
|
// Always take some damage when hit while ragdolling to create the AI response
|
|
if( pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE && pPed->GetPedType() == PEDTYPE_ANIMAL)
|
|
fDamage = 0.000001f;
|
|
else
|
|
fDamage = 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_UNDER_VEHICLE;
|
|
}
|
|
|
|
// Reduce damage if this ped is a player.
|
|
if(pPed->IsPlayer())
|
|
{
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_PLAYER_VEHICLE_SCALE;
|
|
}
|
|
|
|
return fDamage;
|
|
}
|
|
|
|
|
|
extern f32 g_HealthLostForLowPain;
|
|
|
|
void CCollisionEventScanner::ProcessRagdollImpact(CPed* pPed, float fMag, CEntity* pEntity, const Vector3& vPedNormal, int nComponent,
|
|
phMaterialMgr::Id nMaterialId)
|
|
{
|
|
// TODO: Move these declarations back to their original place within the code when removing the old damage code.
|
|
u32 weaponHash;
|
|
float fDamage;
|
|
float fKillFallHeight = KILL_FALL_HEIGHT;
|
|
float fOriginalHealth;
|
|
|
|
pPed->ProcessFallCollision();
|
|
|
|
CPhysical* pPhysical = (pEntity && pEntity->GetIsPhysical()) ? static_cast<CPhysical*>(pEntity) : nullptr;
|
|
|
|
// Don't want to take any damage from objects that we are attached to.
|
|
if(pPhysical)
|
|
{
|
|
if(pPhysical->GetAttachmentExtension() && pPhysical->GetAttachmentExtension()->GetAttachParent() == pPed)
|
|
{
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->ped is attached to %s[%p], ignoring damage", pEntity->GetModelName(), pEntity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Work out the relative speed difference between this ped and pEntity from the impulse.
|
|
float fRelSpeed = 0.0f;
|
|
float fPedEntRelVelMag = 0.0f;
|
|
float fPedEntTangVelMagSq = 0.0f;
|
|
ComputeRelativeSpeedFromImpulse(pPed, pEntity, fMag, vPedNormal, fRelSpeed, fPedEntRelVelMag, fPedEntTangVelMagSq);
|
|
|
|
// we can only process ragdoll impacts on clones when they are already running an NM task, otherwise the ped must wait for an update to be sure it
|
|
// should be running an NM task. We also need to keep the task running, hence the HandleRagdollImpact method.
|
|
if(pPed->IsNetworkClone() && pPhysical)
|
|
{
|
|
CTask* pTaskActive = pPed->GetPedIntelligence()->GetTaskActive();
|
|
// if the ped is already running an NM control task, inform the task about the collision
|
|
if(pTaskActive && pTaskActive->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
|
{
|
|
// No need to poke the task here. Local reactions should keep the ped in ragdoll
|
|
}
|
|
else
|
|
{
|
|
// pass damage events through to the dying task so that the dead ped reacts
|
|
if (pTaskActive && pTaskActive->GetTaskType() == CTaskTypes::TASK_DYING_DEAD)
|
|
{
|
|
CDamageInfo damageInfo(WEAPONTYPE_FALL, NULL, NULL, NULL, 0.0f, NULL);
|
|
pTaskActive->RecordDamage(damageInfo);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Detect if our ped has fallen and if so whether the fall should be fatal.
|
|
fKillFallHeight = KILL_FALL_HEIGHT;
|
|
if(pPed->IsAPlayerPed())
|
|
fKillFallHeight = PLAYER_KILL_FALL_HEIGHT;
|
|
float fActualFallHeight = pPed->GetFallingHeight() - pPed->GetTransform().GetPosition().GetZf();
|
|
// NB - If ped isn't attached (important as falling height is only useful if not attached) and has fallen more than
|
|
// some threshold, set the damage to be fatal.
|
|
bool bHasFallen = !pPed->GetAttachParent() && fActualFallHeight>RAGDOLL_DAMAGE_PED_FALL_HEIGHT_THRESHOLD;
|
|
|
|
// As soon as the AI system turns off the "in air" flag, reset the fall height so we don't continue to take fall damage
|
|
// while rolling around on the ground.
|
|
if(bHasFallen && !pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsInTheAir ))
|
|
{
|
|
pPed->SetFallingHeight(pPed->GetTransform().GetPosition().GetZf());
|
|
}
|
|
|
|
// We want to be alter damage magnitude based on context like whether the ped is standing up.
|
|
bool bPedStanding = true;
|
|
Assert(pPed->GetPedIntelligence());
|
|
if(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL))
|
|
{
|
|
CTaskNMControl* pTask = smart_cast<CTaskNMControl*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL));
|
|
bPedStanding = !pTask->IsFeedbackFlagSet(CTaskNMControl::BALANCE_FAILURE);
|
|
}
|
|
|
|
#if __BANK
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact--> %s has impacted with %s[%p]: bPedStanding - %s, bHasFallen - %s, fActualFallHeight - %.2f, fMag - %.2f, fRelSpeed - %.2f, fPedEntRelVelMag - %.2f, fPedEntTangVelMagSq - %.2f,",
|
|
AILogging::GetDynamicEntityNameSafe(pPed),
|
|
pEntity ? (pEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast<const CDynamicEntity*>(pEntity)) : pEntity->GetModelName()) : "NULL",
|
|
pEntity, AILogging::GetBooleanAsString(bPedStanding), AILogging::GetBooleanAsString(bHasFallen), fActualFallHeight, fMag, fRelSpeed, fPedEntRelVelMag, fPedEntTangVelMagSq);
|
|
#endif
|
|
|
|
// Scale the damage appropriately for the entity type inflicting the damage:
|
|
fDamage = 0.0f;
|
|
fOriginalHealth = pPed->GetHealth();
|
|
weaponHash = WEAPONTYPE_FALL;
|
|
if(pEntity)
|
|
{
|
|
switch(pEntity->GetType())
|
|
{
|
|
//////////////////////
|
|
case ENTITY_TYPE_VEHICLE:
|
|
//////////////////////
|
|
{
|
|
weaponHash = WEAPONTYPE_RUNOVERBYVEHICLE;
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pEntity);
|
|
|
|
// Don't want to take too much damage from stationary cars when a ragdoll is thrown into one.
|
|
if(pVehicle->GetVelocity().Mag2() < 1.0f)
|
|
{
|
|
fPedEntRelVelMag = Min(fPedEntRelVelMag, RAGDOLL_SPEED_VEHICLE_SLOW);
|
|
}
|
|
|
|
switch(pVehicle->GetVehicleType())
|
|
{
|
|
case VEHICLE_TYPE_TRAIN:
|
|
{
|
|
// If ped hits train and train is moving over threshold
|
|
// ... then we work out a force proportional to the relative speed of the train to the ped
|
|
if(pVehicle->GetVelocity().Mag2()>RAGDOLL_DAMAGE_TRAIN_VEL_THRESHOLD*RAGDOLL_DAMAGE_TRAIN_VEL_THRESHOLD
|
|
&& !pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_RidingTrain)
|
|
&& pVehicle->GetPhysArch()&&pVehicle->GetPhysArch()->GetBound())
|
|
{
|
|
// Compare peds position with bounding box of train
|
|
Vector3 vBoundSize = VEC3V_TO_VECTOR3(pVehicle->GetPhysArch()->GetBound()->GetBoundingBoxSize());
|
|
Vector3 vLocalImpact = VEC3V_TO_VECTOR3(pVehicle->GetTransform().UnTransform(pPed->GetTransform().GetPosition()));
|
|
// Apply much more damage when roughly at the front of the train.
|
|
if(vLocalImpact.y+RAGDOLL_DAMAGE_TRAIN_BB_ADJUST_Y > (vBoundSize.y*0.5f) && rage::Abs(vLocalImpact.x)<vBoundSize.x*0.5f)
|
|
{
|
|
fDamage = fRelSpeed*DotProduct(pVehicle->GetVelocity(), VEC3V_TO_VECTOR3(pVehicle->GetTransform().GetB()));
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_TRAIN;
|
|
}
|
|
else
|
|
{
|
|
// Base damage is calculated from the relative velocity of the inflictor vehicle. The vehicle's velocity
|
|
// range is divided into 5 equal regions.
|
|
if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_CRAZY)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_CRAZY;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_FAST)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_FAST;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_MEDIUM)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_MEDIUM;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_SLOW)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_SLOW;
|
|
}
|
|
else
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_CRAWLING;
|
|
}
|
|
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_TRAIN;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VEHICLE_TYPE_PLANE:
|
|
{
|
|
// Base damage is calculated from the relative velocity of the inflictor vehicle. The vehicle's velocity
|
|
// range is divided into 5 equal regions.
|
|
if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_CRAZY)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_CRAZY;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_FAST)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_FAST;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_MEDIUM)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_MEDIUM;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_SPEED_VEHICLE_SLOW)
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_SLOW;
|
|
}
|
|
else
|
|
{
|
|
fDamage = RAGDOLL_DAMAGE_VEHICLE_CRAWLING;
|
|
}
|
|
|
|
// Temporary fix to prevent ped from dying when colliding with wing.
|
|
CTask* pTask = pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE_SEAT);
|
|
if(pTask && pTask->GetState() == CTaskExitVehicleSeat::State_JumpOutOfSeat)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_PLANE;
|
|
}
|
|
|
|
// The "no ragdoll" poly flag is set on interiors of vehicles and can be used so that ragdolls don't take any
|
|
// more damage than just falling on the ground normally.
|
|
if(PGTAMATERIALMGR->GetPolyFlagNoRagdoll(nMaterialId))
|
|
{
|
|
fDamage = 0.0f;
|
|
if(fPedEntRelVelMag > RAGDOLL_DAMAGE_MIN_SPEED_FALL
|
|
&& !(nComponent==RAGDOLL_FOOT_LEFT || nComponent==RAGDOLL_FOOT_RIGHT || nComponent==RAGDOLL_SHIN_LEFT || nComponent==RAGDOLL_SHIN_RIGHT))
|
|
{
|
|
const CPedIntelligence* pPedIntelligence = pPed->GetPedIntelligence();
|
|
Assert(pPedIntelligence);
|
|
// Put a threshold on relative velocity too so that we don't accumulate damage from sliding along a stationary
|
|
// object. Threshold is roughly defined by the vertical velocity an object can attain due to gravity in the
|
|
// worst case frame rate.
|
|
const float sfRelSpeedThreshold = 1.1f*CPhysics::GetGravitationalAcceleration()*(1.0f/15.0f);
|
|
if(fRelSpeed > sfRelSpeedThreshold
|
|
&& !pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_HIGH_FALL))
|
|
{
|
|
// Ped is sliding along a stationary object but with a total relative velocity which is high enough for damage.
|
|
fDamage = fRelSpeed * RAGDOLL_DAMAGE_MULTIPLIER_GENERAL_COLLISION;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VEHICLE_TYPE_BIKE:
|
|
case VEHICLE_TYPE_BICYCLE:
|
|
case VEHICLE_TYPE_QUADBIKE:
|
|
case VEHICLE_TYPE_BOAT:
|
|
case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE:
|
|
{
|
|
/***** Damage for vehicles you can be knocked off *****/
|
|
|
|
// B*2796249: Like the above comment suggests, we only want this to apply to jetski vehicles and not *all* boat types...
|
|
if (pVehicle->GetVehicleType() == VEHICLE_TYPE_BOAT && !pVehicle->GetIsJetSki())
|
|
{
|
|
break;
|
|
}
|
|
|
|
static float RAGDOLL_DAMAGE_MULTIPLIER_BIKE_IN_WATER = 0.1f;
|
|
// Only generate damage for impacts with sufficiently horizontal impact normal. Peds which fall onto
|
|
// a car's bonnet, roof, etc. will take damage from the fall code at the end.
|
|
if(!bHasFallen)
|
|
{
|
|
float fScalarProd = vPedNormal.Dot(ZAXIS);
|
|
if(fScalarProd<RAGDOLL_DAMAGE_VEHICLE_IMPACT_ANGLE_PARAM)
|
|
{
|
|
// Don't apply damage to peds which are standing when the closing speed between the vehicle and ped
|
|
// is less than some threshold or when the vehicle is hardly moving unless the ped is under the vehicle.
|
|
if(fScalarProd>RAGDOLL_DAMAGE_UNDER_VEHICLE_IMPACT_ANGLE_PARAM)
|
|
{
|
|
fDamage = fPedEntRelVelMag * (pVehicle->GetIsInWater() ? RAGDOLL_DAMAGE_MULTIPLIER_BIKE_IN_WATER : RAGDOLL_DAMAGE_MULTIPLIER_BIKE);
|
|
if((bPedStanding && fRelSpeed<RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE)
|
|
|| pVehicle->GetVelocity().Mag2()<RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE*RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE)
|
|
{
|
|
fDamage = 0.0f;
|
|
}
|
|
}
|
|
else if(pVehicle->GetVehicleType()!=VEHICLE_TYPE_BIKE && !pPed->IsLocalPlayer())
|
|
{
|
|
// Only take damage for being under a quad, not an ordinary bike.
|
|
fDamage = fRelSpeed*RAGDOLL_DAMAGE_MULTIPLIER_BIKE;
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_UNDER_VEHICLE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float ragdollDamageMinSpeedVehicle = RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE;
|
|
|
|
if( pVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE &&
|
|
pVehicle->GetStatus() == STATUS_WRECKED )
|
|
{
|
|
ragdollDamageMinSpeedVehicle = RAGDOLL_DAMAGE_MIN_SPEED_WRECKED_BIKE;
|
|
}
|
|
if( fPedEntRelVelMag > ragdollDamageMinSpeedVehicle )
|
|
{
|
|
if(!pVehicle->GetIsInWater())
|
|
{
|
|
fDamage = fRelSpeed*RAGDOLL_DAMAGE_MULTIPLIER_BIKE;
|
|
}
|
|
else
|
|
{
|
|
fDamage = fRelSpeed*RAGDOLL_DAMAGE_MULTIPLIER_BIKE_IN_WATER;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We don't want fall damage if we are falling on a bike in water.
|
|
if(pVehicle->GetIsInWater())
|
|
{
|
|
bHasFallen = false;
|
|
}
|
|
}
|
|
break;
|
|
default: // All other vehicle types handled here.
|
|
{
|
|
fDamage = ComputeDamageFromVehicle(pPed, pVehicle, nComponent, fPedEntRelVelMag, vPedNormal, bPedStanding, fRelSpeed);
|
|
}
|
|
break;
|
|
} // End of vehicle-type switch statement.
|
|
|
|
// Apply the weapon damage modifier
|
|
const CItemInfo* pItemInfo = CWeaponInfoManager::GetInfo(weaponHash);
|
|
if(pItemInfo && pItemInfo->GetIsClassId(CWeaponInfo::GetStaticClassId()))
|
|
{
|
|
const CWeaponInfo* pWeaponInfo = static_cast<const CWeaponInfo*>(pItemInfo);
|
|
{
|
|
fDamage *= pWeaponInfo->GetWeaponDamageModifier();
|
|
}
|
|
}
|
|
|
|
// First time through here, we want the collision with the vehicle to perhaps knock any held props from our ped.
|
|
if(pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE)
|
|
{
|
|
float fKnockOffProp = fwRandom::GetRandomNumberInRange(0.0, 1.0f);
|
|
// More likely to drop the prop if hit harder.
|
|
if(fMag > 30.0f)
|
|
fKnockOffProp *= 2.0f;
|
|
|
|
if(fKnockOffProp > 0.9f && !NetworkInterface::IsGameInProgress())
|
|
CPedPropsMgr::KnockOffProps(pPed, true, true, true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
//////////////////
|
|
case ENTITY_TYPE_PED:
|
|
//////////////////
|
|
{
|
|
// Set the weapon type to be unarmed - this is necessary to trigger appropriate facial animations.
|
|
weaponHash = WEAPONTYPE_UNARMED;
|
|
CPed* pInflictorPed = static_cast<CPed*>(pEntity);
|
|
//if(pInflictorPed->IsPlayer() && (!pPed->GetAttacker() || pPed->GetAttacker() != pInflictorPed))
|
|
//{
|
|
// if(!pPed->GetIsDeadOrDying() && pPed->GetSpeechAudioEntity())
|
|
// {
|
|
// // play some low pain
|
|
// audDamageStats damageStats;
|
|
// damageStats.Fatal = false;
|
|
// damageStats.RawDamage = g_HealthLostForLowPain + 1.0f;
|
|
// damageStats.DamageReason = AUD_DAMAGE_REASON_SHOVE;
|
|
// pPed->GetSpeechAudioEntity()->InflictPain(damageStats);
|
|
// }
|
|
//}
|
|
// Only take damage from ragdolling peds flying through the air, not from animated peds running into other peds.
|
|
fDamage = 0.0f;
|
|
if(pInflictorPed->GetUsingRagdoll() && fRelSpeed>RAGDOLL_DAMAGE_MIN_SPEED_PED)
|
|
{
|
|
fDamage = fRelSpeed * RAGDOLL_DAMAGE_MULTIPLIER_PED;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/////////////////////
|
|
case ENTITY_TYPE_OBJECT:
|
|
/////////////////////
|
|
{
|
|
// Only apply damage from objects (not static ones!) deemed heavy enough -- we don't want ped props to do damage.
|
|
Assert(dynamic_cast<CObject*>(pEntity));
|
|
// Don't take damage from certain props.
|
|
if(pEntity->GetModelIndex()==MI_PED_PARACHUTE || static_cast<CObject*>(pEntity)->GetIsParachute())
|
|
{
|
|
fDamage = 0.0f;
|
|
bHasFallen = false;
|
|
break;
|
|
}
|
|
if(pEntity->GetCurrentPhysicsInst() && pEntity->GetCurrentPhysicsInst()->IsInLevel())
|
|
{
|
|
phInst* pPhysInst = pEntity->GetCurrentPhysicsInst();
|
|
u32 nLevelIndex = pPhysInst->GetLevelIndex();
|
|
if(CPhysics::GetLevel()->IsActive(nLevelIndex)
|
|
&& pEntity->GetCollider()
|
|
&& MagSquared(pEntity->GetCollider()->GetVelocity()).Getf() >= RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE*RAGDOLL_DAMAGE_MIN_SPEED_VEHICLE)
|
|
{
|
|
float fMass = static_cast<CObject*>(pEntity)->GetMass();
|
|
if(fMass > 10000.0f)
|
|
{
|
|
fDamage = fRelSpeed * RAGDOLL_DAMAGE_MULTIPLIER_OBJECT_HEAVY;
|
|
break;
|
|
}
|
|
else if(fMass > RAGDOLL_DAMAGE_MIN_OBJECT_MASS)
|
|
{
|
|
fDamage = fRelSpeed * RAGDOLL_DAMAGE_MULTIPLIER_OBJECT_LIGHT;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// INTENTIONAL FALL-THROUGH TO BUILDING DAMAGE BECAUSE WE HAVE HIT A STATIC OBJECT.
|
|
}
|
|
}
|
|
}
|
|
////////////////////////////////
|
|
case ENTITY_TYPE_BUILDING:
|
|
case ENTITY_TYPE_ANIMATED_BUILDING:
|
|
case ENTITY_TYPE_MLO:
|
|
////////////////////////////////
|
|
{
|
|
// We haven't fallen if we are in here. Only inflict damage if we hit something really hard (not with feet).
|
|
fDamage = 0.0f;
|
|
if(fPedEntRelVelMag > RAGDOLL_DAMAGE_MIN_SPEED_FALL
|
|
&& !(nComponent==RAGDOLL_FOOT_LEFT || nComponent==RAGDOLL_FOOT_RIGHT || nComponent==RAGDOLL_SHIN_LEFT || nComponent==RAGDOLL_SHIN_RIGHT))
|
|
{
|
|
const CPedIntelligence* pPedIntelligence = pPed->GetPedIntelligence();
|
|
Assert(pPedIntelligence);
|
|
// Put a threshold on relative velocity too so that we don't accumulate damage from sliding along a stationary
|
|
// object. Threshold is roughly defined by the vertical velocity an object can attain due to gravity in the
|
|
// worst case frame rate.
|
|
const float sfRelSpeedThreshold = 1.1f*CPhysics::GetGravitationalAcceleration()*(1.0f/15.0f);
|
|
if(fRelSpeed > sfRelSpeedThreshold
|
|
&& (!pPedIntelligence->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_HIGH_FALL)
|
|
|| pPed->GetPedResetFlag(CPED_RESET_FLAG_IsParachuting) ) )
|
|
{
|
|
// Ped is sliding along a stationary object but with a total relative velocity which is high enough for damage.
|
|
fDamage = fRelSpeed * RAGDOLL_DAMAGE_MULTIPLIER_GENERAL_COLLISION;
|
|
}
|
|
}
|
|
|
|
if(!bPedStanding && !(nComponent==RAGDOLL_FOOT_LEFT || nComponent==RAGDOLL_FOOT_RIGHT)
|
|
&& fPedEntTangVelMagSq > RAGDOLL_DAMAGE_MIN_SPEED_SLIDING_DAMAGE*RAGDOLL_DAMAGE_MIN_SPEED_SLIDING_DAMAGE)
|
|
{
|
|
// Ped is sliding along a stationary object with most of its velocity along the collision entity.
|
|
fDamage += fPedEntTangVelMagSq * RAGDOLL_DAMAGE_MULTIPLIER_SLIDING_COLLISION;
|
|
|
|
// We still want to generate a small amount of rumble on the control pad even when not taking damage from sliding.
|
|
if(pPed->IsLocalPlayer())
|
|
{
|
|
const u32 knRumbleDuration = 10;
|
|
const u32 knRumbleDelay = 0;
|
|
const float kfRumbleIntensityScale = 0.1f;
|
|
CControlMgr::StartPlayerPadShakeByIntensity(knRumbleDuration, kfRumbleIntensityScale, knRumbleDelay);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
Assertf(false, "Unhandled damage inflictor type: %d", pEntity->GetType());
|
|
fDamage = fRelSpeed * RAGDOLL_DAMAGE_MULTIPLIER_PED;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->hit entity %s[%p] processed. Initial damage - %.2f",
|
|
pEntity ? (pEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast<const CDynamicEntity*>(pEntity)) : pEntity->GetModelName()) : "NULL", pEntity, fDamage);
|
|
#endif
|
|
|
|
// Do fall processing here as the ped could fall onto any entity type.
|
|
if(bHasFallen)
|
|
{
|
|
if(fActualFallHeight>fKillFallHeight &&
|
|
!pPed->GetIsInWater() && // GTAV - B*1853590 - Don't just kill the ped if they've fallen into water.
|
|
!pPed->GetHighFallInstantDeathDisabled() ) // GTAV - B*2410881 - Script want to be able to disable this
|
|
{
|
|
fDamage = 100.0f*pPed->GetHealth();
|
|
fKillFallHeight = -1.0f;
|
|
}
|
|
else if(fPedEntRelVelMag > RAGDOLL_DAMAGE_MIN_SPEED_FALL)
|
|
{
|
|
fDamage = fActualFallHeight;
|
|
// Ped has landed on their feet after a fall. Want to give less damage in this case. General fall damage will be done in
|
|
// the "else" block.
|
|
if((pEntity==NULL || pEntity->GetIsTypeBuilding())
|
|
&& (nComponent==RAGDOLL_FOOT_LEFT || nComponent==RAGDOLL_FOOT_RIGHT || nComponent==RAGDOLL_SHIN_LEFT || nComponent==RAGDOLL_SHIN_RIGHT))
|
|
{
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_FALL_LAND_FOOT;
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->applying 'land on foot' modifier to damage");
|
|
}
|
|
else
|
|
{
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_FALL;
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->applying 'fall' modifier to damage");
|
|
}
|
|
|
|
}
|
|
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->ped has fallen resulting in %.2f damage. fPedEntRelVelMag - %.2f, fActualFallHeight - %.2f, fKillFallHeight - %.2f, inWater - %s, FallInstantDeathDisabled - %s",
|
|
fDamage, fPedEntRelVelMag, fActualFallHeight, fKillFallHeight,
|
|
pPed->GetIsInWater() ? "TRUE" : "FALSE",
|
|
pPed->GetHighFallInstantDeathDisabled() ? "TRUE" : "FALSE");
|
|
|
|
// Reset the fall height so we don't continue to take fall damage while rolling around on the ground.
|
|
if(!pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir))
|
|
{
|
|
pPed->SetFallingHeight(pPed->GetTransform().GetPosition().GetZf());
|
|
}
|
|
}
|
|
else if(fDamage < 0.0f)
|
|
{
|
|
fDamage = 0.0f;
|
|
}
|
|
|
|
// Perform any scaling for special case ped types.
|
|
if(pPed->GetPedType() == PEDTYPE_ANIMAL)
|
|
{
|
|
if (!pPed->IsAPlayerPed())
|
|
{
|
|
fDamage *= RAGDOLL_DAMAGE_MULTIPLIER_ANIMAL_SCALE;
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->applying 'animal' modifier to damage");
|
|
}
|
|
}
|
|
// Random peds hit by cars are always fatigued
|
|
else
|
|
{
|
|
// Scale the computed damage value per component.
|
|
Assertf(nComponent >= 0 && nComponent < RAGDOLL_NUM_COMPONENTS, "Invalid ragdoll component: %d.", nComponent);
|
|
fDamage *= RAGDOLL_COMPONENT_SCALES[nComponent];
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->applying 'component scale' modifier to damage, nComponent - %i", nComponent);
|
|
|
|
if( pEntity && pEntity->GetType() == ENTITY_TYPE_VEHICLE && !pPed->PopTypeIsMission() )
|
|
{
|
|
if( pPed->GetHealth() > pPed->GetFatiguedHealthThreshold() )
|
|
{
|
|
fDamage = MAX(fDamage, (pPed->GetHealth() - pPed->GetFatiguedHealthThreshold()) + 1.0f );
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->updating damage when hit a vehicle to %.2f", fDamage);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We will scale the damage for players based on their strength stat. Maximum strength => 90% of total computed damage.
|
|
if(pPed->IsLocalPlayer())
|
|
{
|
|
float fStrengthStatDamageScale = 1.0f;
|
|
const float fStrengthStatDamageMaxReductionFactor = 0.1f;
|
|
|
|
float fStrengthValue = 1.0f - Clamp(static_cast<float>(StatsInterface::GetIntStat(STAT_STRENGTH.GetStatId())) / 100.0f, 0.0f, 1.0f);
|
|
fStrengthStatDamageScale = 1.0f - fStrengthStatDamageMaxReductionFactor*fStrengthValue;
|
|
fDamage *= fStrengthStatDamageScale;
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->applying 'player strenght scale' modifier to damage. fStrengthStatDamageScale - %.2f", fStrengthStatDamageScale);
|
|
}
|
|
|
|
// Scale the damage if we have been thrown from a bike due to exhaustion.
|
|
if(pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_ThrownFromVehicleDueToExhaustion))
|
|
{
|
|
// Go easy on this ped for some number of milliseconds after falling off.
|
|
const u32 nReducedDamageWhileExhaustedTimePeriod = 2000;
|
|
|
|
u32 nTimeNow = fwTimer::GetTimeInMilliseconds();
|
|
if(nTimeNow - pPed->GetExhaustionDamageTimer() > nReducedDamageWhileExhaustedTimePeriod)
|
|
{
|
|
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_ThrownFromVehicleDueToExhaustion, false);
|
|
}
|
|
else
|
|
{
|
|
// x -> x^3 but scaled so that damage of 100.0f is still 100.0f.
|
|
fDamage = fDamage*fDamage*fDamage / 10000.0f;
|
|
}
|
|
}
|
|
|
|
// Check if this ped is set to be immune to collision damage.
|
|
if(pPed->m_nPhysicalFlags.bNotDamagedByCollisions || pPed->m_nPhysicalFlags.bNotDamagedByAnything ||
|
|
(pEntity && pEntity->GetIsTypePed() && pPed->GetPedResetFlag(CPED_RESET_FLAG_NoCollisionDamageFromOtherPeds)))
|
|
{
|
|
fDamage = 0.0f;
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->reducing this damage to 0.f due to being immune to collion damage: bNotDamagedByCollisions - %s, bNotDamagedByAnything - %s, _NoCollisionDamageFromOtherPeds - %s",
|
|
pPed->m_nPhysicalFlags.bNotDamagedByCollisions ? "TRUE":"FALSE",
|
|
pPed->m_nPhysicalFlags.bNotDamagedByAnything ? "TRUE" : "FALSE",
|
|
pPed->GetPedResetFlag(CPED_RESET_FLAG_NoCollisionDamageFromOtherPeds) ? "TRUE" : "FALSE");
|
|
}
|
|
|
|
//Process bumps.
|
|
ProcessBumps(*pPed);
|
|
|
|
//Helper flags to detect the collision type.
|
|
bool bHitByGround = pEntity && (pEntity == pPed->GetGroundPhysical());
|
|
bool bHitByVehicle = !bHitByGround && pEntity && pEntity->GetIsTypeVehicle();
|
|
bool bHitByObject = !bHitByGround && pEntity && pEntity->GetIsTypeObject();
|
|
|
|
// Ragdolls don't currently register a ground physical. So instead, try to figure out via other means if the ragdoll is on a train or boat.
|
|
if (bHitByVehicle)
|
|
{
|
|
CVehicle *pVehicle = static_cast<CVehicle *>(pEntity);
|
|
if (pVehicle && (pVehicle->InheritsFromTrain() || pVehicle->InheritsFromBoat()) && pPed->GetCollider())
|
|
{
|
|
static float fSmallVelDif = 1.0f;
|
|
Vector3 vUp(0.0f,0.0f,1.0f);
|
|
Vector3 vTrainPedVelDif = pVehicle->GetVelocity() - pPed->GetVelocity();
|
|
Vector3 vTrainPedRefVelDif = pVehicle->GetVelocity() - RCC_VECTOR3(pPed->GetCollider()->GetReferenceFrameVelocity());
|
|
if (vTrainPedVelDif.Mag2() < fSmallVelDif || vTrainPedRefVelDif.Mag2() < fSmallVelDif || vPedNormal.Dot(vUp) > 0.9f)
|
|
{
|
|
bHitByVehicle = false;
|
|
bHitByGround = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine which entity instigated this damage, if any
|
|
CEntity* pImpactDamageInstigator = CalculateRagdollImpactDamageInstigator(pPed, pPhysical, weaponHash, bHasFallen);
|
|
|
|
// Don't allow similar damage to get applied repeatedly over a short amount of time from the same source.
|
|
// This is an attempt at determining the most important impact from a particular source over a short amount of time that
|
|
// isn't frame-rate dependent.
|
|
if (pEntity && pPed->GetRagdollInst())
|
|
{
|
|
if (pPed->GetRagdollInst()->GetLastImpactDamageEntity() == pEntity && pPed->GetRagdollInst()->GetTimeSinceImpactDamage() < RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT)
|
|
{
|
|
float newDamage = fDamage - pPed->GetRagdollInst()->GetAccumulatedImpactDamageFromLastEntity();
|
|
if (newDamage > 0.0f)
|
|
{
|
|
pPed->GetRagdollInst()->SetAccumulatedImpactDamageFromLastEntity(fDamage + newDamage);
|
|
fDamage = newDamage;
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->already applied damage from %s recently. reducing this damage to %.2f", pEntity->GetModelName(), fDamage);
|
|
}
|
|
else
|
|
{
|
|
// We've already applied more than the new damage from this entity over the past <RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT> milliseconds, so ignore this new damage
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->already applied damage from %s recently. reducing this damage to 0.f", pEntity->GetModelName());
|
|
fDamage = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Note the new entity and damage taken from it
|
|
pPed->GetRagdollInst()->SetImpactDamageEntity(pEntity);
|
|
pPed->GetRagdollInst()->SetAccumulatedImpactDamageFromLastEntity(fDamage);
|
|
}
|
|
}
|
|
|
|
// don't need to do anything further if damage is zero and ped isn't currently activating
|
|
if(fDamage <= 0.0f && pPed->GetRagdollState()!=RAGDOLL_STATE_PHYS_ACTIVATE)
|
|
{
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->ignore negative damage (%.2f) as ped is not active", fDamage);
|
|
return;
|
|
}
|
|
|
|
//Create the damage event.
|
|
CEventDamage tempDamageEvent(pImpactDamageInstigator, fwTimer::GetTimeInMilliseconds(), weaponHash);
|
|
|
|
//Apply the damage.
|
|
weaponDebugf3("CCollisionEventScanner::ProcessRagdollImpact-->invoke ApplyDamageAndComputeResponse: damage - %.2f, hitComponent[%d]", fDamage, nComponent);
|
|
CPedDamageCalculator damageCalculator(pImpactDamageInstigator, fDamage, weaponHash, nComponent, false);
|
|
damageCalculator.ApplyDamageAndComputeResponse(pPed, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
|
|
if (bHitByVehicle)
|
|
{
|
|
CVehicle* veh = (CVehicle*)pEntity;
|
|
CPlayerSpecialAbilityManager::ChargeEvent(ACET_RAN_OVER_PED, veh->GetDriver(), fDamage);
|
|
}
|
|
|
|
#if __DEV
|
|
static bool PRINT_RESULTS = false;
|
|
if(PRINT_RESULTS)
|
|
Displayf("ped (%d) health = %3.2f\n", CPed::GetPool()->GetIndex(pPed), pPed->GetHealth());
|
|
#endif
|
|
|
|
bool bWasKilledOrInjured = (tempDamageEvent.GetDamageResponseData().m_bKilled || tempDamageEvent.GetDamageResponseData().m_bInjured);
|
|
|
|
if(fKillFallHeight < 0.0f && tempDamageEvent.GetDamageResponseData().m_bKilled && fOriginalHealth > 0.0f)
|
|
{
|
|
if (fDamage>0.0f)
|
|
{
|
|
g_vfxBlood.TriggerPtFxFallToDeath(pPed);
|
|
pPed->GetPedAudioEntity()->TriggerFallToDeath();
|
|
}
|
|
}
|
|
else if(fDamage >= pPed->GetHealth())
|
|
{
|
|
pPed->GetPedAudioEntity()->PedFellToDeath();
|
|
}
|
|
|
|
|
|
if(!tempDamageEvent.AffectsPed(pPed))
|
|
{
|
|
// ped doesn't want to react to damage - if ragdoll has already activated though, we will need to deal with it.
|
|
if(pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE)
|
|
;
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (bHitByVehicle)
|
|
{
|
|
//Setting up Custom Idle for after recovery (B* 810427)
|
|
//Possibly data drive this in the future if these become more prevalent
|
|
static fwMvClipSetId s_HitByCarSet("reaction@shake_it_off",0xC6AE9A3E);
|
|
static fwMvClipId s_HitByCarClip("dustoff",0xC9A0759E);
|
|
pPed->GetMotionData()->RequestCustomIdle(s_HitByCarSet, s_HitByCarClip);
|
|
}
|
|
|
|
// Determine if we need a new ragdoll task to handle this impact
|
|
int iRagdollTaskPriority = 0;
|
|
|
|
if (pPed->GetRagdollState()==RAGDOLL_STATE_PHYS)
|
|
{
|
|
// We're already in ragdoll. Ask the existing nm behaviour if it wants to handle this impact itself
|
|
CTask* pTaskSimplest = pPed->GetPedIntelligence()->GetTaskActiveSimplest();
|
|
|
|
if (pTaskSimplest)
|
|
{
|
|
if (pTaskSimplest->IsNMBehaviourTask())
|
|
{
|
|
CTaskNMBehaviour* pRunningNmTask = smart_cast<CTaskNMBehaviour*>(pTaskSimplest);
|
|
if (pRunningNmTask->HandleRagdollImpact(fMag, pEntity, vPedNormal, nComponent, nMaterialId))
|
|
{
|
|
iRagdollTaskPriority = E_PRIORITY_MAX;
|
|
}
|
|
}
|
|
|
|
if (iRagdollTaskPriority == 0)
|
|
{
|
|
// If we're being 'hit' by a slow moving vehicle and are already running some kind of task that handles ragdoll then
|
|
// no need to generate a new task
|
|
static const float s_fMinSpeedNeedsRagdollTask2 = square(2.0f);
|
|
if (bHitByVehicle && static_cast<const CVehicle*>(pEntity)->GetVelocity().Mag2() < s_fMinSpeedNeedsRagdollTask2 && pTaskSimplest->HandlesRagdoll(pPed))
|
|
{
|
|
iRagdollTaskPriority = E_PRIORITY_MAX;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The ragdoll must have just activated in the last update
|
|
// We'll need to add a new event, unless we've already added one
|
|
for (int i = 0; i < pPed->GetPedIntelligence()->GetNumEventsInQueue(); i++)
|
|
{
|
|
const CEvent* pEvent = pPed->GetPedIntelligence()->GetEventByIndex(i);
|
|
if (pEvent->RequiresAbortForRagdoll() && !pEvent->HasExpired() && pEvent->GetEventPriority() > iRagdollTaskPriority)
|
|
{
|
|
iRagdollTaskPriority = pEvent->GetEventPriority();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE)
|
|
{
|
|
// The ragdoll must have just activated in the last update
|
|
// We'll need to add a new event, unless we've already added one
|
|
for (int i = 0; i < pPed->GetPedIntelligence()->GetNumEventsInQueue(); i++)
|
|
{
|
|
const CEvent* pEvent = pPed->GetPedIntelligence()->GetEventByIndex(i);
|
|
if (pEvent->RequiresAbortForRagdoll() && !pEvent->HasExpired() && pEvent->GetEventPriority() > iRagdollTaskPriority)
|
|
{
|
|
iRagdollTaskPriority = pEvent->GetEventPriority();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we already have a ragdoll task, we don't need to do any of the rest of this function.
|
|
if(iRagdollTaskPriority >= tempDamageEvent.GetEventPriority())
|
|
return;
|
|
|
|
CTask* pTaskResponse = NULL;
|
|
if(bWasKilledOrInjured && (fDamage > 0.0f || pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE))
|
|
{
|
|
CTask* pTaskActive = pPed->GetPedIntelligence()->GetTaskActive();
|
|
CDamageInfo damageInfo(weaponHash, NULL, NULL, NULL, 0.0f, NULL);
|
|
CTask* pNMTask = pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL);
|
|
// If an NM task isn't active, check if there's an event in the queue that is about to start an NM task
|
|
if (!pNMTask)
|
|
{
|
|
CEventDamage *damageEvent = static_cast<CEventDamage*>(pPed->GetPedIntelligence()->GetEventOfType(EVENT_DAMAGE));
|
|
if (damageEvent && !damageEvent->HasExpired() && damageEvent->GetPhysicalResponseTask() && damageEvent->GetPhysicalResponseTask()->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
|
{
|
|
pNMTask = (CTask*) damageEvent->GetPhysicalResponseTask();
|
|
}
|
|
}
|
|
// sconde (4/19/2013) This logic seems broken to me...
|
|
// We are essentially checking to see if there is already a damage event with a physical response task that has the ability to handle a ragdoll reaction (pNMTask->HandlesRagdoll(pPed))
|
|
// If there we find a damage event that handles ragdoll reactions we don't create a task response for the 'death' damage event even though it has higher priority than non-'death' ragdoll damage events
|
|
// This means that when we go to handle the events we'll be handling a 'death' event that doesn't have a ragdoll response - essentially leaving a ragdoll without a ragdoll task!
|
|
if( (pNMTask && pNMTask->HandlesRagdoll(pPed)) || (pTaskActive && (pTaskActive->RecordDamage(damageInfo))))
|
|
{
|
|
// Do nothing more, the task has handled the damage response
|
|
}
|
|
else
|
|
{
|
|
// If the ped is on fire relax for only a short period of time,
|
|
// this will give CTaskComplexDie the opportunity to switch back
|
|
// to onFire quickly.
|
|
if(g_fireMan.IsEntityOnFire(pPed))
|
|
{
|
|
//Displayf(__FILE__ " Ln%d CTaskNMRelax: was killed or injured (damage %.3f, ragdoll state %d) and is on fire/n", __LINE__, fDamage, (int)pPed->GetRagdollState());
|
|
nmEntityDebugf(pPed, "CCollisionEventScanner::ProcessRagdollImpact - Adding CTaskNMRelax for fatal collision on flaming ped");
|
|
pTaskResponse = rage_new CTaskNMRelax(250, 500, 25.0f, 0.75f);
|
|
}
|
|
else
|
|
{
|
|
if(IsPlayerOrPlayersVehicle(pEntity) || pPed->IsPlayer())
|
|
{
|
|
//pTaskResponse = rage_new CTaskNMRollUpAndRelax(2000, 60000, fwRandom::GetRandomNumberInRange(1000, 3000), -1, 1.0f);
|
|
nmEntityDebugf(pPed, "CCollisionEventScanner::ProcessRagdollImpact - Adding CTaskNMBrace for fatal collision with player vehicle or player");
|
|
Vector3 vPedVelocity(pPed->GetVelocity());
|
|
if (MagSquared(pPed->GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2())
|
|
{
|
|
vPedVelocity = VEC3V_TO_VECTOR3(pPed->GetGroundVelocityIntegrated());
|
|
}
|
|
pTaskResponse = rage_new CTaskNMBrace(1000, 60000, pEntity, CTaskNMBrace::BRACE_DEFAULT, vPedVelocity);
|
|
}
|
|
else
|
|
{
|
|
//Displayf(__FILE__ " Ln%d CTaskNMRelax: was killed or injured (damage %.3f, ragdoll state %d)/n", __LINE__, fDamage, (int)pPed->GetRagdollState());
|
|
nmEntityDebugf(pPed, "CCollisionEventScanner::ProcessRagdollImpact - Adding CTaskNMRelax for fatal collision with non player vehicle and non player");
|
|
pTaskResponse = rage_new CTaskNMRelax(2000, 60000, -1, 1.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CEvent* pCurrentEvent = pPed->GetPedIntelligence()->GetCurrentEvent();
|
|
if(pPed->GetRagdollState()==RAGDOLL_STATE_PHYS_ACTIVATE || ( bHitByVehicle && (!pCurrentEvent || pCurrentEvent->GetEventType()!=EVENT_DAMAGE)))
|
|
{
|
|
bool bHitByVehicleOrObject = bHitByVehicle || bHitByObject;
|
|
CEntity* pHitByEntity = NULL;
|
|
|
|
CPed* pLocalPlayer = NULL;
|
|
|
|
// If we do have a collision scanningEntity that is a player or vehicle we
|
|
// are happy, otherwise we do a search for better options.
|
|
if (pEntity && ((pEntity->GetIsTypePed() && ((CPed*)pEntity)->IsPlayer()) || bHitByVehicleOrObject))
|
|
{
|
|
pHitByEntity = pEntity;
|
|
}
|
|
else
|
|
{
|
|
// Search a collision record involving a player or player car.
|
|
const CCollisionRecord* pColRecord = NULL;
|
|
|
|
for(pColRecord = pPed->GetFrameCollisionHistory()->GetFirstPedCollisionRecord();
|
|
pColRecord != NULL ; pColRecord = pColRecord->GetNext())
|
|
{
|
|
CEntity* pCollisionEntity = pColRecord->m_pRegdCollisionEntity.Get();
|
|
|
|
if(pCollisionEntity != NULL && static_cast<const CPed*>(pCollisionEntity)->IsPlayer())
|
|
{
|
|
//- look at the player and balance
|
|
pHitByEntity = pCollisionEntity;
|
|
bHitByVehicleOrObject = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( pHitByEntity == NULL )
|
|
{
|
|
for(pColRecord = pPed->GetFrameCollisionHistory()->GetFirstVehicleCollisionRecord();
|
|
pColRecord != NULL ; pColRecord = pColRecord->GetNext())
|
|
{
|
|
CEntity* pCollisionEntity = pColRecord->m_pRegdCollisionEntity.Get();
|
|
|
|
const CPed* pDriver = static_cast<CVehicle*>(pCollisionEntity)->GetDriver();
|
|
|
|
if(pDriver && pDriver->IsPlayer())
|
|
{
|
|
//- brace against the vehicle
|
|
pHitByEntity = pCollisionEntity;
|
|
bHitByVehicleOrObject = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not successful yet?
|
|
if(!pHitByEntity)
|
|
{
|
|
static float DIST_THRESHOLD_CAR_2 = 5.0f * 5.0f;
|
|
static float DIST_THRESHOLD_PLAYER_2 = 1.28f * 1.28f;
|
|
|
|
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
|
|
// Search for a player car close by
|
|
pLocalPlayer = FindPlayerPed();
|
|
const Vector3 vLocalPlayerPos = VEC3V_TO_VECTOR3(pLocalPlayer->GetTransform().GetPosition());
|
|
|
|
if(pPed == pLocalPlayer || (pLocalPlayer && pLocalPlayer->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle )
|
|
&& (vLocalPlayerPos.Dist2(vPedPos) < DIST_THRESHOLD_CAR_2)))
|
|
{
|
|
static float TEST_FOR_CAR_RADIUS = 0.6f;
|
|
static u32 INCLUDE_FLAGS = ArchetypeFlags::GTA_VEHICLE_TYPE;
|
|
|
|
phIntersection intersection;
|
|
|
|
if(CPhysics::GetLevel()->TestSphere(vPedPos, TEST_FOR_CAR_RADIUS, &intersection, pPed->GetRagdollInst(), INCLUDE_FLAGS))
|
|
{
|
|
//- brace against the vehicle
|
|
pHitByEntity = CPhysics::GetEntityFromInst(intersection.GetInstance());
|
|
bHitByVehicleOrObject = true;
|
|
}
|
|
}
|
|
|
|
// Search for a player close by
|
|
if (!pHitByEntity && pLocalPlayer && pPed!=pLocalPlayer
|
|
&& (vLocalPlayerPos.Dist2(vPedPos) < DIST_THRESHOLD_PLAYER_2))
|
|
{
|
|
pHitByEntity = pLocalPlayer;
|
|
bHitByVehicleOrObject = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!pHitByEntity)
|
|
pHitByEntity = pEntity;
|
|
|
|
bool bRollUp = false;
|
|
|
|
// If we are blending from NM we check if we should balance/brace
|
|
// or rollUp.
|
|
const CTaskBlendFromNM* const pTask = static_cast<const CTaskBlendFromNM*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_BLEND_FROM_NM));
|
|
if(pTask != NULL && pTask->ShouldRollupOnRagdollImpact())
|
|
{
|
|
bRollUp = true;
|
|
}
|
|
|
|
// rollUp or balance ?
|
|
if(bRollUp)
|
|
{
|
|
nmEntityDebugf(pPed, "CCollisionEventScanner::ProcessRagdollImpact - Adding CTaskNMRollUpAndRelax for non-fatal hit");
|
|
pTaskResponse = rage_new CTaskNMRollUpAndRelax(2000, 60000, fwRandom::GetRandomNumberInRange(1000, 3000), -1, 1.0f);
|
|
|
|
CFacialDataComponent* pFacialData = pPed->GetFacialData();
|
|
if(pFacialData)
|
|
{
|
|
pFacialData->PlayPainFacialAnim(pPed);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CTaskSitIdle* pTaskSit = (CTaskSitIdle*)pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_SIT_IDLE);
|
|
//if(pTaskSit && pHitByEntity && pHitByEntity->GetIsTypePed())
|
|
//{
|
|
// pTaskResponse = rage_new CTaskNMSit(2000, 60000, pTaskSit->GetClipSet(), pTaskSit->GetClipId(), VEC3_ZERO, pHitByEntity, VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()));
|
|
//}
|
|
//else
|
|
if(bHitByVehicleOrObject)
|
|
{
|
|
Assert(pHitByEntity);
|
|
|
|
nmEntityDebugf(pPed, "CCollisionEventScanner::ProcessRagdollImpact - Adding CTaskNMBrace for non-fatal hit with vehicle or object");
|
|
Vector3 vPedVelocity(pPed->GetVelocity());
|
|
if (MagSquared(pPed->GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2())
|
|
{
|
|
vPedVelocity = VEC3V_TO_VECTOR3(pPed->GetGroundVelocityIntegrated());
|
|
}
|
|
pTaskResponse = rage_new CTaskNMBrace(1000, 60000, pHitByEntity, CTaskNMBrace::BRACE_DEFAULT, vPedVelocity);
|
|
|
|
/// set weak option based on agility and health
|
|
static float fHealthThresholdMissionCharPlayer = 110.0f, fHealthThresholdOther = 140.0f;
|
|
const float fHealthThreshold = (pPed->IsPlayer() || (pPed->PopTypeIsMission()) ? fHealthThresholdMissionCharPlayer : fHealthThresholdOther);
|
|
|
|
if (CTaskNMBehaviour::ms_bUseParameterSets && (!pPed->CheckAgilityFlags(AF_RAGDOLL_BRACE_STRONG) || (pPed->GetHealth() < fHealthThreshold))) {
|
|
((CTaskNMBrace*)pTaskResponse)->SetType(CTaskNMBrace::BRACE_WEAK);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert(!bHitByVehicleOrObject);
|
|
|
|
u32 grabFlags = 0;
|
|
if (!CTaskNMBehaviour::ms_bDisableBumpGrabForPlayer || !pPed->IsPlayer())
|
|
{
|
|
grabFlags|=CGrabHelper::TARGET_AUTO_WHEN_FALLING;
|
|
}
|
|
|
|
nmEntityDebugf(pPed, "CCollisionEventScanner::ProcessRagdollImpact - Adding CTaskNMBalance for non-fatal hit");
|
|
pTaskResponse = rage_new CTaskNMBalance(250, 60000, pHitByEntity, grabFlags );
|
|
|
|
if(!pHitByEntity)
|
|
{
|
|
Vector3 vColNormal = vPedNormal;
|
|
vColNormal.z = 0.0f;
|
|
vColNormal *= 3.0f;
|
|
|
|
((CTaskNMBalance*)pTaskResponse)->SetLookAtPosition(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()) - vColNormal);
|
|
}
|
|
|
|
/// do facials (100%/50% angry, 25% pain, 25% shocked) on the player or if bumped into by a player
|
|
CFacialDataComponent* pFacialData = pPed->GetFacialData();
|
|
if(pFacialData)
|
|
{
|
|
if (pPed->IsPlayer())
|
|
{
|
|
pFacialData->PlayFacialAnim(pPed, FACIAL_CLIP_ANGRY);
|
|
}
|
|
else if (pHitByEntity && (pHitByEntity->GetIsTypePed() && ((CPed*)pHitByEntity)->IsPlayer()))
|
|
{
|
|
if(fwRandom::GetRandomTrueFalse())
|
|
{
|
|
if(fwRandom::GetRandomTrueFalse())
|
|
pFacialData->PlayPainFacialAnim(pPed);
|
|
else
|
|
pFacialData->PlayFacialAnim(pPed, FACIAL_CLIP_SHOCKED);
|
|
}
|
|
else
|
|
{
|
|
pFacialData->PlayFacialAnim(pPed, FACIAL_CLIP_ANGRY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there's no response, then there's no need to add the event
|
|
if(pTaskResponse)
|
|
{
|
|
if(pTaskResponse->GetTaskType() == CTaskTypes::TASK_NM_BALANCE) {
|
|
static_cast<CTaskNMBalance*>(pTaskResponse)->EvaluateAndSetType(pPed);
|
|
}
|
|
|
|
// if ped is going to accept the damage response - fine, send that
|
|
if(tempDamageEvent.AffectsPed(pPed))
|
|
{
|
|
{
|
|
// And the event and physical response (if one was found).
|
|
CEvent* pEventAdded = pPed->GetPedIntelligence()->AddEvent(tempDamageEvent);
|
|
if(pEventAdded && pEventAdded != &tempDamageEvent
|
|
&& pTaskResponse
|
|
&& pEventAdded->GetEventType()==EVENT_DAMAGE)
|
|
{
|
|
// We need to wrap the response in a complexNM task.
|
|
CTask* pTempResponse = pTaskResponse;
|
|
pTaskResponse = rage_new CTaskNMControl(1000, 10000, pTempResponse, CTaskNMControl::DO_BLEND_FROM_NM, fDamage);
|
|
|
|
nmDebugf2("[%u] ProcessRagdollImpact:%s(%p) Added event: %s, %s", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed, pEventAdded->GetName().c_str(), pTempResponse->GetTaskName());
|
|
|
|
((CEventDamage*)pEventAdded)->SetPhysicalResponseTask(pTaskResponse, true);
|
|
}
|
|
else if(pTaskResponse)
|
|
{
|
|
delete pTaskResponse;
|
|
pTaskResponse = nullptr;
|
|
}
|
|
}
|
|
}
|
|
// if not then force it through with a switch event
|
|
else
|
|
{
|
|
{
|
|
CEventSwitch2NM event(60000, pTaskResponse);
|
|
pPed->GetPedIntelligence()->AddEvent(event);
|
|
nmDebugf2("[%u] ProcessRagdollImpact:%s(%p) Added event: %s, %s", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed, event.GetName().c_str(), pTaskResponse->GetTaskName());
|
|
// think we might be in trouble if we get in here with a dead ped, because it won't trigger the complex die task
|
|
Assert(!bWasKilledOrInjured);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CTaskNMControl* pTaskNM = smart_cast<CTaskNMControl*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL));
|
|
if(pTaskNM)
|
|
pTaskNM->SetDamageDone(fDamage);
|
|
}
|
|
|
|
#if CNC_MODE_ENABLED
|
|
if (pTaskResponse && pPed->GetPedIntelligence()->GetTaskActive()->GetTaskType() == CTaskTypes::TASK_INCAPACITATED)
|
|
{
|
|
if (tempDamageEvent.GetEventPriority() > E_PRIORITY_INCAPACITATED && pTaskResponse->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
|
{
|
|
CTaskIncapacitated* pIncapacitationTask = (CTaskIncapacitated*)pPed->GetPedIntelligence()->GetTaskActive();
|
|
pIncapacitationTask->SetForcedNaturalMotionTask(pTaskResponse);
|
|
}
|
|
}
|
|
#endif //CNC_MODE_ENABLED
|
|
}
|
|
|
|
CEntity* CCollisionEventScanner::CalculateRagdollImpactDamageInstigator(const CPed* pPed, CPhysical* pImpactInflictor, const u32 uWeaponHash, bool bHasFallen) const
|
|
{
|
|
CEntity* pInstigator = pImpactInflictor;
|
|
|
|
if (pPed->GetPedResetFlag(CPED_RESET_FLAG_UseExtendedRagdollCollisionCalculator))
|
|
{
|
|
// Calculate relative velocity to determine if oncoming vehicle collided with falling ped
|
|
if (pImpactInflictor && pImpactInflictor->GetIsTypeVehicle())
|
|
{
|
|
// Assume vehicle is not instigator by default
|
|
pInstigator = nullptr;
|
|
|
|
const Vector3& vDamagerVelocity = pImpactInflictor->GetVelocity();
|
|
|
|
// Only consider vehicle as instigator if it is moving
|
|
if (vDamagerVelocity.IsNonZero() && vDamagerVelocity.Mag2() >= square(RAGDOLL_SPEED_VEHICLE_SLOW))
|
|
{
|
|
// Evaluate damager velocity and impact normal; vehicle is only the instigator if moving towards impact
|
|
const CCollisionRecord* pCollisionRecord = PhysicsHelpers::GetCollisionRecord(pImpactInflictor, pPed);
|
|
if (pCollisionRecord && pCollisionRecord->m_MyCollisionNormal.IsNonZero())
|
|
{
|
|
const float fAngleVectorsPointSameDirectionRadians = PI * 0.25f; // 45 degrees
|
|
|
|
Vector3 vDamagerVelocityNorm = vDamagerVelocity;
|
|
vDamagerVelocityNorm.NormalizeFast();
|
|
|
|
if (DotProduct(pCollisionRecord->m_MyCollisionNormal, vDamagerVelocityNorm) <= fAngleVectorsPointSameDirectionRadians)
|
|
{
|
|
// Vehicle collided with ragdoll at sufficient speed in direction of travel
|
|
return pImpactInflictor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bHasFallen)
|
|
{
|
|
// If present, assign blame to last entity this ragdoll collided with
|
|
if (pPed->GetRagdollInst())
|
|
{
|
|
CEntity* pLastImpactEntity = pPed->GetRagdollInst()->GetLastImpactDamageEntity();
|
|
// ProcessRagdollImpact may be called multiple times during a collision.
|
|
// Assume that we can't be colliding with the same entity that caused us to ragdoll
|
|
if (pLastImpactEntity && pLastImpactEntity != pImpactInflictor)
|
|
{
|
|
return pLastImpactEntity;
|
|
}
|
|
}
|
|
|
|
// Ped has fallen with no known instigator, treat this as self-inflicted
|
|
pInstigator = nullptr;
|
|
}
|
|
}
|
|
|
|
// If we are hit by a ped reacting to an impact from another entity, we consider that entity to be our instigator
|
|
if (pImpactInflictor && pImpactInflictor->GetIsTypePed() && uWeaponHash == WEAPONTYPE_UNARMED)
|
|
{
|
|
CPed* pInflictorPed = SafeCast(CPed, pImpactInflictor);
|
|
const CPedIntelligence* pInflictorPedIntelligence = pInflictorPed->GetPedIntelligence();
|
|
if (pInflictorPedIntelligence->GetLowestLevelNMTask(pInflictorPed))
|
|
{
|
|
const CEvent* pInflictorCurrentEvent = pInflictorPedIntelligence->GetCurrentEvent();
|
|
if (pInflictorCurrentEvent && pInflictorCurrentEvent->GetEventType() == EVENT_DAMAGE)
|
|
{
|
|
const CEventDamage* pInflictorCurrentDamageEvent = SafeCast(const CEventDamage, pInflictorCurrentEvent);
|
|
if (pInflictorCurrentDamageEvent->GetInflictor() && pInflictorCurrentDamageEvent->GetInflictor() != pPed)
|
|
{
|
|
pInstigator = pInflictorCurrentDamageEvent->GetInflictor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are hit by a recently wrecked vehicle, then set the entity that wrecked the vehicle as our instigator
|
|
if (pImpactInflictor && pImpactInflictor->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = SafeCast(CVehicle, pImpactInflictor);
|
|
if (pVehicle->GetStatus() == STATUS_WRECKED && fwTimer::GetCamTimeInMilliseconds() < pVehicle->GetTimeOfDestruction() + m_TimeToConsiderWreckedVehicleSource)
|
|
{
|
|
pInstigator = pVehicle->GetSourceOfDestruction();
|
|
}
|
|
}
|
|
|
|
if (pImpactInflictor && pImpactInflictor->GetIsClassId(CProjectile::GetStaticClassId()))
|
|
{
|
|
CProjectile* pProjectile = (CProjectile*)pImpactInflictor;
|
|
if (pProjectile->GetWeaponFiredFromHash() == ATSTRINGHASH("WEAPON_RAYPISTOL", 0xAF3696A1))
|
|
{
|
|
if (CEntity* pOwner = pProjectile->GetOwner())
|
|
{
|
|
pInstigator = pOwner;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pInstigator;
|
|
}
|
|
|
|
bool CCollisionEventScanner::ProcessVehicleImpact(CPed* pPed, CEntity* pImpactEntity, float fImpactMag, const Vector3& vecImpactNormal, int nComponent, bool bForceBecauseDriverFell, s32 iSeatIndex)
|
|
{
|
|
// If the car's seats have collided then knock off the peds
|
|
|
|
CVehicle* pVehicle = pPed->GetMyVehicle();
|
|
if(!physicsVerifyf(pVehicle,"Unexpected NULL vehicle"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
physicsFatalAssertf(pVehicle->GetVehicleModelInfo(),"Unexpected NULL vehicle model info");
|
|
|
|
if (pPed->GetSpecialAbility() && pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* impactVeh = (CVehicle*)pImpactEntity;
|
|
if (impactVeh->GetDriver())
|
|
{
|
|
float impactVehVel = impactVeh->GetVelocity().Mag2();
|
|
float vehVel = pVehicle->GetVelocity().Mag2();
|
|
const float minAiThresholdSqr = 4.f * 4.f;
|
|
const float minThresholdSqr = 6.f * 6.f;
|
|
|
|
// ai vehicle velocity can't be too low for any of the events to count
|
|
if (impactVehVel > minAiThresholdSqr)
|
|
{
|
|
Vector3 vel0 = pVehicle->GetVelocity();
|
|
vel0.Normalize();
|
|
Vector3 dir = VEC3V_TO_VECTOR3(impactVeh->GetVehiclePosition()) - VEC3V_TO_VECTOR3(pVehicle->GetVehiclePosition());
|
|
dir.Normalize();
|
|
|
|
float dot = dir.Dot(vel0);
|
|
if (dot > 0.2f)
|
|
{
|
|
// we crashed into ai, most likely
|
|
if (vehVel > minThresholdSqr)
|
|
CPlayerSpecialAbilityManager::ChargeEvent(ACET_RAMMED_INTO_CAR, pPed, fImpactMag);
|
|
}
|
|
else if (impactVehVel > vehVel)
|
|
{
|
|
// ai crashed into us, this will only happen when our veicle faces away from the ai one
|
|
CPlayerSpecialAbilityManager::ChargeEvent(ACET_RAMMED_BY_CAR, pPed, fImpactMag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CPlayerSpecialAbilityManager::ChargeEvent(ACET_CRASHED_VEHICLE, pPed, fImpactMag);
|
|
|
|
bool bFallOff = false;
|
|
|
|
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iSeatIndex);
|
|
int iSeatFragChild = pVehicle->GetVehicleModelInfo()->GetFragChildForSeat(iSeatIndex);
|
|
bool bRagdollWhenVehicleUpsideDown = (pSeatAnimInfo && pSeatAnimInfo->GetRagdollWhenVehicleUpsideDown());
|
|
if((iSeatFragChild > -1 && iSeatFragChild == nComponent) || bRagdollWhenVehicleUpsideDown)
|
|
{
|
|
if(pVehicle->GetTransform().GetC().GetZf() < 0.0f)
|
|
{
|
|
bFallOff = true;
|
|
}
|
|
}
|
|
|
|
if(!bFallOff)
|
|
{
|
|
// Scale the impulse if the impacted object requests this through tunableobjects.meta.
|
|
if(pImpactEntity->GetIsTypeObject())
|
|
{
|
|
u32 nModelNameHash = pImpactEntity->GetBaseModelInfo()->GetModelNameHash();
|
|
const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(nModelNameHash);
|
|
if(pTuning)
|
|
{
|
|
fImpactMag *= pTuning->GetKnockOffBikeImpulseScalar();
|
|
}
|
|
}
|
|
if (pVehicle->InheritsFromBike())
|
|
{
|
|
bFallOff = ProcessBikeImpact(pPed,pImpactEntity,fImpactMag,vecImpactNormal,nComponent,bForceBecauseDriverFell);
|
|
}
|
|
else if (pVehicle->InheritsFromQuadBike() ||
|
|
pVehicle->InheritsFromAmphibiousQuadBike())
|
|
{
|
|
bFallOff = ProcessQuadBikeImpact(pPed,pImpactEntity,fImpactMag,vecImpactNormal,nComponent,bForceBecauseDriverFell);
|
|
}
|
|
else if(pVehicle->GetIsJetSki())
|
|
{
|
|
bFallOff = ProcessJetSkiImpact(pPed,pImpactEntity,fImpactMag,vecImpactNormal,nComponent,bForceBecauseDriverFell);
|
|
}
|
|
}
|
|
|
|
// We allow peds on bikes to ragdoll off as we don't have an animated upside down exit
|
|
if (iSeatFragChild <= -1 && !pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_CanActivateRagdollWhenVehicleUpsideDown))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(bFallOff)
|
|
{
|
|
if(CTaskNMBehaviour::CanUseRagdoll(pPed, RAGDOLL_TRIGGER_VEHICLE_FALLOUT, pImpactEntity, fImpactMag))
|
|
{
|
|
static float sfDamageMultiplier = 15.0f;
|
|
float fDamage = 0.0f;
|
|
if(pVehicle->InheritsFromBike()||pVehicle->InheritsFromQuadBike()||pVehicle->GetIsJetSki()||pVehicle->InheritsFromAmphibiousQuadBike()||bRagdollWhenVehicleUpsideDown)
|
|
{
|
|
Vector3 vVehicleVelocity = pVehicle->GetVelocity();
|
|
float fVelocityIntoGroundPoly = vecImpactNormal.Dot(-vVehicleVelocity);
|
|
float fVerticalVelocityToKnockOffThisBike = pVehicle->GetVelocityThresholdBeforeKnockOffVehicle();
|
|
|
|
fDamage = sfDamageMultiplier * fVelocityIntoGroundPoly/fVerticalVelocityToKnockOffThisBike;
|
|
if(fDamage < 0.0f)
|
|
fDamage = 0.0f;
|
|
|
|
// Check if this ped is set to be immune to collision damage.
|
|
if(pPed->m_nPhysicalFlags.bNotDamagedByCollisions || pPed->m_nPhysicalFlags.bNotDamagedByAnything)
|
|
fDamage = 0.0f;
|
|
}
|
|
|
|
CEventDamage tempDamageEvent(pImpactEntity, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_RAMMEDBYVEHICLE);
|
|
CPedDamageCalculator damageCalculator(pImpactEntity, fDamage, WEAPONTYPE_RAMMEDBYVEHICLE, 0, false);
|
|
damageCalculator.ApplyDamageAndComputeResponse(pPed, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
|
|
if(tempDamageEvent.AffectsPed(pPed))
|
|
{
|
|
CEvent* pEventAdded = pPed->GetPedIntelligence()->AddEvent(tempDamageEvent);
|
|
if(pEventAdded && pEventAdded != &tempDamageEvent && pEventAdded->GetEventType()==EVENT_DAMAGE)
|
|
{
|
|
// AUDIO, i've just been thrown off my bike audio
|
|
pPed->NewSay("OVER_HANDLEBARS");
|
|
|
|
nmDebugf2("[%u] ProcessVehicleImpact:%s(%p) Added High fall task (ped knocked from vehicle)", fwTimer::GetTimeInMilliseconds(), pPed->GetModelName(), pPed);
|
|
|
|
// Determine velocity prior to processing all attachments as processing all attachments will update our current position
|
|
Mat34V lastMatrix = PHSIM->GetLastInstanceMatrix(pPed->GetRagdollInst());
|
|
Vector3 vVelocityFromLastMatrix = VEC3V_TO_VECTOR3(pPed->GetRagdollInst()->GetMatrix().GetCol3() - lastMatrix.GetCol3());
|
|
vVelocityFromLastMatrix *= fwTimer::GetInvTimeStep();
|
|
|
|
// do an explicit attachment update here to
|
|
// make sure the ped is in the correct position
|
|
if(pPed->GetIsAttached())
|
|
{
|
|
CPhysical* pAttachParent = static_cast<CPhysical*>(pPed->GetAttachParent());
|
|
if(pAttachParent && pAttachParent->GetCurrentPhysicsInst())
|
|
{
|
|
pAttachParent->UpdateEntityFromPhysics(pAttachParent->GetCurrentPhysicsInst(), 1);
|
|
}
|
|
pPed->ProcessAllAttachments();
|
|
}
|
|
|
|
CTask* pTaskRoll = rage_new CTaskNMThroughWindscreen(2000, 30000, false, pVehicle);
|
|
CTask* pTaskComplexNM = rage_new CTaskNMControl(2000, 30000, pTaskRoll, CTaskNMControl::DO_BLEND_FROM_NM);
|
|
|
|
((CEventDamage*)pEventAdded)->SetPhysicalResponseTask(pTaskComplexNM, true);
|
|
|
|
pPed->SetRagdollState(RAGDOLL_STATE_PHYS_ACTIVATE);
|
|
|
|
if (pPed->GetRagdollInst() != NULL)
|
|
{
|
|
pPed->GetRagdollInst()->SetRagdollEjectedFromVehicle(pVehicle);
|
|
// don't reduce the velocity of the ragdoll on activation (by default we seem to half it)
|
|
pPed->GetRagdollInst()->SetIncomingAnimVelocityScaleReset(1.0f);
|
|
}
|
|
|
|
float fSpeedFromLastMatrix = 0.0f;
|
|
// If we don't have a meaningful velocity then just use the impact normal
|
|
if (vVelocityFromLastMatrix.Mag2() > square(1.0f))
|
|
{
|
|
fSpeedFromLastMatrix = NormalizeAndMag(vVelocityFromLastMatrix);
|
|
}
|
|
else
|
|
{
|
|
vVelocityFromLastMatrix = vecImpactNormal;
|
|
}
|
|
|
|
// Calculate the amount of forward impulse
|
|
|
|
Vector3 vImpulse(vVelocityFromLastMatrix);
|
|
|
|
// Determine the thrown force (adds some upwards force and scales the amount of forward force) and then scale the overall thrown force (including
|
|
// the upwards component) by the speed of the ped
|
|
|
|
float fSpeedUpwardComponent = 0.0f;
|
|
// 0.0f -> s_fMinSpeed = 0.0f -> s_fUpMinComponent
|
|
// s_fMinSpeed -> s_fMaxSpeed = s_fUpMinComponent -> s_fUpMaxComponent
|
|
// s_fMaxComponent -> +INF = s_fUpMaxComponent
|
|
if (fSpeedFromLastMatrix < CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMinSpeed)
|
|
{
|
|
fSpeedUpwardComponent = RampValue(fSpeedFromLastMatrix, 0.0f, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMinSpeed, 0.0f, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeUpMinComponent);
|
|
}
|
|
else
|
|
{
|
|
fSpeedUpwardComponent = RampValue(fSpeedFromLastMatrix, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMinSpeed, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMaxSpeed, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeUpMinComponent, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeUpMaxComponent);
|
|
}
|
|
|
|
float fSpeedForwardComponent = RampValue(fSpeedFromLastMatrix, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMinSpeed, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMaxSpeed, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeForwardMinComponent, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeForwardMaxComponent);
|
|
|
|
// Scale the impact depending on how upright the ped is
|
|
Vector3 vPedUp = VEC3V_TO_VECTOR3(pPed->GetTransform().GetUp());
|
|
float fMassForApplyVehicleForce = RampValue(vPedUp.z, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMinUpright, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMaxUpright, 0.0f, pPed->GetMassForApplyVehicleForce());
|
|
vImpulse = fMassForApplyVehicleForce * ((vImpulse * fSpeedForwardComponent) + (vPedUp * fSpeedUpwardComponent));
|
|
|
|
// GTAV - B*2189036 - If the ped is on a bike and is hit head on by a large vehicle, e.g. a truck, the impulse can be in the wrong direction
|
|
// which pushes the last matrix into the vehicle and results in the ped not colliding with it.
|
|
if( Dot( vImpulse, VEC3V_TO_VECTOR3( lastMatrix.GetCol1().GetY() ) ) > 0.0f )
|
|
{
|
|
Vector3 vDistance = vImpulse * fwTimer::GetTimeStep();
|
|
lastMatrix.SetCol3(pPed->GetRagdollInst()->GetMatrix().GetCol3() - RCC_VEC3V(vDistance));
|
|
}
|
|
|
|
// pitch the last matrix back a little so we get pitched forward when we activate
|
|
float fSpeedPitchComponent = RampValue(fSpeedFromLastMatrix, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMinSpeed, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikeMaxSpeed, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikePitchMinComponent, CTaskNMThroughWindscreen::sm_Tunables.m_KnockOffBikePitchMaxComponent);
|
|
Mat34VRotateLocalX(lastMatrix, ScalarV(fSpeedPitchComponent*fwTimer::GetTimeStep()));
|
|
|
|
Assertf(IsFiniteStable(lastMatrix.GetCol3()), "lastMatrix.GetCol3() is invalid. timestep is %f. vImpulse.Mag2() is %f.",
|
|
fwTimer::GetTimeStep(), vImpulse.Mag2());
|
|
PHSIM->SetLastInstanceMatrix(pPed->GetRagdollInst(), lastMatrix);
|
|
|
|
// returning true will trigger the SwitchToRagdoll
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
#if __BANK
|
|
void CCollisionEventScanner::AddKnockOffVehicleWidgets(rage::bkBank& bank)
|
|
{
|
|
bank.PushGroup("Knock off vehicle");
|
|
bank.AddSlider("Player mult", &sfBikeKO_PlayerMult, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Player mult MP", &sfBikeKO_PlayerMultMP, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Vehicle mult (player)", &sfBikeKO_VehicleMultPlayer, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Vehicle mult (AI)", &sfBikeKO_VehicleMultAI, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Train mult", &sfBikeKO_TrainMult, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Bike mult", &sfBikeKO_BikeMult, 0.0f, 100.0f, 0.1f);
|
|
|
|
bank.AddSlider("Bike KO mag default", &sfBikeKO_Mag, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Bike KO mag easy", &sfBikeKO_EasyMag, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Bike KO mag hard", &sfBikeKO_HardMag, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Push bike KO mag default", &sfPushBikeKO_Mag, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Push bike KO mag easy", &sfPushBikeKO_EasyMag, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Push bike KO mag hard", &sfPushBikeKO_HardMag, 0.0f, 100.0f, 0.1f);
|
|
|
|
|
|
bank.AddSlider("Head on dot", &sfBikeKO_HeadOnDot, 0.0f, 1.0f, 0.01f);
|
|
|
|
bank.AddSlider("Front mult", &sfBikeKO_FrontMult, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Front Z mult", &sfBikeKO_FrontZMult, 0.0f, 100.0f, 0.1f);
|
|
|
|
bank.AddSlider("Top mult", &sfBikeKO_TopMult, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Upside down mult", &sfBikeKO_TopUpsideDownMult, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Top upside down multi AI", &sfBikeKO_TopUpsideDownMultAI, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Bottom mult", &sfBikeKO_BottomMult, 0.0f, 100.0f, 0.1f);
|
|
bank.AddSlider("Side mult", &sfBikeKO_SideMult, 0.0f, 100.0f, 0.1f);
|
|
bank.PopGroup(); // "Knock off vehicle"
|
|
}
|
|
#endif // __BANK
|
|
|
|
bool CCollisionEventScanner::ProcessBikeImpact(CPed* pPed, CEntity* pImpactEntity, float fImpactMag, const Vector3& vecImpactNormal, int UNUSED_PARAM(nComponent), bool bForceBecauseDriverFell)
|
|
{
|
|
// Note: there's an initial threshold in CPhysics::PostSimUpdate that can stop us getting in here in the first place
|
|
#if AI_OPTIMISATIONS_OFF && 0
|
|
aiLogf("KO Bike original impact %f\n", fImpactMag);
|
|
#endif
|
|
if(!pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || pPed->GetMyVehicle()==NULL)
|
|
return false;
|
|
|
|
if(!pPed->GetMyVehicle()->InheritsFromBike())
|
|
{
|
|
Assertf(false, "ProcessBikeImpact called on a non-bike vehicle");
|
|
return false;
|
|
}
|
|
|
|
// don't knock off peds when we're doing a car recording
|
|
if(pPed->GetMyVehicle()->GetIntelligence()->GetRecordingNumber() >= 0
|
|
&& !CVehicleRecordingMgr::GetUseCarAI(pPed->GetMyVehicle()->GetIntelligence()->GetRecordingNumber()))
|
|
return false;
|
|
|
|
if(pPed->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_NEVER && !pPed->IsInjured())
|
|
return false;
|
|
|
|
CBike* pBike = (CBike*)pPed->GetMyVehicle();
|
|
bool bFallOff = bForceBecauseDriverFell;
|
|
|
|
// if we are the passenger on a bike driven by a network clone we only fall off when the driver does.
|
|
// This avoid passengers being knocked off bikes due to collisions that occur because of network lag
|
|
if(pBike->GetDriver() && pBike->GetDriver() != pPed && pBike->GetDriver()->IsNetworkClone())
|
|
{
|
|
return bFallOff;
|
|
}
|
|
|
|
if(!bFallOff)
|
|
{
|
|
fImpactMag *= pBike->GetInvMass();
|
|
|
|
// For consistency all peds use the same settings on a bike as the rider unless they are the player.
|
|
CPed* pPedToCheckForces = pPed;
|
|
if( pBike->GetDriver() && pBike->GetDriver()->IsAPlayerPed() && !pPed->IsAPlayerPed() )
|
|
{
|
|
pPedToCheckForces = pBike->GetDriver();
|
|
}
|
|
|
|
if(pPedToCheckForces->IsPlayer())
|
|
{
|
|
if(NetworkInterface::IsGameInProgress())
|
|
fImpactMag *= sfBikeKO_PlayerMultMP;
|
|
else
|
|
fImpactMag *= sfBikeKO_PlayerMult;
|
|
|
|
// Scale for impacts with various vehicle types.
|
|
if(pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pImpactVehicle = static_cast<CVehicle*>(pImpactEntity);
|
|
if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
fImpactMag *= sfBikeKO_TrainMult;
|
|
}
|
|
else
|
|
{
|
|
fImpactMag *= sfBikeKO_VehicleMultPlayer;
|
|
}
|
|
}
|
|
}
|
|
else // A.I. peds.
|
|
{
|
|
// Scale for impacts with various vehicle types.
|
|
if(pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pImpactVehicle = static_cast<CVehicle*>(pImpactEntity);
|
|
if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
fImpactMag *= sfBikeKO_TrainMult;
|
|
}
|
|
else if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_BIKE)
|
|
{
|
|
//reduce impact for bikes that're chasing the same target (and thus aren't avoiding each other)
|
|
if(pImpactVehicle->GetIntelligence()->GetAvoidanceCache().m_pTarget &&
|
|
pImpactVehicle->GetIntelligence()->GetAvoidanceCache().m_pTarget == pBike->GetIntelligence()->GetAvoidanceCache().m_pTarget)
|
|
{
|
|
fImpactMag *= sfBikeKO_BikeMult;
|
|
}
|
|
else
|
|
{
|
|
fImpactMag *= sfBikeKO_VehicleMultAI;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float fScaledFront = sfBikeKO_FrontMult;
|
|
float fFrontDotProd = rage::Abs(vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pBike->GetTransform().GetB())));
|
|
if(fFrontDotProd > sfBikeKO_HeadOnDot)
|
|
{
|
|
// check incidence of impact to the ground
|
|
float fScaledFrontVertical = rage::Max(0.0f, vecImpactNormal.Dot(ZAXIS));
|
|
// if bike is hitting the ground head first
|
|
if( fScaledFrontVertical > sfBikeKO_HeadOnDot)
|
|
fScaledFront = sfBikeKO_FrontMult + sfBikeKO_FrontZMult*fScaledFrontVertical*fScaledFrontVertical;
|
|
}
|
|
|
|
// different multipliers for collisions from above if bike is upside down
|
|
float fScaledTop = sfBikeKO_TopMult;
|
|
if(pBike->GetTransform().GetC().GetZf() < -0.5f)
|
|
{
|
|
fScaledTop = pBike->GetStatus()==STATUS_PLAYER ? sfBikeKO_TopUpsideDownMult : sfBikeKO_TopUpsideDownMultAI;
|
|
}
|
|
|
|
// calculate combined scaled impact mag
|
|
Vector3 vecBikeUp(VEC3V_TO_VECTOR3(pBike->GetTransform().GetC()));
|
|
fImpactMag *= fScaledFront * rage::Abs( vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pBike->GetTransform().GetB())) )
|
|
+ sfBikeKO_SideMult * rage::Abs( vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pBike->GetTransform().GetA())) )
|
|
+ sfBikeKO_BottomMult * rage::Max(0.0f, vecImpactNormal.Dot(vecBikeUp) )
|
|
+ fScaledTop * rage::Max(0.0f, -vecImpactNormal.Dot(vecBikeUp) );
|
|
|
|
bool bPushBike = pBike->GetVehicleType()==VEHICLE_TYPE_BICYCLE;
|
|
float fKoMag = bPushBike ? sfPushBikeKO_Mag : sfBikeKO_Mag;
|
|
if(pPedToCheckForces->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_EASY)
|
|
fKoMag = bPushBike ? sfPushBikeKO_EasyMag : sfBikeKO_EasyMag;
|
|
else if(pPedToCheckForces->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_HARD)
|
|
fKoMag = bPushBike ? sfPushBikeKO_HardMag : sfBikeKO_HardMag;
|
|
|
|
if( pImpactEntity->GetIsTypeVehicle() )
|
|
{
|
|
if( static_cast<CVehicle*>( pImpactEntity )->GetHitByWeaponBlade() )
|
|
{
|
|
static dev_float sfHitByWeaponBladeKOScale = 2.0f;
|
|
fKoMag *= sfHitByWeaponBladeKOScale;
|
|
}
|
|
}
|
|
#if AI_OPTIMISATIONS_OFF && 0
|
|
aiLogf(DIAG_SEVERITY_DISPLAY,"KO Bike scaled impact %f\n", fImpactMag);
|
|
#endif
|
|
if(fImpactMag > fKoMag)
|
|
{
|
|
if(pImpactEntity->GetIsPhysical() && !pImpactEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
if(pImpactEntity->GetCurrentPhysicsInst()->GetArchetype()->GetMass() > 60.0f)
|
|
bFallOff = true;
|
|
}
|
|
else
|
|
{
|
|
bFallOff = true;
|
|
}
|
|
}
|
|
|
|
// For new handling technique we need to KO if we are scraping along on the side of the bike
|
|
if(CBike::ms_bNewLeanMethod && !bFallOff)
|
|
{
|
|
bool bIsWheelCollision = false;
|
|
bool bWheelsTouching = false;
|
|
for(int i = 0; i < pBike->GetNumWheels(); i++)
|
|
{
|
|
if(pBike->GetWheel(i)->GetIsTouching())
|
|
{
|
|
bWheelsTouching = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!bIsWheelCollision && !bWheelsTouching)
|
|
{
|
|
// Check for scraping along ground
|
|
// Max ground slope at which we knock people off
|
|
static dev_float fScrapeAngleCos = 0.5f;
|
|
static dev_float fSideTolerance = 0.707f; // what do we count as a side collision?
|
|
|
|
float fSideCollisionFactor = rage::Abs(vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pBike->GetTransform().GetA())));
|
|
if(vecImpactNormal.z > fScrapeAngleCos && fSideCollisionFactor > fSideTolerance)
|
|
{
|
|
bFallOff = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return bFallOff;
|
|
}
|
|
|
|
bool CCollisionEventScanner::ProcessQuadBikeImpact(CPed* pPed, CEntity* pImpactEntity, float fImpactMag, const Vector3& vecImpactNormal, int UNUSED_PARAM(nComponent), bool bForceBecauseDriverFell)
|
|
{
|
|
// Note: there's an initial threshold in CPhysics::PostSimUpdate that can stop us getting in here in the first place
|
|
#if AI_OPTIMISATIONS_OFF && 0
|
|
aiLogf("KO quad bike original impact %f\n", fImpactMag);
|
|
#endif
|
|
if(!pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || pPed->GetMyVehicle()==NULL)
|
|
return false;
|
|
|
|
if(!pPed->GetMyVehicle()->InheritsFromQuadBike() &&
|
|
!pPed->GetMyVehicle()->InheritsFromAmphibiousQuadBike() )
|
|
{
|
|
Assertf(false, "ProcessQuadBikeImpact called on a non-quad vehicle");
|
|
return false;
|
|
}
|
|
|
|
// don't knock off peds when we're doing a car recording
|
|
if(pPed->GetMyVehicle()->GetIntelligence()->GetRecordingNumber() >= 0
|
|
&& !CVehicleRecordingMgr::GetUseCarAI(pPed->GetMyVehicle()->GetIntelligence()->GetRecordingNumber()))
|
|
return false;
|
|
|
|
if(pPed->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_NEVER && !pPed->IsInjured())
|
|
return false;
|
|
|
|
CVehicle* pQuad = (CVehicle*)pPed->GetMyVehicle();
|
|
bool bFallOff = bForceBecauseDriverFell;
|
|
|
|
// if we are the passenger on a quad driven by a network clone we only fall off when the driver does.
|
|
// This avoid passengers being knocked off bikes due to collisions that occur because of network lag
|
|
if(pQuad->GetDriver() && pQuad->GetDriver() != pPed && pQuad->GetDriver()->IsNetworkClone())
|
|
{
|
|
return bFallOff;
|
|
}
|
|
|
|
// If the quad bike is on its side or upside down, we throw the rider.
|
|
dev_float sfQuadKnockOffZ = 0.1f;
|
|
if(pQuad->GetTransform().GetC().GetZf() < sfQuadKnockOffZ)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if(!bFallOff)
|
|
{
|
|
fImpactMag *= pQuad->GetInvMass();
|
|
|
|
// For consistency all peds use the same settings on a quad as the rider unless they are the player.
|
|
CPed* pPedToCheckForces = pPed;
|
|
if( pQuad->GetDriver() && pQuad->GetDriver()->IsAPlayerPed() && !pPed->IsAPlayerPed() )
|
|
{
|
|
pPedToCheckForces = pQuad->GetDriver();
|
|
}
|
|
|
|
if(pPedToCheckForces->IsPlayer())
|
|
{
|
|
if(NetworkInterface::IsGameInProgress())
|
|
fImpactMag *= sfBikeKO_PlayerMultMP;
|
|
else
|
|
fImpactMag *= sfBikeKO_PlayerMult;
|
|
|
|
// Scale for impacts with various vehicle types.
|
|
if(pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pImpactVehicle = static_cast<CVehicle*>(pImpactEntity);
|
|
if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
fImpactMag *= sfBikeKO_TrainMult;
|
|
}
|
|
else
|
|
{
|
|
fImpactMag *= sfBikeKO_VehicleMultPlayer;
|
|
}
|
|
}
|
|
}
|
|
else // A.I. peds.
|
|
{
|
|
// Scale for impacts with various vehicle types.
|
|
if(pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pImpactVehicle = static_cast<CVehicle*>(pImpactEntity);
|
|
if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
fImpactMag *= sfBikeKO_TrainMult;
|
|
}
|
|
else
|
|
{
|
|
fImpactMag *= sfBikeKO_VehicleMultAI;
|
|
}
|
|
}
|
|
}
|
|
|
|
float fScaledFront = sfBikeKO_FrontMult;
|
|
float fFrontDotProd = rage::Abs(vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pQuad->GetTransform().GetB())));
|
|
if(fFrontDotProd > sfBikeKO_HeadOnDot)
|
|
{
|
|
// check incidence of impact to the ground
|
|
float fScaledFrontVertical = rage::Max(0.0f, vecImpactNormal.Dot(ZAXIS));
|
|
// if quad is hitting the ground head first
|
|
if( fScaledFront > sfBikeKO_HeadOnDot)
|
|
fScaledFront = sfBikeKO_FrontMult + sfBikeKO_FrontZMult*fScaledFrontVertical*fScaledFrontVertical;
|
|
}
|
|
|
|
// different multipliers for collisions from above if quad is upside down
|
|
float fScaledTop = sfBikeKO_TopMult;
|
|
if(pQuad->GetTransform().GetC().GetZf() < -0.5f)
|
|
{
|
|
fScaledTop = pQuad->GetStatus()==STATUS_PLAYER ? sfBikeKO_TopUpsideDownMult : sfBikeKO_TopUpsideDownMultAI;
|
|
}
|
|
|
|
// calculate combined scaled impact mag
|
|
Vector3 vecBikeUp(VEC3V_TO_VECTOR3(pQuad->GetTransform().GetC()));
|
|
fImpactMag *= fScaledFront * rage::Abs( vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pQuad->GetTransform().GetB())) )
|
|
+ sfBikeKO_SideMult * rage::Abs( vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pQuad->GetTransform().GetA())) )
|
|
+ sfBikeKO_BottomMult * rage::Max(0.0f, vecImpactNormal.Dot(vecBikeUp) )
|
|
+ fScaledTop * rage::Max(0.0f, -vecImpactNormal.Dot(vecBikeUp) );
|
|
|
|
|
|
float fKoMag = sfBikeKO_Mag;
|
|
if(pPedToCheckForces->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_EASY)
|
|
fKoMag = sfBikeKO_EasyMag;
|
|
else if(pPedToCheckForces->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_HARD)
|
|
fKoMag = sfBikeKO_HardMag;
|
|
|
|
#if AI_OPTIMISATIONS_OFF && 0
|
|
aiLogf(DIAG_SEVERITY_DISPLAY,"KO Bike scaled impact %f\n", fImpactMag);
|
|
#endif
|
|
if(fImpactMag > fKoMag)
|
|
{
|
|
if(pImpactEntity->GetIsPhysical() && !pImpactEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
if(pImpactEntity->GetCurrentPhysicsInst()->GetArchetype()->GetMass() > 60.0f)
|
|
bFallOff = true;
|
|
}
|
|
else
|
|
{
|
|
bFallOff = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bFallOff;
|
|
}
|
|
|
|
bool CCollisionEventScanner::ProcessJetSkiImpact(CPed* pPed, CEntity* pImpactEntity, float fImpactMag, const Vector3& vecImpactNormal, int UNUSED_PARAM(nComponent), bool bForceBecauseDriverFell)
|
|
{
|
|
// Note: there's an initial threshold in CPhysics::PostSimUpdate that can stop us getting in here in the first place
|
|
#if AI_OPTIMISATIONS_OFF && 0
|
|
aiLogf("KO jetski original impact %f\n", fImpactMag);
|
|
#endif
|
|
if(!pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || pPed->GetMyVehicle()==NULL)
|
|
return false;
|
|
|
|
if(!pPed->GetMyVehicle()->GetIsJetSki())
|
|
{
|
|
Assertf(false, "ProcessJetSkiImpact called on a non-jetski vehicle");
|
|
return false;
|
|
}
|
|
|
|
// don't knock off peds when we're doing a car recording
|
|
if(pPed->GetMyVehicle()->GetIntelligence()->GetRecordingNumber() >= 0
|
|
&& !CVehicleRecordingMgr::GetUseCarAI(pPed->GetMyVehicle()->GetIntelligence()->GetRecordingNumber()))
|
|
return false;
|
|
|
|
if(pPed->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_NEVER && !pPed->IsInjured())
|
|
return false;
|
|
|
|
CBoat* pJetSki = (CBoat*)pPed->GetMyVehicle();
|
|
bool bFallOff = bForceBecauseDriverFell;
|
|
|
|
// if we are the passenger on a jetski driven by a network clone we only fall off when the driver does.
|
|
// This avoid passengers being knocked off bikes due to collisions that occur because of network lag
|
|
if(pJetSki->GetDriver() && pJetSki->GetDriver() != pPed && pJetSki->GetDriver()->IsNetworkClone())
|
|
{
|
|
return bFallOff;
|
|
}
|
|
|
|
if(!bFallOff)
|
|
{
|
|
fImpactMag *= pJetSki->GetInvMass();
|
|
|
|
// For consistency all peds use the same settings on a jetski as the rider unless they are the player.
|
|
CPed* pPedToCheckForces = pPed;
|
|
if( pJetSki->GetDriver() && pJetSki->GetDriver()->IsAPlayerPed() && !pPed->IsAPlayerPed() )
|
|
{
|
|
pPedToCheckForces = pJetSki->GetDriver();
|
|
}
|
|
|
|
if(pPedToCheckForces->IsPlayer())
|
|
{
|
|
if(NetworkInterface::IsGameInProgress())
|
|
fImpactMag *= sfBikeKO_PlayerMultMP;
|
|
else
|
|
fImpactMag *= sfBikeKO_PlayerMult;
|
|
|
|
// Scale for impacts with various vehicle types.
|
|
if(pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pImpactVehicle = static_cast<CVehicle*>(pImpactEntity);
|
|
if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
fImpactMag *= sfBikeKO_TrainMult;
|
|
}
|
|
else
|
|
{
|
|
fImpactMag *= sfBikeKO_VehicleMultPlayer;
|
|
}
|
|
}
|
|
}
|
|
else // A.I. peds.
|
|
{
|
|
// Scale for impacts with various vehicle types.
|
|
if(pImpactEntity->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pImpactVehicle = static_cast<CVehicle*>(pImpactEntity);
|
|
if(pImpactVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
fImpactMag *= sfBikeKO_TrainMult;
|
|
}
|
|
else
|
|
{
|
|
fImpactMag *= sfBikeKO_VehicleMultAI;
|
|
}
|
|
}
|
|
}
|
|
|
|
float fScaledFront = sfBikeKO_FrontMult;
|
|
float fFrontDotProd = rage::Abs(vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pJetSki->GetTransform().GetB())));
|
|
if(fFrontDotProd > sfBikeKO_HeadOnDot)
|
|
{
|
|
// check incidence of impact to the ground
|
|
float fScaledFrontVertical = rage::Max(0.0f, vecImpactNormal.Dot(ZAXIS));
|
|
// if jetski is hitting the ground head first
|
|
if( fScaledFront > sfBikeKO_HeadOnDot)
|
|
fScaledFront = sfBikeKO_FrontMult + sfBikeKO_FrontZMult*fScaledFrontVertical*fScaledFrontVertical;
|
|
}
|
|
|
|
// different multipliers for collisions from above if jetski is upside down
|
|
float fScaledTop = sfBikeKO_TopMult;
|
|
if(pJetSki->GetTransform().GetC().GetZf() < -0.5f)
|
|
{
|
|
fScaledTop = pJetSki->GetStatus()==STATUS_PLAYER ? sfBikeKO_TopUpsideDownMult : sfBikeKO_TopUpsideDownMultAI;
|
|
}
|
|
|
|
// calculate combined scaled impact mag
|
|
Vector3 vecBikeUp(VEC3V_TO_VECTOR3(pJetSki->GetTransform().GetC()));
|
|
fImpactMag *= fScaledFront * rage::Abs( vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pJetSki->GetTransform().GetB())) )
|
|
+ sfBikeKO_SideMult * rage::Abs( vecImpactNormal.Dot(VEC3V_TO_VECTOR3(pJetSki->GetTransform().GetA())) )
|
|
+ sfBikeKO_BottomMult * rage::Max(0.0f, vecImpactNormal.Dot(vecBikeUp) )
|
|
+ fScaledTop * rage::Max(0.0f, -vecImpactNormal.Dot(vecBikeUp) );
|
|
|
|
|
|
float fKoMag = sfBikeKO_Mag;
|
|
if(pPedToCheckForces->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_EASY)
|
|
fKoMag = sfBikeKO_EasyMag;
|
|
else if(pPedToCheckForces->m_PedConfigFlags.GetCantBeKnockedOffVehicle()==KNOCKOFFVEHICLE_HARD)
|
|
fKoMag = sfBikeKO_HardMag;
|
|
|
|
#if AI_OPTIMISATIONS_OFF && 0
|
|
aiLogf(DIAG_SEVERITY_DISPLAY,"KO Bike scaled impact %f\n", fImpactMag);
|
|
#endif
|
|
if(fImpactMag > fKoMag)
|
|
{
|
|
if(pImpactEntity->GetIsPhysical() && !pImpactEntity->GetIsAnyFixedFlagSet())
|
|
{
|
|
if(pImpactEntity->GetCurrentPhysicsInst()->GetArchetype()->GetMass() > 60.0f)
|
|
bFallOff = true;
|
|
}
|
|
else
|
|
{
|
|
bFallOff = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bFallOff;
|
|
}
|
|
|
|
void CCollisionEventScanner::ProcessBumps(CPed& rPed)
|
|
{
|
|
//Ensure the collision history is valid.
|
|
const CCollisionHistory* pCollisionHistory = rPed.GetFrameCollisionHistory();
|
|
if(!pCollisionHistory)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Check if the most significant collision entity is valid.
|
|
const CCollisionRecord* pMostSignificantCollisionRecord = pCollisionHistory->GetMostSignificantCollisionRecord();
|
|
const CEntity* pMostSignificantCollisionEntity = pMostSignificantCollisionRecord ? pMostSignificantCollisionRecord->m_pRegdCollisionEntity.Get() : NULL;
|
|
if(pMostSignificantCollisionEntity)
|
|
{
|
|
rPed.BumpedByEntity(*pMostSignificantCollisionEntity);
|
|
}
|
|
|
|
//Sometimes, checking the most significant collision entity isn't quite enough to detect all player bumps.
|
|
//For this reason, check at least the first ped/vehicle collision records as well.
|
|
|
|
//Check if the first ped collision entity is valid.
|
|
const CCollisionRecord* pFirstPedCollisionRecord = pCollisionHistory->GetFirstPedCollisionRecord();
|
|
const CEntity* pFirstPedCollisionEntity = pFirstPedCollisionRecord ? pFirstPedCollisionRecord->m_pRegdCollisionEntity.Get() : NULL;
|
|
if(pFirstPedCollisionEntity && (pFirstPedCollisionEntity != pMostSignificantCollisionEntity))
|
|
{
|
|
rPed.BumpedByEntity(*pFirstPedCollisionEntity);
|
|
}
|
|
|
|
//Check if the first ped collision entity is valid.
|
|
const CCollisionRecord* pFirstVehicleCollisionRecord = pCollisionHistory->GetFirstVehicleCollisionRecord();
|
|
const CEntity* pFirstVehicleCollisionEntity = pFirstVehicleCollisionRecord ? pFirstVehicleCollisionRecord->m_pRegdCollisionEntity.Get() : NULL;
|
|
if(pFirstVehicleCollisionEntity && (pFirstVehicleCollisionEntity != pMostSignificantCollisionEntity))
|
|
{
|
|
rPed.BumpedByEntity(*pFirstVehicleCollisionEntity);
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////
|
|
//EVENT SCANNER
|
|
/////////////////
|
|
u32 CEventScanner::m_sDeadPedWalkingTimer = 0;
|
|
//
|
|
CEventScanner::CEventScanner()
|
|
: m_TimeTranqDamageStarted(0)
|
|
{
|
|
}
|
|
|
|
CEventScanner::~CEventScanner()
|
|
{
|
|
}
|
|
|
|
|
|
void CEventScanner::ScanForEventsNow(const CPed& ped, const int iScanType)
|
|
{
|
|
const bool bForce = true;
|
|
switch(iScanType)
|
|
{
|
|
case VEHICLE_POTENTIAL_COLLISION_SCAN:
|
|
m_vehicleCollisionScanner.ResetTimer();
|
|
m_vehicleCollisionScanner.Scan(ped, bForce);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
EXT_PF_TIMER(GroupScanner);
|
|
|
|
const int CGroupEventScanner::ms_iLatencyPeriod=100;
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Scan for group events, e.g. geting in and out of cars, attacking...
|
|
//-------------------------------------------------------------------------
|
|
void CGroupEventScanner::Scan(CPed& ped)
|
|
{
|
|
PF_FUNC(GroupScanner);
|
|
|
|
CPedGroup* pPedGroup=ped.GetPedsGroup();
|
|
if( !pPedGroup )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!pPedGroup->GetNeedsGroupEventScan())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CPed* pLeader = pPedGroup->GetGroupMembership()->GetLeader();
|
|
if( !pLeader )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If this ped currently has a primary task, don't scan (it has been scripted to do something specific
|
|
if( ped.GetPedIntelligence()->GetTaskAtPriority(PED_TASK_PRIORITY_PRIMARY) != NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bShouldScan = false;
|
|
if( IsRegistered() )
|
|
{
|
|
bShouldScan = ShouldBeProcessedThisFrame();
|
|
}
|
|
else
|
|
{
|
|
//Set the timer if it isn't already set.
|
|
if(!m_timer.IsSet())
|
|
{
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),0);
|
|
}
|
|
|
|
//Test if its time to do another check.
|
|
if(m_timer.IsOutOfTime())
|
|
{
|
|
m_timer.Set(fwTimer::GetTimeInMilliseconds(),ms_iLatencyPeriod);
|
|
bShouldScan = true;
|
|
}
|
|
}
|
|
|
|
if( bShouldScan || ped.GetPedResetFlag(CPED_RESET_FLAG_ForceScanForEventsThisFrame) )
|
|
{
|
|
StartProcess();
|
|
if( pLeader && pLeader != &ped )
|
|
{
|
|
|
|
const bool bLeaderOnBoat = (pLeader->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && pLeader->GetMyVehicle() && pLeader->GetMyVehicle()->GetVehicleType()==VEHICLE_TYPE_BOAT) ||
|
|
(pLeader->GetGroundPhysical() && pLeader->GetGroundPhysical()->GetIsTypeVehicle() && ((CVehicle*)pLeader->GetGroundPhysical())->GetVehicleType()==VEHICLE_TYPE_BOAT);
|
|
|
|
const bool bPedOnBoat = (ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && ped.GetMyVehicle() && ped.GetMyVehicle()->GetVehicleType()==VEHICLE_TYPE_BOAT) ||
|
|
(ped.GetGroundPhysical() && ped.GetGroundPhysical()->GetIsTypeVehicle() && ((CVehicle*)ped.GetGroundPhysical())->GetVehicleType()==VEHICLE_TYPE_BOAT);
|
|
const bool bPedEnteringVehicle = ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE) != NULL;
|
|
const bool bLeaderEnteringVehicle = pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE, true) || pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE, true);
|
|
|
|
bool bPedInVehicle = ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) || ped.GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_ENTER_VEHICLE );
|
|
|
|
const bool bLeaderInVehicle = bLeaderOnBoat || (pLeader->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) &&
|
|
!pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_EXIT_VEHICLE )) ||
|
|
(pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_ENTER_VEHICLE, true ) &&
|
|
pLeader->GetPedIntelligence()->GetQueriableInterface()->GetStateForTaskType( CTaskTypes::TASK_ENTER_VEHICLE ) < CTaskEnterVehicle::State_Finish);
|
|
|
|
// Double check we're in the same vehicle
|
|
if( ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && ped.GetMyVehicle() && pLeader->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && pLeader->GetMyVehicle() && ped.GetMyVehicle() != pLeader->GetMyVehicle() )
|
|
{
|
|
bPedInVehicle = false;
|
|
}
|
|
|
|
// VEHICLE EVENTS
|
|
{
|
|
if( bLeaderInVehicle && !bPedInVehicle )
|
|
{
|
|
bool bIgnore = false;
|
|
|
|
// Ignore leader entering vehicle events if this ped cant use vehicles.
|
|
if( ped.GetTaskData().GetIsFlagSet(CTaskFlags::DisableVehicleUsage) )
|
|
{
|
|
bIgnore = true;
|
|
}
|
|
else if( ped.GetPedType() == PEDTYPE_MEDIC
|
|
&& ped.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_FOLLOW_LEADER_IN_FORMATION))
|
|
{
|
|
bIgnore = true;
|
|
}
|
|
else if(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_WillFollowLeaderAnyMeans ) || ped.GetPedConfigFlag( CPED_CONFIG_FLAG_DontEnterVehiclesInPlayersGroup ) ||
|
|
ped.GetPedConfigFlag(CPED_CONFIG_FLAG_DontEnterLeadersVehicle))
|
|
{
|
|
// Only issue this event if this ped is not set to follow any means, for in that case the
|
|
// CTaskComplexFollowLeaderAnyMeans (see SeekEntity.cpp) will handle the car entering/exiting
|
|
bIgnore = true;
|
|
}
|
|
else
|
|
{
|
|
const CVehicle* pLeadersVehicle = pLeader->GetMyVehicle();
|
|
if(pLeadersVehicle)
|
|
{
|
|
const CPed* pDriver = pLeadersVehicle->GetDriver();
|
|
if(pDriver)
|
|
{
|
|
CTaskVehiclePersuit* pDriverTask = static_cast<CTaskVehiclePersuit*>(pDriver->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_VEHICLE_PERSUIT));
|
|
if(pDriverTask)
|
|
{
|
|
// This shouldn't affect us if the driver of our leader's vehicle is still stopping or exiting
|
|
s32 iDriverState = pDriverTask->GetState();
|
|
if( iDriverState == CTaskVehiclePersuit::State_emergencyStop ||
|
|
iDriverState == CTaskVehiclePersuit::State_exitVehicle ||
|
|
iDriverState == CTaskVehiclePersuit::State_exitVehicleImmediately )
|
|
{
|
|
bIgnore = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !bIgnore && !ped.GetPedIntelligence()->GetEventOfType(EVENT_LEADER_ENTERED_CAR_AS_DRIVER) )
|
|
{
|
|
CEventLeaderEnteredCarAsDriver EventLeaderEnteredCarAsDriver;
|
|
ped.GetPedIntelligence()->AddEvent(EventLeaderEnteredCarAsDriver);
|
|
}
|
|
}
|
|
// Only issue this event if this ped is not set to follow any means, for in that case the
|
|
// CTaskComplexFollowLeaderAnyMeans (see SeekEntity.cpp) will handle the car entering/exiting
|
|
else if( !bLeaderInVehicle && bPedInVehicle && !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_WillFollowLeaderAnyMeans ))
|
|
{
|
|
if( !ped.GetPedIntelligence()->GetEventOfType(EVENT_LEADER_EXITED_CAR_AS_DRIVER) )
|
|
{
|
|
CEventLeaderExitedCarAsDriver EventLeaderExitedCarAsDriver;
|
|
ped.GetPedIntelligence()->AddEvent(EventLeaderExitedCarAsDriver);
|
|
}
|
|
}
|
|
}
|
|
|
|
// SETUP DEFENSIVE AREA, not for cops/swat they still use normal decision makers in groups
|
|
if( !ped.IsLawEnforcementPed() || ped.PopTypeIsMission() )
|
|
{
|
|
static float PLAYER_GROUP_SIZE = 15.0f;
|
|
static float AI_GROUP_SIZE = 20.0f;
|
|
float fAreaSize = PLAYER_GROUP_SIZE;
|
|
if( pPedGroup->GetGroupMembership()->GetLeader() &&
|
|
!pPedGroup->GetGroupMembership()->GetLeader()->IsAPlayerPed() )
|
|
{
|
|
fAreaSize = AI_GROUP_SIZE;
|
|
}
|
|
Vector3 v1(0.0f, -fAreaSize*0.5f, -7.0f);
|
|
Vector3 v2(0.0f, fAreaSize*0.5f, 7.0f);
|
|
ped.GetPedIntelligence()->GetPrimaryDefensiveArea()->Set( v1, v2, fAreaSize, pLeader, false );
|
|
}
|
|
|
|
// The following group events are only applicable if neither ped is in a vehicle (as they are problematic if so)
|
|
if(!bPedInVehicle && !bLeaderInVehicle)
|
|
{
|
|
// HOLSTER/UNHOLSTER weapon
|
|
// Only when not ourselves in combat
|
|
const bool bInCombat = ped.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT, true);
|
|
const bool bLeaderSwappingWeapon = pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SWAP_WEAPON, true);
|
|
const bool bPedSwappingWeapon = pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SWAP_WEAPON, true);
|
|
const bool bInCover = ped.GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_IN_COVER);
|
|
const bool bIsUnarmed = !ped.GetWeaponManager() || (ped.GetWeaponManager()->GetBestWeaponSlotHash() == atHashString("SLOT_UNARMED"));
|
|
|
|
const CWeapon* pPedWeapon = ped.GetWeaponManager() ? ped.GetWeaponManager()->GetEquippedWeapon() : NULL;
|
|
|
|
if(!bIsUnarmed && !bInCover && !bInCombat && !bLeaderSwappingWeapon && !bPedSwappingWeapon && !ped.GetPedResetFlag( CPED_RESET_FLAG_IsDrowning ) && !ped.GetPedResetFlag( CPED_RESET_FLAG_IsClimbing ) && !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_BlockWeaponSwitching ))
|
|
{
|
|
const CWeapon* pLeaderWeapon = pLeader->GetWeaponManager() ? pLeader->GetWeaponManager()->GetEquippedWeapon() : NULL;
|
|
const bool bLeaderWeaponIsFists = !pLeaderWeapon || pLeaderWeapon->GetWeaponInfo()->GetIsUnarmed()
|
|
#if 0 // CS
|
|
|| pLeaderWeapon->GetWeaponType()==WEAPONTYPE_OBJECT
|
|
#endif // 0
|
|
;
|
|
const bool bPedWeaponIsFists = !pPedWeapon || pPedWeapon->GetWeaponInfo()->GetIsUnarmed()
|
|
#if 0 // CS
|
|
|| pPedWeapon->GetWeaponType()==WEAPONTYPE_OBJECT
|
|
#endif // 0
|
|
;
|
|
|
|
if(!bLeaderWeaponIsFists && bPedWeaponIsFists)
|
|
{
|
|
CEventLeaderUnholsteredWeapon EventLeaderUnholsteredWeapon;
|
|
ped.GetPedIntelligence()->AddEvent(EventLeaderUnholsteredWeapon);
|
|
}
|
|
if(bLeaderWeaponIsFists && !bPedWeaponIsFists)
|
|
{
|
|
CEventLeaderHolsteredWeapon EventLeaderHolsteredWeapon;
|
|
ped.GetPedIntelligence()->AddEvent(EventLeaderHolsteredWeapon);
|
|
}
|
|
}
|
|
|
|
// LEADER ENTERING/EXITING COVER
|
|
if(!bInCombat)
|
|
{
|
|
const bool bLeaderInCover = pLeader->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_IN_COVER, true);
|
|
if( !bLeaderInCover )
|
|
{
|
|
ped.SetPedConfigFlag( CPED_CONFIG_FLAG_GroupPedFailedToEnterCover, false );
|
|
}
|
|
else if( !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_GroupPedFailedToEnterCover ) )
|
|
{
|
|
if( bInCover && ped.GetCoverPoint() == NULL )
|
|
{
|
|
ped.SetPedConfigFlag( CPED_CONFIG_FLAG_GroupPedFailedToEnterCover, true );
|
|
}
|
|
}
|
|
if(bLeaderInCover && !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_GroupPedFailedToEnterCover ) && !bInCover)
|
|
{
|
|
// Disabling to try getting a position or cover point to move to earlier
|
|
//CEventLeaderEnteredCover EventLeaderEnteredCover;
|
|
//ped.GetPedIntelligence()->AddEvent(EventLeaderEnteredCover);
|
|
}
|
|
else if( ( !bLeaderInCover || ped.GetPedConfigFlag( CPED_CONFIG_FLAG_GroupPedFailedToEnterCover ) ) && bInCover &&
|
|
!ped.GetPedResetFlag(CPED_RESET_FLAG_IfLeaderStopsSeekCover))
|
|
{
|
|
CEventLeaderLeftCover EventLeaderLeftCover;
|
|
ped.GetPedIntelligence()->AddEvent(EventLeaderLeftCover);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ped.SetPedConfigFlag( CPED_CONFIG_FLAG_GroupPedFailedToEnterCover, false );
|
|
}
|
|
|
|
if (CTaskFollowLeaderInFormation::IsLeaderInMeleeCombat(pLeader))
|
|
{
|
|
ped.NewSay("FIGHT_ENCOURAGE");
|
|
}
|
|
}
|
|
|
|
// If the leader is not on any boat, but this ped is - then get them to leave the boat.
|
|
// OR if the leader is on a different boat to this ped!
|
|
// Don't do this if either ped is currently entering a vehicle as one might be about to get into
|
|
// the boat AND their ground physical pointer might not be pointing to anything.
|
|
|
|
if(!bPedEnteringVehicle && !bLeaderEnteringVehicle)
|
|
{
|
|
if( (bPedOnBoat && !bLeaderOnBoat) || (bLeaderOnBoat && bPedOnBoat && pLeader->GetGroundPhysical() && ped.GetGroundPhysical() && (pLeader->GetGroundPhysical() != ped.GetGroundPhysical())) )
|
|
{
|
|
CBoat * pBoatToLeave = NULL;
|
|
if(ped.GetGroundPhysical() && ped.GetGroundPhysical()->GetIsTypeVehicle() && ((CVehicle*)ped.GetGroundPhysical())->GetVehicleType()==VEHICLE_TYPE_BOAT)
|
|
{
|
|
pBoatToLeave = (CBoat*)ped.GetGroundPhysical();
|
|
}
|
|
else if(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && ped.GetMyVehicle() && ped.GetMyVehicle()->GetVehicleType()==VEHICLE_TYPE_BOAT)
|
|
{
|
|
pBoatToLeave = (CBoat*)ped.GetMyVehicle();
|
|
}
|
|
else
|
|
{
|
|
Assertf(pBoatToLeave, "No boat to leave!");
|
|
}
|
|
if(pBoatToLeave)
|
|
{
|
|
CEventMustLeaveBoat leaveBoatEvent(pBoatToLeave);
|
|
ped.GetPedIntelligence()->AddEvent(leaveBoatEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
StopProcess();
|
|
}
|
|
|
|
// OFFENSIVE EVENT - CHECK MADE EACH FRAME
|
|
if(pLeader != &ped)
|
|
{
|
|
CEntity* pLeaderTarget = pLeader->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget();
|
|
if( pLeaderTarget &&
|
|
pLeaderTarget->GetIsTypePed() &&
|
|
!ped.GetPedIntelligence()->GetEventOfType(EVENT_ACQUAINTANCE_PED_HATE ) &&
|
|
!ped.GetPedIntelligence()->IsFriendlyWith(*((CPed*)pLeaderTarget)) )
|
|
{
|
|
CEventAcquaintancePedHate event((CPed*)pLeaderTarget);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CShockingEventScanner::ms_bLocalPlayerVehicleHornOn = false;
|
|
Vec3V CShockingEventScanner::ms_PlayerVehiclePosition = Vec3V(V_ZERO);
|
|
|
|
void CShockingEventScanner::UpdateClass()
|
|
{
|
|
// Reset the static values for local player vehicle
|
|
ms_bLocalPlayerVehicleHornOn = false;
|
|
ms_PlayerVehiclePosition.ZeroComponents();
|
|
|
|
// If the local player is in a vehicle
|
|
const CVehicle* pPlayerVehicle = CGameWorld::FindLocalPlayerVehicle();
|
|
if( pPlayerVehicle )
|
|
{
|
|
// cache the position this frame
|
|
ms_PlayerVehiclePosition = pPlayerVehicle->GetTransform().GetPosition();
|
|
|
|
// cache whether the horn is on or not this frame
|
|
if( pPlayerVehicle->IsHornOn() )
|
|
{
|
|
ms_bLocalPlayerVehicleHornOn = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
EXT_PF_TIMER(ShockingEventScanner);
|
|
|
|
dev_float DISTANCE_TO_SCAN_IMMEDIATE = 10.0f;
|
|
void CShockingEventScanner::ScanForShockingEvents(CPed* pPed)
|
|
{
|
|
PF_FUNC(ShockingEventScanner);
|
|
|
|
if(!pPed->GetPedIntelligence()->GetCheckShockFlag())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DisableShockingEvents))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!pPed->PopTypeIsRandom())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CEventGroupGlobal& global = *GetEventGlobalGroup();
|
|
const int num = global.GetNumEvents();
|
|
if( num == 0 )
|
|
{
|
|
// no events to process
|
|
return;
|
|
}
|
|
|
|
// If the local player is in a vehicle with the horn on, check if this ped is nearby
|
|
const bool bNearPlayersCarUsingHorn = ms_bLocalPlayerVehicleHornOn && DistSquared(ms_PlayerVehiclePosition, pPed->GetTransform().GetPosition()).Getf() < rage::square(DISTANCE_TO_SCAN_IMMEDIATE);
|
|
|
|
// Check if this ped should be processed this frame
|
|
Assert(IsRegistered());
|
|
if(!bNearPlayersCarUsingHorn && !ShouldBeProcessedThisFrame())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Note: more or less all of the checks on the ped here could potentially
|
|
// be moved to CEventShocking::AffectsPedCore(). Doing that would make the code
|
|
// cleaner, but there is a trade-off as for performance it's nice to be able to
|
|
// skip the whole iteration and dispatch of the events to the individual.
|
|
|
|
if(pPed->IsPlayer() || !pPed->GetShockingEvent()) // TODO: maybe remove GetShockingEvent()?
|
|
{
|
|
return;
|
|
}
|
|
if(pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) && pPed->GetMyVehicle())
|
|
{
|
|
CVehicle* pPedVehicle = pPed->GetMyVehicle();
|
|
if(pPedVehicle->GetVehicleType() == VEHICLE_TYPE_BOAT
|
|
|| pPedVehicle->GetVehicleType() == VEHICLE_TYPE_TRAIN
|
|
|| pPedVehicle->GetVehicleType() == VEHICLE_TYPE_HELI
|
|
|| pPedVehicle->GetVehicleType() == VEHICLE_TYPE_PLANE
|
|
|| pPedVehicle->HasMissionCharsInIt()
|
|
|| pPedVehicle->PopTypeIsMission())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if(pPed->IsLawEnforcementPed())
|
|
{
|
|
// if this cop is chasing someone (it has an order) don't do shocking events
|
|
if(pPed->GetPedIntelligence()->GetOrder())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
StartProcess();
|
|
|
|
CPedShockingEvent& shockingEventMem = *pPed->GetShockingEvent();
|
|
for(int j = 0; j < num; j++)
|
|
{
|
|
fwEvent* ev = global.GetEventByIndex(j);
|
|
if(!ev || !static_cast<const CEvent*>(ev)->IsShockingEvent())
|
|
{
|
|
continue;
|
|
}
|
|
CEventShocking* pThisEvent = static_cast<CEventShocking*>(ev);
|
|
|
|
// Note: we could move the two checks below to CEventShocking::AffectsPedCore(),
|
|
// but again, it's nice to stop the processing early.
|
|
if(pThisEvent->GetSourceEntity() == pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pThisEvent->GetOtherEntity() == pPed && pThisEvent->GetTunables().m_IgnoreIfSensingPedIsOtherEntity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check if we've already reacted to this event.
|
|
if(shockingEventMem.HasReactedToShockingEvent(pThisEvent->GetId()) && !pThisEvent->GetTunables().m_AllowScanningEvenIfPreviouslyReacted)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pPed->GetPedIntelligence()->AddEvent(*pThisEvent);
|
|
|
|
// Note: we don't call shockingEventMem.SetShockingEventReactedTo() here
|
|
// to remember that we've handled the event, because we don't know yet if
|
|
// it can be sensed and pass other tests. We could perhaps call
|
|
// CEventShocking::AffectsPedCore() to find out, but then we may be doing
|
|
// duplicate work, and may be in trouble (miss an event) if the conditions
|
|
// change between now and the time the ped gets to process the event.
|
|
}
|
|
|
|
StopProcess();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Scan for in water (swimming) events, e.g. drowning.
|
|
//-------------------------------------------------------------------------
|
|
CInWaterEventScanner::CInWaterEventScanner()
|
|
: m_fTimeInWater(0.0f),
|
|
m_fTimeSubmerged(0.0f),
|
|
m_fTimeOnSurface(0.0f),
|
|
m_fApplyDamageTimer(0.0f)
|
|
{
|
|
|
|
}
|
|
|
|
CInWaterEventScanner::~CInWaterEventScanner()
|
|
{
|
|
}
|
|
|
|
dev_float SUBMERGE_RESET_TIME = 1.0f; // How long do we have to spend on surface before we reset the submerge timer?
|
|
dev_float DROWNING_DAMAGE_INTERVAL = 0.5f;
|
|
dev_float DROWNING_DAMAGE_AMOUNT = 8.0f;
|
|
dev_float DROWNING_DAMAGE_AMOUNT_FLEE = 2.0f;
|
|
dev_float UNDERWATER_UPSIDE_DOWN_TIME = 1.0f;
|
|
|
|
//Not sure this definitely belongs in here, but it seems like the best home for now.
|
|
void CInWaterEventScanner::Scan(CPed& ped)
|
|
{
|
|
bool bInVehicle = ped.GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle );
|
|
bool bAddedDetachFlag = false;
|
|
|
|
if( bInVehicle && !ped.IsNetworkClone() && ped.GetPedConfigFlag(CPED_CONFIG_FLAG_CanActivateRagdollWhenVehicleUpsideDown))
|
|
{
|
|
CVehicle* pVehicle = ped.GetMyVehicle();
|
|
if (pVehicle && pVehicle->InheritsFromBoat())
|
|
{
|
|
if (static_cast<CBoat*>(pVehicle)->m_BoatHandling.GetCapsizedTimer() >= UNDERWATER_UPSIDE_DOWN_TIME)
|
|
{
|
|
s32 iSeatIndex = ped.GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(&ped);
|
|
if (pVehicle->IsSeatIndexValid(iSeatIndex))
|
|
{
|
|
bool bFallOff = false;
|
|
const CVehicleSeatAnimInfo* pSeatAnimInfo = pVehicle->GetSeatAnimationInfo(iSeatIndex);
|
|
if(pSeatAnimInfo && pSeatAnimInfo->GetRagdollWhenVehicleUpsideDown())
|
|
{
|
|
if(pVehicle->GetTransform().GetC().GetZf() < 0.0f)
|
|
{
|
|
bFallOff = true;
|
|
|
|
if(!ped.GetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL))
|
|
{
|
|
ped.SetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL, true);
|
|
bAddedDetachFlag = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bFallOff)
|
|
{
|
|
if(CTaskNMBehaviour::CanUseRagdoll(&ped, RAGDOLL_TRIGGER_VEHICLE_FALLOUT))
|
|
{
|
|
CEventDamage tempDamageEvent(NULL, fwTimer::GetTimeInMilliseconds(), WEAPONTYPE_DROWNINGINVEHICLE);
|
|
CPedDamageCalculator damageCalculator(NULL, 0.0f, WEAPONTYPE_DROWNINGINVEHICLE, 0, false);
|
|
damageCalculator.ApplyDamageAndComputeResponse(&ped, tempDamageEvent.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
|
|
if(tempDamageEvent.AffectsPed(&ped))
|
|
{
|
|
CTask* pTaskRoll = rage_new CTaskNMJumpRollFromRoadVehicle(2000, 30000, false, false, atHashString::Null(), pVehicle);
|
|
|
|
ped.SetPedOutOfVehicle(CPed::PVF_Warp|CPed::PVF_IgnoreSafetyPositionCheck);
|
|
|
|
//! Kick into ragdoll immediately.
|
|
CEventSwitch2NM event(6000, pTaskRoll);
|
|
ped.SwitchToRagdoll(event);
|
|
|
|
//! Apply instant -z velocity to push ped away from boat.
|
|
Vector3 vel(Vector3::ZeroType);
|
|
static dev_float s_fVel = -20.0f;
|
|
vel.z += s_fVel;
|
|
ped.SetVelocity(vel);
|
|
ped.SetDesiredVelocity(vel);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! If we had to add detach flag, but couldn't ragdoll, remove it.
|
|
if(bAddedDetachFlag)
|
|
{
|
|
ped.SetAttachFlag(ATTACH_FLAG_AUTODETACH_ON_RAGDOLL, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We want to increment TIME_UNDERWATER whenever local player is underwater, even if SP or MP, in or out of a vehicle, and capable or not of drowning (ie in submarine or in scuba gear)
|
|
if (ped.IsLocalPlayer() && CStatsMgr::IsPlayerActive())
|
|
{
|
|
const bool bSwimmingUnderWater = ped.GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning) && ped.m_Buoyancy.GetStatus()==FULLY_IN_WATER;
|
|
const bool bInAVehicleUnderWater = bInVehicle && IsOccupantsHeadUnderWater(ped);
|
|
if(bSwimmingUnderWater || bInAVehicleUnderWater)
|
|
{
|
|
//If the ped doesn't die under water do not count as time underwater.
|
|
if (ped.GetPedConfigFlag(CPED_CONFIG_FLAG_DrownsInWater))
|
|
{
|
|
StatId stat = StatsInterface::GetStatsModelHashId("TOTAL_TIME_UNDERWATER");
|
|
StatsInterface::IncrementStat(stat, fwTimer::GetTimeStep()*1000.0f);
|
|
|
|
if (bSwimmingUnderWater)
|
|
{
|
|
StatId stat = StatsInterface::GetStatsModelHashId("TIME_UNDERWATER");
|
|
StatsInterface::IncrementStat(stat, fwTimer::GetTimeStep()*1000.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StatId stat = StatsInterface::GetStatsModelHashId("TIME_NOTDROWNINWATER");
|
|
StatsInterface::IncrementStat(stat, fwTimer::GetTimeStep()*1000.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ped.IsNetworkClone() || (!ped.GetPedConfigFlag(CPED_CONFIG_FLAG_SwimmingTasksRunning) && !IsDrowningInVehicle(ped) && !IsDrowningOnLadder(ped) && ped.m_Buoyancy.GetStatus()!=FULLY_IN_WATER && !ped.GetPedResetFlag(CPED_RESET_FLAG_IsDrowning)) )
|
|
{
|
|
//Reset in water timers.
|
|
m_fTimeInWater = 0.0f;
|
|
m_fTimeSubmerged = 0.0f;
|
|
m_fTimeOnSurface = 0.0f;
|
|
//m_fApplyDamageTimer = 0.0f;
|
|
|
|
return;
|
|
}
|
|
|
|
if( (ped.GetPedConfigFlag( CPED_CONFIG_FLAG_DrownsInWater ) && !bInVehicle) ||
|
|
(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_DrownsInSinkingVehicle ) && bInVehicle) )
|
|
{
|
|
const CQueriableInterface* pQueriableInterface = ped.GetPedIntelligence()->GetQueriableInterface();
|
|
bool bScenarioPed = pQueriableInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_USE_SCENARIO, true) ||
|
|
pQueriableInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_WANDERING_SCENARIO, true);
|
|
|
|
// Update water timers
|
|
if (CanDrown(ped))
|
|
{
|
|
float fTimeSubmerged = fwTimer::GetTimeStep();
|
|
|
|
m_fTimeInWater += fTimeSubmerged;
|
|
m_fTimeSubmerged += fTimeSubmerged;
|
|
|
|
m_fTimeOnSurface = 0.0f;
|
|
}
|
|
else if (ped.m_Buoyancy.GetStatus() == PARTIALLY_IN_WATER)
|
|
{
|
|
float fTimeInWater = fwTimer::GetTimeStep();
|
|
|
|
//! scenario peds don't do damage whilst in water, so don't update timer either. This gives them longer to swim away as otherwise they
|
|
//! start taking damage immediately.
|
|
if(!bScenarioPed)
|
|
{
|
|
m_fTimeInWater += fTimeInWater;
|
|
}
|
|
|
|
m_fTimeOnSurface += fTimeInWater;
|
|
|
|
if(m_fTimeOnSurface > SUBMERGE_RESET_TIME)
|
|
{
|
|
// If we're the player & we have just surfaced after being submerged for some
|
|
// time - then play a gasp-for-air sound.
|
|
//Now handled in audSpeechAudioEntity::Update() so we can preload it -R Katz 2/28/13
|
|
|
|
// Reset submerged timer when on surface, if we have been on surface for long enough
|
|
// (avoid resetting if we are flicking between two states)
|
|
m_fTimeSubmerged = 0.0;
|
|
}
|
|
|
|
// Update time in water
|
|
if (ped.IsPlayer() && !bInVehicle)
|
|
{
|
|
StatId stat = StatsInterface::GetStatsModelHashId("TIME_IN_WATER");
|
|
StatsInterface::IncrementStat(stat, fTimeInWater*1000.0f);
|
|
}
|
|
}
|
|
|
|
// Has ped been in water long enough to drown?
|
|
float fDrownDamage = 0.0f;
|
|
float fMaxTimeInWater = ped.GetMaxTimeInWater();
|
|
static dev_float sf_MaximumWaterPressureDepth = -170.0f;
|
|
bool bGettingCrushedByWater = !bInVehicle && ped.GetTransform().GetPosition().GetZf() < sf_MaximumWaterPressureDepth;
|
|
bool bPlayerAnimalWithNoWaterData = ped.IsLocalPlayer() && CPedType::IsAnimalType(ped.GetPedType()) && ped.GetMotionTaskDataSet() && ped.GetMotionTaskDataSet()->GetInWaterData() == nullptr;
|
|
if(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_DiesInstantlyWhenSwimming ) || bPlayerAnimalWithNoWaterData)
|
|
{
|
|
// Ped cannot swim - Kill it instantly
|
|
fDrownDamage = ped.GetHealth();
|
|
}
|
|
else if(bGettingCrushedByWater || (fMaxTimeInWater >= 0.0f && m_fTimeInWater > fMaxTimeInWater) ||
|
|
(m_fTimeSubmerged > ped.GetMaxTimeUnderwater() && CanDrown(ped)))
|
|
{
|
|
// Ped has been in water for too long... it should start to take damage
|
|
|
|
if( m_fApplyDamageTimer > 0.0f )
|
|
m_fApplyDamageTimer -= fwTimer::GetTimeStep();
|
|
else
|
|
{
|
|
fDrownDamage = (ped.GetPedResetFlag(CPED_RESET_FLAG_MoveBlend_bFleeTaskRunning) ? DROWNING_DAMAGE_AMOUNT_FLEE : DROWNING_DAMAGE_AMOUNT);
|
|
m_fApplyDamageTimer = DROWNING_DAMAGE_INTERVAL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Ped hasn't been in water for long enough to start taking any damage
|
|
m_fApplyDamageTimer = DROWNING_DAMAGE_INTERVAL;
|
|
}
|
|
|
|
if ( fDrownDamage > 0.0f )
|
|
{
|
|
if ( !(pQueriableInterface->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE_SEAT,true) ||
|
|
bScenarioPed) )
|
|
{
|
|
u32 nWeaponUsedHash = bInVehicle ? WEAPONTYPE_DROWNINGINVEHICLE : WEAPONTYPE_DROWNING;
|
|
CPedDamageCalculator damageCalculator(NULL,fDrownDamage,nWeaponUsedHash, 0, false);
|
|
CEventDamage event(NULL, fwTimer::GetTimeInMilliseconds(), nWeaponUsedHash);
|
|
damageCalculator.ApplyDamageAndComputeResponse(&ped, event.GetDamageResponseData(), CPedDamageCalculator::DF_None);
|
|
|
|
// Apply damage will vibrate the pad based on the amount of damage done. We want the pad to vibrate based on the health left
|
|
// so that it gets more intense as your health goes down.
|
|
if(ped.IsLocalPlayer())
|
|
{
|
|
const float MAX_RUMBLE_DURATION = 1000.0f; // 1 second
|
|
|
|
float intensity = 0.0f;
|
|
|
|
// if the player is not dead.
|
|
if(ped.GetHealth() > ped.GetDyingHealthThreshold())
|
|
{
|
|
if(event.GetDamageApplied() > 0.0f)
|
|
{
|
|
float maxHealth = ped.GetMaxHealth() - ped.GetDyingHealthThreshold();
|
|
intensity = (maxHealth - (ped.GetHealth() - ped.GetDyingHealthThreshold())) / maxHealth;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
intensity = 1.0f;
|
|
}
|
|
|
|
if(intensity > 0.0f)
|
|
{
|
|
// We are using the intensity as the duration so as the rumble gets stronger it lasts longer. This also gives more
|
|
// time for the motor to speed up.
|
|
CControlMgr::StartPlayerPadShakeByIntensity(static_cast<u32>(intensity * MAX_RUMBLE_DURATION), intensity);
|
|
}
|
|
}
|
|
|
|
bool bWasKilledOrInjured = (event.GetDamageResponseData().m_bKilled || event.GetDamageResponseData().m_bInjured);
|
|
if( bWasKilledOrInjured )
|
|
{
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CInWaterEventScanner::StartDrowningNow(CPed& ped)
|
|
{
|
|
if( ped.m_Buoyancy.GetStatus() != NOT_IN_WATER )
|
|
{
|
|
m_fTimeSubmerged = ped.GetMaxTimeUnderwater();
|
|
m_fApplyDamageTimer = 0.0f;
|
|
}
|
|
}
|
|
|
|
bool CInWaterEventScanner::CanDrown(CPed& ped)
|
|
{
|
|
//Disable drowning while wearing scuba gear.
|
|
if(CTaskMotionSwimming::IsScubaGearVariationActiveForPed(ped))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Disable drowning while in a functioning submarine.
|
|
CVehicle* vehicle = ped.GetVehiclePedInside();
|
|
|
|
if( vehicle )
|
|
{
|
|
CSubmarineHandling* subHandling = vehicle->GetSubHandling();
|
|
|
|
if( subHandling &&
|
|
!subHandling->PassengersShouldDrown( vehicle ))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return ped.m_Buoyancy.GetStatus() == FULLY_IN_WATER || IsDrowningInVehicle(ped) || IsDrowningOnLadder(ped);
|
|
}
|
|
|
|
bool CInWaterEventScanner::IsDrowningInVehicle(CPed& Ped)
|
|
{
|
|
// If we are drowning start damaging drivers and passengers
|
|
CVehicle* pVehicle = Ped.GetVehiclePedInside();
|
|
|
|
if(pVehicle)
|
|
{
|
|
// Don't need to check if the vehicle is drowning unless it's a submarine / boat.
|
|
CBoatHandling* boatHandling = pVehicle->GetBoatHandling();
|
|
|
|
if(boatHandling)
|
|
{
|
|
if(pVehicle->GetStatus()==STATUS_WRECKED && IsOccupantsHeadUnderWater(Ped))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CSubmarineHandling* subHandling = pVehicle->GetSubHandling();
|
|
|
|
if( subHandling )
|
|
{
|
|
return subHandling->PassengersShouldDrown( pVehicle ) && IsOccupantsHeadUnderWater(Ped);
|
|
}
|
|
else
|
|
{
|
|
if(IsOccupantsHeadUnderWater(Ped))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CInWaterEventScanner::IsDrowningOnLadder(CPed& Ped)
|
|
{
|
|
const CTaskClimbLadder* pClimbLadderTask = (const CTaskClimbLadder*)Ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_CLIMB_LADDER);
|
|
if (pClimbLadderTask)
|
|
{
|
|
// Get the head bone position in global space
|
|
Vector3 headPosition;
|
|
Ped.GetBonePosition(headPosition, BONETAG_HEAD);
|
|
|
|
//Get the water level in global space
|
|
float waterLevel;
|
|
if (Ped.m_Buoyancy.GetWaterLevelIncludingRivers(headPosition, &waterLevel, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL) != WATERTEST_TYPE_NONE)
|
|
{
|
|
return headPosition.z < waterLevel;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CInWaterEventScanner::IsTakingDamageDueToFatigue(CPed& ped)
|
|
{
|
|
if( ped.GetIsInWater() && ped.GetPedConfigFlag( CPED_CONFIG_FLAG_DrownsInWater ) && ped.GetMaxTimeInWater() >= 0.0f && m_fTimeInWater > ped.GetMaxTimeInWater() )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CInWaterEventScanner::IsOccupantsHeadUnderWater(CPed& ped)
|
|
{
|
|
Vector3 headPosition;
|
|
|
|
//get the head bone position in global space
|
|
ped.GetBonePosition(headPosition, BONETAG_HEAD);
|
|
//get the water level in global space
|
|
float waterLevel;
|
|
|
|
CVehicle* pVehicle = ped.GetMyVehicle();
|
|
|
|
if(pVehicle && pVehicle->m_Buoyancy.GetWaterLevelIncludingRivers(headPosition, &waterLevel, true, POOL_DEPTH, REJECTIONABOVEWATER, NULL)
|
|
!= WATERTEST_TYPE_NONE)
|
|
{
|
|
#if __DEV
|
|
static dev_bool bDebugDrowningCheck = false;
|
|
|
|
if (bDebugDrowningCheck)
|
|
{
|
|
headPosition.x+=1.5f; //offset the debug draw so we can see what's happening
|
|
//head position
|
|
grcDebugDraw::Sphere(headPosition,0.05f,Color_red);
|
|
|
|
//water level
|
|
grcDebugDraw::Sphere(Vector3(headPosition.x, headPosition.y, waterLevel),0.05f,Color_blue);
|
|
}
|
|
|
|
#endif //__DEV
|
|
|
|
return (headPosition.z<waterLevel);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Scan for Core events (common to all projects)
|
|
//-------------------------------------------------------------------------
|
|
EXT_PF_TIMER(AI_ScanForEvents);
|
|
|
|
static const u32 NUM_UNINTERRUPTIBLE_TASKS = 8;
|
|
static s32 UNINTERRUPTIBLE_TASKS[NUM_UNINTERRUPTIBLE_TASKS] =
|
|
{
|
|
CTaskTypes::TASK_ENTER_VEHICLE,
|
|
CTaskTypes::TASK_EXIT_VEHICLE,
|
|
CTaskTypes::TASK_IN_VEHICLE_SEAT_SHUFFLE,
|
|
CTaskTypes::TASK_RELOAD_GUN,
|
|
CTaskTypes::TASK_ENTER_COVER,
|
|
CTaskTypes::TASK_EXIT_COVER,
|
|
CTaskTypes::TASK_AIM_GUN_SCRIPTED,
|
|
CTaskTypes::TASK_CUTSCENE
|
|
};
|
|
|
|
void CEventScanner::ScanForEvents(CPed& ped, bool bFullUpdate)
|
|
{
|
|
PF_FUNC(AI_ScanForEvents);
|
|
|
|
CPedIntelligence& pedIntelligence=*(ped.GetPedIntelligence());
|
|
|
|
if (ped.GetPedConfigFlag(CPED_CONFIG_FLAG_WaitingForPlayerControlInterrupt))
|
|
{
|
|
if (ped.IsLocalPlayer() && ped.GetPlayerInfo())
|
|
{
|
|
// If we're already running a player on foot task, just clear the flags,
|
|
// we've already got control, this can happen if script switch, but don't clear the peds tasks
|
|
// then give the ped a put ped directly into cover task which already handles creating a parent
|
|
// on foot task. This prevents the cover task from restarting again unnecessarily
|
|
if (ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_PLAYER_ON_FOOT))
|
|
{
|
|
ped.GetPedIntelligence()->ClearTaskEventResponse();
|
|
ped.SetPedConfigFlag(CPED_CONFIG_FLAG_WaitingForPlayerControlInterrupt, false);
|
|
ped.SetPedConfigFlag(CPED_CONFIG_FLAG_ForcedToStayInCoverDueToPlayerSwitch, false);
|
|
}
|
|
else
|
|
{
|
|
CControl* pControl = ped.GetControlFromPlayer();
|
|
if (pControl)
|
|
{
|
|
if (ShouldInterruptCurrentTask(ped, *pControl))
|
|
{
|
|
bool bUsingCover = false;
|
|
|
|
// Check if we're using cover
|
|
CTaskInCover* pInCoverTask = static_cast<CTaskInCover*>(ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_IN_COVER));
|
|
if (pInCoverTask && ped.GetCoverPoint() && pInCoverTask->GetState() != CTaskInCover::State_Aim)
|
|
{
|
|
ped.SetPedResetFlag(CPED_RESET_FLAG_InCoverFacingLeft, pInCoverTask->IsCoverFlagSet(CTaskCover::CF_FacingLeft));
|
|
ped.SetPlayerResetFlag(CPlayerResetFlags::PRF_SPECIFY_INITIAL_COVER_HEADING);
|
|
ped.SetPlayerResetFlag(CPlayerResetFlags::PRF_SKIP_COVER_ENTRY_ANIM);
|
|
CTaskPlayerOnFoot* pPlayerTask = rage_new CTaskPlayerOnFoot();
|
|
pPlayerTask->SetScriptedToGoIntoCover(true);
|
|
// In cover task pointer will get nuked the next line, don't use it again!
|
|
ped.GetPedIntelligence()->AddTaskDefault(pPlayerTask);
|
|
ped.SetPedResetFlag(CPED_RESET_FLAG_KeepCoverPoint, true);
|
|
bUsingCover = true;
|
|
}
|
|
|
|
ped.GetPedIntelligence()->ClearPrimaryTask();
|
|
ped.GetPedIntelligence()->ClearTaskEventResponse();
|
|
ped.SetPedConfigFlag(CPED_CONFIG_FLAG_WaitingForPlayerControlInterrupt, false);
|
|
ped.SetPedConfigFlag(CPED_CONFIG_FLAG_ForcedToStayInCoverDueToPlayerSwitch, false);
|
|
|
|
if (!bUsingCover)
|
|
{
|
|
ped.GetPedIntelligence()->AddTaskDefault(ped.ComputeDefaultTask(ped));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ped.SetPedConfigFlag(CPED_CONFIG_FLAG_ForcedToStayInCoverDueToPlayerSwitch, false);
|
|
}
|
|
|
|
// A check to make sure this ped is either not dead or correctly playing the dead tasks
|
|
ScanForDeadPedNotRunningCorrectTasks(ped);
|
|
|
|
if( !ped.GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEventScanning) )
|
|
{
|
|
//Scan for potential collisions with peds.
|
|
m_playerToPedWalkIntoScanner.Scan(ped,pedIntelligence.GetClosestPedInRange());
|
|
|
|
//Scan for ped acquaintances
|
|
CEntityScannerIterator entityList = pedIntelligence.GetNearbyPeds();
|
|
m_acquaintanceScanner.Scan(ped,entityList);
|
|
|
|
//Scan for agitation
|
|
m_agitationScanner.Scan(ped, entityList);
|
|
|
|
//Scan for personal space encroachment
|
|
m_encroachmentScanner.Scan(ped, entityList);
|
|
|
|
//Scan for group events (e.g. getting in and out of cars).
|
|
m_groupScanner.Scan(ped);
|
|
|
|
//Scan for nearby fires
|
|
m_fireScanner.Scan(ped);
|
|
|
|
// Scan for AI responses related to vehicles
|
|
m_passengerEventScanner.Scan(ped);
|
|
|
|
// Scan the nearby doors
|
|
ScanForDoorResponses(ped);
|
|
|
|
// Scan for the ped being in water and drowning
|
|
m_inWaterScanner.Scan(ped);
|
|
}
|
|
|
|
// Scan for health stuff
|
|
m_pedHealthScanner.Scan(ped);
|
|
|
|
// Only run physics event scanning when the ped is active in the physics world (or standing on an active physical)
|
|
if( !ped.IsAsleep() || (ped.GetGroundPhysical() != NULL && !ped.GetGroundPhysical()->IsAsleep()) )
|
|
{
|
|
//Scan for potential collisions with vehicles.
|
|
m_vehicleCollisionScanner.Scan(ped);
|
|
|
|
// Scan for physics related events, e.g. standing on cars
|
|
m_physicsEventScanner.Scan(ped);
|
|
|
|
// Collision events
|
|
// needs to be done before data gets cleared in Physical::ProcessControl()
|
|
if(m_stuckChecker.TestPedStuck(&ped))
|
|
{
|
|
// do nothing, event will have been added if it was required
|
|
}
|
|
else
|
|
{
|
|
m_collisionEventScanner.ScanForCollisionEvents(&ped);
|
|
}
|
|
|
|
// Scans for the ped movement being stopped
|
|
m_staticMovementScanner.Scan(ped);
|
|
}
|
|
else
|
|
{
|
|
// A ped's physics can go to sleep even whilst they are trying to move but stuck against something.
|
|
// We need to run the static movement test for all peds (high or low physics) who are trying to move somewhere
|
|
if(ped.GetMotionData()->GetCurrentMoveBlendRatio().Mag2() > SMALL_FLOAT)
|
|
{
|
|
// Scans for the ped movement being stopped
|
|
m_staticMovementScanner.Scan(ped);
|
|
}
|
|
}
|
|
|
|
CheckForTranquilizerDamage(ped);
|
|
|
|
//Check that this ped's tasks, down to the first non-temporary task, are compatible with its active move blender.
|
|
CheckTasksAreCompatibleWithMotion(ped);
|
|
|
|
// Check our ambient friend and see if we need to respond to them.
|
|
CheckAmbientFriend(ped);
|
|
|
|
// Check if our ambient friend is gone.
|
|
CheckAmbientFriendDeletion(ped);
|
|
|
|
// Scan for shocking events
|
|
m_shockingEventsScanner.ScanForShockingEvents(&ped);
|
|
|
|
if(bFullUpdate)
|
|
{
|
|
// Now when we've had the chance to scan for shocking events, we can reset
|
|
// the check shock flag. If the ped is in a state where it should respond
|
|
// to shocking events, it should be set again this frame by a task.
|
|
ped.GetPedIntelligence()->SetCheckShockFlag(false);
|
|
}
|
|
}
|
|
|
|
bool CEventScanner::ShouldInterruptCurrentTask(const CPed& rPed, const CControl& rControl)
|
|
{
|
|
const CPedIntelligence& rIntelligence = *rPed.GetPedIntelligence();
|
|
|
|
bool bCheckForInterrupt = true;
|
|
|
|
for (s32 i=0; i<NUM_UNINTERRUPTIBLE_TASKS; i++)
|
|
{
|
|
if (rIntelligence.FindTaskActiveByType(UNINTERRUPTIBLE_TASKS[i]))
|
|
{
|
|
bCheckForInterrupt = false;
|
|
}
|
|
}
|
|
|
|
if (bCheckForInterrupt)
|
|
{
|
|
if (rPed.GetPlayerInfo() && fwTimer::GetTimeInMilliseconds() > rPed.GetPlayerInfo()->TimeSinceSwitch + CPlayerInfo::ms_Tunables.m_TimeBetweenSwitchToClearTasks)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#if FPS_MODE_SUPPORTED
|
|
// always interrupt in the first person
|
|
if (rPed.GetPedConfigFlag(CPED_CONFIG_FLAG_SwitchingCharactersInFirstPerson))
|
|
{
|
|
return true;
|
|
}
|
|
#endif // FPS_MODE_SUPPORTED
|
|
|
|
Vector2 vecStick(rControl.GetPedWalkLeftRight().GetNorm(), - rControl.GetPedWalkUpDown().GetNorm());
|
|
float fInputMag = vecStick.Mag();
|
|
TUNE_GROUP_FLOAT(SWITCH_DEBUG, MOVEMENT_BREAKOUT, 0.1f, 0.0f, 1.0f, 0.01f);
|
|
if (fInputMag > MOVEMENT_BREAKOUT
|
|
|| CPlayerInfo::IsAiming()
|
|
|| rControl.GetPedJump().IsPressed()
|
|
|| rControl.GetPedEnter().IsPressed()
|
|
|| rControl.GetPedCover().IsPressed()
|
|
|| rControl.GetVehicleAccelerate().IsDown())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CEventScanner::Clear()
|
|
{
|
|
|
|
}
|
|
|
|
void CEventScanner::ScanForDeadPedNotRunningCorrectTasks( CPed &ped )
|
|
{
|
|
// If the ped isn't dead or dying but
|
|
// Is injured and not playing an injured task, or is fatally injured and not dying
|
|
|
|
// MK: assuming here that the threshold in pedhealth.meta is the same for injured and dying
|
|
// combining the two comparisons into one. Adding an assert that this is a correct assumption
|
|
// Old Check : if(ped.IsInjured() || ped.IsFatallyInjured())
|
|
Assertf(ped.GetDyingHealthThreshold() >= ped.GetInjuredHealthThreshold(), "Assumption that DyingHealthThreshold (%.4f) is >= InjuredHealthThreshold (%.4f) is wrong.",
|
|
ped.GetDyingHealthThreshold(), ped.GetInjuredHealthThreshold() );
|
|
if(ped.IsFatallyInjured() && !ped.GetIsDeadOrDying())
|
|
{
|
|
if (m_sDeadPedWalkingTimer > 0)
|
|
m_sDeadPedWalkingTimer--;
|
|
else
|
|
{
|
|
bool bGenerateEvent = true;
|
|
|
|
// Don't bother trying to generate the damage event if the ped is already running the dying task
|
|
if (ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_DYING_DEAD))
|
|
{
|
|
bGenerateEvent = false;
|
|
}
|
|
else
|
|
{
|
|
// If the active task or any of its subtasks handles running on a dead ped, don't generate the event, otherwise generate it
|
|
CTask* pActiveTask = ped.GetPedIntelligence()->GetTaskActive();
|
|
bGenerateEvent = pActiveTask ? !pActiveTask->HandlesDeadPed() : true;
|
|
}
|
|
|
|
if(bGenerateEvent)
|
|
{
|
|
if(!(ped.GetPedIntelligence()->HasEventOfType(EVENT_DEATH) || ped.GetPedIntelligence()->HasEventOfType(EVENT_DAMAGE)))
|
|
{
|
|
bool bUseRagdoll = ped.GetUsingRagdoll() || CTaskNMBehaviour::CanUseRagdoll(&ped, RAGDOLL_TRIGGER_DIE);
|
|
CEventDeath deathEvent(false, bUseRagdoll);
|
|
|
|
if (!bUseRagdoll)
|
|
{
|
|
// look for an appropriate existing animation to use
|
|
CTaskFallAndGetUp* pTask = static_cast<CTaskFallAndGetUp*>(ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_FALL_AND_GET_UP));
|
|
if (pTask && pTask->GetClipSetId()!=CLIP_SET_ID_INVALID)
|
|
{
|
|
deathEvent.SetClipSet(pTask->GetClipSetId(), pTask->GetClipId(), pTask->GetClipPhase());
|
|
}
|
|
ped.GetPedIntelligence()->AddEvent(deathEvent);
|
|
}
|
|
else
|
|
{
|
|
if (!ped.GetUsingRagdoll())
|
|
ped.SwitchToRagdoll(deathEvent);
|
|
else
|
|
ped.GetPedIntelligence()->AddEvent(deathEvent);
|
|
}
|
|
|
|
m_sDeadPedWalkingTimer = 5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CEventScanner::ScanForDoorResponses( const CPed &ped )
|
|
{
|
|
//Scan for closest door
|
|
if( ped.GetPedConfigFlag( CPED_CONFIG_FLAG_OpenDoorArmIK ) || ped.GetPedResetFlag( CPED_RESET_FLAG_OpenDoorArmIK ) )
|
|
{
|
|
if( ( !ped.GetPedIntelligence()->GetTaskSecondaryPartialAnim() ||
|
|
ped.GetPedIntelligence()->GetTaskSecondaryPartialAnim()->GetTaskType() != CTaskTypes::TASK_OPEN_DOOR) &&
|
|
!ped.GetPedResetFlag( CPED_RESET_FLAG_ScriptDisableSecondaryAnimationTasks ) &&
|
|
!ped.GetPedConfigFlag( CPED_CONFIG_FLAG_IsHandCuffed ) )
|
|
{
|
|
CDoor* pClosestDoor = ped.GetPedIntelligence()->GetClosestDoorInRange();
|
|
if( pClosestDoor && CTaskOpenDoor::ShouldAllowTask( &ped, pClosestDoor ) )
|
|
{
|
|
CEventOpenDoor event( pClosestDoor );
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const fwMvClipSetId ms_TranqDict("anim@fidgets@hit", 0x8CC743AD);
|
|
void CEventScanner::CheckForTranquilizerDamage(CPed& ped)
|
|
{
|
|
TUNE_GROUP_INT(TRANQUILIZER_TUNE, uTranqDamageTimeBeforeDie, 1000, 0, 100000, 100);
|
|
if (ped.GetPedConfigFlag(CPED_CONFIG_FLAG_HitByTranqWeapon) && m_TranqClipRequestHelper.Request(ms_TranqDict) && m_TimeTranqDamageStarted == 0)
|
|
{
|
|
const atHashString clipName("HIT_A", 0x5FF35585);
|
|
|
|
eScriptedAnimFlagsBitSet flags;
|
|
flags.BitSet().Set(AF_UPPERBODY);
|
|
flags.BitSet().Set(AF_SECONDARY);
|
|
flags.BitSet().Set(AF_ADDITIVE);
|
|
|
|
eIkControlFlagsBitSet IKflags;
|
|
|
|
fwClipSet* clipSet = fwClipSetManager::GetClipSet(ms_TranqDict);
|
|
if (clipSet)
|
|
{
|
|
CTaskScriptedAnimation* task = rage_new CTaskScriptedAnimation(clipSet->GetClipDictionaryName(), clipName, CTaskScriptedAnimation::kPriorityMid, (u32)BONEMASK_SPINEONLY, SLOW_BLEND_DURATION, NORMAL_BLEND_DURATION, uTranqDamageTimeBeforeDie, flags, 0.0f, false, false, IKflags);
|
|
|
|
ped.GetPedIntelligence()->AddTaskSecondary(task, PED_TASK_SECONDARY_PARTIAL_ANIM);
|
|
}
|
|
|
|
m_TimeTranqDamageStarted = fwTimer::GetTimeInMilliseconds();
|
|
}
|
|
|
|
if (!ped.IsNetworkClone() && m_TimeTranqDamageStarted > 0 && !ped.GetIsDeadOrDying())
|
|
{
|
|
ped.GetMotionData()->SetScriptedMaxMoveBlendRatio(MOVEBLENDRATIO_STILL);
|
|
|
|
s32 sTimeSince = fwTimer::GetTimeInMilliseconds() - m_TimeTranqDamageStarted;
|
|
if (sTimeSince > uTranqDamageTimeBeforeDie)
|
|
{
|
|
m_TranqClipRequestHelper.Release();
|
|
m_TimeTranqDamageStarted = 0;
|
|
|
|
ped.SetHealth(0);
|
|
|
|
CTask* pNMTask = NULL;
|
|
const CTaskNMSimple::Tunables::Tuning* pTuning = CTaskNMSimple::sm_Tunables.GetTuning("Death_Tranquilizer");
|
|
if (aiVerifyf(pTuning,"Failed to find 'Death_Tranquilizer' NM tuning data"))
|
|
{
|
|
pNMTask = rage_new CTaskNMSimple(*pTuning);
|
|
}
|
|
else
|
|
{
|
|
pNMTask = rage_new CTaskNMRelax(1000, 3000);
|
|
}
|
|
|
|
CEventSwitch2NM eventNM(3000, pNMTask);
|
|
ped.GetPedIntelligence()->AddEvent(eventNM);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CEventScanner::CheckTasksAreCompatibleWithMotion( CPed &ped )
|
|
{
|
|
if (ped.GetAllowTasksIncompatibleWithMotion())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Check that this ped's tasks, down to the first non-temporary task, are compatible with its active move blender.
|
|
// - don't do this for network clones or the player.
|
|
if(ped.GetPedConfigFlag( CPED_CONFIG_FLAG_IsSwimming ) && !ped.IsNetworkClone() && !ped.IsPlayer() )
|
|
{
|
|
CTaskMotionBase* pMotionTask = ped.GetCurrentMotionTask();
|
|
if (pMotionTask && pMotionTask->IsInWater())
|
|
{
|
|
bool isTaskCompatible = false;
|
|
for(int i=0; i<PED_TASK_PRIORITY_MAX; i++)
|
|
{
|
|
aiTask *pTask = ped.GetPedIntelligence()->GetTaskManager()->GetTask(PED_TASK_TREE_PRIMARY, i);
|
|
|
|
CTask * task = static_cast<CTask*>(pTask);
|
|
if(task)
|
|
{
|
|
if (pMotionTask && task->IsValidForMotionTask(*pMotionTask))
|
|
{
|
|
isTaskCompatible = true;
|
|
break;
|
|
}
|
|
else if(i >= PED_TASK_PRIORITY_EVENT_RESPONSE_NONTEMP)
|
|
{
|
|
//This is the first non-temporary task, so stop checking.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!isTaskCompatible)
|
|
{
|
|
//This ped's task is not compatible with its active move blender, so fire an event to get the ped back onto a compatible move blender.
|
|
aiAssert(pMotionTask && pMotionTask->IsInWater());
|
|
//Add new motion types here.
|
|
//We need to get the ped out of the water before it can continue with the active task.
|
|
CEventGetOutOfWater event;
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CEventScanner::CheckAmbientFriend(CPed& ped)
|
|
{
|
|
CPed* pAmbientFriend = ped.GetPedIntelligence()->GetAmbientFriend();
|
|
|
|
// No friends.
|
|
if (!pAmbientFriend)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't do this on the player.
|
|
if (pAmbientFriend->IsAPlayerPed() || ped.IsAPlayerPed())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't do this on mission entities.
|
|
if (pAmbientFriend->PopTypeIsMission() || ped.PopTypeIsMission())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// See if they are doing something worth responding to.
|
|
CEvent* pFriendEvent = pAmbientFriend->GetPedIntelligence()->GetCurrentEvent();
|
|
bool bFriendIsWanderingAway = false;
|
|
|
|
// Check for the owner wandering away (without being sent an event)
|
|
if (pAmbientFriend->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_WANDER, true)
|
|
&& ped.GetTaskData().GetIsFlagSet(CTaskFlags::DependentAmbientFriend))
|
|
{
|
|
bFriendIsWanderingAway = true;
|
|
}
|
|
|
|
// At this point, we know that our friend isn't doing anything interesting.
|
|
if (!pFriendEvent && !bFriendIsWanderingAway)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't respond to temporary events.
|
|
if (pFriendEvent && pFriendEvent->IsTemporaryEvent())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if our friend is responding to us already.
|
|
if (pFriendEvent && pFriendEvent->GetEventType() == EVENT_HELP_AMBIENT_FRIEND)
|
|
{
|
|
// The "independent" friend (the owner of the dog) does not respond to the dog's help event.
|
|
// The dependent friend (the dog) will - this is so it will follow if the situation escalates.
|
|
if (!ped.GetTaskData().GetIsFlagSet(CTaskFlags::DependentAmbientFriend))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Determine how we want to help.
|
|
CEventHelpAmbientFriend::eHelpAmbientReaction iNewResponse = CEventHelpAmbientFriend::DetermineNewHelpResponse(ped, *pAmbientFriend, pFriendEvent);
|
|
|
|
// Check if we are responding to our friend already.
|
|
CEvent* pCurrentEvent = ped.GetPedIntelligence()->GetCurrentEvent();
|
|
if (pCurrentEvent && pCurrentEvent->GetEventType() == EVENT_HELP_AMBIENT_FRIEND)
|
|
{
|
|
// Unless you're the dependent, you don't need to add another event in this case.
|
|
if (!ped.GetTaskData().GetIsFlagSet(CTaskFlags::DependentAmbientFriend))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check how we were helping.
|
|
CEventHelpAmbientFriend::eHelpAmbientReaction iOldResponse = static_cast<CEventHelpAmbientFriend*>(pCurrentEvent)->GetHelpResponse();
|
|
|
|
// Only allow a new response if things got worse.
|
|
if (iNewResponse <= iOldResponse)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Add the event.
|
|
CEventHelpAmbientFriend helpEvent(pAmbientFriend, pFriendEvent ? pFriendEvent->GetSourcePed() : NULL, iNewResponse);
|
|
ped.GetPedIntelligence()->AddEvent(helpEvent, false, true);
|
|
}
|
|
|
|
void CEventScanner::CheckAmbientFriendDeletion(CPed& ped)
|
|
{
|
|
// We had a friend but they were destroyed and we were dependent on them.
|
|
if (ped.GetPedIntelligence()->HadAmbientFriend() && !ped.GetPedIntelligence()->GetAmbientFriend() && ped.GetTaskData().GetIsFlagSet(CTaskFlags::DependentAmbientFriend))
|
|
{
|
|
// Do short range removal on this ped.
|
|
ped.SetRemoveAsSoonAsPossible(true);
|
|
}
|
|
}
|
|
|
|
CInterestingEvents g_InterestingEvents;
|
|
|
|
const u32 CInterestingEvents::ms_iScanFrequency = 500;
|
|
|
|
|
|
CInterestingEvents::CInterestingEvents(void)
|
|
{
|
|
m_bIsActive = false;//true; // NB : this must be removed, and only set when camera goes idle and then reset again when it exits idle-mode
|
|
m_bIgnoreEventsBehindPlayer = true;
|
|
m_bWaitForEventDurationToComplete = true;
|
|
m_bUseTimeDelayBeforeAddingSimilarEvent = true;
|
|
m_iLookingAtEvent = -1;
|
|
m_iLastScanTime = 0;
|
|
m_fEventRadius = 30.0f;
|
|
m_iCurrentFrameCounter = fwTimer::GetSystemFrameCount() - 1;
|
|
|
|
for(int e=0; e<MAX_NUM_INTERESTING_EVENTS; e++)
|
|
{
|
|
m_Events[e].m_eType = ENone;
|
|
m_Events[e].m_iStartTime = 0;
|
|
m_Events[e].m_pEntity = NULL;
|
|
m_Events[e].m_bVisibleToCamera = false;
|
|
}
|
|
|
|
for(int i=0; i<ENumCategories; i++)
|
|
{
|
|
m_NextTimeToAcceptEvents[i] = 0;
|
|
// set defaults in case I forgot any below..
|
|
m_EventDurations[i] = 2000;
|
|
m_EventPriorities[i] = 5;
|
|
}
|
|
|
|
m_EventPriorities[EPedGotKilled] = 10;
|
|
m_EventPriorities[EExplosion] = 10;
|
|
m_EventPriorities[ESwatTeamAbseiling] = 9;
|
|
m_EventPriorities[ECopKillingCriminal] = 9;
|
|
m_EventPriorities[EGangFight] = 9;
|
|
m_EventPriorities[EGangAttackingPed] = 9;
|
|
m_EventPriorities[EMeleeAction] = 9;
|
|
m_EventPriorities[ESeenMeleeAction] = 6;
|
|
m_EventPriorities[EGunshotFired] = 9;
|
|
m_EventPriorities[EHelicopterOverhead] = 8;
|
|
m_EventPriorities[ECarJacking] = 7;
|
|
m_EventPriorities[ERoadRage] = 7;
|
|
m_EventPriorities[EFistFight] = 6;
|
|
m_EventPriorities[ECarCrash] = 8;
|
|
m_EventPriorities[EPedKnockedOffBike] = 9;
|
|
m_EventPriorities[EPedRunOver] = 9;
|
|
m_EventPriorities[EMadDriver] = 5;
|
|
m_EventPriorities[EEmergencyServicesArrived] = 6;
|
|
m_EventPriorities[EPanickedPed] = 6;
|
|
m_EventPriorities[EPedRevived] = 6;
|
|
m_EventPriorities[EPlaneFlyby] = 5; // not done
|
|
m_EventPriorities[ESexyPed] = 4;
|
|
m_EventPriorities[ESexyCar] = 4;
|
|
m_EventPriorities[EGangMemberNearby] = 2;
|
|
m_EventPriorities[ECriminalNearby] = 2;
|
|
m_EventPriorities[ECopNearby] = 2;
|
|
m_EventPriorities[EProzzyNearby] = 2;
|
|
m_EventPriorities[EPedUsingAttractor] = 1;
|
|
m_EventPriorities[EPedSunbathing] = 1;
|
|
m_EventPriorities[EPedsChatting] = 1;
|
|
|
|
m_EventDurations[EPedGotKilled] = 4000;
|
|
m_EventDurations[EExplosion] = 4000;
|
|
m_EventDurations[ESwatTeamAbseiling] = 8000;
|
|
m_EventDurations[ECopKillingCriminal] = 6000;
|
|
m_EventDurations[EGangFight] = 6000;
|
|
m_EventDurations[EGangAttackingPed] = 6000;
|
|
m_EventDurations[EMeleeAction] = 5000;
|
|
m_EventDurations[ESeenMeleeAction] = 5000;
|
|
m_EventDurations[EGunshotFired] = 5000;
|
|
m_EventDurations[EHelicopterOverhead] = 8000;
|
|
m_EventDurations[ECarJacking] = 6000;
|
|
m_EventDurations[ERoadRage] = 6000;
|
|
m_EventDurations[EFistFight] = 5000;
|
|
m_EventDurations[ECarCrash] = 6000;
|
|
m_EventDurations[EPedKnockedOffBike] = 6000;
|
|
m_EventDurations[EPedRunOver] = 6000;
|
|
m_EventDurations[EMadDriver] = 6000;
|
|
m_EventDurations[EEmergencyServicesArrived] = 8000;
|
|
m_EventDurations[EPanickedPed] = 5000;
|
|
m_EventDurations[EPedRevived] = 6000;
|
|
m_EventDurations[EPlaneFlyby] = 6000; // not done
|
|
m_EventDurations[ESexyPed] = 3000;
|
|
m_EventDurations[ESexyCar] = 3000;
|
|
m_EventDurations[EGangMemberNearby] = 3000;
|
|
m_EventDurations[ECriminalNearby] = 3000;
|
|
m_EventDurations[ECopNearby] = 3000;
|
|
m_EventDurations[EProzzyNearby] = 3000;
|
|
m_EventDurations[EPedUsingAttractor] = 5000;
|
|
m_EventDurations[EPedSunbathing] = 5000;
|
|
m_EventDurations[EPedsChatting] = 5000;
|
|
}
|
|
|
|
CInterestingEvents::~CInterestingEvents(void)
|
|
{
|
|
for(int e=0; e<MAX_NUM_INTERESTING_EVENTS; e++)
|
|
{
|
|
TInterestingEvent & pEvent = m_Events[e];
|
|
if(pEvent.m_pEntity)
|
|
{
|
|
pEvent.m_pEntity = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CInterestingEvents::Add(EType eType, CEntity * pEntity)
|
|
{
|
|
if(!m_bIsActive || !pEntity)
|
|
return;
|
|
|
|
bool bVisibleToCamera = true;
|
|
|
|
Vector3 vCameraOrigin = camInterface::GetPos();
|
|
|
|
if(m_iCurrentFrameCounter != fwTimer::GetSystemFrameCount())
|
|
{
|
|
m_iCurrentFrameCounter = fwTimer::GetSystemFrameCount();
|
|
CPed * pPlayerPed = FindPlayerPed();
|
|
const Vector3 vPlayerPos = VEC3V_TO_VECTOR3(pPlayerPed->GetTransform().GetPosition());
|
|
m_ViewVec = vPlayerPos - vCameraOrigin;
|
|
m_ViewVec.z = 0;
|
|
// if(m_ViewVec.NormalizeAndMag() == 0.0f)
|
|
if(!m_ViewVec.NormalizeSafeRet())
|
|
{
|
|
// NB: just in case camera is directly above player
|
|
m_ViewVec = VEC3V_TO_VECTOR3(pPlayerPed->GetTransform().GetB());
|
|
}
|
|
m_ScanOrigin = vPlayerPos + (m_ViewVec * m_fEventRadius);
|
|
}
|
|
|
|
// get position 'fRadius' units in front of player, in view direction
|
|
const float fRadiusSqr = m_fEventRadius * m_fEventRadius;
|
|
|
|
const Vector3 vEntityPos = VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition());
|
|
// sqr'd 2d distance from scanningEntity to center
|
|
Vector3 vDiff = m_ScanOrigin - vEntityPos;
|
|
float fRadialDistSqr = (vDiff.x * vDiff.x) + (vDiff.y * vDiff.y);
|
|
|
|
// scanningEntity must be within circle of interest, centered 'fRadius' units in front of player
|
|
if(fRadialDistSqr > fRadiusSqr)
|
|
{
|
|
bVisibleToCamera = false;
|
|
}
|
|
|
|
if(m_bIgnoreEventsBehindPlayer)
|
|
{
|
|
// planar distance in front of player
|
|
float fPlaneDist = - DotProduct(m_ViewVec, vCameraOrigin);
|
|
float fPlanarDist = DotProduct(vEntityPos, m_ViewVec) + fPlaneDist;
|
|
// scanningEntity must be in front of player
|
|
if(fPlanarDist < 0.0f)
|
|
{
|
|
bVisibleToCamera = false;
|
|
}
|
|
}
|
|
|
|
// do a line-test to see if this scanningEntity is initially visible
|
|
WorldProbe::CShapeTestProbeDesc probeDesc;
|
|
probeDesc.SetStartAndEnd(vCameraOrigin, vEntityPos);
|
|
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
|
|
probeDesc.SetOptions(WorldProbe::LOS_IGNORE_SEE_THRU);
|
|
probeDesc.SetContext(WorldProbe::LOS_GeneralAI);
|
|
bool bLOS = bVisibleToCamera && !WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
|
|
|
|
if(!bLOS)
|
|
{
|
|
bVisibleToCamera = false;
|
|
}
|
|
|
|
u32 iTimeMs = fwTimer::GetTimeInMilliseconds();
|
|
int iPriority = m_EventPriorities[eType];
|
|
bool bCanAddNewEventOfType = iTimeMs > m_NextTimeToAcceptEvents[eType];
|
|
|
|
|
|
for(int e=0; e<MAX_NUM_INTERESTING_EVENTS; e++)
|
|
{
|
|
TInterestingEvent & pEvent = m_Events[e];
|
|
|
|
bool bLookingAtThisEvent = (m_iLookingAtEvent == e);
|
|
|
|
// old event's duration has expired ?
|
|
bool bDurationExpired;
|
|
if(pEvent.m_eType > ENone && pEvent.m_eType < ENumCategories)
|
|
{
|
|
bDurationExpired = iTimeMs > pEvent.m_iStartTime + m_EventDurations[pEvent.m_eType];
|
|
}
|
|
else
|
|
{
|
|
bDurationExpired = true;
|
|
}
|
|
|
|
// old event's scanningEntity disappeared ?
|
|
if(!pEvent.m_pEntity)
|
|
{
|
|
pEvent.m_eType = ENone;
|
|
pEvent.m_pEntity = NULL;
|
|
bLookingAtThisEvent = false;
|
|
}
|
|
|
|
EType eExistingType = (EType)pEvent.m_eType;
|
|
int iExistingPriority = m_EventPriorities[pEvent.m_eType];
|
|
|
|
// to add the new event, the existing one must've had it's scanningEntity disappeared - or else :
|
|
// it must a greater priority, the existing event's duration must have expired & the delay
|
|
// before making a new similar event must have expired
|
|
if(eExistingType == ENone || ((iPriority >= iExistingPriority || bDurationExpired) && bCanAddNewEventOfType && !bLookingAtThisEvent))
|
|
{
|
|
pEvent.m_eType = eType;
|
|
pEvent.m_pEntity = pEntity;
|
|
pEvent.m_iStartTime = iTimeMs;
|
|
pEvent.m_bVisibleToCamera = bVisibleToCamera;
|
|
|
|
// Optionally wait an amount of time before accepting any events of the same type
|
|
if(m_bUseTimeDelayBeforeAddingSimilarEvent)
|
|
{
|
|
// m_NextTimeToAcceptEvents[eType] = iTimeMs + m_EventDurations[eType];
|
|
m_NextTimeToAcceptEvents[eType] = iTimeMs + (m_EventDurations[eType] / 2);
|
|
}
|
|
else
|
|
{
|
|
m_NextTimeToAcceptEvents[eType] = iTimeMs;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const TInterestingEvent *
|
|
CInterestingEvents::GetInterestingEvent(bool bForCamera)
|
|
{
|
|
u32 iTimeMs = fwTimer::GetTimeInMilliseconds();
|
|
|
|
// Optionally aways return the same event until its duration expires
|
|
if(bForCamera && m_bWaitForEventDurationToComplete && m_iLookingAtEvent != -1)
|
|
{
|
|
// if current event not expired, return it again - to stop camera focus from flicking constantly
|
|
TInterestingEvent * pLookingAtEvent = &m_Events[m_iLookingAtEvent];
|
|
if(pLookingAtEvent->m_pEntity && iTimeMs < pLookingAtEvent->m_iStartTime + m_EventDurations[pLookingAtEvent->m_eType])
|
|
{
|
|
return pLookingAtEvent;
|
|
}
|
|
}
|
|
|
|
s8 iBestEventPriority = ENone;
|
|
s8 iBestEventIndex = -1;
|
|
|
|
for(s8 e=0; e<MAX_NUM_INTERESTING_EVENTS; e++)
|
|
{
|
|
TInterestingEvent * pEvent = &m_Events[e];
|
|
|
|
if(pEvent->m_pEntity &&
|
|
(pEvent->m_bVisibleToCamera || !bForCamera) &&
|
|
iTimeMs < pEvent->m_iStartTime + m_EventDurations[pEvent->m_eType])
|
|
{
|
|
if(m_EventPriorities[pEvent->m_eType] > iBestEventPriority || (fwRandom::GetRandomNumber() & 0xFFFF) < 128)
|
|
{
|
|
iBestEventPriority = m_EventPriorities[pEvent->m_eType];
|
|
iBestEventIndex = e;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bForCamera )
|
|
{
|
|
m_iLookingAtEvent = iBestEventIndex;
|
|
}
|
|
|
|
if(iBestEventIndex != -1)
|
|
{
|
|
return &m_Events[iBestEventIndex];
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// If an event is no good, or no longer applicable - this function can be called
|
|
// to ensure that it gets deleted or superseded by a new event
|
|
void
|
|
CInterestingEvents::InvalidateEvent(const TInterestingEvent * pInvalidEvent)
|
|
{
|
|
for(int i=0; i<MAX_NUM_INTERESTING_EVENTS; i++)
|
|
{
|
|
TInterestingEvent * pEvent = &m_Events[i];
|
|
if(pEvent == pInvalidEvent)
|
|
{
|
|
pEvent->m_iStartTime = 0;
|
|
|
|
if(pEvent->m_pEntity)
|
|
{
|
|
pEvent->m_pEntity = NULL;
|
|
}
|
|
|
|
if(m_iLookingAtEvent == i)
|
|
{
|
|
m_iLookingAtEvent = -1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Tests all events to see if they're still visible, and invalidates those which are not
|
|
void
|
|
CInterestingEvents::InvalidateNonVisibleEvents(void)
|
|
{
|
|
Vector3 vCameraOrigin = camInterface::GetPos();
|
|
|
|
for(int i=0; i<MAX_NUM_INTERESTING_EVENTS; i++)
|
|
{
|
|
TInterestingEvent * pEvent = &m_Events[i];
|
|
if(pEvent->m_pEntity)
|
|
{
|
|
// do a line-test to see if this scanningEntity is still visible
|
|
WorldProbe::CShapeTestProbeDesc probeDesc;
|
|
probeDesc.SetStartAndEnd(vCameraOrigin, VEC3V_TO_VECTOR3(pEvent->m_pEntity->GetTransform().GetPosition()));
|
|
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
|
|
probeDesc.SetOptions(WorldProbe::LOS_IGNORE_SEE_THRU);
|
|
probeDesc.SetContext(WorldProbe::LOS_GeneralAI);
|
|
bool bLOS = !WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
|
|
|
|
// no line of sight, so invalidate this event
|
|
if(!bLOS)
|
|
{
|
|
pEvent->m_bVisibleToCamera = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPhysicsEventScanner::Scan( CPed &ped )
|
|
{
|
|
TUNE_GROUP_FLOAT(PED_MOVEMENT, fMinFallBeforeInAir, 2.0f, 0.0f, 10.0f, 0.001f);
|
|
|
|
// Check if ped is in air and not being handled by the in air task
|
|
// Disable this if the physics lod is active, until it disables the capsule collision correctly.
|
|
if ( (ped.GetPedConfigFlag( CPED_CONFIG_FLAG_IsInTheAir ) &&
|
|
!ped.GetPedResetFlag(CPED_RESET_FLAG_IsClimbing) &&
|
|
!ped.GetPedResetFlag(CPED_RESET_FLAG_IsUsingJetpack) &&
|
|
!ped.IsDead() &&
|
|
!ShouldIgnoreInAirEventDueToFallTask(ped) &&
|
|
!(ped.GetAttachParent() && ((CPhysical*)ped.GetAttachParentForced())->GetIsTypeVehicle() && !ped.GetPedResetFlag(CPED_RESET_FLAG_CanAbortExitForInAirEvent))) ||
|
|
(!ped.GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics) && !ped.GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics) && !ped.GetIsStanding() && !ped.GetIsInWater() && !ped.GetPedConfigFlag( CPED_CONFIG_FLAG_SwimmingTasksRunning ) &&
|
|
!ped.GetPedConfigFlag( CPED_CONFIG_FLAG_IsInTheAir ) && CPedGeometryAnalyser::IsInAir(ped) ))
|
|
{
|
|
Vector3 vecStart = VEC3V_TO_VECTOR3(ped.GetTransform().GetPosition());
|
|
Vector3 vecEnd = vecStart - (ZAXIS * fMinFallBeforeInAir);
|
|
|
|
//grcDebugDraw::Line(vecStart, vecEnd, Color32(255, 0, 0, 255), Color32(255, 0, 0, 255), 60 );
|
|
|
|
WorldProbe::CShapeTestProbeDesc probeDesc;
|
|
probeDesc.SetStartAndEnd(vecStart, vecEnd);
|
|
probeDesc.SetExcludeEntity(&ped); // Exclude ourselves! (IMPORTANT!)
|
|
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_TYPES_MOVER);
|
|
if(!WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
|
|
{
|
|
// We didn't hit anything, generate the in Air Event
|
|
CEventInAir event(&ped);
|
|
ped.GetPedIntelligence()->AddEvent(event);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!ped.GetPedResetFlag(CPED_RESET_FLAG_DisableNMForRiverRapids) &&
|
|
ped.GetIsInWater() &&
|
|
!ped.GetUsingRagdoll() &&
|
|
!ped.IsDead() &&
|
|
!ped.GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir) &&
|
|
!(ped.GetAttachParent() && ((CPhysical*)ped.GetAttachParentForced())->GetIsTypeVehicle()) &&
|
|
!ped.GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics) &&
|
|
!ped.GetPedResetFlag(CPED_RESET_FLAG_OverridePhysics) &&
|
|
!CPedType::IsAnimalType(ped.GetPedType()))
|
|
{
|
|
if (CTaskMotionSwimming::CheckForRapids(&ped))
|
|
{
|
|
if (CPhysics::CanUseRagdolls() && ped.GetRagdollState() != RAGDOLL_STATE_ANIM_LOCKED && CTaskNMBehaviour::CanUseRagdoll(&ped, RAGDOLL_TRIGGER_FALL))
|
|
{
|
|
nmDebugf2("[%u] Adding nm river rapids task:%s(%p) Ped moving in rapid water.", fwTimer::GetTimeInMilliseconds(), ped.GetModelName(), &ped);
|
|
CTaskNMRiverRapids* pTaskNM = rage_new CTaskNMRiverRapids(2000, 30000);
|
|
CEventSwitch2NM eventRagdoll(30000, pTaskNM);
|
|
ped.SwitchToRagdoll(eventRagdoll);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search for peds standing on moving objects such as vehicles, generate a natural
|
|
// motion response that will throw them off.
|
|
CPhysical* pGround = ped.GetGroundPhysical();
|
|
if(pGround && !ped.GetUsingRagdoll())
|
|
{
|
|
bool canStandOnPhysical = false;
|
|
if(pGround->GetIsTypePed())
|
|
{
|
|
canStandOnPhysical = true;
|
|
}
|
|
else
|
|
{
|
|
//Check if the ground is a vehicle, and the ped can stand on it.
|
|
if(pGround->GetIsTypeVehicle())
|
|
{
|
|
CVehicle* pVehicle = static_cast<CVehicle*>(pGround);
|
|
|
|
bool bIsClimbingLadder = (ped.GetPedResetFlag(CPED_RESET_FLAG_IsClimbing) && ped.GetPedIntelligence()->GetTaskClimbLadder());
|
|
if(!bIsClimbingLadder)
|
|
{
|
|
canStandOnPhysical = pVehicle->CanPedsStandOnTop();
|
|
}
|
|
|
|
CTaskEnterVehicle* pEnterVehicleTask = static_cast<CTaskEnterVehicle*>(ped.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE));
|
|
if (pEnterVehicleTask)
|
|
{
|
|
if (pVehicle->IsTurretSeat(pEnterVehicleTask->GetTargetSeat()))
|
|
{
|
|
canStandOnPhysical = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't allow similar event to get applied repeatedly over a short amount of time from the same source.
|
|
// This is an attempt at determining the most important impact from a particular source over a short amount of time that
|
|
// isn't frame-rate dependent.
|
|
if (ped.GetRagdollInst())
|
|
{
|
|
if (ped.GetRagdollInst()->GetLastImpactDamageEntity() == pGround && ped.GetRagdollInst()->GetTimeSinceImpactDamage() < CCollisionEventScanner::RAGDOLL_VEHICLE_IMPACT_DAMAGE_TIME_LIMIT)
|
|
{
|
|
canStandOnPhysical = true;
|
|
}
|
|
else
|
|
{
|
|
// Note the new entity and damage taken from it
|
|
ped.GetRagdollInst()->SetImpactDamageEntity(pGround);
|
|
ped.GetRagdollInst()->SetAccumulatedImpactDamageFromLastEntity(0.0f);
|
|
}
|
|
}
|
|
|
|
// Under certain circumstances we don't wish to do this - eg. when swimming above another model
|
|
if(!canStandOnPhysical && !ped.GetIsSwimming() && !ped.GetCapsuleInfo()->IsQuadruped() && !ped.GetPedResetFlag(CPED_RESET_FLAG_BlockRagdollFromVehicleFallOff) && !ped.GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir)) // horses are ok too B* 476793
|
|
{
|
|
if(CPhysics::CanUseRagdolls() && ped.GetRagdollState()!=RAGDOLL_STATE_ANIM_LOCKED)
|
|
{
|
|
if(CTaskNMBehaviour::CanUseRagdoll(&ped, RAGDOLL_TRIGGER_PHYSICAL_FALLOFF, ped.GetGroundPhysical()))
|
|
{
|
|
static float SPEED_TO_SAY_SCREAM_AUDIO = 7.5f;
|
|
if(ped.GetVelocity().Mag2() > rage::square(SPEED_TO_SAY_SCREAM_AUDIO) && ped.GetSpeechAudioEntity())
|
|
{
|
|
audSpeechInitParams speechParams;
|
|
speechParams.forcePlay = true;
|
|
speechParams.allowRecentRepeat = true;
|
|
ped.GetSpeechAudioEntity()->Say("HIGH_FALL", speechParams, ATSTRINGHASH("PAIN_VOICE", 0x048571d8d));
|
|
}
|
|
|
|
u32 grabFlags = 0;
|
|
if (!CTaskNMBehaviour::ms_bDisableBumpGrabForPlayer || !ped.IsPlayer())
|
|
{
|
|
grabFlags|=CGrabHelper::TARGET_AUTO_WHEN_FALLING;
|
|
}
|
|
|
|
nmDebugf2("[%u] Adding nm balance task:%s(%p) Ped balancing on moving vehicle.", fwTimer::GetTimeInMilliseconds(), ped.GetModelName(), &ped);
|
|
CTaskNMBalance* pTaskNM = rage_new CTaskNMBalance(2000, 30000, ped.GetGroundPhysical(), grabFlags,NULL,0.0f,NULL,CTaskNMBalance::BALANCE_FORCE_FALL_FROM_MOVING_CAR);
|
|
|
|
CEventSwitch2NM eventRagdoll(30000, pTaskNM);
|
|
ped.SwitchToRagdoll(eventRagdoll);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ped.GetPedResetFlag(CPED_RESET_FLAG_StandingOnForkliftForks) && !ped.GetUsingRagdoll() && !ped.GetIsSwimming())
|
|
{
|
|
if(CTaskNMBehaviour::CanUseRagdoll(&ped, RAGDOLL_TRIGGER_FORKLIFT_FORKS_FALLOFF, ped.GetGroundPhysical()))
|
|
{
|
|
static float SPEED_TO_SAY_SCREAM_AUDIO = 7.5f;
|
|
if(ped.GetVelocity().Mag2() > rage::square(SPEED_TO_SAY_SCREAM_AUDIO) && ped.GetSpeechAudioEntity())
|
|
{
|
|
audSpeechInitParams speechParams;
|
|
speechParams.forcePlay = true;
|
|
speechParams.allowRecentRepeat = true;
|
|
ped.GetSpeechAudioEntity()->Say("HIGH_FALL", speechParams, ATSTRINGHASH("PAIN_VOICE", 0x048571d8d));
|
|
}
|
|
|
|
u32 grabFlags = 0;
|
|
if(!CTaskNMBehaviour::ms_bDisableBumpGrabForPlayer || !ped.IsPlayer())
|
|
{
|
|
grabFlags|=CGrabHelper::TARGET_AUTO_WHEN_FALLING;
|
|
}
|
|
|
|
nmDebugf2("[%u] Adding nm balance task:%s(%p) Ped balancing on forklift forks.", fwTimer::GetTimeInMilliseconds(), ped.GetModelName(), &ped);
|
|
CTaskNMHighFall* pTaskNM = rage_new CTaskNMHighFall(1000, NULL, CTaskNMHighFall::HIGHFALL_TEETER_EDGE);
|
|
CEventSwitch2NM eventRagdoll(30000, pTaskNM);
|
|
ped.SwitchToRagdoll(eventRagdoll);
|
|
}
|
|
}
|
|
|
|
const CCollisionHistory* pCollisionHistory = ped.GetFrameCollisionHistory();
|
|
// if ped is walking into something flag it for later so we can do stuff in CPedIntelligence()->Process()
|
|
CCollisionHistoryIterator obstacleIterator(pCollisionHistory, /*bool bIterateBuildings=*/ true, /*bool bIterateVehicles=*/ true,
|
|
/*bool bIteratePeds=*/ false, /*bool bIterateObjects=*/ true, /*bool bIterateOthers=*/ false);
|
|
|
|
while(const CCollisionRecord* pObstacleColRecord = obstacleIterator.GetNext())
|
|
{
|
|
if(DotProduct(pObstacleColRecord->m_MyCollisionNormal, VEC3V_TO_VECTOR3(ped.GetTransform().GetB())) < -0.5f)
|
|
{
|
|
ped.SetPedResetFlag( CPED_RESET_FLAG_PedHitWallLastFrame, true );
|
|
|
|
CEntity *pHitEntity = pObstacleColRecord->m_pRegdCollisionEntity;
|
|
|
|
//B*1748361: If we're standing on a vehicle and hit an object at a reasonable velocity then trigger the ped to ragdoll
|
|
//same ragdoll effect as above: if(!canStandOnVehicle && !ped.GetIsSwimming() && !ped.GetCapsuleInfo()->IsQuadruped() && !ped.GetPedResetFlag(CPED_RESET_FLAG_BlockRagdollFromVehicleFallOff) && !ped.GetPedConfigFlag(CPED_CONFIG_FLAG_IsInTheAir) condition
|
|
if(pGround && pGround->GetIsTypeVehicle() && (pHitEntity != pGround) )
|
|
{
|
|
static dev_float fMinRagdollVelocity = 5.0f;
|
|
if (pGround->GetVelocity().Mag() > fMinRagdollVelocity)
|
|
{
|
|
if(CPhysics::CanUseRagdolls() && ped.GetRagdollState()!=RAGDOLL_STATE_ANIM_LOCKED)
|
|
{
|
|
if(CTaskNMBehaviour::CanUseRagdoll(&ped, RAGDOLL_TRIGGER_PHYSICAL_FALLOFF, ped.GetGroundPhysical()))
|
|
{
|
|
static float SPEED_TO_SAY_SCREAM_AUDIO = 7.5f;
|
|
if(ped.GetVelocity().Mag2() > rage::square(SPEED_TO_SAY_SCREAM_AUDIO) && ped.GetSpeechAudioEntity())
|
|
{
|
|
audSpeechInitParams speechParams;
|
|
speechParams.forcePlay = true;
|
|
speechParams.allowRecentRepeat = true;
|
|
ped.GetSpeechAudioEntity()->Say("HIGH_FALL", speechParams, ATSTRINGHASH("PAIN_VOICE", 0x048571d8d));
|
|
}
|
|
|
|
u32 grabFlags = 0;
|
|
if (!CTaskNMBehaviour::ms_bDisableBumpGrabForPlayer || !ped.IsPlayer())
|
|
{
|
|
grabFlags|=CGrabHelper::TARGET_AUTO_WHEN_FALLING;
|
|
}
|
|
|
|
nmDebugf2("[%u] Adding nm balance task:%s(%p) Ped balancing on moving vehicle.", fwTimer::GetTimeInMilliseconds(), ped.GetModelName(), &ped);
|
|
CTaskNMBalance* pTaskNM = rage_new CTaskNMBalance(2000, 30000, ped.GetGroundPhysical(), grabFlags,NULL,0.0f,NULL,CTaskNMBalance::BALANCE_FORCE_FALL_FROM_MOVING_CAR);
|
|
|
|
CEventSwitch2NM eventRagdoll(30000, pTaskNM);
|
|
ped.SwitchToRagdoll(eventRagdoll);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CPhysicsEventScanner::ShouldIgnoreInAirEventDueToFallTask(const CPed& rPed) const
|
|
{
|
|
//Check if the fall task is valid.
|
|
const CTaskFall* pTaskFall = static_cast<CTaskFall *>(rPed.GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_FALL));
|
|
if(pTaskFall)
|
|
{
|
|
//Check if the parachute task is invalid.
|
|
const CTaskParachute* pTaskParachute = static_cast<CTaskParachute *>(pTaskFall->FindSubTaskOfType(CTaskTypes::TASK_PARACHUTE));
|
|
if(!pTaskParachute)
|
|
{
|
|
return true;
|
|
}
|
|
//Check if the ped is not landing.
|
|
else if(!pTaskParachute->IsLanding())
|
|
{
|
|
return true;
|
|
}
|
|
//Check if the ped is crash landing, and can't parachute.
|
|
else if(pTaskParachute->IsCrashLanding() && !CTaskParachute::CanPedParachute(rPed))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const float CStaticMovementScanner::ms_fMillisecsWithoutCollisionTolerance = 4.0f * (1000.0f / 30.0f);
|
|
const float CStaticMovementScanner::ms_iStaticCountColPosToleranceSqr = (0.25f * 0.25f);
|
|
const int CStaticMovementScanner::ms_iStaticCounterStuckLimit = 30;
|
|
const int CStaticMovementScanner::ms_iStaticCounterStuckLimitPlayerMP = 90;
|
|
const int CStaticMovementScanner::ms_iStaticActivateObjectLimit = 10;
|
|
const float CStaticMovementScanner::ms_fMillisecsPerCount = 1000.0f / 30.0f;
|
|
|
|
void CStaticMovementScanner::Scan( CPed& ped )
|
|
{
|
|
if(ped.GetIsInVehicle() || ped.GetPedResetFlag(CPED_RESET_FLAG_ResetMovementStaticCounter))
|
|
{
|
|
m_iStaticCounter = 0;
|
|
m_fMillisecsCounter = 0.0f;
|
|
return;
|
|
}
|
|
|
|
// Static counter check only makes sense for peds with physics enabled, since only these may collide with things
|
|
|
|
if( ped.GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics)==false )
|
|
{
|
|
const float fTimestepMS = fwTimer::GetTimeStep() * 1000.0f;
|
|
|
|
// We used to do this here:
|
|
// const bool bMovingInCover = ped.GetPedIntelligence()->GetPedCoverStatus(coverState,bCanFire) && (coverState == CTaskInCover::State_AimIntro || coverState == CTaskInCover::State_AimOutro);
|
|
// but now CTaskInCover sets CPED_RESET_FLAG_ForceMovementScannerCheck instead, for performance reasons.
|
|
const bool forceCheck = ped.GetPedResetFlag(CPED_RESET_FLAG_ForceMovementScannerCheck);
|
|
|
|
CTask * pTaskMotion = ped.GetPedIntelligence()->GetMotionTaskActiveSimplest();
|
|
const bool bTurn180 =
|
|
pTaskMotion && pTaskMotion->GetTaskType()==CTaskTypes::TASK_HUMAN_LOCOMOTION && pTaskMotion->GetState()==CTaskHumanLocomotion::State_Turn180;
|
|
|
|
if(forceCheck || (ped.GetMotionData()->GetIsStill()==false && bTurn180==false) )
|
|
{
|
|
const Vector3 vPedPosition = VEC3V_TO_VECTOR3(ped.GetTransform().GetPosition());
|
|
|
|
if(ped.GetFrameCollisionHistory()->GetNumCollidedEntities() > 0)
|
|
{
|
|
if(m_fMillisecsWithoutCollision > ms_fMillisecsWithoutCollisionTolerance)
|
|
{
|
|
m_vPedPositionAtFirstCollision = vPedPosition;
|
|
}
|
|
m_fMillisecsWithoutCollision = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
m_fMillisecsWithoutCollision += fTimestepMS;
|
|
m_fMillisecsWithoutCollision = Min(m_fMillisecsWithoutCollision, 9999.0f);
|
|
}
|
|
|
|
if(m_fMillisecsWithoutCollision > ms_fMillisecsWithoutCollisionTolerance)
|
|
{
|
|
m_iStaticCounter = 0;
|
|
m_fMillisecsCounter = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
if(m_vPedPositionAtFirstCollision.IsNonZero())
|
|
{
|
|
pedAssertf(m_vPedPositionAtFirstCollision.IsNonZero(), "m_vPedPositionAtFirstCollision has not been initialised!");
|
|
|
|
#if __BANK // Ensure that debug timescale does not cause static counter to activate
|
|
const Vector3 vDiff = (vPedPosition - m_vPedPositionAtFirstCollision) / fwTimer::GetDebugTimeScale();
|
|
#else
|
|
const Vector3 vDiff = vPedPosition - m_vPedPositionAtFirstCollision;
|
|
#endif
|
|
const float fPosTolerance = ped.GetCurrentMotionTask()->IsInWater() ? ms_iStaticCountColPosToleranceSqr * 2.0f : ms_iStaticCountColPosToleranceSqr;
|
|
const float fFwdMovementSqr = (ped.IsStrafing() ? vDiff.XYMag2() : square(vDiff.Dot(VEC3V_TO_VECTOR3(ped.GetTransform().GetForward()))));
|
|
|
|
if(fFwdMovementSqr < fPosTolerance)
|
|
{
|
|
m_fMillisecsCounter += fTimestepMS;
|
|
while(m_fMillisecsCounter >= ms_fMillisecsPerCount)
|
|
{
|
|
m_fMillisecsCounter -= ms_fMillisecsPerCount;
|
|
m_iStaticCounter++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fMillisecsCounter = 0.0f;
|
|
m_iStaticCounter = 0;
|
|
m_vPedPositionAtFirstCollision = vPedPosition;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fMillisecsWithoutCollision += fTimestepMS;
|
|
m_fMillisecsWithoutCollision = Min(m_fMillisecsWithoutCollision, 9999.0f);
|
|
|
|
m_iStaticCounter = 0;
|
|
m_fMillisecsCounter = 0.0f;
|
|
}
|
|
}
|
|
}
|