diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index bd2f4476a..88920a050 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1028,12 +1028,6 @@ AiPlayerbot.RestrictedHealerDPSMaps = "33,34,36,43,47,48,70,90,109,129,209,229,2 # Default: 1 (enabled) AiPlayerbot.EnableNewRpgStrategy = 1 -# Use pre-computed travel node paths for long-distance movement (>300 yards). -# When enabled, bots use the travel node graph (A*, flight paths, transports) -# instead of repeated mmap hops. Experimental. -# Default: 0 (disabled) -AiPlayerbot.EnableTravelNodes = 0 - # Control probability weights for RPG status of bots. Takes effect only when the status meets its premise.​ # Sum of weights need not be 100. Set to 0 to disable the status. # diff --git a/src/Ai/Base/Actions/DebugAction.cpp b/src/Ai/Base/Actions/DebugAction.cpp index b79707077..edf1ef925 100644 --- a/src/Ai/Base/Actions/DebugAction.cpp +++ b/src/Ai/Base/Actions/DebugAction.cpp @@ -275,7 +275,7 @@ bool DebugAction::Execute(Event event) [] { TravelNodeMap::instance().removeNodes(); - TravelNodeMap::instance().LoadNodeStore(); + TravelNodeMap::instance().loadNodeStore(); }); t.detach(); @@ -297,7 +297,7 @@ bool DebugAction::Execute(Event event) // uint32 time = 60 * IN_MILLISECONDS; //not used, line marked for removal. - std::vector ppath = l.second->GetPath(); + std::vector ppath = l.second->getPath(); for (auto p : ppath) { diff --git a/src/Ai/Base/Actions/FollowActions.cpp b/src/Ai/Base/Actions/FollowActions.cpp index 1100ab460..48ce74ad3 100644 --- a/src/Ai/Base/Actions/FollowActions.cpp +++ b/src/Ai/Base/Actions/FollowActions.cpp @@ -19,8 +19,83 @@ #include "Transport.h" #include "Map.h" -// Transport helpers (GetTransportForPosTolerant, FindBoardingPointOnTransport, -// BoardTransport) are now on MovementAction — inherited by FollowAction. +namespace +{ + Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z) + { + if (!map || !ref) + return nullptr; + + std::array const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f }; + for (float const pz : probes) + { + if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref)) + return t; + } + + return nullptr; + } + + // Attempts to find a point on the leader's transport that is closer to the bot, + // by probing along the segment from master -> bot and returning the last point + // that is still detected as being on the expected transport. + bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref, + float masterX, float masterY, float masterZ, + float botX, float botY, float botZ, + float& outX, float& outY, float& outZ) + { + if (!map || !expectedTransport || !ref) + return false; + + uint32 const phaseMask = ref->GetPhaseMask(); + + // Ensure master is actually detected on that transport (tolerant). + if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport) + return false; + + // The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z. + float const probeZ = std::max(masterZ, botZ); + + // Adaptive step count: small platforms need tighter sampling. + float const dx2 = botX - masterX; + float const dy2 = botY - masterY; + float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2); + int32 const steps = std::clamp(static_cast(dist2d / 0.75f), 10, 28); + + float const dx = (botX - masterX) / static_cast(steps); + float const dy = (botY - masterY) / static_cast(steps); + + // Master must actually be on the expected transport for this to work. + if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport) + return false; + + float lastX = masterX; + float lastY = masterY; + bool found = false; + + for (int32 i = 1; i <= steps; ++i) + { + float const px = masterX + dx * i; + float const py = masterY + dy * i; + + Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ); + if (t != expectedTransport) + break; + + lastX = px; + lastY = py; + found = true; + } + + if (!found) + return false; + + outX = lastX; + outY = lastY; + outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat + return true; + } +} bool FollowAction::Execute(Event /*event*/) { diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 81d3c6da2..85855f60d 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -11,7 +11,6 @@ #include #include "Corpse.h" -#include "DBCStores.h" #include "Event.h" #include "FleeManager.h" #include "G3D/Vector3.h" @@ -20,9 +19,7 @@ #include "LootObjectStack.h" #include "Map.h" #include "MotionMaster.h" -#include "MoveSpline.h" #include "MoveSplineInitArgs.h" -#include "TravelNode.h" #include "MovementGenerator.h" #include "ObjectDefines.h" #include "ObjectGuid.h" @@ -39,7 +36,6 @@ #include "SpellInfo.h" #include "Stances.h" #include "Timer.h" -#include "Transport.h" #include "Unit.h" #include "Vehicle.h" #include "WaypointMovementGenerator.h" @@ -132,8 +128,10 @@ bool MovementAction::MoveToLOS(WorldObject* target, bool ranged) float z = target->GetPositionZ(); // Use standard PathGenerator to find a route. - PathResult path = GeneratePath(x, y, z, DEFAULT_PATH_ACCEPT_MASK, false); - if (!path.reachable) + PathGenerator path(bot); + path.CalculatePath(x, y, z, false); + PathType type = path.GetPathType(); + if (type != PATHFIND_NORMAL && type != PATHFIND_INCOMPLETE) return false; if (!ranged) @@ -142,9 +140,9 @@ bool MovementAction::MoveToLOS(WorldObject* target, bool ranged) float dist = FLT_MAX; PositionInfo dest; - if (!path.points.empty()) + if (!path.GetPath().empty()) { - for (auto& point : path.points) + for (auto& point : path.GetPath()) { if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT)) CreateWp(bot, point.x, point.y, point.z, 0.0, 2334); @@ -1734,19 +1732,6 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d // return current_z; // } -PathResult MovementAction::GeneratePath(float x, float y, float z, uint32 acceptMask, bool forceDestination) -{ - PathResult result; - PathGenerator gen(bot); - gen.CalculatePath(x, y, z, forceDestination); - result.pathType = gen.GetPathType(); - result.reachable = !(result.pathType & (~acceptMask)); - result.points = gen.GetPath(); - result.actualEnd = gen.GetActualEndPosition(); - result.end = gen.GetEndPosition(); - return result; -} - const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount, bool normal_only, float step) { @@ -2987,509 +2972,4 @@ bool MoveAwayFromPlayerWithDebuffAction::Execute(Event /*event*/) return false; } -bool MoveAwayFromPlayerWithDebuffAction::isPossible() -{ - return bot->CanFreeMove(); -} - -bool MovementAction::CheckSplineProgress(TravelPlan& state) -{ - if (!state.splineActive) - return false; - - if (bot->movespline->Finalized()) - { - G3D::Vector3 const& endPt = state.walkPoints.back(); - float distToEnd = bot->GetExactDist(endPt.x, endPt.y, endPt.z); - - if (distToEnd < 10.0f) - { - state.splineActive = false; - state.walkPoints.clear(); - return true; // Arrived - } - - // If we havent arrived to destination, but are done moving then something interrupted it. - // Need to restart. Reset state - state.splineActive = false; - return false; - } - - // Stuck detection - if (state.splineStartTime && - GetMSTimeDiffToNow(state.splineStartTime) > state.expectedDuration * 2 + (30 * IN_MILLISECONDS)) - { - G3D::Vector3 const& endPt = state.walkPoints.back(); - botAI->TeleportTo(WorldLocation(bot->GetMapId(), endPt.x, endPt.y, endPt.z)); - state.splineActive = false; - state.walkPoints.clear(); - return true; - } - - return false; // Still moving -} - -bool MovementAction::LaunchWalkSpline(TravelPlan& state) -{ - if (state.walkPoints.size() < 2) - { - state.walkPoints.clear(); - return false; - } - - // Trim to current position - G3D::Vector3 botPos(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); - float closestDist = FLT_MAX; - size_t closestIdx = 0; - for (size_t i = 0; i < state.walkPoints.size(); ++i) - { - float distance = (state.walkPoints[i] - botPos).squaredLength(); - if (distance < closestDist) - { - closestDist = distance; - closestIdx = i; - } - } - if (closestIdx > 0) - state.walkPoints.erase(state.walkPoints.begin(), state.walkPoints.begin() + closestIdx); - state.walkPoints.insert(state.walkPoints.begin(), botPos); - - if (state.walkPoints.size() < 2) - { - state.walkPoints.clear(); - return true; - } - - // Mount up - if (!bot->IsMounted() && !bot->IsInCombat() && bot->IsOutdoors() && bot->IsAlive()) - botAI->DoSpecificAction("check mount state", Event(), true); - - float totalDist = 0; - for (size_t i = 1; i < state.walkPoints.size(); ++i) - totalDist += (state.walkPoints[i] - state.walkPoints[i - 1]).length(); - - float speed = bot->GetSpeed(MOVE_RUN); - state.expectedDuration = static_cast((totalDist / speed) * IN_MILLISECONDS); - - bot->GetMotionMaster()->MoveSplinePath(&state.walkPoints, FORCED_MOVEMENT_RUN); - - state.splineStartTime = getMSTime(); - state.splineActive = true; - - LOG_DEBUG("playerbots", "[TravelPlan] Bot {} walk spline: {} points, {:.0f}y", - bot->GetName(), state.walkPoints.size(), totalDist); - - return false; // Walking -} - -bool MovementAction::MoveToSpline(TravelPlan& state, WorldPosition target) -{ - if (!IsMovingAllowed()) - return false; - - // Generate path - state.walkPoints.clear(); - PathResult path = GeneratePath(target.GetPositionX(), target.GetPositionY(), - target.GetPositionZ()); - for (auto const& pt : path.points) - state.walkPoints.push_back(G3D::Vector3(pt.x, pt.y, pt.z)); - - if (state.walkPoints.size() < 2) - { - state.walkPoints.clear(); - return false; - } - - // Launch spline movement - LaunchWalkSpline(state); - return true; -} - -bool MovementAction::GetTravelPlan(TravelPlan& plan, WorldPosition destination) -{ - WorldPosition botPos(bot->GetMapId(), bot->GetPositionX(), - bot->GetPositionY(), bot->GetPositionZ()); - - bool havePlan = sTravelNodeMap.GetFullPath(plan, botPos, - bot->GetZoneId(), bot->GetTeamId(), - destination); - - if (!havePlan) - TeleportFallback(plan, destination, "no plan"); - - return havePlan; -} - -bool MovementAction::ExecuteTravelPlan(TravelPlan& state) -{ - if (!state.IsActive()) - return false; - - if (bot->IsInFlight()) - return true; - - // Handle active spline - if (state.splineActive) - { - if (!CheckSplineProgress(state)) - { - if (state.splineActive) - return true; // Still moving - else - LaunchWalkSpline(state); // Interrupted, re-launch - } - return true; - } - - if (state.stepIdx >= state.steps.size()) - { - state.Reset(); - return true; - } - - const PathNodePoint& pt = state.steps[state.stepIdx]; - - switch (pt.type) - { - case PathNodeType::NODE_PREPATH: - case PathNodeType::NODE_PATH: - case PathNodeType::NODE_NODE: - { - // Batch consecutive walk points into one spline, capped at 100 points per tick. - static constexpr uint32 MAX_SPLINE_POINTS = 100; - state.walkPoints.clear(); - while (state.stepIdx < state.steps.size() && state.walkPoints.size() < MAX_SPLINE_POINTS) - { - const PathNodePoint& wp = state.steps[state.stepIdx]; - if (wp.type != PathNodeType::NODE_PREPATH && wp.type != PathNodeType::NODE_PATH && wp.type != PathNodeType::NODE_NODE) - break; - state.walkPoints.push_back(G3D::Vector3(wp.point.GetPositionX(), wp.point.GetPositionY(), wp.point.GetPositionZ())); - state.stepIdx++; - } - - if (state.walkPoints.empty()) - return true; - - // Already near end of batch? - G3D::Vector3 const& last = state.walkPoints.back(); - float dist = bot->GetExactDist(last.x, last.y, last.z); - if (dist < 10.0f) - { - state.walkPoints.clear(); - return true; - } - - // Too far from first point — teleport - if (state.walkPoints.size() >= 2) - { - G3D::Vector3 const& first = state.walkPoints.front(); - float distToFirst = bot->GetExactDist( first.x, first.y, first.z); - if (distToFirst > MAX_PATHFINDING_DISTANCE) - { - TeleportFallback(state, WorldPosition(bot->GetMapId(), last.x, last.y, last.z), "walk batch too far"); - state.walkPoints.clear(); - return true; - } - } - // Single point — use PathGenerator directly - if (state.walkPoints.size() < 2) - { - WorldPosition target(bot->GetMapId(), last.x, last.y, last.z); - MoveToSpline(state, target); - state.walkPoints.clear(); - return true; - } - LaunchWalkSpline(state); - return true; - } - - case PathNodeType::NODE_PORTAL: - { - // Pair: source (pointIdx) + dest (pointIdx+1) - if (state.stepIdx + 1 >= state.steps.size()) - { - state.Reset(); - return false; - } - - const PathNodePoint& src = state.steps[state.stepIdx]; - const PathNodePoint& dst = state.steps[state.stepIdx + 1]; - - // Already on destination map? - if (bot->GetMapId() == dst.point.GetMapId()) - { - state.stepIdx += 2; - return true; - } - // Walk to portal source - float dist = bot->GetExactDist(src.point.GetPositionX(), src.point.GetPositionY(), src.point.GetPositionZ()); - if (dist > INTERACTION_DISTANCE) - return MoveTo(src.point.GetMapId(), src.point.GetPositionX(), src.point.GetPositionY(), src.point.GetPositionZ()); - - // At portal, but havent teleported though - TeleportFallback(state, dst.point, "portal walk-through"); - state.stepIdx += 2; - return true; - } - - case PathNodeType::NODE_TRANSPORT: - { - if (state.stepIdx + 1 >= state.steps.size()) - { - state.Reset(); - return false; - } - - const PathNodePoint& board = state.steps[state.stepIdx]; - const PathNodePoint& arrive = state.steps[state.stepIdx + 1]; - // Arrived at destination? - if (bot->GetMapId() == arrive.point.GetMapId() && !bot->GetTransport()) - { - state.stepIdx += 2; - return true; - } - // On transport — wait - if (bot->GetTransport()) - { - if (bot->GetMapId() == arrive.point.GetMapId()) - { - bot->GetTransport()->RemovePassenger(bot); - bot->StopMovingOnCurrentPos(); - state.stepIdx += 2; - } - return true; - } - - // Walk to boarding point - float dist = bot->GetExactDist(board.point.GetPositionX(), board.point.GetPositionY(), board.point.GetPositionZ()); - if (dist > 60.0f) - return MoveTo(board.point.GetMapId(), board.point.GetPositionX(), board.point.GetPositionY(), board.point.GetPositionZ()); - - // Try to board - if (board.entry) - { - Map* map = bot->GetMap(); - if (map) - { - Transport* transport = - GetTransportForPosTolerant(map, bot, bot->GetPhaseMask(), board.point.GetPositionX(), - board.point.GetPositionY(), board.point.GetPositionZ()); - if (transport && transport->GetEntry() == board.entry) - { - BoardTransport(transport); - return true; - } - } - } - // Wait at boarding point - if (dist > INTERACTION_DISTANCE) - return MoveTo(board.point.GetMapId(), board.point.GetPositionX(), board.point.GetPositionY(), board.point.GetPositionZ()); - return true; - } - - case PathNodeType::NODE_FLIGHTPATH: - { - if (state.stepIdx + 1 >= state.steps.size()) - { - state.Reset(); - return false; - } - - const PathNodePoint& dep = state.steps[state.stepIdx]; - const PathNodePoint& arr = state.steps[state.stepIdx + 1]; - - if (bot->IsInFlight()) - return true; - - // Resolve taxi path - if (state.route.empty()) - { - uint32 fromTaxi = sObjectMgr->GetNearestTaxiNode(dep.point.GetPositionX(), dep.point.GetPositionY(), - dep.point.GetPositionZ(), dep.point.GetMapId(), bot->GetTeamId()); - uint32 toTaxi = sObjectMgr->GetNearestTaxiNode(arr.point.GetPositionX(), arr.point.GetPositionY(), - arr.point.GetPositionZ(), arr.point.GetMapId(), bot->GetTeamId()); - - if (fromTaxi && toTaxi && fromTaxi != toTaxi) - state.route = sTravelNodeMap.FindTaxiPath(fromTaxi, toTaxi); - - if (state.route.empty()) - { - state.stepIdx += 2; - return true; - } - } - - Creature* flightMaster = sTravelMgr.GetNearestFlightMaster(bot); - if (!flightMaster || !flightMaster->IsAlive()) - { - state.route.clear(); - state.stepIdx += 2; - return true; - } - - if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE) - return MoveTo(flightMaster, INTERACTION_DISTANCE); - - botAI->RemoveShapeshift(); - if (bot->IsMounted()) - bot->Dismount(); - - if (bot->ActivateTaxiPathTo(state.route, flightMaster, 0)) - LOG_DEBUG("playerbots","[TravelPlan] Bot {} taking flight ({} nodes)", bot->GetName(), state.route.size()); - - state.route.clear(); - state.stepIdx += 2; - return true; - } - - case PathNodeType::NODE_TELEPORT: - { - if (state.stepIdx + 1 >= state.steps.size()) - { - state.Reset(); - return false; - } - - const PathNodePoint& dst = state.steps[state.stepIdx + 1]; - TeleportFallback(state, dst.point, "teleport spell"); - state.stepIdx += 2; - return true; - } - - case PathNodeType::NODE_FLYING_MOUNT: - { - if (state.stepIdx + 1 >= state.steps.size()) - { - state.Reset(); - return false; - } - - const PathNodePoint& dst = state.steps[state.stepIdx + 1]; - TeleportFallback(state, dst.point, "flying mount not implemented"); - state.stepIdx += 2; - return true; - } - } - return false; -} - -void MovementAction::TeleportFallback(TravelPlan& state, WorldPosition target, char const* reason) -{ - LOG_INFO("playerbots", "[TravelPlan] Bot {} teleport fallback ({}): from map={} ({:.0f},{:.0f},{:.0f}) to map={} ({:.0f},{:.0f},{:.0f})", - bot->GetName(), reason, bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), target.GetMapId(), target.GetPositionX(), - target.GetPositionY(), target.GetPositionZ()); - - botAI->TeleportTo(target); -} - -Transport* MovementAction::GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z) -{ - if (!map || !ref) - return nullptr; - - std::array const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f }; - for (float const pz : probes) - { - if (Transport* transport = map->GetTransportForPos(phaseMask, x, y, pz, ref)) - return transport; - } - return nullptr; -} - -bool MovementAction::FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref, - float refX, float refY, float refZ, float botX, float botY, float botZ, float& outX, float& outY, float& outZ) -{ - if (!map || !expectedTransport || !ref) - return false; - - uint32 const phaseMask = ref->GetPhaseMask(); - if (GetTransportForPosTolerant(map, ref, phaseMask, refX, refY, refZ) - != expectedTransport) - return false; - - float const probeZ = std::max(refZ, botZ); - float const dx2 = botX - refX; - float const dy2 = botY - refY; - float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2); - int32 const steps = std::clamp(static_cast(dist2d / 0.75f), 10, 28); - float const dx = (botX - refX) / static_cast(steps); - float const dy = (botY - refY) / static_cast(steps); - - if (map->GetTransportForPos(phaseMask, refX, refY, probeZ, ref) != expectedTransport) - return false; - - float lastX = refX; - float lastY = refY; - bool found = false; - - for (int32 i = 1; i <= steps; ++i) - { - float const px = refX + dx * i; - float const py = refY + dy * i; - Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ); - if (t != expectedTransport) - break; - lastX = px; - lastY = py; - found = true; - } - - if (!found) - return false; - - outX = lastX; - outY = lastY; - outZ = refZ; - return true; -} - -bool MovementAction::BoardTransport(Transport* transport) -{ - if (!transport || transport->IsStaticTransport()) - return false; - - Map* map = bot->GetMap(); - if (!map) - return false; - - // Already on this transport - if (bot->GetTransport() == transport) - return true; - - // Check if bot is on the transport surface - float probeZ = std::max(bot->GetPositionZ(), transport->GetPositionZ()); - Transport* surface = GetTransportForPosTolerant(map, bot, bot->GetPhaseMask(), bot->GetPositionX(), - bot->GetPositionY(), probeZ); - - if (surface == transport) - { - transport->AddPassenger(bot, true); - bot->StopMovingOnCurrentPos(); - return true; - } - // Not on surface — move toward the transport - float destX = transport->GetPositionX(); - float destY = transport->GetPositionY(); - float destZ = transport->GetPositionZ(); - - // Try to find nearest boarding edge - float edgeX, edgeY, edgeZ; - if (FindBoardingPointOnTransport(map, transport, transport, transport->GetPositionX(), transport->GetPositionY(), - transport->GetPositionZ(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), edgeX, edgeY, edgeZ)) - { - destX = edgeX; - destY = edgeY; - destZ = edgeZ; - } - - // MovePoint without pathfinding (transport is a moving object) - if (MotionMaster* mm = bot->GetMotionMaster()) - { - if (bot->IsSitState()) - bot->SetStandState(UNIT_STAND_STATE_STAND); - - mm->MovePoint(0, destX, destY, destZ, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, false, false); - } - - return false; -} +bool MoveAwayFromPlayerWithDebuffAction::isPossible() { return bot->CanFreeMove(); } diff --git a/src/Ai/Base/Actions/MovementActions.h b/src/Ai/Base/Actions/MovementActions.h index a0d160610..377e8360a 100644 --- a/src/Ai/Base/Actions/MovementActions.h +++ b/src/Ai/Base/Actions/MovementActions.h @@ -10,7 +10,6 @@ #include "Action.h" #include "LastMovementValue.h" -#include "PathGenerator.h" #include "PlayerbotAIConfig.h" class Player; @@ -23,19 +22,6 @@ class Position; #define ANGLE_90_DEG M_PI_2 #define ANGLE_120_DEG (2.f * static_cast(M_PI) / 3.f) -// Default acceptable path types for GeneratePath -constexpr uint32 DEFAULT_PATH_ACCEPT_MASK = 0x01 /*PATHFIND_NORMAL*/ | 0x04 /*PATHFIND_INCOMPLETE*/; -uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY; - -struct PathResult -{ - Movement::PointsArray points; - G3D::Vector3 actualEnd; - G3D::Vector3 end; - PathType pathType; - bool reachable; -}; - class MovementAction : public Action { public: @@ -81,26 +67,6 @@ protected: bool FleePosition(Position pos, float radius, uint32 minInterval = 1000); bool CheckLastFlee(float curAngle, std::list& infoList); - PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = true); - - bool GetTravelPlan(TravelPlan& plan, WorldPosition destination); - bool ExecuteTravelPlan(TravelPlan& state); - - // Transport boarding helpers (shared by FollowAction and travel plan) - static Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, - uint32 phaseMask, float x, float y, float z); - static bool FindBoardingPointOnTransport(Map* map, Transport* transport, - WorldObject* ref, float refX, float refY, float refZ, - float botX, float botY, float botZ, - float& outX, float& outY, float& outZ); - bool BoardTransport(Transport* transport); - -private: - bool LaunchWalkSpline(TravelPlan& state); - bool CheckSplineProgress(TravelPlan& state); - bool MoveToSpline(TravelPlan& state, WorldPosition target); - void TeleportFallback(TravelPlan& state, WorldPosition target, char const* reason); - protected: struct CheckAngle { diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index f8797ab22..ca0ca2433 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -468,21 +468,26 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/) data.inFlight = true; return false; } - - if (bot->GetDistance(data.fromPos) > INTERACTION_DISTANCE) - return MoveFarTo(data.fromPos); - - Creature* flightMaster = ObjectAccessor::GetCreature(*bot, data.fromFlightMasterGuid); + Creature* flightMaster = ObjectAccessor::GetCreature(*bot, data.fromFlightMaster); if (!flightMaster || !flightMaster->IsAlive()) { botAI->rpgInfo.ChangeToIdle(); return true; } + if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE) + return MoveFarTo(flightMaster); - if (!TakeFlight(data.path, flightMaster)) + std::vector nodes = data.path; + + botAI->RemoveShapeshift(); + if (bot->IsMounted()) + bot->Dismount(); + + if (!bot->ActivateTaxiPathTo(nodes, flightMaster, 0)) { + LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(), + flightMaster->GetEntry(), nodes[0], nodes[nodes.size() - 1]); botAI->rpgInfo.ChangeToIdle(); - return true; } return true; } diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.h b/src/Ai/World/Rpg/Action/NewRpgAction.h index 5433284d1..83594204b 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgAction.h @@ -47,11 +47,11 @@ public: protected: // static NewRpgStatusTransitionProb transitionMat; - const int32 statusWanderNpcDuration = 5 * MINUTE * IN_MILLISECONDS; - const int32 statusWanderRandomDuration = 5 * MINUTE * IN_MILLISECONDS; - const int32 statusRestDuration = 30 * IN_MILLISECONDS; - const int32 statusDoQuestDuration = 30 * MINUTE * IN_MILLISECONDS; - const int32 statusOutDoorPvPDuration = HOUR * IN_MILLISECONDS; + const int32 statusWanderNpcDuration = 5 * MINUTE * IN_MILLISECONDS ; + const int32 statusWanderRandomDuration = 5 * MINUTE * IN_MILLISECONDS ; + const int32 statusRestDuration = 30 * IN_MILLISECONDS ; + const int32 statusDoQuestDuration = 30 * MINUTE * IN_MILLISECONDS ; + const int32 statusOutDoorPvPDuration = HOUR * IN_MILLISECONDS ; }; class NewRpgGoGrindAction : public NewRpgBaseAction diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index eb989dd42..092b11538 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -96,35 +96,25 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) botAI->rpgInfo.stuckAttempts = 0; const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId()); std::string zone_name = PlayerbotAI::GetLocalizedAreaName(entry); - LOG_DEBUG("playerbots","[New RPG] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", + LOG_DEBUG( + "playerbots", + "[New RPG] Teleport {} from ({},{},{},{}) to ({},{},{},{}) as it stuck when moving far - Zone: {} ({})", bot->GetName(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(), - dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.GetMapId(), bot->GetZoneId(), zone_name); - botAI->TeleportTo(dest); - return true; + dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.GetMapId(), bot->GetZoneId(), + zone_name); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); + return bot->TeleportTo(dest); } float dis = bot->GetExactDist(dest); - - // Long distance + travel nodes enabled: use the pre-computed node graph - // (A*, flight paths, transports) instead of repeated mmap hops. - if (dis > MAX_PATHFINDING_DISTANCE && sPlayerbotAIConfig.enableTravelNodes) - { - if (!botAI->rpgInfo.HasActiveTravelPlan()) - StartTravelPlan(dest); - - return UpdateTravelPlan(); - } - - // Crossed below the travel-node threshold — clear any leftover plan - if (botAI->rpgInfo.HasActiveTravelPlan()) - botAI->rpgInfo.ClearTravel(); - - // Short range: close enough for a single mmap call if (dis < pathFinderDis) { return MoveTo(dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), false, false, 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 @@ -136,18 +126,23 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) // subsequent ticks early-out via IsWaitingForLastMove and no // further PathGenerator calls fire until the bot arrives. { - PathResult path = GeneratePath(dest.GetPositionX(), dest.GetPositionY(), - dest.GetPositionZ(), RELAXED_PATH_ACCEPT_MASK); - if (path.reachable) + 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(path.actualEnd.x, path.actualEnd.y, path.actualEnd.z); + float endDistToDest = dest.GetExactDist(endPos.x, endPos.y, endPos.z); if (endDistToDest + 5.0f < disToDest) - return MoveTo(bot->GetMapId(), path.actualEnd.x, path.actualEnd.y, path.actualEnd.z, false, false, false, true); + { + return MoveTo(bot->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, true); + } } } @@ -171,14 +166,18 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) float dx = x + cos(angle) * sampleDis; float dy = y + sin(angle) * sampleDis; float dz = z + 0.5f; - PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK); + PathGenerator path(bot); + path.CalculatePath(dx, dy, dz); + PathType type = path.GetPathType(); + bool canReach = !(type & (~typeOk)); - if (path.reachable && fabs(delta) <= minDelta) + if (canReach && fabs(delta) <= minDelta) { found = true; - rx = path.actualEnd.x; - ry = path.actualEnd.y; - rz = path.actualEnd.z; + const G3D::Vector3& endPos = path.GetActualEndPosition(); + rx = endPos.x; + ry = endPos.y; + rz = endPos.z; minDelta = fabs(delta); } } @@ -189,31 +188,12 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) return false; } -void NewRpgBaseAction::StartTravelPlan(WorldPosition dest) -{ - TravelPlan& plan = botAI->rpgInfo.travelPlan; - GetTravelPlan(plan, dest); - - LOG_DEBUG("playerbots","[New RPG] Bot {} starting travel plan to ({:.0f},{:.0f},{:.0f}) map={}, {} points", - bot->GetName(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.GetMapId(), plan.steps.size()); -} - -bool NewRpgBaseAction::UpdateTravelPlan() -{ - TravelPlan& plan = botAI->rpgInfo.travelPlan; - - bool result = ExecuteTravelPlan(plan); - - if (!plan.IsActive()) - botAI->rpgInfo.ClearTravel(); - - return result; -} - bool NewRpgBaseAction::MoveWorldObjectTo(ObjectGuid guid, float distance) { if (IsWaitingForLastMove(MovementPriority::MOVEMENT_NORMAL)) + { return false; + } WorldObject* object = botAI->GetWorldObject(guid); if (!object) @@ -265,9 +245,13 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority, float dy = y + distance * sin(angle); float dz = z; - PathResult path = GeneratePath(dx, dy, dz, RELAXED_PATH_ACCEPT_MASK); + 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 (!path.reachable) + if (!canReach) continue; if (!map->CanReachPositionAndGetValidCoords(bot, dx, dy, dz)) @@ -292,28 +276,6 @@ bool NewRpgBaseAction::ForceToWait(uint32 duration, MovementPriority priority) return true; } - -bool NewRpgBaseAction::TakeFlight(std::vector const& taxiNodes, Creature* flightMaster) -{ - if (taxiNodes.size() < 2 || !flightMaster || !flightMaster->IsAlive()) - return false; - - botAI->RemoveShapeshift(); - if (bot->IsMounted()) - bot->Dismount(); - - if (!bot->ActivateTaxiPathTo(taxiNodes, flightMaster, 0)) - { - LOG_DEBUG("playerbots", "[New RPG] Bot {} flight ({} nodes, {} to {}) failed", - bot->GetName(), taxiNodes.size(), taxiNodes.front(), taxiNodes.back()); - return false; - } - - LOG_DEBUG("playerbots", "[New RPG] Bot {} taking flight ({} nodes, {} to {})", - bot->GetName(), taxiNodes.size(), taxiNodes.front(), taxiNodes.back()); - return true; -} - /// @TODO: Fix redundant code /// Quest related method refer to TalkToQuestGiverAction.h bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid) @@ -1016,10 +978,6 @@ WorldPosition NewRpgBaseAction::SelectRandomGrindPos(Player* bot) uint32 idx = urand(0, lo_prepared_locs.size() - 1); dest = lo_prepared_locs[idx]; } - - if (!dest.IsValid()) - return dest; - LOG_DEBUG("playerbots", "[New RPG] Bot {} select random grind pos Map:{} X:{} Y:{} Z:{} ({}+{} available in {})", bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), hi_prepared_locs.size(), lo_prepared_locs.size() - hi_prepared_locs.size(), locs.size()); @@ -1063,10 +1021,6 @@ WorldPosition NewRpgBaseAction::SelectRandomCampPos(Player* bot) uint32 idx = urand(0, prepared_locs.size() - 1); dest = prepared_locs[idx]; } - - if (!dest.IsValid()) - return dest; - LOG_DEBUG("playerbots", "[New RPG] Bot {} select random inn keeper pos Map:{} X:{} Y:{} Z:{} ({} available in {})", bot->GetName(), dest.GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), prepared_locs.size(), locs.size()); @@ -1104,6 +1058,7 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector candidateSta probSum += sPlayerbotAIConfig.RpgStatusProbWeight[status]; } } + // Safety check. Default to "rest" if all RPG weights = 0 if (availableStatus.empty() || probSum == 0) { botAI->rpgInfo.ChangeToRest(); @@ -1184,11 +1139,11 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector candidateSta } case RPG_TRAVEL_FLIGHT: { - ObjectGuid flightMasterGuid; + ObjectGuid flightMaster; std::vector path; - if (SelectRandomFlightTaxiNode(flightMasterGuid, path)) + if (SelectRandomFlightTaxiNode(flightMaster, path)) { - botAI->rpgInfo.ChangeToTravelFlight(flightMasterGuid, path); + botAI->rpgInfo.ChangeToTravelFlight(flightMaster, path); return true; } return false; @@ -1285,5 +1240,3 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status) } return false; } - - diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h index 245d78ece..eaba72446 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h @@ -33,7 +33,6 @@ protected: bool MoveWorldObjectTo(ObjectGuid guid, float distance = INTERACTION_DISTANCE); bool MoveRandomNear(float moveStep = 50.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, WorldObject* center = nullptr); bool ForceToWait(uint32 duration, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); - bool TakeFlight(std::vector const& taxiNodes, Creature* flightMaster); /* QUEST RELATED CHECK */ ObjectGuid ChooseNpcOrGameObjectToInteract(bool questgiverOnly = false, float distanceLimit = 0.0f); @@ -70,10 +69,6 @@ protected: // the teleport fires, but long enough that a genuine long // walk that is slowly making progress never triggers it. const uint32 stuckTime = 90 * 1000; - -private: - void StartTravelPlan(WorldPosition dest); - bool UpdateTravelPlan(); }; #endif diff --git a/src/Ai/World/Rpg/NewRpgInfo.cpp b/src/Ai/World/Rpg/NewRpgInfo.cpp index 83c780647..4935503fc 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.cpp +++ b/src/Ai/World/Rpg/NewRpgInfo.cpp @@ -4,43 +4,33 @@ #include "Timer.h" -void NewRpgInfo::ClearTravel() -{ - travelPlan.Reset(); -} - void NewRpgInfo::ChangeToGoGrind(WorldPosition pos) { startT = getMSTime(); - ClearTravel(); data = GoGrind{pos}; } void NewRpgInfo::ChangeToGoCamp(WorldPosition pos) { startT = getMSTime(); - ClearTravel(); data = GoCamp{pos}; } void NewRpgInfo::ChangeToWanderNpc() { startT = getMSTime(); - ClearTravel(); data = WanderNpc{}; } void NewRpgInfo::ChangeToWanderRandom() { startT = getMSTime(); - ClearTravel(); data = WanderRandom{}; } void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest) { startT = getMSTime(); - ClearTravel(); DoQuest do_quest; do_quest.questId = questId; do_quest.quest = quest; @@ -50,7 +40,6 @@ void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest) void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector path) { startT = getMSTime(); - ClearTravel(); TravelFlight flight; flight.fromFlightMaster = fromFlightMaster; flight.path = std::move(path); @@ -69,14 +58,12 @@ void NewRpgInfo::ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId) void NewRpgInfo::ChangeToRest() { startT = getMSTime(); - ClearTravel(); data = Rest{}; } void NewRpgInfo::ChangeToIdle() { startT = getMSTime(); - ClearTravel(); data = Idle{}; } @@ -89,7 +76,6 @@ void NewRpgInfo::Reset() { data = Idle{}; startT = getMSTime(); - ClearTravel(); } void NewRpgInfo::SetMoveFarTo(WorldPosition pos) diff --git a/src/Ai/World/Rpg/NewRpgInfo.h b/src/Ai/World/Rpg/NewRpgInfo.h index 4a9188489..9e6abdda4 100644 --- a/src/Ai/World/Rpg/NewRpgInfo.h +++ b/src/Ai/World/Rpg/NewRpgInfo.h @@ -74,9 +74,7 @@ struct NewRpgInfo uint32 stuckTs{0}; uint32 stuckAttempts{0}; WorldPosition moveFarPos; - TravelPlan travelPlan; - bool HasActiveTravelPlan() const { return travelPlan.IsActive(); } - void ClearTravel(); + // END MOVE_FAR using RpgData = std::variant< Idle, diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 288ee0180..e9de581de 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -735,21 +735,6 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr } } -void PlayerbotAI::TeleportTo(WorldLocation loc, bool resetAI) -{ - if (!bot || bot->IsBeingTeleported() || !bot->IsInWorld()) - return; - - bot->GetMotionMaster()->Clear(); - if (resetAI) - Reset(true); - else - InterruptSpell(); - bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); - bot->TeleportTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), 0); - bot->SendMovementFlagUpdate(); -} - void PlayerbotAI::HandleTeleportAck() { if (!bot || !bot->GetSession()) diff --git a/src/Bot/PlayerbotAI.h b/src/Bot/PlayerbotAI.h index 1aafcdefc..cfa27ed4e 100644 --- a/src/Bot/PlayerbotAI.h +++ b/src/Bot/PlayerbotAI.h @@ -396,7 +396,6 @@ public: void HandleMasterIncomingPacket(WorldPacket const& packet); void HandleMasterOutgoingPacket(WorldPacket const& packet); void HandleTeleportAck(); - void TeleportTo(WorldLocation loc, bool resetAI = false); void ChangeEngine(BotState type); void ChangeEngineOnCombat(); void ChangeEngineOnNonCombat(); diff --git a/src/Bot/RandomPlayerbotMgr.cpp b/src/Bot/RandomPlayerbotMgr.cpp index 04297822c..ee06c58f8 100644 --- a/src/Bot/RandomPlayerbotMgr.cpp +++ b/src/Bot/RandomPlayerbotMgr.cpp @@ -1696,7 +1696,14 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& break; } - botAI->TeleportTo(WorldLocation(loc.GetMapId(), x, y, z, 0), true); + bot->GetMotionMaster()->Clear(); + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->Reset(true); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); + bot->TeleportTo(loc.GetMapId(), x, y, z, 0); + bot->SendMovementFlagUpdate(); + if (pmo) pmo->finish(); diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index 5d0e014f7..1868bc2e3 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -687,6 +687,93 @@ std::vector WorldPosition::frommGridCoord(mGridCoord GridCoord) return retVec; } +// TODO: Cleanup — make this actually work. +void WorldPosition::loadMapAndVMap(uint32 mapId, uint8 x, uint8 y) +{ + std::string const fileName = "load_map_grid.csv"; +/* + if (isOverworld() && false || false) + { + if (!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(mapId, x, y)) + if (sPlayerbotAIConfig.hasLog(fileName)) + { + std::ostringstream out; + out << sPlayerbotAIConfig.GetTimestampStr(); + out << "+00,\"mmap\", " << x << "," << y << "," << (TravelMgr::instance().isBadMmap(mapId, x, y) ? "0" : "1") + << ","; + printWKT(fromGridCoord(GridCoord(x, y)), out, 1, true); + sPlayerbotAIConfig.log(fileName, out.str().c_str()); + } + } + else + { + // This needs to be disabled or maps will not load. + // Needs more testing to check for impact on movement. + if (false) + if (!TravelMgr::instance().isBadVmap(mapId, x, y)) + { + // load VMAPs for current map/grid... + const MapEntry* i_mapEntry = sMapStore.LookupEntry(mapId); + //const char* mapName = i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0"; //not used, (usage are commented out below), line marked for removal. + + int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap( + (sWorld->GetDataPath() + "vmaps").c_str(), mapId, x, y); + switch (vmapLoadResult) + { + case VMAP::VMAP_LOAD_RESULT_OK: + // LOG_ERROR("playerbots", "VMAP loaded name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", + // mapName, mapId, x, y, x, y); + break; + case VMAP::VMAP_LOAD_RESULT_ERROR: + // LOG_ERROR("playerbots", "Could not load VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, + // y:{})", mapName, mapId, x, y, x, y); + TravelMgr::instance().addBadVmap(mapId, x, y); + break; + case VMAP::VMAP_LOAD_RESULT_IGNORED: + TravelMgr::instance().addBadVmap(mapId, x, y); + // LOG_INFO("playerbots", "Ignored VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", + // mapName, mapId, x, y, x, y); + break; + } + + if (sPlayerbotAIConfig.hasLog(fileName)) + { + std::ostringstream out; + out << sPlayerbotAIConfig.GetTimestampStr(); + out << "+00,\"vmap\", " << x << "," << y << ", " << (TravelMgr::instance().isBadVmap(mapId, x, y) ? "0" : "1") + << ","; + printWKT(frommGridCoord(mGridCoord(x, y)), out, 1, true); + sPlayerbotAIConfig.log(fileName, out.str().c_str()); + } + } +*/ + if (!TravelMgr::instance().isBadMmap(mapId, x, y)) + { + // load navmesh + Map* map = getMap(); + if (map && map->GetMapCollisionData().LoadMMapTile(x, y) == MMAP::MMAP_LOAD_RESULT_ERROR) + TravelMgr::instance().addBadMmap(mapId, x, y); + + if (sPlayerbotAIConfig.hasLog(fileName)) + { + std::ostringstream out; + out << sPlayerbotAIConfig.GetTimestampStr(); + out << "+00,\"mmap\", " << x << "," << y << "," << (TravelMgr::instance().isBadMmap(mapId, x, y) ? "0" : "1") + << ","; + printWKT(fromGridCoord(GridCoord(x, y)), out, 1, true); + sPlayerbotAIConfig.log(fileName, out.str().c_str()); + } + } +} + +void WorldPosition::loadMapAndVMaps(WorldPosition secondPos) +{ + for (auto& grid : getmGridCoords(secondPos)) + { + loadMapAndVMap(GetMapId(), grid.first, grid.second); + } +} + std::vector WorldPosition::fromPointsArray(std::vector path) { std::vector retVec; @@ -699,42 +786,34 @@ std::vector WorldPosition::fromPointsArray(std::vector WorldPosition::getPathStepFrom(WorldPosition startPos, Unit* bot) { - Unit* pathUnit = bot; - Creature* tempCreature = nullptr; + if (!bot) + return {}; - if (!pathUnit) - { - // Create a temporary creature for PathGenerator (same entry as DebugAction "show node") - Map* map = sMapMgr->FindBaseMap(startPos.GetMapId()); - if (!map) - return {}; + // Load mmaps and vmaps between the two points. + loadMapAndVMaps(startPos); - tempCreature = new Creature(); - if (!tempCreature->Create(map->GenerateLowGuid(), map, - PHASEMASK_NORMAL, 1 /*entry*/, 0, - startPos.GetPositionX(), startPos.GetPositionY(), - startPos.GetPositionZ(), 0)) - { - delete tempCreature; - return {}; - } - pathUnit = tempCreature; - - // Ensure grids are created at both endpoints so mmap tiles are available. - // EnsureGridCreated loads terrain + vmaps + mmaps but NOT objects, - // which is all PathGenerator needs. - map->EnsureGridCreated(Acore::ComputeGridCoord(startPos.GetPositionX(), startPos.GetPositionY())); - map->EnsureGridCreated(Acore::ComputeGridCoord(GetPositionX(), GetPositionY())); - } - - PathGenerator path(pathUnit); - path.CalculatePath(GetPositionX(), GetPositionY(), GetPositionZ()); + PathGenerator path(bot); + path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ()); Movement::PointsArray points = path.GetPath(); PathType type = path.GetPathType(); - if (tempCreature) - delete tempCreature; + if (sPlayerbotAIConfig.hasLog("pathfind_attempt_point.csv")) + { + std::ostringstream out; + out << std::fixed << std::setprecision(1); + printWKT({startPos, *this}, out); + sPlayerbotAIConfig.log("pathfind_attempt_point.csv", out.str().c_str()); + } + + if (sPlayerbotAIConfig.hasLog("pathfind_attempt.csv") && (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL)) + { + std::ostringstream out; + out << sPlayerbotAIConfig.GetTimestampStr() << "+00,"; + out << std::fixed << std::setprecision(1) << type << ","; + printWKT(fromPointsArray(points), out, 1); + sPlayerbotAIConfig.log("pathfind_attempt.csv", out.str().c_str()); + } if (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL) return fromPointsArray(points); @@ -1000,14 +1079,6 @@ GuidPosition::GuidPosition(GameObjectData const& goData) loadedFromDB = true; } -TravelDestination::~TravelDestination() -{ - for (WorldPosition* point : points) - delete point; - - points.clear(); -} - std::vector TravelDestination::getPoints(bool ignoreFull) { if (ignoreFull) @@ -2314,7 +2385,9 @@ void TravelMgr::LoadQuestTravelTable() sPlayerbotAIConfig.openLog("unload_grid.csv", "w"); sPlayerbotAIConfig.openLog("unload_obj.csv", "w"); - // Node loading/generation is handled by TravelNodeMap::Init() called from TravelMgr::Init(). + TravelNodeMap::instance().loadNodeStore(); + + TravelNodeMap::instance().generateAll(); /* bool fullNavPointReload = false; @@ -2705,7 +2778,7 @@ void TravelMgr::LoadQuestTravelTable() //if (preloadUnlinkedPaths && !startNode->hasLinkTo(endNode) && startNode->isUselessLink(endNode)) // continue; - startNode->BuildPath(endNode, nullptr, false); + startNode->buildPath(endNode, nullptr, false); //if (startNode->hasLinkTo(endNode) && !startNode->getPathTo(endNode)->getComplete()) //startNode->removeLinkTo(endNode); @@ -2829,7 +2902,7 @@ void TravelMgr::LoadQuestTravelTable() TravelNodePath nodePath = *path.second; - std::vector pPath = nodePath.GetPath(); + std::vector pPath = nodePath.getPath(); std::reverse(pPath.begin(), pPath.end()); nodePath.setPath(pPath); @@ -4292,7 +4365,8 @@ void TravelMgr::Init() PrepareZone2LevelBracket(); PrepareDestinationCache(); } - sTravelNodeMap.Init(); + sTravelNodeMap.InitTaxiGraph(); + LOG_INFO("playerbots", "Playerbots Taxi graph and destination cache built."); } Creature* TravelMgr::GetNearestFlightMaster(Player* bot) @@ -4444,34 +4518,6 @@ std::vector TravelMgr::GetCityLocations(Player* bot) return fallbackLocations; } -bool TravelMgr::SelectAuctioneerByMap(Player* bot, NpcLocation& outAuctioneer) -{ - uint16 botMapId = bot->GetMapId(); - auto const& cache = (bot->GetTeamId() == TEAM_HORDE) ? hordeAuctioneerCache : allianceAuctioneerCache; - - auto mapIt = cache.find(botMapId); - if (mapIt == cache.end() || mapIt->second.empty()) - return false; - - // Collect all areas on this map that have auctioneers - std::vector areaIds; - areaIds.reserve(mapIt->second.size()); - for (auto const& [areaId, npcs] : mapIt->second) - { - if (!npcs.empty()) - areaIds.push_back(areaId); - } - - if (areaIds.empty()) - return false; - - // Pick a random area, then a random auctioneer in that area - uint32 selectedArea = areaIds[urand(0, areaIds.size() - 1)]; - auto const& auctioneers = mapIt->second.at(selectedArea); - outAuctioneer = auctioneers[urand(0, auctioneers.size() - 1)]; - return true; -} - void TravelMgr::PrepareZone2LevelBracket() { // Classic WoW - Low - level zones @@ -4558,7 +4604,6 @@ void TravelMgr::PrepareDestinationCache() uint32 flightMastersCount = 0; uint32 innkeepersCount = 0; uint32 bankerCount = 0; - uint32 auctioneerCount = 0; LOG_INFO("playerbots", "Preparing destination caches for {} levels...", maxLevel); // Temporary map to group creatures by entry and area @@ -4683,7 +4728,7 @@ void TravelMgr::PrepareDestinationCache() creatureTemplate->Entry != 30606 && creatureTemplate->Entry != 30608 && creatureTemplate->Entry != 29282) { - NpcLocation bLoc; + BankerLocation bLoc; bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI); bLoc.entry = templateEntry; uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2; @@ -4706,31 +4751,6 @@ void TravelMgr::PrepareDestinationCache() } bankerCount++; } - // === AUCTIONEERS === - else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_AUCTIONEER) - { - FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(creatureTemplate->faction); - if (!factionEntry) - continue; - - bool forHorde = !(factionEntry->hostileMask & 4); - bool forAlliance = !(factionEntry->hostileMask & 2); - - if (!forHorde && !forAlliance) - continue; - - NpcLocation aLoc; - aLoc.loc = WorldLocation(mapId, x + cos(orient) * 3.0f, y + sin(orient) * 3.0f, z + 0.5f, orient + M_PI); - aLoc.entry = templateEntry; - - if (forHorde) - hordeAuctioneerCache[mapId][areaId].push_back(aLoc); - - if (forAlliance) - allianceAuctioneerCache[mapId][areaId].push_back(aLoc); - - auctioneerCount++; - } } // Process temporary caches @@ -4740,29 +4760,13 @@ void TravelMgr::PrepareDestinationCache() { CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureDataList[0].id1); uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2; - - float totalX = 0.0f; - float totalY = 0.0f; - float totalZ = 0.0f; - for (CreatureData const& creatureData : creatureDataList) - { - totalX += creatureData.posX; - totalY += creatureData.posY; - totalZ += creatureData.posZ; - } - - float avgX = totalX / creatureDataList.size(); - float avgY = totalY / creatureDataList.size(); - float avgZ = totalZ / creatureDataList.size(); - uint32 mapId = std::get<0>(gridTuple); - for (int32 l = (int32)level - (int32)sPlayerbotAIConfig.randomBotTeleLowerLevel; l <= (int32)level + (int32)sPlayerbotAIConfig.randomBotTeleHigherLevel; l++) { if (l < 1 || l > maxLevel) continue; - locsPerLevelCache[(uint8)l].push_back(WorldLocation(mapId, avgX, avgY, avgZ, 0.0f)); + locsPerLevelCache[(uint8)l].push_back(WorldLocation(std::get<0>(gridTuple))); } } } @@ -4808,5 +4812,5 @@ void TravelMgr::PrepareDestinationCache() break; } } - LOG_INFO("playerbots", ">> {} flight masters, {} innkeepers, {} bankers, {} auctioneers collected.", flightMastersCount, innkeepersCount, bankerCount, auctioneerCount); + LOG_INFO("playerbots", ">> {} flight masters and {} innkeepers and {} banker locations for level collected.", flightMastersCount, innkeepersCount, bankerCount); } diff --git a/src/Mgr/Travel/TravelMgr.h b/src/Mgr/Travel/TravelMgr.h index 98d86c36a..f300ae636 100644 --- a/src/Mgr/Travel/TravelMgr.h +++ b/src/Mgr/Travel/TravelMgr.h @@ -268,6 +268,12 @@ public: std::vector getmGridCoords(WorldPosition secondPos); std::vector frommGridCoord(mGridCoord GridCoord); + void loadMapAndVMap(uint32 mapId, uint8 x, uint8 y); + + void loadMapAndVMap() { loadMapAndVMap(GetMapId(), getmGridCoord().first, getmGridCoord().second); } + + void loadMapAndVMaps(WorldPosition secondPos); + // Display functions WorldPosition getDisplayLocation(); float getDisplayX() { return getDisplayLocation().GetPositionY() * -1.0; } @@ -501,15 +507,9 @@ public: radiusMin = radiusMin1; radiusMax = radiusMax1; } - virtual ~TravelDestination(); + virtual ~TravelDestination() = default; - void addPoint(WorldPosition* pos) - { - if (!pos) - return; - - points.push_back(new WorldPosition(*pos)); - } + void addPoint(WorldPosition* pos) { points.push_back(pos); } void setExpireDelay(uint32 delay) { expireDelay = delay; } @@ -673,7 +673,7 @@ public: bool isActive(Player* bot) override; virtual CreatureTemplate const* GetCreatureTemplate(); std::string const getName() override { return "RpgTravelDestination"; } - int32 getEntry() override { return entry; } + int32 getEntry() override { return 0; } std::string const getTitle() override; protected: @@ -846,12 +846,6 @@ protected: class TravelMgr { public: - struct NpcLocation - { - WorldLocation loc; - uint32 entry; - }; - static TravelMgr& instance() { static TravelMgr instance; @@ -870,7 +864,6 @@ public: const std::vector GetTeleportLocations(Player* bot); const std::vector GetTravelHubs(Player* bot); std::vector GetCityLocations(Player* bot); - bool SelectAuctioneerByMap(Player* bot, NpcLocation& outAuctioneer); const std::vector& GetLocsPerLevelCache(uint8 level) { return locsPerLevelCache[level]; } template @@ -975,14 +968,18 @@ private: bool InsideBracket(uint32 val) const { return val >= low && val <= high; } }; + struct BankerLocation + { + WorldLocation loc; + uint32 entry; + }; + // Navigation caches std::map allianceFlightMasterCache; std::map hordeFlightMasterCache; std::map> allianceHubsPerLevelCache; std::map> hordeHubsPerLevelCache; - std::map> bankerLocsPerLevelCache; - std::unordered_map>> hordeAuctioneerCache; - std::unordered_map>> allianceAuctioneerCache; + std::map> bankerLocsPerLevelCache; std::unordered_map bankerEntryToLocation; std::map> locsPerLevelCache; std::unordered_map> creatureSpawnsByTemplate; diff --git a/src/Mgr/Travel/TravelNode.cpp b/src/Mgr/Travel/TravelNode.cpp index d4bdd21ee..3b4996e97 100644 --- a/src/Mgr/Travel/TravelNode.cpp +++ b/src/Mgr/Travel/TravelNode.cpp @@ -5,14 +5,11 @@ #include "TravelNode.h" -#include #include -#include #include #include #include "BudgetValues.h" -#include "MapMgr.h" #include "PathGenerator.h" #include "Playerbots.h" #include "RaceMgr.h" @@ -161,18 +158,8 @@ float TravelNodePath::getCost(Player* bot, uint32 cGold) if (factionAnnoyance > 0) modifier += 0.3 * factionAnnoyance; // For each level the whole path takes 10% longer. } - if (getPathType() == TravelNodePathType::flyingMount) - { - if (!bot->IsAlive() || bot->GetLevel() < 70 || !bot->CanFly()) - return -1; - - float flySpeed = bot->GetSpeed(MOVE_FLIGHT); - if (flySpeed < 1.0f) - flySpeed = 20.0f; // 280% base flying speed fallback - return (distance / flySpeed) * modifier; - } } - else if (getPathType() == TravelNodePathType::flightPath || getPathType() == TravelNodePathType::flyingMount) + else if (getPathType() == TravelNodePathType::flightPath) return -1; if (getPathType() != TravelNodePathType::walk) @@ -200,9 +187,9 @@ uint32 TravelNodePath::getPrice() } // Creates or appends the path from one node to another. Returns if the path. -TravelNodePath* TravelNode::BuildPath(TravelNode* endNode, Unit* bot, bool postProcess) +TravelNodePath* TravelNode::buildPath(TravelNode* endNode, Unit* bot, bool postProcess) { - if (GetMapId() != endNode->GetMapId()) + if (getMapId() != endNode->getMapId()) return nullptr; TravelNodePath* returnNodePath; @@ -215,7 +202,7 @@ TravelNodePath* TravelNode::BuildPath(TravelNode* endNode, Unit* bot, bool postP if (returnNodePath->getComplete()) // Path is already complete. Return it. return returnNodePath; - std::vector path = returnNodePath->GetPath(); + std::vector path = returnNodePath->getPath(); if (path.empty()) path = {*getPosition()}; // Start the path from the current Node. @@ -232,7 +219,7 @@ TravelNodePath* TravelNode::BuildPath(TravelNode* endNode, Unit* bot, bool postP if (backNodePath.getPathType() == TravelNodePathType::walk) { - std::vector bPath = backNodePath.GetPath(); + std::vector bPath = backNodePath.getPath(); if (!backNodePath.getComplete()) // Build it if it's not already complete. { @@ -412,7 +399,7 @@ bool TravelNode::isUselessLink(TravelNode* farNode) } else { - TravelNodeRoute route = TravelNodeMap::instance().GetNodeRoute(nearNode, farNode, nullptr); + TravelNodeRoute route = TravelNodeMap::instance().getRoute(nearNode, farNode, nullptr); if (route.isEmpty()) continue; @@ -444,8 +431,6 @@ bool TravelNode::cropUselessLinks() TravelNode* farNode = firstLink.first; if (this->hasLinkTo(farNode) && this->isUselessLink(farNode)) { - LOG_DEBUG("playerbots", "[CropLink] '{}' → '{}' (dist {:.0f}) — redundant, removing", - getName(), farNode->getName(), getPathTo(farNode)->getDistance()); this->removeLinkTo(farNode); hasRemoved = true; @@ -463,8 +448,6 @@ bool TravelNode::cropUselessLinks() if (farNode->hasLinkTo(this) && farNode->isUselessLink(this)) { - LOG_DEBUG("playerbots", "[CropLink] '{}' → '{}' (dist {:.0f}) — redundant, removing", - farNode->getName(), getName(), farNode->getPathTo(this)->getDistance()); farNode->removeLinkTo(this); hasRemoved = true; @@ -515,7 +498,7 @@ bool TravelNode::cropUselessLinks() } else { - TravelNodeRoute route = TravelNodeMap::instance().GetNodeRoute(firstNode, secondNode, nullptr); + TravelNodeRoute route = TravelNodeMap::instance().getRoute(firstNode, secondNode, false); if (route.isEmpty()) continue; @@ -563,7 +546,7 @@ bool TravelNode::cropUselessLinks() } else { - TravelNodeRoute route = TravelNodeMap::instance().GetNodeRoute(firstNode, secondNode, nullptr); + TravelNodeRoute route = TravelNodeMap::instance().getRoute(firstNode, secondNode, false); if (route.isEmpty()) continue; @@ -647,7 +630,7 @@ void TravelNode::print([[maybe_unused]] bool printFailed) if (!hasLinkTo(endNode) && urand(0, 20) && !printFailed) continue; - ppath = path->GetPath(); + ppath = path->getPath(); if (ppath.size() < 2 && hasLinkTo(endNode)) { @@ -659,11 +642,19 @@ void TravelNode::print([[maybe_unused]] bool printFailed) { std::ostringstream out; - uint32 pathType = static_cast(path->getPathType()); + uint32 pathType = 1; if (!hasLinkTo(endNode)) pathType = 0; + else if (path->getPathType() == TravelNodePathType::transport) + pathType = 2; + else if (path->getPathType() == TravelNodePathType::portal && getMapId() == endNode->getMapId()) + pathType = 3; + else if (path->getPathType() == TravelNodePathType::portal) + pathType = 4; + else if (path->getPathType() == TravelNodePathType::flightPath) + pathType = 5; else if (!path->getComplete()) - pathType = 0; + pathType = 6; out << pathType << ","; out << std::fixed << std::setprecision(2); @@ -684,7 +675,7 @@ void TravelNode::print([[maybe_unused]] bool printFailed) // Attempts to move ahead of the path. bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist) { - if (GetPath().empty()) + if (getPath().empty()) return false; float maxDistSq = maxDist * maxDist; @@ -695,7 +686,7 @@ bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist) for (auto& p : fullPath) // cycle over the full path { - // if (p.point.GetMapId() != startPos.GetMapId()) + // if (p.point.getMapId() != startPos.getMapId()) // continue; if (p.point.GetMapId() == startPos.GetMapId()) @@ -714,7 +705,7 @@ bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist) newPath.clear(); } - if (p.type != PathNodeType::NODE_PREPATH) // Only look at the part after the first node and in the same map. + if (p.type != NODE_PREPATH) // Only look at the part after the first node and in the same map. { if (!firstNode) firstNode = p.point; @@ -766,6 +757,152 @@ bool TravelPath::makeShortCut(WorldPosition startPos, float maxDist) return true; } +bool TravelPath::shouldMoveToNextPoint(WorldPosition startPos, std::vector::iterator beg, + std::vector::iterator ed, std::vector::iterator p, + float& moveDist, float maxDist) +{ + if (p == ed) // We are the end. Stop now. + return false; + + auto nextP = std::next(p); + + // We are moving to a area trigger node and want to move to the next teleport node. + if (p->type == NODE_PORTAL && nextP->type == NODE_PORTAL && p->entry == nextP->entry) + { + return false; // Move to teleport and activate area trigger. + } + + // We are using a hearthstone. + if (p->type == NODE_TELEPORT && nextP->type == NODE_TELEPORT && p->entry == nextP->entry) + { + return false; // Move to teleport and activate area trigger. + } + + // We are almost at a transport node. Move to the node before this. + if (nextP->type == NODE_TRANSPORT && nextP->entry && moveDist > INTERACTION_DISTANCE) + { + return false; + } + + // We are moving to a transport node. + if (p->type == NODE_TRANSPORT && p->entry) + { + if (nextP->type != NODE_TRANSPORT && p != beg && + std::prev(p)->type != NODE_TRANSPORT) // We are not using the transport. Skip it. + return true; + + return false; // Teleport to exit of transport. + } + + // We are moving to a flightpath and want to fly. + if (p->type == NODE_FLIGHTPATH && nextP->type == NODE_FLIGHTPATH) + { + return false; + } + + float nextMove = p->point.distance(nextP->point); + + if (p->point.GetMapId() != startPos.GetMapId() || + ((moveDist + nextMove > maxDist || startPos.distance(nextP->point) > maxDist) && moveDist > 0)) + { + return false; + } + + moveDist += nextMove; + + return true; +} + +// Next position to move to +WorldPosition TravelPath::getNextPoint(WorldPosition startPos, float maxDist, TravelNodePathType& pathType, + uint32& entry) +{ + if (getPath().empty()) + return WorldPosition(); + + auto beg = fullPath.begin(); + auto ed = fullPath.end(); + + float minDist = 0.0f; + auto startP = beg; + + // Get the closest point on the path to start from. + for (auto p = startP; p != ed; p++) + { + if (p->point.GetMapId() != startPos.GetMapId()) + continue; + + float curDist = p->point.distance(startPos); + + if (curDist <= minDist || p == beg) + { + minDist = curDist; + startP = p; + } + } + + float moveDist = startP->point.distance(startPos); + + // Move as far as we are allowed + for (auto p = startP; p != ed; p++) + { + if (shouldMoveToNextPoint(startPos, beg, ed, p, moveDist, maxDist)) + continue; + + startP = p; + + break; + } + + // We are moving towards a teleport. Move to portal an activate area trigger + if (startP->type == NODE_PORTAL) + { + pathType = TravelNodePathType::portal; + entry = startP->entry; + return startP->point; + } + + // We are using a hearthstone + if (startP->type == NODE_TELEPORT) + { + pathType = TravelNodePathType::teleportSpell; + entry = startP->entry; + return startP->point; + } + + // We are moving towards a flight path. Move to flight master and activate flight path. + if (startP->type == NODE_FLIGHTPATH && startPos.distance(startP->point) < INTERACTION_DISTANCE) + { + pathType = TravelNodePathType::flightPath; + entry = startP->entry; + return startP->point; + } + + // We are moving towards transport. Teleport to next normal point instead. + if (startP->type == NODE_TRANSPORT) + { + for (auto p = startP + 1; p != ed; p++) + { + if (p->type != NODE_TRANSPORT) + { + pathType = TravelNodePathType::portal; + entry = 0; + return p->point; + } + } + } + + // We have to move far for next point. Try to make a cropped path. + if (moveDist < sPlayerbotAIConfig.targetPosRecalcDistance && std::next(startP) != ed) + { + // std::vector path = startPos.getPathTo(std::next(startP)->point, nullptr); + // startP->point = startPos.lastInRange(path, -1, maxDist); + return WorldPosition(); + } + + return startP->point; +} + std::ostringstream const TravelPath::print() { std::ostringstream out; @@ -782,23 +919,22 @@ std::ostringstream const TravelPath::print() float TravelNodeRoute::getTotalDistance() { - if (nodes.size() < 2) - return 0; - float totalLength = 0; - for (uint32 i = 0; i < nodes.size() - 1; i++) + for (uint32 i = 0; i < nodes.size() - 2; i++) + { totalLength += nodes[i]->linkDistanceTo(nodes[i + 1]); + } return totalLength; } -TravelPath TravelNodeRoute::BuildPath(std::vector pathToStart, std::vector pathToEnd, +TravelPath TravelNodeRoute::buildPath(std::vector pathToStart, std::vector pathToEnd, [[maybe_unused]] Unit* bot) { TravelPath travelPath; if (!pathToStart.empty()) // From start position to start of path. - travelPath.addPath(pathToStart, PathNodeType::NODE_PREPATH); + travelPath.addPath(pathToStart, NODE_PREPATH); TravelNode* prevNode = nullptr; for (auto& node : nodes) @@ -811,81 +947,69 @@ TravelPath TravelNodeRoute::BuildPath(std::vector pathToStart, st if (!nodePath || !nodePath->getComplete()) // Build the path to the next node if it doesn't exist. { - // Only attempt runtime path building when we have a bot entity. - if (bot) + if (!prevNode->isTransport()) + nodePath = prevNode->buildPath(node, nullptr); + else // For transports we have no proper path since the node is in air/water. Instead we build a + // reverse path and follow that. { - if (!prevNode->isTransport()) - nodePath = prevNode->BuildPath(node, bot); - else - { - node->BuildPath(prevNode, bot); - nodePath = prevNode->getPathTo(node); - } + node->buildPath(prevNode, nullptr); // Reverse build to get proper path. + nodePath = prevNode->getPathTo(node); } } TravelNodePath returnNodePath; - if (!nodePath || !nodePath->getComplete()) + if (!nodePath || !nodePath->getComplete()) // It looks like we can't properly path to our node. Make a + // temporary reverse path and see if that works instead. { - if (bot) - { - returnNodePath = - *node->BuildPath(prevNode, bot); - std::vector path = returnNodePath.GetPath(); - std::reverse(path.begin(), path.end()); - returnNodePath.setPath(path); - nodePath = &returnNodePath; - } + returnNodePath = + *node->buildPath(prevNode, nullptr); // Build reverse path and save it to a temporary variable. + std::vector path = returnNodePath.getPath(); + std::reverse(path.begin(), path.end()); // Reverse the path + returnNodePath.setPath(path); + nodePath = &returnNodePath; } if (!nodePath || !nodePath->getComplete()) // If we can not build a path just try to move to the node. { - travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_NODE); + travelPath.addPoint(*prevNode->getPosition(), NODE_NODE); prevNode = node; continue; } - if (nodePath->getPathType() == TravelNodePathType::portal || - nodePath->getPathType() == TravelNodePathType::staticPortal) // Teleport to next node. + if (nodePath->getPathType() == TravelNodePathType::portal) // Teleport to next node. { - travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_PORTAL, nodePath->getPathObject()); // Entry point - travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_PORTAL, nodePath->getPathObject()); // Exit point + travelPath.addPoint(*prevNode->getPosition(), NODE_PORTAL, nodePath->getPathObject()); // Entry point + travelPath.addPoint(*node->getPosition(), NODE_PORTAL, nodePath->getPathObject()); // Exit point } else if (nodePath->getPathType() == TravelNodePathType::transport) // Move onto transport { - travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_TRANSPORT, + travelPath.addPoint(*prevNode->getPosition(), NODE_TRANSPORT, nodePath->getPathObject()); // Departure point - travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_TRANSPORT, nodePath->getPathObject()); // Arrival point + travelPath.addPoint(*node->getPosition(), NODE_TRANSPORT, nodePath->getPathObject()); // Arrival point } else if (nodePath->getPathType() == TravelNodePathType::flightPath) // Use the flightpath { - travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_FLIGHTPATH, + travelPath.addPoint(*prevNode->getPosition(), NODE_FLIGHTPATH, nodePath->getPathObject()); // Departure point - travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_FLIGHTPATH, nodePath->getPathObject()); // Arrival point + travelPath.addPoint(*node->getPosition(), NODE_FLIGHTPATH, nodePath->getPathObject()); // Arrival point } else if (nodePath->getPathType() == TravelNodePathType::teleportSpell) { - travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject()); - travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_TELEPORT, nodePath->getPathObject()); - } - else if (nodePath->getPathType() == TravelNodePathType::flyingMount) - { - travelPath.addPoint(*prevNode->getPosition(), PathNodeType::NODE_FLYING_MOUNT, 0); - travelPath.addPoint(*node->getPosition(), PathNodeType::NODE_FLYING_MOUNT, 0); + travelPath.addPoint(*prevNode->getPosition(), NODE_TELEPORT, nodePath->getPathObject()); + travelPath.addPoint(*node->getPosition(), NODE_TELEPORT, nodePath->getPathObject()); } else { - std::vector path = nodePath->GetPath(); + std::vector path = nodePath->getPath(); if (path.size() > 1 && node != nodes.back()) // Remove the last point since that will also be the start of the next path. path.pop_back(); if (path.size() > 1 && prevNode->isPortal() && - nodePath->getPathType() != TravelNodePathType::portal && - nodePath->getPathType() != TravelNodePathType::staticPortal) // Do not move to the area trigger if we - // don't plan to take the portal. + nodePath->getPathType() != TravelNodePathType::portal) // Do not move to the area trigger if we + // don't plan to take the portal. path.erase(path.begin()); if (path.size() > 1 && prevNode->isTransport() && @@ -893,14 +1017,14 @@ TravelPath TravelNodeRoute::BuildPath(std::vector pathToStart, st TravelNodePathType::transport) // Do not move to the transport if we aren't going to take it. path.erase(path.begin()); - travelPath.addPath(path, PathNodeType::NODE_PATH); + travelPath.addPath(path, NODE_PATH); } } prevNode = node; } if (!pathToEnd.empty()) - travelPath.addPath(pathToEnd, PathNodeType::NODE_PATH); + travelPath.addPath(pathToEnd, NODE_PATH); return travelPath; } @@ -957,7 +1081,7 @@ TravelNode* TravelNodeMap::addNode(WorldPosition pos, std::string const prefered newNode = new TravelNode(pos, finalName, isImportant); - nodes.push_back(newNode); + m_nodes.push_back(newNode); return newNode; } @@ -966,7 +1090,7 @@ void TravelNodeMap::removeNode(TravelNode* node) { node->removeLinkTo(nullptr, true); - for (auto& tnode : nodes) + for (auto& tnode : m_nodes) { if (tnode == node) { @@ -975,7 +1099,7 @@ void TravelNodeMap::removeNode(TravelNode* node) } } - nodes.erase(std::remove(nodes.begin(), nodes.end(), nullptr), nodes.end()); + m_nodes.erase(std::remove(m_nodes.begin(), m_nodes.end(), nullptr), m_nodes.end()); } void TravelNodeMap::fullLinkNode(TravelNode* startNode, Unit* bot) @@ -991,8 +1115,8 @@ void TravelNodeMap::fullLinkNode(TravelNode* startNode, Unit* bot) if (startNode->hasLinkTo(endNode)) continue; - startNode->BuildPath(endNode, bot); - endNode->BuildPath(startNode, bot); + startNode->buildPath(endNode, bot); + endNode->buildPath(startNode, bot); } startNode->setLinked(true); @@ -1002,9 +1126,9 @@ std::vector TravelNodeMap::getNodes(WorldPosition pos, float range) { std::vector retVec; - for (auto& node : nodes) + for (auto& node : m_nodes) { - if (node->GetMapId() == pos.GetMapId()) + if (node->getMapId() == pos.GetMapId()) if (range == -1 || node->getDistance(pos) <= range) retVec.push_back(node); } @@ -1043,15 +1167,14 @@ TravelNode* TravelNodeMap::getNode(WorldPosition pos, [[maybe_unused]] std::vect return nullptr; } -TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal, - Player* bot) +TravelNodeRoute TravelNodeMap::getRoute(TravelNode* start, TravelNode* goal, Player* bot) { float botSpeed = bot ? bot->GetSpeed(MOVE_RUN) : 7.0f; if (start == goal) return TravelNodeRoute(); - // Basic A* algorithm + // Basic A* algoritm std::unordered_map m_stubs; TravelNodeStub* startStub = &m_stubs.insert(std::make_pair(start, TravelNodeStub(start))).first->second; @@ -1079,35 +1202,62 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal, } else startStub->currentGold = bot->GetMoney(); + + if (!bot->HasSpellCooldown(8690) && bot->IsAlive()) + { + AiObjectContext* context = botAI->GetAiObjectContext(); + + TravelNode* homeNode = TravelNodeMap::instance().getNode(AI_VALUE(WorldPosition, "home bind"), nullptr, 10.0f); + if (homeNode) + { + PortalNode* portNode = (PortalNode*)TravelNodeMap::instance().teleportNodes[bot->GetGUID()][8690]; + { + portNode = new PortalNode(start); + + TravelNodeMap::instance().teleportNodes[bot->GetGUID()][8690] = portNode; + } + + portNode->SetPortal(start, homeNode, 8690); + + childNode = &m_stubs.insert(std::make_pair(portNode, TravelNodeStub(portNode))).first->second; + + childNode->m_g = 10 * MINUTE; + childNode->m_h = childNode->dataNode->fDist(goal) / botSpeed; + childNode->m_f = childNode->m_g + childNode->m_h; + // childNode->parent = startStub; + + open.push_back(childNode); + std::push_heap(open.begin(), open.end(), + [](TravelNodeStub* i, TravelNodeStub* j) { return i->m_f < j->m_f; }); + childNode->open = true; + } + } } - if (!start->hasRouteTo(goal)) + if (open.size() == 0 && !start->hasRouteTo(goal)) return TravelNodeRoute(); - // Min-heap: smallest f at front - auto heapComp = [](TravelNodeStub* i, TravelNodeStub* j) { return i->totalCost > j->totalCost; }; + std::make_heap(open.begin(), open.end(), [](TravelNodeStub* i, TravelNodeStub* j) { return i->m_f < j->m_f; }); open.push_back(startStub); - std::push_heap(open.begin(), open.end(), heapComp); + std::push_heap(open.begin(), open.end(), [](TravelNodeStub* i, TravelNodeStub* j) { return i->m_f < j->m_f; }); startStub->open = true; - constexpr uint32 MAX_A_STAR_EXPLORED = 500; - uint32 nodesExplored = 0; - while (!open.empty()) { - if (++nodesExplored > MAX_A_STAR_EXPLORED) - return TravelNodeRoute(); + std::sort(open.begin(), open.end(), [](TravelNodeStub* i, TravelNodeStub* j) { return i->m_f < j->m_f; }); - std::pop_heap(open.begin(), open.end(), heapComp); - currentNode = open.back(); + currentNode = open.front(); // pop n node from open for which f is minimal + + std::pop_heap(open.begin(), open.end(), [](TravelNodeStub* i, TravelNodeStub* j) { return i->m_f < j->m_f; }); open.pop_back(); currentNode->open = false; - currentNode->closed = true; + currentNode->close = true; closed.push_back(currentNode); - if (currentNode->dataNode == goal) + if (currentNode->dataNode == goal || + (currentNode->dataNode->getMapId() != start->getMapId() && currentNode->dataNode->isWalking())) { TravelNodeStub* parent = currentNode->parent; @@ -1134,28 +1284,29 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal, continue; childNode = &m_stubs.insert(std::make_pair(linkNode, TravelNodeStub(linkNode))).first->second; - g = currentNode->costFromStart + linkCost; // stance from start + distance between the two nodes - if ((childNode->open || childNode->closed) && - childNode->costFromStart <= g) // n' is already in opend or closed with a lower cost g(n') + g = currentNode->m_g + linkCost; // stance from start + distance between the two nodes + if ((childNode->open || childNode->close) && + childNode->m_g <= g) // n' is already in opend or closed with a lower cost g(n') continue; // consider next successor h = childNode->dataNode->fDist(goal) / botSpeed; - f = g + h; // compute f(n') - childNode->totalCost = f; - childNode->costFromStart = g; - childNode->heuristic = h; + f = g + h; // compute f(n') + childNode->m_f = f; + childNode->m_g = g; + childNode->m_h = h; childNode->parent = currentNode; if (bot && !bot->isTaxiCheater()) childNode->currentGold = currentNode->currentGold - link.second->getPrice(); - if (childNode->closed) - childNode->closed = false; + if (childNode->close) + childNode->close = false; if (!childNode->open) { open.push_back(childNode); - std::push_heap(open.begin(), open.end(), heapComp); + std::push_heap(open.begin(), open.end(), + [](TravelNodeStub* i, TravelNodeStub* j) { return i->m_f < j->m_f; }); childNode->open = true; } } @@ -1164,69 +1315,55 @@ TravelNodeRoute TravelNodeMap::GetNodeRoute(TravelNode* start, TravelNode* goal, return TravelNodeRoute(); } -TravelNodeRoute TravelNodeMap::GetNearestNodes(WorldPosition startPos, WorldPosition endPos, - std::vector& startPath, Player* bot) +TravelNodeRoute TravelNodeMap::getRoute(WorldPosition startPos, WorldPosition endPos, + std::vector& startPath, Player* bot) { - if (nodes.empty() || !bot) + if (m_nodes.empty()) return TravelNodeRoute(); - constexpr uint32 K = 3; - if (nodes.size() < K) - return TravelNodeRoute(); + std::vector newStartPath; + std::vector startNodes = m_nodes, endNodes = m_nodes; - // Single copy of the node list, find closest K for start and end - std::vector nodesCopy = this->nodes; + if (!startNodes.size() || !endNodes.size()) + return TravelNodeRoute(); - // nth_element is O(n) — partitions so the first K are the closest (unordered) - std::nth_element(nodesCopy.begin(), nodesCopy.begin() + K, nodesCopy.end(), - [startPos](TravelNode* i, TravelNode* j) { return i->fDist(startPos) < j->fDist(startPos); }); - // Sort just the K closest - std::sort(nodesCopy.begin(), nodesCopy.begin() + K, - [startPos](TravelNode* i, TravelNode* j) { return i->fDist(startPos) < j->fDist(startPos); }); + // Partial sort to get the closest 5 nodes at the begin of the array. + std::partial_sort(startNodes.begin(), startNodes.begin() + 5, startNodes.end(), + [startPos](TravelNode* i, TravelNode* j) { return i->fDist(startPos) < j->fDist(startPos); }); - // Save the K closest start nodes before reusing the vector for end nodes - std::array startNodes; - std::copy_n(nodesCopy.begin(), K, startNodes.begin()); + std::partial_sort(endNodes.begin(), endNodes.begin() + 5, endNodes.end(), + [endPos](TravelNode* i, TravelNode* j) { return i->fDist(endPos) < j->fDist(endPos); }); - std::nth_element(nodesCopy.begin(), nodesCopy.begin() + K, nodesCopy.end(), - [endPos](TravelNode* i, TravelNode* j) { return i->fDist(endPos) < j->fDist(endPos); }); - std::sort(nodesCopy.begin(), nodesCopy.begin() + K, - [endPos](TravelNode* i, TravelNode* j) { return i->fDist(endPos) < j->fDist(endPos); }); - - std::array endNodes; - std::copy_n(nodesCopy.begin(), K, endNodes.begin()); - - // Cycle over the combinations of these K nodes. + // Cycle over the combinations of these 5 nodes. uint32 startI = 0, endI = 0; - while (startI < K && endI < K) + while (startI < 5 && endI < 5) { TravelNode* startNode = startNodes[startI]; TravelNode* endNode = endNodes[endI]; WorldPosition startNodePosition = *startNode->getPosition(); + WorldPosition endNodePosition = *endNode->getPosition(); - TravelNodeRoute route = GetNodeRoute(startNode, endNode, bot); + float maxStartDistance = startNode->isTransport() ? 20.0f : sPlayerbotAIConfig.targetPosRecalcDistance; + + TravelNodeRoute route = getRoute(startNode, endNode, bot); if (!route.isEmpty()) { - // Check if the bot can actually walk to this start node using mmap pathfinding. - if (startNodePosition.GetMapId() == bot->GetMapId()) + // Check if the bot can actually walk to this start position. + newStartPath = startPath; + if (startNodePosition.cropPathTo(newStartPath, maxStartDistance) || + startNode->getPosition()->isPathTo(newStartPath = startPos.getPathTo(startNodePosition, nullptr), + maxStartDistance)) { - PathGenerator path(bot); - path.CalculatePath(startNodePosition.GetPositionX(), startNodePosition.GetPositionY(), startNodePosition.GetPositionZ()); - PathType type = path.GetPathType(); - bool reachable = !(type & ~(PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY)); - - if (reachable) - { - startPath = {startPos, startNodePosition}; - return route; - } + startPath = newStartPath; + return route; } + startI++; } - // Prefer a different end-node. + // Prefer a differnt end-node. endI++; // Cycle to a different start-node if needed. @@ -1237,58 +1374,79 @@ TravelNodeRoute TravelNodeMap::GetNearestNodes(WorldPosition startPos, WorldPosi } } + if (bot && !bot->HasSpellCooldown(8690)) + { + startPath.clear(); + TravelNode* botNode = TravelNodeMap::instance().teleportNodes[bot->GetGUID()][0]; + { + botNode = new TravelNode(startPos, "Bot Pos", false); + TravelNodeMap::instance().teleportNodes[bot->GetGUID()][0] = botNode; + } + + botNode->setPoint(startPos); + + endI = 0; + while (endI < 5) + { + TravelNode* endNode = endNodes[endI]; + TravelNodeRoute route = getRoute(botNode, endNode, bot); + + if (!route.isEmpty()) + return route; + endI++; + } + } + return TravelNodeRoute(); } -bool TravelNodeMap::GetFullPath(TravelPlan& plan, - WorldPosition botPos, uint32 botZoneId, - uint32 teamId, WorldPosition destination) +TravelPath TravelNodeMap::getFullPath(WorldPosition startPos, WorldPosition endPos, Player* bot) { - plan.Reset(); - plan.destination = destination; + TravelPath movePath; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + std::vector beginPath, endPath; - // Short distance — direct walk, no nodes needed - if (botPos.fDist(destination) < MAX_PATHFINDING_DISTANCE && - botPos.GetMapId() == destination.GetMapId()) + beginPath = endPos.getPathFromPath({startPos}, nullptr, 40); + + if (endPos.isPathTo(beginPath)) + return TravelPath(beginPath); + + //[[Node pathfinding system]] + // We try to find nodes near the bot and near the end position that have a route between them. + // Then bot has to move towards/along the route. + TravelNodeMap::instance().m_nMapMtx.lock_shared(); + + // Find the route of nodes starting at a node closest to the start position and ending at a node closest to the + // endposition. Also returns longPath: The path from the start position to the first node in the route. + TravelNodeRoute route = TravelNodeMap::instance().getRoute(startPos, endPos, beginPath, bot); + + if (route.isEmpty()) + return movePath; + + if (sPlayerbotAIConfig.hasLog("bot_pathfinding.csv")) { - plan.steps.addPoint(botPos, PathNodeType::NODE_PREPATH); - plan.steps.addPoint(destination, PathNodeType::NODE_PATH); - return true; + if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT)) + { + sPlayerbotAIConfig.openLog("bot_pathfinding.csv", "w"); + sPlayerbotAIConfig.log("bot_pathfinding.csv", route.print().str().c_str()); + } } - std::shared_lock guard(m_nMapMtx); + endPath = route.getNodes().back()->getPosition()->getPathTo(endPos, nullptr); + movePath = route.buildPath(beginPath, endPath); - // Find nearest nodes (zone-indexed, fast) - TravelNode* startNode = GetNearestNodeInZone(botPos, botZoneId); - if (!startNode) - startNode = GetNearestNodeOnMap(botPos); + if (sPlayerbotAIConfig.hasLog("bot_pathfinding.csv")) + { + if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT)) + { + sPlayerbotAIConfig.openLog("bot_pathfinding.csv", "w"); + sPlayerbotAIConfig.log("bot_pathfinding.csv", movePath.print().str().c_str()); + } + } - uint32 destZone = sMapMgr->GetZoneId(PHASEMASK_NORMAL, destination); - TravelNode* endNode = GetNearestNodeInZone(destination, destZone); - if (!endNode) - endNode = GetNearestNodeOnMap(destination); + TravelNodeMap::instance().m_nMapMtx.unlock_shared(); - if (!startNode || !endNode) - return false; - - if (!startNode->hasRouteTo(endNode)) - return false; - - TravelNodeRoute route = GetNodeRoute(startNode, endNode, nullptr); - if (route.isEmpty()) - return false; - - // Build flat waypoint path from A* route - std::vector pathToStart = {botPos}; - std::vector pathToEnd = {destination}; - plan.steps = route.BuildPath(pathToStart, pathToEnd, nullptr); - - LOG_DEBUG("playerbots", - "[TravelPlan] '{}' → '{}', {} points", - startNode->getName(), endNode->getName(), - plan.steps.size()); - - return !plan.steps.empty(); + return movePath; } bool TravelNodeMap::cropUselessNode(TravelNode* startNode) @@ -1307,9 +1465,6 @@ bool TravelNodeMap::cropUselessNode(TravelNode* startNode) return false; } - LOG_INFO("playerbots", "[CropNode] Removing useless node '{}' (map {}, {:.0f},{:.0f},{:.0f}) — no neighbor depends on it", - startNode->getName(), startNode->GetMapId(), startNode->getX(), startNode->getY(), startNode->getZ()); - removeNode(startNode); return true; @@ -1322,7 +1477,7 @@ TravelNode* TravelNodeMap::addZoneLinkNode(TravelNode* startNode) //TravelNode* endNode = path.first; //not used, line marked for removal. std::string zoneName = startNode->getPosition()->getAreaName(true, true); - for (auto& pos : path.second.GetPath()) + for (auto& pos : path.second.getPath()) { std::string const newZoneName = pos.getAreaName(true, true); if (zoneName != newZoneName) @@ -1353,7 +1508,7 @@ TravelNode* TravelNodeMap::addRandomExtNode(TravelNode* startNode) auto random_it = std::next(std::begin(paths), urand(0, paths.size() - 1)); TravelNode* endNode = random_it->first; - std::vector path = random_it->second.GetPath(); + std::vector path = random_it->second.getPath(); if (path.empty()) continue; @@ -1377,10 +1532,60 @@ TravelNode* TravelNodeMap::addRandomExtNode(TravelNode* startNode) void TravelNodeMap::manageNodes(Unit* bot, bool mapFull) { - // Runtime node mutation disabled — the graph is fully built at startup via Init() - // and .generate. Taking a unique_lock here caused 100-250ms contention spikes for - // all bot threads holding shared_locks in TravelPlan. - // Node pruning/creation is still available via the .generate console command. + bool rePrint = false; + + if (!bot->GetMap()) + return; + + if (m_nMapMtx.try_lock()) + { + TravelNode* startNode; + TravelNode* newNode; + + for (auto startNode : m_nodes) + { + cropUselessNode(startNode); + } + + // Pick random Node + for (uint32 i = 0; i < (mapFull ? (uint32)20 : (uint32)1); i++) + { + std::vector rnodes = getNodes(WorldPosition(bot)); + + if (!rnodes.empty()) + { + uint32 j = urand(0, rnodes.size() - 1); + + startNode = rnodes[j]; + newNode = nullptr; + + bool nodeDone = false; + + if (!nodeDone) + nodeDone = cropUselessNode(startNode); + + if (!nodeDone && !urand(0, 20)) + newNode = addZoneLinkNode(startNode); + + if (!nodeDone && !newNode && !urand(0, 20)) + newNode = addRandomExtNode(startNode); + + rePrint = nodeDone || rePrint || newNode; + } + } + + if (rePrint && (mapFull || !urand(0, 20))) + printMap(); + + m_nMapMtx.unlock(); + } + + TravelNodeMap::instance().m_nMapMtx.lock_shared(); + + if (!rePrint && mapFull) + printMap(); + + m_nMapMtx.unlock_shared(); } void TravelNodeMap::generateNpcNodes() @@ -1662,26 +1867,18 @@ void TravelNodeMap::generateWalkPaths() std::map nodeMaps; - uint32 totalProcessed = 0, totalSkipped = 0; - for (auto& startNode : TravelNodeMap::instance().getNodes()) { - nodeMaps[startNode->GetMapId()] = true; + nodeMaps[startNode->getMapId()] = true; } for (auto& map : nodeMaps) { - uint32 mapProcessed = 0, mapSkipped = 0; for (auto& startNode : TravelNodeMap::instance().getNodes(WorldPosition(map.first, 1, 1))) { if (startNode->isLinked()) - { - mapSkipped++; continue; - } - uint32 nearby = 0; - uint32 linked = 0; for (auto& endNode : TravelNodeMap::instance().getNodes(*startNode->getPosition(), 2000.0f)) { if (startNode == endNode) @@ -1690,30 +1887,17 @@ void TravelNodeMap::generateWalkPaths() if (startNode->hasCompletePathTo(endNode)) continue; - if (startNode->GetMapId() != endNode->GetMapId()) + if (startNode->getMapId() != endNode->getMapId()) continue; - nearby++; - startNode->BuildPath(endNode, nullptr, false); - if (startNode->hasCompletePathTo(endNode)) - linked++; + startNode->buildPath(endNode, nullptr, false); } - LOG_INFO("playerbots", " Node '{}' (map {}): {} nearby, {} linked", - startNode->getName(), startNode->GetMapId(), nearby, linked); - startNode->setLinked(true); - mapProcessed++; } - - LOG_INFO("playerbots", "[WalkPaths] Map {}: processed {} nodes, skipped {} (already linked)", - map.first, mapProcessed, mapSkipped); - totalProcessed += mapProcessed; - totalSkipped += mapSkipped; } - LOG_INFO("playerbots", ">> Generated paths for {} nodes ({} processed, {} skipped as already linked).", - TravelNodeMap::instance().getNodes().size(), totalProcessed, totalSkipped); + LOG_INFO("playerbots", ">> Generated paths for {} nodes.", TravelNodeMap::instance().getNodes().size()); } void TravelNodeMap::generateTaxiPaths() @@ -1767,14 +1951,11 @@ void TravelNodeMap::removeLowNodes() { std::vector goodNodes; std::vector remNodes; - uint32 totalOverworld = 0; for (auto& node : TravelNodeMap::instance().getNodes()) { if (!node->getPosition()->isOverworld()) continue; - totalOverworld++; - if (std::find(goodNodes.begin(), goodNodes.end(), node) != goodNodes.end()) continue; @@ -1784,32 +1965,17 @@ void TravelNodeMap::removeLowNodes() std::vector nodes = node->getNodeMap(true); if (nodes.size() < 5) - { - std::vector allInCluster = node->getNodeMap(); - LOG_INFO("playerbots", "[RemoveLow] Cluster starting at '{}' (map {}) has only {} important nodes, {} total — marking for removal:", - node->getName(), node->GetMapId(), nodes.size(), allInCluster.size()); - for (auto& rn : allInCluster) - LOG_INFO("playerbots", "[RemoveLow] - '{}' (map {}, {:.0f},{:.0f},{:.0f}) important={}", - rn->getName(), rn->GetMapId(), rn->getX(), rn->getY(), rn->getZ(), rn->isImportant()); remNodes.insert(remNodes.end(), nodes.begin(), nodes.end()); - } else goodNodes.insert(goodNodes.end(), nodes.begin(), nodes.end()); } - LOG_INFO("playerbots", "[RemoveLow] {} overworld nodes evaluated, {} good, {} to remove", - totalOverworld, goodNodes.size(), remNodes.size()); - for (auto& node : remNodes) TravelNodeMap::instance().removeNode(node); } void TravelNodeMap::removeUselessPaths() { - uint32 linksBefore = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - linksBefore += n->getLinks()->size(); - // Clean up node links for (auto& startNode : TravelNodeMap::instance().getNodes()) { @@ -1817,7 +1983,7 @@ void TravelNodeMap::removeUselessPaths() if (path.second.getComplete() && startNode->hasLinkTo(path.first)) ASSERT(true); } - uint32 it = 0, totalRemoved = 0; + uint32 it = 0/*, rem = 0*/; //rem not used in this scope, (shadowing) fragment marked for removal. while (true) { uint32 rem = 0; @@ -1832,18 +1998,11 @@ void TravelNodeMap::removeUselessPaths() break; hasToSave = true; - totalRemoved += rem; + it++; - LOG_INFO("playerbots", "[RemoveUseless] Iteration {}, removed {} links from nodes", it, rem); + LOG_INFO("playerbots", "Iteration {}, removed {}", it, rem); } - - uint32 linksAfter = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - linksAfter += n->getLinks()->size(); - - LOG_INFO("playerbots", "[RemoveUseless] Done: {} iterations, links {} → {} (removed {})", - it, linksBefore, linksAfter, linksBefore - linksAfter); } void TravelNodeMap::calculatePathCosts() @@ -1867,103 +2026,38 @@ void TravelNodeMap::calculatePathCosts() LOG_INFO("playerbots", ">> Calculated pathcost for {} nodes.", TravelNodeMap::instance().getNodes().size()); } -void TravelNodeMap::generatePaths(bool fullGen) +void TravelNodeMap::generatePaths() { - uint32 totalLinks = 0; - uint32 totalPathPoints = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - { - totalLinks += n->getLinks()->size(); - for (auto& l : *n->getLinks()) - totalPathPoints += l.second->GetPath().size(); - } - LOG_INFO("playerbots", "[GenPaths] ENTRY (fullGen={}): {} nodes, {} links, {} path points", - fullGen, TravelNodeMap::instance().getNodes().size(), totalLinks, totalPathPoints); - LOG_INFO("playerbots", "-Calculating walkable paths"); generateWalkPaths(); - - totalLinks = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - totalLinks += n->getLinks()->size(); - LOG_INFO("playerbots", "[GenPaths] After generateWalkPaths: {} nodes, {} links", - TravelNodeMap::instance().getNodes().size(), totalLinks); - - if (fullGen) - { - LOG_INFO("playerbots", "-Removing useless nodes"); - removeLowNodes(); - - totalLinks = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - totalLinks += n->getLinks()->size(); - LOG_INFO("playerbots", "[GenPaths] After removeLowNodes: {} nodes, {} links", - TravelNodeMap::instance().getNodes().size(), totalLinks); - - LOG_INFO("playerbots", "-Removing useless paths"); - removeUselessPaths(); - - totalLinks = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - totalLinks += n->getLinks()->size(); - LOG_INFO("playerbots", "[GenPaths] After removeUselessPaths: {} nodes, {} links", - TravelNodeMap::instance().getNodes().size(), totalLinks); - } - else - LOG_INFO("playerbots", "-Skipping node/path pruning (incremental generation)"); - + LOG_INFO("playerbots", "-Removing useless nodes"); + removeLowNodes(); + LOG_INFO("playerbots", "-Removing useless paths"); + removeUselessPaths(); LOG_INFO("playerbots", "-Calculating path costs"); calculatePathCosts(); LOG_INFO("playerbots", "-Generating taxi paths"); generateTaxiPaths(); - - totalLinks = 0; - totalPathPoints = 0; - for (auto& n : TravelNodeMap::instance().getNodes()) - { - totalLinks += n->getLinks()->size(); - for (auto& l : *n->getLinks()) - totalPathPoints += l.second->GetPath().size(); - } - LOG_INFO("playerbots", "[GenPaths] EXIT: {} nodes, {} links, {} path points", - TravelNodeMap::instance().getNodes().size(), totalLinks, totalPathPoints); } void TravelNodeMap::generateAll() { - LOG_INFO("playerbots", "[GenerateAll] Regenerating: {} nodes", nodes.size()); + if (hasToFullGen) + generateNodes(); - generatePaths(false); - saveNodeStore(); - - BuildZoneIndex(); - PrecomputeReachability(); - LOG_INFO("playerbots", "[GenerateAll] Done: {} nodes, indexes rebuilt.", nodes.size()); -} - -void TravelNodeMap::Init() -{ - LoadNodeStore(); + LOG_INFO("playerbots", "-Calculating mapoffset"); calcMapOffset(); + LOG_INFO("playerbots", "-Generating maptransfers"); + TravelMgr::instance().loadMapTransfers(); + if (hasToGen || hasToFullGen) { - LOG_INFO("playerbots", "[Init] Generating paths (fullGen={}, {} nodes)...", hasToFullGen, nodes.size()); - - if (hasToFullGen) - generateNodes(); - - generatePaths(hasToFullGen); + generatePaths(); hasToGen = false; hasToFullGen = false; - saveNodeStore(); + hasToSave = true; } - - BuildZoneIndex(); - PrecomputeReachability(); - InitTaxiGraph(); - LOG_INFO("playerbots", "TravelNodeMap initialized: {} nodes, zone index and reachability built.", - nodes.size()); } void TravelNodeMap::printMap() @@ -2024,7 +2118,7 @@ void TravelNodeMap::printNodeStore() // struct addNode {uint32 node; WorldPosition point; std::string const name; bool isPortal; bool // isTransport; uint32 transportId; }; out << std::fixed << std::setprecision(2) << " addNodes.push_back(addNode{" << i << ","; - out << "WorldPosition(" << node->GetMapId() << ", " << node->getX() << "f, " << node->getY() << "f, " + out << "WorldPosition(" << node->getMapId() << ", " << node->getX() << "f, " << node->getY() << "f, " << node->getZ() << "f, " << node->getO() << "f),"; out << "\"" << name << "\""; if (node->isTransport()) @@ -2033,7 +2127,7 @@ void TravelNodeMap::printNodeStore() /* out << std::fixed << std::setprecision(2) << " nodes[" << i << "] = - TravelNodeMap::instance().addNode(&WorldPosition(" << node->GetMapId() << "," << node->getX() << "f," << node->getY() + TravelNodeMap::instance().addNode(&WorldPosition(" << node->getMapId() << "," << node->getX() << "f," << node->getY() << "f," << node->getZ() << "f,"<< node->getO() <<"f), \"" << name << "\", " << (node->isImportant() ? "true" : "false") << ", true"; if (node->isTransport()) @@ -2078,10 +2172,7 @@ void TravelNodeMap::printNodeStore() void TravelNodeMap::saveNodeStore() { if (!hasToSave) - { - LOG_INFO("playerbots", "[SaveNodes] Skipped — hasToSave is false"); return; - } hasToSave = false; @@ -2104,7 +2195,7 @@ void TravelNodeMap::saveNodeStore() PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_TRAVELNODE); stmt->SetData(0, i); stmt->SetData(1, name); - stmt->SetData(2, node->GetMapId()); + stmt->SetData(2, node->getMapId()); stmt->SetData(3, node->getX()); stmt->SetData(4, node->getY()); stmt->SetData(5, node->getZ()); @@ -2116,97 +2207,62 @@ void TravelNodeMap::saveNodeStore() LOG_INFO("playerbots", ">> Saved {} travelNodes.", anodes.size()); - uint32 paths = 0; - for (uint32 i = 0; i < anodes.size(); i++) { - TravelNode* node = anodes[i]; - - for (auto& link : *node->getLinks()) + uint32 paths = 0, points = 0; + for (uint32 i = 0; i < anodes.size(); i++) { - TravelNodePath* path = link.second; + TravelNode* node = anodes[i]; - PlayerbotsDatabasePreparedStatement* stmt = - PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_TRAVELNODE_LINK); - stmt->SetData(0, i); - stmt->SetData(1, saveNodes.find(link.first)->second); - stmt->SetData(2, static_cast(path->getPathType())); - stmt->SetData(3, path->getPathObject()); - stmt->SetData(4, path->getDistance()); - stmt->SetData(5, path->getSwimDistance()); - stmt->SetData(6, path->getExtraCost()); - stmt->SetData(7, path->getCalculated()); - stmt->SetData(8, path->getMaxLevelCreature()[0]); - stmt->SetData(9, path->getMaxLevelCreature()[1]); - stmt->SetData(10, path->getMaxLevelCreature()[2]); - trans->Append(stmt); - - paths++; - } - } - LOG_INFO("playerbots", ">> Saved {} travelNode links.", paths); - - // Path points: bulk raw SQL multi-row INSERTs (~500 rows each) instead of - // 1M+ individual prepared statements. Appended to the same transaction so - // ordering is guaranteed. - constexpr uint32 BATCH_SIZE = 500; - uint32 points = 0; - std::ostringstream ss; - uint32 batchCount = 0; - - auto flushBatch = [&]() - { - if (batchCount == 0) - return; - - std::string sql = ss.str(); - sql.back() = ';'; // Replace trailing comma - trans->Append(sql.c_str()); - ss.str(""); - ss.clear(); - batchCount = 0; - }; - - for (uint32 i = 0; i < anodes.size(); i++) - { - TravelNode* node = anodes[i]; - - for (auto& link : *node->getLinks()) - { - TravelNodePath* path = link.second; - uint32 toId = saveNodes.find(link.first)->second; - std::vector ppath = path->GetPath(); - - for (uint32 j = 0; j < ppath.size(); j++) + for (auto& link : *node->getLinks()) { - WorldPosition& point = ppath[j]; + TravelNodePath* path = link.second; - if (batchCount == 0) - ss << "INSERT INTO `playerbots_travelnode_path` (`node_id`,`to_node_id`,`nr`,`map_id`,`x`,`y`,`z`) VALUES "; + PlayerbotsDatabasePreparedStatement* stmt = + PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_TRAVELNODE_LINK); + stmt->SetData(0, i); + stmt->SetData(1, saveNodes.find(link.first)->second); + stmt->SetData(2, static_cast(path->getPathType())); + stmt->SetData(3, path->getPathObject()); + stmt->SetData(4, path->getDistance()); + stmt->SetData(5, path->getSwimDistance()); + stmt->SetData(6, path->getExtraCost()); + stmt->SetData(7, path->getCalculated()); + stmt->SetData(8, path->getMaxLevelCreature()[0]); + stmt->SetData(9, path->getMaxLevelCreature()[1]); + stmt->SetData(10, path->getMaxLevelCreature()[2]); + trans->Append(stmt); - ss << std::fixed << std::setprecision(4) - << "(" << i << "," << toId << "," << j << "," - << point.GetMapId() << "," - << point.GetPositionX() << "," - << point.GetPositionY() << "," - << point.GetPositionZ() << "),"; + paths++; - batchCount++; - points++; + std::vector ppath = path->getPath(); - if (batchCount >= BATCH_SIZE) - flushBatch(); + for (uint32 j = 0; j < ppath.size(); j++) + { + WorldPosition point = ppath[j]; + + PlayerbotsDatabasePreparedStatement* stmt = + PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_TRAVELNODE_PATH); + stmt->SetData(0, i); + stmt->SetData(1, saveNodes.find(link.first)->second); + stmt->SetData(2, j); + stmt->SetData(3, point.GetMapId()); + stmt->SetData(4, point.GetPositionX()); + stmt->SetData(5, point.GetPositionY()); + stmt->SetData(6, point.GetPositionZ()); + trans->Append(stmt); + + points++; + } } } + + LOG_INFO("playerbots", ">> Saved {} travelNode Paths, {} points.", paths, points); } - flushBatch(); - - LOG_INFO("playerbots", ">> Saved {} travelNode path points.", points); - PlayerbotsDatabase.CommitTransaction(trans); } -void TravelNodeMap::LoadNodeStore() +void TravelNodeMap::loadNodeStore() { std::string const query = "SELECT id, name, map_id, x, y, z, linked FROM playerbots_travelnode"; @@ -2227,11 +2283,7 @@ void TravelNodeMap::LoadNodeStore() if (fields[6].Get()) node->setLinked(true); else - { hasToGen = true; - LOG_INFO("playerbots", "[LoadNodes] Node '{}' (id={}) is unlinked — will trigger generation", - fields[1].Get(), fields[0].Get()); - } saveNodes.insert(std::make_pair(fields[0].Get(), node)); @@ -2254,15 +2306,12 @@ void TravelNodeMap::LoadNodeStore() { Field* fields = result->Fetch(); - auto startIt = saveNodes.find(fields[0].Get()); - auto endIt = saveNodes.find(fields[1].Get()); + TravelNode* startNode = saveNodes.find(fields[0].Get())->second; + TravelNode* endNode = saveNodes.find(fields[1].Get())->second; - if (startIt == saveNodes.end() || endIt == saveNodes.end()) + if (!startNode || !endNode) continue; - TravelNode* startNode = startIt->second; - TravelNode* endNode = endIt->second; - startNode->setPathTo( endNode, TravelNodePath(fields[4].Get(), fields[6].Get(), fields[2].Get(), @@ -2272,15 +2321,11 @@ void TravelNodeMap::LoadNodeStore() true); if (!fields[7].Get()) - { hasToGen = true; - LOG_DEBUG("playerbots", "[LoadNodes] Link {}->{} not calculated — will trigger generation", - fields[0].Get(), fields[1].Get()); - } } while (result->NextRow()); - LOG_INFO("playerbots", ">> Loaded {} travelNode links.", result->GetRowCount()); + LOG_INFO("playerbots", ">> Loaded {} travelNode paths.", result->GetRowCount()); } else { @@ -2296,21 +2341,15 @@ void TravelNodeMap::LoadNodeStore() { Field* fields = result->Fetch(); - auto startIt = saveNodes.find(fields[0].Get()); - auto endIt = saveNodes.find(fields[1].Get()); + TravelNode* startNode = saveNodes.find(fields[0].Get())->second; + TravelNode* endNode = saveNodes.find(fields[1].Get())->second; - if (startIt == saveNodes.end() || endIt == saveNodes.end()) - continue; - - TravelNode* startNode = startIt->second; - TravelNode* endNode = endIt->second; - - if (!startNode->hasPathTo(endNode)) + if (!startNode || !endNode || !startNode->hasPathTo(endNode)) continue; TravelNodePath* path = startNode->getPathTo(endNode); - std::vector ppath = path->GetPath(); + std::vector ppath = path->getPath(); ppath.push_back(WorldPosition(fields[3].Get(), fields[4].Get(), fields[5].Get(), fields[6].Get())); @@ -2339,11 +2378,11 @@ void TravelNodeMap::calcMapOffset() std::vector mapIds; - for (auto& node : nodes) + for (auto& node : m_nodes) { if (!node->getPosition()->isOverworld()) - if (std::find(mapIds.begin(), mapIds.end(), node->GetMapId()) == mapIds.end()) - mapIds.push_back(node->GetMapId()); + if (std::find(mapIds.begin(), mapIds.end(), node->getMapId()) == mapIds.end()) + mapIds.push_back(node->getMapId()); } std::sort(mapIds.begin(), mapIds.end()); @@ -2353,9 +2392,9 @@ void TravelNodeMap::calcMapOffset() for (auto& mapId : mapIds) { bool doPush = true; - for (auto& node : nodes) + for (auto& node : m_nodes) { - if (node->GetMapId() != mapId) + if (node->getMapId() != mapId) continue; if (doPush) @@ -2410,7 +2449,10 @@ WorldPosition TravelNodeMap::getMapOffset(uint32 mapId) return WorldPosition(mapId, 0, 0, 0, 0); } +// ============================================================ // TravelNodeMap taxi graph (BFS-based flight path lookup) +// ============================================================ + void TravelNodeMap::InitTaxiGraph() { BuildTaxiGraph(); @@ -2428,8 +2470,8 @@ std::vector TravelNodeMap::FindTaxiPath(uint32 fromNode, uint32 toNode) if (!startNode || !endNode || startNode->map_id != endNode->map_id) return {}; - auto cacheItr = m_taxiPathCache.find(fromNode); - if (cacheItr == m_taxiPathCache.end()) + auto cacheItr = taxiPathCache.find(fromNode); + if (cacheItr == taxiPathCache.end()) return {}; auto toNodeItr = cacheItr->second.find(toNode); @@ -2441,7 +2483,7 @@ std::vector TravelNodeMap::FindTaxiPath(uint32 fromNode, uint32 toNode) void TravelNodeMap::BuildTaxiGraph() { - m_taxiGraph.clear(); + taxiGraph.clear(); std::unordered_map> tempGraph; for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i) { @@ -2456,13 +2498,13 @@ void TravelNodeMap::BuildTaxiGraph() tempGraph[path->to].insert(path->from); } for (auto const& [node, neighbors] : tempGraph) - m_taxiGraph[node] = std::vector(neighbors.begin(), neighbors.end()); + taxiGraph[node] = std::vector(neighbors.begin(), neighbors.end()); } void TravelNodeMap::ComputeAllPaths() { std::set allNodes; - for (auto const& [source, neighbors] : m_taxiGraph) + for (auto const& [source, neighbors] : taxiGraph) allNodes.insert(source); for (uint32 source : allNodes) @@ -2476,7 +2518,7 @@ void TravelNodeMap::ComputeAllPaths() auto path = BuildPath(source, target, parentMap); if (!path.empty()) - m_taxiPathCache[source][target] = path; + taxiPathCache[source][target] = path; } } } @@ -2496,7 +2538,7 @@ std::unordered_map TravelNodeMap::BFS(uint32 fromNode) uint32 current = workQueue.front(); workQueue.pop(); - for (uint32 next : m_taxiGraph.at(current)) + for (uint32 next : taxiGraph.at(current)) { if (visited.count(next)) continue; @@ -2530,171 +2572,3 @@ std::vector TravelNodeMap::BuildPath(uint32 fromNode, uint32 toNode, std::reverse(path.begin(), path.end()); return path; } - -void TravelNodeMap::BuildZoneIndex() -{ - m_zoneIndex.clear(); - m_mapIndex.clear(); - - for (auto* node : nodes) - { - if (!node) - continue; - - WorldPosition* pos = node->getPosition(); - uint32 mapId = pos->GetMapId(); - - m_mapIndex[mapId].push_back(node); - - uint32 zoneId = sMapMgr->GetZoneId(PHASEMASK_NORMAL, *pos); - if (zoneId) - m_zoneIndex[zoneId].push_back(node); - } - - LOG_INFO("playerbots", "[ZoneIndex] Built index: {} zones, {} maps", m_zoneIndex.size(), m_mapIndex.size()); -} - -TravelNode* TravelNodeMap::GetNearestNodeInZone(WorldPosition pos, uint32 zoneId) -{ - auto it = m_zoneIndex.find(zoneId); - if (it == m_zoneIndex.end() || it->second.empty()) - return GetNearestNodeOnMap(pos); // Fallback to map-wide - - TravelNode* bestNode = nullptr; - float bestDist = FLT_MAX; - - for (auto* node : it->second) - { - if (!node || node->GetMapId() != pos.GetMapId()) - continue; - float dist = node->fDist(pos); - if (dist < bestDist) - { - bestDist = dist; - bestNode = node; - } - } - - if (!bestNode) - return GetNearestNodeOnMap(pos); - - return bestNode; -} - -TravelNode* TravelNodeMap::GetNearestNodeOnMap(WorldPosition pos) -{ - auto it = m_mapIndex.find(pos.GetMapId()); - if (it == m_mapIndex.end() || it->second.empty()) - return nullptr; - - TravelNode* bestNode = nullptr; - float bestDist = FLT_MAX; - - for (auto* node : it->second) - { - if (!node) - continue; - float d = node->fDist(pos); - if (d < bestDist) - { - bestDist = d; - bestNode = node; - } - } - - return bestNode; -} - -void TravelNodeMap::PrecomputeReachability() -{ - // Find connected components via BFS - std::unordered_set visited; - std::vector> components; - - for (auto* node : nodes) - { - if (!node || visited.count(node)) - continue; - - // BFS from this node - std::vector component; - std::queue q; - q.push(node); - visited.insert(node); - - while (!q.empty()) - { - TravelNode* current = q.front(); - q.pop(); - component.push_back(current); - - for (auto const& link : *current->getLinks()) - { - TravelNode* neighbor = link.first; - if (neighbor && !visited.count(neighbor)) - { - visited.insert(neighbor); - q.push(neighbor); - } - } - } - - components.push_back(std::move(component)); - } - - // Populate routes: every node in a component can reach every other node - // in the same component - for (auto const& comp : components) - { - for (auto* node : comp) - { - node->clearRoutes(); - for (auto* other : comp) - node->setRouteTo(other); - } - } - - uint32 totalNodes = 0; - for (auto const& c : components) - totalNodes += c.size(); - - // Sort components by size descending for logging - std::sort(components.begin(), components.end(), - [](auto const& a, auto const& b) { return a.size() > b.size(); }); - - uint32 singletons = 0; - for (auto const& c : components) - { - if (c.size() == 1) - singletons++; - } - - LOG_INFO("playerbots", "[Reachability] {} nodes in {} connected components ({} singletons)", - totalNodes, components.size(), singletons); - - // Log top 10 largest components - for (uint32 i = 0; i < std::min(10, components.size()); ++i) - { - auto const& c = components[i]; - std::string sampleName = c.front() ? c.front()->getName() : "?"; - uint32 mapId = c.front() ? c.front()->GetMapId() : 0; - LOG_INFO("playerbots", " Component {}: {} nodes, map={}, sample='{}'", i, c.size(), mapId, sampleName); - } - if (singletons > 0) - { - LOG_INFO("playerbots", " Singleton nodes (no links):"); - uint32 logged = 0; - for (auto const& c : components) - { - if (c.size() == 1 && logged < 20) - { - auto* n = c.front(); - LOG_INFO("playerbots", " '{}' map={} pos=({:.0f},{:.0f},{:.0f})", n->getName(), n->GetMapId(), - n->getPosition()->GetPositionX(), n->getPosition()->GetPositionY(), n->getPosition()->GetPositionZ()); - logged++; - } - } - if (singletons > 20) - LOG_INFO("playerbots", " ... and {} more singletons", singletons - 20); - } -} diff --git a/src/Mgr/Travel/TravelNode.h b/src/Mgr/Travel/TravelNode.h index b6d1d48ed..9e05e2490 100644 --- a/src/Mgr/Travel/TravelNode.h +++ b/src/Mgr/Travel/TravelNode.h @@ -8,12 +8,11 @@ #include -#include "G3D/Vector3.h" #include "TravelMgr.h" // THEORY // -// Pathfinding in (c)mangos is based on detour recast, an opensource navmesh creation and pathfinding codebase. +// Pathfinding in (c)mangos is based on detour recast an opensource nashmesh creation and pathfinding codebase. // This system is used for mob and npc pathfinding and in this codebase also for bots. // Because mobs and npc movement is based on following a player or a set path the PathGenerator is limited to 296y. // This means that when trying to find a path from A to B distances beyond 296y will be a best guess often moving in a @@ -25,68 +24,33 @@ // ---> [N1] ---> [N2] ---> [N3] ---> // // Bot at wants to move to -// [N1],[N2],[N3] are predefined nodes for which we know we can move from [N1] to [N2] and from [N2] to [N3] but not -// from [N1] to [N3]. If we can move from [S] to [N1] and from [N3] to [E] we have a complete route to travel. +// [N1],[N2],[N3] are predefined nodes for wich we know we can move from [N1] to [N2] and from [N2] to [N3] but not +// from [N1] to [N3] If we can move fom [S] to [N1] and from [N3] to [E] we have a complete route to travel. // -// Terminology: -// Node: A location on a map for which we know bots are likely to want to travel to or need to travel past to reach -// other nodes. Stored in DB table `playerbots_travelnode`. -// Link: The connection between two nodes. A link signifies that the bot can travel from one node to another. -// A link is one-directional. Stored in `playerbots_travelnode_link`. -// Path: The waypoint path returned by the standard PathGenerator to move from one node (or position) to another. -// A path can be incomplete or empty which means there is no link. Stored in `playerbots_travelnode_path`. -// Route: The list of nodes that give the shortest route from a node to a distant node. Routes are calculated using -// a standard A* search based on links. +// Termonology: +// Node: a location on a map for which we know bots are likely to want to travel to or need to travel past to reach +// other nodes. Link: the connection between two nodes. A link signifies that the bot can travel from one node to +// another. A link is one-directional. Path: the waypointpath returned by the standard PathGenerator to move from one +// node (or position) to another. A path can be imcomplete or empty which means there is no link. Route: the list of +// nodes that give the shortest route from a node to a distant node. Routes are calculated using a standard A* search +// based on links. // -// Edge types (TravelNodePathType): -// walk(1) — Walk via navmesh waypoints (stored in DB) -// portal(2) — AreaTrigger teleport (auto-discovered at startup) -// transport(3) — Boat/zeppelin (auto-discovered from MO_TRANSPORT) -// flightPath(4) — Taxi flight between flight masters -// teleportSpell(5) — Spell-based teleport (e.g. mage portals) -// staticPortal(6) — Manually defined teleport link (DB only, not pruned by generation) -// flyingMount (7) — Use Bots Flying mount to travel (Not currently enabled) -// -// On server start saved nodes and links are loaded via TravelNodeMap::Init(). An index of nodes by zone is prepared -// (instead of scanning all ~4000 nodes), precomputes connected components for O(1) reachability checks, and builds -// a taxi BFS graph. Paths and routes are calculated on the fly and saved for future use. Nodes are only added at -// startup or via the console `.generate` command — runtime mutation was removed because taking a unique_lock -// caused 100-250ms contention spikes against bot threads. +// On server start saved nodes and links are loaded. Paths and routes are calculated on the fly but saved for future +// use. Nodes can be added and removed realtime however because bots access the nodes from different threads this +// requires a locking mechanism. // // Initially the current nodes have been made: // Flightmasters and Inns (Bots can use these to fast-travel so eventually they will be included in the route -// calculation) WorldBosses and Unique bosses in instances (These are logical places bots might want to go in +// calculation) WorldBosses and Unique bosses in instances (These are a logical places bots might want to go in // instances) Player start spawns (Obviously all lvl1 bots will spawn and move from here) Area triggers locations with // teleport and their teleport destinations (These used to travel in or between maps) Transports including elevators // (Again used to travel in and in maps) (sub)Zone means (These are the center most point for each sub-zone which is -// good for global coverage). +// good for global coverage) // -// To increase coverage/linking extra nodes must be manually created via the "playerbot travel generatenode" -// console command after importing the specified node. Current implementation places nodes on paths (including -// complete) at sub-zone transitions or randomly. After calculating possible links the node is removed if it -// does not create local coverage (.fullgenerate only). +// To increase coverage/linking extra nodes can be automatically be created. +// Current implentation places nodes on paths (including complete) at sub-zone transitions or randomly. +// After calculating possible links the node is removed if it does not create local coverage. // -// Travel Flow: -// -// GetFullPath finds nearest nodes (zone-indexed), runs A* to get a node route, then -// BuildPath assembles a flat TravelPath with typed waypoints (walk, portal, transport, flight). -// ExecuteTravelPlan iterates the path by stepIdx, dispatching on each point's PathNodeType. -// Cross-map travel is handled naturally by portal/transport edges in the A* graph. -// -// If setup cannot resolve (no node, no route, no flight), the bot teleports directly to the destination -// as a fallback. -// -// The use of hearthstones and mage teleporting was removed — it caused route mutations requiring locking that no longer made sense. Mage portals may be future item. -// -// Thread Safety: -// -// The node graph is immutable at runtime (no adds/removes after Init). A shared_timed_mutex (m_nMapMtx) still -// exists and shared_locks are taken in GetFullPath and GenerateWalkPath for safety, but since there are no -// runtime mutations these are effectively uncontested. The only exclusive locks are taken at startup -// (saveNodeStore) and by the debug dump command. -// - -constexpr float MAX_PATHFINDING_DISTANCE = 296.0f; enum class TravelNodePathType : uint8 { @@ -95,20 +59,21 @@ enum class TravelNodePathType : uint8 portal = 2, transport = 3, flightPath = 4, - teleportSpell = 5, - staticPortal = 6, - flyingMount = 7 + teleportSpell = 5 }; // A connection between two nodes. class TravelNodePath { public: + // Legacy Constructor for travelnodestore + // TravelNodePath(float distance1, float extraCost1, bool portal1 = false, uint32 portalId1 = 0, bool transport1 = + // false, bool calculated = false, uint8 maxLevelMob1 = 0, uint8 maxLevelAlliance1 = 0, uint8 maxLevelHorde1 = 0, + // float swimDistance1 = 0, bool flightPath1 = false); + // Constructor - TravelNodePath(float distance = 0.1f, float extraCost = 0, - uint8 pathType = (uint8)TravelNodePathType::walk, - uint32 pathObject = 0, bool calculated = false, - std::vector maxLevelCreature = {0, 0, 0}, + TravelNodePath(float distance = 0.1f, float extraCost = 0, uint8 pathType = (uint8)TravelNodePathType::walk, + uint32 pathObject = 0, bool calculated = false, std::vector maxLevelCreature = {0, 0, 0}, float swimDistance = 0) : extraCost(extraCost), calculated(calculated), @@ -120,7 +85,7 @@ public: { if (pathType != (uint8)TravelNodePathType::walk) complete = true; - } + }; TravelNodePath(TravelNodePath* basePath) { @@ -133,11 +98,11 @@ public: swimDistance = basePath->swimDistance; pathType = basePath->pathType; pathObject = basePath->pathObject; - } + }; // Getters bool getComplete() { return complete || pathType != TravelNodePathType::walk; } - std::vector GetPath() { return path; } + std::vector getPath() { return path; } TravelNodePathType getPathType() { return pathType; } uint32 getPathObject() { return pathObject; } @@ -165,6 +130,9 @@ public: extraCost = distance / speed; } + // void setPortal(bool portal1, uint32 portalId1 = 0) { portal = portal1; portalId = portalId1; } + // void setTransport(bool transport1) { transport = transport1; } + void setPathType(TravelNodePathType pathType1) { pathType = pathType1; } void setPathObject(uint32 pathObject1) { pathObject = pathObject1; } @@ -218,10 +186,9 @@ class TravelNode { public: // Constructors - TravelNode() {} + TravelNode(){}; - TravelNode(WorldPosition point1, std::string const nodeName1 = "Travel Node", - bool important1 = false) + TravelNode(WorldPosition point1, std::string const nodeName1 = "Travel Node", bool important1 = false) { nodeName = nodeName1; point = point1; @@ -240,11 +207,11 @@ public: void setPoint(WorldPosition point1) { point = point1; } // Getters - std::string const getName() { return nodeName; } - WorldPosition* getPosition() { return &point; } + std::string const getName() { return nodeName; }; + WorldPosition* getPosition() { return &point; }; std::unordered_map* getPaths() { return &paths; } std::unordered_map* getLinks() { return &links; } - bool isImportant() { return important; } + bool isImportant() { return important; }; bool isLinked() { return linked; } bool isTransport() @@ -268,8 +235,7 @@ public: bool isPortal() { for (auto const& link : *getLinks()) - if (link.second->getPathType() == TravelNodePathType::portal || - link.second->getPathType() == TravelNodePathType::staticPortal) + if (link.second->getPathType() == TravelNodePathType::portal) return true; return false; @@ -285,25 +251,17 @@ public: } // WorldLocation shortcuts - uint32 GetMapId() { return point.GetMapId(); } + uint32 getMapId() { return point.GetMapId(); } float getX() { return point.GetPositionX(); } float getY() { return point.GetPositionY(); } float getZ() { return point.GetPositionZ(); } float getO() { return point.GetOrientation(); } float getDistance(WorldPosition pos) { return point.distance(pos); } - float getDistance(TravelNode* node) - { - return point.distance(node->getPosition()); - } - float fDist(TravelNode* node) - { - return point.fDist(node->getPosition()); - } + float getDistance(TravelNode* node) { return point.distance(node->getPosition()); } + float fDist(TravelNode* node) { return point.fDist(node->getPosition()); } float fDist(WorldPosition pos) { return point.fDist(pos); } - TravelNodePath* setPathTo(TravelNode* node, - TravelNodePath path = TravelNodePath(), - bool isLink = true) + TravelNodePath* setPathTo(TravelNode* node, TravelNodePath path = TravelNodePath(), bool isLink = true) { if (this != node) { @@ -317,20 +275,10 @@ public: return nullptr; } - bool hasPathTo(TravelNode* node) - { - return paths.find(node) != paths.end(); - } - TravelNodePath* getPathTo(TravelNode* node) - { - return &paths[node]; - } - bool hasCompletePathTo(TravelNode* node) - { - return hasPathTo(node) && getPathTo(node)->getComplete(); - } - TravelNodePath* BuildPath(TravelNode* endNode, Unit* bot, - bool postProcess = false); + bool hasPathTo(TravelNode* node) { return paths.find(node) != paths.end(); } + TravelNodePath* getPathTo(TravelNode* node) { return &paths[node]; } + bool hasCompletePathTo(TravelNode* node) { return hasPathTo(node) && getPathTo(node)->getComplete(); } + TravelNodePath* buildPath(TravelNode* endNode, Unit* bot, bool postProcess = false); void setLinkTo(TravelNode* node, float distance = 0.1f) { @@ -343,18 +291,9 @@ public: } } - bool hasLinkTo(TravelNode* node) - { - return links.find(node) != links.end(); - } - float linkCostTo(TravelNode* node) - { - return paths.find(node)->second.getDistance(); - } - float linkDistanceTo(TravelNode* node) - { - return paths.find(node)->second.getDistance(); - } + bool hasLinkTo(TravelNode* node) { return links.find(node) != links.end(); } + float linkCostTo(TravelNode* node) { return paths.find(node)->second.getDistance(); } + float linkDistanceTo(TravelNode* node) { return paths.find(node)->second.getDistance(); } void removeLinkTo(TravelNode* node, bool removePaths = false); bool isEqual(TravelNode* compareNode); @@ -365,8 +304,7 @@ public: bool cropUselessLinks(); // Returns all nodes that can be reached from this node. - std::vector getNodeMap(bool importantOnly = false, - std::vector ignoreNodes = {}); + std::vector getNodeMap(bool importantOnly = false, std::vector ignoreNodes = {}); // Checks if it is even possible to route to this node. bool hasRouteTo(TravelNode* node) @@ -376,10 +314,7 @@ public: routes[mNode] = true; return routes.find(node) != routes.end(); - } - - void clearRoutes() { routes.clear(); } - void setRouteTo(TravelNode* node) { routes[node] = true; } + }; void print(bool printFailed = true); @@ -409,8 +344,24 @@ protected: // uint32 transportId = 0; }; +class PortalNode : public TravelNode +{ +public: + PortalNode(TravelNode* baseNode) : TravelNode(baseNode){}; + + void SetPortal(TravelNode* baseNode, TravelNode* endNode, uint32 portalSpell) + { + nodeName = baseNode->getName(); + point = *baseNode->getPosition(); + paths.clear(); + links.clear(); + TravelNodePath path(0.1f, 0.1f, (uint8)TravelNodePathType::teleportSpell, portalSpell, true); + setPathTo(endNode, path); + }; +}; + // Route step type -enum class PathNodeType : uint8 +enum PathNodeType { NODE_PREPATH = 0, NODE_PATH = 1, @@ -418,14 +369,13 @@ enum class PathNodeType : uint8 NODE_PORTAL = 3, NODE_TRANSPORT = 4, NODE_FLIGHTPATH = 5, - NODE_TELEPORT = 6, - NODE_FLYING_MOUNT = 7 + NODE_TELEPORT = 6 }; struct PathNodePoint { WorldPosition point; - PathNodeType type = PathNodeType::NODE_PATH; + PathNodeType type = NODE_PATH; uint32 entry = 0; }; @@ -433,31 +383,24 @@ struct PathNodePoint class TravelPath { public: - TravelPath() {} - TravelPath(std::vector fullPath1) - { - fullPath = fullPath1; - } - TravelPath(std::vector path, - PathNodeType type = PathNodeType::NODE_PATH, - uint32 entry = 0) + TravelPath(){}; + TravelPath(std::vector fullPath1) { fullPath = fullPath1; } + TravelPath(std::vector path, PathNodeType type = NODE_PATH, uint32 entry = 0) { addPath(path, type, entry); } void addPoint(PathNodePoint point) { fullPath.push_back(point); } - void addPoint(WorldPosition point, - PathNodeType type = PathNodeType::NODE_PATH, - uint32 entry = 0) + void addPoint(WorldPosition point, PathNodeType type = NODE_PATH, uint32 entry = 0) { fullPath.push_back(PathNodePoint{point, type, entry}); } - void addPath(std::vector path, - PathNodeType type = PathNodeType::NODE_PATH, - uint32 entry = 0) + void addPath(std::vector path, PathNodeType type = NODE_PATH, uint32 entry = 0) { for (auto& p : path) + { fullPath.push_back(PathNodePoint{p, type, entry}); + }; } void addPath(std::vector newPath) { @@ -465,11 +408,8 @@ public: } void clear() { fullPath.clear(); } - bool empty() const { return fullPath.empty(); } - size_t size() const { return fullPath.size(); } - const PathNodePoint& operator[](size_t idx) const { return fullPath[idx]; } - std::vector GetPath() { return fullPath; } - const std::vector& GetPathRef() const { return fullPath; } + bool empty() { return fullPath.empty(); } + std::vector getPath() { return fullPath; } WorldPosition getFront() { return fullPath.front().point; } WorldPosition getBack() { return fullPath.back().point; } @@ -479,9 +419,13 @@ public: for (auto const& p : fullPath) retVec.push_back(p.point); return retVec; - } + }; bool makeShortCut(WorldPosition startPos, float maxDist); + bool shouldMoveToNextPoint(WorldPosition startPos, std::vector::iterator beg, + std::vector::iterator ed, std::vector::iterator p, + float& moveDist, float maxDist); + WorldPosition getNextPoint(WorldPosition startPos, float maxDist, TravelNodePathType& pathType, uint32& entry); std::ostringstream const print(); @@ -494,25 +438,17 @@ class TravelNodeRoute { public: TravelNodeRoute() {} - TravelNodeRoute(std::vector nodes1) - { - nodes = nodes1; - } + TravelNodeRoute(std::vector nodes1) { nodes = nodes1; /*currentNode = route.begin();*/ } bool isEmpty() { return nodes.empty(); } - bool hasNode(TravelNode* node) - { - return findNode(node) != nodes.end(); - } + bool hasNode(TravelNode* node) { return findNode(node) != nodes.end(); } float getTotalDistance(); std::vector getNodes() { return nodes; } - TravelPath BuildPath( - std::vector pathToStart = {}, - std::vector pathToEnd = {}, - Unit* bot = nullptr); + TravelPath buildPath(std::vector pathToStart = {}, std::vector pathToEnd = {}, + Unit* bot = nullptr); std::ostringstream const print(); @@ -531,47 +467,12 @@ public: TravelNodeStub(TravelNode* dataNode1) { dataNode = dataNode1; } TravelNode* dataNode; - float totalCost = 0.0; - float costFromStart = 0.0; - float heuristic = 0.0; - bool open = false; - bool closed = false; + float m_f = 0.0, m_g = 0.0, m_h = 0.0; + bool open = false, close = false; TravelNodeStub* parent = nullptr; uint32 currentGold = 0; }; -struct TravelPlan -{ - WorldPosition destination; - - // Flat waypoint path built upfront by GetFullPath: - TravelPath steps; - uint32 stepIdx{0}; - - // Spline scratch (used by executor): - std::vector walkPoints; - bool splineActive{false}; - uint32 splineStartTime{0}; - uint32 expectedDuration{0}; - - // Taxi scratch: - std::vector route; - - bool IsActive() const { return !steps.empty(); } - - void Reset() - { - destination = WorldPosition(); - steps.clear(); - stepIdx = 0; - walkPoints.clear(); - splineActive = false; - splineStartTime = 0; - expectedDuration = 0; - route.clear(); - } -}; - // The container of all nodes. class TravelNodeMap { @@ -583,18 +484,14 @@ public: return instance; } - TravelNode* addNode(WorldPosition pos, - std::string const preferedName = "Travel Node", - bool isImportant = false, - bool checkDuplicate = true, - bool transport = false, - uint32 transportId = 0); + TravelNode* addNode(WorldPosition pos, std::string const preferedName = "Travel Node", bool isImportant = false, + bool checkDuplicate = true, bool transport = false, uint32 transportId = 0); void removeNode(TravelNode* node); bool removeNodes() { if (m_nMapMtx.try_lock_for(std::chrono::seconds(10))) { - for (auto& node : nodes) + for (auto& node : m_nodes) removeNode(node); m_nMapMtx.unlock(); @@ -602,32 +499,28 @@ public: } return false; - } + }; void fullLinkNode(TravelNode* startNode, Unit* bot); // Get all nodes - std::vector getNodes() { return nodes; } + std::vector getNodes() { return m_nodes; } std::vector getNodes(WorldPosition pos, float range = -1); // Find nearest node. TravelNode* getNode(TravelNode* sameNode) { - for (auto& node : nodes) + for (auto& node : m_nodes) { - if (node->getName() == sameNode->getName() - && node->getPosition() == sameNode->getPosition()) + if (node->getName() == sameNode->getName() && node->getPosition() == sameNode->getPosition()) return node; } return nullptr; } - TravelNode* getNode(WorldPosition pos, - std::vector& ppath, - Unit* bot = nullptr, float range = -1); - TravelNode* getNode(WorldPosition pos, Unit* bot = nullptr, - float range = -1) + TravelNode* getNode(WorldPosition pos, std::vector& ppath, Unit* bot = nullptr, float range = -1); + TravelNode* getNode(WorldPosition pos, Unit* bot = nullptr, float range = -1) { std::vector ppath; return getNode(pos, ppath, bot, range); @@ -643,16 +536,15 @@ public: return rNodes[urand(0, rNodes.size() - 1)]; } - // Finds the best nodePath between two nodes (A* over the node graph) - TravelNodeRoute GetNodeRoute(TravelNode* start, TravelNode* goal, - Player* bot); + // Finds the best nodePath between two nodes + TravelNodeRoute getRoute(TravelNode* start, TravelNode* goal, Player* bot = nullptr); - // Find the nearest start/end nodes for two world positions - TravelNodeRoute GetNearestNodes(WorldPosition startPos, - WorldPosition endPos, - std::vector& startPath, - Player* bot = nullptr); + // Find the best node between two positions + TravelNodeRoute getRoute(WorldPosition startPos, WorldPosition endPos, std::vector& startPath, + Player* bot = nullptr); + // Find the full path between those locations + static TravelPath getFullPath(WorldPosition startPos, WorldPosition endPos, Player* bot = nullptr); // Manage/update nodes void manageNodes(Unit* bot, bool mapFull = false); @@ -671,17 +563,15 @@ public: void removeUselessPaths(); void calculatePathCosts(); void generateTaxiPaths(); - void generatePaths(bool fullGen = false); + void generatePaths(); void generateAll(); - void Init(); - void printMap(); void printNodeStore(); void saveNodeStore(); - void LoadNodeStore(); + void loadNodeStore(); bool cropUselessNode(TravelNode* startNode); TravelNode* addZoneLinkNode(TravelNode* startNode); @@ -694,25 +584,8 @@ public: void InitTaxiGraph(); std::vector FindTaxiPath(uint32 fromNode, uint32 toNode); - void BuildZoneIndex(); - void PrecomputeReachability(); - - TravelNode* GetNearestNodeInZone(WorldPosition pos, uint32 zoneId); - TravelNode* GetNearestNodeOnMap(WorldPosition pos); - - bool GetFullPath(TravelPlan& plan, WorldPosition botPos, - uint32 botZoneId, uint32 teamId, - WorldPosition destination); - - // Resolve A* route between two world positions (returns node vector) - std::vector ResolveRoute(WorldPosition startPos, - WorldPosition endPos); - - // Get stored walk points for one edge (from→to). Empty if no path. - std::vector GetEdgeWalkPoints(TravelNode* from, - TravelNode* to); - std::shared_timed_mutex m_nMapMtx; + std::unordered_map> teleportNodes; private: TravelNodeMap() = default; @@ -728,18 +601,13 @@ private: void BuildTaxiGraph(); void ComputeAllPaths(); std::unordered_map BFS(uint32 startNode); - std::vector BuildPath( - uint32 fromNode, uint32 toNode, - const std::unordered_map& parentMap); + std::vector BuildPath(uint32 fromNode, uint32 toNode, + const std::unordered_map& parentMap); - std::unordered_map> m_taxiGraph; - std::map>> - m_taxiPathCache; + std::unordered_map> taxiGraph; + std::map>> taxiPathCache; - std::vector nodes; - - std::unordered_map> m_zoneIndex; - std::unordered_map> m_mapIndex; + std::vector m_nodes; std::vector> mapOffsets; diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 4c8979ace..8c8343db2 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -644,7 +644,6 @@ bool PlayerbotAIConfig::Initialize() autoTeleportForLevel = sConfigMgr->GetOption("AiPlayerbot.AutoTeleportForLevel", false); autoDoQuests = sConfigMgr->GetOption("AiPlayerbot.AutoDoQuests", true); enableNewRpgStrategy = sConfigMgr->GetOption("AiPlayerbot.EnableNewRpgStrategy", true); - enableTravelNodes = sConfigMgr->GetOption("AiPlayerbot.EnableTravelNodes", false); RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15); RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 8b5dc0922..4e758e79e 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -368,7 +368,6 @@ public: bool autoLearnTrainerSpells; bool autoDoQuests; bool enableNewRpgStrategy; - bool enableTravelNodes; std::unordered_map RpgStatusProbWeight; bool syncLevelWithPlayers; bool autoLearnQuestSpells; diff --git a/src/Script/PlayerbotCommandScript.cpp b/src/Script/PlayerbotCommandScript.cpp index 105f8a28f..a7a073952 100644 --- a/src/Script/PlayerbotCommandScript.cpp +++ b/src/Script/PlayerbotCommandScript.cpp @@ -20,7 +20,6 @@ #include "PlayerbotMgr.h" #include "RandomPlayerbotMgr.h" #include "ScriptMgr.h" -#include "TravelNode.h" using namespace Acore::ChatCommands; @@ -42,16 +41,11 @@ public: {"unlink", HandleUnlinkAccountCommand, SEC_PLAYER, Console::No}, }; - static ChatCommandTable playerbotsTravelCommandTable = { - {"generatenode", HandleGenerateTravelNodesCommand, SEC_GAMEMASTER, Console::Yes}, - }; - static ChatCommandTable playerbotsCommandTable = { {"bot", HandlePlayerbotCommand, SEC_PLAYER, Console::No}, {"gtask", HandleGuildTaskCommand, SEC_GAMEMASTER, Console::Yes}, {"pmon", HandlePerfMonCommand, SEC_GAMEMASTER, Console::Yes}, {"rndbot", HandleRandomPlayerbotCommand, SEC_GAMEMASTER, Console::Yes}, - {"travel", playerbotsTravelCommandTable}, {"debug", playerbotsDebugCommandTable}, {"account", playerbotsAccountCommandTable}, }; @@ -112,15 +106,6 @@ public: return true; } - static bool HandleGenerateTravelNodesCommand(ChatHandler* handler, char const* /*args*/) - { - handler->PSendSysMessage("Regenerating travel node paths..."); - LOG_INFO("playerbots", "Manual travel node regeneration started via console command."); - sTravelNodeMap.generateAll(); - handler->PSendSysMessage("Travel node regeneration complete. Paths saved to database."); - return true; - } - static bool HandleDebugBGCommand(ChatHandler* handler, char const* args) { return BGTactics::HandleConsoleCommand(handler, args);