diff --git a/src/modules/Playerbot/AI/BotAI.cpp b/src/modules/Playerbot/AI/BotAI.cpp index 8ba00cb27..cbca5e4b8 100644 --- a/src/modules/Playerbot/AI/BotAI.cpp +++ b/src/modules/Playerbot/AI/BotAI.cpp @@ -183,6 +183,15 @@ BotAI::BotAI(Player* bot, bool instanceOnlyMode) // Bots far from human players will have reduced update frequency _aiUpdateThrottler = std::make_unique(_bot, this); + // NEW: Initialize movement controller and register with global manager + if (_bot) + { + _movementController = std::make_unique(_bot); + sBotMovementManager->RegisterController(_bot); + TC_LOG_DEBUG("module.playerbot.movement", "BotAI: Movement controller initialized for bot {}", + _cachedBotGuid.ToString()); + } + // Initialize default strategies for basic functionality InitializeDefaultStrategies(); @@ -312,6 +321,15 @@ BotAI::~BotAI() // Phase 4: CRITICAL - Unsubscribe from all event buses to prevent dangling pointers UnsubscribeFromEventBuses(); + // NEW: Unregister movement controller from global manager + // CRITICAL: Use _cachedBotGuid instead of _bot->GetGUID() as _bot may be dangling + if (_movementController && !_cachedBotGuid.IsEmpty()) + { + sBotMovementManager->UnregisterController(_cachedBotGuid); + TC_LOG_DEBUG("module.playerbot.movement", "BotAI: Movement controller unregistered for bot {}", + _cachedBotGuid.ToString()); + } + // ======================================================================== // PHASE 6: GAME SYSTEMS FACADE - Automatic Manager Cleanup // ======================================================================== @@ -525,6 +543,24 @@ void BotAI::UpdateAI(uint32 diff) if (auto deathRecovery = GetDeathRecoveryManager()) deathRecovery->Update(diff); + // ======================================================================== + // MOVEMENT CONTROLLER - Update validated movement system FIRST + // ======================================================================== + // NEW: Update movement controller for validated pathfinding and stuck detection + // This runs after death recovery but before normal AI to ensure movement is always updated + if (_movementController && _bot->IsAlive()) + { + _movementController->Update(diff); + + // Check for stuck state and log for debugging + if (_movementController->IsStuck()) + { + TC_LOG_DEBUG("module.playerbot.movement", "Bot {} is stuck, recovery is being handled by controller", + _bot->GetName()); + // Recovery is handled internally by the movement controller + } + } + // PRIORITY: If bot is in death recovery, skip expensive AI updates // Death recovery handles its own movement (corpse run), so we don't need strategies/combat // But we still allow managers to update (see PHASE 5) to prevent system freezing @@ -2196,4 +2232,65 @@ uint32 BotAI::GetAIThrottleTier() const return static_cast(_aiUpdateThrottler->GetCurrentTier()); } +// ============================================================================ +// MOVEMENT SYSTEM INTEGRATION - NEW: Validated movement helper methods +// ============================================================================ + +bool BotAI::MoveTo(Position const& dest, bool validated) +{ + if (!_movementController) + { + TC_LOG_WARN("module.playerbot.movement", "BotAI::MoveTo: Movement controller not initialized for bot {}", + _bot ? _bot->GetName() : "UNKNOWN"); + return false; + } + + if (validated) + { + // Use validated pathfinding with ground/collision/liquid checks + return _movementController->MoveToPosition(dest, false); + } + else + { + // Fallback to unvalidated movement (legacy behavior) + if (_bot && _bot->IsInWorld()) + { + _bot->GetMotionMaster()->MovePoint(0, dest); + return true; + } + return false; + } +} + +bool BotAI::MoveToUnit(::Unit* target, float distance) +{ + if (!_movementController) + { + TC_LOG_WARN("module.playerbot.movement", "BotAI::MoveToUnit: Movement controller not initialized for bot {}", + _bot ? _bot->GetName() : "UNKNOWN"); + return false; + } + + if (!target || !target->IsInWorld()) + { + TC_LOG_DEBUG("module.playerbot.movement", "BotAI::MoveToUnit: Invalid target for bot {}", + _bot ? _bot->GetName() : "UNKNOWN"); + return false; + } + + // Use validated follow movement + return _movementController->MoveFollow(target, distance, 0.0f); +} + +bool BotAI::IsMovementBlocked() const +{ + // Movement is blocked if controller indicates stuck or invalid state + return _movementController && _movementController->IsStuck(); +} + +bool BotAI::IsStuck() const +{ + return _movementController && _movementController->IsStuck(); +} + } // namespace Playerbot \ No newline at end of file diff --git a/src/modules/Playerbot/AI/BotAI.h b/src/modules/Playerbot/AI/BotAI.h index b306d898d..d02df730b 100644 --- a/src/modules/Playerbot/AI/BotAI.h +++ b/src/modules/Playerbot/AI/BotAI.h @@ -25,6 +25,8 @@ #include "Core/Events/IEventHandler.h" #include "Core/Managers/IGameSystemsManager.h" #include "Advanced/GroupCoordinator.h" +#include "Movement/BotMovement/Core/BotMovementController.h" +#include "Movement/BotMovement/Core/BotMovementManager.h" #include #include #include @@ -384,11 +386,22 @@ public: // MOVEMENT CONTROL - Strategy-driven movement // ======================================================================== + // Legacy movement methods (keep for backward compatibility) void MoveTo(float x, float y, float z); void Follow(::Unit* target, float distance = 5.0f); void StopMovement(); bool IsMoving() const; + // NEW: Movement System Integration - Validated pathfinding + BotMovementController* GetMovementController() { return _movementController.get(); } + BotMovementController const* GetMovementController() const { return _movementController.get(); } + + // Movement convenience methods with validation + bool MoveTo(Position const& dest, bool validated = true); + bool MoveToUnit(::Unit* target, float distance = 0.0f); + bool IsMovementBlocked() const; + bool IsStuck() const; + // ======================================================================== // GAME SYSTEM MANAGERS - Quest, profession, trade management (Phase 6: Delegation) // ======================================================================== @@ -1023,6 +1036,9 @@ protected: // ST-1: Adaptive AI Update Throttling - reduces CPU for bots far from human players std::unique_ptr _aiUpdateThrottler; + // Movement System Integration - Validated pathfinding and state machine + std::unique_ptr _movementController; + // Performance tracking mutable PerformanceMetrics _performanceMetrics; diff --git a/src/modules/Playerbot/Spatial/PathCache.cpp b/src/modules/Playerbot/Spatial/PathCache.cpp index bd3db0257..c70edd416 100644 --- a/src/modules/Playerbot/Spatial/PathCache.cpp +++ b/src/modules/Playerbot/Spatial/PathCache.cpp @@ -9,6 +9,9 @@ #include "PathCache.h" #include "Log.h" +#include "Movement/BotMovement/Pathfinding/ValidatedPathGenerator.h" +#include "Movement/BotMovement/Core/BotMovementConfig.h" +#include "Movement/BotMovement/Core/BotMovementManager.h" #include namespace Playerbot @@ -70,6 +73,50 @@ PathCache::PathResult PathCache::CalculateNewPath(Position const& src, Position { PathResult result; + // NEW: Use ValidatedPathGenerator if BotMovement system is enabled + if (sBotMovementManager->GetConfig().IsEnabled()) + { + ValidatedPathGenerator validatedPath(owner); + ValidatedPath vpResult = validatedPath.CalculateValidatedPath(src, dest, false); + + if (vpResult.IsValid()) + { + // Success - validated path found + result.points = vpResult.points; + result.pathType = vpResult.pathType; + result.length = validatedPath.GetPathLength(); + result.timestamp = ::std::chrono::steady_clock::now(); + + TC_LOG_DEBUG("module.playerbot.movement", + "ValidatedPath SUCCESS: {} waypoints, type={}, validated={}", + result.points.size(), + static_cast(result.pathType), + vpResult.validationResult.isValid); + + return result; + } + else + { + // Validation failed - log reason and fallback to legacy + TC_LOG_WARN("module.playerbot.movement", + "ValidatedPath FAILED: reason='{}', falling back to legacy pathfinding", + vpResult.validationResult.failureReason); + + // Fallback to legacy pathfinding + return CalculateNewPathLegacy(src, dest, owner); + } + } + else + { + // BotMovement system disabled - use legacy pathfinding + return CalculateNewPathLegacy(src, dest, owner); + } +} + +PathCache::PathResult PathCache::CalculateNewPathLegacy(Position const& src, Position const& dest, WorldObject const* owner) +{ + PathResult result; + // Create PathGenerator instance PathGenerator path(owner); @@ -94,6 +141,11 @@ PathCache::PathResult PathCache::CalculateNewPath(Position const& src, Position result.length = path.GetPathLength(); result.timestamp = ::std::chrono::steady_clock::now(); + TC_LOG_TRACE("module.playerbot.movement", + "Legacy PathGenerator: {} waypoints, type={}", + result.points.size(), + static_cast(result.pathType)); + return result; } diff --git a/src/modules/Playerbot/Spatial/PathCache.h b/src/modules/Playerbot/Spatial/PathCache.h index cf9c8f1c5..75ce7712a 100644 --- a/src/modules/Playerbot/Spatial/PathCache.h +++ b/src/modules/Playerbot/Spatial/PathCache.h @@ -250,7 +250,7 @@ private: void EvictOldest(); /** - * @brief Calculate new path using TrinityCore PathGenerator + * @brief Calculate new path using ValidatedPathGenerator (or legacy fallback) * * @param src Source position * @param dest Destination position @@ -258,13 +258,34 @@ private: * @return PathResult with waypoints and path type * * IMPLEMENTATION: - * - Create PathGenerator instance - * - Call CalculatePath() + * - Check if BotMovement system is enabled + * - If enabled: Use ValidatedPathGenerator with ground/collision/liquid validation + * - If validation fails or system disabled: Fall back to CalculateNewPathLegacy * - Extract waypoints and path type * - Return result for caching */ PathResult CalculateNewPath(Position const& src, Position const& dest, WorldObject const* owner); + /** + * @brief Legacy pathfinding fallback using standard PathGenerator + * + * @param src Source position + * @param dest Destination position + * @param owner WorldObject performing pathfinding + * @return PathResult with waypoints and path type + * + * IMPLEMENTATION: + * - Create standard PathGenerator instance (no validation) + * - Call CalculatePath() + * - Extract waypoints and path type + * - Return result for caching + * + * USAGE: + * - Fallback when ValidatedPathGenerator fails validation + * - Used when BotMovement system is disabled + */ + PathResult CalculateNewPathLegacy(Position const& src, Position const& dest, WorldObject const* owner); + Map* _map; // Map pointer (not owned, must remain valid) ::std::unordered_map _cache; // Path cache (hash key → result) ::std::deque _lruQueue; // LRU access order (front = oldest, back = newest)