diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a31e64dd3..02594258d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -25,6 +25,7 @@ #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" +#include "character.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -1170,7 +1171,7 @@ namespace MWMechanics updateCrimePersuit(iter->first, duration); if (iter->first != player) - iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first, duration); + iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first,iter->second->getAiState(), duration); CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if(!stats.isDead()) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 54bcd67b5..9e25084d3 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -21,7 +21,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const { return new AiActivate(*this); } -bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) +bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 2a0f67709..e25afe285 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -28,7 +28,7 @@ namespace MWMechanics AiActivate(const ESM::AiSequence::AiActivate* activate); virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; virtual void writeState(ESM::AiSequence::AiSequence& sequence) const; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 7cb4f1c25..b9954337d 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -18,7 +18,7 @@ MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::Ptr& doorPtr) } -bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration) +bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 2374fbc1b..7590c8fcb 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -20,7 +20,7 @@ namespace MWMechanics virtual AiAvoidDoor *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0bea76c58..67fd54456 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -26,12 +26,6 @@ namespace { - static float sgn(Ogre::Radian a) - { - if(a.valueDegrees() > 0) - return 1.0; - return -1.0; - } //chooses an attack depending on probability to avoid uniformity ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); @@ -41,16 +35,15 @@ namespace Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, float duration, int weapType, float strength); - float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) + float getZAngleToDir(const Ogre::Vector3& dir) { - 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(); + return Ogre::Math::ATan2(dir.x,dir.y).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(); + return -Ogre::Math::ASin(dir.z / len).valueDegrees(); } @@ -88,40 +81,60 @@ namespace MWMechanics static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp + + /// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive. + struct AiCombatStorage : AiTemporaryBase + { + float mTimerAttack; + float mTimerReact; + float mTimerCombatMove; + bool mReadyToAttack; + bool mAttack; + bool mFollowTarget; + bool mCombatMove; + Ogre::Vector3 mLastTargetPos; + const MWWorld::CellStore* mCell; + boost::shared_ptr mCurrentAction; + float mActionCooldown; + float mStrength; + float mMinMaxAttackDuration[3][2]; + bool mMinMaxAttackDurationInitialised; + bool mForceNoShortcut; + ESM::Position mShortcutFailPos; + Ogre::Vector3 mLastActorPos; + MWMechanics::Movement mMovement; + + AiCombatStorage(): + mTimerAttack(0), + mTimerReact(0), + mTimerCombatMove(0), + mAttack(false), + mFollowTarget(false), + mCombatMove(false), + mReadyToAttack(false), + mForceNoShortcut(false), + mCell(NULL), + mCurrentAction(), + mActionCooldown(0), + mStrength(), + mMinMaxAttackDurationInitialised(false), + mLastTargetPos(0,0,0), + mLastActorPos(0,0,0), + mMovement(){} + }; + AiCombat::AiCombat(const MWWorld::Ptr& actor) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mMinMaxAttackDuration() - , mMovement() - { - init(); - - mLastTargetPos = Ogre::Vector3(actor.getRefData().getPosition().pos); - } + {} AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) - : mMinMaxAttackDuration() - , mMovement() { mTargetActorId = combat->mTargetActorId; - - init(); } void AiCombat::init() { - mActionCooldown = 0; - mTimerAttack = 0; - mTimerReact = 0; - mTimerCombatMove = 0; - mFollowTarget = false; - mReadyToAttack = false; - mAttack = false; - mCombatMove = false; - mForceNoShortcut = false; - mStrength = 0; - mCell = NULL; - mLastTargetPos = Ogre::Vector3(0,0,0); - mMinMaxAttackDurationInitialised = false; + } /* @@ -170,8 +183,13 @@ namespace MWMechanics * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ - bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) + bool AiCombat::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { + // get or create temporary storage + AiCombatStorage& storage = state.get(); + + + //General description if(actor.getClass().getCreatureStats(actor).isDead()) return true; @@ -197,32 +215,39 @@ namespace MWMechanics { actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; - } + } + + + + //Update every frame - if(mCombatMove) + bool& combatMove = storage.mCombatMove; + float& timerCombatMove = storage.mTimerCombatMove; + MWMechanics::Movement& movement = storage.mMovement; + if(combatMove) { - mTimerCombatMove -= duration; - if( mTimerCombatMove <= 0) + timerCombatMove -= duration; + if( timerCombatMove <= 0) { - mTimerCombatMove = 0; - mMovement.mPosition[1] = mMovement.mPosition[0] = 0; - mCombatMove = false; + timerCombatMove = 0; + movement.mPosition[1] = movement.mPosition[0] = 0; + combatMove = false; } } - actorClass.getMovementSettings(actor) = mMovement; + actorClass.getMovementSettings(actor) = movement; actorClass.getMovementSettings(actor).mRotation[0] = 0; actorClass.getMovementSettings(actor).mRotation[2] = 0; - if(mMovement.mRotation[2] != 0) + if(movement.mRotation[2] != 0) { - if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0; + if(zTurn(actor, Ogre::Degree(movement.mRotation[2]))) movement.mRotation[2] = 0; } - if(mMovement.mRotation[0] != 0) + if(movement.mRotation[0] != 0) { - if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0; + if(smoothTurn(actor, Ogre::Degree(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; } //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f @@ -230,44 +255,57 @@ namespace MWMechanics ESM::Weapon::AttackType attackType; - if(mReadyToAttack) + + + + bool& attack = storage.mAttack; + bool& readyToAttack = storage.mReadyToAttack; + float& timerAttack = storage.mTimerAttack; + + bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised; + float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration; + + if(readyToAttack) { - if (!mMinMaxAttackDurationInitialised) + if (!minMaxAttackDurationInitialised) { // TODO: this must be updated when a different weapon is equipped - getMinMaxAttackDuration(actor, mMinMaxAttackDuration); - mMinMaxAttackDurationInitialised = true; + getMinMaxAttackDuration(actor, minMaxAttackDuration); + minMaxAttackDurationInitialised = true; } - if (mTimerAttack < 0) mAttack = false; + if (timerAttack < 0) attack = false; - mTimerAttack -= duration; + timerAttack -= duration; } else { - mTimerAttack = -attacksPeriod; - mAttack = false; + timerAttack = -attacksPeriod; + attack = false; } - actorClass.getCreatureStats(actor).setAttackingOrSpell(mAttack); + actorClass.getCreatureStats(actor).setAttackingOrSpell(attack); - mActionCooldown -= duration; + float& actionCooldown = storage.mActionCooldown; + actionCooldown -= duration; + + float& timerReact = storage.mTimerReact; float tReaction = 0.25f; - if(mTimerReact < tReaction) + if(timerReact < tReaction) { - mTimerReact += duration; + timerReact += duration; return false; } //Update with period = tReaction - mTimerReact = 0; - - bool cellChange = mCell && (actor.getCell() != mCell); - if(!mCell || cellChange) + timerReact = 0; + const MWWorld::CellStore*& currentCell = storage.mCell; + bool cellChange = currentCell && (actor.getCell() != currentCell); + if(!currentCell || cellChange) { - mCell = actor.getCell(); + currentCell = actor.getCell(); } MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); @@ -276,18 +314,19 @@ namespace MWMechanics actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - if (mActionCooldown > 0) + if (actionCooldown > 0) return false; float rangeAttack = 0; float rangeFollow = 0; + boost::shared_ptr& currentAction = storage.mCurrentAction; if (anim->upperBodyReady()) { - mCurrentAction = prepareNextAction(actor, target); - mActionCooldown = mCurrentAction->getActionCooldown(); + currentAction = prepareNextAction(actor, target); + actionCooldown = currentAction->getActionCooldown(); } - if (mCurrentAction.get()) - mCurrentAction->getCombatRange(rangeAttack, rangeFollow); + if (currentAction.get()) + currentAction->getCombatRange(rangeAttack, rangeFollow); // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; @@ -346,21 +385,23 @@ namespace MWMechanics weapRange = 150.f; } + + float& strength = storage.mStrength; // start new attack - if(mReadyToAttack) + if(readyToAttack) { - if(mTimerAttack <= -attacksPeriod) + if(timerAttack <= -attacksPeriod) { - mAttack = true; // attack starts just now + attack = true; // attack starts just now - if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement); + if (!distantCombat) attackType = chooseBestAttack(weapon, movement); else attackType = ESM::Weapon::AT_Chop; // cause it's =0 - mStrength = static_cast(rand()) / RAND_MAX; + strength = static_cast(rand()) / RAND_MAX; // Note: may be 0 for some animations - mTimerAttack = mMinMaxAttackDuration[attackType][0] + - (mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength; + timerAttack = minMaxAttackDuration[attackType][0] + + (minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength; //say a provoking combat phrase if (actor.getClass().isNpc()) @@ -411,13 +452,16 @@ namespace MWMechanics Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; float distToTarget = vDirToTarget.length(); + + Ogre::Vector3& lastActorPos = storage.mLastActorPos; + bool& followTarget = storage.mFollowTarget; bool isStuck = false; float speed = 0.0f; - if(mMovement.mPosition[1] && (mLastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) + if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) isStuck = true; - mLastActorPos = vActorPos; + lastActorPos = vActorPos; // check if actor can move along z-axis bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) @@ -427,7 +471,7 @@ namespace MWMechanics bool inLOS = distantCombat ? world->getLOS(actor, target) : true; // (within attack dist) || (not quite attack dist while following) - if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck))) + if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { //Melee and Close-up combat @@ -437,29 +481,30 @@ namespace MWMechanics // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate if (distantCombat) { - Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, mLastTargetPos, tReaction, weaptype, mStrength); - mLastTargetPos = vTargetPos; - mMovement.mRotation[0] = getXAngleToDir(vAimDir); - mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vAimDir.x, vAimDir.y, 0)); + Ogre::Vector3& lastTargetPos = storage.mLastTargetPos; + Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength); + lastTargetPos = vTargetPos; + movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir(vAimDir); } else { - mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + movement.mRotation[2] = getZAngleToDir(vDirToTarget); } // (not quite attack dist while following) - if (mFollowTarget && distToTarget > rangeAttack) + if (followTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target - mMovement.mPosition[1] = 1; + movement.mPosition[1] = 1; } else // (within attack dist) { - if(mMovement.mPosition[0] || mMovement.mPosition[1]) + if(movement.mPosition[0] || movement.mPosition[1]) { - mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; - mCombatMove = true; + timerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; + combatMove = true; } // only NPCs are smart enough to use dodge movements else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) @@ -467,20 +512,20 @@ namespace MWMechanics //apply sideway movement (kind of dodging) with some probability if(static_cast(rand())/RAND_MAX < 0.25) { - mMovement.mPosition[0] = static_cast(rand())/RAND_MAX < 0.5? 1: -1; - mTimerCombatMove = 0.05f + 0.15f * static_cast(rand())/RAND_MAX; - mCombatMove = true; + movement.mPosition[0] = static_cast(rand())/RAND_MAX < 0.5? 1: -1; + timerCombatMove = 0.05f + 0.15f * static_cast(rand())/RAND_MAX; + combatMove = true; } } if(distantCombat && distToTarget < rangeAttack/4) { - mMovement.mPosition[1] = -1; + movement.mPosition[1] = -1; } - mReadyToAttack = true; + readyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting - mFollowTarget = true; + followTarget = true; } } else // remote pathfinding @@ -489,8 +534,11 @@ namespace MWMechanics if (!distantCombat) inLOS = world->getLOS(actor, target); // check if shortcut is available - if(inLOS && (!isStuck || mReadyToAttack) - && (!mForceNoShortcut || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + bool& forceNoShortcut = storage.mForceNoShortcut; + ESM::Position& shortcutFailPos = storage.mShortcutFailPos; + + if(inLOS && (!isStuck || readyToAttack) + && (!forceNoShortcut || (Ogre::Vector3(shortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed @@ -504,21 +552,21 @@ namespace MWMechanics if(preferShortcut) { if (canMoveByZ) - mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); - mForceNoShortcut = false; - mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; + movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + movement.mRotation[2] = getZAngleToDir(vDirToTarget); + forceNoShortcut = false; + shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.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) + if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f) { - mForceNoShortcut = true; - mShortcutFailPos = pos; + forceNoShortcut = true; + shortcutFailPos = pos; } - mFollowTarget = false; + followTarget = false; buildNewPath(actor, target); //may fail to build a path, check before use @@ -536,7 +584,7 @@ namespace MWMechanics // 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); + movement.mRotation[2] = getZAngleToDir(vDirToTarget); preferShortcut = true; } } @@ -545,20 +593,20 @@ namespace MWMechanics if(!preferShortcut) { if(!mPathFinder.getPath().empty()) - mMovement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + movement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); else - mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + movement.mRotation[2] = getZAngleToDir(vDirToTarget); } } - mMovement.mPosition[1] = 1; - if (mReadyToAttack) + movement.mPosition[1] = 1; + if (readyToAttack) { // to stop possible sideway moving after target moved out of attack range - mCombatMove = true; - mTimerCombatMove = 0; + combatMove = true; + timerCombatMove = 0; } - mReadyToAttack = false; + readyToAttack = false; } if(!isStuck && distToTarget > rangeAttack && !distantCombat) @@ -579,16 +627,16 @@ namespace MWMechanics float t = s1/speed1; float s2 = speed2 * t; float t_swing = - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + - (mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast(rand()) / RAND_MAX; + minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + + (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast(rand()) / RAND_MAX; if (t + s2/speed1 <= t_swing) { - mReadyToAttack = true; - if(mTimerAttack <= -attacksPeriod) + readyToAttack = true; + if(timerAttack <= -attacksPeriod) { - mTimerAttack = t_swing; - mAttack = true; + timerAttack = t_swing; + attack = true; } } } @@ -598,7 +646,7 @@ namespace MWMechanics // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? - if((distToTarget > rangeAttack || mFollowTarget) && + if((distToTarget > rangeAttack || followTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { // probably walking into another NPC TODO: untested in combat situation @@ -610,8 +658,8 @@ namespace MWMechanics if(mPathFinder.isPathConstructed()) zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - if(mFollowTarget) - mFollowTarget = false; + if(followTarget) + followTarget = false; // FIXME: can fool actors to stay behind doors, etc. // Related to Bug#1102 and to some degree #1155 as well } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 916a1a1d5..307df3872 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -42,7 +42,7 @@ namespace MWMechanics virtual AiCombat *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; @@ -54,40 +54,14 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; private: - PathFinder mPathFinder; - // controls duration of the actual strike - float mTimerAttack; - float mTimerReact; - // controls duration of the sideway & forward moves - // when mCombatMove is true - float mTimerCombatMove; - - // AiCombat states - bool mReadyToAttack, mAttack; - bool mFollowTarget; - bool mCombatMove; - - float mStrength; // this is actually make sense only in ranged combat - float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations - bool mMinMaxAttackDurationInitialised; - - bool mForceNoShortcut; - ESM::Position mShortcutFailPos; - - Ogre::Vector3 mLastActorPos; - MWMechanics::Movement mMovement; int mTargetActorId; - Ogre::Vector3 mLastTargetPos; - const MWWorld::CellStore* mCell; - ObstacleCheck mObstacleCheck; - - boost::shared_ptr mCurrentAction; - float mActionCooldown; void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; + + } #endif diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 324bef322..c89cfe492 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -64,7 +64,7 @@ namespace MWMechanics return new AiEscort(*this); } - bool AiEscort::execute (const MWWorld::Ptr& actor,float duration) + bool AiEscort::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 820df969f..f02cdba22 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -33,7 +33,7 @@ namespace MWMechanics virtual AiEscort *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; @@ -48,7 +48,6 @@ namespace MWMechanics float mMaxDist; float mRemainingDuration; // In seconds - PathFinder mPathFinder; int mCellX; int mCellY; }; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index abde80c71..f309dc740 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -41,7 +41,7 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) } -bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) +bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { MWWorld::Ptr target = getTarget(); diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 483901b69..d5dd42826 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -35,7 +35,7 @@ namespace MWMechanics virtual AiFollow *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index ff3e84b98..df970f801 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -6,6 +6,7 @@ #include "../mwbase/world.hpp" #include "obstacle.hpp" +#include "aistate.hpp" namespace MWWorld { @@ -20,8 +21,10 @@ namespace ESM } } + namespace MWMechanics { + /// \brief Base class for AI packages class AiPackage { @@ -50,7 +53,7 @@ namespace MWMechanics /// Updates and runs the package (Should run every frame) /// \return Package completed? - virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0; + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration) = 0; /// Returns the TypeID of the AiPackage /// \see enum TypeId diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 3ef0e8e96..0c3de9643 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -30,7 +30,7 @@ AiPursue *MWMechanics::AiPursue::clone() const { return new AiPursue(*this); } -bool AiPursue::execute (const MWWorld::Ptr& actor, float duration) +bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { if(actor.getClass().getCreatureStats(actor).isDead()) return true; diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index a6eef2984..493a27985 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -31,7 +31,7 @@ namespace MWMechanics AiPursue(const ESM::AiSequence::AiPursue* pursue); virtual AiPursue *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; MWWorld::Ptr getTarget() const; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d76e513f6..990145c8d 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -2,6 +2,7 @@ #include "aisequence.hpp" #include "aipackage.hpp" +#include "aistate.hpp" #include "aiwander.hpp" #include "aiescort.hpp" @@ -146,7 +147,7 @@ bool AiSequence::isPackageDone() const return mDone; } -void AiSequence::execute (const MWWorld::Ptr& actor,float duration) +void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float duration) { if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr() && !actor.getClass().getCreatureStats(actor).getKnockedDown()) @@ -208,7 +209,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor,float duration) } } - if (package->execute (actor,duration)) + if (package->execute (actor,state,duration)) { // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 56f5dee31..7fd649d26 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -4,6 +4,7 @@ #include #include +//#include "aistate.hpp" namespace MWWorld { @@ -18,9 +19,15 @@ namespace ESM } } + + namespace MWMechanics { class AiPackage; + + template< class Base > class DerivedClassStorage; + class AiTemporaryBase; + typedef DerivedClassStorage AiState; /// \brief Sequence of AI-packages for a single actor /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ @@ -88,7 +95,7 @@ namespace MWMechanics void stopPursuit(); /// Execute current package, switching if needed. - void execute (const MWWorld::Ptr& actor,float duration); + void execute (const MWWorld::Ptr& actor, MWMechanics::AiState& state, float duration); /// Remove all packages. void clear(); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp new file mode 100644 index 000000000..7b670ad47 --- /dev/null +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -0,0 +1,136 @@ +#ifndef AISTATE_H +#define AISTATE_H + +#include +#include + +// c++11 replacement +#include +#include + +namespace MWMechanics +{ + + /** \brief stores one object of any class derived from Base. + * Requesting a certain dereived class via get() either returns + * the stored object if it has the correct type or otherwise replaces + * it with an object of the requested type. + */ + template< class Base > + class DerivedClassStorage + { + private: + Base* mStorage; + + // assert that Derived is derived from Base. + template< class Derived > + void assert_derived() + { + // c++11: + // static_assert( std::is_base_of , "DerivedClassStorage may only store derived classes" ); + + // boost: + BOOST_STATIC_ASSERT((boost::is_base_of::value));//,"DerivedClassStorage may only store derived classes"); + } + + //if needed you have to provide a clone member function + DerivedClassStorage( const DerivedClassStorage& other ); + DerivedClassStorage& operator=( const DerivedClassStorage& ); + + public: + /// \brief returns reference to stored object or deletes it and creates a fitting + template< class Derived > + Derived& get() + { + assert_derived(); + + Derived* result = dynamic_cast(mStorage); + + if(!result) + { + if(mStorage) + delete mStorage; + mStorage = result = new Derived(); + } + + //return a reference to the (new allocated) object + return *result; + } + + template< class Derived > + void store( const Derived& payload ) + { + assert_derived(); + if(mStorage) + delete mStorage; + mStorage = new Derived(payload); + } + + /// \brief takes ownership of the passed object + template< class Derived > + void moveIn( Derived* p ) + { + assert_derived(); + if(mStorage) + delete mStorage; + mStorage = p; + } + + /// \brief gives away ownership of object. Throws exception if storage does not contain Derived or is empty. + template< class Derived > + Derived* moveOut() + { + assert_derived(); + + + if(!mStorage) + throw std::runtime_error("Cant move out: empty storage."); + + Derived* result = dynamic_cast(mStorage); + + if(!mStorage) + throw std::runtime_error("Cant move out: wrong type requested."); + + return result; + } + + bool empty() const + { + return mStorage == NULL; + } + + const std::type_info& getType() const + { + return typeid(mStorage); + } + + + DerivedClassStorage():mStorage(NULL){}; + ~DerivedClassStorage() + { + if(mStorage) + delete mStorage; + }; + + + + }; + + + /// \brief base class for the temporary storage of AiPackages. + /** + * Each AI package with temporary values needs a AiPackageStorage class + * which is derived from AiTemporaryBase. The CharacterController holds a container + * AiState where one of these storages can be stored at a time. + * The execute(...) member function takes this container as an argument. + * */ + struct AiTemporaryBase + { + virtual ~AiTemporaryBase(){}; + }; + + /// \brief Container for AI package status. + typedef DerivedClassStorage AiState; +} + +#endif // AISTATE_H diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 7278e74f2..959784983 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -17,7 +17,7 @@ namespace MWMechanics { AiTravel::AiTravel(float x, float y, float z) - : mX(x),mY(y),mZ(z),mPathFinder() + : mX(x),mY(y),mZ(z) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -25,7 +25,6 @@ namespace MWMechanics AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) - , mPathFinder() , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -37,7 +36,7 @@ namespace MWMechanics return new AiTravel(*this); } - bool AiTravel::execute (const MWWorld::Ptr& actor,float duration) + bool AiTravel::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 91ee30253..c2c33c2cf 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -27,7 +27,7 @@ namespace MWMechanics virtual AiTravel *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; @@ -39,7 +39,6 @@ namespace MWMechanics int mCellX; int mCellY; - PathFinder mPathFinder; }; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index aee3e654d..a70200833 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -18,6 +18,8 @@ #include "steering.hpp" #include "movement.hpp" + + namespace MWMechanics { static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed @@ -26,6 +28,55 @@ namespace MWMechanics static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_END = 10; + /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. + struct AiWanderStorage : AiTemporaryBase + { + // the z rotation angle (degrees) we want to reach + // used every frame when mRotate is true + Ogre::Radian mTargetAngle; + bool mRotate; + float mReaction; // update some actions infrequently + + + AiWander::GreetingState mSaidGreeting; + int mGreetingTimer; + + // Cached current cell location + int mCellX; + int mCellY; + // Cell location multiplied by ESM::Land::REAL_SIZE + float mXCell; + float mYCell; + + const MWWorld::CellStore* mCell; // for detecting cell change + + // AiWander states + bool mChooseAction; + bool mIdleNow; + bool mMoveNow; + bool mWalking; + + unsigned short mPlayedIdle; + + AiWanderStorage(): + mTargetAngle(0), + mRotate(false), + mReaction(0), + mSaidGreeting(AiWander::Greet_None), + mGreetingTimer(0), + mCellX(std::numeric_limits::max()), + mCellY(std::numeric_limits::max()), + mXCell(0), + mYCell(0), + mCell(NULL), + mChooseAction(true), + mIdleNow(false), + mMoveNow(false), + mWalking(false), + mPlayedIdle(0) + {}; + }; + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) { @@ -37,19 +88,11 @@ namespace MWMechanics { // NOTE: mDistance and mDuration must be set already - mCellX = std::numeric_limits::max(); - mCellY = std::numeric_limits::max(); - mXCell = 0; - mYCell = 0; - mCell = NULL; + mStuckCount = 0;// TODO: maybe no longer needed mDoorCheckDuration = 0; mTrimCurrentNode = false; - mReaction = 0; - mRotate = false; - mTargetAngle = 0; - mSaidGreeting = Greet_None; - mGreetingTimer = 0; + mHasReturnPosition = false; mReturnPosition = Ogre::Vector3(0,0,0); @@ -61,13 +104,9 @@ namespace MWMechanics mTimeOfDay = 0; mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mPlayedIdle = 0; mStoredAvailableNodes = false; - mChooseAction = true; - mIdleNow = false; - mMoveNow = false; - mWalking = false; + } AiPackage * MWMechanics::AiWander::clone() const @@ -125,53 +164,65 @@ namespace MWMechanics * actors will enter combat (i.e. no longer wandering) and different pathfinding * will kick in. */ - bool AiWander::execute (const MWWorld::Ptr& actor,float duration) + bool AiWander::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { + // get or create temporary storage + AiWanderStorage& storage = state.get(); + + + const MWWorld::CellStore*& currentCell = storage.mCell; MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0) return true; // Don't bother with dead actors - bool cellChange = mCell && (actor.getCell() != mCell); - if(!mCell || cellChange) + bool cellChange = currentCell && (actor.getCell() != currentCell); + if(!currentCell || cellChange) { - mCell = actor.getCell(); + currentCell = actor.getCell(); mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 } - const ESM::Cell *cell = mCell->getCell(); + const ESM::Cell *cell = currentCell->getCell(); cStats.setDrawState(DrawState_Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); ESM::Position pos = actor.getRefData().getPosition(); - + + + bool& idleNow = storage.mIdleNow; + bool& moveNow = storage.mMoveNow; + bool& walking = storage.mWalking; // Check if an idle actor is too close to a door - if so start walking mDoorCheckDuration += duration; if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) { mDoorCheckDuration = 0; // restart timer if(mDistance && // actor is not intended to be stationary - mIdleNow && // but is in idle - !mWalking && // FIXME: some actors are idle while walking + idleNow && // but is in idle + !walking && // FIXME: some actors are idle while walking proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6*1.6)) // NOTE: checks interior cells only { - mIdleNow = false; - mMoveNow = true; + idleNow = false; + moveNow = true; mTrimCurrentNode = false; // just in case } } // Are we there yet? - if(mWalking && + bool& chooseAction = storage.mChooseAction; + if(walking && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; + moveNow = false; + walking = false; + chooseAction = true; mHasReturnPosition = false; } - if(mWalking) // have not yet reached the destination + + + if(walking) // have not yet reached the destination { // turn towards the next point in mPath zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); @@ -188,8 +239,8 @@ namespace MWMechanics trimAllowedNodes(mAllowedNodes, mPathFinder); mObstacleCheck.clear(); mPathFinder.clearPath(); - mWalking = false; - mMoveNow = true; + walking = false; + moveNow = true; } else // probably walking into another NPC { @@ -210,29 +261,33 @@ namespace MWMechanics mObstacleCheck.clear(); stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; + moveNow = false; + walking = false; + chooseAction = true; } //#endif } - - if (mRotate) + + + Ogre::Radian& targetAngle = storage.mTargetAngle; + bool& rotate = storage.mRotate; + if (rotate) { // Reduce the turning animation glitch by using a *HUGE* value of // epsilon... TODO: a proper fix might be in either the physics or the // animation subsystem - if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(5))) - mRotate = false; + if (zTurn(actor, targetAngle, Ogre::Degree(5))) + rotate = false; } - mReaction += duration; - if(mReaction < REACTION_INTERVAL) + float& lastReaction = storage.mReaction; + lastReaction += duration; + if(lastReaction < REACTION_INTERVAL) { return false; } else - mReaction = 0; + lastReaction = 0; // NOTE: everything below get updated every REACTION_INTERVAL seconds @@ -263,6 +318,12 @@ namespace MWMechanics } } + + + int& cachedCellX = storage.mCellX; + int& cachedCellY = storage.mCellY; + float& cachedCellXposition = storage.mXCell; + float& cachedCellYposition = storage.mYCell; // Initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { @@ -271,8 +332,8 @@ namespace MWMechanics pathgrid = world->getStore().get().search(*cell); // cache the current cell location - mCellX = cell->mData.mX; - mCellY = cell->mData.mY; + cachedCellX = cell->mData.mX; + cachedCellY = cell->mData.mY; // If there is no path this actor doesn't go anywhere. See: // https://forum.openmw.org/viewtopic.php?t=1556 @@ -286,12 +347,12 @@ namespace MWMechanics // destinations within the allowed set of pathgrid points (nodes). if(mDistance) { - mXCell = 0; - mYCell = 0; + cachedCellXposition = 0; + cachedCellYposition = 0; if(cell->isExterior()) { - mXCell = mCellX * ESM::Land::REAL_SIZE; - mYCell = mCellY * ESM::Land::REAL_SIZE; + cachedCellXposition = cachedCellX * ESM::Land::REAL_SIZE; + cachedCellYposition = cachedCellY * ESM::Land::REAL_SIZE; } // FIXME: There might be a bug here. The allowed node points are @@ -301,8 +362,8 @@ namespace MWMechanics // // convert npcPos to local (i.e. cell) co-ordinates Ogre::Vector3 npcPos(pos.pos); - npcPos[0] = npcPos[0] - mXCell; - npcPos[1] = npcPos[1] - mYCell; + npcPos[0] = npcPos[0] - cachedCellXposition; + npcPos[1] = npcPos[1] - cachedCellYposition; // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // NOTE: mPoints and mAllowedNodes are in local co-ordinates @@ -347,8 +408,8 @@ namespace MWMechanics mHasReturnPosition = false; if (mDistance == 0 && mHasReturnPosition && Ogre::Vector3(pos.pos).squaredDistance(mReturnPosition) > 20*20) { - mChooseAction = false; - mIdleNow = false; + chooseAction = false; + idleNow = false; if (!mPathFinder.isPathConstructed()) { @@ -370,30 +431,32 @@ namespace MWMechanics if(mPathFinder.isPathConstructed()) { - mMoveNow = false; - mWalking = true; + moveNow = false; + walking = true; } } } - if(mChooseAction) + AiWander::GreetingState& greetingState = storage.mSaidGreeting; + short unsigned& playedIdle = storage.mPlayedIdle; + if(chooseAction) { - mPlayedIdle = 0; - getRandomIdle(); // NOTE: sets mPlayedIdle with a random selection + playedIdle = 0; + getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection - if(!mPlayedIdle && mDistance) + if(!playedIdle && mDistance) { - mChooseAction = false; - mMoveNow = true; + chooseAction = false; + moveNow = true; } else { // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: MWWorld::TimeStamp currentTime = world->getTimeStamp(); mStartTime = currentTime; - playIdle(actor, mPlayedIdle); - mChooseAction = false; - mIdleNow = true; + playIdle(actor, playedIdle); + chooseAction = false; + idleNow = true; // Play idle voiced dialogue entries randomly int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); @@ -417,7 +480,7 @@ namespace MWMechanics } // Allow interrupting a walking actor to trigger a greeting - if(mIdleNow || mWalking) + if(idleNow || walking) { // Play a random voice greeting if the player gets too close int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); @@ -432,77 +495,76 @@ namespace MWMechanics Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); float playerDistSqr = playerPos.squaredDistance(actorPos); - if (mSaidGreeting == Greet_None) + int& greetingTimer = storage.mGreetingTimer; + if (greetingState == Greet_None) { if ((playerDistSqr <= helloDistance*helloDistance) && !player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) - mGreetingTimer++; + greetingTimer++; - if (mGreetingTimer >= GREETING_SHOULD_START) + if (greetingTimer >= GREETING_SHOULD_START) { - mSaidGreeting = Greet_InProgress; + greetingState = Greet_InProgress; MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); - mGreetingTimer = 0; + greetingTimer = 0; } } - if(mSaidGreeting == Greet_InProgress) + if(greetingState == Greet_InProgress) { - mGreetingTimer++; + greetingTimer++; - if(mWalking) + if(walking) { stopWalking(actor); - mMoveNow = false; - mWalking = false; + moveNow = false; + walking = false; mObstacleCheck.clear(); - mIdleNow = true; - getRandomIdle(); + idleNow = true; + getRandomIdle(playedIdle); } - if(!mRotate) + if(!rotate) { Ogre::Vector3 dir = playerPos - actorPos; - float length = dir.length(); - float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) * - ((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees(); - float actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll().valueDegrees(); + Ogre::Radian faceAngle = Ogre::Math::ATan2(dir.x,dir.y); + Ogre::Radian actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll(); // an attempt at reducing the turning animation glitch - if(abs(abs(faceAngle) - abs(actorAngle)) >= 5) // TODO: is there a better way? + if( Ogre::Math::Abs( faceAngle - actorAngle ) >= Ogre::Degree(5) ) // TODO: is there a better way? { - mTargetAngle = faceAngle; - mRotate = true; + targetAngle = faceAngle; + rotate = true; } } - if (mGreetingTimer >= GREETING_SHOULD_END) + if (greetingTimer >= GREETING_SHOULD_END) { - mSaidGreeting = Greet_Done; - mGreetingTimer = 0; + greetingState = Greet_Done; + greetingTimer = 0; } } - if (mSaidGreeting == MWMechanics::AiWander::Greet_Done) + if (greetingState == MWMechanics::AiWander::Greet_Done) { static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() .get().find("fGreetDistanceReset")->getFloat(); if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) - mSaidGreeting = Greet_None; + greetingState = Greet_None; } // Check if idle animation finished - if(!checkIdle(actor, mPlayedIdle) && (playerDistSqr > helloDistance*helloDistance || mSaidGreeting == MWMechanics::AiWander::Greet_Done)) + if(!checkIdle(actor, playedIdle) && (playerDistSqr > helloDistance*helloDistance || greetingState == MWMechanics::AiWander::Greet_Done)) { - mPlayedIdle = 0; - mIdleNow = false; - mChooseAction = true; + playedIdle = 0; + idleNow = false; + chooseAction = true; } } - if(mMoveNow && mDistance) + if(moveNow && mDistance) { // Construct a new path if there isn't one if(!mPathFinder.isPathConstructed()) @@ -516,8 +578,8 @@ namespace MWMechanics // convert dest to use world co-ordinates ESM::Pathgrid::Point dest; - dest.mX = destNodePos[0] + mXCell; - dest.mY = destNodePos[1] + mYCell; + dest.mX = destNodePos[0] + cachedCellXposition; + dest.mY = destNodePos[1] + cachedCellYposition; dest.mZ = destNodePos[2]; // actor position is already in world co-ordinates @@ -548,8 +610,8 @@ namespace MWMechanics mAllowedNodes.push_back(mCurrentNode); mCurrentNode = temp; - mMoveNow = false; - mWalking = true; + moveNow = false; + walking = true; } // Choose a different node and delete this one from possible nodes because it is uncreachable: else @@ -648,7 +710,7 @@ namespace MWMechanics } } - void AiWander::getRandomIdle() + void AiWander::getRandomIdle(short unsigned& playedIdle) { unsigned short idleRoll = 0; @@ -661,7 +723,7 @@ namespace MWMechanics unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { - mPlayedIdle = counter+2; + playedIdle = counter+2; idleRoll = randSelect; } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 59a51446e..0600909ba 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -12,6 +12,9 @@ #include "../mwworld/timestamp.hpp" + +#include "aistate.hpp" + namespace ESM { namespace AiSequence @@ -21,7 +24,9 @@ namespace ESM } namespace MWMechanics -{ +{ + + /// \brief Causes the Actor to wander within a specified range class AiWander : public AiPackage { @@ -36,12 +41,11 @@ namespace MWMechanics AiWander (const ESM::AiSequence::AiWander* wander); - // NOTE: mDistance and mDuration must be set already - void init(); + virtual AiPackage *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); virtual int getTypeId() const; @@ -51,11 +55,20 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + + enum GreetingState { + Greet_None, + Greet_InProgress, + Greet_Done + }; private: + // NOTE: mDistance and mDuration must be set already + void init(); + void stopWalking(const MWWorld::Ptr& actor); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - void getRandomIdle(); + void getRandomIdle(unsigned short& playedIdle); int mDistance; // how far the actor can wander from the spawn point int mDuration; @@ -63,36 +76,21 @@ namespace MWMechanics std::vector mIdle; bool mRepeat; - enum GreetingState { - Greet_None, - Greet_InProgress, - Greet_Done - }; - GreetingState mSaidGreeting; - int mGreetingTimer; + bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... Ogre::Vector3 mReturnPosition; - // Cached current cell location - int mCellX; - int mCellY; - // Cell location multiplied by ESM::Land::REAL_SIZE - float mXCell; - float mYCell; - const MWWorld::CellStore* mCell; // for detecting cell change + + // if false triggers calculating allowed nodes based on mDistance bool mStoredAvailableNodes; - // AiWander states - bool mChooseAction; - bool mIdleNow; - bool mMoveNow; - bool mWalking; - unsigned short mPlayedIdle; + + MWWorld::TimeStamp mStartTime; @@ -103,18 +101,16 @@ namespace MWMechanics void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); - PathFinder mPathFinder; +// PathFinder mPathFinder; - ObstacleCheck mObstacleCheck; +// ObstacleCheck mObstacleCheck; float mDoorCheckDuration; int mStuckCount; - // the z rotation angle (degrees) we want to reach - // used every frame when mRotate is true - float mTargetAngle; - bool mRotate; - float mReaction; // update some actions infrequently + }; + + } #endif diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 550cae5fc..2a14c53b9 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -6,6 +6,7 @@ #include #include "../mwworld/ptr.hpp" +#include "aistate.hpp" namespace MWWorld { @@ -138,6 +139,9 @@ class CharacterController { MWWorld::Ptr mPtr; MWRender::Animation *mAnimation; + + // + AiState mAiState; typedef std::deque > AnimationQueue; AnimationQueue mAnimQueue; @@ -218,6 +222,8 @@ public: { return mDeathState != CharState_None; } void forceStateUpdate(); + + AiState& getAiState() { return mAiState; } }; void getWeaponGroup(WeaponType weaptype, std::string &group); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 62e23db58..f1279c415 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -278,9 +278,8 @@ namespace MWMechanics const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); float directionX = nextPoint.mX - x; float directionY = nextPoint.mY - y; - float directionResult = sqrt(directionX * directionX + directionY * directionY); - return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees(); + return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); } bool PathFinder::checkWaypoint(float x, float y, float z)