From 89c524a13d4fdeac77cfa36d6c818d9a8aa18d18 Mon Sep 17 00:00:00 2001 From: lukas Date: Thu, 16 Jul 2026 15:33:55 +0200 Subject: [PATCH 01/16] fixed emergency protocol by removing sheduled wares requested before activation --- libs/s25main/GamePlayer.cpp | 22 ++++++++++++++++++++++ libs/s25main/GamePlayer.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index c444ebc90e..e20ac12cd7 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -2089,6 +2089,25 @@ bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, Harb return false; } +void GamePlayer::CancelWaresForEmergencyProtocol() +{ + for(auto it = ware_list.begin(); it != ware_list.end();){ + Ware * ware = *it; + if(ware->IsWaitingInWarehouse()) + { + auto* goal = ware->GetGoal(); + if(goal != nullptr && goal->GetBuildingType() != BuildingType::Sawmill && goal->GetBuildingType() != BuildingType::Woodcutter) + { + ware->NotifyGoalAboutLostWare(); + static_cast(ware->GetLocation())->CancelWare(ware); + it = ware_list.erase(it); + continue; + } + } + it++; + } +} + void GamePlayer::TestForEmergencyProgramm() { // we are already defeated, do not even think about an emergency program - it's too late :-( @@ -2119,6 +2138,9 @@ void GamePlayer::TestForEmergencyProgramm() SendPostMessage(std::make_unique( world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy)); } + + //remove all existing ware deliveries to buildings not sawmill or woodcutter + CancelWaresForEmergencyProtocol(); } else { // Sobald Notfall vorbei, Notfallprogramm beenden, evtl. Baustellen wieder mit Kram versorgen diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index ada0ba552a..beb910ee5f 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -331,6 +331,8 @@ class GamePlayer : public GamePlayerInfo const Statistic& GetStatistic(StatisticTime time) const { return statistic[time]; }; unsigned GetStatisticCurrentValue(StatisticType idx) const { return statisticCurrentData[idx]; } + // Lösht alle waren die bereits zur Auslieferung vorbereitet sind aber dem Notfallprogramm wiedersprechen + void CancelWaresForEmergencyProtocol(); // Testet ob Notfallprogramm aktiviert werden muss und tut dies dann void TestForEmergencyProgramm(); bool hasEmergency() const { return emergency; } From 0e13b2c0808d8cfbb860fbd0dbe33ad92c498dff Mon Sep 17 00:00:00 2001 From: lukas Date: Thu, 16 Jul 2026 16:26:40 +0200 Subject: [PATCH 02/16] added type = boards for additinal condigion to remove from emergency wares --- libs/s25main/GamePlayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index e20ac12cd7..7229e44dca 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -2093,7 +2093,7 @@ void GamePlayer::CancelWaresForEmergencyProtocol() { for(auto it = ware_list.begin(); it != ware_list.end();){ Ware * ware = *it; - if(ware->IsWaitingInWarehouse()) + if(ware->type == GoodType::Boards && ware->IsWaitingInWarehouse()) { auto* goal = ware->GetGoal(); if(goal != nullptr && goal->GetBuildingType() != BuildingType::Sawmill && goal->GetBuildingType() != BuildingType::Woodcutter) From 97f5b63c1de661fcbdd7f434559ddeb83bdfadbe Mon Sep 17 00:00:00 2001 From: lukas Date: Thu, 16 Jul 2026 17:41:16 +0200 Subject: [PATCH 03/16] pr improvements (comments, extra check emergency ware function, check only on activation) --- libs/s25main/GamePlayer.cpp | 28 ++++++++++++++-------------- libs/s25main/GamePlayer.h | 3 ++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index 7229e44dca..8f44195dd2 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -889,6 +889,10 @@ void GamePlayer::FindWarehouseForAllJobs(const Job job) } } +bool GamePlayer::IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal){ + return (goodType != GoodType::Boards && goodType != GoodType::Stones) || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill; +} + Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal) { /// Gibt es ein Lagerhaus mit dieser Ware? @@ -902,8 +906,7 @@ Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal) else { // Wenn Notfallprogramm aktiv nur an Holzfäller und Sägewerke Bretter/Steine liefern - if((ware != GoodType::Boards && ware != GoodType::Stones) - || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill) + if(IsWareFineWithEmergencyProtocol(ware,goal)) return wh->OrderWare(ware, goal); else return nullptr; @@ -2093,16 +2096,13 @@ void GamePlayer::CancelWaresForEmergencyProtocol() { for(auto it = ware_list.begin(); it != ware_list.end();){ Ware * ware = *it; - if(ware->type == GoodType::Boards && ware->IsWaitingInWarehouse()) + // checks if this ware is + if(ware->IsWaitingInWarehouse() && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal())) { - auto* goal = ware->GetGoal(); - if(goal != nullptr && goal->GetBuildingType() != BuildingType::Sawmill && goal->GetBuildingType() != BuildingType::Woodcutter) - { - ware->NotifyGoalAboutLostWare(); - static_cast(ware->GetLocation())->CancelWare(ware); - it = ware_list.erase(it); - continue; - } + ware->NotifyGoalAboutLostWare(); + static_cast(ware->GetLocation())->CancelWare(ware); + it = ware_list.erase(it); + continue; } it++; } @@ -2137,10 +2137,10 @@ void GamePlayer::TestForEmergencyProgramm() emergency = true; SendPostMessage(std::make_unique( world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy)); - } - //remove all existing ware deliveries to buildings not sawmill or woodcutter - CancelWaresForEmergencyProtocol(); + //Handle wares already ordered + CancelWaresForEmergencyProtocol(); + } } else { // Sobald Notfall vorbei, Notfallprogramm beenden, evtl. Baustellen wieder mit Kram versorgen diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index beb910ee5f..20b06a8975 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -331,7 +331,8 @@ class GamePlayer : public GamePlayerInfo const Statistic& GetStatistic(StatisticTime time) const { return statistic[time]; }; unsigned GetStatisticCurrentValue(StatisticType idx) const { return statisticCurrentData[idx]; } - // Lösht alle waren die bereits zur Auslieferung vorbereitet sind aber dem Notfallprogramm wiedersprechen + bool IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal); + // remove all wares that are already scheduled but ignoring emergency protocol void CancelWaresForEmergencyProtocol(); // Testet ob Notfallprogramm aktiviert werden muss und tut dies dann void TestForEmergencyProgramm(); From 324d8ed2b9f2225891daa0cabdc9a89ab7fdf77b Mon Sep 17 00:00:00 2001 From: lukas Date: Fri, 17 Jul 2026 12:07:50 +0200 Subject: [PATCH 04/16] pr-improvements (nullcheck, static function) --- libs/s25main/GamePlayer.cpp | 4 ++-- libs/s25main/GamePlayer.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index 8f44195dd2..896594e134 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -889,7 +889,7 @@ void GamePlayer::FindWarehouseForAllJobs(const Job job) } } -bool GamePlayer::IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal){ +static bool IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal){ return (goodType != GoodType::Boards && goodType != GoodType::Stones) || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill; } @@ -2097,7 +2097,7 @@ void GamePlayer::CancelWaresForEmergencyProtocol() for(auto it = ware_list.begin(); it != ware_list.end();){ Ware * ware = *it; // checks if this ware is - if(ware->IsWaitingInWarehouse() && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal())) + if(ware->IsWaitingInWarehouse() && ware->GetGoal() != nullptr && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal())) { ware->NotifyGoalAboutLostWare(); static_cast(ware->GetLocation())->CancelWare(ware); diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 20b06a8975..04cbda484b 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -331,7 +331,6 @@ class GamePlayer : public GamePlayerInfo const Statistic& GetStatistic(StatisticTime time) const { return statistic[time]; }; unsigned GetStatisticCurrentValue(StatisticType idx) const { return statisticCurrentData[idx]; } - bool IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal); // remove all wares that are already scheduled but ignoring emergency protocol void CancelWaresForEmergencyProtocol(); // Testet ob Notfallprogramm aktiviert werden muss und tut dies dann From 9dac6095cd3d9b1319d20c10d8441431fbfa841c Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Fri, 17 Jul 2026 12:28:05 +0200 Subject: [PATCH 05/16] Remove comparison to nullptr --- libs/s25main/GamePlayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index 896594e134..aef834687f 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -2097,7 +2097,7 @@ void GamePlayer::CancelWaresForEmergencyProtocol() for(auto it = ware_list.begin(); it != ware_list.end();){ Ware * ware = *it; // checks if this ware is - if(ware->IsWaitingInWarehouse() && ware->GetGoal() != nullptr && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal())) + if(ware->IsWaitingInWarehouse() && ware->GetGoal() && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal())) { ware->NotifyGoalAboutLostWare(); static_cast(ware->GetLocation())->CancelWare(ware); From 2b5735f38f4c37671c85dd5a10abb9ca6eeab78f Mon Sep 17 00:00:00 2001 From: lukas Date: Fri, 17 Jul 2026 17:26:23 +0200 Subject: [PATCH 06/16] clang formater --- libs/s25main/GamePlayer.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index aef834687f..cd23c09feb 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -889,8 +889,10 @@ void GamePlayer::FindWarehouseForAllJobs(const Job job) } } -static bool IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal){ - return (goodType != GoodType::Boards && goodType != GoodType::Stones) || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill; +static bool IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal) +{ + return (goodType != GoodType::Boards && goodType != GoodType::Stones) + || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill; } Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal) @@ -906,7 +908,7 @@ Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal) else { // Wenn Notfallprogramm aktiv nur an Holzfäller und Sägewerke Bretter/Steine liefern - if(IsWareFineWithEmergencyProtocol(ware,goal)) + if(IsWareFineWithEmergencyProtocol(ware, goal)) return wh->OrderWare(ware, goal); else return nullptr; @@ -2094,10 +2096,12 @@ bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, Harb void GamePlayer::CancelWaresForEmergencyProtocol() { - for(auto it = ware_list.begin(); it != ware_list.end();){ - Ware * ware = *it; + for(auto it = ware_list.begin(); it != ware_list.end();) + { + Ware* ware = *it; // checks if this ware is - if(ware->IsWaitingInWarehouse() && ware->GetGoal() && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal())) + if(ware->IsWaitingInWarehouse() && ware->GetGoal() + && !IsWareFineWithEmergencyProtocol(ware->type, *ware->GetGoal())) { ware->NotifyGoalAboutLostWare(); static_cast(ware->GetLocation())->CancelWare(ware); @@ -2138,7 +2142,7 @@ void GamePlayer::TestForEmergencyProgramm() SendPostMessage(std::make_unique( world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy)); - //Handle wares already ordered + // Handle wares already ordered CancelWaresForEmergencyProtocol(); } } else From 0fe2d3c242e8dbc05eb388b84268d8333790b39b Mon Sep 17 00:00:00 2001 From: lukas Date: Sat, 18 Jul 2026 00:52:58 +0200 Subject: [PATCH 07/16] added tests for emergency protocol --- .../integration/testEmergencyProtocol.cpp | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/s25Main/integration/testEmergencyProtocol.cpp diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp new file mode 100644 index 0000000000..84c447d520 --- /dev/null +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -0,0 +1,89 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "EconomyModeHandler.h" +#include "EventManager.h" +#include "GamePlayer.h" +#include "Savegame.h" +#include "SerializedGameData.h" +#include "addons/AddonEconomyModeGameLength.h" +#include "factories/BuildingFactory.h" +#include "worldFixtures/MockLocalGameState.h" +#include "worldFixtures/WorldFixture.h" +#include "worldFixtures/WorldWithGCExecution.h" +#include "worldFixtures/initGameRNG.hpp" +#include "gameTypes/GO_Type.h" +#include +#include +#include + +struct EmergencyFixture : public WorldFixture +{ + nobHQ * HQ = world.GetPlayer(0).GetHQ(); + EmergencyFixture() + { + HQ->AddToInventory(HQ->getStartInventory(StartWares::VLow), true); + MapPoint pos; + + pos = world.GetPlayer(0).GetHQPos() + MapPoint(3, 0); + world.SetBuildingSite(BuildingType::Farm, pos, 0); + world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), + std::vector(3, Direction::West)); + + + pos = world.GetPlayer(0).GetHQPos() + MapPoint(-3, 0); + world.SetBuildingSite(BuildingType::Farm, pos, 0); + world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), + std::vector(3, Direction::East)); + + + //wait until emergency protocol should be activated + RTTR_EXEC_TILL(500,HQ->GetInventory()[GoodType::Boards] == 10); + + //activate program (with 10 boards it should trigger) + world.GetPlayer(0).TestForEmergencyProgramm(); + + //wait for some more ticks to give time if not working to deliver more boards + RTTR_SKIP_GFS(200); + //check boards are still fine and protocol working + BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); + } +}; + +BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveWoodcutterAndSawmillCanBuild, EmergencyFixture) +{ + initGameRNG(); + + MapPoint posWoodcutter = world.GetPlayer(0).GetHQPos() + MapPoint(-1, 2); + world.SetBuildingSite(BuildingType::Woodcutter, posWoodcutter, 0); + world.BuildRoad(0, false, world.GetNeighbour(posWoodcutter, Direction::SouthEast), + std::vector(2, Direction::NorthEast)); + + MapPoint posSawmill = world.GetPlayer(0).GetHQPos() + MapPoint(-2, 4); + world.SetBuildingSite(BuildingType::Sawmill, posSawmill, 0); + world.BuildRoad(0, false, world.GetNeighbour(posSawmill, Direction::SouthEast), + std::vector(2, Direction::NorthEast)); + + //check if inventory boards are given out + RTTR_EXEC_TILL(500,HQ->GetInventory()[GoodType::Boards] < 10); + + //check if building where found + RTTR_EXEC_TILL(10000,world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); + RTTR_EXEC_TILL(10000,world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); +} + +BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFixture) +{ + initGameRNG(); + + MapPoint pos = world.GetPlayer(0).GetHQPos() + MapPoint(-1, 2); + world.SetBuildingSite(BuildingType::Watchtower, pos, 0); + world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), + std::vector(2, Direction::NorthEast)); + + //wait for some more ticks to give time if not working to deliver more boards + RTTR_SKIP_GFS(200); + //check boards are still fine and protocol working + BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); +} From 689112e905ccbeaebd0da1ce17eb521ddbd00be7 Mon Sep 17 00:00:00 2001 From: lukas Date: Sat, 18 Jul 2026 01:01:57 +0200 Subject: [PATCH 08/16] fixed gameframe condition timeouts on emergency protocol tests --- tests/s25Main/integration/testEmergencyProtocol.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index 84c447d520..d5b2161255 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -30,14 +30,12 @@ struct EmergencyFixture : public WorldFixture world.SetBuildingSite(BuildingType::Farm, pos, 0); world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), std::vector(3, Direction::West)); - - + pos = world.GetPlayer(0).GetHQPos() + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Farm, pos, 0); world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), std::vector(3, Direction::East)); - //wait until emergency protocol should be activated RTTR_EXEC_TILL(500,HQ->GetInventory()[GoodType::Boards] == 10); @@ -66,11 +64,11 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveWoodcutterAndSawmillCanBuild, Emer std::vector(2, Direction::NorthEast)); //check if inventory boards are given out - RTTR_EXEC_TILL(500,HQ->GetInventory()[GoodType::Boards] < 10); + RTTR_EXEC_TILL(200,HQ->GetInventory()[GoodType::Boards] < 10); //check if building where found - RTTR_EXEC_TILL(10000,world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); - RTTR_EXEC_TILL(10000,world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); + RTTR_EXEC_TILL(2000,world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); + RTTR_EXEC_TILL(2000,world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); } BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFixture) @@ -83,7 +81,7 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFi std::vector(2, Direction::NorthEast)); //wait for some more ticks to give time if not working to deliver more boards - RTTR_SKIP_GFS(200); + RTTR_SKIP_GFS(500); //check boards are still fine and protocol working BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); } From 0c79309a84adb05d6eeeb40073a390b4b5cbdf7f Mon Sep 17 00:00:00 2001 From: lukas Date: Sat, 18 Jul 2026 14:31:50 +0200 Subject: [PATCH 09/16] formated test file --- .../integration/testEmergencyProtocol.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index d5b2161255..f8f419d311 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -15,12 +15,12 @@ #include "worldFixtures/initGameRNG.hpp" #include "gameTypes/GO_Type.h" #include -#include #include +#include struct EmergencyFixture : public WorldFixture { - nobHQ * HQ = world.GetPlayer(0).GetHQ(); + nobHQ* HQ = world.GetPlayer(0).GetHQ(); EmergencyFixture() { HQ->AddToInventory(HQ->getStartInventory(StartWares::VLow), true); @@ -30,21 +30,21 @@ struct EmergencyFixture : public WorldFixture world.SetBuildingSite(BuildingType::Farm, pos, 0); world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), std::vector(3, Direction::West)); - + pos = world.GetPlayer(0).GetHQPos() + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Farm, pos, 0); world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), std::vector(3, Direction::East)); - //wait until emergency protocol should be activated - RTTR_EXEC_TILL(500,HQ->GetInventory()[GoodType::Boards] == 10); + // wait until emergency protocol should be activated + RTTR_EXEC_TILL(500, HQ->GetInventory()[GoodType::Boards] == 10); - //activate program (with 10 boards it should trigger) + // activate program (with 10 boards it should trigger) world.GetPlayer(0).TestForEmergencyProgramm(); - //wait for some more ticks to give time if not working to deliver more boards + // wait for some more ticks to give time if not working to deliver more boards RTTR_SKIP_GFS(200); - //check boards are still fine and protocol working + // check boards are still fine and protocol working BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); } }; @@ -63,12 +63,12 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveWoodcutterAndSawmillCanBuild, Emer world.BuildRoad(0, false, world.GetNeighbour(posSawmill, Direction::SouthEast), std::vector(2, Direction::NorthEast)); - //check if inventory boards are given out - RTTR_EXEC_TILL(200,HQ->GetInventory()[GoodType::Boards] < 10); + // check if inventory boards are given out + RTTR_EXEC_TILL(200, HQ->GetInventory()[GoodType::Boards] < 10); - //check if building where found - RTTR_EXEC_TILL(2000,world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); - RTTR_EXEC_TILL(2000,world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); + // check if building where found + RTTR_EXEC_TILL(2000, world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); + RTTR_EXEC_TILL(2000, world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); } BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFixture) @@ -80,8 +80,8 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFi world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), std::vector(2, Direction::NorthEast)); - //wait for some more ticks to give time if not working to deliver more boards + // wait for some more ticks to give time if not working to deliver more boards RTTR_SKIP_GFS(500); - //check boards are still fine and protocol working + // check boards are still fine and protocol working BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); } From c4bb8696ecfcd3725ba6477f69f564d2983fd529 Mon Sep 17 00:00:00 2001 From: tost11 Date: Sun, 9 Aug 2026 22:29:37 +0200 Subject: [PATCH 10/16] Apply suggestions from code review Co-authored-by: Alexander Grund --- libs/s25main/GamePlayer.h | 2 +- .../integration/testEmergencyProtocol.cpp | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 04cbda484b..35207d77fc 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -331,7 +331,7 @@ class GamePlayer : public GamePlayerInfo const Statistic& GetStatistic(StatisticTime time) const { return statistic[time]; }; unsigned GetStatisticCurrentValue(StatisticType idx) const { return statisticCurrentData[idx]; } - // remove all wares that are already scheduled but ignoring emergency protocol + // Stop wares restricted in emergency mode that are waiting in warehouse to be transported already void CancelWaresForEmergencyProtocol(); // Testet ob Notfallprogramm aktiviert werden muss und tut dies dann void TestForEmergencyProgramm(); diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index f8f419d311..7cf2e10dd4 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -20,7 +20,7 @@ struct EmergencyFixture : public WorldFixture { - nobHQ* HQ = world.GetPlayer(0).GetHQ(); + nobHQ* hq = world.GetPlayer(0).GetHQ(); EmergencyFixture() { HQ->AddToInventory(HQ->getStartInventory(StartWares::VLow), true); @@ -42,14 +42,13 @@ struct EmergencyFixture : public WorldFixture // activate program (with 10 boards it should trigger) world.GetPlayer(0).TestForEmergencyProgramm(); - // wait for some more ticks to give time if not working to deliver more boards + // No more boards are carried out to the farms due to emergency protocol RTTR_SKIP_GFS(200); - // check boards are still fine and protocol working - BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); + BOOST_TEST_CHECK(hq->GetInventory()[GoodType::Boards] == 10); } }; -BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveWoodcutterAndSawmillCanBuild, EmergencyFixture) +BOOST_FIXTURE_TEST_CASE(EmergencyProtocolActiveWoodcutterAndSawmillCanBuild, EmergencyFixture) { initGameRNG(); @@ -66,7 +65,7 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveWoodcutterAndSawmillCanBuild, Emer // check if inventory boards are given out RTTR_EXEC_TILL(200, HQ->GetInventory()[GoodType::Boards] < 10); - // check if building where found + // check that buildings are built RTTR_EXEC_TILL(2000, world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); RTTR_EXEC_TILL(2000, world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); } @@ -80,8 +79,7 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFi world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), std::vector(2, Direction::NorthEast)); - // wait for some more ticks to give time if not working to deliver more boards + // No boards are carried out to the farms or watchtower due to emergency protocol RTTR_SKIP_GFS(500); - // check boards are still fine and protocol working - BOOST_TEST_CHECK(world.GetPlayer(0).GetHQ()->GetInventory()[GoodType::Boards] == 10); + BOOST_TEST_CHECK(hq->GetInventory()[GoodType::Boards] == 10); } From c1bdd558b7f68d8a8ab62ed8e0456c26e1ace5d4 Mon Sep 17 00:00:00 2001 From: lukas Date: Sun, 9 Aug 2026 23:03:07 +0200 Subject: [PATCH 11/16] pr improvements for tests --- libs/s25main/GamePlayer.cpp | 1 - .../integration/testEmergencyProtocol.cpp | 38 +++++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index cd23c09feb..d4e9050b68 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -2099,7 +2099,6 @@ void GamePlayer::CancelWaresForEmergencyProtocol() for(auto it = ware_list.begin(); it != ware_list.end();) { Ware* ware = *it; - // checks if this ware is if(ware->IsWaitingInWarehouse() && ware->GetGoal() && !IsWareFineWithEmergencyProtocol(ware->type, *ware->GetGoal())) { diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index 7cf2e10dd4..9a598cfaac 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -18,26 +18,24 @@ #include #include -struct EmergencyFixture : public WorldFixture +struct EmergencyFixture : public WorldWithGCExecution1P { nobHQ* hq = world.GetPlayer(0).GetHQ(); EmergencyFixture() { - HQ->AddToInventory(HQ->getStartInventory(StartWares::VLow), true); + hq->AddToInventory(hq->getStartInventory(StartWares::VLow), true); MapPoint pos; - pos = world.GetPlayer(0).GetHQPos() + MapPoint(3, 0); + pos = hqPos + MapPoint(3, 0); world.SetBuildingSite(BuildingType::Farm, pos, 0); - world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), - std::vector(3, Direction::West)); + BuildRoadForBlds(pos,hqPos); - pos = world.GetPlayer(0).GetHQPos() + MapPoint(-3, 0); + pos = hqPos + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Farm, pos, 0); - world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), - std::vector(3, Direction::East)); + BuildRoadForBlds(pos,hqPos); // wait until emergency protocol should be activated - RTTR_EXEC_TILL(500, HQ->GetInventory()[GoodType::Boards] == 10); + RTTR_EXEC_TILL(500, hq->GetInventory()[GoodType::Boards] == 10); // activate program (with 10 boards it should trigger) world.GetPlayer(0).TestForEmergencyProgramm(); @@ -45,6 +43,8 @@ struct EmergencyFixture : public WorldFixture // No more boards are carried out to the farms due to emergency protocol RTTR_SKIP_GFS(200); BOOST_TEST_CHECK(hq->GetInventory()[GoodType::Boards] == 10); + + initGameRNG(); } }; @@ -52,18 +52,16 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtocolActiveWoodcutterAndSawmillCanBuild, Eme { initGameRNG(); - MapPoint posWoodcutter = world.GetPlayer(0).GetHQPos() + MapPoint(-1, 2); + MapPoint posWoodcutter = hqPos + MapPoint(-1, 2); world.SetBuildingSite(BuildingType::Woodcutter, posWoodcutter, 0); - world.BuildRoad(0, false, world.GetNeighbour(posWoodcutter, Direction::SouthEast), - std::vector(2, Direction::NorthEast)); + BuildRoadForBlds(posWoodcutter,hqPos); - MapPoint posSawmill = world.GetPlayer(0).GetHQPos() + MapPoint(-2, 4); + MapPoint posSawmill = hqPos + MapPoint(-2, 4); world.SetBuildingSite(BuildingType::Sawmill, posSawmill, 0); - world.BuildRoad(0, false, world.GetNeighbour(posSawmill, Direction::SouthEast), - std::vector(2, Direction::NorthEast)); + BuildRoadForBlds(posSawmill,hqPos); // check if inventory boards are given out - RTTR_EXEC_TILL(200, HQ->GetInventory()[GoodType::Boards] < 10); + RTTR_EXEC_TILL(200, hq->GetInventory()[GoodType::Boards] < 10); // check that buildings are built RTTR_EXEC_TILL(2000, world.GetNO(posWoodcutter)->GetType() == NodalObjectType::Building); @@ -72,12 +70,10 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtocolActiveWoodcutterAndSawmillCanBuild, Eme BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFixture) { - initGameRNG(); - - MapPoint pos = world.GetPlayer(0).GetHQPos() + MapPoint(-1, 2); + MapPoint pos = hqPos + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Watchtower, pos, 0); - world.BuildRoad(0, false, world.GetNeighbour(pos, Direction::SouthEast), - std::vector(2, Direction::NorthEast)); + + BuildRoadForBlds(pos,hqPos); // No boards are carried out to the farms or watchtower due to emergency protocol RTTR_SKIP_GFS(500); From a6e2ee5403b4e5f573929183548e121e33d135d3 Mon Sep 17 00:00:00 2001 From: tost11 Date: Tue, 11 Aug 2026 10:00:53 +0200 Subject: [PATCH 12/16] Formated emergencyProtocolTest code --- tests/s25Main/integration/testEmergencyProtocol.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index 9a598cfaac..6884ea9318 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -28,11 +28,11 @@ struct EmergencyFixture : public WorldWithGCExecution1P pos = hqPos + MapPoint(3, 0); world.SetBuildingSite(BuildingType::Farm, pos, 0); - BuildRoadForBlds(pos,hqPos); + BuildRoadForBlds(pos, hqPos); pos = hqPos + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Farm, pos, 0); - BuildRoadForBlds(pos,hqPos); + BuildRoadForBlds(pos, hqPos); // wait until emergency protocol should be activated RTTR_EXEC_TILL(500, hq->GetInventory()[GoodType::Boards] == 10); @@ -54,11 +54,11 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtocolActiveWoodcutterAndSawmillCanBuild, Eme MapPoint posWoodcutter = hqPos + MapPoint(-1, 2); world.SetBuildingSite(BuildingType::Woodcutter, posWoodcutter, 0); - BuildRoadForBlds(posWoodcutter,hqPos); + BuildRoadForBlds(posWoodcutter, hqPos); MapPoint posSawmill = hqPos + MapPoint(-2, 4); world.SetBuildingSite(BuildingType::Sawmill, posSawmill, 0); - BuildRoadForBlds(posSawmill,hqPos); + BuildRoadForBlds(posSawmill, hqPos); // check if inventory boards are given out RTTR_EXEC_TILL(200, hq->GetInventory()[GoodType::Boards] < 10); @@ -73,7 +73,7 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFi MapPoint pos = hqPos + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Watchtower, pos, 0); - BuildRoadForBlds(pos,hqPos); + BuildRoadForBlds(pos, hqPos); // No boards are carried out to the farms or watchtower due to emergency protocol RTTR_SKIP_GFS(500); From 23ba8abaa3714e21e28a15d667c81876892dac1b Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 11 Aug 2026 19:31:03 +0200 Subject: [PATCH 13/16] Cleanup testEmergencyProtocol.cpp --- .../integration/testEmergencyProtocol.cpp | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index 6884ea9318..bb27303997 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -2,22 +2,13 @@ // // SPDX-License-Identifier: GPL-2.0-or-later -#include "EconomyModeHandler.h" -#include "EventManager.h" -#include "GamePlayer.h" -#include "Savegame.h" -#include "SerializedGameData.h" -#include "addons/AddonEconomyModeGameLength.h" -#include "factories/BuildingFactory.h" -#include "worldFixtures/MockLocalGameState.h" -#include "worldFixtures/WorldFixture.h" +#include "NodalObjectTypes.h" +#include "buildings/nobHQ.h" #include "worldFixtures/WorldWithGCExecution.h" #include "worldFixtures/initGameRNG.hpp" -#include "gameTypes/GO_Type.h" #include -#include -#include +/// Start with low wares and build 2 farms to trigger emergency protocol activation struct EmergencyFixture : public WorldWithGCExecution1P { nobHQ* hq = world.GetPlayer(0).GetHQ(); @@ -48,15 +39,14 @@ struct EmergencyFixture : public WorldWithGCExecution1P } }; -BOOST_FIXTURE_TEST_CASE(EmergencyProtocolActiveWoodcutterAndSawmillCanBuild, EmergencyFixture) +BOOST_FIXTURE_TEST_SUITE(EmergencyProtocol, EmergencyFixture) +BOOST_AUTO_TEST_CASE(CanBuildWoodcutterAndSawmill) { - initGameRNG(); - - MapPoint posWoodcutter = hqPos + MapPoint(-1, 2); + const MapPoint posWoodcutter = hqPos + MapPoint(-1, 2); world.SetBuildingSite(BuildingType::Woodcutter, posWoodcutter, 0); BuildRoadForBlds(posWoodcutter, hqPos); - MapPoint posSawmill = hqPos + MapPoint(-2, 4); + const MapPoint posSawmill = hqPos + MapPoint(-2, 4); world.SetBuildingSite(BuildingType::Sawmill, posSawmill, 0); BuildRoadForBlds(posSawmill, hqPos); @@ -68,9 +58,9 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtocolActiveWoodcutterAndSawmillCanBuild, Eme RTTR_EXEC_TILL(2000, world.GetNO(posSawmill)->GetType() == NodalObjectType::Building); } -BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFixture) +BOOST_FIXTURE_TEST_CASE(CannotBuildOtherBuldings, EmergencyFixture) { - MapPoint pos = hqPos + MapPoint(-3, 0); + const MapPoint pos = hqPos + MapPoint(-3, 0); world.SetBuildingSite(BuildingType::Watchtower, pos, 0); BuildRoadForBlds(pos, hqPos); @@ -79,3 +69,5 @@ BOOST_FIXTURE_TEST_CASE(EmergencyProtoclActiveOtherBuldingsnotBuild, EmergencyFi RTTR_SKIP_GFS(500); BOOST_TEST_CHECK(hq->GetInventory()[GoodType::Boards] == 10); } + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 6824fbf77590f496eeb7a47feec4ab58dab09032 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 11 Aug 2026 19:48:18 +0200 Subject: [PATCH 14/16] Add and use `getPlayers` (returning a span) --- external/libutil | 2 +- extras/ai-battle/HeadlessGame.cpp | 11 +++++------ libs/s25main/Game.cpp | 11 +++++------ libs/s25main/SerializedGameData.cpp | 12 ++++++------ libs/s25main/ingameWindows/iwStatistics.cpp | 4 ++-- libs/s25main/network/GameClient.cpp | 8 ++++---- libs/s25main/world/GameWorldBase.cpp | 10 ++++++++++ libs/s25main/world/GameWorldBase.h | 5 ++++- tests/s25Main/autoplay/main.cpp | 4 ++-- tests/s25Main/integration/testArmor.cpp | 6 +++--- tests/s25Main/integration/testEconomyMode.cpp | 4 ++-- tests/s25Main/integration/testGameCommands.cpp | 4 ++-- tests/s25Main/integration/testSerialization.cpp | 8 ++++---- 13 files changed, 50 insertions(+), 39 deletions(-) diff --git a/external/libutil b/external/libutil index 08e49918de..48b813d04b 160000 --- a/external/libutil +++ b/external/libutil @@ -1 +1 @@ -Subproject commit 08e49918deae1e67ee4a0ddd6b290f5a59e13989 +Subproject commit 48b813d04b8651b9d9bca7472b03df2e35719bf5 diff --git a/extras/ai-battle/HeadlessGame.cpp b/extras/ai-battle/HeadlessGame.cpp index 86c544ca25..444b9e8b92 100644 --- a/extras/ai-battle/HeadlessGame.cpp +++ b/extras/ai-battle/HeadlessGame.cpp @@ -157,8 +157,8 @@ void HeadlessGame::RecordReplay(const bfs::path& path, unsigned random_init) mapInfo.luaData.CompressFromFile(luaPath_, &mapInfo.luaChecksum); } - for(unsigned playerId = 0; playerId < world_.GetNumPlayers(); ++playerId) - replay_.AddPlayer(world_.GetPlayer(playerId)); + for(auto& player : world_.getPlayers()) + replay_.AddPlayer(player); replay_.ggs = game_.ggs_; if(!replay_.StartRecording(path, mapInfo, random_init)) throw std::runtime_error("Replayfile could not be opened!"); @@ -170,8 +170,8 @@ void HeadlessGame::SaveGame(const bfs::path& path) const bfs::remove(path); Savegame save; - for(unsigned playerId = 0; playerId < world_.GetNumPlayers(); ++playerId) - save.AddPlayer(world_.GetPlayer(playerId)); + for(auto& player : world_.getPlayers()) + save.AddPlayer(player); save.ggs = game_.ggs_; save.ggs.exploration = Exploration::Disabled; // no FOW save.start_gf = em_.GetCurrentGF(); @@ -220,9 +220,8 @@ void HeadlessGame::PrintState() printConsole("┌────────────────────────┬─────────────────┬─────────────┬───────────┬───────────┐\n"); printConsole("│ Player │ Country │ Buildings │ Military │ Gold │\n"); printConsole("├────────────────────────┼─────────────────┼─────────────┼───────────┼───────────┤\n"); - for(unsigned playerId = 0; playerId < world_.GetNumPlayers(); ++playerId) + for(const auto& player : world_.getPlayers()) { - const GamePlayer& player = world_.GetPlayer(playerId); printConsole("│ %s%-22s%s │ %15s │ %11s │ %9s │ %9s │\n", player.IsDefeated() ? "\x1b[9m" : "", player.name.c_str(), player.IsDefeated() ? "\x1b[29m" : "", HumanReadableNumber(player.GetStatisticCurrentValue(StatisticType::Country)).c_str(), diff --git a/libs/s25main/Game.cpp b/libs/s25main/Game.cpp index 02b3fdea3e..d855f8ed80 100644 --- a/libs/s25main/Game.cpp +++ b/libs/s25main/Game.cpp @@ -61,9 +61,9 @@ namespace { unsigned getNumAlivePlayers(const GameWorldBase& world) { unsigned numPlayersAlive = 0; - for(unsigned i = 0; i < world.GetNumPlayers(); ++i) + for(const auto& player : world.getPlayers()) { - if(!world.GetPlayer(i).IsDefeated()) + if(!player.IsDefeated()) ++numPlayersAlive; } return numPlayersAlive; @@ -76,9 +76,8 @@ void Game::RunGF() // EventManager Bescheid sagen em_->ExecuteNextGF(); // Notfallprogramm durchlaufen lassen - for(unsigned i = 0; i < world_.GetNumPlayers(); ++i) + for(GamePlayer& player : world_.getPlayers()) { - GamePlayer& player = world_.GetPlayer(i); if(player.isUsed()) { // Auf Notfall testen (Wenige Bretter/Steine und keine Holzindustrie) @@ -100,8 +99,8 @@ void Game::RunGF() void Game::StatisticStep() { - for(unsigned i = 0; i < world_.GetNumPlayers(); ++i) - world_.GetPlayer(i).StatisticStep(); + for(auto& player : world_.getPlayers()) + player.StatisticStep(); CheckObjective(); } diff --git a/libs/s25main/SerializedGameData.cpp b/libs/s25main/SerializedGameData.cpp index 1b0997c910..50ef81f662 100644 --- a/libs/s25main/SerializedGameData.cpp +++ b/libs/s25main/SerializedGameData.cpp @@ -258,13 +258,13 @@ void SerializedGameData::MakeSnapshot(const Game& game) PushObject(gw.getEconHandler(), true); } // Spieler serialisieren - for(unsigned i = 0; i < gw.GetNumPlayers(); ++i) + for(const auto& player : gw.getPlayers()) { if(debugMode) - LOG.write("Start serializing player %1% at %2%\n") % i % GetLength(); - gw.GetPlayer(i).Serialize(*this); + LOG.write("Start serializing player %1% at %2%\n") % player.GetPlayerId() % GetLength(); + player.Serialize(*this); if(debugMode) - LOG.write("Done serializing player %1% at %2%\n") % i % GetLength(); + LOG.write("Done serializing player %1% at %2%\n") % player.GetPlayerId() % GetLength(); } if(writtenEventIds.size() != writeEm->GetNumActiveEvents()) @@ -301,8 +301,8 @@ void SerializedGameData::ReadSnapshot(Game& game, ILocalGameState& localGameStat std::unique_ptr(PopObject(GO_Type::Economymodehandler))); } - for(unsigned i = 0; i < gw.GetNumPlayers(); ++i) - gw.GetPlayer(i).Deserialize(*this); + for(auto& player : gw.getPlayers()) + player.Deserialize(*this); // If this check fails, we did not serialize all objects or there was an async if(readEvents.size() != em->GetNumActiveEvents()) diff --git a/libs/s25main/ingameWindows/iwStatistics.cpp b/libs/s25main/ingameWindows/iwStatistics.cpp index 9ede3a7ead..360204744a 100644 --- a/libs/s25main/ingameWindows/iwStatistics.cpp +++ b/libs/s25main/ingameWindows/iwStatistics.cpp @@ -82,9 +82,9 @@ iwStatistics::iwStatistics(const GameWorldViewer& gwv) // Count active players numPlayingPlayers = 0; const GameWorldBase& world = gwv.GetWorld(); - for(const auto i : helpers::range(world.GetNumPlayers())) + for(const auto& player : world.getPlayers()) { - if(world.GetPlayer(i).isUsed()) + if(player.isUsed()) numPlayingPlayers++; } diff --git a/libs/s25main/network/GameClient.cpp b/libs/s25main/network/GameClient.cpp index 0060d5964f..4d9950a682 100644 --- a/libs/s25main/network/GameClient.cpp +++ b/libs/s25main/network/GameClient.cpp @@ -327,8 +327,8 @@ void GameClient::StartGame(const unsigned random_init) { RTTR_Assert(mapinfo.type != MapType::Savegame); /// Startbündnisse setzen - for(unsigned i = 0; i < gameWorld.GetNumPlayers(); ++i) - gameWorld.GetPlayer(i).MakeStartPacts(); + for(auto& player : gameWorld.getPlayers()) + player.MakeStartPacts(); MapLoader loader(gameWorld); if(!loader.Load(mapinfo.filepath) @@ -1580,8 +1580,8 @@ bool GameClient::StartReplay(const boost::filesystem::path& path) idx++; } - for(unsigned i = 0; i < game->world_.GetNumPlayers(); i++) - game->world_.GetPlayer(i).ChangeDistribution(newDistributions); + for(auto& player : game->world_.getPlayers()) + player.ChangeDistribution(newDistributions); } replayinfo->next_gf = replayinfo->replay.ReadGF(); diff --git a/libs/s25main/world/GameWorldBase.cpp b/libs/s25main/world/GameWorldBase.cpp index 38363427fc..b0578519c4 100644 --- a/libs/s25main/world/GameWorldBase.cpp +++ b/libs/s25main/world/GameWorldBase.cpp @@ -66,6 +66,16 @@ unsigned GameWorldBase::GetNumPlayers() const return players.size(); } +s25util::span GameWorldBase::getPlayers() +{ + return players; +} + +s25util::span GameWorldBase::getPlayers() const +{ + return players; +} + bool GameWorldBase::IsSinglePlayer() const { bool foundPlayer = false; diff --git a/libs/s25main/world/GameWorldBase.h b/libs/s25main/world/GameWorldBase.h index 3d5ff465d6..1a4e1f0265 100644 --- a/libs/s25main/world/GameWorldBase.h +++ b/libs/s25main/world/GameWorldBase.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 @@ -12,6 +12,7 @@ #include "notifications/NotificationManager.h" #include "postSystem/PostManager.h" #include "world/World.h" +#include "s25util/span.hpp" #include #include #include @@ -158,6 +159,8 @@ class GameWorldBase : public World GamePlayer& GetPlayer(unsigned id); const GamePlayer& GetPlayer(unsigned id) const; unsigned GetNumPlayers() const; + s25util::span getPlayers(); + s25util::span getPlayers() const; bool IsSinglePlayer() const; /// Return the game settings const GlobalGameSettings& GetGGS() const { return gameSettings; } diff --git a/tests/s25Main/autoplay/main.cpp b/tests/s25Main/autoplay/main.cpp index bc83245bd6..1558a7a9db 100644 --- a/tests/s25Main/autoplay/main.cpp +++ b/tests/s25Main/autoplay/main.cpp @@ -88,8 +88,8 @@ static void playReplay(const boost::filesystem::path& replayPath, const bool isS BOOST_TEST_REQUIRE(replay.GetMinorVersion() < 3u); MapLoader::SetupResources(gameWorld, false); - for(unsigned i = 0; i < gameWorld.GetNumPlayers(); ++i) - gameWorld.GetPlayer(i).MakeStartPacts(); + for(auto& player : gameWorld.getPlayers()) + player.MakeStartPacts(); } gameWorld.InitAfterLoad(); diff --git a/tests/s25Main/integration/testArmor.cpp b/tests/s25Main/integration/testArmor.cpp index e18c13a3fa..d78b570889 100644 --- a/tests/s25Main/integration/testArmor.cpp +++ b/tests/s25Main/integration/testArmor.cpp @@ -83,10 +83,10 @@ struct ArmorTradeFixture : public ArmoredSoldierFixture void testExpectedFiguresInGlobalInventoryMatchWithHQInventory() const { - for(unsigned i = 0; i < world.GetNumPlayers(); i++) + for(const auto& player : world.getPlayers()) { - auto const& playerWh = world.GetSpecObj(players[i]->GetHQPos()); - auto const& globalInventoryPlayer = world.GetPlayer(i).GetInventory(); + auto const& playerWh = world.GetSpecObj(player.GetHQPos()); + auto const& globalInventoryPlayer = player.GetInventory(); for(unsigned i = 0; i < NUM_SOLDIER_RANKS; i++) { BOOST_TEST(playerWh->GetNumRealArmoredFigures(jobEnumToAmoredSoldierEnum(SOLDIER_JOBS[i])) diff --git a/tests/s25Main/integration/testEconomyMode.cpp b/tests/s25Main/integration/testEconomyMode.cpp index e5ddac51ec..14d1cf5414 100644 --- a/tests/s25Main/integration/testEconomyMode.cpp +++ b/tests/s25Main/integration/testEconomyMode.cpp @@ -118,8 +118,8 @@ BOOST_FIXTURE_TEST_CASE(EconomyModeSerialization, EconModeFixture) world.getEconHandler()->UpdateAmounts(); Savegame save; - for(unsigned i = 0; i < world.GetNumPlayers(); i++) - save.AddPlayer(world.GetPlayer(i)); + for(const auto& player : world.getPlayers()) + save.AddPlayer(player); save.ggs = ggs; save.start_gf = game->em_->GetCurrentGF(); save.sgd.MakeSnapshot(*game); diff --git a/tests/s25Main/integration/testGameCommands.cpp b/tests/s25Main/integration/testGameCommands.cpp index 22ba315bde..72c3363e82 100644 --- a/tests/s25Main/integration/testGameCommands.cpp +++ b/tests/s25Main/integration/testGameCommands.cpp @@ -738,8 +738,8 @@ void InitPactsAndPost(GameWorldBase& world) BOOST_FIXTURE_TEST_CASE(NotifyAllies, WorldWithGCExecution3P) { // At first there are no teams - for(unsigned i = 0; i < world.GetNumPlayers(); i++) - BOOST_TEST_REQUIRE(world.GetPlayer(i).team == Team::None); + for(const auto& player : world.getPlayers()) + BOOST_TEST_REQUIRE(player.team == Team::None); PostManager& postMgr = world.GetPostMgr(); // Add postbox for each player for(unsigned i = 0; i < world.GetNumPlayers(); i++) diff --git a/tests/s25Main/integration/testSerialization.cpp b/tests/s25Main/integration/testSerialization.cpp index 0b06c034b4..a5bca55979 100644 --- a/tests/s25Main/integration/testSerialization.cpp +++ b/tests/s25Main/integration/testSerialization.cpp @@ -250,8 +250,8 @@ BOOST_FIXTURE_TEST_CASE(BaseSaveLoad, RandWorldFixture) Savegame save; - for(unsigned i = 0; i < world.GetNumPlayers(); i++) - save.AddPlayer(world.GetPlayer(i)); + for(const auto& player : world.getPlayers()) + save.AddPlayer(player); save.ggs = ggs; save.start_gf = em.GetCurrentGF(); @@ -563,8 +563,8 @@ BOOST_FIXTURE_TEST_CASE(ReplayWithSavegame, RandWorldFixture) map.filepath = "Map.swd"; map.luaFilepath = "Map.lua"; map.savegame = std::make_unique(); - for(unsigned i = 0; i < world.GetNumPlayers(); i++) - map.savegame->AddPlayer(world.GetPlayer(i)); + for(const auto& player : world.getPlayers()) + map.savegame->AddPlayer(player); // We can change players std::vector players(4); players[0].ps = PlayerState::AI; From 363865c827b5d36391a6d698a4c8350918dae1c8 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 11 Aug 2026 20:14:04 +0200 Subject: [PATCH 15/16] Test pacts and emergency mode when skipping GFs in tests --- .../integration/testEmergencyProtocol.cpp | 7 +++--- tests/s25Main/integration/testProduction.cpp | 3 ++- .../worldFixtures/TestEventManager.cpp | 23 +++++++++++++++++-- .../s25Main/worldFixtures/TestEventManager.h | 8 ++++++- tests/s25Main/worldFixtures/WorldFixture.h | 4 +++- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/tests/s25Main/integration/testEmergencyProtocol.cpp b/tests/s25Main/integration/testEmergencyProtocol.cpp index bb27303997..162726be7e 100644 --- a/tests/s25Main/integration/testEmergencyProtocol.cpp +++ b/tests/s25Main/integration/testEmergencyProtocol.cpp @@ -28,12 +28,11 @@ struct EmergencyFixture : public WorldWithGCExecution1P // wait until emergency protocol should be activated RTTR_EXEC_TILL(500, hq->GetInventory()[GoodType::Boards] == 10); - // activate program (with 10 boards it should trigger) - world.GetPlayer(0).TestForEmergencyProgramm(); + BOOST_TEST_REQUIRE(world.GetPlayer(0).hasEmergency()); // No more boards are carried out to the farms due to emergency protocol RTTR_SKIP_GFS(200); - BOOST_TEST_CHECK(hq->GetInventory()[GoodType::Boards] == 10); + BOOST_TEST(hq->GetInventory()[GoodType::Boards] == 10); initGameRNG(); } @@ -67,7 +66,7 @@ BOOST_FIXTURE_TEST_CASE(CannotBuildOtherBuldings, EmergencyFixture) // No boards are carried out to the farms or watchtower due to emergency protocol RTTR_SKIP_GFS(500); - BOOST_TEST_CHECK(hq->GetInventory()[GoodType::Boards] == 10); + BOOST_TEST(hq->GetInventory()[GoodType::Boards] == 10); } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/s25Main/integration/testProduction.cpp b/tests/s25Main/integration/testProduction.cpp index 5ce05b76b4..e73014a1bb 100644 --- a/tests/s25Main/integration/testProduction.cpp +++ b/tests/s25Main/integration/testProduction.cpp @@ -74,7 +74,8 @@ BOOST_FIXTURE_TEST_CASE(MetalWorkerStopped, WorldWithGCExecution1P) BOOST_FIXTURE_TEST_CASE(MetalWorkerOrders, WorldWithGCExecution1P) { GoodsAndPeopleCounts inv; - inv[GoodType::Boards] = 10; + inv[GoodType::Boards] = 20; + inv[GoodType::Stones] = 20; inv[GoodType::Iron] = 10; inv[Job::Metalworker] = 1; world.GetSpecObj(hqPos)->AddToInventory(inv, true); diff --git a/tests/s25Main/worldFixtures/TestEventManager.cpp b/tests/s25Main/worldFixtures/TestEventManager.cpp index 49a02e6766..b2f5f967d2 100644 --- a/tests/s25Main/worldFixtures/TestEventManager.cpp +++ b/tests/s25Main/worldFixtures/TestEventManager.cpp @@ -1,11 +1,13 @@ -// 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 #include "TestEventManager.h" #include "GameEvent.h" +#include "GamePlayer.h" +#include "world/GameWorldBase.h" -unsigned TestEventManager::ExecuteNextEvent(unsigned maxGF) +unsigned TestEventManager::doExecuteNextEvent(unsigned maxGF) { if(GetCurrentGF() >= maxGF) return 0; @@ -29,6 +31,23 @@ unsigned TestEventManager::ExecuteNextEvent(unsigned maxGF) return numGFs; } +unsigned TestEventManager::ExecuteNextEvent(unsigned maxGF) +{ + const auto numGFs = doExecuteNextEvent(maxGF); + if(numGFs > 0) + { + for(auto& player : world_->getPlayers()) + { + if(player.isUsed()) + { + player.TestForEmergencyProgramm(); + player.TestPacts(); + } + } + } + return numGFs; +} + std::vector TestEventManager::GetObjEvents(const GameObject& obj) const { std::vector objEvnts; diff --git a/tests/s25Main/worldFixtures/TestEventManager.h b/tests/s25Main/worldFixtures/TestEventManager.h index 020f836e44..6902252b83 100644 --- a/tests/s25Main/worldFixtures/TestEventManager.h +++ b/tests/s25Main/worldFixtures/TestEventManager.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 @@ -7,8 +7,13 @@ #include "EventManager.h" #include +class GameWorldBase; + class TestEventManager : public EventManager { + GameWorldBase* world_ = nullptr; + unsigned doExecuteNextEvent(unsigned maxGF); + public: TestEventManager(unsigned startGF = 0) : EventManager(startGF) {} /// Execute the next event increasing the GF to the events GF @@ -22,4 +27,5 @@ class TestEventManager : public EventManager /// Remove the event and add a copy that is executed at the given GF const GameEvent* RescheduleEvent(const GameEvent* event, unsigned targetGF); std::vector GetEvents() const; + void setWorld(GameWorldBase& world) { world_ = &world; } }; diff --git a/tests/s25Main/worldFixtures/WorldFixture.h b/tests/s25Main/worldFixtures/WorldFixture.h index d7b9ae4a6a..af630533bc 100644 --- a/tests/s25Main/worldFixtures/WorldFixture.h +++ b/tests/s25Main/worldFixtures/WorldFixture.h @@ -97,7 +97,9 @@ struct WorldFixtureBase std::vector(numPlayers, GetPlayer()))), em(static_cast(*game->em_)), ggs(const_cast(game->ggs_)), world(game->world_) - { // Fast moving ships + { + em.setWorld(world); + // Fast moving ships ggs.setSelection(AddonId::SHIP_SPEED, 4); // Explored area stays explored. Avoids fow creation ggs.exploration = Exploration::Classic; From 3fbfa86c047fa9cda3cc6a015eda20243b267859 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 11 Aug 2026 20:35:11 +0200 Subject: [PATCH 16/16] Make `HQPlacement` test more reliable Don't require ALL positions to be different. --- tests/s25Main/integration/testWorld.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/s25Main/integration/testWorld.cpp b/tests/s25Main/integration/testWorld.cpp index dc25e23549..c38e2d784f 100644 --- a/tests/s25Main/integration/testWorld.cpp +++ b/tests/s25Main/integration/testWorld.cpp @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(HQPlacement) // The loader stores the HQ positions read from the map BOOST_TEST(hqsShuffledMap == hqsOriginalMap, boost::test_tools::per_element()); // When shuffled the positions should have changed - BOOST_TEST(hqsShuffledWorld != hqsOriginalWorld, boost::test_tools::per_element()); + BOOST_TEST(hqsShuffledWorld != hqsOriginalWorld); helpers::sort(hqsOriginalMap, MapPointLess{}); helpers::sort(hqsShuffledWorld, MapPointLess{}); helpers::sort(hqsOriginalWorld, MapPointLess{});