Exemple #1
0
// PLAYERPREDICTION
void CNetPlayerInput::GetDesiredVel(const Vec3 &pos, Vec3 &vel) const
{
	bool doInterp = !m_passedPredictionPos;
	vel.Set(0.0f, 0.0f, 0.0f);
	if (doInterp)
	{
		Vec3 offset = m_predictedPosition - pos;
		offset.z = 0.0f;
		float dist  = offset.GetLength2D();
		if (dist > 0.0f)
		{
			vel = offset * (m_netLerpSpeed / dist);

			float parallelSpeed = m_initialDir.Dot(vel);
			Vec3	parallelVel		= m_initialDir * parallelSpeed;
			Vec3  pathCorrection = vel - parallelVel;
			vel = parallelVel + (g_pGameCVars->pl_velocityInterpPathCorrection * pathCorrection);

			if (g_pGameCVars->pl_debugInterpolation)
			{
				CryWatch("Offset: (%f, %f, %f) InitDir: (%f, %f, %f) DesiredVel: (%f, %f, %f)", offset.x, offset.y, offset.z, m_initialDir.x, m_initialDir.y, m_initialDir.z, vel.x, vel.y, vel.z);
			}
		}
	}
}
//////////////////////////////////////////////////////////////////////////
// IsMountedWeaponUsableWithTarget
// A piece of game-code moved from CryAction when scriptbind_AI moved to the AI system
//////////////////////////////////////////////////////////////////////////
int CScriptBind_Game::IsMountedWeaponUsableWithTarget(IFunctionHandler *pH)
{
	int paramCount = pH->GetParamCount();
	if(paramCount<2)
	{
		GameWarning("%s: too few parameters.", __FUNCTION__);
		return pH->EndFunction();
	}

	GET_ENTITY(1);

	if(!pEntity)
	{
		GameWarning("%s: wrong entity id in parameter 1.", __FUNCTION__);
		return pH->EndFunction();
	}

	IAIObject* pAI = pEntity->GetAI();
	if (!pAI)
	{
		GameWarning("%s: Entity '%s' does not have AI.",__FUNCTION__,  pEntity->GetName());
		return pH->EndFunction();
	}

	EntityId itemEntityId;
	ScriptHandle hdl2;

	if(!pH->GetParam(2,hdl2))
	{
		GameWarning("%s: wrong parameter 2 format.", __FUNCTION__);
		return pH->EndFunction();
	}

	itemEntityId = (EntityId)hdl2.n;

	if (!itemEntityId)
	{
		GameWarning("%s: wrong entity id in parameter 2.", __FUNCTION__);
		return pH->EndFunction();
	}
	
	IGameFramework *pGameFramework = gEnv->pGame->GetIGameFramework();
	IItem* pItem = pGameFramework->GetIItemSystem()->GetItem(itemEntityId);
	if (!pItem)
	{
		//gEnv->pAISystem->Warning("<CScriptBind> ", "entity in parameter 2 is not an item/weapon");
		GameWarning("%s: entity in parameter 2 is not an item/weapon.", __FUNCTION__);
		return pH->EndFunction();
	}

	float minDist = 7;
	bool bSkipTargetCheck = false;
	Vec3 targetPos(ZERO);

	if(paramCount > 2)
	{
		for(int i=3;i <= paramCount ; i++)
		{
			if(pH->GetParamType(i) == svtBool)
				pH->GetParam(i,bSkipTargetCheck);
			else if(pH->GetParamType(i) == svtNumber)
				pH->GetParam(i,minDist);
			else if(pH->GetParamType(i) == svtObject)
				pH->GetParam(i,targetPos);
		}
	}

	IAIActor* pAIActor = CastToIAIActorSafe(pAI);
	if (!pAIActor)
	{
		GameWarning("%s: entity '%s' in parameter 1 is not an AI actor.", __FUNCTION__, pEntity->GetName());
		return pH->EndFunction();
	}


	IEntity* pItemEntity = pItem->GetEntity();
	if(!pItemEntity)
		return pH->EndFunction();


	if(!pItem->GetOwnerId())
	{
		// weapon is not used, check if it is on a vehicle
		IEntity* pParentEntity = pItemEntity->GetParent();
		if(pParentEntity)
		{
			IAIObject* pParentAI = pParentEntity->GetAI();
			if(pParentAI && pParentAI->GetAIType()==AIOBJECT_VEHICLE)
			{
				// (MATT) Feature was cut and code was tricky, hence ignore weapons in vehicles  {2008/02/15:11:08:51}
				return pH->EndFunction();
			}
		}
	}
	else if( pItem->GetOwnerId()!= pEntity->GetId()) // item is used by someone else?
		return pH->EndFunction(false);

	// check target
	if(bSkipTargetCheck)
		return pH->EndFunction(true);

	IAIObject* pTarget = pAIActor->GetAttentionTarget();
	if(targetPos.IsZero())
	{
		if(!pTarget)
			return pH->EndFunction();
		targetPos = pTarget->GetPos();
	}

	Vec3 targetDir(targetPos - pItemEntity->GetWorldPos());
	Vec3 targetDirXY(targetDir.x, targetDir.y, 0);

	float length2D = targetDirXY.GetLength();
	if(length2D < minDist || length2D<=0)
		return pH->EndFunction();

	targetDirXY /= length2D;//normalize

	IWeapon* pWeapon = pItem->GetIWeapon(); 
	bool vehicleGun = pWeapon && pWeapon->GetHostId();

	if (!vehicleGun)
	{
		Vec3 mountedAngleLimits(pItem->GetMountedAngleLimits());

		float yawRange = DEG2RAD(mountedAngleLimits.z);
		if(yawRange > 0 && yawRange < gf_PI)
		{
			float deltaYaw = pItem->GetMountedDir().Dot(targetDirXY);
			if(deltaYaw < cosf(yawRange))
				return pH->EndFunction(false);
		}

		float minPitch = DEG2RAD(mountedAngleLimits.x);
		float maxPitch = DEG2RAD(mountedAngleLimits.y);

		//maxPitch = (maxPitch - minPitch)/2;
		//minPitch = -maxPitch;

		float pitch = atanf(targetDir.z / length2D);

		if ( pitch < minPitch || pitch > maxPitch )
			return pH->EndFunction(false);
	}

	if(pTarget)
	{
		IEntity* pTargetEntity = pTarget->GetEntity();
		if(pTargetEntity)
		{
			// check target distance and where he's going
			IPhysicalEntity *phys = pTargetEntity->GetPhysics();
			if(phys)
			{
				pe_status_dynamics	dyn;
				phys->GetStatus(&dyn);
				Vec3 velocity ( dyn.v);
				velocity.z = 0;

				float speed = velocity.GetLength2D();
				if(speed>0)
				{
					//velocity /= speed;
					if(length2D< minDist * 0.75f && velocity.Dot(targetDirXY)<=0)
						return pH->EndFunction(false);
				}
			}
		}
	}
	return pH->EndFunction(true);

}
Exemple #3
0
void CNetPlayerInput::UpdateInterpolation()
{
	Vec3 desiredPosition = m_curInput.position;

	Vec3 entPos = m_pPlayer->GetEntity()->GetPos();
	Vec3 displacement = desiredPosition - entPos;
	displacement.z = 0.0f;
	float dist = displacement.len();

	CTimeValue	curTime=gEnv->pTimer->GetFrameStartTime();
	Vec3 desiredVelocity = m_curInput.deltaMovement * g_pGameCVars->pl_netSerialiseMaxSpeed;
	m_netDesiredSpeed = desiredVelocity.GetLength2D();

	if (m_newInterpolation)
	{
		InitialiseInterpolation(dist, displacement, desiredVelocity, curTime);
	}

	float dt = curTime.GetDifferenceInSeconds(m_netLastUpdate) + k_lerpTargetTime;
	dt = min(dt, k_maxPredictTime);
	m_predictedPosition = desiredPosition + (desiredVelocity * dt);

	Vec3 predOffset = m_predictedPosition - entPos;
	float predDist = predOffset.GetLength2D();

	float lerpSpeed = (predDist/k_lerpTargetTime);
	lerpSpeed=clamp(lerpSpeed, k_minInterpolateSpeed, k_maxInterpolateSpeed);

	m_netLerpSpeed = lerpSpeed;

	UpdateErrorSnap(entPos, desiredPosition, dist, displacement, curTime);

	if (!m_passedNetPos && (m_initialDir.Dot(displacement) < 0.0f))
	{
		m_passedNetPos = true;
	}
	Vec3 maxPrediction = desiredPosition + (desiredVelocity * k_maxPredictTime);
	if (m_passedNetPos && !m_passedPredictionPos && (m_initialDir.Dot(maxPrediction - entPos) < 0.0f))
	{
		m_passedPredictionPos = true;
	}

#if !defined(_RELEASE)

	if (g_pGameCVars->pl_debugInterpolation)
	{
		CryWatch("Cur: (%f, %f, %f) Des: (%f, %f, %f) Pred: (%f, %f, %f) ", entPos.x, entPos.y, entPos.z, desiredPosition.x, desiredPosition.y, desiredPosition.z, m_predictedPosition.x, m_predictedPosition.y, m_predictedPosition.z);
		CryWatch("BlockTime: (%f) PredictTime (%f) LastNetTime (%f) CurTime (%f)", m_blockedTime, dt, m_netLastUpdate.GetSeconds(), curTime.GetSeconds());
		CryWatch("Lerp Speed: (%f) Passed pred pos (%d) Passed net pos (%d)", m_netLerpSpeed, m_passedPredictionPos, m_passedNetPos);
		CryWatch("InputSpeed: (%f, %f, %f) ", desiredVelocity.x, desiredVelocity.y, desiredVelocity.z);

		IRenderAuxGeom* pRender = gEnv->pRenderer->GetIRenderAuxGeom();

		SAuxGeomRenderFlags flags = pRender->GetRenderFlags();
		SAuxGeomRenderFlags oldFlags = pRender->GetRenderFlags();
		flags.SetDepthWriteFlag(e_DepthWriteOff);
		flags.SetDepthTestFlag(e_DepthTestOff);
		pRender->SetRenderFlags(flags);
		pRender->DrawSphere(desiredPosition + Vec3(0.0f, 0.0f, 0.035f), 0.07f, ColorB(255,0,0,255));
		pRender->DrawSphere(m_predictedPosition + Vec3(0.0f, 0.0f, 0.025f), 0.05f, ColorB(255,255,255,255));
		pRender->SetRenderFlags(oldFlags);

		ColorF ballCol = m_passedPredictionPos ? ColorF(1.0f,1.0f,0.0f,0.75f) : (m_passedNetPos ? ColorF(1.0f,1.0f,1.0f,0.75f) : ColorF(0.0f,1.0f,0.0f,0.75f));

		g_pGame->GetIGameFramework()->GetIPersistantDebug()->Begin("INTERPOLATION TRAIL", false);
		g_pGame->GetIGameFramework()->GetIPersistantDebug()->AddSphere(desiredPosition + Vec3(0.0f, 0.0f, 0.1f),  0.04f, ColorF(1.0f,0.0f,0.0f,0.75f), 30.f);
		g_pGame->GetIGameFramework()->GetIPersistantDebug()->AddSphere(m_predictedPosition + Vec3(0.0f, 0.0f, 0.1f),  0.03f, ColorF(0.0f,0.0f,1.0f,0.8f), 30.f);

		ballCol.a = 1.0f;
		g_pGame->GetIGameFramework()->GetIPersistantDebug()->AddSphere(entPos + Vec3(0.0f, 0.0f, 0.1f),  0.02f, ballCol, 30.f);
		g_pGame->GetIGameFramework()->GetIPersistantDebug()->AddLine(entPos + Vec3(0.0f, 0.0f, 0.1f), m_predictedPosition + Vec3(0.0f, 0.0f, 0.1f), ballCol, 30.f);
	}

#endif //!_RELEASE
}
void CVehicleMovementHelicopter::ProcessAI(const float deltaTime)
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_GAME );

	CryAutoCriticalSection lk(m_lock);
	SVehiclePhysicsStatus* physStatus = &m_physStatus[k_physicsThread];
		
	if (m_arcade.m_handling.maxSpeedForward>0.f) // Use the new handling code
	{
		//ResetActions();
		m_movementAction.Clear();
		m_movementAction.isAI = true;

		SVehiclePhysicsHelicopterProcessAIParams params;
		params.pPhysStatus = physStatus;
		params.pInputAction = &m_inputAction;
		params.pAiRequest = &m_aiRequest;
		params.dt = deltaTime;
		params.aiRequiredVel = m_CurrentVel;
		params.aiCurrentSpeed = m_CurrentSpeed;
		params.aiYawResponseScalar = m_yawResponseScalar;
		m_yawResponseScalar = 1.f;
	
		// Use helper class to process the AI input
		// It will return a requested velocity, and change the input
		m_arcade.ProcessAI(params);

		// Get the output velocity
		m_CurrentVel = params.aiRequiredVel;
		m_CurrentSpeed = params.aiCurrentSpeed;
		return;
	}
	////////////////////// OLD DEPRECATED CODE :( //////////////////////////////////

	m_movementAction.Clear();
	ResetActions();

	// Our current state
	const Vec3 worldPos =  physStatus->pos;
	const Matrix33 worldMat( physStatus->q);
	const Matrix33 localMat( physStatus->q.GetInverted());
	const Ang3 worldAngles = Ang3::GetAnglesXYZ(worldMat);
	const Ang3 localAngles = Ang3::GetAnglesXYZ(localMat);

	const Vec3 currentVel = physStatus->v;
	const Vec3 currentVel2D(currentVel.x, currentVel.y, 0.0f);
	m_CurrentSpeed = m_CurrentVel.len(); //currentVel.len();
  float currentSpeed2d = currentVel2D.len();

	// +ve direction mean rotation anti-clocwise about the z axis - 0 means along y
	float currentDir = worldAngles.z;

	// to avoid singularity
	const Vec3 vWorldDir = worldMat.GetRow(1);
	const Vec3 vSideWays = worldMat.GetRow(0);
	const Vec3 vWorldDir2D =  Vec3( vWorldDir.x,  vWorldDir.y, 0.0f ).GetNormalizedSafe();

	// Our inputs
	float desiredSpeed = m_aiRequest.HasDesiredSpeed() ? m_aiRequest.GetDesiredSpeed() : 0.0f;
	Limit(desiredSpeed, -m_maxSpeed, m_maxSpeed);

	const Vec3 desiredMoveDir = m_aiRequest.HasMoveTarget() ? (m_aiRequest.GetMoveTarget() - worldPos).GetNormalizedSafe() : vWorldDir;
	Vec3 desiredMoveDir2D = Vec3(desiredMoveDir.x, desiredMoveDir.y, 0.0f);
	desiredMoveDir2D = desiredMoveDir2D.GetNormalizedSafe(desiredMoveDir2D);

	const Vec3 desiredVel = desiredMoveDir * desiredSpeed; 
	const Vec3 desiredVel2D(desiredVel.x, desiredVel.y, 0.0f);

  Vec3 desiredLookDir(desiredMoveDir);

  if (m_aiRequest.HasDesiredBodyDirectionAtTarget())
  {
	    desiredLookDir = m_aiRequest.GetDesiredBodyDirectionAtTarget().GetNormalizedSafe(desiredMoveDir);
  }
  else if (m_aiRequest.HasLookTarget())
  {
    desiredLookDir = (m_aiRequest.GetLookTarget() - worldPos).GetNormalizedSafe(desiredMoveDir);  
  }


	//const Vec3 desiredLookDir = m_aiRequest.HasLookTarget() ? (m_aiRequest.GetLookTarget() - worldPos).GetNormalizedSafe() : desiredMoveDir;
	const Vec3 desiredLookDir2D = Vec3(desiredLookDir.x, desiredLookDir.y, 0.0f).GetNormalizedSafe(vWorldDir2D);

  Vec3 prediction = m_aiRequest.HasBodyTarget() ? m_aiRequest.GetBodyTarget() : ZERO;

  prediction = (prediction.IsEquivalent(ZERO)) ? desiredMoveDir2D : prediction - worldPos;
  prediction.z = 0.0f;

  float speedLimit = prediction.GetLength2D();

	if(speedLimit > 0.0f)
	{
		prediction *= 1.0f / speedLimit;
	}

  Vec3 tempDir = currentVel2D.IsEquivalent(ZERO) ? localMat.GetRow(1) : currentVel2D;
  tempDir.z = 0.0f;
  tempDir.NormalizeFast();

	float dotProd = tempDir.dot(prediction);
	Limit(dotProd, FLT_EPSILON, 1.0f);

	float accel = m_enginePowerMax * min(2.0f,  1.0f / dotProd); // * dotProd;

	if (!m_aiRequest.HasDesiredBodyDirectionAtTarget())
	{
		dotProd *= dotProd;
		dotProd *= dotProd;
		float tempf = min(max(speedLimit * speedLimit, 2.0f), m_maxSpeed * dotProd);
		Limit(desiredSpeed, -tempf, tempf);
	}
	else if (dotProd < 0.0125f)
	{
		Limit(desiredSpeed, -m_maxSpeed * 0.25f, m_maxSpeed * 0.25f);
	}
  
  float posNeg = (float)__fsel(desiredSpeed - m_CurrentSpeed, 1.0f, -5.0f);

  if (desiredVel2D.GetLengthSquared() > FLT_EPSILON)
  {
	  m_CurrentSpeed = m_CurrentSpeed + posNeg * accel * deltaTime;
  }
  else
  {
    m_CurrentSpeed = m_CurrentSpeed + posNeg * accel * deltaTime;
  }

	if (posNeg > 0.0f && m_CurrentSpeed > desiredSpeed)
	{
		m_CurrentSpeed = desiredSpeed;
	}
	else if (posNeg < 0.0f && m_CurrentSpeed < desiredSpeed)
	{
		m_CurrentSpeed = desiredSpeed;
	}

	
	// ---------------------------- Rotation ----------------------------
	
	float desiredDir = (desiredLookDir2D.GetLengthSquared() > 0.0f) ? atan2f(-desiredLookDir2D.x, desiredLookDir2D.y) : atan2f(-vWorldDir2D.x, vWorldDir2D.y);
	while (currentDir < desiredDir - gf_PI)
		currentDir += 2.0f * gf_PI;
	while (currentDir > desiredDir + gf_PI)
		currentDir -= 2.0f * gf_PI;

	// ---------------------------- Yaw ----------------------------

	Ang3 dirDiff(0.0f, 0.0f, desiredDir - currentDir);
	dirDiff.RangePI();
	float absDiff   = fabsf(dirDiff.z);

	float rotSpeed		= (float)__fsel(dirDiff.z, m_yawPerRoll, -m_yawPerRoll);
	m_actionYaw		= m_actionYaw + deltaTime * (rotSpeed - m_actionYaw);

	float temp = fabsf(m_actionYaw);

	float multiplier = ((absDiff / (temp + 0.001f)) + 1.0f) * 0.5f;
	
	m_actionYaw	*= (float)__fsel(absDiff - temp, 1.0f, multiplier);

	// ---------------------------- Yaw ------------------------------

	m_CurrentVel = desiredMoveDir * m_CurrentSpeed;

  // ---------------------------- Pitch ----------------------------

  if (m_CurrentVel.GetLengthSquared2D() > 0.1f)
  {
    CalculatePitch(worldAngles, desiredMoveDir, currentSpeed2d, desiredSpeed, deltaTime);
  }
  else
  {
    Quat rot;
    rot.SetRotationVDir(desiredLookDir, 0.0f);
    float desiredXRot = Ang3::GetAnglesXYZ(rot).x + m_steeringDamage.x;

    m_actionPitch = worldAngles.x + (desiredXRot - worldAngles.x) * deltaTime/* * 10.0f*/;
    Limit(m_actionPitch, -m_maxPitchAngle * 2.0f, m_maxPitchAngle * 2.0f);
  }


	// ---------------------------- Roll ----------------------------
	float rollSpeed = GetRollSpeed();

	rollSpeed *= deltaTime;
	rollSpeed		= (float)__fsel(absDiff - rollSpeed, rollSpeed, absDiff);
	float roll		=(float) __fsel(dirDiff.z, -rollSpeed, rollSpeed);

	float speedPerUnit	   = 1.5f;
	float desiredRollSpeed = absDiff * speedPerUnit * (float)__fsel(dirDiff.z, 1.0f, -1.0f);
  
	desiredRollSpeed = -m_actionYaw * 2.5f;
  desiredRollSpeed += m_steeringDamage.y;

	m_actionRoll = m_actionRoll + deltaTime * (desiredRollSpeed - m_actionRoll);
	Limit(m_actionRoll, -m_maxRollAngle + m_steeringDamage.y, m_maxRollAngle - m_steeringDamage.y);
	m_actionRoll *= m_rollDamping;
	// ---------------------------- Roll ----------------------------


	// ---------------------------- Convert and apply ----------------------------
	Ang3 angles(m_actionPitch, m_actionRoll, worldAngles.z + deltaTime * m_actionYaw);
	
	pe_params_pos paramPos;
	paramPos.q.SetRotationXYZ(angles);
	paramPos.q.Normalize();

	IPhysicalEntity * pPhysicalEntity = GetPhysics();
	pPhysicalEntity->SetParams(&paramPos, 1);

	pe_action_set_velocity vel;
	vel.v = m_CurrentVel + m_netPosAdjust;
	pPhysicalEntity->Action(&vel, 1);

	// ---------------------------- Convert and apply ----------------------------

  m_rpmScale = max(0.2f, cry_fabsf(m_CurrentSpeed / m_maxSpeed));
}
STransitionSelectionParams::STransitionSelectionParams(
	const CMovementTransitions& transitions,
	const CPlayer& player,
	const CMovementRequest& request,
	const Vec3& playerPos,
	const SMovementTransitionsSample& oldSample,
	const SMovementTransitionsSample& newSample,
	const bool bHasLockedBodyTarget,
	const Vec3& targetBodyDirection,
	const Lineseg& safeLine,
	const CTimeValue runningDuration,
	const uint8 _allowedTransitionFlags,
	const float entitySpeed2D,
	const float entitySpeed2DAvg,
	const SExactPositioningTarget*const pExactPositioningTarget,
	const EStance stance,

	SActorFrameMovementParams*const pMoveParams) : m_transitionType(eTT_None), m_transitionDistance(0.0f), m_pseudoSpeed(0.0f), m_travelAngle(0.0f), m_jukeAngle(0.0f), m_stance(stance)
{
	// TODO: check for flatness?

	m_travelAngle = Ang3::CreateRadZ( newSample.bodyDirection, oldSample.moveDirection ); // probably should be oldSample?
	m_context = request.HasContext() ? request.GetContext() : 0;

	// --------------------------------------------------------------------------
	// Calculate vToMoveTarget, vAfterMoveTarget, distToMoveTarget, distAfterMoveTarget & allowedTransitionFlags

	Vec3 vToMoveTarget, vAfterMoveTarget;
	float distToMoveTarget, distAfterMoveTarget;
	uint8 allowedTransitionFlags = _allowedTransitionFlags;

	{
		if (request.HasMoveTarget())
		{
			const Vec3& vMoveTarget = request.GetMoveTarget();
			vToMoveTarget = vMoveTarget - playerPos;
			distToMoveTarget = vToMoveTarget.GetLength2D();

			m_future.hasMoveTarget = true;
			m_future.vMoveTarget = vMoveTarget;

			// Disallow certain transitions when preparing an exact positioning target
			// and fudge the distance to make sure we don't start when too close to it
			if (pExactPositioningTarget && pExactPositioningTarget->preparing && !pExactPositioningTarget->activated)
			{
				allowedTransitionFlags &= ~BIT(eTT_Stop);
				allowedTransitionFlags &= ~BIT(eTT_DirectionChange);

				const Vec3& exactPosLocation = pExactPositioningTarget->location.t;
				const float distFromMoveTargetToExactPosSquared = vMoveTarget.GetSquaredDistance(exactPosLocation);

				const float minimumDangerDistance = 0.1f;
				const float maxDistanceTraveledPerFrame = gEnv->pTimer->GetFrameTime() * 12.5f;
				const float dangerDistance = max(minimumDangerDistance, maxDistanceTraveledPerFrame);

				const bool moveTargetIsWithinDangerDistance = (distFromMoveTargetToExactPosSquared <= sqr(dangerDistance));
				if (moveTargetIsWithinDangerDistance)
				{
					// Fudge distToMoveTarget so we start at least distanceTraveledInTwoFrames
					// This only works for eTT_Start transitions (but we disabled the others above)
					distToMoveTarget = max(0.0f, distToMoveTarget - dangerDistance);
				}
			}

			if (request.HasInflectionPoint())
			{
				const Vec3& vInflectionPoint = request.GetInflectionPoint();
				vAfterMoveTarget = vInflectionPoint - vMoveTarget;
				distAfterMoveTarget = vAfterMoveTarget.GetLength2D();
			}
			else
			{
				vAfterMoveTarget.zero();
				distAfterMoveTarget = 0.0f;
			}
		}
		else
		{
			m_future.hasMoveTarget = false;

			vToMoveTarget.zero();
			vAfterMoveTarget.zero();
			distToMoveTarget = distAfterMoveTarget = 0.0f;
		}
	}

	// --------------------------------------------------------------------------

	const float maximumSpeedForStart = 0.5f;
	const float minimumSpeedForWalkStop = 1.0f;
	const float minimumSpeedForRunStop = 3.5f;
	const float minimumRunningDurationForRunStop = 1.0f; // (seconds)

	const float minimumSpeedForJuke = 4.4f*0.6f; // 4.4 is slowest runspeed in Crysis2; 0.6 is the strafing slowdown
	const float minimumRunningDurationForJuke = 0.1f; // (seconds)

	if (newSample.pseudoSpeed > 0.0f)
	{
		// Either:
		// - we are in a Stop and want to start again <-- note oldPseudoSpeed cannot be used to detect this, it could be already 0 from prev. frame
		// - we are in a Start and want to continue starting [but possibly stop or change direction at the movetarget]
		// - we are stopped and want to Start [but possibly stop or change direction at the movetarget]
		// - we are moving and want to continue moving [but possibly stop or change direction at the movetarget]

		m_pseudoSpeed = newSample.pseudoSpeed;

		if ( (allowedTransitionFlags & (1<<eTT_Start)) && (entitySpeed2DAvg <= maximumSpeedForStart) )
		{
			//New sample's movement direction is accurate for start transitions.
			m_travelAngle = Ang3::CreateRadZ( newSample.bodyDirection, newSample.moveDirection );
			m_transitionType = eTT_Start;
			m_bPredicted = true;
			m_transitionDistance = distToMoveTarget;
			m_future.vMoveDirection = newSample.moveDirection;
			const Vec3 realTargetBodyDirection = bHasLockedBodyTarget ? targetBodyDirection : m_future.vMoveDirection;
			m_targetTravelAngle = Ang3::CreateRadZ( realTargetBodyDirection, m_future.vMoveDirection );
			m_future.qOrientation = Quat::CreateRotationVDir( realTargetBodyDirection );
			MovementTransitionsLog("[%x] Juke failed because we are trying to start", gEnv->pRenderer->GetFrameID());
		}
		else // at the moment start & stop are mutually exclusive
		{
			if (!(allowedTransitionFlags & (1<<eTT_Start)))
				MovementTransitionsLog("[%x] Start failed because current animation state (%s) doesn't support starting", gEnv->pRenderer->GetFrameID(), const_cast<CPlayer&>(player).GetAnimationGraphState() ? const_cast<CPlayer&>(player).GetAnimationGraphState()->GetCurrentStateName() : "");
			else
				MovementTransitionsLog("[%x] Start failed because speed is %f while maximum %f is allowed", gEnv->pRenderer->GetFrameID(), player.GetAnimatedCharacter()->GetEntitySpeedHorizontal(), maximumSpeedForStart);

			m_transitionType = eTT_None;

			// try immediate directionchange first
			if (allowedTransitionFlags & (1<<eTT_DirectionChange))
			{
				if (
						((oldSample.pseudoSpeed == AISPEED_RUN) || (oldSample.pseudoSpeed == AISPEED_SPRINT)) &&
						(runningDuration > minimumRunningDurationForJuke) &&
						(entitySpeed2D >= minimumSpeedForJuke)
					)
				{
					if (gEnv->pAISystem && !gEnv->pAISystem->GetSmartObjectManager()->CheckSmartObjectStates(player.GetEntity(), "Busy"))
					{
						// === IMMEDIATE DIRECTIONCHANGE ===

						// ---------------------------------
						// Assume a directionchange after moving forward for one meter (assumedDistanceToJuke=1)
						// Look up a transition math for that proposed directionchange
						// ---------------------------------
						m_pseudoSpeed = oldSample.pseudoSpeed;
						m_transitionDistance = -1;

						float assumedDistanceToJuke = 1.0f;
						CRY_ASSERT(assumedDistanceToJuke > FLT_EPSILON);

						Vec3 vToProposedMoveTarget = newSample.moveDirection * distToMoveTarget; // vector from current position to current movetarget
						Vec3 vToProposedJukePoint = oldSample.moveDirection * assumedDistanceToJuke;
						Vec3 vAfterProposedJukePoint = (vToProposedMoveTarget - vToProposedJukePoint).GetNormalizedSafe(newSample.moveDirection);

						m_jukeAngle = Ang3::CreateRadZ( vToProposedJukePoint, vAfterProposedJukePoint );
						m_transitionType = eTT_DirectionChange;
						m_bPredicted = false;
						m_future.vMoveDirection = vAfterProposedJukePoint;
						Vec3 realTargetBodyDirection = bHasLockedBodyTarget ? targetBodyDirection : vAfterProposedJukePoint;
						m_targetTravelAngle = Ang3::CreateRadZ( realTargetBodyDirection, vAfterProposedJukePoint );

						MovementTransitionsLog("[%x] Considering angle %+3.2f", gEnv->pRenderer->GetFrameID(), RAD2DEG(m_jukeAngle));

						const STransition* pTransition = NULL;
						int index = -1;
						STransitionMatch bestMatch;
						transitions.FindBestMatch(*this, &pTransition, &index, &bestMatch);

						if (pTransition)
						{
							// -------------------------------------------------------
							// We found a transition matching our guess. Adjust juke point to match the distance of the transition we found
							// -------------------------------------------------------
							float proposedTransitionDistance = (pTransition->minDistance + pTransition->maxDistance)*0.5f;
							CRY_ASSERT(proposedTransitionDistance > FLT_EPSILON);
							vToProposedJukePoint = oldSample.moveDirection * proposedTransitionDistance;
							vAfterProposedJukePoint = vToProposedMoveTarget - vToProposedJukePoint;
							float proposedDistAfterMoveTarget = vAfterProposedJukePoint.GetLength2D();
							vAfterProposedJukePoint.NormalizeSafe(newSample.moveDirection);

							if (proposedDistAfterMoveTarget >= transitions.GetMinDistanceAfterDirectionChange())
							{
								m_jukeAngle = Ang3::CreateRadZ( vToProposedJukePoint, vAfterProposedJukePoint );
								m_future.vMoveDirection = vAfterProposedJukePoint;
								realTargetBodyDirection = bHasLockedBodyTarget ? targetBodyDirection : vAfterProposedJukePoint;
								m_targetTravelAngle = Ang3::CreateRadZ( realTargetBodyDirection, vAfterProposedJukePoint );

								MovementTransitionsLog("[%x] Proposing angle %+3.2f", gEnv->pRenderer->GetFrameID(), RAD2DEG(m_jukeAngle));

								m_transitionDistance = proposedTransitionDistance;
								m_future.qOrientation = Quat::CreateRotationVDir( realTargetBodyDirection );
							}
							else
							{
								MovementTransitionsLog("[%x] Immediate Juke failed because not enough distance after the juke (distance needed = %f, max distance = %f)", gEnv->pRenderer->GetFrameID(), transitions.GetMinDistanceAfterDirectionChange(), proposedDistAfterMoveTarget);
								m_transitionType = eTT_None;
							}
						}
						else
						{
							MovementTransitionsLog("[%x] Immediate Juke failed because no animation found for this angle/stance/speed", gEnv->pRenderer->GetFrameID());
							m_transitionType = eTT_None;
						}
					}
					else
					{
						MovementTransitionsLog("[%x] Immediate Juke failed because smart object is playing", gEnv->pRenderer->GetFrameID());
					}
				}
				else
				{
					if (!((oldSample.pseudoSpeed == AISPEED_RUN) || (oldSample.pseudoSpeed == AISPEED_SPRINT)))
					{
						MovementTransitionsLog("[%x] Immediate Juke failed because current pseudospeed (%f) is not supported", gEnv->pRenderer->GetFrameID(), oldSample.pseudoSpeed);
					}
					else if (runningDuration <= minimumRunningDurationForJuke)
					{
						MovementTransitionsLog("[%x] Immediate Juke failed because running only %f seconds while more than %f is needed", gEnv->pRenderer->GetFrameID(), runningDuration.GetSeconds(), minimumRunningDurationForJuke);
					}
					else //if (entitySpeed2D < minimumSpeedForJuke)
					{
						MovementTransitionsLog("[%x] Immediate Juke failed because speed is only %f while %f is needed", gEnv->pRenderer->GetFrameID(), entitySpeed2D, minimumSpeedForJuke);
					}
				}
			}
			else
			{
				MovementTransitionsLog("[%x] Immediate Juke failed because current animation state (%s) doesn't support juking", gEnv->pRenderer->GetFrameID(), const_cast<CPlayer&>(player).GetAnimationGraphState() ? const_cast<CPlayer&>(player).GetAnimationGraphState()->GetCurrentStateName() : NULL);
			}

			if (m_transitionType == eTT_None) // directionchange wasn't found
			{
				if ((allowedTransitionFlags & (1<<eTT_Stop)) && (distAfterMoveTarget < FLT_EPSILON))
				{
					// === PREDICTED STOP ===
					// We want to stop in the future
					m_transitionType = eTT_Stop;
					m_bPredicted = true;
					m_transitionDistance = distToMoveTarget;
					m_future.vMoveDirection = vToMoveTarget.GetNormalizedSafe(newSample.moveDirection);
					m_arrivalAngle = request.HasDesiredBodyDirectionAtTarget() ? Ang3::CreateRadZ( request.GetDesiredBodyDirectionAtTarget(), m_future.vMoveDirection ) : 0.0f;
					m_future.qOrientation = request.HasDesiredBodyDirectionAtTarget() ? Quat::CreateRotationVDir(request.GetDesiredBodyDirectionAtTarget()) : Quat::CreateRotationVDir(m_future.vMoveDirection);
					MovementTransitionsLog("[%x] Predicted Juke failed because we are trying to stop", gEnv->pRenderer->GetFrameID());
				}
				else if ((allowedTransitionFlags & (1<<eTT_DirectionChange)) && (distAfterMoveTarget >= transitions.GetMinDistanceAfterDirectionChange()))
				{
					// === PREDICTED DIRECTIONCHANGE ===
					// We want to change direction in the future
					// NOTE: This logic will fail if we trigger the juke really late, because then the distToMoveTarget will be very small and the angle calculation not precise
					m_transitionType = eTT_DirectionChange;
					m_bPredicted = true;
					m_transitionDistance = distToMoveTarget;
					m_jukeAngle = Ang3::CreateRadZ( vToMoveTarget, vAfterMoveTarget );
					m_future.vMoveDirection = vAfterMoveTarget.GetNormalized();
					const Vec3 realTargetBodyDirection = bHasLockedBodyTarget ? targetBodyDirection : m_future.vMoveDirection;
					m_future.qOrientation = Quat::CreateRotationVDir( realTargetBodyDirection );
					m_targetTravelAngle = Ang3::CreateRadZ( realTargetBodyDirection, m_future.vMoveDirection );
				}
			}
		}
	}
	else // if (newSample.pseudoSpeed <= 0.0f)
	{
		// Either:
		// - we are in a Stop and want to continue stopping
		// - we are moving and suddenly want to stop
		// - we are in a Start and want to stop <-- oldPseudoSpeed logic is wrong, oldPseudoSpeed will be 0 for a while so we should use real velocity
		// - we are stopped already and just want to stay stopped
		MovementTransitionsLog("[%x] Juke failed because we are not running or trying to stop", gEnv->pRenderer->GetFrameID());
		MovementTransitionsLog("[%x] Start failed because we are not requesting movement", gEnv->pRenderer->GetFrameID());

		if (
			(( (oldSample.pseudoSpeed == AISPEED_RUN) || (oldSample.pseudoSpeed == AISPEED_SPRINT)) && (runningDuration > minimumRunningDurationForRunStop) && (entitySpeed2D >= minimumSpeedForRunStop))
			||
			((oldSample.pseudoSpeed == AISPEED_WALK) && (entitySpeed2D >= minimumSpeedForWalkStop))
		)
		{
			if (allowedTransitionFlags & (1<<eTT_Stop))
			{
				// === IMMEDIATE STOP ===
				if( gEnv->pAISystem )
				{
					ISmartObjectManager* pSmartObjectManager = gEnv->pAISystem->GetSmartObjectManager();
					if (!pSmartObjectManager || !pSmartObjectManager->CheckSmartObjectStates(player.GetEntity(), "Busy"))
					{
						// Trigger immediate stop when currently running and suddenly wanting to stop.
						//
						// NOTE: If this happens right before a forbidden area and the safeLine is not correct
						//    or the Stop transition distance isn't configured properly the AI will enter it..
						//
						m_pseudoSpeed = oldSample.pseudoSpeed;
						m_transitionDistance = -1;
						m_arrivalAngle = 0.0f;
							m_transitionType = eTT_Stop;
						m_bPredicted = false;

						const STransition* pTransition = NULL;
						int index = -1;
						STransitionMatch bestMatch;
						transitions.FindBestMatch(*this, &pTransition, &index, &bestMatch);

						float minDistanceForStop = pTransition ? pTransition->minDistance : 0.0f;

						bool bIsOnSafeLine = IsOnSafeLine(safeLine, playerPos, newSample.moveDirection, minDistanceForStop);

						if (bIsOnSafeLine)
						{
							m_transitionDistance = minDistanceForStop;
							m_future.vMoveDirection = newSample.moveDirection;
							m_future.qOrientation = Quat::CreateRotationVDir(newSample.moveDirection);

							pMoveParams->desiredVelocity = player.GetEntity()->GetWorldRotation().GetInverted() * player.GetLastRequestedVelocity();

							float maxSpeed = player.GetStanceMaxSpeed(m_stance);
							if (maxSpeed > 0.01f)
								pMoveParams->desiredVelocity /= maxSpeed;
						}
						else
						{
							m_transitionType = eTT_None;
						}
					}
				}
			}
		}
	}

	if (request.HasDesiredSpeed())
	{
		m_future.speed = request.GetDesiredTargetSpeed();
	}
}
//////////////////////////////////////////////////////////////////////////
// NOTE: This function must be thread-safe. Before adding stuff contact MarcoC.
void CVehicleMovementVTOL::ProcessAI(const float deltaTime)
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_GAME );
	
	if (!m_isVTOLMovement)
	{
		CVehicleMovementHelicopter::ProcessAI(deltaTime);
		return;
	}

	m_velDamp = 0.15f;

	const float maxDirChange = 15.0f;

	// it's useless to progress further if the engine has yet to be turned on
	if (!m_isEnginePowered)
		return;

	m_movementAction.Clear();
	m_movementAction.isAI = true;
	ResetActions();

	// Our current state
	const Vec3 worldPos =  m_PhysPos.pos;
	const Matrix33 worldMat( m_PhysPos.q);
	Ang3 worldAngles = Ang3::GetAnglesXYZ(worldMat);

	const Vec3 currentVel = m_PhysDyn.v;
	const Vec3 currentVel2D(currentVel.x, currentVel.y, 0.0f);
	// +ve direction mean rotation anti-clocwise about the z axis - 0 means along y
	float currentDir = worldAngles.z;

	// to avoid singularity
	const Vec3 vWorldDir = worldMat * FORWARD_DIRECTION;
	const Vec3 vWorldDir2D =  Vec3( vWorldDir.x,  vWorldDir.y, 0.0f ).GetNormalizedSafe();

	// Our inputs
	const float desiredSpeed = m_aiRequest.HasDesiredSpeed() ? m_aiRequest.GetDesiredSpeed() : 0.0f;
	const Vec3 desiredMoveDir = m_aiRequest.HasMoveTarget() ? (m_aiRequest.GetMoveTarget() - worldPos).GetNormalizedSafe() : vWorldDir;
	const Vec3 desiredMoveDir2D = Vec3(desiredMoveDir.x, desiredMoveDir.y, 0.0f).GetNormalizedSafe(vWorldDir2D);
	const Vec3 desiredVel = desiredMoveDir * desiredSpeed; 
	const Vec3 desiredVel2D(desiredVel.x, desiredVel.y, 0.0f);
	const Vec3 desiredLookDir = m_aiRequest.HasLookTarget() ? (m_aiRequest.GetLookTarget() - worldPos).GetNormalizedSafe() : desiredMoveDir;
	const Vec3 desiredLookDir2D = Vec3(desiredLookDir.x, desiredLookDir.y, 0.0f).GetNormalizedSafe(vWorldDir2D);

	// Calculate the desired 2D velocity change
	Vec3 desiredVelChange2D = desiredVel2D - currentVel2D;
	float velChangeLength = desiredVelChange2D.GetLength2D();

	bool isLandingMode = false;
	if (m_pLandingGears && m_pLandingGears->AreLandingGearsOpen())
		isLandingMode = true;

	bool isHorizontal = (desiredSpeed >= 5.0f) && (desiredMoveDir.GetLength2D() > desiredMoveDir.z);
	float desiredPitch = 0.0f;
	float desiredRoll = 0.0f;

	float desiredDir = atan2f(-desiredLookDir2D.x, desiredLookDir2D.y);

	while (currentDir < desiredDir - gf_PI)
		currentDir += 2.0f * gf_PI;
	while (currentDir > desiredDir + gf_PI)
		currentDir -= 2.0f * gf_PI;

	float diffDir = (desiredDir - currentDir);
	m_actionYaw = diffDir * m_yawInputConst;
	m_actionYaw += m_yawInputDamping * (currentDir - m_lastDir) / deltaTime;
	m_lastDir = currentDir;

	if (isHorizontal && !isLandingMode)
	{
		float desiredFwdSpeed = desiredVelChange2D.GetLength();

		desiredFwdSpeed *= min(1.0f, diffDir / DEG2RAD(maxDirChange));

		if (!iszero(desiredFwdSpeed))
		{
			const Vec3 desiredWorldTiltAxis = Vec3(-desiredVelChange2D.y, desiredVelChange2D.x, 0.0f);
			const Vec3 desiredLocalTiltAxis = worldMat.GetTransposed() * desiredWorldTiltAxis;

			m_forwardAction = m_fwdPID.Update(currentVel.y, desiredLocalTiltAxis.GetLength(), -1.0f, 1.0f);

		float desiredTiltAngle = m_tiltPerVelDifference * desiredVelChange2D.GetLength();
		Limit(desiredTiltAngle, -m_maxTiltAngle, m_maxTiltAngle);

		if (desiredTiltAngle > 0.0001f)
		{
			const Vec3 desiredWorldTiltAxis2 = Vec3(-desiredVelChange2D.y, desiredVelChange2D.x, 0.0f).GetNormalizedSafe();
			const Vec3 desiredLocalTiltAxis2 = worldMat.GetTransposed() * desiredWorldTiltAxis2;

			Vec3 vVelLocal = worldMat.GetTransposed() * desiredVel;
			vVelLocal.NormalizeSafe();

			float dotup = vVelLocal.Dot(Vec3( 0.0f,0.0f,1.0f ) );
			float currentSpeed = currentVel.GetLength();

			desiredPitch = dotup *currentSpeed / 100.0f;
			desiredRoll = desiredTiltAngle * desiredLocalTiltAxis2.y *currentSpeed/30.0f;
		}

		}
	}
	else
	{
		float desiredTiltAngle = m_tiltPerVelDifference * desiredVelChange2D.GetLength();
		Limit(desiredTiltAngle, -m_maxTiltAngle, m_maxTiltAngle);

		if (desiredTiltAngle > 0.0001f)
		{
			const Vec3 desiredWorldTiltAxis = Vec3(-desiredVelChange2D.y, desiredVelChange2D.x, 0.0f).GetNormalizedSafe();
			const Vec3 desiredLocalTiltAxis = worldMat.GetTransposed() * desiredWorldTiltAxis;

			desiredPitch = desiredTiltAngle * desiredLocalTiltAxis.x;
			desiredRoll = desiredTiltAngle * desiredLocalTiltAxis.y;
		}
	}

	float currentHeight = m_PhysPos.pos.z;
	if ( m_aiRequest.HasMoveTarget() )
	{
		m_hoveringPower = m_powerPID.Update(currentVel.z, desiredVel.z, -1.0f, 4.0f);
		
		//m_hoveringPower = (m_desiredHeight - currentHeight) * m_powerInputConst;
		//m_hoveringPower += m_powerInputDamping * (currentHeight - m_lastHeight) / deltaTime;

		if (isHorizontal)
		{
			if (desiredMoveDir.z > 0.6f || desiredMoveDir.z < -0.85f)
			{
				desiredPitch = max(-0.5f, min(0.5f, desiredMoveDir.z)) * DEG2RAD(35.0f);
				m_forwardAction += abs(desiredMoveDir.z);
			}

			m_liftAction = min(2.0f, max(m_liftAction, m_hoveringPower * 2.0f));
		}
		else
		{
			m_liftAction = 0.0f;
		}
	}
	else
	{
		// to keep hovering at the same place
		m_hoveringPower = m_powerPID.Update(currentVel.z, m_desiredHeight - currentHeight, -1.0f, 1.0f);
		m_liftAction = 0.0f;

		if (m_pVehicle->GetAltitude() > 10.0f)	//TODO: this line is not MTSafe
			m_liftAction = m_forwardAction;
	}

	m_actionPitch += m_pitchActionPerTilt * (desiredPitch - worldAngles.x);
	m_actionRoll += m_pitchActionPerTilt * (desiredRoll - worldAngles.y);

	Limit(m_actionPitch, -1.0f, 1.0f);
	Limit(m_actionRoll, -1.0f, 1.0f);
	Limit(m_actionYaw, -1.0f, 1.0f);

	if (m_horizontal > 0.0001f)
		m_desiredHeight = m_PhysPos.pos.z;
	
	Limit(m_forwardAction, -1.0f, 1.0f);
}