diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 807e08c1d..8a3c23aef 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -38,6 +38,13 @@ namespace return Ogre::Radian( Ogre::Math::ACos(dir.y / len) * sgn(Ogre::Math::ASin(dir.x / len)) ).valueDegrees(); } + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) + { + float len = (dirLen >= 0.0f)? dirLen : dir.length(); + return Ogre::Radian(-Ogre::Math::ASin(dir.z / len)).valueDegrees(); + } + + const float PATHFIND_Z_REACH = 50.0f; // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid const float PATHFIND_CAUTION_DIST = 500.0f; @@ -79,7 +86,6 @@ namespace MWMechanics mStrike(false), mCombatMove(false), mMovement(), - mTargetAngle(0), mForceNoShortcut(false), mShortcutFailPos() { @@ -108,13 +114,18 @@ namespace MWMechanics } actor.getClass().getMovementSettings(actor) = mMovement; + actor.getClass().getMovementSettings(actor).mRotation[0] = 0; + actor.getClass().getMovementSettings(actor).mRotation[2] = 0; - if(actor.getRefData().getPosition().rot[2] != mTargetAngle) + if(mMovement.mRotation[2] != 0) { - zTurn(actor, Ogre::Degree(mTargetAngle)); + if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0; + } + + if(mMovement.mRotation[0] != 0) + { + if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0; } - - mTimerAttack -= duration; actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); @@ -225,7 +236,7 @@ namespace MWMechanics bool isStuck = false; float speed = 0.0f; - if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = cls.getSpeed(actor)) / 10.0f) + if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = cls.getSpeed(actor)) / 10.0f) isStuck = true; mLastPos = pos; @@ -235,31 +246,28 @@ namespace MWMechanics if(canMoveByZ = ((actor.getClass().isNpc() || actor.getClass().canSwim(actor)) && MWBase::Environment::get().getWorld()->isSwimming(actor)) || (actor.getClass().canFly(actor) && MWBase::Environment::get().getWorld()->isFlying(actor))) { - float zToTarget = vTargetPos.z - pos.pos[2]; - - mMovement.mPosition[1] = sqrt(distToTarget*distToTarget - zToTarget*zToTarget); // XY-plane vec length - mMovement.mPosition[2] = zToTarget; + // determine vertical angle to target + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); } if(distToTarget < rangeMelee || (distToTarget <= rangeCloseUp && mFollowTarget && !isStuck) ) { //Melee and Close-up combat vDirToTarget.z = 0; - mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); if (mFollowTarget && distToTarget > rangeMelee) { //Close-up combat: just run up on target - if(!canMoveByZ) mMovement.mPosition[1] = 1; + mMovement.mPosition[1] = 1; } else { //Melee: stop running and attack mMovement.mPosition[1] = 0; - if(canMoveByZ) mMovement.mPosition[2] = 0; // When attacking with a weapon, choose between slash, thrust or chop - if (actor.getClass().hasInventoryStore(actor)) + if (mStrike && actor.getClass().hasInventoryStore(actor)) chooseBestAttack(weapon, mMovement); if(mMovement.mPosition[0] || mMovement.mPosition[1]) @@ -304,15 +312,15 @@ namespace MWMechanics if(speed == 0.0f) speed = cls.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; - //if(actor.getRefData().getPosition().rot[2] != mTargetAngle) preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } + // don't use pathgrid when actor can move in 3 dimensions if(canMoveByZ) preferShortcut = true; if(preferShortcut) { - mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); mForceNoShortcut = false; mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); @@ -338,12 +346,12 @@ namespace MWMechanics // get point just before target std::list::iterator pntIter = --mPathFinder.getPath().end(); --pntIter; - Ogre::Vector3 vBeforeTarget = Ogre::Vector3(pntIter->mX, pntIter->mY, pntIter->mZ); + Ogre::Vector3 vBeforeTarget = Ogre::Vector3(pntIter->mX, pntIter->mY, pntIter->mZ); // if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target if(distToTarget <= (vTargetPos - vBeforeTarget).length()) { - mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); preferShortcut = true; } } @@ -352,13 +360,13 @@ namespace MWMechanics if(!preferShortcut) { if(!mPathFinder.getPath().empty()) - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mMovement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); else - mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); } } - if(!canMoveByZ) mMovement.mPosition[1] = 1; + mMovement.mPosition[1] = 1; mReadyToAttack = false; } @@ -392,8 +400,6 @@ namespace MWMechanics } } - actor.getClass().getMovementSettings(actor) = mMovement; - return false; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index b71dd9cf0..8130df0e8 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -36,10 +36,6 @@ namespace MWMechanics // when mCombatMove is true float mTimerCombatMove; - // the z rotation angle (degrees) we want to reach - // used every frame when mRotate is true - float mTargetAngle; - bool mReadyToAttack, mStrike; bool mFollowTarget; bool mCombatMove; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2c63d5a14..21f2ed883 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1014,11 +1014,11 @@ void CharacterController::update(float duration) if(mHitState != CharState_None && mJumpState == JumpState_None) vec = Ogre::Vector3(0.0f); Ogre::Vector3 rot = cls.getRotationVector(mPtr); + mMovementSpeed = cls.getSpeed(mPtr); vec.x *= mMovementSpeed; vec.y *= mMovementSpeed; - if(inwater || flying) vec.z *= mMovementSpeed; CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; @@ -1085,8 +1085,7 @@ void CharacterController::update(float duration) fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); cls.getCreatureStats(mPtr).setFatigue(fatigue); - // kind of hack, reason - creatures can move along z when in water/flying - if(sneak || ((inwater || flying) && mPtr.getRefData().getHandle() == "player")) + if(sneak || inwater || flying) vec.z = 0.0f; if (inwater || flying) @@ -1121,7 +1120,7 @@ void CharacterController::update(float duration) vec.y *= mult; vec.z = 0.0f; } - else if(!inwater && !flying && vec.z > 0.0f && mJumpState == JumpState_None) + else if(vec.z > 0.0f && mJumpState == JumpState_None) { // Started a jump. float z = cls.getJump(mPtr); @@ -1183,7 +1182,7 @@ void CharacterController::update(float duration) { if(!(vec.z > 0.0f)) mJumpState = JumpState_None; - if(!inwater && !flying) vec.z = 0.0f; + vec.z = 0.0f; if(std::abs(vec.x/2.0f) > std::abs(vec.y)) { diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 86fa3b6bc..ff3bf06fa 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -67,7 +67,7 @@ namespace MWMechanics /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times @note If the first point is chosen as the nearest one - the cituation can occure when the 1st point of the new path is undesirable + the situation can occur when the 1st point of the new path is undesirable (i.e. the 2nd point of new path == the 1st point of old path). @param path - old path @return true if such point was found and deleted diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index 7646e832d..d10733b36 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -10,6 +10,35 @@ namespace MWMechanics { +bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis) +{ + Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[axis]); + Ogre::Radian diff (targetAngle - currentAngle); + if (diff >= Ogre::Degree(180)) + { + // Turning the other way would be a better idea + diff = diff-Ogre::Degree(360); + } + else if (diff <= Ogre::Degree(-180)) + { + diff = Ogre::Degree(360)-diff; + } + Ogre::Radian absDiff = Ogre::Math::Abs(diff); + + // The turning animation actually moves you slightly, so the angle will be wrong again. + // Use epsilon to prevent jerkiness. + const Ogre::Degree epsilon (0.5); + if (absDiff < epsilon) + return true; + + Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration(); + if (absDiff > limit) + diff = Ogre::Math::Sign(diff) * limit; + + actor.getClass().getMovementSettings(actor).mRotation[axis] = diff.valueRadians(); + return false; +} + bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) { Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 9bdf7d4a3..1c00b508d 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -17,6 +17,8 @@ const Ogre::Radian MAX_VEL_ANGULAR(10); /// @return have we reached the target angle? bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle); +bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis); + } #endif