diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 36aebfe4a..03532cbae 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -16,7 +16,6 @@ #include "movement.hpp" /* - TODO: Test vanilla behavior on passing x0, y0, and z0 with duration of anything including 0. TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z. TODO: Take account for actors being in different cells. */ @@ -109,7 +108,7 @@ namespace MWMechanics } else { - // Stop moving if the player is to far away + // Stop moving if the player is too far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 250; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 0bf02aba9..a6694c314 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -75,6 +75,21 @@ namespace MWMechanics std::vector mBadIdles; // Idle animations that when called cause errors PathFinder mPathFinder; + + // do we need to calculate allowed nodes based on mDistance + bool mPopulateAvailableNodes; + + // allowed pathgrid nodes based on mDistance from the spawn point + // in local coordinates of mCell + std::vector mAllowedNodes; + + ESM::Pathgrid::Point mCurrentNode; + bool mTrimCurrentNode; + + ObstacleCheck mObstacleCheck; + + float mDoorCheckDuration; + int mStuckCount; AiWanderStorage(): mTargetAngleRadians(0), @@ -87,7 +102,12 @@ namespace MWMechanics mIsWanderingManually(false), mCanWanderAlongPathGrid(true), mIdleAnimation(0), - mBadIdles() + mBadIdles(), + mPopulateAvailableNodes(true), + mAllowedNodes(), + mTrimCurrentNode(false), + mDoorCheckDuration(0), // TODO: maybe no longer needed + mStuckCount(0) {}; void setState(const AiWander::WanderState wanderState, const bool isManualWander = false) { @@ -108,11 +128,6 @@ namespace MWMechanics { // NOTE: mDistance and mDuration must be set already - - mStuckCount = 0;// TODO: maybe no longer needed - mDoorCheckDuration = 0; - mTrimCurrentNode = false; - mHasReturnPosition = false; mReturnPosition = osg::Vec3f(0,0,0); @@ -120,16 +135,6 @@ namespace MWMechanics mDistance = 0; if(mDuration < 0) mDuration = 0; - if(mDuration == 0) - mTimeOfDay = 0; - if(mDuration > 24) - { - mDuration = 24; - mRemainingDuration = (mDuration); - } - - mPopulateAvailableNodes = true; - } AiPackage * MWMechanics::AiWander::clone() const @@ -202,7 +207,7 @@ namespace MWMechanics if(!currentCell || cellChange) { currentCell = actor.getCell(); - mPopulateAvailableNodes = true; + storage.mPopulateAvailableNodes = true; } mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); @@ -248,7 +253,7 @@ namespace MWMechanics } // Initialization to discover & store allowed node points for this actor. - if (mPopulateAvailableNodes) + if (storage.mPopulateAvailableNodes) { getAllowedNodes(actor, currentCell->getCell(), storage); } @@ -256,19 +261,19 @@ namespace MWMechanics // 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(mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { + 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) { wanderNearStart(actor, storage, mDistance); } else { storage.setState(Wander_IdleNow); } - } else if (mAllowedNodes.empty() && !storage.mIsWanderingManually) { + } else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) { storage.mCanWanderAlongPathGrid = false; } // If Wandering manually and hit an obstacle, stop - if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) { + if (storage.mIsWanderingManually && storage.mObstacleCheck.check(actor, duration, 2.0f)) { completeManualWalking(actor, storage); } @@ -297,7 +302,7 @@ namespace MWMechanics // Construct a new path if there isn't one if(!storage.mPathFinder.isPathConstructed()) { - if (!mAllowedNodes.empty()) + if (!storage.mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } @@ -314,6 +319,7 @@ namespace MWMechanics return mRepeat; } + bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) { if (mDuration) @@ -401,7 +407,7 @@ namespace MWMechanics void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { stopWalking(actor, storage); - mObstacleCheck.clear(); + storage.mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -434,15 +440,15 @@ namespace MWMechanics void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { // Check if an idle actor is too close to a door - if so start walking - mDoorCheckDuration += duration; - if (mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + storage.mDoorCheckDuration += duration; + if (storage.mDoorCheckDuration >= DOOR_CHECK_INTERVAL) { - mDoorCheckDuration = 0; // restart timer + storage.mDoorCheckDuration = 0; // restart timer if (mDistance && // actor is not intended to be stationary proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only { storage.setState(Wander_MoveNow); - mTrimCurrentNode = false; // just in case + storage.mTrimCurrentNode = false; // just in case return; } } @@ -515,15 +521,15 @@ namespace MWMechanics zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - if (mObstacleCheck.check(actor, duration)) + if (storage.mObstacleCheck.check(actor, duration)) { // first check if we're walking into a door if (proximityToDoor(actor)) // NOTE: checks interior cells only { // remove allowed points then select another random destination - mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, storage.mPathFinder); - mObstacleCheck.clear(); + storage.mTrimCurrentNode = true; + trimAllowedNodes(storage.mAllowedNodes, storage.mPathFinder); + storage.mObstacleCheck.clear(); storage.mPathFinder.clearPath(); storage.setState(Wander_MoveNow); } @@ -531,9 +537,9 @@ namespace MWMechanics { // TODO: diagonal should have same animation as walk forward // but doesn't seem to do that? - mObstacleCheck.takeEvasiveAction(movement); + storage.mObstacleCheck.takeEvasiveAction(movement); } - mStuckCount++; // TODO: maybe no longer needed + storage.mStuckCount++; // TODO: maybe no longer needed } else { @@ -541,14 +547,14 @@ namespace MWMechanics } // if stuck for sufficiently long, act like current location was the destination - if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset { //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; - mObstacleCheck.clear(); + storage.mObstacleCheck.clear(); stopWalking(actor, storage); storage.setState(Wander_ChooseAction); - mStuckCount = 0; + storage.mStuckCount = 0; } } @@ -621,7 +627,7 @@ namespace MWMechanics if (storage.mState == Wander_Walking) { stopWalking(actor, storage); - mObstacleCheck.clear(); + storage.mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -653,8 +659,8 @@ namespace MWMechanics void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) { - unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); - ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); + unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); + ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); ToWorldCoordinates(dest, storage.mCell->getCell()); // actor position is already in world co-ordinates @@ -666,20 +672,20 @@ namespace MWMechanics if (storage.mPathFinder.isPathConstructed()) { // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): - ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; + storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); // check if mCurrentNode was taken out of mAllowedNodes - if (mTrimCurrentNode && mAllowedNodes.size() > 1) - mTrimCurrentNode = false; + if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1) + storage.mTrimCurrentNode = false; else - mAllowedNodes.push_back(mCurrentNode); - mCurrentNode = temp; + storage.mAllowedNodes.push_back(storage.mCurrentNode); + storage.mCurrentNode = temp; storage.setState(Wander_Walking); } // Choose a different node and delete this one from possible nodes because it is uncreachable: else - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); } void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) @@ -788,17 +794,17 @@ namespace MWMechanics if (mDistance == 0) return; - if (mPopulateAvailableNodes) - getAllowedNodes(actor, actor.getCell()->getCell(), state.get()); + AiWanderStorage& storage = state.get(); + if (storage.mPopulateAvailableNodes) + getAllowedNodes(actor, actor.getCell()->getCell(), storage); - if (mAllowedNodes.empty()) + if (storage.mAllowedNodes.empty()) return; + int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); + ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; state.moveIn(new AiWanderStorage()); - int index = Misc::Rng::rollDice(mAllowedNodes.size()); - ESM::Pathgrid::Point dest = mAllowedNodes[index]; - dest.mX += OffsetToPreventOvercrowding(); dest.mY += OffsetToPreventOvercrowding(); ToWorldCoordinates(dest, actor.getCell()->getCell()); @@ -808,7 +814,7 @@ namespace MWMechanics actor.getClass().adjustPosition(actor, false); // may have changed cell - mPopulateAvailableNodes = true; + storage.mPopulateAvailableNodes = true; } int AiWander::OffsetToPreventOvercrowding() @@ -823,7 +829,7 @@ namespace MWMechanics pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); const MWWorld::CellStore* cellStore = actor.getCell(); - mAllowedNodes.clear(); + storage.mAllowedNodes.clear(); // If there is no path this actor doesn't go anywhere. See: // https://forum.openmw.org/viewtopic.php?t=1556 @@ -856,40 +862,40 @@ namespace MWMechanics if((npcPos - nodePos).length2() <= mDistance * mDistance && cellStore->isPointConnected(closestPointIndex, counter)) { - mAllowedNodes.push_back(pathgrid->mPoints[counter]); + storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); pointIndex = counter; } } - if (mAllowedNodes.size() == 1) + if (storage.mAllowedNodes.size() == 1) { - AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex); + AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex, storage); } - if(!mAllowedNodes.empty()) + if(!storage.mAllowedNodes.empty()) { - SetCurrentNodeToClosestAllowedNode(npcPos); + SetCurrentNodeToClosestAllowedNode(npcPos, storage); } } - mPopulateAvailableNodes = false; + storage.mPopulateAvailableNodes = false; } // When only one path grid point in wander distance, // additional points for NPC to wander to are: // 1. NPC's initial location // 2. Partway along the path between the point and its connected points. - void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex) + void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage) { - mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); + storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); for (std::vector::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it) { if (it->mV0 == pointIndex) { - AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]); + AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1], storage); } } } - void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end) + void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage) { osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start); osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart; @@ -901,16 +907,16 @@ namespace MWMechanics // must not travel longer than distance between waypoints or NPC goes past waypoint distance = std::min(distance, static_cast(length)); delta *= distance; - mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); + storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); } - void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos) + void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos, AiWanderStorage& storage) { float distanceToClosestNode = std::numeric_limits::max(); unsigned int index = 0; - for (unsigned int counterThree = 0; counterThree < mAllowedNodes.size(); counterThree++) + for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++) { - osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); + osg::Vec3f nodePos(PathFinder::MakeOsgVec3(storage.mAllowedNodes[counterThree])); float tempDist = (npcPos - nodePos).length2(); if (tempDist < distanceToClosestNode) { @@ -918,8 +924,8 @@ namespace MWMechanics distanceToClosestNode = tempDist; } } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); + storage.mCurrentNode = storage.mAllowedNodes[index]; + storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index); } void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 2a277de16..d652d4799 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -10,7 +10,6 @@ #include "../mwworld/timestamp.hpp" - #include "aistate.hpp" namespace ESM @@ -23,9 +22,7 @@ namespace ESM } namespace MWMechanics -{ - - +{ struct AiWanderStorage; /// \brief Causes the Actor to wander within a specified range @@ -42,8 +39,6 @@ namespace MWMechanics AiWander (const ESM::AiSequence::AiWander* wander); - - virtual AiPackage *clone() const; virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); @@ -72,10 +67,10 @@ namespace MWMechanics Wander_MoveNow, Wander_Walking }; + private: // NOTE: mDistance and mDuration must be set already void init(); - void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); /// Have the given actor play an idle animation @@ -107,42 +102,16 @@ namespace MWMechanics int mTimeOfDay; 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; - - - // do we need to calculate allowed nodes based on mDistance - bool mPopulateAvailableNodes; - - - - - - // allowed pathgrid nodes based on mDistance from the spawn point - // in local coordinates of mCell - // FIXME: move to AiWanderStorage - std::vector mAllowedNodes; - void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); - - // FIXME: move to AiWanderStorage - ESM::Pathgrid::Point mCurrentNode; - bool mTrimCurrentNode; - void trimAllowedNodes(std::vector& nodes, - const PathFinder& pathfinder); - - - // FIXME: move to AiWanderStorage -// ObstacleCheck mObstacleCheck; - float mDoorCheckDuration; - int mStuckCount; + + void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); // constants for converting idleSelect values into groupNames enum GroupIndex @@ -154,19 +123,17 @@ namespace MWMechanics /// convert point from local (i.e. cell) to world co-ordinates void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); - void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos); + void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos, AiWanderStorage& storage); - void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex); + void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage); - void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end); + void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static int OffsetToPreventOvercrowding(); - }; - - + }; } #endif