From 4e7ee970504334321c15654ce2ecceddcebfe783 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Mon, 14 Apr 2014 17:18:29 +0400 Subject: [PATCH 1/8] fix for windows builds --- apps/openmw/mwgui/savegamedialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index caa082646..3ff583264 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -259,7 +259,11 @@ namespace MWGui timeinfo = localtime(&time); // Use system/environment locale settings for datetime formatting +#if defined(_WIN32) || defined(__WINDOWS__) + setlocale(LC_TIME, ""); +#else std::setlocale(LC_TIME, ""); +#endif const int size=1024; char buffer[size]; From f811abb752efdd8306c081df67d92edd57d86728 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 20 Apr 2014 20:35:07 +0400 Subject: [PATCH 2/8] pathgrid shortcutting extended --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwmechanics/aicombat.cpp | 204 ++++++++++++++++-------- apps/openmw/mwmechanics/aicombat.hpp | 5 +- apps/openmw/mwmechanics/pathfinding.cpp | 11 +- apps/openmw/mwmechanics/pathfinding.hpp | 13 +- apps/openmw/mwmechanics/steering.cpp | 3 +- apps/openmw/mwmechanics/steering.hpp | 3 + apps/openmw/mwworld/worldimp.cpp | 15 +- apps/openmw/mwworld/worldimp.hpp | 2 + 9 files changed, 181 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f03a9197d..98ec9cfce 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/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index fd2fa61ba..84798ca02 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -31,6 +31,40 @@ 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(); + } + + 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 @@ -44,9 +78,10 @@ namespace MWMechanics mReadyToAttack(false), mStrike(false), mCombatMove(false), - mRotate(false), mMovement(), - mTargetAngle(0) + mTargetAngle(0), + mForceNoShortcut(false), + mShortcutFailPos() { } @@ -71,13 +106,12 @@ namespace MWMechanics mCombatMove = false; } } - + actor.getClass().getMovementSettings(actor) = mMovement; - if (mRotate) + if(actor.getRefData().getPosition().rot[2] != mTargetAngle) { - if (zTurn(actor, Ogre::Degree(mTargetAngle))) - mRotate = false; + zTurn(actor, Ogre::Degree(mTargetAngle)); } @@ -92,7 +126,7 @@ namespace MWMechanics } //Update with period = tReaction - + mTimerReact = 0; //actual attacking logic @@ -136,6 +170,7 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + // Get weapon characteristics if (actor.getClass().hasInventoryStore(actor)) { MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); @@ -166,15 +201,13 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } - ESM::Position pos = actor.getRefData().getPosition(); - float rangeMelee; float rangeCloseUp; bool distantCombat = false; 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 + rangeCloseUp = 0; // not needed in ranged combat distantCombat = true; } else @@ -182,23 +215,27 @@ namespace MWMechanics rangeMelee = weapRange; rangeCloseUp = 300; } + + ESM::Position pos = actor.getRefData().getPosition(); + Ogre::Vector3 vActorPos(pos.pos); + Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos); + Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + float distToTarget = vDirToTarget.length(); - 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(); + bool isStuck = false; + float speed = 0.0f; + if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = cls.getSpeed(actor)) / 10.0f) + isStuck = true; - if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) ) + mLastPos = pos; + + if(distToTarget < rangeMelee || (distToTarget <= rangeCloseUp && 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; + vDirToTarget.z = 0; + mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); - //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); - if (mFollowTarget && distBetween > rangeMelee) + if (mFollowTarget && distToTarget > rangeMelee) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; @@ -228,7 +265,7 @@ namespace MWMechanics } } - if(distantCombat && distBetween < rangeMelee/4) + if(distantCombat && distToTarget < rangeMelee/4) { mMovement.mPosition[1] = -1; } @@ -238,32 +275,80 @@ namespace MWMechanics mFollowTarget = true; } } - else + else // remote pathfinding { - //target is at far distance: build path to target OR follow target (if previously actor had reached it once) - mFollowTarget = false; + 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 = 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); + } + + if(preferShortcut) + { + mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + mForceNoShortcut = false; + mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; + if(mPathFinder.isPathConstructed()) + 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; + } - buildNewPath(actor); //may fail to build a path, check before use + mFollowTarget = false; - //delete visited path node - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); + buildNewPath(actor); //may fail to build a path, check before use - //if no new path leave mTargetAngle unchanged - if(!mPathFinder.getPath().empty()) - { - //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; + //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()) + { + mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + preferShortcut = true; + } + } + + // if there is no new path, then go straight on target + if(!preferShortcut) + { + if(!mPathFinder.getPath().empty()) + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + else + mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); + } } mMovement.mPosition[1] = 1; mReadyToAttack = false; } - if(distBetween > rangeMelee) + if(distToTarget > rangeMelee) { //special run attack; it shouldn't affect melee combat tactics if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1) @@ -277,7 +362,7 @@ namespace MWMechanics && 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 @@ -300,31 +385,23 @@ namespace MWMechanics void AiCombat::buildNewPath(const MWWorld::Ptr& actor) { - //Construct path to target - ESM::Pathgrid::Point dest; - dest.mX = mTarget.getRefData().getPosition().pos[0]; - dest.mY = mTarget.getRefData().getPosition().pos[1]; - dest.mZ = mTarget.getRefData().getPosition().pos[2]; - Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ); - - float dist = -1; //hack to indicate first time, to construct a new path + Ogre::Vector3 newPathTarget = Ogre::Vector3(mTarget.getRefData().getPosition().pos); + + float dist; + if(!mPathFinder.getPath().empty()) { ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); - dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + dist = (newPathTarget - currPathTarget).length(); } + else dist = 1e+38F; // necessarily construct a new path - float targetPosThreshold; - bool isOutside = actor.getCell()->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; @@ -332,17 +409,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 767a36292..b71dd9cf0 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -43,8 +43,11 @@ namespace MWMechanics bool mReadyToAttack, mStrike; bool mFollowTarget; bool mCombatMove; - bool mRotate; + bool mForceNoShortcut; + ESM::Position mShortcutFailPos; + + ESM::Position mLastPos; MWMechanics::Movement mMovement; MWWorld::Ptr mTarget; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 3ecd40743..4aee6f6e4 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -384,22 +384,21 @@ namespace MWMechanics return false; } - 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 ecaaef568..86fa3b6bc 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -64,10 +64,15 @@ namespace MWMechanics return mPath; } - //When first point of newly created path is the nearest to actor point, then - //the cituation 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 cituation can occure 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 d911fd81b..7646e832d 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -31,8 +31,7 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) 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; diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 504dc3ac3..9bdf7d4a3 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -10,6 +10,9 @@ 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); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 594a9f7f4..47536ab6c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1875,7 +1875,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()) @@ -1893,6 +1893,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 f1e89bf6b..0eea3c6a0 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(); From fbd0ffe86ff00da9bac3ae24670d1cbca32c1860 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Tue, 22 Apr 2014 22:59:39 +0400 Subject: [PATCH 3/8] enable z-moving for flying/water combatants --- apps/openmw/mwmechanics/aicombat.cpp | 32 +++++++++++++++++++-------- apps/openmw/mwmechanics/character.cpp | 10 +++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 84798ca02..807e08c1d 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -38,7 +38,7 @@ namespace return Ogre::Radian( Ogre::Math::ACos(dir.y / len) * sgn(Ogre::Math::ASin(dir.x / len)) ).valueDegrees(); } - const float PATHFIND_Z_REACH = 50.0f; + 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 @@ -115,6 +115,7 @@ namespace MWMechanics } + mTimerAttack -= duration; actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); @@ -229,6 +230,17 @@ namespace MWMechanics mLastPos = pos; + // check if can move along z-axis + bool canMoveByZ; + 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; + } + if(distToTarget < rangeMelee || (distToTarget <= rangeCloseUp && mFollowTarget && !isStuck) ) { //Melee and Close-up combat @@ -238,12 +250,13 @@ namespace MWMechanics if (mFollowTarget && distToTarget > rangeMelee) { //Close-up combat: just run up on target - mMovement.mPosition[1] = 1; + if(!canMoveByZ) 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)) @@ -295,15 +308,16 @@ namespace MWMechanics preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } - if(preferShortcut) - { + if(canMoveByZ) preferShortcut = true; + + if(preferShortcut) + { mTargetAngle = getZAngleToDir(vDirToTarget, distToTarget); mForceNoShortcut = false; mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; - if(mPathFinder.isPathConstructed()) - mPathFinder.clearPath(); - } - else // if shortcut failed stick to path grid + 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) { @@ -344,7 +358,7 @@ namespace MWMechanics } } - mMovement.mPosition[1] = 1; + if(!canMoveByZ) mMovement.mPosition[1] = 1; mReadyToAttack = false; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 93c789af1..2c63d5a14 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1018,6 +1018,7 @@ void CharacterController::update(float duration) vec.x *= mMovementSpeed; vec.y *= mMovementSpeed; + if(inwater || flying) vec.z *= mMovementSpeed; CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; @@ -1084,7 +1085,8 @@ void CharacterController::update(float duration) fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); cls.getCreatureStats(mPtr).setFatigue(fatigue); - if(sneak || inwater || flying) + // kind of hack, reason - creatures can move along z when in water/flying + if(sneak || ((inwater || flying) && mPtr.getRefData().getHandle() == "player")) vec.z = 0.0f; if (inwater || flying) @@ -1119,7 +1121,7 @@ void CharacterController::update(float duration) vec.y *= mult; vec.z = 0.0f; } - else if(vec.z > 0.0f && mJumpState == JumpState_None) + else if(!inwater && !flying && vec.z > 0.0f && mJumpState == JumpState_None) { // Started a jump. float z = cls.getJump(mPtr); @@ -1179,9 +1181,9 @@ void CharacterController::update(float duration) } else { - if(!(vec.z > 0.0f)) + if(!(vec.z > 0.0f)) mJumpState = JumpState_None; - vec.z = 0.0f; + if(!inwater && !flying) vec.z = 0.0f; if(std::abs(vec.x/2.0f) > std::abs(vec.y)) { From f3626adc868c7e3a9080e63dd102219c294c20f0 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 26 Apr 2014 00:20:55 +0400 Subject: [PATCH 4/8] remake of z-moving in combat for flying/swimming enemies --- apps/openmw/mwmechanics/aicombat.cpp | 52 ++++++++++++++----------- apps/openmw/mwmechanics/aicombat.hpp | 4 -- apps/openmw/mwmechanics/character.cpp | 9 ++--- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- apps/openmw/mwmechanics/steering.cpp | 29 ++++++++++++++ apps/openmw/mwmechanics/steering.hpp | 2 + 6 files changed, 65 insertions(+), 33 deletions(-) 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 From dbe1307de0914d3eb6ecda03b59bf624366dca85 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 26 Apr 2014 22:21:20 +0400 Subject: [PATCH 5/8] code refining + minor fixes --- apps/openmw/mwmechanics/aicombat.cpp | 35 ++++++++++++++-------------- apps/openmw/mwmechanics/steering.cpp | 26 +-------------------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8a3c23aef..fa7ef28a3 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -175,23 +175,23 @@ namespace MWMechanics mStrike = 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); // Get weapon characteristics - if (actor.getClass().hasInventoryStore(actor)) + 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) { @@ -216,7 +216,7 @@ namespace MWMechanics float rangeMelee; float rangeCloseUp; 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; // not needed in ranged combat @@ -236,15 +236,15 @@ 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 = actorCls.getSpeed(actor)) / 10.0f) isStuck = true; mLastPos = pos; // check if can move along z-axis bool canMoveByZ; - 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))) + if(canMoveByZ = ((actorCls.isNpc() || actorCls.canSwim(actor)) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || (actorCls.canFly(actor) && MWBase::Environment::get().getWorld()->isFlying(actor))) { // determine vertical angle to target mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); @@ -266,16 +266,15 @@ namespace MWMechanics //Melee: stop running and attack mMovement.mPosition[1] = 0; - // When attacking with a weapon, choose between slash, thrust or chop - if (mStrike && actor.getClass().hasInventoryStore(actor)) - chooseBestAttack(weapon, mMovement); + // set slash/thrust/chop attack + if (mStrike) 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))) + else if(actorCls.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeMelee/2))) { //apply sideway movement (kind of dodging) with some probability if(static_cast(rand())/RAND_MAX < 0.25) @@ -309,7 +308,7 @@ namespace MWMechanics || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST) && (inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget))) { - if(speed == 0.0f) speed = cls.getSpeed(actor); + 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; preferShortcut = checkWayIsClear(vActorPos, vTargetPos, distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); @@ -370,15 +369,15 @@ namespace MWMechanics mReadyToAttack = false; } - if(distToTarget > rangeMelee) + if(distToTarget > rangeMelee && !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) diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index d10733b36..0a8a37ecb 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -41,31 +41,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis) bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) { - Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); - 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[2] = diff.valueRadians(); - return false; + return smoothTurn(actor, targetAngle, 2); } } From 82121e040172b4d586363e427c09f2df795e443a Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 27 Apr 2014 16:59:21 +0400 Subject: [PATCH 6/8] some checks reworked --- apps/openmw/mwclass/npc.hpp | 8 ++++++++ apps/openmw/mwmechanics/aicombat.cpp | 4 ++-- apps/openmw/mwworld/physicssystem.cpp | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) 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 2b612d734..42d963117 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -341,8 +341,8 @@ namespace MWMechanics // check if can move along z-axis bool canMoveByZ; - if(canMoveByZ = ((actorCls.isNpc() || actorCls.canSwim(actor)) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || (actorCls.canFly(actor) && MWBase::Environment::get().getWorld()->isFlying(actor))) + if(canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor)) { // determine vertical angle to target mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 7d531d6d3..5bf8305ba 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); From 22cdb166f2227ff6f6653027ed56485dd22d47ad Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 27 Apr 2014 22:38:04 +0400 Subject: [PATCH 7/8] warning fix, vars renaming --- apps/openmw/mwmechanics/aicombat.cpp | 89 +++++++++++++--------------- apps/openmw/mwmechanics/aicombat.hpp | 2 +- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 42d963117..e64639c34 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -76,6 +76,7 @@ namespace 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 @@ -86,7 +87,7 @@ namespace MWMechanics mTimerCombatMove(0), mFollowTarget(false), mReadyToAttack(false), - mStrike(false), + mAttack(false), mCombatMove(false), mMovement(), mForceNoShortcut(false), @@ -182,7 +183,7 @@ namespace MWMechanics } mTimerAttack -= duration; - actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); float tReaction = 0.25f; if(mTimerReact < tReaction) @@ -203,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()) @@ -227,12 +228,12 @@ namespace MWMechanics } } else if (mTimerAttack <= 0) - mStrike = false; + mAttack = false; } else { - mTimerAttack = -attackPeriod; - mStrike = false; + mTimerAttack = -attacksPeriod; + mAttack = false; } const MWWorld::Class &actorCls = actor.getClass(); @@ -277,31 +278,23 @@ namespace MWMechanics /* * 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: * @@ -311,19 +304,19 @@ namespace MWMechanics * 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) { - rangeMelee = 1000; // TODO: should depend on archer skill - rangeCloseUp = 0; // not needed in ranged combat + 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(); @@ -339,42 +332,42 @@ namespace MWMechanics mLastPos = pos; - // check if can move along z-axis - bool canMoveByZ; - if(canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || MWBase::Environment::get().getWorld()->isFlying(actor)) + // 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); + if(canMoveByZ) { // determine vertical angle to target mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); } // (within strike dist) || (not quite strike dist while following) - if(distToTarget < rangeMelee || (distToTarget <= rangeCloseUp && mFollowTarget && !isStuck) ) + if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) { //Melee and Close-up combat vDirToTarget.z = 0; mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); // (not quite strike dist while following) - if (mFollowTarget && distToTarget > 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; // set slash/thrust/chop attack - if (mStrike && !distantCombat) chooseBestAttack(weapon, mMovement); + 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(actorCls.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeMelee/2))) + // 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) @@ -385,7 +378,7 @@ namespace MWMechanics } } - if(distantCombat && distToTarget < rangeMelee/4) + if(distantCombat && distToTarget < rangeAttack/4) { mMovement.mPosition[1] = -1; } @@ -469,7 +462,7 @@ namespace MWMechanics mReadyToAttack = false; } - if(distToTarget > rangeMelee && !distantCombat) + if(distToTarget > rangeAttack && !distantCombat) { //special run attack; it shouldn't affect melee combat tactics if(actorCls.getMovementSettings(actor).mPosition[1] == 1) @@ -486,14 +479,14 @@ namespace MWMechanics 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; } } } @@ -503,7 +496,7 @@ namespace MWMechanics // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? - if((distToTarget > rangeMelee || mFollowTarget) && + if((distToTarget > rangeAttack || mFollowTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { // first check if we're walking into a door diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 716101637..30b72acd9 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -40,7 +40,7 @@ namespace MWMechanics float mTimerCombatMove; // AiCombat states - bool mReadyToAttack, mStrike; + bool mReadyToAttack, mAttack; bool mFollowTarget; bool mCombatMove; bool mBackOffDoor; From 35c1724d39d5ca8d9a36107ac3514fd2bf78e765 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Mon, 28 Apr 2014 16:34:49 +0400 Subject: [PATCH 8/8] unblock vertical aiming for combatants --- apps/openmw/mwmechanics/aicombat.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index e64639c34..d0555cdc3 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -34,13 +34,13 @@ namespace float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) { - float len = (dirLen >= 0.0f)? dirLen : dir.length(); + 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(); + float len = (dirLen > 0.0f)? dirLen : dir.length(); return Ogre::Radian(-Ogre::Math::ASin(dir.z / len)).valueDegrees(); } @@ -323,7 +323,6 @@ namespace MWMechanics Ogre::Vector3 vActorPos(pos.pos); Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos); Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; - float distToTarget = vDirToTarget.length(); bool isStuck = false; float speed = 0.0f; @@ -335,17 +334,19 @@ namespace MWMechanics // 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); - if(canMoveByZ) - { - // determine vertical angle to target - mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - } + + // 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(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) { //Melee and Close-up combat - vDirToTarget.z = 0; mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); // (not quite strike dist while following)