mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-21 07:59: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.
|
> **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 |
|
||||||
|
|||||||
@ -343,6 +343,10 @@ 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
|
||||||
|
|
||||||
|
|||||||
@ -385,29 +385,32 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Direct dispatch — engine MovePoint(generatePath=true) handles
|
float modifiedZ;
|
||||||
// path-finding internally. Previously called SearchForBestPath
|
Movement::PointsArray path =
|
||||||
// here to probe ±step around the target z; that helped find
|
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig.maxMovementSearchTime, normal_only);
|
||||||
// polygons when the input z was several yards off the navmesh,
|
if (modifiedZ == INVALID_HEIGHT)
|
||||||
// but its "shortest path" preference would shift modifiedZ to
|
return false;
|
||||||
// an unreachable nearby polygon (upper terrace, ledge above)
|
float distance = bot->GetExactDist(x, y, modifiedZ);
|
||||||
// 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);
|
||||||
|
|
||||||
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);
|
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, z, bot->GetOrientation(), delay, priority);
|
.Set(mapId, x, y, modifiedZ, bot->GetOrientation(), delay, priority);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1893,6 +1896,75 @@ 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)
|
||||||
|
|||||||
@ -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 = 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 GetTravelPlan(TravelPlan& plan, WorldPosition destination);
|
||||||
bool ExecuteTravelPlan(TravelPlan& state);
|
bool ExecuteTravelPlan(TravelPlan& state);
|
||||||
@ -115,6 +115,10 @@ 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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
|
// 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
|
||||||
@ -351,16 +330,7 @@ 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",
|
||||||
@ -454,7 +424,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, /*forceDestination=*/false);
|
PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK);
|
||||||
|
|
||||||
if (!path.reachable)
|
if (!path.reachable)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -739,23 +739,14 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
|
|||||||
if (tempCreature)
|
if (tempCreature)
|
||||||
delete tempCreature;
|
delete tempCreature;
|
||||||
|
|
||||||
// PathType is a bitmask. Two things to handle:
|
// PathType is a bitmask (PathGenerator.h). Detour can return e.g.
|
||||||
//
|
// PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END (0x84) when the
|
||||||
// 1. AC's PathGenerator can return INCOMPLETE | FARFROMPOLY_END
|
// destination is a few yards off the nearest polygon — a strict
|
||||||
// (0x84) etc. — strict `== PATHFIND_INCOMPLETE` would reject
|
// `== PATHFIND_INCOMPLETE` check would reject the perfectly usable
|
||||||
// these perfectly usable partial paths. Use bitwise to accept
|
// partial path and the chained probe would terminate empty on the
|
||||||
// NORMAL/INCOMPLETE plus auxiliary flags.
|
// very first call. PathGenerator's own internal code uses bitwise
|
||||||
//
|
// tests like `!(_type & PATHFIND_INCOMPLETE)`.
|
||||||
// 2. AC's PathGenerator at PathGenerator.cpp:177-188 returns
|
if (type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE))
|
||||||
// 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 {};
|
||||||
|
|||||||
@ -71,6 +71,7 @@ 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);
|
||||||
|
|||||||
@ -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, expireActionTime,
|
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, 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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user