From 31aec23f20775431c6188632c7b3957c0721ca98 Mon Sep 17 00:00:00 2001 From: Manu Date: Fri, 1 May 2026 15:25:53 +0200 Subject: [PATCH 1/4] Add optional free harbor spots addon --- libs/s25main/GlobalGameSettings.cpp | 1 + libs/s25main/addons/AddonFreeHarborSpots.h | 17 +++++ libs/s25main/addons/Addons.h | 1 + libs/s25main/addons/const_addons.h | 2 +- libs/s25main/world/BQCalculator.h | 25 ++++++- libs/s25main/world/GameWorldBase.cpp | 2 +- libs/s25main/world/GameWorldViewer.cpp | 3 +- libs/s25main/world/MapLoader.cpp | 43 +++++++++++- libs/s25main/world/MapLoader.h | 3 +- .../integration/testSeaWorldCreation.cpp | 66 +++++++++++++++++++ 10 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 libs/s25main/addons/AddonFreeHarborSpots.h diff --git a/libs/s25main/GlobalGameSettings.cpp b/libs/s25main/GlobalGameSettings.cpp index 63f240221e..a092dfe9b5 100644 --- a/libs/s25main/GlobalGameSettings.cpp +++ b/libs/s25main/GlobalGameSettings.cpp @@ -75,6 +75,7 @@ void GlobalGameSettings::registerAllAddons() AddonDurableGeologistSigns, AddonEconomyModeGameLength, AddonExhaustibleWater, + AddonFreeHarborSpots, AddonFrontierDistanceReachable, AddonHalfCostMilEquip, AddonInexhaustibleFish, diff --git a/libs/s25main/addons/AddonFreeHarborSpots.h b/libs/s25main/addons/AddonFreeHarborSpots.h new file mode 100644 index 0000000000..5d42cbff9e --- /dev/null +++ b/libs/s25main/addons/AddonFreeHarborSpots.h @@ -0,0 +1,17 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "AddonBool.h" +#include "mygettext/mygettext.h" + +class AddonFreeHarborSpots : public AddonBool +{ +public: + AddonFreeHarborSpots() + : AddonBool(AddonId::FREE_HARBOR_SPOTS, AddonGroup::GamePlay, _("Build harbors without map markers"), + _("Allows harbors on suitable coastal castle sites even if the map does not define harbor spots.")) + {} +}; diff --git a/libs/s25main/addons/Addons.h b/libs/s25main/addons/Addons.h index 4b773587d0..2d01258e60 100644 --- a/libs/s25main/addons/Addons.h +++ b/libs/s25main/addons/Addons.h @@ -53,6 +53,7 @@ #include "addons/AddonCoinsCapturedBld.h" #include "addons/AddonDemolishBldWORes.h" +#include "addons/AddonFreeHarborSpots.h" #include "addons/AddonFrontierDistanceReachable.h" #include "addons/AddonDurableGeologistSigns.h" diff --git a/libs/s25main/addons/const_addons.h b/libs/s25main/addons/const_addons.h index 4ae5da8c88..f066b0c423 100644 --- a/libs/s25main/addons/const_addons.h +++ b/libs/s25main/addons/const_addons.h @@ -75,7 +75,7 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x AUTOFLAGS = 0x00F00000, WINE = 0x01000000, LEATHER = 0x01000001, NO_ARMOR_DEFAULT = 0x01000002, - ARMOR_CAPTURED_BLD = 0x01000003) + ARMOR_CAPTURED_BLD = 0x01000003, FREE_HARBOR_SPOTS = 0x01000004) //-V:AddonId:801 enum class AddonGroup : unsigned diff --git a/libs/s25main/world/BQCalculator.h b/libs/s25main/world/BQCalculator.h index 88db722d3c..6452b6fa15 100644 --- a/libs/s25main/world/BQCalculator.h +++ b/libs/s25main/world/BQCalculator.h @@ -10,13 +10,16 @@ struct BQCalculator { - BQCalculator(const World& world) : world(world) {} + BQCalculator(const World& world, const bool allowFreeHarborSpots = false) + : world(world), allowFreeHarborSpots(allowFreeHarborSpots) + {} template BuildingQuality operator()(MapPoint pt, T_IsOnRoad isOnRoad, bool flagOnly = false) const; private: const World& world; + bool allowFreeHarborSpots; }; template @@ -219,8 +222,24 @@ BuildingQuality BQCalculator::operator()(const MapPoint pt, T_IsOnRoad isOnRoad, } // If we can build a castle and this is a harbor point -> Allow harbor - if(curBQ == BuildingQuality::Castle && world.GetNode(pt).harborId) - curBQ = BuildingQuality::Harbor; + if(curBQ == BuildingQuality::Castle) + { + bool isHarborPoint = world.GetNode(pt).harborId.isValid(); + if(!isHarborPoint && allowFreeHarborSpots) + { + for(const auto dir : helpers::EnumRange{}) + { + // Keep this in sync with harbor initialization: NW-only coasts are rejected there. + if(dir != Direction::NorthWest && world.GetSeaFromCoastalPoint(neighbours[dir])) + { + isHarborPoint = true; + break; + } + } + } + if(isHarborPoint) + curBQ = BuildingQuality::Harbor; + } ////////////////////////////////////////////////////////////////////////// // At this point we can still build a building/mine diff --git a/libs/s25main/world/GameWorldBase.cpp b/libs/s25main/world/GameWorldBase.cpp index 9e7c1d1971..4158fdd602 100644 --- a/libs/s25main/world/GameWorldBase.cpp +++ b/libs/s25main/world/GameWorldBase.cpp @@ -696,7 +696,7 @@ GameWorldBase::GetSoldiersForSeaAttack(const unsigned char player_attacker, cons void GameWorldBase::RecalcBQ(const MapPoint pt) { - BQCalculator calcBQ(*this); + BQCalculator calcBQ(*this, GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS)); if(SetBQ(pt, calcBQ(pt, [this](auto pt) { return this->IsOnRoad(pt); }))) { GetNotifications().publish(NodeNote(NodeNote::BQ, pt)); diff --git a/libs/s25main/world/GameWorldViewer.cpp b/libs/s25main/world/GameWorldViewer.cpp index 9230026b2f..47aee3e13c 100644 --- a/libs/s25main/world/GameWorldViewer.cpp +++ b/libs/s25main/world/GameWorldViewer.cpp @@ -7,6 +7,7 @@ #include "GameInterface.h" #include "GamePlayer.h" #include "GlobalGameSettings.h" +#include "addons/const_addons.h" #include "RttrForeachPt.h" #include "buildings/nobMilitary.h" #include "network/GameClient.h" @@ -278,7 +279,7 @@ void GameWorldViewer::RoadConstructionEnded(const RoadNote& note) void GameWorldViewer::RecalcBQ(const MapPoint& pt) { - BQCalculator calcBQ(GetWorld()); + BQCalculator calcBQ(GetWorld(), GetWorld().GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS)); visualNodes[pt].bq = calcBQ(pt, [this](const MapPoint& pos) { return IsOnRoad(pos); }); } diff --git a/libs/s25main/world/MapLoader.cpp b/libs/s25main/world/MapLoader.cpp index da4d04a4a3..deaf6809c7 100644 --- a/libs/s25main/world/MapLoader.cpp +++ b/libs/s25main/world/MapLoader.cpp @@ -3,12 +3,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "world/MapLoader.h" +#include "BQCalculator.h" #include "Game.h" #include "GamePlayer.h" #include "GameWorldBase.h" #include "GlobalGameSettings.h" #include "PointOutput.h" #include "RttrForeachPt.h" +#include "addons/const_addons.h" #include "buildings/nobHQ.h" #include "factories/BuildingFactory.h" #include "helpers/IdRange.h" @@ -53,7 +55,7 @@ bool MapLoader::Load(const libsiedler2::ArchivItem_Map& map, Exploration explora return false; PlaceObjects(map); PlaceAnimals(map); - if(!InitSeasAndHarbors(world_)) + if(!InitSeasAndHarbors(world_, std::vector(), world_.GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS))) return false; /// Schatten @@ -420,10 +422,39 @@ bool MapLoader::PlaceHQs(GameWorldBase& world, const std::vector& hqPo return true; } -bool MapLoader::InitSeasAndHarbors(World& world, const std::vector& additionalHarbors) +namespace { +bool hasHarborAt(const World& world, const MapPoint pt) +{ + for(const auto harborId : helpers::idRange(world.GetNumHarborPoints())) + { + if(world.GetHarborPoint(harborId) == pt) + return true; + } + return false; +} + +std::vector getGeneratedHarbors(const World& world) +{ + std::vector generatedHarbors; + BQCalculator calcBQ(world, true); + RTTR_FOREACH_PT(MapPoint, world.GetSize()) + { + if(!hasHarborAt(world, pt) + && calcBQ(pt, [](const MapPoint&) { return false; }) == BuildingQuality::Harbor) + generatedHarbors.push_back(pt); + } + return generatedHarbors; +} +} // namespace + +bool MapLoader::InitSeasAndHarbors(World& world, const std::vector& additionalHarbors, + const bool generateHarborSpots) { for(MapPoint pt : additionalHarbors) - world.harborData.push_back(HarborPos(pt)); + { + if(!hasHarborAt(world, pt)) + world.harborData.push_back(HarborPos(pt)); + } // Clear current harbors and seas RTTR_FOREACH_PT(MapPoint, world.GetSize()) //-V807 { @@ -446,6 +477,12 @@ bool MapLoader::InitSeasAndHarbors(World& world, const std::vector& ad } } + if(generateHarborSpots) + { + for(MapPoint pt : getGeneratedHarbors(world)) + world.harborData.push_back(HarborPos(pt)); + } + /// Determine seas adjacent to the harbor places HarborId curHarborId(1); for(auto it = world.harborData.begin(); it != world.harborData.end();) diff --git a/libs/s25main/world/MapLoader.h b/libs/s25main/world/MapLoader.h index 4dd35e9a36..3415d42682 100644 --- a/libs/s25main/world/MapLoader.h +++ b/libs/s25main/world/MapLoader.h @@ -57,7 +57,8 @@ class MapLoader static void InitShadows(World& world); static void SetMapExplored(World& world); static bool InitSeasAndHarbors(World& world, - const std::vector& additionalHarbors = std::vector()); + const std::vector& additionalHarbors = std::vector(), + bool generateHarborSpots = false); /// Place the HQs on a loaded map and add starting wares if desired. /// Return false if there was an error. static bool PlaceHQs(GameWorldBase& world, const std::vector& hqPositions, bool addStartWares = true); diff --git a/tests/s25Main/integration/testSeaWorldCreation.cpp b/tests/s25Main/integration/testSeaWorldCreation.cpp index 499a480b10..76072b42c6 100644 --- a/tests/s25Main/integration/testSeaWorldCreation.cpp +++ b/tests/s25Main/integration/testSeaWorldCreation.cpp @@ -3,10 +3,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "RTTR_AssertError.h" +#include "RttrForeachPt.h" #include "helpers/IdRange.h" +#include "addons/const_addons.h" #include "worldFixtures/SeaWorldWithGCExecution.h" +#include "worldFixtures/terrainHelpers.h" #include "gameTypes/GameTypesOutput.h" #include "gameTypes/ShipDirection.h" +#include "lua/GameDataLoader.h" +#include "world/MapLoader.h" #include #include @@ -61,6 +66,30 @@ void testShipDir(const MapBase& world, const MapPoint fromPt) BOOST_TEST_REQUIRE(getShipDir(world, fromPt, DiffPt(100, -173)) == ShipDirection::NorthEast); BOOST_TEST_REQUIRE(getShipDir(world, fromPt, DiffPt(100, -174)) == ShipDirection::North); } + +void createMarkerlessIslandWorld(GameWorld& world) +{ + world.Unload(); + loadGameData(world.GetDescriptionWriteable()); + world.Init(MapExtent(30, 30)); + + const auto water = GetWaterTerrain(world.GetDescription()); + RTTR_FOREACH_PT(MapPoint, world.GetSize()) + { + MapNode& node = world.GetNodeWriteable(pt); + node.t1 = node.t2 = water; + } + + const auto land = GetLandTerrain(world.GetDescription(), ETerrain::Buildable); + for(MapPoint pt(8, 8); pt.y < 22; ++pt.y) + { + for(pt.x = 8; pt.x < 22; ++pt.x) + { + MapNode& node = world.GetNodeWriteable(pt); + node.t1 = node.t2 = land; + } + } +} } // namespace BOOST_AUTO_TEST_CASE(GetShipDir) @@ -128,6 +157,43 @@ BOOST_FIXTURE_TEST_CASE(HarborSpotCreation, SeaWorldWithGCExecution<>) } } +BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonAddsCoastalHarbors, SeaWorldWithGCExecution<>) +{ + const unsigned initialHarbors = world.GetNumHarborPoints(); + + ggs.setSelection(AddonId::FREE_HARBOR_SPOTS, 1); + BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world, std::vector(), true)); + world.InitAfterLoad(); + + BOOST_TEST_REQUIRE(world.GetNumHarborPoints() > initialHarbors); + for(unsigned harborIdx = initialHarbors + 1; harborIdx <= world.GetNumHarborPoints(); ++harborIdx) + { + const MapPoint harborPt = world.GetHarborPoint(HarborId(harborIdx)); + BOOST_TEST_REQUIRE(harborPt.isValid()); + BOOST_TEST_REQUIRE(world.GetHarborPointID(harborPt) == HarborId(harborIdx)); + BOOST_TEST_REQUIRE(world.GetNode(harborPt).bq == BuildingQuality::Harbor); + } +} + +BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonWorksWithoutMapMarkers, SeaWorldWithGCExecution<>) +{ + createMarkerlessIslandWorld(world); + BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world)); + BOOST_TEST_REQUIRE(world.GetNumHarborPoints() == 0u); + + ggs.setSelection(AddonId::FREE_HARBOR_SPOTS, 1); + BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world, std::vector(), true)); + world.InitAfterLoad(); + + BOOST_TEST_REQUIRE(world.GetNumHarborPoints() > 0u); + for(const auto harborId : helpers::idRange(world.GetNumHarborPoints())) + { + const MapPoint harborPt = world.GetHarborPoint(harborId); + BOOST_TEST_REQUIRE(world.GetHarborPointID(harborPt) == harborId); + BOOST_TEST_REQUIRE(world.GetNode(harborPt).bq == BuildingQuality::Harbor); + } +} + BOOST_FIXTURE_TEST_CASE(HarborNeighbors, SeaWorldWithGCExecution<>) { // Now just test some assumptions: 2 harbor spots per possible HQ. From 7eb77282b725159efab74ae1aab380867cb388ee Mon Sep 17 00:00:00 2001 From: Manu Date: Sat, 2 May 2026 13:04:02 +0200 Subject: [PATCH 2/4] Generate free harbor spots only during harbor init --- libs/s25main/world/GameWorldBase.cpp | 2 +- libs/s25main/world/GameWorldViewer.cpp | 3 +- .../integration/testSeaWorldCreation.cpp | 49 ++++++++++++++++--- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/libs/s25main/world/GameWorldBase.cpp b/libs/s25main/world/GameWorldBase.cpp index 4158fdd602..9e7c1d1971 100644 --- a/libs/s25main/world/GameWorldBase.cpp +++ b/libs/s25main/world/GameWorldBase.cpp @@ -696,7 +696,7 @@ GameWorldBase::GetSoldiersForSeaAttack(const unsigned char player_attacker, cons void GameWorldBase::RecalcBQ(const MapPoint pt) { - BQCalculator calcBQ(*this, GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS)); + BQCalculator calcBQ(*this); if(SetBQ(pt, calcBQ(pt, [this](auto pt) { return this->IsOnRoad(pt); }))) { GetNotifications().publish(NodeNote(NodeNote::BQ, pt)); diff --git a/libs/s25main/world/GameWorldViewer.cpp b/libs/s25main/world/GameWorldViewer.cpp index 47aee3e13c..9230026b2f 100644 --- a/libs/s25main/world/GameWorldViewer.cpp +++ b/libs/s25main/world/GameWorldViewer.cpp @@ -7,7 +7,6 @@ #include "GameInterface.h" #include "GamePlayer.h" #include "GlobalGameSettings.h" -#include "addons/const_addons.h" #include "RttrForeachPt.h" #include "buildings/nobMilitary.h" #include "network/GameClient.h" @@ -279,7 +278,7 @@ void GameWorldViewer::RoadConstructionEnded(const RoadNote& note) void GameWorldViewer::RecalcBQ(const MapPoint& pt) { - BQCalculator calcBQ(GetWorld(), GetWorld().GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS)); + BQCalculator calcBQ(GetWorld()); visualNodes[pt].bq = calcBQ(pt, [this](const MapPoint& pos) { return IsOnRoad(pos); }); } diff --git a/tests/s25Main/integration/testSeaWorldCreation.cpp b/tests/s25Main/integration/testSeaWorldCreation.cpp index 76072b42c6..a25d326096 100644 --- a/tests/s25Main/integration/testSeaWorldCreation.cpp +++ b/tests/s25Main/integration/testSeaWorldCreation.cpp @@ -90,6 +90,39 @@ void createMarkerlessIslandWorld(GameWorld& world) } } } + +unsigned countHarborBQ(const GameWorld& world) +{ + unsigned result = 0; + RTTR_FOREACH_PT(MapPoint, world.GetSize()) + { + if(world.GetNode(pt).bq == BuildingQuality::Harbor) + ++result; + } + return result; +} + +void testHarborPoint(const GameWorld& world, const HarborId harborId) +{ + const MapPoint harborPt = world.GetHarborPoint(harborId); + BOOST_TEST_REQUIRE(harborPt.isValid()); + BOOST_TEST_REQUIRE(world.GetHarborPointID(harborPt) == harborId); + + bool hasSea = false; + for(const auto dir : helpers::EnumRange{}) + { + const SeaId seaId = world.GetSeaId(harborId, dir); + if(!seaId) + continue; + + hasSea = true; + const MapPoint coastalPt = world.GetCoastalPoint(harborId, seaId); + BOOST_TEST_REQUIRE(coastalPt.isValid()); + BOOST_TEST_REQUIRE(world.GetSeaFromCoastalPoint(coastalPt) == seaId); + } + BOOST_TEST_REQUIRE(hasSea); + BOOST_TEST_REQUIRE(world.GetNode(harborPt).bq == BuildingQuality::Harbor); +} } // namespace BOOST_AUTO_TEST_CASE(GetShipDir) @@ -168,10 +201,7 @@ BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonAddsCoastalHarbors, SeaWorldWithGCEx BOOST_TEST_REQUIRE(world.GetNumHarborPoints() > initialHarbors); for(unsigned harborIdx = initialHarbors + 1; harborIdx <= world.GetNumHarborPoints(); ++harborIdx) { - const MapPoint harborPt = world.GetHarborPoint(HarborId(harborIdx)); - BOOST_TEST_REQUIRE(harborPt.isValid()); - BOOST_TEST_REQUIRE(world.GetHarborPointID(harborPt) == HarborId(harborIdx)); - BOOST_TEST_REQUIRE(world.GetNode(harborPt).bq == BuildingQuality::Harbor); + testHarborPoint(world, HarborId(harborIdx)); } } @@ -179,18 +209,23 @@ BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonWorksWithoutMapMarkers, SeaWorldWith { createMarkerlessIslandWorld(world); BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world)); + world.InitAfterLoad(); BOOST_TEST_REQUIRE(world.GetNumHarborPoints() == 0u); + BOOST_TEST_REQUIRE(countHarborBQ(world) == 0u); ggs.setSelection(AddonId::FREE_HARBOR_SPOTS, 1); + BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world)); + world.InitAfterLoad(); + BOOST_TEST_REQUIRE(world.GetNumHarborPoints() == 0u); + BOOST_TEST_REQUIRE(countHarborBQ(world) == 0u); + BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world, std::vector(), true)); world.InitAfterLoad(); BOOST_TEST_REQUIRE(world.GetNumHarborPoints() > 0u); for(const auto harborId : helpers::idRange(world.GetNumHarborPoints())) { - const MapPoint harborPt = world.GetHarborPoint(harborId); - BOOST_TEST_REQUIRE(world.GetHarborPointID(harborPt) == harborId); - BOOST_TEST_REQUIRE(world.GetNode(harborPt).bq == BuildingQuality::Harbor); + testHarborPoint(world, harborId); } } From 676ed66278ae5172db3bd1a43988b332f74b827e Mon Sep 17 00:00:00 2001 From: Manu Date: Sat, 2 May 2026 13:37:53 +0200 Subject: [PATCH 3/4] Clarify free harbor spot review changes --- libs/s25main/addons/AddonFreeHarborSpots.h | 2 +- libs/s25main/world/BQCalculator.h | 15 +++++++-------- .../s25Main/integration/testSeaWorldCreation.cpp | 14 +++++++++++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/libs/s25main/addons/AddonFreeHarborSpots.h b/libs/s25main/addons/AddonFreeHarborSpots.h index 5d42cbff9e..a06f4f044c 100644 --- a/libs/s25main/addons/AddonFreeHarborSpots.h +++ b/libs/s25main/addons/AddonFreeHarborSpots.h @@ -12,6 +12,6 @@ class AddonFreeHarborSpots : public AddonBool public: AddonFreeHarborSpots() : AddonBool(AddonId::FREE_HARBOR_SPOTS, AddonGroup::GamePlay, _("Build harbors without map markers"), - _("Allows harbors on suitable coastal castle sites even if the map does not define harbor spots.")) + _("Allows harbors on all suitable coastal castle sites even if the map does not define harbor spots.")) {} }; diff --git a/libs/s25main/world/BQCalculator.h b/libs/s25main/world/BQCalculator.h index 6452b6fa15..750d77cf81 100644 --- a/libs/s25main/world/BQCalculator.h +++ b/libs/s25main/world/BQCalculator.h @@ -10,8 +10,8 @@ struct BQCalculator { - BQCalculator(const World& world, const bool allowFreeHarborSpots = false) - : world(world), allowFreeHarborSpots(allowFreeHarborSpots) + BQCalculator(const World& world, const bool allowHarborsWithoutMapMarkers = false) + : world(world), allowHarborsWithoutMapMarkers(allowHarborsWithoutMapMarkers) {} template @@ -19,7 +19,7 @@ struct BQCalculator private: const World& world; - bool allowFreeHarborSpots; + bool allowHarborsWithoutMapMarkers; }; template @@ -224,21 +224,20 @@ BuildingQuality BQCalculator::operator()(const MapPoint pt, T_IsOnRoad isOnRoad, // If we can build a castle and this is a harbor point -> Allow harbor if(curBQ == BuildingQuality::Castle) { - bool isHarborPoint = world.GetNode(pt).harborId.isValid(); - if(!isHarborPoint && allowFreeHarborSpots) + if(world.GetNode(pt).harborId.isValid()) + curBQ = BuildingQuality::Harbor; + else if(allowHarborsWithoutMapMarkers) { for(const auto dir : helpers::EnumRange{}) { // Keep this in sync with harbor initialization: NW-only coasts are rejected there. if(dir != Direction::NorthWest && world.GetSeaFromCoastalPoint(neighbours[dir])) { - isHarborPoint = true; + curBQ = BuildingQuality::Harbor; break; } } } - if(isHarborPoint) - curBQ = BuildingQuality::Harbor; } ////////////////////////////////////////////////////////////////////////// diff --git a/tests/s25Main/integration/testSeaWorldCreation.cpp b/tests/s25Main/integration/testSeaWorldCreation.cpp index a25d326096..58133bcae6 100644 --- a/tests/s25Main/integration/testSeaWorldCreation.cpp +++ b/tests/s25Main/integration/testSeaWorldCreation.cpp @@ -6,7 +6,9 @@ #include "RttrForeachPt.h" #include "helpers/IdRange.h" #include "addons/const_addons.h" +#include "worldFixtures/CreateSeaWorld.h" #include "worldFixtures/SeaWorldWithGCExecution.h" +#include "worldFixtures/WorldFixture.h" #include "worldFixtures/terrainHelpers.h" #include "gameTypes/GameTypesOutput.h" #include "gameTypes/ShipDirection.h" @@ -123,6 +125,13 @@ void testHarborPoint(const GameWorld& world, const HarborId harborId) BOOST_TEST_REQUIRE(hasSea); BOOST_TEST_REQUIRE(world.GetNode(harborPt).bq == BuildingQuality::Harbor); } + +using SeaWorldFixture = WorldFixture; + +struct MarkerlessIslandFixture : WorldFixtureBase +{ + MarkerlessIslandFixture() : WorldFixtureBase(3) { createMarkerlessIslandWorld(world); } +}; } // namespace BOOST_AUTO_TEST_CASE(GetShipDir) @@ -190,7 +199,7 @@ BOOST_FIXTURE_TEST_CASE(HarborSpotCreation, SeaWorldWithGCExecution<>) } } -BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonAddsCoastalHarbors, SeaWorldWithGCExecution<>) +BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonAddsCoastalHarbors, SeaWorldFixture) { const unsigned initialHarbors = world.GetNumHarborPoints(); @@ -205,9 +214,8 @@ BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonAddsCoastalHarbors, SeaWorldWithGCEx } } -BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonWorksWithoutMapMarkers, SeaWorldWithGCExecution<>) +BOOST_FIXTURE_TEST_CASE(FreeHarborSpotsAddonWorksWithoutMapMarkers, MarkerlessIslandFixture) { - createMarkerlessIslandWorld(world); BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world)); world.InitAfterLoad(); BOOST_TEST_REQUIRE(world.GetNumHarborPoints() == 0u); From 49cd2274fb6b2c4dff9ac87854601ae9581f21a1 Mon Sep 17 00:00:00 2001 From: Manu Date: Sun, 3 May 2026 10:08:12 +0200 Subject: [PATCH 4/4] gameplay: warn about free harbor spots addon risk --- libs/s25main/addons/AddonFreeHarborSpots.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/s25main/addons/AddonFreeHarborSpots.h b/libs/s25main/addons/AddonFreeHarborSpots.h index a06f4f044c..86c409dae2 100644 --- a/libs/s25main/addons/AddonFreeHarborSpots.h +++ b/libs/s25main/addons/AddonFreeHarborSpots.h @@ -11,7 +11,8 @@ class AddonFreeHarborSpots : public AddonBool { public: AddonFreeHarborSpots() - : AddonBool(AddonId::FREE_HARBOR_SPOTS, AddonGroup::GamePlay, _("Build harbors without map markers"), - _("Allows harbors on all suitable coastal castle sites even if the map does not define harbor spots.")) + : AddonBool(AddonId::FREE_HARBOR_SPOTS, AddonGroup::GamePlay, _("Dangerous: Build harbors without map markers"), + _("Advanced option. Allows harbors on all suitable coastal castle sites even if the map does not " + "define harbor spots. May heavily alter intended map seafaring design.")) {} };