Compare commits

..

No commits in common. "7772dc4c0d774ec6858a5e19ee45d1696f3015b0" and "899f2cba94ae9a30d23bdacd28af8ebe7f59bf6c" have entirely different histories.

View File

@ -1472,12 +1472,13 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
TravelNodeStub* hsStub = &m_stubs.insert(std::make_pair( TravelNodeStub* hsStub = &m_stubs.insert(std::make_pair(
static_cast<TravelNode*>(portNode), TravelNodeStub(portNode))).first->second; static_cast<TravelNode*>(portNode), TravelNodeStub(portNode))).first->second;
// Cost: max(2, (10 - deathCount) * MINUTE) — matches // Cost: max(2 seconds, (10 - deathCount) * MINUTE).
// reference exactly, including the uint32 underflow at // Fresh bot → 10 minutes (walks if anything's closer);
// deathCount > 10 (which makes hearthstone prohibitive // recently-died bot → drops toward 2 seconds (hearth wins).
// for very-dead bots — apparently intentional). // Clamp deathCount to 10 to avoid uint32 underflow that
hsStub->costFromStart = std::max<uint32>(2, // the reference implementation has at deathCount > 10.
(10 - AI_VALUE(uint32, "death count")) * MINUTE); uint32 const dc = std::min<uint32>(10, AI_VALUE(uint32, "death count"));
hsStub->costFromStart = std::max<uint32>(2, (10 - dc) * MINUTE);
hsStub->heuristic = hsStub->dataNode->fDist(goal) / botSpeed; hsStub->heuristic = hsStub->dataNode->fDist(goal) / botSpeed;
hsStub->totalCost = hsStub->costFromStart + hsStub->heuristic; hsStub->totalCost = hsStub->costFromStart + hsStub->heuristic;
@ -1547,8 +1548,18 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal,
// PortalNode stubs injected above. // PortalNode stubs injected above.
std::make_heap(open.begin(), open.end(), heapComp); std::make_heap(open.begin(), open.end(), heapComp);
constexpr uint32 MAX_A_STAR_EXPLORED = 500;
uint32 nodesExplored = 0;
while (!open.empty()) while (!open.empty())
{ {
if (++nodesExplored > MAX_A_STAR_EXPLORED)
{
for (auto* p : portNodes)
delete p;
return TravelNodeRoute();
}
std::pop_heap(open.begin(), open.end(), heapComp); std::pop_heap(open.begin(), open.end(), heapComp);
currentNode = open.back(); currentNode = open.back();
open.pop_back(); open.pop_back();
@ -1702,17 +1713,36 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
{ {
TravelPath path; TravelPath path;
// Probe-first short-circuit (matches reference exactly): if a 40-step // AC-side workaround that reference doesn't have: if a 40-step mmap
// mmap probe from bot to destination reaches within spellDistance of // probe from bot to destination either reaches close to dest OR
// dest, use the probe directly and skip graph routing. Otherwise // makes any meaningful forward progress (>30y absolute), prefer
// fall through to the graph A* below — the failed probe waypoints // that direct path over the graph. The graph's K=5 endNode pick +
// would ideally feed into getRoute as startPath (reference does // strict 1y endPath validation rejects destinations that are
// this; we don't yet — TODO). // slightly off-mesh (cave shelves, alcoves), so without this the
// bot falls to a single-point MoveTo fallback and wiggles in place.
// Loosened from "50% AND >30y" to just ">30y" so a partial probe
// toward the cave entrance gets accepted; the next tick's
// re-resolve from the new bot position can extend further.
if (botPos.GetMapId() == destination.GetMapId()) if (botPos.GetMapId() == destination.GetMapId())
{ {
std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40); std::vector<WorldPosition> probe = destination.getPathFromPath({botPos}, bot, 40);
if (destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance)) if (probe.size() >= 2)
return TravelPath(probe); {
float const totalDist = botPos.distance(destination);
float const probeEndToDest = destination.distance(probe.back());
float const probeProgress = totalDist - probeEndToDest;
bool const closeEnough = probeEndToDest < 30.0f;
bool const meaningfulProgress = probeProgress > 30.0f;
if (closeEnough || meaningfulProgress)
{
path.addPoint(botPos, PathNodeType::NODE_PREPATH);
for (size_t i = 1; i < probe.size(); ++i)
path.addPoint(probe[i], PathNodeType::NODE_PATH);
return path;
}
}
} }
std::shared_lock<std::shared_timed_mutex> guard(m_nMapMtx); std::shared_lock<std::shared_timed_mutex> guard(m_nMapMtx);
@ -1765,17 +1795,19 @@ TravelPath TravelNodeMap::GetFullPath(WorldPosition botPos, [[maybe_unused]] uin
continue; continue;
WorldPosition endNodePos = *e->getPosition(); WorldPosition endNodePos = *e->getPosition();
// Validate endNode -> destination is pathable within 1y (matches // Validate endNode -> destination is pathable. Reference uses 1y
// reference exactly). Off-mesh destinations that fail this check // strict tolerance, but that rejects valid cave-interior nodes
// need a fix at the data layer (node placement, quest dest coords), // when the destination (quest GO, mob, item) is slightly off the
// not a loosened threshold here. // navmesh (small shelf, alcove). Use INTERACTION_DISTANCE so any
// endNode whose mmap can reach close enough for the bot to
// interact with the destination is accepted.
std::vector<WorldPosition> endProbe; std::vector<WorldPosition> endProbe;
bool endPathOk = false; bool endPathOk = false;
if (endNodePos.GetMapId() == destination.GetMapId()) if (endNodePos.GetMapId() == destination.GetMapId())
{ {
Unit* pathBot = (bot && bot->GetMapId() == destination.GetMapId()) ? bot : nullptr; Unit* pathBot = (bot && bot->GetMapId() == destination.GetMapId()) ? bot : nullptr;
endProbe = endNodePos.getPathTo(destination, pathBot); endProbe = endNodePos.getPathTo(destination, pathBot);
endPathOk = destination.isPathTo(endProbe, 1.0f); endPathOk = destination.isPathTo(endProbe, INTERACTION_DISTANCE);
} }
else else
{ {