mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
improving RPG traveland minimize wierd path selections but still happen
This commit is contained in:
parent
cd16f6baf1
commit
03db0c34b2
@ -74,8 +74,18 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
|
||||
|
||||
float maxDistance = target->getDestination()->getRadiusMin();
|
||||
|
||||
// Evenly distribute around the target.
|
||||
float angle = 2 * M_PI * urand(0, 100) / 100.0;
|
||||
// Spread bots around the target but keep the offset stable per
|
||||
// (bot, destination) pair. Previously the angle and radius were
|
||||
// re-rolled every time the action re-entered (i.e. every tick the
|
||||
// bot wasn't already moving), which made bots oscillate between
|
||||
// two random points around the same quest POI instead of
|
||||
// committing to one approach.
|
||||
uint32 botLow = bot->GetGUID().GetCounter();
|
||||
int32 destSeed = static_cast<int32>(location.GetPositionX()) * 73856093 ^
|
||||
static_cast<int32>(location.GetPositionY()) * 19349663;
|
||||
uint32 seed = botLow ^ static_cast<uint32>(destSeed);
|
||||
float angle = 2.0f * static_cast<float>(M_PI) * static_cast<float>(seed % 1000) / 1000.0f;
|
||||
float mod = 0.5f + static_cast<float>((seed / 1000) % 1000) / 2000.0f; // [0.5, 1.0]
|
||||
|
||||
if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up.
|
||||
{
|
||||
@ -89,9 +99,6 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
|
||||
float z = location.GetPositionZ();
|
||||
float mapId = location.GetMapId();
|
||||
|
||||
// Move between 0.5 and 1.0 times the maxDistance.
|
||||
float mod = frand(50.f, 100.f) / 100.0f;
|
||||
|
||||
x += cos(angle) * maxDistance * mod;
|
||||
y += sin(angle) * maxDistance * mod;
|
||||
|
||||
|
||||
@ -151,7 +151,14 @@ bool NewRpgGoGrindAction::Execute(Event /*event*/)
|
||||
if (SearchQuestGiverAndAcceptOrReward())
|
||||
return true;
|
||||
if (auto* data = std::get_if<NewRpgInfo::GoGrind>(&botAI->rpgInfo.data))
|
||||
return MoveFarTo(data->pos);
|
||||
{
|
||||
if (MoveFarTo(data->pos))
|
||||
return true;
|
||||
// Small nudge so the next tick's MoveFarTo starts from a
|
||||
// slightly different position. Kept small so it doesn't look
|
||||
// like the bot is abandoning its destination.
|
||||
return MoveRandomNear(10.0f);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -162,7 +169,11 @@ bool NewRpgGoCampAction::Execute(Event /*event*/)
|
||||
return true;
|
||||
|
||||
if (auto* data = std::get_if<NewRpgInfo::GoCamp>(&botAI->rpgInfo.data))
|
||||
return MoveFarTo(data->pos);
|
||||
{
|
||||
if (MoveFarTo(data->pos))
|
||||
return true;
|
||||
return MoveRandomNear(10.0f);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -215,7 +226,14 @@ bool NewRpgWanderNpcAction::Execute(Event /*event*/)
|
||||
data.lastReach = 0;
|
||||
}
|
||||
else
|
||||
return MoveWorldObjectTo(data.npcOrGo);
|
||||
{
|
||||
if (MoveWorldObjectTo(data.npcOrGo))
|
||||
return true;
|
||||
// NPC pathing failed (random offset in a wall, mmap hiccup, etc).
|
||||
// Take a small random step so the next tick retries from a
|
||||
// different spot instead of staring at the NPC from afar.
|
||||
return MoveRandomNear(15.0f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -305,7 +323,12 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
|
||||
|
||||
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
|
||||
{
|
||||
return MoveFarTo(data.pos);
|
||||
if (MoveFarTo(data.pos))
|
||||
return true;
|
||||
// Long-range sampler couldn't land a candidate — nudge the
|
||||
// bot a short distance so the next tick retries from a
|
||||
// different position instead of sitting idle.
|
||||
return MoveRandomNear(10.0f);
|
||||
}
|
||||
// Now we are near the quest objective
|
||||
// kill mobs and looting quest should be done automatically by grind strategy
|
||||
@ -352,7 +375,11 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
|
||||
return true;
|
||||
}
|
||||
|
||||
return MoveRandomNear(20.0f);
|
||||
// At the POI: keep the bot actively placed but avoid large
|
||||
// random 20yd hops that look like pacing back and forth. A small
|
||||
// ~8yd wander reads as the bot looking around while grind/loot
|
||||
// strategies do their work.
|
||||
return MoveRandomNear(8.0f);
|
||||
}
|
||||
|
||||
bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
||||
@ -392,7 +419,11 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
||||
return false;
|
||||
|
||||
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
|
||||
return MoveFarTo(data.pos);
|
||||
{
|
||||
if (MoveFarTo(data.pos))
|
||||
return true;
|
||||
return MoveRandomNear(10.0f);
|
||||
}
|
||||
|
||||
// Now we are near the qoi of reward
|
||||
// the quest should be rewarded by SearchQuestGiverAndAcceptOrReward
|
||||
|
||||
@ -46,17 +46,51 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let previously committed movement finish before recomputing.
|
||||
//
|
||||
// MoveTo internally caps its stored delay at maxWaitForMove
|
||||
// (default 5s), but a long path (200+ yd routed around a
|
||||
// mountain) takes 30+ seconds to walk. After 5s
|
||||
// IsWaitingForLastMove returns false and MoveFarTo re-enters.
|
||||
// Without this gate, DoMovePoint would call mm->Clear() and
|
||||
// reissue MovePoint from the new bot position — and from a new
|
||||
// position mmap's partial-path endpoint often differs, so the
|
||||
// bot gets clobbered mid-walk and ends up oscillating (e.g.
|
||||
// cave entrance -> inside cave -> cave entrance -> mountain
|
||||
// base -> cave entrance...) around an unreachable destination.
|
||||
//
|
||||
// If the bot is still actively walking toward its last
|
||||
// committed point on the same map, just let the current spline
|
||||
// finish. The stuck counter below continues to track real
|
||||
// progress toward dest and triggers teleport recovery if the
|
||||
// committed paths genuinely aren't closing the gap.
|
||||
{
|
||||
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
|
||||
if (bot->isMoving() && lastMove.lastMoveToMapId == bot->GetMapId())
|
||||
{
|
||||
float remaining = bot->GetExactDist(lastMove.lastMoveToX, lastMove.lastMoveToY, lastMove.lastMoveToZ);
|
||||
if (remaining > 10.0f)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// stuck check
|
||||
float disToDest = bot->GetDistance(dest);
|
||||
if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
|
||||
// Require a meaningful improvement (5yd) to reset the stuck counter.
|
||||
// The old 1yd threshold was small enough that bots oscillating back
|
||||
// and forth around an obstacle would keep "making progress" forever
|
||||
// and never trigger the teleport recovery below.
|
||||
if (disToDest + 5.0f < botAI->rpgInfo.nearestMoveFarDis)
|
||||
{
|
||||
botAI->rpgInfo.nearestMoveFarDis = disToDest;
|
||||
botAI->rpgInfo.stuckTs = getMSTime();
|
||||
botAI->rpgInfo.stuckAttempts = 0;
|
||||
}
|
||||
else if (++botAI->rpgInfo.stuckAttempts >= 10 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
|
||||
else if (++botAI->rpgInfo.stuckAttempts >= 5 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
|
||||
{
|
||||
// Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
|
||||
// No meaningful progress toward dest for `stuckTime`: fall
|
||||
// back to teleporting directly so the bot can get on with
|
||||
// its RPG objective instead of oscillating indefinitely.
|
||||
botAI->rpgInfo.stuckTs = getMSTime();
|
||||
botAI->rpgInfo.stuckAttempts = 0;
|
||||
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
|
||||
@ -78,26 +112,62 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
||||
false, true);
|
||||
}
|
||||
|
||||
const uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
|
||||
|
||||
// Primary strategy: ask mmap for a route to the TRUE destination.
|
||||
// If mmap can reach it directly (PATHFIND_NORMAL) or partially
|
||||
// (PATHFIND_INCOMPLETE — destinations beyond the smooth-path cap
|
||||
// of ~296 yards, or where local geometry blocks the final step),
|
||||
// walk to the furthest reachable waypoint mmap computed. This
|
||||
// lets bots follow the real route around obstacles (mountains,
|
||||
// cave walls, cliffs) instead of trying to cut straight through.
|
||||
// The spline system walks the whole returned path smoothly, so
|
||||
// subsequent ticks early-out via IsWaitingForLastMove and no
|
||||
// further PathGenerator calls fire until the bot arrives.
|
||||
{
|
||||
PathGenerator path(bot);
|
||||
path.CalculatePath(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||
PathType type = path.GetPathType();
|
||||
bool canReach = !(type & (~typeOk));
|
||||
if (canReach)
|
||||
{
|
||||
const G3D::Vector3& endPos = path.GetActualEndPosition();
|
||||
// Only commit if the mmap endpoint actually makes progress
|
||||
// toward the destination. For pathological INCOMPLETE
|
||||
// results (e.g. disconnected polys that still report
|
||||
// INCOMPLETE) the endpoint can land right under the bot;
|
||||
// fall through to cone sampling in that case.
|
||||
float endDistToDest = dest.GetExactDist(endPos.x, endPos.y, endPos.z);
|
||||
if (endDistToDest + 5.0f < disToDest)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: mmap couldn't route to the destination. Sample the
|
||||
// forward cone for a reachable stepping stone so the bot keeps
|
||||
// moving and can try again from a new vantage point. Cap at 2
|
||||
// samples — we already spent one PathGenerator call above and at
|
||||
// 3000 bots every extra CalculatePath matters.
|
||||
float minDelta = M_PI;
|
||||
const float x = bot->GetPositionX();
|
||||
const float y = bot->GetPositionY();
|
||||
const float z = bot->GetPositionZ();
|
||||
const float baseAngle = bot->GetAngle(&dest);
|
||||
float rx, ry, rz;
|
||||
bool found = false;
|
||||
int attempt = 3;
|
||||
while (attempt--)
|
||||
for (int attempt = 0; attempt < 2; ++attempt)
|
||||
{
|
||||
float angle = bot->GetAngle(&dest);
|
||||
float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
|
||||
angle += delta;
|
||||
float dis = rand_norm() * pathFinderDis;
|
||||
float dx = x + cos(angle) * dis;
|
||||
float dy = y + sin(angle) * dis;
|
||||
float delta = (rand_norm() - 0.5f) * static_cast<float>(M_PI); // ±π/2, forward cone
|
||||
float sampleDis = (0.5f + rand_norm() * 0.5f) * pathFinderDis;
|
||||
float angle = baseAngle + delta;
|
||||
float dx = x + cos(angle) * sampleDis;
|
||||
float dy = y + sin(angle) * sampleDis;
|
||||
float dz = z + 0.5f;
|
||||
PathGenerator path(bot);
|
||||
path.CalculatePath(dx, dy, dz);
|
||||
PathType type = path.GetPathType();
|
||||
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
|
||||
bool canReach = !(type & (~typeOk));
|
||||
|
||||
if (canReach && fabs(delta) <= minDelta)
|
||||
@ -159,14 +229,18 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
|
||||
return false;
|
||||
}
|
||||
|
||||
float distance = rand_norm() * moveStep;
|
||||
Map* map = bot->GetMap();
|
||||
const float x = bot->GetPositionX();
|
||||
const float y = bot->GetPositionY();
|
||||
const float z = bot->GetPositionZ();
|
||||
int attempts = 1;
|
||||
while (attempts--)
|
||||
// Previously: attempts = 1. A single random sample often landed in
|
||||
// water / blocked geometry / unreachable poly, the function returned
|
||||
// false, and the caller had no fallback — bot stood still. Retry a
|
||||
// handful of times with a fresh distance each loop so a bad roll
|
||||
// doesn't lock the bot in place.
|
||||
for (int attempt = 0; attempt < 8; ++attempt)
|
||||
{
|
||||
float distance = (0.4f + rand_norm() * 0.6f) * moveStep;
|
||||
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
|
||||
float dx = x + distance * cos(angle);
|
||||
float dy = y + distance * sin(angle);
|
||||
|
||||
@ -61,7 +61,14 @@ protected:
|
||||
protected:
|
||||
/* FOR MOVE FAR */
|
||||
const float pathFinderDis = 70.0f;
|
||||
const uint32 stuckTime = 5 * 60 * 1000;
|
||||
// Time without real progress toward dest before MoveFarTo
|
||||
// falls back to teleport recovery. Kept short enough that a
|
||||
// bot truly oscillating around an unreachable destination
|
||||
// (mmap returning non-progressing partial paths, or NOPATH +
|
||||
// cone fallback wandering) doesn't spin for 5 minutes before
|
||||
// the teleport fires, but long enough that a genuine long
|
||||
// walk that is slowly making progress never triggers it.
|
||||
const uint32 stuckTime = 90 * 1000;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user