diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d68228904..44c8c96be 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -407,6 +407,8 @@ namespace MWBase virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; ///< get Line of Sight (morrowind stupid implementation) + virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0; + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; virtual int canRest() = 0; diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 596bf0e56..4b9c8988e 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -170,6 +170,14 @@ namespace MWClass virtual int getBaseGold(const MWWorld::Ptr& ptr) const; virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + + virtual bool canSwim (const MWWorld::Ptr &ptr) const { + return true; + } + + virtual bool canWalk (const MWWorld::Ptr &ptr) const { + return true; + } }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 335bc8702..d0555cdc3 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -31,10 +31,52 @@ namespace //chooses an attack depending on probability to avoid uniformity void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + + float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) + { + float len = (dirLen > 0.0f)? dirLen : dir.length(); + 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; + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offset) + { + if((to - from).length() >= PATHFIND_CAUTION_DIST || abs(from.z - to.z) <= PATHFIND_Z_REACH) + { + Ogre::Vector3 dir = to - from; + dir.z = 0; + dir.normalise(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + Ogre::Vector3 _from = from + dir*offset + Ogre::Vector3::UNIT_Z * verticalOffset; + + // cast up-down ray and find height in world space of hit + float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); + + if(abs(from.z - h) <= PATHFIND_Z_REACH) + return true; + } + + return false; + } } namespace MWMechanics { + static const float MAX_ATTACK_DURATION = 0.35f; static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp @@ -45,16 +87,16 @@ namespace MWMechanics mTimerCombatMove(0), mFollowTarget(false), mReadyToAttack(false), - mStrike(false), + mAttack(false), mCombatMove(false), - mBackOffDoor(false), - mRotate(false), mMovement(), + mForceNoShortcut(false), + mShortcutFailPos(), + mBackOffDoor(false), mCell(NULL), mDoorIter(actor.getCell()->get().mList.end()), mDoors(actor.getCell()->get()), - mDoorCheckDuration(0), - mTargetAngle(0) + mDoorCheckDuration(0) { } @@ -125,17 +167,23 @@ namespace MWMechanics mCombatMove = false; } } - + actor.getClass().getMovementSettings(actor) = mMovement; + actor.getClass().getMovementSettings(actor).mRotation[0] = 0; + actor.getClass().getMovementSettings(actor).mRotation[2] = 0; - if (mRotate) + if(mMovement.mRotation[2] != 0) + { + if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0; + } + + if(mMovement.mRotation[0] != 0) { - if (zTurn(actor, Ogre::Degree(mTargetAngle))) - mRotate = false; + if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0; } mTimerAttack -= duration; - actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); float tReaction = 0.25f; if(mTimerReact < tReaction) @@ -145,7 +193,7 @@ namespace MWMechanics } //Update with period = tReaction - + mTimerReact = 0; bool cellChange = mCell && (actor.getCell() != mCell); @@ -156,16 +204,16 @@ namespace MWMechanics //actual attacking logic //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f - float attackPeriod = 1.0f; + float attacksPeriod = 1.0f; if(mReadyToAttack) { - if(mTimerAttack <= -attackPeriod) + if(mTimerAttack <= -attacksPeriod) { //TODO: should depend on time between 'start' to 'min attack' //for better controlling of NPCs' attack strength. //Also it seems that this time is different for slash/thrust/chop - mTimerAttack = 0.35f * static_cast(rand())/RAND_MAX; - mStrike = true; + mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mAttack = true; //say a provoking combat phrase if (actor.getClass().isNpc()) @@ -180,30 +228,31 @@ namespace MWMechanics } } else if (mTimerAttack <= 0) - mStrike = false; + mAttack = false; } else { - mTimerAttack = -attackPeriod; - mStrike = false; + mTimerAttack = -attacksPeriod; + mAttack = false; } - const MWWorld::Class &cls = actor.getClass(); + const MWWorld::Class &actorCls = actor.getClass(); const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; float weapRange, weapSpeed = 1.0f; - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + actorCls.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - if (actor.getClass().hasInventoryStore(actor)) + // Get weapon characteristics + if (actorCls.hasInventoryStore(actor)) { - MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); + MWMechanics::DrawState_ state = actorCls.getCreatureStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + actorCls.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype); + MWMechanics::getActiveWeapon(actorCls.getCreatureStats(actor), actorCls.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { @@ -225,36 +274,27 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } - ESM::Position pos = actor.getRefData().getPosition(); /* * Some notes on meanings of variables: * - * rangeMelee: + * rangeAttack: * - * - Distance where attack using the actor's weapon is possible - * - longer for ranged weapons (obviously?) vs. melee weapons + * - Distance where attack using the actor's weapon is possible: + * longer for ranged weapons (obviously?) vs. melee weapons + * - Determined by weapon's reach parameter; hardcoded value + * for ranged weapon and for creatures * - Once within this distance mFollowTarget is triggered - * (TODO: check whether the follow logic still works for ranged - * weapons, since rangeCloseup is set to zero) - * - TODO: The variable name is confusing. It was ok when AiCombat only - * had melee weapons but now that ranged weapons are supported that is - * no longer the case. It should really be renamed to something - * like rangeStrike - alternatively, keep this name for melee - * weapons and use a different variable for tracking ranged weapon - * distance (rangeRanged maybe?) * - * rangeCloseup: + * rangeFollow: * * - Applies to melee weapons or hand to hand only (or creatures without * weapons) - * - Distance a little further away from the actor's weapon strike - * i.e. rangeCloseup > rangeMelee for melee weapons - * (the variable names make this simple concept counter-intuitive, - * something like rangeMelee > rangeStrike may be better) + * - Distance a little further away than the actor's weapon reach + * i.e. rangeFollow > rangeAttack for melee weapons + * - Hardcoded value (0 for ranged weapons) * - Once the target gets beyond this distance mFollowTarget is cleared * and a path to the target needs to be found - * - TODO: Possibly rename this variable to rangeMelee or even rangeFollow * * mFollowTarget: * @@ -263,58 +303,72 @@ namespace MWMechanics * available, since the default path without pathgrids is direct to * target even if LOS is not achieved) */ - float rangeMelee; - float rangeCloseUp; + + float rangeAttack; + float rangeFollow; bool distantCombat = false; - if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown) + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) { - rangeMelee = 1000; // TODO: should depend on archer skill - rangeCloseUp = 0; //doesn't needed when attacking from distance + rangeAttack = 1000; // TODO: should depend on archer skill + rangeFollow = 0; // not needed in ranged combat distantCombat = true; } else { - rangeMelee = weapRange; - rangeCloseUp = 300; + rangeAttack = weapRange; + rangeFollow = 300; } + + ESM::Position pos = actor.getRefData().getPosition(); + Ogre::Vector3 vActorPos(pos.pos); + Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos); + Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + + bool isStuck = false; + float speed = 0.0f; + if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) / 10.0f) + isStuck = true; + + mLastPos = pos; + + // check if actor can move along z-axis + bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); - Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]); - ESM::Position targetPos = mTarget.getRefData().getPosition(); - Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]); - Ogre::Vector3 vDir = vDest - vStart; - float distBetween = vDir.length(); + // determine vertical angle to target + // if actor can move along z-axis it will control movement dir + // if can't - it will control correct aiming + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget); + + vDirToTarget.z = 0; + float distToTarget = vDirToTarget.length(); // (within strike dist) || (not quite strike dist while following) - if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) ) + if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) { //Melee and Close-up combat - vDir.z = 0; - float dirLen = vDir.length(); - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); - mRotate = true; + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); - //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); // (not quite strike dist while following) - if (mFollowTarget && distBetween > rangeMelee) + if (mFollowTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } else // (within strike dist) { - //Melee: stop running and attack mMovement.mPosition[1] = 0; - // When attacking with a weapon, choose between slash, thrust or chop - if (actor.getClass().hasInventoryStore(actor)) - chooseBestAttack(weapon, mMovement); + // set slash/thrust/chop attack + if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement); if(mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; mCombatMove = true; } - else if(actor.getClass().isNpc() && (!distantCombat || (distantCombat && rangeMelee/5))) + // only NPCs are smart enough to use dodge movements + else if(actorCls.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability if(static_cast(rand())/RAND_MAX < 0.25) @@ -325,7 +379,7 @@ namespace MWMechanics } } - if(distantCombat && distBetween < rangeMelee/4) + if(distantCombat && distToTarget < rangeAttack/4) { mMovement.mPosition[1] = -1; } @@ -335,56 +389,105 @@ namespace MWMechanics mFollowTarget = true; } } - else + else // remote pathfinding { - //target is at far distance: build path to target - mFollowTarget = false; - - buildNewPath(actor); //may fail to build a path, check before use + bool preferShortcut = false; + bool inLOS; + + if(mReadyToAttack) isStuck = false; + + // check if shortcut is available + if(!isStuck + && (!mForceNoShortcut + || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST) + && (inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget))) + { + if(speed == 0.0f) speed = actorCls.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; // *2 - for reliability + preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); + } - //delete visited path node - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); + // don't use pathgrid when actor can move in 3 dimensions + if(canMoveByZ) preferShortcut = true; - //if no new path leave mTargetAngle unchanged - if(!mPathFinder.getPath().empty()) + if(preferShortcut) { - //try shortcut - if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); - else - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - mRotate = true; + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + mForceNoShortcut = false; + mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; + mPathFinder.clearPath(); + } + else // if shortcut failed stick to path grid + { + if(!isStuck && mShortcutFailPos.pos[0] == 0.0f && mShortcutFailPos.pos[1] == 0.0f && mShortcutFailPos.pos[2] == 0.0f) + { + mForceNoShortcut = true; + mShortcutFailPos = pos; + } + + mFollowTarget = false; + + buildNewPath(actor); //may fail to build a path, check before use + + //delete visited path node + mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]); + + // This works on the borders between the path grid and areas with no waypoints. + if(inLOS && mPathFinder.getPath().size() > 1) + { + // get point just before target + std::list::iterator pntIter = --mPathFinder.getPath().end(); + --pntIter; + 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()) + { + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + preferShortcut = true; + } + } + + // if there is no new path, then go straight on target + if(!preferShortcut) + { + if(!mPathFinder.getPath().empty()) + mMovement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + else + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + } } mMovement.mPosition[1] = 1; mReadyToAttack = false; } - if(distBetween > rangeMelee) + if(distToTarget > rangeAttack && !distantCombat) { //special run attack; it shouldn't affect melee combat tactics - if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1) + if(actorCls.getMovementSettings(actor).mPosition[1] == 1) { //check if actor can overcome the distance = distToTarget - attackerWeapRange //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) //then start attacking - float speed1 = cls.getSpeed(actor); + float speed1 = actorCls.getSpeed(actor); float speed2 = mTarget.getClass().getSpeed(mTarget); if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) speed2 = 0; - float s1 = distBetween - weapRange; + float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; - float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; - if(mTimerAttack <= -attackPeriod) + if(mTimerAttack <= -attacksPeriod) { - mTimerAttack = 0.3f*static_cast(rand())/RAND_MAX; - mStrike = true; + mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mAttack = true; } } } @@ -394,7 +497,7 @@ namespace MWMechanics // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? - if((distBetween > rangeMelee || mFollowTarget) && + if((distToTarget > rangeAttack || mFollowTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { // first check if we're walking into a door @@ -406,12 +509,11 @@ namespace MWMechanics // Check all the doors in this cell mDoors = cell->get(); // update mDoorIter = mDoors.mList.begin(); - Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) { MWWorld::LiveCellRef& ref = *mDoorIter; float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility - if(actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr && + if(vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr && ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening { //std::cout<<"closed door id \""<& ref = *mDoorIter; - Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility // TODO: add reaction to checking open doors if(mBackOffDoor && - actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr) + vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr) { mMovement.mPosition[1] = -0.2; // back off, but slowly } @@ -454,47 +555,38 @@ namespace MWMechanics ref.mData.getLocalRotation().rot[2] >= 1) { mDoorIter = mDoors.mList.end(); - mBackOffDoor = false; + mBackOffDoor = false; //std::cout<<"open door id \""<getCell()->isExterior(); - if (isOutside) - targetPosThreshold = 300; - else - targetPosThreshold = 100; + float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300 : 100; - if((dist < 0) || (dist > targetPosThreshold)) + //construct new path only if target has moved away more than on [targetPosThreshold] + if(dist > targetPosThreshold) { - //construct new path only if target has moved away more than on ESM::Position pos = actor.getRefData().getPosition(); ESM::Pathgrid::Point start; @@ -502,17 +594,18 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + ESM::Pathgrid::Point dest; + dest.mX = newPathTarget.x; + dest.mY = newPathTarget.y; + dest.mZ = newPathTarget.z; + if(!mPathFinder.isPathConstructed()) - mPathFinder.buildPath(start, dest, actor.getCell(), isOutside); + mPathFinder.buildPath(start, dest, actor.getCell(), false); else { PathFinder newPathFinder; - newPathFinder.buildPath(start, dest, actor.getCell(), isOutside); + newPathFinder.buildPath(start, dest, actor.getCell(), false); - //TO EXPLORE: - //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, - //not the actual path length. Here we should know if the new path is actually more effective. - //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) if(!mPathFinder.getPath().empty()) { newPathFinder.syncStart(mPathFinder.getPath()); diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 5a7cc8485..30b72acd9 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -39,17 +39,16 @@ 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; - // AiCombat states - bool mReadyToAttack, mStrike; + bool mReadyToAttack, mAttack; bool mFollowTarget; bool mCombatMove; bool mBackOffDoor; - bool mRotate; + bool mForceNoShortcut; + ESM::Position mShortcutFailPos; + + ESM::Position mLastPos; MWMechanics::Movement mMovement; MWWorld::Ptr mTarget; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 4cabaee09..8ad2b4a67 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1010,6 +1010,7 @@ 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; @@ -1175,7 +1176,7 @@ void CharacterController::update(float duration) } else { - if(!(vec.z > 0.0f)) + if(!(vec.z > 0.0f)) mJumpState = JumpState_None; vec.z = 0.0f; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 11d436700..cd03939bd 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -325,22 +325,22 @@ namespace MWMechanics } // used by AiCombat, see header for the rationale - void PathFinder::syncStart(const std::list &path) + bool PathFinder::syncStart(const std::list &path) { if (mPath.size() < 2) - return; //nothing to pop + return false; //nothing to pop + std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); if( (*iter).mX == oldStart->mX && (*iter).mY == oldStart->mY - && (*iter).mZ == oldStart->mZ - && (*iter).mAutogenerated == oldStart->mAutogenerated - && (*iter).mConnectionNum == oldStart->mConnectionNum ) + && (*iter).mZ == oldStart->mZ) { mPath.pop_front(); + return true; } - + return false; } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index eb093ad69..97bb88c52 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -62,11 +62,15 @@ namespace MWMechanics return mPath; } - // When first point of newly created path is the nearest to actor point, - // then a situation can occure when this point is undesirable - // (if the 2nd point of new path == the 1st point of old path) - // This functions deletes that point. - void syncStart(const std::list &path); + /** 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 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 + */ + bool syncStart(const std::list &path); void addPointToPath(ESM::Pathgrid::Point &point) { diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index 054107f73..9f887f5ca 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -10,9 +10,9 @@ namespace MWMechanics { -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon) +bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, Ogre::Degree epsilon) { - Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); + Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[axis]); Ogre::Radian diff (targetAngle - currentAngle); if (diff >= Ogre::Degree(180)) { @@ -30,13 +30,17 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree eps if (absDiff < epsilon) return true; - // Max. speed of 10 radian per sec - Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration(); + Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration(); if (absDiff > limit) diff = Ogre::Math::Sign(diff) * limit; - actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians(); + actor.getClass().getMovementSettings(actor).mRotation[axis] = diff.valueRadians(); return false; } +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon) +{ + return smoothTurn(actor, targetAngle, 2, epsilon); +} + } diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 4042b5412..91df49f0d 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -10,11 +10,17 @@ class Ptr; namespace MWMechanics { +// Max rotating speed, radian/sec +const Ogre::Radian MAX_VEL_ANGULAR(10); + /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon = Ogre::Degree(0.5)); +bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, + Ogre::Degree epsilon = Ogre::Degree(0.5)); + } #endif diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index d1f328f06..247c0d4bd 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -313,8 +313,9 @@ namespace MWWorld // NOTE: stepMove modifies newPosition if successful if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) { - // don't let slaughterfish move out of water after stepMove - if(ptr.getClass().canSwim(ptr) && newPosition.z > (waterlevel - halfExtents.z * 0.5)) + // don't let pure water creatures move out of water after stepMove + if((ptr.getClass().canSwim(ptr) && !ptr.getClass().canWalk(ptr)) + && newPosition.z > (waterlevel - halfExtents.z * 0.5)) newPosition = oldPosition; else // Only on the ground if there's gravity isOnGround = !(newPosition.z < waterlevel || isFlying); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4c1a8b1c0..de9c8f04a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1909,7 +1909,7 @@ namespace MWWorld out.push_back(searchPtrViaHandle(*it)); } } - + bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled()) @@ -1927,6 +1927,19 @@ namespace MWWorld return false; } + float World::getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) + { + btVector3 btFrom(from.x, from.y, from.z); + btVector3 btTo = btVector3(dir.x, dir.y, dir.z); + btTo.normalize(); + btTo = btFrom + btTo * maxDist; + + std::pair result = mPhysEngine->rayTest(btFrom, btTo, false); + + if(result.second == -1) return maxDist; + else return result.second*(btTo-btFrom).length(); + } + void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 40ba8fa82..645330683 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -510,6 +510,8 @@ namespace MWWorld virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); ///< get Line of Sight (morrowind stupid implementation) + virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist); + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); virtual int canRest();