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();
|
float maxDistance = target->getDestination()->getRadiusMin();
|
||||||
|
|
||||||
// Evenly distribute around the target.
|
// Spread bots around the target but keep the offset stable per
|
||||||
float angle = 2 * M_PI * urand(0, 100) / 100.0;
|
// (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.
|
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 z = location.GetPositionZ();
|
||||||
float mapId = location.GetMapId();
|
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;
|
x += cos(angle) * maxDistance * mod;
|
||||||
y += sin(angle) * maxDistance * mod;
|
y += sin(angle) * maxDistance * mod;
|
||||||
|
|
||||||
|
|||||||
@ -151,7 +151,14 @@ bool NewRpgGoGrindAction::Execute(Event /*event*/)
|
|||||||
if (SearchQuestGiverAndAcceptOrReward())
|
if (SearchQuestGiverAndAcceptOrReward())
|
||||||
return true;
|
return true;
|
||||||
if (auto* data = std::get_if<NewRpgInfo::GoGrind>(&botAI->rpgInfo.data))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@ -162,7 +169,11 @@ bool NewRpgGoCampAction::Execute(Event /*event*/)
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (auto* data = std::get_if<NewRpgInfo::GoCamp>(&botAI->rpgInfo.data))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@ -215,7 +226,14 @@ bool NewRpgWanderNpcAction::Execute(Event /*event*/)
|
|||||||
data.lastReach = 0;
|
data.lastReach = 0;
|
||||||
}
|
}
|
||||||
else
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -305,7 +323,12 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
|
|||||||
|
|
||||||
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
|
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
|
// Now we are near the quest objective
|
||||||
// kill mobs and looting quest should be done automatically by grind strategy
|
// 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 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)
|
bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
||||||
@ -392,7 +419,11 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
|
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
|
// Now we are near the qoi of reward
|
||||||
// the quest should be rewarded by SearchQuestGiverAndAcceptOrReward
|
// the quest should be rewarded by SearchQuestGiverAndAcceptOrReward
|
||||||
|
|||||||
@ -46,17 +46,51 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
return false;
|
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
|
// stuck check
|
||||||
float disToDest = bot->GetDistance(dest);
|
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.nearestMoveFarDis = disToDest;
|
||||||
botAI->rpgInfo.stuckTs = getMSTime();
|
botAI->rpgInfo.stuckTs = getMSTime();
|
||||||
botAI->rpgInfo.stuckAttempts = 0;
|
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.stuckTs = getMSTime();
|
||||||
botAI->rpgInfo.stuckAttempts = 0;
|
botAI->rpgInfo.stuckAttempts = 0;
|
||||||
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
|
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
|
||||||
@ -78,26 +112,62 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
|
|||||||
false, true);
|
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;
|
float minDelta = M_PI;
|
||||||
const float x = bot->GetPositionX();
|
const float x = bot->GetPositionX();
|
||||||
const float y = bot->GetPositionY();
|
const float y = bot->GetPositionY();
|
||||||
const float z = bot->GetPositionZ();
|
const float z = bot->GetPositionZ();
|
||||||
|
const float baseAngle = bot->GetAngle(&dest);
|
||||||
float rx, ry, rz;
|
float rx, ry, rz;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
int attempt = 3;
|
for (int attempt = 0; attempt < 2; ++attempt)
|
||||||
while (attempt--)
|
|
||||||
{
|
{
|
||||||
float angle = bot->GetAngle(&dest);
|
float delta = (rand_norm() - 0.5f) * static_cast<float>(M_PI); // ±π/2, forward cone
|
||||||
float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
|
float sampleDis = (0.5f + rand_norm() * 0.5f) * pathFinderDis;
|
||||||
angle += delta;
|
float angle = baseAngle + delta;
|
||||||
float dis = rand_norm() * pathFinderDis;
|
float dx = x + cos(angle) * sampleDis;
|
||||||
float dx = x + cos(angle) * dis;
|
float dy = y + sin(angle) * sampleDis;
|
||||||
float dy = y + sin(angle) * dis;
|
|
||||||
float dz = z + 0.5f;
|
float dz = z + 0.5f;
|
||||||
PathGenerator path(bot);
|
PathGenerator path(bot);
|
||||||
path.CalculatePath(dx, dy, dz);
|
path.CalculatePath(dx, dy, dz);
|
||||||
PathType type = path.GetPathType();
|
PathType type = path.GetPathType();
|
||||||
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
|
|
||||||
bool canReach = !(type & (~typeOk));
|
bool canReach = !(type & (~typeOk));
|
||||||
|
|
||||||
if (canReach && fabs(delta) <= minDelta)
|
if (canReach && fabs(delta) <= minDelta)
|
||||||
@ -159,14 +229,18 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float distance = rand_norm() * moveStep;
|
|
||||||
Map* map = bot->GetMap();
|
Map* map = bot->GetMap();
|
||||||
const float x = bot->GetPositionX();
|
const float x = bot->GetPositionX();
|
||||||
const float y = bot->GetPositionY();
|
const float y = bot->GetPositionY();
|
||||||
const float z = bot->GetPositionZ();
|
const float z = bot->GetPositionZ();
|
||||||
int attempts = 1;
|
// Previously: attempts = 1. A single random sample often landed in
|
||||||
while (attempts--)
|
// 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 angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
|
||||||
float dx = x + distance * cos(angle);
|
float dx = x + distance * cos(angle);
|
||||||
float dy = y + distance * sin(angle);
|
float dy = y + distance * sin(angle);
|
||||||
|
|||||||
@ -61,7 +61,14 @@ protected:
|
|||||||
protected:
|
protected:
|
||||||
/* FOR MOVE FAR */
|
/* FOR MOVE FAR */
|
||||||
const float pathFinderDis = 70.0f;
|
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
|
#endif
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user