From 41d41780a836b5e7f7ec4ffd052c86c945d17f8c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 18 Feb 2024 13:16:47 +0300 Subject: [PATCH] CharacterController: rework movement queueing logic (#7835) --- apps/openmw/mwmechanics/character.cpp | 86 +++++++++++++++------------ 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index cbd09b30d8..d89d4d8d53 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2237,8 +2237,6 @@ namespace MWMechanics if (mAnimation->isPlaying(mCurrentJump)) jumpstate = JumpState_Landing; - vec.x() *= scale; - vec.y() *= scale; vec.z() = 0.0f; if (movementSettings.mIsStrafing) @@ -2371,7 +2369,8 @@ namespace MWMechanics const float speedMult = speed / mMovementAnimSpeed; mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult)); // Make sure the actual speed is the "expected" speed even though the animation is slower - scale *= std::max(1.f, speedMult / maxSpeedMult); + if (isMovementAnimationControlled()) + scale *= std::max(1.f, speedMult / maxSpeedMult); } if (!mSkipAnim) @@ -2390,20 +2389,17 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + updateHeadTracking(duration); } movement = vec; movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; + + // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicsSystem will + // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will + // be reset in PhysicsSystem::move once the jump is handled. if (movement.z() == 0.f) movementSettings.mPosition[2] = 0; - // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will - // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will - // be reset in PhysicSystem::move once the jump is handled. - - if (!mSkipAnim) - updateHeadTracking(duration); } else if (cls.getCreatureStats(mPtr).isDead()) { @@ -2420,35 +2416,42 @@ namespace MWMechanics osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && !isScriptedAnimPlaying()) { - if (duration > 0.0f) - movementFromAnimation /= duration; - else - movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - - movementFromAnimation.x() *= scale; - movementFromAnimation.y() *= scale; + if (isMovementAnimationControlled()) + { + if (duration != 0.f && movementFromAnimation != osg::Vec3f()) + { + movementFromAnimation /= duration; + + // Ensure we're moving in the right general direction. + // In vanilla, all horizontal movement is taken from animations, even when moving diagonally (which + // doesn't have a corresponding animation). So to achieve diagonal movement, we have to rotate the + // movement taken from the animation to the intended direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no + // individual frame will, and therefore we have to determine the direction based on the currently + // playing cycle instead. + if (speed > 0.f) + { + float animMovementAngle = getAnimationMovementDirection(); + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + } - if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + movement = movementFromAnimation; + } + else + { + movement = osg::Vec3f(); + } + } + else if (mSkipAnim) { - // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from - // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive - // diagonal movement, we have to rotate the movement taken from the animation to the intended - // direction. - // - // Note that while a complete movement animation cycle will have a well defined direction, no individual - // frame will, and therefore we have to determine the direction based on the currently playing cycle - // instead. - float animMovementAngle = getAnimationMovementDirection(); - float targetMovementAngle = std::atan2(-movement.x(), movement.y()); - float diff = targetMovementAngle - animMovementAngle; - movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + movement = osg::Vec3f(); } - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - movement = movementFromAnimation; - if (mFloatToSurface) { if (cls.getCreatureStats(mPtr).isDead() @@ -2463,8 +2466,11 @@ namespace MWMechanics } } + movement.x() *= scale; + movement.y() *= scale; // Update movement - world->queueMovement(mPtr, movement); + if (movement != osg::Vec3f()) + world->queueMovement(mPtr, movement); } mSkipAnim = false; @@ -2681,11 +2687,15 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { + if (Settings::game().mPlayerMovementIgnoresAnimation && mPtr == getPlayer()) + return false; + + if (mInJump) + return false; + bool movementAnimationControlled = mIdleState != CharState_None; if (mMovementState != CharState_None) movementAnimationControlled = mMovementAnimationHasMovement; - if (mInJump) - movementAnimationControlled = false; return movementAnimationControlled; }