1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-28 11:39:40 +00:00

Improved strafe movement

This commit is contained in:
Petr Mikheev 2020-06-22 02:03:38 +02:00
parent d3bd67d747
commit b4c699348f
10 changed files with 142 additions and 71 deletions

View file

@ -536,10 +536,11 @@ namespace MWClass
moveSpeed = getSwimSpeed(ptr); moveSpeed = getSwimSpeed(ptr);
else else
moveSpeed = getWalkSpeed(ptr); 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; return moveSpeed;
} }

View file

@ -966,13 +966,14 @@ namespace MWClass
moveSpeed = getRunSpeed(ptr); moveSpeed = getRunSpeed(ptr);
else else
moveSpeed = getWalkSpeed(ptr); 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) if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing)
moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); 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; return moveSpeed;
} }

View file

@ -1939,64 +1939,76 @@ void CharacterController::update(float duration, bool animationOnly)
bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying; bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying;
bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying;
CreatureStats &stats = cls.getCreatureStats(mPtr); CreatureStats &stats = cls.getCreatureStats(mPtr);
Movement& movementSettings = cls.getMovementSettings(mPtr);
//Force Jump Logic //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) if(!inwater && !flying && solid)
{ {
//Force Jump //Force Jump
if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump))
{ movementSettings.mPosition[2] = onground ? 1 : 0;
if(onground)
{
cls.getMovementSettings(mPtr).mPosition[2] = 1;
}
else
cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
//Force Move Jump, only jump if they're otherwise moving //Force Move Jump, only jump if they're otherwise moving
if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving)
{ movementSettings.mPosition[2] = onground ? 1 : 0;
if(onground)
{
cls.getMovementSettings(mPtr).mPosition[2] = 1;
}
else
cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
} }
osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); osg::Vec3f rot = cls.getRotationVector(mPtr);
osg::Vec3f vec(movementSettings.asVec3());
vec.normalize(); vec.normalize();
if(mHitState != CharState_None && mJumpState == JumpState_None) float analogueMult = 1.0f;
vec = osg::Vec3f(0.f, 0.f, 0.f); if (isPlayer)
osg::Vec3f rot = cls.getRotationVector(mPtr);
speed = cls.getSpeed(mPtr);
float analogueMult = 1.f;
if(isPlayer)
{ {
// TODO: Move this code to mwinput.
// Joystick analogue movement. // Joystick analogue movement.
float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]); float xAxis = std::abs(movementSettings.mPosition[0]);
float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]); float yAxis = std::abs(movementSettings.mPosition[1]);
analogueMult = ((xAxis > yAxis) ? xAxis : yAxis); analogueMult = std::max(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;
// Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. // 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) if(!isrunning && !sneak && !flying && analogueMult <= 0.5f)
analogueMult *= 2.f; 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.x() *= speed;
vec.y() *= speed; vec.y() *= speed;
if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = osg::Vec3f();
CharacterState movestate = CharState_None; CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle; CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None; JumpingState jumpstate = JumpState_None;
@ -2158,7 +2170,7 @@ void CharacterController::update(float duration, bool animationOnly)
inJump = false; inJump = false;
if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) if (movementSettings.mIsStrafing)
{ {
if(vec.x() > 0.0f) if(vec.x() > 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight)
@ -2169,18 +2181,18 @@ void CharacterController::update(float duration, bool animationOnly)
: (sneak ? CharState_SneakLeft : (sneak ? CharState_SneakLeft
: (isrunning ? CharState_RunLeft : CharState_WalkLeft))); : (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) movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward)
: (sneak ? CharState_SneakForward : (sneak ? CharState_SneakForward
: (isrunning ? CharState_RunForward : CharState_WalkForward))); : (isrunning ? CharState_RunForward : CharState_WalkForward)));
else if(vec.y() < 0.0f) else
movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack)
: (sneak ? CharState_SneakBack : (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack))); : (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. // Do not play turning animation for player if rotation speed is very slow.
// Actual threshold should take framerate in account. // 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(); bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr)) if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr))
{ {
if(rot.z() > rotationThreshold) if(effectiveRotation > rotationThreshold)
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
else if(rot.z() < -rotationThreshold) else if(effectiveRotation < -rotationThreshold)
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; 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)); world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
movement = vec; 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) 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 // 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. // 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) if(speed > 0.f)
{ {
float l = moved.length(); float l = moved.length();
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2)
if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) ||
(movement.x() > 0.0f && movement.x() > moved.x()*2.0f))
moved.x() = movement.x(); moved.x() = movement.x();
if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || if (std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2)
(movement.y() > 0.0f && movement.y() > moved.y()*2.0f))
moved.y() = movement.y(); moved.y() = movement.y();
if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || if (std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2)
(movement.z() > 0.0f && movement.z() > moved.z()*2.0f))
moved.z() = movement.z(); moved.z() = movement.z();
// but keep the original speed // but keep the original speed
float newLength = moved.length(); float newLength = moved.length();

View file

@ -23,7 +23,7 @@ namespace MWMechanics
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
mHitRecovery(false), mBlock(false), mMovementFlags(0), mHitRecovery(false), mBlock(false), mMovementFlags(0),
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), 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) for (int i=0; i<4; ++i)
mAiSettings[i] = 0; mAiSettings[i] = 0;

View file

@ -80,6 +80,9 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeOfDeath; MWWorld::TimeStamp mTimeOfDeath;
// The difference between view direction and lower body direction.
float mSideMovementAngle;
public: public:
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID> typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
private: private:
@ -298,6 +301,9 @@ namespace MWMechanics
void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); void addCorprusSpell(const std::string& sourceId, CorprusStats& stats);
void removeCorprusSpell(const std::string& sourceId); void removeCorprusSpell(const std::string& sourceId);
float getSideMovementAngle() const { return mSideMovementAngle; }
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
}; };
} }

View file

@ -11,12 +11,14 @@ namespace MWMechanics
float mPosition[3]; float mPosition[3];
float mRotation[3]; float mRotation[3];
float mSpeedFactor; float mSpeedFactor;
bool mIsStrafing;
Movement() Movement()
{ {
mPosition[0] = mPosition[1] = mPosition[2] = 0.0f; mPosition[0] = mPosition[1] = mPosition[2] = 0.0f;
mRotation[0] = mRotation[1] = mRotation[2] = 0.0f; mRotation[0] = mRotation[1] = mRotation[2] = 0.0f;
mSpeedFactor = 1.f; mSpeedFactor = 1.f;
mIsStrafing = false;
} }
osg::Vec3f asVec3() osg::Vec3f asVec3()

View file

@ -621,6 +621,8 @@ namespace MWRender
, mTextKeyListener(nullptr) , mTextKeyListener(nullptr)
, mHeadYawRadians(0.f) , mHeadYawRadians(0.f)
, mHeadPitchRadians(0.f) , mHeadPitchRadians(0.f)
, mUpperBodyYawRadians(0.f)
, mLegsYawRadians(0.f)
, mHasMagicEffects(false) , mHasMagicEffects(false)
, mAlpha(1.f) , mAlpha(1.f)
{ {
@ -1334,13 +1336,36 @@ namespace MWRender
updateEffects(); 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) if (mHeadController)
{ {
const float epsilon = 0.001f; float yaw = mHeadYawRadians - yawOffset;
bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(mHeadYawRadians) > epsilon); bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon);
mHeadController->setEnabled(enable); mHeadController->setEnabled(enable);
if (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 // Scripted animations should not cause movement
@ -1801,13 +1826,17 @@ namespace MWRender
void Animation::addControllers() void Animation::addControllers()
{ {
mHeadController = nullptr; mHeadController = addRotateController("bip01 head");
mSpineController = addRotateController("bip01 spine1");
mRootController = addRotateController("bip01");
}
NodeMap::const_iterator found = getNodeMap().find("bip01 head"); RotateController* Animation::addRotateController(std::string bone)
if (found == getNodeMap().end()) {
return; auto iter = getNodeMap().find(bone);
if (iter == getNodeMap().end())
osg::MatrixTransform* node = found->second; return nullptr;
osg::MatrixTransform* node = iter->second;
bool foundKeyframeCtrl = false; bool foundKeyframeCtrl = false;
osg::Callback* cb = node->getUpdateCallback(); osg::Callback* cb = node->getUpdateCallback();
@ -1820,13 +1849,15 @@ namespace MWRender
} }
cb = cb->getNestedCallback(); cb = cb->getNestedCallback();
} }
// Without KeyframeController the orientation will not be reseted each frame, so
// RotateController shouldn't be used for such nodes.
if (!foundKeyframeCtrl) if (!foundKeyframeCtrl)
return; return nullptr;
mHeadController = new RotateController(mObjectRoot.get()); RotateController* controller = new RotateController(mObjectRoot.get());
node->addUpdateCallback(mHeadController); node->addUpdateCallback(controller);
mActiveControllers.emplace_back(node, mHeadController); mActiveControllers.emplace_back(node, controller);
return controller;
} }
void Animation::setHeadPitch(float pitchRadians) void Animation::setHeadPitch(float pitchRadians)

View file

@ -267,8 +267,15 @@ protected:
TextKeyListener* mTextKeyListener; TextKeyListener* mTextKeyListener;
osg::ref_ptr<RotateController> mHeadController; osg::ref_ptr<RotateController> mHeadController;
osg::ref_ptr<RotateController> mSpineController;
osg::ref_ptr<RotateController> mRootController;
float mHeadYawRadians; float mHeadYawRadians;
float mHeadPitchRadians; float mHeadPitchRadians;
float mUpperBodyYawRadians;
float mLegsYawRadians;
RotateController* addRotateController(std::string bone);
bool mHasMagicEffects; bool mHasMagicEffects;
osg::ref_ptr<SceneUtil::LightSource> mGlowLight; osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
@ -477,6 +484,12 @@ public:
virtual void setHeadYaw(float yawRadians); virtual void setHeadYaw(float yawRadians);
virtual float getHeadPitch() const; virtual float getHeadPitch() const;
virtual float getHeadYaw() 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 void setAccurateAiming(bool enabled) {}
virtual bool canBeHarvested() const { return false; } virtual bool canBeHarvested() const { return false; }

View file

@ -244,6 +244,9 @@ namespace MWRender
else else
mViewModeToggleQueued = false; mViewModeToggleQueued = false;
if (mTrackingPtr.getClass().isActor())
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView; mFirstPersonView = !mFirstPersonView;
processViewChange(); processViewChange();
} }

View file

@ -302,6 +302,12 @@ projectiles enchant multiplier = 0
# This means that unlike Morrowind you will be able to knock down actors using this effect. # This means that unlike Morrowind you will be able to knock down actors using this effect.
uncapped damage fatigue = false 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] [General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).