Skip to content
Open
23 changes: 22 additions & 1 deletion libs/s25main/pathfinding/FindPathReachable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
DevOpsOfChaos marked this conversation as resolved.
struct PathConditionReachableWithStaticBlockers : PathConditionReachable
Comment thread
DevOpsOfChaos marked this conversation as resolved.
{
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));
}
52 changes: 52 additions & 0 deletions tests/s25Main/integration/testFrontierDistance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <boost/test/unit_test.hpp>
#include <stdexcept>

Expand Down Expand Up @@ -56,6 +60,7 @@ using FrontierWorldSmall = FrontierWorld<34u, 20u>;
using FrontierWorldMiddle = FrontierWorld<38u, 20u>;
using FrontierWorldBig = FrontierWorld<60u, 20u>;
using FrontierWorldSea = FrontierWorld<SmallSeaWorldDefault<2>::width, SmallSeaWorldDefault<2>::height, CreateSeaWorld>;
using FrontierWorldStaticBlocker = WorldFixture<CreateEmptyWorld, 0, 20u, 12u>;

} // namespace

Expand Down Expand Up @@ -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<TerrainDesc> 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<MapCoord>(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<MapCoord>(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<MapCoord>(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()
Loading