998 lines
25 KiB
C++
998 lines
25 KiB
C++
// FILE : TaskTargetUnreachable.h
|
|
// PURPOSE : Subtask of combat used for when the target is unreachable
|
|
|
|
// File header
|
|
#include "Task/Combat/Subtasks/TaskTargetUnreachable.h"
|
|
|
|
// Game headers
|
|
#include "pathserver/PathServer.h"
|
|
#include "Peds/Ped.h"
|
|
#include "Peds/PedIntelligence.h"
|
|
#include "task/Combat/TaskCombat.h"
|
|
#include "task/Combat/TaskNewCombat.h"
|
|
#include "task/General/TaskBasic.h"
|
|
#include "task/Movement/TaskIdle.h"
|
|
#include "task/Movement/TaskMoveWithinAttackWindow.h"
|
|
#include "task/Movement/TaskNavMesh.h"
|
|
#include "Task/Weapons/TaskWeaponBlocked.h"
|
|
|
|
AI_OPTIMISATIONS()
|
|
|
|
//=========================================================================
|
|
// CTaskTargetUnreachable
|
|
//=========================================================================
|
|
|
|
CTaskTargetUnreachable::Tunables CTaskTargetUnreachable::sm_Tunables;
|
|
IMPLEMENT_COMBAT_TASK_TUNABLES(CTaskTargetUnreachable, 0xf87bd311);
|
|
|
|
CTaskTargetUnreachable::CTaskTargetUnreachable(const CAITarget& rTarget, fwFlags8 uConfigFlags)
|
|
: m_Target(rTarget)
|
|
, m_uConfigFlags(uConfigFlags)
|
|
, m_fTimeUntilNextRouteSearch(0.0f)
|
|
{
|
|
m_routeSearchHelper.ResetSearch();
|
|
SetInternalTaskType(CTaskTypes::TASK_TARGET_UNREACHABLE);
|
|
}
|
|
|
|
CTaskTargetUnreachable::~CTaskTargetUnreachable()
|
|
{
|
|
}
|
|
|
|
void CTaskTargetUnreachable::SetTarget(const CAITarget& rTarget)
|
|
{
|
|
if(m_Target != rTarget)
|
|
{
|
|
m_routeSearchHelper.ResetSearch();
|
|
m_Target = rTarget;
|
|
|
|
if(GetState() == State_InExterior)
|
|
{
|
|
CTaskTargetUnreachableInExterior* pSubTask = static_cast<CTaskTargetUnreachableInExterior*>(FindSubTaskOfType(CTaskTypes::TASK_TARGET_UNREACHABLE_IN_EXTERIOR));
|
|
if(pSubTask)
|
|
{
|
|
pSubTask->SetTarget(rTarget);
|
|
}
|
|
}
|
|
else if(GetState() == State_InInterior)
|
|
{
|
|
CTaskTargetUnreachableInInterior* pSubTask = static_cast<CTaskTargetUnreachableInInterior*>(FindSubTaskOfType(CTaskTypes::TASK_TARGET_UNREACHABLE_IN_INTERIOR));
|
|
if(pSubTask)
|
|
{
|
|
pSubTask->SetTarget(rTarget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !__FINAL
|
|
void CTaskTargetUnreachable::Debug() const
|
|
{
|
|
CTask::Debug();
|
|
}
|
|
|
|
const char* CTaskTargetUnreachable::GetStaticStateName(s32 iState)
|
|
{
|
|
Assert(iState >= State_Start && iState <= State_Finish);
|
|
static const char* aStateNames[] =
|
|
{
|
|
"State_Start",
|
|
"State_InInterior",
|
|
"State_InExterior",
|
|
"State_Finish"
|
|
};
|
|
|
|
return aStateNames[iState];
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachable::ProcessPreFSM()
|
|
{
|
|
//Ensure the target is valid.
|
|
if(!m_Target.GetIsValid())
|
|
{
|
|
return FSM_Quit;
|
|
}
|
|
|
|
//Check if we should quit due to line of sight being clear.
|
|
if(ShouldQuitDueToLosClear())
|
|
{
|
|
return FSM_Quit;
|
|
}
|
|
|
|
// Check if we have a route to the target
|
|
if(ProcessRouteSearch())
|
|
{
|
|
return FSM_Quit;
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachable::UpdateFSM(const s32 iState, const FSM_Event iEvent)
|
|
{
|
|
FSM_Begin
|
|
|
|
FSM_State(State_Start)
|
|
FSM_OnUpdate
|
|
return Start_OnUpdate();
|
|
|
|
FSM_State(State_InInterior)
|
|
FSM_OnEnter
|
|
InInterior_OnEnter();
|
|
FSM_OnUpdate
|
|
return InInterior_OnUpdate();
|
|
|
|
FSM_State(State_InExterior)
|
|
FSM_OnEnter
|
|
InExterior_OnEnter();
|
|
FSM_OnUpdate
|
|
return InExterior_OnUpdate();
|
|
|
|
FSM_State(State_Finish)
|
|
FSM_OnUpdate
|
|
return FSM_Quit;
|
|
|
|
FSM_End
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachable::Start_OnUpdate()
|
|
{
|
|
//Grab the ped.
|
|
const CPed* pPed = GetPed();
|
|
|
|
//Check if the ped is in an exterior.
|
|
if(pPed->GetIsInExterior())
|
|
{
|
|
//Move to the exterior state.
|
|
SetState(State_InExterior);
|
|
}
|
|
else
|
|
{
|
|
//Move to the interior state.
|
|
SetState(State_InInterior);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachable::InInterior_OnEnter()
|
|
{
|
|
//Create the task.
|
|
CTask* pTask = rage_new CTaskTargetUnreachableInInterior(m_Target);
|
|
|
|
//Start the task.
|
|
SetNewTask(pTask);
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachable::InInterior_OnUpdate()
|
|
{
|
|
//Check if the sub-task has finished.
|
|
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
//Finish the task.
|
|
SetState(State_Finish);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachable::InExterior_OnEnter()
|
|
{
|
|
//Create the task.
|
|
CTask* pTask = rage_new CTaskTargetUnreachableInExterior(m_Target);
|
|
|
|
//Start the task.
|
|
SetNewTask(pTask);
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachable::InExterior_OnUpdate()
|
|
{
|
|
//Check if the sub-task has finished.
|
|
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
//Finish the task.
|
|
SetState(State_Finish);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
Vec3V_Out CTaskTargetUnreachable::GenerateTargetPositionForRouteSearch() const
|
|
{
|
|
//Use the last nav mesh intersection.
|
|
Vec3V vPosition;
|
|
if(CLastNavMeshIntersectionHelper::Get(m_Target, vPosition))
|
|
{
|
|
return vPosition;
|
|
}
|
|
|
|
//Get the target position.
|
|
m_Target.GetPosition(RC_VECTOR3(vPosition));
|
|
|
|
return vPosition;
|
|
}
|
|
|
|
bool CTaskTargetUnreachable::ShouldQuitDueToLosClear() const
|
|
{
|
|
//Ensure the flag is set.
|
|
if(!m_uConfigFlags.IsFlagSet(CF_QuitIfLosClear))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the targeting is valid.
|
|
CPedTargetting* pTargeting = GetPed()->GetPedIntelligence()->GetTargetting(false);
|
|
if(!pTargeting)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the entity is valid.
|
|
const CEntity* pEntity = m_Target.GetEntity();
|
|
if(!taskVerifyf(pEntity, "The entity is invalid."))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Ensure the LOS is clear.
|
|
if(pTargeting->GetLosStatus(pEntity, false) != Los_clear)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CTaskTargetUnreachable::ProcessRouteSearch()
|
|
{
|
|
// Only do these tests if told to
|
|
if(!m_uConfigFlags.IsFlagSet(CTaskTargetUnreachable::CF_QuitIfRouteFound))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(m_routeSearchHelper.IsSearchActive())
|
|
{
|
|
float fDistance = 0.0f;
|
|
Vector3 vLastPoint(0.0f, 0.0f, 0.0f);
|
|
SearchStatus eSearchStatus;
|
|
|
|
if(m_routeSearchHelper.GetSearchResults( eSearchStatus, fDistance, vLastPoint ))
|
|
{
|
|
m_routeSearchHelper.ResetSearch();
|
|
|
|
if(eSearchStatus == SS_SearchSuccessful)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are still looking for a route and our timer has run up we will start a new search
|
|
m_fTimeUntilNextRouteSearch -= GetTimeStep();
|
|
if(m_fTimeUntilNextRouteSearch <= 0.0f)
|
|
{
|
|
const CEntity* pTarget = m_Target.GetEntity();
|
|
if(pTarget && pTarget->GetIsTypePed() && IsTargetUnreachableUsingNavMesh(static_cast<const CPed&>(*pTarget)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CPed* pPed = GetPed();
|
|
Vec3V vTargetPosition = GenerateTargetPositionForRouteSearch();
|
|
|
|
// If the target is outside of our defensive area then we know we won't be able to reach them so don't run the route check
|
|
CDefensiveArea* pDefensiveArea = pPed->GetPedIntelligence()->GetDefensiveArea();
|
|
if(!pDefensiveArea || !pDefensiveArea->IsActive() || pDefensiveArea->IsPointInDefensiveArea(VEC3V_TO_VECTOR3(vTargetPosition)))
|
|
{
|
|
u64 iPathFlags = pPed->GetPedIntelligence()->GetNavCapabilities().BuildPathStyleFlags() | PATH_FLAG_NEVER_ENTER_WATER;
|
|
m_routeSearchHelper.StartSearch( pPed, VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), VEC3V_TO_VECTOR3(vTargetPosition), 1.0f, iPathFlags );
|
|
}
|
|
|
|
m_fTimeUntilNextRouteSearch = sm_Tunables.m_fTimeBetweenRouteSearches;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CTaskTargetUnreachable::IsTargetUnreachableUsingNavMesh(const CPed& rTarget)
|
|
{
|
|
CVehicle* pTargetVehicle = rTarget.GetVehiclePedInside();
|
|
if( pTargetVehicle && (pTargetVehicle->InheritsFromPlane() || pTargetVehicle->InheritsFromHeli()) && !pTargetVehicle->HasContactWheels() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//=========================================================================
|
|
// CTaskTargetUnreachableInInterior
|
|
//=========================================================================
|
|
|
|
CTaskTargetUnreachableInInterior::Tunables CTaskTargetUnreachableInInterior::sm_Tunables;
|
|
IMPLEMENT_COMBAT_TASK_TUNABLES(CTaskTargetUnreachableInInterior, 0xf2b71c9f);
|
|
|
|
CTaskTargetUnreachableInInterior::CTaskTargetUnreachableInInterior(const CAITarget& rTarget)
|
|
: m_Target(rTarget)
|
|
, m_fAdditionalProbeRotationZ(0.0f)
|
|
, m_fOriginalDirection(-LARGE_FLOAT)
|
|
, m_bHasFacingDirection(false)
|
|
, m_fBestFacingDistSqToIntersection(-LARGE_FLOAT)
|
|
, m_fBestFacingDirection(-LARGE_FLOAT)
|
|
{
|
|
m_asyncProbeHelper.ResetProbe();
|
|
SetInternalTaskType(CTaskTypes::TASK_TARGET_UNREACHABLE_IN_INTERIOR);
|
|
}
|
|
|
|
CTaskTargetUnreachableInInterior::~CTaskTargetUnreachableInInterior()
|
|
{
|
|
}
|
|
|
|
#if !__FINAL
|
|
void CTaskTargetUnreachableInInterior::Debug() const
|
|
{
|
|
CTask::Debug();
|
|
}
|
|
|
|
const char* CTaskTargetUnreachableInInterior::GetStaticStateName(s32 iState)
|
|
{
|
|
Assert(iState >= State_Start && iState <= State_Finish);
|
|
static const char* aStateNames[] =
|
|
{
|
|
"State_Start",
|
|
"State_FaceDirection",
|
|
"State_Idle",
|
|
"State_Finish"
|
|
};
|
|
|
|
return aStateNames[iState];
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInInterior::ProcessPreFSM()
|
|
{
|
|
//Ensure the target is valid.
|
|
if(!m_Target.GetIsValid())
|
|
{
|
|
return FSM_Quit;
|
|
}
|
|
|
|
ProcessAsyncProbes();
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInInterior::UpdateFSM(const s32 iState, const FSM_Event iEvent)
|
|
{
|
|
FSM_Begin
|
|
|
|
FSM_State(State_Start)
|
|
FSM_OnEnter
|
|
Start_OnEnter();
|
|
FSM_OnUpdate
|
|
return Start_OnUpdate();
|
|
|
|
FSM_State(State_FaceDirection)
|
|
FSM_OnEnter
|
|
FaceDirection_OnEnter();
|
|
FSM_OnUpdate
|
|
return FaceDirection_OnUpdate();
|
|
|
|
FSM_State(State_Idle)
|
|
FSM_OnEnter
|
|
Idle_OnEnter();
|
|
FSM_OnUpdate
|
|
return Idle_OnUpdate();
|
|
|
|
FSM_State(State_Finish)
|
|
FSM_OnUpdate
|
|
return FSM_Quit;
|
|
|
|
FSM_End
|
|
}
|
|
|
|
void CTaskTargetUnreachableInInterior::Start_OnEnter()
|
|
{
|
|
m_fOriginalDirection = fwAngle::LimitRadianAngleSafe(GetPed()->GetCurrentHeading());
|
|
SetNewTask( rage_new CTaskDoNothing(-1) );
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInInterior::Start_OnUpdate()
|
|
{
|
|
if(!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
SetState(State_Finish);
|
|
}
|
|
else if(m_bHasFacingDirection)
|
|
{
|
|
SetState(State_FaceDirection);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInInterior::FaceDirection_OnEnter()
|
|
{
|
|
// Face the best direction that we found
|
|
Vector3 vMyPosition = VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetPosition());
|
|
Vector3 vTargetPosition(0.0f, sm_Tunables.m_fDirectionTestProbeLength, 0.0f);
|
|
vTargetPosition.RotateZ(m_fBestFacingDirection);
|
|
vTargetPosition += vMyPosition;
|
|
|
|
SetNewTask( rage_new CTaskTurnToFaceEntityOrCoord(vTargetPosition) );
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInInterior::FaceDirection_OnUpdate()
|
|
{
|
|
if(!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
SetState(State_Idle);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInInterior::Idle_OnEnter()
|
|
{
|
|
SetNewTask( rage_new CTaskWeaponBlocked(CWeaponController::WCT_Aim) );
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInInterior::Idle_OnUpdate()
|
|
{
|
|
if(!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
SetState(State_Finish);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInInterior::ProcessAsyncProbes()
|
|
{
|
|
// If we've got a direction or haven't set our original direction then we shouldn't run any probes
|
|
if(m_bHasFacingDirection || m_fOriginalDirection < -PI)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check our results if the probe is already active
|
|
if(m_asyncProbeHelper.IsProbeActive())
|
|
{
|
|
Vector3 vIntersection;
|
|
ProbeStatus probeStatus;
|
|
if(m_asyncProbeHelper.GetProbeResultsWithIntersection(probeStatus, &vIntersection))
|
|
{
|
|
// If a probe fails we still need to check if it's our best possible direction
|
|
if(probeStatus == PS_Blocked)
|
|
{
|
|
// Get the distance to the intersection and see if it's further than our current best
|
|
float fDistSqToIntersection = vIntersection.Dist2(VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetPosition()));
|
|
if(fDistSqToIntersection > m_fBestFacingDistSqToIntersection)
|
|
{
|
|
m_fBestFacingDirection = (m_fOriginalDirection + m_fAdditionalProbeRotationZ);
|
|
m_fBestFacingDistSqToIntersection = fDistSqToIntersection;
|
|
}
|
|
|
|
// If we've gone full circle with our tests, we'll just go with whatever our best direction is
|
|
m_fAdditionalProbeRotationZ += fwRandom::GetRandomNumberInRange(.5f, .65f);
|
|
if(m_fAdditionalProbeRotationZ > TWO_PI)
|
|
{
|
|
m_bHasFacingDirection = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we didn't find an intersection then it means the entire probe was clear, go ahead and choose this direction
|
|
m_fBestFacingDirection = (m_fOriginalDirection + m_fAdditionalProbeRotationZ);
|
|
m_bHasFacingDirection = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Setup the probe by starting at our position and going to a position at an angle with our desired probe length
|
|
Vector3 vMyPosition = VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetPosition());
|
|
|
|
Vector3 vTargetPosition(0.0f, sm_Tunables.m_fDirectionTestProbeLength, 0.0f);
|
|
vTargetPosition.RotateZ(fwAngle::LimitRadianAngleSafe(m_fOriginalDirection + m_fAdditionalProbeRotationZ));
|
|
vTargetPosition += vMyPosition;
|
|
|
|
WorldProbe::CShapeTestProbeDesc probeData;
|
|
probeData.SetStartAndEnd(VEC3V_TO_VECTOR3(GetPed()->GetTransform().GetPosition()), vTargetPosition);
|
|
probeData.SetContext(WorldProbe::ELosCombatAI);
|
|
probeData.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES|ArchetypeFlags::GTA_OBJECT_TYPE|ArchetypeFlags::GTA_VEHICLE_TYPE);
|
|
probeData.SetOptions(WorldProbe::LOS_IGNORE_SEE_THRU);
|
|
m_asyncProbeHelper.ResetProbe();
|
|
m_asyncProbeHelper.StartTestLineOfSight(probeData);
|
|
}
|
|
}
|
|
|
|
//=========================================================================
|
|
// CTaskTargetUnreachableInExterior
|
|
//=========================================================================
|
|
|
|
CTaskTargetUnreachableInExterior::Tunables CTaskTargetUnreachableInExterior::sm_Tunables;
|
|
IMPLEMENT_COMBAT_TASK_TUNABLES(CTaskTargetUnreachableInExterior, 0xce6e1955);
|
|
|
|
CTaskTargetUnreachableInExterior::CTaskTargetUnreachableInExterior(const CAITarget& rTarget)
|
|
: m_vPosition(V_ZERO)
|
|
, m_Target(rTarget)
|
|
{
|
|
SetInternalTaskType(CTaskTypes::TASK_TARGET_UNREACHABLE_IN_EXTERIOR);
|
|
}
|
|
|
|
CTaskTargetUnreachableInExterior::~CTaskTargetUnreachableInExterior()
|
|
{
|
|
}
|
|
|
|
#if !__FINAL
|
|
void CTaskTargetUnreachableInExterior::Debug() const
|
|
{
|
|
CTask::Debug();
|
|
|
|
#if DEBUG_DRAW
|
|
|
|
static dev_bool s_bDebugDrawCoverPoints = false;
|
|
if( s_bDebugDrawCoverPoints )
|
|
{
|
|
const CPed* pPed = GetPed();
|
|
if( pPed )
|
|
{
|
|
const CCoverPoint* pCoverPoint = pPed->GetCoverPoint();
|
|
Vector3 vCoverPointPos;
|
|
if( pCoverPoint && pCoverPoint->GetCoverPointPosition(vCoverPointPos) )
|
|
{
|
|
grcDebugDraw::Sphere(vCoverPointPos, 1.0f, Color_green, false);
|
|
grcDebugDraw::Line(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), vCoverPointPos, Color_green, Color_green);
|
|
}
|
|
|
|
const CCoverPoint* pDesiredCoverPoint = pPed->GetDesiredCoverPoint();
|
|
Vector3 vDesiredCoverPointPos;
|
|
if( pDesiredCoverPoint && pDesiredCoverPoint->GetCoverPointPosition(vDesiredCoverPointPos) )
|
|
{
|
|
grcDebugDraw::Sphere(vDesiredCoverPointPos, 1.0f, Color_blue, false);
|
|
grcDebugDraw::Line(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), vDesiredCoverPointPos, Color_blue, Color_blue);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG_DRAW
|
|
}
|
|
|
|
const char* CTaskTargetUnreachableInExterior::GetStaticStateName(s32 iState)
|
|
{
|
|
Assert(iState >= State_Start && iState <= State_Finish);
|
|
static const char* aStateNames[] =
|
|
{
|
|
"State_Start",
|
|
"State_UseCover",
|
|
"State_FindPosition",
|
|
"State_Reposition",
|
|
"State_Wait",
|
|
"State_Finish"
|
|
};
|
|
|
|
return aStateNames[iState];
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::ProcessPreFSM()
|
|
{
|
|
//Ensure the target is valid.
|
|
if(!GetTarget())
|
|
{
|
|
return FSM_Quit;
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::UpdateFSM(const s32 iState, const FSM_Event iEvent)
|
|
{
|
|
FSM_Begin
|
|
|
|
FSM_State(State_Start)
|
|
FSM_OnUpdate
|
|
return Start_OnUpdate();
|
|
|
|
FSM_State(State_UseCover)
|
|
FSM_OnEnter
|
|
UseCover_OnEnter();
|
|
FSM_OnUpdate
|
|
return UseCover_OnUpdate();
|
|
|
|
FSM_State(State_FindPosition)
|
|
FSM_OnEnter
|
|
FindPosition_OnEnter();
|
|
FSM_OnUpdate
|
|
return FindPosition_OnUpdate();
|
|
|
|
FSM_State(State_Reposition)
|
|
FSM_OnEnter
|
|
Reposition_OnEnter();
|
|
FSM_OnUpdate
|
|
return Reposition_OnUpdate();
|
|
|
|
FSM_State(State_Wait)
|
|
FSM_OnEnter
|
|
Wait_OnEnter();
|
|
FSM_OnUpdate
|
|
return Wait_OnUpdate();
|
|
|
|
FSM_State(State_Finish)
|
|
FSM_OnUpdate
|
|
return FSM_Quit;
|
|
|
|
FSM_End
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::Start_OnUpdate()
|
|
{
|
|
if( HasValidDesiredCover() )
|
|
{
|
|
SetState(State_UseCover);
|
|
}
|
|
else
|
|
{
|
|
SetState(State_FindPosition);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInExterior::UseCover_OnEnter()
|
|
{
|
|
// Get local handle to ped
|
|
CPed* pPed = GetPed();
|
|
|
|
// If locally owned
|
|
if(!pPed->IsNetworkClone())
|
|
{
|
|
// bail if no desired coverpoint at this stage
|
|
if(!pPed->GetDesiredCoverPoint())
|
|
{
|
|
return;
|
|
}
|
|
|
|
weaponAssert(pPed->GetWeaponManager());
|
|
taskAssertf(pPed->GetDesiredCoverPoint()->CanAccomodateAnotherPed(), "Desired cover can't add another ped");
|
|
|
|
// setup the ped for moving to cover
|
|
pPed->SetCoverPoint(pPed->GetDesiredCoverPoint());
|
|
pPed->GetCoverPoint()->ReserveCoverPointForPed(pPed);
|
|
pPed->SetDesiredCoverPoint(NULL);
|
|
pPed->SetIsCrouching(false);
|
|
}
|
|
|
|
// Initialize cover task flags
|
|
s32 iFlags = CTaskCover::CF_RunSubtaskWhenMoving | CTaskCover::CF_RunSubtaskWhenStationary | CTaskCover::CF_MoveToPedsCover |
|
|
CTaskCover::CF_UseLargerSearchExtents | CTaskCover::CF_AllowClimbLadderSubtask;
|
|
|
|
// If already moving we should keep moving
|
|
Vector2 vCurrentMbr;
|
|
pPed->GetMotionData()->GetCurrentMoveBlendRatio(vCurrentMbr);
|
|
if(vCurrentMbr.IsNonZero())
|
|
{
|
|
iFlags |= CTaskCover::CF_KeepMovingWhilstWaitingForPath;
|
|
}
|
|
|
|
// Compute move blend ratio and strafing
|
|
float fDesiredMBR = MOVEBLENDRATIO_RUN;
|
|
bool bShouldStrafe = true;
|
|
CTaskCombat::ChooseMovementAttributes(pPed, bShouldStrafe, fDesiredMBR);
|
|
|
|
// Create the cover task and set parameters
|
|
CTaskCover* pCoverTask = rage_new CTaskCover(m_Target);
|
|
pCoverTask->SetSearchFlags(iFlags);
|
|
pCoverTask->SetMoveBlendRatio(fDesiredMBR);
|
|
CTask* pSubTask = CreateSubTaskForCombat();
|
|
if( pSubTask )
|
|
{
|
|
pCoverTask->SetSubtaskToUseWhilstSearching( pSubTask );
|
|
}
|
|
|
|
// Assign cover task as subtask
|
|
SetNewTask(pCoverTask);
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::UseCover_OnUpdate()
|
|
{
|
|
// Local handle
|
|
CPed* pPed = GetPed();
|
|
|
|
// Mark our coverpoint as in use if we have one
|
|
pPed->SetPedResetFlag( CPED_RESET_FLAG_KeepCoverPoint, true );
|
|
|
|
// If subtask quits
|
|
if (GetIsSubtaskFinished(CTaskTypes::TASK_COVER))
|
|
{
|
|
SetState(State_Wait);
|
|
return FSM_Continue;
|
|
}
|
|
|
|
// If we have a target entity then we should update the in cover subtask
|
|
if(m_Target.GetEntity())
|
|
{
|
|
CTaskInCover* pInCoverSubtask = static_cast<CTaskInCover*>(FindSubTaskOfType(CTaskTypes::TASK_IN_COVER));
|
|
if (pInCoverSubtask)
|
|
{
|
|
pInCoverSubtask->UpdateTarget(m_Target.GetEntity());
|
|
}
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInExterior::FindPosition_OnEnter()
|
|
{
|
|
//Grab the ped.
|
|
CPed* pPed = GetPed();
|
|
|
|
//Grab the attack window.
|
|
float fWindowMin = 0.0f;
|
|
float fWindowMax = 0.0f;
|
|
CTaskMoveWithinAttackWindow::GetAttackWindowMinMax(pPed, fWindowMin, fWindowMax, false);
|
|
|
|
//Calculate the range.
|
|
float fRange = fWindowMax - fWindowMin;
|
|
|
|
//Generate a random distance.
|
|
float fDistance = fwRandom::GetRandomNumberInRange(fWindowMax - (fRange * sm_Tunables.m_RangePercentage), fWindowMax);
|
|
|
|
//Generate a random angle.
|
|
float fAngle = fwRandom::GetRandomNumberInRange(-PI, PI);
|
|
|
|
//Grab the target position.
|
|
Vector3 vTargetPosition;
|
|
m_Target.GetPosition(vTargetPosition);
|
|
|
|
//Calculate the offset.
|
|
Vector3 vTargetOffset(0.0f, fDistance, 0.0f);
|
|
vTargetOffset.RotateZ(fAngle);
|
|
|
|
//Calculate the new position.
|
|
Vector3 vNewPosition = vTargetPosition + vTargetOffset;
|
|
|
|
//Move the height to the ped's level.
|
|
vNewPosition.z = pPed->GetTransform().GetPosition().GetZf();
|
|
|
|
//Find the closest position on the nav mesh.
|
|
m_NavmeshClosestPositionHelper.ResetSearch();
|
|
if(!m_NavmeshClosestPositionHelper.StartClosestPositionSearch(vNewPosition, sm_Tunables.m_MaxDistanceFromNavMesh,
|
|
CNavmeshClosestPositionHelper::Flag_ConsiderDynamicObjects|CNavmeshClosestPositionHelper::Flag_ConsiderExterior|
|
|
CNavmeshClosestPositionHelper::Flag_ConsiderOnlyLand))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Create the task.
|
|
CTask* pTask = CreateSubTaskForCombat();
|
|
|
|
//Start the task.
|
|
SetNewTask(pTask);
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::FindPosition_OnUpdate()
|
|
{
|
|
//Check if the search is not active.
|
|
if(!m_NavmeshClosestPositionHelper.IsSearchActive())
|
|
{
|
|
SetState(State_Wait);
|
|
}
|
|
else
|
|
{
|
|
//Check if the search is done.
|
|
SearchStatus nSearchStatus;
|
|
static const int s_iMaxNumPositions = 1;
|
|
Vector3 aPositions[1];
|
|
s32 iNumPositions = 0;
|
|
if(m_NavmeshClosestPositionHelper.GetSearchResults(nSearchStatus, iNumPositions, aPositions, s_iMaxNumPositions))
|
|
{
|
|
//Check if the search was successful.
|
|
if((nSearchStatus == SS_SearchSuccessful) && (iNumPositions > 0) && !IsCloseAll(VECTOR3_TO_VEC3V(aPositions[0]), Vec3V(V_ZERO), ScalarVFromF32(SMALL_FLOAT)))
|
|
{
|
|
//Set the position.
|
|
m_vPosition = VECTOR3_TO_VEC3V(aPositions[0]);
|
|
|
|
SetState(State_Reposition);
|
|
}
|
|
else
|
|
{
|
|
SetState(State_Wait);
|
|
}
|
|
}
|
|
}
|
|
|
|
ActivateTimeslicing();
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInExterior::Reposition_OnEnter()
|
|
{
|
|
//Assert that the position is valid.
|
|
taskAssert(!IsCloseAll(m_vPosition, Vec3V(V_ZERO), ScalarVFromF32(SMALL_FLOAT)));
|
|
|
|
//Ensure the closest position is not outside the defensive area.
|
|
if(GetPed()->GetPedIntelligence()->GetDefensiveArea()->IsActive() &&
|
|
!GetPed()->GetPedIntelligence()->GetDefensiveArea()->IsPointInArea(VEC3V_TO_VECTOR3(m_vPosition)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Create the params.
|
|
CNavParams params;
|
|
params.m_vTarget = VEC3V_TO_VECTOR3(m_vPosition);
|
|
params.m_fTargetRadius = sm_Tunables.m_TargetRadius;
|
|
params.m_fMoveBlendRatio = sm_Tunables.m_MoveBlendRatio;
|
|
params.m_fCompletionRadius = sm_Tunables.m_CompletionRadius;
|
|
|
|
//Create the move task.
|
|
CTaskMoveFollowNavMesh* pMoveTask = rage_new CTaskMoveFollowNavMesh(params);
|
|
|
|
//Use larger search extents.
|
|
pMoveTask->SetUseLargerSearchExtents(true);
|
|
|
|
//Never enter water.
|
|
pMoveTask->SetNeverEnterWater(true);
|
|
|
|
//Create the sub-task.
|
|
CTask* pSubTask = CreateSubTaskForCombat();
|
|
|
|
//Create the task.
|
|
CTask* pTask = rage_new CTaskComplexControlMovement(pMoveTask, pSubTask);
|
|
|
|
//Start the task.
|
|
SetNewTask(pTask);
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::Reposition_OnUpdate()
|
|
{
|
|
// No need to change state by default
|
|
bool bNeedToChangeState = false;
|
|
|
|
//Check if the sub-task has finished.
|
|
if(!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
bNeedToChangeState = true;
|
|
}
|
|
else
|
|
{
|
|
//Find the task.
|
|
CTaskMoveFollowNavMesh* pTask = static_cast<CTaskMoveFollowNavMesh *>(GetPed()->GetPedIntelligence()->FindTaskActiveMovementByType(CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH));
|
|
if(pTask && pTask->IsUnableToFindRoute())
|
|
{
|
|
//Abort the task.
|
|
if(!pTask->GetIsFlagSet(aiTaskFlags::IsAborted) && pTask->MakeAbortable(ABORT_PRIORITY_URGENT, NULL))
|
|
{
|
|
bNeedToChangeState = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bNeedToChangeState )
|
|
{
|
|
// Check to see if ped has cover to go to
|
|
if( HasValidDesiredCover() )
|
|
{
|
|
SetState(State_UseCover);
|
|
}
|
|
else
|
|
{
|
|
SetState(State_Wait);
|
|
}
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
ActivateTimeslicing();
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
void CTaskTargetUnreachableInExterior::Wait_OnEnter()
|
|
{
|
|
//Calculate the time to wait.
|
|
float fTimeToWait = fwRandom::GetRandomNumberInRange(sm_Tunables.m_MinTimeToWait, sm_Tunables.m_MaxTimeToWait);
|
|
|
|
//Create the move task.
|
|
CTask* pMoveTask = rage_new CTaskMoveStandStill((int)(fTimeToWait * 1000.0f));
|
|
|
|
//Create the sub-task.
|
|
CTask* pSubTask = CreateSubTaskForCombat();
|
|
|
|
//Create the task.
|
|
CTask* pTask = rage_new CTaskComplexControlMovement(pMoveTask, pSubTask);
|
|
|
|
//Start the task.
|
|
SetNewTask(pTask);
|
|
}
|
|
|
|
CTask::FSM_Return CTaskTargetUnreachableInExterior::Wait_OnUpdate()
|
|
{
|
|
//Check if the sub-task has finished.
|
|
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
|
|
{
|
|
// Check to see if ped has cover to go to
|
|
if( HasValidDesiredCover() )
|
|
{
|
|
SetState(State_UseCover);
|
|
}
|
|
else
|
|
{
|
|
//Grab the target.
|
|
const CPed* pTarget = GetTarget();
|
|
if(pTarget && CTaskTargetUnreachable::IsTargetUnreachableUsingNavMesh(*pTarget))
|
|
{
|
|
SetFlag(aiTaskFlags::RestartCurrentState);
|
|
return FSM_Continue;
|
|
}
|
|
|
|
SetState(State_FindPosition);
|
|
}
|
|
}
|
|
|
|
ActivateTimeslicing();
|
|
|
|
return FSM_Continue;
|
|
}
|
|
|
|
CTask* CTaskTargetUnreachableInExterior::CreateSubTaskForCombat() const
|
|
{
|
|
//Grab the target.
|
|
const CPed* pTarget = GetTarget();
|
|
|
|
//Create the sub-task.
|
|
CTask* pTask = rage_new CTaskCombatAdditionalTask(
|
|
CTaskCombatAdditionalTask::RT_Default | CTaskCombatAdditionalTask::RT_AllowAimFixupIfLosBlocked | CTaskCombatAdditionalTask::RT_AllowAimFireToggle |
|
|
CTaskCombatAdditionalTask::RT_AllowAimingOrFiringIfLosBlocked | CTaskCombatAdditionalTask::RT_MakeDynamicAimFireDecisions,
|
|
pTarget, VEC3V_TO_VECTOR3(pTarget->GetTransform().GetPosition()));
|
|
|
|
return pTask;
|
|
}
|
|
|
|
const CPed* CTaskTargetUnreachableInExterior::GetTarget() const
|
|
{
|
|
//Ensure the target is valid.
|
|
if(!m_Target.GetIsValid())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//Ensure the entity is valid.
|
|
const CEntity* pEntity = m_Target.GetEntity();
|
|
if(!pEntity)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//Ensure the entity is a ped.
|
|
if(!pEntity->GetIsTypePed())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return static_cast<const CPed *>(pEntity);
|
|
}
|
|
|
|
bool CTaskTargetUnreachableInExterior::HasValidDesiredCover() const
|
|
{
|
|
const CTaskCombat* pParentCombatTask = static_cast<const CTaskCombat*>(FindParentTaskOfType(CTaskTypes::TASK_COMBAT));
|
|
if( pParentCombatTask )
|
|
{
|
|
return pParentCombatTask->HasValidatedDesiredCover();
|
|
}
|
|
|
|
// no valid desired cover by default
|
|
return false;
|
|
}
|
|
|
|
|
|
void CTaskTargetUnreachableInExterior::ActivateTimeslicing()
|
|
{
|
|
GetPed()->GetPedAiLod().ClearBlockedLodFlag(CPedAILod::AL_LodTimesliceIntelligenceUpdate);
|
|
}
|