void CPlayerStateJump::SetJumpState(CPlayer& player, EJumpState jumpState) { if (jumpState != m_jumpState) { CRY_ASSERT( m_jumpState >= JState_None && m_jumpState < JState_Total ); const EJumpState previousJumpState = m_jumpState; m_jumpState = jumpState; if (IActionController *actionController = player.GetAnimatedCharacter()->GetActionController()) { if (!m_jumpAction && !player.IsAIControlled()) { FragmentID fragID = FRAGMENT_ID_INVALID; switch (jumpState) { case JState_Jump: fragID = PlayerMannequin.fragmentIDs.MotionJump; break; case JState_Falling: fragID = PlayerMannequin.fragmentIDs.MotionInAir; break; } if (fragID != FRAGMENT_ID_INVALID) { m_jumpAction = new CPlayerJump(fragID, PP_PlayerAction); m_jumpAction->AddRef(); actionController->Queue(*m_jumpAction); } } } } }
void CCameraTracking::UpdateAutoFollow(const SViewParams &viewParams, const CPlayer &hero) { if(g_pGameCVars->cl_cam_auto_follow_rate > 0.0f) //auto-tracking { //if there is an obstacle nearby, don't try to rotate into it float lastObstacleDist = (m_vLastObstaclePos - viewParams.position).len(); if(lastObstacleDist < g_fAutoTrackObstacleDistance) return; if(hero.GetActorStats()->speedFlat < g_pGameCVars->cl_cam_auto_follow_movement_speed) { //only rotate when player moves m_fAutoRotateSpeed = 0.0f; return; } //get camera direction Vec3 camDir = -m_pCamRayScan->GetRayDir(eRAY_CENTER); camDir.z = 0.0f; camDir.Normalize(); //get Player direction Vec3 heroDirection = (hero.GetAnimatedCharacter()->GetAnimLocation().q * FORWARD_DIRECTION); //compute angle between directions float dt = camDir.Dot(heroDirection); dt = clamp(dt, -1.0f, 1.0f); float angle = cry_acosf(dt); //check angle being bigger than threshold if(angle > g_pGameCVars->cl_cam_auto_follow_threshold) { float moveSpeed = max(0.002f, gf_PI*m_fFrameTime*g_pGameCVars->cl_cam_auto_follow_rate); m_fAutoRotateSpeed = InterpolateTo(max(0.002f, m_fAutoRotateSpeed), moveSpeed, 0.2f); //compute rotation direction by taking height part of cross-prod float dirVal = camDir.x * heroDirection.y - camDir.y * heroDirection.x; CCameraInputHelper *const pCamHelper = hero.GetCameraInputHelper(); CRY_ASSERT(pCamHelper); if(dirVal > 0) //rotate right pCamHelper->SetTrackingDelta(-m_fAutoRotateSpeed, 0.0f); else //rotate left pCamHelper->SetTrackingDelta(m_fAutoRotateSpeed, 0.0f); } else m_fAutoRotateSpeed = 0.0f; } }
void CPlayerStateUtil::RestorePlayerPhysics( CPlayer& player ) { IAnimatedCharacter* pAC = player.GetAnimatedCharacter(); if( pAC ) { SAnimatedCharacterParams params; params = pAC->GetParams(); params.inertia = player.GetInertia(); params.inertiaAccel = player.GetInertiaAccel(); params.timeImpulseRecover = player.GetTimeImpulseRecover(); player.SetAnimatedCharacterParams( params ); } }
//----------------------------------------------------------------------------------------------- void CPlayerStateGround::ProcessAlignToTarget(const CAutoAimManager& autoAimManager, CPlayer& player, const IActor* pTarget) { CRY_ASSERT(pTarget); if( !m_inertiaIsZero ) { SAnimatedCharacterParams params = player.m_pAnimatedCharacter->GetParams(); params.inertia = 0.f; player.SetAnimatedCharacterParams(params); m_inertiaIsZero = true; } Vec3 targetPos = pTarget->GetAnimatedCharacter()->GetAnimLocation().t; Vec3 playerPos = player.GetAnimatedCharacter()->GetAnimLocation().t; Vec3 displacement = (targetPos - playerPos); float targetDistance = autoAimManager.GetCloseCombatSnapTargetRange(); SCharacterMoveRequest& moveRequest = player.GetMoveRequest(); moveRequest.type = eCMT_Fly; moveRequest.velocity.zero(); if(displacement.len2() > sqr(targetDistance)) { float distanceToTravel = displacement.len() - targetDistance; displacement.z = 0.f; displacement.Normalize(); const CActor *pTargetActor = static_cast<const CActor*>(pTarget); const Vec3 &targetVelocity = pTargetActor->GetActorPhysics().velocity; moveRequest.velocity = (displacement * distanceToTravel * autoAimManager.GetCloseCombatSnapTargetMoveSpeed()) + targetVelocity; } }
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(); } }