diff --git a/src/Ai/Base/Value/LastMovementValue.cpp b/src/Ai/Base/Value/LastMovementValue.cpp index 22c35450d..bd5ed3349 100644 --- a/src/Ai/Base/Value/LastMovementValue.cpp +++ b/src/Ai/Base/Value/LastMovementValue.cpp @@ -9,23 +9,6 @@ LastMovement::LastMovement() { clear(); } -LastMovement::LastMovement(LastMovement& other) - : taxiNodes(other.taxiNodes), - taxiMaster(other.taxiMaster), - lastFollow(other.lastFollow), - lastAreaTrigger(other.lastAreaTrigger), - lastMoveToX(other.lastMoveToX), - lastMoveToY(other.lastMoveToY), - lastMoveToZ(other.lastMoveToZ), - lastMoveToOri(other.lastMoveToOri), - lastFlee(other.lastFlee) -{ - lastMoveShort = other.lastMoveShort; - nextTeleport = other.nextTeleport; - lastPath = other.lastPath; - priority = other.priority; -} - void LastMovement::clear() { lastMoveShort = WorldPosition(); diff --git a/src/Ai/Base/Value/LastMovementValue.h b/src/Ai/Base/Value/LastMovementValue.h index ef09d31fe..1eb123325 100644 --- a/src/Ai/Base/Value/LastMovementValue.h +++ b/src/Ai/Base/Value/LastMovementValue.h @@ -27,20 +27,11 @@ class LastMovement { public: LastMovement(); - LastMovement(LastMovement& other); - LastMovement& operator=(LastMovement const& other) - { - taxiNodes = other.taxiNodes; - taxiMaster = other.taxiMaster; - lastFollow = other.lastFollow; - lastAreaTrigger = other.lastAreaTrigger; - lastMoveShort = other.lastMoveShort; - lastPath = other.lastPath; - nextTeleport = other.nextTeleport; - priority = other.priority; - return *this; - }; + // Non-copyable: holds a std::future and several state fields the + // historical copy-helpers omitted. Always pass by reference. + LastMovement(LastMovement const&) = delete; + LastMovement& operator=(LastMovement const&) = delete; void clear(); @@ -75,7 +66,7 @@ public: LastMovementValue(PlayerbotAI* botAI) : ManualSetValue(botAI, data) {} private: - LastMovement data = LastMovement(); + LastMovement data{}; }; class StayTimeValue : public ManualSetValue diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index c9184b400..670f61dfd 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -137,24 +137,40 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) bool const needsLongPath = (bot->GetMapId() != dest.GetMapId()) || (dis > sPlayerbotAIConfig.sightDistance); - // Skip travel-node planning inside battlegrounds — the graph is - // built for the open world and yields nonsense routes inside BGs. - // Mirrors cmangos MovementActions.cpp:705. - if (needsLongPath && sPlayerbotAIConfig.enableTravelNodes && - !bot->InBattleground()) + // needsLongPath branch — graph routing only. cmangos's getFullPath + // already attempts a 40-step mmap probe internally before falling + // back to A* node routing, so we don't need a second probe here. + // If the graph yields no plan, fall straight to the single-point + // fallback (cmangos addPoint(endPosition)). BG gate per + // MovementActions.cpp:705. + if (needsLongPath) { - StartTravelPlan(dest); - if (botAI->rpgInfo.HasActiveTravelPlan()) + if (sPlayerbotAIConfig.enableTravelNodes && !bot->InBattleground()) { - LOG_INFO("playerbots", "[MoveFar] {} nodetravel | dis={:.0f}", - bot->GetName(), dis); - EmitDebugMove("MoveFar", "travelplan", - dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); - return UpdateTravelPlan(); + StartTravelPlan(dest); + if (botAI->rpgInfo.HasActiveTravelPlan()) + { + LOG_INFO("playerbots", "[MoveFar] {} nodetravel | dis={:.0f}", + bot->GetName(), dis); + EmitDebugMove("MoveFar", "travelplan", + dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); + return UpdateTravelPlan(); + } } + + // Long-path fallback. Cross-map can't be served by a same-map + // straight-line spline — bail rather than splining toward + // unreachable coords. + if (bot->GetMapId() != dest.GetMapId()) + return false; + + Movement::PointsArray fallback; + fallback.emplace_back(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + fallback.emplace_back(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); + return DispatchPathPoints(dest, fallback, "spline-long"); } - // 40-step chained mmap probe. + // Short-path branch — 40-step chained mmap probe. WorldPosition botPos(bot); std::vector probe = botPos.getPathTo(dest, bot); @@ -245,14 +261,12 @@ bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest, lm.setPath(TravelPath(wpts)); } - // Item 5 — underwater fixup. Push waypoints submerged below the - // water surface up to the surface itself, unless the destination - // is itself underwater (e.g. fishing-loot quest GO, sunken ruins). + // Push submerged waypoints to the water surface unless dest is itself underwater. + if (Map* map = bot->GetMap()) { WorldPosition destWp = dest; if (!destWp.isUnderWater()) { - Map* map = bot->GetMap(); for (auto& pt : points) { WorldPosition wp(dest.GetMapId(), pt.x, pt.y, pt.z); @@ -307,11 +321,21 @@ bool NewRpgBaseAction::DispatchPathPoints(WorldPosition const& dest, break; } } - if (clipAt > 0 && clipAt + 1 < points.size()) + // Truncate after the first hostile waypoint. cmangos's + // analog at TravelNode.cpp:1158 sets endP=path.begin() if + // i=0 (mob right at start) and erases everything after, + // collapsing to a 1-point path. Our caller then sees + // points.size() < 2 and bails out — the bot just stops. + if (clipAt < points.size() && clipAt + 1 < points.size()) points.erase(points.begin() + clipAt + 1, points.end()); } } + // After clip, points may have collapsed to one element (mob right + // at start). Bail rather than dispatching a degenerate spline. + if (points.size() < 2) + return false; + G3D::Vector3 const& last = points.back(); float totalDist = 0.f; diff --git a/src/Ai/World/Rpg/NewRpgInfo.cpp b/src/Ai/World/Rpg/NewRpgInfo.cpp index 59eaca93f..481b5721c 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.cpp +++ b/src/Ai/World/Rpg/NewRpgInfo.cpp @@ -50,7 +50,7 @@ void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition fl void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId) { - startT = getMSTime(); + Reset(); OutdoorPvP pvp; pvp.capturePointSpawnId = capturePointSpawnId; data = pvp; diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index b524a69ca..0c6b37df7 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -873,14 +873,20 @@ std::vector WorldPosition::getPathFromPath(std::vector probe = destination.getPathFromPath({botPos}, bot, 40); - if (destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance)) + // Guard the degenerate case: getPathFromPath always returns at + // least {botPos}, and isPathTo(probe, spellDistance) succeeds + // when the bot is already within spellDistance of dest. + // Without this guard we'd emit a 1-element plan [botPos as + // PREPATH] and ExecuteTravelPlan would silently no-op every + // tick. + if (probe.size() >= 2 && destination.isPathTo(probe, sPlayerbotAIConfig.spellDistance)) { plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH); for (size_t i = 1; i < probe.size(); ++i)