mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Compare commits
No commits in common. "cee4a067fad003c7ab2ec543e8bf4a3e79eb9976" and "91b217c962829295728091ec5c3e79c619fe56e5" have entirely different histories.
cee4a067fa
...
91b217c962
10
README.md
10
README.md
@ -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 |
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {};
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user