Compare commits

..

No commits in common. "cee4a067fad003c7ab2ec543e8bf4a3e79eb9976" and "91b217c962829295728091ec5c3e79c619fe56e5" have entirely different histories.

8 changed files with 104 additions and 72 deletions

View File

@ -48,16 +48,6 @@ Then build the server following the platform-specific instructions in our **[Ins
> **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues.
### Required server configuration
In `worldserver.conf` (AzerothCore core config), set:
```ini
PreloadAllNonInstancedMapGrids = 1
```
This is required for `mod-playerbots`.
### Detailed Guides
| Guide | Description |

View File

@ -343,6 +343,10 @@ AiPlayerbot.MaxWaitForMove = 5000
# 2 - MoveSplinePath disabled everywhere
AiPlayerbot.DisableMoveSplinePath = 0
# Max search time for movement (higher for better movement on slopes)
# Default: 3
AiPlayerbot.MaxMovementSearchTime = 3
# Action expiration time
AiPlayerbot.ExpireActionTime = 5000

View File

@ -385,29 +385,32 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
}
else
{
// Direct dispatch — engine MovePoint(generatePath=true) handles
// path-finding internally. Previously called SearchForBestPath
// here to probe ±step around the target z; that helped find
// polygons when the input z was several yards off the navmesh,
// but its "shortest path" preference would shift modifiedZ to
// an unreachable nearby polygon (upper terrace, ledge above)
// and then the engine's straight-spline NOPATH fallback would
// air-walk the bot up to it. cmangos doesn't have an
// equivalent — single-z PathFinder call is sufficient.
float distance = bot->GetExactDist(x, y, z);
float modifiedZ;
Movement::PointsArray path =
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig.maxMovementSearchTime, normal_only);
if (modifiedZ == INVALID_HEIGHT)
return false;
float distance = bot->GetExactDist(x, y, modifiedZ);
if (distance > 0.01f)
{
if (bot->IsSitState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
DoMovePoint(bot, x, y, z, generatePath, backwards);
// if (bot->IsNonMeleeSpellCast(true))
// {
// bot->CastStop();
// botAI->InterruptSpell();
// }
DoMovePoint(bot, x, y, modifiedZ, generatePath, backwards);
float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay)
{
delay -= botAI->GetReactDelay();
}
delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement")
.Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
.Set(mapId, x, y, modifiedZ, bot->GetOrientation(), delay, priority);
return true;
}
}
@ -1893,6 +1896,75 @@ PathResult MovementAction::GeneratePath(float x, float y, float z, uint32 accept
return result;
}
const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y, float z, float& modified_z,
int maxSearchCount, bool normal_only, float step)
{
bool found = false;
modified_z = INVALID_HEIGHT;
float tempZ = bot->GetMapHeight(x, y, z);
PathGenerator gen(bot);
gen.CalculatePath(x, y, tempZ);
Movement::PointsArray result = gen.GetPath();
float min_length = gen.getPathLength();
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
if ((gen.GetPathType() & typeOk) && abs(tempZ - z) < 0.5f)
{
modified_z = tempZ;
return result;
}
// Start searching
if (gen.GetPathType() & typeOk)
{
modified_z = tempZ;
found = true;
}
int count = 1;
for (float delta = step; count < maxSearchCount / 2 + 1; count++, delta += step)
{
tempZ = bot->GetMapHeight(x, y, z + delta);
if (tempZ == INVALID_HEIGHT)
{
continue;
}
PathGenerator gen(bot);
gen.CalculatePath(x, y, tempZ);
if ((gen.GetPathType() & typeOk) && gen.getPathLength() < min_length)
{
found = true;
min_length = gen.getPathLength();
result = gen.GetPath();
modified_z = tempZ;
}
}
for (float delta = -step; count < maxSearchCount; count++, delta -= step)
{
tempZ = bot->GetMapHeight(x, y, z + delta);
if (tempZ == INVALID_HEIGHT)
{
continue;
}
PathGenerator gen(bot);
gen.CalculatePath(x, y, tempZ);
if ((gen.GetPathType() & typeOk) && gen.getPathLength() < min_length)
{
found = true;
min_length = gen.getPathLength();
result = gen.GetPath();
modified_z = tempZ;
}
}
if (!found && normal_only)
{
modified_z = INVALID_HEIGHT;
return Movement::PointsArray{};
}
if (!found && !normal_only)
{
return result;
}
return result;
}
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
{
if (!unit)

View File

@ -88,7 +88,7 @@ protected:
bool FleePosition(Position pos, float radius, uint32 minInterval = 1000);
bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList);
PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = false);
PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = true);
bool GetTravelPlan(TravelPlan& plan, WorldPosition destination);
bool ExecuteTravelPlan(TravelPlan& state);
@ -115,6 +115,10 @@ protected:
};
private:
// float SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range = 20.0f, bool
// normal_only = false, float step = 8.0f);
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5,
bool normal_only = false, float step = 8.0f);
bool wasMovementRestricted = false;
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
};

View File

@ -314,27 +314,6 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
}
}
// Probe failed or didn't progress — emit visibility whisper so
// the user can see WHY mmap didn't dispatch. Without this the
// do-quest action's `MoveRandomNear` nudge appears with no
// preceding MoveFar whisper, and the failure mode is invisible.
{
bool const probeProgressed = !probe.empty() && probe.size() >= 2 &&
(dest.GetExactDist(probe.back().GetPositionX(),
probe.back().GetPositionY(), probe.back().GetPositionZ()) + 5.0f < disToDest);
if (!probeProgressed)
{
char fails[32];
snprintf(fails, sizeof(fails), "mF=%d nF=%d",
botAI->rpgInfo.CountRecentAttempts(dest, false),
botAI->rpgInfo.CountRecentAttempts(dest, true));
char const* reason = (probe.empty() || probe.size() < 2) ? "mmap-empty" : "mmap-noprogress";
EmitDebugMove("MoveFar", reason,
dest.GetPositionX(), dest.GetPositionY(),
dest.GetPositionZ(), fails);
}
}
// Empty / non-progressing path falls back to dispatching the
// destination as a single waypoint. Spline only when target is
// line-of-sight: dispatching a straight line through walls
@ -351,16 +330,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
forceNodesOverMmap ? "F-nodes " : "",
bothExhausted ? "EXHAUST " : "");
if (!inLOS)
{
char fails[32];
snprintf(fails, sizeof(fails), "mF=%d nF=%d",
botAI->rpgInfo.CountRecentAttempts(dest, false),
botAI->rpgInfo.CountRecentAttempts(dest, true));
EmitDebugMove("MoveFar", "spline-blocked",
dest.GetPositionX(), dest.GetPositionY(),
dest.GetPositionZ(), fails);
return false; // Refuse to dispatch a straight line through geometry.
}
{
char fails[32];
snprintf(fails, sizeof(fails), "mF=%d nF=%d",
@ -454,7 +424,7 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority,
float dy = y + distance * sin(angle);
float dz = z;
PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK, /*forceDestination=*/false);
PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK);
if (!path.reachable)
continue;

View File

@ -739,23 +739,14 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
if (tempCreature)
delete tempCreature;
// PathType is a bitmask. Two things to handle:
//
// 1. AC's PathGenerator can return INCOMPLETE | FARFROMPOLY_END
// (0x84) etc. — strict `== PATHFIND_INCOMPLETE` would reject
// these perfectly usable partial paths. Use bitwise to accept
// NORMAL/INCOMPLETE plus auxiliary flags.
//
// 2. AC's PathGenerator at PathGenerator.cpp:177-188 returns
// NORMAL | NOT_USING_PATH for player units when start or end
// polygon is INVALID_POLYREF (BuildShortcut → 2-point straight
// line through whatever's in the way). cmangos by contrast
// returns NOPATH for the same case (PathFinder.cpp:437-441).
// To match cmangos's intent (never silently dispatch a
// geometry-ignoring shortcut), reject any path with the
// NOT_USING_PATH bit set.
if ((type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE))
&& !(type & PATHFIND_NOT_USING_PATH))
// PathType is a bitmask (PathGenerator.h). Detour can return e.g.
// PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END (0x84) when the
// destination is a few yards off the nearest polygon — a strict
// `== PATHFIND_INCOMPLETE` check would reject the perfectly usable
// partial path and the chained probe would terminate empty on the
// very first call. PathGenerator's own internal code uses bitwise
// tests like `!(_type & PATHFIND_INCOMPLETE)`.
if (type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE))
return fromPointsArray(points);
return {};

View File

@ -71,6 +71,7 @@ bool PlayerbotAIConfig::Initialize()
globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 500);
maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000);
disableMoveSplinePath = sConfigMgr->GetOption<int32>("AiPlayerbot.DisableMoveSplinePath", 0);
maxMovementSearchTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxMovementSearchTime", 3);
expireActionTime = sConfigMgr->GetOption<int32>("AiPlayerbot.ExpireActionTime", 5000);
dispelAuraDuration = sConfigMgr->GetOption<int32>("AiPlayerbot.DispelAuraDuration", 700);
reactDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReactDelay", 100);

View File

@ -84,7 +84,7 @@ public:
bool EnableICCBuffs;
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, expireActionTime,
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
bool dynamicReactDelay;
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,