Fix #5713: FindClosestShipDepot only considers depots that are actually reachable (#11768)

(cherry picked from commit 8a4a99b7e8)
This commit is contained in:
Kuhnovic
2024-01-27 15:06:14 +01:00
committed by Jonathan G Rennison
parent f9321686a7
commit a7c2f489f6
4 changed files with 63 additions and 18 deletions

View File

@@ -27,6 +27,7 @@ constexpr TWaterRegionPatchLabel FIRST_REGION_LABEL = 1;
constexpr TWaterRegionPatchLabel INVALID_WATER_REGION_PATCH = 0; constexpr TWaterRegionPatchLabel INVALID_WATER_REGION_PATCH = 0;
static_assert(sizeof(TWaterRegionTraversabilityBits) * 8 == WATER_REGION_EDGE_LENGTH); static_assert(sizeof(TWaterRegionTraversabilityBits) * 8 == WATER_REGION_EDGE_LENGTH);
static_assert(sizeof(TWaterRegionPatchLabel) == sizeof(byte)); // Important for the hash calculation.
static inline TrackBits GetWaterTracks(TileIndex tile) { return TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)); } static inline TrackBits GetWaterTracks(TileIndex tile) { return TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)); }
static inline bool IsAqueductTile(TileIndex tile) { return IsBridgeTile(tile) && GetTunnelBridgeTransportType(tile) == TRANSPORT_WATER; } static inline bool IsAqueductTile(TileIndex tile) { return IsBridgeTile(tile) && GetTunnelBridgeTransportType(tile) == TRANSPORT_WATER; }
@@ -318,14 +319,23 @@ WaterRegionReference GetUpdatedWaterRegion(TileIndex tile)
} }
/** /**
* Returns the index of the water region * Returns the index of the water region.
* @param water_region The Water region to return the index for * @param water_region The water region to return the index for.
*/ */
TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region) TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region)
{ {
return GetWaterRegionIndex(water_region.x, water_region.y); return GetWaterRegionIndex(water_region.x, water_region.y);
} }
/**
* Calculates a number that uniquely identifies the provided water region patch.
* @param water_region_patch The Water region to calculate the hash for.
*/
uint32_t CalculateWaterRegionPatchHash(const WaterRegionPatchDesc &water_region_patch)
{
return water_region_patch.label | GetWaterRegionIndex(water_region_patch) << 8;
}
/** /**
* Returns the center tile of a particular water region. * Returns the center tile of a particular water region.
* @param water_region The water region to find the center tile for. * @param water_region The water region to find the center tile for.

View File

@@ -58,6 +58,8 @@ struct WaterRegionDesc
TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region); TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region);
uint32_t CalculateWaterRegionPatchHash(const WaterRegionPatchDesc &water_region_patch);
TileIndex GetWaterRegionCenterTile(const WaterRegionDesc &water_region); TileIndex GetWaterRegionCenterTile(const WaterRegionDesc &water_region);
WaterRegionDesc GetWaterRegionInfo(TileIndex tile); WaterRegionDesc GetWaterRegionInfo(TileIndex tile);

View File

@@ -25,14 +25,12 @@ constexpr uint32_t MAX_NUMBER_OF_NODES = 65536;
struct CYapfRegionPatchNodeKey { struct CYapfRegionPatchNodeKey {
WaterRegionPatchDesc m_water_region_patch; WaterRegionPatchDesc m_water_region_patch;
static_assert(sizeof(TWaterRegionPatchLabel) == sizeof(byte)); // Important for the hash calculation.
inline void Set(const WaterRegionPatchDesc &water_region_patch) inline void Set(const WaterRegionPatchDesc &water_region_patch)
{ {
m_water_region_patch = water_region_patch; m_water_region_patch = water_region_patch;
} }
inline int CalcHash() const { return m_water_region_patch.label | GetWaterRegionIndex(m_water_region_patch) << 8; } inline uint32_t CalcHash() const { return CalculateWaterRegionPatchHash(m_water_region_patch); }
inline bool operator==(const CYapfRegionPatchNodeKey &other) const { return CalcHash() == other.CalcHash(); } inline bool operator==(const CYapfRegionPatchNodeKey &other) const { return CalcHash() == other.CalcHash(); }
}; };

View File

@@ -18,6 +18,7 @@
#include "station_base.h" #include "station_base.h"
#include "newgrf_engine.h" #include "newgrf_engine.h"
#include "pathfinder/yapf/yapf.h" #include "pathfinder/yapf/yapf.h"
#include "pathfinder/yapf/yapf_ship_regions.h"
#include "newgrf_sound.h" #include "newgrf_sound.h"
#include "spritecache.h" #include "spritecache.h"
#include "strings_func.h" #include "strings_func.h"
@@ -38,11 +39,16 @@
#include "industry_map.h" #include "industry_map.h"
#include "core/checksum_func.hpp" #include "core/checksum_func.hpp"
#include "articulated_vehicles.h" #include "articulated_vehicles.h"
#include "core/ring_buffer.hpp"
#include "3rdparty/robin_hood/robin_hood.h"
#include "table/strings.h" #include "table/strings.h"
#include "safeguards.h" #include "safeguards.h"
/** Max distance in tiles (as the crow flies) to search for depots when user clicks "go to depot". */
constexpr int MAX_SHIP_DEPOT_SEARCH_DISTANCE = 80;
/** Directions to search towards given track bits and the ship's enter direction. */ /** Directions to search towards given track bits and the ship's enter direction. */
const DiagDirection _ship_search_directions[6][4] = { const DiagDirection _ship_search_directions[6][4] = {
{ DIAGDIR_NE, INVALID_DIAGDIR, DIAGDIR_SW, INVALID_DIAGDIR }, { DIAGDIR_NE, INVALID_DIAGDIR, DIAGDIR_SW, INVALID_DIAGDIR },
@@ -155,21 +161,50 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri
static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance) static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance)
{ {
/* Find the closest depot */ const uint max_region_distance = (max_distance / WATER_REGION_EDGE_LENGTH) + 1;
const Depot *best_depot = nullptr;
/* If we don't have a maximum distance, i.e. distance = 0,
* we want to find any depot so the best distance of no
* depot must be more than any correct distance. On the
* other hand if we have set a maximum distance, any depot
* further away than max_distance can safely be ignored. */
uint best_dist = max_distance == 0 ? UINT_MAX : max_distance + 1;
static robin_hood::unordered_flat_set<uint32_t> visited_patch_hashes;
static ring_buffer<WaterRegionPatchDesc> patches_to_search;
visited_patch_hashes.clear();
patches_to_search.clear();
/* Step 1: find a set of reachable Water Region Patches using BFS. */
const WaterRegionPatchDesc start_patch = GetWaterRegionPatchInfo(v->tile);
patches_to_search.push_back(start_patch);
visited_patch_hashes.insert(CalculateWaterRegionPatchHash(start_patch));
while (!patches_to_search.empty()) {
/* Remove first patch from the queue and make it the current patch. */
const WaterRegionPatchDesc current_node = patches_to_search.front();
patches_to_search.pop_front();
/* Add neighbors of the current patch to the search queue. */
TVisitWaterRegionPatchCallBack visitFunc = [&](const WaterRegionPatchDesc &water_region_patch) {
/* Note that we check the max distance per axis, not the total distance. */
if (Delta(water_region_patch.x, start_patch.x) > max_region_distance ||
Delta(water_region_patch.y, start_patch.y) > max_region_distance) return;
const uint32_t hash = CalculateWaterRegionPatchHash(water_region_patch);
auto res = visited_patch_hashes.insert(hash);
if (res.second) {
patches_to_search.push_back(water_region_patch);
}
};
VisitWaterRegionPatchNeighbors(current_node, visitFunc);
}
/* Step 2: Find the closest depot within the reachable Water Region Patches. */
const uint max_distance_sq = max_distance * max_distance;
const Depot *best_depot = nullptr;
uint best_dist_sq = std::numeric_limits<uint>::max();
for (const Depot *depot : Depot::Iterate()) { for (const Depot *depot : Depot::Iterate()) {
TileIndex tile = depot->xy; const TileIndex tile = depot->xy;
if (IsShipDepotTile(tile) && IsInfraTileUsageAllowed(VEH_SHIP, v->owner, tile)) { if (IsShipDepotTile(tile) && IsInfraTileUsageAllowed(VEH_SHIP, v->owner, tile)) {
uint dist = DistanceManhattan(tile, v->tile); const uint dist_sq = DistanceSquare(tile, v->tile);
if (dist < best_dist) { if (dist_sq < best_dist_sq && dist_sq <= max_distance_sq &&
best_dist = dist; visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) {
best_dist_sq = dist_sq;
best_depot = depot; best_depot = depot;
} }
} }
@@ -1190,7 +1225,7 @@ CommandCost CmdBuildShip(TileIndex tile, DoCommandFlag flags, const Engine *e, V
ClosestDepot Ship::FindClosestDepot() ClosestDepot Ship::FindClosestDepot()
{ {
const Depot *depot = FindClosestShipDepot(this, 0); const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE);
if (depot == nullptr) return ClosestDepot(); if (depot == nullptr) return ClosestDepot();
return ClosestDepot(depot->xy, depot->index); return ClosestDepot(depot->xy, depot->index);