diff --git a/contrib/mmap/offmesh.txt b/contrib/mmap/offmesh.txt index cb89ff58138..63863c4f1e9 100644 --- a/contrib/mmap/offmesh.txt +++ b/contrib/mmap/offmesh.txt @@ -1,4 +1,7 @@ -30 32,33 (-759.16 -350.45 68.96) (-758.2 -348.56 67.56) 2.5 // AV -30 32,33 (-761.69 -349.45 69.0) (-761 -347.83 67.61) 2.5 -30 32,33 (-765.1 -348 69) (-764.53 -346.58 67.66) 2.5 -189 29,28 (1827.839478 1315.610962 17.235508) (1830.25244 1315.189697 19.016930) 2.5 \ No newline at end of file +// Format: mapID tileX,tileY (start_x start_y start_z) (end_x end_y end_z) size +// size: width/radius of the connection path +30 32,33 (-759.16 -350.45 68.96) (-758.2 -348.56 67.56) 2.5 // AV - Tower entrance +30 32,33 (-761.69 -349.45 69.0) (-761 -347.83 67.61) 2.5 // AV - Tower entrance +30 32,33 (-765.1 -348 69) (-764.53 -346.58 67.66) 2.5 // AV - Tower entrance +189 29,28 (1827.839478 1315.610962 17.235508) (1830.25244 1315.189697 19.016930) 2.5 // Scarlet Monastery Graveyard - path out of spawn position +36 33,32 (-23.427919 -797.150940 20.145432) (-23.941082 -797.715393 19.456278) 2.5 // The Deadmines - Mr. Smite ship -> bridge diff --git a/contrib/mmap/src/IntermediateValues.cpp b/contrib/mmap/src/IntermediateValues.cpp index 580c8566236..a10d177e425 100644 --- a/contrib/mmap/src/IntermediateValues.cpp +++ b/contrib/mmap/src/IntermediateValues.cpp @@ -202,7 +202,7 @@ namespace MMAP void IntermediateValues::generateObjFile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData) { char objFileName[255]; - snprintf(objFileName, sizeof(objFileName), "map%03u%02u%02u", mapID, tileX, tileY); + sprintf(objFileName, "map%03u%02u%02u", mapID, tileY, tileX); generateObjFile(objFileName, meshData); } void IntermediateValues::generateObjFile(std::string filename, MeshData& meshData) diff --git a/contrib/mmap/src/MapBuilder.cpp b/contrib/mmap/src/MapBuilder.cpp index 418352890da..d34d8663daf 100644 --- a/contrib/mmap/src/MapBuilder.cpp +++ b/contrib/mmap/src/MapBuilder.cpp @@ -147,7 +147,9 @@ namespace MMAP m_cancel.store(false); buildMap(mapID); processQueuedTiles(); - printf("Done."); + + printf("[Map %03i] Updated map file: mmaps/%03u.mmap\n", mapID, mapID); + printf("Done.\n"); } void MapBuilder::buildAllMaps() @@ -161,7 +163,7 @@ namespace MMAP } processQueuedTiles(); - printf("Done."); + printf("Done.\n"); } void MapBuilder::processQueuedTiles() @@ -242,15 +244,17 @@ namespace MMAP uint32 minX, minY, maxX, maxY; getGridBounds(mapID, minX, minY, maxX, maxY); - // add all tiles within bounds to tile list. - for (uint32 i = minX; i <= maxX; ++i) - for (uint32 j = minY; j <= maxY; ++j) - if (i == tileX && j == tileY) - tiles.insert(StaticMapTree::packTileID(i, j)); + // Only add the requested tile to avoid allocating NavMesh for entire map + // when building a single tile (which would cause massive memory overhead). + if (tileX >= minX && tileX <= maxX && tileY >= minY && tileY <= maxY) + tiles.insert(StaticMapTree::packTileID(tileX, tileY)); } if (!tiles.size()) + { + printf("[Map %03i] Tile [%02u,%02u] not found in valid tile range!\n", mapID, tileX, tileY); return; + } dtNavMesh* navMesh = nullptr; buildNavMesh(mapID, navMesh); @@ -260,32 +264,23 @@ namespace MMAP return; } - printf("Adding %i, %i, %i", mapID, tileX, tileY); + printf("[Map %03i] Building single tile [%02u,%02u]\n", mapID, tileX, tileY); TileInfo tileInfo; tileInfo.m_mapId = mapID; tileInfo.m_tileX = tileX; tileInfo.m_tileY = tileY; - tileInfo.m_curTile = 0; - tileInfo.m_tileCount = uint32(tiles.size()); + tileInfo.m_curTile = 1; + tileInfo.m_tileCount = 1; + tileInfo.m_forceRebuild = true; // Always rebuild when building a single tile memcpy(&tileInfo.m_navMeshParams, navMesh->getParams(), sizeof(dtNavMeshParams)); m_tileQueue.Push(tileInfo); - auto worker = std::make_unique(this, false, m_quick, m_debug, m_config); - - while (!m_tileQueue.Empty() && !m_cancel.load()) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - } - - m_cancel.store(true); - m_tileQueue.Cancel(); - - worker->WaitCompletion(); - dtFreeNavMesh(navMesh); - printf("Building single tile finished."); + processQueuedTiles(); + + printf("[Map %03i] Generated file: mmaps/%03u%02u%02u.mmtile\n", mapID, mapID, tileY, tileX); } void MapBuilder::buildMap(uint32 mapID) diff --git a/contrib/mmap/src/TerrainBuilder.cpp b/contrib/mmap/src/TerrainBuilder.cpp index f19b92ec19d..d570757dd67 100644 --- a/contrib/mmap/src/TerrainBuilder.cpp +++ b/contrib/mmap/src/TerrainBuilder.cpp @@ -1006,7 +1006,7 @@ namespace MMAP } /**************************************************************************/ - void TerrainBuilder::loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, char const* offMeshFilePath) + void TerrainBuilder::loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, char const* offMeshFilePath, bool printOffmeshData) { // no meshfile input given? if (offMeshFilePath == nullptr) @@ -1033,6 +1033,12 @@ namespace MMAP if (mapID == mid && tileX == tx && tileY == ty) { + if (printOffmeshData) + { + printf(" loadOffMeshConnections:: Found offmesh connection for map %u tile [%u,%u]: (%.2f %.2f %.2f) -> (%.2f %.2f %.2f) size %.2f\n", + mapID, tileX, tileY, p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], size); + } + meshData.offMeshConnections.append(p0[1]); meshData.offMeshConnections.append(p0[2]); meshData.offMeshConnections.append(p0[0]); diff --git a/contrib/mmap/src/TerrainBuilder.h b/contrib/mmap/src/TerrainBuilder.h index 11bcbae8dac..d90c8501a21 100644 --- a/contrib/mmap/src/TerrainBuilder.h +++ b/contrib/mmap/src/TerrainBuilder.h @@ -98,7 +98,7 @@ namespace MMAP void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData); bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData); void unloadVMap(uint32 mapID, uint32 tileX, uint32 tileY); - void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, const char* offMeshFilePath); + void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, const char* offMeshFilePath, bool printOffmeshData = false); bool usesLiquids() { return !m_skipLiquid; } diff --git a/contrib/mmap/src/TileWorker.cpp b/contrib/mmap/src/TileWorker.cpp index dc67d94f8a9..eb681b987f8 100644 --- a/contrib/mmap/src/TileWorker.cpp +++ b/contrib/mmap/src/TileWorker.cpp @@ -239,7 +239,7 @@ namespace MMAP return; } - buildTile(tileInfo.m_mapId, tileInfo.m_tileX, tileInfo.m_tileY, navMesh, tileInfo.m_curTile, tileInfo.m_tileCount); + buildTile(tileInfo.m_mapId, tileInfo.m_tileX, tileInfo.m_tileY, navMesh, tileInfo.m_curTile, tileInfo.m_tileCount, tileInfo.m_forceRebuild); dtFreeNavMesh(navMesh); } else @@ -374,9 +374,9 @@ namespace MMAP return true; } - void TileWorker::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh, uint32 curTile, uint32 tileCount) + void TileWorker::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh, uint32 curTile, uint32 tileCount, bool forceRebuild) { - if (shouldSkipTile(mapID, tileX, tileY)) + if (!forceRebuild && shouldSkipTile(mapID, tileX, tileY)) { return; } @@ -410,8 +410,8 @@ namespace MMAP float bmin[3], bmax[3]; m_mapBuilder->getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax); - // offmesh.txt - m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_mapBuilder->m_offMeshFilePath); + // offmesh.txt (verbose output only for single tile builds) + m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_mapBuilder->m_offMeshFilePath, tileCount == 1); // build navmesh tile buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh); diff --git a/contrib/mmap/src/TileWorker.h b/contrib/mmap/src/TileWorker.h index 20dab903dc2..ddfaf5107c1 100644 --- a/contrib/mmap/src/TileWorker.h +++ b/contrib/mmap/src/TileWorker.h @@ -20,7 +20,7 @@ namespace MMAP { struct TileInfo { - TileInfo() : m_mapId(uint32(-1)), m_tileX(), m_tileY(), m_navMeshParams(), m_curTile(0), m_tileCount(0) {} + TileInfo() : m_mapId(uint32(-1)), m_tileX(), m_tileY(), m_navMeshParams(), m_curTile(0), m_tileCount(0), m_forceRebuild(false) {} uint32 m_mapId; uint32 m_tileX; @@ -28,6 +28,7 @@ namespace MMAP uint32 m_curTile; uint32 m_tileCount; dtNavMeshParams m_navMeshParams; + bool m_forceRebuild; }; template @@ -153,7 +154,7 @@ namespace MMAP bool duDumpPolyMeshToObj(rcPolyMesh& pmesh, uint32 mapID, uint32 tileY, uint32 tileX); bool duDumpPolyMeshDetailToObj(rcPolyMeshDetail& dmesh, uint32 mapID, uint32 tileY, uint32 tileX); bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY); - void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh, uint32 curTile, uint32 tileCount); + void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh, uint32 curTile, uint32 tileCount, bool forceRebuild = false); void buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, float bmin[3], float bmax[3], dtNavMesh* navMesh); json getDefaultConfig(); diff --git a/contrib/vmap_extractor/vmapextract/adtfile.cpp b/contrib/vmap_extractor/vmapextract/adtfile.cpp index ec1a5c5d8c6..843140a1a3f 100644 --- a/contrib/vmap_extractor/vmapextract/adtfile.cpp +++ b/contrib/vmap_extractor/vmapextract/adtfile.cpp @@ -168,8 +168,8 @@ bool ADTFile::init(uint32 map_num, uint32 tileX, uint32 tileY, StringSet& failed char* p = buf; while (p < buf + size) { - FixNameCase(p, strlen(p)); - std::string path(p); // Store copy after name fixed + // Store original path before any modifications for ExtractSingleModel's fallback logic + std::string path(p); // Store copy of original path std::string fixedName; ExtractSingleModel(path, fixedName, failedPaths); diff --git a/contrib/vmap_extractor/vmapextract/gameobject_extract.cpp b/contrib/vmap_extractor/vmapextract/gameobject_extract.cpp index ee9904407f6..a401ccfb67e 100644 --- a/contrib/vmap_extractor/vmapextract/gameobject_extract.cpp +++ b/contrib/vmap_extractor/vmapextract/gameobject_extract.cpp @@ -2,10 +2,20 @@ #include "dbcfile.h" #include "wmo.h" #include "vmapexport.h" +#include "libmpq/mpq_libmpq.h" #include #include +// Check if file exists in MPQ without adding to failedPaths +static bool MPQFileExists(const std::string& path) +{ + MPQFile f(path.c_str()); + bool exists = !f.isEof(); + f.close(); + return exists; +} + bool ExtractSingleModel(std::string& origPath, std::string& fixedName, StringSet& failedPaths) { if (origPath.length() < 4) @@ -23,25 +33,50 @@ bool ExtractSingleModel(std::string& origPath, std::string& fixedName, StringSet // >= 3.1.0 ADT MMDX section store filename.m2 filenames for corresponded .m2 file // nothing do + std::string originalPath = origPath; + char* name = GetPlainName((char*)origPath.c_str()); FixNameCase(name, strlen(name)); - FixNameSpaces(name, strlen(name)); // Fix a few models with spaces instead of underscores in their filenames (razorfen leanto03) - - std::string output(szWorkDirWmo); // Stores output filename (possible changed) - output += "/"; - output += name; + FixNameSpaces(name, strlen(name)); + fixedName = name; + std::string output = std::string(szWorkDirWmo) + "/" + name; if (FileExists(output.c_str())) return true; + // Try 1: Path with underscores Model mdl(origPath); - if (!mdl.open(failedPaths)) // Possible changed fname - return false; + if (mdl.open(failedPaths)) + return mdl.ConvertToVMAPModel(output.c_str()); + + // Try 2: Original path (with spaces if it had them) + if (originalPath != origPath && MPQFileExists(originalPath)) + { + failedPaths.erase(origPath); + Model mdl2(originalPath); + if (mdl2.open(failedPaths)) + return mdl2.ConvertToVMAPModel(output.c_str()); + } - return mdl.ConvertToVMAPModel(output.c_str()); + // Try 3: Filename with underscores converted to spaces + size_t lastSlash = origPath.find_last_of("\\/"); + if (lastSlash != std::string::npos) + { + std::string altPath = origPath; + std::replace(altPath.begin() + lastSlash + 1, altPath.end(), '_', ' '); + if (altPath != origPath && altPath != originalPath && MPQFileExists(altPath)) + { + failedPaths.erase(origPath); + Model mdl3(altPath); + if (mdl3.open(failedPaths)) + return mdl3.ConvertToVMAPModel(output.c_str()); + } + } + + return false; } -void ExtractGameobjectModels() +bool ExtractGameobjectModels() { printf("\n"); printf("Extracting GameObject models...\n"); @@ -68,28 +103,29 @@ void ExtractGameobjectModels() FixNameCase((char*)path.c_str(), path.size()); char* name = GetPlainName((char*)path.c_str()); - FixNameSpaces(name, strlen(name)); char const* ch_ext = GetExtension(name); if (!ch_ext) continue; - //strToLower(ch_ext); - bool result = false; + std::string fixedName; + if (!strcmp(ch_ext, ".wmo")) { result = ExtractSingleWmo(path); + if (result) + FixNameSpaces(name, strlen(name)); } else if (!strcmp(ch_ext, ".mdl")) { - // TODO: extract .mdl files, if needed continue; } - else //if (!strcmp(ch_ext, ".mdx") || !strcmp(ch_ext, ".m2")) + else { - std::string fixedName; result = ExtractSingleModel(path, fixedName, failedPaths); + if (result) + name = (char*)fixedName.c_str(); } if (result) @@ -109,8 +145,10 @@ void ExtractGameobjectModels() printf("Warning: Some models could not be extracted, see below\n"); for (StringSet::const_iterator itr = failedPaths.begin(); itr != failedPaths.end(); ++itr) printf("Could not find file of model %s\n", itr->c_str()); - printf("A few of these warnings are expected to happen, so be not alarmed!\n"); + printf("Done!\n"); + return false; } printf("Done!\n"); + return true; } diff --git a/contrib/vmap_extractor/vmapextract/vmapexport.cpp b/contrib/vmap_extractor/vmapextract/vmapexport.cpp index 7e296d6da82..c086143e0a0 100644 --- a/contrib/vmap_extractor/vmapextract/vmapexport.cpp +++ b/contrib/vmap_extractor/vmapextract/vmapexport.cpp @@ -132,6 +132,7 @@ bool ExtractWmo() bool ExtractSingleWmo(std::string& fname) { // Copy files from archive + // Note: This function does not have fallback logic for path variants (underscores vs spaces) char szLocalFile[1024]; char* plain_name = GetPlainName(&fname[0]); @@ -234,10 +235,9 @@ bool ExtractSingleWmo(std::string& fname) return true; } -void ParsMapFiles() +bool ParsMapFiles() { char fn[512]; - //char id_filename[64]; char id[10]; StringSet failedPaths; for (unsigned int i = 0; i < map_count; ++i) @@ -254,7 +254,6 @@ void ParsMapFiles() { if (ADTFile* ADT = WDT.GetMap(x, y)) { - //sprintf(id_filename,"%02u %02u %03u",x,y,map_ids[i].id);//!!!!!!!!! ADT->init(map_ids[i].id, x, y, failedPaths); delete ADT; } @@ -271,8 +270,9 @@ void ParsMapFiles() printf("Warning: Some models could not be extracted, see below\n"); for (StringSet::const_iterator itr = failedPaths.begin(); itr != failedPaths.end(); ++itr) printf("Could not find file of model %s\n", itr->c_str()); - printf("A few not found models can be expected and are not alarming.\n"); + return false; } + return true; } void getGamePath() @@ -483,20 +483,24 @@ int main(int argc, char** argv) delete dbc; - ParsMapFiles(); + bool mapSuccess = ParsMapFiles(); delete[] map_ids; - //nError = ERROR_SUCCESS; - // Extract models, listed in DameObjectDisplayInfo.dbc - ExtractGameobjectModels(); - } + // Extract models, listed in GameObjectDisplayInfo.dbc + bool goSuccess = ExtractGameobjectModels(); - printf("\n"); - if (!success) + bool hasWarnings = !mapSuccess || !goSuccess; + + printf("\n"); + if (hasWarnings) + printf("Extract for %s. Work complete with warnings.\n", szRawVMAPMagic); + else + printf("Extract for %s. Work complete. No errors.\n", szRawVMAPMagic); + } + else { - printf("ERROR: Extract for %s. Work NOT complete.\n Precise vector data=%d.\nPress any key.\n", szRawVMAPMagic, preciseVectorData); - getchar(); + printf("\n"); + printf("ERROR: Extract for %s. Work NOT complete.\n", szRawVMAPMagic); } - printf("Extract for %s. Work complete. No errors.\n", szRawVMAPMagic); - return 0; + return success ? 0 : 1; } diff --git a/contrib/vmap_extractor/vmapextract/vmapexport.h b/contrib/vmap_extractor/vmapextract/vmapexport.h index 3dbf7a428bc..b4f5fcc8fe7 100644 --- a/contrib/vmap_extractor/vmapextract/vmapexport.h +++ b/contrib/vmap_extractor/vmapextract/vmapexport.h @@ -55,6 +55,6 @@ bool ExtractSingleWmo(std::string& fname); */ bool ExtractSingleModel(std::string& origPath, std::string& fixedName, StringSet& failedPaths); -void ExtractGameobjectModels(); +bool ExtractGameobjectModels(); #endif diff --git a/contrib/vmap_extractor/vmapextract/wdtfile.cpp b/contrib/vmap_extractor/vmapextract/wdtfile.cpp index df5e4b8e7c6..755c3186adf 100644 --- a/contrib/vmap_extractor/vmapextract/wdtfile.cpp +++ b/contrib/vmap_extractor/vmapextract/wdtfile.cpp @@ -82,7 +82,7 @@ bool WDTFile::init(char* map_id, unsigned int mapID) std::string path(p); char* s = wdtGetPlainName(p); FixNameCase(s, strlen(s)); - // FixNameSpaces(s, strlen(s)); not needed here? + // FixNameSpaces(s, strlen(s)); // Not needed for WMO names in WDT files p = p + strlen(p) + 1; m_wmoNames.push_back(s); } diff --git a/contrib/vmap_extractor/vmapextract/wmo.cpp b/contrib/vmap_extractor/vmapextract/wmo.cpp index d68b09fe648..dc0318efd3c 100644 --- a/contrib/vmap_extractor/vmapextract/wmo.cpp +++ b/contrib/vmap_extractor/vmapextract/wmo.cpp @@ -91,10 +91,6 @@ bool WMORoot::open() { std::string path = ptr; - char* s = GetPlainName(ptr); - FixNameCase(s, strlen(s)); - FixNameSpaces(s, strlen(s)); - uint32 doodadNameIndex = ptr - f.getPointer(); ptr += path.length() + 1; diff --git a/src/game/Commands/DebugCommands.cpp b/src/game/Commands/DebugCommands.cpp index f2d48980b2b..bc8189a7cf1 100644 --- a/src/game/Commands/DebugCommands.cpp +++ b/src/game/Commands/DebugCommands.cpp @@ -2494,48 +2494,67 @@ bool ChatHandler::HandleMmap(char* args) return true; } -enum MmapConnectionStep +bool ChatHandler::HandleMmapConnection(char* /*args*/) { - FIRST_STEP, - SECOND_STEP, -}; + static bool hasStartPoint = false; + static float startX = 0.0f, startY = 0.0f, startZ = 0.0f; + static uint32 startMapId = 0; -bool ChatHandler::HandleMmapConnection(char* args) -{ - FILE* fOffmeshFile = fopen("offmesh_conn", "a"); - if (!fOffmeshFile) - { - SendSysMessage("Unable to open file."); - return true; - } - // map tileY,X (X,Y,Z) (X,Y,Z) Size - // 0 31,59 (-14429.889648 450.344452 15.430828) (-14424.218750 444.332855 12.773965) 2.5 // booty bay dock - static MmapConnectionStep step = FIRST_STEP; - static float firstX = 0.0f, firstY = 0.0f, firstZ = 0.0f; Player* pPlayer = m_session->GetPlayer(); - if (step == FIRST_STEP) + + if (!hasStartPoint) { - pPlayer->GetPosition(firstX, firstY, firstZ); - step = SECOND_STEP; - SendSysMessage("Enregistre ..."); + // First call: record start position + pPlayer->GetPosition(startX, startY, startZ); + startMapId = pPlayer->GetMapId(); + hasStartPoint = true; + PSendSysMessage("Start point recorded at (%.2f, %.2f, %.2f). Move to end point and run command again.", startX, startY, startZ); } else { - int32 gx = 32 - pPlayer->GetPositionX() / SIZE_OF_GRIDS; - int32 gy = 32 - pPlayer->GetPositionY() / SIZE_OF_GRIDS; - PSendSysMessage("%u %u,%u (%f %f %f) (%f %f %f) %f", - pPlayer->GetMapId(), gy, gx, firstX, firstY, firstZ, - pPlayer->GetPositionX(), pPlayer->GetPositionY(), pPlayer->GetPositionZ(), - pPlayer->GetObjectScale()); - fprintf(fOffmeshFile, "%u %u,%u (%f %f %f) (%f %f %f) %f\n", - pPlayer->GetMapId(), gy, gx, firstX, firstY, firstZ, - pPlayer->GetPositionX(), pPlayer->GetPositionY(), pPlayer->GetPositionZ(), - pPlayer->GetObjectScale()); - - step = FIRST_STEP; - firstX = firstY = firstZ = 0.0f; - } - fclose(fOffmeshFile); + // Second call: record end position and write connection + if (pPlayer->GetMapId() != startMapId) + { + SendSysMessage("Error: You changed maps! Connection cancelled. Start again."); + hasStartPoint = false; + startX = startY = startZ = 0.0f; + return true; + } + + // Switched x/y + int32 tileY = 32 - pPlayer->GetPositionX() / SIZE_OF_GRIDS; + int32 tileX = 32 - pPlayer->GetPositionY() / SIZE_OF_GRIDS; + + // Format: mapID tileX,tileY (start_x start_y start_z) (end_x end_y end_z) size + PSendSysMessage("Offmesh connection recorded:"); + PSendSysMessage("%u %d,%d (%.6f %.6f %.6f) (%.6f %.6f %.6f) 2.5", + pPlayer->GetMapId(), tileX, tileY, + startX, startY, startZ, + pPlayer->GetPositionX(), pPlayer->GetPositionY(), pPlayer->GetPositionZ()); + PSendSysMessage("Rebuild with: MoveMapGenerator %u --tile %d,%d", pPlayer->GetMapId(), tileX, tileY); + sLog.Out(LOG_BASIC, LOG_LVL_BASIC, "Rebuild with: MoveMapGenerator %u --tile %d,%d", pPlayer->GetMapId(), tileX, tileY); + + // Write to file + FILE* file = fopen("offmesh_connections.txt", "a"); + if (file) + { + fprintf(file, "%u %d,%d (%.6f %.6f %.6f) (%.6f %.6f %.6f) 2.5\n", + pPlayer->GetMapId(), tileX, tileY, + startX, startY, startZ, + pPlayer->GetPositionX(), pPlayer->GetPositionY(), pPlayer->GetPositionZ()); + fclose(file); + SendSysMessage("Written to offmesh_connections.txt"); + } + else + { + SendSysMessage("Warning: Could not write to offmesh_connections.txt"); + } + + // Reset state + hasStartPoint = false; + startX = startY = startZ = 0.0f; + } + return true; }