From 4f08084e7921603e2b590a645b1dbaa8d0777d87 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 30 Apr 2017 22:29:59 +0900 Subject: [PATCH] Make stationary actors return to position on load Also makes wandering actors resume their previous destination when an interrupting combat or pursuit ends. (Fixes #3656) --- apps/openmw/mwmechanics/aisequence.cpp | 15 ----- apps/openmw/mwmechanics/aiwander.cpp | 79 ++++++++++++++------------ apps/openmw/mwmechanics/aiwander.hpp | 16 ++---- 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 82a2e2c3d..2f33d6e4e 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -295,21 +295,6 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) if (actor == getPlayer()) throw std::runtime_error("Can't add AI packages to player"); - if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) - { - // Notify AiWander of our current position so we can return to it after combat finished - for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) - { - if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat - && (*iter)->getTarget() == (&package)->getTarget()) - { - return; // already in combat with this actor - } - else if ((*iter)->getTypeId() == AiPackage::TypeIdWander) - static_cast(*iter)->setReturnPosition(actor.getRefData().getPosition().asVec3()); - } - } - // Stop combat when a non-combat AI package is added if (isActualAiPackage(package.getTypeId())) stopCombat(); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a992bc8d4..ad6db4d01 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -113,7 +113,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), - mRepeat(repeat), mStoredInitialActorPosition(false) + mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)) { mIdle.resize(8, 0); init(); @@ -123,9 +123,6 @@ namespace MWMechanics { // NOTE: mDistance and mDuration must be set already - mHasReturnPosition = false; - mReturnPosition = osg::Vec3f(0,0,0); - if(mDistance < 0) mDistance = 0; if(mDuration < 0) @@ -211,6 +208,19 @@ namespace MWMechanics cStats.setMovementFlag(CreatureStats::Flag_Run, false); ESM::Position pos = actor.getRefData().getPosition(); + + // If there is already a destination due to the package having been interrupted by a combat or pursue package, + // rebuild a path to it + if (!mPathFinder.isPathConstructed() && mHasDestination) + { + ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); + + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + + if (mPathFinder.isPathConstructed()) + storage.setState(Wander_Walking); + } doPerFrameActionsForState(actor, duration, storage, pos); @@ -253,9 +263,8 @@ namespace MWMechanics getAllowedNodes(actor, currentCell->getCell(), storage); } - // Actor becomes stationary - see above URL's for previous research - // If a creature or an NPC with a wander distance and no pathgrid is available, - // randomly idle or wander around near spawn point + // If the package has a wander distance but no pathgrid is available, + // randomly idle or wander near spawn point if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 96) { @@ -277,10 +286,8 @@ namespace MWMechanics mDistance = 0; // For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere - if (cellChange) - mHasReturnPosition = false; - if (mDistance == 0 && mHasReturnPosition - && (pos.asVec3() - mReturnPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE)) + if (mDistance == 0 && !cellChange + && (pos.asVec3() - mInitialActorPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE)) { returnToStartLocation(actor, storage, pos); } @@ -334,7 +341,8 @@ namespace MWMechanics { if (!mPathFinder.isPathConstructed()) { - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); + mDestination = mInitialActorPosition; + ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); // actor position is already in world coordinates ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); @@ -345,6 +353,7 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) { storage.setState(Wander_Walking); + mHasDestination = true; } } } @@ -357,7 +366,6 @@ namespace MWMechanics const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ); std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here - osg::Vec3f destination; ESM::Pathgrid::Point destinationPosition; bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); do { @@ -369,17 +377,18 @@ namespace MWMechanics const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); const float destinationZ = mInitialActorPosition.z(); destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ); - destination = osg::Vec3f(destinationX, destinationY, destinationZ); + mDestination = osg::Vec3f(destinationX, destinationY, destinationZ); // Check if land creature will walk onto water or if water creature will swim onto land - if ((!isWaterCreature && !destinationIsAtWater(actor, destination)) || - (isWaterCreature && !destinationThroughGround(currentPositionVec3f, destination))) { + if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || + (isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) { mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); mPathFinder.addPointToPath(destinationPosition); if (mPathFinder.isPathConstructed()) { storage.setState(Wander_Walking, true); + mHasDestination = true; } return; } @@ -466,7 +475,10 @@ namespace MWMechanics GreetingState& greetingState = storage.mSaidGreeting; if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { - storage.setState(Wander_ChooseAction); + if (mPathFinder.isPathConstructed()) + storage.setState(Wander_Walking); + else + storage.setState(Wander_ChooseAction); } } @@ -478,7 +490,6 @@ namespace MWMechanics { stopWalking(actor, storage); storage.setState(Wander_ChooseAction); - mHasReturnPosition = false; } else { @@ -490,8 +501,8 @@ namespace MWMechanics void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - short unsigned& idleAnimation = storage.mIdleAnimation; - idleAnimation = getRandomIdle(); + unsigned short idleAnimation = getRandomIdle(); + storage.mIdleAnimation = idleAnimation; if (!idleAnimation && mDistance) { @@ -525,7 +536,7 @@ namespace MWMechanics storage.mTrimCurrentNode = true; trimAllowedNodes(storage.mAllowedNodes, mPathFinder); mObstacleCheck.clear(); - mPathFinder.clearPath(); + stopWalking(actor, storage); storage.setState(Wander_MoveNow); } @@ -535,9 +546,7 @@ namespace MWMechanics // if stuck for sufficiently long, act like current location was the destination if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset { - //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; mObstacleCheck.clear(); - stopWalking(actor, storage); storage.setState(Wander_ChooseAction); storage.mStuckCount = 0; @@ -612,7 +621,7 @@ namespace MWMechanics if (storage.mState == Wander_Walking) { - stopWalking(actor, storage); + stopWalking(actor, storage, false); mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -657,6 +666,8 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) { + mDestination = osg::Vec3f(dest.mX, dest.mY, dest.mZ); + mHasDestination = true; // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); @@ -710,9 +721,13 @@ namespace MWMechanics return TypeIdWander; } - void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) + void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage, bool clearPath) { - mPathFinder.clearPath(); + if (clearPath) + { + mPathFinder.clearPath(); + mHasDestination = false; + } actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } @@ -743,15 +758,6 @@ namespace MWMechanics } } - void AiWander::setReturnPosition(const osg::Vec3f& position) - { - if (!mHasReturnPosition) - { - mHasReturnPosition = true; - mReturnPosition = position; - } - } - short unsigned AiWander::getRandomIdle() { unsigned short idleRoll = 0; @@ -945,6 +951,8 @@ namespace MWMechanics , mTimeOfDay(wander->mData.mTimeOfDay) , mRepeat(wander->mData.mShouldRepeat != 0) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) + , mHasDestination(false) + , mDestination(osg::Vec3f(0, 0, 0)) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; @@ -953,7 +961,6 @@ namespace MWMechanics if (mRemainingDuration <= 0 || mRemainingDuration >= 24) mRemainingDuration = mDuration; - init(); } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 13a4a1b4a..01d889e2f 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -44,10 +44,6 @@ namespace MWMechanics virtual int getTypeId() const; - /// Set the position to return to for a stationary (non-wandering) actor - /** In case another AI package moved the actor elsewhere **/ - void setReturnPosition (const osg::Vec3f& position); - virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); @@ -70,7 +66,7 @@ namespace MWMechanics private: // NOTE: mDistance and mDuration must be set already void init(); - void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage, bool clearPath = true); /// Have the given actor play an idle animation /// @return Success or error @@ -87,7 +83,7 @@ namespace MWMechanics void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); @@ -102,11 +98,11 @@ namespace MWMechanics std::vector mIdle; bool mRepeat; - bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, - // if we had the actor in the AiWander constructor... - osg::Vec3f mReturnPosition; - osg::Vec3f mInitialActorPosition; bool mStoredInitialActorPosition; + osg::Vec3f mInitialActorPosition; + + bool mHasDestination; + osg::Vec3f mDestination; void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage);