diff --git a/src/common/Collision/Maps/MapDefines.h b/src/common/Collision/Maps/MapDefines.h index a3527f16c..1e0b5f749 100644 --- a/src/common/Collision/Maps/MapDefines.h +++ b/src/common/Collision/Maps/MapDefines.h @@ -26,7 +26,7 @@ #define SIZE_OF_GRIDS 533.3333f #define MMAP_MAGIC 0x4d4d4150 // 'MMAP' -#define MMAP_VERSION 19 +#define MMAP_VERSION 20 struct MmapTileRecastConfig { @@ -87,15 +87,15 @@ static_assert(sizeof(MmapTileHeader) == (sizeof(MmapTileHeader::mmapMagic) + 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 + NAV_EMPTY = 0x00, + NAV_GROUND = 0x01, + NAV_MAGMA = 0x02, + NAV_SLIME = 0x04, + NAV_WATER = 0x08, + NAV_GROUND_STEEP = 0x10, + NAV_UNUSED2 = 0x20, + NAV_UNUSED3 = 0x40, + NAV_UNUSED4 = 0x80 // we only have 8 bits }; diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp index 607428cb6..9ec7f27e3 100644 --- a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp @@ -23,6 +23,10 @@ #include "MMapMgr.h" #include "Map.h" #include "Metric.h" +#ifdef MOD_PLAYERBOTS +#include "Player.h" +#include "WorldSession.h" +#endif ////////////////// PathGenerator ////////////////// PathGenerator::PathGenerator(WorldObject const* owner) : @@ -648,12 +652,15 @@ void PathGenerator::CreateFilter() { uint16 includeFlags = 0; uint16 excludeFlags = 0; +#ifdef MOD_PLAYERBOTS + bool isBot = false; +#endif if (_source->IsCreature()) { Creature* creature = (Creature*)_source; if (creature->CanWalk()) - includeFlags |= NAV_GROUND; // walk + includeFlags |= (NAV_GROUND | NAV_GROUND_STEEP); // creatures don't take environmental damage if (creature->CanEnterWater()) @@ -661,13 +668,36 @@ void PathGenerator::CreateFilter() } else // assume Player { - // perfect support not possible, just stay 'safe' - includeFlags |= (NAV_GROUND | NAV_WATER | NAV_MAGMA); +#ifdef MOD_PLAYERBOTS + // Bots navigate with a stricter filter: include ground + water but exclude lava/slime and + // NAV_GROUND_STEEP (the 50-60deg slopes the extractor tags via modAlmostUnwalkableTriangles), so + // they keep off steep mountainsides and follow gentle ground/roads. Real players are unchanged and + // may still path across steep terrain. + Player const* player = _source->ToPlayer(); + if (player && player->GetSession() && player->GetSession()->IsBot()) + { + includeFlags |= (NAV_GROUND | NAV_WATER); + excludeFlags |= (NAV_MAGMA | NAV_SLIME | NAV_GROUND_STEEP); + isBot = true; + } + else +#endif + { + // perfect support not possible, just stay 'safe' + includeFlags |= (NAV_GROUND | NAV_GROUND_STEEP | NAV_WATER | NAV_MAGMA); + } } _filter.setIncludeFlags(includeFlags); _filter.setExcludeFlags(excludeFlags); +#ifdef MOD_PLAYERBOTS + // Bots bias their routes away from deep water (swim only when necessary). poly.area == poly.flags == + // NavTerrain, so NAV_WATER doubles as the water area index. Real players and creatures assign no cost. + if (isBot) + _filter.setAreaCost(NAV_WATER, 20.0f); +#endif + UpdateFilter(); } diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.h b/src/server/game/Movement/MovementGenerators/PathGenerator.h index 56b55d2f9..a2390152b 100644 --- a/src/server/game/Movement/MovementGenerators/PathGenerator.h +++ b/src/server/game/Movement/MovementGenerators/PathGenerator.h @@ -32,8 +32,16 @@ class WorldObject; // 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 +#ifdef MOD_PLAYERBOTS +// Bots travel long-distance to quests; the default 74-poly cap forces +// repeated re-pathfinding mid-route and produces partial paths short of +// the destination. 148 covers most quest movements end-to-end. +#define MAX_PATH_LENGTH 148 +#define MAX_POINT_PATH_LENGTH 148 +#else #define MAX_PATH_LENGTH 74 #define MAX_POINT_PATH_LENGTH 74 +#endif #define SMOOTH_PATH_STEP_SIZE 4.0f #define SMOOTH_PATH_SLOP 0.3f @@ -81,6 +89,16 @@ class PathGenerator void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; } void SetPathLengthLimit(float distance) { _pointPathLimit = std::min(uint32(distance/SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); } void SetUseRaycast(bool useRaycast) { _useRaycast = useRaycast; } + // Adjust per-terrain Detour traversal cost on the active query + // filter. Persists across CalculatePath calls until overwritten. + void SetNavTerrainCost(NavTerrain terrain, float cost) + { + _filter.setAreaCost(static_cast(terrain), cost); + } + // Replace the active filter's exclude bitmask. Caller may pass + // a single NavTerrain or an OR'd combination (NavTerrain values + // implicitly convert through uint16). + void SetExcludeFlags(uint16 flags) { _filter.setExcludeFlags(flags); } // result getters [[nodiscard]] G3D::Vector3 const& GetStartPosition() const { return _startPosition; } diff --git a/src/tools/mmaps_generator/Config.cpp b/src/tools/mmaps_generator/Config.cpp index dabdb0f0d..d334b3812 100644 --- a/src/tools/mmaps_generator/Config.cpp +++ b/src/tools/mmaps_generator/Config.cpp @@ -142,7 +142,11 @@ namespace MMAP config.vertexPerTileEdge = vertexPerTile; config.baseUnitDim = ComputeBaseUnitDim(vertexPerMap); config.tilesPerMapEdge = vertexPerMap / vertexPerTile; - config.maxSimplificationError = _global.maxSimplificationError; + config.maxSimplificationError = resolveFloat( + [](const TileOverride* t) { return t->maxSimplificationError; }, + [](const MapOverride* m) { return m->maxSimplificationError; }, + _global.maxSimplificationError + ); config.cellSizeHorizontal = config.baseUnitDim; config.cellSizeVertical = config.baseUnitDim; @@ -227,10 +231,14 @@ namespace MMAP override.walkableClimb = mapNode["walkableClimb"].get_value(); if (mapNode.contains("vertexPerMapEdge")) override.vertexPerMapEdge = mapNode["vertexPerMapEdge"].get_value(); + if (mapNode.contains("vertexPerTileEdge")) + override.vertexPerTileEdge = mapNode["vertexPerTileEdge"].get_value(); if (mapNode.contains("cellSizeHorizontal")) override.cellSizeHorizontal = mapNode["cellSizeHorizontal"].get_value(); if (mapNode.contains("cellSizeVertical")) override.cellSizeVertical = mapNode["cellSizeVertical"].get_value(); + if (mapNode.contains("maxSimplificationError")) + override.maxSimplificationError = mapNode["maxSimplificationError"].get_value(); // Tile overrides if (mapNode.contains("tilesOverrides")) @@ -257,6 +265,8 @@ namespace MMAP tileOverride.walkableHeight = tileNode["walkableHeight"].get_value(); if (tileNode.contains("walkableClimb")) tileOverride.walkableClimb = tileNode["walkableClimb"].get_value(); + if (tileNode.contains("maxSimplificationError")) + tileOverride.maxSimplificationError = tileNode["maxSimplificationError"].get_value(); override.tileOverrides[{tileX, tileY}] = std::move(tileOverride); } diff --git a/src/tools/mmaps_generator/Config.h b/src/tools/mmaps_generator/Config.h index c97fa7999..0b738d4cf 100644 --- a/src/tools/mmaps_generator/Config.h +++ b/src/tools/mmaps_generator/Config.h @@ -86,6 +86,7 @@ namespace MMAP std::optional walkableRadius; std::optional walkableHeight; std::optional walkableClimb; + std::optional maxSimplificationError; }; struct MapOverride { @@ -95,6 +96,7 @@ namespace MMAP std::optional walkableClimb; std::optional vertexPerMapEdge; std::optional vertexPerTileEdge; + std::optional maxSimplificationError; // The width/depth of each cell in the XZ-plane grid used for voxelization. [Units: world units] // A smaller value increases navmesh resolution but also memory and CPU usage. diff --git a/src/tools/mmaps_generator/MapBuilder.cpp b/src/tools/mmaps_generator/MapBuilder.cpp index 45c71367b..a60086bf8 100644 --- a/src/tools/mmaps_generator/MapBuilder.cpp +++ b/src/tools/mmaps_generator/MapBuilder.cpp @@ -30,6 +30,34 @@ namespace MMAP { + static void modAlmostUnwalkableTriangles(float const playerSlopeAngle, + float const* verts, int /*nv*/, + int const* tris, int nt, + unsigned char* areas) + { + float const walkableThr = std::cos(playerSlopeAngle / 180.0f * static_cast(M_PI)); + + float norm[3]; + + for (int i = 0; i < nt; ++i) + { + if (areas[i] == RC_NULL_AREA) + continue; + + int const* tri = &tris[i * 3]; + + float e0[3], e1[3]; + rcVsub(e0, &verts[tri[1] * 3], &verts[tri[0] * 3]); + rcVsub(e1, &verts[tri[2] * 3], &verts[tri[0] * 3]); + rcVcross(norm, e0, e1); + rcVnormalize(norm); + + if (norm[1] <= walkableThr) + areas[i] = NAV_GROUND_STEEP; + } + } + + TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool debugOutput) : m_debugOutput(debugOutput), m_mapBuilder(mapBuilder), @@ -660,6 +688,8 @@ namespace MMAP unsigned char* triFlags = new unsigned char[tTriCount]; memset(triFlags, NAV_GROUND, tTriCount * sizeof(unsigned char)); rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); + // mod_playerbots (bots should not attempt to use paths that includes slopes beyound 50 degrees) + modAlmostUnwalkableTriangles(50.0f, tVerts, tVertCount, tTris, tTriCount, triFlags); rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb); delete[] triFlags; diff --git a/src/tools/mmaps_generator/mmaps-config.yaml b/src/tools/mmaps_generator/mmaps-config.yaml index 7adcee64c..88b4e1bed 100644 --- a/src/tools/mmaps_generator/mmaps-config.yaml +++ b/src/tools/mmaps_generator/mmaps-config.yaml @@ -22,14 +22,14 @@ mmapsConfig: # In RecastDemo, you often work with world units instead of cell units. # By default, these cell units are converted to world units using the formula: # - # cellSize = MMAP::GRID_SIZE / (verticesPerMapEdge - 1) + # cellSize = MMAP::GRID_SIZE / (vertexPerMapEdge - 1) # # Where: # MMAP::GRID_SIZE = 533.3333f (the size of one map tile in world units) - # verticesPerMapEdge = number of vertices along one edge of the full map grid + # vertexPerMapEdge = number of vertices along one edge of the full map grid # # Example: - # If verticesPerMapEdge = 2000, then: + # If vertexPerMapEdge = 2000, then: # cellSize ≈ 533.3333 / (2000 - 1) ≈ 0.2667 world units per cell # # To convert a value from cell units to world units (e.g., walkableClimb), @@ -47,7 +47,7 @@ mmapsConfig: # # Vanilla WotLK uses 6, which allows creatures to "jump" over fences. # Classic WotLK uses 4, which forces creatures to walk around fences. - walkableClimb: 6 + walkableClimb: 4 # Minimum distance (in cell units) around walkable surfaces. # Helps prevent NPCs from clipping into walls and narrow gaps. @@ -56,17 +56,17 @@ mmapsConfig: # Number of vertices along one edge of the entire map's navmesh grid. # Higher values increase mesh resolution but also CPU/memory usage. - verticesPerMapEdge: 2000 + vertexPerMapEdge: 2000 # Number of vertices along one edge of each tile chunk. # Must divide (vertexPerMapEdge - 1) evenly for seamless tiles. # A higher vertex count per tile means fewer total tiles, # reducing runtime work to load, unload, and manage tiles. - verticesPerTileEdge: 80 + vertexPerTileEdge: 80 # Tolerance for how much a polygon can deviate from the original geometry when simplified. # Higher values produce simpler (faster) meshes but can reduce accuracy. - maxSimplificationError: 1.8 + maxSimplificationError: 0.8 # You can override any global parameter for a specific map by specifying its map ID. # Inside each map override, you can also override parameters per individual tile,