// 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); }
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(¶mPos, 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); }