2265 lines
53 KiB
C++
2265 lines
53 KiB
C++
// File header
|
|
#include "Task/Combat/CombatManager.h"
|
|
|
|
// Rage headers
|
|
#include "bank/bank.h"
|
|
#include "bank/bkmgr.h"
|
|
#include "math/angmath.h"
|
|
|
|
// Game headers
|
|
#include "Debug/DebugScene.h"
|
|
#include "Game/Dispatch/Incidents.h"
|
|
#include "Game/Dispatch/Orders.h"
|
|
#include "Peds/Ped.h"
|
|
#include "Peds/PedIntelligence.h"
|
|
#include "Scene/World/GameWorld.h"
|
|
#include "Task/Combat/TaskCombatMelee.h"
|
|
#include "Task/Combat/TaskCombatMounted.h"
|
|
#include "Task/Combat/TaskNewCombat.h"
|
|
#include "Task/Combat/TaskThreatResponse.h"
|
|
|
|
AI_OPTIMISATIONS()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CCombatManager *CCombatManager::sm_Instance = NULL;
|
|
|
|
CCombatManager::CCombatManager()
|
|
{
|
|
#if __BANK
|
|
m_bDebugRender = false;
|
|
#endif
|
|
}
|
|
|
|
CCombatManager::~CCombatManager()
|
|
{
|
|
}
|
|
|
|
void CCombatManager::InitClass( void )
|
|
{
|
|
Assert(!sm_Instance);
|
|
sm_Instance = rage_new CCombatManager;
|
|
}
|
|
|
|
void CCombatManager::ShutdownClass( void )
|
|
{
|
|
Assert(sm_Instance);
|
|
delete sm_Instance;
|
|
sm_Instance = NULL;
|
|
}
|
|
|
|
void CCombatManager::Update( void )
|
|
{
|
|
m_MountedCombatManager.Update();
|
|
m_MeleeCombatManager.Update();
|
|
m_CombatTaskManager.Update();
|
|
}
|
|
|
|
#if __BANK
|
|
void CCombatManager::AddWidgets( bkBank &bank )
|
|
{
|
|
m_MountedCombatManager.AddWidgets( bank );
|
|
m_MeleeCombatManager.AddWidgets( bank );
|
|
m_CombatTaskManager.AddWidgets( bank );
|
|
}
|
|
#endif // __BANK
|
|
|
|
#if !__FINAL
|
|
void CCombatManager::DebugRender( void )
|
|
{
|
|
#if __BANK
|
|
m_MountedCombatManager.DebugRender();
|
|
m_MeleeCombatManager.DebugRender();
|
|
m_CombatTaskManager.DebugRender();
|
|
#endif // __BANK
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_COMBAT_TASK_TUNABLES(CCombatTaskManager, 0x9d23a8fb)
|
|
CCombatTaskManager::Tunables CCombatTaskManager::sm_Tunables;
|
|
|
|
const int CCombatTaskManager::sMaxPedsInCombatWithNearbyIncidents[WANTED_LEVEL_LAST] = { 0, 10, 12, 18, 24, 24 };
|
|
const int CCombatTaskManager::sMaxHelisInCombatWithNearbyIncidents[WANTED_LEVEL_LAST] = { 0, 2, 2, 2, 3, 3 };
|
|
|
|
#if __BANK
|
|
bool CCombatTaskManager::sm_bDebugDisplayPedsInCombatWithTarget = false;
|
|
bool CCombatTaskManager::sm_bDebugDisplay_MustHaveClearLOS = false;
|
|
bool CCombatTaskManager::sm_bDebugDisplay_MustHaveGunWeaponEquipped = false;
|
|
#endif // __BANK
|
|
|
|
int CCombatTaskManager::CountPedsInCombatWithTarget(const CPed &pTargetPed, fwFlags8 uOptionFlags, const Vec3V* pvSearchCenter, const ScalarV* scSearchRadiusSq)
|
|
{
|
|
int iCount = 0;
|
|
for( int i = 0; i < m_PedList.GetCount(); i++ )
|
|
{
|
|
const CPed* pPed = m_PedList[i];
|
|
if(!pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( uOptionFlags.IsFlagSet(OF_MustBeLawEnforcement) && !pPed->IsLawEnforcementPed() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( uOptionFlags.IsFlagSet(OF_MustHaveGunWeaponEquipped) && !HelperPedHasWeaponEquipped(pPed) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( uOptionFlags.IsFlagSet(OF_MustBeVehicleDriver) && !pPed->GetIsDrivingVehicle() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( uOptionFlags.IsFlagSet(OF_MustBeOnFoot) && !pPed->GetIsOnFoot() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const CEntity* pHostileTarget = pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget();
|
|
if( pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT, true) && pHostileTarget == &pTargetPed )
|
|
{
|
|
if( uOptionFlags.IsFlagSet(OF_MustHaveClearLOS) && !HelperPedHasClearLOSToTarget(pPed, pHostileTarget) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( pvSearchCenter && scSearchRadiusSq )
|
|
{
|
|
ScalarV scDistToSearchCenterSq = DistSquared(pPed->GetTransform().GetPosition(), *pvSearchCenter);
|
|
if(IsGreaterThanAll(scDistToSearchCenterSq, *scSearchRadiusSq))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
iCount++;
|
|
#if __BANK
|
|
if( uOptionFlags.IsFlagSet(OF_DebugRender) )
|
|
{
|
|
grcDebugDraw::Line(VEC3V_TO_VECTOR3(pTargetPed.GetTransform().GetPosition()), VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), Color_red, Color_red);
|
|
}
|
|
#endif // __BANK
|
|
|
|
if(uOptionFlags.IsFlagSet(OF_ReturnAfterInitialValidPed))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
int CCombatTaskManager::CountPedsInCombatStateWithTarget(const CPed& rTarget, s32 iState) const
|
|
{
|
|
int iCount = 0;
|
|
for( int i = 0; i < m_PedList.GetCount(); i++ )
|
|
{
|
|
const CPed* pPed = m_PedList[i];
|
|
if(!pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT, true) &&
|
|
(pPed->GetPedIntelligence()->GetQueriableInterface()->GetStateForTaskType(CTaskTypes::TASK_COMBAT) == iState) &&
|
|
(pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() == &rTarget))
|
|
|
|
{
|
|
iCount++;
|
|
}
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
int CCombatTaskManager::CountPedsWithClearLosToTarget(const CPed& rTarget, fwFlags8 uFlags) const
|
|
{
|
|
int iCount = 0;
|
|
for( int i = 0; i < m_PedList.GetCount(); i++ )
|
|
{
|
|
const CPed* pPed = m_PedList[i];
|
|
if(!pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(uFlags.IsFlagSet(CPWCLTTF_MustBeOnFoot) && !pPed->GetIsOnFoot())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!HelperPedHasClearLOSToTarget(pPed, &rTarget))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
iCount++;
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
// If looking for a combat target for a non-player ped, consider CQueriableInterface::FindHostileTarget().
|
|
CPed* CCombatTaskManager::GetPedInCombatWithPlayer(const CPed& pPlayerPed)
|
|
{
|
|
aiAssert(pPlayerPed.IsPlayer());
|
|
|
|
for( int i = 0; i < m_PedList.GetCount(); i++ )
|
|
{
|
|
CPed* pPed = m_PedList[i];
|
|
if(!pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT, true) &&
|
|
pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget() == &pPlayerPed )
|
|
{
|
|
return pPed;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bool CCombatTaskManager::SortByDistanceFromTarget(const sCombatPed& pFirstPed, const sCombatPed& pSecondPed)
|
|
{
|
|
if(pFirstPed.m_fDistToTargetSq != pSecondPed.m_fDistToTargetSq)
|
|
{
|
|
return (pFirstPed.m_fDistToTargetSq > pSecondPed.m_fDistToTargetSq);
|
|
}
|
|
else
|
|
{
|
|
return (pFirstPed.m_pPed > pSecondPed.m_pPed);
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
void CCombatTaskManager::AddWidgets( bkBank& bank )
|
|
{
|
|
bank.PushGroup("Combat Task Manager", false);
|
|
|
|
bank.PushGroup("CountPedsInCombatWithTarget");
|
|
bank.AddToggle("Render counted ped lines", &sm_bDebugDisplayPedsInCombatWithTarget);
|
|
bank.AddToggle("Render filter: require clear LOS", &sm_bDebugDisplay_MustHaveClearLOS);
|
|
bank.AddToggle("Render filter: require gun", &sm_bDebugDisplay_MustHaveGunWeaponEquipped);
|
|
bank.PopGroup();
|
|
|
|
bank.PopGroup();
|
|
}
|
|
#endif // __BANK
|
|
|
|
#if !__FINAL
|
|
void CCombatTaskManager::DebugRender()
|
|
{
|
|
#if __BANK
|
|
if( sm_bDebugDisplayPedsInCombatWithTarget )
|
|
{
|
|
for( int i = 0; i < m_PedList.GetCount(); i++ )
|
|
{
|
|
const CPed* pPed = m_PedList[i];
|
|
if(!pPed)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( sm_bDebugDisplay_MustHaveGunWeaponEquipped && !HelperPedHasWeaponEquipped(pPed) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const CEntity* pHostileTarget = pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget();
|
|
if( pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_COMBAT, true) && pHostileTarget != NULL )
|
|
{
|
|
if( sm_bDebugDisplay_MustHaveClearLOS && !HelperPedHasClearLOSToTarget(pPed, pHostileTarget) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
grcDebugDraw::Line(VEC3V_TO_VECTOR3(pHostileTarget->GetTransform().GetPosition()), VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), Color_red, Color_red);
|
|
}
|
|
}
|
|
}
|
|
#endif // __BANK
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
void CCombatTaskManager::FindAndRemoveLawPedsMP(sCombatPed* aCombatPeds, s32 iNumPedsInList, int iMaxPeds)
|
|
{
|
|
// Only remove law peds when over a certain count in combat
|
|
int iNumPedsOverMax = iNumPedsInList - iMaxPeds;
|
|
if(iNumPedsOverMax <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static dev_float s_fMinDistanceForLawToFleeInMP = 20.0f;
|
|
ScalarV scMinDistanceForLawToFleeSq = LoadScalar32IntoScalarV(s_fMinDistanceForLawToFleeInMP);
|
|
scMinDistanceForLawToFleeSq = (scMinDistanceForLawToFleeSq * scMinDistanceForLawToFleeSq);
|
|
|
|
int iNumPedsRemoved = 0;
|
|
for( int i = 0; i < iNumPedsInList; i++ )
|
|
{
|
|
CPed* pPed = aCombatPeds[i].m_pPed;
|
|
|
|
if(pPed->IsNetworkClone())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(pPed->PopTypeIsMission())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pPed->IsLawEnforcementPed())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(pPed->GetIsInVehicle())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(IsLessThanAll(LoadScalar32IntoScalarV(aCombatPeds[i].m_fDistToTargetSq), scMinDistanceForLawToFleeSq))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CTaskThreatResponse* pThreatResponseTask = static_cast<CTaskThreatResponse*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_THREAT_RESPONSE));
|
|
if(pThreatResponseTask && pThreatResponseTask->GetState() == CTaskThreatResponse::State_Combat)
|
|
{
|
|
pThreatResponseTask->SetFleeFromCombat(true);
|
|
pPed->SetRemoveAsSoonAsPossible(true);
|
|
|
|
++iNumPedsRemoved;
|
|
if(iNumPedsRemoved >= iNumPedsOverMax)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCombatTaskManager::FindAndRemoveHelisMP(sCombatPed* aCombatPeds, s32 iNumPedsInList, int iNumHelis, int iMaxHelis)
|
|
{
|
|
// Only remove over the max
|
|
int iNumHelisOverMax = iNumHelis - iMaxHelis;
|
|
if(iNumHelisOverMax <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int iNumHelisRemoved = 0;
|
|
for( int i = 0; i < iNumPedsInList; i++ )
|
|
{
|
|
CPed* pPed = aCombatPeds[i].m_pPed;
|
|
|
|
if(pPed->IsNetworkClone())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pPed->PopTypeIsRandom())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pPed->IsLawEnforcementPed())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CVehicle* pMyVehicle = pPed->GetVehiclePedInside();
|
|
if(!pMyVehicle)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pMyVehicle->InheritsFromHeli())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pMyVehicle->IsDriver(pPed))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We are checking for any police helis (ignoring swat) so they must have a valid order
|
|
COrder* pOrder = pPed->GetPedIntelligence()->GetOrder();
|
|
if(!pOrder || !pOrder->GetIsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Must be a swat order type
|
|
if(pOrder->GetDispatchType() != DT_PoliceHelicopter)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CTaskThreatResponse* pThreatResponseTask = static_cast<CTaskThreatResponse*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_THREAT_RESPONSE));
|
|
if(pThreatResponseTask && pThreatResponseTask->GetState() == CTaskThreatResponse::State_Combat)
|
|
{
|
|
pThreatResponseTask->SetFleeFromCombat(true);
|
|
|
|
++iNumHelisRemoved;
|
|
if(iNumHelisRemoved >= iNumHelisOverMax)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// RETURNS - true if no more peds are needed to be removed, false if we need to remove more peds
|
|
bool CCombatTaskManager::FindPedsToRemove(sCombatPed* aCombatPeds, u32 iNumPedsInList, s32 &iNumPedsToRemove, bool bIgnoreLaw)
|
|
{
|
|
ScalarV scMinDistanceForLawToFleeSq = LoadScalar32IntoScalarV(square(CTaskCombat::ms_Tunables.m_MinDistanceForLawToFleeFromCombat));
|
|
bool bFoundLawWithinMinFleeRange = false;
|
|
|
|
for( int i = 0; i < iNumPedsInList && iNumPedsToRemove > 0; i++ )
|
|
{
|
|
CPed* pPed = aCombatPeds[i].m_pPed;
|
|
|
|
if(pPed->IsNetworkClone())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(pPed->PopTypeIsMission())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(bIgnoreLaw && pPed->IsLawEnforcementPed())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(pPed->IsSecurityPed() || pPed->CheckBraveryFlags(BF_DONT_FORCE_FLEE_COMBAT))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
COrder* pOrder = pPed->GetPedIntelligence()->GetOrder();
|
|
if (pOrder && pOrder->GetIsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(pPed->GetPedIntelligence()->GetCombatBehaviour().IsFlagSet(CCombatData::BF_DisableFleeFromCombat))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CVehicle* pVehicle = pPed->GetVehiclePedInside();
|
|
if(pVehicle && pVehicle->IsTank())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CTaskThreatResponse* pThreatResponseTask = static_cast<CTaskThreatResponse*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_THREAT_RESPONSE));
|
|
if(pThreatResponseTask && pThreatResponseTask->GetState() == CTaskThreatResponse::State_Combat)
|
|
{
|
|
if(pPed->IsLawEnforcementPed())
|
|
{
|
|
if(!bFoundLawWithinMinFleeRange && IsGreaterThanAll(LoadScalar32IntoScalarV(aCombatPeds[i].m_fDistToTargetSq), scMinDistanceForLawToFleeSq))
|
|
{
|
|
pThreatResponseTask->SetFleeFromCombat(true);
|
|
pPed->SetRemoveAsSoonAsPossible(true);
|
|
iNumPedsToRemove--;
|
|
}
|
|
else
|
|
{
|
|
bFoundLawWithinMinFleeRange = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pThreatResponseTask->SetFleeFromCombat(true);
|
|
pPed->SetRemoveAsSoonAsPossible(true);
|
|
iNumPedsToRemove--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (iNumPedsToRemove == 0);
|
|
}
|
|
|
|
bool CCombatTaskManager::HelperPedHasClearLOSToTarget(const CPed* pPed, const CEntity* pTarget) const
|
|
{
|
|
const bool bWakeUpIfInactive = false;
|
|
const CPedTargetting* pPedTargetting = pPed->GetPedIntelligence()->GetTargetting(bWakeUpIfInactive);
|
|
if( pPedTargetting )
|
|
{
|
|
const CSingleTargetInfo* pCurrentTargetInfo = pPedTargetting->FindTarget(pTarget);
|
|
if( pCurrentTargetInfo )
|
|
{
|
|
const LosStatus losStatus = pCurrentTargetInfo->GetLosStatus();
|
|
if( losStatus == Los_clear )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCombatTaskManager::HelperPedHasWeaponEquipped(const CPed* pPed) const
|
|
{
|
|
const CPedWeaponManager* pWeaponManager = pPed->GetWeaponManager();
|
|
if( pWeaponManager )
|
|
{
|
|
const CObject* pWeaponObj = pWeaponManager->GetEquippedWeaponObject();
|
|
if( NULL == pWeaponObj )
|
|
{
|
|
// no equipped weapon object
|
|
return false;
|
|
}
|
|
// else pWeaponObj != NULL
|
|
const CWeaponInfo* pWeaponInfo = pWeaponManager->GetEquippedWeaponInfo();
|
|
if(pWeaponInfo)
|
|
{
|
|
if( pWeaponInfo->GetIsGunOrCanBeFiredLikeGun() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CCombatTaskManager::Update()
|
|
{
|
|
for( int i = 0; i < m_PedList.GetCount(); i++ )
|
|
{
|
|
if(!m_PedList[i])
|
|
{
|
|
m_PedList.Delete(i--);
|
|
}
|
|
}
|
|
|
|
// Only update our timer if we haven't added a ped recently, because we want to update no matter what when we add new peds
|
|
if(!m_bPedRecentlyAdded)
|
|
{
|
|
// If we haven't added a new ped then check if we can update based on our timer
|
|
m_fUpdateTimer -= fwTimer::GetTimeStep();
|
|
if(m_fUpdateTimer > 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
int iMaxPedsInCombatTask = !NetworkInterface::IsGameInProgress() ? sm_Tunables.m_iMaxPedsInCombatTask :
|
|
Min(sMaxPedsInCombatWithNearbyIncidents[WANTED_LEVEL1], sm_Tunables.m_iMaxPedsInCombatTask);
|
|
|
|
int nCount = m_PedList.GetCount();
|
|
if(nCount > iMaxPedsInCombatTask)
|
|
{
|
|
const CPed* pLocalPlayer = CGameWorld::FindLocalPlayer();
|
|
if(!pLocalPlayer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Copy over the peds and store their distance from the local player
|
|
Vec3V vPlayerPosition = pLocalPlayer->GetTransform().GetPosition();
|
|
sCombatPed aPedInfo[sMaxPedsInArray];
|
|
|
|
if(!NetworkInterface::IsGameInProgress())
|
|
{
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
CPed* pPed = m_PedList[i];
|
|
Vec3V vPedPosition = pPed->GetTransform().GetPosition();
|
|
|
|
aPedInfo[i].m_fDistToTargetSq = DistSquared(vPedPosition, vPlayerPosition).Getf();
|
|
aPedInfo[i].m_pPed = pPed;
|
|
}
|
|
|
|
// sort from largest to smallest distance
|
|
std::sort(&aPedInfo[0], &aPedInfo[0] + nCount, CCombatTaskManager::SortByDistanceFromTarget);
|
|
|
|
// First try to remove any non law peds
|
|
s32 iNumPedsToRemove = nCount - sm_Tunables.m_iMaxPedsInCombatTask;
|
|
if(!FindPedsToRemove(aPedInfo, nCount, iNumPedsToRemove, true))
|
|
{
|
|
// If we still have more peds to be removed, we can go ahead and include law enforcement ones
|
|
FindPedsToRemove(aPedInfo, nCount, iNumPedsToRemove, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For network games, make sure it's our turn to try and remove peds from combat before trying to do so
|
|
Vec3V vPosition(V_ZERO);
|
|
static dev_float s_fMaxDistance = FLT_MAX;
|
|
static dev_float s_fDurationOfTurns = 0.25f;
|
|
static dev_float s_fTimeBetweenTurns = 0.25f;
|
|
if(!NetworkInterface::IsLocalPlayersTurn(vPosition, s_fMaxDistance, s_fDurationOfTurns, s_fTimeBetweenTurns))
|
|
{
|
|
return;
|
|
}
|
|
|
|
static dev_float sMaxPedDistanceFromLocalPlayerMP = 200.0f;
|
|
ScalarV scMaxDistanceFromLocalPlayerMPSq = LoadScalar32IntoScalarV(sMaxPedDistanceFromLocalPlayerMP);
|
|
scMaxDistanceFromLocalPlayerMPSq = (scMaxDistanceFromLocalPlayerMPSq * scMaxDistanceFromLocalPlayerMPSq);
|
|
|
|
s32 nPedsAdded = 0;
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
CPed* pPed = m_PedList[i];
|
|
|
|
Vec3V vPedPosition = pPed->GetTransform().GetPosition();
|
|
ScalarV scDistToLocalPlayerSq = DistSquared(vPedPosition, vPlayerPosition);
|
|
if(IsLessThanAll(scDistToLocalPlayerSq, scMaxDistanceFromLocalPlayerMPSq))
|
|
{
|
|
aPedInfo[nPedsAdded].m_pPed = pPed;
|
|
|
|
// We need to hack the distance for heli peds because we don't want them to flee but we need to consider them as part of the
|
|
// max peds we keep in combat, otherwise they will be the "possible" flee peds but refuse to flee due to being in a vehicle
|
|
CEntity* pTarget = pPed->GetPedIntelligence()->GetQueriableInterface()->GetHostileTarget();
|
|
CVehicle* pMyVehicle = pPed->GetVehiclePedInside();
|
|
if(pMyVehicle && pMyVehicle->InheritsFromHeli())
|
|
{
|
|
aPedInfo[nPedsAdded].m_fDistToTargetSq = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
aPedInfo[nPedsAdded].m_fDistToTargetSq = DistSquared(vPedPosition, pTarget ? pTarget->GetTransform().GetPosition() : vPlayerPosition).Getf();
|
|
}
|
|
++nPedsAdded;
|
|
}
|
|
}
|
|
|
|
// sort from largest to smallest distance
|
|
std::sort(&aPedInfo[0], &aPedInfo[0] + nPedsAdded, CCombatTaskManager::SortByDistanceFromTarget);
|
|
|
|
// First try to remove any non law peds
|
|
s32 iNumPedsToRemove = nPedsAdded - sm_Tunables.m_iMaxPedsInCombatTask;
|
|
if(iNumPedsToRemove > 0)
|
|
{
|
|
FindPedsToRemove(aPedInfo, nPedsAdded, iNumPedsToRemove, true);
|
|
}
|
|
|
|
//Find the nearby wanted incidents.
|
|
static const int s_iMaxIncidents = 15;
|
|
CWantedIncident* aNearbyIncidents[s_iMaxIncidents];
|
|
int iNumNearbyIncidents = CWantedIncident::FindNearbyIncidents(CWantedIncident::GetMaxDistanceForNearbyIncidents(), aNearbyIncidents, s_iMaxIncidents);
|
|
|
|
//Find the highest wanted level.
|
|
int iHighestWantedLevel = WANTED_CLEAN;
|
|
for(int i = 0; i < iNumNearbyIncidents; ++i)
|
|
{
|
|
iHighestWantedLevel = Max(iHighestWantedLevel, aNearbyIncidents[i]->GetWantedLevel());
|
|
}
|
|
|
|
//Find the nearby wanted incidents.
|
|
iNumNearbyIncidents = CWantedIncident::FindNearbyIncidents(CWantedIncident::GetMaxDistanceForNearbyIncidents(iHighestWantedLevel), aNearbyIncidents, s_iMaxIncidents);
|
|
|
|
//Find the highest wanted level.
|
|
iHighestWantedLevel = WANTED_CLEAN;
|
|
for(int i = 0; i < iNumNearbyIncidents; ++i)
|
|
{
|
|
iHighestWantedLevel = Max(iHighestWantedLevel, aNearbyIncidents[i]->GetWantedLevel());
|
|
}
|
|
|
|
// Make sure the wanted level is valid before trying to remove helicopters
|
|
if(iHighestWantedLevel > WANTED_CLEAN && iHighestWantedLevel < WANTED_LEVEL_LAST)
|
|
{
|
|
// Get the number of helis in combat
|
|
int iNumHelis = GetNumHelis(aPedInfo, nPedsAdded);
|
|
|
|
// Try and remove helis if possible/needed
|
|
FindAndRemoveHelisMP(aPedInfo, nPedsAdded, iNumHelis, sMaxHelisInCombatWithNearbyIncidents[iHighestWantedLevel]);
|
|
}
|
|
|
|
// Get the number of law peds in combat, minus the peds who are in vehicles where the driver has been told to flee
|
|
int iNumLawPeds = GetNumLawPedsInCombat(aPedInfo, nPedsAdded);
|
|
|
|
// Now tell any peds over our max to flee
|
|
int iMaxPeds = iHighestWantedLevel > WANTED_CLEAN && iHighestWantedLevel < WANTED_LEVEL_LAST ? sMaxPedsInCombatWithNearbyIncidents[iHighestWantedLevel] : sMaxPedsInCombatMP;
|
|
if(iNumLawPeds > iMaxPeds)
|
|
{
|
|
FindAndRemoveLawPedsMP(aPedInfo, nPedsAdded, iMaxPeds);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset the timer and recently added variables
|
|
m_fUpdateTimer = sm_Tunables.m_fTimeBetweenUpdates;
|
|
m_bPedRecentlyAdded = false;
|
|
}
|
|
|
|
int CCombatTaskManager::GetNumLawPedsInCombat(sCombatPed* aCombatPeds, s32 iNumPedsInList)
|
|
{
|
|
int iNumLawPeds = 0;
|
|
for( int i = 0; i < iNumPedsInList; i++ )
|
|
{
|
|
CPed* pPed = aCombatPeds[i].m_pPed;
|
|
|
|
if(!pPed->IsLawEnforcementPed())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Is this ped in a heli
|
|
CVehicle* pMyVehicle = pPed->GetVehiclePedInside();
|
|
if(pMyVehicle && pMyVehicle->InheritsFromHeli())
|
|
{
|
|
// Get our driver
|
|
CPed* pDriver = pMyVehicle->GetDriver();
|
|
if(pDriver)
|
|
{
|
|
// Anybody that has a police dispatch type should be checked, swat rappelling and landing helis should still be considered in combat
|
|
COrder* pOrder = pDriver->GetPedIntelligence()->GetOrder();
|
|
if( pOrder && pOrder->GetIsValid() && pOrder->GetDispatchType() == DT_PoliceHelicopter )
|
|
{
|
|
// See if our driver is fleeing
|
|
if(pDriver->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_SMART_FLEE, true))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// See if they were told to flee
|
|
const CTaskThreatResponse* pTaskThreatResponse = static_cast<CTaskThreatResponse *>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_THREAT_RESPONSE));
|
|
if(pTaskThreatResponse && pTaskThreatResponse->IsFleeFromCombatFlagSet())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
iNumLawPeds++;
|
|
}
|
|
|
|
return iNumLawPeds;
|
|
}
|
|
|
|
int CCombatTaskManager::GetNumHelis(sCombatPed* aCombatPeds, s32 iNumPedsInList)
|
|
{
|
|
int iNumHelis = 0;
|
|
for( int i = 0; i < iNumPedsInList; i++ )
|
|
{
|
|
CPed* pPed = aCombatPeds[i].m_pPed;
|
|
|
|
// Ped must be random
|
|
if(!pPed->PopTypeIsRandom())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Must be law enforcement
|
|
if(!pPed->IsLawEnforcementPed())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Must be the driver of the heli
|
|
CVehicle* pMyVehicle = pPed->GetVehiclePedInside();
|
|
if(!pMyVehicle)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pMyVehicle->InheritsFromHeli())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!pMyVehicle->IsDriver(pPed))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We can count this ped as a "heli"
|
|
iNumHelis++;
|
|
}
|
|
|
|
return iNumHelis;
|
|
}
|
|
|
|
void CCombatTaskManager::AddPed(CPed* pPed)
|
|
{
|
|
if(!pPed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int nCount = m_PedList.GetCount();
|
|
for(int i = 0; i < nCount; i++)
|
|
{
|
|
if(m_PedList[i] == pPed)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if( aiVerifyf(!m_PedList.IsFull(), "Too many peds running CTaskCombat.") )
|
|
{
|
|
m_PedList.Append() = pPed;
|
|
m_bPedRecentlyAdded = true;
|
|
}
|
|
}
|
|
|
|
void CCombatTaskManager::RemovePed(CPed* pPed)
|
|
{
|
|
if(!pPed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int nCount = m_PedList.GetCount();
|
|
for(int i = 0; i < nCount; i++)
|
|
{
|
|
if(m_PedList[i] == pPed)
|
|
{
|
|
m_PedList.Delete(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
s32 CCombatMeleeManager::sm_nMaxNumActiveCombatants = 1;
|
|
s32 CCombatMeleeManager::sm_nMaxNumActiveSupportCombatants = 3;
|
|
|
|
CCombatMeleeManager::CCombatMeleeManager()
|
|
: m_Groups( NULL )
|
|
, m_MaxGroups( 0 )
|
|
, m_MaxGroupsMP( 0 )
|
|
, m_NumGroups( 0 )
|
|
{
|
|
const int nMaxGroups = fwConfigManager::GetInstance().GetSizeOfPool( ATSTRINGHASH( "CombatMeleeManager_Groups", 0xb7d08969 ) , CONFIGURED_FROM_FILE );
|
|
const int nMaxGroupsMP = fwConfigManager::GetInstance().GetSizeOfPool( ATSTRINGHASH("CombatMeleeManager_GroupsMP",0x3FA6EC68) , CONFIGURED_FROM_FILE );
|
|
m_MaxGroups = nMaxGroups;
|
|
m_MaxGroupsMP = nMaxGroupsMP;
|
|
if( nMaxGroups > 0 )
|
|
m_Groups = rage_new CCombatMeleeGroup[ Max(nMaxGroups,nMaxGroupsMP) ]; // Allocate enough memory to store both SP and MP because we do it once (when booting the game)
|
|
|
|
#if __BANK
|
|
m_bDebugRender = false;
|
|
#endif
|
|
}
|
|
|
|
CCombatMeleeManager::~CCombatMeleeManager()
|
|
{
|
|
DestroyAllGroups();
|
|
Assert( !m_NumGroups );
|
|
delete[] m_Groups;
|
|
}
|
|
|
|
void CCombatMeleeManager::Update( void )
|
|
{
|
|
for( int i = 0; i < GetMaxGroupsCount(); i++ )
|
|
{
|
|
CCombatMeleeGroup& group = m_Groups[i];
|
|
if( !group.IsActive() )
|
|
continue;
|
|
|
|
if( !group.Update() )
|
|
DestroyGroup( group );
|
|
}
|
|
}
|
|
|
|
void CCombatMeleeManager::DestroyGroup( CCombatMeleeGroup& group )
|
|
{
|
|
Assert( group.IsActive() );
|
|
group.Shutdown();
|
|
m_NumGroups--;
|
|
Assert( m_NumGroups >= 0 );
|
|
}
|
|
|
|
void CCombatMeleeManager::DestroyAllGroups( void )
|
|
{
|
|
for( int i = 0; i < GetMaxGroupsCount(); i++ )
|
|
{
|
|
CCombatMeleeGroup& group = m_Groups[i];
|
|
if( !group.IsActive() )
|
|
continue;
|
|
|
|
DestroyGroup( group );
|
|
}
|
|
}
|
|
|
|
CCombatMeleeGroup *CCombatMeleeManager::FindAvailableMeleeGroup()
|
|
{
|
|
if( m_NumGroups >= GetMaxGroupsCount() )
|
|
return NULL;
|
|
|
|
for( int i = 0; i < GetMaxGroupsCount(); i++ )
|
|
{
|
|
CCombatMeleeGroup& group = m_Groups[i];
|
|
if( !group.IsActive() )
|
|
return &group;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
CCombatMeleeGroup* CCombatMeleeManager::FindMeleeGroup( const CEntity& target )
|
|
{
|
|
for( int i = 0; i < GetMaxGroupsCount(); i++ )
|
|
{
|
|
CCombatMeleeGroup& group = m_Groups[i];
|
|
if( !group.IsActive() )
|
|
continue;
|
|
|
|
if( group.GetTarget() == &target )
|
|
return &m_Groups[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
CCombatMeleeGroup* CCombatMeleeManager::FindOrCreateMeleeGroup( const CEntity& target )
|
|
{
|
|
CCombatMeleeGroup* pMeleeGroup = FindMeleeGroup( target );
|
|
if( pMeleeGroup )
|
|
return pMeleeGroup;
|
|
|
|
pMeleeGroup = FindAvailableMeleeGroup();
|
|
if( !pMeleeGroup )
|
|
return NULL;
|
|
|
|
pMeleeGroup->Init( target );
|
|
m_NumGroups++;
|
|
Assert( m_NumGroups <= GetMaxGroupsCount() );
|
|
return pMeleeGroup;
|
|
}
|
|
|
|
#if __BANK
|
|
void CCombatMeleeManager::AddWidgets( bkBank &bank )
|
|
{
|
|
bank.PushGroup("Melee Combat", false);
|
|
bank.AddToggle("Draw", &m_bDebugRender);
|
|
bank.PopGroup();
|
|
}
|
|
|
|
bool CCombatMeleeManager::IsMeleeOpponentInGroup(CCombatMeleeOpponent *pOpponent)
|
|
{
|
|
for( int i = 0; i < GetMaxGroupsCount(); i++ )
|
|
{
|
|
CCombatMeleeGroup& group = m_Groups[i];
|
|
if(group.ContainsOpponent(pOpponent))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endif // __BANK
|
|
|
|
#if !__FINAL
|
|
void CCombatMeleeManager::DebugRender( void )
|
|
{
|
|
#if __BANK
|
|
if( !m_bDebugRender )
|
|
return;
|
|
|
|
for( int i = 0; i < GetMaxGroupsCount(); i++ )
|
|
{
|
|
CCombatMeleeGroup& group = m_Groups[i];
|
|
if( !group.IsActive() )
|
|
continue;
|
|
|
|
group.DebugRender();
|
|
}
|
|
#endif // __BANK
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CCombatMeleeGroup::CCombatMeleeGroup()
|
|
: m_v3Center( VEC3_ZERO )
|
|
, m_fRadius( 0.0f )
|
|
, m_Target( NULL )
|
|
, m_uTimeTargetStartedMovingAwayFromActiveCombatant(0)
|
|
, m_uLastInitialPursuitTime(0)
|
|
, m_FlagActive( false )
|
|
, m_bHasCheckedBattleAwareness( false )
|
|
{
|
|
}
|
|
|
|
CCombatMeleeGroup::~CCombatMeleeGroup()
|
|
{
|
|
Assert( !m_FlagActive );
|
|
}
|
|
|
|
void CCombatMeleeGroup::Init( const CEntity& target )
|
|
{
|
|
Assert( !m_Target );
|
|
Assert( !m_FlagActive );
|
|
m_FlagActive = true;
|
|
m_Target = ⌖
|
|
m_v3Center = target.GetTransform().GetPosition();
|
|
m_aOpponents.Reset();
|
|
m_uLastInitialPursuitTime = fwTimer::GetTimeInMilliseconds();
|
|
m_bHasCheckedBattleAwareness = false;
|
|
}
|
|
|
|
void CCombatMeleeGroup::Shutdown()
|
|
{
|
|
Assert( m_FlagActive );
|
|
m_FlagActive = false;
|
|
m_Target.Reset( NULL );
|
|
m_uLastInitialPursuitTime = 0;
|
|
m_bHasCheckedBattleAwareness = false;
|
|
}
|
|
|
|
int CCombatMeleeGroup::SortByDistance( CCombatMeleeOpponent* const* pA1, CCombatMeleeOpponent* const* pA2 )
|
|
{
|
|
return (s32)((*pA1)->GetDistanceToTarget() - (*pA2)->GetDistanceToTarget());
|
|
}
|
|
|
|
bool CCombatMeleeGroup::Update( void )
|
|
{
|
|
if( !IsTargetValid() )
|
|
return false;
|
|
|
|
int nCount = m_aOpponents.GetCount();
|
|
if( nCount < 1 )
|
|
return false;
|
|
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
Assert(m_aOpponents[ i ]->m_pMeleeGroup == this);
|
|
|
|
Vec3V vTargetToPed = m_aOpponents[ i ]->GetPedPos() - GetTargetPos();
|
|
m_aOpponents[ i ]->SetDistanceToTarget( MagSquared( vTargetToPed ).Getf() );
|
|
m_aOpponents[ i ]->SetAngleToTarget( fwAngle::LimitRadianAngle( rage::Atan2f( -vTargetToPed.GetXf(), vTargetToPed.GetYf() ) ) );
|
|
}
|
|
|
|
// Sort
|
|
m_aOpponents.QSort( 0, -1, CCombatMeleeGroup::SortByDistance );
|
|
|
|
//Check if we have not checked battle awareness.
|
|
if(!m_bHasCheckedBattleAwareness)
|
|
{
|
|
//Set the flag.
|
|
m_bHasCheckedBattleAwareness = true;
|
|
|
|
//Check if the target is a player.
|
|
const CPed* pTargetPed = GetTargetPed();
|
|
if(pTargetPed && pTargetPed->IsLocalPlayer())
|
|
{
|
|
//Check if the opponent is valid.
|
|
const CPed* pOpponent = m_aOpponents[0]->GetPed();
|
|
if(pOpponent && pOpponent->PopTypeIsRandom())
|
|
{
|
|
//Check if the opponent is close.
|
|
float fDistance = m_aOpponents[0]->GetDistanceToTarget();
|
|
static dev_float s_fMaxDistance = 15.0f;
|
|
if(fDistance < s_fMaxDistance)
|
|
{
|
|
//Set the battle awareness.
|
|
pTargetPed->GetPedIntelligence()->IncreaseBattleAwareness(CPedIntelligence::BATTLE_AWARENESS_MELEE_FIGHT, false, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// walk through once more and update the melee opponent accordingly
|
|
float fBaseHeading = 0.0f;
|
|
fBaseHeading = m_aOpponents[ 0 ]->GetAngleToTarget();
|
|
|
|
bool bInInactiveTauntMode = false;
|
|
bool bForceIgnoreMeleeActiveCombatant = false;
|
|
bool bForceIgnoreMaxMeleeActiveSupportCombatant = false;
|
|
CTaskMelee* pTaskForActiveCombatant = NULL;
|
|
float fAngleSpread = fBaseHeading;
|
|
float fSpreadOffset = 0.0f;
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
CPed* pPed = m_aOpponents[ i ]->GetPed();
|
|
if( pPed )
|
|
{
|
|
Assert( pPed->GetPedIntelligence() );
|
|
CTaskMelee* pTaskMelee = pPed->GetPedIntelligence()->GetTaskMelee();
|
|
if( pTaskMelee )
|
|
{
|
|
bInInactiveTauntMode = pTaskMelee->ShouldBeInInactiveTauntMode() || pTaskMelee->GetIsTaskFlagSet( CTaskMelee::MF_CannotFindNavRoute );
|
|
bForceIgnoreMeleeActiveCombatant = pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_ForceIgnoreMeleeActiveCombatant );
|
|
bForceIgnoreMaxMeleeActiveSupportCombatant = pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_ForceIgnoreMaxMeleeActiveSupportCombatants );
|
|
|
|
// First figure out the scale direction
|
|
fSpreadOffset = SubtractAngleShorter( fBaseHeading, m_aOpponents[ i ]->GetAngleToTarget() ) > 0.0f ? -1.0f : 1.0f;
|
|
|
|
// Scale
|
|
fSpreadOffset *= (float)i;
|
|
|
|
// Random angular offset
|
|
static dev_float sfAngleSpreadMin = 7.5f * DtoR;
|
|
static dev_float sfAngleSpreadMax = 12.5f * DtoR;
|
|
fSpreadOffset *= fwRandom::GetRandomNumberInRange( sfAngleSpreadMin, sfAngleSpreadMax );
|
|
|
|
// Apply
|
|
fAngleSpread = fwAngle::LimitRadianAngle( fBaseHeading + fSpreadOffset );
|
|
|
|
if( ( i - CCombatMeleeManager::sm_nMaxNumActiveCombatants ) < 0 && !bForceIgnoreMeleeActiveCombatant && !bInInactiveTauntMode )
|
|
{
|
|
pTaskMelee->SetAngleSpread( fAngleSpread );
|
|
pTaskMelee->SetQueueType( CTaskMelee::QT_ActiveCombatant );
|
|
|
|
// Set the task for the active combatant
|
|
pTaskForActiveCombatant = pTaskMelee;
|
|
}
|
|
else if( ( i - CCombatMeleeManager::sm_nMaxNumActiveSupportCombatants ) < 0 || bForceIgnoreMaxMeleeActiveSupportCombatant)
|
|
{
|
|
pTaskMelee->SetAngleSpread( fAngleSpread );
|
|
pTaskMelee->SetQueueType( CTaskMelee::QT_SupportCombatant );
|
|
}
|
|
else
|
|
{
|
|
pTaskMelee->SetAngleSpread( fAngleSpread );
|
|
pTaskMelee->SetQueueType( CTaskMelee::QT_InactiveObserver );
|
|
}
|
|
|
|
//If there are more than 3 peds fighting the same target, send the extra random civilians away.
|
|
static const int s_iMaxCivilians = 3;
|
|
if((i >= s_iMaxCivilians) && pPed->PopTypeIsRandom() && pPed->IsCivilianPed())
|
|
{
|
|
//Check if the task is valid.
|
|
CTaskThreatResponse* pTaskThreatResponse = static_cast<CTaskThreatResponse *>(
|
|
pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_THREAT_RESPONSE));
|
|
if(pTaskThreatResponse)
|
|
{
|
|
//Change the decision from combat to flee.
|
|
pTaskThreatResponse->SetFleeFromCombat(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check if the task for the active combatant is valid.
|
|
if(pTaskForActiveCombatant)
|
|
{
|
|
//Check if the target is moving away from the active combatant.
|
|
const CPed* pActiveCombatant = pTaskForActiveCombatant->GetPed();
|
|
if(IsTargetMovingAwayFromPed(*pActiveCombatant))
|
|
{
|
|
if(m_uTimeTargetStartedMovingAwayFromActiveCombatant == 0)
|
|
{
|
|
m_uTimeTargetStartedMovingAwayFromActiveCombatant = fwTimer::GetTimeInMilliseconds();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_uTimeTargetStartedMovingAwayFromActiveCombatant = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_uTimeTargetStartedMovingAwayFromActiveCombatant = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCombatMeleeGroup::Insert( CCombatMeleeOpponent& opponent )
|
|
{
|
|
Assert(!CCombatManager::GetMeleeCombatManager()->IsMeleeOpponentInGroup(&opponent));
|
|
|
|
if(Verifyf(!opponent.GetMeleeGroup(), "Trying to add a melee opponent more than once!"))
|
|
{
|
|
opponent.m_pMeleeGroup = this;
|
|
|
|
if( m_aOpponents.GetCount() < MAX_NUM_OPPONENTS_PER_GROUP )
|
|
{
|
|
m_aOpponents.Append() = &opponent;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CCombatMeleeGroup::Remove( CCombatMeleeOpponent& opponent )
|
|
{
|
|
if(Verifyf(opponent.GetMeleeGroup() == this, "Removing melee opponent from wrong group!"))
|
|
{
|
|
opponent.m_pMeleeGroup = NULL;
|
|
|
|
int nCount = m_aOpponents.GetCount();
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
if( m_aOpponents[ i ] == &opponent )
|
|
{
|
|
m_aOpponents[ i ]->Shutdown();
|
|
m_aOpponents.Delete( i );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bool CCombatMeleeGroup::HasAllyInDirection( const CPed* pCombatPed, eAllyDirection allyDirection )
|
|
{
|
|
if( pCombatPed && IsPedInMeleeGroup( pCombatPed ) )
|
|
{
|
|
static dev_float sfAngularThreshold = 35.0f * DtoR;
|
|
Vec3V vAllyDirection( VEC3_ZERO );
|
|
ScalarV vDotProduct( 0.0f );
|
|
int nCount = m_aOpponents.GetCount();
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
CPed* pOpponentPed = m_aOpponents[ i ]->GetPed();
|
|
if( pOpponentPed && pOpponentPed != pCombatPed )
|
|
{
|
|
// Calculate the difference between the combat peds
|
|
vAllyDirection = pOpponentPed->GetTransform().GetPosition() - pCombatPed->GetTransform().GetPosition();
|
|
vAllyDirection = Normalize( vAllyDirection );
|
|
|
|
switch( allyDirection )
|
|
{
|
|
case AD_BACK_LEFT:
|
|
{
|
|
// first make sure they are behind
|
|
vDotProduct = Dot( vAllyDirection, pCombatPed->GetTransform().GetB() );
|
|
if( vDotProduct.Getf() > 0.0f )
|
|
continue;
|
|
|
|
// otherwise they are behind and we can test to see if they are on the right side
|
|
vDotProduct = Dot( vAllyDirection, Negate( pCombatPed->GetTransform().GetA() ) );
|
|
if( vDotProduct.Getf() > 0.0f && vDotProduct.Getf() < sfAngularThreshold )
|
|
return true;
|
|
break;
|
|
}
|
|
case AD_BACK_RIGHT:
|
|
{
|
|
// first make sure they are behind
|
|
vDotProduct = Dot( vAllyDirection, pCombatPed->GetTransform().GetB() );
|
|
if( vDotProduct.Getf() > 0.0f )
|
|
continue;
|
|
|
|
// otherwise they are behind and we can test to see if they are on the right side
|
|
vDotProduct = Dot( vAllyDirection, pCombatPed->GetTransform().GetA() );
|
|
if( vDotProduct.Getf() > 0.0f && vDotProduct.Getf() < sfAngularThreshold )
|
|
return true;
|
|
break;
|
|
}
|
|
case AD_FRONT:
|
|
{
|
|
vDotProduct = Dot( vAllyDirection, pCombatPed->GetTransform().GetB() );
|
|
if( vDotProduct.Getf() > 0.0f )
|
|
return true;
|
|
break;
|
|
}
|
|
case AD_BACK:
|
|
{
|
|
vDotProduct = Dot( vAllyDirection, pCombatPed->GetTransform().GetB() );
|
|
if( vDotProduct.Getf() < 0.0f )
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCombatMeleeGroup::IsPedInMeleeGroup( const CPed* pCombatPed )
|
|
{
|
|
if( pCombatPed )
|
|
{
|
|
int nCount = m_aOpponents.GetCount();
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
if( m_aOpponents[ i ]->GetPed() == pCombatPed )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCombatMeleeGroup::HasActiveCombatant( void )
|
|
{
|
|
CPed* pPed = NULL;
|
|
CTaskMelee* pTaskMelee = NULL;
|
|
int nCount = m_aOpponents.GetCount();
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
pPed = m_aOpponents[ i ]->GetPed();
|
|
if( pPed )
|
|
{
|
|
Assert( pPed->GetPedIntelligence() );
|
|
pTaskMelee = pPed->GetPedIntelligence()->GetTaskMelee();
|
|
if( pTaskMelee && pTaskMelee->GetQueueType() == CTaskMelee::QT_ActiveCombatant )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCombatMeleeGroup::IsTargetMovingAwayFromPed(const CPed& rPed) const
|
|
{
|
|
//Ensure the target is a ped.
|
|
if(!m_Target || !m_Target->GetIsTypePed())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Check if the target is on foot.
|
|
Vec3V vPosition;
|
|
Vec3V vVelocity;
|
|
const CPed* pTarget = static_cast<const CPed *>(m_Target.Get());
|
|
if(pTarget->GetIsOnFoot())
|
|
{
|
|
vPosition = pTarget->GetTransform().GetPosition();
|
|
vVelocity = VECTOR3_TO_VEC3V(pTarget->GetVelocity());
|
|
}
|
|
//Check if the target is in a vehicle.
|
|
else if(pTarget->GetIsInVehicle())
|
|
{
|
|
vPosition = pTarget->GetVehiclePedInside()->GetTransform().GetPosition();
|
|
vVelocity = VECTOR3_TO_VEC3V(pTarget->GetVehiclePedInside()->GetVelocity());
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the target is not strafing.
|
|
if (pTarget->IsStrafing())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the velocity exceeds the threshold.
|
|
ScalarV scSpeedSq = MagSquared(vVelocity);
|
|
static dev_float s_fMinSpeed = 0.5f;
|
|
ScalarV scMinSpeedSq = ScalarVFromF32(square(s_fMinSpeed));
|
|
if(IsLessThanAll(scSpeedSq, scMinSpeedSq))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the target is moving away from the ped.
|
|
Vec3V vPedToTarget = Subtract(vPosition, rPed.GetTransform().GetPosition());
|
|
ScalarV scDot = Dot(vPedToTarget, vVelocity);
|
|
if(IsLessThanAll(scDot, ScalarV(V_ZERO)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const CPed* CCombatMeleeGroup::GetTargetPed( void ) const
|
|
{
|
|
//Ensure the target is valid.
|
|
if(!m_Target)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//Ensure the target is a ped.
|
|
if(!m_Target->GetIsTypePed())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return static_cast<const CPed *>(m_Target.Get());
|
|
}
|
|
|
|
#if !__FINAL
|
|
void CCombatMeleeGroup::DebugRender( void ) const
|
|
{
|
|
#if DEBUG_DRAW
|
|
|
|
#endif // DEBUG_DRAW
|
|
}
|
|
|
|
#endif // !__FINAL
|
|
|
|
#if __BANK
|
|
|
|
bool CCombatMeleeGroup::ContainsOpponent( CCombatMeleeOpponent *pOpponent )
|
|
{
|
|
int nCount = m_aOpponents.GetCount();
|
|
for( int i = 0; i < nCount; i++ )
|
|
{
|
|
if(m_aOpponents[i] == pOpponent)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CCombatMeleeOpponent::CCombatMeleeOpponent()
|
|
: m_pMeleeGroup( NULL )
|
|
, m_pPed( NULL )
|
|
, m_fDistToTarget( LARGE_FLOAT )
|
|
, m_fAngleToTaget( 0.0f )
|
|
{
|
|
}
|
|
|
|
CCombatMeleeOpponent::~CCombatMeleeOpponent()
|
|
{
|
|
Assert( !m_pPed ); // User should have called Shutdown().
|
|
|
|
//! Verify that this opponent has been removed from melee group.
|
|
Assert(!CCombatManager::GetMeleeCombatManager()->IsMeleeOpponentInGroup(this));
|
|
}
|
|
|
|
void CCombatMeleeOpponent::Init( CPed& ped )
|
|
{
|
|
Assert( !m_pPed );
|
|
m_pPed = &ped;
|
|
}
|
|
|
|
void CCombatMeleeOpponent::Shutdown( void )
|
|
{
|
|
m_pPed = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CNavMeshCircleTestHelper::CNavMeshCircleTestHelper()
|
|
{
|
|
m_bWaitingForLosResult = false;
|
|
m_hPathHandle = PATH_HANDLE_NULL;
|
|
}
|
|
|
|
|
|
CNavMeshCircleTestHelper::~CNavMeshCircleTestHelper()
|
|
{
|
|
ResetTest();
|
|
}
|
|
|
|
|
|
bool CNavMeshCircleTestHelper::StartLosTest(Vec3V_In centerV, float radius, bool bTestDynamicObjects)
|
|
{
|
|
ResetTest();
|
|
|
|
// CPathServer has a limit to how many segments can be tested in a single request.
|
|
CompileTimeAssert(kNumSegments + 1 <= MAX_LINEOFSIGHT_POINTS);
|
|
|
|
Vec3V vVerts[kNumSegments + 1];
|
|
const int numVerts = GenerateVerts(centerV, radius, vVerts, NELEM(vVerts));
|
|
|
|
bool bQuitAtFirstLOSFail = true;
|
|
m_hPathHandle = CPathServer::RequestLineOfSight((const Vector3*)&vVerts[0], numVerts, 0.0f, bTestDynamicObjects, bQuitAtFirstLOSFail);
|
|
if(m_hPathHandle != PATH_HANDLE_NULL)
|
|
{
|
|
m_bWaitingForLosResult = true;
|
|
}
|
|
return m_bWaitingForLosResult;
|
|
}
|
|
|
|
|
|
bool CNavMeshCircleTestHelper::GetTestResults(bool& bLosIsClear)
|
|
{
|
|
Assert(m_bWaitingForLosResult);
|
|
|
|
EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_hPathHandle);
|
|
if(ret == ERequest_NotReady)
|
|
{
|
|
m_bWaitingForLosResult = false;
|
|
|
|
EPathServerErrorCode errCode = CPathServer::GetLineOfSightResult(m_hPathHandle, bLosIsClear);
|
|
|
|
m_hPathHandle = PATH_HANDLE_NULL;
|
|
|
|
if(errCode == PATH_FOUND)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
bLosIsClear = false;
|
|
return true;
|
|
}
|
|
}
|
|
else if(ret == ERequest_NotFound)
|
|
{
|
|
m_hPathHandle = PATH_HANDLE_NULL;
|
|
bLosIsClear = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void CNavMeshCircleTestHelper::ResetTest()
|
|
{
|
|
if(m_bWaitingForLosResult)
|
|
{
|
|
if(m_hPathHandle != PATH_HANDLE_NULL)
|
|
{
|
|
CPathServer::CancelRequest(m_hPathHandle);
|
|
m_hPathHandle = PATH_HANDLE_NULL;
|
|
}
|
|
m_bWaitingForLosResult = false;
|
|
}
|
|
}
|
|
|
|
#if !__FINAL
|
|
|
|
void CNavMeshCircleTestHelper::DebugDraw(Vec3V_In DEBUG_DRAW_ONLY(centerV), float DEBUG_DRAW_ONLY(radius), Color32 DEBUG_DRAW_ONLY(col))
|
|
{
|
|
#if DEBUG_DRAW
|
|
Vec3V vVerts[kNumSegments + 1];
|
|
const int numVerts = GenerateVerts(centerV, radius, vVerts, NELEM(vVerts));
|
|
|
|
for(int i = 0; i < numVerts - 1; i++)
|
|
{
|
|
grcDebugDraw::Line(vVerts[i], vVerts[i + 1], col);
|
|
}
|
|
#endif // DEBUG_DRAW
|
|
}
|
|
|
|
#endif // !__FINAL
|
|
|
|
int CNavMeshCircleTestHelper::GenerateVerts(Vec3V_In centerV, float radius, Vec3V* pVertexArray, int ASSERT_ONLY(maxVerts))
|
|
{
|
|
Assert(maxVerts >= kNumSegments + 1);
|
|
|
|
const float centerX = centerV.GetXf();
|
|
const float centerY = centerV.GetYf();
|
|
const ScalarV centerZV = centerV.GetZ();
|
|
const float deltaAngle = 2.0f*PI/kNumSegments;
|
|
|
|
float angle = 0.0f;
|
|
int numVerts = 0;
|
|
for(int i = 0; i < kNumSegments + 1; i++)
|
|
{
|
|
Vec3V& ptV = pVertexArray[numVerts++];
|
|
float dirX, dirY;
|
|
cos_and_sin(dirX, dirY, angle);
|
|
ptV.SetXf(centerX + radius*dirX);
|
|
ptV.SetYf(centerY + radius*dirY);
|
|
ptV.SetZ(centerZV);
|
|
angle += deltaAngle;
|
|
}
|
|
|
|
return numVerts;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CCombatMountedAttackerGroup::CircleData::Reset()
|
|
{
|
|
if(m_FlagActive)
|
|
{
|
|
while(1)
|
|
{
|
|
atDNode<CCombatMountedAttacker*>* pNode = m_AttackersUsingCircle.PopHead();
|
|
if(pNode)
|
|
{
|
|
Assert(pNode->Data->m_InCircle == this);
|
|
pNode->Data->m_InCircle = NULL;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_CenterAndRadius = Vec4V(V_ZERO);
|
|
m_FlagActive = m_FlagTested = m_FlagTestedFreeFromObstructions = false;
|
|
m_RadiusIndex = -1;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CCombatMountedAttackerGroup::CCombatMountedAttackerGroup()
|
|
: m_CircleTestsRequested(false)
|
|
, m_FlagActive(false)
|
|
, m_TimeNoGood(0.0f)
|
|
, m_CurrentCircleTestRadiusIndex(-1)
|
|
, m_NextCircleTestRadiusIndex(-1)
|
|
, m_CircleBeingTested(-1)
|
|
{
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
m_Circles[i].Reset();
|
|
}
|
|
|
|
m_TimeSinceLastCircleJoined = LARGE_FLOAT;
|
|
m_LastCircleJoined = -1;
|
|
}
|
|
|
|
|
|
CCombatMountedAttackerGroup::~CCombatMountedAttackerGroup()
|
|
{
|
|
Assert(!m_FlagActive);
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::Init(const CEntity& tgt)
|
|
{
|
|
Assert(!m_Target);
|
|
Assert(!m_FlagActive);
|
|
m_FlagActive = true;
|
|
|
|
m_TimeSinceLastCircleJoined = LARGE_FLOAT;
|
|
m_LastCircleJoined = -1;
|
|
|
|
m_CircleTestsRequested = false;
|
|
|
|
m_Target = &tgt;
|
|
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
m_Circles[i].m_FlagActive = false;
|
|
m_Circles[i].Reset();
|
|
}
|
|
|
|
Assert(m_CircleBeingTested < 0);
|
|
|
|
m_CurrentCircleTestRadiusIndex = m_NextCircleTestRadiusIndex = -1;
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::Shutdown()
|
|
{
|
|
if(m_CircleBeingTested >= 0)
|
|
{
|
|
m_CircleTestHelper.ResetTest();
|
|
m_CircleBeingTested = -1;
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
atDNode<CCombatMountedAttacker*>* pHeadNode = m_List.GetHead();
|
|
if(!pHeadNode)
|
|
{
|
|
break;
|
|
}
|
|
Remove(*pHeadNode->Data);
|
|
}
|
|
|
|
Assert(m_FlagActive);
|
|
m_FlagActive = false;
|
|
|
|
m_Target.Reset(NULL);
|
|
}
|
|
|
|
|
|
bool CCombatMountedAttackerGroup::Update()
|
|
{
|
|
if(!m_Target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!m_List.GetHead())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const float deltaTime = fwTimer::GetTimeStep();
|
|
|
|
m_TimeSinceLastCircleJoined += deltaTime;
|
|
|
|
CheckExistingCircles();
|
|
UpdateCircleTests();
|
|
|
|
#if 0
|
|
// MAGIC!
|
|
const int maxMembers = GetGroup().GetNumMembers();
|
|
static const float removeTime = 1.5f;
|
|
static const float timeNoChange = 2.0f;
|
|
|
|
if(m_TimeNoGood > timeNoChange)
|
|
{
|
|
float dt = m_TimeNoGood - timeNoChange;
|
|
m_NumDesiredMembers = Max((int)(maxMembers - dt*(1.0f/removeTime)), 0);
|
|
}
|
|
else
|
|
{
|
|
m_NumDesiredMembers = -1;
|
|
}
|
|
#endif
|
|
|
|
for(atDNode<CCombatMountedAttacker*>* pNode = m_List.GetHead(); pNode; pNode = pNode->GetNext())
|
|
{
|
|
BehaviorUpdate(*pNode->Data);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::Insert(CCombatMountedAttacker &attacker)
|
|
{
|
|
Assert(!attacker.m_AttackerGroup);
|
|
attacker.m_AttackerGroup = this;
|
|
|
|
m_List.Append(attacker.m_ListNode);
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::Remove(CCombatMountedAttacker &attacker)
|
|
{
|
|
if(attacker.m_InCircle)
|
|
{
|
|
RemoveMemberFromCircles(attacker);
|
|
}
|
|
|
|
Assert(attacker.m_AttackerGroup == this);
|
|
attacker.m_AttackerGroup = NULL;
|
|
|
|
m_List.PopNode(attacker.m_ListNode);
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::RemoveMemberFromCircles(CCombatMountedAttacker& attacker)
|
|
{
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
CircleData &c = m_Circles[i];
|
|
Assert(c.m_FlagActive || !c.m_AttackersUsingCircle.GetHead());
|
|
|
|
if(attacker.m_InCircle == &c)
|
|
{
|
|
c.m_AttackersUsingCircle.PopNode(attacker.m_CircleNode);
|
|
attacker.m_InCircle = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
attacker.m_CircleIsNew = false;
|
|
}
|
|
|
|
#if !__FINAL
|
|
|
|
void CCombatMountedAttackerGroup::DebugRender() const
|
|
{
|
|
#if DEBUG_DRAW
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
const CircleData &c = m_Circles[i];
|
|
|
|
if(!c.m_FlagActive)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Color32 col;
|
|
if(c.m_FlagTested)
|
|
{
|
|
if(c.m_FlagTestedFreeFromObstructions)
|
|
{
|
|
col = Color32(0x40, 0xff, 0x40, 0x80);
|
|
}
|
|
else
|
|
{
|
|
col = Color32(0xff, 0x40, 0x40, 0x80);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
col = Color32(0x80, 0x80, 0x80, 0x80);
|
|
}
|
|
|
|
CNavMeshCircleTestHelper::DebugDraw(c.m_CenterAndRadius.GetXYZ(),
|
|
c.m_CenterAndRadius.GetWf(), col);
|
|
|
|
Vector3 p1;
|
|
p1 = VEC3V_TO_VECTOR3(c.m_CenterAndRadius.GetXYZ());
|
|
p1.x += c.m_CenterAndRadius.GetWf();
|
|
|
|
char buff[256];
|
|
formatf(buff, "%c", 'A' + i);
|
|
grcDebugDraw::Text(p1, col, 0, 0, buff);
|
|
|
|
const atDNode<CCombatMountedAttacker*>* pNode;
|
|
for(pNode = c.m_AttackersUsingCircle.GetHead(); pNode; pNode = pNode->GetNext())
|
|
{
|
|
CPed* pPed = pNode->Data->GetPed();
|
|
Color32 col2;
|
|
col2 = Color32(0xff, 0xff, 0x40, 0x80);
|
|
grcDebugDraw::Line(VECTOR3_TO_VEC3V(p1), pPed->GetTransform().GetPosition(), col2);
|
|
}
|
|
}
|
|
#endif // DEBUG_DRAW
|
|
}
|
|
|
|
#endif // !__FINAL
|
|
|
|
void CCombatMountedAttackerGroup::BehaviorUpdate(CCombatMountedAttacker &member)
|
|
{
|
|
if(!member.m_RequestCircle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int activeCircleIndices[kMaxCircles];
|
|
int numActiveCircles = 0;
|
|
|
|
int invalidCircle = -1;
|
|
if(m_LastCircleJoined >= 0 && m_TimeSinceLastCircleJoined < CTaskMoveCombatMounted::sm_Tunables.m_MinTimeSinceSameCircleJoined)
|
|
{
|
|
invalidCircle = m_LastCircleJoined;
|
|
}
|
|
|
|
int usingCircle = -1;
|
|
for(int circleIndex = 0; circleIndex < kMaxCircles; circleIndex++)
|
|
{
|
|
const CircleData &c = m_Circles[circleIndex];
|
|
|
|
if(c.m_FlagActive)
|
|
{
|
|
if(member.m_InCircle == &c)
|
|
{
|
|
usingCircle = circleIndex;
|
|
break;
|
|
}
|
|
|
|
if(invalidCircle == circleIndex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Assert(numActiveCircles < kMaxCircles);
|
|
activeCircleIndices[numActiveCircles++] = circleIndex;
|
|
}
|
|
}
|
|
|
|
if(usingCircle < 0 && numActiveCircles > 0
|
|
&& m_TimeSinceLastCircleJoined > CTaskMoveCombatMounted::sm_Tunables.m_MinTimeSinceAnyCircleJoined)
|
|
{
|
|
int r = g_ReplayRand.GetRanged(0, numActiveCircles - 1);
|
|
Assert(r >= 0 && r < kMaxCircles);
|
|
|
|
const int circleIndex = activeCircleIndices[r];
|
|
CircleData &c = m_Circles[circleIndex];
|
|
|
|
if(c.m_FlagActive)
|
|
{
|
|
Assert(!member.m_InCircle);
|
|
member.m_InCircle = &c;
|
|
c.m_AttackersUsingCircle.Append(member.m_CircleNode);
|
|
|
|
member.m_CircleIsNew = true;
|
|
|
|
usingCircle = circleIndex;
|
|
|
|
m_TimeSinceLastCircleJoined = 0.0f;
|
|
m_LastCircleJoined = circleIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::CheckExistingCircles()
|
|
{
|
|
bool considerGood = false;
|
|
|
|
if(HasValidTarget())
|
|
{
|
|
const Vec3V tgtPosV = GetTargetPos();
|
|
|
|
bool hasGood = false;
|
|
bool good[kMaxCircles];
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
CircleData &c = m_Circles[i];
|
|
|
|
if(!c.m_FlagActive)
|
|
{
|
|
good[i] = false;
|
|
continue;
|
|
}
|
|
|
|
const float thresholdSq = square(c.m_CenterAndRadius.GetWf()*0.5f);
|
|
|
|
// const float distSq = ((const Vector3&)c.m_CenterAndRadius).FlatDist2(tgtPos);
|
|
Vec3V deltaV = Subtract(c.m_CenterAndRadius.GetXYZ(), tgtPosV);
|
|
deltaV.SetZ(ScalarV(V_ZERO));
|
|
const ScalarV distSqV = MagSquared(deltaV);
|
|
const float distSq = distSqV.Getf();
|
|
|
|
bool g;
|
|
good[i] = g = distSq <= thresholdSq;
|
|
if(g)
|
|
hasGood = true;
|
|
}
|
|
|
|
if(hasGood || !m_CircleTestsRequested)
|
|
{
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
CircleData &c = m_Circles[i];
|
|
if(c.m_FlagActive && !good[i])
|
|
{
|
|
c.Reset();
|
|
if(i == m_LastCircleJoined)
|
|
{
|
|
m_LastCircleJoined = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
considerGood = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
CircleData &c = m_Circles[i];
|
|
c.Reset();
|
|
}
|
|
m_LastCircleJoined = -1;
|
|
}
|
|
|
|
if(considerGood)
|
|
{
|
|
m_TimeNoGood = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
m_TimeNoGood += TIME.GetSeconds();
|
|
}
|
|
}
|
|
|
|
|
|
void CCombatMountedAttackerGroup::UpdateCircleTests()
|
|
{
|
|
bool gotRequests = m_CircleTestsRequested;
|
|
m_CircleTestsRequested = false;
|
|
|
|
if(m_CircleBeingTested >= 0)
|
|
{
|
|
bool losIsClear = false;
|
|
if(m_CircleTestHelper.GetTestResults(losIsClear))
|
|
{
|
|
Assert(m_CircleBeingTested >= 0 && m_CircleBeingTested < kMaxCircles);
|
|
CircleData &c = m_Circles[m_CircleBeingTested];
|
|
|
|
Assert(!c.m_FlagActive);
|
|
|
|
c.m_CenterAndRadius = m_CircleBeingTestedCenterAndRadius;
|
|
// c.m_FlagActive = true;
|
|
c.m_FlagTested = true;
|
|
c.m_FlagTestedFreeFromObstructions = losIsClear;
|
|
|
|
c.m_FlagActive = c.m_FlagTestedFreeFromObstructions;
|
|
|
|
Assert(m_CurrentCircleTestRadiusIndex >= 0);
|
|
c.m_RadiusIndex = m_CurrentCircleTestRadiusIndex;
|
|
|
|
m_CircleBeingTested = -1;
|
|
m_CurrentCircleTestRadiusIndex = -1;
|
|
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(!gotRequests)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!HasValidTarget())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float tgtMoveDistSq = square(CTaskMoveCombatMounted::sm_Tunables.m_CircleTestsMoveDistToTestNewPos);
|
|
|
|
int availableCircle = -1;
|
|
m_CircleBeingTested = -1;
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
if(!m_Circles[i].m_FlagActive)
|
|
{
|
|
availableCircle = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(availableCircle < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
const Vec3V targetPositionV = GetTargetPos();
|
|
if(m_NextCircleTestRadiusIndex >= 0)
|
|
{
|
|
Vec3V deltaV = Subtract(targetPositionV, m_TargetPosForFirstCircleTest);
|
|
deltaV.SetZ(ScalarV(V_ZERO));
|
|
|
|
// if(targetPosition.FlatDist2(m_TargetPosForFirstCircleTest) >= tgtMoveDistSq)
|
|
if(MagSquared(deltaV).Getf() >= tgtMoveDistSq)
|
|
{
|
|
m_NextCircleTestRadiusIndex = -1;
|
|
}
|
|
}
|
|
|
|
if(m_NextCircleTestRadiusIndex < 0)
|
|
{
|
|
m_TargetPosForFirstCircleTest = targetPositionV;
|
|
m_NextCircleTestRadiusIndex = 0;
|
|
}
|
|
|
|
if(m_NextCircleTestRadiusIndex >= CTaskMoveCombatMounted::sm_Tunables.m_CircleTestRadii.GetCount())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool found = false;
|
|
for(int i = 0; i < kMaxCircles; i++)
|
|
{
|
|
if(!m_Circles[i].m_FlagActive)
|
|
{
|
|
continue;
|
|
}
|
|
if(m_Circles[i].m_RadiusIndex == m_NextCircleTestRadiusIndex)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
|
|
m_CircleBeingTested = availableCircle;
|
|
|
|
// m_CircleBeingTestedCenterAndRadius.SetVector3(m_MovementModel.GetTargetPosition());
|
|
m_CircleBeingTestedCenterAndRadius.SetXYZ(GetTargetPos());
|
|
|
|
m_CircleBeingTestedCenterAndRadius.SetWf(CTaskMoveCombatMounted::sm_Tunables.m_CircleTestRadii[m_NextCircleTestRadiusIndex]);
|
|
|
|
m_CurrentCircleTestRadiusIndex = m_NextCircleTestRadiusIndex;
|
|
|
|
// Not sure about testing for dynamic objects here. This code doesn't
|
|
// really deal with changing environments (it assumes that tests that have
|
|
// passed don't have to be repeated, etc), but if we don't include dynamic
|
|
// objects in the tests, circles can pass through fences and other breakable
|
|
// objects, it seems.
|
|
bool testDynamicObjects = true;
|
|
|
|
if(!m_CircleTestHelper.StartLosTest(m_CircleBeingTestedCenterAndRadius.GetXYZ(), m_CircleBeingTestedCenterAndRadius.GetWf(), testDynamicObjects))
|
|
{
|
|
m_CircleBeingTested = -1;
|
|
|
|
// Do we need to do something else in this case? Needs to be tested.
|
|
break;
|
|
}
|
|
}
|
|
m_NextCircleTestRadiusIndex++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CCombatMountedManager::Update()
|
|
{
|
|
int i;
|
|
const int num = m_MaxAttacks;
|
|
for(i = 0; i < num; i++)
|
|
{
|
|
CCombatMountedAttackerGroup &atk = m_Attacks[i];
|
|
if(!atk.IsActive())
|
|
continue;
|
|
|
|
if(!atk.Update())
|
|
{
|
|
DestroyAttack(atk);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CCombatMountedManager::DestroyAllAttacks()
|
|
{
|
|
int i;
|
|
const int num = m_MaxAttacks;
|
|
for(i = 0; i < num; i++)
|
|
{
|
|
CCombatMountedAttackerGroup &atk = m_Attacks[i];
|
|
if(!atk.IsActive())
|
|
continue;
|
|
DestroyAttack(atk);
|
|
}
|
|
}
|
|
|
|
|
|
void CCombatMountedManager::DestroyAttack(CCombatMountedAttackerGroup &atk)
|
|
{
|
|
Assert(atk.IsActive());
|
|
|
|
Assert(&atk - m_Attacks >= 0 && &atk - m_Attacks < m_MaxAttacks);
|
|
atk.Shutdown();
|
|
|
|
m_NumAttacks--;
|
|
Assert(m_NumAttacks >= 0);
|
|
}
|
|
|
|
|
|
CCombatMountedAttackerGroup *CCombatMountedManager::FindAttackerGroup(const CEntity& tgt)
|
|
{
|
|
const int num = m_MaxAttacks;
|
|
int i;
|
|
for(i = 0; i < num; i++)
|
|
{
|
|
const CCombatMountedAttackerGroup &atk = m_Attacks[i];
|
|
if(!atk.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
if(atk.GetTarget() == &tgt)
|
|
{
|
|
return &m_Attacks[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
CCombatMountedAttackerGroup *CCombatMountedManager::FindOrCreateAttackerGroup(const CEntity& tgt)
|
|
{
|
|
CCombatMountedAttackerGroup *atk = FindAttackerGroup(tgt);
|
|
if(atk)
|
|
return atk;
|
|
atk = FindAvailableAttack();
|
|
if(!atk)
|
|
return atk; // == NULL
|
|
atk->Init(tgt);
|
|
m_NumAttacks++;
|
|
Assert(m_NumAttacks <= m_MaxAttacks);
|
|
|
|
return atk;
|
|
}
|
|
|
|
|
|
#if __BANK
|
|
|
|
void CCombatMountedManager::AddWidgets(bkBank &bank)
|
|
{
|
|
bank.PushGroup("Mounted Combat", false);
|
|
bank.AddToggle("Draw", &m_BankDraw);
|
|
bank.PopGroup();
|
|
}
|
|
|
|
#endif // __BANK
|
|
|
|
#if !__FINAL
|
|
|
|
void CCombatMountedManager::DebugRender()
|
|
{
|
|
#if __BANK
|
|
if(!m_BankDraw)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int num = m_MaxAttacks;
|
|
int i;
|
|
for(i = 0; i < num; i++)
|
|
{
|
|
const CCombatMountedAttackerGroup &atk = m_Attacks[i];
|
|
if(!atk.IsActive())
|
|
{
|
|
continue;
|
|
}
|
|
atk.DebugRender();
|
|
}
|
|
#endif // __BANK
|
|
}
|
|
|
|
#endif // !__FINAL
|
|
|
|
CCombatMountedManager::CCombatMountedManager()
|
|
: m_Attacks(NULL)
|
|
, m_MaxAttacks(0)
|
|
, m_NumAttacks(0)
|
|
{
|
|
const int maxAttacks = fwConfigManager::GetInstance().GetSizeOfPool(ATSTRINGHASH("CombatMountedManager_Attacks", 0x9aad80fc), CONFIGURED_FROM_FILE);
|
|
m_MaxAttacks = maxAttacks;
|
|
if(maxAttacks > 0)
|
|
{
|
|
m_Attacks = rage_new CCombatMountedAttackerGroup[maxAttacks];
|
|
}
|
|
|
|
#if __BANK
|
|
m_BankDraw = false;
|
|
#endif
|
|
}
|
|
|
|
|
|
CCombatMountedManager::~CCombatMountedManager()
|
|
{
|
|
DestroyAllAttacks();
|
|
|
|
Assert(!m_NumAttacks);
|
|
delete[] m_Attacks;
|
|
}
|
|
|
|
|
|
CCombatMountedAttackerGroup *CCombatMountedManager::FindAvailableAttack()
|
|
{
|
|
const int num = m_MaxAttacks;
|
|
|
|
if(m_NumAttacks >= num)
|
|
return NULL;
|
|
|
|
int i;
|
|
for(i = 0; i < num; i++)
|
|
{
|
|
CCombatMountedAttackerGroup &atk = m_Attacks[i];
|
|
if(!atk.IsActive())
|
|
{
|
|
return &atk;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CCombatMountedAttacker::CCombatMountedAttacker()
|
|
: m_AttackerGroup(NULL)
|
|
, m_Ped(NULL)
|
|
{
|
|
m_InCircle = NULL;
|
|
m_CircleNode.Data = this;
|
|
m_RequestCircle = false;
|
|
m_CircleIsNew = false;
|
|
m_ListNode.Data = this;
|
|
}
|
|
|
|
|
|
CCombatMountedAttacker::~CCombatMountedAttacker()
|
|
{
|
|
Assert(!m_Ped); // User should have called Shutdown().
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|