1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-02 03:15:35 +00:00

Merge remote-tracking branch 'mrcheko/master'

This commit is contained in:
Marc Zinnschlag 2014-04-29 10:03:29 +02:00
commit 8fea2cfa2a
12 changed files with 284 additions and 151 deletions

View file

@ -407,6 +407,8 @@ namespace MWBase
virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0;
///< get Line of Sight (morrowind stupid implementation) ///< 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 void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
virtual int canRest() = 0; virtual int canRest() = 0;

View file

@ -170,6 +170,14 @@ namespace MWClass
virtual int getBaseGold(const MWWorld::Ptr& ptr) const; virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) 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;
}
}; };
} }

View file

@ -31,10 +31,52 @@ namespace
//chooses an attack depending on probability to avoid uniformity //chooses an attack depending on probability to avoid uniformity
void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); 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 namespace MWMechanics
{ {
static const float MAX_ATTACK_DURATION = 0.35f;
static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander
// NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp
@ -45,16 +87,16 @@ namespace MWMechanics
mTimerCombatMove(0), mTimerCombatMove(0),
mFollowTarget(false), mFollowTarget(false),
mReadyToAttack(false), mReadyToAttack(false),
mStrike(false), mAttack(false),
mCombatMove(false), mCombatMove(false),
mBackOffDoor(false),
mRotate(false),
mMovement(), mMovement(),
mForceNoShortcut(false),
mShortcutFailPos(),
mBackOffDoor(false),
mCell(NULL), mCell(NULL),
mDoorIter(actor.getCell()->get<ESM::Door>().mList.end()), mDoorIter(actor.getCell()->get<ESM::Door>().mList.end()),
mDoors(actor.getCell()->get<ESM::Door>()), mDoors(actor.getCell()->get<ESM::Door>()),
mDoorCheckDuration(0), mDoorCheckDuration(0)
mTargetAngle(0)
{ {
} }
@ -127,15 +169,21 @@ namespace MWMechanics
} }
actor.getClass().getMovementSettings(actor) = mMovement; 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(mTargetAngle))) if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0;
mRotate = false; }
if(mMovement.mRotation[0] != 0)
{
if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0;
} }
mTimerAttack -= duration; mTimerAttack -= duration;
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
float tReaction = 0.25f; float tReaction = 0.25f;
if(mTimerReact < tReaction) if(mTimerReact < tReaction)
@ -156,16 +204,16 @@ namespace MWMechanics
//actual attacking logic //actual attacking logic
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f //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(mReadyToAttack)
{ {
if(mTimerAttack <= -attackPeriod) if(mTimerAttack <= -attacksPeriod)
{ {
//TODO: should depend on time between 'start' to 'min attack' //TODO: should depend on time between 'start' to 'min attack'
//for better controlling of NPCs' attack strength. //for better controlling of NPCs' attack strength.
//Also it seems that this time is different for slash/thrust/chop //Also it seems that this time is different for slash/thrust/chop
mTimerAttack = 0.35f * static_cast<float>(rand())/RAND_MAX; mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
mStrike = true; mAttack = true;
//say a provoking combat phrase //say a provoking combat phrase
if (actor.getClass().isNpc()) if (actor.getClass().isNpc())
@ -180,30 +228,31 @@ namespace MWMechanics
} }
} }
else if (mTimerAttack <= 0) else if (mTimerAttack <= 0)
mStrike = false; mAttack = false;
} }
else else
{ {
mTimerAttack = -attackPeriod; mTimerAttack = -attacksPeriod;
mStrike = false; mAttack = false;
} }
const MWWorld::Class &cls = actor.getClass(); const MWWorld::Class &actorCls = actor.getClass();
const ESM::Weapon *weapon = NULL; const ESM::Weapon *weapon = NULL;
MWMechanics::WeaponType weaptype; MWMechanics::WeaponType weaptype;
float weapRange, weapSpeed = 1.0f; 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) 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 //Get weapon speed and range
MWWorld::ContainerStoreIterator weaponSlot = 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) if (weaptype == WeapType_HandToHand)
{ {
@ -225,36 +274,27 @@ namespace MWMechanics
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) 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: * Some notes on meanings of variables:
* *
* rangeMelee: * rangeAttack:
* *
* - Distance where attack using the actor's weapon is possible * - Distance where attack using the actor's weapon is possible:
* - longer for ranged weapons (obviously?) vs. melee weapons * 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 * - 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 * - Applies to melee weapons or hand to hand only (or creatures without
* weapons) * weapons)
* - Distance a little further away from the actor's weapon strike * - Distance a little further away than the actor's weapon reach
* i.e. rangeCloseup > rangeMelee for melee weapons * i.e. rangeFollow > rangeAttack for melee weapons
* (the variable names make this simple concept counter-intuitive, * - Hardcoded value (0 for ranged weapons)
* something like rangeMelee > rangeStrike may be better)
* - Once the target gets beyond this distance mFollowTarget is cleared * - Once the target gets beyond this distance mFollowTarget is cleared
* and a path to the target needs to be found * and a path to the target needs to be found
* - TODO: Possibly rename this variable to rangeMelee or even rangeFollow
* *
* mFollowTarget: * mFollowTarget:
* *
@ -263,58 +303,72 @@ namespace MWMechanics
* available, since the default path without pathgrids is direct to * available, since the default path without pathgrids is direct to
* target even if LOS is not achieved) * target even if LOS is not achieved)
*/ */
float rangeMelee;
float rangeCloseUp; float rangeAttack;
float rangeFollow;
bool distantCombat = false; 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 rangeAttack = 1000; // TODO: should depend on archer skill
rangeCloseUp = 0; //doesn't needed when attacking from distance rangeFollow = 0; // not needed in ranged combat
distantCombat = true; distantCombat = true;
} }
else else
{ {
rangeMelee = weapRange; rangeAttack = weapRange;
rangeCloseUp = 300; rangeFollow = 300;
} }
Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]); ESM::Position pos = actor.getRefData().getPosition();
ESM::Position targetPos = mTarget.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos);
Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]); Ogre::Vector3 vTargetPos(mTarget.getRefData().getPosition().pos);
Ogre::Vector3 vDir = vDest - vStart; Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos;
float distBetween = vDir.length();
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);
// 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) // (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 //Melee and Close-up combat
vDir.z = 0; mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
float dirLen = vDir.length();
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees();
mRotate = true;
//bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
// (not quite strike dist while following) // (not quite strike dist while following)
if (mFollowTarget && distBetween > rangeMelee) if (mFollowTarget && distToTarget > rangeAttack)
{ {
//Close-up combat: just run up on target //Close-up combat: just run up on target
mMovement.mPosition[1] = 1; mMovement.mPosition[1] = 1;
} }
else // (within strike dist) else // (within strike dist)
{ {
//Melee: stop running and attack
mMovement.mPosition[1] = 0; mMovement.mPosition[1] = 0;
// When attacking with a weapon, choose between slash, thrust or chop // set slash/thrust/chop attack
if (actor.getClass().hasInventoryStore(actor)) if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement);
chooseBestAttack(weapon, mMovement);
if(mMovement.mPosition[0] || mMovement.mPosition[1]) if(mMovement.mPosition[0] || mMovement.mPosition[1])
{ {
mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX; mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX;
mCombatMove = true; 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 //apply sideway movement (kind of dodging) with some probability
if(static_cast<float>(rand())/RAND_MAX < 0.25) if(static_cast<float>(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; mMovement.mPosition[1] = -1;
} }
@ -335,56 +389,105 @@ namespace MWMechanics
mFollowTarget = true; mFollowTarget = true;
} }
} }
else else // remote pathfinding
{ {
//target is at far distance: build path to target 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);
}
// don't use pathgrid when actor can move in 3 dimensions
if(canMoveByZ) preferShortcut = true;
if(preferShortcut)
{
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; mFollowTarget = false;
buildNewPath(actor); //may fail to build a path, check before use buildNewPath(actor); //may fail to build a path, check before use
//delete visited path node //delete visited path node
mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]);
//if no new path leave mTargetAngle unchanged // This works on the borders between the path grid and areas with no waypoints.
if(!mPathFinder.getPath().empty()) if(inLOS && mPathFinder.getPath().size() > 1)
{ {
//try shortcut // get point just before target
if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) std::list<ESM::Pathgrid::Point>::iterator pntIter = --mPathFinder.getPath().end();
mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); --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 else
mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
mRotate = true; }
} }
mMovement.mPosition[1] = 1; mMovement.mPosition[1] = 1;
mReadyToAttack = false; mReadyToAttack = false;
} }
if(distBetween > rangeMelee) if(distToTarget > rangeAttack && !distantCombat)
{ {
//special run attack; it shouldn't affect melee combat tactics //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 //check if actor can overcome the distance = distToTarget - attackerWeapRange
//less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing)
//then start attacking //then start attacking
float speed1 = cls.getSpeed(actor); float speed1 = actorCls.getSpeed(actor);
float speed2 = mTarget.getClass().getSpeed(mTarget); float speed2 = mTarget.getClass().getSpeed(mTarget);
if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0
&& mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0)
speed2 = 0; speed2 = 0;
float s1 = distBetween - weapRange; float s1 = distToTarget - weapRange;
float t = s1/speed1; float t = s1/speed1;
float s2 = speed2 * t; 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) if (t + s2/speed1 <= t_swing)
{ {
mReadyToAttack = true; mReadyToAttack = true;
if(mTimerAttack <= -attackPeriod) if(mTimerAttack <= -attacksPeriod)
{ {
mTimerAttack = 0.3f*static_cast<float>(rand())/RAND_MAX; mTimerAttack = MAX_ATTACK_DURATION * static_cast<float>(rand())/RAND_MAX;
mStrike = true; mAttack = true;
} }
} }
} }
@ -394,7 +497,7 @@ namespace MWMechanics
// coded at 250ms or 1/4 second // coded at 250ms or 1/4 second
// //
// TODO: Add a parameter to vary DURATION_SAME_SPOT? // 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 mObstacleCheck.check(actor, tReaction)) // check if evasive action needed
{ {
// first check if we're walking into a door // first check if we're walking into a door
@ -406,12 +509,11 @@ namespace MWMechanics
// Check all the doors in this cell // Check all the doors in this cell
mDoors = cell->get<ESM::Door>(); // update mDoors = cell->get<ESM::Door>(); // update
mDoorIter = mDoors.mList.begin(); mDoorIter = mDoors.mList.begin();
Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos);
for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) for (; mDoorIter != mDoors.mList.end(); ++mDoorIter)
{ {
MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter; MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter;
float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility 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 ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening
{ {
//std::cout<<"closed door id \""<<ref.mRef.mRefID<<"\""<<std::endl; //std::cout<<"closed door id \""<<ref.mRef.mRefID<<"\""<<std::endl;
@ -441,11 +543,10 @@ namespace MWMechanics
} }
MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter; MWWorld::LiveCellRef<ESM::Door>& ref = *mDoorIter;
Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos);
float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility
// TODO: add reaction to checking open doors // TODO: add reaction to checking open doors
if(mBackOffDoor && 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 mMovement.mPosition[1] = -0.2; // back off, but slowly
} }
@ -458,43 +559,34 @@ namespace MWMechanics
//std::cout<<"open door id \""<<ref.mRef.mRefID<<"\""<<std::endl; //std::cout<<"open door id \""<<ref.mRef.mRefID<<"\""<<std::endl;
mMovement.mPosition[1] = 1; mMovement.mPosition[1] = 1;
} }
else // these lines break ranged combat distance keeping
{ //else
mMovement.mPosition[1] = 1; // FIXME: oscillation? //{
} // mMovement.mPosition[1] = 1; // FIXME: oscillation?
//}
actor.getClass().getMovementSettings(actor) = mMovement;
return false; return false;
} }
void AiCombat::buildNewPath(const MWWorld::Ptr& actor) void AiCombat::buildNewPath(const MWWorld::Ptr& actor)
{ {
//Construct path to target Ogre::Vector3 newPathTarget = Ogre::Vector3(mTarget.getRefData().getPosition().pos);
ESM::Pathgrid::Point dest;
dest.mX = mTarget.getRefData().getPosition().pos[0]; float dist;
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
if(!mPathFinder.getPath().empty()) if(!mPathFinder.getPath().empty())
{ {
ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); 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; float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300 : 100;
bool isOutside = actor.getCell()->getCell()->isExterior();
if (isOutside)
targetPosThreshold = 300;
else
targetPosThreshold = 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 <targetPosThreshold>
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
ESM::Pathgrid::Point start; ESM::Pathgrid::Point start;
@ -502,17 +594,18 @@ namespace MWMechanics
start.mY = pos.pos[1]; start.mY = pos.pos[1];
start.mZ = pos.pos[2]; start.mZ = pos.pos[2];
ESM::Pathgrid::Point dest;
dest.mX = newPathTarget.x;
dest.mY = newPathTarget.y;
dest.mZ = newPathTarget.z;
if(!mPathFinder.isPathConstructed()) if(!mPathFinder.isPathConstructed())
mPathFinder.buildPath(start, dest, actor.getCell(), isOutside); mPathFinder.buildPath(start, dest, actor.getCell(), false);
else else
{ {
PathFinder newPathFinder; 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()) if(!mPathFinder.getPath().empty())
{ {
newPathFinder.syncStart(mPathFinder.getPath()); newPathFinder.syncStart(mPathFinder.getPath());

View file

@ -39,17 +39,16 @@ namespace MWMechanics
// when mCombatMove is true // when mCombatMove is true
float mTimerCombatMove; float mTimerCombatMove;
// the z rotation angle (degrees) we want to reach
// used every frame when mRotate is true
float mTargetAngle;
// AiCombat states // AiCombat states
bool mReadyToAttack, mStrike; bool mReadyToAttack, mAttack;
bool mFollowTarget; bool mFollowTarget;
bool mCombatMove; bool mCombatMove;
bool mBackOffDoor; bool mBackOffDoor;
bool mRotate;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
ESM::Position mLastPos;
MWMechanics::Movement mMovement; MWMechanics::Movement mMovement;
MWWorld::Ptr mTarget; MWWorld::Ptr mTarget;

View file

@ -1010,6 +1010,7 @@ void CharacterController::update(float duration)
if(mHitState != CharState_None && mJumpState == JumpState_None) if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = Ogre::Vector3(0.0f); vec = Ogre::Vector3(0.0f);
Ogre::Vector3 rot = cls.getRotationVector(mPtr); Ogre::Vector3 rot = cls.getRotationVector(mPtr);
mMovementSpeed = cls.getSpeed(mPtr); mMovementSpeed = cls.getSpeed(mPtr);
vec.x *= mMovementSpeed; vec.x *= mMovementSpeed;

View file

@ -325,22 +325,22 @@ namespace MWMechanics
} }
// used by AiCombat, see header for the rationale // used by AiCombat, see header for the rationale
void PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path) bool PathFinder::syncStart(const std::list<ESM::Pathgrid::Point> &path)
{ {
if (mPath.size() < 2) if (mPath.size() < 2)
return; //nothing to pop return false; //nothing to pop
std::list<ESM::Pathgrid::Point>::const_iterator oldStart = path.begin(); std::list<ESM::Pathgrid::Point>::const_iterator oldStart = path.begin();
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin(); std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
if( (*iter).mX == oldStart->mX if( (*iter).mX == oldStart->mX
&& (*iter).mY == oldStart->mY && (*iter).mY == oldStart->mY
&& (*iter).mZ == oldStart->mZ && (*iter).mZ == oldStart->mZ)
&& (*iter).mAutogenerated == oldStart->mAutogenerated
&& (*iter).mConnectionNum == oldStart->mConnectionNum )
{ {
mPath.pop_front(); mPath.pop_front();
return true;
} }
return false;
} }
} }

View file

@ -62,11 +62,15 @@ namespace MWMechanics
return mPath; return mPath;
} }
// When first point of newly created path is the nearest to actor point, /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
// then a situation can occure when this point is undesirable @note
// (if the 2nd point of new path == the 1st point of old path) If the first point is chosen as the nearest one
// This functions deletes that point. the situation can occur when the 1st point of the new path is undesirable
void syncStart(const std::list<ESM::Pathgrid::Point> &path); (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<ESM::Pathgrid::Point> &path);
void addPointToPath(ESM::Pathgrid::Point &point) void addPointToPath(ESM::Pathgrid::Point &point)
{ {

View file

@ -10,9 +10,9 @@
namespace MWMechanics 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); Ogre::Radian diff (targetAngle - currentAngle);
if (diff >= Ogre::Degree(180)) if (diff >= Ogre::Degree(180))
{ {
@ -30,13 +30,17 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree eps
if (absDiff < epsilon) if (absDiff < epsilon)
return true; return true;
// Max. speed of 10 radian per sec Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration();
Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration();
if (absDiff > limit) if (absDiff > limit)
diff = Ogre::Math::Sign(diff) * 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; return false;
} }
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon)
{
return smoothTurn(actor, targetAngle, 2, epsilon);
}
} }

View file

@ -10,11 +10,17 @@ class Ptr;
namespace MWMechanics 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) /// configure rotation settings for an actor to reach this target angle (eventually)
/// @return have we reached the target angle? /// @return have we reached the target angle?
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle,
Ogre::Degree epsilon = Ogre::Degree(0.5)); 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 #endif

View file

@ -313,8 +313,9 @@ namespace MWWorld
// NOTE: stepMove modifies newPosition if successful // NOTE: stepMove modifies newPosition if successful
if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) if(stepMove(colobj, newPosition, velocity, remainingTime, engine))
{ {
// don't let slaughterfish move out of water after stepMove // don't let pure water creatures move out of water after stepMove
if(ptr.getClass().canSwim(ptr) && newPosition.z > (waterlevel - halfExtents.z * 0.5)) if((ptr.getClass().canSwim(ptr) && !ptr.getClass().canWalk(ptr))
&& newPosition.z > (waterlevel - halfExtents.z * 0.5))
newPosition = oldPosition; newPosition = oldPosition;
else // Only on the ground if there's gravity else // Only on the ground if there's gravity
isOnGround = !(newPosition.z < waterlevel || isFlying); isOnGround = !(newPosition.z < waterlevel || isFlying);

View file

@ -1927,6 +1927,19 @@ namespace MWWorld
return false; 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<std::string, float> 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) void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable)
{ {
OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle());

View file

@ -510,6 +510,8 @@ namespace MWWorld
virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc);
///< get Line of Sight (morrowind stupid implementation) ///< 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 void enableActorCollision(const MWWorld::Ptr& actor, bool enable);
virtual int canRest(); virtual int canRest();