diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index 17d9b099d..4276bf727 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -1426,6 +1426,10 @@ void GamePlayer::Surrender() if(isDefeated) return; + const auto shipsCopy = ships; // copy to avoid modification during iteration + for(auto* ship : shipsCopy) + ship->Sink(); + isDefeated = true; // GUI Bescheid sagen @@ -1897,17 +1901,10 @@ bool GamePlayer::OrderShip(nobHarborBuilding& hb) return (false); } -/// Meldet das Schiff wieder ab -void GamePlayer::RemoveShip(noShip* ship) +void GamePlayer::RemoveShip(noShip& ship) { - for(unsigned i = 0; i < ships.size(); ++i) - { - if(ships[i] == ship) - { - ships.erase(ships.begin() + i); - return; - } - } + RTTR_Assert(helpers::contains(ships, &ship)); + helpers::erase(ships, &ship); } /// Versucht, für ein untätiges Schiff eine Arbeit zu suchen diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 922c62ba8..a2804e389 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -114,7 +114,7 @@ class GamePlayer : public GamePlayerInfo /// Returns true if the given wh does still exist and hence the ptr is valid bool IsWarehouseValid(nobBaseWarehouse* wh) const; /// Gibt erstes Lagerhaus zurück - nobBaseWarehouse* GetFirstWH() + nobBaseWarehouse* GetFirstWH() const { return buildings.GetStorehouses().empty() ? nullptr : buildings.GetStorehouses().front(); } @@ -234,7 +234,7 @@ class GamePlayer : public GamePlayerInfo /// Registriert ein Schiff beim Einwohnermeldeamt void RegisterShip(noShip& ship); /// Meldet das Schiff wieder ab - void RemoveShip(noShip* ship); + void RemoveShip(noShip& ship); /// Versucht, für ein untätiges Schiff eine Arbeit zu suchen void GetJobForShip(noShip& ship); /// Schiff für Hafen bestellen. Wenn ein Schiff kommt, true. diff --git a/libs/s25main/buildings/nobHarborBuilding.cpp b/libs/s25main/buildings/nobHarborBuilding.cpp index 7f829e1fe..11f5e81c0 100644 --- a/libs/s25main/buildings/nobHarborBuilding.cpp +++ b/libs/s25main/buildings/nobHarborBuilding.cpp @@ -441,8 +441,7 @@ void nobHarborBuilding::StopExplorationExpedition() /// Bestellt die zusätzlichen erforderlichen Waren für eine Expedition void nobHarborBuilding::OrderExpeditionWares() { - RTTR_Assert(!IsBeingDestroyedNow()); // Wares should already be canceled! - if(this->IsBeingDestroyedNow()) // don't order new stuff if we are about to be destroyed + if(IsBeingDestroyedNow()) // don't order new stuff if we are about to be destroyed return; if(!expedition.active) // expedition no longer active? @@ -496,11 +495,8 @@ void nobHarborBuilding::OrderExpeditionWares() orderware_ev = GetEvMgr().AddEvent(this, 210, 10); } -/// Eine bestellte Ware konnte doch nicht kommen void nobHarborBuilding::WareLost(Ware& ware) { - RTTR_Assert(!IsBeingDestroyedNow()); - // ggf. neue Waren für Expedition bestellen if(expedition.active && (ware.type == GoodType::Boards || ware.type == GoodType::Stones)) OrderExpeditionWares(); nobBaseWarehouse::WareLost(ware); diff --git a/libs/s25main/nodeObjs/noShip.cpp b/libs/s25main/nodeObjs/noShip.cpp index 5a3fec80c..0fe4c9f00 100644 --- a/libs/s25main/nodeObjs/noShip.cpp +++ b/libs/s25main/nodeObjs/noShip.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -104,7 +104,7 @@ void noShip::Destroy() RTTR_Assert(wares.empty()); world->GetNotifications().publish(ShipNote(ShipNote::Destroyed, ownerId_, pos)); // Schiff wieder abmelden - world->GetPlayer(ownerId_).RemoveShip(this); + world->GetPlayer(ownerId_).RemoveShip(*this); } void noShip::Draw(DrawPoint drawPt) @@ -1230,3 +1230,25 @@ void noShip::NewHarborBuilt(nobHarborBuilding* hb) break; } } + +void noShip::Sink() +{ + for(auto& figure : figures) + { + figure->Abrogate(); + figure->SetGoalTonullptr(); + figure->RemoveFromInventory(); + } + figures.clear(); + + for(auto& ware : wares) + { + ware->WareLost(ownerId_); + ware->Destroy(); + } + wares.clear(); + + GetEvMgr().RemoveEvent(current_ev); + GetEvMgr().AddToKillList(world->RemoveFigure(pos, *this)); + world->RecalcVisibilitiesAroundPoint(pos, GetVisualRange(), ownerId_, nullptr); +} diff --git a/libs/s25main/nodeObjs/noShip.h b/libs/s25main/nodeObjs/noShip.h index bf154146f..5f8999e7f 100644 --- a/libs/s25main/nodeObjs/noShip.h +++ b/libs/s25main/nodeObjs/noShip.h @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -223,4 +223,7 @@ class noShip : public noMovable void HarborDestroyed(nobHarborBuilding* hb); /// Sagt dem Schiff, dass ein neuer Hafen erbaut wurde void NewHarborBuilt(nobHarborBuilding* hb); + + /// Destroy the ship immediately + void Sink(); }; diff --git a/tests/s25Main/integration/testSeafaring.cpp b/tests/s25Main/integration/testSeafaring.cpp index e2d6e507f..d801f3f0d 100644 --- a/tests/s25Main/integration/testSeafaring.cpp +++ b/tests/s25Main/integration/testSeafaring.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -718,4 +718,73 @@ BOOST_FIXTURE_TEST_CASE(AddWareWithUnreachableGoalToHarbor, ShipAndHarborsReadyF BOOST_TEST(MockWare::destroyed); } +BOOST_FIXTURE_TEST_CASE(SinkShipLoosesCargo, ShipAndHarborsReadyFixture<1>) +{ + const GamePlayer& player = world.GetPlayer(curPlayer); + noShip& ship = *player.GetShipByID(0); + + const auto& harbors = player.GetBuildingRegister().GetHarbors(); + BOOST_TEST_REQUIRE(harbors.size() >= 2u); + nobHarborBuilding& harbor1 = *harbors.front(); + nobHarborBuilding& harbor2 = **(++harbors.begin()); + + // Transport something + BOOST_TEST_REQUIRE(harbor1.OrderJob(Job::Woodcutter, harbor2, false)); + BOOST_TEST_REQUIRE(harbor1.OrderWare(GoodType::Wood, harbor2)); + BOOST_TEST_REQUIRE(harbor1.OrderWare(GoodType::Wood, harbor2)); + + RTTR_EXEC_TILL(90, ship.IsLoading()); + RTTR_EXEC_TILL(200, ship.IsMoving()); + BOOST_TEST(ship.GetWares().size() == 2u); + BOOST_TEST(ship.GetFigures().size() == 1u); + + const auto shipPos = ship.GetPos(); + BOOST_TEST_REQUIRE(player.GetNumShips() == 1u); + BOOST_TEST_REQUIRE(world.GetFigures(shipPos).size() == 1u); + BOOST_TEST_REQUIRE(dynamic_cast(&world.GetFigures(shipPos).front())); + + const auto numWood = player.GetInventory()[GoodType::Wood]; + const auto numWoodcutters = player.GetInventory()[Job::Woodcutter]; + ship.Sink(); + RTTR_SKIP_GFS(1); // Handle delayed destruction + BOOST_TEST(player.GetNumShips() == 0u); + BOOST_TEST(world.GetFigures(shipPos).size() == 0u); + BOOST_TEST(player.GetInventory()[GoodType::Wood] == numWood - 2); + BOOST_TEST(player.GetInventory()[Job::Woodcutter] == numWoodcutters - 1); +} + +BOOST_FIXTURE_TEST_CASE(RemoveShipsOnDefeat, ShipAndHarborsReadyFixture<2>) +{ + const GamePlayer& player = world.GetPlayer(curPlayer); + const noShip& ship = *player.GetShipByID(0); + + const auto& harbors = player.GetBuildingRegister().GetHarbors(); + BOOST_TEST_REQUIRE(harbors.size() >= 2u); + nobHarborBuilding& harbor1 = *harbors.front(); + nobHarborBuilding& harbor2 = **(++harbors.begin()); + + // Transport something + BOOST_TEST_REQUIRE(harbor1.OrderJob(Job::Woodcutter, harbor2, false)); + BOOST_TEST_REQUIRE(harbor1.OrderWare(GoodType::Wood, harbor2)); + + RTTR_EXEC_TILL(90, ship.IsLoading()); + RTTR_EXEC_TILL(200, ship.IsMoving()); + const auto numWood = ship.GetWares().size(); + const auto numWoodcutters = ship.GetFigures().size(); + BOOST_TEST(numWood == 1u); + BOOST_TEST(numWoodcutters == 1u); + + const auto shipPos = ship.GetPos(); + BOOST_TEST_REQUIRE(player.GetNumShips() == 1u); + BOOST_TEST_REQUIRE(world.GetFigures(shipPos).size() == 1u); + BOOST_TEST_REQUIRE(dynamic_cast(&world.GetFigures(shipPos).front())); + const auto warehouses = player.GetBuildingRegister().GetStorehouses(); // Copy for iteration + for(const auto* bld : warehouses) + world.DestroyBuilding(bld->GetPos(), curPlayer); + RTTR_SKIP_GFS(1); // Handle delayed destruction + BOOST_TEST(player.IsDefeated()); + BOOST_TEST(player.GetNumShips() == 0u); + BOOST_TEST(world.GetFigures(shipPos).size() == 0u); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/s25Main/worldFixtures/TestEventManager.cpp b/tests/s25Main/worldFixtures/TestEventManager.cpp index 49a02e676..e12213abb 100644 --- a/tests/s25Main/worldFixtures/TestEventManager.cpp +++ b/tests/s25Main/worldFixtures/TestEventManager.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,22 +9,18 @@ unsigned TestEventManager::ExecuteNextEvent(unsigned maxGF) { if(GetCurrentGF() >= maxGF) return 0; - if(events.empty()) + unsigned numGFs; + if(events.empty() || events.begin()->first > maxGF) { - unsigned numGFs = maxGF - GetCurrentGF(); + numGFs = maxGF - GetCurrentGF(); currentGF = maxGF; - return numGFs; - } - auto itEvents = events.begin(); - if(itEvents->first > maxGF) + } else { - unsigned numGFs = maxGF - GetCurrentGF(); - currentGF = maxGF; - return numGFs; + auto itEvents = events.begin(); + numGFs = itEvents->first - GetCurrentGF(); + currentGF = itEvents->first; + ExecuteEvents(itEvents); } - unsigned numGFs = itEvents->first - GetCurrentGF(); - currentGF = itEvents->first; - ExecuteEvents(itEvents); DestroyCurrentObjects(); return numGFs; }