Files
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2621 lines
87 KiB
C++

// Title : Targetting.cpp
// Author : Phil Hooker
// Started : 10/08/05
// Framework headers
#include "grcore/debugdraw.h"
#include "fwmaths/Angle.h"
// Game headers
#include "Debug/DebugScene.h"
#include "Event/Events.h"
#include "Network/Objects/Entities/NetObjPlayer.h"
#include "PedGroup/PedGroup.h"
#include "Peds/Ped.h"
#include "Peds/PedEventScanner.h"
#include "Peds/PedGeometryAnalyser.h"
#include "Peds/PedIntelligence.h"
#include "Peds/Ped.h"
#include "Peds/PedWeapons/PedTargeting.h"
#include "Peds/HealthConfig.h"
#include "Physics/GtaInst.h"
#include "Physics/Physics.h"
#include "physics/WorldProbe/worldprobe.h"
#include "Scene/Entity.h"
#include "scene/world/GameWorld.h"
#include "task/Combat/Cover/Cover.h"
#include "Task/Vehicle/TaskCar.h"
#include "Task/Combat/TaskCombat.h"
#include "Task/Combat/TaskNewCombat.h"
#include "vehicles/vehicle.h"
#include "weapons/Weapon.h"
AI_OPTIMISATIONS()
FW_INSTANTIATE_BASECLASS_POOL_SPILLOVER(CPedTargetting, MAX_NO_TARGETTING_SYSTEMS, 0.23f, atHashString("Targetting",0x652a20af), sizeof(CPedTargetting));
const static int TARGET_FADE_TIME_MS = 10000;
const static float TARGET_FORCED_REMOVAL_FADE_PERC = 0.8f;
//-------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------
CSingleTargetInfo::CSingleTargetInfo()
{
SetLastPositionSeen(Vector3(Vector3::ZeroType));
SetDistance(0.0f);
m_pEntity = NULL;
m_pBlockedByFriendlyPed = NULL;
m_fTotalScore = 0.0f;
m_iTimeExpiringMs = 0;
m_losStatus = Los_unchecked;
m_bLosBlockedNear = false;
m_bNeverScored = true;
m_bLastSeenInVehicle = false;
m_bLosWasDoneAsync = false;
m_bIsBeingTargeted = false;
m_bHasEverHadClearLos = false;
m_iCalcHeadingAndVehicleCountdown = 0;
m_iLastSeenExpirationTimeMs = 0;
m_fLastDirectionSeenMovingIn = 0.0f;
m_fVisibleTimer = 0.0f;
m_fBlockedTimer = 0.0f;
m_iTimeLastHostileActionMs = 0;
}
//-------------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------------
CSingleTargetInfo::~CSingleTargetInfo()
{
}
//-------------------------------------------------------------------------
// Reset the target information
//-------------------------------------------------------------------------
void CSingleTargetInfo::Revalidate( void )
{
Assert( m_pEntity );
m_iTimeExpiringMs = fwTimer::GetTimeInMilliseconds() + TARGET_FADE_TIME_MS;
}
#define MIN_MOVE_SPEED (0.0025f)
//-------------------------------------------------------------------------
// Calculate the current heading direction of the target
//-------------------------------------------------------------------------
void CSingleTargetInfo::CalculateHeading( void )
{
// Set m_iCalcHeadingAndVehicleCountdown so we call this function again
// after waiting for kCalcHeadingAndVehiclePeriodFrames - 1 frames.
// Note: sharing the countdown between CalculateHeading() and CalculateInVehicle() basically
// assumes that they are called together, which currently always seems to be the case.
m_iCalcHeadingAndVehicleCountdown = kCalcHeadingAndVehiclePeriodFrames - 1;
// IF the entity is physical work out its heading from the current velocity.
if( m_pEntity->GetIsPhysical() )
{
ScalarV scMinMoveSpeedSq = ScalarVFromF32(MIN_MOVE_SPEED);
const CPhysical* pPhysical = static_cast<const CPhysical*>(m_pEntity.Get());
Vec3V vVelocity = RCC_VEC3V(pPhysical->GetVelocity());
ScalarV scMagSq = MagSquared(vVelocity);
if( IsGreaterThanAll(scMagSq, scMinMoveSpeedSq) )
{
StoreScalar32FromScalarV(m_fLastDirectionSeenMovingIn, Arctan2(-vVelocity.GetX(), vVelocity.GetY()));
return;
}
}
// If not physical, or if the velocity isn't high enough, work ou the direction
// they were likely moving in from the heading
m_fLastDirectionSeenMovingIn = m_pEntity->GetTransform().GetHeading();
}
void CSingleTargetInfo::CalculateInVehicle( void )
{
//Check if the entity is a ped.
if(m_pEntity->GetIsTypePed())
{
//Grab the ped.
const CPed* pPed = static_cast<const CPed *>(m_pEntity.Get());
//Set the flag.
m_bLastSeenInVehicle = pPed->GetIsInVehicle();
}
else
{
//Clear the flag.
m_bLastSeenInVehicle = false;
}
}
bool CSingleTargetInfo::AreWhereaboutsKnown() const
{
//Check if we have been seen recently.
if(m_iLastSeenExpirationTimeMs > fwTimer::GetTimeInMilliseconds())
{
#if !__NO_OUTPUT
wantedDebugf3("CSingleTargetInfo::AreWhereaboutsKnown m_iLastSeenExpirationTimeMs[%u] > GetTimeInMilliseconds[%u] --> return true",m_iLastSeenExpirationTimeMs,fwTimer::GetTimeInMilliseconds());
#endif
return true;
}
//Check if the entity is valid.
const CEntity* pEntity = m_pEntity;
if(pEntity)
{
//Check if the entity hasn't deviated much from the position where we last saw them.
Vec3V vEntityPosition = pEntity->GetTransform().GetPosition();
Vec3V vLastPositionSeen = VECTOR3_TO_VEC3V(GetLastPositionSeen());
#if !__NO_OUTPUT
ScalarV scDist = Dist(vEntityPosition, vLastPositionSeen);
float fDist = scDist.Getf();
#endif
ScalarV scDistSq = DistSquared(vEntityPosition, vLastPositionSeen);
ScalarV scMaxDistSq = ScalarVFromF32(square(10.0f));
if(IsLessThanAll(scDistSq, scMaxDistSq))
{
#if !__NO_OUTPUT
wantedDebugf3("CSingleTargetInfo::AreWhereaboutsKnown fDist[%f] --> return true",fDist);
#endif
return true;
}
}
return false;
}
unsigned int CSingleTargetInfo::GetTimeUntilLastSeenTimerExpiresMs() const
{
const u32 currentTimeMs = fwTimer::GetTimeInMilliseconds();
if(m_iLastSeenExpirationTimeMs > currentTimeMs)
{
return m_iLastSeenExpirationTimeMs - currentTimeMs;
}
return 0;
}
//-------------------------------------------------------------------------
// Reset the target information
//-------------------------------------------------------------------------
void CSingleTargetInfo::Reset( void )
{
SetLastPositionSeen(Vector3(Vector3::ZeroType));
SetDistance(0.0f);
m_pEntity = NULL;
m_pBlockedByFriendlyPed = NULL;
m_fTotalScore = 0.0f;
m_iTimeExpiringMs = 0;
m_iLastSeenExpirationTimeMs = 0;
m_losStatus = Los_unchecked;
m_bLosBlockedNear = false;
m_bNeverScored = true;
m_bLastSeenInVehicle = false;
m_bLosWasDoneAsync = false;
m_bIsBeingTargeted = false;
m_bHasEverHadClearLos = false;
m_iCalcHeadingAndVehicleCountdown = 0;
m_fLastDirectionSeenMovingIn = 0.0f;
m_fVisibleTimer = 0.0f;
m_fBlockedTimer = 0.0f;
m_iTimeLastHostileActionMs = 0;
}
//-------------------------------------------------------------------------
// Sets the target
//-------------------------------------------------------------------------
void CSingleTargetInfo::SetTarget( const CEntity* pEntity )
{
wantedDebugf3("CSingleTargetInfo::SetTarget -- SetLastPositionSeen / set m_iLastSeenExpirationTimeMs");
Assertf( !m_pEntity, "Overwriting an already valid target" );
m_pEntity = pEntity;
m_iTimeExpiringMs = fwTimer::GetTimeInMilliseconds() + TARGET_FADE_TIME_MS;
m_iLastSeenExpirationTimeMs = fwTimer::GetTimeInMilliseconds() + CPedTargetting::GetTunables().m_iTargetNotSeenIgnoreTimeMs;
SetLastPositionSeen(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
CalculateHeading();
CalculateInVehicle();
}
//-------------------------------------------------------------------------
// Standalone targetting class, base class to be inherited from for
// specific uses
//-------------------------------------------------------------------------
#if !__FINAL
bool CPedTargetting::DebugTargetting = false;
bool CPedTargetting::DebugTargettingLos = false;
bool CPedTargetting::DebugAcquaintanceScanner = false;
#endif
IMPLEMENT_COMBAT_TASK_TUNABLES(CPedTargetting, 0xea30d255);
CPedTargetting::Tunables CPedTargetting::ms_Tunables;
//-------------------------------------------------------------------------
// Gets the target at a given index
//-------------------------------------------------------------------------
const CEntity* CPedTargetting::GetTargetAtIndex( int iTargetIndex )
{
const CSingleTargetInfo* pTargetInfo = GetTargetInfoAtIndex(iTargetIndex);
if (!pTargetInfo)
{
return NULL;
}
#if LAZY_RAGDOLL_BOUNDS_UPDATE
// If targeting a human, force it's bounds to update
if (pTargetInfo->m_pEntity && pTargetInfo->m_pEntity->GetIsTypePed())
{
CPed *pTargetPed = (CPed*)pTargetInfo->m_pEntity.Get();
if (pTargetPed && pTargetPed->GetRagdollInst())
{
pTargetPed->RequestRagdollBoundsUpdate();
}
}
#endif
return pTargetInfo->m_pEntity;
}
//-------------------------------------------------------------------------
// Gets the target info at a given index
//-------------------------------------------------------------------------
const CSingleTargetInfo* CPedTargetting::GetTargetInfoAtIndex( int iTargetIndex ) const
{
// Has a target been set, return NULL if not
if(iTargetIndex < 0)
{
return NULL;
}
// If this function gets called with an index outside of the current number of
// active targets, there is probably a bug somewhere - that should be avoidable.
Assert(iTargetIndex < m_iNumActiveTargets);
Assert(iTargetIndex < MAX_NUM_TARGETS);
// Is the selected target still valid
if( !m_aTargets[iTargetIndex].IsValid() )
{
return NULL;
}
return &m_aTargets[iTargetIndex];
}
//-------------------------------------------------------------------------
// returns a pointer to the currently targeted entity
//-------------------------------------------------------------------------
const CEntity* CPedTargetting::GetCurrentTarget( void )
{
return GetTargetAtIndex(m_iCurrentTargetIndex);
}
//-------------------------------------------------------------------------
// returns a pointer to the second best target entity
//-------------------------------------------------------------------------
const CEntity* CPedTargetting::GetSecondaryTarget( void )
{
return GetTargetAtIndex(m_iSecondBestTargetIndex);
}
//-------------------------------------------------------------------------
// returns the LOS status of the given entity if it is in the list
//-------------------------------------------------------------------------
LosStatus CPedTargetting::GetLosStatus( const CEntity* pEntity, bool bForceImmediateUpdate )
{
return GetLosStatusAtIndex(FindTargetIndex( pEntity ), bForceImmediateUpdate);
}
//-------------------------------------------------------------------------
// returns the LOS status of the given entity if it is in the list
//-------------------------------------------------------------------------
LosStatus CPedTargetting::GetLosStatusAtIndex( int iIndex, bool bForceImmediateUpdate )
{
if( iIndex != -1 )
{
CSingleTargetInfo* pTargetInfo = &m_aTargets[iIndex];
Assert( pTargetInfo->IsValid() );
// If currently undefined, update the Los status
if( bForceImmediateUpdate && (pTargetInfo->GetLosStatus() == Los_unchecked) )
{
UpdateSingleLos( iIndex, true );
Assert(pTargetInfo->GetLosStatus() != Los_unchecked );
}
return pTargetInfo->GetLosStatus();
}
return Los_unchecked;
}
//-------------------------------------------------------------------------
// Gets the position of the entity.
// Returns true if successful
//-------------------------------------------------------------------------
bool CPedTargetting::GetTargetLastSeenPos( const CEntity* pEntity, Vector3& vOutPos )
{
CSingleTargetInfo* pTargetInfo = FindTarget( pEntity );
if( pTargetInfo )
{
vOutPos = pTargetInfo->GetLastPositionSeen();
return true;
}
return false;
}
//-------------------------------------------------------------------------
// Gets the last known heading of the target
// Returns true if successful
//-------------------------------------------------------------------------
bool CPedTargetting::GetTargetDirectionLastSeenMovingIn( const CEntity* pEntity, float& fOutDirection )
{
CSingleTargetInfo* pTargetInfo = FindTarget( pEntity );
if( pTargetInfo )
{
fOutDirection = pTargetInfo->m_fLastDirectionSeenMovingIn;
return true;
}
return false;
}
bool CPedTargetting::GetTargetLastSeenInVehicle( const CEntity* pEntity, bool& bOutInVehicle )
{
CSingleTargetInfo* pTargetInfo = FindTarget( pEntity );
if( pTargetInfo )
{
bOutInVehicle = pTargetInfo->GetLastSeenInVehicle();
return true;
}
return false;
}
//-------------------------------------------------------------------------
// returns whether the target has been out of sight for enough time
// so that the ped no longer knows it's position
//-------------------------------------------------------------------------
bool CPedTargetting::AreTargetsWhereaboutsKnown( Vector3* pvBestPos, const CEntity* pTarget, float* pfLastSeenTimer, bool* pbHasTargetInfo )
{
CSingleTargetInfo* pTargetInfo = FindTarget( pTarget ? pTarget : GetCurrentTarget() );
if( pTargetInfo )
{
if( pbHasTargetInfo )
{
*pbHasTargetInfo = true;
}
if( pvBestPos )
{
*pvBestPos = pTargetInfo->GetLastPositionSeen();
}
if ( pfLastSeenTimer )
{
*pfLastSeenTimer = pTargetInfo->GetLastSeenTimer();
}
return ( pTargetInfo->AreWhereaboutsKnown() );
}
else if( pbHasTargetInfo )
{
*pbHasTargetInfo = false;
}
return true;
}
dev_float TIME_TO_ZERO_ON_TARGET = (3.0f);
//-------------------------------------------------------------------------
// Gets the distance to the given entity if it is in the list,
// returns true if successful
//-------------------------------------------------------------------------
bool CPedTargetting::GetDistance( const CEntity* pEntity, float &fDistance )
{
CSingleTargetInfo* pTargetInfo = FindTarget( pEntity );
if( pTargetInfo )
{
fDistance = pTargetInfo->GetDistance();
// Distance is 0 when uninitialised
return fDistance != 0.0f;
}
return false;
}
//-------------------------------------------------------------------------
// Sets the targeting in use and wakes it up if needed
//-------------------------------------------------------------------------
void CPedTargetting::SetInUseAndWakeUp( void )
{
m_fActivityTimer = ms_Tunables.m_fTargetingInactiveDisableTime;
// If the targeting is currently inactive, wake it up
if( !m_bActive )
{
m_bActive = true;
// Carry out a single update
UpdateTargetting( false );
// By default the first update sets the los status of all targets to clear
SetAllLosStatus( Los_unchecked );
}
}
//-------------------------------------------------------------------------
// Sets the LOS status of all currently valid targets
//-------------------------------------------------------------------------
void CPedTargetting::SetAllLosStatus( LosStatus losStatus )
{
const int numTargets = m_iNumActiveTargets;
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].IsValid() )
{
m_aTargets[i].m_losStatus = losStatus;
m_aTargets[i].m_bLosBlockedNear = false;
if( losStatus == Los_clear )
{
wantedDebugf3("CPedTargetting::SetAllLosStatus clear -- SetLastPositionSeen / set m_iLastSeenExpirationTimeMs");
m_aTargets[i].m_iLastSeenExpirationTimeMs = fwTimer::GetTimeInMilliseconds() + ms_Tunables.m_iTargetNotSeenIgnoreTimeMs;
m_aTargets[i].SetLastPositionSeen(VEC3V_TO_VECTOR3(m_aTargets[i].m_pEntity->GetTransform().GetPosition()));
m_aTargets[i].CalculateHeading();
m_aTargets[i].CalculateInVehicle();
m_aTargets[i].m_fBlockedTimer = 0.0f;
m_aTargets[i].m_bHasEverHadClearLos = true;
}
else
{
m_aTargets[i].m_fVisibleTimer = 0.0f;
}
}
}
}
//-------------------------------------------------------------------------
// calculates the two best targets and stores their indices
//-------------------------------------------------------------------------
void CPedTargetting::CalculateBestTargets( void )
{
s32 iBestTarget = -1, iSecondaryTarget = -1;
float fBestScore = 0.0f, fSecondaryScore = 0.0f;
const int numTgs = m_iNumActiveTargets;
for(int i = 0; i < numTgs; i++)
{
if( m_aTargets[i].IsValid() )
{
if( ( iBestTarget == -1 ) || ( m_aTargets[i].m_fTotalScore > fBestScore ) )
{
// Set our secondary target variables to what the current best is
iSecondaryTarget = iBestTarget;
fSecondaryScore = fBestScore;
// Set our best target info
iBestTarget = i;
fBestScore = m_aTargets[i].m_fTotalScore;
}
else if( iSecondaryTarget == -1 || m_aTargets[i].m_fTotalScore > fSecondaryScore )
{
// This index didn't best the best but still beat our secondary score
iSecondaryTarget = i;
fSecondaryScore = m_aTargets[i].m_fTotalScore;
}
}
}
#if !__FINAL
/*if( m_iCurrentTargetIndex != iBestTarget )
{
printf( "Target switching from %i to %i\n", m_iCurrentTargetIndex, iBestTarget );
}*/
#endif
// If currently targetting a target without a LOS and whose location is unknown,
// prevent them from switching to another target without a LOS
if( m_iSecondBestTargetIndex != iSecondaryTarget && m_iSecondBestTargetIndex != -1 && m_aTargets[m_iSecondBestTargetIndex].IsValid() &&
m_aTargets[m_iSecondBestTargetIndex].HasLastSeenTimerExpired() && iSecondaryTarget != -1 && m_aTargets[iSecondaryTarget].m_losStatus == Los_blocked )
{
iSecondaryTarget = m_iSecondBestTargetIndex;
}
// If valid, Make sure the secondary target does not decay
if( iSecondaryTarget >= 0 )
{
Assert(iSecondaryTarget < m_iNumActiveTargets);
Assert(iSecondaryTarget < MAX_NUM_TARGETS);
m_aTargets[iSecondaryTarget].m_iTimeExpiringMs = fwTimer::GetTimeInMilliseconds() + TARGET_FADE_TIME_MS;
}
m_iSecondBestTargetIndex = (s16)iSecondaryTarget;
// If currently targetting a target without a LOS and whose location is unknown,
// prevent them from switching to another target without a LOS
if( m_iCurrentTargetIndex != iBestTarget && m_iCurrentTargetIndex != -1 && m_aTargets[m_iCurrentTargetIndex].IsValid() &&
m_aTargets[m_iCurrentTargetIndex].HasLastSeenTimerExpired() && iBestTarget != -1 && m_aTargets[iBestTarget].m_losStatus == Los_blocked )
{
iBestTarget = m_iCurrentTargetIndex;
}
// If valid, Make sure the currently selected target does not decay
if( iBestTarget >= 0 )
{
Assert(iBestTarget < m_iNumActiveTargets);
Assert(iBestTarget < MAX_NUM_TARGETS);
m_aTargets[iBestTarget].m_iTimeExpiringMs = fwTimer::GetTimeInMilliseconds() + TARGET_FADE_TIME_MS;
}
m_iCurrentTargetIndex = (s16)iBestTarget;
}
#include "system/xtl.h"
EXT_PF_TIMER(TargettingLos);
//-------------------------------------------------------------------------
// Main update function to add new targets and update scores
// Returns whether the targeting system is currently active
//-------------------------------------------------------------------------
bool CPedTargetting::UpdateTargettingInternal( const CEntityScanner& entityScanner, const Vector3& vSourcePos, const float fSourceHeading, bool bUpdateActivityTimer, bool bDoFullUpdate )
{
#if __XENON && !__FINAL
PIXBeginC(0, Color_orange.GetColor(), "CPedTargetting::UpdateTargettingInternal");
#endif
PF_FUNC(TargettingLos);
// Should the targeting be active?
// Targeting is deactivated if not used for an amount of time
if( m_fActivityTimer <= 0.0f )
{
// If the system is currently active, reset
if( m_bActive )
{
ResetTargetting();
}
return false;
}
if(bUpdateActivityTimer)
{
m_fActivityTimer -= fwTimer::GetTimeStep();
}
// First update any active timers
UpdateTargets();
// Take a note of each peds previous LOS state
LosStatus aPreviousLosStatus[MAX_NUM_TARGETS];
const int numActiveTargets = m_iNumActiveTargets;
Assert(numActiveTargets <= MAX_NUM_TARGETS);
for( int i = 0; i < numActiveTargets; i++ )
{
aPreviousLosStatus[i] = m_aTargets[i].m_losStatus;
// Reset the being targeted value back to false
if(bDoFullUpdate)
{
m_aTargets[i].m_bIsBeingTargeted = false;
}
}
// If probes have been set up asynchronously, then the next frame when this is called they will be collated
ProcessAsyncResults();
Assert(numActiveTargets == m_iNumActiveTargets); // Make sure nothing changed in the array while we keep aPreviousLosStatus[] in parallel.
const bool bProcessThisFrame = !IsRegistered() || ShouldBeProcessedThisFrame();
// Update the LOS status to the targets
if( bProcessThisFrame )
{
StartProcess();
UpdateLosStatus();
StopProcess();
}
Assert(numActiveTargets == m_iNumActiveTargets); // Make sure nothing changed in the array while we keep aPreviousLosStatus[] in parallel.
UpdatePostLos( aPreviousLosStatus );
// Score each of the chosen targets
ScoreTargets( entityScanner, vSourcePos, fSourceHeading, bProcessThisFrame );
// Calculates and stores the best current and secondary targets
CalculateBestTargets();
#if __XENON && !__FINAL
PIXEnd();
#endif
return true;
}
//-------------------------------------------------------------------------
// Updates the validity timers for each target
//-------------------------------------------------------------------------
void CPedTargetting::UpdateTargets( void )
{
const int numTargets = m_iNumActiveTargets;
if(numTargets == 0)
{
return;
}
int index = m_iNextTargetForUpdateTargetsToCheck;
if(index >= numTargets)
{
index = 0;
}
Assert(index >= 0 && index < m_iNumActiveTargets);
if( m_aTargets[index].IsValid() )
{
// Make sure this target is still considered valid
if( TargetShouldBeRemoved( &m_aTargets[index] ) )
{
m_aTargets[index].m_iTimeExpiringMs = 0;
}
// If target locking is enabled, make sure all other entities are removed and
// the locked target always remains
if( m_pLockedTarget )
{
// all other entities are removed
if( m_aTargets[index].m_pEntity != m_pLockedTarget )
{
m_aTargets[index].m_iTimeExpiringMs = 0;
}
// the locked target always remains
if( m_aTargets[index].m_pEntity == m_pLockedTarget )
{
m_aTargets[index].m_iTimeExpiringMs = fwTimer::GetTimeInMilliseconds() + TARGET_FADE_TIME_MS;
}
}
if(m_aTargets[index].m_iTimeExpiringMs <= fwTimer::GetTimeInMilliseconds())
{
ResetTarget(index);
DeleteTargetFromArrays(index);
index--;
}
}
else
{
// This ResetTarget() may not be strictly necessary, but may help
// to clear out some registered pointers and otherwise ensure that
// all elements past the first m_iNumActiveTargets are kept in a consistently
// cleared out state.
ResetTarget(index);
DeleteTargetFromArrays(index);
index--;
}
CompileTimeAssert(MAX_NUM_TARGETS < 0xff);
m_iNextTargetForUpdateTargetsToCheck = (u8)(index + 1);
}
//-------------------------------------------------------------------------
// Find the target in the array, if present, return a pointer to it
//-------------------------------------------------------------------------
const CSingleTargetInfo* CPedTargetting::FindTarget( const CEntity* pEntity ) const
{
if(pEntity)
{
const int numActiveTargets = m_iNumActiveTargets;
for(int i = 0; i < numActiveTargets; i++)
{
if( m_aTargets[i].m_pEntity == pEntity )
{
return &m_aTargets[i];
}
}
}
return NULL;
}
//-------------------------------------------------------------------------
// Find the target in the array, if present, return a pointer to it
//-------------------------------------------------------------------------
CSingleTargetInfo* CPedTargetting::FindTarget( const CEntity* pEntity )
{
if(pEntity)
{
const int numActiveTargets = m_iNumActiveTargets;
for(int i = 0; i < numActiveTargets; i++)
{
if( m_aTargets[i].m_pEntity == pEntity )
{
return &m_aTargets[i];
}
}
}
return NULL;
}
//-------------------------------------------------------------------------
// Find the target in the array, if present, return a pointer to it
//-------------------------------------------------------------------------
s32 CPedTargetting::FindTargetIndex( const CEntity* pEntity )
{
if(pEntity)
{
const int numActiveTargets = m_iNumActiveTargets;
for(int i = 0; i < numActiveTargets; i++)
{
if( m_aTargets[i].m_pEntity == pEntity )
{
return i;
}
}
}
return -1;
}
//-------------------------------------------------------------------------
// Resets the target in the given slot
//-------------------------------------------------------------------------
void CPedTargetting::ResetTarget( const s32 iTargetIndex )
{
OnTargetReset( iTargetIndex );
m_aTargets[iTargetIndex].Reset();
}
//-------------------------------------------------------------------------
// Removes a target (if we find it)
//-------------------------------------------------------------------------
void CPedTargetting::RemoveThreat( const CEntity* pTarget )
{
s32 iTargetIndex = FindTargetIndex(pTarget);
if(iTargetIndex != -1)
{
ResetTarget(iTargetIndex);
}
}
dev_float DISTANCE_TO_TARGET_TO_IGNORE_FRIENDLIES = 3.0f;
dev_float DISTANCE_TO_TARGET_TO_REDUCE_FRIENDLYS_INFLUENCE = 15.0f;
//-------------------------------------------------------------------------
// Returns the reduction calculated based on the no. of friendlies also
// targeting this entity
//-------------------------------------------------------------------------
float CPedTargetting::CalculateFriendlyTargettingReduction( const CEntityScanner& entityScanner, const Vector3& vPos, const CEntity* pEntity )
{
float fTargettingReduction = 1.0f;
float fToTarget = vPos.Dist(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
// Don't adjust for friendlies when we're really close to the target
if( fToTarget < DISTANCE_TO_TARGET_TO_IGNORE_FRIENDLIES )
{
return 1.0f;
}
// Get the list of peds from the ped scanner passed
const CEntityScannerIterator entityList = entityScanner.GetIterator();
for( const CEntity* pEnt = entityList.GetFirst(); pEnt; pEnt = entityList.GetNext() )
{
// Reduce the targeting score if already targeted by this entity
if( IsEntityTargettingEntity( pEnt, pEntity ) )
{
fTargettingReduction *= GetFriendlyTargettingScoreReduction( pEnt, pEntity );
}
}
if( fToTarget < DISTANCE_TO_TARGET_TO_REDUCE_FRIENDLYS_INFLUENCE )
{
float fOneMinusReduction = 1.0f - fTargettingReduction;
fTargettingReduction += fOneMinusReduction * (1.0f - (fToTarget/DISTANCE_TO_TARGET_TO_REDUCE_FRIENDLYS_INFLUENCE));
}
// reduce the amount of targeting reduction based on the distance to the target.
return fTargettingReduction;
}
//-------------------------------------------------------------------------
// Calculates a component of the targetting score based on the location
// and visibility of the entity
//-------------------------------------------------------------------------
float CPedTargetting::CalculateSpatialTargettingScore( float& fOutDistance, const Vector3& vPos, const float, CSingleTargetInfo* pTargetInfo )
{
float fScore = 0.0f;
const CEntity* pTarget = pTargetInfo->m_pEntity;
const CPed* pTargetPed = pTarget && pTarget->GetIsTypePed() ? static_cast<const CPed*>(pTarget) : NULL;
// The score is based on the distance to the target
fOutDistance = rage::Clamp( vPos.Dist( VEC3V_TO_VECTOR3(pTarget->GetTransform().GetPosition()) ), 0.0f, m_fMaxTargetRange );
fScore = 1.0f - ( fOutDistance / m_fMaxTargetRange );
// and and angle to the target unless the limit is set to 1.0f
if( m_fDotAngularLimit != 1.0f )
{
// calculate the angle to the target and use it in the equation
}
else
{
fScore += 1.0f;
}
// Reduce the score for target who are in aircrafts if we prefer targets who are on foot or in grounded vehicles
if( m_pPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_PreferNonAircraftTargets) &&
pTargetPed && pTargetPed->GetVehiclePedInside() && pTargetPed->GetVehiclePedInside()->GetIsAircraft() )
{
fScore *= ms_Tunables.m_fTargetInAircraftWeighting;
}
// Finally this target is reduced in value if there is no Los
if( pTargetInfo->m_losStatus != Los_clear )
{
if( !m_pPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_CanIgnoreBlockedLosWeighting) ||
pTargetInfo->GetTimeLosBlocked() > ms_Tunables.m_fTimeToIgnoreBlockedLosWeighting )
{
fScore *= ms_Tunables.m_fBlockedLosWeighting;
}
}
else if( !NetworkInterface::IsGameInProgress() && pTargetPed )
{
// Extra weight for close players in SP games
if( pTargetPed->IsLocalPlayer() || pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_TreatAsPlayerDuringTargeting) )
{
float fExtaFuzzyDistance = pTargetInfo->GetIsBeingTargeted() ? ms_Tunables.m_fPlayerBeingTargetedExtraDistance : 0.0f;
ScalarV scDistanceToTarget = ScalarVFromF32(fOutDistance);
if( IsLessThanAll(scDistanceToTarget, ScalarVFromF32(ms_Tunables.m_fPlayerThreatDistance + fExtaFuzzyDistance)) ||
((pTargetInfo->m_iTimeLastHostileActionMs && (fwTimer::GetTimeInMilliseconds() < pTargetInfo->m_iTimeLastHostileActionMs + ms_Tunables.m_iPlayerDirectThreatTimeMs))
&& (IsLessThanAll(scDistanceToTarget, ScalarVFromF32(ms_Tunables.m_fPlayerDirectThreatDistance + fExtaFuzzyDistance)))))
{
fScore *= ms_Tunables.m_fPlayerHighThreatWeighting;
}
}
}
return fScore;
}
//-------------------------------------------------------------------------
// Updates the validity timers for each target
//-------------------------------------------------------------------------
void CPedTargetting::ScoreTargets( const CEntityScanner& entityScanner, const Vector3& vPos, const float fHeading, const bool bProcessAll )
{
const int numTargets = m_iNumActiveTargets;
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].IsValid() && ( m_aTargets[i].GetNeverScored() || bProcessAll ) )
{
m_aTargets[i].SetNeverScored(false);
// If the target has not been seen recently, it scores hardly anything
if( m_aTargets[i].HasLastSeenTimerExpired() )
{
// Make the score more than 0, this is so the t
m_aTargets[i].m_fTotalScore = 0.1f;
// If this is the existing target, weight it more heavily so the ped is more likely to stick with it
if( i == m_iCurrentTargetIndex )
{
m_aTargets[i].m_fTotalScore *= ms_Tunables.m_fExistingTargetScoreWeight;
}
#if DEBUG_DRAW
if( CPedTargetting::DebugTargettingLos )
grcDebugDraw::Line( vPos + Vector3(0.0f, 0.0f, 0.3f), m_aTargets[i].GetLastPositionSeen(), Color32( 0xff, 0xff, 0xff, 0xff ) );
#endif
continue;
}
// Calculate the specific score associated with this target
m_aTargets[i].m_fTotalScore = GetTargettingScore( m_aTargets[i].m_pEntity );
if(m_aTargets[i].m_fTotalScore > 0.0f)
{
// Calculate the score adjustment based on how many friendly entities are targeting it
m_aTargets[i].m_fTotalScore *= CalculateFriendlyTargettingReduction( entityScanner, vPos, m_aTargets[i].m_pEntity );
// If this is the existing target, weight it more heavily so the ped is more likely to stick with it
if( i == m_iCurrentTargetIndex )
{
m_aTargets[i].m_fTotalScore *= GetExistingTargetReduction();
}
// Calculate the spatial score associated with this target
// e.g. how close it is, is it in the correct direction, can you see it?
m_aTargets[i].m_fTotalScore += CalculateSpatialTargettingScore( m_aTargets[i].GetDistance(), vPos, fHeading, &m_aTargets[i] );
CPed * pTargetPed = (CPed*)m_aTargets[i].m_pEntity.Get();
if (pTargetPed && pTargetPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsDecoyPed))
{
m_aTargets[i].m_fTotalScore *= DECOY_PED_SCORING_MULTIPLIER;
}
}
}
}
}
//-------------------------------------------------------------------------
// Adds a single target to the target list
//-------------------------------------------------------------------------
bool CPedTargetting::AddTarget( const CEntity* pEntity, bool bForceInsert )
{
// Loop through the existing targets trying to find an invalid one
const int numActiveTargets = m_iNumActiveTargets;
for( int i = 0; i < numActiveTargets; i++ )
{
if( !m_aTargets[i].IsValid() )
{
ResetTarget(i);
m_aTargets[i].SetTarget( pEntity );
return true;
}
}
// If we didn't find an unused existing target, check to see if we have space
// to add one to the array.
if(numActiveTargets < MAX_NUM_TARGETS)
{
const int newTargetIndex = m_iNumActiveTargets++;
ResetTarget(newTargetIndex);
m_aTargets[newTargetIndex].SetTarget( pEntity );
return true;
}
if(bForceInsert)
{
s32 iWorstScoredIndex = -1;
float fWorstScore = FLT_MAX;
// Check for the worst scores valid target
for( int i = 0; i < numActiveTargets; i++ )
{
if(m_aTargets[i].m_fTotalScore < fWorstScore)
{
iWorstScoredIndex = i;
fWorstScore = m_aTargets[i].m_fTotalScore;
}
}
// If we found a valid target we should replace then go ahead and do so
if(iWorstScoredIndex >= 0)
{
ResetTarget(iWorstScoredIndex);
m_aTargets[iWorstScoredIndex].SetTarget( pEntity );
return true;
}
}
return false;
}
/*
PURPOSE
Delete an element from the target array
NOTES
The target element is assumed to have already been cleared out using
ResetTarget().
*/
void CPedTargetting::DeleteTargetFromArrays(int targetIndex)
{
#if __ASSERT
const CEntity* tgt1Before = GetCurrentTarget();
const CEntity* tgt2Before = GetSecondaryTarget();
#endif
int numTargets = m_iNumActiveTargets;
Assert(targetIndex >= 0 && targetIndex < numTargets);
Assert(!m_aTargets[targetIndex].m_pEntity); // We expect ResetTarget() to have been called already.
const int targetIndexToCopy = numTargets - 1;
OnTargetDeleteFromArray(targetIndex);
// If the element we are about to delete was considered the
// current or secondary target, set the relevant target index back to -1.
if(m_iCurrentTargetIndex == targetIndex)
{
m_iCurrentTargetIndex = -1;
// Note: we could probably copy m_iSecondBestTargetIndex into m_iCurrentTargetIndex here.
// However, the way it worked before, when we just reset the targets that became invalidated,
// we didn't do anything like that, so presumably the next target selection call will be able
// to sort it out OK.
}
if(m_iSecondBestTargetIndex == targetIndex)
{
m_iSecondBestTargetIndex = -1;
}
if(targetIndexToCopy != targetIndex)
{
// If the current or secondary target was the one we are now moving to the element
// that got removed, keep the target index up to date.
if(m_iCurrentTargetIndex == targetIndexToCopy)
{
m_iCurrentTargetIndex = (s16)targetIndex;
}
if(m_iSecondBestTargetIndex == targetIndexToCopy)
{
m_iSecondBestTargetIndex = (s16)targetIndex;
}
m_aTargets[targetIndex] = m_aTargets[targetIndexToCopy];
ResetTarget(targetIndexToCopy);
}
numTargets--;
m_iNumActiveTargets = (s16)numTargets;
// Maintain m_iNextLosIndex: it probably doesn't matter too much how we
// do it, since we have reordered the array we can't exactly preserve
// the LOS ordering. m_iNextLosIndex is in fact the last index that we checked,
// and the thing that matters is that it doesn't go any higher than the number
// of active targets.
if(m_iNextLosIndex >= numTargets)
{
m_iNextLosIndex = 0;
}
// Note: for m_iNextTargetForUpdateTargetsToCheck, there isn't really anything to maintain.
// It's already set up to be allowed to go beyond the number of targets and will just reset
// itself on the next call to UpdateTargets() if that's the case. Plus, this function is currently
// only called from UpdateTargets() who will overwrite it anyway.
Assert(tgt1Before == GetCurrentTarget());
Assert(tgt2Before == GetSecondaryTarget());
}
//-------------------------------------------------------------------------
// Adds targets from the ped scanner depending on the hated
// Returns false if it failed to add all valid targets due to space restrictions
//-------------------------------------------------------------------------
/*bool CPedTargetting::AddTargets( CEntityScanner& entityScanner )
{
// If the target is locked, there's no need to add new targets
if( m_pLockedTarget )
{
return true;
}
// Get the list of peds from the ped scanner passed
CEntityScannerIterator entityList = entityScanner.GetIterator();
for( CEntity* pEnt = entityList.GetFirst(); pEnt; pEnt = entityList.GetNext() )
{
// Is the target valid?
if( IsValidTarget( pEnt ) )
{
CSingleTargetInfo* pTargetInfo = FindTarget( pEnt );
// Is the target already present?
if( pTargetInfo )
{
// Revalidate the existing target
pTargetInfo->Revalidate();
}
// The target is not already present
// add it to the list
else if( !AddTarget( pEnt ) )
{
// Adding failed, try and clear space for the new target
if( RemoveLeastValidTarget() )
{
AddTarget( pEnt );
}
else
{
// No point trying to add any more targets,
// the list is already full
return false;
}
}
}
}
return true;
}*/
//-------------------------------------------------------------------------
// Resets all targetting information
//-------------------------------------------------------------------------
void CPedTargetting::ResetTargetting( void )
{
const int numActiveTargets = m_iNumActiveTargets;
for(int i = 0; i < numActiveTargets; i++)
{
ResetTarget(i);
}
m_iNumActiveTargets = 0;
m_fActivityTimer = 0.0f;
m_iCurrentTargetIndex = -1;
m_iSecondBestTargetIndex= -1;
m_bActive = false;
m_iNextLosIndex = 0;
m_iNextTargetForUpdateTargetsToCheck = 0;
}
//-------------------------------------------------------------------------
// Locks the targetting to only consider this entity
//-------------------------------------------------------------------------
void CPedTargetting::LockTarget( CEntity* pLockedEntity )
{
// Make sure any previous entity is unlocked
UnlockTarget();
m_pLockedTarget = pLockedEntity;
if( m_pLockedTarget )
{
AddTarget( m_pLockedTarget );
}
}
//-------------------------------------------------------------------------
// Unlocks the targetting making it free to target any entity
//-------------------------------------------------------------------------
void CPedTargetting::UnlockTarget( void )
{
if( m_pLockedTarget )
{
m_pLockedTarget = NULL;
}
}
//-------------------------------------------------------------------------
// Sets the targetting as currently active, being used by some external system
//-------------------------------------------------------------------------
void CPedTargetting::SetInUse()
{
m_fActivityTimer = ms_Tunables.m_fTargetingInactiveDisableTime;
}
//-------------------------------------------------------------------------
// Ped targetting class, to be used by peds targetting other peds.
//-------------------------------------------------------------------------
float CPedTargetting::BASIC_THREAT_WEIGHTING = 0.5f;
float CPedTargetting::ACTIVE_THREAT_WEIGHTING = 1.0f;
float CPedTargetting::FLEEING_THREAT_WEIGHTING = 0.5f;
float CPedTargetting::WEAPON_PISTOL_WEIGHTING = 1.5f;
float CPedTargetting::WEAPON_2HANDED_WEIGHTING = 2.0f;
float CPedTargetting::DIRECT_THREAT_WEIGHTING = 1.6f;
float CPedTargetting::IN_VEHICLE_WEIGHTING = 0.75;
float CPedTargetting::UNARMED_DIRECT_THREAT_WEIGHTING = 1.6f;
float CPedTargetting::PLAYER_THREAT_WEIGHTING = 2.5f;
float CPedTargetting::DEFEND_PLAYER_WEIGHTING = 1.5f;
float CPedTargetting::FRIENDLY_TARGETTING_REDUCTION = 0.75f;
float CPedTargetting::SCRIPT_THREAT_WEIGHTING = 5.0f;
float CPedTargetting::DECOY_PED_SCORING_MULTIPLIER = 100.f;
//-------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------
CPedTargetting::CPedTargetting( CPed* pPed ):
CExpensiveProcess(PPT_TargettingLos),
m_pScoringFn(DefaultTargetScoring),
m_fScoringOverrideTimer(0.0f),
m_pScoringEntity(NULL),
m_bActive(false),
m_fActivityTimer(0.0f),
m_iCurrentTargetIndex(-1),
m_iSecondBestTargetIndex(-1),
m_fMaxTargetRange(0.0f),
m_fDotAngularLimit(0.0f),
m_iNextLosIndex(0),
m_pLockedTarget(NULL),
m_bRunningInCheapMode(false),
m_bUseAlternatePosition(false),
m_iNextTargetForUpdateTargetsToCheck(0),
m_iNumActiveTargets(0)
{
RegisterSlot();
m_pPed = pPed;
Assert( m_pPed );
for( int i = 0; i < MAX_NUM_TARGETS; i++ )
{
m_apVehiclesTargetsLastSeenIn[i] = NULL;
}
}
//-------------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------------
CPedTargetting::~CPedTargetting()
{
if( !m_bRunningInCheapMode )
UnregisterSlot();
if( m_pPed )
m_pPed = NULL;
}
//-------------------------------------------------------------------------
// Carries out an entire targetting update pass
//-------------------------------------------------------------------------
bool CPedTargetting::UpdateTargetting( bool bDoFullUpdate )
{
Assert(m_pPed);
if( m_pPed->GetWeaponManager() && m_pPed->GetWeaponManager()->GetIsArmed() )
SetSpatialTargettingLimits( CPedPerception::ms_fSenseRangeOfMissionPeds, 1.0f );
else
SetSpatialTargettingLimits( 5.0f, 1.0f );
// Disable targetting update for dead peds by setting the timer to 0,
// this allows the targetting system to clear up correctly
if( m_pPed->IsDead() )
{
m_fActivityTimer = 0.0f;
}
// If the target scoring function is overridden, and the timer is set, count
// down until expired
if( m_pScoringFn != m_pPed->GetPedIntelligence()->GetDefaultTargetScoringFunction() )
{
// If the timer is set, count down
if( m_fScoringOverrideTimer > 0.0f )
{
m_fScoringOverrideTimer -= fwTimer::GetTimeStep();
// Timer expired, reset the target scoring function
if( m_fScoringOverrideTimer <= 0.0f )
{
ResetScoringFunction();
}
}
}
bool bShouldUpdateActivityTimer = !m_pPed->GetUsingRagdoll();
const Vector3& vTestPosition = m_bUseAlternatePosition ? m_vAlternatePosition : VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition());
bool bReturn = UpdateTargettingInternal(
*m_pPed->GetPedIntelligence()->GetPedScanner(),
vTestPosition,
m_pPed->GetTransform().GetHeading(),
bShouldUpdateActivityTimer,
bDoFullUpdate
);
// Want to make sure to reset this at the very end of the update
m_bUseAlternatePosition = false;
return bReturn;
}
dev_float INTERSECTION_BLOCKED_DISTANCE = 1.25f;
//*******************************************************************************
void
CPedTargetting::ProcessAsyncResults()
{
const int numTargets = m_iNumActiveTargets;
for(int t = 0; t < numTargets; t++)
{
if(m_aTargets[t].m_bLosWasDoneAsync)
{
if(!m_aTargets[t].m_pEntity)
continue; // cancel request, or let time-out?
Assert(m_aTargets[t].m_pEntity->GetIsTypePed());
if(!m_aTargets[t].m_pEntity->GetIsTypePed())
continue;
CPed * pOtherPed = (CPed*)m_aTargets[t].m_pEntity.Get();
Vector3 vLosStartPos = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition());
Vector3 vLosEndPos = VEC3V_TO_VECTOR3(pOtherPed->GetTransform().GetPosition());
ECanTargetResult eRet;
if (pOtherPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsDecoyPed))
{
eRet = ECanTarget;
}
else
{
eRet = CPedGeometryAnalyser::CanPedTargetPedAsync(*m_pPed, *pOtherPed, 0, true, CPedGeometryAnalyser::ms_fCanTargetCacheMaxPosChangeSqr, CAN_TARGET_CACHE_TIMEOUT, &vLosStartPos, &vLosEndPos);
}
if (eRet == ECanTarget)
{
// Set the status to clear, unless we were blocked by friendly before.
// If we are blocked by friendly, we will reevaluate that the next time
// UpdateSingleLos() gets called.
if(m_aTargets[t].m_losStatus != Los_blockedByFriendly)
{
m_aTargets[t].m_losStatus = Los_clear;
}
m_aTargets[t].m_bLosWasDoneAsync = false; // Set this, other we'll keep doing this forever
m_aTargets[t].m_bLosBlockedNear = false;
}
else if(eRet == ECanNotTarget)
{
m_aTargets[t].m_losStatus = Los_blocked;
m_aTargets[t].m_bLosWasDoneAsync = false; // Set this, other we'll keep doing this forever
m_aTargets[t].m_bLosBlockedNear = vLosStartPos.Dist2(vLosEndPos) < rage::square(INTERSECTION_BLOCKED_DISTANCE);
#if DEBUG_DRAW
TUNE_GROUP_BOOL(COVER_DEBUG, RENDER_BLOCKED_NEAR_CHECK, false);
if (RENDER_BLOCKED_NEAR_CHECK)
{
CTask::ms_debugDraw.AddLine(RCC_VEC3V(vLosStartPos), RCC_VEC3V(vLosEndPos), m_aTargets[t].m_bLosBlockedNear ? Color_red : Color_green, 1000);
}
#endif // DEBUG_DRAW
}
}
}
}
//-------------------------------------------------------------------------
// Allows a component using the targetting system to override the default target scoring function
// with the one passed. If a time is passed > 0, the default function will revert after that time
//-------------------------------------------------------------------------
void CPedTargetting::SetScoringFunction( TargetScoringFunction* pScoringFunction, CEntity* pScoringEntity, float fTime)
{
// Cannot overwrite an already overridden target scoring function
if( m_pScoringFn != m_pPed->GetPedIntelligence()->GetDefaultTargetScoringFunction() &&
pScoringEntity != (CEntity*) CGameWorld::FindLocalPlayer() )
{
return;
}
m_pScoringFn = pScoringFunction;
m_fScoringOverrideTimer = fTime;
m_pScoringEntity = pScoringEntity;
}
//-------------------------------------------------------------------------
// Resets the scoring function to default
//-------------------------------------------------------------------------
void CPedTargetting::ResetScoringFunction( void )
{
m_pScoringFn = m_pPed->GetPedIntelligence()->GetDefaultTargetScoringFunction();
m_fScoringOverrideTimer = 0.0f;
m_pScoringEntity = NULL;
}
#define LOS_DOT_LIMIT (0.3f)
#if __ASSERT
// static bool DEBUG_LOS_TESTS = false;
#endif
//-------------------------------------------------------------------------
// Updates the Los status of a single entity
//-------------------------------------------------------------------------
void CPedTargetting::UpdateSingleLos( int iIndex, bool bForceImmediateUpdate )
{
Assert(iIndex >= 0 && iIndex < m_iNumActiveTargets);
const Vector3 vPedPosition = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition());
Assert(m_aTargets[iIndex].IsValid());
CPed* pTargetPed = (CPed*) m_aTargets[iIndex].m_pEntity.Get();
const Vector3 vTargetPedPosition = VEC3V_TO_VECTOR3(pTargetPed->GetTransform().GetPosition());
bool bCheckLos = true;
bool bIgnoreThisPedsVehicle = false;
// Force an immediate update if the los status has never been checked
// Note: we only do this for the player now, to improve performance when new AI
// targets get added. We may want to skip it for the player too, but we would
// have to be careful to make sure it doesn't introduce noticeable delays.
if( !bForceImmediateUpdate && m_aTargets[iIndex].m_losStatus == Los_unchecked )
{
bForceImmediateUpdate = pTargetPed->IsLocalPlayer();
}
bool bPedCanSeeInAnyDir = m_pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && m_pPed->GetMyVehicle() && (m_pPed->GetMyVehicle()->GetVehicleType() == VEHICLE_TYPE_HELI || m_pPed->PopTypeIsMission() || pTargetPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ));
// Can hear the target if within a certain range and in a vehicle
if( pTargetPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && vTargetPedPosition.Dist2(vPedPosition) < rage::square(10.0f) )
{
bPedCanSeeInAnyDir = true;
}
// Ignore hits against this vehicle if we're in a turret seat (might be better to exclude the turret only somehow??)
if (m_pPed->GetMyVehicle())
{
const s32 iSeatindex = m_pPed->GetMyVehicle()->GetSeatManager()->GetPedsSeatIndex(m_pPed);
if (m_pPed->GetMyVehicle()->IsTurretSeat(iSeatindex))
{
bIgnoreThisPedsVehicle = true;
}
}
// Can see in any dir if the target has been seen recently
if( m_aTargets[iIndex].GetTimeUntilLastSeenTimerExpiresMs() > 2500 )
bPedCanSeeInAnyDir = true;
// If this target is currently blocked, only regain LOS if facing in the correct direction
if( m_aTargets[iIndex].m_losStatus == Los_blocked && !bPedCanSeeInAnyDir )
{
Vector3 vPedHeading = Vector3( ANGLE_TO_VECTOR_X( ( RtoD * m_pPed->GetTransform().GetHeading() ) ), ANGLE_TO_VECTOR_Y( ( RtoD * m_pPed->GetTransform().GetHeading() ) ), 0.0f );
Vector3 vPedToTarget = vTargetPedPosition - vPedPosition;
vPedHeading.Normalize();
vPedToTarget.Normalize();
if( vPedHeading.Dot( vPedToTarget ) < LOS_DOT_LIMIT )
{
bCheckLos = false;
}
}
// Cheap targetting mode.
m_bRunningInCheapMode = false;
if( m_bRunningInCheapMode )
{
RegisterSlot();
m_bRunningInCheapMode = false;
}
#if !__FINAL
const bool bInvisiblePlayer = pTargetPed->IsPlayer() && CPlayerInfo::ms_bDebugPlayerInvisible;
#else
const bool bInvisiblePlayer = false;
#endif // !__FINAL
//Generate the target flags.
s32 iTargetFlags = 0;
//Check if we know where the target is.
if(m_aTargets[iIndex].AreWhereaboutsKnown())
{
iTargetFlags |= TargetOption_UseCoverVantagePoint;
}
else
{
iTargetFlags |= TargetOption_IgnoreTargetsCover;
iTargetFlags |= TargetOption_UseFOVPerception;
}
if(m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IgnoreTargetsCoverForLOS))
{
iTargetFlags |= TargetOption_IgnoreTargetsCover;
}
if (bIgnoreThisPedsVehicle)
{
iTargetFlags |= TargetOption_IgnoreVehicleThisPedIsIn;
}
if( bCheckLos && !bInvisiblePlayer )
{
bool bCanTarget;
Vector3 vLosStartPos = vPedPosition;
Vector3 vLosEndPos = vTargetPedPosition;
bool bLosBlockedNear = false;
if(CPedGeometryAnalyser::ms_bProcessCanPedTargetPedAsynchronously && !bForceImmediateUpdate)
{
ECanTargetResult eRet = CPedGeometryAnalyser::CanPedTargetPedAsync( *m_pPed, *pTargetPed, iTargetFlags, false, CPedGeometryAnalyser::ms_fCanTargetCacheMaxPosChangeSqr, CAN_TARGET_CACHE_TIMEOUT, &vLosStartPos, &vLosEndPos );
if(eRet==ECanTarget)
{
m_aTargets[iIndex].m_bLosWasDoneAsync = false;
bCanTarget = true;
}
else if(eRet==ECanNotTarget)
{
m_aTargets[iIndex].m_bLosWasDoneAsync = false;
bLosBlockedNear = vLosStartPos.Dist2(vLosEndPos) < rage::square(INTERSECTION_BLOCKED_DISTANCE);
bCanTarget = false;
}
else
{
// If we're not sure about the result yet, then don't change from the prev state
m_aTargets[iIndex].m_bLosWasDoneAsync = true;
// If the status from before was either clear or blocked by friendly, we still
// need to go ahead and do the friendly fire check even if we are waiting for a probe
// to finish. If we don't, we may never update the friendly fire status properly, if
// we move fast enough to invalidate the cached results.
const LosStatus status = m_aTargets[iIndex].GetLosStatus();
if(status == Los_clear || status == Los_blockedByFriendly)
{
bCanTarget = true;
}
else
{
return;
}
}
}
else
{
// Note: even in this bForceImmediateUpdate case, it would probably be good to try to cache
// the result, but we don't do that now.
bCanTarget = CPedGeometryAnalyser::CanPedTargetPed( *m_pPed, *pTargetPed, iTargetFlags, NULL, &vLosStartPos, &vLosEndPos );
bLosBlockedNear = vLosStartPos.Dist2(vLosEndPos) < rage::square(INTERSECTION_BLOCKED_DISTANCE);;
}
// By default not reset
m_aTargets[iIndex].m_bLosBlockedNear = false;
if(bCanTarget)
{
// The LOS is not blocked by any peds or any buildings
// Check for friendly fire
CPed* pBlockingPed = ProbeForFriendlyFireBlockingPed(pTargetPed, VECTOR3_TO_VEC3V(vPedPosition), VECTOR3_TO_VEC3V(vTargetPedPosition));
// Set the blocking ped
m_aTargets[iIndex].m_losStatus = pBlockingPed ? Los_blockedByFriendly : Los_clear;
m_aTargets[iIndex].m_pBlockedByFriendlyPed = pBlockingPed;
}
else
{
// LOS blocked by buildings or map objects
m_aTargets[iIndex].m_losStatus = Los_blocked;
m_aTargets[iIndex].m_bLosBlockedNear = bLosBlockedNear;
}
}
else
{
// LOS blocked by buildings or map objects
m_aTargets[iIndex].m_losStatus = Los_blocked;
m_aTargets[iIndex].m_bLosBlockedNear = false;
}
}
//-------------------------------------------------------------------------
// Carries out an LOS status update
//-------------------------------------------------------------------------
void CPedTargetting::UpdateLosStatus( void )
{
s32 iIndex = m_iNextLosIndex;
const int numTargets = m_iNumActiveTargets;
for( s32 i = 1; i < ( numTargets + 1 ); i++ )
{
s32 iThisIndex = i + iIndex;
if( iThisIndex >= numTargets )
{
iThisIndex -= numTargets;
Assert(iThisIndex >= 0 && iThisIndex < numTargets);
}
// If this is a valid target, check the los
if( m_aTargets[iThisIndex].IsValid() )
{
UpdateSingleLos( iThisIndex, false );
m_iNextLosIndex = (s16)iThisIndex;
return;
}
}
// No valid targets, reset the next los to 0
m_iNextLosIndex = 0;
}
bool CPedTargetting::AllowedToTargetPed( const CPed* pTarget, bool bIsBeingTargeted ) const
{
if(!pTarget)
{
return false;
}
// We can continue attacking some peds from combat even if dead/injured
// so make sure a ped isn't being targeted before removing them due to being dead/injured
if(!bIsBeingTargeted)
{
if(pTarget->IsDead() )
{
return false;
}
if( pTarget->IsInjured() && !m_pPed->WillAttackInjuredPeds() )
{
return false;
}
}
// Check if this is a respawning network player
if(NetworkInterface::IsGameInProgress() && pTarget->IsAPlayerPed())
{
CPlayerInfo* pTargetPlayerInfo = pTarget->GetPlayerInfo();
if( pTargetPlayerInfo->GetPlayerState() == CPlayerInfo::PLAYERSTATE_RESPAWN ||
(pTarget->IsNetworkClone() && pTargetPlayerInfo->GetPlayerState() == CPlayerInfo::PLAYERSTATE_HASDIED) )
{
return false;
}
// Check if the player has been respawned recently
if(pTarget->GetPlayerInfo()->GetHasRespawnedWithinTime(CTaskCombat::ms_Tunables.m_MaxTimeToRejectRespawnedTarget))
{
// Also need to check how long we've been in the combat task (if we have one)
const CTaskCombat* pCombatTask = static_cast<const CTaskCombat*>(m_pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_COMBAT));
if(pCombatTask && (pCombatTask->GetTimeRunning() * 1000.0f) > CTaskCombat::ms_Tunables.m_MaxTimeToRejectRespawnedTarget)
{
return false;
}
}
// ignore players in a different tutorial space
CNetObjPed* pNetObjPed = static_cast<CNetObjPed*>(pTarget->GetNetworkObject());
if(pNetObjPed && pNetObjPed->GetPlayerOwner() && pNetObjPed->GetPlayerOwner()->IsInDifferentTutorialSession())
{
return false;
}
//Ensure that if the playerped is dead - they are not allowed to be targeted (lavalley)
if (pTarget->IsDead())
{
return false;
}
//If the player has just respawned and has an invincibility timer active - they are not allowed to be targeted (lavalley)
if (pTarget->GetNetworkObject())
{
CNetObjPlayer* netObjPlayer = static_cast<CNetObjPlayer*>(pTarget->GetNetworkObject());
if(netObjPlayer && netObjPlayer->GetRespawnInvincibilityTimer() > 0)
{
return false;
}
}
}
CPedIntelligence* pPedIntelligence = m_pPed->GetPedIntelligence();
const CRelationshipGroup* pRelGroup = pPedIntelligence->GetRelationshipGroup();
const int otherRelGroupIndex = pTarget->GetPedIntelligence()->GetRelationshipGroupIndex();
// Check if we want to ignore the ped
if( pRelGroup && pRelGroup->CheckRelationship(ACQUAINTANCE_TYPE_PED_IGNORE, otherRelGroupIndex) )
{
return false;
}
// Is the target a cop and we shouldn't attack cops unless the player is wanted?
if( !pPedIntelligence->CanAttackPed(pTarget) )
{
return false;
}
return true;
}
CPed* CPedTargetting::ProbeForFriendlyFireBlockingPed(CPed* pTargetPed, Vec3V_In pedPositionV, Vec3V_In targetPedPositionV) const
{
// These are non-critical, increasing them to be large enough will only cost us some extra stack space.
static const int kMaxPedsNearLine = 64;
static const int kMaxAdditionalExcludePeds = 16;
// This is also not very critical, as long as it's large enough so that a sphere with this
// radius around a ped's transform position includes the whole body.
static const float pedBoundingRadius = 2.5f;
// Query the spatial array for all peds that are near the targeting segment.
CSpatialArrayNode* foundArray[kMaxPedsNearLine];
int numFound = CPed::GetSpatialArray().FindNearSegment(pedPositionV, targetPedPositionV, pedBoundingRadius, foundArray, kMaxPedsNearLine);
CPed* pTargetingPed = m_pPed;
CPed* pBlockingPed = NULL;
// Next, we will build an array of peds that we don't want to test against.
int numAdditionalExcludePeds = 0;
CPed* pAdditionalExcludePeds[kMaxAdditionalExcludePeds];
// If this ped is in a car, add any other peds in the car.
// This was adapted from WorldProbe::CShapeTestTaskData::ConvertExcludeEntitiesToInstances(), which
// is what would have been used when we did the test through the physics level.
if(pTargetingPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle))
{
CVehicle* pVehicle = pTargetingPed->GetMyVehicle();
if(pVehicle && pVehicle->GetFragInst()) // Note: this GetFragInst() thing may not really be needed.
{
// Add any passengers in the car.
const int maxSeats = pVehicle->GetSeatManager()->GetMaxSeats();
for(int nPassenger = 0; nPassenger < maxSeats; nPassenger++)
{
CPed* pPassenger = pVehicle->GetSeatManager()->GetPedInSeat(nPassenger);
if(pPassenger && pPassenger != pTargetingPed)
{
if(aiVerifyf(numAdditionalExcludePeds < kMaxAdditionalExcludePeds, "Too many vehicle passengers, increase kMaxAdditionalExcludePeds."))
{
pAdditionalExcludePeds[numAdditionalExcludePeds++] = pPassenger;
}
else
{
break;
}
}
}
}
}
// Set up a shape test for testing just an individual object, which we will use further down.
WorldProbe::CShapeTestFixedResults<1> probeResult;
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetResultsStructure(&probeResult);
probeDesc.SetStartAndEnd(RCC_VECTOR3(pedPositionV), RCC_VECTOR3(targetPedPositionV));
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_PED_TYPE);
probeDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS);
// Loop over the potentially blocking peds that we found.
float closestTVal = LARGE_FLOAT;
for(int i = 0; i < numFound; i++)
{
// Get the ped through the spatial array node pointer.
CPed* pPotentialBlockingPed = CPed::GetPedFromSpatialArrayNode(foundArray[i]);
Assert(pPotentialBlockingPed);
// Never test against the target or against ourselves. These two peds would generally
// have been found by the spatial array operation, since the segment goes from one to the other.
if(pPotentialBlockingPed == pTargetPed || pPotentialBlockingPed == pTargetingPed)
{
continue;
}
// Next, test if this ped should otherwise be excluded (possibly a passenger of the same vehicle).
bool shouldBeExcluded = false;
for(int j = 0; j < numAdditionalExcludePeds; j++)
{
if(pAdditionalExcludePeds[j] == pPotentialBlockingPed)
{
shouldBeExcluded = true;
break;
}
}
if(shouldBeExcluded)
{
continue;
}
// If injured or not friendly, move on to the next target.
// Note: the original friendly fire code would still test against these,
// but I don't think that was a good thing - it probably prevented us from
// finding true blocking allies further back.
if(pPotentialBlockingPed->IsInjured() || !IsFriendlyEntity(pPotentialBlockingPed))
{
continue;
}
// Make sure that the ped has a physics instance in the level, otherwise
// we wouldn't have found it with a probe through the physics level.
phInst* pInst = pPotentialBlockingPed->GetCurrentPhysicsInst();
if(!pInst || !pInst->IsInLevel())
{
continue;
}
// Perform the shape test against just this one ped's instance.
probeDesc.SetIncludeInstance(pInst);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
// Get the T value, and see if it's closer than any we've already found.
const float hitT = probeResult[0].GetHitTValue();
if(hitT < closestTVal)
{
// Update what we've found so far.
closestTVal = hitT;
pBlockingPed = pPotentialBlockingPed;
// We could potentially break out here, if we don't care about
// m_pBlockedByFriendlyPed being the closest. But, it might be useful
// to make sure we find the closest, so let's keep that for now.
}
// Reset the probe result, since we are done with it. This may not be
// strictly necessary but feels safest.
probeResult.Reset();
}
}
// Return what we found.
return pBlockingPed;
}
//-------------------------------------------------------------------------
// Checks to see if this entity is considered a valid target
//-------------------------------------------------------------------------
/*bool CPedTargetting::IsValidTarget( const CEntity* pEntity ) const
{
Assert( pEntity );
if( !pEntity->GetIsTypePed() )
{
return false;
}
CPed* pPed = (CPed*)pEntity;
// Don't target this ped
if( pPed == m_pPed )
{
return false;
}
if( !AllowedToTargetPed(pPed) )
{
return false;
}
if( m_pPed->GetPedIntelligence()->IsFriendlyWith(*pPed ) )
{
return false;
}
// Ignore other group members
CPedGroup* pPedGroup = m_pPed->GetPedsGroup();
if( pPedGroup )
{
if( pPedGroup->GetGroupMembership()->IsMember(pPed) )
{
return false;
}
}
// Valid target if the type is considered threatening
if( m_pPed->GetPedIntelligence()->IsThreatenedBy(*pPed) )
{
Assertf( !m_pPed->GetPedIntelligence()->IsFriendlyWith(*pPed ), "Ped in relgroup (%s) is trying to fight friendly ped in relgroup (%s)", m_pPed->GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr(), pPed->GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr() );
return true;
}
// Also a valid target if in combat and targeting us or a friend
CEntity* pEntityTarget = pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget();
if( pEntityTarget && pEntityTarget->GetIsTypePed() )
{
CPed* pPedTarget = (CPed*) pEntityTarget;
if( ( ( pPedTarget == m_pPed ) ||
m_pPed->GetPedIntelligence()->IsFriendlyWith( *pPedTarget ) ) )
{
return true;
}
}
return false;
}*/
//-------------------------------------------------------------------------
// Returns if the target should be removed, note this is different to the the opposite of hte above function
// to allow temporarily registered threats to persist unlesss dead or completely invalid targets.
//-------------------------------------------------------------------------
bool CPedTargetting::TargetShouldBeRemoved( const CSingleTargetInfo* pTarget ) const
{
Assert( pTarget );
const CEntity* pEntity = pTarget->m_pEntity;
Assert( pEntity );
// Never remove the locked target
if( pEntity == m_pLockedTarget )
{
return false;
}
if( pEntity->GetIsTypePed() )
{
CPed* pPed = (CPed*)pEntity;
if( !AllowedToTargetPed(pPed, pTarget->m_bIsBeingTargeted) )
{
return true;
}
}
return false;
}
#define INJURED_PED_WEIGHTING (0.2f)
#define DYING_PED_WEIGHTING (0.1f)
#define AIR_COMBAT_TARGET_WEIGHTING (0.05f)
dev_float REDUCE_PLAYER_PASSENGERS_SCALE = 0.1f;
//-------------------------------------------------------------------------
// Default score calculation function
//-------------------------------------------------------------------------
float CPedTargetting::DefaultTargetScoring( CPed* pTargettingPed, const CEntity* pEntity )
{
Assert( pEntity );
Assert( pEntity->GetIsTypePed() );
CPed* pPed = (CPed*) pEntity;
// Dead peds aren't a target at all
if( pPed->IsDead() )
{
return 0.0f;
}
float fScore = BASIC_THREAT_WEIGHTING;
CQueriableInterface* pPedQueriableInterface = pPed->GetPedIntelligence()->GetQueriableInterface();
const CEntity* pHostileTarget = pPedQueriableInterface->GetHostileTarget();
// Targeting score is reduced if the target is fleeing
if( pPedQueriableInterface->IsTaskCurrentlyRunning( CTaskTypes::TASK_SMART_FLEE ) )
{
fScore *= FLEEING_THREAT_WEIGHTING;
}
// Targeting score is increased if the target is targeting us
else if( pHostileTarget == (CEntity*)pTargettingPed )
{
// If unarmed, more likely to fight whoever is fighting us
const CPedWeaponManager *pedWeaponMgr = pPed->GetWeaponManager();
if( pedWeaponMgr && pedWeaponMgr->GetEquippedWeapon() && !pedWeaponMgr->GetEquippedWeapon()->GetWeaponInfo()->GetIsMelee() )
{
fScore *= DIRECT_THREAT_WEIGHTING;
}
else
{
fScore *= UNARMED_DIRECT_THREAT_WEIGHTING;
}
}
// Or if the target is the player
else if( pPed->IsPlayer() || pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_TreatAsPlayerDuringTargeting) )
{
if( pPed->GetWeaponManager() && pPed->GetWeaponManager()->GetIsArmed() )
fScore *= PLAYER_THREAT_WEIGHTING;
else
fScore *= ACTIVE_THREAT_WEIGHTING;
}
else if( pHostileTarget != NULL )
{
fScore *= ACTIVE_THREAT_WEIGHTING;
}
// If the target is in the players vehicle, the player has a higher target rating.
if( pTargettingPed->GetPedType() == PEDTYPE_COP && !pPed->IsAPlayerPed() && pPed->GetWeaponManager() && !pPed->GetWeaponManager()->GetIsArmed() &&
CGameWorld::FindLocalPlayerWanted() && CGameWorld::FindLocalPlayerWanted()->GetWantedLevel() > WANTED_CLEAN &&
pPed->GetVehiclePedInside() && pPed->GetVehiclePedInside() == CGameWorld::FindLocalPlayerVehicle() )
{
fScore *= REDUCE_PLAYER_PASSENGERS_SCALE;
}
// Less likely to attack peds travelling in vehicles
if( pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && pPed->GetMyVehicle() )
{
fScore *= IN_VEHICLE_WEIGHTING;
}
// Adjust the weighting if the target is injured or dying
if( pPed->IsInjured() )
{
fScore *= INJURED_PED_WEIGHTING;
}
else if( pPed->IsFatallyInjured())
{
fScore *= DYING_PED_WEIGHTING;
}
// If you're friendly with the player and the ped is targeting the player, increase the score by a factor
if( pHostileTarget &&
pHostileTarget->GetIsTypePed() &&
((CPed*) pHostileTarget )->IsPlayer() &&
pTargettingPed->GetPedIntelligence()->IsFriendlyWith( * ((CPed*) pHostileTarget ) ) )
{
fScore *= DEFEND_PLAYER_WEIGHTING;
}
//adjust weighting if we're preferring air combat
if(pTargettingPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_PreferAirCombatWhenInAircraft))
{
if(pTargettingPed->GetVehiclePedInside() && pTargettingPed->GetVehiclePedInside()->GetIsAircraft())
{
if(!pPed->GetVehiclePedInside() || !pPed->GetVehiclePedInside()->GetIsAircraft())
{
fScore *= AIR_COMBAT_TARGET_WEIGHTING;
}
}
}
if(!pTargettingPed->IsAPlayerPed() && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_ThisPedIsATargetPriorityForAI))
{
fScore *= SCRIPT_THREAT_WEIGHTING;
}
// Maximum available score to lock the result between 0 and 1
const static float sfMaxScore = BASIC_THREAT_WEIGHTING * PLAYER_THREAT_WEIGHTING * DEFEND_PLAYER_WEIGHTING * SCRIPT_THREAT_WEIGHTING;
return fScore / sfMaxScore;
}
//-------------------------------------------------------------------------
// Default score calculation function
//-------------------------------------------------------------------------
float CPedTargetting::EasyKillTargetScoring( CPed* pTargettingPed, const CEntity* pEntity )
{
Assert( pEntity );
Assert( pEntity->GetIsTypePed() );
CPed* pPed = (CPed*) pEntity;
float fScore = BASIC_THREAT_WEIGHTING;
// Dead peds aren't a target at all
if( pPed->IsDead() )
{
return 0.0f;
}
fScore = DefaultTargetScoring( pTargettingPed, pEntity );
// Peds are weighted inversly proportional to their health,
// so that the weakest return the highest targetting score
const CHealthConfigInfo* pHealthInfo = pPed->GetPedHealthInfo();
Assert( pHealthInfo );
Assert( pHealthInfo->GetDefaultHealth() );
fScore *= ( rage::Max( pHealthInfo->GetDefaultHealth() - pPed->GetHealth(), 0.0f ) / pHealthInfo->GetDefaultHealth() );
return fScore;
}
#define COVERING_FIRE_TARGET_WEIGHTING (10.0f)
//-------------------------------------------------------------------------
// Default score calculation function
//-------------------------------------------------------------------------
float CPedTargetting::CoveringFireTargetScoring( CPed* pTargettingPed, const CEntity* pEntity )
{
Assert( pEntity );
Assert( pEntity->GetIsTypePed() );
float fScore = DefaultTargetScoring( pTargettingPed, pEntity );
CEntity* pScoringEntity = pTargettingPed->GetPedIntelligence()->GetTargetting()->m_pScoringEntity;
CPed* pPed = (CPed*) pEntity;
// If you're friendly with the player and the ped is targetting hte player,
// increase the score by a factor
if( pScoringEntity &&
// pPed->GetPedIntelligence()->GetCurrentTarget() == ( CEntity* ) pScoringEntity )
pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() == ( CEntity* ) pScoringEntity )
{
fScore *= COVERING_FIRE_TARGET_WEIGHTING;
}
return fScore;
}
//-------------------------------------------------------------------------
// Calculates an appropriate targetting score for this entity
//-------------------------------------------------------------------------
float CPedTargetting::GetTargettingScore( const CEntity* pEntity ) const
{
Assert( m_pScoringFn );
return m_pScoringFn( m_pPed, pEntity );
}
//-------------------------------------------------------------------------
// Checks to see if this entity is considered friendly
//-------------------------------------------------------------------------
bool CPedTargetting::IsFriendlyEntity( const CEntity* pEntity ) const
{
Assert( pEntity );
// Ignore non-peds
if( !pEntity->GetIsTypePed() )
{
return false;
}
// Ignore this ped
if( pEntity == (CEntity*) m_pPed )
{
return false;
}
// Dead peds are not considered friendly
if( ((CPed*)pEntity)->IsDead() )
{
return false;
}
return m_pPed->GetPedIntelligence()->IsFriendlyWith(*(CPed*)pEntity);
}
//-------------------------------------------------------------------------
// Returns true if this friendly entity is targetting the given entity
//-------------------------------------------------------------------------
bool CPedTargetting::IsEntityTargettingEntity( const CEntity* pFriendlyEntity, const CEntity* pEntity ) const
{
Assert( pEntity->GetIsTypePed() );
// return ((CPed*)pFriendlyEntity)->GetPedIntelligence()->GetCurrentTarget() == pEntity;
return ((CPed*)pFriendlyEntity)->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() == pEntity;
}
//-------------------------------------------------------------------------
// Returns the targetting reduction caused by a friendly entity already targetting
// this entity
//-------------------------------------------------------------------------
float CPedTargetting::GetFriendlyTargettingScoreReduction( const CEntity* ASSERT_ONLY(pFriendlyEntity), const CEntity* ASSERT_ONLY(pEntity ) ) const
{
Assert( pEntity->GetIsTypePed() );
//Assert( IsFriendlyEntity( pFriendlyEntity ) );
Assert( IsEntityTargettingEntity( pFriendlyEntity, pEntity ) );
return FRIENDLY_TARGETTING_REDUCTION;
}
//-------------------------------------------------------------------------
// returns a pointer to the currently targetted entity
//-------------------------------------------------------------------------
CPed* CPedTargetting::GetBestTarget( void )
{
SetInUseAndWakeUp();
return (CPed*)GetCurrentTarget();
}
//-------------------------------------------------------------------------
// returns a pointer to the second best target
//-------------------------------------------------------------------------
CPed* CPedTargetting::GetSecondBestTarget( void )
{
SetInUseAndWakeUp();
return (CPed*)GetSecondaryTarget();
}
void CPedTargetting::OnTargetDeleteFromArray(const int iTargetIndex)
{
const int numTargets = m_iNumActiveTargets;
const int targetIndexToCopy = numTargets - 1;
Assert(targetIndexToCopy >= 0 && targetIndexToCopy < MAX_NUM_TARGETS);
Assert(iTargetIndex >= 0 && iTargetIndex < MAX_NUM_TARGETS);
if(targetIndexToCopy != iTargetIndex)
{
m_apVehiclesTargetsLastSeenIn[iTargetIndex] = m_apVehiclesTargetsLastSeenIn[targetIndexToCopy];
}
m_apVehiclesTargetsLastSeenIn[targetIndexToCopy] = NULL;
}
//-------------------------------------------------------------------------
// Called each time the given target is reset
//-------------------------------------------------------------------------
void CPedTargetting::OnTargetReset (const int iTargetIndex )
{
// Remove the associated vehicle
Assert(iTargetIndex >= 0 && iTargetIndex < MAX_NUM_TARGETS);
Assert(iTargetIndex < m_iNumActiveTargets);
if( m_apVehiclesTargetsLastSeenIn[iTargetIndex] )
{
m_apVehiclesTargetsLastSeenIn[iTargetIndex] = NULL;
}
}
//-------------------------------------------------------------------------
//Updates the targets after an LOS update
//-------------------------------------------------------------------------
void CPedTargetting::UpdatePostLos( LosStatus aPreviousLosStatus[MAX_NUM_TARGETS] )
{
CPed* pTarget = NULL;
const int numTargets = m_iNumActiveTargets;
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].IsValid() )
{
//MP ONLY: If the los status is unchecked don't process this any further as it will give false positives, but continue to next m_aTarget evaluation
if (NetworkInterface::IsGameInProgress() && m_aTargets[i].m_losStatus == Los_unchecked)
continue;
pTarget = (CPed*) m_aTargets[i].m_pEntity.Get();
Vector3 vTargetPosition = VEC3V_TO_VECTOR3(pTarget->GetTransform().GetPosition());
#if DEBUG_DRAW
if( CPedTargetting::DebugTargettingLos )
{
Color32 colour( 0x00, 0x00, 0x00, 0xff );
switch( m_aTargets[i].m_losStatus )
{
case Los_blocked:
colour = Color32( 0xff, 0x00, 0x00, 0xff );
break;
case Los_clear:
colour = Color32( 0x00, 0xff, 0x00, 0xff );
break;
case Los_blockedByFriendly:
colour = Color32( 0x00, 0x00, 0xff, 0xff );
break;
default:
break;
}
grcDebugDraw::Line( VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()) + Vector3(0.0f, 0.0f, 0.3f), vTargetPosition, colour );
}
#endif
bool bReportedPoliceSpottingPlayer = false;
CPed* pPlayerToReportSpotted = NULL;
Vector3 vPositionToReportSpotted(VEC3_ZERO);
if(pTarget->IsPlayer())
{
pPlayerToReportSpotted = pTarget;
vPositionToReportSpotted = vTargetPosition;
}
else if(!pTarget->IsPlayer() && pTarget->GetIsInVehicle() && pTarget->GetVehiclePedInside()->ContainsLocalPlayer())
{
pPlayerToReportSpotted = CGameWorld::FindLocalPlayer();
if(pPlayerToReportSpotted)
{
vPositionToReportSpotted = VEC3V_TO_VECTOR3(pPlayerToReportSpotted->GetTransform().GetPosition());
}
}
// If this ped will not lose it's targets, set hte timer to max
// and set the last position as the current position
if( m_pPed->GetPedIntelligence()->GetCombatBehaviour().GetTargetLossResponse() == CCombatData::TLR_NeverLoseTarget )
{
wantedDebugf3("CPedTargetting::UpdatePosLos TLR_NeverLoseTarget -- SetLastPositionSeen / set m_iLastSeenExpirationTimeMs");
m_aTargets[i].m_iLastSeenExpirationTimeMs = fwTimer::GetTimeInMilliseconds() + ms_Tunables.m_iTargetNotSeenIgnoreTimeMs;
m_aTargets[i].SetLastPositionSeen(vTargetPosition);
m_aTargets[i].PeriodicallyCalcHeadingAndVehicle();
// Only alert to local player, CNetObjPed::CreateCloneCommunication deals with each local player being spotted by cloned ped
if( m_aTargets[i].GetLosStatus() != Los_blocked && pPlayerToReportSpotted && m_pPed->ShouldBehaveLikeLaw() )
{
// OBBE: The cop can see the player here, called during the targeting update every few frames:
CWanted* pPlayerWanted = pPlayerToReportSpotted->GetPlayerWanted();
wantedDebugf3("CPedTargetting::UpdatePostLos m_aTargets[i].GetLosStatus[%d] invoke ReportPoliceSpottingPlayer A",m_aTargets[i].GetLosStatus());
pPlayerWanted->ReportPoliceSpottingPlayer(m_pPed, vPositionToReportSpotted, pPlayerWanted->m_WantedLevel, true, true);
bReportedPoliceSpottingPlayer = true;
}
}
else
{
if( m_aTargets[i].GetLosStatus() != Los_blocked && m_aTargets[i].GetLosStatus() != Los_unchecked )
{
// If not in a vehicle
// OR in the same vehicle
// OR in an unknown vehicle but was visible last frame
// OR in an unknown vehicle but close, allow the LOS
// AND this is a cop
if( m_pPed->PopTypeIsMission() ||
(( aPreviousLosStatus[i] != Los_blocked &&
aPreviousLosStatus[i] != Los_unchecked) ||
( ( !m_pPed->IsLawEnforcementPed() ||
!CPedGeometryAnalyser::IsPedInUnknownCar(*pTarget) ||
CPedGeometryAnalyser::CanPedSeePedInUnknownCar(*m_pPed, *pTarget) ) &&
(!CPedGeometryAnalyser::IsPedInBush(*pTarget) ||
CPedGeometryAnalyser::CanPedSeePedInBush(*m_pPed, *pTarget) ) ) ) )
{
#if !__NO_OUTPUT
#if __BANK
wantedDebugf3("CPedTargetting::UpdatePostLos m_pPed[%p]->GetDebugName[%s] GetLosStatus[%d] != Los_blocked -- SetLastPositionSeen / set m_iLastSeenExpirationTimeMs",m_pPed,m_pPed->GetDebugName(),m_aTargets[i].GetLosStatus());
#else
wantedDebugf3("CPedTargetting::UpdatePostLos m_pPed[%p] GetLosStatus[%d] != Los_blocked -- SetLastPositionSeen / set m_iLastSeenExpirationTimeMs",m_pPed,m_aTargets[i].GetLosStatus());
#endif
#endif
m_aTargets[i].m_iLastSeenExpirationTimeMs = fwTimer::GetTimeInMilliseconds() + ms_Tunables.m_iTargetNotSeenIgnoreTimeMs;
m_aTargets[i].SetLastPositionSeen(vTargetPosition);
m_aTargets[i].PeriodicallyCalcHeadingAndVehicle();
// If the player was previously in an unknown vehicle and a cop can see him, he isn't any more!
// Only alert to local player, CNetObjPed::CreateCloneCommunication deals with each local player being spotted by cloned ped
if( pPlayerToReportSpotted && m_pPed->ShouldBehaveLikeLaw() )
{
// OBBE: The cop can see the player here, called during the targeting update every few frames:
CWanted* pPlayerWanted = pPlayerToReportSpotted->GetPlayerWanted();
wantedDebugf3("CPedTargetting::UpdatePostLos m_aTargets[i].GetLosStatus[%d] invoke ReportPoliceSpottingPlayer B",m_aTargets[i].GetLosStatus());
pPlayerWanted->ReportPoliceSpottingPlayer(m_pPed, vPositionToReportSpotted, pPlayerWanted->m_WantedLevel, true, true);
bReportedPoliceSpottingPlayer = true;
}
// When seen in a vehicle, take note of the last vehicle the target has been seen in
if( m_apVehiclesTargetsLastSeenIn[i] != pTarget->GetMyVehicle() )
{
if( pTarget->GetMyVehicle() && pTarget->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) )
{
m_apVehiclesTargetsLastSeenIn[i] = pTarget->GetMyVehicle();
}
else
{
m_apVehiclesTargetsLastSeenIn[i] = NULL;
}
}
}
// otherwise the LOS is invalid, and the target is lost
else
{
m_aTargets[i].m_iLastSeenExpirationTimeMs = 0;
m_aTargets[i].m_losStatus = Los_blocked;
m_aTargets[i].m_bLosBlockedNear = false;
}
}
/// Update the visibility timer based on the los status
if( m_aTargets[i].GetLosStatus() == Los_blocked ||
( m_aTargets[i].m_pEntity && m_aTargets[i].m_pEntity->GetIsPhysical() &&
( ((CPhysical*)m_aTargets[i].m_pEntity.Get())->GetVelocity().Mag2() > (3.0f) ) ) )
{
m_aTargets[i].m_fVisibleTimer -= fwTimer::GetTimeStep();
if( m_aTargets[i].m_fVisibleTimer < 0.0f )
{
m_aTargets[i].m_fVisibleTimer = 0.0f;
}
}
else
{
m_aTargets[i].m_fVisibleTimer += fwTimer::GetTimeStep();
if( m_aTargets[i].m_fVisibleTimer > TIME_TO_ZERO_ON_TARGET )
{
m_aTargets[i].m_fVisibleTimer = TIME_TO_ZERO_ON_TARGET;
}
}
}
if( m_aTargets[i].GetLosStatus() != Los_clear )
{
m_aTargets[i].m_fBlockedTimer += fwTimer::GetTimeStep();
}
else
{
m_aTargets[i].m_fBlockedTimer = 0.0f;
m_aTargets[i].m_bHasEverHadClearLos = true;
}
//We may know where the target is, even if we can't see him.
if( !bReportedPoliceSpottingPlayer && pTarget->IsPlayer() && m_pPed->ShouldBehaveLikeLaw() &&
!m_pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_OnlyUpdateTargetWantedIfSeen) && m_aTargets[i].AreWhereaboutsKnown() && m_aTargets[i].m_bHasEverHadClearLos)
{
CWanted* pPlayerWanted = pTarget->GetPlayerWanted();
#if !__NO_OUTPUT
#if __BANK
wantedDebugf3("CPedTargetting::UpdatePostLos m_pPed[%p]->GetDebugName[%s] IsNetworkClone[%d] m_aTargets[i].GetLosStatus[%d] invoke ReportPoliceSpottingPlayer C",m_pPed,m_pPed->GetDebugName(),m_pPed->IsNetworkClone(),m_aTargets[i].GetLosStatus());
#else
wantedDebugf3("CPedTargetting::UpdatePostLos m_pPed[%p] IsNetworkClone[%d] m_aTargets[i].GetLosStatus[%d] invoke ReportPoliceSpottingPlayer C",m_pPed,m_pPed->IsNetworkClone(),m_aTargets[i].GetLosStatus());
#endif
#endif
pPlayerWanted->ReportPoliceSpottingPlayer(m_pPed, m_aTargets[i].GetLastPositionSeen(), pPlayerWanted->m_WantedLevel, false, true);
}
}
else
{
// Remove the associated vehicle
if( m_apVehiclesTargetsLastSeenIn[i] )
{
m_apVehiclesTargetsLastSeenIn[i] = NULL;
}
}
}
}
//-------------------------------------------------------------------------
// Registers a fresh target, updates the activity timer if it already exists
//-------------------------------------------------------------------------
void CPedTargetting::RegisterThreat( const CEntity* pEntity, bool bTargetSeen, bool bForceInsert, bool bWasDirectlyHostile )
{
#if !__NO_OUTPUT
#if __BANK
wantedDebugf3("CPedTargetting::RegisterThreat m_pPed[%p] debugname[%s] bTargetSeen[%d] bForceInsert[%d] bWasDirectlyHostile[%d]",m_pPed,m_pPed->GetDebugName(),bTargetSeen,bForceInsert,bWasDirectlyHostile);
#else
wantedDebugf3("CPedTargetting::RegisterThreat m_pPed[%p] bTargetSeen[%d] bForceInsert[%d] bWasDirectlyHostile[%d]",m_pPed,bTargetSeen,bForceInsert,bWasDirectlyHostile);
#endif
#endif
CPed* pTargetPed = (CPed*) pEntity;
Assertf( pTargetPed != m_pPed, "CPedTargetting::RegisterThreat Ped registering themselves as a target!");
// Don't register friendly peds.
if( m_pPed->GetPedIntelligence()->IsFriendlyWith(*pTargetPed) )
{
Assertf(!m_pPed->GetPedIntelligence()->IsFriendlyWith(*pTargetPed), "RegisterThreat called with friendly ped, ped in group (%s) is attacking ped in group (%s)",
m_pPed->GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr(),
pTargetPed->GetPedIntelligence()->GetRelationshipGroup()->GetName().GetCStr() );
#if __DEV
const CRelationshipGroup* pRelGroup = m_pPed->GetPedIntelligence()->GetRelationshipGroup();
const int otherRelationshipGroupIndex = pTargetPed->GetPedIntelligence()->GetRelationshipGroupIndex();
if(pRelGroup && pTargetPed->GetPedIntelligence()->GetRelationshipGroup())
{
Assertf(!pRelGroup->CheckRelationship(ACQUAINTANCE_TYPE_PED_RESPECT, otherRelationshipGroupIndex), "Ped %s called RegisterThreat, RESPECTS target %s",
m_pPed->GetBaseModelInfo()->GetModelName(), pTargetPed->GetBaseModelInfo()->GetModelName());
Assertf(!pRelGroup->CheckRelationship(ACQUAINTANCE_TYPE_PED_LIKE, otherRelationshipGroupIndex), "Ped %s called RegisterThreat LIKES target %s",
m_pPed->GetBaseModelInfo()->GetModelName(), pTargetPed->GetBaseModelInfo()->GetModelName());
}
CPedGroup* pMyGroup = m_pPed->GetPedsGroup();
Assertf(!pMyGroup || pMyGroup != pTargetPed->GetPedsGroup(), "Ped %s called RegisterThreat and is in the same ped group as target %s",
m_pPed->GetBaseModelInfo()->GetModelName(), pTargetPed->GetBaseModelInfo()->GetModelName());
if(m_pPed->GetPedType() == PEDTYPE_MEDIC || m_pPed->GetPedType() == PEDTYPE_FIRE || m_pPed->GetPedType() == PEDTYPE_COP)
{
Assertf(m_pPed->GetPedType() != pTargetPed->GetPedType(), "Ped %s called RegisterThreat, has some ped type (%d) as target %s",
m_pPed->GetBaseModelInfo()->GetModelName(), m_pPed->GetPedType(), pTargetPed->GetBaseModelInfo()->GetModelName());
}
#endif
return;
}
CSingleTargetInfo* pTargetInfo = FindTarget( pEntity );
// Is the target already present?
if( pTargetInfo )
{
// Revalidate the existing target
pTargetInfo->Revalidate();
// If the target is seen, reset the timer
if( bTargetSeen )
{
pTargetInfo->m_iLastSeenExpirationTimeMs = fwTimer::GetTimeInMilliseconds() + ms_Tunables.m_iTargetNotSeenIgnoreTimeMs;
pTargetInfo->SetLastPositionSeen(VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
// I guess we can afford counting down the counter by one and possibly do it if it's about time:
pTargetInfo->PeriodicallyCalcHeadingAndVehicle();
}
}
else
{
// Add it is a fresh target
AddTarget( pEntity, bForceInsert );
}
// If the target has actually been seen, remember the vehicle last seen in.
if( bTargetSeen || bWasDirectlyHostile )
{
const int numTargets = m_iNumActiveTargets;
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].m_pEntity == pEntity )
{
if(bTargetSeen)
{
// remember the current vehicle the ped has been seen in
if( pTargetPed->GetMyVehicle() && pTargetPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) )
{
m_apVehiclesTargetsLastSeenIn[i] = pTargetPed->GetMyVehicle();
}
else
{
m_apVehiclesTargetsLastSeenIn[i] = NULL;
}
}
if(bWasDirectlyHostile)
{
m_aTargets[i].m_iTimeLastHostileActionMs = fwTimer::GetTimeInMilliseconds();
}
}
}
}
}
//-------------------------------------------------------------------------
// Returns the vehicle the target was last seen in
//-------------------------------------------------------------------------
CVehicle* CPedTargetting::GetVehicleTargetLastSeenIn( CEntity* pTarget )
{
const int numTargets = m_iNumActiveTargets;
Assert(numTargets <= NELEM(m_apVehiclesTargetsLastSeenIn));
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].m_pEntity == pTarget )
{
return m_apVehiclesTargetsLastSeenIn[i];
}
}
return NULL;
}
//-------------------------------------------------------------------------
// Find the next stored target in the direction specified.
//-------------------------------------------------------------------------
const CEntity* CPedTargetting::FindNextTarget( CEntity* pCurrentTarget, const bool bWithLos, const bool bCheckLeft, const float fCurrentHeading, const float fHeadingLimit )
{
float fOrientationToCurrentTarget = 0.0f;
float fCurrentClosest = -999999.0f;
const CEntity* pNewTarget = NULL;
Assert(m_pPed);
const Vector3 vPedPosition = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition());
if( pCurrentTarget )
{
const Vector3 vCurrentTargetPosition = VEC3V_TO_VECTOR3(pCurrentTarget->GetTransform().GetPosition());
fOrientationToCurrentTarget = fwAngle::GetATanOfXY( (vCurrentTargetPosition.x - vPedPosition.x), (vCurrentTargetPosition.y - vPedPosition.y) );
}
else
{
Vector3 vecPedForward(VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetB()));
fOrientationToCurrentTarget = fwAngle::GetATanOfXY(vecPedForward.x, vecPedForward.y );
}
// Loop through all the targets and try to find one in hte direction given
const int numTargets = m_iNumActiveTargets;
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].m_pEntity && pCurrentTarget != m_aTargets[i].m_pEntity )
{
const CEntity* pTarget = m_aTargets[i].m_pEntity;
// Check that there is a clear LOS if one is required.
if( bWithLos && m_aTargets[i].GetLosStatus() == Los_blocked )
{
continue;
}
Vector3 vTargetPosition = VEC3V_TO_VECTOR3(pTarget->GetTransform().GetPosition());
const float fOrientationToPossibleTarget = fwAngle::GetATanOfXY( (vTargetPosition.x - vPedPosition.x), (vTargetPosition.y - vPedPosition.y) );
// Check the ped is in the range from the current heading
float fOrientationDiff = fOrientationToPossibleTarget - fCurrentHeading;
fOrientationDiff = fwAngle::LimitRadianAngle(fOrientationDiff);
if( ABS(fOrientationDiff) > fHeadingLimit )
{
continue;
}
fOrientationDiff = fOrientationToPossibleTarget - fOrientationToCurrentTarget;
fOrientationDiff = fwAngle::LimitRadianAngle(fOrientationDiff);
float fScore = 0.0f;
if (bCheckLeft)
{
if (fOrientationDiff <= 0.0f)
{
fScore = -ABS(fOrientationDiff);
}
else
{
fScore = -1000.0f + ABS(fOrientationDiff);
}
}
else
{
if (fOrientationDiff >= 0.0f)
{
fScore = -ABS(fOrientationDiff);
}
else
{
fScore = -1000.0f + ABS(fOrientationDiff);
}
}
if (fScore > fCurrentClosest)
{
pNewTarget = pTarget;
fCurrentClosest = fScore;
}
}
}
return pNewTarget;
}
float CPedTargetting::GetExistingTargetReduction() const
{
return ms_Tunables.m_fExistingTargetScoreWeight;
}
bool CPedTargetting::IsFriendlyWithAnyTarget()
{
// Loop through all the targets and try to find one in hte direction given
const int numTargets = m_iNumActiveTargets;
for(int i = 0; i < numTargets; i++)
{
if( m_aTargets[i].m_pEntity && m_aTargets[i].m_pEntity->GetIsTypePed() )
{
const CPed* pTarget = static_cast<const CPed*>(m_aTargets[i].m_pEntity.Get());
if( m_pPed->GetPedIntelligence()->IsFriendlyWith(*pTarget) )
return true;
}
}
return false;
}