From 1e7cf4ae1c3fd90ebace6f0de1e34fe9387be1e7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 25 May 2013 04:15:24 +0200 Subject: [PATCH 01/13] GLES2 experiment --- apps/openmw/mwrender/renderingmanager.cpp | 13 +++++++++---- extern/shiny/Docs/Materials.dox | 2 ++ extern/shiny/Editor/Actions.hpp | 2 +- extern/shiny/Editor/Query.hpp | 2 +- files/materials/core.h | 2 +- libs/openengine/ogre/renderer.cpp | 1 + 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9061f8402..a5dc8ec68 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -64,13 +64,16 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b { // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); + bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos); // glsl is only supported in opengl mode and hlsl only in direct3d mode. - if (Settings::Manager::getString("shader mode", "General") == "" - || (openGL && Settings::Manager::getString("shader mode", "General") == "hlsl") - || (!openGL && Settings::Manager::getString("shader mode", "General") == "glsl")) + std::string currentMode = Settings::Manager::getString("shader mode", "General"); + if (currentMode == "" + || (openGL && currentMode == "hlsl") + || (!openGL && currentMode == "glsl") + || (glES && currentMode != "glsles")) { - Settings::Manager::setString("shader mode", "General", openGL ? "glsl" : "hlsl"); + Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl"); } mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); @@ -93,6 +96,8 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b std::string l = Settings::Manager::getString("shader mode", "General"); if (l == "glsl") lang = sh::Language_GLSL; + else if (l == "glsles") + lang = sh::Language_GLSLES; else if (l == "hlsl") lang = sh::Language_HLSL; else diff --git a/extern/shiny/Docs/Materials.dox b/extern/shiny/Docs/Materials.dox index 91e9be4b3..d08599a04 100644 --- a/extern/shiny/Docs/Materials.dox +++ b/extern/shiny/Docs/Materials.dox @@ -87,6 +87,8 @@ Now, let's get into writing our shader! As you can guess from above, the filename should be 'example.shader'. Make sure to also copy the 'core.h' file to the same location. It is included in shiny's source tree under 'Extra/'. + Important: a newline at the end of the file is required. Many editors do this automatically or can be configured to do so. If there is no newline at the end of the file, and the last line is '#endif', you will get the rather cryptic error message " ill formed preprocessor directive: #endif" from boost::wave. + \code #include "core.h" diff --git a/extern/shiny/Editor/Actions.hpp b/extern/shiny/Editor/Actions.hpp index e5cb6df6a..1bbdbe5a9 100644 --- a/extern/shiny/Editor/Actions.hpp +++ b/extern/shiny/Editor/Actions.hpp @@ -10,7 +10,7 @@ namespace sh { public: virtual void execute() = 0; - virtual ~Action(); + virtual ~Action() {} }; class ActionDeleteMaterial : public Action diff --git a/extern/shiny/Editor/Query.hpp b/extern/shiny/Editor/Query.hpp index 7aec68488..d98c8c9b2 100644 --- a/extern/shiny/Editor/Query.hpp +++ b/extern/shiny/Editor/Query.hpp @@ -15,7 +15,7 @@ class Query public: Query() : mDone(false) {} - virtual ~Query(); + virtual ~Query() {} void execute(); diff --git a/files/materials/core.h b/files/materials/core.h index 3385e5fac..c15ec6bad 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -58,7 +58,7 @@ #endif -#if SH_GLSL == 1 +#if SH_GLSL == 1 || SH_GLSLES == 1 #define shFract(val) fract(val) diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 7be713796..e699fbbcc 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -206,6 +206,7 @@ void OgreRenderer::configure(const std::string &logPath, pluginDir = absPluginPath.string(); Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); From cc747d8ae4c8f46c789fe024b7c5489291ca9057 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 25 May 2013 04:23:07 +0200 Subject: [PATCH 02/13] GLSL ES: precision qualifiers are required --- files/materials/core.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/files/materials/core.h b/files/materials/core.h index c15ec6bad..d53d8d4a9 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -64,6 +64,11 @@ @version 120 +#if SH_GLSLES == 1 +precision mediump int; +precision mediump float; +#endif + #define float2 vec2 #define float3 vec3 #define float4 vec4 From c2c88acc7b83cf40e3f367dfd8ca50157d570a9a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 25 May 2013 04:26:40 +0200 Subject: [PATCH 03/13] GLSLES: only the fragment shader needs precision explicitely set. --- files/materials/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/materials/core.h b/files/materials/core.h index d53d8d4a9..e5d58ba25 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -64,7 +64,7 @@ @version 120 -#if SH_GLSLES == 1 +#if SH_GLSLES == 1 && SH_FRAGMENT_SHADER precision mediump int; precision mediump float; #endif From de5a08a07d359841828827f8b72fdc87ca1421c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 25 May 2013 05:40:35 +0200 Subject: [PATCH 04/13] Version should be 100 for GLSLES --- files/materials/core.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/files/materials/core.h b/files/materials/core.h index e5d58ba25..6f8179c08 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -62,7 +62,11 @@ #define shFract(val) fract(val) +#if SH_GLSLES == 1 + @version 100 +#else @version 120 +#endif #if SH_GLSLES == 1 && SH_FRAGMENT_SHADER precision mediump int; From 2850032d9e442f77cec9e6ebac90ac09bd645db8 Mon Sep 17 00:00:00 2001 From: eroen Date: Sun, 26 May 2013 10:36:17 +0200 Subject: [PATCH 05/13] libc++ fixes: avcodec/avformat workaround With libc++, string includes stdint.h, which breaks the fragile avformat.h workaround, which depends on __STDC_CONSTANT_MACROS being defined before stdint.h is included. Moving the string inclusion after that eyesore shouldn't break anything. --- apps/openmw/mwsound/ffmpeg_decoder.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 32b2797ed..a5e5b5083 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -1,8 +1,6 @@ #ifndef GAME_SOUND_FFMPEG_DECODER_H #define GAME_SOUND_FFMPEG_DECODER_H -#include - // FIXME: This can't be right? The headers refuse to build without UINT64_C, // which only gets defined in stdint.h in either C99 mode or with this macro // defined... @@ -14,6 +12,8 @@ extern "C" #include } +#include + #include "sound_decoder.hpp" From 886bc7e2f6563aaa6bd01608991dfec54a95a488 Mon Sep 17 00:00:00 2001 From: eroen Date: Sun, 26 May 2013 12:44:10 +0200 Subject: [PATCH 06/13] libc++ fixes: don't rely on tr1 libc++ doesn't ship tr1, but ships unordered_map as it is part of c++11. Since this is the only tr1 header used in openmw, add a check for c++11 unordered_map and fallback to tr1 unordered_map if it's not found. --- CMakeLists.txt | 6 ++++++ components/files/configurationmanager.hpp | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b989297b3..43415c953 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,6 +181,12 @@ if (UNIX AND NOT APPLE) find_package (Threads) endif() +include (CheckIncludeFileCXX) +check_include_file_cxx(unordered_map HAVE_UNORDERED_MAP) +if (HAVE_UNORDERED_MAP) + add_definitions(-DHAVE_UNORDERED_MAP) +endif () + set(BOOST_COMPONENTS system filesystem program_options thread date_time wave) diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 9056e792d..765f1cebf 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -3,6 +3,8 @@ #ifdef _WIN32 #include +#elif defined HAVE_UNORDERED_MAP +#include #else #include #endif @@ -48,7 +50,11 @@ struct ConfigurationManager typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; - typedef std::tr1::unordered_map TokensMappingContainer; + #if defined HAVE_UNORDERED_MAP + typedef std::unordered_map TokensMappingContainer; + #else + typedef std::tr1::unordered_map TokensMappingContainer; + #endif void loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, From e37324b967aa99406ebfed37c33585c5d169de05 Mon Sep 17 00:00:00 2001 From: Torben Carrington Date: Sun, 26 May 2013 08:49:44 -0700 Subject: [PATCH 07/13] AIWander everything works. only two things to do: Implement limiting selection on z axis, and correcting decision algorithm. --- apps/openmw/mwmechanics/aiwander.cpp | 297 ++++++++++++++++++++++++++- apps/openmw/mwmechanics/aiwander.hpp | 35 ++++ 2 files changed, 330 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a73047548..52ab34f24 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,9 +1,54 @@ #include "aiwander.hpp" #include +#include "movement.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + +#include + +namespace +{ + float sgn(float a) + { + if(a > 0) return 1.0; + else return -1.0; + } +} + MWMechanics::AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) { + std::cout << "AIWander: " << mDistance << " " << mDuration << " " << mTimeOfDay << " "; + for(unsigned short counter = 0; counter < mIdle.size(); counter++) + { + std::cout << mIdle[counter] << " "; + if(mIdle[counter] >= 127 || mIdle[counter] < 0) + mIdle[counter] = 0; + } + std::cout << mRepeat << std::endl; + + if(mDistance < 0) + mDistance = 0; + if(mDuration < 0) + mDuration = 0; + if(mDuration == 0) + mTimeOfDay = 0; + + srand(time(NULL)); + mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + mChanceMultiplyer = 0.75; + mPlayedIdle = 0; + + mStoredAvailableNodes = false; + mChooseAction = true; + mIdleNow = false; + mMoveNow = false; + mWalking = false; } MWMechanics::AiPackage * MWMechanics::AiWander::clone() const @@ -13,11 +58,259 @@ MWMechanics::AiPackage * MWMechanics::AiWander::clone() const bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) { - // Return completed - return true; + if(mDuration) + { + // End package if duration is complete or mid-night hits: + MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + if(currentTime.getHour() >= mStartTime.getHour() + mDuration) + { + if(!mRepeat) + { + stopWalking(actor, mPathFinder); + return true; + } + else + mStartTime = currentTime; + } + else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()) + { + stopWalking(actor, mPathFinder); + + if(!mRepeat) + { + stopWalking(actor, mPathFinder); + return true; + } + else + mStartTime = currentTime; + } + } + + ESM::Position pos = actor.getRefData().getPosition(); + + if(!mStoredAvailableNodes) + { + mStoredAvailableNodes = true; + mPathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + mCellX = actor.getCell()->mCell->mData.mX; + mCellY = actor.getCell()->mCell->mData.mY; + + if(mDistance != 0 && !mPathgrid->mPoints.empty()) + { + // TODO: Limit selecting range in the Z axis. + mXCell = 0; + mYCell = 0; + if(actor.getCell()->mCell->isExterior()) + { + mXCell = mCellX * ESM::Land::REAL_SIZE; + mYCell = mCellY * ESM::Land::REAL_SIZE; + } + + Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); + npcPos[0] = npcPos[0] - mXCell; + npcPos[1] = npcPos[1] - mYCell; + + for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) + { + Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY, mPathgrid->mPoints[counter].mZ); + if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) + mAllowedNodes.push_back(mPathgrid->mPoints[counter]); + } + if(!mAllowedNodes.empty()) + { + Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ); + float closestNode = npcPos.squaredDistance(firstNodePos); + unsigned int index = 0; + for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) + { + Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, mAllowedNodes[counterThree].mZ); + float tempDist = npcPos.squaredDistance(nodePos); + if(tempDist < closestNode) + index = counterThree; + } + mCurrentNode = mAllowedNodes[index]; + mAllowedNodes.erase(mAllowedNodes.begin() + index); + } + } + } + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY; + + if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) + { + int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); + // Check if actor is near the border of an inactive cell. If so, disable AiWander. + // FIXME: This *should* pause the AiWander package instead of terminating it. + if(sideX*(pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / 2.0 - 200)) + { + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + return true; + } + } + + if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) + { + int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); + // Check if actor is near the border of an inactive cell. If so, disable AiWander. + // FIXME: This *should* pause the AiWander package instead of terminating it. + if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / 2.0 - 200)) + { + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + return true; + } + } + + // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. + if(cellChange || (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) + mDistance = 0; + + if(mChooseAction) + { + mPlayedIdle = 0; + unsigned short idleRoll = 0; + + for(unsigned int counter = 1; counter < mIdle.size(); counter++) + { + unsigned short idleChance = mChanceMultiplyer * mIdle[counter]; + unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mChanceMultiplyer)); + if(randSelect < idleChance && randSelect > idleRoll) + { + mPlayedIdle = counter; + idleRoll = randSelect; + } + } + + if(!mPlayedIdle && mDistance) + { + std::cout << "Walking!" << std::endl; + mChooseAction = false; + mMoveNow = true; + } + else + { + // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: + std::cout << "Idling!" << std::endl; + MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + mStartTime = currentTime; + playIdle(actor, mPlayedIdle + 1); + mChooseAction = false; + mIdleNow = true; + } + } + + if(mIdleNow) + { + // TODO: This is where we should be checking to see if the current idle animation is done, if it is then + // set mChooseAction to true, because there is no function for this yet we will only set mChooseAction to true. + if(!checkIdle(actor, mPlayedIdle + 1)) + { + std::cout << "Idle Really Completed" << std::endl; + mPlayedIdle = 0; + mIdleNow = false; + mChooseAction = true; + } + } + + if(mMoveNow == true && (mDistance != 0 && !mAllowedNodes.empty())) + { + if(!mPathFinder.isPathConstructed()) + { + unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); + Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); + + // 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); + mAllowedNodes.push_back(mCurrentNode); + mCurrentNode = temp; + + ESM::Pathgrid::Point dest; + dest.mX = destNodePos[0] + mXCell; + dest.mY = destNodePos[1] + mYCell; + dest.mZ = destNodePos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + mPathFinder.buildPath(start,dest,mPathgrid,mXCell,mYCell); + mWalking = true; + } + } + + if(mWalking) + { + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0],pos.pos[1],pos.pos[2]); + MWBase::Environment::get().getWorld()->rotateObject(actor,0,0,zAngle,false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + } + + if(mWalking && mPathFinder.checkIfNextPointReached(pos.pos[0],pos.pos[1],pos.pos[2])) + { + stopWalking(actor, mPathFinder); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + } + + return false; } int MWMechanics::AiWander::getTypeId() const { return 0; } + +void MWMechanics::AiWander::stopWalking(const MWWorld::Ptr& actor, PathFinder& path) +{ + PathFinder pathClearer; + path = pathClearer; + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; +} + +void MWMechanics::AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) +{ + if(idleSelect == 2) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle2", 0, 1); + else if(idleSelect == 3) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); + else if(idleSelect == 4) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle4", 0, 1); + else if(idleSelect == 5) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle5", 0, 1); + else if(idleSelect == 6) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle6", 0, 1); + else if(idleSelect == 7) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle7", 0, 1); + else if(idleSelect == 8) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle8", 0, 1); + else if(idleSelect == 9) + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle9", 0, 1); +} + +bool MWMechanics::AiWander::checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) +{ + if(idleSelect == 2) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle2"); + else if(idleSelect == 3) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle3"); + else if(idleSelect == 4) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle4"); + else if(idleSelect == 5) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle5"); + else if(idleSelect == 6) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle6"); + else if(idleSelect == 7) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle7"); + else if(idleSelect == 8) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle8"); + else if(idleSelect == 9) + return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle9"); + else + return false; +} + diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index e06e714bc..0a00059cc 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -4,6 +4,10 @@ #include "aipackage.hpp" #include +#include "pathfinding.hpp" + +#include "../mwworld/timestamp.hpp" + namespace MWMechanics { class AiWander : public AiPackage @@ -18,11 +22,42 @@ namespace MWMechanics ///< 0: Wander private: + void stopWalking(const MWWorld::Ptr& actor, PathFinder& path); + void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + int mDistance; int mDuration; int mTimeOfDay; std::vector mIdle; bool mRepeat; + + float mX; + float mY; + float mZ; + + int mCellX; + int mCellY; + float mXCell; + float mYCell; + + bool mStoredAvailableNodes; + bool mChooseAction; + bool mIdleNow; + bool mMoveNow; + bool mWalking; + + float mChanceMultiplyer; + unsigned short mPlayedIdle; + + MWWorld::TimeStamp mStartTime; + + std::vector mAllowedNodes; + ESM::Pathgrid::Point mCurrentNode; + + PathFinder mPathFinder; + const ESM::Pathgrid *mPathgrid; + }; } From 96daad7a226e0fe83bd2e9c92e3c752c283a5286 Mon Sep 17 00:00:00 2001 From: Torben Carrington Date: Sun, 26 May 2013 09:31:56 -0700 Subject: [PATCH 08/13] AIWander - shortened some if statements and made super minor optimizations to others, now using proper GMST to load proper idlechance instead of hacky set value." --- apps/openmw/mwmechanics/aiwander.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 52ab34f24..fd96071cc 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -41,8 +41,9 @@ MWMechanics::AiWander::AiWander(int distance, int duration, int timeOfDay, const srand(time(NULL)); mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mChanceMultiplyer = 0.75; mPlayedIdle = 0; + mChanceMultiplyer = + MWBase::Environment::get().getWorld()->getStore().get().find("fIdleChanceMultiplier")->getFloat(); mStoredAvailableNodes = false; mChooseAction = true; @@ -74,8 +75,6 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) } else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()) { - stopWalking(actor, mPathFinder); - if(!mRepeat) { stopWalking(actor, mPathFinder); @@ -97,7 +96,7 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) mCellX = actor.getCell()->mCell->mData.mX; mCellY = actor.getCell()->mCell->mData.mY; - if(mDistance != 0 && !mPathgrid->mPoints.empty()) + if(mDistance && !mPathgrid->mPoints.empty()) { // TODO: Limit selecting range in the Z axis. mXCell = 0; @@ -164,7 +163,7 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) } // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. - if(cellChange || (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) + if(mDistance && (cellChange || (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY))) mDistance = 0; if(mChooseAction) @@ -185,14 +184,12 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) if(!mPlayedIdle && mDistance) { - std::cout << "Walking!" << std::endl; mChooseAction = false; mMoveNow = true; } else { // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: - std::cout << "Idling!" << std::endl; MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mStartTime = currentTime; playIdle(actor, mPlayedIdle + 1); @@ -203,18 +200,15 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) if(mIdleNow) { - // TODO: This is where we should be checking to see if the current idle animation is done, if it is then - // set mChooseAction to true, because there is no function for this yet we will only set mChooseAction to true. if(!checkIdle(actor, mPlayedIdle + 1)) { - std::cout << "Idle Really Completed" << std::endl; mPlayedIdle = 0; mIdleNow = false; mChooseAction = true; } } - if(mMoveNow == true && (mDistance != 0 && !mAllowedNodes.empty())) + if(mMoveNow && mDistance && !mAllowedNodes.empty()) { if(!mPathFinder.isPathConstructed()) { From fd96d47fe4ab81af86d8dc89b39b399f9d6bf014 Mon Sep 17 00:00:00 2001 From: Torben Carrington Date: Sun, 26 May 2013 11:30:42 -0700 Subject: [PATCH 09/13] AIWander Completed. Replicates vanilla as best possible, pathfinding needs fixing before this looks correct but once the pathfinding files are edited this will behave pretty much exactly as vanilla. Major credit to Hrnchamd for major research. --- apps/openmw/mwmechanics/aiwander.cpp | 34 ++++++++++++++++------------ apps/openmw/mwmechanics/aiwander.hpp | 2 +- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index fd96071cc..67f9cc112 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,5 +1,4 @@ #include "aiwander.hpp" -#include #include "movement.hpp" @@ -23,14 +22,11 @@ namespace MWMechanics::AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) { - std::cout << "AIWander: " << mDistance << " " << mDuration << " " << mTimeOfDay << " "; for(unsigned short counter = 0; counter < mIdle.size(); counter++) { - std::cout << mIdle[counter] << " "; if(mIdle[counter] >= 127 || mIdle[counter] < 0) mIdle[counter] = 0; } - std::cout << mRepeat << std::endl; if(mDistance < 0) mDistance = 0; @@ -42,7 +38,7 @@ MWMechanics::AiWander::AiWander(int distance, int duration, int timeOfDay, const srand(time(NULL)); mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - mChanceMultiplyer = + mIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find("fIdleChanceMultiplier")->getFloat(); mStoredAvailableNodes = false; @@ -98,7 +94,6 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) if(mDistance && !mPathgrid->mPoints.empty()) { - // TODO: Limit selecting range in the Z axis. mXCell = 0; mYCell = 0; if(actor.getCell()->mCell->isExterior()) @@ -173,8 +168,8 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) for(unsigned int counter = 1; counter < mIdle.size(); counter++) { - unsigned short idleChance = mChanceMultiplyer * mIdle[counter]; - unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mChanceMultiplyer)); + unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter]; + unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { mPlayedIdle = counter; @@ -241,14 +236,23 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) float zAngle = mPathFinder.getZAngleToNext(pos.pos[0],pos.pos[1],pos.pos[2]); MWBase::Environment::get().getWorld()->rotateObject(actor,0,0,zAngle,false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - } - if(mWalking && mPathFinder.checkIfNextPointReached(pos.pos[0],pos.pos[1],pos.pos[2])) - { - stopWalking(actor, mPathFinder); - mMoveNow = false; - mWalking = false; - mChooseAction = true; + // Unclog path nodes by allowing the NPC to be a small distance away from the center. This way two NPCs can be + // at the same path node at the same time and both will complete instead of endlessly walking into eachother: + Ogre::Vector3 destNodePos(mCurrentNode.mX, mCurrentNode.mY, mCurrentNode.mZ); + Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + actorPos[0] = actorPos[0] - mXCell; + actorPos[1] = actorPos[1] - mYCell; + float distance = actorPos.squaredDistance(destNodePos); + + if(distance < 1200 || mPathFinder.checkIfNextPointReached(pos.pos[0],pos.pos[1],pos.pos[2])) + { + stopWalking(actor, mPathFinder); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + } + } return false; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 0a00059cc..cf3820527 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -47,7 +47,7 @@ namespace MWMechanics bool mMoveNow; bool mWalking; - float mChanceMultiplyer; + float mIdleChanceMultiplier; unsigned short mPlayedIdle; MWWorld::TimeStamp mStartTime; From 3a6e54c4f515d1643506a5a672f4bd76c6b786e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 27 May 2013 02:18:36 +0200 Subject: [PATCH 10/13] Sell owned items in the cell --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwgui/containeritemmodel.cpp | 71 +++++++++++++++++++++-- apps/openmw/mwgui/containeritemmodel.hpp | 3 +- apps/openmw/mwgui/itemmodel.cpp | 34 +++++++++-- apps/openmw/mwgui/sortfilteritemmodel.cpp | 2 +- apps/openmw/mwgui/tradewindow.cpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 27 +++++++++ apps/openmw/mwworld/worldimp.hpp | 2 + 8 files changed, 131 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index de1a34792..86a6a89d2 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -342,6 +342,8 @@ namespace MWBase virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; ///< get all containers in active cells owned by this Npc + virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; + ///< get all items in active cells owned by this Npc virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 79351c6ca..5d23744b2 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -3,11 +3,39 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +namespace +{ + + bool stacks (const MWWorld::Ptr& left, const MWWorld::Ptr& right) + { + if (left == right) + return true; + + // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure + if (left.getContainerStore() && right.getContainerStore()) + return left.getContainerStore()->stacks(left, right) + && right.getContainerStore()->stacks(left, right); + + if (left.getContainerStore()) + return left.getContainerStore()->stacks(left, right); + if (right.getContainerStore()) + return right.getContainerStore()->stacks(left, right); + + MWWorld::ContainerStore store; + return store.stacks(left, right); + } + +} + namespace MWGui { -ContainerItemModel::ContainerItemModel(const std::vector& itemSources) +ContainerItemModel::ContainerItemModel(const std::vector& itemSources, const std::vector& worldItems) : mItemSources(itemSources) + , mWorldItems(worldItems) { assert (mItemSources.size()); } @@ -65,8 +93,7 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure - if (*it == item.mBase || (store.stacks(*it, item.mBase) && item.mBase.getContainerStore()->stacks(*it, item.mBase))) + if (stacks(*it, item.mBase)) { int refCount = it->getRefData().getCount(); it->getRefData().setCount(std::max(0, refCount - toRemove)); @@ -76,6 +103,21 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) } } } + for (std::vector::iterator source = mWorldItems.begin(); source != mWorldItems.end(); ++source) + { + if (stacks(*source, item.mBase)) + { + int refCount = source->getRefData().getCount(); + if (refCount - toRemove <= 0) + MWBase::Environment::get().getWorld()->deleteObject(*source); + else + source->getRefData().setCount(std::max(0, refCount - toRemove)); + toRemove -= refCount; + if (toRemove <= 0) + return; + } + } + throw std::runtime_error("Not enough items to remove could be found"); } @@ -91,8 +133,7 @@ void ContainerItemModel::update() std::vector::iterator itemStack = mItems.begin(); for (; itemStack != mItems.end(); ++itemStack) { - // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure - if (store.stacks(itemStack->mBase, *it) && it->getContainerStore()->stacks(itemStack->mBase, *it)) + if (stacks(*it, itemStack->mBase)) { // we already have an item stack of this kind, add to it itemStack->mCount += it->getRefData().getCount(); @@ -108,6 +149,26 @@ void ContainerItemModel::update() } } } + for (std::vector::iterator source = mWorldItems.begin(); source != mWorldItems.end(); ++source) + { + std::vector::iterator itemStack = mItems.begin(); + for (; itemStack != mItems.end(); ++itemStack) + { + if (stacks(*source, itemStack->mBase)) + { + // we already have an item stack of this kind, add to it + itemStack->mCount += source->getRefData().getCount(); + break; + } + } + + if (itemStack == mItems.end()) + { + // no stack yet, create one + ItemStack newItem (*source, this, source->getRefData().getCount()); + mItems.push_back(newItem); + } + } } } diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 2f4c708ba..22595582e 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -11,7 +11,7 @@ namespace MWGui class ContainerItemModel : public ItemModel { public: - ContainerItemModel (const std::vector& itemSources); + ContainerItemModel (const std::vector& itemSources, const std::vector& worldItems); ///< @note The order of elements \a itemSources matters here. The first element has the highest priority for removal, /// while the last element will be used to add new items to. @@ -28,6 +28,7 @@ namespace MWGui private: std::vector mItemSources; + std::vector mWorldItems; std::vector mItems; }; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 0dc2bb62c..58e89c4fc 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -13,8 +13,6 @@ namespace MWGui , mType(Type_Normal) , mBase(base) { - assert(base.getContainerStore()); - if (MWWorld::Class::get(base).getEnchantment(base) != "") mFlags |= Flag_Enchanted; } @@ -31,18 +29,42 @@ namespace MWGui { if(mBase == other.mBase) return true; - return mBase.getContainerStore()->stacks(mBase, other.mBase) - && other.mBase.getContainerStore()->stacks(mBase, other.mBase); + + // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure + if (mBase.getContainerStore() && other.mBase.getContainerStore()) + return mBase.getContainerStore()->stacks(mBase, other.mBase) + && other.mBase.getContainerStore()->stacks(mBase, other.mBase); + + if (mBase.getContainerStore()) + return mBase.getContainerStore()->stacks(mBase, other.mBase); + if (other.mBase.getContainerStore()) + return other.mBase.getContainerStore()->stacks(mBase, other.mBase); + + MWWorld::ContainerStore store; + return store.stacks(mBase, other.mBase); + } bool operator == (const ItemStack& left, const ItemStack& right) { if (left.mType != right.mType) return false; + if(left.mBase == right.mBase) return true; - return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase) - && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); + + // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure + if (left.mBase.getContainerStore() && right.mBase.getContainerStore()) + return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase) + && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); + + if (left.mBase.getContainerStore()) + return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase); + if (right.mBase.getContainerStore()) + return right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); + + MWWorld::ContainerStore store; + return store.stacks(left.mBase, right.mBase); } ItemModel::ItemModel() diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 60cc2fb0e..b88ec7c0b 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -71,7 +71,7 @@ namespace MWGui if (item.mType == ItemStack::Type_Equipped && !mShowEquipped) return false; - int category; + int category = 0; if (base.getTypeName() == typeid(ESM::Armor).name() || base.getTypeName() == typeid(ESM::Clothing).name()) category = Category_Apparel; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 6a46478af..01f7e2419 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -88,8 +88,10 @@ namespace MWGui MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); // Important: actor goes last, so that items purchased by the merchant go into his inventory itemSources.push_back(actor); + std::vector worldItems; + MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); - mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources), mPtr); + mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources, worldItems), mPtr); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel (mSortModel); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8b76efdae..16cba2ea8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1724,4 +1724,31 @@ namespace MWWorld } } } + + struct ListHandlesFunctor + { + std::vector mHandles; + + bool operator() (ESM::CellRef& ref, RefData& data) + { + Ogre::SceneNode* handle = data.getBaseNode(); + if (handle) + mHandles.push_back(handle->getName()); + return true; + } + }; + + void World::getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) + { + const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); + for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + { + ListHandlesFunctor functor; + (*cellIt)->forEach(functor); + + for (std::vector::iterator it = functor.mHandles.begin(); it != functor.mHandles.end(); ++it) + if (Misc::StringUtils::ciEqual(searchPtrViaHandle(*it).mCellRef->mOwner, npc.getCellRef().mRefID)) + out.push_back(searchPtrViaHandle(*it)); + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1baf3d4ba..12438efd4 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -391,6 +391,8 @@ namespace MWWorld virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out); ///< get all containers in active cells owned by this Npc + virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); + ///< get all items in active cells owned by this Npc virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); From feb180724cb5a94ad856d1a2c8f0b71947cf0d8b Mon Sep 17 00:00:00 2001 From: Torben Carrington Date: Sun, 26 May 2013 19:33:45 -0700 Subject: [PATCH 11/13] AI Execution Fix - Preiovusly AiExecute was being called even when in a menu, this was not correct behavior. --- apps/openmw/mwmechanics/actors.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 00f2ac6fe..37bba6d3a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -30,7 +30,8 @@ namespace MWMechanics // AI CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - creatureStats.getAiSequence().execute (ptr); + if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) + creatureStats.getAiSequence().execute (ptr); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) From 3b43ee751e2999d9599b476f0367507eed5079eb Mon Sep 17 00:00:00 2001 From: Torben Carrington Date: Sun, 26 May 2013 19:54:59 -0700 Subject: [PATCH 12/13] AI Execute Fix - May as well not create the object either to save extra time. --- apps/openmw/mwmechanics/actors.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 37bba6d3a..e85aa9b83 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,9 +29,11 @@ namespace MWMechanics calculateCreatureStatModifiers (ptr); // AI - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); creatureStats.getAiSequence().execute (ptr); + } } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) From bd6d54cc8fdeafeaaa4328484aa978ef17f2d41b Mon Sep 17 00:00:00 2001 From: Torben Carrington Date: Mon, 27 May 2013 03:24:41 -0700 Subject: [PATCH 13/13] AIWander Minor Patch - Forgot an else statement and another check on an empty node vector, previously no nodes in range or only one would cause the AIWander to not do anything, now they will play idles correctly still. --- apps/openmw/mwmechanics/aiwander.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 67f9cc112..4f7b69016 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -126,7 +126,11 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) } mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); + if(mAllowedNodes.empty()) + mDistance = 0; } + else + mDistance = 0; } } @@ -203,7 +207,7 @@ bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor) } } - if(mMoveNow && mDistance && !mAllowedNodes.empty()) + if(mMoveNow && mDistance) { if(!mPathFinder.isPathConstructed()) {