Skip to content

Commit 8934341

Browse files
committed
Correctly handle wares carried into harbors with no path to goal
When the goal for a ware becomes unreachable while it is carried into a harbor `RecalcRoute` will call `FindRouteToWarehouse`. Usually the selected warehouse will be the harbor as it is trivially the closest. However when the harbor does not accept this ware type another one might be chosen and the ware needs to be moved out instead of stored. But `FindRouteToWarehouse` does NOT set `next_dir` while the ware is carried even when called from `RecalcRoute` where the caller assumes it is set. Changing this causes replays to go async, so work around it by calling `RecalcRoute` again which now succeeds as it reached the goal in the previous call. This bug also affects `WantInBuilding` which also calls `RecalcRoute` assuming it does so in all cases. Fixes #1843
1 parent 0069f8b commit 8934341

File tree

5 files changed

+117
-8
lines changed

5 files changed

+117
-8
lines changed

libs/s25main/Ware.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ void Ware::RecalcRoute()
112112
static_cast<nobHarborBuilding*>(goal)->WareDontWantToTravelByShip(this);
113113
} else
114114
{
115+
// TODO(Replay) This should calculate the next dir even when carried
115116
FindRouteToWarehouse();
116117
}
117118
} else

libs/s25main/Ware.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class Ware : public GameObject
8383
bool IsWaitingAtFlag() const { return (state == State::WaitAtFlag); }
8484
bool IsWaitingInWarehouse() const { return (state == State::WaitInWarehouse); }
8585
bool IsWaitingForShip() const { return (state == State::WaitForShip); }
86+
bool IsCarried() const { return (state == State::Carried); }
8687
/// Sagt dem Träger Bescheid, dass sie in die aktuelle (next_dir) Richtung nicht mehr getragen werden will
8788
void RemoveWareJobForDir(RoadPathDirection last_next_dir);
8889
/// Überprüft, ob es noch ein Weg zum Ziel gibt

libs/s25main/buildings/nobHarborBuilding.cpp

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -635,23 +635,42 @@ void nobHarborBuilding::AddWare(std::unique_ptr<Ware> ware)
635635
// This is not the goal but we have one -> Get new route
636636
ware->RecalcRoute();
637637

638-
// Will diese Ware mit dem Schiff irgendwo hin fahren?
638+
// Go by ship next?
639639
if(ware->GetNextDir() == RoadPathDirection::Ship)
640640
{
641-
// Dann fügen wir die mal bei uns hinzu
642641
AddWareForShip(std::move(ware));
643642
return;
644-
} else if(ware->GetNextDir() != RoadPathDirection::None)
643+
}
644+
if(ware->GetNextDir() != RoadPathDirection::None)
645645
{
646646
// Travel on roads -> Carry out
647647
RTTR_Assert(ware->GetGoal() != this);
648648
AddWaitingWare(std::move(ware));
649649
return;
650-
} else
650+
}
651+
// No next dir means the ware reached its goal, i.e. us,
652+
// or there is no valid, reachable goal
653+
// In both cases we take it as we initially would have.
654+
if(ware->GetGoal() && ware->GetGoal() != this)
651655
{
652-
// Pathfinding failed -> Ware would want to go here
653-
RTTR_Assert(ware->GetGoal() == this);
654-
// Regular handling below
656+
// We have a goal but it isn't this and there is no next dir
657+
// This can only happen when the ware was redirected to a warehouse while being carried,
658+
// see Ware::FindRouteToWarehouse called by Ware::RecalcRoute
659+
660+
// TODO(Replay) When Ware::FindRouteToWarehouse recalculates the route when called from RecalcRoute
661+
// and next_dir is None then goal will be NULL or us.
662+
// So the condition of this branch can never be true and the branch can be replaced by:
663+
// RTTR_Assert(!ware->GetGoal() || ware->GetGoal() == this)
664+
RTTR_Assert(ware->IsCarried());
665+
// Explicitly calculate the route which now should set it.
666+
ware->RecalcRoute();
667+
RTTR_Assert(ware->GetGoal());
668+
RTTR_Assert(ware->GetNextDir() != RoadPathDirection::None);
669+
if(ware->GetNextDir() == RoadPathDirection::Ship)
670+
AddWareForShip(std::move(ware));
671+
else
672+
AddWaitingWare(std::move(ware));
673+
return;
655674
}
656675
}
657676
// When ware should be transported to any other goal we returned above, so now we need to take it

libs/s25main/gameTypes/GameTypesOutput.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "gameTypes/Direction.h"
2323
#include "gameTypes/FoWNode.h"
2424
#include "gameTypes/MapTypes.h"
25+
#include "gameTypes/RoadPathDirection.h"
2526
#include "gameTypes/TeamTypes.h"
2627
#include "gameData/DescIdx.h"
2728
#include <boost/preprocessor/seq/for_each.hpp>
@@ -84,9 +85,10 @@ RTTR_ENUM_OUTPUT(Level, Easy, Medium, Hard)
8485
}
8586

8687
RTTR_ENUM_OUTPUT(BuildingType)
88+
RTTR_ENUM_OUTPUT(GO_Type)
8789
RTTR_ENUM_OUTPUT(Job)
8890
RTTR_ENUM_OUTPUT(Nation)
89-
RTTR_ENUM_OUTPUT(GO_Type)
91+
RTTR_ENUM_OUTPUT(RoadPathDirection)
9092

9193
#undef RTTR_ENUM_OUTPUT
9294

tests/s25Main/integration/testSeafaring.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "GamePlayer.h"
66
#include "PointOutput.h"
7+
#include "Ware.h"
78
#include "buildings/noBuildingSite.h"
89
#include "buildings/nobHarborBuilding.h"
910
#include "buildings/nobShipYard.h"
@@ -633,4 +634,89 @@ BOOST_FIXTURE_TEST_CASE(HarborDestroyed, ShipAndHarborsReadyFixture<1>)
633634
BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == 1u);
634635
}
635636

637+
struct MockWare : Ware
638+
{
639+
static bool destroyed;
640+
MockWare(GoodType type, noBaseBuilding* goal, noRoadNode* location) : Ware(type, goal, location)
641+
{
642+
destroyed = false;
643+
}
644+
~MockWare() { destroyed = true; }
645+
};
646+
bool MockWare::destroyed = false;
647+
648+
BOOST_FIXTURE_TEST_CASE(AddWareWithUnreachableGoalToHarbor, ShipAndHarborsReadyFixture<1>)
649+
{
650+
// Recreate issue sensitive to timing:
651+
// 1. Ware almost at harbor flag and should be shipped -> will be carried into building
652+
// 2. Goal becomes unreachable (e.g. road removed)
653+
// 3. Ware reaches harbor and needs to be carried into a storehouse
654+
// 4. Harbor itself does not accept the ware so another storehouse must be found
655+
GamePlayer& player = world.GetPlayer(curPlayer);
656+
auto& harbors = player.GetBuildingRegister().GetHarbors();
657+
BOOST_TEST_REQUIRE(harbors.size() >= 2u);
658+
nobHarborBuilding& harbor1 = *harbors.front();
659+
nobHarborBuilding& harbor2 = **(++harbors.begin());
660+
auto& hq = *player.GetFirstWH();
661+
auto* wh = static_cast<nobBaseWarehouse*>(BuildingFactory::CreateBuilding(
662+
world, BuildingType::Storehouse, harbor2.GetPos() + MapPoint(2, 0), curPlayer, Nation::Romans));
663+
BOOST_TEST_REQUIRE(wh);
664+
BuildRoadForBlds(harbor2.GetPos(), wh->GetPos());
665+
constexpr auto goodType = GoodType::Wood;
666+
667+
// Sanity check: Move from harbor1 to wh (over harbor2)
668+
auto ware = std::make_unique<MockWare>(goodType, wh, &harbor1);
669+
auto* warePtr = ware.get();
670+
ware->Carry(&harbor1);
671+
auto numVisWaresBefore = harbor1.GetNumVisualWares(goodType);
672+
auto numRealWaresBefore = harbor1.GetNumRealWares(goodType);
673+
harbor1.AddWare(std::move(ware));
674+
BOOST_TEST(harbor1.GetNumVisualWares(goodType) == numVisWaresBefore + 1u);
675+
// Wares that will be moved out are only in visual count
676+
BOOST_TEST(harbor1.GetNumRealWares(goodType) == numRealWaresBefore);
677+
BOOST_TEST_REQUIRE(!MockWare::destroyed);
678+
BOOST_TEST(warePtr->GetGoal() == wh);
679+
BOOST_TEST(warePtr->GetNextDir() == RoadPathDirection::Ship);
680+
681+
// Add ware with unreachable goal -> Added to harbor
682+
// First fully disconnect 2nd harbor -> Only reachable via ship
683+
for(const auto d : helpers::enumRange<Direction>())
684+
{
685+
if(d != Direction::NorthWest)
686+
DestroyRoad(harbor2.GetFlagPos(), d);
687+
}
688+
ware = std::make_unique<MockWare>(goodType, wh, &harbor1);
689+
ware->Carry(&harbor1);
690+
numVisWaresBefore = harbor1.GetNumVisualWares(goodType);
691+
numRealWaresBefore = harbor1.GetNumRealWares(goodType);
692+
harbor1.AddWare(std::move(ware));
693+
BOOST_TEST(harbor1.GetNumVisualWares(goodType) == numVisWaresBefore + 1u);
694+
BOOST_TEST(harbor1.GetNumRealWares(goodType) == numRealWaresBefore + 1u);
695+
BOOST_TEST(MockWare::destroyed);
696+
697+
// Same but disallow harbor as goal -> Find another storehouse
698+
SetInventorySetting(harbor1.GetPos(), goodType, EInventorySetting::Stop);
699+
ware = std::make_unique<MockWare>(goodType, wh, &harbor1);
700+
ware->Carry(&harbor1);
701+
warePtr = ware.get();
702+
numVisWaresBefore = harbor1.GetNumVisualWares(goodType);
703+
numRealWaresBefore = harbor1.GetNumRealWares(goodType);
704+
harbor1.AddWare(std::move(ware));
705+
BOOST_TEST(harbor1.GetNumVisualWares(goodType) == numVisWaresBefore + 1u);
706+
BOOST_TEST(harbor1.GetNumRealWares(goodType) == numRealWaresBefore);
707+
BOOST_TEST_REQUIRE(!MockWare::destroyed);
708+
BOOST_TEST(warePtr->GetGoal() == &harbor2);
709+
710+
// If no other storehouse is available take it anyway
711+
SetInventorySetting(harbor2.GetPos(), goodType, EInventorySetting::Stop);
712+
ware = std::make_unique<MockWare>(goodType, wh, &harbor1);
713+
ware->Carry(&harbor1);
714+
numVisWaresBefore = harbor1.GetNumVisualWares(goodType);
715+
numRealWaresBefore = harbor1.GetNumRealWares(goodType);
716+
harbor1.AddWare(std::move(ware));
717+
BOOST_TEST(harbor1.GetNumVisualWares(goodType) == numVisWaresBefore + 1u);
718+
BOOST_TEST(harbor1.GetNumRealWares(goodType) == numRealWaresBefore + 1u);
719+
BOOST_TEST(MockWare::destroyed);
720+
}
721+
636722
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)