@@ -712,6 +712,105 @@ BOOST_FIXTURE_TEST_CASE(HarborDestroyed, ShipAndHarborsReadyFixture<1>)
712712 BOOST_TEST_REQUIRE (ship.GetTargetHarbor () == hb1Id);
713713}
714714
715+ // Dimensions must be large enough so path tolerances get too small
716+ using ShipReadyFixtureLarger = ShipReadyFixture<1 , 2 , 90 , 260 >;
717+ BOOST_FIXTURE_TEST_CASE (IncreasedPathLengthWhenHarborDestroyed, ShipReadyFixtureLarger)
718+ {
719+ // Edge case: Path to harbor longer than any direct connection
720+ /*
721+ * 2
722+ * W
723+ * WLW
724+ * WLW
725+ * WWLWW
726+ * 4WLLLLLW5
727+ * WWLWW
728+ * WWLWW
729+ * 7
730+ */
731+ // Shortest path 4->5 is over south, ship might be between 2 & 4 => still finds path to 5
732+ constexpr HarborId hbIdTop (2 );
733+ constexpr HarborId hbIdLeft (4 );
734+ constexpr HarborId hbIdRight (5 );
735+ constexpr HarborId hbIdBottom (7 );
736+ const MapPoint hbTopPos = world.GetHarborPoint (hbIdTop);
737+ const MapPoint hbLeftPos = world.GetHarborPoint (hbIdLeft);
738+ const MapPoint hbRightPos = world.GetHarborPoint (hbIdRight);
739+ const MapPoint hbBottomPos = world.GetHarborPoint (hbIdBottom);
740+ // Sanity check expected geometry, ensured by fixture
741+ BOOST_TEST_REQUIRE (hbLeftPos.x < hbRightPos.x );
742+ BOOST_TEST_REQUIRE (hbLeftPos.y == hbRightPos.y );
743+ BOOST_TEST_REQUIRE (hbTopPos.y < hbLeftPos.y );
744+ BOOST_TEST_REQUIRE (hbLeftPos.y < hbBottomPos.y );
745+ const unsigned yDiff = hbLeftPos.y - hbTopPos.y ;
746+ BOOST_TEST_REQUIRE (yDiff >= 4u * 3u ); // So divisions below yield noticeable difference
747+ const auto t = GetLandTerrain (world.GetDescription (), ETerrain::Buildable);
748+ for (const auto y : helpers::range (hbLeftPos.y - yDiff * 3u / 4u , hbLeftPos.y + yDiff / 2u ))
749+ {
750+ auto & node = world.GetNodeWriteable (MapPoint (hbTopPos.x , y));
751+ node.t1 = node.t2 = t;
752+ }
753+ constexpr auto horStripHeight = 2u ;
754+ // Horizontal land strip to avoid short cuts
755+ for (const auto y : helpers::range (hbLeftPos.y - horStripHeight, hbLeftPos.y + horStripHeight))
756+ {
757+ for (const auto x : helpers::range (hbLeftPos.x + 4 , hbRightPos.x - 4 ))
758+ {
759+ auto & node = world.GetNodeWriteable (MapPoint (x, y));
760+ node.t1 = node.t2 = t;
761+ }
762+ }
763+ MapLoader::InitSeasAndHarbors (world);
764+ // Harbors unchanged
765+ BOOST_TEST_REQUIRE (world.GetHarborPointID (hbTopPos) == hbIdTop);
766+ BOOST_TEST_REQUIRE (world.GetHarborPointID (hbLeftPos) == hbIdLeft);
767+ BOOST_TEST_REQUIRE (world.GetHarborPointID (hbRightPos) == hbIdRight);
768+ BOOST_TEST_REQUIRE (world.GetHarborPointID (hbBottomPos) == hbIdBottom);
769+
770+ const GamePlayer& player = world.GetPlayer (curPlayer);
771+ const noShip& ship = ensureNonNull (player.GetShipByID (0 ));
772+ const SeaId seaId = ship.GetSeaID ();
773+
774+ // Start shipment to left harbor
775+ nobHarborBuilding& topHarbor = *player.GetBuildingRegister ().GetHarbors ().front ();
776+ BuildingFactory::CreateBuilding (world, BuildingType::HarborBuilding, hbLeftPos, curPlayer, Nation::Romans);
777+
778+ topHarbor.AddToInventory (GoodCounts::make (GoodType::Wood, 10 ), true );
779+ SetInventorySetting (hbLeftPos, GoodType::Wood, EInventorySetting::Collect);
780+
781+ RTTR_EXEC_TILL (90 , ship.IsLoading ());
782+ BOOST_TEST_REQUIRE (ship.GetPos () == world.GetCoastalPoint (hbIdTop, seaId));
783+ RTTR_EXEC_TILL (400 , ship.IsMoving ());
784+ BOOST_TEST_REQUIRE (ship.GetTargetHarbor () == hbIdLeft);
785+ const auto leftCoastPt = world.GetCoastalPoint (hbIdLeft, seaId);
786+ const auto dist = world.CalcDistance (ship.GetPos (), leftCoastPt);
787+
788+ // Wait till ship is close to left harbor
789+ constexpr auto extraDist = 16u ; // Choosen to be larger than a magic number used as tolerance
790+ RTTR_EXEC_TILL (2000 , dist < extraDist * 2 );
791+
792+ // Destroy home so it cannot go back, then target harbor
793+ destroyBldAndFire (world, hbTopPos);
794+ destroyBldAndFire (world, hbLeftPos);
795+ RTTR_EXEC_TILL (10 , ship.IsLost ());
796+ BOOST_TEST_REQUIRE (!ship.GetHomeHarbor ());
797+ BOOST_TEST_REQUIRE (!ship.GetTargetHarbor ());
798+
799+ // Building harbor at right makes ship go there even though distance is (too) long
800+ unsigned len;
801+ BOOST_TEST_REQUIRE (
802+ world.FindShipPath (ship.GetPos (), world.GetCoastalPoint (hbIdRight, seaId), unsigned (-1 ), nullptr , &len));
803+ const auto hbDistance = world.CalcHarborDistance (hbIdLeft, hbIdRight);
804+ // Reproducer requires the path to be longer than the direct distance and other connections
805+ BOOST_TEST_REQUIRE (len > hbDistance + extraDist);
806+ BOOST_TEST_REQUIRE (hbDistance > world.CalcHarborDistance (hbIdRight, hbIdTop));
807+ BOOST_TEST_REQUIRE (hbDistance > world.CalcHarborDistance (hbIdRight, hbIdBottom));
808+ BuildingFactory::CreateBuilding (world, BuildingType::HarborBuilding, hbRightPos, curPlayer, Nation::Romans);
809+ BOOST_TEST_REQUIRE (ship.IsMoving ());
810+ BOOST_TEST_REQUIRE (ship.GetHomeHarbor () == hbIdRight);
811+ BOOST_TEST_REQUIRE (ship.GetTargetHarbor () == hbIdRight);
812+ }
813+
715814struct MockWare : Ware
716815{
717816 static bool destroyed;
0 commit comments