Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/s25main/GlobalGameSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void GlobalGameSettings::registerAllAddons()
AddonDurableGeologistSigns,
AddonEconomyModeGameLength,
AddonExhaustibleWater,
AddonFreeHarborSpots,
AddonFrontierDistanceReachable,
AddonHalfCostMilEquip,
AddonInexhaustibleFish,
Expand Down
18 changes: 18 additions & 0 deletions libs/s25main/addons/AddonFreeHarborSpots.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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, _("Dangerous: Add limited extra harbor spots"),
_("Advanced option. Adds only a small deterministic set of suitable coastal castle sites as extra "
"harbor spots. May alter intended map seafaring design."))
Comment on lines +14 to +16

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest:

Suggested change
: AddonBool(AddonId::FREE_HARBOR_SPOTS, AddonGroup::GamePlay, _("Dangerous: Add limited extra harbor spots"),
_("Advanced option. Adds only a small deterministic set of suitable coastal castle sites as extra "
"harbor spots. May alter intended map seafaring design."))
: AddonBool(AddonId::FREE_HARBOR_SPOTS, AddonGroup::GamePlay, _("Dangerous: Add extra harbor spots"),
_("Advanced option. Converts a small set of suitable coastal castle sites to harbor spots. "
"Caution: May alter intended map seafaring design."))

The "limited" isn't required as there is no other option and "only a small" also seems like comparing against something.
Could also replace "suitable coastal castle sites" by "suitable castle sites" or even "suitable points", as e.g. I can't find a good German translation that sounds good and "suitable" should be specific enough

{}
};
1 change: 1 addition & 0 deletions libs/s25main/addons/Addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

#include "addons/AddonCoinsCapturedBld.h"
#include "addons/AddonDemolishBldWORes.h"
#include "addons/AddonFreeHarborSpots.h"
#include "addons/AddonFrontierDistanceReachable.h"

#include "addons/AddonDurableGeologistSigns.h"
Expand Down
2 changes: 1 addition & 1 deletion libs/s25main/addons/const_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,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,

FORESTER_FARM_FIELD_AVOIDANCE = 0x01100000,

Expand Down
92 changes: 90 additions & 2 deletions libs/s25main/world/MapLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "world/MapLoader.h"
#include "BQCalculator.h"
#include "Game.h"
#include "GamePlayer.h"
#include "GameWorldBase.h"
Expand Down Expand Up @@ -35,6 +36,8 @@
#include "s25util/Log.h"
#include <boost/filesystem/operations.hpp>
#include <algorithm>
#include <iterator>
#include <limits>
#include <map>
#include <queue>

Expand All @@ -57,7 +60,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<MapPoint>(), world_.GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS)))
return false;

/// Schatten
Expand Down Expand Up @@ -523,7 +526,86 @@ bool MapLoader::PlaceHQs(GameWorldBase& world, const std::vector<MapPoint>& hqPo
return true;
}

bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& additionalHarbors)
namespace {
bool hasEligibleHarborCoast(const World& world, const MapPoint pt)
{
for(const auto dir : helpers::EnumRange<Direction>{})
{
if(dir != Direction::NorthWest && world.GetSeaFromCoastalPoint(world.GetNeighbour(pt, dir)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a comment on the !=NW

return true;
}
return false;
}

bool isFarEnoughFromHarbors(const World& world, const MapPoint pt, const std::vector<MapPoint>& harborPositions)
{
for(const MapPoint harborPt : harborPositions)
{
if(world.CalcDistance(pt, harborPt) < MapLoader::MIN_GENERATED_HARBOR_DISTANCE)
return false;
}
return true;
}

unsigned getMinimumHarborDistance(const World& world, const MapPoint pt, const std::vector<MapPoint>& harborPositions)
{
unsigned minDistance = std::numeric_limits<unsigned>::max();
for(const MapPoint harborPt : harborPositions)
minDistance = std::min(minDistance, world.CalcDistance(pt, harborPt));
return minDistance;
}

std::vector<MapPoint> getGeneratedHarbors(const World& world)
{
std::vector<MapPoint> harborPositions;
harborPositions.reserve(world.GetNumHarborPoints() + MapLoader::MAX_GENERATED_HARBOR_SPOTS);
for(const auto harborId : helpers::idRange<HarborId>(world.GetNumHarborPoints()))
harborPositions.push_back(world.GetHarborPoint(harborId));

std::vector<MapPoint> candidates;
BQCalculator calcBQ(world);
RTTR_FOREACH_PT(MapPoint, world.GetSize())
{
if(helpers::contains(harborPositions, pt))
continue;
if(calcBQ(pt, [](const MapPoint&) { return false; }) != BuildingQuality::Castle)
continue;
if(!hasEligibleHarborCoast(world, pt))
continue;
if(!isFarEnoughFromHarbors(world, pt, harborPositions))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a wrapper around a raw loop only used here: Always consider using algorithms instead of raw loops. Here I'd rather have:

Suggested change
if(!isFarEnoughFromHarbors(world, pt, harborPositions))
if(!helpers::contains_if(harborPositions, [&](const MapPoint harborPt){ return world.CalcDistance(pt, harborPt) < MapLoader::MIN_GENERATED_HARBOR_DISTANCE; }))

Or a named var for the lambda if you consider that not readable enough. Or put that into the function instead of the raw loop

continue;

candidates.push_back(pt);
}

std::vector<MapPoint> generatedHarbors;
while(!candidates.empty() && generatedHarbors.size() < MapLoader::MAX_GENERATED_HARBOR_SPOTS)
{
auto bestCandidate = candidates.begin();
unsigned bestDistance = getMinimumHarborDistance(world, *bestCandidate, harborPositions);
for(auto it = std::next(candidates.begin()); it != candidates.end(); ++it)
Comment on lines +584 to +586

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say the single saved check per generated harbor isn't worth the readability-hit compared to range-based-for (compare to getMinimumHarborDistance)
So use that instead please. If you start with bestDistance = 0 you don't even need to check candidates.empty() above

{
const unsigned distance = getMinimumHarborDistance(world, *it, harborPositions);
if(distance > bestDistance)
{
bestDistance = distance;
bestCandidate = it;
}
}

if(bestDistance < MapLoader::MIN_GENERATED_HARBOR_DISTANCE)
break;

generatedHarbors.push_back(*bestCandidate);
harborPositions.push_back(*bestCandidate);
candidates.erase(bestCandidate);
}
return generatedHarbors;
}
} // namespace

bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& additionalHarbors,
const bool generateHarborSpots)
{
for(MapPoint pt : additionalHarbors)
world.harborData.push_back(HarborPos(pt));
Expand All @@ -549,6 +631,12 @@ bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& 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();)
Expand Down
6 changes: 5 additions & 1 deletion libs/s25main/world/MapLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class MapLoader
static void CalcHarborPosNeighbors(World& world);

public:
static constexpr unsigned MAX_GENERATED_HARBOR_SPOTS = 4;
static constexpr unsigned MIN_GENERATED_HARBOR_DISTANCE = 12;

/// Construct a loader for the given world.
explicit MapLoader(GameWorldBase& world);
/// Load the map from the given archive, resetting previous state. Return false on error
Expand All @@ -61,7 +64,8 @@ class MapLoader
static void InitShadows(World& world);
static void SetMapExplored(World& world);
static bool InitSeasAndHarbors(World& world,
const std::vector<MapPoint>& additionalHarbors = std::vector<MapPoint>());
const std::vector<MapPoint>& additionalHarbors = std::vector<MapPoint>(),
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<MapPoint>& hqPositions, bool addStartWares = true);
Expand Down
Loading