Compare commits

...

11 Commits

8 changed files with 72 additions and 104 deletions

View File

@ -48,6 +48,16 @@ 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. > **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 ### Detailed Guides
| Guide | Description | | Guide | Description |

View File

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

View File

@ -385,32 +385,29 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
} }
else else
{ {
float modifiedZ; // Direct dispatch — engine MovePoint(generatePath=true) handles
Movement::PointsArray path = // path-finding internally. Previously called SearchForBestPath
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig.maxMovementSearchTime, normal_only); // here to probe ±step around the target z; that helped find
if (modifiedZ == INVALID_HEIGHT) // polygons when the input z was several yards off the navmesh,
return false; // but its "shortest path" preference would shift modifiedZ to
float distance = bot->GetExactDist(x, y, modifiedZ); // 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);
if (distance > 0.01f) if (distance > 0.01f)
{ {
if (bot->IsSitState()) if (bot->IsSitState())
bot->SetStandState(UNIT_STAND_STATE_STAND); bot->SetStandState(UNIT_STAND_STATE_STAND);
// if (bot->IsNonMeleeSpellCast(true)) DoMovePoint(bot, x, y, z, generatePath, backwards);
// {
// bot->CastStop();
// botAI->InterruptSpell();
// }
DoMovePoint(bot, x, y, modifiedZ, generatePath, backwards);
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{
delay -= botAI->GetReactDelay(); delay -= botAI->GetReactDelay();
}
delay = std::max(.0f, delay); delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay); delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement") AI_VALUE(LastMovement&, "last movement")
.Set(mapId, x, y, modifiedZ, bot->GetOrientation(), delay, priority); .Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
return true; return true;
} }
} }
@ -1896,75 +1893,6 @@ PathResult MovementAction::GeneratePath(float x, float y, float z, uint32 accept
return result; 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) void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
{ {
if (!unit) if (!unit)

View File

@ -88,7 +88,7 @@ protected:
bool FleePosition(Position pos, float radius, uint32 minInterval = 1000); bool FleePosition(Position pos, float radius, uint32 minInterval = 1000);
bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList); bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList);
PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = true); PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = false);
bool GetTravelPlan(TravelPlan& plan, WorldPosition destination); bool GetTravelPlan(TravelPlan& plan, WorldPosition destination);
bool ExecuteTravelPlan(TravelPlan& state); bool ExecuteTravelPlan(TravelPlan& state);
@ -115,10 +115,6 @@ protected:
}; };
private: 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; bool wasMovementRestricted = false;
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards); void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
}; };

View File

@ -314,6 +314,27 @@ 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 // Empty / non-progressing path falls back to dispatching the
// destination as a single waypoint. Spline only when target is // destination as a single waypoint. Spline only when target is
// line-of-sight: dispatching a straight line through walls // line-of-sight: dispatching a straight line through walls
@ -330,7 +351,16 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
forceNodesOverMmap ? "F-nodes " : "", forceNodesOverMmap ? "F-nodes " : "",
bothExhausted ? "EXHAUST " : ""); bothExhausted ? "EXHAUST " : "");
if (!inLOS) 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. return false; // Refuse to dispatch a straight line through geometry.
}
{ {
char fails[32]; char fails[32];
snprintf(fails, sizeof(fails), "mF=%d nF=%d", snprintf(fails, sizeof(fails), "mF=%d nF=%d",
@ -424,7 +454,7 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority,
float dy = y + distance * sin(angle); float dy = y + distance * sin(angle);
float dz = z; float dz = z;
PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK); PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK, /*forceDestination=*/false);
if (!path.reachable) if (!path.reachable)
continue; continue;

View File

@ -739,14 +739,23 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
if (tempCreature) if (tempCreature)
delete tempCreature; delete tempCreature;
// PathType is a bitmask (PathGenerator.h). Detour can return e.g. // PathType is a bitmask. Two things to handle:
// PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END (0x84) when the //
// destination is a few yards off the nearest polygon — a strict // 1. AC's PathGenerator can return INCOMPLETE | FARFROMPOLY_END
// `== PATHFIND_INCOMPLETE` check would reject the perfectly usable // (0x84) etc. — strict `== PATHFIND_INCOMPLETE` would reject
// partial path and the chained probe would terminate empty on the // these perfectly usable partial paths. Use bitwise to accept
// very first call. PathGenerator's own internal code uses bitwise // NORMAL/INCOMPLETE plus auxiliary flags.
// tests like `!(_type & PATHFIND_INCOMPLETE)`. //
if (type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE)) // 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))
return fromPointsArray(points); return fromPointsArray(points);
return {}; return {};

View File

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

View File

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