mirror of
https://github.com/liyunfan1223/azerothcore-wotlk.git
synced 2026-08-05 22:37:57 +00:00
feat(Core): mmaps config overrides + bot steep-slope-aware nav filter
mmaps extraction (mmaps-config.yaml + MapBuilder): - Per-map/tile overrides for vertexPerTileEdge and maxSimplificationError; singular vertex*Edge keys. - Tag 50-60deg slopes NAV_GROUND_STEEP (modAlmostUnwalkableTriangles) on top of clearing >60deg. Bot nav filter (PathGenerator, MOD_PLAYERBOTS): - CreateFilter detects a bot via GetSession()->IsBot() and applies a stricter filter: include ground/water, exclude lava/slime and NAV_GROUND_STEEP, and cost deep water. It runs in the constructor so all bot movement is covered; real players and creatures are unchanged. - Add NAV_GROUND_STEEP (0x10) nav flag. - Double MAX_PATH_LENGTH to 148 for long bot routes; expose SetNavTerrainCost/SetExcludeFlags.
This commit is contained in:
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>(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<uint8>(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; }
|
||||
|
||||
@@ -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<int>();
|
||||
if (mapNode.contains("vertexPerMapEdge"))
|
||||
override.vertexPerMapEdge = mapNode["vertexPerMapEdge"].get_value<int>();
|
||||
if (mapNode.contains("vertexPerTileEdge"))
|
||||
override.vertexPerTileEdge = mapNode["vertexPerTileEdge"].get_value<int>();
|
||||
if (mapNode.contains("cellSizeHorizontal"))
|
||||
override.cellSizeHorizontal = mapNode["cellSizeHorizontal"].get_value<float>();
|
||||
if (mapNode.contains("cellSizeVertical"))
|
||||
override.cellSizeVertical = mapNode["cellSizeVertical"].get_value<float>();
|
||||
if (mapNode.contains("maxSimplificationError"))
|
||||
override.maxSimplificationError = mapNode["maxSimplificationError"].get_value<float>();
|
||||
|
||||
// Tile overrides
|
||||
if (mapNode.contains("tilesOverrides"))
|
||||
@@ -257,6 +265,8 @@ namespace MMAP
|
||||
tileOverride.walkableHeight = tileNode["walkableHeight"].get_value<int>();
|
||||
if (tileNode.contains("walkableClimb"))
|
||||
tileOverride.walkableClimb = tileNode["walkableClimb"].get_value<int>();
|
||||
if (tileNode.contains("maxSimplificationError"))
|
||||
tileOverride.maxSimplificationError = tileNode["maxSimplificationError"].get_value<float>();
|
||||
|
||||
override.tileOverrides[{tileX, tileY}] = std::move(tileOverride);
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace MMAP
|
||||
std::optional<int> walkableRadius;
|
||||
std::optional<int> walkableHeight;
|
||||
std::optional<int> walkableClimb;
|
||||
std::optional<float> maxSimplificationError;
|
||||
};
|
||||
|
||||
struct MapOverride {
|
||||
@@ -95,6 +96,7 @@ namespace MMAP
|
||||
std::optional<int> walkableClimb;
|
||||
std::optional<int> vertexPerMapEdge;
|
||||
std::optional<int> vertexPerTileEdge;
|
||||
std::optional<float> 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.
|
||||
|
||||
@@ -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<float>(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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user