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."))
{}
};
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
24 changes: 21 additions & 3 deletions libs/s25main/world/BQCalculator.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@

struct BQCalculator
{
BQCalculator(const World& world) : world(world) {}
BQCalculator(const World& world, const bool allowHarborsWithoutMapMarkers = false)
: world(world), allowHarborsWithoutMapMarkers(allowHarborsWithoutMapMarkers)
{}

template<typename T_IsOnRoad>
BuildingQuality operator()(MapPoint pt, T_IsOnRoad isOnRoad, bool flagOnly = false) const;

private:
const World& world;
bool allowHarborsWithoutMapMarkers;
};

template<typename T_IsOnRoad>
Expand Down Expand Up @@ -219,8 +222,23 @@ 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)
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 should not be in here as it slows down processing during the game. This can be fully done at the place where this information is required and it makes the "keep in sync" part easier as both will be in the same file

{
if(world.GetNode(pt).harborId.isValid())
curBQ = BuildingQuality::Harbor;
else if(allowHarborsWithoutMapMarkers)
{
for(const auto dir : helpers::EnumRange<Direction>{})
{
// Keep this in sync with harbor initialization: NW-only coasts are rejected there.
if(dir != Direction::NorthWest && world.GetSeaFromCoastalPoint(neighbours[dir]))
{
curBQ = BuildingQuality::Harbor;
break;
}
}
}
}

//////////////////////////////////////////////////////////////////////////
// At this point we can still build a building/mine
Expand Down
57 changes: 55 additions & 2 deletions libs/s25main/world/MapLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<MapPoint>(), world_.GetGGS().isEnabled(AddonId::FREE_HARBOR_SPOTS)))
return false;

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

bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& additionalHarbors)
namespace {
bool hasHarborAt(const World& world, const MapPoint pt)
{
for(const auto harborId : helpers::idRange<HarborId>(world.GetNumHarborPoints()))
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 might be unnecessarily slow: You can simply collect all harbor positions in a vector and use helpers::contains, can't you?

{
if(world.GetHarborPoint(harborId) == pt)
return true;
}
return false;
}

bool isFarEnoughFromHarbors(const World& world, const MapPoint pt, const std::vector<MapPoint>& generatedHarbors)
{
for(const auto harborId : helpers::idRange<HarborId>(world.GetNumHarborPoints()))
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.

Similar as above: If you have a single vector of all harbor points this is much faster and shorter

{
if(world.CalcDistance(pt, world.GetHarborPoint(harborId)) < MapLoader::MIN_GENERATED_HARBOR_DISTANCE)
return false;
}
for(const MapPoint generatedHarbor : generatedHarbors)
{
if(world.CalcDistance(pt, generatedHarbor) < MapLoader::MIN_GENERATED_HARBOR_DISTANCE)
return false;
}
return true;
}

std::vector<MapPoint> getGeneratedHarbors(const World& world)
{
std::vector<MapPoint> generatedHarbors;
BQCalculator calcBQ(world, true);
RTTR_FOREACH_PT(MapPoint, world.GetSize())
{
if(!hasHarborAt(world, pt) && calcBQ(pt, [](const MapPoint&) { return false; }) == BuildingQuality::Harbor
&& isFarEnoughFromHarbors(world, pt, generatedHarbors))
{
generatedHarbors.push_back(pt);
if(generatedHarbors.size() == MapLoader::MAX_GENERATED_HARBOR_SPOTS)
return generatedHarbors;
}
}
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 @@ -446,6 +493,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 @@ -39,6 +39,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 @@ -57,7 +60,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
Loading