Files
GTASource/game/task/Movement/TaskNavBase.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

6849 lines
254 KiB
C++

#include "TaskNavBase.h"
// Rage headers
#include "Math/AngMath.h"
// Game headers
#if !__FINAL
#include "Camera/CamInterface.h"
#include "Camera/Debug/DebugDirector.h"
#include "Camera/Helpers/Frame.h"
#endif
#include "Event/EventMovement.h"
#include "Peds/Ped.h"
#include "Peds/PedCapsule.h"
#include "Peds/PedDebugVisualiser.h"
#include "Peds/PedIntelligence.h"
#include "Peds/PedMoveBlend/PedMoveBlendInWater.h"
#include "Peds/PedMoveBlend/PedMoveBlendOnFoot.h"
#include "Renderer/Water.h"
#include "Task/Motion/Locomotion/TaskHumanLocomotion.h"
#include "Task/Movement/TaskGetUp.h"
#include "Task/Movement/TaskGoTo.h"
#include "Task/Movement/TaskGoToPoint.h"
#include "Task/Movement/TaskSlideToCoord.h"
#include "Task/Movement/Climbing/TaskLadderClimb.h"
#include "Task/Movement/Jumping/TaskInAir.h"
#include "Task/General/TaskBasic.h"
#include "Task/Motion/Locomotion/TaskMotionPed.h"
#include "Task/Motion/Locomotion/TaskQuadLocomotion.h"
#include "Task/Scenario/Types/TaskUseScenario.h"
#include "Task/Vehicle/TaskCarUtils.h"
#include "Task/Vehicle/TaskExitVehicle.h"
#include "Task/Vehicle/TaskGoToVehicleDoor.h"
FW_INSTANTIATE_CLASS_POOL_SPILLOVER(CNavMeshRoute, CONFIGURED_FROM_FILE, 0.45f, atHashString("NavMeshRoute",0xf66ca574));
AI_NAVIGATION_OPTIMISATIONS()
AI_OPTIMISATIONS()
// Always use a look-ahead along the route (not just for spline sections)
#define __USE_LOOKAHEAD_ON_ROUTE 1
// Allows walkstops to operate over multiple linear segments (otherwise will only occur if the last segment has enough length)
#define __MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS 1
// Creates a follow point route task with the upcoming navmesh route, for use when moving whilst waiting for a path
#define __USE_POINTROUTE_WHEN_WAITING_FOR_PATH 0
// This will allow the navigation tasks to request a new route whilst continuing to follow their current route until the new one is ready
#define __REQUEST_ROUTE_WHILST_FOLLOWING_PATH 1
// This enables a more optimized version of CNavMeshRoute::ExpandRoute()
#define __USE_OPTIMIZED_EXPAND_ROUTE 1
// Attempt to use the CTaskDropDown and dropdown detector, instead of just walking off drop edges
#define __USE_DROPDOWN_TASK 0
static const float g_fFixupRunHeadingRadiusSqr = 0.5f * 0.5f;
// NAME: ExpandRoute
// PURPOSE : Dump out route as a series of linear segments, returning the number of points generated
// Using this format simplifies some operations, but allows us to use a compacted representation to store curves.
// NB: pRoutePoints must point to an array of MAX_NUM_ROUTE_ELEMENTS * RUNTIME_PATH_SPLINE_MAX_SEGMENTS in size
// NB: pRouteLength is an optional parameter to retrieve the length of the entire route
#if !__USE_OPTIMIZED_EXPAND_ROUTE
int CNavMeshRoute::ExpandRoute(CNavMeshExpandedRouteVert * RESTRICT pRoutePoints, float * RESTRICT pRouteLength, int startSegmentIndex, int stopSegmentIndex, const float& distanceToExpand) const
{
if(pRouteLength)
*pRouteLength = 0.0f;
if(!m_iRouteSize)
{
return 0;
}
if(startSegmentIndex >= m_iRouteSize || stopSegmentIndex <= startSegmentIndex)
{
return 0;
}
//
// if we're on a spline then we need to expand the previous point
//
static bool s_EnableSplineCheck = true;
if ( s_EnableSplineCheck && GetSplineEnum(startSegmentIndex) != -1 )
{
startSegmentIndex = Max(0, startSegmentIndex-1);
}
pRoutePoints[0].SetPosAndSplineT(RCC_VEC3V(m_routePoints[startSegmentIndex]), ScalarV(V_ZERO));
pRoutePoints[0].m_iProgress = startSegmentIndex;
pRoutePoints[0].m_iWptFlag = m_routeWayPoints[startSegmentIndex];
Vector3 vLast, vCurrent, vNext, vLastToCurrent, vCurrentToNext, vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4;
float fCtrlPt2TVal, t;
int iNum = 1;
int i, s;
for(i=startSegmentIndex+1; i<stopSegmentIndex; i++)
{
if(m_routeSplineEnums[i]==-1)
{
pRoutePoints[iNum].SetPosAndSplineT(RCC_VEC3V(m_routePoints[i]), ScalarV(V_ZERO));
pRoutePoints[iNum].m_iProgress = i;
pRoutePoints[iNum].m_iWptFlag = m_routeWayPoints[i];
if(pRouteLength)
*pRouteLength += (VEC3V_TO_VECTOR3(pRoutePoints[iNum].GetPos()) - VEC3V_TO_VECTOR3(pRoutePoints[iNum-1].GetPos())).Mag();
iNum++;
Assert(iNum <= CTaskNavBase::ms_iMaxExpandedVerts);
}
else
{
vLast = m_routePoints[i-1];
vCurrent = m_routePoints[i];
vNext = m_routePoints[i+1];
vLastToCurrent = vCurrent - vLast;
vCurrentToNext = vNext - vCurrent;
fCtrlPt2TVal = GetSplineCtrlPt2TVal(i);
CalcPathSplineCtrlPts(m_routeSplineEnums[i], fCtrlPt2TVal, vLast, vNext, vLastToCurrent, vCurrentToNext, vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4);
t = 0.0f;
s = RUNTIME_PATH_SPLINE_MAX_SEGMENTS;
while(s >= 0)
{
pRoutePoints[iNum].m_vPosAndSplineT.SetIntrin128(VECTOR3_TO_VEC3V(PathSpline(vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4, t)).GetIntrin128());
pRoutePoints[iNum].m_iProgress = (t < 0.5) ? i-1 : i;
pRoutePoints[iNum].m_vPosAndSplineT.SetWf(t);
pRoutePoints[iNum].m_iWptFlag.Clear();
if(pRouteLength)
*pRouteLength += (VEC3V_TO_VECTOR3(pRoutePoints[iNum].GetPos()) - VEC3V_TO_VECTOR3(pRoutePoints[iNum-1].GetPos())).Mag();
iNum++;
Assert(iNum <= CTaskNavBase::ms_iMaxExpandedVerts);
t += RUNTIME_PATH_SPLINE_STEPVAL;
s--;
}
}
// stop if we have expanded past our desired distance
if(pRouteLength)
{
if ( *pRouteLength >= distanceToExpand )
{
break;
}
}
}
Assert(iNum <= CTaskNavBase::ms_iMaxExpandedVerts);
return iNum;
}
#else // !__USE_OPTIMIZED_EXPAND_ROUTE
int CNavMeshRoute::ExpandRoute(CNavMeshExpandedRouteVert * RESTRICT pRoutePoints, float * RESTRICT pRouteLength, int startSegmentIndex, int stopSegmentIndex, const float& distanceToExpand) const
{
if(!m_iRouteSize
|| startSegmentIndex >= m_iRouteSize
|| stopSegmentIndex <= startSegmentIndex)
{
if(pRouteLength)
{
*pRouteLength = 0.0f;
}
return 0;
}
const bool shouldComputeDistances = (pRouteLength != NULL); // || distanceToExpand < 1000.0f ??
const ScalarV distanceToExpandV = LoadScalar32IntoScalarV(distanceToExpand);
//
// if we're on a spline then we need to expand the previous point
//
if (GetSplineEnum(startSegmentIndex) != -1 )
{
startSegmentIndex = Max(0, startSegmentIndex-1);
}
Vec3V prevExpandedV = VECTOR3_TO_VEC3V(m_routePoints[startSegmentIndex]);
pRoutePoints[0].SetPosAndSplineT(prevExpandedV, ScalarV(V_ZERO));
pRoutePoints[0].m_iProgress = startSegmentIndex;
pRoutePoints[0].m_iWptFlag = m_routeWayPoints[startSegmentIndex];
int iNum = 1;
ScalarV routeLengthV(V_ZERO);
for(int i=startSegmentIndex+1; i<stopSegmentIndex; i++)
{
if(m_routeSplineEnums[i]==-1)
{
const Vec3V currentExpandedV = VECTOR3_TO_VEC3V(m_routePoints[i]);
pRoutePoints[iNum].SetPosAndSplineT(currentExpandedV, ScalarV(V_ZERO));
pRoutePoints[iNum].m_iProgress = i;
pRoutePoints[iNum].m_iWptFlag = m_routeWayPoints[i];
if(shouldComputeDistances)
{
// Could go back to Dist() if DistFast() isn't accurate enough:
// routeLengthV = rage::Add(routeLengthV, Dist(currentExpandedV, prevExpandedV));
routeLengthV = rage::Add(routeLengthV, DistFast(currentExpandedV, prevExpandedV));
}
prevExpandedV = currentExpandedV;
iNum++;
Assert(iNum <= CTaskNavBase::ms_iMaxExpandedVerts);
}
else
{
// Grab the route points we will build the spline for.
const Vec3V vLast = RCC_VEC3V(m_routePoints[i-1]);
const Vec3V vCurrent = RCC_VEC3V(m_routePoints[i]);
const Vec3V vNext = RCC_VEC3V(m_routePoints[i+1]);
const Vec3V vLastToCurrent = Subtract(vCurrent, vLast);
const Vec3V vCurrentToNext = Subtract(vNext, vCurrent);
// Compute the control points of the spline.
const ScalarV ctrlPt2TValV = GetSplineCtrlPt2TValV(i);
Vec3V ctrlPt0V, ctrlPt1V, ctrlPt2V, ctrlPt3V;
CalcPathSplineCtrlPtsV(m_routeSplineEnums[i], ctrlPt2TValV, vLast, vNext, vLastToCurrent, vCurrentToNext, ctrlPt0V, ctrlPt1V, ctrlPt2V, ctrlPt3V);
// A lot of what follows is assuming that RUNTIME_PATH_SPLINE_MAX_SEGMENTS is 8, so we can
// fit the coordinates, T values, etc, of eight points in two 4D vectors. Extending it to
// 12 or 16 should be fairly straightforward, but then we may want to de-unroll the code
// and set it up as a loop. Using something that's not an even 4 would be slightly trickier.
CompileTimeAssert(RUNTIME_PATH_SPLINE_MAX_SEGMENTS == 8);
// We will keep T values of four points in a vector. These are the first four:
const Vec4V tV(Vec::V4VConstant<
FLOAT_TO_INT(0.0f/8.0f), // 8.0f == RUNTIME_PATH_SPLINE_MAX_SEGMENTS
FLOAT_TO_INT(1.0f/8.0f),
FLOAT_TO_INT(2.0f/8.0f),
FLOAT_TO_INT(3.0f/8.0f)
>());
// This is the amount that the T values increase by for the next four points.
// const Vec4V tStepV(ScalarV(4.0f/(float)RUNTIME_PATH_SPLINE_MAX_SEGMENTS));
const Vec4V tStepV(V_HALF);
// Compute the square and cube of all T values for eight points.
const Vec4V tAV = tV;
const Vec4V t2AV = Scale(tAV, tAV);
const Vec4V t3AV = Scale(t2AV, tAV);
const Vec4V tBV = rage::Add(tV, tStepV);
const Vec4V t2BV = Scale(tBV, tBV);
const Vec4V t3BV = Scale(t2BV, tBV);
// Next, we will compute coefficients for computing the spline points
// from control points. This was adapted from PathSpline():
// const Vector3 vTargetAfterSlide =
// vCtrlPt1 * (-0.5f*u*u*u + u*u - 0.5f*u) +
// vCtrlPt2 * (1.5f*u*u*u + -2.5f*u*u + 1.0f) +
// vCtrlPt3 * (-1.5f*u*u*u + 2.0f*u*u + 0.5f*u) +
// vCtrlPt4 * (0.5f*u*u*u - 0.5f*u*u);
// through something like this:
// const Vec4V k0V = ScalarV(-0.5f)*t3V + t2V - ScalarV(0.5f)*tV;
// const Vec4V k1V = ScalarV(1.5f)*t3V - ScalarV(2.5f)*t2V + Vec4V(ScalarV(1.0f));
// const Vec4V k2V = ScalarV(-1.5f)*t3V + ScalarV(2.0f)*t2V + ScalarV(0.5f)*tV;
// const Vec4V k3V = ScalarV(0.5f)*t3V - ScalarV(0.5f)*t2V;
// First, we will need some constants:
const ScalarV halfV(V_HALF);
const ScalarV oneV(V_ONE);
const ScalarV twoV(V_TWO);
const ScalarV negOnePointFiveV = Subtract(halfV, twoV);
const ScalarV twoPointFiveV = rage::Add(twoV, halfV);
// Compute the coefficients to use for the eight points on the spline,
// for the four control points. k0V are the coefficients for four spline points
// for control point 0, etc.
const Vec4V k0V = SubtractScaled(SubtractScaled(t2AV, tAV, halfV), t3AV, halfV);
const Vec4V k1V = SubtractScaled(SubtractScaled(Vec4V(oneV), t2AV, twoPointFiveV), t3AV, negOnePointFiveV);
const Vec4V k2V = AddScaled(AddScaled(Scale(tAV, halfV), t2AV, twoV), t3AV, negOnePointFiveV);
const Vec4V k3V = SubtractScaled(Scale(t3AV, halfV), t2AV, halfV);
const Vec4V k4V = SubtractScaled(SubtractScaled(t2BV, tBV, halfV), t3BV, halfV);
const Vec4V k5V = SubtractScaled(SubtractScaled(Vec4V(oneV), t2BV, twoPointFiveV), t3BV, negOnePointFiveV);
const Vec4V k6V = AddScaled(AddScaled(Scale(tBV, halfV), t2BV, twoV), t3BV, negOnePointFiveV);
const Vec4V k7V = SubtractScaled(Scale(t3BV, halfV), t2BV, halfV);
// Multiply control point 0 for all the points.
Vec4V xAV = Scale(k0V, ctrlPt0V.GetX());
Vec4V yAV = Scale(k0V, ctrlPt0V.GetY());
Vec4V zAV = Scale(k0V, ctrlPt0V.GetZ());
Vec4V xBV = Scale(k4V, ctrlPt0V.GetX());
Vec4V yBV = Scale(k4V, ctrlPt0V.GetY());
Vec4V zBV = Scale(k4V, ctrlPt0V.GetZ());
// Multiply control point 1 for all the points.
xAV = AddScaled(xAV, k1V, ctrlPt1V.GetX());
yAV = AddScaled(yAV, k1V, ctrlPt1V.GetY());
zAV = AddScaled(zAV, k1V, ctrlPt1V.GetZ());
xBV = AddScaled(xBV, k5V, ctrlPt1V.GetX());
yBV = AddScaled(yBV, k5V, ctrlPt1V.GetY());
zBV = AddScaled(zBV, k5V, ctrlPt1V.GetZ());
// Multiply control point 2 for all the points.
xAV = AddScaled(xAV, k2V, ctrlPt2V.GetX());
yAV = AddScaled(yAV, k2V, ctrlPt2V.GetY());
zAV = AddScaled(zAV, k2V, ctrlPt2V.GetZ());
xBV = AddScaled(xBV, k6V, ctrlPt2V.GetX());
yBV = AddScaled(yBV, k6V, ctrlPt2V.GetY());
zBV = AddScaled(zBV, k6V, ctrlPt2V.GetZ());
// Multiply control point 3 for all the points.
xAV = AddScaled(xAV, k3V, ctrlPt3V.GetX());
yAV = AddScaled(yAV, k3V, ctrlPt3V.GetY());
zAV = AddScaled(zAV, k3V, ctrlPt3V.GetZ());
xBV = AddScaled(xBV, k7V, ctrlPt3V.GetX());
yBV = AddScaled(yBV, k7V, ctrlPt3V.GetY());
zBV = AddScaled(zBV, k7V, ctrlPt3V.GetZ());
// As at least in some cases, we will need distances along the path,
// we will compute those here as well.
// First, compute X, Y, and Z vectors shifted over by one point.
// For example, in shiftedYAV, the first component is the Y coordinate of the previous
// point (before the spline), the second component is the Y coordinate of the first point
// on the spline, and so on.
const Vec4V shiftedXAV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(Vec4V(prevExpandedV.GetX()), xAV);
const Vec4V shiftedYAV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(Vec4V(prevExpandedV.GetY()), yAV);
const Vec4V shiftedZAV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(Vec4V(prevExpandedV.GetZ()), zAV);
const Vec4V shiftedXBV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(xAV, xBV);
const Vec4V shiftedYBV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(yAV, yBV);
const Vec4V shiftedZBV = GetFromTwo<Vec::W1, Vec::X2, Vec::Y2, Vec::Z2>(zAV, zBV);
// Compute the deltas between these points and the previous points, in X, Y, and Z.
const Vec4V deltaXAV = Subtract(xAV, shiftedXAV);
const Vec4V deltaYAV = Subtract(yAV, shiftedYAV);
const Vec4V deltaZAV = Subtract(zAV, shiftedZAV);
const Vec4V deltaXBV = Subtract(xBV, shiftedXBV);
const Vec4V deltaYBV = Subtract(yBV, shiftedYBV);
const Vec4V deltaZBV = Subtract(zBV, shiftedZBV);
// Compute the squared sum of the deltas.
const Vec4V deltaXSqAV = Scale(deltaXAV, deltaXAV);
const Vec4V deltaXSqBV = Scale(deltaXBV, deltaXBV);
const Vec4V deltaXYSqAV = AddScaled(deltaXSqAV, deltaYAV, deltaYAV);
const Vec4V deltaXYSqBV = AddScaled(deltaXSqBV, deltaYBV, deltaYBV);
const Vec4V deltaSqAV = AddScaled(deltaXYSqAV, deltaZAV, deltaZAV);
const Vec4V deltaSqBV = AddScaled(deltaXYSqBV, deltaZBV, deltaZBV);
// Compute the square roots of the sum of squares, to get the distance.
// Note: could go for Sqrt() rather than SqrtFast() if the latter is not accurate enough:
// const Vec4V distancesAV = Sqrt(deltaSqAV);
// const Vec4V distancesBV = Sqrt(deltaSqBV);
const Vec4V distancesAV = SqrtFast(deltaSqAV);
const Vec4V distancesBV = SqrtFast(deltaSqBV);
// Add together the distances for the two vectors. We still need
// to sum up the components, but we do that later.
const Vec4V distSumFourV = rage::Add(distancesAV, distancesBV);
// Next, we have to transpose the points from SoA to AoS format, similar to how
// it would be done for a matrix multiplication.
const Vec4V temp0AV = MergeXY(xAV, zAV);
const Vec4V temp1AV = MergeXY(yAV, tAV);
const Vec4V temp2AV = MergeZW(xAV, zAV);
const Vec4V temp3AV = MergeZW(yAV, tAV);
const Vec4V temp0BV = MergeXY(xBV, zBV);
const Vec4V temp1BV = MergeXY(yBV, tBV);
const Vec4V temp2BV = MergeZW(xBV, zBV);
const Vec4V temp3BV = MergeZW(yBV, tBV);
const Vec4V xyzt0V = MergeXY(temp0AV, temp1AV);
const Vec4V xyzt1V = MergeZW(temp0AV, temp1AV);
const Vec4V xyzt2V = MergeXY(temp2AV, temp3AV);
const Vec4V xyzt3V = MergeZW(temp2AV, temp3AV);
const Vec4V xyzt4V = MergeXY(temp0BV, temp1BV);
const Vec4V xyzt5V = MergeZW(temp0BV, temp1BV);
const Vec4V xyzt6V = MergeXY(temp2BV, temp3BV);
const Vec4V xyzt7V = MergeZW(temp2BV, temp3BV);
// Store the points:
pRoutePoints[iNum ].SetPosAndSplineT(xyzt0V);
pRoutePoints[iNum + 1].SetPosAndSplineT(xyzt1V);
pRoutePoints[iNum + 2].SetPosAndSplineT(xyzt2V);
pRoutePoints[iNum + 3].SetPosAndSplineT(xyzt3V);
pRoutePoints[iNum + 4].SetPosAndSplineT(xyzt4V);
pRoutePoints[iNum + 5].SetPosAndSplineT(xyzt5V);
pRoutePoints[iNum + 6].SetPosAndSplineT(xyzt6V);
pRoutePoints[iNum + 7].SetPosAndSplineT(xyzt7V);
// We actually need to add 9 points if RUNTIME_PATH_SPLINE_MAX_SEGMENTS is 8.
// The last point is the same as the third control point, so this is easy:
pRoutePoints[iNum + 8].SetPosAndSplineT(ctrlPt2V, ScalarV(V_ONE));
// Keep track of the last point, for measuring the distance for the next segment.
prevExpandedV = ctrlPt2V;
// Compute the distance for the last segment we added. Unfortunately this is somewhat
// costly since the square root here is for just one segment, not for like what we do
// for the rest of the spline.
if(shouldComputeDistances)
{
const Vec4V distTemp1V = distSumFourV.Get<Vec::Z, Vec::W, Vec::X, Vec::Y>();
const Vec4V distTemp2V = rage::Add(distSumFourV, distTemp1V); // x+z y+w z+x w+y
const Vec4V distTemp3V = distTemp2V.Get<Vec::Y, Vec::Z, Vec::W, Vec::X>(); // y+w z+x w+y x+z
ScalarV distSumV = rage::Add(distTemp2V, distTemp3V).GetX();
distSumV = rage::Add(distSumV, DistFast(ctrlPt2V, xyzt7V.GetXYZ())); // Note: using DistFast() as an approximation of Dist().
routeLengthV = rage::Add(routeLengthV, distSumV);
}
// Set the progress of the points: this is equivalent to t < 0.5.
const int prevI = i - 1;
pRoutePoints[iNum ].m_iProgress = prevI;
pRoutePoints[iNum + 1].m_iProgress = prevI;
pRoutePoints[iNum + 2].m_iProgress = prevI;
pRoutePoints[iNum + 3].m_iProgress = prevI;
pRoutePoints[iNum + 4].m_iProgress = i;
pRoutePoints[iNum + 5].m_iProgress = i;
pRoutePoints[iNum + 6].m_iProgress = i;
pRoutePoints[iNum + 7].m_iProgress = i;
pRoutePoints[iNum + 8].m_iProgress = i;
// Clear the flags. Note: we could probably do this faster if we made some assumptions
// about us always wanting everything to be zero, etc.
pRoutePoints[iNum ].m_iWptFlag.Clear();
pRoutePoints[iNum + 1].m_iWptFlag.Clear();
pRoutePoints[iNum + 2].m_iWptFlag.Clear();
pRoutePoints[iNum + 3].m_iWptFlag.Clear();
pRoutePoints[iNum + 4].m_iWptFlag.Clear();
pRoutePoints[iNum + 5].m_iWptFlag.Clear();
pRoutePoints[iNum + 6].m_iWptFlag.Clear();
pRoutePoints[iNum + 7].m_iWptFlag.Clear();
pRoutePoints[iNum + 8].m_iWptFlag.Clear();
iNum += RUNTIME_PATH_SPLINE_MAX_SEGMENTS + 1;
Assert(iNum <= CTaskNavBase::ms_iMaxExpandedVerts);
}
// stop if we have expanded past our desired distance
// Note: there is potentially a bit of a bug here: it seems like calling
// code expects us to break out even if we pass in NULL as pRouteLength.
// If we want to change the behavior of the code to work like that,
// we will need to make sure we compute the distances everywhere, since we
// currently skip some of the math in that case.
if(shouldComputeDistances)
{
if(IsGreaterThanAll(routeLengthV, distanceToExpandV))
{
break;
}
}
}
if(pRouteLength)
{
StoreScalar32FromScalarV(*pRouteLength, routeLengthV);
}
Assert(iNum <= CTaskNavBase::ms_iMaxExpandedVerts);
return iNum;
}
#endif // !__USE_OPTIMIZED_EXPAND_ROUTE
bool CNavMeshRoute::GetPositionAheadOnSpline(const Vector3 & vPedPos, const int iInitialProgress, float fInitialSplineTVal, const float fInitialDistanceAhead, Vector3 & vOutPos) const
{
Vector3 vSegmentStart = vPedPos;
int iProgress = iInitialProgress;
float fSplineTVal = fInitialSplineTVal;
float fDistanceAhead = fInitialDistanceAhead;
while(fDistanceAhead >= 0.0f && iProgress < m_iRouteSize)
{
const int iSplineEnum = (iProgress < m_iRouteSize) ? GetSplineEnum(iProgress) : -1;
if(iSplineEnum == -1)
{
Vector3 vDiff = m_routePoints[iProgress] - vSegmentStart;
const float fMag2 = vDiff.Mag2();
if( fMag2 > fDistanceAhead*fDistanceAhead )
{
vDiff.Normalize();
vOutPos = vSegmentStart + (vDiff * fDistanceAhead);
return true;
}
fDistanceAhead -= Sqrtf(fMag2);
vSegmentStart = m_routePoints[iProgress];
iProgress++;
fSplineTVal = RUNTIME_PATH_SPLINE_STEPVAL;
fInitialSplineTVal = 0.0f;
}
else
{
const Vector3 & vLast = GetPosition(iProgress-1);
const Vector3 & vCurrent = GetPosition(iProgress);
const Vector3 & vNext = GetPosition(iProgress+1);
const Vector3 vLastToCurrent = vCurrent - vLast;
const Vector3 vCurrentToNext = vNext - vCurrent;
const float fCtrlPt2TVal = GetSplineCtrlPt2TVal(iProgress);
Vector3 vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4;
CalcPathSplineCtrlPts(iSplineEnum, fCtrlPt2TVal, vLast, vNext, vLastToCurrent, vCurrentToNext, vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4);
// Check for position along straight segment before start of spline
if(fInitialSplineTVal == 0.0f)
{
const Vector3 vSegStartToCtrlPt1 = vCtrlPt1 - vSegmentStart;
Vector3 vDiff = vCtrlPt2 - vSegmentStart;
const float fMag2 = vDiff.Mag2();
vDiff.Normalize();
if(DotProduct(vDiff, vSegStartToCtrlPt1) < 0.0f)
{
if( fMag2 > fDistanceAhead*fDistanceAhead )
{
vOutPos = vSegmentStart + (vDiff * fDistanceAhead);
return true;
}
}
vSegmentStart = vCtrlPt2;
fDistanceAhead -= Sqrtf(fMag2);
fSplineTVal = RUNTIME_PATH_SPLINE_STEPVAL;
fInitialSplineTVal = 0.0f;
}
if(fInitialSplineTVal == 0.0f)
vSegmentStart = vCtrlPt2;
while(fSplineTVal < 1.0f)
{
const Vector3 vSplinePt = PathSpline(vCtrlPt1, vCtrlPt2, vCtrlPt3, vCtrlPt4, fSplineTVal);
Vector3 vDiff = vSplinePt - vSegmentStart;
const float fMag2 = vDiff.Mag2();
if( fMag2 > fDistanceAhead*fDistanceAhead )
{
vDiff.Normalize();
vOutPos = vSegmentStart + (vDiff * fDistanceAhead);
return true;
}
fDistanceAhead -= Sqrtf(fMag2);
fSplineTVal += RUNTIME_PATH_SPLINE_STEPVAL;
vSegmentStart = vSplinePt;
}
vSegmentStart = vCtrlPt3;
iProgress++;
fSplineTVal = RUNTIME_PATH_SPLINE_STEPVAL;
fInitialSplineTVal = 0.0f;
}
}
return false;
}
bank_float CTaskNavBase::ms_fCorneringLookAheadTime = 1.0f; //0.75f;
bank_float CTaskNavBase::ms_fGotoTargetLookAheadDist = 1.5f;
const float CTaskNavBase::ms_fTargetRadiusForWaypoints = 0.5f;
const float CTaskNavBase::ms_fHeightAboveReturnedPointsForRouteVerts = 0.0f;
const float CTaskNavBase::ms_fTargetEqualEpsilon = 0.5f;
const float CTaskNavBase::ms_fWaypointSlowdownProximitySqr = 1.5f*1.5f;
const s32 CTaskNavBase::ms_iAttemptToSwimStraightToTargetFreqMs = 4000;
const float CTaskNavBase::ms_DefaultPathDistanceToExpand = 10.0f;
bank_bool CTaskNavBase::ms_bScanForObstructions = true;
bank_s32 CTaskNavBase::ms_iScanForObstructionsFreq_Walk = 4000;
bank_s32 CTaskNavBase::ms_iScanForObstructionsFreq_Run = 4000;
bank_s32 CTaskNavBase::ms_iScanForObstructionsFreq_Sprint = 3000;
bank_float CTaskNavBase::ms_fScanForObstructionsDistance = 30.0f;
bank_bool CTaskNavBase::ms_bUseExandRouteOptimisations = true;
bank_float CTaskNavBase::ms_fDistanceFromRouteEndToRequestNextSection = 6.0f;
bank_float CTaskNavBase::ms_fDefaultDistanceAheadForSetNewTarget = 0.0f;
bank_float CTaskNavBase::ms_fDistanceAheadForSetNewTargetObstruction = 4.0f;
bank_float CTaskNavBase::ms_fDistanceFromPathToTurnOnNavMeshAvoidance = 0.5f;
bank_float CTaskNavBase::ms_fDistanceFromPathToTurnOnObjectAvoidance = 0.5f;
bank_float CTaskNavBase::ms_fDistanceFromPathToTurnOnVehicleAvoidance = 0.5f;
#if __BANK
float CTaskNavBase::ms_fOverrideRadius = PATHSERVER_PED_RADIUS;
bank_bool CTaskNavBase::ms_bDebugRequests = false;
#endif
CNavMeshExpandedRouteVert CTaskNavBase::ms_vExpandedRouteVerts[CTaskNavBase::ms_iMaxExpandedVerts];
s32 CTaskNavBase::ms_iNumExpandedRouteVerts = 0;
Vector3 CTaskNavBase::ms_vClosestSegmentOnRoute = VEC3_ZERO;
u64 CNavBaseConfigFlags::GetAsPathServerFlags() const
{
u64 iFlags = 0;
if(IsSet(NB_ScriptedRoute))
iFlags |= PATH_FLAG_SCRIPTED_ROUTE;
if(IsSet(NB_HighPrioRoute))
iFlags |= PATH_FLAG_HIGH_PRIO_ROUTE;
if(IsSet(NB_UseLargerSearchExtents))
iFlags |= PATH_FLAG_USE_LARGER_SEARCH_EXTENTS;
if(!IsSet(NB_AvoidObjects))
iFlags |= PATH_FLAG_DONT_AVOID_DYNAMIC_OBJECTS;
if(IsSet(NB_UseDirectionalCover))
iFlags |= PATH_FLAG_USE_DIRECTIONAL_COVER;
if(IsSet(NB_DontUseLadders))
iFlags |= PATH_FLAG_NEVER_USE_LADDERS;
if(IsSet(NB_DontUseClimbs))
iFlags |= PATH_FLAG_NEVER_CLIMB_OVER_STUFF;
if(IsSet(NB_DontUseDrops))
iFlags |= PATH_FLAG_NEVER_DROP_FROM_HEIGHT;
if(IsSet(NB_SmoothPathCorners))
iFlags |= PATH_FLAG_CUT_SHARP_CORNERS;
if(IsSet(NB_AllowToNavigateUpSteepPolygons))
iFlags |= PATH_FLAG_ALLOW_TO_NAVIGATE_UP_STEEP_POLYGONS;
if(IsSet(NB_AllowToPushVehicleDoorsClosed))
iFlags |= PATH_FLAG_ALLOW_TO_PUSH_VEHICLE_DOORS_CLOSED;
if(IsSet(NB_NeverLeaveWater))
iFlags |= PATH_FLAG_NEVER_LEAVE_WATER;
// if(!IsSet(NB_DontAdjustTargetPosition))
// iFlags |= PATH_FLAG_ENSURE_LOS_BEFORE_ENDING;
if(IsSet(NB_ExpandStartEndTessellationRadius))
iFlags |= PATH_FLAG_EXPAND_START_END_TESSELLATION_RADIUS;
if(IsSet(NB_PullFromEdgeExtra))
iFlags |= PATH_FLAG_PULL_FROM_EDGE_EXTRA;
if(IsSet(NB_SofterFleeHeuristics))
iFlags |= PATH_FLAG_SOFTER_FLEE_HEURISTICS;
if(IsSet(NB_NeverLeaveDeepWater))
iFlags |= PATH_FLAG_NEVER_LEAVE_DEEP_WATER;
return iFlags;
}
void CNavBaseInternalFlags::ResetTemporaryFlags()
{
// Save off the flags we need to persist
bool bInitialised = IsSet(NB_HasBeenInitialised);
bool bAbortedForDoorCollision = IsSet(NB_AbortedForDoorCollision);
bool bAbortedForCollisionEvent = IsSet(NB_AbortedForCollisionEvent);
bool bExceededVerticalDistanceFromPath = IsSet(NB_ExceededVerticalDistanceFromPath);
ResetAll();
// Restore saved flags
Set(NB_HasBeenInitialised, bInitialised);
Set(NB_AbortedForDoorCollision, bAbortedForDoorCollision);
Set(NB_AbortedForCollisionEvent, bAbortedForCollisionEvent);
Set(NB_ExceededVerticalDistanceFromPath, bExceededVerticalDistanceFromPath);
}
CTaskNavBase::CTaskNavBase(const float fMoveBlendRatio, const Vector3 & vTarget) :
CTaskMove(fMoveBlendRatio),
m_fInitialMoveBlendRatio(fMoveBlendRatio),
m_vTarget(vTarget),
m_vNewTarget(vTarget),
m_pOnEntityRouteData(NULL),
m_pRoute(NULL),
m_pNextRouteSection(NULL),
m_iPathHandle(PATH_HANDLE_NULL),
m_iLastPathHandle(PATH_HANDLE_NULL),
m_pLadderClipRequestHelper(NULL),
m_fTargetRadius(0.0f),
m_fCurrentTargetRadius(0.0f),
m_fTargetStopHeading(DEFAULT_NAVMESH_FINAL_HEADING),
m_fSlowDownDistance(0.0f),
m_fDistanceToMoveWhileWaitingForPath(4.0f),
m_fMaxSlopeNavigable(0.0f),
m_fSlideToCoordHeading(0.0f),
m_fSlideToCoordSpeed(0.0f),
m_fSlideToCoordHeadingChangeSpeed(0.0f),
m_fAnimatedStoppingDistance(0.0f),
m_iWarpTimeMs(-1),
m_iTimeSpentOnCurrentRouteSegmentMs(0),
m_iTimeShouldSpendOnCurrentRouteSegmentMs(0),
m_iRecalcPathTimerMs(0),
m_iRecalcPathFreqMs(0),
m_iTimeOfLastSetTargetCall(0),
m_iProgress(0),
m_iClampMinProgress(0),
m_iClampMaxSearchDistance(0),
m_fSplineTVal(0.0f),
m_fCurrentLookAheadDistance(0.0f),
m_iFreeSpaceReferenceValue(0),
m_iNavMeshRouteMethod(eRouteMethod_Normal),
m_iNumRouteAttempts(0),
m_iLastRecoveryMode(0)
{
Assertf(fMoveBlendRatio >= MOVEBLENDRATIO_STILL && fMoveBlendRatio <= MOVEBLENDRATIO_SPRINT, "Move blend ratio must be between MOVEBLENDRATIO_STILL and MOVEBLENDRATIO_SPRINT inclusive. The value passed in was %.1f", fMoveBlendRatio);
ASSERT_ONLY(const float fLargeValue = 10000.0f;)
Assertf(vTarget.x > -fLargeValue && vTarget.y > -fLargeValue && vTarget.z > -fLargeValue && vTarget.x < fLargeValue && vTarget.y < fLargeValue && vTarget.z < fLargeValue, "vTarget (%.2f, %.2f, %.2f) is outside of the world", vTarget.x, vTarget.y, vTarget.z);
m_vAdjustedTarget[0] = m_vAdjustedTarget[1] = m_vAdjustedTarget[2] = 0.0f;
m_vDirectionalCoverThreatPos[0] = m_vDirectionalCoverThreatPos[1] = 0.0f;
m_NavBaseConfigFlags.ResetAll();
m_NavBaseInternalFlags.ResetAll();
// Set default flags
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_AvoidObjects);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_AvoidEntityTypeObject);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_AvoidEntityTypeVehicle);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_AvoidPeds);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_TryToGetOntoMainNavMeshIfStranded);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_ScanForObstructions);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_SetAllowNavmeshAvoidance);
SetMaxDistanceMayAdjustPathStartPosition(TRequestPathStruct::ms_fDefaultMaxDistanceToAdjustPathEndPoints);
SetMaxDistanceMayAdjustPathEndPosition(TRequestPathStruct::ms_fDefaultMaxDistanceToAdjustPathEndPoints);
#if __ALWAYS_STOP_EXACTLY
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_StopExactly);
#endif
TUNE_GROUP_BOOL(PATH_FOLLOWING, ms_bUseExandRouteOptimisations, true);
}
CTaskNavBase::~CTaskNavBase()
{
if(m_iPathHandle)
{
CPathServer::CancelRequest(m_iPathHandle);
}
if(m_pRoute)
{
delete m_pRoute;
}
if(m_pNextRouteSection)
{
delete m_pNextRouteSection;
}
if(m_pOnEntityRouteData)
{
delete m_pOnEntityRouteData;
}
if(m_pLadderClipRequestHelper)
{
CLadderClipSetRequestHelperPool::ReleaseClipSetRequestHelper(m_pLadderClipRequestHelper);
m_pLadderClipRequestHelper = NULL; // Will the destructor ever be called twice anyway!?
}
}
s32 CTaskNavBase::GetDefaultStateAfterAbort() const
{
return NavBaseState_Initial;
}
void CTaskNavBase::Restart()
{
if(GetState()==NavBaseState_FollowingPath)
{
if(m_iPathHandle)
{
CPathServer::CancelRequest(m_iPathHandle);
m_iLastPathHandle = m_iPathHandle;
m_iPathHandle = PATH_HANDLE_NULL;
}
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_RestartNextFrame, true);
}
}
void CTaskNavBase::ResetForRestart()
{
// Cancel any pending path request
if(m_iPathHandle)
{
CPathServer::CancelRequest(m_iPathHandle);
m_iLastPathHandle = m_iPathHandle;
m_iPathHandle = PATH_HANDLE_NULL;
}
// Reset all base flags.
// Any derived classes which implement this, must also call this base class function.
m_NavBaseInternalFlags.ResetTemporaryFlags();
}
void CTaskNavBase::CleanUp()
{
if(m_iPathHandle)
{
CPathServer::CancelRequest(m_iPathHandle);
m_iLastPathHandle = m_iPathHandle;
m_iPathHandle = PATH_HANDLE_NULL;
}
}
void CTaskNavBase::CopySettings(CTaskNavBase * pClone) const
{
pClone->m_NavBaseConfigFlags = m_NavBaseConfigFlags;
pClone->m_NavBaseInternalFlags = m_NavBaseInternalFlags;
pClone->SetMaxSlopeNavigable(GetMaxSlopeNavigable());
pClone->SetDontUseLadders(GetDontUseLadders());
pClone->SetDontUseClimbs(GetDontUseClimbs());
pClone->SetDontUseDrops(GetDontUseDrops());
pClone->SetUseLargerSearchExtents(GetUseLargerSearchExtents());
pClone->SetIsScriptedRoute(GetIsScriptedRoute());
pClone->SetSlideToCoordAtEnd(GetSlideToCoordAtEnd(), m_fSlideToCoordHeading, m_fSlideToCoordSpeed, m_fSlideToCoordHeadingChangeSpeed);
Vector3 vThreatPos(m_vDirectionalCoverThreatPos[0], m_vDirectionalCoverThreatPos[0], 0.0f);
pClone->SetUseDirectionalCover(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_UseDirectionalCover), &vThreatPos);
pClone->SetUseFreeSpaceReferenceValue(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_UseFreeSpaceReferenceValue), m_iFreeSpaceReferenceValue, m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_FavourGreaterThanFreeSpaceReferenceValue));
pClone->SetSmoothPathCorners(GetSmoothPathCorners());
pClone->SetStartAtFirstRoutePoint(GetStartAtFirstRoutePoint());
pClone->SetAllowToNavigateUpSteepPolygons(GetAllowToNavigateUpSteepPolygons());
pClone->SetAllowToPushVehicleDoorsClosed(GetAllowToPushVehicleDoorsClosed());
pClone->SetRecalcPathFrequency(m_iRecalcPathFreqMs, m_iRecalcPathTimerMs);
pClone->SetHighPrioRoute(GetHighPrioRoute());
pClone->SetConsiderRunStartForPathLookAhead(GetConsiderRunStartForPathLookAhead());
pClone->SetScanForObstructions(GetScanForObstructions());
pClone->m_ScanForObstructionsTimer.ChangeDuration(m_ScanForObstructionsTimer.GetDuration());
pClone->SetDontStopForClimbs(GetDontStopForClimbs());
pClone->SetClampMaxSearchDistance(GetClampMaxSearchDistance());
pClone->m_iMaxDistanceToMovePathStart = m_iMaxDistanceToMovePathStart;
pClone->m_iMaxDistanceToMovePathEnd = m_iMaxDistanceToMovePathEnd;
}
bool CTaskNavBase::ShouldAbort(const AbortPriority iPriority, const aiEvent* pEvent)
{
return CTask::ShouldAbort(iPriority, pEvent);
}
// Handle aborting the task - this will be called in addition to CleanUp when we are aborting
void CTaskNavBase::DoAbort(const AbortPriority UNUSED_PARAM(iPriority), const aiEvent* pEvent)
{
if(pEvent)
{
switch(pEvent->GetEventType())
{
case EVENT_OBJECT_COLLISION:
{
CEventObjectCollision * pObjectCollision = (CEventObjectCollision*)pEvent;
if(pObjectCollision->GetCObject() && pObjectCollision->GetCObject()->IsADoor())
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_AbortedForDoorCollision);
}
else
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_AbortedForCollisionEvent);
}
break;
}
case EVENT_VEHICLE_COLLISION:
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_AbortedForCollisionEvent);
break;
}
}
}
}
void CTaskNavBase::SwitchToNextRoute()
{
Assert(m_pRoute);
Assert(m_pNextRouteSection);
#if __ASSERT
m_pRoute->CheckValid();
m_pNextRouteSection->CheckValid();
#endif
CNavMeshRoute * pNewRoute = rage_new CNavMeshRoute();
s32 p;
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_OnTheFlyPathStartedAtRouteEnd))
{
// Add the remainder of the previous route to the beginning of the new route
// We start from m_iProgress-1 so that we include the current segment (and spline)
// which we are on; and we continue to GetSize()-1, because the final point will
// be duplicated as the starting point of m_pNextRouteSection.
p = Max(m_iProgress-1, 0); // Make sure we dont' pass in a negative value. B* 1199441
for( ; p<m_pRoute->GetSize()-1; p++)
{
pNewRoute->Add(
m_pRoute->GetPosition(p),
m_pRoute->GetWaypoint(p),
m_pRoute->GetSplineEnum(p),
m_pRoute->GetSplineCtrlPt2TVal(p),
m_pRoute->GetFreeSpaceApproachingWaypoint(p)
);
}
}
else
{
// Find which expanded route section the start of the next section is along
float fTVelTmp;
Vector3 vClosestPosTmp;
Vector3 vSegmentTmp;
s32 iClosestSegToSectionStart = (s16) CalculateClosestSegmentOnExpandedRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, m_pNextRouteSection->GetPosition(0), fTVelTmp, &vClosestPosTmp, &vSegmentTmp);
Assert(iClosestSegToSectionStart != -1);
// Add the expanded route segments up until the start of the next section
// Since we already specified stopping at climbs/drops when we called GetPositionAheadOnRoute in InitPathRequestStruct()
// we shouldn't have to worry about hittinga any of these.
TNavMeshWaypointFlag wptEmpty;
wptEmpty.Clear();
const s32 iStartSeg =
(m_iClosestSegOnExpandedRoute > 0 && IsSpecialActionClimbOrDrop(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute-1].m_iWptFlag.GetSpecialActionFlags())==0)
? m_iClosestSegOnExpandedRoute - 1 : m_iClosestSegOnExpandedRoute;
for(s32 s=iStartSeg; s<=iClosestSegToSectionStart; s++)
{
pNewRoute->Add( RCC_VECTOR3(ms_vExpandedRouteVerts[s].GetPosRef()), wptEmpty );
}
}
// Append the next route to the new route up until capacity
for(p=0; p<m_pNextRouteSection->GetSize() && pNewRoute->GetSize() < CNavMeshRoute::MAX_NUM_ROUTE_ELEMENTS; p++)
{
pNewRoute->Add(
m_pNextRouteSection->GetPosition(p),
m_pNextRouteSection->GetWaypoint(p),
m_pNextRouteSection->GetSplineEnum(p),
m_pNextRouteSection->GetSplineCtrlPt2TVal(p),
m_pNextRouteSection->GetFreeSpaceApproachingWaypoint(p)
);
}
// Ensure that we didn't end on a climb/drop waypoint, we require there always to be
// space for a normal waypoint to exist in the route after these.
// Also ensure that there is no spline enum set up for the final point
if(pNewRoute->GetSize()==CNavMeshRoute::MAX_NUM_ROUTE_ELEMENTS)
{
TNavMeshWaypointFlag wpt = pNewRoute->GetWaypoint(pNewRoute->GetSize()-1);
if(wpt.IsClimb() || wpt.IsDrop() || wpt.IsLadderClimb())
{
pNewRoute->SetSize( pNewRoute->GetSize()-1 );
}
#if __ASSERT
// Test again; should always be false
wpt = pNewRoute->GetWaypoint(pNewRoute->GetSize()-1);
Assert(!(wpt.IsClimb() || wpt.IsDrop() || wpt.IsLadderClimb()));
#endif
// Clear spline enum on last point
pNewRoute->Set(pNewRoute->GetSize()-1, pNewRoute->GetPosition(pNewRoute->GetSize()-1), pNewRoute->GetWaypoint(pNewRoute->GetSize()-1));
}
pNewRoute->SetRouteFlags( m_pNextRouteSection->GetRouteFlags() );
delete m_pRoute;
delete m_pNextRouteSection;
m_pRoute = pNewRoute;
m_pNextRouteSection = NULL;
#if __ASSERT
m_pRoute->CheckValid();
#endif
}
CTask::FSM_Return CTaskNavBase::ProcessPreFSM()
{
ms_iNumExpandedRouteVerts = 0;
ms_vClosestSegmentOnRoute.Zero();
m_iClosestSegOnExpandedRoute = 0;
//*************************************************************************
CPed *pPed = GetPed(); // Get the ped ptr.
if(m_pOnEntityRouteData && m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn)
{
// Supress timeslicing when navigating on top of a dynamic entity
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
// We need to favour maneuverability over aesthetics when navigating around on top of a dynamic entity
pPed->SetPedResetFlag(CPED_RESET_FLAG_UseTighterTurnSettings, true);
}
//*************************************************************************
// If this path is on a dynamic navmesh, then we transform it every frame
RetransformRouteOnDynamicNavmesh(pPed);
//****************************************************************************
// Handle aborting the task & warping ped to the destination, due to time-out
if(m_WarpTimer.IsSet() && m_WarpTimer.IsOutOfTime())
{
if(!GetSubTask() || GetSubTask()->MakeAbortable( CTask::ABORT_PRIORITY_URGENT, 0))
{
// Find the actual ground position below the target point
bool bStupidUnusedVariable = true;
Vector3 vGroundNormal(VEC3_ZERO);
Vector3 vTeleportPos = m_vTarget;
// Raise up by 1 meter in case the position was on/intersecting the ground
vTeleportPos.z += 1.0f;
float fGroundZ = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, m_vTarget, &bStupidUnusedVariable, &vGroundNormal);
// If ground Z was relatively close to the specified target Z, we accept it
if(Abs(vTeleportPos.z - fGroundZ) < 4.0f)
{
// Add on the height from the ground to the ped's root bone, to place them correctly on the ground surface
vTeleportPos.z = fGroundZ + Abs(pPed->GetCapsuleInfo()->GetGroundToRootOffset());
}
else
{
// If position was greatly different, revert to the specified target to be on the safe side
vTeleportPos = m_vTarget;
}
float fTeleportHeading = (m_fTargetStopHeading == DEFAULT_NAVMESH_FINAL_HEADING) ? pPed->GetDesiredHeading() : m_fTargetStopHeading;
pPed->GetMotionData()->SetDesiredMoveBlendRatio(0.0f,0.0f);
pPed->Teleport(vTeleportPos, fTeleportHeading, true);
return FSM_Quit;
}
}
if(GetHasNewMoveSpeed())
{
PropagateNewMoveSpeed(m_fMoveBlendRatio);
}
if(m_pRoute)
{
if(ms_bUseExandRouteOptimisations)
{
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, Max(0, m_iProgress - 1), m_pRoute->GetSize(), ms_DefaultPathDistanceToExpand);
}
else
{
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, 0, m_pRoute->GetSize(), 9999.0f);
}
#if __DEV && DEBUG_DRAW
static dev_bool bDrawExpandedRoute = false;
if(bDrawExpandedRoute)
{
for(int i=0; i<ms_iNumExpandedRouteVerts-1; i++)
{
grcDebugDraw::Line(VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[i].GetPos()) + ZAXIS, VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[i+1].GetPos()) + ZAXIS, Color_cyan, Color_cyan, 2);
}
}
#endif
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
// Get the ped's progress along the linear sections stored in 'ms_vExpandedRouteVerts'
// Don't allow ever progress along the route to go backwards
if(ms_iNumExpandedRouteVerts)
{
m_iClosestSegOnExpandedRoute = (s16) CalculateClosestSegmentOnExpandedRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, vPedPos, m_fSplineTVal, &m_vClosestPosOnRoute, &ms_vClosestSegmentOnRoute);
// Advance to minimum 'm_iClampMinProgress'
while(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].m_iProgress < m_iClampMinProgress && m_iClosestSegOnExpandedRoute < ms_iNumExpandedRouteVerts-1)
m_iClosestSegOnExpandedRoute++;
}
else
{
m_iClosestSegOnExpandedRoute = 0;
}
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::UpdateFSM(const s32 iState, const FSM_Event iEvent)
{
CPed *pPed = GetPed(); //Get the ped ptr.
FSM_Begin
FSM_State(NavBaseState_Initial)
FSM_OnEnter
return StateInitial_OnEnter(pPed);
FSM_OnUpdate
return StateInitial_OnUpdate(pPed);
FSM_State(NavBaseState_WaitingInVehiclePriorToRequest)
FSM_OnUpdate
return StateWaitingInVehiclePriorToRequest_OnUpdate(pPed);
FSM_State(NavBaseState_RequestPath)
FSM_OnEnter
return StateRequestPath_OnEnter(pPed);
FSM_OnUpdate
return StateRequestPath_OnUpdate(pPed);
FSM_State(NavBaseState_MovingWhilstWaitingForPath)
FSM_OnEnter
return StateMovingWhilstWaitingForPath_OnEnter(pPed);
FSM_OnUpdate
return StateMovingWhilstWaitingForPath_OnUpdate(pPed);
FSM_State(NavBaseState_StationaryWhilstWaitingForPath)
FSM_OnEnter
return StateStationaryWhilstWaitingForPath_OnEnter(pPed);
FSM_OnUpdate
return StateStationaryWhilstWaitingForPath_OnUpdate(pPed);
FSM_State(NavBaseState_WaitingToLeaveVehicle)
FSM_OnUpdate
return StateWaitingToLeaveVehicle_OnUpdate(pPed);
FSM_State(NavBaseState_WaitingAfterFailureToObtainPath)
FSM_OnEnter
return StateWaitingAfterFailureToObtainPath_OnEnter(pPed);
FSM_OnUpdate
return StateWaitingAfterFailureToObtainPath_OnUpdate(pPed);
FSM_State(NavBaseState_FollowingPath)
FSM_OnEnter
return StateFollowingPath_OnEnter(pPed);
FSM_OnUpdate
return StateFollowingPath_OnUpdate(pPed);
FSM_State(NavBaseState_SlideToCoord)
FSM_OnEnter
return StateSlideToCoord_OnEnter(pPed);
FSM_OnUpdate
return StateSlideToCoord_OnUpdate(pPed);
FSM_State(NavBaseState_StoppingAtTarget)
FSM_OnEnter
return StateStoppingAtTarget_OnEnter(pPed);
FSM_OnUpdate
return StateStoppingAtTarget_OnUpdate(pPed);
FSM_State(NavBaseState_WaitingForWaypointActionEvent)
FSM_OnEnter
return StateWaitingForWaypointActionEvent_OnEnter(pPed);
FSM_OnUpdate
return StateWaitingForWaypointActionEvent_OnUpdate(pPed);
FSM_State(NavBaseState_WaitingAfterFailedWaypointAction)
FSM_OnEnter
return StateWaitingAfterFailedWaypointAction_OnEnter(pPed);
FSM_OnUpdate
return StateWaitingAfterFailedWaypointAction_OnUpdate(pPed);
FSM_State(NavBaseState_GetOntoMainNavMesh)
FSM_OnEnter
return StateGetOntoMainNavMesh_OnEnter(pPed);
FSM_OnUpdate
return StateGetOntoMainNavMesh_OnUpdate(pPed);
FSM_State(NavBaseState_AchieveHeading)
FSM_OnEnter
return StateAchieveHeading_OnEnter(pPed);
FSM_OnUpdate
return StateAchieveHeading_OnUpdate(pPed);
FSM_State(NavBaseState_UsingLadder)
FSM_OnEnter
return StateUsingLadder_OnEnter(pPed);
FSM_OnUpdate
return StateUsingLadder_OnUpdate(pPed);
FSM_End
}
CTask::FSM_Return CTaskNavBase::ProcessPostFSM()
{
// m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_WasAborted);
ms_iNumExpandedRouteVerts = 0;
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateInitial_OnEnter(CPed * UNUSED_PARAM(pPed))
{
if(!m_pRoute)
m_pRoute = rage_new CNavMeshRoute();
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateInitial_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
//Check if we should wait in the vehicle.
if(ShouldWaitInVehicle())
{
//Wait in the vehicle.
SetState(NavBaseState_WaitingInVehiclePriorToRequest);
}
else
{
//Request a path.
SetState(NavBaseState_RequestPath);
}
// Set the initial look ahead distance.
m_fCurrentLookAheadDistance = GetInitialGotoTargetLookAheadDistance();
// Look the default max navigable slope for the ped type, unless previously set.
if (!m_NavBaseConfigFlags.IsSet(CNavBaseInternalFlags::NB_UsingMaxSlopeNavigableOverride))
{
m_fMaxSlopeNavigable = GetPed()->GetPedIntelligence()->GetNavCapabilities().GetDefaultMaxSlopeNavigable();
}
return FSM_Continue;
}
bool CTaskNavBase::ForceVehicleDoorOpenForNavigation(CPed * pPed)
{
if(pPed->GetVehiclePedInside())
{
//Check if the ped is exiting the vehicle.
//Force the vehicle's door to be considered opened for navigation.
CTaskExitVehicle* pTask = static_cast<CTaskExitVehicle*>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE));
if(pTask && pTask->IsRunningFlagSet(CVehicleEnterExitFlags::IsFleeExit))
{
CCarDoor * pDoor = pTask->GetDoor();
if(pDoor)
{
//Ensure the vehicle is up-to-date (especially the doors).
pDoor->SetForceOpenForNavigation(true);
CVehicle* pVehicle = pPed->GetMyVehicle();
if(pVehicle->WasUnableToAddToPathServer())
{
CPathServerGta::MaybeAddDynamicObject(pVehicle);
}
if(pVehicle->IsInPathServer())
{
CPathServerGta::UpdateDynamicObject(pVehicle, true, true);
// This may cause doors to be updated twice but no matter
// Its important we call this to ensure doors get added to pathfinder if not already
pVehicle->UpdateDoorsForNavigation();
}
return true;
}
}
}
return false;
}
CTask::FSM_Return CTaskNavBase::StateWaitingInVehiclePriorToRequest_OnUpdate(CPed * pPed)
{
ForceVehicleDoorOpenForNavigation(pPed);
//Check if we should not wait in the vehicle.
if(!ShouldWaitInVehicle())
{
//Ensure the vehicle is up-to-date (especially the doors).
/*
CVehicle* pVehicle = pPed->GetMyVehicle();
if(pVehicle && pVehicle->IsInPathServer())
{
CPathServerGta::UpdateDynamicObject(pVehicle, true);
}
else if(pVehicle && pVehicle->WasUnableToAddToPathServer())
{
CPathServerGta::MaybeAddDynamicObject(pVehicle);
}
*/
//Request a path.
SetState(NavBaseState_RequestPath);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateRequestPath_OnEnter(CPed * pPed)
{
if(!m_pRoute)
m_pRoute = rage_new CNavMeshRoute;
ResetForRestart();
float fMinLookAhead = GetInitialGotoTargetLookAheadDistance();
m_fCurrentLookAheadDistance = Max(m_fCurrentLookAheadDistance, fMinLookAhead);
bool bOnDynamicEntity = false;
if(!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_HasBeenInitialised))
{
//***************************************************************************************
// If this route is on a dynamic entity, then initialise our m_pOnEntityRouteData member
if(pPed->GetGroundPhysical() &&
pPed->GetGroundPhysical()->GetType()==ENTITY_TYPE_VEHICLE &&
((CVehicle*)pPed->GetGroundPhysical())->m_nVehicleFlags.bHasDynamicNavMesh)
{
bOnDynamicEntity = true;
if(!m_pOnEntityRouteData)
m_pOnEntityRouteData = rage_new COnEntityRouteData();
m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn = pPed->GetGroundPhysical();
Matrix34 mInvMat;
mInvMat.Inverse(MAT34V_TO_MATRIX34(((CVehicle*)pPed->GetGroundPhysical())->GetMatrix()));
mInvMat.Transform(m_vTarget, m_pOnEntityRouteData->m_vLocalSpaceTarget);
}
}
if(!bOnDynamicEntity && m_pOnEntityRouteData)
{
delete m_pOnEntityRouteData;
m_pOnEntityRouteData = NULL;
}
//***************************************************************************
// Do anything which needs to be done on the first running of this task only
const bool bForceTarget = !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_HasBeenInitialised);
if(!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_HasBeenInitialised))
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_HasBeenInitialised);
if(m_iWarpTimeMs > 0)
m_WarpTimer.Set(fwTimer::GetTimeInMilliseconds(), m_iWarpTimeMs);
//***************************************************************
// Record if we are in a sequence of move tasks before/after us
bool bGotoTaskBefore, bGotoTaskAfter;
GetIsTaskSequencedBefore(pPed, bGotoTaskBefore);
GetIsTaskSequencedAfter(pPed, bGotoTaskAfter);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_GotoTaskSequencedBefore, bGotoTaskBefore);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_GotoTaskSequencedAfter, bGotoTaskAfter);
}
//******************************************
// Request the route in from the pathfinder
SetTarget(pPed, VECTOR3_TO_VEC3V(m_vNewTarget), bForceTarget);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_HadNewTarget);
SetTargetRadius(m_fTargetRadius);
SetSlowDownDistance(m_fSlowDownDistance);
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p due to StateRequestPath_OnEnter", fwTimer::GetFrameCount(), pPed); )
RequestPath(pPed);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateRequestPath_OnUpdate(CPed * pPed)
{
if(m_iPathHandle==PATH_HANDLE_NULL)
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_AbortedForDoorCollision);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_AbortedForCollisionEvent);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_ExceededVerticalDistanceFromPath);
SetState(NavBaseState_WaitingAfterFailureToObtainPath);
return FSM_Continue;
}
bool bMayUsePreviousTarget = true;
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath, true);
// Normal processing
if(pPed->GetVehiclePedInside()==NULL && ShouldKeepMovingWhilstWaitingForPath(pPed, bMayUsePreviousTarget))
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_AbortedForDoorCollision);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_AbortedForCollisionEvent);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_ExceededVerticalDistanceFromPath);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath, bMayUsePreviousTarget);
SetState(NavBaseState_MovingWhilstWaitingForPath);
return FSM_Continue;
}
else
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_AbortedForDoorCollision);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_AbortedForCollisionEvent);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_ExceededVerticalDistanceFromPath);
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateRequestPath_OnUpdate", fwTimer::GetFrameCount(), pPed); )
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
}
CTask::FSM_Return CTaskNavBase::StateMovingWhilstWaitingForPath_OnEnter(CPed * pPed)
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_OnTheFlyPathStartedAtRouteEnd);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_StartedFromInVehicle);
if(m_iPathHandle==PATH_HANDLE_NULL)
{
return FSM_Continue;
}
Vector3 vTempTarget;
bool bMoveTargetDecided = false;
if( GetSubTask() && m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath) &&
(GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_ON_ROUTE ||
GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL ||
(GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT &&
m_iProgress==CNavMeshRoute::MAX_NUM_ROUTE_ELEMENTS &&
GetPreviousState()==NavBaseState_FollowingPath) ||
(GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT &&
GetPreviousState()==NavBaseState_FollowingPath &&
m_pRoute && m_iProgress==m_pRoute->GetSize()-1) ) )
{
Vector3 vTarget = ((CTaskMove*)GetSubTask())->GetTarget();
Vector3 vVec = vTarget - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
if (vVec.Mag2() > 0.2f * 0.2f || m_fInitialMoveBlendRatio < MBR_RUN_BOUNDARY)
{
vVec.Normalize();
float fDotReq = (m_fInitialMoveBlendRatio < MBR_RUN_BOUNDARY ? 0.f : 0.866f);
if (vVec.Dot(VEC3V_TO_VECTOR3(pPed->GetTransform().GetForward())) > fDotReq)
{
vVec.Scale(m_fDistanceToMoveWhileWaitingForPath);
vTempTarget = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()) + vVec;
bMoveTargetDecided = true;
}
}
}
if (!bMoveTargetDecided)
{
// If strafing then move in the strafe direction
if(pPed->IsStrafing() && pPed->GetMotionData()->GetCurrentMoveBlendRatio().Mag2() > 0.1f)
{
Vector3 vTargetOffset( pPed->GetMotionData()->GetCurrentMoveBlendRatio(), Vector2::kXY);
vTargetOffset.Normalize();
vTargetOffset.Scale(m_fDistanceToMoveWhileWaitingForPath);
// moveblend ratio is in local space, so rotate by ped's heading
Matrix34 rotMat;
rotMat.Identity();
rotMat.RotateZ(pPed->GetCurrentHeading());
rotMat.Transform3x3(vTargetOffset);
vTempTarget = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()) + vTargetOffset;
}
// Otherwise move in the ped's heading
else
{
Vector3 vTargetOffset(-rage::Sinf(pPed->GetCurrentHeading()), rage::Cosf(pPed->GetCurrentHeading()), 0.0f);
vTargetOffset.Scale(m_fDistanceToMoveWhileWaitingForPath);
vTempTarget = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()) + vTargetOffset;
}
}
const float fTargetRadius =
(IsNavigatingUnderwater(pPed) || pPed->GetIsSkiing()) ?
2.0f : CTaskMoveGoToPoint::ms_fTargetRadius;
CTaskMoveGoToPoint * pMove = rage_new CTaskMoveGoToPoint(m_fInitialMoveBlendRatio, vTempTarget, fTargetRadius);
pMove->SetAllowNavmeshAvoidance(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SetAllowNavmeshAvoidance));
// turn on all avoidance since we don't have a path
pMove->SetDistanceFromPathToEnableNavMeshAvoidance(-1.0f);
pMove->SetDistanceFromPathToEnableObstacleAvoidance(-1.0f);
pMove->SetDistanceFromPathToEnableVehicleAvoidance(-1.0f);
SetNewTask(pMove);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath);
if(m_pRoute)
m_pRoute->Clear();
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateMovingWhilstWaitingForPath_OnUpdate(CPed * pPed)
{
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_LowPhysicsLodMayPlaceOnNavMesh, true);
// In this state we have to get an update every frame, or run the risk of penetrating collision
if( CPedAILodManager::IsTimeslicingEnabled() )
{
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
}
// If we are a lod physics LOD ped, and have moved off the navmesh - then immediately go into
// state NavBaseState_StationaryWhilstWaitingForPath. Peds may otherwise run through collision.
if(pPed->GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodPhysics) && !pPed->GetNavMeshTracker().GetIsValid())
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateMovingWhilstWaitingForPath_OnUpdate (tracker invalid)", fwTimer::GetFrameCount(), pPed); )
pPed->GetMotionData()->SetDesiredMoveBlendRatio(0.0f, 0.0f);
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
// If we are running directly into some obstruction then go immediately into stationary state
if(IsMovingIntoObstacle(pPed, true, true))
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateMovingWhilstWaitingForPath_OnUpdate (moving into obstacle)", fwTimer::GetFrameCount(), pPed); )
pPed->GetMotionData()->SetDesiredMoveBlendRatio(0.0f, 0.0f);
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
if(m_iPathHandle==PATH_HANDLE_NULL)
{
SetState(NavBaseState_WaitingAfterFailureToObtainPath);
return FSM_Continue;
}
Assert(GetSubTask() && (GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT || GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_FOLLOW_POINT_ROUTE));
EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_iPathHandle, EPath);
if(ret == ERequest_Ready)
{
TPathResultInfo pathResultInfo;
if(!RetrievePath(pathResultInfo, pPed, m_pRoute))
{
return OnFailedRoute(pPed, pathResultInfo);
}
if(OnSuccessfulRoute(pPed))
{
SetState(NavBaseState_FollowingPath);
}
else
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateMovingWhilstWaitingForPath_OnUpdate (unsuccessful route)", fwTimer::GetFrameCount(), pPed); )
SetState(NavBaseState_StationaryWhilstWaitingForPath);
}
return FSM_Continue;
}
else if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateMovingWhilstWaitingForPath_OnUpdate (move subtask is finished)", fwTimer::GetFrameCount(), pPed); )
Assert(m_iPathHandle!=PATH_HANDLE_NULL);
SetState(NavBaseState_StationaryWhilstWaitingForPath);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateStationaryWhilstWaitingForPath_OnEnter(CPed * pPed)
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_OnTheFlyPathStartedAtRouteEnd);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_StartedFromInVehicle, pPed->GetVehiclePedInside()!=NULL );
CTaskMoveStandStill * pTaskStill = rage_new CTaskMoveStandStill(1);
SetNewTask(pTaskStill);
if(m_pRoute)
m_pRoute->Clear();
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateStationaryWhilstWaitingForPath_OnUpdate(CPed * pPed)
{
if(pPed->GetVehiclePedInside()==NULL)
{
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_LowPhysicsLodMayPlaceOnNavMesh, true);
}
else
{
ForceVehicleDoorOpenForNavigation(pPed);
}
if(m_iPathHandle == PATH_HANDLE_NULL)
{
SetState(NavBaseState_WaitingAfterFailureToObtainPath);
return FSM_Continue;
}
Assert(!GetSubTask() || GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_STAND_STILL);
EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_iPathHandle, EPath);
if(ret == ERequest_Ready)
{
TPathResultInfo pathResultInfo;
if(!RetrievePath(pathResultInfo, pPed, m_pRoute))
{
return OnFailedRoute(pPed, pathResultInfo);
}
if( OnSuccessfulRoute(pPed) )
{
m_fCurrentLookAheadDistance = GetInitialGotoTargetLookAheadDistance();
if(pPed->GetVehiclePedInside())
{
SetState(NavBaseState_WaitingToLeaveVehicle);
}
else
{
SetState(NavBaseState_FollowingPath);
}
}
return FSM_Continue;
}
else if(GetIsFlagSet(aiTaskFlags::SubTaskFinished) || (!GetSubTask()) || ret == ERequest_NotFound)
{
if(ret == ERequest_NotFound)
m_iPathHandle = PATH_HANDLE_NULL;
//*************************************************
// Handle a new target being assigned to this task
s32 iStateBefore = GetState();
if(HandleNewTarget(pPed))
{
if(iStateBefore != GetState())
{
return FSM_Continue;
}
else
{
SetFlag(aiTaskFlags::RestartCurrentState);
return FSM_Continue;
}
}
else
{
CTaskMoveStandStill * pTaskStill = rage_new CTaskMoveStandStill(1);
SetNewTask(pTaskStill);
return FSM_Continue;
}
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingToLeaveVehicle_OnUpdate(CPed * pPed)
{
if(pPed->GetVehiclePedInside())
{
return FSM_Continue;
}
SetState(NavBaseState_FollowingPath);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingAfterFailureToObtainPath_OnEnter(CPed * UNUSED_PARAM(pPed))
{
CTaskMoveStandStill * pTaskStill = rage_new CTaskMoveStandStill(500);
SetNewTask(pTaskStill);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingAfterFailureToObtainPath_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
if(!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
SetState(NavBaseState_Initial);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateFollowingPath_OnEnter(CPed * pPed)
{
Assert(pPed->GetVehiclePedInside()==NULL);
CreateRouteFollowingTask(pPed);
CalcTimeNeededToCompleteRouteSegment(pPed, m_iProgress);
// Perform the initial scan only 1 second after we start following this route;
// helps with cases where the route has been calculated early (fleeing on foot, fleeing on vehicle) whilst some
// animation has been played out, but something has moved in our path in the meantime.
// Subsequent scans will be at ms_iScanForObstructionsFreq.
if( ms_bScanForObstructions && GetScanForObstructions() )
{
const u32 iTime = GetInitialScanForObstructionsTime();
m_ScanForObstructionsTimer.Set( fwTimer::GetTimeInMilliseconds(), iTime );
}
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_StartedFromInVehicle);
// Check the union of the special action flags on the route, before doing the full loop over the route points.
if(m_pRoute->GetPossiblySetSpecialActionFlags() & (WAYPOINT_FLAG_CLIMB_LADDER|WAYPOINT_FLAG_DESCEND_LADDER))
{
for (s32 p = 0; p < m_pRoute->GetSize(); ++p)
{
u32 SpecialActionFlags = m_pRoute->GetWaypoint(p).GetSpecialActionFlags();
if (SpecialActionFlags & (WAYPOINT_FLAG_CLIMB_LADDER|WAYPOINT_FLAG_DESCEND_LADDER))
{
// Stream the bloody ladder
if (!m_pLadderClipRequestHelper)
m_pLadderClipRequestHelper = CLadderClipSetRequestHelperPool::GetNextFreeHelper(pPed);
taskAssertf(NetworkInterface::IsGameInProgress() || m_pLadderClipRequestHelper, "CLadderClipSetRequestHelperPool::GetNextFreeHelper() Gave us NULL!? in CTaskNavBase::StateFollowingPath_OnEnter()");
if (m_pLadderClipRequestHelper)
{
int iStreamStates = CTaskClimbLadder::NEXT_STATE_CLIMBING;
if (SpecialActionFlags & WAYPOINT_FLAG_IS_ON_WATER_SURFACE)
iStreamStates |= CTaskClimbLadder::NEXT_STATE_WATERMOUNT;
CTaskClimbLadder::StreamReqResourcesIn(m_pLadderClipRequestHelper, pPed, iStreamStates);
}
break;
}
}
}
return FSM_Continue;
}
/*
PURPOSE
Helper function that takes two vectors, flattens them onto the XY plane, measures the angle between them,
and checks if it's less than some threshold. The threshold angle has to be less than 90 degrees, and is
specified as the square of the sine of the angle. The input vectors don't have to be normalized. It's safe
to call even if the length of the vectors in the XY plane is zero, though the results are not well defined.
The function effectively computes the angle using a cross product rather than a dot product, which is done
to make it more accurate for very acute angles.
PARAMS
inputVecA - Input vector A.
inputVecB - Input vector B.
squareSineAngleV - The square of the sine of the acute threshold angle.
RETURNS
True if the two vectors are within the specified angle.
NOTES
This function should basically be equivalent to this, but very much faster:
const float angA = fwAngle::GetRadianAngleBetweenPoints(inputVecA.GetXf(), inputVecA.GetYf(), 0.0f, 0.0f);
const float angB = fwAngle::GetRadianAngleBetweenPoints(inputVecB.GetXf(), inputVecB.GetYf(), 0.0f, 0.0f);
const float absDelta = Abs(SubtractAngleShorter(angA, angB));
return absDelta <= angle;
*/
static bool sIsAngleBetweenFlattenedVectorsLessThan(Vec3V_In inputVecA, Vec3V_In inputVecB, ScalarV_In squareSineAngleV)
{
// Treat the input as 4D vectors so we can build other 4D vectors from them.
const Vec4V inputVecA4V(inputVecA.GetIntrin128());
const Vec4V inputVecB4V(inputVecB.GetIntrin128());
// Build a 4D vectors with the X and Y elements from the both input vectors.
const Vec4V inputVecBXAXBYAYV = GetFromTwo<Vec::X1, Vec::X2, Vec::Y1, Vec::Y2>(inputVecB4V, inputVecA4V);
// Multiply each element, rotate, and add, so that lenProductsSumV contains the square
// of the lengths of the two vectors.
const Vec4V lenProductsV = Scale(inputVecBXAXBYAYV, inputVecBXAXBYAYV);
const Vec4V lenProductsRotV = lenProductsV.Get<Vec::Z, Vec::W, Vec::X, Vec::Y>();
const Vec4V lenProductsSumV = Add(lenProductsV, lenProductsRotV);
// Build vectors to use for computing the dot product between the two vectors, and between one input vector
// and the orthonormal vector of the other.
const Vec4V dotTermsAV = GetFromTwo<Vec::X1, Vec::Y1, Vec::Y1, Vec::X2>(inputVecA4V, Negate(inputVecA4V));
const Vec4V dotTermsBV = inputVecB4V.Get<Vec::X, Vec::X, Vec::Y, Vec::Y>();
// Multiply the elements, rotate, and add together.
const Vec4V dotProductsV = Scale(dotTermsBV, dotTermsAV);
const Vec4V dotProductsRotV = dotProductsV.Get<Vec::Z, Vec::W, Vec::X, Vec::Y>();
const Vec4V dotProductSumsV = Add(dotProductsV, dotProductsRotV);
// Now, we have all the dot products and lengths that we need.
const ScalarV fwdDotV = dotProductSumsV.GetX();
const ScalarV orthDotV = dotProductSumsV.GetY();
const ScalarV inputLenASqV = lenProductsSumV.GetY();
const ScalarV inputLenBSqV = lenProductsSumV.GetX();
// For the two vectors to be within the angle threshold, the dot product with the orthonormal
// (basically the Z component of the cross product of the flattened vectors in 3D space) must
// have an absolute value less than the sine of the angle, after dividing by the length of the
// input vectors in 2D. We multiply by the lengths and then square both sides of the inequality
// to avoid both division and square roots. In addition to this condition, the dot product between
// the two vectors also has to be positive.
const ScalarV orthDotSqV = Scale(orthDotV, orthDotV);
const ScalarV thresholdV = Scale(squareSineAngleV, Scale(inputLenASqV, inputLenBSqV));
const BoolV sineWithinThresholdV = IsLessThanOrEqual(orthDotSqV, thresholdV);
const BoolV cosRightSignV = IsGreaterThanOrEqual(fwdDotV, ScalarV(V_ZERO));
return IsTrue(And(sineWithinThresholdV, cosRightSignV));
}
// NAME : OnSuccessfulRoute
// PURPOSE : Base implementation of this, which must be called from within the derived version to set up various data for route-following
bool CTaskNavBase::OnSuccessfulRoute(CPed * pPed)
{
float fRouteLength;
m_iClosestSegOnExpandedRoute = 0;
m_iClampMinProgress = 0;
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, &fRouteLength, 0, m_pRoute->GetSize(), 9999.0f);
Assert(ms_iNumExpandedRouteVerts);
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
if(ms_iNumExpandedRouteVerts)
{
m_iClosestSegOnExpandedRoute = (s16) CalculateClosestSegmentOnExpandedRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, vPedPos, m_fSplineTVal, &m_vClosestPosOnRoute, &ms_vClosestSegmentOnRoute);
// Advance to minimum 'm_iClampMinProgress'
while(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].m_iProgress < m_iClampMinProgress && m_iClosestSegOnExpandedRoute < ms_iNumExpandedRouteVerts-1)
m_iClosestSegOnExpandedRoute++;
}
else
{
m_iClosestSegOnExpandedRoute = 0;
m_iProgress = 0;
return false;
}
s32 iProgress = Min(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].m_iProgress + 1, m_pRoute->GetSize() - 1);
Assert(iProgress >= 0 && iProgress < 127);
m_iProgress = (s8) iProgress;
if (!GetNeverSlowDownForPathLength())
{
m_fMoveBlendRatio = AdjustMoveBlendRatioForRouteLength(m_fInitialMoveBlendRatio, fRouteLength);
}
return true;
}
CTask::FSM_Return CTaskNavBase::StateFollowingPath_OnUpdate(CPed * pPed)
{
Assert(ms_iNumExpandedRouteVerts != 0);
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_LowPhysicsLodMayPlaceOnNavMesh, true);
if (pPed->GetIsSwimming())
pPed->SetPedResetFlag(CPED_RESET_FLAG_UseTighterTurnSettings, true);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_HasPathed, true);
// Keep try to stream in those anims if any
if (m_pLadderClipRequestHelper)
m_pLadderClipRequestHelper->RequestAllClipSets();
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_RestartNextFrame))
{
SetState(NavBaseState_Initial);
return FSM_Continue;
}
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_ForceRestartFollowPathState))
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_ForceRestartFollowPathState, false);
SetFlag(aiTaskFlags::RestartCurrentState);
return FSM_Continue;
}
#if __REQUEST_ROUTE_WHILST_FOLLOWING_PATH
//----------------------------------------------------------------------------
// Handle retrieving a path which has been pending whilst we follow the route
// TODO: Most of this copied from StateMovingWhilstWaitingForPath_OnUpdate()
// and as such should be copied into a shared function.
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly) && m_iPathHandle != PATH_HANDLE_NULL)
{
EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_iPathHandle, EPath);
if(ret == ERequest_Ready)
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly);
TPathResultInfo pathResultInfo;
Assert(!m_pNextRouteSection);
if(m_pNextRouteSection)
m_pNextRouteSection->Clear();
else
m_pNextRouteSection = rage_new CNavMeshRoute();
if(!RetrievePath(pathResultInfo, pPed, m_pNextRouteSection))
{
delete m_pNextRouteSection;
m_pNextRouteSection = NULL;
return OnFailedRoute(pPed, pathResultInfo);
}
}
else if(ret == ERequest_NotFound)
{
AI_OPTIMISATIONS_OFF_ONLY( Assert(false); )
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateFollowingPath_OnUpdate (path request not found)", fwTimer::GetFrameCount(), pPed); )
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly);
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
}
//--------------------------------------------------------
// Determine if we are ready to switch to our new route?
if(m_pNextRouteSection)
{
SwitchToNextRoute();
if(OnSuccessfulRoute(pPed))
{
SetFlag(aiTaskFlags::RestartCurrentState);
return FSM_Continue;
}
else
{
SetState(NavBaseState_WaitingAfterFailureToObtainPath);
return FSM_Continue;
}
}
#endif // __REQUEST_ROUTE_WHILST_FOLLOWING_PATH
if(m_pRoute && IsSpecialActionClimbOrDrop(m_pRoute->GetPossiblySetSpecialActionFlags()))
{
// TODO: We should probably look at each route just once and see if it even contains
// any of these points, so we don't have to do it for each update, in most cases.
const int routeSize = m_pRoute->GetSize();
for(s32 i=m_iProgress; i<routeSize; i++)
{
if( IsSpecialActionClimbOrDrop( m_pRoute->GetWaypoint(i).GetSpecialActionFlags() ) )
{
// Prevent ped entering low-lod physics if we are approaching a waypoint which prohibits it
pPed->GetPedAiLod().SetBlockedLodFlag( CPedAILod::AL_LodPhysics );
break;
}
}
// Check for the possibility of having missed a climb point, and being stuck running into geometry
// Note: an assumption that is made here is that ScanForMissedClimb() can't return true
// unless IsSpecialActionClimbOrDrop() returns true for any of the points on the path.
if( ScanForMissedClimb(pPed) )
{
return FSM_Continue;
}
}
CTask* pSubTask = GetSubTask();
if(!pSubTask)
{
#if NAVMESH_OPTIMISATIONS_OFF
Vector3 pos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
Assertf(false, "ped 0x%p at (%.1f, %.1f, %.1f) was left in navmesh state FollowingPath, but with no subtask (task will restart)", pPed, pos.x, pos.y, pos.z);
#endif
m_NavBaseConfigFlags.Reset(CNavBaseConfigFlags::NB_KeepMovingWhilstWaitingForPath);
SetState(NavBaseState_Initial);
return FSM_Continue;
}
const Vec3V pedPosV = pPed->GetMatrixRef().GetCol3();
//---------------------------------------------------------------------------------
// Detect if the ped has fallen off something and is now underneath their route
static dev_float fFallenThresholdZ = 3.0f;
if(!pPed->GetIsSwimming() && !pPed->GetCapsuleInfo()->IsBird() && !pPed->GetCapsuleInfo()->IsFish())
{
const float fDeltaZ = m_vClosestPosOnRoute.z - pedPosV.GetZf();
if(fDeltaZ > fFallenThresholdZ)
{
// We set this flag so that ShouldKeepMovingWhilstWaitingForPath() will may return true
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_ExceededVerticalDistanceFromPath, true);
SetState(NavBaseState_Initial);
return FSM_Continue;
}
}
bool bForceNextWptStep = false;
bool bForceProceedToNextWaypoint = false;
bool bForceCompleteRoute = false;
const s32 iPreviousProgress = m_iProgress;
//************************************************************************************************************
// Check whether target radius from target has been reached
// This needs to be done even if we are not on the last leg of the route, or our final point isn't our target
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_TargetIsDestination)) // Only if our target position is our destination (ie. not for fleeing or wandering paths)
{
// Code before optimization:
// const float fTargetHeightDelta = pPed->GetIsSwimming() ? 4.0f : 1.5f;//CTaskMoveGoToPoint::ms_fTargetHeightDelta;
// const Vector3 vDiff = m_vTarget - VEC3V_TO_VECTOR3(pedPosV);
// if(vDiff.XYMag2() < (m_fTargetRadius*m_fTargetRadius) && Abs(vDiff.z) < fTargetHeightDelta)
const Vec4V constantsV(Vec::V4VConstant<
FLOAT_TO_INT(4.0f*4.0f), // Square of swimming target height delta
FLOAT_TO_INT(1.5f*1.5f), // Square of regular target height delta
FLOAT_TO_INT(0.5f*0.5f), // Underwater.
FLOAT_TO_INT(0.0f)>()); // Unused
ScalarV targetHeightDeltaV;
if(pPed->GetIsSwimming() )
{
//! Special case for navigating to vehicle entry point. Don't assume we are there if we are 4m under ground!
if(IsNavigatingUnderwater(pPed) &&
pPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringVehicle) &&
pedPosV.GetZf() < m_vTarget.z)
{
targetHeightDeltaV = constantsV.GetZ();
}
else
{
targetHeightDeltaV = constantsV.GetX();
}
}
else
{
targetHeightDeltaV = constantsV.GetY();
}
const Vec3V diffV = Subtract(VECTOR3_TO_VEC3V(m_vTarget), pedPosV);
const Vec3V diffSqV = Scale(diffV, diffV);
const ScalarV diffSqXYV = Add(diffSqV.GetX(), diffSqV.GetY());
const ScalarV targetRadiusV = LoadScalar32IntoScalarV(m_fTargetRadius);
const ScalarV targetRadiusSqV = Scale(targetRadiusV, targetRadiusV);
const BoolV withinTargetXYV = IsLessThan(diffSqXYV, targetRadiusSqV);
const BoolV withinTargetZV = IsLessThan(diffSqV.GetZ(), targetHeightDeltaV); // Use the square we already computed rather than an Abs().
const BoolV withinTargetV = And(withinTargetXYV, withinTargetZV);
if(IsTrue(withinTargetV))
{
// Force entire navigation task to complete
Assign(m_iProgress, m_pRoute->GetSize()-1);
bForceCompleteRoute = true;
m_pRoute->SetRouteFlags( m_pRoute->GetRouteFlags() | CNavMeshRoute::FLAG_LAST_ROUTE_POINT_IS_TARGET );
//m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_LastRoutePointIsTarget, true);
}
}
//***************************************
// Update ped's progress along the route
if(!bForceCompleteRoute)
{
const s32 iProgress = ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].m_iProgress + 1;
// Never allow progress to go back to a progress which has a waypoint we've already completed
if(iProgress < m_iProgress) // && IsSpecialActionClimbOrDrop(m_pRoute->GetWaypoint(iProgress).GetSpecialActionFlags()))
{
}
else if( iProgress != m_iProgress )
{
if( iProgress > m_iProgress )
{
CalcTimeNeededToCompleteRouteSegment(pPed, iProgress);
m_fCurrentTargetRadius = GetTargetRadiusForWaypoint(m_iProgress);
((CTaskMove*)pSubTask)->SetTargetRadius(m_fCurrentTargetRadius);
}
Assert(iProgress >= 0 && iProgress < 127);
m_iProgress = (s8) iProgress;
bForceProceedToNextWaypoint = true;
}
// If we are approaching a drop-down then start scanning for a drop-down so that we may have the chance
// to use the proper CTaskDropDown, instead of just running off the edge
#if __USE_DROPDOWN_TASK
if( !bForceProceedToNextWaypoint && m_iClosestSegOnExpandedRoute+1 < ms_iNumExpandedRouteVerts &&
ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute+1].m_iWptFlag.IsDrop() )
{
const float fDistXY = MagXY(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute+1].GetPos() - pedPosV).Getf();
static dev_float fDropDownDist = 1.0f;
if( fDistXY < fDropDownDist )
{
pPed->SetPedResetFlag(CPED_RESET_FLAG_SearchingForDropDown, true);
const u8 nFlags = CDropDownDetector::DSF_AsyncTests | CDropDownDetector::DSF_TestForDive;
pPed->GetPedIntelligence()->GetDropDownDetector().Process(NULL, nFlags);
if(pPed->GetPedIntelligence()->GetDropDownDetector().HasDetectedDropDown())
{
bForceProceedToNextWaypoint = true;
}
}
}
#endif
}
//************************
// Process path following
// This will be set later
bool bWithinApproxStoppingDist = false;
bool bApproachingWaypoint = false;
bool distToStopWithinStoppingDist = false;
u32 iRetFlags = 0;
ScalarV distanceRemainingV(V_FLT_MAX);
if(!bForceProceedToNextWaypoint && !bForceCompleteRoute)
{
//*****************************************************************************
// Handle case of ped swimming into geometry whilst trying to get out of water.
// Detect this case and automatically create a climb event
static dev_s32 iSwimClimbOutStaticCount = 5;
// Ped is in water (at least partially), and their next waypoint requires for them to be out of water
if( pPed->GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().GetStaticCounter() >= iSwimClimbOutStaticCount &&
(pPed->GetIsSwimming() || pPed->m_Buoyancy.GetStatus()!=NOT_IN_WATER) &&
//(m_iProgress+1 < m_pRoute->GetSize()) &&
((m_pRoute->GetWaypoint(m_iProgress).GetSpecialActionFlags()&WAYPOINT_FLAG_IS_ON_WATER_SURFACE)==0))
{
if( CreateClimbOutOfWaterEvent(pPed, RCC_VECTOR3(pedPosV)) )
{
SetState(NavBaseState_WaitingForWaypointActionEvent);
return FSM_Continue;
}
}
//**************************************************************************************
// If we are stuck running into something, then (optionally?) generate a breakout event
// TODO: Allow this behaviour to be switchable via a flag
if(pPed->GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().GetStaticCounter() >= CStaticMovementScanner::ms_iStaticCounterStuckLimit)
{
m_NavBaseConfigFlags.Reset(CNavBaseConfigFlags::NB_KeepMovingWhilstWaitingForPath);
CEventStaticCountReachedMax staticEvent(GetTaskType());
pPed->GetPedIntelligence()->AddEvent(staticEvent);
}
//****************************************************************************
// If we are adaptively scaling the SetTarget() epsilon, then see how long it
// has been since we rejected the last target. If it is over some delta value
// then we should check for a target update using the normal means.
if(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_UseAdaptiveSetTargetEpsilon) && m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_LastSetTargetWasNotUsed))
{
static const u32 iMaxNewTargetTimeDelta = 5000;
const u32 iDeltaMs = fwTimer::GetTimeInMilliseconds() - m_iTimeOfLastSetTargetCall;
if(iDeltaMs >= iMaxNewTargetTimeDelta)
{
m_NavBaseConfigFlags.Reset(CNavBaseConfigFlags::NB_UseAdaptiveSetTargetEpsilon);
SetTarget(pPed, VECTOR3_TO_VEC3V(m_vNewTarget), false);
m_NavBaseConfigFlags.Set(CNavBaseConfigFlags::NB_UseAdaptiveSetTargetEpsilon);
}
}
//*******************************************************************************************************
// If the task is set up to recalculate the path at regular intervals, then see if the timer has elapsed
if(m_iRecalcPathFreqMs > 0)
{
m_iRecalcPathTimerMs -= (s16)GetTimeStepInMilliseconds();
if(m_iRecalcPathTimerMs <= 0)
{
m_iRecalcPathTimerMs = m_iRecalcPathFreqMs;
//*****************************************************************************
// Setting this flag forces a target update, which in turn requests a new path
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_HadNewTarget, true);
}
}
//*************************************************
// Handle a new target being assigned to this task
s32 iStateBefore = GetState();
if(HandleNewTarget(pPed))
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CheckedStoppingDistance);
// Ensure that we break out of any stopping behaviour in the subtask, or we might end
// up sliding the rest of the way to the new target.. (url:bugstar:1246800)
if(pSubTask && pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
CTaskMoveGoToPointAndStandStill * pGotoTask = (CTaskMoveGoToPointAndStandStill*)pSubTask;
if(pGotoTask->GetState() != CTaskMoveGoToPointAndStandStill::EGoingToPoint)
{
pGotoTask->RestartNextFrame();
}
}
// Did HandleNewTarget() cause a state transition?
if(iStateBefore != GetState())
{
return FSM_Continue;
}
}
//#if __REQUEST_ROUTE_WHILST_FOLLOWING_PATH
// return FSM_Continue;
//#endif
//********************************************************************************************
// Process the timer which handles whether we've spent too long on the current route segment
if(m_iTimeShouldSpendOnCurrentRouteSegmentMs != 0)
{
// values are uint16s, so we want to avoid overflow
int iTimeSoFar = m_iTimeSpentOnCurrentRouteSegmentMs + GetTimeStepInMilliseconds();
if(iTimeSoFar > 65535)
iTimeSoFar = 65535;
m_iTimeSpentOnCurrentRouteSegmentMs = (u16)iTimeSoFar;
if(m_iTimeSpentOnCurrentRouteSegmentMs >= m_iTimeShouldSpendOnCurrentRouteSegmentMs)
{
if(pSubTask->MakeAbortable( ABORT_PRIORITY_URGENT, NULL))
{
SetState(NavBaseState_Initial);
return FSM_Continue;
}
}
}
if( ms_bScanForObstructions && m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_ScanForObstructions) )
{
if( m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidObjects) )
{
if( m_ScanForObstructionsTimer.IsOutOfTime() )
{
m_ScanForObstructionsTimer.Set( fwTimer::GetTimeInMilliseconds(), CalcScanForObstructionsFreq() );
// Take this opportunity to detect whether we've left proximity of any plane we started our route inside
if( m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_PathStartedInsidePlaneVehicle) )
{
ScanForMovedOutsidePlaneBounds(pPed);
}
// Scan ahead along the route for obstructions
float fDistanceToObstruction;
if( ScanForObstructions(pPed, ms_fScanForObstructionsDistance, fDistanceToObstruction ) )
{
#if __BANK && AI_OPTIMISATIONS_OFF
static dev_bool bNotifyOnRepath = false;
if(bNotifyOnRepath)
{
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
Displayf("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
Displayf("Ped 0x%x at (%.1f, %.1f, %.1f) has repathed round obstacle\n", pPed, vPedPos.x, vPedPos.y, vPedPos.z);
Displayf("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n");
}
#endif
// If we are currently in the middle of calculating a new path on-the-fly, then
// don't bother responding to this obstruction: it is most likely that the new path
// will already deal with it when it is ready.
if(m_iPathHandle == PATH_HANDLE_NULL
&& !m_pNextRouteSection
&& !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly))
{
// If the distance to the obstruction is sufficiently far away, calculate new route whilst following this one
if(fDistanceToObstruction > ms_fDistanceAheadForSetNewTargetObstruction)
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p on-the-fly, due to ScanForObstruction()", fwTimer::GetFrameCount(), pPed); )
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_OnTheFlyPathStartedAtRouteEnd, true);
RequestPath(pPed, Max(fDistanceToObstruction - 1.0f, 0.0f));
}
// Otherwise we must stop and search for a new path
else
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p stationary, due to ScanForObstruction()", fwTimer::GetFrameCount(), pPed); )
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateFollowingPath_OnUpdate (impending obstruction)", fwTimer::GetFrameCount(), pPed); )
RequestPath(pPed);
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
}
}
}
}
}
//***************************************************************************************************************
// Determine if we are reaching the end of entire route, or are approaching a waypoint which must be stopped for
// by comparing the ped's animated stopping distance with the distance left to this event.
const ScalarV stoppingDistV(V_TEN /* 10.0f */); // some value which is longer than any actual move-stop animation
const ScalarV noEdgeAvoidanceDistV(V_TWO);
#if !__MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
ScalarV lengthOfFinalSegmentV;
ScalarV* lengthOfFinalSegmentPtr = &lengthOfFinalSegmentV;
#else
ScalarV* lengthOfFinalSegmentPtr = NULL;
#endif
ScalarV distRemainingToClimbWptV(V_FLT_MAX);
ScalarV distRemainingToDropWptV(V_FLT_MAX);
bool bHaltAtClimbs = m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_DontStopForClimbs) ? false : true;
if (pPed->GetWaterLevelOnPed() > 0.33f) // Yeah.. Some peds are having issues exiting pools
bHaltAtClimbs = true;
Vec3V stopAtPosV; // Note: should always be getting a value by GetPositionAheadOnRoute().
Assert(ms_iNumExpandedRouteVerts != 0);
// See whether we are in range of the end of route, or a waypoint which requires stopping for
iRetFlags = GetPositionAheadOnRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts,
pedPosV, stoppingDistV, stopAtPosV,
distanceRemainingV, lengthOfFinalSegmentPtr, bHaltAtClimbs, false, &distRemainingToClimbWptV, &distRemainingToDropWptV);
bWithinApproxStoppingDist = ((iRetFlags&(GPAFlag_HitWaypointFlag | GPAFlag_HitRouteEnd)) != 0);
bApproachingWaypoint = ((iRetFlags&GPAFlag_HitWaypointFlag) != 0);
if (iRetFlags & GPAFlag_HitWaypointFlagPullOut)
{
pPed->SetPedResetFlag(CPED_RESET_FLAG_UseTighterTurnSettings, true);
}
const Vec2V velV(VECTOR3_TO_INTRIN(pPed->GetVelocity()));
const ScalarV XYSpeedV = Mag(velV);
if(IsGreaterThanAll(XYSpeedV, ScalarV(V_ZERO)))
{
const ScalarV TValueV = InvScale(Min(distRemainingToClimbWptV, distanceRemainingV), XYSpeedV);
const ScalarV maxAvoidanceTValueV = InvScale(LoadScalar32IntoScalarV(CTaskMoveGoToPoint::ms_fAvoidanceScanAheadDistance), XYSpeedV);
if(pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
CTaskMoveGoToPointAndStandStill * pGotoTask = (CTaskMoveGoToPointAndStandStill*)pSubTask;
pGotoTask->SetMaxAvoidanceTValue(Min(maxAvoidanceTValueV, TValueV));
}
else if ( pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT )
{
CTaskMoveGoToPoint * pGotoTask = (CTaskMoveGoToPoint*)pSubTask;
pGotoTask->SetMaxAvoidanceTValue(Min(maxAvoidanceTValueV, TValueV));
}
}
// If we are close approaching a climb/drop then turn off navmesh edge avoidance, since we'll need to leave the navmesh
if( ((iRetFlags & GPAFlag_UpcomingClimbOrDrop)!=0) && (IsLessThanAll(distRemainingToClimbWptV, noEdgeAvoidanceDistV) || IsLessThanAll(distRemainingToDropWptV, noEdgeAvoidanceDistV)) )
{
pPed->SetPedResetFlag(CPED_RESET_FLAG_DisableSteeringAroundNavMeshEdges, true);
}
const Vec3V pedPosToStopAtPosV = Subtract(stopAtPosV, pedPosV);
const ScalarV flatDistToStopAtPosSqV(MagSquared(Vec2V(pedPosToStopAtPosV.GetX(), pedPosToStopAtPosV.GetY())));
// So we are close to that waypoint then we must try to nail it or we might miss it as we don't got swimstops
// We also do this for ladders for more "seemless/quicker" get ons
// Also, be less sensitive to stopping at waypoints whilst in water
if (((pPed->GetIsSwimming() || (iRetFlags&GPSFlag_HitWaypointFlagLadder) /* = bCloseToLadderNode*/)) ||
((iRetFlags & GPAFlag_HitWaypointFlag) && pPed->GetWaterLevelOnPed() > 0.33f))
{
if(IsLessThanAll(flatDistToStopAtPosSqV, ScalarV(0.3f)))
{
bForceProceedToNextWaypoint = true;
bForceNextWptStep = true;
}
}
const ScalarV currentTargetRadiusV = LoadScalar32IntoScalarV(m_fCurrentTargetRadius);
const ScalarV currentTargetRadiusSqV = Scale(currentTargetRadiusV, currentTargetRadiusV);
// Incase we got a huge radius we must make sure we are not stopping for a waypoint, if so, we would slide all the way to it
distToStopWithinStoppingDist = !bApproachingWaypoint && bWithinApproxStoppingDist && IsLessThanAll(flatDistToStopAtPosSqV, currentTargetRadiusSqV);
// If our subtask is CTaskMoveGoToPointAndStandStill, then we must tell it how much distance is left before we need to stop
// Also if we are unable to play a stop animation we should instruct the subtask to slow approaching the target instead.
if(pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
CTaskMoveGoToPointAndStandStill * pGotoTask = (CTaskMoveGoToPointAndStandStill*)pSubTask;
// If we're stopping & are within target radius, then we're as good as done
if(distToStopWithinStoppingDist)
{
const ScalarV distRemainingZeroThresholdV(0.05f);
distanceRemainingV = Sqrt(flatDistToStopAtPosSqV);
distanceRemainingV = SelectFT(IsLessThan(distanceRemainingV, distRemainingZeroThresholdV), distanceRemainingV, ScalarV(V_ZERO));
// if(fDistanceRemaining < 0.05f)
// fDistanceRemaining = 0.0f;
}
#if !__MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
if(bWithinApproxStoppingDist && m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_StopExactly) && pGotoTask->GetStopExactly()
&& !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CheckedStoppingDistance))
{
FastAssert(pPed->GetCurrentMotionTask());
m_fAnimatedStoppingDistance = pPed->GetCurrentMotionTask()->GetStoppingDistance();
if(m_fAnimatedStoppingDistance > lengthOfFinalSegmentV.Getf())
{
pGotoTask->SetUseRunstopInsteadOfSlowingToWalk(false);
pGotoTask->SetSlowDownDistance(m_fSlowDownDistance > 0.0f ? m_fSlowDownDistance : CTaskMoveGoToPointAndStandStill::ms_fSlowDownDistance);
pGotoTask->SetStopExactly(false);
}
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_CheckedStoppingDistance, true);
}
#endif // !__MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
pGotoTask->SetDistanceRemaining(distanceRemainingV);
}
#if __MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
// Obtain the exact distance which this ped will take to stop
if(bWithinApproxStoppingDist && m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_StopExactly) &&
!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CheckedStoppingDistance))
{
float fStopHeading = m_fTargetStopHeading != DEFAULT_NAVMESH_FINAL_HEADING ? m_fTargetStopHeading : pPed->GetCurrentHeading();
m_fAnimatedStoppingDistance = pPed->GetCurrentMotionTask()->GetStoppingDistance(fStopHeading);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_CheckedStoppingDistance, true);
}
#endif
// When reaching the end of our route it is essential that we have an update every frame, or we will
// get peds running into/through collision geometry.
if( CPedAILodManager::IsTimeslicingEnabled() )
{
const ScalarV animatedStoppingDistanceV = LoadScalar32IntoScalarV(m_fAnimatedStoppingDistance);
// m_fAnimatedStoppingDistance will always be 0.0 for ped running low LOD motion tasks..
// Old float version:
// if( m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CheckedStoppingDistance) &&
// fDistanceRemaining < m_fAnimatedStoppingDistance)
if( m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CheckedStoppingDistance) &&
IsLessThanAll(distanceRemainingV, animatedStoppingDistanceV))
{
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
}
// ..in which case we will use some arbitrary distance which we hope will be enough to trigger despite the low frequency task updates
// Old float version:
// else if( m_fAnimatedStoppingDistance==0.0f && fDistanceRemaining < 2.0f )
else if( IsTrue(And(IsEqual(animatedStoppingDistanceV, ScalarV(V_ZERO)), IsLessThan(distanceRemainingV, ScalarV(V_TWO)))))
{
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
}
// We also have to suppress timeslicing when we are approaching the end of a route, and we
// intend to calculate the next route section 'on-the-fly'; if timeslicing is enabled here then
// we will miss the opportunity to issue the path request in enough time, resulting in the ped
// transitioning to stationary.
else if( !GetLastRoutePointIsTarget() && (!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly)) &&
IsLessThan(distanceRemainingV, ScalarV(ms_fDistanceFromRouteEndToRequestNextSection) + ScalarV(V_TWO)).Getb() )
{
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
}
}
//*************************************************************************
// Calculate distances and times to look ahead from current route position
#if !__MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
static dev_float fMinAngleDeltaForLos = 2.0f * DtoR;
Vector3 vGotoPointTarget;
ProcessLookAhead(pPed, bWithinStoppingDist, vGotoPointTarget);
((CTaskMove*)pSubTask)->SetTarget(pPed, vGotoPointTarget);
if( pPed->GetPedAiLod().IsLodFlagSet( CPedAILod::AL_LodPhysics ) == false && !pPed->IsStrafing() )
{
const float fAngleToGotoTarget = fwAngle::GetRadianAngleBetweenPoints(vGotoPointTarget.x, vGotoPointTarget.y, vPedPos.x, vPedPos.y);
const float fPedHeading = pPed->GetCurrentHeading();
const float fAbsDelta = Abs( SubtractAngleShorter(fAngleToGotoTarget, fPedHeading) );
if( fAbsDelta > fMinAngleDeltaForLos )
{
pPed->GetNavMeshTracker().SetPerformLosAheadLineTest(true);
}
}
#else // !__MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
const bool bSubtaskIsStopping =
pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL &&
((CTaskMoveGoToPointAndStandStill*)pSubTask)->GetIsStopping();
if(!bSubtaskIsStopping)
{
Vector3 vGotoPointTarget, vLookAheadTarget;
ProcessLookAhead(pPed, bWithinApproxStoppingDist, vGotoPointTarget, vLookAheadTarget);
((CTaskMove*)pSubTask)->SetTarget(pPed, VECTOR3_TO_VEC3V(vGotoPointTarget));
((CTaskMove*)pSubTask)->SetLookAheadTarget(pPed, vLookAheadTarget);
if( pPed->GetPedAiLod().IsLodFlagSet( CPedAILod::AL_LodPhysics ) == false && !pPed->IsStrafing() )
{
if(iRetFlags & GPAFlag_HitWaypointFlagPullOut)
{
pPed->GetNavMeshTracker().SetPerformLosAheadLineTest(true);
}
else
{
// This used to be
// static dev_float fMinAngleDeltaForLos = 2.0f * DtoR;
// const float fAngleToGotoTarget = fwAngle::GetRadianAngleBetweenPoints(vLookAheadTarget.x, vLookAheadTarget.y, pedPosV.GetXf(), pedPosV.GetYf());
// const float fPedHeading = pPed->GetCurrentHeading();
// const float fAbsDelta = Abs( SubtractAngleShorter(fAngleToGotoTarget, fPedHeading) );
// if(fAbsDelta > fMinAngleDeltaForLos)
// The new code should be equivalent, but much faster (no atan2(), etc):
const Vec3V pedFwdV = pPed->GetMatrixRef().GetCol1();
const Vec3V toTargetV = Subtract(VECTOR3_TO_VEC3V(vLookAheadTarget), pedPosV);
const ScalarV squareSineAngleV(0.00121797487f); // square(sinf(fMinAngleDeltaForLos)) fMinAngleDeltaForLos = 2.0f * DtoR;
if(!sIsAngleBetweenFlattenedVectorsLessThan(pedFwdV, toTargetV, squareSineAngleV))
{
pPed->GetNavMeshTracker().SetPerformLosAheadLineTest(true);
}
}
}
//It is important to get frequent updates when we are running and deviate from our path, to avoid wobbling and over-correction.
//However, this really only matters for on-screen peds.
if(CPedAILodManager::IsTimeslicingEnabled() && !pPed->GetPedAiLod().IsBlockedFlagSet(CPedAILod::AL_LodTimesliceIntelligenceUpdate) &&
!pPed->GetPedAiLod().GetForceNoTimesliceIntelligenceUpdate() && !pPed->GetMotionData()->GetIsLessThanRun() &&
pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_VisibleOnScreen))
{
//After a lot of testing, it seems we can really only allow time slicing
//in this situation when moving in the correct direction on a straight path.
Vec3V vPedToGotoPointTarget = Subtract(VECTOR3_TO_VEC3V(vGotoPointTarget), pPed->GetTransform().GetPosition());
Vec3V vPedToGotoPointTargetXY = vPedToGotoPointTarget;
vPedToGotoPointTargetXY.SetZ(ScalarV(V_ZERO));
Vec3V vPedToGotoPointTargetXYDirection = NormalizeFastSafe(vPedToGotoPointTargetXY, Vec3V(V_ZERO));
Vec3V vPedForward = pPed->GetTransform().GetForward();
Vec3V vPedForwardXY = vPedForward;
vPedForwardXY.SetZ(ScalarV(V_ZERO));
Vec3V vPedVelocity = VECTOR3_TO_VEC3V(pPed->GetVelocity());
Vec3V vPedVelocityXY = vPedVelocity;
vPedVelocityXY.SetZ(ScalarV(V_ZERO));
Vec3V vPedXYDirection = NormalizeFastSafe(vPedVelocityXY, vPedForwardXY);
ScalarV scDot = Dot(vPedXYDirection, vPedToGotoPointTargetXYDirection);
static dev_float s_fMinDotForFacing = 0.98f;
ScalarV scMinDot = ScalarVFromF32(s_fMinDotForFacing);
if(IsLessThanAll(scDot, scMinDot))
{
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
}
else
{
Vec3V vGotoPointTargetToLookAheadTarget = Subtract(VECTOR3_TO_VEC3V(vLookAheadTarget), VECTOR3_TO_VEC3V(vGotoPointTarget));
Vec3V vGotoPointTargetToLookAheadTargetXY = vGotoPointTargetToLookAheadTarget;
vGotoPointTargetToLookAheadTargetXY.SetZ(ScalarV(V_ZERO));
Vec3V vGotoPointTargetToLookAheadTargetXYDirection = NormalizeFastSafe(vGotoPointTargetToLookAheadTargetXY, Vec3V(V_ZERO));
scDot = Dot(vPedToGotoPointTargetXYDirection, vGotoPointTargetToLookAheadTargetXYDirection);
static dev_float s_fMinDotForLookAhead = 0.98f;
scMinDot = ScalarVFromF32(s_fMinDotForLookAhead);
if(IsLessThanAll(scDot, scMinDot))
{
pPed->GetPedAiLod().SetForceNoTimesliceIntelligenceUpdate(true);
}
}
}
}
if (bSubtaskIsStopping)
{
// m_fCurrentLookAheadDistance = 0.5f;
// This is a hack to make the destination for points in shallow water more sane, and fixing odd sliding on the spot issues
s32 iSeg = m_iClosestSegOnExpandedRoute;
if ((ms_vExpandedRouteVerts[iSeg+1].m_iWptFlag.GetSpecialActionFlags() & WAYPOINT_FLAG_IS_ON_WATER_SURFACE) && pPed->GetWaterLevelOnPed() < 0.33f)
{
const Vec3V vecToWptV = Subtract(ms_vExpandedRouteVerts[iSeg+1].GetPos(), pedPosV);
const ScalarV distToWptSqV = MagXYSquared(vecToWptV);
const ScalarV minDistToWptSqV(0.36f);
if (IsLessThanAll(distToWptSqV, minDistToWptSqV))
{
ScalarV newZV = ms_vExpandedRouteVerts[iSeg+1].m_vPosAndSplineT.GetZ() + ScalarV(pPed->GetCapsuleInfo()->GetGroundToRootOffset());
ms_vExpandedRouteVerts[iSeg+1].m_vPosAndSplineT.SetZ(newZV);
ms_vExpandedRouteVerts[iSeg+1].m_iWptFlag.m_iSpecialActionFlags = ms_vExpandedRouteVerts[iSeg+1].m_iWptFlag.m_iSpecialActionFlags & (~WAYPOINT_FLAG_IS_ON_WATER_SURFACE);
}
}
Vector3 vGotoPointTarget, vLookAheadTarget;
ProcessLookAhead(pPed, bWithinApproxStoppingDist, vGotoPointTarget, vLookAheadTarget);
((CTaskMove*)pSubTask)->SetTarget(pPed, VECTOR3_TO_VEC3V(vGotoPointTarget));
((CTaskMove*)pSubTask)->SetLookAheadTarget(pPed, vLookAheadTarget);
bool bLocomotionTaskIsStopping = false;
// Only do this if stopping
const CTaskMotionBase* pTask = pPed->GetCurrentMotionTask();
if (pTask)
{
if (pTask->GetTaskType() == CTaskTypes::TASK_HUMAN_LOCOMOTION)
{
const CTaskHumanLocomotion* pHumanLocomotion = static_cast<const CTaskHumanLocomotion*>(pTask);
bLocomotionTaskIsStopping = pHumanLocomotion->IsFullyInStop() || pHumanLocomotion->IsTryingToStop() || pTask->GetState() == CTaskHumanLocomotion::State_Stop;
}
else if (pTask->GetTaskType() == CTaskTypes::TASK_ON_FOOT_QUAD)
{
bLocomotionTaskIsStopping = pTask->GetState() == CTaskQuadLocomotion::State_StopLocomotion;
}
}
if(bLocomotionTaskIsStopping) // Start rotate as we blend also
{
const bool bSubtaskIsRunStop =
pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL &&
((CTaskMoveGoToPointAndStandStill*)pSubTask)->GetIsRunStop();
if (CPedMotionData::GetIsWalking(m_fMoveBlendRatio) && !bSubtaskIsRunStop)
{
}
else
{
Vector3 vDist = vGotoPointTarget - VEC3V_TO_VECTOR3(pedPosV);
// fix for heading issues, taken from CL 3047955
// Current heading is not interpolated towards desired whilst ped is stopping
// so we manually achieve this by applying extra heading change
const float fCurrentHeading = pPed->GetMotionData()->GetCurrentHeading();
if (vDist.XYMag2() > 0.4f * 0.4f)
{
static dev_float fHeadingSpeed = 6.0f;
// static dev_float fHeadingSpeedFleeFixup = 2.0f;
float fModHeadingSpeed = fHeadingSpeed;
const float fTargetHeading = fwAngle::LimitRadianAngleFast( fwAngle::GetRadianAngleBetweenPoints( vGotoPointTarget.x, vGotoPointTarget.y, pedPosV.GetXf(), pedPosV.GetYf() ) );
const float fHeadingDelta = SubtractAngleShorter( fTargetHeading, fCurrentHeading );
pPed->GetMotionData()->SetDesiredHeading( fTargetHeading );
pPed->GetMotionData()->SetExtraHeadingChangeThisFrame( fHeadingDelta * fModHeadingSpeed * fwTimer::GetTimeStep() );
}
else
{
static dev_float fHeadingSpeed = 4.0f;
const float fTargetHeading = pPed->GetDesiredHeading();
float fHeadingDelta = SubtractAngleShorter( fTargetHeading, fCurrentHeading ) / fwTimer::GetTimeStep();
if (fHeadingDelta > 0.f)
fHeadingDelta = Min(fHeadingSpeed, fHeadingDelta);
else
fHeadingDelta = Max(-fHeadingSpeed, fHeadingDelta);
pPed->GetMotionData()->SetExtraHeadingChangeThisFrame( fHeadingDelta * fwTimer::GetTimeStep() );
}
}
}
}
#endif // !__MAY_WALKSTOP_OVER_MULTIPLE_ROUTE_SEGMENTS
//***************************************************
// Set the bFollowingRoute reset flag if appropriate
pPed->SetPedResetFlag( CPED_RESET_FLAG_FollowingRoute, true );
//*********************************************************************************
// If we have overshot the route end, identify this case and increment the progress
// This can happen because we cannot slide the goto target past the route end, and
// thus risk circling the target position if we don't achieve the target radius.
const int routeSize = m_pRoute->GetSize();
const int expandedSize = ms_iNumExpandedRouteVerts;
if( m_iProgress+1 >= routeSize && routeSize >= 2 &&
m_iClosestSegOnExpandedRoute + 2 >= expandedSize && expandedSize >= 2 &&
IsLessThanAll(distanceRemainingV, ScalarV(ms_DefaultPathDistanceToExpand)))
{
// Old Vector3/float version of the code:
// const Vector3 & v1 = m_pRoute->GetPosition(routeSize-2);
// const Vector3 & v2 = m_pRoute->GetPosition(routeSize-1);
// Vector3 vSeg = v2 - v1;
// vSeg.Normalize();
// const float fD = - DotProduct( vSeg, v2 );
// const float fPlanarDist = DotProduct(VEC3V_TO_VECTOR3(pedPosV), vSeg) + fD;
// if( -fPlanarDist < m_fCurrentTargetRadius )
//
// The new version basically has the math rearranged a bit, allowing us to
// eliminate the square root and division in the normalization, by basically
// squaring on both sides of the inequality, taking into account that -fPlanarDist
// can be negative.
const Vec3V seg1V = ms_vExpandedRouteVerts[expandedSize-2].GetPos();
const Vec3V seg2V = ms_vExpandedRouteVerts[expandedSize-1].GetPos();
const Vec3V segDeltaV = Subtract(seg2V, seg1V);
const Vec3V pedPosToSeg2V = Subtract(seg2V, pedPosV);
const ScalarV dotV = Dot(segDeltaV, pedPosToSeg2V);
const ScalarV zeroV(V_ZERO);
const ScalarV dotSqV = Scale(dotV, dotV);
const ScalarV currentTargetRadiusSqV = Scale(currentTargetRadiusV, currentTargetRadiusV);
const ScalarV segLenSqV = MagSquared(segDeltaV);
const ScalarV thresholdSqV = Scale(currentTargetRadiusSqV, segLenSqV);
const BoolV dotLessThanZeroV = IsLessThan(dotV, zeroV);
const BoolV dotLessThanThresholdV = IsLessThan(dotSqV, thresholdSqV);
const BoolV planarDistWithinTargetV = Or(dotLessThanZeroV, dotLessThanThresholdV);
if(IsTrue(planarDistWithinTargetV))
{
if(pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
CTaskMoveGoToPointAndStandStill * pGotoTask = (CTaskMoveGoToPointAndStandStill*)pSubTask;
if (m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_StopExactly))
{
pGotoTask->ResetDistanceRemaining(pPed); // So we can slide that last bit
}
else
{
pGotoTask->SetDistanceRemaining(0.0f);
}
}
bForceCompleteRoute = true;
}
}
}
if(!distToStopWithinStoppingDist)
{
if(pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
((CTaskMoveGoToPointAndStandStill*)pSubTask)->SetDontCompleteThisFrame();
}
else if(pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT)
{
((CTaskMoveGoToPoint*)pSubTask)->SetDontCompleteThisFrame();
}
else
{
Assert(false);
}
}
//*******************************************************************************************
// Save state information about the closest point on the current route. This used to be set
// directly into the running CTaskMoveGoToPoint task, but it was problematic to set this if
// a new task was set this frame (especially in the case of CTaskMoveGoToPointAndStandStill,
// which wont even have a CTaskMoveGoToPoint subtask until it is run.
pPed->GetPedIntelligence()->SetClosestPosOnRoute(m_vClosestPosOnRoute, ms_vClosestSegmentOnRoute);
//***********************************************************************************************************
// Process when the goto subtask has finished.
// NB: The previous logic may have terminated the subtask, so don't enclose this as in 'else..' statement
const bool bSubtaskFinished = GetIsFlagSet(aiTaskFlags::SubTaskFinished);
if(bSubtaskFinished || bForceProceedToNextWaypoint || bForceCompleteRoute)
{
//*****************************************
// Otherwise increment progress as normal
if(!bForceCompleteRoute)
{
if( bSubtaskFinished && !bForceProceedToNextWaypoint )
{
m_iProgress++;
((CTaskMove*)pSubTask)->SetTargetRadius(GetTargetRadiusForWaypoint(m_iProgress));
}
const s32 iNewProgress = m_iProgress;
//************************************************************************************
// Process special waypoints which may involve climbing, dropping, using ladders etc.
// These are usually handled by way of an event. We need to iterate from the last
// progress to the current to avoid missing any actions in between.
s32 iMaxProgress = Min((s32)m_iProgress, m_pRoute->GetSize());
if (bForceNextWptStep && iPreviousProgress == iMaxProgress)
iMaxProgress = Min(iMaxProgress + 2, m_pRoute->GetSize());
for(s32 p=iPreviousProgress; p<iMaxProgress; p++)
{
const u32 iSpecialAction = m_pRoute->GetWaypoint(p).GetSpecialActionFlags();
if(IsSpecialActionClimbOrDrop(iSpecialAction))
{
// Prevent ped entering low-lod physics if we are approaching a waypoint which prohibits it
pPed->GetPedAiLod().SetBlockedLodFlag( CPedAILod::AL_LodPhysics );
Assert(p >= 0 && p < 127);
m_iProgress = (s8) p;
ASSERT_ONLY(bool bOk;)
const EWaypointActionResult eWptActionResult = CreateWaypointActionEvent(pPed, p);
switch(eWptActionResult)
{
case WaypointAction_EventCreated:
SetState(NavBaseState_WaitingForWaypointActionEvent);
return FSM_Continue;
case WaypointAction_ActionFailed:
SetState(NavBaseState_WaitingAfterFailedWaypointAction);
return FSM_Continue;
case WaypointAction_EventNotNeeded:
case WaypointAction_ActionNotFound:
Assert(iNewProgress >= 0 && iNewProgress < 127);
m_iProgress = (s8) iNewProgress;
ASSERT_ONLY(bOk =) CreateRouteFollowingTask(pPed);
Assert(bOk);
return FSM_Continue;
case WaypointAction_StateTransitioned:
return FSM_Continue;
}
}
}
}
else
{
// Force route completion
s32 iProgress = m_pRoute->GetSize();
Assert(iProgress >= 0 && iProgress < 127);
m_iProgress = (s8) iProgress;
}
if( m_iProgress >= m_pRoute->GetSize() || (bSubtaskFinished && bWithinApproxStoppingDist) )
{
//if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_LastRoutePointIsTarget))
if(GetLastRoutePointIsTarget())
{
#if __REQUEST_ROUTE_WHILST_FOLLOWING_PATH
// We may have reached out subtask's destination at the same time as having received a
// new target; if we are calculating our path on the fly then we need to restart.
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly))
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateFollowingPath_OnUpdate (reached end of route whilst repathing on-the-fly)", fwTimer::GetFrameCount(), pPed); )
Assert(!m_pNextRouteSection);
if(m_pNextRouteSection)
delete m_pNextRouteSection;
m_pNextRouteSection = NULL;
Assert(m_iPathHandle != PATH_HANDLE_NULL);
//SetState(NavBaseState_StationaryWhilstWaitingForPath);
SetState(NavBaseState_MovingWhilstWaitingForPath);
return FSM_Continue;
}
#endif
if(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SlideToCoordAtEnd))
{
SetState(NavBaseState_SlideToCoord);
return FSM_Continue;
}
else
{
if (pSubTask && pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
CTaskMoveGoToPointAndStandStill* pTask = static_cast<CTaskMoveGoToPointAndStandStill*>(pSubTask);
if (pTask->IsStoppedAtTargetPoint())
{
//pPed->GetMotionData()->SetDesiredHeading( CalculateFinalHeading(pPed) );
return FSM_Quit;
}
else
{
return DecideStoppingState(pPed);
}
}
else
{
return FSM_Quit;
}
}
}
else
{
if(m_pNextRouteSection)
{
SwitchToNextRoute();
if(OnSuccessfulRoute(pPed))
{
SetFlag(aiTaskFlags::RestartCurrentState);
return FSM_Continue;
}
else
{
SetState(NavBaseState_WaitingAfterFailureToObtainPath);
return FSM_Continue;
}
}
else
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p stationary, due to reaching route end with no m_pNextRouteSection", fwTimer::GetFrameCount(), pPed); )
RequestPath(pPed);
bool bMayUsePreviousTarget = true;
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath, true);
if(ShouldKeepMovingWhilstWaitingForPath(pPed, bMayUsePreviousTarget))
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath, bMayUsePreviousTarget);
SetState(NavBaseState_MovingWhilstWaitingForPath);
return FSM_Continue;
}
else
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from StateFollowingPath_OnUpdate (reached end of route but we were not on-the-fly repathing && ShouldKeepMovingWhilstWaitingForPath returned false)", fwTimer::GetFrameCount(), pPed); )
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
}
}
}
}
#if __REQUEST_ROUTE_WHILST_FOLLOWING_PATH
//-------------------------------------------------------------------------------------------------
// If we are within 'ms_fDistanceFromRouteEndToRequestNextSection' from the end of our route, and
// the current route does not take us to our target, then request another path 'on-the-fly'
const bool bWithinEndRequestDistance =
(distanceRemainingV.Getf() < GetRouteDistanceFromEndForNextRequest()) ||
(!GetLastRoutePointIsTarget() && distanceRemainingV.Getf() < ms_fDistanceFromRouteEndToRequestNextSection);
if(m_iPathHandle == PATH_HANDLE_NULL
&& !m_pNextRouteSection
&& !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly)
&& !m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_DisableCalculatePathOnTheFly)
&& bWithinEndRequestDistance
&& !bApproachingWaypoint )
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p on-the-fly following path, due to approaching end of current route section", fwTimer::GetFrameCount(), pPed); )
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_OnTheFlyPathStartedAtRouteEnd, true);
RequestPath(pPed, 9999.0f);
}
#endif
// Gradually increase the look-ahead distance up to the maximum
if(!pPed->IsStrafing())
{
const bool bSubtaskIsStopping =
pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL &&
((CTaskMoveGoToPointAndStandStill*)pSubTask)->GetIsStopping();
const float fMinLookAheadDistance = (bSubtaskIsStopping ? 0.5f : GetInitialGotoTargetLookAheadDistance());
const float fMaxLookAheadDistance = GetGotoTargetLookAheadDistance();
const float fLookAheadInc = GetLookAheadDistanceIncrement();
// JB: Turned this off again as it creates ugly swerving around objects (eg. url:bugstar:1085569)
TUNE_GROUP_BOOL(PATH_FOLLOWING, bPerformObjectLookAheadLOS, false);
// Only perform a dynamic object LOS if we have a clear navmesh LOS; to save processing time
const bool bObjectLOS =
(bPerformObjectLookAheadLOS && pPed->GetNavMeshTracker().GetWasLosAheadTestClear()) ? TestObjectLookAheadLOS(pPed) : true;
float timeStep = GetTimeStep();
// Peds party submerged in water might fail their LOS tests as they are below the navmesh
if(!bSubtaskIsStopping && ((pPed->GetNavMeshTracker().GetWasLosAheadTestClear() && bObjectLOS) || pPed->GetWaterLevelOnPed() > 0.5f))
{
const float newUnclampedDist = m_fCurrentLookAheadDistance + fLookAheadInc * timeStep;
m_fCurrentLookAheadDistance = Min(newUnclampedDist, fMaxLookAheadDistance);
}
else
{
const float newUnclampedDist = m_fCurrentLookAheadDistance - fLookAheadInc * timeStep;
m_fCurrentLookAheadDistance = Max(newUnclampedDist, fMinLookAheadDistance);
}
}
else
{
static dev_float fDistStrafing = 0.6f;
static dev_float fDistStrafingTight = 0.3f;
m_fCurrentLookAheadDistance = (iRetFlags & GPAFlag_HitWaypointFlagPullOut) ? fDistStrafingTight : fDistStrafing;
}
if (pSubTask && pSubTask->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
if (((CTaskMoveGoToPointAndStandStill*)pSubTask)->GetIsStopDueToInsideRadius())
{
return DecideStoppingState(pPed);
}
}
return FSM_Continue;
}
float CTaskNavBase::CalculateFinalHeading(CPed * pPed) const
{
int iPt1, iPt2;
if(m_iClosestSegOnExpandedRoute < ms_iNumExpandedRouteVerts)
{
iPt2 = m_iClosestSegOnExpandedRoute;
iPt1 = m_iClosestSegOnExpandedRoute-1;
}
else
{
iPt2 = ms_iNumExpandedRouteVerts-1;
iPt1 = ms_iNumExpandedRouteVerts-2;
}
if(iPt1 >= 0 && iPt2 >= 0)
{
Vector3 v1 = VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[ iPt1 ].GetPos());
Vector3 v2 = VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[ iPt2 ].GetPos());
const float fFinalHdg = fwAngle::GetRadianAngleBetweenPoints(v2.x, v2.y, v1.x, v1.y);
return fFinalHdg;
}
return pPed->GetMotionData()->GetCurrentHeading();
}
void CTaskNavBase::ProcessLookAhead(CPed * pPed, const bool bWithinSlowDownDist, Vector3 & vOut_GotoPointTarget, Vector3 & vOut_LookAheadTarget)
{
const Vec3V pedPosV = pPed->GetTransform().GetPosition();
Assert( rage::FPIsFinite(pedPosV.GetXf()) && rage::FPIsFinite(pedPosV.GetYf()) && rage::FPIsFinite(pedPosV.GetZf()) );
//*************************************************************************
// Calculate distances and times to look ahead from current route position
const float fCorneringLookAheadTime = ms_fCorneringLookAheadTime;
const ScalarV goToPointLookAheadDistV = LoadScalar32IntoScalarV(m_fCurrentLookAheadDistance);
const ScalarV lookAheadPointDistanceV = Add(goToPointLookAheadDistV, ScalarVFromF32(GetLookAheadExtra())); // TODO: Would be good if GetLookAheadExtra() returned a ScalarV.
//**********************************************
// Find the target to use for our goto subtask
Assert(ms_iNumExpandedRouteVerts != 0);
const bool bHaltAtWaypoint = bWithinSlowDownDist && (m_iProgress < m_pRoute->GetSize() && ShouldSlowDownForWaypoint(m_pRoute->GetWaypoint(m_iProgress)));
ScalarV distanceRemainingV; // Note: unused
GetPositionAheadOnRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, pedPosV, goToPointLookAheadDistV, RC_VEC3V(vOut_GotoPointTarget), distanceRemainingV, NULL, bHaltAtWaypoint, false);
GetPositionAheadOnRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, pedPosV, lookAheadPointDistanceV, RC_VEC3V(vOut_LookAheadTarget), distanceRemainingV, NULL, bHaltAtWaypoint, false);
pPed->SetPedResetFlag( CPED_RESET_FLAG_TakingRouteSplineCorner, (m_fSplineTVal > 0.0f && m_fSplineTVal < 1.0f) );
//*************************************************************************
// Update the subtask's target to implement following curved spline paths
CTask * pSubTask = GetSubTask();
if(pSubTask)
{
const int iSubTaskType = pSubTask->GetTaskType();
if(iSubTaskType==CTaskTypes::TASK_MOVE_GO_TO_POINT ||
iSubTaskType==CTaskTypes::TASK_MOVE_GO_TO_POINT_ON_ROUTE ||
iSubTaskType==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
{
//**************************************************************************************
// Look ahead to find a position ahead along the path to use for slowing for corners
const float fCurrMbr = Clamp(pPed->GetMotionData()->GetCurrentMbrY(), MOVEBLENDRATIO_STILL, MOVEBLENDRATIO_SPRINT);
if( !pPed->IsStrafing() && !pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InStrafeTransition) )
{
CMoveBlendRatioSpeeds moveSpeeds;
CMoveBlendRatioTurnRates turnRates;
CTaskMotionBase* pMotionTask = pPed->GetCurrentMotionTask();
pMotionTask->GetMoveSpeeds(moveSpeeds);
pMotionTask->GetTurnRates(turnRates);
const float fMoveSpeed = CTaskMotionBase::GetActualMoveSpeed(moveSpeeds.GetSpeedsArray(), fCurrMbr);
const float fCorneringLookAheadDist = fMoveSpeed * fCorneringLookAheadTime;
ScalarV distanceRemainingV; // Note: unused
Assert(ms_iNumExpandedRouteVerts != 0);
Vec3V corneringTargetV;
GetPositionAheadOnRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, pedPosV, ScalarVFromF32(fCorneringLookAheadDist), corneringTargetV, distanceRemainingV, NULL, bHaltAtWaypoint, false);
#if __BANK && defined(DEBUG_DRAW)
if(CPedDebugVisualiser::GetDebugDisplay()==CPedDebugVisualiser::eTasksFullDebugging || CPedDebugVisualiser::GetDebugDisplay()==CPedDebugVisualiser::eTasksNoText)
grcDebugDraw::Cross(corneringTargetV, 0.25f, Color_orange);
#endif
//*************************************************************************
// Slow this ped when taking sharp corners, to ensure we stay on our route
ProcessCornering(pPed, corneringTargetV, fCorneringLookAheadTime, moveSpeeds, turnRates);
}
//*********************************************************************************************
// Ensure we reset the moveblendratio to this task's MBR if we didn't call ProcessCornering()
else
{
((CTaskMove*)pSubTask)->SetMoveBlendRatio(m_fMoveBlendRatio);
}
pPed->SetPedResetFlag( CPED_RESET_FLAG_HasProcessedCornering, true );
}
}
if (GetFollowNavmeshAtCurrentHeight())
{
vOut_GotoPointTarget.z = pedPosV.GetZf();
}
Assert( rage::FPIsFinite(vOut_GotoPointTarget.x) && rage::FPIsFinite(vOut_GotoPointTarget.y) && rage::FPIsFinite(vOut_GotoPointTarget.z) );
Assert( rage::FPIsFinite(vOut_LookAheadTarget.x) && rage::FPIsFinite(vOut_LookAheadTarget.y) && rage::FPIsFinite(vOut_LookAheadTarget.z) );
}
float CTaskNavBase::GetLookAheadExtra() const
{
const float fFrames = 4.f;
return GetPed()->GetVelocity().Mag() * fwTimer::GetTimeStep() * fFrames;
}
float CTaskNavBase::GetLookAheadDistanceIncrement() const
{
return GetPed()->GetPedIntelligence()->GetNavCapabilities().GetGotoTargetLookAheadDistanceIncrement();
}
float CTaskNavBase::GetInitialGotoTargetLookAheadDistance() const
{
const float fMinInitialLookAhead = (GetPed()->GetIsSwimming() ? 0.5f : 0.0f); // Shit maybe but swimming peds got issues as their path is way below or above them
return Max(GetPed()->GetPedIntelligence()->GetNavCapabilities().GetGotoTargetInitialLookAheadDistance(), fMinInitialLookAhead);
}
float CTaskNavBase::GetGotoTargetLookAheadDistance() const
{
return GetPed()->GetPedIntelligence()->GetNavCapabilities().GetGotoTargetLookAheadDistance();
}
CTask::FSM_Return CTaskNavBase::StateSlideToCoord_OnEnter(CPed * pPed)
{
Assert(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SlideToCoordAtEnd));
const float fHdg = m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SlideToCoordUsingCurrentHeading) ? pPed->GetCurrentHeading() : m_fSlideToCoordHeading;
CTaskMoveSlideToCoord * pSlideTask = rage_new CTaskMoveSlideToCoord(m_vTarget, fHdg, m_fSlideToCoordSpeed, m_fSlideToCoordHeadingChangeSpeed, 2000);
SetNewTask(pSlideTask);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateSlideToCoord_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
if(!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
return FSM_Quit;
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateStoppingAtTarget_OnEnter(CPed * pPed)
{
// Make sure we set the end as target or we might in some circumstances when we just look ahead a bit, not stop where we should B* 1235560, 1270405
if (GetSubTask())
{
// If we nailed the radius we just stop at a good point inside it
if (GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL/* &&
((CTaskMoveGoToPointAndStandStill*)GetSubTask())->GetIsStopDueToInsideRadius()*/)
{
float fStopHeading = m_fTargetStopHeading != DEFAULT_NAVMESH_FINAL_HEADING ? m_fTargetStopHeading : pPed->GetCurrentHeading();
m_fCurrentLookAheadDistance = pPed->GetCurrentMotionTask()->GetStoppingDistance(fStopHeading);
Vector3 vGotoPointTarget, vLookAheadTarget;
ProcessLookAhead(pPed, true, vGotoPointTarget, vLookAheadTarget);
((CTaskMove*)GetSubTask())->SetTarget(pPed, VECTOR3_TO_VEC3V(vGotoPointTarget));
}
else if (m_pRoute && m_pRoute->GetSize() > 0)
{
((CTaskMove*)GetSubTask())->SetTarget(pPed, VECTOR3_TO_VEC3V(m_pRoute->GetPosition(m_pRoute->GetSize() - 1)));
}
else
{
((CTaskMove*)GetSubTask())->SetTarget(pPed, VECTOR3_TO_VEC3V(m_vTarget));
}
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateStoppingAtTarget_OnUpdate(CPed * pPed)
{
pPed->SetPedConfigFlag(CPED_CONFIG_FLAG_LowPhysicsLodMayPlaceOnNavMesh, true);
// Handle the case where the task was assigned a new target whilst stopping
if(HandleNewTarget(pPed))
{
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return FSM_Continue;
}
Assert(!GetSubTask() || GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL);
if(!GetSubTask() || GetSubTask()->GetTaskType()!=CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL)
return FSM_Quit;
CTaskMoveGoToPointAndStandStill* pTask = static_cast<CTaskMoveGoToPointAndStandStill*>(GetSubTask());
if (m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_StopExactly))
((CTaskMoveGoToPointAndStandStill*)GetSubTask())->ResetDistanceRemaining(pPed);
else
((CTaskMoveGoToPointAndStandStill*)GetSubTask())->SetDistanceRemaining(0.0f);
if (m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_StopExactly))
{
// So runstop nail desired heading code
Vector3 vDist = pTask->GetTarget() - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
if (CPedMotionData::GetIsWalking(m_fMoveBlendRatio) && !pTask->GetIsRunStop())
{
}
else if(vDist.XYMag2() <= g_fFixupRunHeadingRadiusSqr) //
{
static dev_float fHeadingSpeed = 4.0f;
const float fTargetHeading = pPed->GetDesiredHeading();
const float fCurrentHeading = pPed->GetMotionData()->GetCurrentHeading();
float fHeadingDelta = SubtractAngleShorter( fTargetHeading, fCurrentHeading ) / fwTimer::GetTimeStep();
if (fHeadingDelta > 0.f)
fHeadingDelta = Min(fHeadingSpeed, fHeadingDelta);
else
fHeadingDelta = Max(-fHeadingSpeed, fHeadingDelta);
pPed->GetMotionData()->SetExtraHeadingChangeThisFrame( fHeadingDelta * fwTimer::GetTimeStep() );
}
}
if (pTask->IsStoppedAtTargetPoint())
{
return FSM_Quit;
}
//--------------------------------------------------------------------------------
// Handle ped becoming stuck whilst sliding to target (see url:bugstar:1388664)
if(pPed->GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().GetStaticCounter() >= CStaticMovementScanner::ms_iStaticCounterStuckLimit)
{
m_NavBaseConfigFlags.Reset(CNavBaseConfigFlags::NB_KeepMovingWhilstWaitingForPath);
CEventStaticCountReachedMax staticEvent(GetTaskType());
pPed->GetPedIntelligence()->AddEvent(staticEvent);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingForWaypointActionEvent_OnEnter(CPed * UNUSED_PARAM(pPed))
{
//Assert(m_pRoute && m_iProgress >= 0 && m_iProgress < m_pRoute->GetSize()-1);
if(m_iPathHandle!=PATH_HANDLE_NULL)
{
CPathServer::CancelRequest(m_iPathHandle);
m_iPathHandle = PATH_HANDLE_NULL;
}
CTaskMoveStandStill * pTaskStandStill = rage_new CTaskMoveStandStill(2000);
SetNewTask(pTaskStandStill);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingForWaypointActionEvent_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
Assert(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_STAND_STILL);
// If the stand-still task has completed without the special-action event having occured
// then we will restart the task.
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
AI_OPTIMISATIONS_OFF_ONLY( Assertf(false, "StateWaitingForWaypointActionEvent_OnUpdate - never received the waypoint event."); )
SetState(NavBaseState_Initial);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingAfterFailedWaypointAction_OnEnter(CPed * UNUSED_PARAM(pPed))
{
CTaskMoveStandStill * pTaskStandStill = rage_new CTaskMoveStandStill(2000);
SetNewTask(pTaskStandStill);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateWaitingAfterFailedWaypointAction_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
// Restart the task
SetState(NavBaseState_Initial);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateGetOntoMainNavMesh_OnEnter(CPed * ASSERT_ONLY(pPed))
{
#if NAVMESH_OPTIMISATIONS_OFF && __ASSERT
Displayf("PED 0x%p at (%.1f, %.1f, %.1f) about to run CTaskMoveGetOntoMainNavMesh", pPed, pPed->GetTransform().GetPosition().GetXf(), pPed->GetTransform().GetPosition().GetYf(), pPed->GetTransform().GetPosition().GetZf());
#endif
Assert(pPed->GetIsTypePed()); // just to silence the compiler when the above statement is #defined out
CTaskMoveGetOntoMainNavMesh * pRecoveryTask = rage_new CTaskMoveGetOntoMainNavMesh(m_fInitialMoveBlendRatio, m_vTarget, m_iLastRecoveryMode);
SetNewTask(pRecoveryTask);
m_iLastRecoveryMode++;
if(m_iLastRecoveryMode == CTaskMoveGetOntoMainNavMesh::RecoveryMode_NumModes)
m_iLastRecoveryMode = 0;
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateGetOntoMainNavMesh_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
// Restart the task
SetState(NavBaseState_Initial);
}
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateAchieveHeading_OnEnter(CPed * UNUSED_PARAM(pPed))
{
Assert(m_fTargetStopHeading < DEFAULT_NAVMESH_FINAL_HEADING);
CTaskMoveAchieveHeading* pTask = rage_new CTaskMoveAchieveHeading(m_fTargetStopHeading);
SetNewTask(pTask);
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateAchieveHeading_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
if (!GetSubTask() || GetIsFlagSet(aiTaskFlags::SubTaskFinished))
return FSM_Quit;
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateUsingLadder_OnEnter(CPed * UNUSED_PARAM(pPed))
{
return FSM_Continue;
}
CTask::FSM_Return CTaskNavBase::StateUsingLadder_OnUpdate(CPed * pPed)
{
// Wait until CTaskComplexControlMovement task has completed ladder climb subtask, before returning to path following
CTaskComplexControlMovement * pCtrlMove = (CTaskComplexControlMovement*) pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_COMPLEX_CONTROL_MOVEMENT);
CTask* pRuningMovementTask = pCtrlMove ? pCtrlMove->GetRunningMovementTask() : NULL;
if( pRuningMovementTask == this || (pRuningMovementTask && pRuningMovementTask->FindSubTaskOfType(GetTaskType()) == this) )
{
if(!pCtrlMove->GetIsClimbingLadder())
{
if (!pCtrlMove->HasLastClimbingLadderFailed())
{
m_iProgress = m_iClampMinProgress;
SetState(NavBaseState_FollowingPath);
return FSM_Continue;
}
else
{
return FSM_Quit;
}
}
}
else
{
Assert(false);
SetState(NavBaseState_FollowingPath);
return FSM_Continue;
}
return FSM_Continue;
}
void CTaskNavBase::ModifyPathFlagsForPedLod(CPed * pPed, u64 & iFlags) const
{
if( pPed->GetPedAiLod().IsLodFlagSet( CPedAILod::AL_LodPhysics ) )
{
iFlags |= PATH_FLAG_NEVER_CLIMB_OVER_STUFF;
iFlags |= PATH_FLAG_NEVER_DROP_FROM_HEIGHT;
iFlags |= PATH_FLAG_NEVER_USE_LADDERS;
iFlags &= ~PATH_FLAG_IF_NOT_ON_PAVEMENT_ALLOW_DROPS_AND_CLIMBS;
}
}
//***********************************************************************************************
// Create an event which will perform a special action at this waypoint. For example climbing /
// descending a ladder, climbing up an edge, dropping down over an edge. This function is
// virtual so that derived CTaskNavBase::classes can implement custom waypoint actions.
CTaskNavBase::EWaypointActionResult CTaskNavBase::CreateWaypointActionEvent(CPed * pPed, const s32 iProgress)
{
if(iProgress+1 >= m_pRoute->GetSize())
{
#if AI_OPTIMISATIONS_OFF
Assertf( iProgress+1 >= m_pRoute->GetSize(), "waypoint at route end (%i/%i)", iProgress, m_pRoute->GetSize() );
#endif
// Error - special action waypoints should never be created at the end of a route.
// We need to have a waypoint after them in order to know where our destination is
// (eg. top of ladder, climb target, etc).
// In this case return 'WaypointAction_ActionFailed' which will cause a re-path
// NB: This error probably has occurred due to some kind of path post-processing,
// such as inserting points to preserve the Z height of the terrain
return WaypointAction_ActionFailed;
}
const TNavMeshWaypointFlag & iWptFlags = m_pRoute->GetWaypoint(iProgress);
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
const Vector3 vWptPos = m_pRoute->GetPosition(iProgress);
const Vector3 vDiff = vWptPos - vPedPos;
const float fInPositionSqr = 2.0f*2.0f;
#if AI_OPTIMISATIONS_OFF
Assertf(vDiff.XYMag2() < fInPositionSqr, "Ped is not in position for waypoint.\nPed 0x%p at (%.1f,%.1f,%.1f) is %.1fm away from waypoint type %u", pPed, vPedPos.x, vPedPos.y, vPedPos.z, vDiff.Mag(), iWptFlags.GetSpecialAction());
#endif
if(vDiff.XYMag2() > fInPositionSqr)
return WaypointAction_ActionFailed;
//**********
// Ladders
if(iWptFlags.m_iSpecialActionFlags & (WAYPOINT_FLAG_CLIMB_LADDER|WAYPOINT_FLAG_DESCEND_LADDER))
{
Vector3 vBase, vTop;
const int iDir = (iWptFlags.m_iSpecialActionFlags & WAYPOINT_FLAG_CLIMB_LADDER) ? 1 : -1;
if(iWptFlags.m_iSpecialActionFlags & WAYPOINT_FLAG_CLIMB_LADDER)
{
vBase = m_pRoute->GetPosition(iProgress);
vTop = m_pRoute->GetPosition(iProgress+1);
}
else
{
vTop = m_pRoute->GetPosition(iProgress);
vBase = m_pRoute->GetPosition(iProgress+1);
}
const float fHeading = iWptFlags.GetHeading();
// Poll any controlling CTaskComplexControlMovement task to see whether it is able to run a ladder-climbing task as its subtask.
// If we signal it to climb the ladder, and transition to state NavBaseState_UsingLadder whilst it does so.
CTaskComplexControlMovement * pCtrlMove = (CTaskComplexControlMovement*) pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_COMPLEX_CONTROL_MOVEMENT);
if(pCtrlMove && pCtrlMove->MayClimbLadderAsSubtask())
{
CTask* pRuningMovementTask = pCtrlMove->GetRunningMovementTask();
if( pRuningMovementTask == this || (pRuningMovementTask && pRuningMovementTask->FindSubTaskOfType(GetTaskType()) == this) )
{
Assert(!pCtrlMove->GetIsClimbingLadder());
pCtrlMove->SetIsClimbingLadder(iDir == 1 ? vBase : vTop, fHeading, m_fInitialMoveBlendRatio);
SetState(NavBaseState_UsingLadder);
m_iClampMinProgress = (u8)(iProgress+1);
return WaypointAction_StateTransitioned;
}
}
// Otherwise we use the default behaviour for all ladders/climbs/drops, which is to create
// an event which handles the waypoint action as a response task.
CEventClimbLadderOnRoute ladderEvent(vBase, vTop, iDir, fHeading, m_fInitialMoveBlendRatio);
pPed->GetPedIntelligence()->AddEvent(ladderEvent);
return WaypointAction_EventCreated;
}
//**********************
// Climbs & drop-downs
else if(iWptFlags.m_iSpecialActionFlags & (WAYPOINT_FLAG_CLIMB_LOW|WAYPOINT_FLAG_CLIMB_HIGH|WAYPOINT_FLAG_DROPDOWN|WAYPOINT_FLAG_CLIMB_OBJECT))
{
//*******************************************************************************************
// If this route is taking place on a dynamic navmesh/entity, then the points we give to the
// CEventClimbNavMeshOnRoute event must be local, and we'll include an entity pointer whose
// matrix we must transform them by.
CNavMeshRoute * pRoute;
if(m_pOnEntityRouteData && m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn)
{
Assert(m_pOnEntityRouteData->m_pLocalSpaceRoute);
pRoute = m_pOnEntityRouteData->m_pLocalSpaceRoute;
}
else
{
pRoute = m_pRoute;
}
const Vector3 & vCurrentTarget = pRoute->GetPosition(iProgress);
const Vector3 & vNextTarget = pRoute->GetPosition(iProgress+1);
Vector3 vClimbVec = vNextTarget - vCurrentTarget;
vClimbVec.z = 0.0f;
vClimbVec.Normalize();
const float fHeading = fwAngle::GetRadianAngleBetweenPoints(vClimbVec.x, vClimbVec.y, 0.0f, 0.0f);
bool bSpecialActionForceJump = false;
//**************************************************************************************
// If this is a climb of some sort, use the CInAirHelper class to determine whether
// we can actually climb here. If not, then we will need to find an alternative route.
if(iWptFlags.m_iSpecialActionFlags & (WAYPOINT_FLAG_CLIMB_LOW|WAYPOINT_FLAG_CLIMB_HIGH|WAYPOINT_FLAG_CLIMB_OBJECT))
{
if(!CheckIfClimbIsPossible(vCurrentTarget, fHeading, pPed))
{
//*******************************************************************************
// No valid climb was found...
// If this was only a "low climb" then make sure that we really do need to do a
// jump/climb here. If the target is vertically very close to us, and there is
// no obstruction - then we can probably just walk there.
if((iWptFlags.m_iSpecialActionFlags & WAYPOINT_FLAG_CLIMB_LOW) && !pPed->GetIsSwimming())
{
const Vector3 vPedPosition = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
if(CTaskNavBase::TestClearance(vPedPosition, vNextTarget, pPed, false))
{
return WaypointAction_EventNotNeeded;
}
// In this situation we did not manage to climb but if this test is true we could jump here instead
// This should happen when the step is too low to allow a climb but too high to walk over
if(CTaskNavBase::TestClearance(vPedPosition, vNextTarget, pPed, true))
{
bSpecialActionForceJump = true;
}
}
if (!bSpecialActionForceJump)
{
//***************************************************************************
// This tags the climb as failed. After a certain number of failed attempts
// in the same location for this ped, the climb will actually be disabled
// within the navmesh itself - thus excluding it from future path searches.
pPed->GetPedIntelligence()->RecordFailedClimbOnRoute(vCurrentTarget);
return WaypointAction_ActionFailed;
}
}
}
const s32 iTimeLeft = m_WarpTimer.IsSet() ? m_WarpTimer.GetTimeLeft() : -1;
CEventClimbNavMeshOnRoute climbNavMeshEvent(
vCurrentTarget,
fHeading,
vNextTarget,
m_fInitialMoveBlendRatio,
iWptFlags.m_iSpecialActionFlags,
m_pOnEntityRouteData ? m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn.Get() : NULL,
iTimeLeft,
m_vTarget,
bSpecialActionForceJump
);
pPed->GetPedIntelligence()->AddEvent(climbNavMeshEvent);
return WaypointAction_EventCreated;
}
return WaypointAction_ActionNotFound;
}
bool CTaskNavBase::TestClearance(const Vector3 & vStartIn, const Vector3 & vEndIn, const CPed * pPed, bool bStartTestAboveKnee)
{
#if __DEV && DEBUG_DRAW
static dev_bool bDrawCapsules = false;
#endif
const CBaseCapsuleInfo * pPedCapsuleInfo = pPed->GetCapsuleInfo();
const float fHeadHeight = pPedCapsuleInfo->GetMaxSolidHeight();
const float fKneeHeight = pPedCapsuleInfo->GetMinSolidHeight();
const float fDeltaHeight = fHeadHeight - fKneeHeight;
const float fPedRadius = pPedCapsuleInfo->GetHalfWidth();
const float fTestInc = ((fPedRadius*2.0f) * 0.75f);
const int iNumTests = (int) (fDeltaHeight / fTestInc) + 1;
Vector3 vStart = vStartIn;
vStart.z += fKneeHeight + fPedRadius * 0.5f;
Vector3 vEnd = vEndIn;
vEnd.z += fKneeHeight + fPedRadius * 0.5f;
int i = 0;
if(bStartTestAboveKnee)
{
i = 1;
vStart.z += fTestInc;
vEnd.z += fTestInc;
}
for( ; i<iNumTests; i++)
{
#if __DEV && DEBUG_DRAW
if(bDrawCapsules && (CPedDebugVisualiser::GetDebugDisplay()==CPedDebugVisualiser::eTasksFullDebugging ||
CPedDebugVisualiser::GetDebugDisplay()==CPedDebugVisualiser::eTasksNoText))
grcDebugDraw::Line(vEnd, vStart, Color_white, Color_green);
#endif
const u32 iTestFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER | ArchetypeFlags::GTA_GLASS_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_BOX_VEHICLE_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE;
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
//const Vector3 vPedPosition = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
capsuleDesc.SetCapsule(vStart, vEnd, fPedRadius);
capsuleDesc.SetIncludeFlags(iTestFlags);
capsuleDesc.SetContext(WorldProbe::LOS_GeneralAI);
WorldProbe::CShapeTestFixedResults<> capsuleResult;
capsuleDesc.SetResultsStructure(&capsuleResult);
bool bClearOfObstacles = !WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc);
if (!bClearOfObstacles)
{
u32 i = 0;
for ( ; i < capsuleResult.GetNumHits(); ++i)
{
// Not sure if we should allow null here or not, maybe just assert on null?
const CEntity* pEntity = CPhysics::GetEntityFromInst(capsuleResult[i].GetHitInst());
taskAssert(pEntity);
if (!pEntity->GetIsTypeObject())
break;
if (CPathServerGta::ShouldAvoidObject(pEntity))
break;
}
if (i == capsuleResult.GetNumHits())
bClearOfObstacles = true;
}
#if __DEV && DEBUG_DRAW
if(bDrawCapsules)
CTask::ms_debugDraw.AddCapsule(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), fPedRadius, bClearOfObstacles ? Color_green : Color_red, 1000, 0, false);
#endif
if(!bClearOfObstacles)
return false;
vStart.z += fTestInc;
vEnd.z += fTestInc;
}
return true;
}
Vector3 CTaskNavBase::PathRequest_DistanceAheadCalculation(const float fDistanceAhead, CPed * pPed)
{
TRequestPathStruct tempReqStruct;
InitPathRequest_DistanceAheadCalculation(fDistanceAhead, pPed, tempReqStruct);
return tempReqStruct.m_vPathStart;
}
bool CTaskNavBase::CreateClimbOutOfWaterEvent(CPed * pPed, const Vector3 & vPedPos)
{
const CCollisionRecord * colRec = pPed->GetFrameCollisionHistory()->GetMostSignificantCollisionRecord();
if( colRec && colRec->m_pRegdCollisionEntity && colRec->m_pRegdCollisionEntity->GetIsTypeBuilding() )
{
Vector3 vNext;
if (m_iProgress + 1 < m_pRoute->GetSize())
vNext = m_pRoute->GetPosition(m_iProgress+1);
else
vNext = m_pRoute->GetPosition(m_iProgress);
const float fHeading = fwAngle::GetRadianAngleBetweenPoints(vNext.x, vNext.y, vPedPos.x, vPedPos.y);
if(CheckIfClimbIsPossible(vPedPos, fHeading, pPed))
{
const s32 iTimeLeft = m_WarpTimer.IsSet() ? m_WarpTimer.GetTimeLeft() : -1;
Vector3 vToNext = vNext - vPedPos;
vToNext.Normalize();
const Vector3 vTargetAfterClimb = vPedPos + (vToNext * 1.0f);
CEventClimbNavMeshOnRoute climbNavMeshEvent(
vPedPos,
fHeading,
vNext,
m_fInitialMoveBlendRatio,
WAYPOINT_FLAG_CLIMB_LOW,
m_pOnEntityRouteData ? m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn.Get() : NULL,
iTimeLeft,
vTargetAfterClimb);
pPed->GetPedIntelligence()->AddEvent(climbNavMeshEvent);
return true;
}
}
return false;
}
bool CTaskNavBase::CheckIfClimbIsPossible(const Vector3 & vClimbPos, float fClimbHeading, CPed * pPed) const
{
// Try firstly with the position of the climb as returned from the navmesh
Matrix34 mClimbMat;
mClimbMat.MakeRotateZ(fClimbHeading);
mClimbMat.d = vClimbPos;
CClimbHandHoldDetected handHold;
pPed->SetPedResetFlag(CPED_RESET_FLAG_SearchingForClimb, true);
pPed->GetPedIntelligence()->GetClimbDetector().Process(&mClimbMat);
if(pPed->GetPedIntelligence()->GetClimbDetector().GetDetectedHandHold(handHold))
return true;
// Try again with the ped's current position
mClimbMat.d = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
pPed->SetPedResetFlag(CPED_RESET_FLAG_SearchingForClimb, true);
pPed->GetPedIntelligence()->GetClimbDetector().Process(&mClimbMat);
if(pPed->GetPedIntelligence()->GetClimbDetector().GetDetectedHandHold(handHold))
return true;
return false;
}
bool CTaskNavBase::ShouldSlowDownForWaypoint(const TNavMeshWaypointFlag & wptFlag) const
{
if(TNavMeshWaypointFlag::IsClimb(wptFlag.m_iSpecialActionFlags))
return true;
return false;
}
float CTaskNavBase::GetTargetRadiusForWaypoint(const int iProgress) const
{
if(iProgress < m_pRoute->GetSize())
{
const u32 iWptAction = m_pRoute->GetWaypoint(iProgress).GetSpecialActionFlags();
const u32 iNextWptFlags =
(iProgress < m_pRoute->GetSize()-1 && (m_pRoute->GetPosition(iProgress) - m_pRoute->GetPosition(iProgress+1)).XYMag2() < ms_fWaypointSlowdownProximitySqr)
? m_pRoute->GetWaypoint(iProgress+1).GetSpecialActionFlags() : 0;
if((iWptAction & (WAYPOINT_FLAG_CLIMB_LADDER|WAYPOINT_FLAG_DESCEND_LADDER)) ||
(iNextWptFlags & (WAYPOINT_FLAG_CLIMB_LADDER|WAYPOINT_FLAG_DESCEND_LADDER)))
{
return 0.5f;
}
else if(IsSpecialActionClimbOrDrop(iWptAction))
{
return 0.1f;
}
else
{
return m_fTargetRadius;
}
}
return m_fTargetRadius;
}
float CTaskNavBase::GetPathToleranceForWaypoint(const int iProgress) const
{
// first and last segments have min tolerance
if ( iProgress > 0 && iProgress < m_pRoute->GetSize()-1)
{
const u32 iWptAction = m_pRoute->GetWaypoint(iProgress).GetSpecialActionFlags();
const u32 iNextWptFlags = (iProgress < m_pRoute->GetSize()-1 && (m_pRoute->GetPosition(iProgress) - m_pRoute->GetPosition(iProgress+1)).XYMag2() < ms_fWaypointSlowdownProximitySqr)
? m_pRoute->GetWaypoint(iProgress+1).GetSpecialActionFlags() : 0;
if( (iWptAction & (WAYPOINT_FLAG_IS_NARROW_IN|WAYPOINT_FLAG_IS_NARROW_OUT))
|| (iNextWptFlags & (WAYPOINT_FLAG_IS_NARROW_IN|WAYPOINT_FLAG_IS_NARROW_OUT)) )
{
return 0.0f;
}
return 1.0f;
}
return 0.0f;
}
CTask::FSM_Return CTaskNavBase::DecideStoppingState(CPed * pPed)
{
float fStopHeading = m_fTargetStopHeading != DEFAULT_NAVMESH_FINAL_HEADING ? m_fTargetStopHeading : pPed->GetCurrentHeading();
if (pPed->GetVelocity().Mag2() <= 0.01f || pPed->GetCurrentMotionTask()->GetStoppingDistance(fStopHeading) <= 0.0f)
{
static dev_float s_fMaxExactHeadingDiff = 0.025f;
float fHeadingDiff = (m_fTargetStopHeading < DEFAULT_NAVMESH_FINAL_HEADING ? Abs(SubtractAngleShorter(m_fTargetStopHeading, pPed->GetCurrentHeading())) : DEFAULT_NAVMESH_FINAL_HEADING);
if (fHeadingDiff > s_fMaxExactHeadingDiff && fHeadingDiff < DEFAULT_NAVMESH_FINAL_HEADING)
{
SetState(NavBaseState_AchieveHeading);
return FSM_Continue;
}
else
{
return FSM_Quit;
}
}
SetFlag(aiTaskFlags::KeepCurrentSubtaskAcrossTransition);
SetState(NavBaseState_StoppingAtTarget);
return FSM_Continue;
}
bool CTaskNavBase::IsNavigatingUnderwater(const CPed * pPed) const
{
if (pPed->GetCurrentMotionTask()->IsUnderWater())
{
return true;
}
return false;
}
void CTaskNavBase::SetMoveBlendRatio(const float fMoveBlendRatio, const bool bFlagAsChanged)
{
//Call the base class version.
CTaskMove::SetMoveBlendRatio(fMoveBlendRatio, bFlagAsChanged);
//Set the initial move blend ratio.
m_fInitialMoveBlendRatio = fMoveBlendRatio;
}
void CTaskNavBase::SetTarget(const CPed * pPed, Vec3V_In vTarget1, const bool bForce)
{
const Vector3 vTarget = VEC3V_TO_VECTOR3(vTarget1);
// Ensure that the backed up movement task in CTaskComplexControlMovement is up to date
// TODO: We really need to make this an automatic operation, perhaps in the CTaskMoveBase::SetTarget() base
CTaskMove * pBackup = GetControlMovementBackup(pPed);
if(pBackup && pBackup->InheritsFromTaskNavBase())
((CTaskNavBase*)pBackup)->m_vTarget = vTarget;
Assertf(rage::FPIsFinite(vTarget.x) && rage::FPIsFinite(vTarget.y) && rage::FPIsFinite(vTarget.z), "vTarget is not numerically valid.");
ASSERT_ONLY(const float fLargeValue = 10000.0f;)
Assertf(vTarget.x > -fLargeValue && vTarget.y > -fLargeValue && vTarget.z > -fLargeValue && vTarget.x < fLargeValue && vTarget.y < fLargeValue && vTarget.z < fLargeValue, "vTarget (%.2f, %.2f, %.2f) is outside of the world", vTarget.x, vTarget.y, vTarget.z);
//****************************************************************************************************************
// the "m_bAdaptiveSetTargetEpsilon" feature allows this task to have an adaptive epsilon calculated for testing
// whether to recalculated the route or not. If this is set, then the further the ped is from their target -
// the greater the difference between the current target & new target must be before an update occurs.
static const float fMaxAdaptiveTargetEpsilon = 5.0f;
static const float fMaxAdaptiveTargetDist = 50.0f;
static const float fMinAdaptiveTargetDist = 10.0f;
static const float fRange = fMaxAdaptiveTargetDist - fMinAdaptiveTargetDist;
float fTargetEqualEpsilon;
if(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_UseAdaptiveSetTargetEpsilon))
{
const Vector3 vPedToNewTarget = vTarget - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
const float fDist = vPedToNewTarget.Mag();
if(fDist < fMinAdaptiveTargetDist)
{
fTargetEqualEpsilon = ms_fTargetEqualEpsilon;
}
else
{
fTargetEqualEpsilon = (fDist-fMinAdaptiveTargetDist) / fRange;
fTargetEqualEpsilon = Clamp(fTargetEqualEpsilon, 0.0f, 1.0f);
fTargetEqualEpsilon *= fMaxAdaptiveTargetEpsilon;
fTargetEqualEpsilon = Max(fTargetEqualEpsilon, ms_fTargetEqualEpsilon);
}
}
else
{
fTargetEqualEpsilon = ms_fTargetEqualEpsilon;
}
bool bTargetPosEqual = m_vTarget.IsClose(vTarget, fTargetEqualEpsilon);
m_vNewTarget = vTarget;
if(bTargetPosEqual && !bForce)
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_LastSetTargetWasNotUsed);
}
else
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_HadNewTarget);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_LastSetTargetWasNotUsed);
m_iTimeOfLastSetTargetCall = fwTimer::GetTimeInMilliseconds();
}
}
void CTaskNavBase::SetTargetRadius(const float fTargetRadius)
{
m_fTargetRadius = fTargetRadius;
}
bool CTaskNavBase::HandleNewTarget(CPed * pPed)
{
Assertf(GetState() < NavBaseState_NumStates, "State %i should be handled in the derived class", GetState());
if(!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_HadNewTarget) || m_iPathHandle != PATH_HANDLE_NULL)
{
return false;
}
Vector3 vOldTarget = m_vTarget;
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_HadNewTarget);
m_vTarget = m_vNewTarget;
if(GetState()==NavBaseState_FollowingPath)
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_HadPreviousGotoTarget);
bool bMayUsePreviousTarget = true;
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath, true);
if(ShouldKeepMovingWhilstWaitingForPath(pPed, bMayUsePreviousTarget))
{
#if __REQUEST_ROUTE_WHILST_FOLLOWING_PATH
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p on-the-fly, inside HandleNewTarget (no state change)", fwTimer::GetFrameCount(), pPed); )
if( RequestPath(pPed, GetDistanceAheadForSetNewTarget() ) )
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly, true);
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_OnTheFlyPathStartedAtRouteEnd, false);
return true;
}
#else
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p moving-whilst-waiting-for-path, inside HandleNewTarget", fwTimer::GetFrameCount(), pPed); )
if( RequestPath(pPed) )
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_MayUsePreviousTargetInMovingWaitingForPath, bMayUsePreviousTarget);
SetState(NavBaseState_MovingWhilstWaitingForPath);
return true;
}
#endif
}
else
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p stationary, inside HandleNewTarget", fwTimer::GetFrameCount(), pPed); )
if( RequestPath(pPed) )
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p entering NavBaseState_StationaryWhilstWaitingForPath, from HandleNewTarget (ShouldKeepMovingWhilstWaitingForPath returned false)", fwTimer::GetFrameCount(), pPed); )
SetState(NavBaseState_StationaryWhilstWaitingForPath);
return true;
}
}
}
else
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) RequestPath() : Ped 0x%p inside HandleNewTarget, no state change from %i", fwTimer::GetFrameCount(), pPed, GetState()); )
if( RequestPath(pPed) )
{
return true;
}
}
// If we get here, then we failed to request a path
// This will be due to the pathserver not having any free slots for the request
// Reset target, and NB_HadNewTarget flag for trying again next frame.
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_HadNewTarget);
m_vTarget = vOldTarget;
return false;
}
bool CTaskNavBase::RequestPath(CPed * pPed, const float fDistanceAheadOfPed)
{
//Assert(!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly)); // this flag will be set in InitPathRequestStruct()
//----------------------------------------------------------------------
// Handle the case where we were already calculating a path on the fly
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly))
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly);
if(m_pNextRouteSection)
{
delete m_pNextRouteSection;
m_pNextRouteSection = NULL;
}
if(m_iPathHandle != PATH_HANDLE_NULL)
{
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p cancelling path handle 0x%x", fwTimer::GetFrameCount(), pPed, m_iPathHandle); )
CPathServer::CancelRequest(m_iPathHandle);
m_iPathHandle = PATH_HANDLE_NULL;
}
}
//----------------
// Request path
if(m_iPathHandle == PATH_HANDLE_NULL)
{
TRequestPathStruct pathReq;
InitPathRequestPathStruct(pPed, pathReq, fDistanceAheadOfPed);
ModifyPathFlagsForPedLod(pPed, pathReq.m_iFlags);
m_iPathHandle = CPathServer::RequestPath(pathReq);
}
if(m_iPathHandle == PATH_HANDLE_NULL)
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly);
}
BANK_ONLY( if(ms_bDebugRequests) Displayf("%u) Ped 0x%p path handle was 0x%x", fwTimer::GetFrameCount(), pPed, m_iPathHandle); )
return (m_iPathHandle != PATH_HANDLE_NULL);
}
void CTaskNavBase::InitPathRequest_DistanceAheadCalculation(const float fDistanceAhead, CPed * pPed, TRequestPathStruct & reqPathStruct)
{
const Vector3 vPedPosition = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
bool bPositionAheadFound = false;
if(m_pRoute && m_pRoute->GetSize() > 0)
{
// We must expand the current route in total to calculate the position ahead, and a progress for it
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, 0, m_pRoute->GetSize(), 9999.0f);
if( ms_iNumExpandedRouteVerts )
{
// We must recalculate our 'm_iClosestSegOnExpandedRoute' since we have changed the values in 'ms_vExpandedRouteVerts'
m_iClosestSegOnExpandedRoute = (s16) CalculateClosestSegmentOnExpandedRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, vPedPosition, m_fSplineTVal, &m_vClosestPosOnRoute, &ms_vClosestSegmentOnRoute);
// Advance to minimum 'm_iClampMinProgress'
while(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].m_iProgress < m_iClampMinProgress && m_iClosestSegOnExpandedRoute < ms_iNumExpandedRouteVerts-1)
m_iClosestSegOnExpandedRoute++;
Assert(ms_iNumExpandedRouteVerts != 0);
// Determine the target position ahead along the route, which will be the start of our new path request
ScalarV distanceRemainingV;
GetPositionAheadOnRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, VECTOR3_TO_VEC3V(vPedPosition), ScalarV(fDistanceAhead), RC_VEC3V(reqPathStruct.m_vPathStart), distanceRemainingV, NULL, true, true);
// Flag as calculating this path on the fly
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly, true);
bPositionAheadFound = true;
}
}
// If we failed to calculate a position ahead, we use the current position
if(!bPositionAheadFound)
{
// Search start is set the the ped's current position
reqPathStruct.m_vPathStart = vPedPosition;
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_CalculatingPathOnTheFly, false);
}
}
bool CTaskNavBase::RetrievePath(TPathResultInfo & pathResultInfo, CPed * pPed, CNavMeshRoute * pTargetRoute)
{
Assert(m_iPathHandle != PATH_HANDLE_NULL);
Assert(pTargetRoute);
int iNumPts = 0;
Vector3 * pPoints = NULL;
TNavMeshWaypointFlag * pWaypointFlags = NULL;
const int iRetVal = CPathServer::LockPathResult(m_iPathHandle, &iNumPts, pPoints, pWaypointFlags, &pathResultInfo);
Assert(iRetVal != PATH_STILL_PENDING); // We should have already ensured that the path is ready
if(iRetVal == PATH_FOUND)
{
pTargetRoute->Clear();
//************************************************************
// Record whether the final point in the path is our target
if((!pathResultInfo.m_bLastRoutePointIsTarget) || pathResultInfo.m_bWentFarAsPossibleTowardsTarget)
{
pTargetRoute->SetRouteFlags( m_pRoute->GetRouteFlags() &~ CNavMeshRoute::FLAG_LAST_ROUTE_POINT_IS_TARGET );
}
else
{
pTargetRoute->SetRouteFlags( CNavMeshRoute::FLAG_LAST_ROUTE_POINT_IS_TARGET );
}
//**************************************************
// Record whether this path segment includes water
if(pathResultInfo.m_bPathIncludesWater)
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_PathIncludesWater);
else
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_PathIncludesWater);
//************************************************
// See how many of the returned points we can use
const int iNumPtsUsable = Min(iNumPts, (int)CNavMeshRoute::MAX_NUM_ROUTE_ELEMENTS);
//*********************************************************************
// If the returned route was empty then just go directly to the target
if(!iNumPtsUsable)
{
TNavMeshWaypointFlag wptFlag;
wptFlag.Clear();
pTargetRoute->Add(m_vTarget, wptFlag);
}
else
{
//-----------------------------------------------------------------------------------
// Was the final waypoint adjusted from the required position ?
// For example, if it was inside the bounds of an entity it may have been pushed out.
const TNavMeshWaypointFlag wptFlag = pTargetRoute->GetWaypoint(iNumPtsUsable-1);
// If the path reached the target and we specified NB_DontAdjustTargetPosition, ensure that final point is exact target position
if(pathResultInfo.m_bLastRoutePointIsTarget && m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_DontAdjustTargetPosition))
{
pPoints[iNumPtsUsable-1] = m_vTarget;
}
if((wptFlag.m_iSpecialActionFlags & WAYPOINT_FLAG_ADJUSTED_TARGET_POS)!=0) // (this also implies 'pathResultInfo.m_bLastRoutePointIsTarget')
{
if(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_DontAdjustTargetPosition))
{
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_TargetPositionWasAdjusted);
pPoints[iNumPtsUsable-1] = m_vTarget;
}
else
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_TargetPositionWasAdjusted);
m_vAdjustedTarget[0] = pPoints[iNumPtsUsable-1].x;
m_vAdjustedTarget[1] = pPoints[iNumPtsUsable-1].y;
m_vAdjustedTarget[2] = pPoints[iNumPtsUsable-1].z;
}
}
//------------------
// Create the route
for(int p=0; p<iNumPtsUsable; p++)
{
Vector3 vPoint = pPoints[p];
Assert( rage::FPIsFinite(vPoint.x) && rage::FPIsFinite(vPoint.y) && rage::FPIsFinite(vPoint.z) );
//------------------------------------------------------------------------------------
// Points returned by navmesh searches are at the same height as the navmesh surface
// We raise them up to the root height of the ped here.
// This is important so that low LOD peds with their physics switched off are
// able to interpolate along their paths at the correct Z height.
const bool bShouldNotOffset = (pWaypointFlags[p].GetSpecialActionFlags() & WAYPOINT_FLAG_IS_ON_WATER_SURFACE) != 0;
if(!bShouldNotOffset)
{
vPoint.z += pPed->GetCapsuleInfo()->GetGroundToRootOffset();
}
pTargetRoute->Add(
vPoint,
pWaypointFlags[p],
pathResultInfo.m_iPathSplineDistances[p],
pathResultInfo.m_fPathCtrlPt2TVals[p],
pathResultInfo.m_iMinApproxFreeSpaceApproachingEachWaypoint[p]
);
}
}
//************************************************************************************
// If this was a route on top of a dynamic navmesh, make a backup copy of the route
// in the local-space of the navmesh. We retransform it into world space by the
// entity's matrix every frame.
if(m_pOnEntityRouteData && m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn)
{
// If for some reason the dynamic navmesh instance wasn't found, then
// restart/quit the task? This is an error situation.
if(!pathResultInfo.m_bFoundDynamicEntityPathIsOn)
{
CPathServer::UnlockPathResultAndClear(m_iPathHandle);
m_iLastPathHandle = m_iPathHandle;
m_iPathHandle = PATH_HANDLE_NULL;
m_NavBaseConfigFlags.Reset(CNavBaseConfigFlags::NB_KeepMovingWhilstWaitingForPath);
SetState(NavBaseState_Initial);
return false;
}
if(!m_pOnEntityRouteData->m_pLocalSpaceRoute)
m_pOnEntityRouteData->m_pLocalSpaceRoute = rage_new CNavMeshRoute;
else
m_pOnEntityRouteData->m_pLocalSpaceRoute->Clear();
Matrix34 mInvMat;
mInvMat.Inverse(pathResultInfo.m_MatrixOfEntityAtPathCreationTime);
for(int r=0; r<pTargetRoute->GetSize(); r++)
{
Vector3 vLocalPos;
mInvMat.Transform( pTargetRoute->GetPosition(r), vLocalPos );
m_pOnEntityRouteData->m_pLocalSpaceRoute->Add( vLocalPos, pTargetRoute->GetWaypoint(r) );
}
m_iProgress = 0;
}
//******************************************************************************************
// Test whether the path start was inside a VEHICLE_TYPE_PLANE; if so then we enable
// a special-case avoidance whereby CEventVehicleCollision may create a response task of
// CTaskMoveWalkRoundVehicle vehicle. This is because we cannot simply resolve the start
// position directly outside of the closest edge, due to the plane's wings, propeller, etc.
if( pathResultInfo.m_bStartPositionWasInsideAnObject && m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidObjects) )
{
CEntityScannerIterator vehiclesList = pPed->GetPedIntelligence()->GetNearbyVehicles();
for( const CVehicle* pVeh = (const CVehicle*)vehiclesList.GetFirst(); pVeh; pVeh = (const CVehicle*)vehiclesList.GetNext() )
{
if(pVeh->GetVehicleType()==VEHICLE_TYPE_PLANE)
{
CEntityBoundAI vehicleBound;
vehicleBound.Set(*pVeh, pVeh->GetTransform().GetPosition().GetZf(), pPed->GetCapsuleInfo()->GetHalfWidth(), true, NULL);
Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
if(vPedPos.z >= vehicleBound.GetBottomZ() && vPedPos.z < vehicleBound.GetTopZ() && vehicleBound.LiesInside(vPedPos))
{
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_PathStartedInsidePlaneVehicle, true);
break;
}
}
}
}
CPathServer::UnlockPathResultAndClear(m_iPathHandle);
m_iLastPathHandle = m_iPathHandle;
m_iPathHandle = PATH_HANDLE_NULL;
return pTargetRoute->GetSize()!=0;
}
else
{
CPathServer::UnlockPathResultAndClear(m_iPathHandle);
m_iLastPathHandle = m_iPathHandle;
m_iPathHandle = PATH_HANDLE_NULL;
return false;
}
}
bool CTaskNavBase::CanSwimStraightToNextTarget(CPed * pPed) const
{
if (!pPed->GetCurrentMotionTask()->IsUnderWater())
{
return false;
}
if(GetState()!=NavBaseState_FollowingPath || !m_pRoute || m_pRoute->GetSize() <= 2 || m_iProgress >= m_pRoute->GetSize()-1)
{
return false;
}
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
const Vector3 vNextWaypointPos = m_pRoute->GetPosition(m_iProgress+1);
float fStartWaterLevel, fEndWaterLevel;
if(Water::GetWaterLevelNoWaves(vPedPos, &fStartWaterLevel, true, REJECTIONABOVEWATER, NULL))
{
if(Water::GetWaterLevelNoWaves(vNextWaypointPos, &fEndWaterLevel, true, REJECTIONABOVEWATER, NULL))
{
if(vPedPos.z < fStartWaterLevel && vNextWaypointPos.z < fEndWaterLevel)
{
WorldProbe::CShapeTestProbeDesc probeDesc;
probeDesc.SetStartAndEnd(vPedPos, vNextWaypointPos);
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES|ArchetypeFlags::GTA_VEHICLE_TYPE|ArchetypeFlags::GTA_OBJECT_TYPE);
const bool bClearLos = !WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc);
return bClearLos;
}
}
}
return false;
}
void CTaskNavBase::ScanForMovedOutsidePlaneBounds(CPed * pPed)
{
Assert(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_PathStartedInsidePlaneVehicle));
CEntityScannerIterator vehiclesList = pPed->GetPedIntelligence()->GetNearbyVehicles();
for( const CVehicle* pVeh = (const CVehicle*)vehiclesList.GetFirst(); pVeh; pVeh = (const CVehicle*)vehiclesList.GetNext() )
{
if(pVeh->GetVehicleType()==VEHICLE_TYPE_PLANE)
{
CEntityBoundAI vehicleBound;
vehicleBound.Set(*pVeh, pVeh->GetTransform().GetPosition().GetZf(), pPed->GetCapsuleInfo()->GetHalfWidth(), true, NULL);
Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
if(vPedPos.z >= vehicleBound.GetBottomZ() && vPedPos.z < vehicleBound.GetTopZ() && vehicleBound.LiesInside(vPedPos))
{
return;
}
}
}
// Reset the flag to false
m_NavBaseInternalFlags.Set(CNavBaseInternalFlags::NB_PathStartedInsidePlaneVehicle, false);
}
bool CTaskNavBase::CreateRouteFollowingTask(CPed * pPed)
{
Assert(m_pRoute && m_pRoute->GetSize()>0);
Assert(m_iProgress >= 0 && m_iProgress < m_pRoute->GetSize());
Assertf(m_fMoveBlendRatio >= MOVEBLENDRATIO_STILL && m_fMoveBlendRatio <= MOVEBLENDRATIO_SPRINT, "Move blend ratio was : %.1f", m_fMoveBlendRatio);
m_fMoveBlendRatio = Clamp(m_fMoveBlendRatio, MOVEBLENDRATIO_STILL, MOVEBLENDRATIO_SPRINT);
const int iExpandedRouteIndex = Min(m_iClosestSegOnExpandedRoute + 1, ms_iNumExpandedRouteVerts - 1);
const Vector3 & vInitialTarget = RCC_VECTOR3(ms_vExpandedRouteVerts[ iExpandedRouteIndex ].GetPosRef());
Assert( rage::FPIsFinite(vInitialTarget.x) && rage::FPIsFinite(vInitialTarget.y) && rage::FPIsFinite(vInitialTarget.z) );
m_fCurrentTargetRadius = GetTargetRadiusForWaypoint(m_iProgress);
CalcTimeNeededToCompleteRouteSegment(pPed, m_iProgress);
if (GetConsiderRunStartForPathLookAhead())
{
m_fCurrentLookAheadDistance = Max(m_fCurrentLookAheadDistance, Max(m_fMoveBlendRatio * 0.5f, 0.5f));
}
else if (m_fMoveBlendRatio >= MBR_RUN_BOUNDARY)
{
// We could check the angle between the next segments and if they are more narrow than X we might need a lower look ahead distance
// For this range we assume that ped radius is 0.35f
m_fCurrentLookAheadDistance = Max(m_fCurrentLookAheadDistance, 0.707f); // 1.0f
}
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_HasSlidTargetAlongSpline);
m_NavBaseInternalFlags.Reset(CNavBaseInternalFlags::NB_CheckedStoppingDistance);
bool bApproachingWaypoint = false;
float fWaypointHeading = 0.0f;
for(s32 p=0; p<m_pRoute->GetSize(); p++)
{
if( IsSpecialActionClimbOrDrop( m_pRoute->GetWaypoint(p).GetSpecialActionFlags() ) )
{
bApproachingWaypoint = true;
fWaypointHeading = m_pRoute->GetWaypoint(p).GetHeading();
break;
}
}
bool bStopAtEnd = bApproachingWaypoint ||
(GetLastRoutePointIsTarget() &&
//(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_LastRoutePointIsTarget) &&
!m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_DontStandStillAtEnd) &&
!m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_GotoTaskSequencedAfter));
if(bStopAtEnd)
{
if(bApproachingWaypoint)
{
const float fBoundary = (MOVEBLENDRATIO_WALK + MBR_RUN_BOUNDARY) * 0.5f; // err on the side of caution
const float fHeading = (m_fMoveBlendRatio <= fBoundary) ? fWaypointHeading : DEFAULT_NAVMESH_FINAL_HEADING;
CTaskMoveGoToPointAndStandStill * pGotoTask = rage_new CTaskMoveGoToPointAndStandStill(
m_fMoveBlendRatio,
vInitialTarget,
m_fCurrentTargetRadius,
0.0f,
false,
true,
fHeading);
SetNewTask(pGotoTask);
pGotoTask->SetStandStillDuration(0);
pGotoTask->SetDontCompleteThisFrame();
pGotoTask->SetAllowNavmeshAvoidance(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SetAllowNavmeshAvoidance));
pGotoTask->SetDistanceFromPathToEnableNavMeshAvoidance(ms_fDistanceFromPathToTurnOnNavMeshAvoidance);
pGotoTask->SetDistanceFromPathToEnableObstacleAvoidance(ms_fDistanceFromPathToTurnOnObjectAvoidance);
pGotoTask->SetDistanceFromPathToEnableVehicleAvoidance(ms_fDistanceFromPathToTurnOnVehicleAvoidance);
pGotoTask->SetDistanceFromPathTolerance(GetPathToleranceForWaypoint(m_iProgress));
pGotoTask->SetAvoidPeds(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidPeds));
pGotoTask->SetLenientAchieveHeadingForExactStop(GetLenientAchieveHeadingForExactStop());
Vector3 vGotoPointTarget, vLookAheadTarget;
ProcessLookAhead(pPed, false, vGotoPointTarget, vLookAheadTarget);
pGotoTask->SetTarget(pPed, VECTOR3_TO_VEC3V(vGotoPointTarget));
pGotoTask->SetLookAheadTarget(pPed, vGotoPointTarget); // Should this be using vLookAheadTarget?
Assert( rage::FPIsFinite(vGotoPointTarget.x) && rage::FPIsFinite(vGotoPointTarget.y) && rage::FPIsFinite(vGotoPointTarget.z) );
}
else
{
const bool bStopExactly = m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_StopExactly) || m_fTargetStopHeading < DEFAULT_NAVMESH_FINAL_HEADING;
CTaskMoveGoToPointAndStandStill * pGotoTask = rage_new CTaskMoveGoToPointAndStandStill(
m_fMoveBlendRatio,
vInitialTarget,
m_fCurrentTargetRadius,
bStopExactly ? 0.0f : m_fSlowDownDistance,
false,
bStopExactly,
m_fTargetStopHeading);
SetNewTask(pGotoTask);
pGotoTask->SetStandStillDuration(0);
pGotoTask->SetDontCompleteThisFrame();
pGotoTask->SetAllowNavmeshAvoidance(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SetAllowNavmeshAvoidance));
pGotoTask->SetDistanceFromPathToEnableNavMeshAvoidance(ms_fDistanceFromPathToTurnOnNavMeshAvoidance);
pGotoTask->SetDistanceFromPathToEnableObstacleAvoidance(ms_fDistanceFromPathToTurnOnObjectAvoidance);
pGotoTask->SetDistanceFromPathToEnableVehicleAvoidance(ms_fDistanceFromPathToTurnOnVehicleAvoidance);
pGotoTask->SetDistanceFromPathTolerance(GetPathToleranceForWaypoint(m_iProgress));
pGotoTask->SetAvoidPeds(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidPeds));
pGotoTask->SetLenientAchieveHeadingForExactStop(GetLenientAchieveHeadingForExactStop());
pGotoTask->SetUseRunstopInsteadOfSlowingToWalk(!m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SlowDownInsteadOfUsingRunStops));
Vector3 vGotoPointTarget, vLookAheadTarget;
ProcessLookAhead(pPed, false, vGotoPointTarget, vLookAheadTarget);
pGotoTask->SetTarget(pPed, VECTOR3_TO_VEC3V(vGotoPointTarget));
pGotoTask->SetLookAheadTarget(pPed, vLookAheadTarget);
Assert( rage::FPIsFinite(vGotoPointTarget.x) && rage::FPIsFinite(vGotoPointTarget.y) && rage::FPIsFinite(vGotoPointTarget.z) );
Assert( rage::FPIsFinite(vLookAheadTarget.x) && rage::FPIsFinite(vLookAheadTarget.y) && rage::FPIsFinite(vLookAheadTarget.z) );
}
}
else
{
CTaskMoveGoToPoint * pGotoTask = rage_new CTaskMoveGoToPoint(
m_fMoveBlendRatio,
vInitialTarget,
m_fCurrentTargetRadius,
false);
SetNewTask(pGotoTask);
pGotoTask->SetDontCompleteThisFrame();
pGotoTask->SetAllowNavmeshAvoidance(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_SetAllowNavmeshAvoidance));
pGotoTask->SetDistanceFromPathToEnableNavMeshAvoidance(ms_fDistanceFromPathToTurnOnNavMeshAvoidance);
pGotoTask->SetDistanceFromPathToEnableObstacleAvoidance(ms_fDistanceFromPathToTurnOnObjectAvoidance);
pGotoTask->SetDistanceFromPathToEnableVehicleAvoidance(ms_fDistanceFromPathToTurnOnVehicleAvoidance);
pGotoTask->SetDistanceFromPathTolerance(GetPathToleranceForWaypoint(m_iProgress));
pGotoTask->SetAvoidPeds(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidPeds));
Vector3 vGotoPointTarget, vLookAheadTarget;
ProcessLookAhead(pPed, false, vGotoPointTarget, vLookAheadTarget);
pGotoTask->SetTarget(pPed, VECTOR3_TO_VEC3V(vGotoPointTarget));
pGotoTask->SetLookAheadTarget(pPed, vLookAheadTarget);
Assert( rage::FPIsFinite(vGotoPointTarget.x) && rage::FPIsFinite(vGotoPointTarget.y) && rage::FPIsFinite(vGotoPointTarget.z) );
Assert( rage::FPIsFinite(vLookAheadTarget.x) && rage::FPIsFinite(vLookAheadTarget.y) && rage::FPIsFinite(vLookAheadTarget.z) );
}
return true;
}
void CTaskNavBase::CheckForInitialDoorObstruction(CPed * pPed, const Vector3 & vTarget)
{
if(!pPed->GetPedConfigFlag( CPED_CONFIG_FLAG_JustLeftCarNotCheckedForDoors ) || !pPed->GetMyVehicle() || !pPed->GetMyVehicle()->GetCurrentPhysicsInst())
return;
pPed->SetPedConfigFlag( CPED_CONFIG_FLAG_JustLeftCarNotCheckedForDoors, false );
WorldProbe::CShapeTestCapsuleDesc capsuleDesc;
WorldProbe::CShapeTestFixedResults<> shapeTestResult;
capsuleDesc.SetResultsStructure(&shapeTestResult);
const Vector3 vPedPosition = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
capsuleDesc.SetCapsule(vPedPosition, vTarget, pPed->GetCapsuleInfo()->GetHalfWidth());
capsuleDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS);
capsuleDesc.SetIncludeEntity(pPed->GetMyVehicle());
capsuleDesc.SetDoInitialSphereCheck(true);
capsuleDesc.SetIsDirected(true);
if(!WorldProbe::GetShapeTestManager()->SubmitTest(capsuleDesc))
return;
const CCarDoor* pDoor = CCarEnterExit::GetCollidedDoorFromCollisionComponent(pPed->GetMyVehicle(), shapeTestResult[0].GetHitComponent());
if(!pDoor)
return;
// Our path hits a car door which is sufficiently open to be a pain in the ass.
if(pDoor && pDoor->GetDoorRatio() > 0.1f)
{
// Generate a vehicle collision event which will force the ped to walk around this door.
Vector3 vColPos = shapeTestResult[0].GetHitPosition();
vColPos.z = vPedPosition.z;
Vector3 vToPed = vPedPosition - vColPos;
vToPed.NormalizeSafe();
static const float fImpulseMag = 1.0f;
CEventVehicleCollision eventCollision(pPed->GetMyVehicle(), vToPed, vColPos, fImpulseMag, shapeTestResult[0].GetHitComponent(), m_fMoveBlendRatio);
eventCollision.SetIsForcedDoorCollision();
pPed->GetPedIntelligence()->AddEvent(eventCollision);
}
}
// NAME : GetNearbyEntitiesForLineOfSightTest
// PURPOSE : Retrieve an array of nearby entities (objects and vehicles) which would pose obstacles given the current pathfinding settings
// Returns populated CEntityBoundsAI array, and pEntityArray; note that entries in the latter will be NULL for vehicle doors
s32 CTaskNavBase::GetNearbyEntitiesForLineOfSightTest(CPed * pPed, CEntityBoundAI * pEntityBoundsArray, const CEntity ** pEntityArray, const s32 iNumClosest, const s32 iMaxNum, const float fPedRadius, const float fPedZ) const
{
#if __BANK
static dev_bool bDebugDraw = false;
#endif
CEntity * pEntityPathingOn =
(pPed->GetGroundPhysical() && pPed->GetGroundPhysical()->GetIsTypeVehicle() && ((CVehicle*)pPed->GetGroundPhysical())->m_nVehicleFlags.bHasDynamicNavMesh)
? pPed->GetGroundPhysical() : NULL;
s32 iNumEntities = 0;
if( m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidEntityTypeObject) )
{
// If entity scanning is off, we must force a scan or be unaware of nearby objects
if(pPed->GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEntityScanning))
{
pPed->GetPedIntelligence()->GetObjectScanner()->ScanForEntitiesInRange(*static_cast<CEntity*>(pPed), true);
}
s32 iNumObjects = 0;
const CEntityScannerIterator objectsList = pPed->GetPedIntelligence()->GetNearbyObjects();
for( const CObject* pObj = (const CObject*)objectsList.GetFirst(); pObj; pObj = (const CObject*)objectsList.GetNext() )
{
// Obviously, don't recalculate our path for anything which is not an obstacle in the navmesh
// This also prevents us having to do a million tests here for "isPickUp", "isPedProp", etc.
if( !pObj->IsInPathServer() )
continue;
if( pObj == pEntityPathingOn )
continue;
// Always ignore unlocked doors
if( pObj->IsADoor() && !pObj->GetIsAnyFixedFlagSet() )
continue;
// Always ignore anything which is marked as "no avoid"
if( pObj->GetBaseModelInfo()->GetNotAvoidedByPeds() )
continue;
// Create the CEntityBoundAI here - because we need it to perform out final 'GetIsClimbableByAI' test immediately after
pEntityBoundsArray[iNumEntities].Set( *pObj, fPedZ, (pObj->IsReducedBBForPathfind() ? 0.f : fPedRadius), true, NULL );
// If we specified not to adjust the target position, then ignore any entities which are intersecting it
if( m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_DontAdjustTargetPosition) &&
pEntityBoundsArray[iNumEntities].LiesInside(m_vTarget))
{
continue;
}
// Ignore any objects which are marked as climbable by AI, if we are climbing them.
// How do we decide whether we're climbing them?
// If we have a climb waypoint in the close vicinity to the object, I suppose.
if( pObj->GetBaseModelInfo()->GetIsClimbableByAI() ) // && !pObj->m_nObjectFlags.bHasBeenUprooted )
{
// If the route has any object climbs
if( (m_pRoute->GetPossiblySetSpecialActionFlags() & WAYPOINT_FLAG_CLIMB_OBJECT) != 0)
{
bool bShouldIgnore = false;
for(s32 p=m_iProgress; p<m_pRoute->GetSize(); p++)
{
if( (m_pRoute->GetWaypoint(p).GetSpecialActionFlags() & WAYPOINT_FLAG_CLIMB_OBJECT) != 0)
{
if( pEntityBoundsArray[iNumEntities].LiesInside(m_pRoute->GetPosition(p), 1.0f) )
{
bShouldIgnore = true;
break;
}
}
}
if(bShouldIgnore)
continue;
}
}
pEntityArray[iNumEntities] = pObj;
#if __BANK
if(bDebugDraw)
pEntityBoundsArray[iNumEntities].fwDebugDraw(Color_cyan);
#endif
iNumEntities++;
if(iNumEntities == iMaxNum)
return iMaxNum;
iNumObjects++;
if(iNumObjects == iNumClosest)
break;
}
}
int iIgnoreComponents[32]; // See TDynObjBoundsFuncs::CalcBoundsForEntity() for the origins of this code
if( m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_AvoidEntityTypeVehicle) )
{
CPhysical * pVehicleEntering = NULL;
CTaskMoveGoToVehicleDoor * pGotoDoor = (CTaskMoveGoToVehicleDoor*) pPed->GetPedIntelligence()->FindTaskActiveMovementByType(CTaskTypes::TASK_MOVE_GO_TO_VEHICLE_DOOR);
if(pGotoDoor)
pVehicleEntering = pGotoDoor->GetTargetVehicle();
// If entity scanning is off, we must force a scan or be unaware of nearby vehicles
if(pPed->GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEntityScanning))
{
pPed->GetPedIntelligence()->GetVehicleScanner()->ScanForEntitiesInRange(*static_cast<CEntity*>(pPed), true);
}
s32 iNumVehicles = 0;
const CEntityScannerIterator vehiclesList = pPed->GetPedIntelligence()->GetNearbyVehicles();
for( const CVehicle* pVehicle = (const CVehicle*)vehiclesList.GetFirst(); pVehicle; pVehicle = (const CVehicle*)vehiclesList.GetNext() )
{
if( !pVehicle->IsInPathServer() )
continue;
if( pVehicle->InheritsFromBike() && pVehicle->IsOnItsSide() )
continue;
if( pVehicle == pEntityPathingOn )
continue;
if( pVehicle == pVehicleEntering )
continue;
if( !pVehicle->GetActiveForPedNavitation() )
continue;
int iNumIgnoreComponents = TDynObjBoundsFuncs::GetIgnoreComponentsForEntity(pVehicle, iIgnoreComponents, 32);
Assertf(iNumIgnoreComponents <= 32, "This is very bad, memory overwrite!");
pEntityBoundsArray[iNumEntities].Set( *pVehicle, fPedZ, (pVehicle->IsReducedBBForPathfind() ? 0.f : fPedRadius), true, NULL, iNumIgnoreComponents, iIgnoreComponents );
pEntityArray[iNumEntities] = pVehicle;
#if __BANK
if(bDebugDraw)
pEntityBoundsArray[iNumEntities].fwDebugDraw(Color_cyan);
#endif
iNumEntities++;
if(iNumEntities == iMaxNum)
return iMaxNum;
// Add the doors for this vehicle
// TODO: The code ignores doors if the path was set up to allow them being pushed closed.
// This is *not* correct behaviour; to do this correctly we'll need to logic into the CEntityBoundAI
// so that it is aware of which direction a door can be pushed/ignored.
// (Same as is done in the pathserver object LOS - see __ALLOW_TO_PUSH_VEHICLE_DOORS_CLOSED for reference)
if(!GetAllowToPushVehicleDoorsClosed())
{
for(s32 d=0; d<pVehicle->GetNumDoors(); d++)
{
const CCarDoor * pDoor = pVehicle->GetDoor(d);
if(pDoor->GetDoorRatio() > SMALL_FLOAT)
{
if( pEntityBoundsArray[iNumEntities].Set(*pDoor, pVehicle, fPedZ, fPedRadius, true) )
{
pEntityArray[iNumEntities] = NULL; // Can't set CCarDoor in here, since it doesn't derive from CEntity
#if __BANK
if(bDebugDraw)
pEntityBoundsArray[iNumEntities].fwDebugDraw(Color_cyan);
#endif
iNumEntities++;
if(iNumEntities == iMaxNum)
return iMaxNum;
}
}
}
}
// Iterate to next vehicle
iNumVehicles++;
if(iNumVehicles == iNumClosest)
break;
}
}
// Add the nearby fires
if(pPed->GetPedIntelligence()->GetNavCapabilities().IsFlagSet(CPedNavCapabilityInfo::FLAG_AVOID_FIRE))
{
// If entity scanning is off, we must force a scan or be unaware of nearby fires
if(pPed->GetPedAiLod().IsLodFlagSet(CPedAILod::AL_LodEventScanning))
{
pPed->GetPedIntelligence()->GetEventScanner()->GetFireScanner().Scan(*pPed, true);
}
Matrix34 fireMat;
const Vector3 * pNearbyFires = pPed->GetPedIntelligence()->GetEventScanner()->GetFireScanner().GetNearbyFires();
for(s32 f=0; f<CNearbyFireScanner::NUM_NEARBY_FIRES; f++)
{
const Vector3 & vFirePosAndRadius = pNearbyFires[f];
if(vFirePosAndRadius.IsZero())
break;
Vector3 vFireMin( -vFirePosAndRadius.w, -vFirePosAndRadius.w, -1.0f );
Vector3 vFireMax( vFirePosAndRadius.w, vFirePosAndRadius.w, 1.5f );
fireMat.MakeRotateZ( fwRandom::GetRandomNumberInRange(-PI, PI) );
fireMat.d = vFirePosAndRadius;
pEntityBoundsArray[iNumEntities].Set(vFireMin, vFireMax, fireMat, fPedZ, fPedRadius, true);
pEntityArray[iNumEntities] = NULL; // We have no entity pointer to set in here
#if __BANK
if(bDebugDraw)
pEntityBoundsArray[iNumEntities].fwDebugDraw(Color_cyan);
#endif
iNumEntities++;
if(iNumEntities == iMaxNum)
return iMaxNum;
}
}
return iNumEntities;
}
void CTaskNavBase::SetScanForObstructionsFrequency(u32 iFrequencyMS)
{
m_ScanForObstructionsTimer.ChangeDuration(iFrequencyMS);
}
void CTaskNavBase::SetScanForObstructionsNextFrame()
{
m_ScanForObstructionsTimer.SubtractTime( m_ScanForObstructionsTimer.GetDuration() );
}
u32 CTaskNavBase::GetTimeSinceLastScanForObstructions()
{
if(m_ScanForObstructionsTimer.IsSet())
{
return m_ScanForObstructionsTimer.GetTimeElapsed();
}
// If timer is not set, then we'll return the maximum time deltad
else
{
return CalcScanForObstructionsFreq();
}
}
u32 CTaskNavBase::CalcScanForObstructionsFreq() const
{
if(m_fInitialMoveBlendRatio >= MBR_SPRINT_BOUNDARY)
{
return ms_iScanForObstructionsFreq_Sprint;
}
else if(m_fInitialMoveBlendRatio >= MBR_RUN_BOUNDARY)
{
return ms_iScanForObstructionsFreq_Run;
}
else
{
return ms_iScanForObstructionsFreq_Walk;
}
}
u32 CTaskNavBase::GetInitialScanForObstructionsTime()
{
static dev_u32 iDefaultTime = 1000;
return iDefaultTime;
}
// NAME : ScanForObstructions
// PURPOSE : Scan ahead of the ped for object/vehicle obstructions on path segments
// Obstacles may have moved into position after the route was planned, or may have
// converted from dummy to real in the meantime.
// RETURNS: true if there is an obstruction on the route
//
// TODO: We need to factor in the logic currently in CTaskMoveFollowNavMesh, concerning
// route modes such as objects' bounding-boxes being reduced. Otherwise we will falsely
// identify object collisions.
bool CTaskNavBase::ScanForObstructions(CPed * pPed, const float fMaxDistanceAheadToScan, float & fOut_DistanceToObstruction) const
{
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
const float fPedRadius =
(m_iNavMeshRouteMethod==eRouteMethod_ReducedObjectBoundingBoxes) ?
0.0f : PATHSERVER_PED_RADIUS - 0.1f; // TODO: Should use capsule info here
fOut_DistanceToObstruction = 0.0f;
#if __BANK
static dev_s32 iDbgFrames = 10000;
static dev_bool bDebugDraw = false;
#endif
// NB: I am not going to special-case "eRouteMethod_OnlyAvoidNonMovableObjects" in here, in the understanding
// that ScanForObstructions() will be run only periodically and that it will be advantageous to kick the
// ped out of "eRouteMethod_OnlyAvoidNonMovableObjects" mode when possible.
// What we are doing here is really this:
// CEntityBoundAI entityBounds[CEntityScanner::MAX_NUM_ENTITIES*2];
// but we put them in a vector-aligned buffer without calling the constructor. This is done
// to reduce performance overhead, we are always going to initialize the ones we use separately anyway,
// and while CEntityBoundAI's constructor doesn't currently initialize anything anyway, it contains
// a spdSphere which has a default constructor that writes to memory, so we avoid doing a bunch of
// extra memory accesses and possibly function calls by not calling it.
// The reason for using Vec4V rather than an aligned array of chars or something is that the alignment
// declarations tend to be a bit fragile across platforms.
Vec4V entityBoundsBuffer[CEntityScanner::MAX_NUM_ENTITIES*2*((sizeof(CEntityBoundAI) + sizeof(Vec4V) - 1)/sizeof(Vec4V))];
CompileTimeAssert(sizeof(entityBoundsBuffer) >= sizeof(CEntityBoundAI)*CEntityScanner::MAX_NUM_ENTITIES*2);
CompileTimeAssert(__alignof(CEntityBoundAI) <= sizeof(Vec4V));
CEntityBoundAI* entityBounds = reinterpret_cast<CEntityBoundAI*>(entityBoundsBuffer);
const CEntity * entities[CEntityScanner::MAX_NUM_ENTITIES*2];
const int iNumEntities = GetNearbyEntitiesForLineOfSightTest( pPed, entityBounds, entities, CEntityScanner::MAX_NUM_ENTITIES*2, CEntityScanner::MAX_NUM_ENTITIES*2, fPedRadius, vPedPos.z );
if( !iNumEntities )
{
return false;
}
ScalarV distToObstructionV(V_ZERO);
const ScalarV maxDistanceAheadToScanV(fMaxDistanceAheadToScan);
const int iStartPoint = m_iClosestSegOnExpandedRoute+1;
Vec3V endV;
// Start from 'm_vClosestPosOnRoute'
if(iStartPoint < ms_iNumExpandedRouteVerts)
{
endV = RCC_VEC3V(m_vClosestPosOnRoute);
}
for( int v=iStartPoint; v<ms_iNumExpandedRouteVerts && IsLessThanAll(distToObstructionV, maxDistanceAheadToScanV); v++)
{
const Vec3V startV = endV; // This segment starts where the previous one ended.
endV = ms_vExpandedRouteVerts[v].GetPos();
// Check for special action flags;
// We will not scan beyond a special action link, unless this is the first line-segment which we
// are testing - which would indicate that the peds has already passed that action.
if( IsSpecialActionClimbOrDrop(ms_vExpandedRouteVerts[v-1].m_iWptFlag.GetSpecialActionFlags() ) )
{
if( v == iStartPoint )
{
// If prev waypoint was a climb, then ignore the segment to the next one
distToObstructionV = Add(distToObstructionV, DistFast(startV, endV));
continue;
}
else
{
// If we have reached a special action waypoint, then abort scanning
return false;
}
}
// Put the start and end Z coordinates into a single vector, for faster comparisons with entity bound Z limits within the loop.
const Vec4V startEndZ4V = GetFromTwo<Vec::Z1, Vec::Z1, Vec::Z2, Vec::Z2>(startV, endV);
for( s32 e=0; e<iNumEntities; e++)
{
// Test whether this entity is above or below this route
// NB: This is not even nearly accurate enough, and simply throws out any entity which is above/below
// either the route start or route end. Ideally we should clip start/end same as in TestLineOfSight(), below.
const Vec4V topZ4V(entityBounds[e].GetTopZV());
const Vec4V bottomZ4V(entityBounds[e].GetBottomZV());
const VecBoolV isAboveV = IsGreaterThan(startEndZ4V, topZ4V);
const VecBoolV isBelowV = IsLessThan(startEndZ4V, bottomZ4V);
const VecBoolV isAboveOrBelowV = Or(isAboveV, isBelowV);
if(!IsFalseAll(isAboveOrBelowV))
{
Assert(startV.GetZf() > entityBounds[e].GetTopZ() || endV.GetZf() > entityBounds[e].GetTopZ() || startV.GetZf() < entityBounds[e].GetBottomZ() || endV.GetZf() < entityBounds[e].GetBottomZ());
continue;
}
Assert(!(startV.GetZf() > entityBounds[e].GetTopZ() || endV.GetZf() > entityBounds[e].GetTopZ() || startV.GetZf() < entityBounds[e].GetBottomZ() || endV.GetZf() < entityBounds[e].GetBottomZ()));
ScalarV distToIsectV;
if ( !entityBounds[e].TestLineOfSightReturnDistance( startV, endV, distToIsectV ) )
{
#if __BANK
if( bDebugDraw )
{
grcDebugDraw::Line(VEC3V_TO_VECTOR3(startV) - (ZAXIS*0.5f), VEC3V_TO_VECTOR3(endV) - (ZAXIS*0.5f), Color_red, Color_red, iDbgFrames);
Vector3 vHit1, vHit2;
if(entityBounds[e].ComputeCrossingPoints(VEC3V_TO_VECTOR3(startV), VEC3V_TO_VECTOR3(endV), vHit1, vHit2))
{
Vector3 vHit = (vHit1-VEC3V_TO_VECTOR3(startV)).Mag2() < (vHit2-VEC3V_TO_VECTOR3(startV)).Mag2() ? vHit1 : vHit2;
grcDebugDraw::Sphere(vHit - (ZAXIS*0.5f), 0.3f, Color_red, false, iDbgFrames);
}
}
#endif
distToObstructionV = Add(distToObstructionV, distToIsectV);
StoreScalar32FromScalarV(fOut_DistanceToObstruction, distToObstructionV);
return true;
}
}
#if __BANK
if( bDebugDraw )
{
grcDebugDraw::Line(VEC3V_TO_VECTOR3(startV) - (ZAXIS*0.5f), VEC3V_TO_VECTOR3(endV) - (ZAXIS*0.5f), Color_orange, Color_orange, 50);
}
#endif
distToObstructionV = Add(distToObstructionV, DistFast(startV, endV));
}
// Note: we currently don't bother writing out the distance in this case, though we could if there
// was any benefit in the caller knowing the length of the path.
return false;
}
// NAME : IsMovingIntoObstacle
// PURPOSE : Determine whether ped is moving into specified entity types, based upon examining this frame's collision records
bool CTaskNavBase::IsMovingIntoObstacle(const CPed * pPed, bool bVehicles, bool bObjects) const
{
const CCollisionHistory* pCollisionHistory = pPed->GetFrameCollisionHistory();
if(pCollisionHistory->GetNumCollidedEntities() == 0)
return false;
Vector3 vMoveDirLocal(pPed->GetMotionData()->GetCurrentMbrX(), pPed->GetMotionData()->GetCurrentMbrY(), 0.0f);
if(vMoveDirLocal.Mag2() < MBR_WALK_BOUNDARY)
return false;
vMoveDirLocal.Normalize();
static dev_float fIgnoreImpulseMag = 0.0f;
Vector3 vLocalNormal;
const CCollisionRecord* pColRecord;
//-----------------------------------
// Examine vehicle collision records
if(bVehicles)
{
for(pColRecord = pCollisionHistory->GetFirstVehicleCollisionRecord(); pColRecord != NULL ; pColRecord = pColRecord->GetNext())
{
// Maybe rule out vehicles we are entering/leaving ?
//CVehicle * pVehicle = static_cast<CVehicle*>(pColRecord->m_pRegdCollisionEntity.Get());
if(pColRecord->m_fCollisionImpulseMag < fIgnoreImpulseMag)
continue;
vLocalNormal = VEC3V_TO_VECTOR3(pPed->GetTransform().UnTransform3x3(RCC_VEC3V(pColRecord->m_MyCollisionNormal)));
if(DotProduct(vMoveDirLocal, vLocalNormal) < -0.707f)
{
return true;
}
}
}
//----------------------------------
// Examine object collision records
if(bObjects)
{
for(pColRecord = pCollisionHistory->GetFirstObjectCollisionRecord(); pColRecord != NULL ; pColRecord = pColRecord->GetNext())
{
if(pColRecord->m_fCollisionImpulseMag < fIgnoreImpulseMag)
continue;
vLocalNormal = VEC3V_TO_VECTOR3(pPed->GetTransform().UnTransform3x3(RCC_VEC3V(pColRecord->m_MyCollisionNormal)));
if(DotProduct(vMoveDirLocal, vLocalNormal) < -0.707f)
{
return true;
}
}
}
// Shall we do the same for building collisions?
return false;
}
bool CTaskNavBase::TestObjectLookAheadLOS(CPed * pPed)
{
if( ((CTaskMove*)GetSubTask())->HasTarget()==false )
return true;
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
const float fPedRadius =
(m_iNavMeshRouteMethod==eRouteMethod_ReducedObjectBoundingBoxes) ?
0.0f : PATHSERVER_PED_RADIUS - 0.1f; // TODO: Should use capsule info here
CEntityBoundAI entityBounds[CEntityScanner::MAX_NUM_ENTITIES*2];
const CEntity * entities[CEntityScanner::MAX_NUM_ENTITIES*2];
static dev_s32 iNumOfEachType = 2;
s32 iNumEntities = GetNearbyEntitiesForLineOfSightTest( pPed, entityBounds, entities, iNumOfEachType, CEntityScanner::MAX_NUM_ENTITIES*2, fPedRadius, vPedPos.z );
if( !iNumEntities )
{
return true;
}
const Vector3 vMoveTarget = ((CTaskMove*)GetSubTask())->GetTarget();
for( s32 e=0; e<iNumEntities; e++)
{
if( vPedPos.z > entityBounds[e].GetTopZ() || vMoveTarget.z > entityBounds[e].GetTopZ() || vPedPos.z < entityBounds[e].GetBottomZ() || vMoveTarget.z < entityBounds[e].GetBottomZ() )
{
continue;
}
if ( entityBounds[e].TestLineOfSight( VECTOR3_TO_VEC3V(vPedPos), VECTOR3_TO_VEC3V(vMoveTarget) ) == false )
{
return false;
}
}
return true;
}
// NAME : ScanForMissedClimb
// PURPOSE : It is possible to miss a climb waypoint, if the climb point on the navmesh is some distance away from the climb -
// in which case the climb may fail & the task continue. Alternatively the initial progress on the route may be calcaulted to
// be some distance beyond a climb, in which case it will also be missed.
// As a remedy for this, if our previous progress was a climb - then we should perform tests to determine whether the ped is
// stuck at climbable geometry, and perform the climb belatedly if so.
bool CTaskNavBase::ScanForMissedClimb(CPed * pPed)
{
Assert(GetState()==NavBaseState_FollowingPath);
dev_s32 iStaticThreshold = 4;
if(m_pRoute && m_iProgress > 0)
{
// Note: from StateFollowingPath_OnUpdate(), we now make the assumption that this function can't
// return true unless IsSpecialActionClimbOrDrop() returns true for one of the waypoints. This includes
// WAYPOINT_FLAG_CLIMB_HIGH and WAYPOINT_FLAG_CLIMB_LOW, but we may have to be a bit careful if making changes.
const u32 iWpt = m_pRoute->GetWaypoint(m_iProgress-1).GetSpecialAction();
if(iWpt == WAYPOINT_FLAG_CLIMB_HIGH || iWpt == WAYPOINT_FLAG_CLIMB_LOW)
{
if(pPed->GetPedIntelligence()->GetEventScanner()->GetStaticMovementScanner().GetStaticCounter() > iStaticThreshold)
{
const EWaypointActionResult eWptActionResult = CreateWaypointActionEvent(pPed, m_iProgress-1);
if(eWptActionResult == WaypointAction_EventCreated)
{
SetState(NavBaseState_WaitingForWaypointActionEvent);
return true;
}
else
{
return false;
}
}
}
}
return false;
}
bool CTaskNavBase::GetIsTaskSequencedBefore(const CPed * pPed, bool & bIsGotoTask)
{
return CTaskNavBase::GetIsTaskSequencedAdjacent(pPed, this, bIsGotoTask, -1);
}
bool CTaskNavBase::GetIsTaskSequencedAfter(const CPed * pPed, bool & bIsGotoTask)
{
return CTaskNavBase::GetIsTaskSequencedAdjacent(pPed, this, bIsGotoTask, 1);
}
bool CTaskNavBase::GetIsTaskSequencedAdjacent(const CPed * pPed, const CTaskMove * UNUSED_PARAM(pThisTask), bool & bIsGotoTask, const int iDirection)
{
Assert(iDirection==-1 || iDirection==1);
bIsGotoTask = false;
CPedIntelligence * pPedAi = pPed->GetPedIntelligence();
CTask * pActiveSeqTask = pPedAi->FindTaskActiveByType(CTaskTypes::TASK_USE_SEQUENCE);
if(!pActiveSeqTask)
return false;
CTaskUseSequence * pTaskUseSequence = (CTaskUseSequence*)pActiveSeqTask;
const CTaskSequenceList * pTaskSequence = pTaskUseSequence->GetStaticTaskSequenceList();
if (!pTaskSequence)
return false;
// Check that the task at the current position in the sequence is a navmesh route task
const int iProgress = pTaskUseSequence->GetPrimarySequenceProgress();
const aiTask * pSeqTask = pTaskSequence->GetTask(iProgress);
if(pSeqTask->GetTaskType() != CTaskTypes::TASK_COMPLEX_CONTROL_MOVEMENT)
return false;
if(iProgress + iDirection >= 0 && iProgress + iDirection < CTaskList::MAX_LIST_SIZE)
{
const aiTask * pTask = pTaskSequence->GetTask(iProgress + iDirection);
// There is a task sequenced after this task
if(pTask)
{
if(pTask->GetTaskType()==CTaskTypes::TASK_COMPLEX_CONTROL_MOVEMENT)
{
CTaskTypes::eTaskType iNextTaskType = (CTaskTypes::eTaskType) ((CTaskComplexControlMovement*)pTask)->GetMoveTaskType();
if(iNextTaskType == CTaskTypes::TASK_MOVE_GO_TO_POINT ||
iNextTaskType == CTaskTypes::TASK_MOVE_GO_TO_POINT_ON_ROUTE ||
iNextTaskType == CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH ||
iNextTaskType == CTaskTypes::TASK_MOVE_FOLLOW_POINT_ROUTE)
{
bIsGotoTask = true;
}
}
// Hack
if(pTask->GetTaskType()==CTaskTypes::TASK_COMPLEX_USE_MOBILE_PHONE)
{
bIsGotoTask = true;
}
return true;
}
}
return false;
}
bool CTaskNavBase::TransformRouteOnDynamicNavmesh(const CPed * pPed)
{
Assert(m_pOnEntityRouteData && m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn);
Assert(m_pOnEntityRouteData && m_pOnEntityRouteData->m_pLocalSpaceRoute);
Assert(m_pRoute);
if(!m_pRoute || !m_pOnEntityRouteData || !m_pOnEntityRouteData->m_pLocalSpaceRoute || !m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn)
return false;
AI_OPTIMISATIONS_OFF_ONLY( Assert(m_pRoute->GetSize() == m_pOnEntityRouteData->m_pLocalSpaceRoute->GetSize()) );
if(m_pRoute->GetSize() != m_pOnEntityRouteData->m_pLocalSpaceRoute->GetSize())
return false;
// Transform all the route elements in m_pPointRoute
const Matrix34 mat = MAT34V_TO_MATRIX34(m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn->GetMatrix());
int p;
for(p=0; p<m_pOnEntityRouteData->m_pLocalSpaceRoute->GetSize(); p++)
{
Vector3 vTransformed;
mat.Transform(m_pOnEntityRouteData->m_pLocalSpaceRoute->GetPosition(p), vTransformed);
m_pRoute->Set(p, vTransformed, m_pOnEntityRouteData->m_pLocalSpaceRoute->GetWaypoint(p) );
}
if(GetState() != NavBaseState_FollowingPath)
return false;
Assert(m_iProgress >= 0 && m_iProgress < m_pRoute->GetSize());
const Vector3 & vGoToTarget = m_pRoute->GetPosition(m_iProgress);
if(GetSubTask())
{
Assert(GetSubTask()->IsMoveTask());
CTaskMove * pMoveTask = (CTaskMove*)GetSubTask();
pMoveTask->SetTarget(pPed, VECTOR3_TO_VEC3V(vGoToTarget));
}
return true;
}
// NAME : AdjustMoveBlendRatioForRouteLength
// PURPOSE : Determine whether the MBR needs reducing for small route lengths
// This is to avoid peds performing long run start/stop anims for small routes (and thus overshooting)
float CTaskNavBase::AdjustMoveBlendRatioForRouteLength(const float fMBR, const float fRouteLength) const
{
float fMaxMbr;
if(fRouteLength < 3.0f)
fMaxMbr = 1.0f;
else if(fRouteLength < 6.0f)
fMaxMbr = 2.0;
else fMaxMbr = 3.0f;
return Min(fMBR, fMaxMbr);
}
float CTaskNavBase::GetDistanceLeftOnCurrentRouteSection(const CPed * pPed) const
{
if(!m_pRoute || !GetSubTask() || m_iProgress >= m_pRoute->GetSize())
return 0.0f;
Assert(m_iProgress >= 0 && m_iProgress < m_pRoute->GetSize());
int iProgressToUse;
if(GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_ON_ROUTE &&
m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_HasSlidTargetAlongSpline) &&
m_fSplineTVal > 0.5f)
{
Assert(m_iProgress+1 < m_pRoute->GetSize());
iProgressToUse = m_iProgress+1;
}
else
{
iProgressToUse = m_iProgress;
}
float fDist = 0.0f;
const Vector3 & vCurrTarget = m_pRoute->GetPosition(iProgressToUse);
const Vector3 vToTarget = vCurrTarget - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
const float fMagToTarget2 = vToTarget.XYMag2();
if(fMagToTarget2 > 0.001f)
fDist += Sqrtf(fMagToTarget2);
for(int p=iProgressToUse+1; p<m_pRoute->GetSize(); p++)
{
const Vector3 vToNext = m_pRoute->GetPosition(p) - m_pRoute->GetPosition(p-1);
const float fMagToNext2 = vToNext.XYMag2();
if(fMagToNext2 > 0.001f)
fDist += Sqrtf(fMagToNext2);
}
return fDist;
}
float CTaskNavBase::GetTotalRouteLength() const
{
Assert(m_pRoute);
return m_pRoute->GetLength();
}
float CTaskNavBase::GetTotalRouteLengthSq() const
{
Assert(m_pRoute);
return m_pRoute->GetLengthSqr();
}
bool CTaskNavBase::IsTotalRouteLongerThan(float fLength) const
{
Assert(m_pRoute);
return m_pRoute->IsLongerThan(fLength);
}
TNavMeshWaypointFlag CTaskNavBase::GetWaypointFlag(const int i) const
{
Assert(m_pRoute && m_pRoute->GetSize());
Assert(i >= 0 && i < m_pRoute->GetSize());
return m_pRoute->GetWaypoint(i);
}
/*
PURPOSE
Helper function which is essentially the same as geomTValues::FindTValueSegToPoint(),
but it operates on up to four segments and four target points
in parallel, passed in as struct of array-like vectors. point1XV
contains the X coordinates of the point1 variable for the four segments, etc.
NOTES
This shouldn't really be more expensive than the regular FindTValueSegToPoint(),
and the math should be pretty much the same as the vectorized single segment version
of that function so hopefully there won't be any significant differences in the output.
*/
static Vec4V_Out sFindTValueSegToPoint(
Vec4V_In point1XV, Vec4V_In point1YV, Vec4V_In point1ZV,
Vec4V_In deltaXV, Vec4V_In deltaYV, Vec4V_In deltaZV,
Vec4V_In targetPointXV, Vec4V_In targetPointYV, Vec4V_In targetPointZV)
{
const Vec4V ptXV = Subtract(targetPointXV, point1XV);
const Vec4V ptYV = Subtract(targetPointYV, point1YV);
const Vec4V ptZV = Subtract(targetPointZV, point1ZV);
const Vec4V oneDotXV = Scale(deltaXV, ptXV);
const Vec4V oneDotXYV = AddScaled(oneDotXV, deltaYV, ptYV);
const Vec4V oneDotV = AddScaled(oneDotXYV, deltaZV, ptZV);
const Vec4V bothDotXV = Scale(deltaXV, deltaXV);
const Vec4V bothDotXYV = AddScaled(bothDotXV, deltaYV, deltaYV);
const Vec4V bothDotV = AddScaled(bothDotXYV, deltaZV, deltaZV);
const Vec4V zeroV(V_ZERO);
const Vec4V oneV(V_ONE);
// Note: InvScaleFast() here is an approximation, but it should be the same as
// the __vmulfp(oneDotV,__vrefp(bothDotV)) intrinsics in the single segment version,
// i.e. good enough.
const Vec4V tOnInfLineV = InvScaleFast(oneDotV, bothDotV);
const VecBoolV tMaxMaskV = IsGreaterThanOrEqual(tOnInfLineV, oneV);
const Vec4V tClampedMaxV = SelectFT(tMaxMaskV, tOnInfLineV, oneV);
const VecBoolV tMinMaskV = IsGreaterThan(oneDotV, zeroV); // Note: this was probably IsGreaterThanOrEqual() in the original function.
const Vec4V tClampedV = And(tClampedMaxV, Vec4V(tMinMaskV));
return tClampedV;
}
int CTaskNavBase::CalculateClosestSegmentOnExpandedRoute(CNavMeshExpandedRouteVert * pExpandedRouteVerts, const int iNumVerts, const Vector3 & vTargetPos, float & fOut_SplineTVal, Vector3 * v_OutClosestPosOnRoute, Vector3 * v_OutClosestSegment)
{
if(iNumVerts < 2)
{
fOut_SplineTVal = 0.0f;
if(iNumVerts > 0 && v_OutClosestPosOnRoute)
*v_OutClosestPosOnRoute = VEC3V_TO_VECTOR3(pExpandedRouteVerts[0].GetPos());
return 0;
}
const int iCount = iNumVerts-1;
const Vec4V zeroV(V_ZERO);
// Initialize vectors for keeping track of the closest point found.
Vec4V closestDistSqV(V_FLT_MAX);
Vec4V closestTValV = zeroV;
Vec4V closestSegIntV(V_80000000);
Vec4V closestSegFloatV(V_NEGONE);
// Get the target position.
const Vec3V targetPosV = RCC_VEC3V(vTargetPos);
// We will do up to four segment tests in one operation. Each of the variables below
// store the X, Y, or Z coordinate of all four start or end points of these segments.
Vec4V points1XV = zeroV;
Vec4V points1YV = zeroV;
Vec4V points1ZV = zeroV;
Vec4V points2XV = zeroV;
Vec4V points2YV = zeroV;
Vec4V points2ZV = zeroV;
// We keep track of the current segment index both as integers and floats in vector
// registers, and these variables are related to that:
Vec4V segsIntV(V_80000000);
Vec4V segsFloatV(V_NEGONE);
Vec4V segIntV = zeroV;
Vec4V segFloatV = zeroV;
const Vec4V segAddIntV(V_INT_1);
const Vec4V segAddFloatV(V_ONE);
// Load the first point of the first segment to prepare for the loop.
Vec3V currV = pExpandedRouteVerts[0].GetPos();
// We will load up four segments into vector registers, and numLoadedSegs keeps track
// of how many we currently have loaded.
int numLoadedSegs = 0;
for(int j = 1; j < iNumVerts; j++)
{
// Load the XYZ coordinates of the second point of this segment.
const Vec3V nextV = pExpandedRouteVerts[j].GetPos();
// Next, we take all of our vectors for four segment tests, and shift them one step,
// shifting in this new segment into the W component.
points1XV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(points1XV, Vec4V(currV));
points1YV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::Y2>(points1YV, Vec4V(currV));
points1ZV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::Z2>(points1ZV, Vec4V(currV));
points2XV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::X2>(points2XV, Vec4V(nextV));
points2YV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::Y2>(points2YV, Vec4V(nextV));
points2ZV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::Z2>(points2ZV, Vec4V(nextV));
segsIntV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::W2>(segsIntV, segIntV);
segsFloatV = GetFromTwo<Vec::Y1, Vec::Z1, Vec::W1, Vec::W2>(segsFloatV, segFloatV);
// Prepare for the next iteration.
currV = nextV;
segIntV = AddInt(segIntV, segAddIntV);
segFloatV = Add(segFloatV, segAddFloatV);
// If we don't have four segments yet, and this isn't the very last iteration through
// the loop, we can continue with the next segment.
if(++numLoadedSegs < 4 && j != iCount)
{
continue;
}
// Compute the delta vectors for the (up to) four segments we are about to test.
const Vec4V deltaXV = Subtract(points2XV, points1XV);
const Vec4V deltaYV = Subtract(points2YV, points1YV);
const Vec4V deltaZV = Subtract(points2ZV, points1ZV);
// Find the T values of the closest points to targetPointV, on these four segments.
const Vec4V tV = sFindTValueSegToPoint(points1XV, points1YV, points1ZV, deltaXV, deltaYV, deltaZV, Vec4V(targetPosV.GetX()), Vec4V(targetPosV.GetY()), Vec4V(targetPosV.GetZ()));
// Compute the position of the closets point on each segment.
const Vec4V posOnSegXV = AddScaled(points1XV, deltaXV, tV);
const Vec4V posOnSegYV = AddScaled(points1YV, deltaYV, tV);
const Vec4V posOnSegZV = AddScaled(points1ZV, deltaZV, tV);
// Compute the vector from the target point to the closest point.
const Vec4V deltaToClosestPosXV = Subtract(posOnSegXV, Vec4V(targetPosV.GetX()));
const Vec4V deltaToClosestPosYV = Subtract(posOnSegYV, Vec4V(targetPosV.GetY()));
const Vec4V deltaToClosestPosZV = Subtract(posOnSegZV, Vec4V(targetPosV.GetZ()));
// Compute the squared distance to each of these four closest points.
const Vec4V distToClosestPosSqXV = Scale(deltaToClosestPosXV, deltaToClosestPosXV);
const Vec4V distToClosestPosSqXYV = AddScaled(distToClosestPosSqXV, deltaToClosestPosYV, deltaToClosestPosYV);
const Vec4V distToClosestPosSqV = AddScaled(distToClosestPosSqXYV, deltaToClosestPosZV, deltaToClosestPosZV);
// At the end of the array, we may not actually have had four segments loaded up.
// Here, we create a mask representing the elements of the vectors that are currently valid.
const VecBoolV validMaskV = IsGreaterThanOrEqual(segsFloatV, zeroV);
// Create a mask that's true if the squared distance is closer than the closest previously find
// square distance (for each of the four vector components). We and with validMaskV we computed
// above, so that only the valid segments can cause a new minimum to be found.
const VecBoolV lessThanClosestDistMaskV = And(IsLessThan(distToClosestPosSqV, closestDistSqV), validMaskV);
// Use vector select operations to either keep the old value, or switch to use the new
// values if we found a new best segment.
closestDistSqV = SelectFT(lessThanClosestDistMaskV, closestDistSqV, distToClosestPosSqV);
closestTValV = SelectFT(lessThanClosestDistMaskV, closestTValV, tV);
closestSegIntV = SelectFT(lessThanClosestDistMaskV, closestSegIntV, segsIntV);
closestSegFloatV = SelectFT(lessThanClosestDistMaskV, closestSegFloatV, segsFloatV);
// Clear out the segment information in the buffer, so we can keep track of which ones
// are valid in future iterations. We could clear out the rest of the buffer too (points1XV, etc),
// but that really shouldn't be necessary since we won't use the results of those anyway.
segsIntV = Vec4V(V_80000000);
segsFloatV = Vec4V(V_NEGONE);
}
// At this point, we have a vector containing the closest squared distance for every four
// segments. That is, the X component contains the closest of segments 0, 4, 8, etc, Y is
// the closest of segments 1, 5, 9, etc. We can use MinElement() to find the smallest across
// all the components.
const ScalarV closestDistOfAllSqV = MinElement(closestDistSqV);
// Next, we can compare this value with each of the components, to create a mask which
// tells us which of the values was the smallest.
const VecBoolV maskClosestOfAllV = IsEqual(closestDistSqV, Vec4V(closestDistOfAllSqV));
// Now, potentially there could be more than one that had this smallest value. This would be
// a problem since we need a single T value, etc. We have the indices of each one stored
// in a separate vector, so we can use that to arbitrate. First, we use the mask we computed
// above to come up with a vector where only the winner(s) have valid values, the rest get set to FLT_MAX.
const Vec4V closestSegFloatMaskedV = SelectFT(maskClosestOfAllV, Vec4V(V_FLT_MAX), closestSegFloatV);
// Now, we can find the smallest of these elements, and create a new mask in which only one
// element can be true, corresponding to the final winner.
// Note: this would have been less awkward if we had a Min operation on integer vectors, since
// then we could get rid of the float segment vector. I think that was removed from the 360/PS3
// instruction sets to make space for other things. I tried comparing integers as if they were floats,
// but didn't have much success with that, even if they were all positive, probably because some integers
// correspond to denormalized or otherwise invalid vectors.
const ScalarV closestSmallestIndexFloatV = MinElement(closestSegFloatMaskedV);
const VecBoolV maskSmallestIndexV = IsEqual(closestSegFloatMaskedV, Vec4V(closestSmallestIndexFloatV));
// Now, we use the mask we computed to And the integer index vector.
const Vec4V maskedClosestSmallestIndexV = And(Vec4V(maskSmallestIndexV), closestSegIntV);
// Again, it would have been nice to use a Max() operation here, but since we don't have that for
// vectors, we can get the right value by adding up the components - all but the one that we want
// should be zero due to the And operation above.
const ScalarV closestSmallestIndexV = AddInt(AddInt(maskedClosestSmallestIndexV.GetX(), maskedClosestSmallestIndexV.GetY()),
AddInt(maskedClosestSmallestIndexV.GetZ(), maskedClosestSmallestIndexV.GetW()));
// Finally, we can extract the T value of the closest segment.
const ScalarV finalClosestTValV = MaxElement(And(closestTValV, Vec4V(maskSmallestIndexV)));
// At this point, we need the index of the closest segment in an integer register.
const int iClosestSeg = closestSmallestIndexV.Geti();
Assert(iClosestSeg >= 0 && iClosestSeg < iCount);
// Get the two end points of the segment containing the closest point.
const Vec3V closestSegPt1V = pExpandedRouteVerts[iClosestSeg].GetPos();
const Vec3V closestSegPt2V = pExpandedRouteVerts[iClosestSeg + 1].GetPos();
// Compute the closest point by using the T value to lerp on this segment.
const Vec3V closestPtV = Lerp(finalClosestTValV, closestSegPt1V, closestSegPt2V);
// Load the m_fSplineT values into vector registers.
const ScalarV closestSegSplineT1V = pExpandedRouteVerts[iClosestSeg].GetSplineTV();
const ScalarV closestSegSplineT2V = pExpandedRouteVerts[iClosestSeg + 1].GetSplineTV();
// Now, there used to be logic like this:
// if( (pExpandedRouteVerts[iClosestSeg].m_fSplineT == 0.0f && pExpandedRouteVerts[iClosestSeg+1].m_fSplineT == 1.0f) ||
// (pExpandedRouteVerts[iClosestSeg].m_fSplineT == 1.0f && pExpandedRouteVerts[iClosestSeg+1].m_fSplineT == 0.0f) )
// {
// fOut_SplineTVal = 0.0f;
// }
// else
// {
// fOut_SplineTVal = Lerp(fClosestTVal, pExpandedRouteVerts[iClosestSeg].m_fSplineT, pExpandedRouteVerts[iClosestSeg+1].m_fSplineT);
// }
// To save on float comparisons, etc, this has been rewritten in a vectorized form. We can do the four equality checks
// by building two vectors, basically (t1, t2, t1, t2) and (0, 1, 1, 0).
const Vec4V closestSegSplineTV(closestSegSplineT1V, closestSegSplineT2V, closestSegSplineT1V, closestSegSplineT2V);
const Vec4V closestSegSplineTCmpV = And(Vec4V(V_MASKYZ), Vec4V(V_ONE));
// Do the comparison.
const VecBoolV closestSegSplineTMaskV = IsEqual(closestSegSplineTV, closestSegSplineTCmpV);
// Compute a single boolean value for the expression ((t1 == 0) && (t2 == 1)) || ((t1 == 1) && (t2 == 0)).
const BoolV closestSegSplineTShouldBeZeroV = Or(And(closestSegSplineTMaskV.GetX(), closestSegSplineTMaskV.GetY()), And(closestSegSplineTMaskV.GetZ(), closestSegSplineTMaskV.GetW()));
// Compute the lerped spline T value, if we are going to use it.
const ScalarV lerpedSplineTValV = Lerp(finalClosestTValV, closestSegSplineT1V, closestSegSplineT2V);
// Finally, compute the spline T value, masked to zero if the boolean expression above evaluated to true.
ScalarV closestSegSplineTShouldNotBeZeroV;
closestSegSplineTShouldNotBeZeroV.SetIntrin128(InvertBits(closestSegSplineTShouldBeZeroV).GetIntrin128());
const ScalarV splineTValV = And(closestSegSplineTShouldNotBeZeroV, lerpedSplineTValV);
// Closest point and spline T value, and return the segment index.
StoreScalar32FromScalarV(fOut_SplineTVal, splineTValV);
if(v_OutClosestPosOnRoute)
*v_OutClosestPosOnRoute = VEC3V_TO_VECTOR3(closestPtV);
if(v_OutClosestSegment)
*v_OutClosestSegment = VEC3V_TO_VECTOR3(closestSegPt2V - closestSegPt1V);
Assert(iClosestSeg >= 0);
return iClosestSeg;
}
#if 0 // Unoptimized version, for reference.
int CTaskNavBase::CalculateClosestSegmentOnExpandedRoute(CNavMeshExpandedRouteVert * pExpandedRouteVerts, const int iNumVerts, const Vector3 & vTargetPos, float & fOut_SplineTVal)
{
if(iNumVerts < 2)
{
fOut_SplineTVal = 0.0f;
return 0;
}
const int iCount = iNumVerts-1;
float fClosestDistSqr = FLT_MAX;
float fClosestTVal = 0.0f;
s32 iClosestSeg = -1;
for(s32 i=0; i<iCount; i++)
{
const Vector3 & vCurr = pExpandedRouteVerts[i].m_vPos;
const Vector3 & vNext = pExpandedRouteVerts[i+1].m_vPos;
const Vector3 vVec = vNext-vCurr;
const float t = (vVec.Mag2() > 0.0f) ? geomTValues::FindTValueSegToPoint(vCurr, vVec, vTargetPos) : 0.0f;
const Vector3 vPosOnSeg = (t==0.0f) ? vCurr : (t==1.0f) ? vNext : vCurr + (vVec * t);
const float fDistSqr = (vPosOnSeg-vTargetPos).Mag2();
if(fDistSqr < fClosestDistSqr)
{
fClosestDistSqr = fDistSqr;
fClosestTVal = t;
iClosestSeg = i;
}
}
m_vClosestPosOnRoute =
pExpandedRouteVerts[iClosestSeg].m_vPos +
((pExpandedRouteVerts[iClosestSeg+1].m_vPos - pExpandedRouteVerts[iClosestSeg].m_vPos) * fClosestTVal);
if( (pExpandedRouteVerts[iClosestSeg].m_fSplineT == 0.0f && pExpandedRouteVerts[iClosestSeg+1].m_fSplineT == 1.0f) ||
(pExpandedRouteVerts[iClosestSeg].m_fSplineT == 1.0f && pExpandedRouteVerts[iClosestSeg+1].m_fSplineT == 0.0f) )
{
fOut_SplineTVal = 0.0f;
}
else
{
fOut_SplineTVal = Lerp(fClosestTVal, pExpandedRouteVerts[iClosestSeg].m_fSplineT, pExpandedRouteVerts[iClosestSeg+1].m_fSplineT);
}
return iClosestSeg;
}
#endif // 0 - Unoptimized version, for reference.
bool CTaskNavBase::GetThisWaypointLine(const CPed * UNUSED_PARAM(pPed), Vector3 & vOutStartPos, Vector3 & vOutEndPos, s32 iProgressAhead )
{
if(GetState()==NavBaseState_FollowingPath && m_pRoute && !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_JustDoneClimbResponse))
{
const s32 iProgress = m_iProgress + iProgressAhead;
if(iProgress > 0 && iProgress < m_pRoute->GetSize())
{
vOutStartPos = m_pRoute->GetPosition(iProgress-1);
vOutEndPos = m_pRoute->GetPosition(iProgress);
return true;
}
else if(iProgress+1 < m_pRoute->GetSize())
{
vOutStartPos = m_pRoute->GetPosition(iProgress);
vOutEndPos = m_pRoute->GetPosition(iProgress+1);
return true;
}
}
return false;
}
bool CTaskNavBase::GetPreviousWaypoint(Vector3 & vPos) const
{
if(GetState()==NavBaseState_FollowingPath && m_pRoute && !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_JustDoneClimbResponse))
{
if(m_iProgress > 0 && m_iProgress < m_pRoute->GetSize())
{
vPos = m_pRoute->GetPosition(m_iProgress - 1);
return true;
}
}
return false;
}
bool CTaskNavBase::GetCurrentWaypoint(Vector3 & vPos) const
{
if(GetState()==NavBaseState_FollowingPath && m_pRoute && !m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_JustDoneClimbResponse))
{
if(m_iProgress >= 0 && m_iProgress < m_pRoute->GetSize())
{
vPos = m_pRoute->GetPosition(m_iProgress);
return true;
}
}
return false;
}
// NAME : GetPathIntersectsRegion
// PUPOSE : If we have a route, returns whether it intersects the AABB
// Disregards spline corners - should this be modified to test the expanded route?
bool CTaskNavBase::GetPathIntersectsRegion(const Vector3 & vMin, const Vector3 & vMax)
{
if(m_pRoute)
{
int i;
for(i=1; i<m_pRoute->GetSize(); i++)
{
const Vector3 & v1 = m_pRoute->GetPosition(i-1);
const Vector3 & v2 = m_pRoute->GetPosition(i);
// Either endpoint inside ?
if( v1.IsGreaterThan(vMin) && vMax.IsGreaterThan(v1) )
return true;
if( v2.IsGreaterThan(vMin) && vMax.IsGreaterThan(v2) )
return true;
// Segment intersects ?
int iHits = geomBoxes::TestSegmentToBoxMinMax(v1, v2, vMin, vMax);
if(iHits > 0)
return true;
}
}
return false;
}
// NAME : TestSpheresIntersectingRoute
// PURPOSE : Test an array of up to 32 spheres for intersection with the route
u32 CTaskNavBase::TestSpheresIntersectingRoute( s32 iNumSpheres, const spdSphere * ppSpheres, const bool bOnlyAheadOfCurrentPosition ) const
{
Assertf(iNumSpheres < 32, "Maximum num spheres is 32");
iNumSpheres = Min(iNumSpheres, 32);
s32 i,s;
ScalarV radiiSqr[32];
for(s=0; s<iNumSpheres; s++)
radiiSqr[s] = ppSpheres[s].GetRadiusSquared();
u32 iResults = 0;
s32 iNumIntersections = 0;
if(m_pRoute && m_pRoute->GetSize())
{
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, 0, m_pRoute->GetSize());
if(m_iClosestSegOnExpandedRoute >= ms_iNumExpandedRouteVerts - 1)
{
return iResults;
}
const s32 iStartSeg = bOnlyAheadOfCurrentPosition ? m_iClosestSegOnExpandedRoute : 0;
ScalarV fPedT;
if(bOnlyAheadOfCurrentPosition)
{
Vec3V vVec = ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute+1].GetPos() - ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].GetPos();
fPedT = geomTValues::FindTValueSegToPoint( ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].GetPos(), vVec, GetPed()->GetTransform().GetPosition() );
}
const s32 iMaxVerts=ms_iNumExpandedRouteVerts-1;
for(i=iStartSeg; i<iMaxVerts && iNumIntersections<iNumSpheres; i++)
{
if( !IsSpecialActionClimbOrDrop(ms_vExpandedRouteVerts[i].m_iWptFlag.GetSpecialActionFlags()) )
{
Vec3V vVec = ms_vExpandedRouteVerts[i+1].GetPos() - ms_vExpandedRouteVerts[i].GetPos();
for(s=0; s<iNumSpheres; s++)
{
if( (iResults & (1<<s))==0 ) // Only if we've not already established intersection for this sphere
{
ScalarV T = geomTValues::FindTValueSegToPoint( ms_vExpandedRouteVerts[i].GetPos(), vVec, ppSpheres[s].GetCenter() );
Vec3V vPos = ms_vExpandedRouteVerts[i].GetPos() + Scale(vVec, T);
Vec3V vDiff = vPos - ppSpheres[s].GetCenter();
if( (MagSquared( vDiff ) < radiiSqr[s]).Getb() )
{
// If only checking ahead on the route, and this is the ped's segment, then compare T vals
if( bOnlyAheadOfCurrentPosition && (i==iStartSeg) && (T < fPedT).Getb() )
continue;
iResults |= (1<<s);
iNumIntersections++;
}
}
}
}
}
}
return iResults;
}
bool CTaskNavBase::GetPositionAlongRouteWithDistanceFromTarget(Vec3V_InOut vReturnPosition, Vec3V_In vTargetPosition, float &fDesiredDistanceFromTarget)
{
if(m_pRoute && m_pRoute->GetSize())
{
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, 0, m_pRoute->GetSize());
ScalarV scDesiredDistSq = ScalarVFromF32(square(fDesiredDistanceFromTarget));
for(int i = 0; i < ms_iNumExpandedRouteVerts; i++)
{
// Check if this points position is far enough away from the target position
Vec3V vPoint2 = ms_vExpandedRouteVerts[i].GetPos();
ScalarV scDistToTargetSq = DistSquared(vPoint2, vTargetPosition);
if(IsGreaterThanAll(scDistToTargetSq, scDesiredDistSq))
{
// If this is the first point then we don't have a line to lerp on, so just return this point's position
if(i == 0)
{
vReturnPosition = ms_vExpandedRouteVerts[i].GetPos();
}
else
{
// Otherwise, we need to find the point along the segment that is the desired distance away from the target
Vec3V vPoint1 = ms_vExpandedRouteVerts[i - 1].GetPos();
Vec3V vToTarget = vTargetPosition - vPoint1;
Vec3V vSegmentDirection = vPoint2 - vPoint1;
ScalarV vT = Dot(vToTarget, vSegmentDirection) / Dot(vSegmentDirection, vSegmentDirection);
Vec3V vClosestPointOnSegment = vPoint1 + (vT * vSegmentDirection);
Vec3V vTargetToClosestPoint = vClosestPointOnSegment - vTargetPosition;
ScalarV vClosestToDesiredLength = SqrtFast(Dot(vTargetToClosestPoint, vTargetToClosestPoint) + scDesiredDistSq);
ScalarV vLengthT = vClosestToDesiredLength / Mag(vSegmentDirection);
vReturnPosition = vPoint1 + (vSegmentDirection * (vT + vLengthT));
}
return true;
}
}
}
return false;
}
bool CTaskNavBase::GetPositionAheadOnRoute(const CPed * pPed, const float fDistanceAhead, Vector3 & vOutPos, const bool bHaltAtClimbsOrDrops)
{
if(m_pRoute && m_pRoute->GetSize())
{
const Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, Max(0, m_iProgress - 1), m_pRoute->GetSize(), fDistanceAhead);
// Since we've expanded the route again we need to find the closest segment
if(ms_iNumExpandedRouteVerts)
m_iClosestSegOnExpandedRoute = (s16) CalculateClosestSegmentOnExpandedRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, vPedPos, m_fSplineTVal, NULL, NULL);
else
m_iClosestSegOnExpandedRoute = 0;
// Advance to minimum 'm_iClampMinProgress'
while(ms_vExpandedRouteVerts[m_iClosestSegOnExpandedRoute].m_iProgress < m_iClampMinProgress && m_iClosestSegOnExpandedRoute < ms_iNumExpandedRouteVerts-1)
m_iClosestSegOnExpandedRoute++;
if (m_iClosestSegOnExpandedRoute >= ms_iNumExpandedRouteVerts-1)
return false;
ScalarV distanceRemainingV;
GetPositionAheadOnRoute(ms_vExpandedRouteVerts, ms_iNumExpandedRouteVerts, VECTOR3_TO_VEC3V(vPedPos), ScalarV(fDistanceAhead), RC_VEC3V(vOutPos), distanceRemainingV, NULL, bHaltAtClimbsOrDrops, bHaltAtClimbsOrDrops);
return true;
}
return false;
}
u32 CTaskNavBase::GetPositionAheadOnRoute(const CNavMeshExpandedRouteVert * pPoints, const int iNumPts, Vec3V_In inputPos, ScalarV_In distanceAhead,
Vec3V_Ref outPos, ScalarV_Ref outDistanceRemainingV, ScalarV* outLengthOfFinalSegment, const bool bHaltAtClimbs, const bool bHaltAtDrops, ScalarV* pOutDistToClimb, ScalarV* pOutDistToDrop) const
{
Assert(iNumPts);
Assertf(m_iClosestSegOnExpandedRoute < iNumPts, "m_iClosestSegOnExpandedRoute = %i, iNumPts = %i", m_iClosestSegOnExpandedRoute, iNumPts);
Assert(rage::FPIsFinite(inputPos.GetXf()) && rage::FPIsFinite(inputPos.GetYf()) && rage::FPIsFinite(inputPos.GetZf()));
const ScalarV zeroV(V_ZERO);
if(IsEqualAll(distanceAhead, zeroV))
{
outDistanceRemainingV = zeroV;
outPos = inputPos;
Assert(rage::FPIsFinite(outPos.GetXf()) && rage::FPIsFinite(outPos.GetYf()) && rage::FPIsFinite(outPos.GetZf()));
return 0;
}
const s32 iLastIndex = iNumPts-1;
s32 iSeg = m_iClosestSegOnExpandedRoute;
// Find the position of the input position along the route
Vec3V currV = pPoints[iSeg].GetPos();
Vec3V nextV = pPoints[iSeg+1].GetPos();
Vec3V vecV = Subtract(nextV, currV);
const ScalarV tV = geomTValues::FindTValueSegToPoint(currV, vecV, inputPos);
Assert(rage::FPIsFinite(tV.Getf()));
currV = AddScaled(currV, vecV, tV);
vecV = Subtract(nextV, currV);
bool bFoundClimbWpt = false;
bool bFoundDropWpt = false;
ScalarV distLeftV = distanceAhead;
ScalarV distToCheckForNarrow = LoadScalar32IntoScalarV(GetGotoTargetLookAheadDistance() + GetLookAheadExtra());
ScalarV distanceRemainingV = zeroV;
u32 iRouteFlags = 0;
while(IsGreaterThanAll(distLeftV, zeroV))
{
const ScalarV segLenV = Mag(vecV);
Assert(rage::FPIsFinite(segLenV.Getf()));
if(IsGreaterThanOrEqualAll(segLenV, distLeftV))
{
// Note: segLenV must be >0, because we know distLeftV > 0 and segLenV >= distLeftV,
// so should be OK to divide.
// Note 2: we use InvScaleFast() here, which is a slight approximation, but when comparing
// the output computed with the output compared with a full division, the difference is really
// small, didn't see a 0.01 m tolerance assert fail during testing.
const ScalarV lerpTApproxV = InvScaleFast(distLeftV, segLenV);
const Vec3V outPosV = AddScaled(currV, vecV, lerpTApproxV);
Assert(rage::FPIsFinite(lerpTApproxV.Getf()));
distanceRemainingV = Add(distanceRemainingV, distLeftV);
outPos = outPosV;
outDistanceRemainingV = distanceRemainingV;
Assert(rage::FPIsFinite(outPos.GetXf()) && rage::FPIsFinite(outPos.GetYf()) && rage::FPIsFinite(outPos.GetZf()));
return iRouteFlags;
}
distLeftV = Subtract(distLeftV, segLenV);
distanceRemainingV = Add(distanceRemainingV, segLenV);
iSeg++;
u32 iRetFlags = 0;
if(iSeg == iLastIndex)
iRetFlags |= GPAFlag_HitRouteEnd;
const CNavMeshExpandedRouteVert& pt = pPoints[iSeg];
const bool bIsClimb = pt.m_iWptFlag.IsClimb();
const bool bIsDrop = pt.m_iWptFlag.IsDrop();
const bool bIsLadder = pt.m_iWptFlag.IsLadderClimb();
if (bIsClimb || bIsDrop || bIsLadder)
{
if(bIsLadder) // We always consider ladder climbs for now
{
iRetFlags |= GPAFlag_HitWaypointFlag;
iRetFlags |= GPSFlag_HitWaypointFlagLadder; // For special stops
}
else if( bHaltAtClimbs && bIsClimb )
iRetFlags |= GPAFlag_HitWaypointFlag;
else if( bHaltAtDrops && bIsDrop )
iRetFlags |= GPAFlag_HitWaypointFlag;
// So we can turn off navmesh edge avoidance
// Ladders are always reported even if this function is not to halt at them
// Edge avoidance is turned off for approaching ladders elsewhere anyway
if(bIsClimb || bIsDrop)
iRouteFlags |= GPAFlag_UpcomingClimbOrDrop;
if((bIsClimb || bIsLadder) && !bFoundClimbWpt && pOutDistToClimb)
{
*pOutDistToClimb = distanceRemainingV;
bFoundClimbWpt = true;
}
if(bIsDrop && !bFoundDropWpt && pOutDistToDrop)
{
*pOutDistToDrop = distanceRemainingV;
bFoundDropWpt = true;
}
}
if (pt.m_iWptFlag.IsNarrow() && IsGreaterThanOrEqualAll(distToCheckForNarrow, distanceRemainingV))
iRouteFlags = GPAFlag_HitWaypointFlagPullOut;
if((iRetFlags & GPAFlag_MaskForRouteAhead) != 0)
{
// We've reached the end of the route, or encountered a waypoint which we can't progress past
if(iSeg == m_iClosestSegOnExpandedRoute + 1)
{
outPos = nextV;
if(outLengthOfFinalSegment) // Note: in active code, nothing actually uses this - if that remains the case we should remove this code.
{
const ScalarV lengthOfFinalSegmentV = Dist(pPoints[iSeg-1].GetPos(), nextV);
*outLengthOfFinalSegment = lengthOfFinalSegmentV;
}
}
else
{
outPos = nextV;
if(outLengthOfFinalSegment)
{
*outLengthOfFinalSegment = segLenV;
}
}
outDistanceRemainingV = distanceRemainingV;
return iRetFlags | iRouteFlags;
}
currV = nextV;
nextV = pPoints[iSeg+1].GetPos();
vecV = Subtract(nextV, currV);
}
outPos = pPoints[iLastIndex].GetPos();
outDistanceRemainingV = distanceRemainingV;
Assert(rage::FPIsFinite(outPos.GetXf()) && rage::FPIsFinite(outPos.GetYf()) && rage::FPIsFinite(outPos.GetZf()));
return GPAFlag_HitRouteEnd | iRouteFlags;
}
bool CTaskNavBase::ProcessCornering(CPed * pPed, Vec3V_In vPosAhead, const float fLookAheadTime, const CMoveBlendRatioSpeeds& moveSpeeds, const CMoveBlendRatioTurnRates& turnRates)
{
// Where this function is called from, we have already tested this:
Assert(!pPed->IsStrafing());
CTask* pSubTask = GetSubTask();
if(!pSubTask || !m_pRoute || pPed->GetPedResetFlag( CPED_RESET_FLAG_SuppressSlowingForCorners ) )
return false;
// Shouldn't have to check this, I think, I don't think it's really valid for a move task to have
// anything else than a move task as a subtask, is it?
Assert(pSubTask->IsMoveTask());
float fNewBMR = CTaskMoveGoToPoint::CalculateMoveBlendRatioForCorner(pPed, vPosAhead, fLookAheadTime, pPed->GetMotionData()->GetCurrentMbrY(), m_fMoveBlendRatio, CTaskMoveGoToPoint::ms_fAccelMBR, GetTimeStep(), moveSpeeds, turnRates);
if (pPed->GetPedResetFlag(CPED_RESET_FLAG_MoveBlend_bFleeTaskRunning) && m_fMoveBlendRatio >= MBR_RUN_BOUNDARY)
fNewBMR = Max(fNewBMR, MBR_RUN_BOUNDARY); // Enforce running for fleeing peds even in very sharp corners
pSubTask->PropagateNewMoveSpeed(fNewBMR);
return true;
}
bool CTaskNavBase::RetransformRouteOnDynamicNavmesh(const CPed * pPed)
{
if(!m_pOnEntityRouteData || !m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn || !m_pRoute || !m_pOnEntityRouteData->m_pLocalSpaceRoute)
return false;
AI_OPTIMISATIONS_OFF_ONLY( (m_pRoute->GetSize() == m_pOnEntityRouteData->m_pLocalSpaceRoute->GetSize()); )
if(m_pRoute->GetSize() != m_pOnEntityRouteData->m_pLocalSpaceRoute->GetSize())
return false;
// Transform all the route elements in m_pPointRoute
const Matrix34 mat = MAT34V_TO_MATRIX34(m_pOnEntityRouteData->m_pDynamicEntityRouteIsOn->GetMatrix());
for(int p=0; p<m_pOnEntityRouteData->m_pLocalSpaceRoute->GetSize(); p++)
{
Vector3 vTransformed;
mat.Transform(m_pOnEntityRouteData->m_pLocalSpaceRoute->GetPosition(p), vTransformed);
m_pRoute->Set(p, vTransformed, m_pOnEntityRouteData->m_pLocalSpaceRoute->GetWaypoint(p) );
}
if(GetState()!=NavBaseState_FollowingPath)
return false;
Assert(m_iProgress >= 0 && m_iProgress < m_pRoute->GetSize());
const Vector3 & vGoToTarget = m_pRoute->GetPosition(m_iProgress);
if(GetSubTask())
{
Assert(GetSubTask()->IsMoveTask());
((CTaskMove*)GetSubTask())->SetTarget(pPed, VECTOR3_TO_VEC3V(vGoToTarget));
}
return true;
}
bool CTaskNavBase::IsExitingScenario(Vec3V_InOut vExitPosition) const
{
//Grab the ped.
const CPed* pPed = GetPed();
//Ensure the ped is exiting the vehicle.
CTaskUseScenario* pTask = static_cast<CTaskUseScenario *>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_USE_SCENARIO));
if(!pTask || !pTask->IsExitingAndHasANewPosition())
{
return false;
}
//Set the exit position.
pTask->GetExitPosition(RC_VECTOR3(vExitPosition));
return true;
}
bool CTaskNavBase::IsExitingVehicle(Vec3V_InOut vExitPosition) const
{
//Grab the ped.
const CPed* pPed = GetPed();
//Ensure the ped is exiting the vehicle.
CTaskExitVehicle* pTask = static_cast<CTaskExitVehicle *>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE));
if(!pTask || !pTask->HasValidExitPosition())
{
return false;
}
//Set the exit position.
vExitPosition = pTask->GetExitPosition();
// If this is a plane, then push out the exit position to the edge of the bounding box in the +/- X direction
// If we don't do this, then the path start will be resolved outside of the bbox of the vehicle by the pathfinder,
// in the closest direction to any edge (and this might be directly forwards, through the propellers)
if( pTask->GetVehicle() && pTask->GetVehicle()->InheritsFromPlane() )
{
spdAABB tempBox;
const spdAABB &bbox = pTask->GetVehicle()->GetBoundBox(tempBox);
const float fBoxHalfWidth = (bbox.GetMax().GetXf() - bbox.GetMin().GetXf()) * 0.5f;
Matrix34 mat = MAT34V_TO_MATRIX34(pTask->GetVehicle()->GetTransform().GetMatrix());
float fDistX = DotProduct( mat.a, VEC3V_TO_VECTOR3(vExitPosition) - mat.d );
float fDelta = fBoxHalfWidth - Abs(fDistX);
if( fDelta > 0.0f )
{
vExitPosition += VECTOR3_TO_VEC3V(mat.a * Sign(fDistX) * fDelta);
}
}
return true;
}
bool CTaskNavBase::ShouldWaitInVehicle() const
{
//Grab the ped.
const CPed* pPed = GetPed();
//Ensure the ped is in a vehicle.
const CVehicle* pVehicle = pPed->GetVehiclePedInside();
if(!pVehicle)
{
return false;
}
//Check if the ped is exiting the vehicle.
CTaskExitVehicle* pTask = static_cast<CTaskExitVehicle *>(pPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE));
if(pTask)
{
//Debug: allow ped to leave vehicle earlier, as soon as we have chosen an exit door
static dev_bool bForceDoorsOpenForNavigation = true;
if(bForceDoorsOpenForNavigation)
{
return false;
}
//Check if the door is (mostly) open.
static float s_fMinRatio = 0.8f;
if(pTask->IsDoorOpen(s_fMinRatio))
{
return false;
}
}
return true;
}
void CTaskNavBase::CalcTimeNeededToCompleteRouteSegment(const CPed * pPed, const s32 iProgress)
{
//************************************************************************
// Set a maximum time that the ped should spend getting to their target
CMoveBlendRatioSpeeds moveSpeeds;
pPed->GetCurrentMotionTask()->GetMoveSpeeds(moveSpeeds);
const float fMoveSpeed = CTaskMotionBase::GetActualMoveSpeed(moveSpeeds.GetSpeedsArray(), Clamp(m_fInitialMoveBlendRatio, MOVEBLENDRATIO_STILL, MOVEBLENDRATIO_SPRINT));
static const float fTimeToGetToWaypointMult = 1.5f; // Multiplier to the actual time to get to this waypoint
static const int iMinRouteSegTimeMs = 8000; // be lenient & allow 8secs for all route-segments before retrying
m_iTimeSpentOnCurrentRouteSegmentMs = 0;
if(iProgress < m_pRoute->GetSize())
{
const float fDistToTarget = (m_pRoute->GetPosition(iProgress) - VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition())).Mag();
const float fTimeToTarget = fDistToTarget / fMoveSpeed;
m_iTimeShouldSpendOnCurrentRouteSegmentMs = (u16) MIN(65535, (int)(fTimeToTarget * fTimeToGetToWaypointMult * 1000.0f));
m_iTimeShouldSpendOnCurrentRouteSegmentMs = MAX(m_iTimeShouldSpendOnCurrentRouteSegmentMs, iMinRouteSegTimeMs);
}
else
{
m_iTimeShouldSpendOnCurrentRouteSegmentMs = iMinRouteSegTimeMs;
}
}
CTask::FSM_Return CTaskNavBase::OnFailedRoute(CPed * UNUSED_PARAM(pPed), const TPathResultInfo & UNUSED_PARAM(pathResultInfo))
{
return FSM_Quit;
}
bool CTaskNavBase::IsPedMoving(CPed * pPed) const
{
if (pPed->GetMotionData()->GetCurrentMbrY() > MBR_WALK_BOUNDARY)
return true;
dev_float KeepMovingSpeedThresholdSqr = square(0.25f);
if (pPed->GetVelocity().XYMag2() > KeepMovingSpeedThresholdSqr)
return true;
switch(pPed->GetMotionData()->GetForcedMotionStateThisFrame())
{
case CPedMotionStates::MotionState_Walk:
case CPedMotionStates::MotionState_Run:
case CPedMotionStates::MotionState_Sprint:
case CPedMotionStates::MotionState_Crouch_Walk:
case CPedMotionStates::MotionState_Crouch_Run:
case CPedMotionStates::MotionState_Stealth_Walk:
case CPedMotionStates::MotionState_Stealth_Run:
case CPedMotionStates::MotionState_ActionMode_Walk:
case CPedMotionStates::MotionState_ActionMode_Run:
return true;
}
return false;
}
bool CTaskNavBase::ShouldKeepMovingWhilstWaitingForPath(CPed * pPed, bool & bMayUsePreviousTarget) const
{
bMayUsePreviousTarget =
m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_AbortedForDoorCollision)==false &&
m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_AbortedForCollisionEvent)==false &&
m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_ExceededVerticalDistanceFromPath)==false;
if (!IsPedMoving(pPed))
return false;
if(m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_AbortedForDoorCollision) ||
m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_AbortedForCollisionEvent) ||
m_NavBaseInternalFlags.IsSet(CNavBaseInternalFlags::NB_ExceededVerticalDistanceFromPath))
return true;
// If we have a periodic recalc frequency, and it has just expired
// and this is not our first path - then continue moving whilst we calculate it
if(m_iRecalcPathFreqMs != 0 && m_iRecalcPathTimerMs==m_iRecalcPathFreqMs && m_iLastPathHandle!=PATH_HANDLE_NULL)
return true;
return(m_NavBaseConfigFlags.IsSet(CNavBaseConfigFlags::NB_KeepMovingWhilstWaitingForPath));
}
#if !__FINAL
const char * CTaskNavBase::GetBaseStateName(s32 iState)
{
switch(iState)
{
case NavBaseState_Initial: return "Initial";
case NavBaseState_WaitingInVehiclePriorToRequest: return "WaitingInVehiclePriorToRequest";
case NavBaseState_RequestPath: return "RequestPath";
case NavBaseState_MovingWhilstWaitingForPath: return "MovingWhilstWaitingForPath";
case NavBaseState_StationaryWhilstWaitingForPath: return "StationaryWhilstWaitingForPath";
case NavBaseState_WaitingToLeaveVehicle: return "WaitingToLeaveVehicle";
case NavBaseState_WaitingAfterFailureToObtainPath : return "WaitingAfterFailureToObtainPath";
case NavBaseState_FollowingPath: return "FollowingPath";
case NavBaseState_SlideToCoord: return "SlideToCoord";
case NavBaseState_StoppingAtTarget: return "StoppingAtTarget";
case NavBaseState_WaitingForWaypointActionEvent: return "WaitingForWaypointActionEvent";
case NavBaseState_WaitingAfterFailedWaypointAction: return "WaitingAfterFailedWaypointAction";
case NavBaseState_GetOntoMainNavMesh: return "GetOntoMainNavMesh";
case NavBaseState_AchieveHeading: return "AchieveHeading";
case NavBaseState_UsingLadder: return "UsingLadder";
default: return NULL;
}
}
#endif //!__FINAL
#if !__FINAL && DEBUG_DRAW
const char * CTaskNavBase::GetInfoForWaypoint(const TNavMeshWaypointFlag & wpt, Color32 & col) const
{
const char * pDesc = NULL;
u16 iFlags = wpt.m_iSpecialActionFlags;
static const u16 iMask = (WAYPOINT_FLAG_CLIMB_HIGH|WAYPOINT_FLAG_CLIMB_LOW|WAYPOINT_FLAG_DROPDOWN|WAYPOINT_FLAG_OPEN_DOOR|WAYPOINT_FLAG_CLIMB_LADDER|WAYPOINT_FLAG_DESCEND_LADDER|WAYPOINT_FLAG_CLIMB_OBJECT|WAYPOINT_FLAG_ADJUSTED_TARGET_POS|WAYPOINT_FLAG_HIERARCHICAL_PATHFIND);
iFlags &= iMask;
if(!iFlags)
{
col = Color_grey;
pDesc = NULL; // NULL string indicates a regular wpt
}
else if(iFlags&WAYPOINT_FLAG_CLIMB_HIGH)
{
col = Color_SpringGreen1;
pDesc = "HighClimb";
}
else if(iFlags&WAYPOINT_FLAG_CLIMB_LOW)
{
col = Color_SpringGreen2;
pDesc = "LowClimb";
}
else if(iFlags&WAYPOINT_FLAG_DROPDOWN)
{
col = Color_SpringGreen3;
pDesc = "DropDown";
}
else if(iFlags&WAYPOINT_FLAG_OPEN_DOOR)
{
col = Color_yellow;
pDesc = "OpenDoor";
}
else if(iFlags&WAYPOINT_FLAG_CLIMB_LADDER)
{
col = Color_OrangeRed1;
pDesc = "ClimbLadder";
}
else if(iFlags&WAYPOINT_FLAG_DESCEND_LADDER)
{
col = Color_OrangeRed2;
pDesc = "DescendLadder";
}
else if(iFlags&WAYPOINT_FLAG_CLIMB_OBJECT)
{
col = Color_OrangeRed3;
pDesc = "ClimbObject";
}
else if(iFlags&WAYPOINT_FLAG_ADJUSTED_TARGET_POS)
{
col = Color_grey50;
pDesc = "MovedTarget";
}
else if(iFlags&WAYPOINT_FLAG_HIERARCHICAL_PATHFIND)
{
col = Color_MediumBlue;
pDesc = "Hierarchical";
}
return pDesc;
}
void CTaskNavBase::DrawPedText(const CPed * pPed, const Vector3 & vWorldCoords, const Color32 iTxtCol, int & iOffsetY) const
{
char taskText[256];
//****************
// Path handle
sprintf(taskText, "hPath: 0x%x", m_iPathHandle);
grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
iOffsetY+=grcDebugDraw::GetScreenSpaceTextHeight();
sprintf(taskText, "hLastPath: 0x%x", m_iLastPathHandle);
grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
iOffsetY+=grcDebugDraw::GetScreenSpaceTextHeight();
//***************************
// Progress
if(m_pRoute && m_pRoute->GetSize())
{
sprintf(taskText, "Progress: %i/%i", m_iProgress, m_pRoute->GetSize());
grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
iOffsetY+=grcDebugDraw::GetScreenSpaceTextHeight();
sprintf(taskText, "fSplineProgress: %f", m_fSplineTVal);
grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
iOffsetY+=grcDebugDraw::GetScreenSpaceTextHeight();
}
//**********************************
// Time spent on this route segment
sprintf(taskText, "Time: %i/%i", m_iTimeSpentOnCurrentRouteSegmentMs, m_iTimeShouldSpendOnCurrentRouteSegmentMs);
grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
iOffsetY+=grcDebugDraw::GetScreenSpaceTextHeight();
sprintf(taskText, "Free space metric: %.2f", pPed->GetNavMeshTracker().GetFreeSpaceMetric());
grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
iOffsetY+=grcDebugDraw::GetScreenSpaceTextHeight();
//grcDebugDraw::Text( vWorldCoords, iTxtCol, 0, iOffsetY, taskText);
//***************************
// CTaskNavBase Flags
//draw flags here
}
void CTaskNavBase::DebugVisualiserDisplay(const CPed * pPed, const Vector3 & vWorldCoords, s32 iOffsetY) const
{
int p;
static const Vector3 vMarkerTop(0.0f,0.0f,2.0f);
static const float fMarkerTopRadius = 0.1f;
static const float fWptTxtRange = 30.0f;
static const float fTxtRange = 30.0f;
#if __DEV
camDebugDirector& debugDirector = camInterface::GetDebugDirector();
Vector3 vOrigin = debugDirector.IsFreeCamActive() ? debugDirector.GetFreeCamFrame().GetPosition() : VEC3V_TO_VECTOR3(FindPlayerPed()->GetTransform().GetPosition());
#else
Vector3 vOrigin = VEC3V_TO_VECTOR3(FindPlayerPed()->GetTransform().GetPosition());
#endif
//*********************************************************************************
// Draw the ped's route points. We can draw the route markers for each point,
// with some colour-coded info (or text) to describe the waypoint type.
if(m_pRoute)
{
for(p=0; p<m_pRoute->GetSize(); p++)
{
Vector3 vRoutePt = m_pRoute->GetPosition(p);
Vector3 vRoutePtTop = vRoutePt + vMarkerTop;
// Remove the extra height which we'd added on when the path was created
vRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
const Vector3 vDiff = vRoutePt - vOrigin;
const float fDist = vDiff.Mag();
const float fScale = 1.0f - (fDist / fWptTxtRange);
const u8 iAlpha = (u8)Clamp((int)(255.0f * fScale), 0, 255);
const TNavMeshWaypointFlag & iWpt = m_pRoute->GetWaypoint(p);
Color32 iWptCol;
const char * pWptDesc = GetInfoForWaypoint(iWpt, iWptCol);
if(pWptDesc)
{
grcDebugDraw::Line(vRoutePt, vRoutePtTop, Color_LightCyan);
grcDebugDraw::Sphere(vRoutePtTop, fMarkerTopRadius, iWptCol, true);
Vector3 vTextPos = vRoutePtTop;
if(fDist < fWptTxtRange)
{
if(pWptDesc)
{
iWptCol.SetAlpha(iAlpha);
grcDebugDraw::Text( vTextPos, iWptCol, pWptDesc);
}
vTextPos.y += grcDebugDraw::GetScreenSpaceTextHeight();
int iSpace = m_pRoute->GetFreeSpaceApproachingWaypoint(p);
if(iSpace > 0)
{
iWptCol = Color_OrangeRed3; iWptCol.SetAlpha(iAlpha);
static char tmp[32];
sprintf(tmp, "space approaching : %i", iSpace);
grcDebugDraw::Text(vTextPos, iWptCol, tmp);
}
}
}
}
}
//***********************************************************************
// Draw a ped-width strip following the route at the base height.
Color32 iRouteCol = Color_PaleGreen;
// If this route is being used to return to another route, then draw in amber
if(GetParent() && GetParent()->GetTaskType()==CTaskTypes::TASK_MOVE_RETURN_TO_ROUTE)
iRouteCol = Color_OrangeRed1;
if(m_pRoute)
{
float fPedRadius = pPed->GetCapsuleInfo()->GetHalfWidth();
#if __BANK
if(ms_fOverrideRadius != PATHSERVER_PED_RADIUS)
fPedRadius = ms_fOverrideRadius;
#endif
Vector3 vTriVerts[3];
//-----------------------------------------------
// Render non-splined linear route in low alpha
iRouteCol.SetAlpha(64);
for(p=0; p<m_pRoute->GetSize()-1; p++)
{
Vector3 vRoutePt = m_pRoute->GetPosition(p);
vRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
Vector3 vNextRoutePt = m_pRoute->GetPosition(p+1);
vNextRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
Vector3 vVecToNext = vNextRoutePt - vRoutePt;
vVecToNext.Normalize();
Vector3 vRight = CrossProduct(vVecToNext, Vector3(0.0f,0.0f,1.0f));
vTriVerts[0] = vRoutePt + (vRight * fPedRadius);
vTriVerts[1] = vRoutePt - (vRight * fPedRadius);
vTriVerts[2] = vNextRoutePt + (vRight * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
vTriVerts[0] = vRoutePt - (vRight * fPedRadius);
vTriVerts[1] = vNextRoutePt - (vRight * fPedRadius);
vTriVerts[2] = vNextRoutePt + (vRight * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
// If there's another point after vNextRoutePt, then see if we can make some
// bevel tris so that the path looks a bit nicer.
if(p < m_pRoute->GetSize()-2)
{
Vector3 vNextNextRoutePt = m_pRoute->GetPosition(p+2);
vNextNextRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
Vector3 vVecToNextNext = vNextNextRoutePt - vNextRoutePt;
vVecToNextNext.Normalize();
Vector3 vRightNext = CrossProduct(vVecToNextNext, Vector3(0.0f,0.0f,1.0f));
vTriVerts[0] = vNextRoutePt + (vRight * fPedRadius);
vTriVerts[1] = vNextRoutePt;
vTriVerts[2] = vNextRoutePt + (vRightNext * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
vTriVerts[0] = vNextRoutePt - (vRight * fPedRadius);
vTriVerts[1] = vNextRoutePt;
vTriVerts[2] = vNextRoutePt - (vRightNext * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
}
}
//----------------------------------------------------------
// Render the expanded route using splines, in full opacity
iRouteCol.SetAlpha(255);
static bool s_StartAtCurrentProgress = false;
int startSegment = 0;
if ( s_StartAtCurrentProgress )
{
startSegment = Max(0, m_iProgress-1);
}
ms_iNumExpandedRouteVerts = m_pRoute->ExpandRoute(ms_vExpandedRouteVerts, NULL, startSegment, m_pRoute->GetSize());
for(p=0; p<ms_iNumExpandedRouteVerts-1; p++)
{
Vector3 vRoutePt = VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[p].GetPos());
vRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
Vector3 vNextRoutePt = VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[p+1].GetPos());
vNextRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
Vector3 vVecToNext = vNextRoutePt - vRoutePt;
vVecToNext.Normalize();
Vector3 vRight = CrossProduct(vVecToNext, Vector3(0.0f,0.0f,1.0f));
vTriVerts[0] = vRoutePt + (vRight * fPedRadius);
vTriVerts[1] = vRoutePt - (vRight * fPedRadius);
vTriVerts[2] = vNextRoutePt + (vRight * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
vTriVerts[0] = vRoutePt - (vRight * fPedRadius);
vTriVerts[1] = vNextRoutePt - (vRight * fPedRadius);
vTriVerts[2] = vNextRoutePt + (vRight * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
// If there's another point after vNextRoutePt, then see if we can make some
// bevel tris so that the path looks a bit nicer.
if(p < ms_iNumExpandedRouteVerts-2)
{
Vector3 vNextNextRoutePt = VEC3V_TO_VECTOR3(ms_vExpandedRouteVerts[p+2].GetPos());
vNextNextRoutePt.z -= ms_fHeightAboveReturnedPointsForRouteVerts;
Vector3 vVecToNextNext = vNextNextRoutePt - vNextRoutePt;
vVecToNextNext.Normalize();
Vector3 vRightNext = CrossProduct(vVecToNextNext, Vector3(0.0f,0.0f,1.0f));
vTriVerts[0] = vNextRoutePt + (vRight * fPedRadius);
vTriVerts[1] = vNextRoutePt;
vTriVerts[2] = vNextRoutePt + (vRightNext * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
vTriVerts[0] = vNextRoutePt - (vRight * fPedRadius);
vTriVerts[1] = vNextRoutePt;
vTriVerts[2] = vNextRoutePt - (vRightNext * fPedRadius);
grcDebugDraw::Poly(RCC_VEC3V(vTriVerts[2]), RCC_VEC3V(vTriVerts[1]), RCC_VEC3V(vTriVerts[0]), iRouteCol);
}
}
}
//***********************************************************************
// Draw a line between the startpos & targetpos
//****************************************************************************
// Draw a line from the ped to the closest point on the current route segment
//*****************************************************************************
// Draw some text output which allows us to investigate the state of the task
int iTextOffset = iOffsetY;
Vector3 vDiff = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()) - vOrigin;
float fDist = vDiff.Mag();
if(fDist < fTxtRange)
{
//*********************
// Set up font
float fScale = 1.0f - (fDist / fTxtRange);
fScale = Max(fScale, 0.5f);
u8 iAlpha = (u8)Clamp((int)(255.0f * fScale), 0, 255);
Color32 iTxtCol = Color_chartreuse;
iTxtCol.SetAlpha(iAlpha);
DrawPedText(pPed, vWorldCoords, iTxtCol, iTextOffset);
}
}
#endif
//----------------------------------------------------------------------------------------------------------
//********************************************************************************
// CTaskMoveGetOntoMainNavMesh
// Sometimes ped can get themselves trapped on small sections of navmesh with no
// connectivity to the main mesh. In these circumstances the ped will be unable
// to find a path & just stand still. This task is created as a subtask to the
// CTaskComplexMoveFollowNavMesg task, and is designed to identify if this has
// happened and attempt to fix the situation.
const float CTaskMoveGetOntoMainNavMesh::ms_fTargetRadius = 0.25f;
const float CTaskMoveGetOntoMainNavMesh::ms_fMaxAreaToAttempt = 64.0f;
CTaskMoveGetOntoMainNavMesh::CTaskMoveGetOntoMainNavMesh(const float fMoveBlendRatio, const Vector3 & vTarget, const int iRecoveryMode, const int iAttemptTime) :
CTaskMove(fMoveBlendRatio),
m_vNavMeshTarget(vTarget),
m_vTarget(vTarget),
m_iAttemptTime(iAttemptTime),
m_hPathHandle(PATH_HANDLE_NULL),
m_hCalcAreaUnderfootHandle(PATH_HANDLE_NULL),
m_bIsOnSmallAreaOfNavMesh(false),
m_iRecoveryMode(iRecoveryMode)
{
SetInternalTaskType(CTaskTypes::TASK_MOVE_GET_ONTO_MAIN_NAVMESH);
}
CTaskMoveGetOntoMainNavMesh::~CTaskMoveGetOntoMainNavMesh()
{
if(m_hPathHandle)
{
CPathServer::CancelRequest(m_hPathHandle);
m_hPathHandle = PATH_HANDLE_NULL;
}
if(m_hCalcAreaUnderfootHandle)
{
CPathServer::CancelRequest(m_hCalcAreaUnderfootHandle);
m_hCalcAreaUnderfootHandle = PATH_HANDLE_NULL;
}
}
#if !__FINAL
void CTaskMoveGetOntoMainNavMesh::Debug() const
{
if(GetSubTask())
GetSubTask()->Debug();
}
#endif
CTask::FSM_Return CTaskMoveGetOntoMainNavMesh::ProcessPreFSM()
{
CPed * pPed = GetPed();
if(pPed->GetNavMeshTracker().GetIsValid() && !pPed->GetNavMeshTracker().GetNavPolyData().m_bIsolated)
return FSM_Quit;
// Prevent ped entering low-lod physics whilst performing recovery task
pPed->GetPedAiLod().SetBlockedLodFlag( CPedAILod::AL_LodPhysics );
return FSM_Continue;
}
CTask::FSM_Return
CTaskMoveGetOntoMainNavMesh::UpdateFSM(const s32 iState, const FSM_Event iEvent)
{
CPed *pPed = GetPed(); //Get the ped ptr.
FSM_Begin
FSM_State(State_Initial)
FSM_OnUpdate
return Initial_OnUpdate(pPed);
FSM_State(State_CalculatingAreaUnderfoot)
FSM_OnUpdate
return CalculatingAreaUnderfoot_OnUpdate(pPed);
FSM_State(State_FindingReversePath)
FSM_OnUpdate
return FindingReversePath_OnUpdate(pPed);
FSM_State(State_MovingTowardsTarget)
FSM_OnUpdate
return MovingTowardsTarget_OnUpdate(pPed);
FSM_State(State_MovingToLastNonIsolatedPosition)
FSM_OnUpdate
return MovingToLastNonIsolatedPosition_OnUpdate(pPed);
FSM_End
}
CTask::FSM_Return
CTaskMoveGetOntoMainNavMesh::Initial_OnUpdate(CPed * pPed)
{
//*******************************************************************************
// Request a route in reverse, from the target position to the pPed's position
// with a fairly large completion radius. If this succeeds, then try just doing
// a goto point towards this target. The idea is that this will get us onto
// the navmesh at least, at which point a regular route can be followed.
Assert(m_hPathHandle==PATH_HANDLE_NULL);
if(m_hPathHandle)
{
CPathServer::CancelRequest(m_hPathHandle);
m_hPathHandle = PATH_HANDLE_NULL;
}
Assert(m_hCalcAreaUnderfootHandle==PATH_HANDLE_NULL);
if(m_hCalcAreaUnderfootHandle)
{
CPathServer::CancelRequest(m_hCalcAreaUnderfootHandle);
m_hCalcAreaUnderfootHandle = PATH_HANDLE_NULL;
}
// We can only attempt to goto our last safe position, if we have a valid position
// and if we're still a reasonable distance from it (lets say 10m)
Vector3 vPedPos = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition());
Vector3 vLastGoodPos = pPed->GetPedIntelligence()->GetLastMainNavMeshPosition();
static dev_float fMaxRecoveryDelta = 10.0f;
if( m_iRecoveryMode==RecoveryMode_GoToLastNonIsolatedPosition &&
(!vLastGoodPos.IsClose(VEC3_ZERO, 0.001f)) &&
(vPedPos - vLastGoodPos).Mag2() < fMaxRecoveryDelta*fMaxRecoveryDelta)
{
m_iRecoveryMode = RecoveryMode_ReversePathFind;
}
// Attempt to find a reverse path back to our position, and then move in that direction
if( m_iRecoveryMode == RecoveryMode_ReversePathFind )
{
m_hCalcAreaUnderfootHandle = CPathServer::RequestCalcAreaUnderfootSearch(VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), 30.0f, (void*)pPed);
SetState(State_CalculatingAreaUnderfoot);
SetNewTask( CreateSubTask(CTaskTypes::TASK_MOVE_STAND_STILL, pPed) );
SetFlag(aiTaskFlags::KeepCurrentSubtaskAcrossTransition);
}
// Move towards our last known non-isolated position
else
{
m_AttemptTimer.Set(fwTimer::GetTimeInMilliseconds(), m_iAttemptTime);
CTaskMoveGoToPointAndStandStill * pGotoTask = rage_new CTaskMoveGoToPointAndStandStill(m_fMoveBlendRatio, pPed->GetPedIntelligence()->GetLastMainNavMeshPosition(), ms_fTargetRadius);
// We don't care about the vertical height of the goto target
pGotoTask->SetTargetHeightDelta(1000.0f);
SetNewTask( pGotoTask );
SetState(State_MovingToLastNonIsolatedPosition);
SetFlag(aiTaskFlags::KeepCurrentSubtaskAcrossTransition);
}
return FSM_Continue;
}
CTask::FSM_Return
CTaskMoveGetOntoMainNavMesh::CalculatingAreaUnderfoot_OnUpdate(CPed * pPed)
{
Assert(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_STAND_STILL);
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
if(m_hCalcAreaUnderfootHandle==PATH_HANDLE_NULL)
return FSM_Quit;
const eReqResult ret = GetAreaRequestResult(pPed);
if(ret==EContinue && m_bIsOnSmallAreaOfNavMesh)
{
Assert(m_hPathHandle==PATH_HANDLE_NULL);
const u32 iFlags = PATH_FLAG_REDUCE_OBJECT_BBOXES;
m_hPathHandle = CPathServer::RequestPath(m_vNavMeshTarget, VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition()), iFlags, 8.0f, pPed);
SetState(State_FindingReversePath);
SetFlag(aiTaskFlags::KeepCurrentSubtaskAcrossTransition);
SetNewTask( CreateSubTask(CTaskTypes::TASK_MOVE_STAND_STILL, pPed) );
}
else if(ret==EStillWaiting)
{
SetNewTask( CreateSubTask(CTaskTypes::TASK_MOVE_STAND_STILL, pPed) );
}
else
{
return FSM_Quit;
}
}
return FSM_Continue;
}
CTask::FSM_Return
CTaskMoveGetOntoMainNavMesh::FindingReversePath_OnUpdate(CPed * pPed)
{
Assert(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_STAND_STILL);
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
Assert(m_hPathHandle!=PATH_HANDLE_NULL);
Assert(m_bIsOnSmallAreaOfNavMesh);
if(m_hPathHandle==PATH_HANDLE_NULL)
return FSM_Quit;
const eReqResult ret = GetPathRequestResult(pPed);
if(ret==EContinue)
{
SetState(State_MovingTowardsTarget);
SetFlag(aiTaskFlags::KeepCurrentSubtaskAcrossTransition);
SetNewTask( CreateSubTask(CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL, pPed) );
}
else if(ret==EStillWaiting)
{
SetNewTask( CreateSubTask(CTaskTypes::TASK_MOVE_STAND_STILL, pPed) );
}
else
{
return FSM_Quit;
}
}
return FSM_Continue;
}
CTask::FSM_Return CTaskMoveGetOntoMainNavMesh::MovingTowardsTarget_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
Assert(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL);
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
return FSM_Quit;
}
if(m_AttemptTimer.IsSet() && m_AttemptTimer.IsOutOfTime() && GetSubTask()->MakeAbortable( ABORT_PRIORITY_URGENT, NULL))
{
return FSM_Quit;
}
return FSM_Continue;
}
CTask::FSM_Return CTaskMoveGetOntoMainNavMesh::MovingToLastNonIsolatedPosition_OnUpdate(CPed * UNUSED_PARAM(pPed))
{
Assert(GetSubTask() && GetSubTask()->GetTaskType()==CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL);
if(GetIsFlagSet(aiTaskFlags::SubTaskFinished))
{
return FSM_Quit;
}
if(m_AttemptTimer.IsSet() && m_AttemptTimer.IsOutOfTime() && GetSubTask()->MakeAbortable( ABORT_PRIORITY_URGENT, NULL))
{
return FSM_Quit;
}
return FSM_Continue;
}
void CTaskMoveGetOntoMainNavMesh::DoAbort(const AbortPriority iPriority, const aiEvent* pEvent)
{
if(m_hPathHandle)
{
CPathServer::CancelRequest(m_hPathHandle);
m_hPathHandle = PATH_HANDLE_NULL;
}
if(m_hCalcAreaUnderfootHandle)
{
CPathServer::CancelRequest(m_hCalcAreaUnderfootHandle);
m_hCalcAreaUnderfootHandle = PATH_HANDLE_NULL;
}
// Base class
CTaskMove::DoAbort(iPriority, pEvent);
}
CTaskMoveGetOntoMainNavMesh::eReqResult
CTaskMoveGetOntoMainNavMesh::GetPathRequestResult(const CPed * pPed)
{
Assert(m_hPathHandle!=PATH_HANDLE_NULL);
EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_hPathHandle);
if(ret == ERequest_Ready)
{
int iNumPts = 0;
Vector3 * pPts = NULL;
TNavMeshWaypointFlag * pWpts = NULL;
TPathResultInfo resultInfo;
EPathServerErrorCode errCode = CPathServer::LockPathResult(m_hPathHandle, &iNumPts, pPts, pWpts, &resultInfo);
if(errCode != PATH_NO_ERROR)
{
CPathServer::UnlockPathResultAndClear(m_hPathHandle);
m_hPathHandle = PATH_HANDLE_NULL;
return EQuit;
}
if(iNumPts > 1)
{
static const float fGoToDist = 2.0f;
Vector3 vMoveDir = pPts[iNumPts-2] - pPts[iNumPts-1];
vMoveDir.Normalize();
vMoveDir *= fGoToDist;
m_vTarget = pPts[ iNumPts-1 ] + vMoveDir;
}
else
{
Assert(iNumPts!=0);
if(iNumPts!=0)
{
m_vTarget = pPts[ iNumPts-1 ];
}
else
{
// Error, shouldn't happen, something's fucked up..
m_vTarget = VEC3V_TO_VECTOR3(pPed->GetTransform().GetPosition() + pPed->GetTransform().GetB());
}
}
CPathServer::UnlockPathResultAndClear(m_hPathHandle);
m_hPathHandle = PATH_HANDLE_NULL;
return EContinue;
}
else if(ret == ERequest_NotFound)
{
m_hPathHandle = PATH_HANDLE_NULL;
return EQuit;
}
return EStillWaiting;
}
CTaskMoveGetOntoMainNavMesh::eReqResult
CTaskMoveGetOntoMainNavMesh::GetAreaRequestResult(const CPed * UNUSED_PARAM(pPed))
{
Assert(m_hCalcAreaUnderfootHandle!=PATH_HANDLE_NULL);
EPathServerRequestResult ret = CPathServer::IsRequestResultReady(m_hCalcAreaUnderfootHandle);
if(ret == ERequest_Ready)
{
float fArea;
EPathServerErrorCode errCode = CPathServer::GetAreaUnderfootSearchResultAndClear(m_hCalcAreaUnderfootHandle, fArea);
if(errCode != PATH_NO_ERROR)
{
CPathServer::UnlockPathResultAndClear(m_hCalcAreaUnderfootHandle);
m_hCalcAreaUnderfootHandle = PATH_HANDLE_NULL;
return EQuit;
}
CPathServer::UnlockPathResultAndClear(m_hCalcAreaUnderfootHandle);
m_hCalcAreaUnderfootHandle = PATH_HANDLE_NULL;
m_bIsOnSmallAreaOfNavMesh = (fArea <= ms_fMaxAreaToAttempt);
return EContinue;
}
else if(ret == ERequest_NotFound)
{
m_hCalcAreaUnderfootHandle = PATH_HANDLE_NULL;
return EQuit;
}
return EStillWaiting;
}
CTask * CTaskMoveGetOntoMainNavMesh::CreateSubTask(const int iTaskType, CPed* UNUSED_PARAM(pPed))
{
switch(iTaskType)
{
case CTaskTypes::TASK_MOVE_STAND_STILL:
{
return rage_new CTaskMoveStandStill(1000);
}
case CTaskTypes::TASK_MOVE_GO_TO_POINT_AND_STAND_STILL:
{
m_AttemptTimer.Set(fwTimer::GetTimeInMilliseconds(), m_iAttemptTime);
CTaskMoveGoToPointAndStandStill * pGotoTask = rage_new CTaskMoveGoToPointAndStandStill(m_fMoveBlendRatio, m_vTarget, ms_fTargetRadius);
// We don't care about the vertical height of the goto target
pGotoTask->SetTargetHeightDelta(1000.0f);
return pGotoTask;
}
}
Assert(false);
return NULL;
}