Files
EVEngine/test/combat_navigation.cpp

123 lines
4.7 KiB
C++

#include "combat/navigation/PathfinderCombatNavigation.h"
#include "map/Pathfinder.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <cmath>
#include <utility>
namespace {
eve::SubjectRef fighterSubject() {
auto parsed = eve::PersistentId::parse("51525354-5556-5758-d95a-5b5c5d5e5f60");
REQUIRE(parsed.has_value());
return eve::SubjectRef::fromPersistentId(*parsed);
}
std::unique_ptr<eve::combat::navigation::PathfinderCombatNavigationProvider> provider(
eve::map::Pathfinder& pathfinder) {
auto created = eve::combat::navigation::PathfinderCombatNavigationProvider::create(pathfinder, {});
REQUIRE(created.ok());
return std::move(created).takeValue();
}
eve::combat::CombatLocomotionState stateAt(double x, double z) {
eve::combat::CombatLocomotionState state;
state.subject = fighterSubject();
state.position = {x, z};
return state;
}
} // namespace
TEST_CASE("combatNavigation.pathfinderRoutesAroundBlockedCells") {
eve::map::Pathfinder pathfinder(5, 3);
pathfinder.setTopology("ortho4");
pathfinder.setBlocked(2, 0, true);
pathfinder.setBlocked(2, 1, true);
auto navigation = provider(pathfinder);
auto steering = navigation->steer(stateAt(1.5, 1.5), {{4.5, 1.5}, 0.1}, eve::SimulationTick{1});
REQUIRE(steering.ok());
CHECK(steering.value().phase == eve::combat::CombatNavigationPhase::Moving);
CHECK_EQ(steering.value().direction.x, 0.0);
CHECK(steering.value().direction.z > 0.99);
}
TEST_CASE("combatNavigation.locomotionFollowsDetourAndArrives") {
eve::map::Pathfinder pathfinder(5, 3);
pathfinder.setTopology("ortho4");
pathfinder.setBlocked(2, 0, true);
pathfinder.setBlocked(2, 1, true);
auto navigation = provider(pathfinder);
eve::combat::CombatLocomotionRuntime locomotion(*navigation);
const auto fighter = fighterSubject();
REQUIRE(locomotion.registerSubject({fighter, "fighter:path", {1.5, 1.5}, 2.0, 100.0}).ok());
REQUIRE(locomotion.navigateTo(fighter, {{4.5, 1.5}, 0.05}).ok());
bool arrived = false;
for (std::uint64_t tick = 1; tick <= 64 && !arrived; ++tick) {
auto advanced = locomotion.advance(
{eve::SimulationTick{tick}, eve::Duration::fromNanoseconds(250'000'000)});
REQUIRE(advanced.ok());
auto current = locomotion.state(fighter);
REQUIRE(current.ok());
const int cellX = static_cast<int>(std::floor(current.value().position.x));
const int cellY = static_cast<int>(std::floor(current.value().position.z));
CHECK(pathfinder.isWalkable(cellX, cellY));
arrived = !current.value().navigationGoal.has_value();
}
REQUIRE(arrived);
auto current = locomotion.state(fighter);
REQUIRE(current.ok());
CHECK_EQ(current.value().position.x, 4.5);
CHECK_EQ(current.value().position.z, 1.5);
}
TEST_CASE("combatNavigation.replansAfterCanonicalGridMutation") {
eve::map::Pathfinder pathfinder(5, 3);
pathfinder.setTopology("ortho4");
auto navigation = provider(pathfinder);
const auto current = stateAt(1.5, 1.5);
auto direct = navigation->steer(current, {{4.5, 1.5}, 0.1}, eve::SimulationTick{1});
REQUIRE(direct.ok());
CHECK(direct.value().direction.x > 0.99);
CHECK_EQ(direct.value().direction.z, 0.0);
pathfinder.setBlocked(2, 1, true);
auto replanned = navigation->steer(current, {{4.5, 1.5}, 0.1}, eve::SimulationTick{2});
REQUIRE(replanned.ok());
CHECK(replanned.value().direction.x < 0.01);
CHECK(std::abs(replanned.value().direction.z) > 0.99);
}
TEST_CASE("combatNavigation.unreachableGoalAbortsLocomotionAtomically") {
eve::map::Pathfinder pathfinder(5, 3);
pathfinder.setTopology("ortho4");
for (int y = 0; y < 3; ++y) pathfinder.setBlocked(2, y, true);
auto navigation = provider(pathfinder);
eve::combat::CombatLocomotionRuntime locomotion(*navigation);
const auto fighter = fighterSubject();
REQUIRE(locomotion.registerSubject({fighter, "fighter:navigator", {1.5, 1.5}, 4.0, 8.0}).ok());
REQUIRE(locomotion.navigateTo(fighter, {{4.5, 1.5}, 0.1}).ok());
auto advanced = locomotion.advance(
{eve::SimulationTick{1}, eve::Duration::fromNanoseconds(250'000'000)});
REQUIRE(!advanced.ok());
REQUIRE(advanced.status().diagnostics().front().code() == eve::DiagnosticCode::NotFound);
auto current = locomotion.state(fighter);
REQUIRE(current.ok());
CHECK_EQ(current.value().position.x, 1.5);
CHECK_EQ(current.value().position.z, 1.5);
CHECK(current.value().navigationGoal.has_value());
}
TEST_CASE("combatNavigation.rejectsInvalidGridProjection") {
eve::map::Pathfinder pathfinder(2, 2);
auto invalid = eve::combat::navigation::PathfinderCombatNavigationProvider::create(
pathfinder, {{}, 0.0});
CHECK(!invalid.ok());
}