diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 88defbaa0..24f25e508 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -536,10 +536,11 @@ namespace MWClass moveSpeed = getSwimSpeed(ptr); else moveSpeed = getWalkSpeed(ptr); - if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) - moveSpeed *= 0.75f; - moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor; + const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); + if (movementSettings.mIsStrafing) + moveSpeed *= 0.75f; + moveSpeed *= movementSettings.mSpeedFactor; return moveSpeed; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 319d5f014..b7084aff0 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -966,13 +966,14 @@ namespace MWClass moveSpeed = getRunSpeed(ptr); else moveSpeed = getWalkSpeed(ptr); - if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) - moveSpeed *= 0.75f; if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); - moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor; + const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr); + if (movementSettings.mIsStrafing) + moveSpeed *= 0.75f; + moveSpeed *= movementSettings.mSpeedFactor; return moveSpeed; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e4f870ed0..24d1ebf36 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1939,64 +1939,76 @@ void CharacterController::update(float duration, bool animationOnly) bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying; bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; CreatureStats &stats = cls.getCreatureStats(mPtr); + Movement& movementSettings = cls.getMovementSettings(mPtr); //Force Jump Logic - bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5); if(!inwater && !flying && solid) { //Force Jump if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) - { - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } + movementSettings.mPosition[2] = onground ? 1 : 0; //Force Move Jump, only jump if they're otherwise moving if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) - { - - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } + movementSettings.mPosition[2] = onground ? 1 : 0; } - osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); + osg::Vec3f rot = cls.getRotationVector(mPtr); + osg::Vec3f vec(movementSettings.asVec3()); vec.normalize(); - if(mHitState != CharState_None && mJumpState == JumpState_None) - vec = osg::Vec3f(0.f, 0.f, 0.f); - osg::Vec3f rot = cls.getRotationVector(mPtr); - - speed = cls.getSpeed(mPtr); - float analogueMult = 1.f; - if(isPlayer) + float analogueMult = 1.0f; + if (isPlayer) { + // TODO: Move this code to mwinput. // Joystick analogue movement. - float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]); - float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]); - analogueMult = ((xAxis > yAxis) ? xAxis : yAxis); - - // If Strafing, our max speed is slower so multiply by X axis instead. - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) - analogueMult = xAxis; + float xAxis = std::abs(movementSettings.mPosition[0]); + float yAxis = std::abs(movementSettings.mPosition[1]); + analogueMult = std::max(xAxis, yAxis); // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. if(!isrunning && !sneak && !flying && analogueMult <= 0.5f) analogueMult *= 2.f; + + movementSettings.mSpeedFactor = analogueMult; } - speed *= analogueMult; + float effectiveRotation = rot.z(); + static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); + static const float turnToMovementDirectionSpeedCoef = Settings::Manager::getFloat("turn to movement direction speed coef", "Game"); + if (turnToMovementDirection && !(isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson())) + { + float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); + movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater) + && std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f); + if (movementSettings.mIsStrafing) + targetMovementAngle = 0; + float delta = targetMovementAngle - stats.getSideMovementAngle(); + float cosDelta = cosf(delta); + movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn + float maxDelta = turnToMovementDirectionSpeedCoef * osg::PI * duration * (2.5f - cosDelta); + delta = std::min(delta, maxDelta); + delta = std::max(delta, -maxDelta); + stats.setSideMovementAngle(stats.getSideMovementAngle() + delta); + effectiveRotation += delta; + } + else + movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; + + mAnimation->setLegsYawRadians(stats.getSideMovementAngle()); + if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater) + mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2); + else + mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4); + + speed = cls.getSpeed(mPtr); vec.x() *= speed; vec.y() *= speed; + if(mHitState != CharState_None && mJumpState == JumpState_None) + vec = osg::Vec3f(); + CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; JumpingState jumpstate = JumpState_None; @@ -2158,7 +2170,7 @@ void CharacterController::update(float duration, bool animationOnly) inJump = false; - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) + if (movementSettings.mIsStrafing) { if(vec.x() > 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) @@ -2169,18 +2181,18 @@ void CharacterController::update(float duration, bool animationOnly) : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); } - else if(vec.y() != 0.0f) + else if (vec.length2() > 0.0f) { - if(vec.y() > 0.0f) + if (vec.y() >= 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) : (sneak ? CharState_SneakForward : (isrunning ? CharState_RunForward : CharState_WalkForward))); - else if(vec.y() < 0.0f) + else movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - else if(rot.z() != 0.0f) + else if (effectiveRotation != 0.0f) { // Do not play turning animation for player if rotation speed is very slow. // Actual threshold should take framerate in account. @@ -2193,9 +2205,9 @@ void CharacterController::update(float duration, bool animationOnly) bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr)) { - if(rot.z() > rotationThreshold) + if(effectiveRotation > rotationThreshold) movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - else if(rot.z() < -rotationThreshold) + else if(effectiveRotation < -rotationThreshold) movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; } } @@ -2317,9 +2329,9 @@ void CharacterController::update(float duration, bool animationOnly) world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; + movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; if (movement.z() == 0.f) - cls.getMovementSettings(mPtr).mPosition[2] = 0; + 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. @@ -2355,15 +2367,11 @@ void CharacterController::update(float duration, bool animationOnly) if(speed > 0.f) { float l = moved.length(); - - if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || - (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) + if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2) moved.x() = movement.x(); - if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || - (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) + if (std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2) moved.y() = movement.y(); - if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || - (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) + if (std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) moved.z() = movement.z(); // but keep the original speed float newLength = moved.length(); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 0f11b8b2e..79b8e23de 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -23,7 +23,7 @@ namespace MWMechanics mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), - mDeathAnimation(-1), mTimeOfDeath(), mLevel (0) + mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index b35c1e3b6..5e91a1b5a 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -80,6 +80,9 @@ namespace MWMechanics MWWorld::TimeStamp mTimeOfDeath; + // The difference between view direction and lower body direction. + float mSideMovementAngle; + public: typedef std::pair SummonKey; // private: @@ -298,6 +301,9 @@ namespace MWMechanics void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); void removeCorprusSpell(const std::string& sourceId); + + float getSideMovementAngle() const { return mSideMovementAngle; } + void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } }; } diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp index cb9087359..86b970e60 100644 --- a/apps/openmw/mwmechanics/movement.hpp +++ b/apps/openmw/mwmechanics/movement.hpp @@ -11,12 +11,14 @@ namespace MWMechanics float mPosition[3]; float mRotation[3]; float mSpeedFactor; + bool mIsStrafing; Movement() { mPosition[0] = mPosition[1] = mPosition[2] = 0.0f; mRotation[0] = mRotation[1] = mRotation[2] = 0.0f; mSpeedFactor = 1.f; + mIsStrafing = false; } osg::Vec3f asVec3() diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 594713a1c..69136bac3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -621,6 +621,8 @@ namespace MWRender , mTextKeyListener(nullptr) , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) + , mUpperBodyYawRadians(0.f) + , mLegsYawRadians(0.f) , mHasMagicEffects(false) , mAlpha(1.f) { @@ -1334,13 +1336,36 @@ namespace MWRender updateEffects(); + const float epsilon = 0.001f; + float yawOffset = 0; + if (mRootController) + { + bool enable = std::abs(mLegsYawRadians) > epsilon; + mRootController->setEnabled(enable); + if (enable) + { + mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1))); + yawOffset = mLegsYawRadians; + } + } + if (mSpineController) + { + float yaw = mUpperBodyYawRadians - yawOffset; + bool enable = std::abs(yaw) > epsilon; + mSpineController->setEnabled(enable); + if (enable) + { + mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1))); + yawOffset = mUpperBodyYawRadians; + } + } if (mHeadController) { - const float epsilon = 0.001f; - bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(mHeadYawRadians) > epsilon); + float yaw = mHeadYawRadians - yawOffset; + bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon); mHeadController->setEnabled(enable); if (enable) - mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(mHeadYawRadians, osg::Vec3f(0,0,1))); + mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1))); } // Scripted animations should not cause movement @@ -1801,13 +1826,17 @@ namespace MWRender void Animation::addControllers() { - mHeadController = nullptr; + mHeadController = addRotateController("bip01 head"); + mSpineController = addRotateController("bip01 spine1"); + mRootController = addRotateController("bip01"); + } - NodeMap::const_iterator found = getNodeMap().find("bip01 head"); - if (found == getNodeMap().end()) - return; - - osg::MatrixTransform* node = found->second; + RotateController* Animation::addRotateController(std::string bone) + { + auto iter = getNodeMap().find(bone); + if (iter == getNodeMap().end()) + return nullptr; + osg::MatrixTransform* node = iter->second; bool foundKeyframeCtrl = false; osg::Callback* cb = node->getUpdateCallback(); @@ -1820,13 +1849,15 @@ namespace MWRender } cb = cb->getNestedCallback(); } - + // Without KeyframeController the orientation will not be reseted each frame, so + // RotateController shouldn't be used for such nodes. if (!foundKeyframeCtrl) - return; + return nullptr; - mHeadController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mHeadController); - mActiveControllers.emplace_back(node, mHeadController); + RotateController* controller = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(controller); + mActiveControllers.emplace_back(node, controller); + return controller; } void Animation::setHeadPitch(float pitchRadians) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index c53cf98a9..564952a90 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -267,8 +267,15 @@ protected: TextKeyListener* mTextKeyListener; osg::ref_ptr mHeadController; + osg::ref_ptr mSpineController; + osg::ref_ptr mRootController; float mHeadYawRadians; float mHeadPitchRadians; + float mUpperBodyYawRadians; + float mLegsYawRadians; + + RotateController* addRotateController(std::string bone); + bool mHasMagicEffects; osg::ref_ptr mGlowLight; @@ -477,6 +484,12 @@ public: virtual void setHeadYaw(float yawRadians); virtual float getHeadPitch() const; virtual float getHeadYaw() const; + + virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; } + virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; } + virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; } + virtual float getLegsYawRadians() const { return mLegsYawRadians; } + virtual void setAccurateAiming(bool enabled) {} virtual bool canBeHarvested() const { return false; } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index f5d63343b..251f7f593 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -244,6 +244,9 @@ namespace MWRender else mViewModeToggleQueued = false; + if (mTrackingPtr.getClass().isActor()) + mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0); + mFirstPersonView = !mFirstPersonView; processViewChange(); } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index b42924de0..3a7c9a8b4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -302,6 +302,12 @@ projectiles enchant multiplier = 0 # This means that unlike Morrowind you will be able to knock down actors using this effect. uncapped damage fatigue = false +# Turn lower body to movement direction. 'true' makes diagonal movement more realistic. +turn to movement direction = false + +# Turning speed multiplier. Makes difference only if 'turn to movement direction' is enabled. +turn to movement direction speed coef = 1.0 + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).