diff --git a/src/game/MoveMap.cpp b/src/game/MoveMap.cpp index 96a2ca8e2..0ee3e92e5 100644 --- a/src/game/MoveMap.cpp +++ b/src/game/MoveMap.cpp @@ -22,3 +22,337 @@ #include "MoveMap.h" #include "MoveMapSharedDefines.h" + +namespace MMAP +{ + // ######################## MMapFactory ######################## + // our global singelton copy + MMapManager *g_MMapManager = NULL; + + // stores list of mapids which do not use pathfinding + std::set* g_mmapDisabledIds = NULL; + + MMapManager* MMapFactory::createOrGetMMapManager() + { + if(g_MMapManager == NULL) + g_MMapManager = new MMapManager(); + + return g_MMapManager; + } + + void MMapFactory::preventPathfindingOnMaps(const char* ignoreMapIds) + { + if(!g_mmapDisabledIds) + g_mmapDisabledIds = new std::set(); + + uint32 strLenght = strlen(ignoreMapIds)+1; + char* mapList = new char[strLenght]; + memcpy(mapList, ignoreMapIds, sizeof(char)*strLenght); + + char* idstr = strtok(mapList, ","); + while (idstr) + { + g_mmapDisabledIds->insert(uint32(atoi(idstr))); + idstr = strtok(NULL, ","); + } + + delete[] mapList; + } + + bool MMapFactory::IsPathfindingEnabled(uint32 mapId) + { + return sWorld.getConfig(CONFIG_BOOL_MMAP_ENABLED) + && g_mmapDisabledIds->find(mapId) == g_mmapDisabledIds->end(); + } + + void MMapFactory::clear() + { + if(g_mmapDisabledIds) + { + delete g_mmapDisabledIds; + g_mmapDisabledIds = NULL; + } + + if(g_MMapManager) + { + delete g_MMapManager; + g_MMapManager = NULL; + } + } + + // ######################## MMapManager ######################## + MMapManager::~MMapManager() + { + for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i) + delete i->second; + + // by now we should not have maps loaded + // if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost! + } + + bool MMapManager::loadMapData(uint32 mapId) + { + // we already have this map loaded? + if (loadedMMaps.find(mapId) != loadedMMaps.end()) + return true; + + // load and init dtNavMesh - read parameters from file + uint32 pathLen = sWorld.GetDataPath().length() + strlen("mmaps/%03i.mmap")+1; + char *fileName = new char[pathLen]; + snprintf(fileName, pathLen, (sWorld.GetDataPath()+"mmaps/%03i.mmap").c_str(), mapId); + + FILE* file = fopen(fileName, "rb"); + if (!file) + { + sLog.outDebug("MMAP:loadMapData: Error: Could not open mmap file '%s'", fileName); + delete [] fileName; + return false; + } + + dtNavMeshParams params; + fread(¶ms, sizeof(dtNavMeshParams), 1, file); + fclose(file); + + dtNavMesh* mesh = dtAllocNavMesh(); + MANGOS_ASSERT(mesh); + if (DT_SUCCESS != mesh->init(¶ms)) + { + dtFreeNavMesh(mesh); + sLog.outError("MMAP:loadMapData: Failed to initialize dtNavMesh for mmap %03u from file %s", mapId, fileName); + delete [] fileName; + return false; + } + + delete [] fileName; + + sLog.outDetail("MMAP:loadMapData: Loaded %03i.mmap", mapId); + + // store inside our map list + MMapData* mmap_data = new MMapData(mesh); + mmap_data->mmapLoadedTiles.clear(); + + loadedMMaps.insert(std::pair(mapId, mmap_data)); + return true; + } + + uint32 MMapManager::packTileID(int32 x, int32 y) + { + return uint32(x << 16 | y); + } + + bool MMapManager::loadMap(uint32 mapId, int32 x, int32 y) + { + // make sure the mmap is loaded and ready to load tiles + if(!loadMapData(mapId)) + return false; + + // get this mmap data + MMapData* mmap = loadedMMaps[mapId]; + MANGOS_ASSERT(mmap->navMesh); + + // check if we already have this tile loaded + uint32 packedGridPos = packTileID(x, y); + if (mmap->mmapLoadedTiles.find(packedGridPos) != mmap->mmapLoadedTiles.end()) + { + sLog.outError("MMAP:loadMap: Asked to load already loaded navmesh tile. %03u%02i%02i.mmtile", mapId, x, y); + return false; + } + + // load this tile :: mmaps/MMMXXYY.mmtile + uint32 pathLen = sWorld.GetDataPath().length() + strlen("mmaps/%03i%02i%02i.mmtile")+1; + char *fileName = new char[pathLen]; + snprintf(fileName, pathLen, (sWorld.GetDataPath()+"mmaps/%03i%02i%02i.mmtile").c_str(), mapId, x, y); + + FILE *file = fopen(fileName, "rb"); + if (!file) + { + sLog.outDebug("MMAP:loadMap: Could not open mmtile file '%s'", fileName); + delete [] fileName; + return false; + } + delete [] fileName; + + // read header + MmapTileHeader fileHeader; + fread(&fileHeader, sizeof(MmapTileHeader), 1, file); + + if (fileHeader.mmapMagic != MMAP_MAGIC) + { + sLog.outError("MMAP:loadMap: Bad header in mmap %03u%02i%02i.mmtile", mapId, x, y); + return false; + } + + if (fileHeader.mmapVersion != MMAP_VERSION) + { + sLog.outError("MMAP:loadMap: %03u%02i%02i.mmtile was built with generator v%i, expected v%i", + mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION); + return false; + } + + unsigned char* data = (unsigned char*)dtAlloc(fileHeader.size, DT_ALLOC_PERM); + MANGOS_ASSERT(data); + + size_t result = fread(data, fileHeader.size, 1, file); + if(!result) + { + sLog.outError("MMAP:loadMap: Bad header or data in mmap %03u%02i%02i.mmtile", mapId, x, y); + fclose(file); + return false; + } + + fclose(file); + + dtMeshHeader* header = (dtMeshHeader*)data; + dtTileRef tileRef = 0; + + // memory allocated for data is now managed by detour, and will be deallocated when the tile is removed + if(DT_SUCCESS == mmap->navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef)) + { + mmap->mmapLoadedTiles.insert(std::pair(packedGridPos, tileRef)); + ++loadedTiles; + sLog.outDetail("MMAP:loadMap: Loaded mmtile %03i[%02i,%02i] into %03i[%02i,%02i]", mapId, x, y, mapId, header->x, header->y); + return true; + } + else + { + sLog.outError("MMAP:loadMap: Could not load %03u%02i%02i.mmtile into navmesh", mapId, x, y); + dtFree(data); + return false; + } + + return false; + } + + bool MMapManager::unloadMap(uint32 mapId, int32 x, int32 y) + { + // check if we have this map loaded + if (loadedMMaps.find(mapId) == loadedMMaps.end()) + { + // file may not exist, therefore not loaded + sLog.outDebug("MMAP:unloadMap: Asked to unload not loaded navmesh map. %03u%02i%02i.mmtile", mapId, x, y); + return false; + } + + MMapData* mmap = loadedMMaps[mapId]; + + // check if we have this tile loaded + uint32 packedGridPos = packTileID(x, y); + if (mmap->mmapLoadedTiles.find(packedGridPos) == mmap->mmapLoadedTiles.end()) + { + // file may not exist, therefore not loaded + sLog.outDebug("MMAP:unloadMap: Asked to unload not loaded navmesh tile. %03u%02i%02i.mmtile", mapId, x, y); + return false; + } + + dtTileRef tileRef = mmap->mmapLoadedTiles[packedGridPos]; + + // unload, and mark as non loaded + if(DT_SUCCESS != mmap->navMesh->removeTile(tileRef, NULL, NULL)) + { + // this is technically a memory leak + // if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used + // we cannot recover from this error - assert out + sLog.outError("MMAP:unloadMap: Could not unload %03u%02i%02i.mmtile from navmesh", mapId, x, y); + MANGOS_ASSERT(false); + } + else + { + mmap->mmapLoadedTiles.erase(packedGridPos); + --loadedTiles; + sLog.outDetail("MMAP:unloadMap: Unloaded mmtile %03i[%02i,%02i] from %03i", mapId, x, y, mapId); + return true; + } + + return false; + } + + bool MMapManager::unloadMap(uint32 mapId) + { + if (loadedMMaps.find(mapId) == loadedMMaps.end()) + { + // file may not exist, therefore not loaded + sLog.outDebug("MMAP:unloadMap: Asked to unload not loaded navmesh map %03u", mapId); + return false; + } + + // unload all tiles from given map + MMapData* mmap = loadedMMaps[mapId]; + for (MMapTileSet::iterator i = mmap->mmapLoadedTiles.begin(); i != mmap->mmapLoadedTiles.end(); ++i) + { + uint32 x = (i->first >> 16); + uint32 y = (i->first & 0x0000FFFF); + if(DT_SUCCESS != mmap->navMesh->removeTile(i->second, NULL, NULL)) + sLog.outError("MMAP:unloadMap: Could not unload %03u%02i%02i.mmtile from navmesh", mapId, x, y); + else + { + --loadedTiles; + sLog.outDetail("MMAP:unloadMap: Unloaded mmtile %03i[%02i,%02i] from %03i", mapId, x, y, mapId); + } + } + + delete mmap; + loadedMMaps.erase(mapId); + sLog.outDetail("MMAP:unloadMap: Unloaded %03i.mmap", mapId); + + return true; + } + + bool MMapManager::unloadMapInstance(uint32 mapId, uint32 instanceId) + { + // check if we have this map loaded + if (loadedMMaps.find(mapId) == loadedMMaps.end()) + { + // file may not exist, therefore not loaded + sLog.outDebug("MMAP:unloadMapInstance: Asked to unload not loaded navmesh map %03u", mapId); + return false; + } + + MMapData* mmap = loadedMMaps[mapId]; + if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) + { + sLog.outDebug("MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId %03u instanceId %u", mapId, instanceId); + return false; + } + + dtNavMeshQuery* query = mmap->navMeshQueries[instanceId]; + + dtFreeNavMeshQuery(query); + mmap->navMeshQueries.erase(instanceId); + sLog.outDetail("MMAP:unloadMapInstance: Unloaded mapId %03u instanceId %u", mapId, instanceId); + + return true; + } + + dtNavMesh const* MMapManager::GetNavMesh(uint32 mapId) + { + if (loadedMMaps.find(mapId) == loadedMMaps.end()) + return NULL; + + return loadedMMaps[mapId]->navMesh; + } + + dtNavMeshQuery const* MMapManager::GetNavMeshQuery(uint32 mapId, uint32 instanceId) + { + if (loadedMMaps.find(mapId) == loadedMMaps.end()) + return NULL; + + MMapData* mmap = loadedMMaps[mapId]; + if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end()) + { + // allocate mesh query + dtNavMeshQuery* query = dtAllocNavMeshQuery(); + MANGOS_ASSERT(query); + if(DT_SUCCESS != query->init(mmap->navMesh, 1024)) + { + dtFreeNavMeshQuery(query); + sLog.outError("MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId %03u instanceId %u", mapId, instanceId); + return NULL; + } + + sLog.outDetail("MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId %03u instanceId %u", mapId, instanceId); + mmap->navMeshQueries.insert(std::pair(instanceId, query)); + } + + return mmap->navMeshQueries[instanceId]; + } +} diff --git a/src/game/MoveMap.h b/src/game/MoveMap.h index a5cf93b2f..5c4bcc711 100644 --- a/src/game/MoveMap.h +++ b/src/game/MoveMap.h @@ -25,4 +25,84 @@ #include "../../dep/recastnavigation/Detour/Include/DetourNavMesh.h" #include "../../dep/recastnavigation/Detour/Include/DetourNavMeshQuery.h" +// memory management +inline void* dtCustomAlloc(int size, dtAllocHint /*hint*/) +{ + return (void*)new unsigned char[size]; +} + +inline void dtCustomFree(void* ptr) +{ + delete [] (unsigned char*)ptr; +} + +// move map related classes +namespace MMAP +{ + typedef UNORDERED_MAP MMapTileSet; + typedef UNORDERED_MAP NavMeshQuerySet; + + // dummy struct to hold map's mmap data + struct MMapData + { + MMapData(dtNavMesh* mesh) : navMesh(mesh) {} + ~MMapData() + { + for (NavMeshQuerySet::iterator i = navMeshQueries.begin(); i != navMeshQueries.end(); ++i) + dtFreeNavMeshQuery(i->second); + + if (navMesh) + dtFreeNavMesh(navMesh); + } + + dtNavMesh* navMesh; + + // we have to use single dtNavMeshQuery for every instance, since those are not thread safe + NavMeshQuerySet navMeshQueries; // instanceId to query + MMapTileSet mmapLoadedTiles; // maps [map grid coords] to [dtTile] + }; + + + typedef UNORDERED_MAP MMapDataSet; + + // singelton class + // holds all all access to mmap loading unloading and meshes + class MMapManager + { + public: + MMapManager() : loadedTiles(0) {} + ~MMapManager(); + + bool loadMap(uint32 mapId, int32 x, int32 y); + bool unloadMap(uint32 mapId, int32 x, int32 y); + bool unloadMap(uint32 mapId); + bool unloadMapInstance(uint32 mapId, uint32 instanceId); + + // the returned [dtNavMeshQuery const*] is NOT threadsafe + dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId); + dtNavMesh const* GetNavMesh(uint32 mapId); + + uint32 getLoadedTilesCount() const { return loadedTiles; } + uint32 getLoadedMapsCount() const { return loadedMMaps.size(); } + private: + bool loadMapData(uint32 mapId); + uint32 packTileID(int32 x, int32 y); + + MMapDataSet loadedMMaps; + uint32 loadedTiles; + }; + + // static class + // holds all mmap global data + // access point to MMapManager singelton + class MMapFactory + { + public: + static MMapManager* createOrGetMMapManager(); + static void clear(); + static void preventPathfindingOnMaps(const char* ignoreMapIds); + static bool IsPathfindingEnabled(uint32 mapId); + }; +} + #endif // _MOVE_MAP_H diff --git a/src/game/MoveMapSharedDefines.h b/src/game/MoveMapSharedDefines.h index 6d7388ce1..e9c0b02ae 100644 --- a/src/game/MoveMapSharedDefines.h +++ b/src/game/MoveMapSharedDefines.h @@ -25,4 +25,30 @@ #define MMAP_MAGIC 0x4d4d4150 // 'MMAP' #define MMAP_VERSION 3 +struct MmapTileHeader +{ + uint32 mmapMagic; + uint32 dtVersion; + uint32 mmapVersion; + uint32 size; + bool usesLiquids : 1; + + MmapTileHeader() : mmapMagic(MMAP_MAGIC), dtVersion(DT_NAVMESH_VERSION), + mmapVersion(MMAP_VERSION), size(0), usesLiquids(true) {} +}; + +enum NavTerrain +{ + NAV_EMPTY = 0x00, + NAV_GROUND = 0x01, + NAV_MAGMA = 0x02, + NAV_SLIME = 0x04, + NAV_WATER = 0x08, + NAV_UNUSED1 = 0x10, + NAV_UNUSED2 = 0x20, + NAV_UNUSED3 = 0x40, + NAV_UNUSED4 = 0x80 + // we only have 8 bits +}; + #endif // _MOVE_MAP_SHARED_DEFINES_H diff --git a/src/game/PathFinder.cpp b/src/game/PathFinder.cpp index c69384ad0..673c00622 100644 --- a/src/game/PathFinder.cpp +++ b/src/game/PathFinder.cpp @@ -23,3 +23,753 @@ #include "Log.h" #include "../recastnavigation/Detour/Include/DetourCommon.h" + +////////////////// PathFinder ////////////////// +PathFinder::PathFinder(const Unit* owner) : + m_polyLength(0), m_type(PATHFIND_BLANK), + m_useStraightPath(false), m_forceDestination(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), + m_sourceUnit(owner), m_navMesh(NULL), m_navMeshQuery(NULL) +{ + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::PathInfo for %u \n", m_sourceUnit->GetGUIDLow()); + + uint32 mapId = m_sourceUnit->GetMapId(); + if (MMAP::MMapFactory::IsPathfindingEnabled(mapId)) + { + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + m_navMesh = mmap->GetNavMesh(mapId); + m_navMeshQuery = mmap->GetNavMeshQuery(mapId, m_sourceUnit->GetInstanceId()); + } + + createFilter(); +} + +PathFinder::~PathFinder() +{ + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathInfo() for %u \n", m_sourceUnit->GetGUIDLow()); +} + +bool PathFinder::calculate(float destX, float destY, float destZ, bool forceDest) +{ + Vector3 oldDest = getEndPosition(); + Vector3 dest(destX, destY, destZ); + setEndPosition(dest); + + float x, y, z; + m_sourceUnit->GetPosition(x, y, z); + Vector3 start(x, y, z); + setStartPosition(start); + + m_forceDestination = forceDest; + + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate() for %u \n", m_sourceUnit->GetGUIDLow()); + + // make sure navMesh works - we can run on map w/o mmap + // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?) + if (!m_navMesh || !m_navMeshQuery || m_sourceUnit->hasUnitState(UNIT_STAT_IGNORE_PATHFINDING) || + !HaveTile(start) || !HaveTile(dest)) + { + BuildShortcut(); + m_type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + return true; + } + + updateFilter(); + + // check if destination moved - if not we can optimize something here + // we are following old, precalculated path? + float dist = m_sourceUnit->GetObjectBoundingRadius(); + if (inRange(oldDest, dest, dist, dist) && m_pathPoints.size() > 2) + { + // our target is not moving - we just coming closer + // we are moving on precalculated path - enjoy the ride + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate:: precalculated path\n"); + + m_pathPoints.erase(m_pathPoints.begin()); + return false; + } + else + { + // target moved, so we need to update the poly path + BuildPolyPath(start, dest); + return true; + } +} + +dtPolyRef PathFinder::getPathPolyByPosition(const dtPolyRef *polyPath, uint32 polyPathSize, const float* point, float *distance) const +{ + if (!polyPath || !polyPathSize) + return INVALID_POLYREF; + + dtPolyRef nearestPoly = INVALID_POLYREF; + float minDist2d = FLT_MAX; + float minDist3d = 0.0f; + + for (uint32 i = 0; i < polyPathSize; ++i) + { + float closestPoint[VERTEX_SIZE]; + if (DT_SUCCESS != m_navMeshQuery->closestPointOnPoly(polyPath[i], point, closestPoint)) + continue; + + float d = dtVdist2DSqr(point, closestPoint); + if (d < minDist2d) + { + minDist2d = d; + nearestPoly = polyPath[i]; + minDist3d = dtVdistSqr(point, closestPoint); + } + + if(minDist2d < 1.0f) // shortcut out - close enough for us + break; + } + + if (distance) + *distance = dtSqrt(minDist3d); + + return (minDist2d < 3.0f) ? nearestPoly : INVALID_POLYREF; +} + +dtPolyRef PathFinder::getPolyByLocation(const float* point, float *distance) const +{ + // first we check the current path + // if the current path doesn't contain the current poly, + // we need to use the expensive navMesh.findNearestPoly + dtPolyRef polyRef = getPathPolyByPosition(m_pathPolyRefs, m_polyLength, point, distance); + if(polyRef != INVALID_POLYREF) + return polyRef; + + // we don't have it in our old path + // try to get it by findNearestPoly() + // first try with low search box + float extents[VERTEX_SIZE] = {3.0f, 5.0f, 3.0f}; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = {0.0f, 0.0f, 0.0f}; + dtStatus result = m_navMeshQuery->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + if(DT_SUCCESS == result && polyRef != INVALID_POLYREF) + { + *distance = dtVdist(closestPoint, point); + return polyRef; + } + + // still nothing .. + // try with bigger search box + extents[1] = 200.0f; + result = m_navMeshQuery->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + if(DT_SUCCESS == result && polyRef != INVALID_POLYREF) + { + *distance = dtVdist(closestPoint, point); + return polyRef; + } + + return INVALID_POLYREF; +} + +void PathFinder::BuildPolyPath(const Vector3 &startPos, const Vector3 &endPos) +{ + // *** getting start/end poly logic *** + + float distToStartPoly, distToEndPoly; + float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x}; + float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x}; + + dtPolyRef startPoly = getPolyByLocation(startPoint, &distToStartPoly); + dtPolyRef endPoly = getPolyByLocation(endPoint, &distToEndPoly); + + // we have a hole in our mesh + // make shortcut path and mark it as NOPATH ( with flying exception ) + // its up to caller how he will use this info + if (startPoly == INVALID_POLYREF || endPoly == INVALID_POLYREF) + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPoly == 0 || endPoly == 0)\n"); + BuildShortcut(); + m_type = (m_sourceUnit->GetTypeId() == TYPEID_UNIT && ((Creature*)m_sourceUnit)->CanFly()) + ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; + return; + } + + // we may need a better number here + bool farFromPoly = (distToStartPoly > 7.0f || distToEndPoly > 7.0f); + if (farFromPoly) + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: farFromPoly distToStartPoly=%.3f distToEndPoly=%.3f\n", distToStartPoly, distToEndPoly); + + bool buildShotrcut = false; + if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) + { + Creature* owner = (Creature*)m_sourceUnit; + + Vector3 p = (distToStartPoly > 7.0f) ? startPos : endPos; + if (m_sourceUnit->GetTerrain()->IsUnderWater(p.x, p.y, p.z)) + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: underWater case\n"); + if (owner->CanSwim()) + buildShotrcut = true; + } + else + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: flying case\n"); + if (owner->CanFly()) + buildShotrcut = true; + } + } + + if (buildShotrcut) + { + BuildShortcut(); + m_type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + return; + } + else + { + float closestPoint[VERTEX_SIZE]; + // we may want to use closestPointOnPolyBoundary instead + if (DT_SUCCESS == m_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint)) + { + dtVcopy(endPoint, closestPoint); + setActualEndPosition(Vector3(endPoint[2],endPoint[0],endPoint[1])); + } + + m_type = PATHFIND_INCOMPLETE; + } + } + + // *** poly path generating logic *** + + // start and end are on same polygon + // just need to move in straight line + if (startPoly == endPoly) + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPoly == endPoly)\n"); + + BuildShortcut(); + + m_pathPolyRefs[0] = startPoly; + m_polyLength = 1; + + m_type = farFromPoly ? PATHFIND_INCOMPLETE : PATHFIND_NORMAL; + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: path type %d\n", m_type); + return; + } + + // look for startPoly/endPoly in current path + // TODO: we can merge it with getPathPolyByPosition() loop + bool startPolyFound = false; + bool endPolyFound = false; + uint32 pathStartIndex, pathEndIndex; + + if (m_polyLength) + { + for (pathStartIndex = 0; pathStartIndex < m_polyLength; ++pathStartIndex) + { + // here to carch few bugs + MANGOS_ASSERT(m_pathPolyRefs[pathStartIndex] != INVALID_POLYREF); + + if (m_pathPolyRefs[pathStartIndex] == startPoly) + { + startPolyFound = true; + break; + } + } + + for (pathEndIndex = m_polyLength-1; pathEndIndex > pathStartIndex; --pathEndIndex) + if (m_pathPolyRefs[pathEndIndex] == endPoly) + { + endPolyFound = true; + break; + } + } + + if (startPolyFound && endPolyFound) + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPolyFound && endPolyFound)\n"); + + // we moved along the path and the target did not move out of our old poly-path + // our path is a simple subpath case, we have all the data we need + // just "cut" it out + + m_polyLength = pathEndIndex - pathStartIndex + 1; + memmove(m_pathPolyRefs, m_pathPolyRefs+pathStartIndex, m_polyLength*sizeof(dtPolyRef)); + } + else if (startPolyFound && !endPolyFound) + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (startPolyFound && !endPolyFound)\n"); + + // we are moving on the old path but target moved out + // so we have atleast part of poly-path ready + + m_polyLength -= pathStartIndex; + + // try to adjust the suffix of the path instead of recalculating entire length + // at given interval the target cannot get too far from its last location + // thus we have less poly to cover + // sub-path of optimal path is optimal + + // take ~80% of the original length + // TODO : play with the values here + uint32 prefixPolyLength = uint32(m_polyLength*0.8f + 0.5f); + memmove(m_pathPolyRefs, m_pathPolyRefs+pathStartIndex, prefixPolyLength*sizeof(dtPolyRef)); + + dtPolyRef suffixStartPoly = m_pathPolyRefs[prefixPolyLength-1]; + + // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data + float suffixEndPoint[VERTEX_SIZE]; + if (DT_SUCCESS != m_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint)) + { + // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that + // try to recover by using prev polyref + --prefixPolyLength; + suffixStartPoly = m_pathPolyRefs[prefixPolyLength-1]; + if (DT_SUCCESS != m_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint)) + { + // suffixStartPoly is still invalid, error state + BuildShortcut(); + m_type = PATHFIND_NOPATH; + return; + } + } + + // generate suffix + uint32 suffixPolyLength = 0; + dtStatus dtResult = m_navMeshQuery->findPath( + suffixStartPoly, // start polygon + endPoly, // end polygon + suffixEndPoint, // start position + endPoint, // end position + &m_filter, // polygon search filter + m_pathPolyRefs + prefixPolyLength - 1, // [out] path + (int*)&suffixPolyLength, + MAX_PATH_LENGTH-prefixPolyLength); // max number of polygons in output path + + if (!suffixPolyLength || dtResult != DT_SUCCESS) + { + // this is probably an error state, but we'll leave it + // and hopefully recover on the next Update + // we still need to copy our preffix + sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); + } + + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ m_polyLength=%u prefixPolyLength=%u suffixPolyLength=%u \n",m_polyLength, prefixPolyLength, suffixPolyLength); + + // new path = prefix + suffix - overlap + m_polyLength = prefixPolyLength + suffixPolyLength - 1; + } + else + { + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: (!startPolyFound && !endPolyFound)\n"); + + // either we have no path at all -> first run + // or something went really wrong -> we aren't moving along the path to the target + // just generate new path + + // free and invalidate old path data + clear(); + + dtStatus dtResult = m_navMeshQuery->findPath( + startPoly, // start polygon + endPoly, // end polygon + startPoint, // start position + endPoint, // end position + &m_filter, // polygon search filter + m_pathPolyRefs, // [out] path + (int*)&m_polyLength, + MAX_PATH_LENGTH); // max number of polygons in output path + + if (!m_polyLength || dtResult != DT_SUCCESS) + { + // only happens if we passed bad data to findPath(), or navmesh is messed up + sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); + BuildShortcut(); + m_type = PATHFIND_NOPATH; + return; + } + } + + // by now we know what type of path we can get + if (m_pathPolyRefs[m_polyLength - 1] == endPoly && !(m_type & PATHFIND_INCOMPLETE)) + m_type = PATHFIND_NORMAL; + else + m_type = PATHFIND_INCOMPLETE; + + // generate the point-path out of our up-to-date poly-path + BuildPointPath(startPoint, endPoint); +} + +void PathFinder::BuildPointPath(const float *startPoint, const float *endPoint) +{ + float pathPoints[MAX_POINT_PATH_LENGTH*VERTEX_SIZE]; + uint32 pointCount = 0; + dtStatus dtResult = DT_FAILURE; + if (m_useStraightPath) + { + dtResult = m_navMeshQuery->findStraightPath( + startPoint, // start position + endPoint, // end position + m_pathPolyRefs, // current path + m_polyLength, // lenth of current path + pathPoints, // [out] path corner points + NULL, // [out] flags + NULL, // [out] shortened path + (int*)&pointCount, + m_pointPathLimit); // maximum number of points/polygons to use + } + else + { + dtResult = findSmoothPath( + startPoint, // start position + endPoint, // end position + m_pathPolyRefs, // current path + m_polyLength, // length of current path + pathPoints, // [out] path corner points + (int*)&pointCount, + m_pointPathLimit); // maximum number of points + } + + if (pointCount < 2 || dtResult != DT_SUCCESS) + { + // only happens if pass bad data to findStraightPath or navmesh is broken + // single point paths can be generated here + // TODO : check the exact cases + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::BuildPointPath FAILED! path sized %d returned\n", pointCount); + BuildShortcut(); + m_type = PATHFIND_NOPATH; + return; + } + + m_pathPoints.resize(pointCount); + for (uint32 i = 0; i < pointCount; ++i) + m_pathPoints[i] = Vector3(pathPoints[i*VERTEX_SIZE+2], pathPoints[i*VERTEX_SIZE], pathPoints[i*VERTEX_SIZE+1]); + + // first point is always our current location - we need the next one + setActualEndPosition(m_pathPoints[pointCount-1]); + + // force the given destination, if needed + if(m_forceDestination && + (!(m_type & PATHFIND_NORMAL) || !inRange(getEndPosition(), getActualEndPosition(), 1.0f, 1.0f))) + { + // we may want to keep partial subpath + if(dist3DSqr(getActualEndPosition(), getEndPosition()) < + 0.3f * dist3DSqr(getStartPosition(), getEndPosition())) + { + setActualEndPosition(getEndPosition()); + m_pathPoints[m_pathPoints.size()-1] = getEndPosition(); + } + else + { + setActualEndPosition(getEndPosition()); + BuildShortcut(); + } + + m_type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + } + + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::BuildPointPath path type %d size %d poly-size %d\n", m_type, pointCount, m_polyLength); +} + +void PathFinder::BuildShortcut() +{ + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::BuildShortcut :: making shortcut\n"); + + clear(); + + // make two point path, our curr pos is the start, and dest is the end + m_pathPoints.resize(2); + + // set start and a default next position + m_pathPoints[0] = getStartPosition(); + m_pathPoints[1] = getActualEndPosition(); + + m_type = PATHFIND_SHORTCUT; +} + +void PathFinder::createFilter() +{ + uint16 includeFlags = 0; + uint16 excludeFlags = 0; + + if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) + { + Creature* creature = (Creature*)m_sourceUnit; + if (creature->CanWalk()) + includeFlags |= NAV_GROUND; // walk + + // creatures don't take environmental damage + if (creature->CanSwim()) + includeFlags |= (NAV_WATER | NAV_MAGMA | NAV_SLIME); // swim + } + else if (m_sourceUnit->GetTypeId() == TYPEID_PLAYER) + { + // perfect support not possible, just stay 'safe' + includeFlags |= (NAV_GROUND | NAV_WATER); + } + + m_filter.setIncludeFlags(includeFlags); + m_filter.setExcludeFlags(excludeFlags); + + updateFilter(); +} + +void PathFinder::updateFilter() +{ + // allow creatures to cheat and use different movement types if they are moved + // forcefully into terrain they can't normally move in + if (m_sourceUnit->IsInWater() || m_sourceUnit->IsUnderWater()) + { + uint16 includedFlags = m_filter.getIncludeFlags(); + includedFlags |= getNavTerrain(m_sourceUnit->GetPositionX(), + m_sourceUnit->GetPositionY(), + m_sourceUnit->GetPositionZ()); + + m_filter.setIncludeFlags(includedFlags); + } +} + +NavTerrain PathFinder::getNavTerrain(float x, float y, float z) +{ + GridMapLiquidData data; + m_sourceUnit->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data); + + switch (data.type) + { + case MAP_LIQUID_TYPE_WATER: + case MAP_LIQUID_TYPE_OCEAN: + return NAV_WATER; + case MAP_LIQUID_TYPE_MAGMA: + return NAV_MAGMA; + case MAP_LIQUID_TYPE_SLIME: + return NAV_SLIME; + default: + return NAV_GROUND; + } +} + +bool PathFinder::HaveTile(const Vector3 &p) const +{ + int tx, ty; + float point[VERTEX_SIZE] = {p.y, p.z, p.x}; + + m_navMesh->calcTileLoc(point, &tx, &ty); + return (m_navMesh->getTileAt(tx, ty) != NULL); +} + +uint32 PathFinder::fixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, + const dtPolyRef* visited, uint32 nvisited) +{ + int32 furthestPath = -1; + int32 furthestVisited = -1; + + // Find furthest common polygon. + for (int32 i = npath-1; i >= 0; --i) + { + bool found = false; + for (int32 j = nvisited-1; j >= 0; --j) + { + if (path[i] == visited[j]) + { + furthestPath = i; + furthestVisited = j; + found = true; + } + } + if (found) + break; + } + + // If no intersection found just return current path. + if (furthestPath == -1 || furthestVisited == -1) + return npath; + + // Concatenate paths. + + // Adjust beginning of the buffer to include the visited. + uint32 req = nvisited - furthestVisited; + uint32 orig = uint32(furthestPath+1) < npath ? furthestPath+1 : npath; + uint32 size = npath-orig > 0 ? npath-orig : 0; + if (req+size > maxPath) + size = maxPath-req; + + if (size) + memmove(path+req, path+orig, size*sizeof(dtPolyRef)); + + // Store visited + for (uint32 i = 0; i < req; ++i) + path[i] = visited[(nvisited-1)-i]; + + return req+size; +} + +bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, + float minTargetDist, const dtPolyRef* path, uint32 pathSize, + float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) +{ + // Find steer target. + static const uint32 MAX_STEER_POINTS = 3; + float steerPath[MAX_STEER_POINTS*VERTEX_SIZE]; + unsigned char steerPathFlags[MAX_STEER_POINTS]; + dtPolyRef steerPathPolys[MAX_STEER_POINTS]; + uint32 nsteerPath = 0; + dtStatus dtResult = m_navMeshQuery->findStraightPath(startPos, endPos, path, pathSize, + steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); + if (!nsteerPath || DT_SUCCESS != dtResult) + return false; + + // Find vertex far enough to steer to. + uint32 ns = 0; + while (ns < nsteerPath) + { + // Stop at Off-Mesh link or when point is further than slop away. + if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || + !inRangeYZX(&steerPath[ns*VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) + break; + ns++; + } + // Failed to find good point to steer to. + if (ns >= nsteerPath) + return false; + + dtVcopy(steerPos, &steerPath[ns*VERTEX_SIZE]); + steerPos[1] = startPos[1]; // keep Z value + steerPosFlag = steerPathFlags[ns]; + steerPosRef = steerPathPolys[ns]; + + return true; +} + +dtStatus PathFinder::findSmoothPath(const float* startPos, const float* endPos, + const dtPolyRef* polyPath, uint32 polyPathSize, + float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) +{ + *smoothPathSize = 0; + uint32 nsmoothPath = 0; + + dtPolyRef polys[MAX_PATH_LENGTH]; + memcpy(polys, polyPath, sizeof(dtPolyRef)*polyPathSize); + uint32 npolys = polyPathSize; + + float iterPos[VERTEX_SIZE], targetPos[VERTEX_SIZE]; + if(DT_SUCCESS != m_navMeshQuery->closestPointOnPolyBoundary(polys[0], startPos, iterPos)) + return DT_FAILURE; + + if(DT_SUCCESS != m_navMeshQuery->closestPointOnPolyBoundary(polys[npolys-1], endPos, targetPos)) + return DT_FAILURE; + + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos); + nsmoothPath++; + + // Move towards target a small advancement at a time until target reached or + // when ran out of memory to store the path. + while (npolys && nsmoothPath < maxSmoothPathSize) + { + // Find location to steer towards. + float steerPos[VERTEX_SIZE]; + unsigned char steerPosFlag; + dtPolyRef steerPosRef = INVALID_POLYREF; + + if (!getSteerTarget(iterPos, targetPos, SMOOTH_PATH_SLOP, polys, npolys, steerPos, steerPosFlag, steerPosRef)) + break; + + bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END); + bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); + + // Find movement delta. + float delta[VERTEX_SIZE]; + dtVsub(delta, steerPos, iterPos); + float len = dtSqrt(dtVdot(delta,delta)); + // If the steer target is end of path or off-mesh link, do not move past the location. + if ((endOfPath || offMeshConnection) && len < SMOOTH_PATH_STEP_SIZE) + len = 1.0f; + else + len = SMOOTH_PATH_STEP_SIZE / len; + + float moveTgt[VERTEX_SIZE]; + dtVmad(moveTgt, iterPos, delta, len); + + // Move + float result[VERTEX_SIZE]; + const static uint32 MAX_VISIT_POLY = 16; + dtPolyRef visited[MAX_VISIT_POLY]; + + uint32 nvisited = 0; + m_navMeshQuery->moveAlongSurface(polys[0], iterPos, moveTgt, &m_filter, result, visited, (int*)&nvisited, MAX_VISIT_POLY); + npolys = fixupCorridor(polys, npolys, MAX_PATH_LENGTH, visited, nvisited); + + m_navMeshQuery->getPolyHeight(polys[0], result, &result[1]); + result[1] += 0.5f; + dtVcopy(iterPos, result); + + // Handle end of path and off-mesh links when close enough. + if (endOfPath && inRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f)) + { + // Reached end of path. + dtVcopy(iterPos, targetPos); + if (nsmoothPath < maxSmoothPathSize) + { + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos); + nsmoothPath++; + } + break; + } + else if (offMeshConnection && inRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f)) + { + // Advance the path up to and over the off-mesh connection. + dtPolyRef prevRef = INVALID_POLYREF; + dtPolyRef polyRef = polys[0]; + uint32 npos = 0; + while (npos < npolys && polyRef != steerPosRef) + { + prevRef = polyRef; + polyRef = polys[npos]; + npos++; + } + + for (uint32 i = npos; i < npolys; ++i) + polys[i-npos] = polys[i]; + + npolys -= npos; + + // Handle the connection. + float startPos[VERTEX_SIZE], endPos[VERTEX_SIZE]; + if (DT_SUCCESS == m_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, startPos, endPos)) + { + if (nsmoothPath < maxSmoothPathSize) + { + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], startPos); + nsmoothPath++; + } + // Move position at the other side of the off-mesh link. + dtVcopy(iterPos, endPos); + + m_navMeshQuery->getPolyHeight(polys[0], iterPos, &iterPos[1]); + iterPos[1] += 0.5f; + } + } + + // Store results. + if (nsmoothPath < maxSmoothPathSize) + { + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos); + nsmoothPath++; + } + } + + *smoothPathSize = nsmoothPath; + + // this is most likely a loop + return nsmoothPath < MAX_POINT_PATH_LENGTH ? DT_SUCCESS : DT_FAILURE; +} + +bool PathFinder::inRangeYZX(const float* v1, const float* v2, float r, float h) const +{ + const float dx = v2[0] - v1[0]; + const float dy = v2[1] - v1[1]; // elevation + const float dz = v2[2] - v1[2]; + return (dx*dx + dz*dz) < r*r && fabsf(dy) < h; +} + +bool PathFinder::inRange(const Vector3 &p1, const Vector3 &p2, float r, float h) const +{ + Vector3 d = p1-p2; + return (d.x*d.x + d.y*d.y) < r*r && fabsf(d.z) < h; +} + +float PathFinder::dist3DSqr(const Vector3 &p1, const Vector3 &p2) const +{ + return (p1-p2).squaredLength(); +} diff --git a/src/game/PathFinder.h b/src/game/PathFinder.h index b7c0310cf..f1145211c 100644 --- a/src/game/PathFinder.h +++ b/src/game/PathFinder.h @@ -25,4 +25,112 @@ #include "movement/MoveSplineInitArgs.h" +using Movement::Vector3; +using Movement::PointsArray; + +class Unit; + +// 74*4.0f=296y number_of_points*interval = max_path_len +// this is way more than actual evade range +// I think we can safely cut those down even more +#define MAX_PATH_LENGTH 74 +#define MAX_POINT_PATH_LENGTH 74 + +#define SMOOTH_PATH_STEP_SIZE 4.0f +#define SMOOTH_PATH_SLOP 0.3f + +#define VERTEX_SIZE 3 +#define INVALID_POLYREF 0 + +enum PathType +{ + PATHFIND_BLANK = 0x0000, // path not built yet + PATHFIND_NORMAL = 0x0001, // normal path + PATHFIND_SHORTCUT = 0x0002, // travel through obstacles, terrain, air, etc (old behavior) + PATHFIND_INCOMPLETE = 0x0004, // we have partial path to follow - getting closer to target + PATHFIND_NOPATH = 0x0008, // no valid path at all or error in generating one + PATHFIND_NOT_USING_PATH = 0x0010 // used when we are either flying/swiming or on map w/o mmaps +}; + +class PathFinder +{ + public: + PathFinder(Unit const* owner); + ~PathFinder(); + + // Calculate the path from owner to given destination + // return: true if new path was calculated, false otherwise (no change needed) + bool calculate(float destX, float destY, float destZ, bool forceDest = false); + + // option setters - use optional + void setUseStrightPath(bool useStraightPath) { m_useStraightPath = useStraightPath; }; + void setPathLengthLimit(float distance) { m_pointPathLimit = std::min(uint32(distance/SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); }; + + // result getters + Vector3 getStartPosition() const { return m_startPosition; } + Vector3 getEndPosition() const { return m_endPosition; } + Vector3 getActualEndPosition() const { return m_actualEndPosition; } + + PointsArray& getPath() { return m_pathPoints; } + PathType getPathType() const { return m_type; } + + private: + + dtPolyRef m_pathPolyRefs[MAX_PATH_LENGTH]; // array of detour polygon references + uint32 m_polyLength; // number of polygons in the path + + PointsArray m_pathPoints; // our actual (x,y,z) path to the target + PathType m_type; // tells what kind of path this is + + bool m_useStraightPath; // type of path will be generated + bool m_forceDestination; // when set, we will always arrive at given point + uint32 m_pointPathLimit; // limit point path size; min(this, MAX_POINT_PATH_LENGTH) + + Vector3 m_startPosition; // {x, y, z} of current location + Vector3 m_endPosition; // {x, y, z} of the destination + Vector3 m_actualEndPosition;// {x, y, z} of the closest possible point to given destination + + const Unit* const m_sourceUnit; // the unit that is moving + const dtNavMesh* m_navMesh; // the nav mesh + const dtNavMeshQuery* m_navMeshQuery; // the nav mesh query used to find the path + + dtQueryFilter m_filter; // use single filter for all movements, update it when needed + + void setStartPosition(Vector3 point) { m_startPosition = point; } + void setEndPosition(Vector3 point) { m_actualEndPosition = point; m_endPosition = point; } + void setActualEndPosition(Vector3 point) { m_actualEndPosition = point; } + + void clear() + { + m_polyLength = 0; + m_pathPoints.clear(); + } + + bool inRange(const Vector3 &p1, const Vector3 &p2, float r, float h) const; + float dist3DSqr(const Vector3 &p1, const Vector3 &p2) const; + bool inRangeYZX(const float* v1, const float* v2, float r, float h) const; + + dtPolyRef getPathPolyByPosition(const dtPolyRef *polyPath, uint32 polyPathSize, const float* point, float *distance = NULL) const; + dtPolyRef getPolyByLocation(const float* point, float *distance) const; + bool HaveTile(const Vector3 &p) const; + + void BuildPolyPath(const Vector3 &startPos, const Vector3 &endPos); + void BuildPointPath(const float *startPoint, const float *endPoint); + void BuildShortcut(); + + NavTerrain getNavTerrain(float x, float y, float z); + void createFilter(); + void updateFilter(); + + // smooth path aux functions + uint32 fixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, + const dtPolyRef* visited, uint32 nvisited); + bool getSteerTarget(const float* startPos, const float* endPos, float minTargetDist, + const dtPolyRef* path, uint32 pathSize, float* steerPos, + unsigned char& steerPosFlag, dtPolyRef& steerPosRef); + dtStatus findSmoothPath(const float* startPos, const float* endPos, + const dtPolyRef* polyPath, uint32 polyPathSize, + float* smoothPath, int* smoothPathSize, uint32 smoothPathMaxSize); +}; + #endif diff --git a/src/game/Unit.h b/src/game/Unit.h index 646d54ef2..ab93021cc 100644 --- a/src/game/Unit.h +++ b/src/game/Unit.h @@ -407,6 +407,7 @@ enum UnitState UNIT_STAT_FOLLOW_MOVE = 0x00010000, UNIT_STAT_FLEEING = 0x00020000, // FleeMovementGenerator/TimedFleeingMovementGenerator active/onstack UNIT_STAT_FLEEING_MOVE = 0x00040000, + UNIT_STAT_IGNORE_PATHFINDING = 0x00080000, // do not use pathfinding in any MovementGenerator // masks (only for check) diff --git a/src/game/World.cpp b/src/game/World.cpp index 1ec37502a..b59ba8457 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -52,6 +52,7 @@ #include "BattleGroundMgr.h" #include "TemporarySummon.h" #include "VMapFactory.h" +#include "MoveMap.h" #include "GameEventMgr.h" #include "PoolManager.h" #include "Database/DatabaseImpl.h" @@ -134,6 +135,7 @@ World::~World() delete command; VMAP::VMapFactory::clear(); + MMAP::MMapFactory::clear(); //TODO free addSessQueue } @@ -887,6 +889,11 @@ void World::LoadConfigSettings(bool reload) sLog.outString( "WORLD: VMap support included. LineOfSight:%i, getHeight:%i, indoorCheck:%i", enableLOS, enableHeight, getConfig(CONFIG_BOOL_VMAP_INDOOR_CHECK) ? 1 : 0); sLog.outString( "WORLD: VMap data directory is: %svmaps",m_dataPath.c_str()); + + setConfig(CONFIG_BOOL_MMAP_ENABLED, "mmap.enabled", true); + std::string ignoreMapIds = sConfig.GetStringDefault("mmap.ignoreMapIds", ""); + MMAP::MMapFactory::preventPathfindingOnMaps(ignoreMapIds.c_str()); + sLog.outString("WORLD: mmap pathfinding %sabled", getConfig(CONFIG_BOOL_MMAP_ENABLED) ? "en" : "dis"); } /// Initialize the World @@ -898,6 +905,9 @@ void World::SetInitialWorldSettings() ///- Time server startup uint32 uStartTime = WorldTimer::getMSTime(); + ///- Initialize detour memory management + dtAllocSetCustom(dtCustomAlloc, dtCustomFree); + ///- Initialize config settings LoadConfigSettings(); diff --git a/src/game/World.h b/src/game/World.h index 2e71be990..6e0cc08ca 100644 --- a/src/game/World.h +++ b/src/game/World.h @@ -332,6 +332,7 @@ enum eConfigBoolValues CONFIG_BOOL_CLEAN_CHARACTER_DB, CONFIG_BOOL_VMAP_INDOOR_CHECK, CONFIG_BOOL_PET_UNSUMMON_AT_MOUNT, + CONFIG_BOOL_MMAP_ENABLED, CONFIG_BOOL_VALUE_COUNT }; diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in index 84d104678..2175180a6 100644 --- a/src/mangosd/mangosd.conf.dist.in +++ b/src/mangosd/mangosd.conf.dist.in @@ -168,6 +168,15 @@ BindIP = "0.0.0.0" # More distance let have better performence, less distance let have more sensitive reaction at target move. # Default: 1.5 # +# mmap.enabled +# Enable/Disable pathfinding using mmaps +# Default: 1 (enable) +# 0 (disable) +# +# mmap.ignoreMapIds +# Disable mmap pathfinding on the listed maps. +# List of map ids with delimiter ',' +# # UpdateUptimeInterval # Update realm uptime period in minutes (for save data in 'uptime' table). Must be > 0 # Default: 10 (minutes) @@ -209,6 +218,8 @@ vmap.ignoreSpellIds = "7720" vmap.enableIndoorCheck = 1 DetectPosCollision = 1 TargetPosRecalculateRange = 1.5 +mmap.enabled = 1 +mmap.ignoreMapIds = "" UpdateUptimeInterval = 10 MaxCoreStuckTime = 0 AddonChannel = 1 diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 3a4a71ad4..21b4559f8 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "11908" + #define REVISION_NR "11909" #endif // __REVISION_NR_H__