feat(movement): Port BotMovement system Phase 1 from playerbot-dev
Port movement system refactoring commits from TrinityCore playerbot-dev: - Phase 1.1: Core Infrastructure Setup (BotMovementDefines, ValidationResult) - Phase 1.2: Configuration System (BotMovementConfig) - Phase 1.3: BotMovementManager Singleton - Phase 1.6: BotMovementController Base Implementation - Phase 1.7: Integration Testing Movement System Components: - BotMovementManager: Singleton managing bot movement globally - BotMovementController: Per-bot movement control - BotMovementConfig: Configuration with validation levels and thresholds - PositionValidator: Position bounds and map ID validation - GroundValidator: Ground/terrain validation - PathCache: Path caching for performance - MovementMetrics: Movement statistics tracking - ValidationResult: Validation result structures Key Features: - ValidationLevel enum (None, Basic, Standard, Strict) - MovementStateType enum (Idle, Ground, Swimming, Flying, Falling, Stuck) - RecoveryLevel progressive recovery (5 levels from path recalculation to evade) - Stuck detection with configurable thresholds - Path caching with configurable TTL - NaN/Infinity coordinate detection - Map ID validation - Comprehensive integration tests Ported commits: - ad0cf3c6af Phase 1.1 - Core Infrastructure Setup - a819487d00 Phase 1.2 - Configuration System - 4abacbc805, f6aab87e7d, 7713626fc0 Phase 1.3 - BotMovementManager - f96836f550 Phase 1.6 - BotMovementController Base - 79ddf8479d Phase 1.7 - Integration Testing Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
3dbf17ae4b
commit
2277d32f74
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BotMovementConfig.h"
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
void BotMovementConfig::Load()
|
||||||
|
{
|
||||||
|
_enabled = sConfigMgr->GetBoolDefault("BotMovement.Enable", true);
|
||||||
|
|
||||||
|
uint32 validationLevelValue = sConfigMgr->GetIntDefault("BotMovement.ValidationLevel", 2);
|
||||||
|
if (validationLevelValue > static_cast<uint32>(ValidationLevel::Strict))
|
||||||
|
validationLevelValue = static_cast<uint32>(ValidationLevel::Standard);
|
||||||
|
_validationLevel = static_cast<ValidationLevel>(validationLevelValue);
|
||||||
|
|
||||||
|
_stuckPosThreshold = Milliseconds(sConfigMgr->GetIntDefault("BotMovement.StuckDetection.PositionThreshold", 3000));
|
||||||
|
_stuckDistThreshold = sConfigMgr->GetFloatDefault("BotMovement.StuckDetection.DistanceThreshold", 2.0f);
|
||||||
|
|
||||||
|
_maxRecoveryAttempts = sConfigMgr->GetIntDefault("BotMovement.Recovery.MaxAttempts", 5);
|
||||||
|
|
||||||
|
_pathCacheSize = sConfigMgr->GetIntDefault("BotMovement.PathCache.Size", 1000);
|
||||||
|
_pathCacheTTL = Seconds(sConfigMgr->GetIntDefault("BotMovement.PathCache.TTL", 60));
|
||||||
|
|
||||||
|
_debugLogLevel = sConfigMgr->GetIntDefault("BotMovement.Debug.LogLevel", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementConfig::Reload()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITYCORE_BOT_MOVEMENT_CONFIG_H
|
||||||
|
#define TRINITYCORE_BOT_MOVEMENT_CONFIG_H
|
||||||
|
|
||||||
|
#include "Define.h"
|
||||||
|
#include "Duration.h"
|
||||||
|
#include "BotMovementDefines.h"
|
||||||
|
|
||||||
|
class TC_GAME_API BotMovementConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BotMovementConfig() = default;
|
||||||
|
~BotMovementConfig() = default;
|
||||||
|
|
||||||
|
void Load();
|
||||||
|
void Reload();
|
||||||
|
|
||||||
|
bool IsEnabled() const { return _enabled; }
|
||||||
|
ValidationLevel GetValidationLevel() const { return _validationLevel; }
|
||||||
|
Milliseconds GetStuckPositionThreshold() const { return _stuckPosThreshold; }
|
||||||
|
float GetStuckDistanceThreshold() const { return _stuckDistThreshold; }
|
||||||
|
uint32 GetMaxRecoveryAttempts() const { return _maxRecoveryAttempts; }
|
||||||
|
uint32 GetPathCacheSize() const { return _pathCacheSize; }
|
||||||
|
Seconds GetPathCacheTTL() const { return _pathCacheTTL; }
|
||||||
|
uint32 GetDebugLogLevel() const { return _debugLogLevel; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _enabled = true;
|
||||||
|
ValidationLevel _validationLevel = ValidationLevel::Standard;
|
||||||
|
Milliseconds _stuckPosThreshold = Milliseconds(3000);
|
||||||
|
float _stuckDistThreshold = 2.0f;
|
||||||
|
uint32 _maxRecoveryAttempts = 5;
|
||||||
|
uint32 _pathCacheSize = 1000;
|
||||||
|
Seconds _pathCacheTTL = Seconds(60);
|
||||||
|
uint32 _debugLogLevel = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BotMovementController.h"
|
||||||
|
#include "Unit.h"
|
||||||
|
#include "Position.h"
|
||||||
|
|
||||||
|
BotMovementController::BotMovementController(Unit* owner)
|
||||||
|
: _owner(owner)
|
||||||
|
, _totalTimePassed(0)
|
||||||
|
{
|
||||||
|
if (_owner)
|
||||||
|
RecordPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
BotMovementController::~BotMovementController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementController::Update(uint32 diff)
|
||||||
|
{
|
||||||
|
_totalTimePassed += diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position const* BotMovementController::GetLastPosition() const
|
||||||
|
{
|
||||||
|
if (_positionHistory.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &_positionHistory.back().pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementController::RecordPosition()
|
||||||
|
{
|
||||||
|
if (!_owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Position currentPos = _owner->GetPosition();
|
||||||
|
_positionHistory.emplace_back(currentPos, _totalTimePassed);
|
||||||
|
|
||||||
|
if (_positionHistory.size() > MAX_POSITION_HISTORY)
|
||||||
|
_positionHistory.pop_front();
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_BOTMOVEMENTCONTROLLER_H
|
||||||
|
#define TRINITY_BOTMOVEMENTCONTROLLER_H
|
||||||
|
|
||||||
|
#include "Define.h"
|
||||||
|
#include "BotMovementDefines.h"
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
class Unit;
|
||||||
|
struct Position;
|
||||||
|
|
||||||
|
class TC_GAME_API BotMovementController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BotMovementController(Unit* owner);
|
||||||
|
~BotMovementController();
|
||||||
|
|
||||||
|
Unit* GetOwner() const { return _owner; }
|
||||||
|
void Update(uint32 diff);
|
||||||
|
|
||||||
|
Position const* GetLastPosition() const;
|
||||||
|
void RecordPosition();
|
||||||
|
|
||||||
|
std::deque<PositionSnapshot> const& GetPositionHistory() const { return _positionHistory; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Unit* _owner;
|
||||||
|
std::deque<PositionSnapshot> _positionHistory;
|
||||||
|
uint32 _totalTimePassed;
|
||||||
|
|
||||||
|
static constexpr size_t MAX_POSITION_HISTORY = 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_BOTMOVEMENTDEFINES_H
|
||||||
|
#define TRINITY_BOTMOVEMENTDEFINES_H
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Position.h"
|
||||||
|
|
||||||
|
struct PositionSnapshot
|
||||||
|
{
|
||||||
|
Position pos;
|
||||||
|
uint32 timestamp;
|
||||||
|
|
||||||
|
PositionSnapshot() : pos(), timestamp(0) { }
|
||||||
|
PositionSnapshot(Position const& p, uint32 time) : pos(p), timestamp(time) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MovementStateType : uint8
|
||||||
|
{
|
||||||
|
Idle = 0,
|
||||||
|
Ground,
|
||||||
|
Swimming,
|
||||||
|
Flying,
|
||||||
|
Falling,
|
||||||
|
Stuck
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ValidationFailureReason : uint8
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
InvalidPosition,
|
||||||
|
OutOfBounds,
|
||||||
|
InvalidMapId,
|
||||||
|
NoGroundHeight,
|
||||||
|
VoidPosition,
|
||||||
|
CollisionDetected,
|
||||||
|
PathBlocked,
|
||||||
|
DestinationUnreachable,
|
||||||
|
LiquidDanger,
|
||||||
|
UnsafeTerrain
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class StuckType : uint8
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
PositionStuck,
|
||||||
|
ProgressStuck,
|
||||||
|
CollisionStuck,
|
||||||
|
PathFailureStuck
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RecoveryLevel : uint8
|
||||||
|
{
|
||||||
|
Level1_RecalculatePath = 1,
|
||||||
|
Level2_BackupAndRetry = 2,
|
||||||
|
Level3_RandomNearbyPosition = 3,
|
||||||
|
Level4_TeleportToSafePosition = 4,
|
||||||
|
Level5_EvadeAndReset = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ValidationLevel : uint8
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Basic,
|
||||||
|
Standard,
|
||||||
|
Strict
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BotMovementManager.h"
|
||||||
|
#include "BotMovementController.h"
|
||||||
|
#include "Unit.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
BotMovementManager::BotMovementManager()
|
||||||
|
{
|
||||||
|
_config.Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
BotMovementManager::~BotMovementManager()
|
||||||
|
{
|
||||||
|
for (auto const& [guid, controller] : _controllers)
|
||||||
|
delete controller;
|
||||||
|
|
||||||
|
_controllers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
BotMovementManager* BotMovementManager::Instance()
|
||||||
|
{
|
||||||
|
static BotMovementManager instance;
|
||||||
|
return &instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
BotMovementController* BotMovementManager::GetControllerForUnit(Unit* unit)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto itr = _controllers.find(unit->GetGUID());
|
||||||
|
if (itr != _controllers.end())
|
||||||
|
return itr->second;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BotMovementController* BotMovementManager::RegisterController(Unit* unit)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
{
|
||||||
|
TC_LOG_ERROR("movement.bot", "BotMovementManager::RegisterController - Attempted to register null unit");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid guid = unit->GetGUID();
|
||||||
|
|
||||||
|
auto itr = _controllers.find(guid);
|
||||||
|
if (itr != _controllers.end())
|
||||||
|
{
|
||||||
|
TC_LOG_WARN("movement.bot", "BotMovementManager::RegisterController - Controller already exists for unit {}", guid.ToString());
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
BotMovementController* controller = new BotMovementController(unit);
|
||||||
|
_controllers[guid] = controller;
|
||||||
|
|
||||||
|
TC_LOG_DEBUG("movement.bot", "BotMovementManager::RegisterController - Registered controller for unit {}", guid.ToString());
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementManager::UnregisterController(Unit* unit)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UnregisterController(unit->GetGUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementManager::UnregisterController(ObjectGuid const& guid)
|
||||||
|
{
|
||||||
|
auto itr = _controllers.find(guid);
|
||||||
|
if (itr == _controllers.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
TC_LOG_DEBUG("movement.bot", "BotMovementManager::UnregisterController - Unregistering controller for unit {}", guid.ToString());
|
||||||
|
|
||||||
|
delete itr->second;
|
||||||
|
_controllers.erase(itr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementManager::ReloadConfig()
|
||||||
|
{
|
||||||
|
TC_LOG_INFO("movement.bot", "BotMovementManager::ReloadConfig - Reloading bot movement configuration");
|
||||||
|
_config.Reload();
|
||||||
|
_globalCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MovementMetrics BotMovementManager::GetGlobalMetrics() const
|
||||||
|
{
|
||||||
|
return _metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotMovementManager::ResetMetrics()
|
||||||
|
{
|
||||||
|
TC_LOG_INFO("movement.bot", "BotMovementManager::ResetMetrics - Resetting global movement metrics");
|
||||||
|
_metrics.Reset();
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_BOTMOVEMENTMANAGER_H
|
||||||
|
#define TRINITY_BOTMOVEMENTMANAGER_H
|
||||||
|
|
||||||
|
#include "Define.h"
|
||||||
|
#include "ObjectGuid.h"
|
||||||
|
#include "BotMovementConfig.h"
|
||||||
|
#include "MovementMetrics.h"
|
||||||
|
#include "PathCache.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
class BotMovementController;
|
||||||
|
class Unit;
|
||||||
|
|
||||||
|
class TC_GAME_API BotMovementManager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
BotMovementManager();
|
||||||
|
~BotMovementManager();
|
||||||
|
|
||||||
|
public:
|
||||||
|
BotMovementManager(BotMovementManager const&) = delete;
|
||||||
|
BotMovementManager(BotMovementManager&&) = delete;
|
||||||
|
BotMovementManager& operator=(BotMovementManager const&) = delete;
|
||||||
|
BotMovementManager& operator=(BotMovementManager&&) = delete;
|
||||||
|
|
||||||
|
static BotMovementManager* Instance();
|
||||||
|
|
||||||
|
BotMovementController* GetControllerForUnit(Unit* unit);
|
||||||
|
BotMovementController* RegisterController(Unit* unit);
|
||||||
|
void UnregisterController(Unit* unit);
|
||||||
|
void UnregisterController(ObjectGuid const& guid);
|
||||||
|
|
||||||
|
BotMovementConfig const& GetConfig() const { return _config; }
|
||||||
|
void ReloadConfig();
|
||||||
|
|
||||||
|
PathCache* GetPathCache() { return &_globalCache; }
|
||||||
|
|
||||||
|
MovementMetrics GetGlobalMetrics() const;
|
||||||
|
void ResetMetrics();
|
||||||
|
|
||||||
|
private:
|
||||||
|
BotMovementConfig _config;
|
||||||
|
PathCache _globalCache;
|
||||||
|
std::unordered_map<ObjectGuid, BotMovementController*> _controllers;
|
||||||
|
MovementMetrics _metrics;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sBotMovementManager BotMovementManager::Instance()
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_MOVEMENTMETRICS_H
|
||||||
|
#define TRINITY_MOVEMENTMETRICS_H
|
||||||
|
|
||||||
|
#include "Define.h"
|
||||||
|
|
||||||
|
struct TC_GAME_API MovementMetrics
|
||||||
|
{
|
||||||
|
uint64 totalPathsCalculated = 0;
|
||||||
|
uint64 totalValidationFailures = 0;
|
||||||
|
uint64 totalStuckIncidents = 0;
|
||||||
|
uint64 totalRecoveryAttempts = 0;
|
||||||
|
uint64 totalRecoverySuccesses = 0;
|
||||||
|
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
totalPathsCalculated = 0;
|
||||||
|
totalValidationFailures = 0;
|
||||||
|
totalStuckIncidents = 0;
|
||||||
|
totalRecoveryAttempts = 0;
|
||||||
|
totalRecoverySuccesses = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_VALIDATIONRESULT_H
|
||||||
|
#define TRINITY_VALIDATIONRESULT_H
|
||||||
|
|
||||||
|
#include "BotMovementDefines.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct ValidationResult
|
||||||
|
{
|
||||||
|
bool isValid = false;
|
||||||
|
ValidationFailureReason failureReason = ValidationFailureReason::None;
|
||||||
|
std::string errorMessage;
|
||||||
|
|
||||||
|
ValidationResult() = default;
|
||||||
|
|
||||||
|
ValidationResult(bool valid, ValidationFailureReason reason = ValidationFailureReason::None, std::string message = "")
|
||||||
|
: isValid(valid), failureReason(reason), errorMessage(std::move(message))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValidationResult Success()
|
||||||
|
{
|
||||||
|
return ValidationResult(true, ValidationFailureReason::None, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValidationResult Failure(ValidationFailureReason reason, std::string message = "")
|
||||||
|
{
|
||||||
|
return ValidationResult(false, reason, std::move(message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_PATHCACHE_H
|
||||||
|
#define TRINITY_PATHCACHE_H
|
||||||
|
|
||||||
|
#include "Define.h"
|
||||||
|
|
||||||
|
class TC_GAME_API PathCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PathCache() = default;
|
||||||
|
~PathCache() = default;
|
||||||
|
|
||||||
|
void Clear() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GroundValidator.h"
|
||||||
|
#include "Unit.h"
|
||||||
|
#include "Map.h"
|
||||||
|
#include "Position.h"
|
||||||
|
#include "GridDefines.h"
|
||||||
|
#include "GameTime.h"
|
||||||
|
#include "MapDefines.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
std::unordered_map<uint64, GroundHeightCache> GroundValidator::_heightCache;
|
||||||
|
|
||||||
|
GroundValidator::GroundValidator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 GroundValidator::MakeCacheKey(uint32 mapId, float x, float y)
|
||||||
|
{
|
||||||
|
uint32 gridX = static_cast<uint32>(std::floor(x / 10.0f));
|
||||||
|
uint32 gridY = static_cast<uint32>(std::floor(y / 10.0f));
|
||||||
|
return (static_cast<uint64>(mapId) << 32) | (static_cast<uint64>(gridX) << 16) | gridY;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GroundValidator::GetGroundHeight(Unit const* unit)
|
||||||
|
{
|
||||||
|
if (!unit || !unit->IsInWorld())
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
|
||||||
|
Map* map = unit->GetMap();
|
||||||
|
if (!map)
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
|
||||||
|
float x = unit->GetPositionX();
|
||||||
|
float y = unit->GetPositionY();
|
||||||
|
float z = unit->GetPositionZ();
|
||||||
|
|
||||||
|
uint64 cacheKey = MakeCacheKey(map->GetId(), x, y);
|
||||||
|
uint32 currentTime = GameTime::GetGameTimeMS();
|
||||||
|
|
||||||
|
auto it = _heightCache.find(cacheKey);
|
||||||
|
if (it != _heightCache.end() && (currentTime - it->second.timestamp) < CACHE_LIFETIME_MS)
|
||||||
|
{
|
||||||
|
return it->second.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
float height = map->GetHeight(unit->GetPhaseShift(), x, y, z, true);
|
||||||
|
|
||||||
|
GroundHeightCache cache;
|
||||||
|
cache.height = height;
|
||||||
|
cache.timestamp = currentTime;
|
||||||
|
_heightCache[cacheKey] = cache;
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult GroundValidator::ValidateGroundHeight(Unit const* unit, float maxHeightDiff)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
{
|
||||||
|
return ValidationResult::Failure(
|
||||||
|
ValidationFailureReason::InvalidPosition,
|
||||||
|
"Unit is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unit->IsInWorld())
|
||||||
|
{
|
||||||
|
return ValidationResult::Failure(
|
||||||
|
ValidationFailureReason::InvalidPosition,
|
||||||
|
"Unit is not in world");
|
||||||
|
}
|
||||||
|
|
||||||
|
float x = unit->GetPositionX();
|
||||||
|
float y = unit->GetPositionY();
|
||||||
|
float z = unit->GetPositionZ();
|
||||||
|
|
||||||
|
float groundHeight = GetGroundHeight(unit);
|
||||||
|
|
||||||
|
if (groundHeight == INVALID_HEIGHT)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "No ground height found at position (" << x << ", " << y << ", " << z << ")";
|
||||||
|
return ValidationResult::Failure(ValidationFailureReason::NoGroundHeight, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groundHeight <= VOID_HEIGHT)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Void position detected at (" << x << ", " << y << ", " << z << "), ground height: " << groundHeight;
|
||||||
|
return ValidationResult::Failure(ValidationFailureReason::VoidPosition, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
float heightDiff = std::abs(z - groundHeight);
|
||||||
|
if (heightDiff > maxHeightDiff)
|
||||||
|
{
|
||||||
|
if (z < groundHeight - maxHeightDiff)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Position too far below ground at (" << x << ", " << y << ", " << z
|
||||||
|
<< "), ground height: " << groundHeight << ", diff: " << heightDiff;
|
||||||
|
return ValidationResult::Failure(ValidationFailureReason::InvalidPosition, oss.str());
|
||||||
|
}
|
||||||
|
else if (z > groundHeight + BOT_MAX_FALL_DISTANCE)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Position too far above ground at (" << x << ", " << y << ", " << z
|
||||||
|
<< "), ground height: " << groundHeight << ", diff: " << heightDiff;
|
||||||
|
return ValidationResult::Failure(ValidationFailureReason::InvalidPosition, oss.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroundValidator::IsVoidPosition(Unit const* unit)
|
||||||
|
{
|
||||||
|
if (!unit || !unit->IsInWorld())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
float groundHeight = GetGroundHeight(unit);
|
||||||
|
return (groundHeight == INVALID_HEIGHT || groundHeight <= VOID_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroundValidator::IsOnBridge(Unit const* unit)
|
||||||
|
{
|
||||||
|
if (!unit || !unit->IsInWorld())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Map* map = unit->GetMap();
|
||||||
|
if (!map)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float x = unit->GetPositionX();
|
||||||
|
float y = unit->GetPositionY();
|
||||||
|
float z = unit->GetPositionZ();
|
||||||
|
|
||||||
|
float heightWithVMap = map->GetHeight(unit->GetPhaseShift(), x, y, z, true);
|
||||||
|
float heightWithoutVMap = map->GetHeight(unit->GetPhaseShift(), x, y, z, false);
|
||||||
|
|
||||||
|
if (heightWithVMap == INVALID_HEIGHT || heightWithoutVMap == INVALID_HEIGHT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return std::abs(heightWithVMap - heightWithoutVMap) > 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroundValidator::IsUnsafeTerrain(Unit const* unit)
|
||||||
|
{
|
||||||
|
if (!unit || !unit->IsInWorld())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (IsVoidPosition(unit))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Map* map = unit->GetMap();
|
||||||
|
if (!map)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
float x = unit->GetPositionX();
|
||||||
|
float y = unit->GetPositionY();
|
||||||
|
float z = unit->GetPositionZ();
|
||||||
|
|
||||||
|
LiquidData liquidData;
|
||||||
|
ZLiquidStatus liquidStatus = map->GetLiquidStatus(unit->GetPhaseShift(), x, y, z, map_liquidHeaderTypeFlags::AllLiquids, &liquidData);
|
||||||
|
|
||||||
|
if (liquidStatus != LIQUID_MAP_NO_WATER)
|
||||||
|
{
|
||||||
|
if (liquidData.type_flags.HasFlag(map_liquidHeaderTypeFlags::Magma) || liquidData.type_flags.HasFlag(map_liquidHeaderTypeFlags::Slime))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroundValidator::ClearCache()
|
||||||
|
{
|
||||||
|
_heightCache.clear();
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_GROUNDVALIDATOR_H
|
||||||
|
#define TRINITY_GROUNDVALIDATOR_H
|
||||||
|
|
||||||
|
#include "ValidationResult.h"
|
||||||
|
#include "Define.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
struct Position;
|
||||||
|
class Unit;
|
||||||
|
class Map;
|
||||||
|
|
||||||
|
struct GroundHeightCache
|
||||||
|
{
|
||||||
|
float height = 0.0f;
|
||||||
|
uint32 timestamp = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TC_GAME_API GroundValidator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroundValidator();
|
||||||
|
~GroundValidator() = default;
|
||||||
|
|
||||||
|
static float GetGroundHeight(Unit const* unit);
|
||||||
|
|
||||||
|
static ValidationResult ValidateGroundHeight(Unit const* unit, float maxHeightDiff = 10.0f);
|
||||||
|
|
||||||
|
static bool IsVoidPosition(Unit const* unit);
|
||||||
|
static bool IsOnBridge(Unit const* unit);
|
||||||
|
static bool IsUnsafeTerrain(Unit const* unit);
|
||||||
|
|
||||||
|
void ClearCache();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr float VOID_HEIGHT = -500.0f;
|
||||||
|
static constexpr float BOT_MAX_FALL_DISTANCE = 50.0f;
|
||||||
|
static constexpr uint32 CACHE_LIFETIME_MS = 5000;
|
||||||
|
|
||||||
|
static std::unordered_map<uint64, GroundHeightCache> _heightCache;
|
||||||
|
|
||||||
|
static uint64 MakeCacheKey(uint32 mapId, float x, float y);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PositionValidator.h"
|
||||||
|
#include "Position.h"
|
||||||
|
#include "Unit.h"
|
||||||
|
#include "GridDefines.h"
|
||||||
|
#include "MapManager.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
ValidationResult PositionValidator::ValidateBounds(Position const& pos)
|
||||||
|
{
|
||||||
|
return ValidateBounds(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult PositionValidator::ValidateBounds(float x, float y, float z)
|
||||||
|
{
|
||||||
|
if (!Trinity::IsValidMapCoord(x, y, z))
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Position out of bounds: (" << x << ", " << y << ", " << z << ")";
|
||||||
|
return ValidationResult::Failure(ValidationFailureReason::OutOfBounds, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult PositionValidator::ValidateMapId(uint32 mapId)
|
||||||
|
{
|
||||||
|
if (!MapManager::IsValidMAP(mapId))
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Invalid map ID: " << mapId;
|
||||||
|
return ValidationResult::Failure(ValidationFailureReason::InvalidMapId, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult PositionValidator::ValidatePosition(uint32 mapId, Position const& pos)
|
||||||
|
{
|
||||||
|
return ValidatePosition(mapId, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult PositionValidator::ValidatePosition(uint32 mapId, float x, float y, float z)
|
||||||
|
{
|
||||||
|
ValidationResult mapResult = ValidateMapId(mapId);
|
||||||
|
if (!mapResult.isValid)
|
||||||
|
return mapResult;
|
||||||
|
|
||||||
|
ValidationResult boundsResult = ValidateBounds(x, y, z);
|
||||||
|
if (!boundsResult.isValid)
|
||||||
|
return boundsResult;
|
||||||
|
|
||||||
|
return ValidationResult::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult PositionValidator::ValidateUnitPosition(Unit const* unit)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
{
|
||||||
|
return ValidationResult::Failure(
|
||||||
|
ValidationFailureReason::InvalidPosition,
|
||||||
|
"Unit is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unit->IsInWorld())
|
||||||
|
{
|
||||||
|
return ValidationResult::Failure(
|
||||||
|
ValidationFailureReason::InvalidPosition,
|
||||||
|
"Unit is not in world");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidatePosition(unit->GetMapId(), *unit);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITY_POSITIONVALIDATOR_H
|
||||||
|
#define TRINITY_POSITIONVALIDATOR_H
|
||||||
|
|
||||||
|
#include "ValidationResult.h"
|
||||||
|
|
||||||
|
struct Position;
|
||||||
|
class Unit;
|
||||||
|
|
||||||
|
class TC_GAME_API PositionValidator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PositionValidator() = default;
|
||||||
|
~PositionValidator() = default;
|
||||||
|
|
||||||
|
static ValidationResult ValidateBounds(Position const& pos);
|
||||||
|
static ValidationResult ValidateBounds(float x, float y, float z);
|
||||||
|
|
||||||
|
static ValidationResult ValidateMapId(uint32 mapId);
|
||||||
|
|
||||||
|
static ValidationResult ValidatePosition(uint32 mapId, Position const& pos);
|
||||||
|
static ValidationResult ValidatePosition(uint32 mapId, float x, float y, float z);
|
||||||
|
|
||||||
|
static ValidationResult ValidateUnitPosition(Unit const* unit);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
|
||||||
|
#include "tc_catch2.h"
|
||||||
|
|
||||||
|
#include "BotMovementConfig.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
std::string CreateBotMovementConfig(std::map<std::string, std::string> const& map)
|
||||||
|
{
|
||||||
|
auto tempFileRel = boost::filesystem::unique_path("botmovement_test.ini");
|
||||||
|
auto tempFileAbs = boost::filesystem::temp_directory_path() / tempFileRel;
|
||||||
|
std::ofstream iniStream;
|
||||||
|
iniStream.open(tempFileAbs.c_str());
|
||||||
|
|
||||||
|
iniStream << "[worldserver]\n";
|
||||||
|
for (auto const& itr : map)
|
||||||
|
iniStream << itr.first << " = " << itr.second << "\n";
|
||||||
|
|
||||||
|
iniStream.close();
|
||||||
|
|
||||||
|
return tempFileAbs.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BotMovementConfig - Default Values", "[BotMovement][Config]")
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> config;
|
||||||
|
auto filePath = CreateBotMovementConfig(config);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
|
||||||
|
REQUIRE(err.empty());
|
||||||
|
|
||||||
|
BotMovementConfig botConfig;
|
||||||
|
botConfig.Load();
|
||||||
|
|
||||||
|
SECTION("Default values are correct")
|
||||||
|
{
|
||||||
|
REQUIRE(botConfig.IsEnabled() == true);
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::Standard);
|
||||||
|
REQUIRE(botConfig.GetStuckPositionThreshold() == Milliseconds(3000));
|
||||||
|
REQUIRE(botConfig.GetStuckDistanceThreshold() == 2.0f);
|
||||||
|
REQUIRE(botConfig.GetMaxRecoveryAttempts() == 5);
|
||||||
|
REQUIRE(botConfig.GetPathCacheSize() == 1000);
|
||||||
|
REQUIRE(botConfig.GetPathCacheTTL() == Seconds(60));
|
||||||
|
REQUIRE(botConfig.GetDebugLogLevel() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove(filePath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BotMovementConfig - Custom Values", "[BotMovement][Config]")
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> config;
|
||||||
|
config["BotMovement.Enable"] = "0";
|
||||||
|
config["BotMovement.ValidationLevel"] = "3";
|
||||||
|
config["BotMovement.StuckDetection.PositionThreshold"] = "5000";
|
||||||
|
config["BotMovement.StuckDetection.DistanceThreshold"] = "3.5";
|
||||||
|
config["BotMovement.Recovery.MaxAttempts"] = "10";
|
||||||
|
config["BotMovement.PathCache.Size"] = "2000";
|
||||||
|
config["BotMovement.PathCache.TTL"] = "120";
|
||||||
|
config["BotMovement.Debug.LogLevel"] = "4";
|
||||||
|
|
||||||
|
auto filePath = CreateBotMovementConfig(config);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
|
||||||
|
REQUIRE(err.empty());
|
||||||
|
|
||||||
|
BotMovementConfig botConfig;
|
||||||
|
botConfig.Load();
|
||||||
|
|
||||||
|
SECTION("Custom values are loaded correctly")
|
||||||
|
{
|
||||||
|
REQUIRE(botConfig.IsEnabled() == false);
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::Strict);
|
||||||
|
REQUIRE(botConfig.GetStuckPositionThreshold() == Milliseconds(5000));
|
||||||
|
REQUIRE(botConfig.GetStuckDistanceThreshold() == 3.5f);
|
||||||
|
REQUIRE(botConfig.GetMaxRecoveryAttempts() == 10);
|
||||||
|
REQUIRE(botConfig.GetPathCacheSize() == 2000);
|
||||||
|
REQUIRE(botConfig.GetPathCacheTTL() == Seconds(120));
|
||||||
|
REQUIRE(botConfig.GetDebugLogLevel() == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove(filePath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BotMovementConfig - Validation Level Bounds", "[BotMovement][Config]")
|
||||||
|
{
|
||||||
|
SECTION("Invalid validation level defaults to Standard")
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> config;
|
||||||
|
config["BotMovement.ValidationLevel"] = "99";
|
||||||
|
|
||||||
|
auto filePath = CreateBotMovementConfig(config);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
|
||||||
|
REQUIRE(err.empty());
|
||||||
|
|
||||||
|
BotMovementConfig botConfig;
|
||||||
|
botConfig.Load();
|
||||||
|
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::Standard);
|
||||||
|
|
||||||
|
std::remove(filePath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ValidationLevel::None is valid")
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> config;
|
||||||
|
config["BotMovement.ValidationLevel"] = "0";
|
||||||
|
|
||||||
|
auto filePath = CreateBotMovementConfig(config);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
|
||||||
|
REQUIRE(err.empty());
|
||||||
|
|
||||||
|
BotMovementConfig botConfig;
|
||||||
|
botConfig.Load();
|
||||||
|
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::None);
|
||||||
|
|
||||||
|
std::remove(filePath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ValidationLevel::Strict is valid")
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> config;
|
||||||
|
config["BotMovement.ValidationLevel"] = "3";
|
||||||
|
|
||||||
|
auto filePath = CreateBotMovementConfig(config);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
|
||||||
|
REQUIRE(err.empty());
|
||||||
|
|
||||||
|
BotMovementConfig botConfig;
|
||||||
|
botConfig.Load();
|
||||||
|
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::Strict);
|
||||||
|
|
||||||
|
std::remove(filePath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BotMovementConfig - Reload", "[BotMovement][Config]")
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> config;
|
||||||
|
config["BotMovement.Enable"] = "1";
|
||||||
|
config["BotMovement.ValidationLevel"] = "2";
|
||||||
|
|
||||||
|
auto filePath = CreateBotMovementConfig(config);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
REQUIRE(sConfigMgr->LoadInitial(filePath, std::vector<std::string>(), err));
|
||||||
|
REQUIRE(err.empty());
|
||||||
|
|
||||||
|
BotMovementConfig botConfig;
|
||||||
|
botConfig.Load();
|
||||||
|
|
||||||
|
REQUIRE(botConfig.IsEnabled() == true);
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::Standard);
|
||||||
|
|
||||||
|
std::ofstream iniStream;
|
||||||
|
iniStream.open(filePath.c_str(), std::ios::trunc);
|
||||||
|
iniStream << "[worldserver]\n";
|
||||||
|
iniStream << "BotMovement.Enable = 0\n";
|
||||||
|
iniStream << "BotMovement.ValidationLevel = 1\n";
|
||||||
|
iniStream.close();
|
||||||
|
|
||||||
|
std::vector<std::string> errors;
|
||||||
|
REQUIRE(sConfigMgr->Reload(errors));
|
||||||
|
REQUIRE(errors.empty());
|
||||||
|
|
||||||
|
botConfig.Reload();
|
||||||
|
|
||||||
|
REQUIRE(botConfig.IsEnabled() == false);
|
||||||
|
REQUIRE(botConfig.GetValidationLevel() == ValidationLevel::Basic);
|
||||||
|
|
||||||
|
std::remove(filePath.c_str());
|
||||||
|
}
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
|
||||||
|
#include "tc_catch2.h"
|
||||||
|
|
||||||
|
#include "BotMovementManager.h"
|
||||||
|
#include "BotMovementController.h"
|
||||||
|
#include "PositionValidator.h"
|
||||||
|
#include "GroundValidator.h"
|
||||||
|
#include "ValidationResult.h"
|
||||||
|
#include "BotMovementDefines.h"
|
||||||
|
#include "Position.h"
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - BotMovementManager Singleton", "[BotMovement][Integration][Phase1]")
|
||||||
|
{
|
||||||
|
SECTION("Manager instance is accessible")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager = sBotMovementManager;
|
||||||
|
REQUIRE(manager != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Multiple calls return same instance")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager1 = sBotMovementManager;
|
||||||
|
BotMovementManager* manager2 = sBotMovementManager;
|
||||||
|
REQUIRE(manager1 == manager2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Manager has valid config")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager = sBotMovementManager;
|
||||||
|
BotMovementConfig const& config = manager->GetConfig();
|
||||||
|
REQUIRE(config.GetValidationLevel() != ValidationLevel::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Manager has path cache")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager = sBotMovementManager;
|
||||||
|
PathCache* cache = manager->GetPathCache();
|
||||||
|
REQUIRE(cache != nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - PositionValidator Bounds", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Valid position within normal bounds")
|
||||||
|
{
|
||||||
|
Position pos(0.0f, 0.0f, 0.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid position with normal coordinates")
|
||||||
|
{
|
||||||
|
Position pos(1000.0f, 1000.0f, 100.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - NaN X coordinate")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
|
||||||
|
REQUIRE_FALSE(result.errorMessage.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - NaN Y coordinate")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(0.0f, nanValue, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - NaN Z coordinate")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(0.0f, 0.0f, nanValue);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - Infinity X coordinate")
|
||||||
|
{
|
||||||
|
float infValue = std::numeric_limits<float>::infinity();
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(infValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - Negative infinity")
|
||||||
|
{
|
||||||
|
float negInfValue = -std::numeric_limits<float>::infinity();
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(negInfValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - extremely large coordinates")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(1000000.0f, 1000000.0f, 100000.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position - extremely small coordinates")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(-1000000.0f, -1000000.0f, -100000.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - PositionValidator Map ID", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Valid map ID 0 (Eastern Kingdoms)")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateMapId(0);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid map ID 1 (Kalimdor)")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateMapId(1);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid map ID - out of range")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateMapId(999999);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::InvalidMapId);
|
||||||
|
REQUIRE_FALSE(result.errorMessage.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - PositionValidator Combined Validation", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Valid position on valid map")
|
||||||
|
{
|
||||||
|
Position pos(0.0f, 0.0f, 0.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidatePosition(0, pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position on valid map - NaN coordinates")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
ValidationResult result = PositionValidator::ValidatePosition(0, nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid position on invalid map")
|
||||||
|
{
|
||||||
|
Position pos(0.0f, 0.0f, 0.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidatePosition(999999, pos);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::InvalidMapId);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid position on invalid map - both checks fail")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
ValidationResult result = PositionValidator::ValidatePosition(999999, nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE((result.failureReason == ValidationFailureReason::InvalidPosition ||
|
||||||
|
result.failureReason == ValidationFailureReason::InvalidMapId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - ValidationResult Structure", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Success factory method")
|
||||||
|
{
|
||||||
|
ValidationResult result = ValidationResult::Success();
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
REQUIRE(result.errorMessage.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Failure factory method")
|
||||||
|
{
|
||||||
|
ValidationResult result = ValidationResult::Failure(
|
||||||
|
ValidationFailureReason::CollisionDetected,
|
||||||
|
"Wall collision detected"
|
||||||
|
);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::CollisionDetected);
|
||||||
|
REQUIRE(result.errorMessage == "Wall collision detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Default constructor")
|
||||||
|
{
|
||||||
|
ValidationResult result;
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE(result.failureReason == ValidationFailureReason::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - BotMovementDefines Enums", "[BotMovement][Integration][Phase1]")
|
||||||
|
{
|
||||||
|
SECTION("MovementStateType values are distinct")
|
||||||
|
{
|
||||||
|
REQUIRE(MovementStateType::Idle != MovementStateType::Ground);
|
||||||
|
REQUIRE(MovementStateType::Ground != MovementStateType::Swimming);
|
||||||
|
REQUIRE(MovementStateType::Swimming != MovementStateType::Flying);
|
||||||
|
REQUIRE(MovementStateType::Flying != MovementStateType::Falling);
|
||||||
|
REQUIRE(MovementStateType::Falling != MovementStateType::Stuck);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ValidationFailureReason values are distinct")
|
||||||
|
{
|
||||||
|
REQUIRE(ValidationFailureReason::None != ValidationFailureReason::InvalidPosition);
|
||||||
|
REQUIRE(ValidationFailureReason::InvalidPosition != ValidationFailureReason::OutOfBounds);
|
||||||
|
REQUIRE(ValidationFailureReason::OutOfBounds != ValidationFailureReason::InvalidMapId);
|
||||||
|
REQUIRE(ValidationFailureReason::CollisionDetected != ValidationFailureReason::PathBlocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ValidationLevel values are ordered correctly")
|
||||||
|
{
|
||||||
|
REQUIRE(static_cast<uint8>(ValidationLevel::None) < static_cast<uint8>(ValidationLevel::Basic));
|
||||||
|
REQUIRE(static_cast<uint8>(ValidationLevel::Basic) < static_cast<uint8>(ValidationLevel::Standard));
|
||||||
|
REQUIRE(static_cast<uint8>(ValidationLevel::Standard) < static_cast<uint8>(ValidationLevel::Strict));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("RecoveryLevel values are ordered correctly")
|
||||||
|
{
|
||||||
|
REQUIRE(static_cast<uint8>(RecoveryLevel::Level1_RecalculatePath) <
|
||||||
|
static_cast<uint8>(RecoveryLevel::Level2_BackupAndRetry));
|
||||||
|
REQUIRE(static_cast<uint8>(RecoveryLevel::Level2_BackupAndRetry) <
|
||||||
|
static_cast<uint8>(RecoveryLevel::Level3_RandomNearbyPosition));
|
||||||
|
REQUIRE(static_cast<uint8>(RecoveryLevel::Level3_RandomNearbyPosition) <
|
||||||
|
static_cast<uint8>(RecoveryLevel::Level4_TeleportToSafePosition));
|
||||||
|
REQUIRE(static_cast<uint8>(RecoveryLevel::Level4_TeleportToSafePosition) <
|
||||||
|
static_cast<uint8>(RecoveryLevel::Level5_EvadeAndReset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - PositionSnapshot Structure", "[BotMovement][Integration][Phase1]")
|
||||||
|
{
|
||||||
|
SECTION("Default constructor initializes correctly")
|
||||||
|
{
|
||||||
|
PositionSnapshot snapshot;
|
||||||
|
REQUIRE(snapshot.timestamp == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Parameterized constructor")
|
||||||
|
{
|
||||||
|
Position pos(100.0f, 200.0f, 50.0f);
|
||||||
|
uint32 time = 12345;
|
||||||
|
PositionSnapshot snapshot(pos, time);
|
||||||
|
|
||||||
|
REQUIRE(snapshot.pos.GetPositionX() == 100.0f);
|
||||||
|
REQUIRE(snapshot.pos.GetPositionY() == 200.0f);
|
||||||
|
REQUIRE(snapshot.pos.GetPositionZ() == 50.0f);
|
||||||
|
REQUIRE(snapshot.timestamp == time);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Copy position data correctly")
|
||||||
|
{
|
||||||
|
Position pos1(1.0f, 2.0f, 3.0f);
|
||||||
|
PositionSnapshot snapshot1(pos1, 100);
|
||||||
|
|
||||||
|
Position pos2(10.0f, 20.0f, 30.0f);
|
||||||
|
PositionSnapshot snapshot2(pos2, 200);
|
||||||
|
|
||||||
|
REQUIRE(snapshot1.pos.GetPositionX() != snapshot2.pos.GetPositionX());
|
||||||
|
REQUIRE(snapshot1.timestamp != snapshot2.timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - Validation Pipeline Correctness", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Sequential validation - first check passes, all checks run")
|
||||||
|
{
|
||||||
|
Position validPos(100.0f, 100.0f, 10.0f);
|
||||||
|
|
||||||
|
ValidationResult boundsResult = PositionValidator::ValidateBounds(validPos);
|
||||||
|
REQUIRE(boundsResult.isValid == true);
|
||||||
|
|
||||||
|
ValidationResult mapResult = PositionValidator::ValidateMapId(0);
|
||||||
|
REQUIRE(mapResult.isValid == true);
|
||||||
|
|
||||||
|
ValidationResult combinedResult = PositionValidator::ValidatePosition(0, validPos);
|
||||||
|
REQUIRE(combinedResult.isValid == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Sequential validation - first check fails, error captured")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
|
||||||
|
ValidationResult boundsResult = PositionValidator::ValidateBounds(nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(boundsResult.isValid == false);
|
||||||
|
REQUIRE(boundsResult.failureReason == ValidationFailureReason::InvalidPosition);
|
||||||
|
|
||||||
|
ValidationResult combinedResult = PositionValidator::ValidatePosition(0, nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(combinedResult.isValid == false);
|
||||||
|
REQUIRE(combinedResult.failureReason == ValidationFailureReason::InvalidPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Multiple validation failures - first failure is reported")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
uint32 invalidMapId = 999999;
|
||||||
|
|
||||||
|
ValidationResult result = PositionValidator::ValidatePosition(invalidMapId, nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - Config and Manager Integration", "[BotMovement][Integration][Phase1]")
|
||||||
|
{
|
||||||
|
SECTION("Manager config can be reloaded")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager = sBotMovementManager;
|
||||||
|
REQUIRE(manager != nullptr);
|
||||||
|
|
||||||
|
REQUIRE_NOTHROW(manager->ReloadConfig());
|
||||||
|
|
||||||
|
BotMovementConfig const& config = manager->GetConfig();
|
||||||
|
REQUIRE(config.GetValidationLevel() != ValidationLevel::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Manager metrics are accessible")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager = sBotMovementManager;
|
||||||
|
REQUIRE(manager != nullptr);
|
||||||
|
|
||||||
|
REQUIRE_NOTHROW(manager->GetGlobalMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Manager metrics can be reset")
|
||||||
|
{
|
||||||
|
BotMovementManager* manager = sBotMovementManager;
|
||||||
|
REQUIRE(manager != nullptr);
|
||||||
|
|
||||||
|
REQUIRE_NOTHROW(manager->ResetMetrics());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - Edge Cases and Boundary Conditions", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Position at origin is valid")
|
||||||
|
{
|
||||||
|
Position origin(0.0f, 0.0f, 0.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(origin);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Position with very small positive values")
|
||||||
|
{
|
||||||
|
Position pos(0.001f, 0.001f, 0.001f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Position with very small negative values")
|
||||||
|
{
|
||||||
|
Position pos(-0.001f, -0.001f, -0.001f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Position with mixed positive and negative coordinates")
|
||||||
|
{
|
||||||
|
Position pos(-100.0f, 100.0f, -50.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Large but valid coordinates")
|
||||||
|
{
|
||||||
|
Position pos(10000.0f, 10000.0f, 1000.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(pos);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Z coordinate at extremes")
|
||||||
|
{
|
||||||
|
Position highZ(0.0f, 0.0f, 5000.0f);
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(highZ);
|
||||||
|
REQUIRE(result.isValid == true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Phase 1 Integration - Error Message Quality", "[BotMovement][Integration][Phase1][Validation]")
|
||||||
|
{
|
||||||
|
SECTION("Invalid position error has descriptive message")
|
||||||
|
{
|
||||||
|
float nanValue = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(nanValue, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE_FALSE(result.errorMessage.empty());
|
||||||
|
REQUIRE(result.errorMessage.length() > 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Out of bounds error has descriptive message")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateBounds(1000000.0f, 0.0f, 0.0f);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE_FALSE(result.errorMessage.empty());
|
||||||
|
REQUIRE(result.errorMessage.length() > 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid map ID error has descriptive message")
|
||||||
|
{
|
||||||
|
ValidationResult result = PositionValidator::ValidateMapId(999999);
|
||||||
|
REQUIRE(result.isValid == false);
|
||||||
|
REQUIRE_FALSE(result.errorMessage.empty());
|
||||||
|
REQUIRE(result.errorMessage.length() > 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user