diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 198b122ec1..320fc4693b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -207,6 +207,9 @@ jobs: - name: Build run: tools/ci/build.sh + - name: Test + run: tools/ci/test.sh + - run: tools/ci/collectCoverageData.sh && external/libutil/tools/ci/uploadCoverageData.sh if: matrix.coverage && success() diff --git a/CMakeLists.txt b/CMakeLists.txt index f9feb0db59..583b11c274 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -336,7 +336,7 @@ add_subdirectory(libs) add_subdirectory(extras) find_package(Gettext REQUIRED) -file(GLOB RTTR_PO_FILES external/languages/*.po) +file(GLOB RTTR_PO_FILES CONFIGURE_DEPENDS external/languages/*.po) set(RTTR_TRANSLATION_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gen/languages) # Used by copyDepsToBuildDir gettext_create_translations(external/languages/rttr.pot ALL DESTINATION ${RTTR_TRANSLATION_OUTPUT} diff --git a/libs/common/CMakeLists.txt b/libs/common/CMakeLists.txt index 0dea795a64..0a4cf2af06 100644 --- a/libs/common/CMakeLists.txt +++ b/libs/common/CMakeLists.txt @@ -4,10 +4,10 @@ set(RTTR_Assert_Enabled 2 CACHE STRING "Status of RTTR assertions: 0=Disabled, 1=Enabled, 2=Default(Enabled only in debug)") -file(GLOB COMMON_SRC src/*.cpp) -file(GLOB COMMON_HEADERS include/*.h include/*.hpp) -file(GLOB COMMON_HELPERS_SRC src/helpers/*.cpp) -file(GLOB COMMON_HELPERS_HEADERS include/helpers/*.h include/helpers/*.hpp) +file(GLOB COMMON_SRC CONFIGURE_DEPENDS src/*.cpp) +file(GLOB COMMON_HEADERS CONFIGURE_DEPENDS include/*.h include/*.hpp) +file(GLOB COMMON_HELPERS_SRC CONFIGURE_DEPENDS src/helpers/*.cpp) +file(GLOB COMMON_HELPERS_HEADERS CONFIGURE_DEPENDS include/helpers/*.h include/helpers/*.hpp) set(ALL_SRC ${COMMON_SRC} ${COMMON_HEADERS} ${COMMON_HELPERS_SRC} ${COMMON_HELPERS_HEADERS}) source_group(src FILES ${COMMON_SRC} ${COMMON_HEADERS}) diff --git a/libs/common/include/helpers/IdRange.h b/libs/common/include/helpers/IdRange.h new file mode 100644 index 0000000000..8d7f497431 --- /dev/null +++ b/libs/common/include/helpers/IdRange.h @@ -0,0 +1,68 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "RTTR_Assert.h" +#include "StrongId.h" +#include +#include + +namespace helpers { +/// Iterate over all valid strong Ids of a given container size +template +class idRange; + +template +class idRange> +{ + using id_t = StrongId; + using underlying_t = typename id_t::underlying_t; + const underlying_t startValue_; + const underlying_t endValue_; + + explicit idRange(underlying_t startValue, underlying_t count) : startValue_(startValue), endValue_(count + 1) + { + RTTR_Assert(count < std::numeric_limits::max()); + RTTR_Assert(startValue_ <= endValue_); + } + +public: + /// Create range for container with given number of elements + explicit idRange(underlying_t count) : idRange(1, count) {} + + class iterator + { + protected: + underlying_t value; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = id_t; + using difference_type = std::make_signed_t>; + using pointer = id_t*; + using reference = id_t&; + + explicit constexpr iterator(underlying_t value) : value(value) {} + constexpr id_t operator*() const noexcept { return id_t(value); } + void operator++() noexcept { ++value; } + constexpr bool operator!=(iterator rhs) const noexcept { return value != rhs.value; } + }; + + iterator begin() const noexcept { return iterator(startValue_); } + iterator end() const noexcept { return iterator(endValue_); } + + template + friend constexpr idRange idRangeAfter(T_Id prev, U2 totalCount); +}; + +/// Return range starting after the given value +/// totalCount is the count of the full range not including the `prev` element +template +constexpr idRange idRangeAfter(T prev, U totalCount) +{ + return idRange(prev.next().value(), totalCount); +} + +} // namespace helpers diff --git a/libs/common/include/helpers/Range.h b/libs/common/include/helpers/Range.h index 9ee81a91f3..469bb229ee 100644 --- a/libs/common/include/helpers/Range.h +++ b/libs/common/include/helpers/Range.h @@ -29,6 +29,7 @@ class range class iterator { + protected: T value; public: diff --git a/libs/common/include/helpers/StrongId.h b/libs/common/include/helpers/StrongId.h new file mode 100644 index 0000000000..108e545197 --- /dev/null +++ b/libs/common/include/helpers/StrongId.h @@ -0,0 +1,54 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace helpers { + +/// Template for declaring strong types used for IDs +/// Very thin wrapper mainly to distinguish different IDs. +/// The "invalid" value is just a marker and could be used as any other value. +/// StrongId can be used as indices into 1-based containers, i.e. the entry at index 0 is the invalid entry. +/// See `StrongIdVector` for avoiding that entry. +/// Usage: using MyId = StrongId +template +class StrongId +{ + static_assert(std::is_integral_v && std::is_unsigned_v); + static constexpr Underlying invalidValue_ = 0; + +public: + using underlying_t = Underlying; + + /// Factory function to get the invalid ID + static constexpr StrongId invalidValue() { return StrongId(invalidValue_); } + + /// Construct an invalid ID + constexpr StrongId() : value_(invalidValue_){}; + constexpr explicit StrongId(underlying_t value) : value_(value) {} + + constexpr explicit operator bool() const noexcept { return isValid(); } + constexpr bool isValid() const noexcept { return value_ != invalidValue_; } + + constexpr underlying_t value() const { return value_; } + + /// Return the ID following this ID + constexpr StrongId next() noexcept { return StrongId(value_ + 1); } + /// Reset to invalid ID (default value) + constexpr void reset() noexcept { value_ = invalidValue_; } + + explicit constexpr operator Underlying() const { return value_; } + + friend constexpr bool operator==(const StrongId r, const StrongId l) { return r.value_ == l.value_; }; + friend constexpr bool operator!=(const StrongId r, const StrongId l) { return r.value_ != l.value_; }; + + friend std::ostream& operator<<(std::ostream& os, const StrongId t) { return os << t.value_; } + +private: + underlying_t value_; +}; +} // namespace helpers diff --git a/libs/common/include/helpers/StrongIdVector.h b/libs/common/include/helpers/StrongIdVector.h new file mode 100644 index 0000000000..19d7872391 --- /dev/null +++ b/libs/common/include/helpers/StrongIdVector.h @@ -0,0 +1,37 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "RTTR_Assert.h" +#include "StrongId.h" +#include + +namespace helpers { + +/// Vector of T-elements (std::vector) indexed by StrongId TIndex +template +struct StrongIdVector; + +template +struct StrongIdVector> : std::vector +{ + using Parent = std::vector; + using index_t = StrongId; + + using Parent::Parent; + + typename Parent::reference operator[](index_t id) + { + RTTR_Assert(id && id.value() <= this->size()); + return Parent::operator[](id.value() - 1); + } + typename Parent::const_reference operator[](index_t id) const + { + RTTR_Assert(id && id.value() <= this->size()); + return Parent::operator[](id.value() - 1); + } +}; + +} // namespace helpers diff --git a/libs/common/include/helpers/serializeContainers.h b/libs/common/include/helpers/serializeContainers.h index 0af2e6f75b..b29a927775 100644 --- a/libs/common/include/helpers/serializeContainers.h +++ b/libs/common/include/helpers/serializeContainers.h @@ -1,10 +1,11 @@ -// Copyright (C) 2005 - 2025 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 #pragma once #include "serializeEnums.h" +#include "underlyingIntegral.h" #include "s25util/Serializer.h" #include @@ -28,18 +29,14 @@ namespace detail { template std::enable_if_t, T> popEnumOrIntegral(Serializer& ser) { - return ser.Pop(); + using Integral = underlyingIntegral_t; + return T(ser.Pop()); } template void pushContainer(Serializer& ser, const T& container, long) { - using Type = typename T::value_type; - // Deactivated to bug in clang-tidy - // NOLINTBEGIN(modernize-type-traits) - using Integral = - typename std::conditional_t, std::underlying_type, std::common_type>::type; - // NOLINTEND(modernize-type-traits) + using Integral = underlyingIntegral_t; for(const auto el : container) { // Cast also required for bool vector -.- @@ -82,7 +79,7 @@ template void pushContainer(Serializer& ser, const T& container, bool ignoreSize = false) { using Type = typename T::value_type; - static_assert(std::is_integral_v || std::is_enum_v, "Only integral types and enums are possible"); + static_assert(IsIntegralLike_v, "Only integral(-like) types are possible"); if(detail::isResizableContainer_v && !ignoreSize) ser.PushVarSize(container.size()); @@ -93,7 +90,7 @@ template void popContainer(Serializer& ser, T&& result, bool ignoreSize = false) { using Type = typename std::remove_reference_t::value_type; - static_assert(std::is_integral_v || std::is_enum_v, "Only integral types and enums are possible"); + static_assert(IsIntegralLike_v, "Only integral(-like) types are possible"); if(!ignoreSize) detail::maybePopSizeAndResize(ser, result); diff --git a/libs/common/include/helpers/toString.h b/libs/common/include/helpers/toString.h index 987dce0234..b49be24d2b 100644 --- a/libs/common/include/helpers/toString.h +++ b/libs/common/include/helpers/toString.h @@ -1,9 +1,10 @@ -// Copyright (C) 2005 - 2025 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 #pragma once +#include "underlyingIntegral.h" #include #include @@ -16,18 +17,13 @@ std::string toString(const T value) return std::to_string(value); } -template, int> = 0> +template, int> = 0> std::string toString(const T value) { - constexpr bool typeAtLeastInt = sizeof(T) >= sizeof(int); - using TargetType = std::conditional_t, int, unsigned>>; - return std::to_string(static_cast(value)); -} - -template, int> = 0> -std::string toString(const T value) -{ - return toString(static_cast>(value)); + using U = underlyingIntegral_t; + constexpr bool typeAtLeastInt = sizeof(U) >= sizeof(int); + using TargetType = std::conditional_t, int, unsigned>>; + return std::to_string(static_cast(static_cast(value))); } } // namespace helpers diff --git a/libs/common/include/helpers/underlyingIntegral.h b/libs/common/include/helpers/underlyingIntegral.h new file mode 100644 index 0000000000..2be9ac38a7 --- /dev/null +++ b/libs/common/include/helpers/underlyingIntegral.h @@ -0,0 +1,45 @@ +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "serializeEnums.h" +#include + +namespace helpers { + +/// Get underlying type, i.e. the "storage" type of T +template +struct underlyingIntegral; +template +struct underlyingIntegral> +{ + using type = typename T::underlying_t; +}; +template +struct underlyingIntegral>> +{ + using type = std::underlying_type_t; +}; +template +struct underlyingIntegral>> +{ + using type = T; +}; +template +using underlyingIntegral_t = typename underlyingIntegral::type; + +/// Detect if the type is an integral, or has an underlying integral +template +struct IsIntegralLike : std::false_type +{}; + +template +struct IsIntegralLike>> : std::true_type +{}; + +template +constexpr bool IsIntegralLike_v = IsIntegralLike::value; + +} // namespace helpers \ No newline at end of file diff --git a/libs/libGamedata/CMakeLists.txt b/libs/libGamedata/CMakeLists.txt index eff5db1cd7..0b18660d23 100644 --- a/libs/libGamedata/CMakeLists.txt +++ b/libs/libGamedata/CMakeLists.txt @@ -4,7 +4,7 @@ set(SOURCES_SUBDIRS ) macro(AddDirectory dir) - file(GLOB SUB_FILES ${dir}/*.cpp ${dir}/*.h ${dir}/*.hpp ${dir}/*.tpp) + file(GLOB SUB_FILES CONFIGURE_DEPENDS ${dir}/*.cpp ${dir}/*.h ${dir}/*.hpp ${dir}/*.tpp) set(SOURCES_SUBDIRS ${SOURCES_SUBDIRS} ${SUB_FILES}) source_group(${dir} FILES ${SUB_FILES}) endmacro() @@ -12,7 +12,7 @@ endmacro() AddDirectory(gameData) AddDirectory(lua) -file(GLOB SOURCES_OTHER *.cpp *.h) +file(GLOB SOURCES_OTHER CONFIGURE_DEPENDS *.cpp *.h) source_group(other FILES ${SOURCES_OTHER}) find_package(Lua 5.1 REQUIRED) diff --git a/libs/s25main/CMakeLists.txt b/libs/s25main/CMakeLists.txt index 0d42d75cef..da699b5af9 100644 --- a/libs/s25main/CMakeLists.txt +++ b/libs/s25main/CMakeLists.txt @@ -7,7 +7,7 @@ gather_dll(BZIP2) set(SOURCES_SUBDIRS ) macro(AddDirectory dir) - file(GLOB SUB_FILES ${dir}/*.cpp ${dir}/*.h ${dir}/*.hpp ${dir}/*.tpp) + file(GLOB SUB_FILES CONFIGURE_DEPENDS ${dir}/*.cpp ${dir}/*.h ${dir}/*.hpp ${dir}/*.tpp) set(SOURCES_SUBDIRS ${SOURCES_SUBDIRS} ${SUB_FILES}) source_group(${dir} FILES ${SUB_FILES}) endmacro() @@ -37,7 +37,7 @@ AddDirectory(postSystem) AddDirectory(random) AddDirectory(resources) AddDirectory(world) -file(GLOB SOURCES_OTHER *.cpp *.h) +file(GLOB SOURCES_OTHER CONFIGURE_DEPENDS *.cpp *.h) source_group(other FILES ${SOURCES_OTHER}) set(s25Main_SRCS diff --git a/libs/s25main/Debug.cpp b/libs/s25main/Debug.cpp index 9c5ded36d8..de0268b96c 100644 --- a/libs/s25main/Debug.cpp +++ b/libs/s25main/Debug.cpp @@ -238,7 +238,7 @@ bool DebugInfo::SendString(const char* str, size_t len) len = strlen(str) + 1; if(!SendUnsigned(len)) - return (false); + return false; return (Send(str, len)); } diff --git a/libs/s25main/GamePlayer.cpp b/libs/s25main/GamePlayer.cpp index 31bd6098a5..924818cd17 100644 --- a/libs/s25main/GamePlayer.cpp +++ b/libs/s25main/GamePlayer.cpp @@ -1954,7 +1954,7 @@ bool GamePlayer::OrderShip(nobHarborBuilding& hb) if(ship.GetPos() == dest) { hb.ShipArrived(ship); - return (true); + return true; } if(world.FindShipPathToHarbor(ship.GetPos(), hb.GetHarborPosID(), ship.GetSeaID(), &route, &distance)) @@ -1973,23 +1973,18 @@ bool GamePlayer::OrderShip(nobHarborBuilding& hb) { best_ship->GoToHarbor(hb, best_route); - return (true); + return true; } - return (false); + return false; } /// Meldet das Schiff wieder ab void GamePlayer::RemoveShip(noShip* ship) { - for(unsigned i = 0; i < ships.size(); ++i) - { - if(ships[i] == ship) - { - ships.erase(ships.begin() + i); - return; - } - } + auto it = helpers::find(ships, ship); + RTTR_Assert(it != ships.end()); + ships.erase(it); } /// Versucht, für ein untätiges Schiff eine Arbeit zu suchen @@ -2049,14 +2044,9 @@ void GamePlayer::GetJobForShip(noShip& ship) ship.GoToHarbor(*best, best_route); } -/// Gibt die ID eines Schiffes zurück -unsigned GamePlayer::GetShipID(const noShip* const ship) const +unsigned GamePlayer::GetShipID(const noShip& ship) const { - for(unsigned i = 0; i < ships.size(); ++i) - if(ships[i] == ship) - return i; - - return 0xFFFFFFFF; + return static_cast(helpers::indexOf(ships, &ship)); } /// Gibt ein Schiff anhand der ID zurück bzw. nullptr, wenn keines mit der ID existiert @@ -2068,16 +2058,15 @@ noShip* GamePlayer::GetShipByID(const unsigned ship_id) const return ships[ship_id]; } -/// Gibt eine Liste mit allen Häfen dieses Spieler zurück, die an ein bestimmtes Meer angrenzen -void GamePlayer::GetHarborsAtSea(std::vector& harbor_buildings, const unsigned short seaId) const +void GamePlayer::AddHarborsAtSea(std::vector& harborBuildings, const SeaId seaId) const { for(nobHarborBuilding* harbor : buildings.GetHarbors()) { - if(helpers::contains(harbor_buildings, harbor)) + if(helpers::contains(harborBuildings, harbor)) continue; if(world.IsHarborAtSea(harbor->GetHarborPosID(), seaId)) - harbor_buildings.push_back(harbor); + harborBuildings.push_back(harbor); } } @@ -2096,7 +2085,7 @@ unsigned GamePlayer::GetShipsToHarbor(const nobHarborBuilding& hb) const /// Sucht einen Hafen in der Nähe, wo dieses Schiff seine Waren abladen kann /// gibt true zurück, falls erfolgreich -bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, unsigned* goal_harborId, +bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, HarborId* goalHarborId, std::vector* route, nobHarborBuilding* exception) { nobHarborBuilding* best = nullptr; @@ -2128,7 +2117,7 @@ bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, unsi { // Weg dorthin suchen route->clear(); - *goal_harborId = best->GetHarborPosID(); + *goalHarborId = best->GetHarborPosID(); const MapPoint coastPt = world.GetCoastalPoint(best->GetHarborPosID(), ship->GetSeaID()); if(start == coastPt || world.FindShipPathToHarbor(start, best->GetHarborPosID(), ship->GetSeaID(), route, nullptr)) diff --git a/libs/s25main/GamePlayer.h b/libs/s25main/GamePlayer.h index 95f4d05ceb..469da20b2a 100644 --- a/libs/s25main/GamePlayer.h +++ b/libs/s25main/GamePlayer.h @@ -12,6 +12,7 @@ #include "gameTypes/BuildingType.h" #include "gameTypes/Inventory.h" #include "gameTypes/MapCoordinates.h" +#include "gameTypes/MapTypes.h" #include "gameTypes/PactTypes.h" #include "gameTypes/SettingsTypes.h" #include "gameTypes/StatisticTypes.h" @@ -243,20 +244,20 @@ class GamePlayer : public GamePlayerInfo /// Schiff für Hafen bestellen. Wenn ein Schiff kommt, true. bool OrderShip(nobHarborBuilding& hb); /// Gibt die ID eines Schiffes zurück - unsigned GetShipID(const noShip* ship) const; + unsigned GetShipID(const noShip& ship) const; /// Gibt ein Schiff anhand der ID zurück bzw. nullptr, wenn keines mit der ID existiert noShip* GetShipByID(unsigned ship_id) const; /// Gibt die Gesamtanzahl von Schiffen zurück unsigned GetNumShips() const { return ships.size(); } /// Gibt liste der Schiffe zurück const std::vector& GetShips() const { return ships; } - /// Gibt eine Liste mit allen Häfen dieses Spieler zurück, die an ein bestimmtes Meer angrenzen - void GetHarborsAtSea(std::vector& harbor_buildings, unsigned short seaId) const; + /// Add all unique harbor buildings at the given sea to the given vector + void AddHarborsAtSea(std::vector& harborBuildings, SeaId seaId) const; /// Gibt die Anzahl der Schiffe, die einen bestimmten Hafen ansteuern, zurück unsigned GetShipsToHarbor(const nobHarborBuilding& hb) const; /// Sucht einen Hafen in der Nähe, wo dieses Schiff seine Waren abladen kann /// gibt true zurück, falls erfolgreich - bool FindHarborForUnloading(noShip* ship, MapPoint start, unsigned* goal_harborId, std::vector* route, + bool FindHarborForUnloading(noShip* ship, MapPoint start, HarborId* goalHarborId, std::vector* route, nobHarborBuilding* exception); /// A ship has discovered new hostile territory --> determines if this is new /// i.e. there is a sufficient distance to older locations diff --git a/libs/s25main/Pathfinding.cpp b/libs/s25main/Pathfinding.cpp index d8a1295a6a..adf55d7ef3 100644 --- a/libs/s25main/Pathfinding.cpp +++ b/libs/s25main/Pathfinding.cpp @@ -50,7 +50,7 @@ RoadPathDirection GameWorld::FindPathForWareOnRoads(const noRoadNode& start, con return RoadPathDirection::None; } -bool GameWorldBase::FindShipPathToHarbor(const MapPoint start, unsigned harborId, unsigned seaId, +bool GameWorldBase::FindShipPathToHarbor(const MapPoint start, HarborId harborId, SeaId seaId, std::vector* route, unsigned* length) { // Find the distance to the furthest harbor from the target harbor and take that as maximum diff --git a/libs/s25main/SerializedGameData.cpp b/libs/s25main/SerializedGameData.cpp index 00847a833c..aab7577029 100644 --- a/libs/s25main/SerializedGameData.cpp +++ b/libs/s25main/SerializedGameData.cpp @@ -105,7 +105,8 @@ /// 9: Drop serialization of node BQ /// 10: troop_limits state introduced to military buildings /// 11: wineaddon added, three new building types and two new goods -/// 12:: leatheraddon added, three new building types and three new goods +/// 12: leatheraddon added, three new building types and three new goods +/// 13: SeaId & HarborId: World::harbor_pos w/o dummy entry at 0 static const unsigned currentGameDataVersion = 12; // clang-format on diff --git a/libs/s25main/ai/AIInterface.cpp b/libs/s25main/ai/AIInterface.cpp index 1f12b4cd2b..8a2a59f042 100644 --- a/libs/s25main/ai/AIInterface.cpp +++ b/libs/s25main/ai/AIInterface.cpp @@ -9,6 +9,7 @@ #include "buildings/nobHarborBuilding.h" #include "buildings/nobMilitary.h" #include "buildings/nobShipYard.h" +#include "helpers/IdRange.h" #include "helpers/containerUtils.h" #include "network/GameMessage_Chat.h" #include "pathfinding/FreePathFinder.h" @@ -49,16 +50,16 @@ bool IsPointOK_RoadPathEvenStep(const GameWorldBase& gwb, const MapPoint pt, con AIInterface::AIInterface(const GameWorldBase& gwb, std::vector& gcs, unsigned char playerID) : gwb(gwb), player_(gwb.GetPlayer(playerID)), gcs(gcs), playerID_(playerID) { - for(unsigned curHarborId = 1; curHarborId <= gwb.GetNumHarborPoints(); curHarborId++) + for(const auto curHarborId : helpers::idRange(gwb.GetNumHarborPoints())) { bool hasOtherHarbor = false; for(const auto dir : helpers::EnumRange{}) { - const unsigned short seaId = gwb.GetSeaId(curHarborId, dir); + const SeaId seaId = gwb.GetSeaId(curHarborId, dir); if(!seaId) continue; - for(unsigned otherHarborId = curHarborId + 1; otherHarborId <= gwb.GetNumHarborPoints(); otherHarborId++) + for(const auto otherHarborId : helpers::idRangeAfter(curHarborId, gwb.GetNumHarborPoints())) { if(gwb.IsHarborAtSea(otherHarborId, seaId)) { @@ -281,11 +282,10 @@ bool AIInterface::isBuildingNearby(BuildingType bldType, const MapPoint pt, unsi bool AIInterface::isHarborPosClose(const MapPoint pt, unsigned maxDistance, bool onlyempty) const { - // skip harbordummy - for(unsigned i = 1; i <= gwb.GetNumHarborPoints(); i++) + for(const auto id : helpers::idRange(gwb.GetNumHarborPoints())) { - const MapPoint harborPoint = gwb.GetHarborPoint(i); - if(gwb.CalcDistance(pt, harborPoint) <= maxDistance && helpers::contains(usableHarbors_, i)) + const MapPoint harborPoint = gwb.GetHarborPoint(id); + if(gwb.CalcDistance(pt, harborPoint) <= maxDistance && helpers::contains(usableHarbors_, id)) { if(!onlyempty || !IsBuildingOnNode(harborPoint, BuildingType::HarborBuilding)) return true; @@ -300,10 +300,10 @@ bool AIInterface::IsExplorationDirectionPossible(const MapPoint pt, const nobHar return IsExplorationDirectionPossible(pt, originHarbor->GetHarborPosID(), direction); } -bool AIInterface::IsExplorationDirectionPossible(const MapPoint pt, unsigned originHarborID, +bool AIInterface::IsExplorationDirectionPossible(const MapPoint pt, HarborId originHarborID, ShipDirection direction) const { - return gwb.GetNextFreeHarborPoint(pt, originHarborID, direction, playerID_) > 0; + return gwb.GetNextFreeHarborPoint(pt, originHarborID, direction, playerID_).isValid(); } bool AIInterface::SetCoinsAllowed(const nobMilitary* building, const bool enabled) diff --git a/libs/s25main/ai/AIInterface.h b/libs/s25main/ai/AIInterface.h index ccdb9b985c..763f4b39f5 100644 --- a/libs/s25main/ai/AIInterface.h +++ b/libs/s25main/ai/AIInterface.h @@ -39,10 +39,10 @@ class AIInterface : public GameCommandFactory unsigned char GetPlayerId() const { return playerID_; } unsigned GetNumPlayers() const { return gwb.GetNumPlayers(); } - const std::vector& getUsableHarbors() const { return usableHarbors_; } + const std::vector& getUsableHarbors() const { return usableHarbors_; } bool IsDefeated() const { return player_.IsDefeated(); } - /// Return the resource buried on a given spot (gold, coal, ironore, granite (sub), fish, nothing) + /// Return the resource buried on a given spot (gold, coal, iron ore, granite (sub), fish, nothing) AISubSurfaceResource GetSubsurfaceResource(MapPoint pt) const; /// Return the resource on top on a given spot (wood, stones, nothing) AISurfaceResource GetSurfaceResource(MapPoint pt) const; @@ -149,14 +149,14 @@ class AIInterface : public GameCommandFactory /// Return the list of ships const std::vector& GetShips() const { return player_.GetShips(); } /// Return the ID of a given ship - unsigned GetShipID(const noShip* ship) const { return player_.GetShipID(ship); } + unsigned GetShipID(const noShip& ship) const { return player_.GetShipID(ship); } /// Test whether there is a possibility to start a expedition in a given direction from a given position, assuming a /// given starting harbor bool IsExplorationDirectionPossible(MapPoint pt, const nobHarborBuilding* originHarbor, ShipDirection direction) const; /// Test whether there is a possibility to start a expedition in a given direction from a given position, assuming a /// given starting harbor - bool IsExplorationDirectionPossible(MapPoint pt, unsigned originHarborID, ShipDirection direction) const; + bool IsExplorationDirectionPossible(MapPoint pt, HarborId originHarborID, ShipDirection direction) const; Nation GetNation() const { return player_.nation; } bool SetCoinsAllowed(const nobMilitary* building, bool enabled); @@ -165,16 +165,16 @@ class AIInterface : public GameCommandFactory bool StartStopExpedition(const nobHarborBuilding* hb, bool start); using GameCommandFactory::StartStopExpedition; - bool FoundColony(const noShip* ship) { return FoundColony(GetShipID(ship)); } + bool FoundColony(const noShip& ship) { return FoundColony(GetShipID(ship)); } using GameCommandFactory::FoundColony; - bool TravelToNextSpot(ShipDirection direction, const noShip* ship) + bool TravelToNextSpot(ShipDirection direction, const noShip& ship) { return TravelToNextSpot(direction, GetShipID(ship)); } using GameCommandFactory::TravelToNextSpot; - bool CancelExpedition(const noShip* ship) { return CancelExpedition(GetShipID(ship)); } + bool CancelExpedition(const noShip& ship) { return CancelExpedition(GetShipID(ship)); } using GameCommandFactory::CancelExpedition; bool SetShipYardMode(const nobShipYard* shipyard, bool buildShips); @@ -212,5 +212,5 @@ class AIInterface : public GameCommandFactory /// ID of AI player const unsigned char playerID_; /// Harbor ids which have at least one other harbor at the same sea - std::vector usableHarbors_; + std::vector usableHarbors_; }; diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index 282efb9a07..72fbee111f 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -17,6 +17,7 @@ #include "buildings/nobHarborBuilding.h" #include "buildings/nobMilitary.h" #include "buildings/nobUsual.h" +#include "helpers/IdRange.h" #include "helpers/MaxEnumValue.h" #include "helpers/containerUtils.h" #include "network/GameMessages.h" @@ -1193,17 +1194,17 @@ void AIPlayerJH::HandleNewColonyFounded(const MapPoint pt) construction->AddConnectFlagJob(gwb.GetSpecObj(gwb.GetNeighbour(pt, Direction::SouthEast))); } -void AIPlayerJH::HandleExpedition(const noShip* ship) +void AIPlayerJH::HandleExpedition(const noShip& ship) { - if(!ship->IsWaitingForExpeditionInstructions()) + if(!ship.IsWaitingForExpeditionInstructions()) return; - if(ship->IsAbleToFoundColony()) + if(ship.IsAbleToFoundColony()) aii.FoundColony(ship); else { for(auto dir : helpers::enumRange(AI::randomEnum())) { - if(aii.IsExplorationDirectionPossible(ship->GetPos(), ship->GetCurrentHarbor(), dir)) + if(aii.IsExplorationDirectionPossible(ship.GetPos(), ship.GetCurrentHarbor(), dir)) { aii.TravelToNextSpot(dir, ship); return; @@ -1232,7 +1233,7 @@ void AIPlayerJH::HandleExpedition(const MapPoint pt) } if(ship) { - HandleExpedition(ship); + HandleExpedition(*ship); } } @@ -1456,19 +1457,19 @@ void AIPlayerJH::CheckExpeditions() for(const nobHarborBuilding* harbor : harbors) { bool isHarborRelevant = HarborPosRelevant(harbor->GetHarborPosID(), true); - if(harbor->IsExpeditionActive() != isHarborRelevant) // harbor is collecting for expedition and shouldnt OR not - // collecting and should -> toggle expedition + if(harbor->IsExpeditionActive() != isHarborRelevant) // harbor is collecting for expedition and shouldn't + // OR not collecting and should -> toggle expedition { aii.StartStopExpedition(harbor->GetPos(), isHarborRelevant); } } - // find lost expedition ships - ai should get a notice and catch them all but just in case some fell through the - // system + // find lost expedition ships + // AI should get a notice and catch them all but just in case some fell through the system const std::vector& ships = aii.GetShips(); - for(const noShip* harbor : ships) + for(const noShip* ship : ships) { - if(harbor->IsWaitingForExpeditionInstructions()) - HandleExpedition(harbor); + if(ship->IsWaitingForExpeditionInstructions()) + HandleExpedition(*ship); } } @@ -1596,12 +1597,11 @@ void AIPlayerJH::TrySeaAttack() return; if(aii.GetHarbors().empty()) return; - std::vector seaidswithattackers; - std::vector attackersatseaid; - std::vector invalidseas; + std::vector seaidswithattackers; + std::vector invalidseas; std::deque potentialTargets; std::deque undefendedTargets; - std::vector searcharoundharborspots; + std::vector searcharoundharborspots; // all seaids with at least 1 ship count available attackers for later checks for(const noShip* ship : aii.GetShips()) { @@ -1613,7 +1613,6 @@ void AIPlayerJH::TrySeaAttack() if(attackercount) // got attackers at this sea id? -> add to valid list { seaidswithattackers.push_back(ship->GetSeaID()); - attackersatseaid.push_back(attackercount); } else // not listed but no attackers? ->invalid { invalidseas.push_back(ship->GetSeaID()); @@ -1628,8 +1627,8 @@ void AIPlayerJH::TrySeaAttack() LOG.write(("attackers at sea ids for player %i, sea id %i, count %i \n",playerId, seaidswithattackers[i], attackersatseaid[i]); }*/ - // first check all harbors there might be some undefended ones - start at 1 to skip the harbor dummy - for(unsigned i = 1; i < gwb.GetNumHarborPoints(); i++) + // first check all harbors there might be some undefended ones + for(const auto i : helpers::idRange(gwb.GetNumHarborPoints())) { const nobHarborBuilding* hb; if((hb = gwb.GetSpecObj(gwb.GetHarborPoint(i)))) @@ -1639,7 +1638,7 @@ void AIPlayerJH::TrySeaAttack() if(aii.IsPlayerAttackable(hb->GetPlayer())) { // attackers for this building? - const std::vector testseaidswithattackers = + const std::vector testseaidswithattackers = gwb.GetFilteredSeaIDsForAttack(gwb.GetHarborPoint(i), seaidswithattackers, playerId); if(!testseaidswithattackers.empty()) // harbor can be attacked? { @@ -1700,7 +1699,7 @@ void AIPlayerJH::TrySeaAttack() && (!milBld->DefendersAvailable())) // undefended headquarter(or unlikely as it is a harbor...) - // priority list! { - const std::vector testseaidswithattackers = + const std::vector testseaidswithattackers = gwb.GetFilteredSeaIDsForAttack(milBld->GetPos(), seaidswithattackers, playerId); if(!testseaidswithattackers.empty()) { @@ -1736,7 +1735,7 @@ void AIPlayerJH::TrySeaAttack() { // TODO: decide if it is worth attacking the target and not just "possible" // test only if we should have attackers from one of our valid sea ids - const std::vector testseaidswithattackers = + const std::vector testseaidswithattackers = gwb.GetFilteredSeaIDsForAttack(ship->GetPos(), seaidswithattackers, playerId); if(!testseaidswithattackers.empty()) // only do the final check if it will probably be a good result { @@ -2224,29 +2223,28 @@ unsigned AIPlayerJH::BQsurroundcheck(const MapPoint pt, unsigned range, bool inc return ((count * 100) / maxvalue); } -bool AIPlayerJH::HarborPosRelevant(unsigned harborid, bool onlyempty) const +bool AIPlayerJH::HarborPosRelevant(HarborId harborId, bool onlyempty) const { - if(harborid < 1 || harborid > gwb.GetNumHarborPoints()) // not a real harbor - shouldnt happen... + if(!harborId || harborId.value() > gwb.GetNumHarborPoints()) // not a real harbor - shouldnt happen... { RTTR_Assert(false); return false; } if(!onlyempty) - return helpers::contains(aii.getUsableHarbors(), harborid); + return helpers::contains(aii.getUsableHarbors(), harborId); for(const auto dir : helpers::EnumRange{}) { - const unsigned short seaId = gwb.GetSeaId(harborid, dir); + const SeaId seaId = gwb.GetSeaId(harborId, dir); if(!seaId) continue; - for(unsigned curHarborId = 1; curHarborId <= gwb.GetNumHarborPoints(); - curHarborId++) // start at 1 harbor dummy yadayada :> + for(const auto curHarborId : helpers::idRange(gwb.GetNumHarborPoints())) { - if(curHarborId != harborid && gwb.IsHarborAtSea(curHarborId, seaId)) + if(curHarborId != harborId && gwb.IsHarborAtSea(HarborId(curHarborId), seaId)) { // check if the spot is actually free for colonization? - if(gwb.IsHarborPointFree(curHarborId, playerId)) + if(gwb.IsHarborPointFree(HarborId(curHarborId), playerId)) return true; } } @@ -2256,7 +2254,7 @@ bool AIPlayerJH::HarborPosRelevant(unsigned harborid, bool onlyempty) const bool AIPlayerJH::NoEnemyHarbor() { - for(unsigned i = 1; i <= gwb.GetNumHarborPoints(); i++) + for(const auto i : helpers::idRange(gwb.GetNumHarborPoints())) { if(aii.IsBuildingOnNode(gwb.GetHarborPoint(i), BuildingType::HarborBuilding) && !aii.IsOwnTerritory(gwb.GetHarborPoint(i))) @@ -2311,13 +2309,13 @@ bool AIPlayerJH::ValidFishInRange(const MapPoint pt) unsigned AIPlayerJH::GetNumAIRelevantSeaIds() const { - std::vector validseaids; - std::list onetimeuseseaids; - for(unsigned i = 1; i <= gwb.GetNumHarborPoints(); i++) + std::vector validseaids; + std::list onetimeuseseaids; + for(const auto i : helpers::idRange(gwb.GetNumHarborPoints())) { for(const auto dir : helpers::EnumRange{}) { - const unsigned short seaId = gwb.GetSeaId(i, dir); + const SeaId seaId = gwb.GetSeaId(i, dir); if(!seaId) continue; // there is a sea id? -> check if it is already a validid or a once found id diff --git a/libs/s25main/ai/aijh/AIPlayerJH.h b/libs/s25main/ai/aijh/AIPlayerJH.h index f30cf0c7a5..0763e9baf7 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.h +++ b/libs/s25main/ai/aijh/AIPlayerJH.h @@ -157,7 +157,7 @@ class AIPlayerJH final : public AIPlayer void HandleBuildingFinished(MapPoint pt, BuildingType bld); void HandleExpedition(MapPoint pt); - void HandleExpedition(const noShip* ship); + void HandleExpedition(const noShip& ship); // Handle chopped tree, test for new space void HandleTreeChopped(MapPoint pt); // Handle new colony @@ -175,8 +175,8 @@ class AIPlayerJH final : public AIPlayer /// sea attack void TrySeaAttack(); /// checks if there is at least 1 sea id connected to the harbor spot with at least 2 harbor spots! when - /// onlyempty=true there has to be at least 1 other free harborid - bool HarborPosRelevant(unsigned harborid, bool onlyempty = false) const; + /// onlyempty=true there has to be at least 1 other free harborId + bool HarborPosRelevant(HarborId harborId, bool onlyempty = false) const; /// Update BQ and farming ground around new building site + road void RecalcGround(MapPoint buildingPos, std::vector& route_road); diff --git a/libs/s25main/buildings/nobHarborBuilding.cpp b/libs/s25main/buildings/nobHarborBuilding.cpp index f6723fc691..6fb9d16c76 100644 --- a/libs/s25main/buildings/nobHarborBuilding.cpp +++ b/libs/s25main/buildings/nobHarborBuilding.cpp @@ -791,7 +791,7 @@ void nobHarborBuilding::ShipLost(noShip* /*ship*/) } /// Gibt die Hafenplatz-ID zurück, auf der der Hafen steht -unsigned nobHarborBuilding::GetHarborPosID() const +HarborId nobHarborBuilding::GetHarborPosID() const { return world->GetHarborPointID(pos); } @@ -850,14 +850,14 @@ std::vector nobHarborBuilding::GetShipConnect if(world->GetGOT(pos) != GO_Type::NobHarborbuilding) return connections; - std::vector harbor_buildings; - for(unsigned short seaId : seaIds) + std::vector harborBuildings; + for(SeaId seaId : seaIds) { - if(seaId != 0) - world->GetPlayer(player).GetHarborsAtSea(harbor_buildings, seaId); + if(seaId) + world->GetPlayer(player).AddHarborsAtSea(harborBuildings, seaId); } - for(auto* harbor_building : harbor_buildings) + for(auto* harbor_building : harborBuildings) { ShipConnection sc; sc.dest = harbor_building; @@ -1181,9 +1181,9 @@ std::vector nobHarborBuilding::GetAttack } return buildings; } -/// Gibt die Angreifergebäude zurück, die dieser Hafen für einen Seeangriff zur Verfügung stellen kann + std::vector -nobHarborBuilding::GetAttackerBuildingsForSeaAttack(const std::vector& defender_harbors) +nobHarborBuilding::GetAttackerBuildingsForSeaAttack(const std::vector& defender_harbors) { std::vector buildings; sortedMilitaryBlds all_buildings = world->LookForMilitaryBuildings(pos, 3); @@ -1204,7 +1204,7 @@ nobHarborBuilding::GetAttackerBuildingsForSeaAttack(const std::vector& // Entfernung zwischen Hafen und möglichen Zielhafenpunkt ausrechnen unsigned min_distance = 0xffffffff; - for(unsigned int defender_harbor : defender_harbors) + for(HarborId defender_harbor : defender_harbors) { min_distance = std::min(min_distance, world->CalcHarborDistance(GetHarborPosID(), defender_harbor)); } @@ -1233,11 +1233,11 @@ nobHarborBuilding::GetAttackerBuildingsForSeaAttack(const std::vector& void nobHarborBuilding::AddSeaAttacker(std::unique_ptr attacker) { unsigned best_distance = 0xffffffff; - unsigned best_harbor_point = 0xffffffff; + HarborId best_harbor_point; RTTR_Assert(attacker->GetAttackedGoal()); - std::vector harbor_points = + std::vector harbor_points = world->GetHarborPointsAroundMilitaryBuilding(attacker->GetAttackedGoal()->GetPos()); - for(unsigned int harbor_point : harbor_points) + for(HarborId harbor_point : harbor_points) { unsigned tmp_distance = world->CalcHarborDistance(this->GetHarborPosID(), harbor_point); if(tmp_distance < best_distance) @@ -1248,7 +1248,7 @@ void nobHarborBuilding::AddSeaAttacker(std::unique_ptr attacker) } // no harbor to target (should not happen) or no target (might happen very very rarely not sure) - if(best_harbor_point == 0xffffffff) + if(!best_harbor_point) { // notify target about noShow, notify home that soldier wont return, add to inventory attacker->SeaAttackFailedBeforeLaunch(); // set state, remove target & home diff --git a/libs/s25main/buildings/nobHarborBuilding.h b/libs/s25main/buildings/nobHarborBuilding.h index 2a2594bcbb..e4d2090f92 100644 --- a/libs/s25main/buildings/nobHarborBuilding.h +++ b/libs/s25main/buildings/nobHarborBuilding.h @@ -6,6 +6,7 @@ #include "helpers/EnumArray.h" #include "nobBaseWarehouse.h" +#include "gameTypes/MapTypes.h" #include "gameData/MilitaryConsts.h" #include @@ -51,7 +52,7 @@ class nobHarborBuilding : public nobBaseWarehouse /// Bestell-Ware-Event const GameEvent* orderware_ev; /// Die Meeres-IDs aller angrenzenden Meere (jeweils für die 6 drumherumliegenden Küstenpunkte) - helpers::EnumArray seaIds; + helpers::EnumArray seaIds; /// Liste von Waren, die weggeschifft werden sollen std::list> wares_for_ships; /// Liste von Menschen, die weggeschifft werden sollen @@ -146,7 +147,7 @@ class nobHarborBuilding : public nobBaseWarehouse void RemoveDependentFigure(noFigure& figure) override; /// Gibt die Hafenplatz-ID zurück, auf der der Hafen steht - unsigned GetHarborPosID() const; + HarborId GetHarborPosID() const; struct ShipConnection { @@ -197,7 +198,7 @@ class nobHarborBuilding : public nobBaseWarehouse /// Gibt die Angreifer zurück, die dieser Hafen für einen Seeangriff zur Verfügung stellen kann /// defender_harbors sind dabei mögliche Zielhäfen - std::vector GetAttackerBuildingsForSeaAttack(const std::vector& defender_harbors); + std::vector GetAttackerBuildingsForSeaAttack(const std::vector& defender_harbors); /// Gibt verfügbare Angreifer zurück std::vector GetAttackerBuildingsForSeaIdAttack(); diff --git a/libs/s25main/gameTypes/HarborPos.h b/libs/s25main/gameTypes/HarborPos.h index 9ac72baa9b..0e147649ea 100644 --- a/libs/s25main/gameTypes/HarborPos.h +++ b/libs/s25main/gameTypes/HarborPos.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,26 +7,27 @@ #include "ShipDirection.h" #include "helpers/EnumArray.h" #include "gameTypes/MapCoordinates.h" +#include "gameTypes/MapTypes.h" #include struct HarborPos { struct Neighbor { - unsigned id; + HarborId id; unsigned distance; - Neighbor(unsigned id, unsigned distance) noexcept : id(id), distance(distance) {} + Neighbor(HarborId id, unsigned distance) noexcept : id(id), distance(distance) {} bool operator<(const Neighbor& two) const { - return (distance < two.distance) || (distance == two.distance && id < two.id); + return (distance < two.distance) || (distance == two.distance && id.value() < two.id.value()); } }; MapPoint pos; /// Seas at the neighbor points in each direction - helpers::EnumArray seaIds = {}; + helpers::EnumArray seaIds = {}; helpers::EnumArray, ShipDirection> neighbors; explicit HarborPos(const MapPoint pt) noexcept : pos(pt) {} diff --git a/libs/s25main/gameTypes/MapNode.cpp b/libs/s25main/gameTypes/MapNode.cpp index 23be67b9a2..2763dd2328 100644 --- a/libs/s25main/gameTypes/MapNode.cpp +++ b/libs/s25main/gameTypes/MapNode.cpp @@ -33,8 +33,8 @@ void MapNode::Serialize(SerializedGameData& sgd, const unsigned numPlayers, cons fow[z].Serialize(sgd); sgd.PushObject(obj); sgd.PushObjectContainer(figures); - sgd.PushUnsignedShort(seaId); - sgd.PushUnsignedInt(harborId); + sgd.PushUnsignedShort(seaId.value()); + sgd.PushUnsignedInt(harborId.value()); } void MapNode::Deserialize(SerializedGameData& sgd, const unsigned numPlayers, const WorldDescription& desc, @@ -72,6 +72,6 @@ void MapNode::Deserialize(SerializedGameData& sgd, const unsigned numPlayers, co fow[z].Deserialize(sgd); obj = sgd.PopObject(); sgd.PopObjectContainer(figures); - seaId = sgd.PopUnsignedShort(); - harborId = sgd.PopUnsignedInt(); + seaId = SeaId(sgd.PopUnsignedShort()); + harborId = HarborId(sgd.PopUnsignedInt()); } diff --git a/libs/s25main/gameTypes/MapNode.h b/libs/s25main/gameTypes/MapNode.h index d533e99498..2c7fbb51a2 100644 --- a/libs/s25main/gameTypes/MapNode.h +++ b/libs/s25main/gameTypes/MapNode.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 @@ -21,21 +21,21 @@ class SerializedGameData; struct TerrainDesc; struct WorldDescription; -/// Eigenschaften von einem Punkt auf der Map +/// Properties of a point/node on a map struct MapNode { /// Roads from this point: E, SE, SW helpers::EnumArray roads; /// Height unsigned char altitude; - /// Schattierung + /// Shading unsigned char shadow; /// Terrain (t1 is the triangle with the edge at the top exactly below this pt, t2 with the edge at the bottom on /// the right lower side of the pt) DescIdx t1, t2; - /// Ressourcen + /// Collectible resources Resource resources; - /// Reservierungen + /// Reserved by worker bool reserved; /// Owner (playerIdx - 1) unsigned char owner; @@ -44,12 +44,12 @@ struct MapNode /// How the players see the point in FoW std::array fow; - /// To which sea this belongs to (0=None) - unsigned short seaId; - /// Hafenpunkt-ID (0 = kein Hafenpunkt) - unsigned harborId; + /// To which sea this belongs to + SeaId seaId; + /// Which harbor is here + HarborId harborId; - /// Objekt, welches sich dort befindet + /// Object built here noBase* obj; /// Figures or fights on this node std::list> figures; diff --git a/libs/s25main/gameTypes/MapTypes.h b/libs/s25main/gameTypes/MapTypes.h index ef13303cc8..b310a68610 100644 --- a/libs/s25main/gameTypes/MapTypes.h +++ b/libs/s25main/gameTypes/MapTypes.h @@ -1,10 +1,11 @@ -// 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 #pragma once #include "FlagType.h" +#include "helpers/StrongId.h" #include /// Visibility of a node @@ -43,3 +44,6 @@ constexpr auto maxEnumValue(PointRoad) { return PointRoad::Boat; } + +using SeaId = helpers::StrongId; +using HarborId = helpers::StrongId; \ No newline at end of file diff --git a/libs/s25main/ingameWindows/iwShip.cpp b/libs/s25main/ingameWindows/iwShip.cpp index 91860ce4e0..f60f25603e 100644 --- a/libs/s25main/ingameWindows/iwShip.cpp +++ b/libs/s25main/ingameWindows/iwShip.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -35,7 +35,7 @@ iwShip::iwShip(GameWorldView& gwv, GameCommandFactory& gcFactory, const noShip* const ship, const DrawPoint& pos) : IngameWindow(CGI_SHIP, pos, Extent(252, 238), _("Ship register"), LOADER.GetImageN("resource", 41)), gwv(gwv), gcFactory(gcFactory), player(ship ? ship->GetPlayerId() : gwv.GetViewer().GetPlayerId()), - ship_id(ship ? gwv.GetWorld().GetPlayer(player).GetShipID(ship) : 0) + ship_id(ship ? gwv.GetWorld().GetPlayer(player).GetShipID(*ship) : 0) { AddImage(0, DrawPoint(126, 101), LOADER.GetImageN("io", 228)); AddImageButton(2, DrawPoint(18, 192), Extent(30, 35), TextureColor::Grey, @@ -112,10 +112,12 @@ void iwShip::DrawContent() GetCtrl(11)->SetVisible(true); for(const auto dir : helpers::EnumRange{}) + { GetCtrl(12 + rttr::enum_cast(dir)) - ->SetVisible(gwv.GetWorld().GetNextFreeHarborPoint(ship->GetPos(), ship->GetCurrentHarbor(), dir, - ship->GetPlayerId()) - > 0); + ->SetVisible(gwv.GetWorld() + .GetNextFreeHarborPoint(ship->GetPos(), ship->GetCurrentHarbor(), dir, ship->GetPlayerId()) + .isValid()); + } } else { // Alle Buttons inklusive Anker in der Mitte ausblenden diff --git a/libs/s25main/nodeObjs/noShip.cpp b/libs/s25main/nodeObjs/noShip.cpp index 5a3fec80c4..3eeec631b0 100644 --- a/libs/s25main/nodeObjs/noShip.cpp +++ b/libs/s25main/nodeObjs/noShip.cpp @@ -47,20 +47,20 @@ constexpr std::array, 2> SHIPS_FLAG_POS }}; noShip::noShip(const MapPoint pos, const unsigned char player) - : noMovable(NodalObjectType::Ship, pos), ownerId_(player), state(State::Idle), seaId_(0), goal_harborId(0), - goal_dir(0), name(RANDOM_ELEMENT(ship_names[world->GetPlayer(player).nation])), curRouteIdx(0), lost(false), - remaining_sea_attackers(0), home_harbor(0), covered_distance(0) + : noMovable(NodalObjectType::Ship, pos), ownerId_(player), state(State::Idle), goal_dir(0), + name(RANDOM_ELEMENT(ship_names[world->GetPlayer(player).nation])), curRouteIdx(0), lost(false), + remaining_sea_attackers(0), covered_distance(0) { // Meer ermitteln, auf dem dieses Schiff fährt for(const auto dir : helpers::EnumRange{}) { - unsigned short seaId = world->GetNeighbourNode(pos, dir).seaId; + SeaId seaId = world->GetNeighbourNode(pos, dir).seaId; if(seaId) this->seaId_ = seaId; } // Auf irgendeinem Meer müssen wir ja sein - RTTR_Assert(seaId_ > 0); + RTTR_Assert(seaId_); } noShip::~noShip() = default; @@ -71,14 +71,14 @@ void noShip::Serialize(SerializedGameData& sgd) const sgd.PushUnsignedChar(ownerId_); sgd.PushEnum(state); - sgd.PushUnsignedShort(seaId_); - sgd.PushUnsignedInt(goal_harborId); + sgd.PushUnsignedShort(seaId_.value()); + sgd.PushUnsignedInt(goalHarbor.value()); sgd.PushUnsignedChar(goal_dir); sgd.PushString(name); sgd.PushUnsignedInt(curRouteIdx); sgd.PushBool(lost); sgd.PushUnsignedInt(remaining_sea_attackers); - sgd.PushUnsignedInt(home_harbor); + sgd.PushUnsignedInt(homeHarbor.value()); sgd.PushUnsignedInt(covered_distance); helpers::pushContainer(sgd, route_); sgd.PushObjectContainer(figures); @@ -87,10 +87,10 @@ void noShip::Serialize(SerializedGameData& sgd) const noShip::noShip(SerializedGameData& sgd, const unsigned obj_id) : noMovable(sgd, obj_id), ownerId_(sgd.PopUnsignedChar()), state(sgd.Pop()), seaId_(sgd.PopUnsignedShort()), - goal_harborId(sgd.PopUnsignedInt()), goal_dir(sgd.PopUnsignedChar()), + goalHarbor(sgd.PopUnsignedInt()), goal_dir(sgd.PopUnsignedChar()), name(sgd.GetGameDataVersion() < 2 ? sgd.PopLongString() : sgd.PopString()), curRouteIdx(sgd.PopUnsignedInt()), route_(sgd.GetGameDataVersion() < 7 ? sgd.PopUnsignedInt() : 0), lost(sgd.PopBool()), - remaining_sea_attackers(sgd.PopUnsignedInt()), home_harbor(sgd.PopUnsignedInt()), + remaining_sea_attackers(sgd.PopUnsignedInt()), homeHarbor(sgd.PopUnsignedInt()), covered_distance(sgd.PopUnsignedInt()) { helpers::popContainer(sgd, route_, sgd.GetGameDataVersion() < 7); @@ -247,7 +247,7 @@ void noShip::HandleEvent(const unsigned id) case State::ExpeditionUnloading: { // Hafen herausfinden - noBase* hb = goal_harborId ? world->GetNO(world->GetHarborPoint(goal_harborId)) : nullptr; + noBase* hb = goalHarbor ? world->GetNO(world->GetHarborPoint(goalHarbor)) : nullptr; if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding) { @@ -270,7 +270,7 @@ void noShip::HandleEvent(const unsigned id) case State::ExplorationexpeditionUnloading: { // Hafen herausfinden - noBase* hb = goal_harborId ? world->GetNO(world->GetHarborPoint(goal_harborId)) : nullptr; + noBase* hb = goalHarbor ? world->GetNO(world->GetHarborPoint(goalHarbor)) : nullptr; unsigned old_visual_range = GetVisualRange(); @@ -301,7 +301,7 @@ void noShip::HandleEvent(const unsigned id) { // Hafen herausfinden RTTR_Assert(state == State::SeaattackUnloading || remaining_sea_attackers == 0); - noBase* hb = goal_harborId ? world->GetNO(world->GetHarborPoint(goal_harborId)) : nullptr; + noBase* hb = goalHarbor ? world->GetNO(world->GetHarborPoint(goalHarbor)) : nullptr; if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding) { static_cast(hb)->ReceiveGoodsFromShip(figures, wares); @@ -424,8 +424,8 @@ void noShip::GoToHarbor(const nobHarborBuilding& hb, const std::vectorGetNode(hb.GetPos()).harborId; - RTTR_Assert(goal_harborId); + goalHarbor = world->GetNode(hb.GetPos()).harborId; + RTTR_Assert(goalHarbor); // Route merken this->route_ = route; @@ -436,19 +436,19 @@ void noShip::GoToHarbor(const nobHarborBuilding& hb, const std::vectorGetCoastalPoint(homeHarborId, seaId_)); - home_harbor = homeHarborId; - goal_harborId = homeHarborId; // This is current goal (commands are relative to current goal) + homeHarbor = homeHarborId; + goalHarbor = homeHarborId; // This is current goal (commands are relative to current goal) } /// Startet eine Erkundungs-Expedition -void noShip::StartExplorationExpedition(unsigned homeHarborId) +void noShip::StartExplorationExpedition(HarborId homeHarborId) { /// Schiff wird "beladen", also kurze Zeit am Hafen stehen, bevor wir bereit sind state = State::ExplorationexpeditionLoading; @@ -456,8 +456,8 @@ void noShip::StartExplorationExpedition(unsigned homeHarborId) covered_distance = 0; RTTR_Assert(homeHarborId); RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_)); - home_harbor = homeHarborId; - goal_harborId = homeHarborId; // This is current goal (commands are relative to current goal) + homeHarbor = homeHarborId; + goalHarbor = homeHarborId; // This is current goal (commands are relative to current goal) // Sichtbarkeiten neu berechnen world->MakeVisibleAroundPoint(pos, GetVisualRange(), ownerId_); } @@ -465,11 +465,9 @@ void noShip::StartExplorationExpedition(unsigned homeHarborId) /// Fährt weiter zu einem Hafen noShip::Result noShip::DriveToHarbour() { - if(!goal_harborId) + if(!goalHarbor) return Result::HarborDoesntExist; - - MapPoint goal(world->GetHarborPoint(goal_harborId)); - RTTR_Assert(goal.isValid()); + const MapPoint goal = world->GetHarborPoint(goalHarbor); // Existiert der Hafen überhaupt noch? if(world->GetGOT(goal) != GO_Type::NobHarborbuilding) @@ -481,7 +479,7 @@ noShip::Result noShip::DriveToHarbour() /// Fährt weiter zu Hafenbauplatz noShip::Result noShip::DriveToHarbourPlace() { - if(goal_harborId == 0) + if(!goalHarbor) return Result::HarborDoesntExist; // Sind wir schon da? @@ -494,7 +492,7 @@ noShip::Result noShip::DriveToHarbourPlace() if(!world->CheckShipRoute(pos, route_, curRouteIdx, &goalRoutePos)) { // Route kann nicht mehr passiert werden --> neue Route suchen - if(!world->FindShipPathToHarbor(pos, goal_harborId, seaId_, &route_, nullptr)) + if(!world->FindShipPathToHarbor(pos, goalHarbor, seaId_, &route_, nullptr)) { // Wieder keine gefunden -> raus return Result::NoRouteFound; @@ -502,14 +500,14 @@ noShip::Result noShip::DriveToHarbourPlace() // Wir fangen bei der neuen Route wieder von vorne an curRouteIdx = 0; - } else if(goalRoutePos != world->GetCoastalPoint(goal_harborId, seaId_)) + } else if(goalRoutePos != world->GetCoastalPoint(goalHarbor, seaId_)) { // Our goal point of the current route has changed // If we are close to it, recalculate the route RTTR_Assert(route_.size() >= curRouteIdx); if(route_.size() - curRouteIdx < 10) { - if(!world->FindShipPathToHarbor(pos, goal_harborId, seaId_, &route_, nullptr)) + if(!world->FindShipPathToHarbor(pos, goalHarbor, seaId_, &route_, nullptr)) // Keiner gefunden -> raus return Result::NoRouteFound; @@ -521,20 +519,20 @@ noShip::Result noShip::DriveToHarbourPlace() return Result::Driving; } -unsigned noShip::GetCurrentHarbor() const +HarborId noShip::GetCurrentHarbor() const { RTTR_Assert(state == State::ExpeditionWaiting); - return goal_harborId; + return goalHarbor; } -unsigned noShip::GetTargetHarbor() const +HarborId noShip::GetTargetHarbor() const { - return goal_harborId; + return goalHarbor; } -unsigned noShip::GetHomeHarbor() const +HarborId noShip::GetHomeHarbor() const { - return home_harbor; + return homeHarbor; } /// Weist das Schiff an, in einer bestimmten Richtung die Expedition fortzusetzen @@ -544,7 +542,7 @@ void noShip::ContinueExpedition(const ShipDirection dir) return; // Nächsten Hafenpunkt in dieser Richtung suchen - unsigned new_goal = world->GetNextFreeHarborPoint(pos, goal_harborId, dir, ownerId_); + HarborId new_goal = world->GetNextFreeHarborPoint(pos, goalHarbor, dir, ownerId_); // Auch ein Ziel gefunden? if(!new_goal) @@ -556,7 +554,7 @@ void noShip::ContinueExpedition(const ShipDirection dir) // Dann fahren wir da mal hin curRouteIdx = 0; - goal_harborId = new_goal; + goalHarbor = new_goal; state = State::ExpeditionDriving; StartDriving(route_[curRouteIdx++]); @@ -575,7 +573,7 @@ void noShip::CancelExpedition() // Zum Heimathafen zurückkehren // Oder sind wir schon dort? - if(goal_harborId == home_harbor) + if(goalHarbor == homeHarbor) { route_.clear(); curRouteIdx = 0; @@ -584,7 +582,7 @@ void noShip::CancelExpedition() } else { state = State::ExpeditionDriving; - goal_harborId = home_harbor; + goalHarbor = homeHarbor; StartDrivingToHarborPlace(); HandleState_ExpeditionDriving(); } @@ -597,7 +595,7 @@ void noShip::FoundColony() return; // Kolonie gründen - if(world->FoundColony(goal_harborId, ownerId_, seaId_)) + if(world->FoundColony(goalHarbor, ownerId_, seaId_)) { // For checks state = State::ExpeditionUnloading; @@ -612,7 +610,7 @@ void noShip::FoundColony() void noShip::HandleState_GoToHarbor() { // Hafen schon zerstört? - if(goal_harborId == 0) + if(!goalHarbor) { StartIdling(); return; @@ -624,8 +622,7 @@ void noShip::HandleState_GoToHarbor() case Result::Driving: return; // Continue case Result::GoalReached: { - MapPoint goal(world->GetHarborPoint(goal_harborId)); - RTTR_Assert(goal.isValid()); + const MapPoint goal = world->GetHarborPoint(goalHarbor); // Go idle here (if harbor does not need it) StartIdling(); // Hafen Bescheid sagen, dass wir da sind (falls er überhaupt noch existiert) @@ -636,7 +633,7 @@ void noShip::HandleState_GoToHarbor() break; case Result::NoRouteFound: { - MapPoint goal(world->GetHarborPoint(goal_harborId)); + MapPoint goal(world->GetHarborPoint(goalHarbor)); RTTR_Assert(goal.isValid()); // Dem Hafen Bescheid sagen world->GetSpecObj(goal)->ShipLost(this); @@ -651,7 +648,7 @@ void noShip::HandleState_ExpeditionDriving() { Result res; // Zum Heimathafen fahren? - if(home_harbor == goal_harborId) + if(homeHarbor == goalHarbor) res = DriveToHarbour(); else res = DriveToHarbourPlace(); @@ -662,7 +659,7 @@ void noShip::HandleState_ExpeditionDriving() case Result::GoalReached: { // Haben wir unsere Expedition beendet? - if(home_harbor == goal_harborId) + if(homeHarbor == goalHarbor) { // Sachen wieder in den Hafen verladen state = State::ExpeditionUnloading; @@ -685,10 +682,10 @@ void noShip::HandleState_ExpeditionDriving() case Result::HarborDoesntExist: // should only happen when an expedition is cancelled and the home harbor no // longer exists { - if(home_harbor != goal_harborId && home_harbor != 0) + if(homeHarbor != goalHarbor && homeHarbor) { // Try to go back - goal_harborId = home_harbor; + goalHarbor = homeHarbor; HandleState_ExpeditionDriving(); } else FindUnloadGoal(State::ExpeditionDriving); // Unload anywhere! @@ -701,7 +698,7 @@ void noShip::HandleState_ExplorationExpeditionDriving() { Result res; // Zum Heimathafen fahren? - if(home_harbor == goal_harborId) + if(homeHarbor == goalHarbor) res = DriveToHarbour(); else res = DriveToHarbourPlace(); @@ -712,7 +709,7 @@ void noShip::HandleState_ExplorationExpeditionDriving() case Result::GoalReached: { // Haben wir unsere Expedition beendet? - if(home_harbor == goal_harborId) + if(homeHarbor == goalHarbor) { // Dann sind wir fertig -> wieder entladen state = State::ExplorationexpeditionUnloading; @@ -730,10 +727,10 @@ void noShip::HandleState_ExplorationExpeditionDriving() break; case Result::NoRouteFound: case Result::HarborDoesntExist: - if(home_harbor != goal_harborId && home_harbor != 0) + if(homeHarbor != goalHarbor && homeHarbor) { // Try to go back - goal_harborId = home_harbor; + goalHarbor = homeHarbor; HandleState_ExplorationExpeditionDriving(); } else FindUnloadGoal(State::ExplorationexpeditionDriving); // Unload anywhere! @@ -794,7 +791,7 @@ void noShip::HandleState_SeaAttackDriving() break; case Result::NoRouteFound: case Result::HarborDoesntExist: - RTTR_Assert(goal_harborId != home_harbor || home_harbor == 0); + RTTR_Assert(goalHarbor != homeHarbor || !homeHarbor); AbortSeaAttack(); break; } @@ -823,9 +820,9 @@ bool noShip::IsAbleToFoundColony() const if(state == State::ExpeditionWaiting) { // We must always have a goal harbor - RTTR_Assert(goal_harborId); + RTTR_Assert(goalHarbor); // Ist der Punkt, an dem wir gerade ankern, noch frei? - if(world->IsHarborPointFree(goal_harborId, ownerId_)) + if(world->IsHarborPointFree(goalHarbor, ownerId_)) return true; } @@ -835,7 +832,7 @@ bool noShip::IsAbleToFoundColony() const /// Gibt zurück, ob das Schiff einen bestimmten Hafen ansteuert bool noShip::IsGoingToHarbor(const nobHarborBuilding& hb) const { - if(goal_harborId != hb.GetHarborPosID()) + if(goalHarbor != hb.GetHarborPosID()) return false; // Explicit switch to check all states switch(state) @@ -865,16 +862,16 @@ bool noShip::IsGoingToHarbor(const nobHarborBuilding& hb) const } /// Belädt das Schiff mit Waren und Figuren, um eine Transportfahrt zu starten -void noShip::PrepareTransport(unsigned homeHarborId, MapPoint goal, std::list> figures, +void noShip::PrepareTransport(HarborId homeHarborId, MapPoint goal, std::list> figures, std::list> wares) { RTTR_Assert(homeHarborId); RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_)); - this->home_harbor = homeHarborId; + this->homeHarbor = homeHarborId; // ID von Zielhafen herausfinden noBase* nb = world->GetNO(goal); RTTR_Assert(nb->GetGOT() == GO_Type::NobHarborbuilding); - this->goal_harborId = static_cast(nb)->GetHarborPosID(); + this->goalHarbor = static_cast(nb)->GetHarborPosID(); this->figures = std::move(figures); this->wares = std::move(wares); @@ -884,14 +881,14 @@ void noShip::PrepareTransport(unsigned homeHarborId, MapPoint goal, std::list> attackers) +void noShip::PrepareSeaAttack(HarborId homeHarborId, MapPoint goal, std::vector> attackers) { // Heimathafen merken RTTR_Assert(homeHarborId); RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_)); - home_harbor = homeHarborId; - goal_harborId = world->GetHarborPointID(goal); - RTTR_Assert(goal_harborId); + homeHarbor = homeHarborId; + goalHarbor = world->GetHarborPointID(goal); + RTTR_Assert(goalHarbor); figures.clear(); for(auto& attacker : attackers) { @@ -916,12 +913,12 @@ void noShip::AbortSeaAttack() RTTR_Assert(state != State::SeaattackWaiting); // figures are not aboard if this fails! RTTR_Assert(remaining_sea_attackers == 0); // Some soldiers are still not aboard - if((state == State::SeaattackLoading || state == State::SeaattackDrivingToDestination) - && goal_harborId != home_harbor && home_harbor != 0) + if((state == State::SeaattackLoading || state == State::SeaattackDrivingToDestination) && goalHarbor != homeHarbor + && homeHarbor) { // We did not start the attack yet and we can (possibly) go back to our home harbor // -> tell the soldiers we go back (like after an attack) - goal_harborId = home_harbor; + goalHarbor = homeHarbor; for(auto& figure : figures) checkedCast(figure.get())->StartReturnViaShip(*this); if(state == State::SeaattackLoading) @@ -958,14 +955,14 @@ void noShip::AbortSeaAttack() /// Fängt an zu einem Hafen zu fahren (berechnet Route usw.) void noShip::StartDrivingToHarborPlace() { - if(!goal_harborId) + if(!goalHarbor) { route_.clear(); curRouteIdx = 0; return; } - MapPoint coastalPos = world->GetCoastalPoint(goal_harborId, seaId_); + MapPoint coastalPos = world->GetCoastalPoint(goalHarbor, seaId_); if(pos == coastalPos) route_.clear(); else @@ -973,13 +970,13 @@ void noShip::StartDrivingToHarborPlace() bool routeFound; // Use upper bound to distance by checking the distance between the harbors if we still have and are at the home // harbor - if(home_harbor && pos == world->GetCoastalPoint(home_harbor, seaId_)) + if(homeHarbor && pos == world->GetCoastalPoint(homeHarbor, seaId_)) { // Use the maximum distance between the harbors plus 6 fields - unsigned maxDistance = world->CalcHarborDistance(home_harbor, goal_harborId) + 6; + unsigned maxDistance = world->CalcHarborDistance(homeHarbor, goalHarbor) + 6; routeFound = world->FindShipPath(pos, coastalPos, maxDistance, &route_, nullptr); } else - routeFound = world->FindShipPathToHarbor(pos, goal_harborId, seaId_, &route_, nullptr); + routeFound = world->FindShipPathToHarbor(pos, goalHarbor, seaId_, &route_, nullptr); if(!routeFound) { // todo @@ -989,9 +986,8 @@ void noShip::StartDrivingToHarborPlace() "pos %u,%u goal " "coastal %u,%u goal-id %i goalpos %u,%u \n") % GetEvMgr().GetCurrentGF() % unsigned(ownerId_) % unsigned(state) % pos.x % pos.y % coastalPos.x - % coastalPos.y % goal_harborId % world->GetHarborPoint(goal_harborId).x - % world->GetHarborPoint(goal_harborId).y; - goal_harborId = 0; + % coastalPos.y % goalHarbor % world->GetHarborPoint(goalHarbor).x % world->GetHarborPoint(goalHarbor).y; + goalHarbor.reset(); return; } } @@ -1013,10 +1009,10 @@ void noShip::FindUnloadGoal(State newState) state = newState; // Das Schiff muss einen Notlandeplatz ansteuern // Neuen Hafen suchen - if(world->GetPlayer(ownerId_).FindHarborForUnloading(this, pos, &goal_harborId, &route_, nullptr)) + if(world->GetPlayer(ownerId_).FindHarborForUnloading(this, pos, &goalHarbor, &route_, nullptr)) { curRouteIdx = 0; - home_harbor = goal_harborId; // To allow unloading here + homeHarbor = goalHarbor; // To allow unloading here if(state == State::ExpeditionDriving) HandleState_ExpeditionDriving(); else if(state == State::ExplorationexpeditionDriving) @@ -1035,7 +1031,8 @@ void noShip::FindUnloadGoal(State newState) { // Ansonsten als verloren markieren, damit uns später Bescheid gesagt wird // wenn es einen neuen Hafen gibt - home_harbor = goal_harborId = 0; + homeHarbor.reset(); + goalHarbor.reset(); lost = true; } } @@ -1043,18 +1040,16 @@ void noShip::FindUnloadGoal(State newState) /// Sagt dem Schiff, das ein bestimmter Hafen zerstört wurde void noShip::HarborDestroyed(nobHarborBuilding* hb) { - const unsigned destroyedHarborId = hb->GetHarborPosID(); + const HarborId destroyedHarborId = hb->GetHarborPosID(); // Almost every case of a destroyed harbor is handled when the ships event fires (the handler detects the destroyed // harbor) So mostly we just reset the corresponding id - if(destroyedHarborId == home_harbor) - home_harbor = 0; + if(destroyedHarborId == homeHarbor) + homeHarbor.reset(); // Ist unser Ziel betroffen? - if(destroyedHarborId != goal_harborId) - { + if(destroyedHarborId != goalHarbor) return; - } State oldState = state; @@ -1063,9 +1058,7 @@ void noShip::HarborDestroyed(nobHarborBuilding* hb) default: // Just reset goal, but not for expeditions if(!IsOnExpedition() && !IsOnExplorationExpedition()) - { - goal_harborId = 0; - } + goalHarbor.reset(); return; // Skip the rest case State::TransportLoading: case State::TransportUnloading: @@ -1095,11 +1088,11 @@ void noShip::HarborDestroyed(nobHarborBuilding* hb) if(oldState == State::TransportLoading) { RTTR_Assert(current_ev); - if(home_harbor) + if(homeHarbor) { // Then save us some time and unload immediately // goal is now the start harbor (if it still exists) - goal_harborId = home_harbor; + goalHarbor = homeHarbor; state = State::TransportUnloading; } else { @@ -1129,8 +1122,8 @@ void noShip::StartIdling() RTTR_Assert(!IsOnExplorationExpedition() || state == State::ExplorationexpeditionUnloading); RTTR_Assert(!IsOnExpedition() || state == State::ExpeditionUnloading); - home_harbor = 0; - goal_harborId = 0; + homeHarbor.reset(); + goalHarbor.reset(); state = State::Idle; } @@ -1150,7 +1143,7 @@ void noShip::SeaAttackerWishesNoReturn() { // Go back home. Note: home_harbor can be 0 if it was destroyed, allow this and let the state handlers // handle that case later - goal_harborId = home_harbor; + goalHarbor = homeHarbor; state = State::SeaattackReturnDriving; StartDrivingToHarborPlace(); HandleState_SeaAttackReturn(); @@ -1180,21 +1173,21 @@ void noShip::ContinueExplorationExpedition() if(covered_distance >= MAX_EXPLORATION_EXPEDITION_DISTANCE) { // Dann steuern wir unseren Heimathafen an! - goal_harborId = home_harbor; + goalHarbor = homeHarbor; } else { // Find the next harbor spot to explore - std::vector hps; - if(goal_harborId) - hps = world->GetUnexploredHarborPoints(goal_harborId, seaId_, GetPlayerId()); + std::vector hps; + if(goalHarbor) + hps = world->GetUnexploredHarborPoints(goalHarbor, seaId_, GetPlayerId()); // No possible spots? -> Go home if(hps.empty()) - goal_harborId = home_harbor; + goalHarbor = homeHarbor; else { // Choose one randomly - goal_harborId = RANDOM_ELEMENT(hps); + goalHarbor = RANDOM_ELEMENT(hps); } } @@ -1213,7 +1206,7 @@ void noShip::NewHarborBuilt(nobHarborBuilding* hb) return; // LOG.write(("lost ship has new goal harbor player %i state %i pos %u,%u \n",player,state,x,y); - home_harbor = goal_harborId = hb->GetHarborPosID(); + homeHarbor = goalHarbor = hb->GetHarborPosID(); lost = false; StartDrivingToHarborPlace(); diff --git a/libs/s25main/nodeObjs/noShip.h b/libs/s25main/nodeObjs/noShip.h index bf154146f3..fd5072dd00 100644 --- a/libs/s25main/nodeObjs/noShip.h +++ b/libs/s25main/nodeObjs/noShip.h @@ -7,6 +7,7 @@ #include "helpers/PtrSpan.h" #include "noMovable.h" #include "gameTypes/MapCoordinates.h" +#include "gameTypes/MapTypes.h" #include "gameTypes/ShipDirection.h" #include #include @@ -52,9 +53,9 @@ class noShip : public noMovable friend constexpr auto maxEnumValue(State) { return State::SeaattackReturnDriving; } /// Das Meer, auf dem dieses Schiff fährt - unsigned short seaId_; + SeaId seaId_; /// Zielpunkt des Schiffes - unsigned goal_harborId; + HarborId goalHarbor; /// Anlegepunkt am Zielhafen, d.h. die Richtung relativ zum Zielpunkt unsigned char goal_dir; /// Namen des Schiffs @@ -72,7 +73,7 @@ class noShip : public noMovable /// Anzahl der Soldaten, die noch kommen müssten unsigned remaining_sea_attackers; /// Heimathafen der Schiffs-Angreifer - unsigned home_harbor; + HarborId homeHarbor; /// Anzahl an Strecke, die das Schiff schon seit Expeditionsstart zurückgelegt hat unsigned covered_distance; @@ -145,7 +146,7 @@ class noShip : public noMovable /// Gibt den Besitzer zurück unsigned char GetPlayerId() const { return ownerId_; } /// Gibt die ID des Meeres zurück, auf dem es sich befindet - unsigned short GetSeaID() const { return seaId_; } + SeaId GetSeaID() const { return seaId_; } /// Gibt den Schiffsnamen zurück const std::string& GetName() const { return name; } /// Hat das Schiff gerade nichts zu tun @@ -182,18 +183,18 @@ class noShip : public noMovable unsigned GetVisualRange() const; /// Return the harbor ID where the ship currently is. Only valid when waiting for expedition instructions - unsigned GetCurrentHarbor() const; + HarborId GetCurrentHarbor() const; /// Return the harbor the ship is currently targeting (0 if ship is idling) - unsigned GetTargetHarbor() const; + HarborId GetTargetHarbor() const; /// Return the source harbor from where the ship left the current mission (0 if ship is idling) - unsigned GetHomeHarbor() const; + HarborId GetHomeHarbor() const; /// Fährt zum Hafen, um dort eine Mission (Expedition) zu erledigen void GoToHarbor(const nobHarborBuilding& hb, const std::vector& route); /// Startet eine Expedition - void StartExpedition(unsigned homeHarborId); + void StartExpedition(HarborId homeHarborId); /// Startet eine Erkundungs-Expedition - void StartExplorationExpedition(unsigned homeHarborId); + void StartExplorationExpedition(HarborId homeHarborId); /// Weist das Schiff an, in einer bestimmten Richtung die Expedition fortzusetzen void ContinueExpedition(ShipDirection dir); /// Weist das Schiff an, eine Expedition abzubrechen (nur wenn es steht) und zum @@ -209,11 +210,11 @@ class noShip : public noMovable bool IsGoingToHarbor(const nobHarborBuilding& hb) const; /// Belädt das Schiff mit Waren und Figuren, um eine Transportfahrt zu starten - void PrepareTransport(unsigned homeHarborId, MapPoint goal, std::list> figures, + void PrepareTransport(HarborId homeHarborId, MapPoint goal, std::list> figures, std::list> wares); /// Belädt das Schiff mit Schiffs-Angreifern - void PrepareSeaAttack(unsigned homeHarborId, MapPoint goal, std::vector> attackers); + void PrepareSeaAttack(HarborId homeHarborId, MapPoint goal, std::vector> attackers); /// Sagt Bescheid, dass ein Schiffsangreifer nicht mehr mit nach Hause fahren will void SeaAttackerWishesNoReturn(); /// Schiffs-Angreifer sind nach dem Angriff wieder zurückgekehrt diff --git a/libs/s25main/world/GameWorld.cpp b/libs/s25main/world/GameWorld.cpp index 1f6b686c0d..ed09640eb4 100644 --- a/libs/s25main/world/GameWorld.cpp +++ b/libs/s25main/world/GameWorld.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2024 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 @@ -16,6 +16,7 @@ #include "figures/nofAttacker.h" #include "figures/nofPassiveSoldier.h" #include "figures/nofScout_Free.h" +#include "helpers/IdRange.h" #include "helpers/containerUtils.h" #include "helpers/mathFuncs.h" #include "helpers/reverse.h" @@ -1375,13 +1376,13 @@ void GameWorld::PlaceAndFixWater() } /// Gründet vom Schiff aus eine neue Kolonie -bool GameWorld::FoundColony(const unsigned harbor_point, const unsigned char player, const unsigned short seaId) +bool GameWorld::FoundColony(const HarborId harbor, const unsigned char player, const SeaId seaId) { // Ist es hier überhaupt noch möglich, eine Kolonie zu gründen? - if(!IsHarborAtSea(harbor_point, seaId) || !IsHarborPointFree(harbor_point, player)) + if(!IsHarborAtSea(harbor, seaId) || !IsHarborPointFree(harbor, player)) return false; - MapPoint pos(GetHarborPoint(harbor_point)); + MapPoint pos(GetHarborPoint(harbor)); DestroyNO(pos, false); // Hafenbaustelle errichten @@ -1412,11 +1413,11 @@ bool GameWorld::IsHarborBuildingSiteFromSea(const noBuildingSite* building_site) return helpers::contains(harbor_building_sites_from_sea, building_site); } -std::vector GameWorld::GetUnexploredHarborPoints(const unsigned hbIdToSkip, const unsigned seaId, +std::vector GameWorld::GetUnexploredHarborPoints(const HarborId hbIdToSkip, const SeaId seaId, unsigned playerId) const { - std::vector hps; - for(unsigned i = 1; i <= GetNumHarborPoints(); ++i) + std::vector hps; + for(const auto i : helpers::idRange(GetNumHarborPoints())) { if(i == hbIdToSkip || !IsHarborAtSea(i, seaId)) continue; diff --git a/libs/s25main/world/GameWorld.h b/libs/s25main/world/GameWorld.h index 3a0747b514..6c811a0505 100644 --- a/libs/s25main/world/GameWorld.h +++ b/libs/s25main/world/GameWorld.h @@ -155,7 +155,7 @@ class GameWorld : public GameWorldBase void PlaceAndFixWater(); /// Gründet vom Schiff aus eine neue Kolonie, gibt true zurück bei Erfolg - bool FoundColony(unsigned harbor_point, unsigned char player, unsigned short seaId); + bool FoundColony(HarborId harbor, unsigned char player, SeaId seaId); /// Registriert eine Baustelle eines Hafens, die vom Schiff aus gesetzt worden ist void AddHarborBuildingSiteFromSea(noBuildingSite* building_site) { @@ -166,7 +166,7 @@ class GameWorld : public GameWorldBase /// Gibt zurück, ob eine bestimmte Baustellen eine Baustelle ist, die vom Schiff aus errichtet wurde bool IsHarborBuildingSiteFromSea(const noBuildingSite* building_site) const; /// Liefert eine Liste der Hafenpunkte, die von einem bestimmten Hafenpunkt erreichbar sind - std::vector GetUnexploredHarborPoints(unsigned hbIdToSkip, unsigned seaId, unsigned playerId) const; + std::vector GetUnexploredHarborPoints(HarborId hbIdToSkip, SeaId seaId, unsigned playerId) const; /// Writeable access to node. Use only for initial map setup! MapNode& GetNodeWriteable(MapPoint pt); diff --git a/libs/s25main/world/GameWorldBase.cpp b/libs/s25main/world/GameWorldBase.cpp index d63e7c50b1..1ecd7a76f9 100644 --- a/libs/s25main/world/GameWorldBase.cpp +++ b/libs/s25main/world/GameWorldBase.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2024 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 @@ -15,6 +15,7 @@ #include "buildings/nobMilitary.h" #include "figures/nofPassiveSoldier.h" #include "helpers/EnumRange.h" +#include "helpers/IdRange.h" #include "helpers/containerUtils.h" #include "lua/LuaInterfaceGame.h" #include "notifications/NodeNote.h" @@ -320,13 +321,12 @@ Visibility GameWorldBase::CalcVisiblityWithAllies(const MapPoint pt, const unsig bool GameWorldBase::IsCoastalPointToSeaWithHarbor(const MapPoint pt) const { - unsigned short sea = GetSeaFromCoastalPoint(pt); + SeaId sea = GetSeaFromCoastalPoint(pt); if(sea) { - const unsigned numHarborPts = GetNumHarborPoints(); - for(unsigned i = 1; i <= numHarborPts; i++) + for(const auto i : helpers::idRange(GetNumHarborPoints())) { - if(IsHarborAtSea(i, sea)) + if(IsHarborAtSea(HarborId(i), sea)) return true; } } @@ -334,14 +334,14 @@ bool GameWorldBase::IsCoastalPointToSeaWithHarbor(const MapPoint pt) const } template -unsigned GameWorldBase::GetHarborInDir(const MapPoint pt, const unsigned origin_harborId, const ShipDirection& dir, +HarborId GameWorldBase::GetHarborInDir(const MapPoint pt, const HarborId originHarborId, const ShipDirection& dir, T_IsHarborOk isHarborOk) const { - RTTR_Assert(origin_harborId); + RTTR_Assert(originHarborId); // Herausfinden, in welcher Richtung sich dieser Punkt vom Ausgangspunkt unterscheidet helpers::OptionalEnum coastal_point_dir; - const MapPoint hbPt = GetHarborPoint(origin_harborId); + const MapPoint hbPt = GetHarborPoint(originHarborId); for(const auto dir : helpers::EnumRange{}) { @@ -354,8 +354,8 @@ unsigned GameWorldBase::GetHarborInDir(const MapPoint pt, const unsigned origin_ RTTR_Assert(coastal_point_dir); - unsigned short seaId = GetSeaId(origin_harborId, *coastal_point_dir); - const std::vector& neighbors = GetHarborNeighbors(origin_harborId, dir); + SeaId seaId = GetSeaId(originHarborId, *coastal_point_dir); + const std::vector& neighbors = GetHarborNeighbors(originHarborId, dir); for(auto neighbor : neighbors) { @@ -364,7 +364,7 @@ unsigned GameWorldBase::GetHarborInDir(const MapPoint pt, const unsigned origin_ } // Nichts gefunden - return 0; + return HarborId::invalidValue(); } /// Functor that returns true, when the owner of a point is set and different than the player @@ -384,7 +384,7 @@ struct IsPointOwnerDifferent }; /// Ist es an dieser Stelle für einen Spieler möglich einen Hafen zu bauen -bool GameWorldBase::IsHarborPointFree(const unsigned harborId, const unsigned char player) const +bool GameWorldBase::IsHarborPointFree(const HarborId harborId, const unsigned char player) const { MapPoint hbPos(GetHarborPoint(harborId)); @@ -401,18 +401,18 @@ bool GameWorldBase::IsHarborPointFree(const unsigned harborId, const unsigned ch } /// Sucht freie Hafenpunkte, also wo noch ein Hafen gebaut werden kann -unsigned GameWorldBase::GetNextFreeHarborPoint(const MapPoint pt, const unsigned origin_harborId, +HarborId GameWorldBase::GetNextFreeHarborPoint(const MapPoint pt, const HarborId originHarborId, const ShipDirection& dir, const unsigned char player) const { - return GetHarborInDir(pt, origin_harborId, dir, + return GetHarborInDir(pt, originHarborId, dir, [this, player](auto harborId) { return this->IsHarborPointFree(harborId, player); }); } /// Bestimmt für einen beliebigen Punkt auf der Karte die Entfernung zum nächsten Hafenpunkt unsigned GameWorldBase::CalcDistanceToNearestHarbor(const MapPoint pos) const { - unsigned min_distance = 0xffffffff; - for(unsigned i = 1; i <= GetNumHarborPoints(); ++i) + unsigned min_distance = std::numeric_limits::max(); + for(const auto i : helpers::idRange(GetNumHarborPoints())) min_distance = std::min(min_distance, this->CalcDistance(pos, GetHarborPoint(i))); return min_distance; @@ -421,7 +421,7 @@ unsigned GameWorldBase::CalcDistanceToNearestHarbor(const MapPoint pos) const /// returns true when a harborpoint is in SEAATTACK_DISTANCE for figures! bool GameWorldBase::IsAHarborInSeaAttackDistance(const MapPoint pos) const { - for(unsigned i = 1; i <= GetNumHarborPoints(); ++i) + for(const auto i : helpers::idRange(GetNumHarborPoints())) { if(CalcDistance(pos, GetHarborPoint(i)) < SEAATTACK_DISTANCE) { @@ -432,16 +432,16 @@ bool GameWorldBase::IsAHarborInSeaAttackDistance(const MapPoint pos) const return false; } -std::vector GameWorldBase::GetUsableTargetHarborsForAttack(const MapPoint targetPt, +std::vector GameWorldBase::GetUsableTargetHarborsForAttack(const MapPoint targetPt, std::vector& use_seas, const unsigned char player_attacker) const { // Walk to the flag of the bld/harbor. Important to check because in some locations where the coast is north of the // harbor this might be blocked const MapPoint flagPt = GetNeighbour(targetPt, Direction::SouthEast); - std::vector harbor_points; + std::vector harbor_points; // Check each possible harbor - for(unsigned curHbId = 1; curHbId <= GetNumHarborPoints(); ++curHbId) + for(const auto curHbId : helpers::idRange(GetNumHarborPoints())) { const MapPoint harborPt = GetHarborPoint(curHbId); @@ -461,7 +461,7 @@ std::vector GameWorldBase::GetUsableTargetHarborsForAttack(const MapPo bool harborinlist = false; for(const auto dir : helpers::enumRange()) { - const unsigned short seaId = GetSeaId(curHbId, dir); + const SeaId seaId = GetSeaId(curHbId, dir); if(!seaId) continue; // checks previously tested sea ids to skip pathfinding @@ -481,7 +481,7 @@ std::vector GameWorldBase::GetUsableTargetHarborsForAttack(const MapPo const MapPoint coastalPt = GetCoastalPoint(curHbId, seaId); if((flagPt == coastalPt) || FindHumanPath(flagPt, coastalPt, SEAATTACK_DISTANCE)) { - use_seas.at(seaId - 1) = true; + use_seas.at(seaId.value() - 1) = true; if(!harborinlist) { harbor_points.push_back(curHbId); @@ -493,16 +493,16 @@ std::vector GameWorldBase::GetUsableTargetHarborsForAttack(const MapPo return harbor_points; } -std::vector GameWorldBase::GetFilteredSeaIDsForAttack(const MapPoint targetPt, - const std::vector& usableSeas, - const unsigned char player_attacker) const +std::vector GameWorldBase::GetFilteredSeaIDsForAttack(const MapPoint targetPt, + const std::vector& usableSeas, + const unsigned char player_attacker) const { // Walk to the flag of the bld/harbor. Important to check because in some locations where the coast is north of the // harbor this might be blocked const MapPoint flagPt = GetNeighbour(targetPt, Direction::SouthEast); - std::vector confirmedSeaIds; + std::vector confirmedSeaIds; // Check each possible harbor - for(unsigned curHbId = 1; curHbId <= GetNumHarborPoints(); ++curHbId) + for(const auto curHbId : helpers::idRange(GetNumHarborPoints())) { const MapPoint harborPt = GetHarborPoint(curHbId); @@ -520,7 +520,7 @@ std::vector GameWorldBase::GetFilteredSeaIDsForAttack(const MapP for(const auto dir : helpers::enumRange()) { - const unsigned short seaId = GetSeaId(curHbId, dir); + const SeaId seaId = GetSeaId(curHbId, dir); if(!seaId) continue; // sea id is not in compare list or already confirmed? -> skip rest @@ -555,12 +555,12 @@ std::vector GameWorldBase::GetFilteredSeaIDsForAttack(const MapP } /// Liefert Hafenpunkte im Umkreis von einem bestimmten Militärgebäude -std::vector GameWorldBase::GetHarborPointsAroundMilitaryBuilding(const MapPoint pt) const +std::vector GameWorldBase::GetHarborPointsAroundMilitaryBuilding(const MapPoint pt) const { - std::vector harbor_points; + std::vector harbor_points; // Nach Hafenpunkten in der Nähe des angegriffenen Gebäudes suchen // Alle unsere Häfen durchgehen - for(unsigned i = 1; i <= GetNumHarborPoints(); ++i) + for(const auto i : helpers::idRange(GetNumHarborPoints())) { const MapPoint harborPt = GetHarborPoint(i); @@ -576,7 +576,7 @@ std::vector GameWorldBase::GetHarborPointsAroundMilitaryBuilding(const /// Gibt Anzahl oder geschätzte Stärke(rang summe + anzahl) der verfügbaren Soldaten die zu einem Schiffsangriff starten /// können von einer bestimmten sea id aus -unsigned GameWorldBase::GetNumSoldiersForSeaAttackAtSea(const unsigned char player_attacker, unsigned short seaid, +unsigned GameWorldBase::GetNumSoldiersForSeaAttackAtSea(const unsigned char player_attacker, SeaId sea, bool returnCount) const { // Liste alle Militärgebäude des Angreifers, die Soldaten liefern @@ -588,7 +588,7 @@ unsigned GameWorldBase::GetNumSoldiersForSeaAttackAtSea(const unsigned char play { // Bestimmen, ob Hafen an einem der Meere liegt, über die sich auch die gegnerischen // Hafenpunkte erreichen lassen - if(!IsHarborAtSea(harbor->GetHarborPosID(), seaid)) + if(!IsHarborAtSea(harbor->GetHarborPosID(), sea)) continue; std::vector tmp = harbor->GetAttackerBuildingsForSeaIdAttack(); @@ -633,7 +633,7 @@ GameWorldBase::GetSoldiersForSeaAttack(const unsigned char player_attacker, cons std::vector use_seas(GetNumSeas()); // Mögliche Hafenpunkte in der Nähe des Gebäudes - std::vector defender_harbors = GetUsableTargetHarborsForAttack(pt, use_seas, player_attacker); + std::vector defender_harbors = GetUsableTargetHarborsForAttack(pt, use_seas, player_attacker); // Liste alle Militärgebäude des Angreifers, die Soldaten liefern std::vector buildings; @@ -647,8 +647,8 @@ GameWorldBase::GetSoldiersForSeaAttack(const unsigned char player_attacker, cons bool is_at_sea = false; for(const auto dir : helpers::EnumRange{}) { - const unsigned short seaId = GetSeaId(harbor->GetHarborPosID(), dir); - if(seaId && use_seas[seaId - 1]) + const SeaId seaId = GetSeaId(harbor->GetHarborPosID(), dir); + if(seaId && use_seas[seaId.value() - 1]) { is_at_sea = true; break; diff --git a/libs/s25main/world/GameWorldBase.h b/libs/s25main/world/GameWorldBase.h index 001bb67d1a..77b7cbdf9c 100644 --- a/libs/s25main/world/GameWorldBase.h +++ b/libs/s25main/world/GameWorldBase.h @@ -116,7 +116,7 @@ class GameWorldBase : public World bool random_route = false, unsigned* length = nullptr, std::vector* route = nullptr) const; /// Find path for ships to a specific harbor and see. Return true on success - bool FindShipPathToHarbor(MapPoint start, unsigned harborId, unsigned seaId, std::vector* route, + bool FindShipPathToHarbor(MapPoint start, HarborId harborId, SeaId seaId, std::vector* route, unsigned* length); /// Find path for ships with a limited distance. Return true on success bool FindShipPath(MapPoint start, MapPoint dest, unsigned maxDistance, std::vector* route, @@ -142,12 +142,12 @@ class GameWorldBase : public World Visibility CalcVisiblityWithAllies(MapPoint pt, unsigned char player) const; /// Ist es an dieser Stelle für einen Spieler möglich einen Hafen zu bauen - bool IsHarborPointFree(unsigned harborId, unsigned char player) const; + bool IsHarborPointFree(HarborId harborId, unsigned char player) const; /// Ermittelt, ob ein Punkt Küstenpunkt ist, d.h. Zugang zu einem schiffbaren Meer, an dem auch mindestens 1 /// Hafenplatz liegt, hat und gibt ggf. die Meeres-ID zurück, ansonsten 0 bool IsCoastalPointToSeaWithHarbor(MapPoint pt) const; /// Sucht freie Hafenpunkte, also wo noch ein Hafen gebaut werden kann - unsigned GetNextFreeHarborPoint(MapPoint pt, unsigned origin_harborId, const ShipDirection& dir, + HarborId GetNextFreeHarborPoint(MapPoint pt, HarborId originHarborId, const ShipDirection& dir, unsigned char player) const; /// Bestimmt für einen beliebigen Punkt auf der Karte die Entfernung zum nächsten Hafenpunkt unsigned CalcDistanceToNearestHarbor(MapPoint pos) const; @@ -195,22 +195,20 @@ class GameWorldBase : public World }; /// Liefert Hafenpunkte im Umkreis von einem bestimmten Milit�rgeb�ude - std::vector GetHarborPointsAroundMilitaryBuilding(MapPoint pt) const; + std::vector GetHarborPointsAroundMilitaryBuilding(MapPoint pt) const; /// Return all harbor Ids that can be used as a landing site for attacking the given point /// Sets all entries in @param use_seas to true, if the given sea can be used for attacking (seaId=1 -> Index 0 as /// seaId=0 is invalid sea) - std::vector GetUsableTargetHarborsForAttack(MapPoint targetPt, std::vector& use_seas, + std::vector GetUsableTargetHarborsForAttack(MapPoint targetPt, std::vector& use_seas, unsigned char player_attacker) const; /// Return all sea Ids from @param usableSeas that can be used for attacking the given point - std::vector GetFilteredSeaIDsForAttack(MapPoint targetPt, - const std::vector& usableSeas, - unsigned char player_attacker) const; + std::vector GetFilteredSeaIDsForAttack(MapPoint targetPt, const std::vector& usableSeas, + unsigned char player_attacker) const; /// Return all soldiers (in no specific order) that can be used to attack the given point via a sea. /// Checks all preconditions for a sea attack (addon, attackable...) std::vector GetSoldiersForSeaAttack(unsigned char player_attacker, MapPoint pt) const; /// Return number or strength (summed ranks) of soldiers that can attack via the given sea - unsigned GetNumSoldiersForSeaAttackAtSea(unsigned char player_attacker, unsigned short seaid, - bool returnCount = true) const; + unsigned GetNumSoldiersForSeaAttackAtSea(unsigned char player_attacker, SeaId sea, bool returnCount = true) const; /// Recalculates the BQ for the given point void RecalcBQ(MapPoint pt); @@ -232,6 +230,6 @@ class GameWorldBase : public World /// Returns the harbor ID of the next matching harbor in the given direction (0 = None) /// T_IsHarborOk must be a predicate taking a harbor Id and returning a bool if the harbor is valid to return template - unsigned GetHarborInDir(MapPoint pt, unsigned origin_harborId, const ShipDirection& dir, + HarborId GetHarborInDir(MapPoint pt, HarborId originHarborId, const ShipDirection& dir, T_IsHarborOk isHarborOk) const; }; diff --git a/libs/s25main/world/MapLoader.cpp b/libs/s25main/world/MapLoader.cpp index b3b60b0dcb..8610f82458 100644 --- a/libs/s25main/world/MapLoader.cpp +++ b/libs/s25main/world/MapLoader.cpp @@ -10,6 +10,7 @@ #include "PointOutput.h" #include "RttrForeachPt.h" #include "factories/BuildingFactory.h" +#include "helpers/IdRange.h" #include "lua/GameDataLoader.h" #include "pathfinding/PathConditionShip.h" #include "random/Random.h" @@ -146,7 +147,7 @@ bool MapLoader::InitNodes(const libsiedler2::ArchivItem_Map& map, Exploration ex world_.harbor_pos.push_back(HarborPos(pt)); // Will be set later - node.harborId = 0; + node.harborId.reset(); node.t1 = getTerrainFromS2(t1 & 0x3F); // Only lower 6 bits node.t2 = getTerrainFromS2(t2 & 0x3F); // Only lower 6 bits @@ -173,7 +174,7 @@ bool MapLoader::InitNodes(const libsiedler2::ArchivItem_Map& map, Exploration ex node.reserved = false; node.owner = 0; std::fill(node.boundary_stones.begin(), node.boundary_stones.end(), 0); - node.seaId = 0; + node.seaId.reset(); Visibility fowVisibility; switch(exploration) @@ -427,42 +428,48 @@ bool MapLoader::InitSeasAndHarbors(World& world, const std::vector& ad RTTR_FOREACH_PT(MapPoint, world.GetSize()) //-V807 { MapNode& node = world.GetNodeInt(pt); - node.seaId = 0u; - node.harborId = 0; + node.seaId.reset(); + node.harborId.reset(); } - /// Weltmeere vermessen + /// Determine all seas world.seas.clear(); + SeaId curSeaId(1); RTTR_FOREACH_PT(MapPoint, world.GetSize()) { - // Noch kein Meer an diesem Punkt Aber trotzdem Teil eines noch nicht vermessenen Meeres? + // Point is not yet assigned a sea but should be if(!world.GetNode(pt).seaId && world.IsSeaPoint(pt)) { - unsigned sea_size = MeasureSea(world, pt, world.seas.size() + 1); - world.seas.push_back(World::Sea(sea_size)); + const auto seaSize = MeasureSea(world, pt, curSeaId); + world.seas.push_back(World::Sea(seaSize)); + curSeaId = curSeaId.next(); } } - /// Die Meere herausfinden, an die die Hafenpunkte grenzen - unsigned curHarborId = 1; - for(auto it = world.harbor_pos.begin() + 1; it != world.harbor_pos.end();) + /// Determine seas adjacent to the harbor places + HarborId curHarborId(1); + for(auto it = world.harbor_pos.begin(); it != world.harbor_pos.end();) { - std::vector hasCoastAtSea(world.seas.size() + 1, false); + helpers::StrongIdVector hasCoastAtSea(world.seas.size(), false); bool foundCoast = false; for(const auto dir : helpers::EnumRange{}) { + SeaId seaId; // Skip point at NW as often there is no path from it if the harbor is north of an island - unsigned short seaId = - (dir == Direction::NorthWest) ? 0 : world.GetSeaFromCoastalPoint(world.GetNeighbour(it->pos, dir)); - // Only 1 coastal point per sea - if(hasCoastAtSea[seaId]) - seaId = 0; - else - hasCoastAtSea[seaId] = true; - + if(dir != Direction::NorthWest) + { + seaId = world.GetSeaFromCoastalPoint(world.GetNeighbour(it->pos, dir)); + if(seaId) + { + foundCoast = true; + // Only 1 coastal point per sea + if(hasCoastAtSea[seaId]) + seaId.reset(); + else + hasCoastAtSea[seaId] = true; + } + } it->seaIds[dir] = seaId; - if(seaId) - foundCoast = true; } if(!foundCoast) { @@ -470,7 +477,8 @@ bool MapLoader::InitSeasAndHarbors(World& world, const std::vector& ad it = world.harbor_pos.erase(it); } else { - world.GetNodeInt(it->pos).harborId = curHarborId++; + world.GetNodeInt(it->pos).harborId = curHarborId; + curHarborId = curHarborId.next(); ++it; } } @@ -479,7 +487,7 @@ bool MapLoader::InitSeasAndHarbors(World& world, const std::vector& ad CalcHarborPosNeighbors(world); // Validate - for(unsigned startHbId = 1; startHbId < world.harbor_pos.size(); ++startHbId) + for(const auto startHbId : helpers::idRange(world.harbor_pos.size())) { const HarborPos& startHbPos = world.harbor_pos[startHbId]; for(const std::vector& neighbors : startHbPos.neighbors) @@ -530,7 +538,7 @@ void MapLoader::CalcHarborPosNeighbors(World& world) // FIFO queue used for a BFS std::queue todo_list; - for(unsigned startHbId = 1; startHbId < world.harbor_pos.size(); ++startHbId) + for(const auto startHbId : helpers::idRange(world.harbor_pos.size())) { RTTR_Assert(todo_list.empty()); @@ -540,17 +548,17 @@ void MapLoader::CalcHarborPosNeighbors(World& world) // 1 - Coast to a harbor std::vector ptToVisitOrHb(ptIsSeaPt); - std::vector hbFound(world.harbor_pos.size(), false); + helpers::StrongIdVector hbFound(world.harbor_pos.size(), false); // For each sea, store the coastal point indices and their harbor - std::vector> coastToHarborPerSea(world.seas.size() + 1); + helpers::StrongIdVector, SeaId> coastToHarborPerSea(world.seas.size()); std::vector ownCoastalPoints; // mark coastal points around harbors - for(unsigned otherHbId = 1; otherHbId < world.harbor_pos.size(); ++otherHbId) + for(const auto otherHbId : helpers::idRange(world.harbor_pos.size())) { for(const auto dir : helpers::EnumRange{}) { - unsigned seaId = world.GetSeaId(otherHbId, dir); + SeaId seaId = world.GetSeaId(otherHbId, dir); // No sea? -> Next if(!seaId) continue; @@ -573,7 +581,7 @@ void MapLoader::CalcHarborPosNeighbors(World& world) for(const MapPoint& ownCoastPt : ownCoastalPoints) { // Special case: Get all harbors that share the coast point with us - unsigned short seaId = world.GetSeaFromCoastalPoint(ownCoastPt); + SeaId seaId = world.GetSeaFromCoastalPoint(ownCoastPt); auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(world.GetIdx(ownCoastPt)); for(auto it = coastToHbs.first; it != coastToHbs.second; ++it) { @@ -605,11 +613,11 @@ void MapLoader::CalcHarborPosNeighbors(World& world) if(ptValue > 0) // found harbor(s) { ShipDirection shipDir = world.GetShipDir(world.harbor_pos[startHbId].pos, curPt); - unsigned seaId = world.GetSeaFromCoastalPoint(curPt); + SeaId seaId = world.GetSeaFromCoastalPoint(curPt); auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(idx); for(auto it = coastToHbs.first; it != coastToHbs.second; ++it) { - unsigned otherHbId = it->second; + const HarborId otherHbId = it->second; if(hbFound[otherHbId]) continue; @@ -623,7 +631,7 @@ void MapLoader::CalcHarborPosNeighbors(World& world) for(const auto hbDir : helpers::EnumRange{}) { if(otherHb.seaIds[hbDir] == seaId && world.GetNeighbour(otherHb.pos, hbDir) != curPt) - otherHb.seaIds[hbDir] = 0; + otherHb.seaIds[hbDir].reset(); } } } @@ -636,7 +644,7 @@ void MapLoader::CalcHarborPosNeighbors(World& world) /// Vermisst ein neues Weltmeer von einem Punkt aus, indem es alle mit diesem Punkt verbundenen /// Wasserpunkte mit der gleichen ID belegt und die Anzahl zur�ckgibt -unsigned MapLoader::MeasureSea(World& world, const MapPoint start, unsigned short seaId) +unsigned MapLoader::MeasureSea(World& world, const MapPoint start, SeaId seaId) { // Breitensuche von diesem Punkt aus durchf�hren std::vector visited(world.GetWidth() * world.GetHeight(), false); diff --git a/libs/s25main/world/MapLoader.h b/libs/s25main/world/MapLoader.h index 43de9133a4..f901675afd 100644 --- a/libs/s25main/world/MapLoader.h +++ b/libs/s25main/world/MapLoader.h @@ -6,6 +6,7 @@ #include "gameTypes/GameSettingTypes.h" #include "gameTypes/MapCoordinates.h" +#include "gameTypes/MapTypes.h" #include "gameData/DescIdx.h" #include #include @@ -34,7 +35,7 @@ class MapLoader /// Vermisst ein neues Weltmeer von einem Punkt aus, indem es alle mit diesem Punkt verbundenen /// Wasserpunkte mit der gleichen seaId belegt und die Anzahl zurückgibt - static unsigned MeasureSea(World& world, MapPoint start, unsigned short seaId); + static unsigned MeasureSea(World& world, MapPoint start, SeaId seaId); static void CalcHarborPosNeighbors(World& world); public: diff --git a/libs/s25main/world/MapSerializer.cpp b/libs/s25main/world/MapSerializer.cpp index e2a289a8d9..39004aba06 100644 --- a/libs/s25main/world/MapSerializer.cpp +++ b/libs/s25main/world/MapSerializer.cpp @@ -48,7 +48,7 @@ void MapSerializer::Serialize(const GameWorldBase& world, SerializedGameData& sg for(const auto& c : curNeighbors) { - sgd.PushUnsignedInt(c.id); + sgd.PushUnsignedInt(c.id.value()); sgd.PushUnsignedInt(c.distance); } } @@ -115,11 +115,6 @@ void MapSerializer::Deserialize(GameWorldBase& world, SerializedGameData& sgd, G for(auto& node : world.nodes) { node.Deserialize(sgd, numPlayers, world.GetDescription(), landscapeTerrains); - if(node.harborId) - { - HarborPos p(curPos); - world.harbor_pos.push_back(p); - } curPos.x++; if(curPos.x >= world.GetWidth()) { @@ -155,12 +150,14 @@ void MapSerializer::Deserialize(GameWorldBase& world, SerializedGameData& sgd, G for(const auto j : helpers::range(numNeighbors)) { RTTR_UNUSED(j); - const auto id = sgd.PopUnsignedInt(); + const auto id = HarborId(sgd.PopUnsignedInt()); const auto distance = sgd.PopUnsignedInt(); neighbor.emplace_back(id, distance); } } } + if(sgd.GetGameDataVersion() < 13 && !world.harbor_pos.empty()) + world.harbor_pos.erase(world.harbor_pos.begin()); sgd.PopObjectContainer(world.harbor_building_sites_from_sea, GO_Type::Buildingsite); diff --git a/libs/s25main/world/World.cpp b/libs/s25main/world/World.cpp index 782fa4c3b1..b9025cac6f 100644 --- a/libs/s25main/world/World.cpp +++ b/libs/s25main/world/World.cpp @@ -36,8 +36,6 @@ void World::Init(const MapExtent& mapSize, DescIdx lt) this->lt = lt; GameObject::ResetCounters(); - // Dummy so that the harbor "0" might be used for ships with no particular destination - harbor_pos.push_back(HarborPos(MapPoint::Invalid())); noNodeObj = std::make_unique(); } @@ -338,25 +336,24 @@ bool World::IsWaterPoint(const MapPoint pt) const return World::IsOfTerrain(pt, [](const auto& desc) { return desc.kind == TerrainKind::Water; }); } -unsigned World::GetSeaSize(const unsigned seaId) const +unsigned World::GetSeaSize(const SeaId seaId) const { - RTTR_Assert(seaId > 0 && seaId <= seas.size()); - return seas[seaId - 1].nodes_count; + RTTR_Assert(seaId && seaId.value() <= seas.size()); + return seas[seaId].nodes_count; } -unsigned short World::GetSeaId(const unsigned harborId, const Direction dir) const +SeaId World::GetSeaId(const HarborId harborId, const Direction dir) const { RTTR_Assert(harborId); return harbor_pos[harborId].seaIds[dir]; } -/// Grenzt der Hafen an ein bestimmtes Meer an? -bool World::IsHarborAtSea(const unsigned harborId, const unsigned short seaId) const +bool World::IsHarborAtSea(const HarborId harborId, const SeaId seaId) const { return GetCoastalPoint(harborId, seaId).isValid(); } -MapPoint World::GetCoastalPoint(const unsigned harborId, const unsigned short seaId) const +MapPoint World::GetCoastalPoint(const HarborId harborId, const SeaId seaId) const { RTTR_Assert(harborId); RTTR_Assert(seaId); @@ -401,28 +398,27 @@ void World::RemoveCatapultStone(CatapultStone* cs) catapult_stones.remove(cs); } -MapPoint World::GetHarborPoint(const unsigned harborId) const +MapPoint World::GetHarborPoint(const HarborId harborId) const { RTTR_Assert(harborId); return harbor_pos[harborId].pos; } -const std::vector& World::GetHarborNeighbors(const unsigned harborId, +const std::vector& World::GetHarborNeighbors(const HarborId harborId, const ShipDirection& dir) const { RTTR_Assert(harborId); return harbor_pos[harborId].neighbors[dir]; } -/// Berechnet die Entfernung zwischen 2 Hafenpunkten -unsigned World::CalcHarborDistance(unsigned habor_id1, unsigned harborId2) const +unsigned World::CalcHarborDistance(HarborId haborId1, HarborId harborId2) const { - if(habor_id1 == harborId2) // special case: distance to self + if(haborId1 == harborId2) // special case: distance to self return 0; for(const auto dir : helpers::EnumRange{}) { - for(const HarborPos::Neighbor& n : harbor_pos[habor_id1].neighbors[dir]) + for(const HarborPos::Neighbor& n : harbor_pos[haborId1].neighbors[dir]) { if(n.id == harborId2) return n.distance; @@ -432,20 +428,20 @@ unsigned World::CalcHarborDistance(unsigned habor_id1, unsigned harborId2) const return 0xffffffff; } -unsigned short World::GetSeaFromCoastalPoint(const MapPoint pt) const +SeaId World::GetSeaFromCoastalPoint(const MapPoint pt) const { // Point itself must not be a sea if(GetNode(pt).seaId) - return 0; + return SeaId::invalidValue(); // Should not be inside water itself if(IsWaterPoint(pt)) - return 0; + return SeaId::invalidValue(); // Surrounding must be valid sea for(const MapPoint nb : GetNeighbours(pt)) { - unsigned short seaId = GetNode(nb).seaId; + SeaId seaId = GetNode(nb).seaId; if(seaId) { // Check size (TODO: Others checks like harbor spots?) @@ -454,7 +450,7 @@ unsigned short World::GetSeaFromCoastalPoint(const MapPoint pt) const } } - return 0; + return SeaId::invalidValue(); } void World::SetRoad(const MapPoint pt, RoadDir roadDir, PointRoad type) diff --git a/libs/s25main/world/World.h b/libs/s25main/world/World.h index 4e12e0adc5..41333546ae 100644 --- a/libs/s25main/world/World.h +++ b/libs/s25main/world/World.h @@ -6,6 +6,7 @@ #include "enum_cast.hpp" #include "helpers/PtrSpan.h" +#include "helpers/StrongIdVector.h" #include "world/MapBase.h" #include "world/MilitarySquares.h" #include "gameTypes/Direction.h" @@ -53,10 +54,10 @@ class World : public MapBase /// Eigenschaften von einem Punkt auf der Map std::vector nodes; - std::vector seas; + helpers::StrongIdVector seas; /// Alle Hafenpositionen - std::vector harbor_pos; + helpers::StrongIdVector harbor_pos; WorldDescription description_; @@ -177,24 +178,24 @@ class World : public MapBase template bool HasTerrain(MapPoint pt, T_Predicate predicate) const; - unsigned GetSeaSize(unsigned seaId) const; + unsigned GetSeaSize(SeaId seaId) const; /// Return the id of the sea at which the coast in the given direction of the harbor lies. 0 = None - unsigned short GetSeaId(unsigned harborId, Direction dir) const; + SeaId GetSeaId(HarborId harborId, Direction dir) const; /// Is the harbor at the given sea - bool IsHarborAtSea(unsigned harborId, unsigned short seaId) const; + bool IsHarborAtSea(HarborId harborId, SeaId seaId) const; /// Return the coast pt for a given harbor (where ships can land) if any - MapPoint GetCoastalPoint(unsigned harborId, unsigned short seaId) const; + MapPoint GetCoastalPoint(HarborId harborId, SeaId seaId) const; /// Return the number of harbor points - unsigned GetNumHarborPoints() const { return harbor_pos.size() - 1; } + unsigned GetNumHarborPoints() const { return harbor_pos.size(); } /// Return the coordinates for a given harbor point - MapPoint GetHarborPoint(unsigned harborId) const; + MapPoint GetHarborPoint(HarborId harborId) const; /// Return the ID of the harbor point on that node or 0 if there is none - unsigned GetHarborPointID(const MapPoint pt) const { return GetNode(pt).harborId; } - const std::vector& GetHarborNeighbors(unsigned harborId, const ShipDirection& dir) const; + HarborId GetHarborPointID(const MapPoint pt) const { return GetNode(pt).harborId; } + const std::vector& GetHarborNeighbors(HarborId harborId, const ShipDirection& dir) const; /// Berechnet die Entfernung zwischen 2 Hafenpunkten - unsigned CalcHarborDistance(unsigned habor_id1, unsigned harborId2) const; + unsigned CalcHarborDistance(HarborId haborId1, HarborId harborId2) const; /// Return the sea id if this is a point at a coast to a sea where ships can go. Else returns 0 - unsigned short GetSeaFromCoastalPoint(MapPoint pt) const; + SeaId GetSeaFromCoastalPoint(MapPoint pt) const; RoadDir toRoadDir(MapPoint& pt, const Direction dir) const { diff --git a/tests/common/commonTestsuite.cpp b/tests/common/commonTestsuite.cpp index 02c577dae6..655325d56f 100644 --- a/tests/common/commonTestsuite.cpp +++ b/tests/common/commonTestsuite.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org) // // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,3 +8,31 @@ #if RTTR_HAS_VLD # include #endif + +#include + +BOOST_AUTO_TEST_SUITE(Helpers) + +BOOST_AUTO_TEST_CASE(CheckStrongId) +{ + using MyId = helpers::StrongId; + MyId id; + BOOST_TEST(!id.isValid()); + BOOST_TEST(!id); + BOOST_TEST(static_cast(id) == 0); + + MyId id2 = MyId(5); + BOOST_TEST(id2.isValid()); + BOOST_TEST(id2); + BOOST_TEST(static_cast(id2) == 5); + BOOST_TEST(id2 != id); + id = id2; + BOOST_TEST(id2 == id); + + BOOST_TEST(id2.next().value() == id2.value() + 1u); + + id.reset(); + BOOST_TEST(!id.isValid()); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/common/testRanges.cpp b/tests/common/testRanges.cpp index 6a0e8fb009..57c6671a38 100644 --- a/tests/common/testRanges.cpp +++ b/tests/common/testRanges.cpp @@ -1,9 +1,11 @@ -// Copyright (C) 2005 - 2024 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 "helpers/EnumRange.h" +#include "helpers/IdRange.h" #include "helpers/Range.h" +#include "helpers/StrongIdVector.h" #include #include @@ -143,3 +145,42 @@ BOOST_AUTO_TEST_CASE(RangeClassIsPythonLike) BOOST_TEST(IntVec(r.begin(), r.end()) == IntVec{}); } } + +BOOST_AUTO_TEST_CASE(IdRangeWorks) +{ + using MyId = helpers::StrongId; + using Vec = std::vector; + + // Range-based-for support + Vec actual; + for(const auto i : helpers::idRange(3)) + actual.push_back(i); + BOOST_TEST_REQUIRE(actual.size() == 3u); + BOOST_TEST(actual[0] == MyId(1)); + BOOST_TEST(actual[1] == MyId(2)); + BOOST_TEST(actual[2] == MyId(3)); + + actual.clear(); + for(const auto i : helpers::idRangeAfter(MyId(2), 4)) + actual.push_back(i); + BOOST_TEST_REQUIRE(actual.size() == 2u); + BOOST_TEST(actual[0] == MyId(3)); + BOOST_TEST(actual[1] == MyId(4)); + + actual.clear(); + for(const auto i : helpers::idRangeAfter(MyId(4), 4)) + actual.push_back(i); // LCOV_EXCL_LINE + BOOST_TEST(actual.empty()); +} + +BOOST_AUTO_TEST_CASE(VectorWithStrongId) +{ + using MyId = helpers::StrongId; + helpers::StrongIdVector vec(3); + int ctr = 0; + for(int& i : vec) + i = ++ctr; + ctr = 0; + for(const auto i : helpers::idRange(vec.size())) + BOOST_TEST(vec[i] == ++ctr); +} diff --git a/tests/legacyFiles/CMakeLists.txt b/tests/legacyFiles/CMakeLists.txt index 64bcb57d43..8cde79ef6e 100644 --- a/tests/legacyFiles/CMakeLists.txt +++ b/tests/legacyFiles/CMakeLists.txt @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later # Library containing old files that are still useful for tests -file(GLOB_RECURSE HEADERS src/*.h src/*.hpp) +file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS src/*.h src/*.hpp) add_library(legacyTestfiles STATIC src/legacy/TerrainData.cpp ${HEADERS}) target_include_directories(legacyTestfiles INTERFACE src) target_link_libraries(legacyTestfiles PUBLIC s25Common gamedata) -enable_warnings(legacyTestfiles) \ No newline at end of file +enable_warnings(legacyTestfiles) diff --git a/tests/s25Main/benchmarks/CMakeLists.txt b/tests/s25Main/benchmarks/CMakeLists.txt index 9906311ead..bf3fbd9813 100644 --- a/tests/s25Main/benchmarks/CMakeLists.txt +++ b/tests/s25Main/benchmarks/CMakeLists.txt @@ -17,7 +17,7 @@ else() FetchContent_MakeAvailable(GoogleBenchmark) endif() -file(GLOB sources *.cpp) +file(GLOB sources CONFIGURE_DEPENDS *.cpp) set(benchmarksCommands "") foreach(src IN LISTS sources) get_filename_component(name ${src} NAME_WE) diff --git a/tests/s25Main/integration/testSeaAttacking.cpp b/tests/s25Main/integration/testSeaAttacking.cpp index 34f8e57650..be3e0f6b44 100644 --- a/tests/s25Main/integration/testSeaAttacking.cpp +++ b/tests/s25Main/integration/testSeaAttacking.cpp @@ -90,12 +90,13 @@ struct SeaAttackFixture : public SeaWorldWithGCExecution<3, 62, 64> BOOST_TEST_REQUIRE(hqPos[1].x < hqPos[2].x); // Harbors must be at same sea - BOOST_TEST_REQUIRE(world.IsHarborAtSea(1, 1)); - BOOST_TEST_REQUIRE(world.IsHarborAtSea(3, 1)); - BOOST_TEST_REQUIRE(world.IsHarborAtSea(6, 1)); - harborPos[0] = world.GetHarborPoint(1); - harborPos[1] = world.GetHarborPoint(3); - harborPos[2] = world.GetHarborPoint(6); + constexpr SeaId seaId(1); + BOOST_TEST_REQUIRE(world.IsHarborAtSea(HarborId(1), seaId)); + BOOST_TEST_REQUIRE(world.IsHarborAtSea(HarborId(3), seaId)); + BOOST_TEST_REQUIRE(world.IsHarborAtSea(HarborId(6), seaId)); + harborPos[0] = world.GetHarborPoint(HarborId(1)); + harborPos[1] = world.GetHarborPoint(HarborId(3)); + harborPos[2] = world.GetHarborPoint(HarborId(6)); // Place harbors (Player 0 has none) for(unsigned i = 1; i < 3; i++) { @@ -105,7 +106,7 @@ struct SeaAttackFixture : public SeaWorldWithGCExecution<3, 62, 64> BuildingFactory::CreateBuilding(world, BuildingType::HarborBuilding, harborPos[i], i, Nation(i)); BOOST_TEST_REQUIRE(hb); BuildRoadForBlds(harborPos[i], hqPos[i]); - MapPoint shipPos = world.GetCoastalPoint(world.GetHarborPointID(harborPos[i]), 1); + MapPoint shipPos = world.GetCoastalPoint(world.GetHarborPointID(harborPos[i]), seaId); shipPos = world.MakeMapPoint(Position(shipPos) + (Position(shipPos) - Position(harborPos[i])) * 8); BOOST_TEST_REQUIRE(shipPos.isValid()); auto& ship = world.AddFigure(shipPos, std::make_unique(shipPos, i)); @@ -114,21 +115,21 @@ struct SeaAttackFixture : public SeaWorldWithGCExecution<3, 62, 64> // Build some military buildings - milBld1NearPos = FindBldPos(world.GetHarborPoint(3) + MapPoint(3, 2), BuildingQuality::House, 1); + milBld1NearPos = FindBldPos(world.GetHarborPoint(HarborId(3)) + MapPoint(3, 2), BuildingQuality::House, 1); BOOST_TEST_REQUIRE(milBld1NearPos.isValid()); BOOST_TEST_REQUIRE(world.GetBQ(milBld1NearPos, 1) >= BuildingQuality::House); milBld1Near = dynamic_cast( BuildingFactory::CreateBuilding(world, BuildingType::Watchtower, milBld1NearPos, 1, Nation::Romans)); BOOST_TEST_REQUIRE(milBld1Near); - milBld1FarPos = FindBldPos(world.GetHarborPoint(4) - MapPoint(1, 4), BuildingQuality::House, 1); + milBld1FarPos = FindBldPos(world.GetHarborPoint(HarborId(4)) - MapPoint(1, 4), BuildingQuality::House, 1); BOOST_TEST_REQUIRE(milBld1FarPos.isValid()); BOOST_TEST_REQUIRE(world.GetBQ(milBld1FarPos, 1) >= BuildingQuality::House); milBld1Far = dynamic_cast( BuildingFactory::CreateBuilding(world, BuildingType::Watchtower, milBld1FarPos, 1, Nation::Romans)); BOOST_TEST_REQUIRE(milBld1Far); - milBld2Pos = FindBldPos(world.GetHarborPoint(6) - MapPoint(2, 2), BuildingQuality::House, 2); + milBld2Pos = FindBldPos(world.GetHarborPoint(HarborId(6)) - MapPoint(2, 2), BuildingQuality::House, 2); BOOST_TEST_REQUIRE(milBld2Pos.isValid()); BOOST_TEST_REQUIRE(world.GetBQ(milBld2Pos, 2) >= BuildingQuality::House); milBld2 = dynamic_cast( @@ -352,8 +353,8 @@ BOOST_FIXTURE_TEST_CASE(HarborsBlock, SeaAttackFixture) BOOST_TEST_REQUIRE(milBld2->GetNumTroops() == 4u); // Test for unowned harbor - const MapPoint harborBot1 = world.GetHarborPoint(7); - const MapPoint harborBot2 = world.GetHarborPoint(8); + const MapPoint harborBot1 = world.GetHarborPoint(HarborId(7)); + const MapPoint harborBot2 = world.GetHarborPoint(HarborId(8)); BOOST_TEST_REQUIRE(world.GetNode(harborBot1).owner == 0u); BOOST_TEST_REQUIRE(world.GetNode(harborBot2).owner == 0u); // Place military bld for player 1, first make some owned land @@ -617,15 +618,15 @@ BOOST_FIXTURE_TEST_CASE(HarborBlocksSpots, SeaAttackFixture) BOOST_TEST_REQUIRE(MapLoader::InitSeasAndHarbors(world)); // Still have our harbor - const unsigned hbId = world.GetHarborPointID(harborPos[1]); + const HarborId hbId = world.GetHarborPointID(harborPos[1]); BOOST_TEST_REQUIRE(hbId); // Should have coast at NE (NW is skipped) - BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::West) == 0u); - BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::NorthWest) == 0u); - BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::NorthEast) == 1u); - BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::East) == 0u); - BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::SouthEast) == 0u); - BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::SouthWest) == 0u); + BOOST_TEST_REQUIRE(!world.GetSeaId(hbId, Direction::West)); + BOOST_TEST_REQUIRE(!world.GetSeaId(hbId, Direction::NorthWest)); + BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::NorthEast) == SeaId(1)); + BOOST_TEST_REQUIRE(!world.GetSeaId(hbId, Direction::East)); + BOOST_TEST_REQUIRE(!world.GetSeaId(hbId, Direction::SouthEast)); + BOOST_TEST_REQUIRE(!world.GetSeaId(hbId, Direction::SouthWest)); // So we should still be able to attack BOOST_TEST_REQUIRE(gwv.GetNumSoldiersForSeaAttack(harborPos[1]) == 5u); diff --git a/tests/s25Main/integration/testSeaWorldCreation.cpp b/tests/s25Main/integration/testSeaWorldCreation.cpp index 1d3954fae8..499a480b10 100644 --- a/tests/s25Main/integration/testSeaWorldCreation.cpp +++ b/tests/s25Main/integration/testSeaWorldCreation.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "RTTR_AssertError.h" +#include "helpers/IdRange.h" #include "worldFixtures/SeaWorldWithGCExecution.h" #include "gameTypes/GameTypesOutput.h" #include "gameTypes/ShipDirection.h" @@ -92,27 +93,25 @@ BOOST_FIXTURE_TEST_CASE(HarborSpotCreation, SeaWorldWithGCExecution<>) BOOST_TEST_REQUIRE(world.GetNumSeas() == 2u); // Harbor ID 0 is means invalid harbor #if RTTR_ENABLE_ASSERTS - RTTR_REQUIRE_ASSERT(world.GetHarborPoint(0)); -#else - BOOST_TEST_REQUIRE(!world.GetHarborPoint(0).isValid()); + RTTR_REQUIRE_ASSERT(world.GetHarborPoint(HarborId::invalidValue())); #endif - // Note: Dummy harbor not counted - for(unsigned curHarborId = 1; curHarborId <= world.GetNumHarborPoints(); curHarborId++) + for(const auto curHarborId : helpers::idRange(world.GetNumHarborPoints())) { const MapPoint curHarborPt = world.GetHarborPoint(curHarborId); BOOST_TEST_REQUIRE(curHarborPt.isValid()); // Reverse mapping must match BOOST_TEST_REQUIRE(world.GetHarborPointID(curHarborPt) == curHarborId); // We must be at one of the 2 seas - unsigned seaId = 2; - if(world.IsHarborAtSea(curHarborId, 1)) - seaId = 1; - else - BOOST_TEST_REQUIRE(world.IsHarborAtSea(curHarborId, 2)); + SeaId seaId(1); + if(!world.IsHarborAtSea(curHarborId, seaId)) + { + seaId = SeaId(2); + BOOST_TEST_REQUIRE(world.IsHarborAtSea(curHarborId, seaId)); + } // We must have a coast point at that sea const MapPoint coastPt = world.GetCoastalPoint(curHarborId, seaId); BOOST_TEST_REQUIRE(coastPt.isValid()); - BOOST_TEST_REQUIRE(world.GetSeaFromCoastalPoint(coastPt) != 0); + BOOST_TEST_REQUIRE(world.GetSeaFromCoastalPoint(coastPt).isValid()); // Sea in the direction of the coast must match bool coastPtFound = false; for(const auto dir : helpers::EnumRange{}) @@ -134,45 +133,47 @@ BOOST_FIXTURE_TEST_CASE(HarborNeighbors, SeaWorldWithGCExecution<>) // Now just test some assumptions: 2 harbor spots per possible HQ. // Square land, 1 HQ on each side, harbors top and bottom or left and right of it // 1) Compare those around each HQ - BOOST_TEST_REQUIRE(world.GetHarborPoint(1).y < world.GetHarborPoint(2).y); //-V807 - BOOST_TEST_REQUIRE(world.GetHarborPoint(7).y < world.GetHarborPoint(8).y); - BOOST_TEST_REQUIRE(world.GetHarborPoint(3).x < world.GetHarborPoint(4).x); - BOOST_TEST_REQUIRE(world.GetHarborPoint(5).x < world.GetHarborPoint(6).x); //-V807 + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(1)).y < world.GetHarborPoint(HarborId(2)).y); //-V807 + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(7)).y < world.GetHarborPoint(HarborId(8)).y); + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(3)).x < world.GetHarborPoint(HarborId(4)).x); + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(5)).x < world.GetHarborPoint(HarborId(6)).x); //-V807 // 2) Compare between them - BOOST_TEST_REQUIRE(world.GetHarborPoint(2).y < world.GetHarborPoint(7).y); - BOOST_TEST_REQUIRE(world.GetHarborPoint(4).x < world.GetHarborPoint(5).x); - BOOST_TEST_REQUIRE(world.GetHarborPoint(3).x < world.GetHarborPoint(1).x); - BOOST_TEST_REQUIRE(world.GetHarborPoint(1).x < world.GetHarborPoint(5).x); + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(2)).y < world.GetHarborPoint(HarborId(7)).y); + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(4)).x < world.GetHarborPoint(HarborId(5)).x); + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(3)).x < world.GetHarborPoint(HarborId(1)).x); + BOOST_TEST_REQUIRE(world.GetHarborPoint(HarborId(1)).x < world.GetHarborPoint(HarborId(5)).x); // Test neighbors std::vector nb; // Hb 1 (outside) - nb = world.GetHarborNeighbors(1, ShipDirection::SouthWest); + HarborId hbId(1); + nb = world.GetHarborNeighbors(hbId, ShipDirection::SouthWest); BOOST_TEST_REQUIRE(nb.size() == 1u); - BOOST_TEST(nb[0].id == 3u); - nb = world.GetHarborNeighbors(1, ShipDirection::SouthEast); + BOOST_TEST(nb[0].id == HarborId(3)); + nb = world.GetHarborNeighbors(hbId, ShipDirection::SouthEast); BOOST_TEST_REQUIRE(nb.size() == 1u); - BOOST_TEST(nb[0].id == 6u); - nb = world.GetHarborNeighbors(1, ShipDirection::North); + BOOST_TEST(nb[0].id == HarborId(6)); + nb = world.GetHarborNeighbors(hbId, ShipDirection::North); BOOST_TEST_REQUIRE(nb.size() == 1u); - BOOST_TEST(nb[0].id == 8u); - BOOST_TEST_REQUIRE(world.GetHarborNeighbors(1, ShipDirection::South).size() == 0u); - BOOST_TEST_REQUIRE(world.GetHarborNeighbors(1, ShipDirection::NorthEast).size() == 0u); - BOOST_TEST_REQUIRE(world.GetHarborNeighbors(1, ShipDirection::NorthWest).size() == 0u); + BOOST_TEST(nb[0].id == HarborId(8)); + BOOST_TEST_REQUIRE(world.GetHarborNeighbors(hbId, ShipDirection::South).size() == 0u); + BOOST_TEST_REQUIRE(world.GetHarborNeighbors(hbId, ShipDirection::NorthEast).size() == 0u); + BOOST_TEST_REQUIRE(world.GetHarborNeighbors(hbId, ShipDirection::NorthWest).size() == 0u); // Hb 7 (inside) - nb = world.GetHarborNeighbors(7, ShipDirection::NorthWest); + hbId = HarborId(7); + nb = world.GetHarborNeighbors(hbId, ShipDirection::NorthWest); BOOST_TEST_REQUIRE(nb.size() == 1u); - BOOST_TEST(nb[0].id == 4u); - nb = world.GetHarborNeighbors(7, ShipDirection::NorthEast); + BOOST_TEST(nb[0].id == HarborId(4)); + nb = world.GetHarborNeighbors(hbId, ShipDirection::NorthEast); BOOST_TEST_REQUIRE(nb.size() == 1u); - BOOST_TEST(nb[0].id == 5u); - nb = world.GetHarborNeighbors(7, ShipDirection::North); + BOOST_TEST(nb[0].id == HarborId(5)); + nb = world.GetHarborNeighbors(hbId, ShipDirection::North); BOOST_TEST_REQUIRE(nb.size() == 1u); - BOOST_TEST(nb[0].id == 2u); - BOOST_TEST_REQUIRE(world.GetHarborNeighbors(7, ShipDirection::South).size() == 0u); - BOOST_TEST_REQUIRE(world.GetHarborNeighbors(7, ShipDirection::SouthEast).size() == 0u); - BOOST_TEST_REQUIRE(world.GetHarborNeighbors(7, ShipDirection::SouthWest).size() == 0u); + BOOST_TEST(nb[0].id == HarborId(2)); + BOOST_TEST_REQUIRE(world.GetHarborNeighbors(hbId, ShipDirection::South).size() == 0u); + BOOST_TEST_REQUIRE(world.GetHarborNeighbors(hbId, ShipDirection::SouthEast).size() == 0u); + BOOST_TEST_REQUIRE(world.GetHarborNeighbors(hbId, ShipDirection::SouthWest).size() == 0u); } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/s25Main/integration/testSeafaring.cpp b/tests/s25Main/integration/testSeafaring.cpp index e2d6e507f3..f1e03592c6 100644 --- a/tests/s25Main/integration/testSeafaring.cpp +++ b/tests/s25Main/integration/testSeafaring.cpp @@ -9,13 +9,16 @@ #include "buildings/nobHarborBuilding.h" #include "buildings/nobShipYard.h" #include "factories/BuildingFactory.h" +#include "helpers/IdRange.h" #include "pathfinding/FindPathForRoad.h" #include "postSystem/PostBox.h" #include "postSystem/ShipPostMsg.h" #include "worldFixtures/SeaWorldWithGCExecution.h" #include "worldFixtures/initGameRNG.hpp" +#include "world/MapLoader.h" #include "nodeObjs/noShip.h" #include "gameTypes/GameTypesOutput.h" +#include "rttr/test/testHelpers.hpp" #include // LCOV_EXCL_START @@ -42,8 +45,8 @@ BOOST_FIXTURE_TEST_CASE(HarborPlacing, SeaWorldWithGCExecution<>) const GamePlayer& player = world.GetPlayer(curPlayer); const BuildingRegister& buildings = player.GetBuildingRegister(); const MapPoint hqPos = player.GetHQPos(); - const unsigned seaId = 1; - const unsigned hbId = 1; + const SeaId seaId(1); + const HarborId hbId(1); const MapPoint hbPos = world.GetHarborPoint(hbId); BOOST_TEST_REQUIRE(world.CalcDistance(hqPos, hbPos) < HQ_RADIUS); @@ -58,7 +61,7 @@ BOOST_FIXTURE_TEST_CASE(HarborPlacing, SeaWorldWithGCExecution<>) std::vector harbors; BOOST_TEST_REQUIRE(world.GetNode(MapPoint(0, 0)).seaId == seaId); BOOST_TEST_REQUIRE(world.GetSeaId(hbId, Direction::NorthEast) == seaId); - player.GetHarborsAtSea(harbors, seaId); + player.AddHarborsAtSea(harbors, seaId); BOOST_TEST_REQUIRE(harbors.size() == 1u); BOOST_TEST_REQUIRE(harbors.front() == harbor); @@ -74,7 +77,7 @@ BOOST_FIXTURE_TEST_CASE(ShipBuilding, SeaWorldWithGCExecution<>) const GamePlayer& player = world.GetPlayer(curPlayer); const MapPoint hqPos = player.GetHQPos(); const MapPoint hqFlagPos = world.GetNeighbour(hqPos, Direction::SouthEast); - const unsigned hbId = 1; + const HarborId hbId(1); const MapPoint hbPos = world.GetHarborPoint(hbId); const MapPoint shipyardPos(hqPos.x + 3, hqPos.y - 5); @@ -122,7 +125,7 @@ BOOST_FIXTURE_TEST_CASE(ShipBuilding, SeaWorldWithGCExecution<>) BOOST_TEST_REQUIRE(player.GetShips().size() == 1u); noShip* ship = player.GetShipByID(0); BOOST_TEST_REQUIRE(ship); - BOOST_TEST_REQUIRE(player.GetShipID(ship) == 0u); + BOOST_TEST_REQUIRE(player.GetShipID(*ship) == 0u); } templateClear(); + initGameRNG(); } }; BOOST_FIXTURE_TEST_CASE(ExplorationExpedition, ShipReadyFixture<>) { - initGameRNG(); - curPlayer = 0; const GamePlayer& player = world.GetPlayer(curPlayer); - const noShip* ship = player.GetShipByID(0); + const noShip& ship = ensureNonNull(player.GetShipByID(0)); const nobHarborBuilding& harbor = *player.GetBuildingRegister().GetHarbors().front(); const MapPoint hbPos = harbor.GetPos(); - const unsigned hbId = world.GetHarborPointID(hbPos); - BOOST_TEST_REQUIRE(ship); - BOOST_TEST_REQUIRE(ship->IsIdling()); + const HarborId hbId = world.GetHarborPointID(hbPos); + BOOST_TEST_REQUIRE(ship.IsIdling()); BOOST_TEST_REQUIRE(!harbor.IsExplorationExpeditionActive()); this->StartStopExplorationExpedition(hbPos, true); BOOST_TEST_REQUIRE(harbor.IsExplorationExpeditionActive()); // Expedition not ready, ship still idling - BOOST_TEST_REQUIRE(ship->IsIdling()); + BOOST_TEST_REQUIRE(ship.IsIdling()); // Wait till scouts arrive - RTTR_EXEC_TILL(380, !ship->IsIdling()); - BOOST_TEST_REQUIRE(ship->IsGoingToHarbor(harbor)); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == hbId); + RTTR_EXEC_TILL(380, !ship.IsIdling()); + BOOST_TEST_REQUIRE(ship.IsGoingToHarbor(harbor)); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == hbId); BOOST_TEST_REQUIRE(player.GetShipsToHarbor(harbor) == 1u); // No available scouts @@ -206,80 +207,79 @@ BOOST_FIXTURE_TEST_CASE(ExplorationExpedition, ShipReadyFixture<>) BOOST_TEST_REQUIRE(harbor.GetNumRealFigures(Job::Scout) == 3u); // Let ship arrive - RTTR_EXEC_TILL(180, ship->IsIdling()); - BOOST_TEST_REQUIRE(!ship->IsGoingToHarbor(harbor)); + RTTR_EXEC_TILL(180, ship.IsIdling()); + BOOST_TEST_REQUIRE(!ship.IsGoingToHarbor(harbor)); BOOST_TEST_REQUIRE(player.GetShipsToHarbor(harbor) == 0u); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == 0u); - BOOST_TEST_REQUIRE(ship->GetHomeHarbor() == 0u); + BOOST_TEST_REQUIRE(!ship.GetTargetHarbor()); + BOOST_TEST_REQUIRE(!ship.GetHomeHarbor()); // We want the ship to only scout unexplored harbors, so set all but one to visible - world.GetNodeWriteable(world.GetHarborPoint(6)).fow[curPlayer].visibility = Visibility::Visible; //-V807 + world.GetNodeWriteable(world.GetHarborPoint(HarborId(6))).fow[curPlayer].visibility = Visibility::Visible; //-V807 // Team visibility, so set one to own team world.GetPlayer(curPlayer).team = Team::Team1; world.GetPlayer(1).team = Team::Team1; world.GetPlayer(curPlayer).MakeStartPacts(); world.GetPlayer(1).MakeStartPacts(); - world.GetNodeWriteable(world.GetHarborPoint(3)).fow[1].visibility = Visibility::Visible; - unsigned targetHbId = 8u; + world.GetNodeWriteable(world.GetHarborPoint(HarborId(3))).fow[1].visibility = Visibility::Visible; + HarborId targetHbId(8); // Start again (everything is here) this->StartStopExplorationExpedition(hbPos, true); // ...so we can start right now - BOOST_TEST_REQUIRE(ship->IsOnExplorationExpedition()); + BOOST_TEST_REQUIRE(ship.IsOnExplorationExpedition()); // Load and start RTTR_SKIP_GFS(202); - BOOST_TEST_REQUIRE(ship->GetHomeHarbor() == hbId); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == targetHbId); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hbId); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == targetHbId); unsigned distance = world.CalcDistance(hbPos, world.GetHarborPoint(targetHbId)); // Let the ship scout the harbor - RTTR_EXEC_TILL(distance * 2 * 20, !ship->IsMoving()); - BOOST_TEST_REQUIRE(ship->IsOnExplorationExpedition()); - BOOST_TEST_REQUIRE(world.CalcDistance(world.GetHarborPoint(targetHbId), ship->GetPos()) <= 2u); + RTTR_EXEC_TILL(distance * 2 * 20, !ship.IsMoving()); + BOOST_TEST_REQUIRE(ship.IsOnExplorationExpedition()); + BOOST_TEST_REQUIRE(world.CalcDistance(world.GetHarborPoint(targetHbId), ship.GetPos()) <= 2u); // Now the ship waits and will select the next harbor. We allow another one: - world.GetNodeWriteable(world.GetHarborPoint(6)).fow[curPlayer].visibility = Visibility::FogOfWar; - targetHbId = 6u; - RTTR_EXEC_TILL(350, ship->IsMoving()); - BOOST_TEST_REQUIRE(ship->GetHomeHarbor() == hbId); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == targetHbId); - distance = world.CalcDistance(ship->GetPos(), world.GetHarborPoint(targetHbId)); + HarborId nextHbId(6); + world.GetNodeWriteable(world.GetHarborPoint(nextHbId)).fow[curPlayer].visibility = Visibility::FogOfWar; + RTTR_EXEC_TILL(350, ship.IsMoving()); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hbId); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == nextHbId); + distance = world.CalcDistance(ship.GetPos(), world.GetHarborPoint(nextHbId)); // Let the ship scout the harbor - RTTR_EXEC_TILL(distance * 2 * 20, !ship->IsMoving()); - BOOST_TEST_REQUIRE(ship->IsOnExplorationExpedition()); - BOOST_TEST_REQUIRE(world.CalcDistance(world.GetHarborPoint(targetHbId), ship->GetPos()) <= 2u); + RTTR_EXEC_TILL(distance * 2 * 20, !ship.IsMoving()); + BOOST_TEST_REQUIRE(ship.IsOnExplorationExpedition()); + BOOST_TEST_REQUIRE(world.CalcDistance(world.GetHarborPoint(nextHbId), ship.GetPos()) <= 2u); // Now disallow the first harbor so ship returns home - world.GetNodeWriteable(world.GetHarborPoint(8)).fow[curPlayer].visibility = Visibility::Visible; + world.GetNodeWriteable(world.GetHarborPoint(targetHbId)).fow[curPlayer].visibility = Visibility::Visible; - RTTR_EXEC_TILL(350, ship->IsMoving()); - BOOST_TEST_REQUIRE(ship->GetHomeHarbor() == hbId); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == hbId); + RTTR_EXEC_TILL(350, ship.IsMoving()); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hbId); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == hbId); - distance = world.CalcDistance(ship->GetPos(), world.GetHarborPoint(hbId)); + distance = world.CalcDistance(ship.GetPos(), world.GetHarborPoint(hbId)); // And at some time it should return home - RTTR_EXEC_TILL(distance * 2 * 20 + 200, ship->IsIdling()); - BOOST_TEST_REQUIRE(ship->GetSeaID() == 1u); - BOOST_TEST_REQUIRE(ship->GetPos() == world.GetCoastalPoint(hbId, 1)); + RTTR_EXEC_TILL(distance * 2 * 20 + 200, ship.IsIdling()); + const SeaId seaId(1); + BOOST_TEST_REQUIRE(ship.GetSeaID() == seaId); + BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(hbId, seaId)); // Now try to start an expedition but all harbors are explored -> Load, Unload, Idle - world.GetNodeWriteable(world.GetHarborPoint(6)).fow[curPlayer].visibility = Visibility::Visible; + world.GetNodeWriteable(world.GetHarborPoint(nextHbId)).fow[curPlayer].visibility = Visibility::Visible; this->StartStopExplorationExpedition(hbPos, true); - BOOST_TEST_REQUIRE(ship->IsOnExplorationExpedition()); - RTTR_EXEC_TILL(2 * 200 + 5, ship->IsIdling()); + BOOST_TEST_REQUIRE(ship.IsOnExplorationExpedition()); + RTTR_EXEC_TILL(2 * 200 + 5, ship.IsIdling()); } BOOST_FIXTURE_TEST_CASE(DestroyHomeOnExplExp, ShipReadyFixture<2>) { - initGameRNG(); - curPlayer = 0; const GamePlayer& player = world.GetPlayer(curPlayer); - const noShip* ship = player.GetShipByID(0); + const noShip& ship = ensureNonNull(player.GetShipByID(0)); const nobHarborBuilding& harbor = *player.GetBuildingRegister().GetHarbors().front(); const MapPoint hbPos = harbor.GetPos(); - const unsigned hbId = world.GetHarborPointID(hbPos); - unsigned numScouts = player.GetInventory().people[Job::Scout]; //-V807 - BOOST_TEST_REQUIRE(ship->IsIdling()); + const HarborId hbId = world.GetHarborPointID(hbPos); + unsigned numScouts = player.GetInventory()[Job::Scout]; //-V807 + BOOST_TEST_REQUIRE(ship.IsIdling()); // We want the ship to only scout unexplored harbors, so set all but one to visible world.GetPlayer(curPlayer).team = Team::Team1; @@ -287,38 +287,38 @@ BOOST_FIXTURE_TEST_CASE(DestroyHomeOnExplExp, ShipReadyFixture<2>) world.GetPlayer(curPlayer).MakeStartPacts(); world.GetPlayer(1).MakeStartPacts(); - world.GetNodeWriteable(world.GetHarborPoint(6)).fow[1].visibility = Visibility::Visible; - world.GetNodeWriteable(world.GetHarborPoint(3)).fow[1].visibility = Visibility::Visible; - unsigned targetHbId = 8u; + world.GetNodeWriteable(world.GetHarborPoint(HarborId(6))).fow[1].visibility = Visibility::Visible; + world.GetNodeWriteable(world.GetHarborPoint(HarborId(3))).fow[1].visibility = Visibility::Visible; + HarborId targetHbId(8); this->StartStopExplorationExpedition(hbPos, true); // Start it - RTTR_EXEC_TILL(600, ship->IsOnExplorationExpedition() && ship->IsMoving()); + RTTR_EXEC_TILL(600, ship.IsOnExplorationExpedition() && ship.IsMoving()); // Incorporate recruitment - BOOST_TEST_REQUIRE(player.GetInventory().people[Job::Scout] >= numScouts); - numScouts = player.GetInventory().people[Job::Scout]; + BOOST_TEST_REQUIRE(player.GetInventory()[Job::Scout] >= numScouts); + numScouts = player.GetInventory()[Job::Scout]; - BOOST_TEST_REQUIRE(ship->GetHomeHarbor() == hbId); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == targetHbId); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hbId); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == targetHbId); // Run till ship is coming back - RTTR_EXEC_TILL(1000, ship->GetTargetHarbor() == hbId); + RTTR_EXEC_TILL(1000, ship.GetTargetHarbor() == hbId); // Avoid that it goes back to that point world.GetNodeWriteable(world.GetHarborPoint(targetHbId)).fow[1].visibility = Visibility::Visible; // Destroy home harbor world.DestroyNO(hbPos); - RTTR_EXEC_TILL(2000, ship->IsLost()); - BOOST_TEST_REQUIRE(!ship->IsMoving()); + RTTR_EXEC_TILL(2000, ship.IsLost()); + BOOST_TEST_REQUIRE(!ship.IsMoving()); - MapPoint newHbPos = world.GetHarborPoint(6); + MapPoint newHbPos = world.GetHarborPoint(HarborId(6)); auto* newHarbor = dynamic_cast( BuildingFactory::CreateBuilding(world, BuildingType::HarborBuilding, newHbPos, curPlayer, Nation::Romans)); - BOOST_TEST_REQUIRE(!ship->IsLost()); - BOOST_TEST_REQUIRE(ship->IsMoving()); - RTTR_EXEC_TILL(1200, ship->IsIdling()); - BOOST_TEST(player.GetInventory().people[Job::Scout] == numScouts); + BOOST_TEST_REQUIRE(!ship.IsLost()); + BOOST_TEST_REQUIRE(ship.IsMoving()); + RTTR_EXEC_TILL(1200, ship.IsIdling()); + BOOST_TEST(player.GetInventory()[Job::Scout] == numScouts); BOOST_TEST(newHarbor->GetNumRealFigures(Job::Scout) == newHarbor->GetNumVisualFigures(Job::Scout)); //-V522 BOOST_TEST(newHarbor->GetNumRealFigures(Job::Scout) + world.GetSpecObj(player.GetHQPos())->GetNumRealFigures(Job::Scout) @@ -327,24 +327,21 @@ BOOST_FIXTURE_TEST_CASE(DestroyHomeOnExplExp, ShipReadyFixture<2>) BOOST_FIXTURE_TEST_CASE(Expedition, ShipReadyFixture<>) { - initGameRNG(); - const GamePlayer& player = world.GetPlayer(curPlayer); - const noShip* ship = player.GetShipByID(0); + const noShip& ship = ensureNonNull(player.GetShipByID(0)); const nobHarborBuilding& harbor = *player.GetBuildingRegister().GetHarbors().front(); const MapPoint hbPos = harbor.GetPos(); - BOOST_TEST_REQUIRE(ship); - BOOST_TEST_REQUIRE(ship->IsIdling()); + BOOST_TEST_REQUIRE(ship.IsIdling()); BOOST_TEST_REQUIRE(!harbor.IsExpeditionActive()); this->StartStopExpedition(hbPos, true); BOOST_TEST_REQUIRE(harbor.IsExpeditionActive()); // Expedition not ready, ship still idling - BOOST_TEST_REQUIRE(ship->IsIdling()); + BOOST_TEST_REQUIRE(ship.IsIdling()); // Wait till wares arrive - RTTR_EXEC_TILL(3400, !ship->IsIdling()); + RTTR_EXEC_TILL(3400, !ship.IsIdling()); // Expedition ready, ship ordered - BOOST_TEST_REQUIRE(ship->IsGoingToHarbor(harbor)); + BOOST_TEST_REQUIRE(ship.IsGoingToHarbor(harbor)); BOOST_TEST_REQUIRE(player.GetShipsToHarbor(harbor) == 1u); // No available boards @@ -360,81 +357,82 @@ BOOST_FIXTURE_TEST_CASE(Expedition, ShipReadyFixture<>) BOOST_TEST_REQUIRE(harbor.GetNumRealWares(GoodType::Boards) > 0u); // Let ship arrive - RTTR_EXEC_TILL(180, ship->IsIdling()); - BOOST_TEST_REQUIRE(!ship->IsGoingToHarbor(harbor)); + RTTR_EXEC_TILL(180, ship.IsIdling()); + BOOST_TEST_REQUIRE(!ship.IsGoingToHarbor(harbor)); BOOST_TEST_REQUIRE(player.GetShipsToHarbor(harbor) == 0u); // Start again (everything is here) this->StartStopExpedition(hbPos, true); // ...so we can start right now - BOOST_TEST_REQUIRE(ship->IsOnExpedition()); + BOOST_TEST_REQUIRE(ship.IsOnExpedition()); // Wait for ship to be "loaded" - RTTR_EXEC_TILL(200, ship->IsWaitingForExpeditionInstructions()); + RTTR_EXEC_TILL(200, ship.IsWaitingForExpeditionInstructions()); // Ship should be waiting for expedition instructions (where to go) and player should have received a message - BOOST_TEST_REQUIRE(ship->GetCurrentHarbor() == harbor.GetHarborPosID()); + BOOST_TEST_REQUIRE(ship.GetCurrentHarbor() == harbor.GetHarborPosID()); BOOST_TEST_REQUIRE(postBox->GetNumMsgs() == 1u); const auto* msg = dynamic_cast(postBox->GetMsg(0)); BOOST_TEST_REQUIRE(msg); - BOOST_TEST_REQUIRE(msg->GetPos() == ship->GetPos()); //-V522 + BOOST_TEST_REQUIRE(msg->GetPos() == ship.GetPos()); //-V522 // Harbor pos taken by other player this->TravelToNextSpot(ShipDirection::SouthEast, player.GetShipID(ship)); - BOOST_TEST_REQUIRE(ship->IsWaitingForExpeditionInstructions()); + BOOST_TEST_REQUIRE(ship.IsWaitingForExpeditionInstructions()); // Last free one (far south -> North is closer) this->TravelToNextSpot(ShipDirection::North, player.GetShipID(ship)); - BOOST_TEST_REQUIRE(!ship->IsWaitingForExpeditionInstructions()); - BOOST_TEST_REQUIRE(ship->IsMoving()); + BOOST_TEST_REQUIRE(!ship.IsWaitingForExpeditionInstructions()); + BOOST_TEST_REQUIRE(ship.IsMoving()); unsigned gfsToDest; - RTTR_EXEC_TILL_CT_GF(1000, ship->IsWaitingForExpeditionInstructions(), gfsToDest); - BOOST_TEST_REQUIRE(ship->GetCurrentHarbor() == 8u); - BOOST_TEST_REQUIRE(world.CalcDistance(ship->GetPos(), world.GetHarborPoint(8)) == 1u); + RTTR_EXEC_TILL_CT_GF(1000, ship.IsWaitingForExpeditionInstructions(), gfsToDest); + HarborId hbId(8); + BOOST_TEST_REQUIRE(ship.GetCurrentHarbor() == hbId); + BOOST_TEST_REQUIRE(world.CalcDistance(ship.GetPos(), world.GetHarborPoint(hbId)) == 1u); // Cancel expedition -> Ship is going back to harbor this->CancelExpedition(player.GetShipID(ship)); - BOOST_TEST_REQUIRE(ship->IsMoving()); + BOOST_TEST_REQUIRE(ship.IsMoving()); // Let ship arrive and unload - RTTR_EXEC_TILL(gfsToDest + 300, ship->IsIdling()); + RTTR_EXEC_TILL(gfsToDest + 300, ship.IsIdling()); // Start again (everything is here) this->StartStopExpedition(hbPos, true); - BOOST_TEST_REQUIRE(ship->IsOnExpedition()); + BOOST_TEST_REQUIRE(ship.IsOnExpedition()); // Wait for ship to be "loaded" - RTTR_EXEC_TILL(200, ship->IsWaitingForExpeditionInstructions()); - BOOST_TEST_REQUIRE(ship->GetCurrentHarbor() == harbor.GetHarborPosID()); + RTTR_EXEC_TILL(200, ship.IsWaitingForExpeditionInstructions()); + BOOST_TEST_REQUIRE(ship.GetCurrentHarbor() == harbor.GetHarborPosID()); // Try to found colony -> Fail this->FoundColony(player.GetShipID(ship)); - BOOST_TEST_REQUIRE(ship->IsWaitingForExpeditionInstructions()); + BOOST_TEST_REQUIRE(ship.IsWaitingForExpeditionInstructions()); // Go back to free spot this->TravelToNextSpot(ShipDirection::North, player.GetShipID(ship)); - BOOST_TEST_REQUIRE(!ship->IsWaitingForExpeditionInstructions()); + BOOST_TEST_REQUIRE(!ship.IsWaitingForExpeditionInstructions()); for(unsigned gf = 0; gf < 2; gf++) { // Send commands that should be ignored as ship is not expecting them this->FoundColony(player.GetShipID(ship)); - BOOST_TEST_REQUIRE(ship->IsMoving()); + BOOST_TEST_REQUIRE(ship.IsMoving()); this->TravelToNextSpot(ShipDirection::South, player.GetShipID(ship)); this->CancelExpedition(player.GetShipID(ship)); - BOOST_TEST_REQUIRE(ship->IsMoving()); + BOOST_TEST_REQUIRE(ship.IsMoving()); this->em.ExecuteNextGF(); } // Go to destination postBox->Clear(); RTTR_SKIP_GFS(gfsToDest); - BOOST_TEST_REQUIRE(ship->IsWaitingForExpeditionInstructions()); + BOOST_TEST_REQUIRE(ship.IsWaitingForExpeditionInstructions()); // This should again trigger a message msg = dynamic_cast(postBox->GetMsg(0)); BOOST_TEST_REQUIRE(msg); - BOOST_TEST_REQUIRE(msg->GetPos() == ship->GetPos()); + BOOST_TEST_REQUIRE(msg->GetPos() == ship.GetPos()); // And now we can found a new colony this->FoundColony(player.GetShipID(ship)); // Ship is free again - BOOST_TEST_REQUIRE(ship->IsIdling()); - const noBuildingSite* newHarbor = world.GetSpecObj(world.GetHarborPoint(8)); + BOOST_TEST_REQUIRE(ship.IsIdling()); + const noBuildingSite* newHarbor = world.GetSpecObj(world.GetHarborPoint(hbId)); BOOST_TEST_REQUIRE(newHarbor); BOOST_TEST_REQUIRE(world.IsHarborBuildingSiteFromSea(newHarbor)); // And it should be completed after some time @@ -446,32 +444,30 @@ using ShipReadyFixtureBig = ShipReadyFixture<1, 2, 64, 800>; BOOST_FIXTURE_TEST_CASE(LongDistanceTravel, ShipReadyFixtureBig) { - initGameRNG(); const GamePlayer& player = world.GetPlayer(curPlayer); - const noShip* ship = player.GetShipByID(0); + const noShip& ship = ensureNonNull(player.GetShipByID(0)); nobHarborBuilding& harbor = *player.GetBuildingRegister().GetHarbors().front(); const MapPoint hbPos = harbor.GetPos(); - BOOST_TEST_REQUIRE(ship); // Go to opposite one - const unsigned targetHbId = 7; + const HarborId targetHbId(7); // Make sure that the other harbor is far away - BOOST_TEST_REQUIRE(world.CalcHarborDistance(2, targetHbId) > 600u); + BOOST_TEST_REQUIRE(world.CalcHarborDistance(HarborId(2), targetHbId) > 600u); // Add some scouts Inventory newScouts; - newScouts.people[Job::Scout] = 20; + newScouts[Job::Scout] = 20; harbor.AddGoods(newScouts, true); // We want the ship to only scout unexplored harbors, so set all but one to visible - for(unsigned i = 1; i <= 8; i++) + for(const auto i : helpers::idRange(world.GetNumHarborPoints())) world.GetNodeWriteable(world.GetHarborPoint(i)).fow[curPlayer].visibility = Visibility::Visible; world.GetNodeWriteable(world.GetHarborPoint(targetHbId)).fow[curPlayer].visibility = Visibility::Invisible; // Start an exploration expedition this->StartStopExplorationExpedition(hbPos, true); BOOST_TEST_REQUIRE(harbor.IsExplorationExpeditionActive()); // Wait till ship has arrived and starts loading - RTTR_EXEC_TILL(100, ship->IsOnExplorationExpedition()); + RTTR_EXEC_TILL(100, ship.IsOnExplorationExpedition()); // Wait till ship has loaded scouts - RTTR_EXEC_TILL(200, ship->IsMoving()); - BOOST_TEST_REQUIRE(ship->GetTargetHarbor() == targetHbId); + RTTR_EXEC_TILL(200, ship.IsMoving()); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == targetHbId); } namespace { @@ -487,7 +483,7 @@ struct ShipAndHarborsReadyFixture : GameWorld& GetWorld() override { return world; } - nobHarborBuilding& createHarbor(unsigned hbPosId) + nobHarborBuilding& createHarbor(HarborId hbPosId) { MapPoint hbPos = world.GetHarborPoint(hbPosId); auto* harbor = static_cast( @@ -503,10 +499,10 @@ struct ShipAndHarborsReadyFixture : ShipAndHarborsReadyFixture() { GamePlayer& player = world.GetPlayer(curPlayer); - createHarbor(1); - createHarbor(2); + createHarbor(HarborId(1)); + createHarbor(HarborId(2)); - MapPoint hbPos = world.GetHarborPoint(1); + MapPoint hbPos = world.GetHarborPoint(HarborId(1)); MapPoint shipPos = world.MakeMapPoint(hbPos - Position(2, 0)); BOOST_TEST_REQUIRE(world.IsSeaPoint(shipPos)); auto& ship = world.AddFigure(shipPos, std::make_unique(shipPos, curPlayer)); @@ -540,9 +536,12 @@ BOOST_FIXTURE_TEST_CASE(HarborDestroyed, ShipAndHarborsReadyFixture<1>) { const GamePlayer& player = world.GetPlayer(curPlayer); const noShip& ship = *player.GetShipByID(0); - const MapPoint hb1Pos = world.GetHarborPoint(1); - const MapPoint hb2Pos = world.GetHarborPoint(2); - const MapPoint hb3Pos = world.GetHarborPoint(3); + constexpr HarborId hb1Id(1); + constexpr HarborId hb2Id(2); + constexpr HarborId hb3Id(3); + const MapPoint hb1Pos = world.GetHarborPoint(hb1Id); + const MapPoint hb2Pos = world.GetHarborPoint(hb2Id); + const MapPoint hb3Pos = world.GetHarborPoint(hb3Id); // Order goods SetInventorySetting(hb2Pos, GoodType::Wood, EInventorySetting::Collect); @@ -555,25 +554,26 @@ BOOST_FIXTURE_TEST_CASE(HarborDestroyed, ShipAndHarborsReadyFixture<1>) BOOST_TEST_REQUIRE(ship.IsIdling()); // Destroy home during load -> Continue - createHarbor(1); + createHarbor(hb1Id); // Just wait for re-order event and instantly go to loading as ship does not need to move RTTR_EXEC_TILL(90, ship.IsLoading()); destroyBldAndFire(world, hb1Pos); BOOST_TEST_REQUIRE(ship.IsLoading()); - BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == 0u); + BOOST_TEST_REQUIRE(!ship.GetHomeHarbor()); RTTR_EXEC_TILL(300, ship.IsUnloading()); - BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(2, 1)); + constexpr SeaId seaId(1); + BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(hb2Id, seaId)); RTTR_EXEC_TILL(200, ship.IsIdling()); // Destroy destination before load -> Abort after ship reaches harbor - createHarbor(1); + createHarbor(hb1Id); RTTR_EXEC_TILL(90, ship.IsMoving()); destroyBldAndFire(world, hb2Pos); RTTR_EXEC_TILL(200, !ship.IsMoving()); BOOST_TEST_REQUIRE(ship.IsIdling()); // Destroy destination during load -> Unload again - createHarbor(2); + createHarbor(hb2Id); // Order goods SetInventorySetting(hb2Pos, GoodType::Wood, EInventorySetting::Collect); SetInventorySetting(hb2Pos, Job::Woodcutter, EInventorySetting::Collect); @@ -583,55 +583,55 @@ BOOST_FIXTURE_TEST_CASE(HarborDestroyed, ShipAndHarborsReadyFixture<1>) RTTR_EXEC_TILL(200, ship.IsIdling()); // Destroy destination during driving -> Go back and unload - createHarbor(2); + createHarbor(hb2Id); // Order goods SetInventorySetting(hb2Pos, GoodType::Wood, EInventorySetting::Collect); SetInventorySetting(hb2Pos, Job::Woodcutter, EInventorySetting::Collect); RTTR_EXEC_TILL(300, ship.IsLoading()); RTTR_EXEC_TILL(200, ship.IsMoving()); - BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == 1u); - BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == 2u); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hb1Id); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == hb2Id); destroyBldAndFire(world, hb2Pos); - RTTR_EXEC_TILL(10, ship.GetTargetHarbor() == 1u); + RTTR_EXEC_TILL(10, ship.GetTargetHarbor() == hb1Id); RTTR_EXEC_TILL(20, ship.IsUnloading()); - BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(1, 1)); + BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(hb1Id, seaId)); RTTR_EXEC_TILL(200, ship.IsIdling()); // Destroy destination during unloading -> Go back and unload - createHarbor(2); + createHarbor(hb2Id); // Order goods SetInventorySetting(hb2Pos, GoodType::Wood, EInventorySetting::Collect); SetInventorySetting(hb2Pos, Job::Woodcutter, EInventorySetting::Collect); RTTR_EXEC_TILL(700, ship.IsUnloading()); - BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == 1u); - BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == 2u); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hb1Id); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == hb2Id); destroyBldAndFire(world, hb2Pos); - RTTR_EXEC_TILL(10, ship.GetTargetHarbor() == 1u); + RTTR_EXEC_TILL(10, ship.GetTargetHarbor() == hb1Id); RTTR_EXEC_TILL(200, ship.IsUnloading()); - BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(1, 1)); + BOOST_TEST_REQUIRE(ship.GetPos() == world.GetCoastalPoint(hb1Id, seaId)); RTTR_EXEC_TILL(200, ship.IsIdling()); // Destroy both during unloading -> Go to 3rd and unload - createHarbor(2); - createHarbor(3); + createHarbor(hb2Id); + createHarbor(hb3Id); // Order goods SetInventorySetting(hb2Pos, GoodType::Wood, EInventorySetting::Collect); SetInventorySetting(hb2Pos, Job::Woodcutter, EInventorySetting::Collect); RTTR_EXEC_TILL(700, ship.IsUnloading()); - BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == 1u); - BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == 2u); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hb1Id); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == hb2Id); destroyBldAndFire(world, hb1Pos); destroyBldAndFire(world, hb2Pos); - RTTR_EXEC_TILL(10, ship.GetTargetHarbor() == 3u); + RTTR_EXEC_TILL(10, ship.GetTargetHarbor() == HarborId(3)); // Destroy last -> destroyBldAndFire(world, hb3Pos); RTTR_EXEC_TILL(10, ship.IsLost()); - BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == 0u); - BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == 0u); - createHarbor(1); + BOOST_TEST_REQUIRE(!ship.GetHomeHarbor()); + BOOST_TEST_REQUIRE(!ship.GetTargetHarbor()); + createHarbor(hb1Id); BOOST_TEST_REQUIRE(ship.IsMoving()); - BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == 1u); - BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == 1u); + BOOST_TEST_REQUIRE(ship.GetHomeHarbor() == hb1Id); + BOOST_TEST_REQUIRE(ship.GetTargetHarbor() == hb1Id); } struct MockWare : Ware diff --git a/tests/s25Main/integration/testSerialization.cpp b/tests/s25Main/integration/testSerialization.cpp index 46323d8cb4..8830b019d1 100644 --- a/tests/s25Main/integration/testSerialization.cpp +++ b/tests/s25Main/integration/testSerialization.cpp @@ -55,8 +55,8 @@ struct RandWorldFixture : public WorldFixture worldNode.shadow = rttr::test::randomValue(0, 20); worldNode.resources = Resource(rttr::test::randomValue()); worldNode.reserved = rttr::test::randomValue(0, 1) == 0; - worldNode.seaId = rttr::test::randomValue(0, 20); - worldNode.harborId = rttr::test::randomValue(0, 20); + worldNode.seaId = SeaId(rttr::test::randomValue(0, 20)); + worldNode.harborId = HarborId(rttr::test::randomValue(0, 20)); } world.InitAfterLoad(); world.GetPlayer(0).name = "Human"; diff --git a/tests/s25Main/integration/testWorld.cpp b/tests/s25Main/integration/testWorld.cpp index ddd86903fb..dfd4f1f0a2 100644 --- a/tests/s25Main/integration/testWorld.cpp +++ b/tests/s25Main/integration/testWorld.cpp @@ -8,6 +8,7 @@ #include "RttrConfig.h" #include "RttrForeachPt.h" #include "files.h" +#include "helpers/IdRange.h" #include "lua/GameDataLoader.h" #include "worldFixtures/CreateEmptyWorld.h" #include "worldFixtures/MockLocalGameState.h" @@ -200,16 +201,16 @@ BOOST_FIXTURE_TEST_CASE(CloseHarborSpots, WorldFixture(world.GetNumHarborPoints())) { for(const auto dir : helpers::EnumRange{}) { - unsigned seaId = world.GetSeaId(startHb, dir); + SeaId seaId = world.GetSeaId(startHb, dir); if(!seaId) continue; MapPoint startPt = world.GetCoastalPoint(startHb, seaId); BOOST_TEST_REQUIRE(startPt == world.GetNeighbour(world.GetHarborPoint(startHb), dir)); - for(unsigned targetHb = 1; targetHb < world.GetNumHarborPoints(); targetHb++) + for(const auto targetHb : helpers::idRange(world.GetNumHarborPoints())) { MapPoint destPt = world.GetCoastalPoint(targetHb, seaId); BOOST_TEST_REQUIRE(destPt.isValid()); diff --git a/tools/ci/build.sh b/tools/ci/build.sh index 4ec1e50d5d..9d8cac2510 100755 --- a/tools/ci/build.sh +++ b/tools/ci/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2005 - 2025 Settlers Freaks +# Copyright (C) 2005 - 2026 Settlers Freaks # # SPDX-License-Identifier: GPL-2.0-or-later @@ -33,29 +33,3 @@ if ! cmake .. -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ fi make -j3 ${MAKE_TARGET} || make VERBOSE=1 ${MAKE_TARGET} - -# Set runtime path for boost libraries -CMAKE_VARS="$(cmake -LA -N .)" -if echo "${CMAKE_VARS}" | grep -q Boost_LIBRARY_DIR_DEBUG; then - boostLibDir="$(echo "${CMAKE_VARS}" | grep Boost_LIBRARY_DIR_DEBUG | cut -d "=" -f2)" -else - boostLibDir="$(echo "${CMAKE_VARS}" | grep Boost_DIR | cut -d "=" -f2)" - boostLibDir="${boostLibDir%/lib/*}/lib" -fi -# ... but not if it is a system boost because that would fail on OSX due to messing up the search order -if ! [[ "$boostLibDir" =~ ^/usr/ ]]; then - export DYLD_LIBRARY_PATH="${boostLibDir}${DYLD_LIBRARY_PATH+":$DYLD_LIBRARY_PATH"}" - export LD_LIBRARY_PATH="${boostLibDir}${LD_LIBRARY_PATH+":$LD_LIBRARY_PATH"}" -fi - -# Execute tests -export RTTR_DISABLE_ASSERT_BREAKPOINT=1 -export UBSAN_OPTIONS=print_stacktrace=1 -export AUDIODEV=null # Avoid errors like: ALSA lib confmisc.c:768:(parse_card) cannot find card '0' -export SDL_VIDEODRIVER=dummy # Avoid crash on travis -if ! ctest --output-on-failure -C "${BUILD_TYPE}" -j2; then - cat CMakeCache.txt - echo "LD:${LD_LIBRARY_PATH:-}" - echo "DYLD:${DYLD_LIBRARY_PATH:-}" - ctest --output-on-failure -C "${BUILD_TYPE}" --rerun-failed --extra-verbose -fi diff --git a/tools/ci/test.sh b/tools/ci/test.sh new file mode 100755 index 0000000000..368677b7f1 --- /dev/null +++ b/tools/ci/test.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Copyright (C) 2005 - 2026 Settlers Freaks +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +# Information +cmake --version +$CXX --version + +cd build + +# Set runtime path for boost libraries +if ! CMAKE_VARS="$(cmake -LA -N .)"; then + echo "Failed to get CMake variables" + exit 1 +fi +if echo "${CMAKE_VARS}" | grep -q Boost_LIBRARY_DIR_DEBUG; then + boostLibDir="$(echo "${CMAKE_VARS}" | grep Boost_LIBRARY_DIR_DEBUG | cut -d "=" -f2)" +else + boostLibDir="$(echo "${CMAKE_VARS}" | grep Boost_DIR | cut -d "=" -f2)" + boostLibDir="${boostLibDir%/lib/*}/lib" +fi +# ... but not if it is a system boost because that would fail on OSX due to messing up the search order +if ! [[ "$boostLibDir" =~ ^/usr/ ]]; then + export DYLD_LIBRARY_PATH="${boostLibDir}${DYLD_LIBRARY_PATH+":$DYLD_LIBRARY_PATH"}" + export LD_LIBRARY_PATH="${boostLibDir}${LD_LIBRARY_PATH+":$LD_LIBRARY_PATH"}" +fi + +# Execute tests +export RTTR_DISABLE_ASSERT_BREAKPOINT=1 +export UBSAN_OPTIONS=print_stacktrace=1 +export AUDIODEV=null # Avoid errors like: ALSA lib confmisc.c:768:(parse_card) cannot find card '0' +export SDL_VIDEODRIVER=dummy # Avoid crash on travis +if ! ctest --output-on-failure -C "${BUILD_TYPE}" -j2; then + echo "::group::CMake cache" + cat CMakeCache.txt + echo "::endgroup::" + echo "LD:${LD_LIBRARY_PATH:-}" + echo "DYLD:${DYLD_LIBRARY_PATH:-}" + echo "::group::Rerun failed tests only" + ctest --output-on-failure -C "${BUILD_TYPE}" --rerun-failed --extra-verbose + echo "::endgroup::" +fi