diff --git a/libs/s25main/pathfinding/FindPathReachable.cpp b/libs/s25main/pathfinding/FindPathReachable.cpp index 212eab420a..cebdf0af2f 100644 --- a/libs/s25main/pathfinding/FindPathReachable.cpp +++ b/libs/s25main/pathfinding/FindPathReachable.cpp @@ -6,10 +6,31 @@ #include "pathfinding/FreePathFinderImpl.h" #include "pathfinding/PathConditionReachable.h" #include "world/GameWorldBase.h" +#include "nodeObjs/noBase.h" + +namespace { + +// Keep this narrower than PathConditionHuman, but reject permanently blocking static objects: +// Human pathing can never accept them and they cannot be removed. +struct PathConditionReachableWithStaticBlockers : PathConditionReachable +{ + PathConditionReachableWithStaticBlockers(const World& world) : PathConditionReachable(world) {} + + BOOST_FORCEINLINE bool IsNodeOk(const MapPoint& pt) const + { + const auto* obj = world.GetNode(pt).obj; + if(obj && obj->GetGOT() == GO_Type::Staticobject && obj->GetBM() != BlockingManner::None) + return false; + + return PathConditionReachable::IsNodeOk(pt); + } +}; + +} // namespace bool DoesReachablePathExist(const GameWorldBase& world, const MapPoint startPt, const MapPoint endPt, unsigned maxLen) { RTTR_Assert(startPt != endPt); return world.GetFreePathFinder().FindPath(startPt, endPt, false, maxLen, nullptr, nullptr, nullptr, - PathConditionReachable(world)); + PathConditionReachableWithStaticBlockers(world)); } diff --git a/tests/s25Main/integration/testFrontierDistance.cpp b/tests/s25Main/integration/testFrontierDistance.cpp index 8435e05270..c35d2029bb 100644 --- a/tests/s25Main/integration/testFrontierDistance.cpp +++ b/tests/s25Main/integration/testFrontierDistance.cpp @@ -3,14 +3,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "GamePlayer.h" +#include "RttrForeachPt.h" #include "buildings/nobMilitary.h" #include "factories/BuildingFactory.h" #include "helpers/Range.h" +#include "pathfinding/FindPathReachable.h" #include "worldFixtures/CreateEmptyWorld.h" #include "worldFixtures/CreateSeaWorld.h" #include "worldFixtures/WorldFixture.h" #include "worldFixtures/terrainHelpers.h" #include "world/MapLoader.h" +#include "nodeObjs/noEnvObject.h" +#include "nodeObjs/noStaticObject.h" #include #include @@ -56,6 +60,7 @@ using FrontierWorldSmall = FrontierWorld<34u, 20u>; using FrontierWorldMiddle = FrontierWorld<38u, 20u>; using FrontierWorldBig = FrontierWorld<60u, 20u>; using FrontierWorldSea = FrontierWorld::width, SmallSeaWorldDefault<2>::height, CreateSeaWorld>; +using FrontierWorldStaticBlocker = WorldFixture; } // namespace @@ -347,4 +352,51 @@ BOOST_FIXTURE_TEST_CASE(FrontierDistanceBug_815, WorldBig) BOOST_TEST_REQUIRE(distance1 == FrontierDistance::Near); } +BOOST_FIXTURE_TEST_CASE(FrontierDistanceReachabilityConsidersStaticObjectBlockers, FrontierWorldStaticBlocker) +{ + const DescIdx tWater = GetWaterTerrain(world.GetDescription()); + const unsigned corridorY = 6; + const MapPoint startPt(2, corridorY); + const MapPoint endPt(10, corridorY); + constexpr unsigned maxPathLen = 8; + + RTTR_FOREACH_PT(MapPoint, world.GetSize()) + { + if(pt.y == corridorY && pt.x >= startPt.x && pt.x <= endPt.x) + continue; + + MapNode& node = world.GetNodeWriteable(pt); + node.t1 = tWater; + node.t2 = tWater; + } + + BOOST_TEST_REQUIRE(DoesReachablePathExist(world, startPt, endPt, maxPathLen)); + + for(const unsigned x : helpers::range(startPt.x + 1, endPt.x)) + { + const MapPoint passablePt(x, corridorY); + world.DestroyNO(passablePt, false); + world.SetNO(passablePt, new noStaticObject(passablePt, 0, 0, 0)); + } + + BOOST_TEST_REQUIRE(DoesReachablePathExist(world, startPt, endPt, maxPathLen)); + + for(const unsigned x : helpers::range(startPt.x + 1, endPt.x)) + { + const MapPoint passablePt(x, corridorY); + world.DestroyNO(passablePt, false); + world.SetNO(passablePt, new noEnvObject(passablePt, 0, 0)); + } + + BOOST_TEST_REQUIRE(DoesReachablePathExist(world, startPt, endPt, maxPathLen)); + + for(const unsigned x : helpers::range(startPt.x + 1, endPt.x)) + { + const MapPoint blockerPt(x, corridorY); + world.DestroyNO(blockerPt, false); + world.SetNO(blockerPt, new noStaticObject(blockerPt, 0, 0, 1)); + } + + BOOST_TEST_REQUIRE(!DoesReachablePathExist(world, startPt, endPt, maxPathLen)); +} BOOST_AUTO_TEST_SUITE_END()