mirror of https://github.com/OpenTTD/OpenTTD
Fix #5713: Use pathfinder to find closest ship depot
When ships are asked to find the closest depot, the depot that is provided is not always reachable. This patch provides the closest reachable ship depot, by utilizing the pathfinder.pull/10544/head
parent
34c0b09764
commit
0b44deedd0
|
@ -430,3 +430,24 @@ void PrintWaterRegionDebugInfo(TileIndex tile)
|
||||||
{
|
{
|
||||||
GetUpdatedWaterRegion(tile).PrintDebugInfo();
|
GetUpdatedWaterRegion(tile).PrintDebugInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the provided callback function on all tiles of the water patch of the region
|
||||||
|
* and returns the first tile that passes the callback test.
|
||||||
|
* @param callback The test function that will be called for the water patch.
|
||||||
|
* @param water_region_patch Water patch within the water region to test the callback.
|
||||||
|
* @return the first tile which passed the callback test, or INVALID_TILE if the callback failed.
|
||||||
|
*/
|
||||||
|
TileIndex GetTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback)
|
||||||
|
{
|
||||||
|
const WaterRegion region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y);
|
||||||
|
|
||||||
|
/* Check if the region has a tile which passes the callback test. */
|
||||||
|
for (const TileIndex tile : region) {
|
||||||
|
if (region.GetLabel(tile) != water_region_patch.label || !callback(tile)) continue;
|
||||||
|
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INVALID_TILE;
|
||||||
|
}
|
||||||
|
|
|
@ -64,4 +64,7 @@ void AllocateWaterRegions();
|
||||||
|
|
||||||
void PrintWaterRegionDebugInfo(TileIndex tile);
|
void PrintWaterRegionDebugInfo(TileIndex tile);
|
||||||
|
|
||||||
|
using TestTileIndexCallBack = std::function<bool(const TileIndex)>;
|
||||||
|
TileIndex GetTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback);
|
||||||
|
|
||||||
#endif /* WATER_REGIONS_H */
|
#endif /* WATER_REGIONS_H */
|
||||||
|
|
|
@ -34,6 +34,16 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
|
||||||
*/
|
*/
|
||||||
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir);
|
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when user sends ship to the nearest depot or if ship needs servicing using YAPF.
|
||||||
|
* @param v ship that needs to go to some depot
|
||||||
|
* @param max_penalty max distance (in pathfinder penalty) from the current ship position
|
||||||
|
* (used also as optimization - the pathfinder can stop path finding if max_penalty
|
||||||
|
* was reached and no depot was seen)
|
||||||
|
* @return the data about the depot
|
||||||
|
*/
|
||||||
|
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the best path for given road vehicle using YAPF.
|
* Finds the best path for given road vehicle using YAPF.
|
||||||
* @param v the RV that needs to find a path
|
* @param v the RV that needs to find a path
|
||||||
|
|
|
@ -36,6 +36,7 @@ protected:
|
||||||
TileIndex dest_tile;
|
TileIndex dest_tile;
|
||||||
TrackdirBits dest_trackdirs;
|
TrackdirBits dest_trackdirs;
|
||||||
StationID dest_station;
|
StationID dest_station;
|
||||||
|
bool any_ship_depot = false;
|
||||||
|
|
||||||
bool has_intermediate_dest = false;
|
bool has_intermediate_dest = false;
|
||||||
TileIndex intermediate_dest_tile;
|
TileIndex intermediate_dest_tile;
|
||||||
|
@ -55,6 +56,11 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetAnyShipDepotDestination()
|
||||||
|
{
|
||||||
|
this->any_ship_depot = true;
|
||||||
|
}
|
||||||
|
|
||||||
void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch)
|
void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||||
{
|
{
|
||||||
this->has_intermediate_dest = true;
|
this->has_intermediate_dest = true;
|
||||||
|
@ -69,10 +75,16 @@ protected:
|
||||||
return *static_cast<Tpf*>(this);
|
return *static_cast<Tpf*>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile)
|
||||||
|
{
|
||||||
|
return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner);
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Called by YAPF to detect if node ends in the desired destination. */
|
/** Called by YAPF to detect if node ends in the desired destination. */
|
||||||
inline bool PfDetectDestination(Node &n)
|
inline bool PfDetectDestination(Node &n)
|
||||||
{
|
{
|
||||||
|
if (this->any_ship_depot) return this->detect_ship_depot(n.key.tile);
|
||||||
return this->PfDetectDestinationTile(n.segment_last_tile, n.segment_last_td);
|
return this->PfDetectDestinationTile(n.segment_last_tile, n.segment_last_td);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +101,11 @@ public:
|
||||||
return tile == this->dest_tile && ((this->dest_trackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE);
|
return tile == this->dest_tile && ((this->dest_trackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline TileIndex GetShipDepotDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||||
|
{
|
||||||
|
return GetTileInWaterRegionPatch(water_region_patch, this->detect_ship_depot);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by YAPF to calculate cost estimate. Calculates distance to the destination
|
* Called by YAPF to calculate cost estimate. Calculates distance to the destination
|
||||||
* adds it to the actual cost from origin and stores the sum to the Node::estimate.
|
* adds it to the actual cost from origin and stores the sum to the Node::estimate.
|
||||||
|
@ -99,7 +116,7 @@ public:
|
||||||
|
|
||||||
static const int dg_dir_to_x_offs[] = { -1, 0, 1, 0 };
|
static const int dg_dir_to_x_offs[] = { -1, 0, 1, 0 };
|
||||||
static const int dg_dir_to_y_offs[] = { 0, 1, 0, -1 };
|
static const int dg_dir_to_y_offs[] = { 0, 1, 0, -1 };
|
||||||
if (this->PfDetectDestination(n)) {
|
if (this->any_ship_depot || this->PfDetectDestination(n)) {
|
||||||
n.estimate = n.cost;
|
n.estimate = n.cost;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +175,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */
|
/** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */
|
||||||
inline void RestrictSearch(const std::vector<WaterRegionPatchDesc> &path)
|
inline void RestrictSearch(const std::span<WaterRegionPatchDesc> &path)
|
||||||
{
|
{
|
||||||
this->water_region_corridor.clear();
|
this->water_region_corridor.clear();
|
||||||
for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry);
|
for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry);
|
||||||
|
@ -211,16 +228,20 @@ public:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs,
|
static Trackdir ChooseShipTrack(const Ship *v, TileIndex &tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs, int max_penalty,
|
||||||
bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir)
|
bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir)
|
||||||
{
|
{
|
||||||
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
|
std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
|
||||||
if (high_level_path.empty()) {
|
if (high_level_path.empty()) {
|
||||||
path_found = false;
|
path_found = false;
|
||||||
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
|
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
|
||||||
return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
|
return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool find_closest_depot = tile == INVALID_TILE;
|
||||||
|
if (find_closest_depot) tile = v->tile;
|
||||||
|
const bool automatic_servicing = find_closest_depot && max_penalty != 0;
|
||||||
|
|
||||||
/* Try one time without restricting the search area, which generally results in better and more natural looking paths.
|
/* Try one time without restricting the search area, which generally results in better and more natural looking paths.
|
||||||
* However the pathfinder can hit the node limit in certain situations such as long aqueducts or maze-like terrain.
|
* However the pathfinder can hit the node limit in certain situations such as long aqueducts or maze-like terrain.
|
||||||
* If that happens we run the pathfinder again, but restricted only to the regions provided by the region pathfinder. */
|
* If that happens we run the pathfinder again, but restricted only to the regions provided by the region pathfinder. */
|
||||||
|
@ -229,13 +250,28 @@ public:
|
||||||
|
|
||||||
/* Set origin and destination nodes */
|
/* Set origin and destination nodes */
|
||||||
pf.SetOrigin(v->tile, forward_dirs | reverse_dirs);
|
pf.SetOrigin(v->tile, forward_dirs | reverse_dirs);
|
||||||
pf.SetDestination(v);
|
if (find_closest_depot) {
|
||||||
const bool is_intermediate_destination = static_cast<int>(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
|
pf.SetAnyShipDepotDestination();
|
||||||
if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back());
|
} else {
|
||||||
|
pf.SetDestination(v);
|
||||||
|
}
|
||||||
|
pf.SetMaxCost(max_penalty);
|
||||||
|
|
||||||
|
const std::span<WaterRegionPatchDesc> high_level_path_span(high_level_path.data(), std::min<size_t>(high_level_path.size(), NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1));
|
||||||
|
const bool is_intermediate_destination = static_cast<int>(high_level_path_span.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
|
||||||
|
if (is_intermediate_destination) {
|
||||||
|
if (automatic_servicing) {
|
||||||
|
/* Automatic servicing requires a valid path cost from start to end.
|
||||||
|
* However, when an intermediate destination is set, the resulting cost
|
||||||
|
* cannot be used to determine if it falls within the maximum allowed penalty. */
|
||||||
|
return INVALID_TRACKDIR;
|
||||||
|
}
|
||||||
|
pf.SetIntermediateDestination(high_level_path_span.back());
|
||||||
|
}
|
||||||
|
|
||||||
/* Restrict the search area to prevent the low level pathfinder from expanding too many nodes. This can happen
|
/* Restrict the search area to prevent the low level pathfinder from expanding too many nodes. This can happen
|
||||||
* when the terrain is very "maze-like" or when the high level path "teleports" via a very long aqueduct. */
|
* when the terrain is very "maze-like" or when the high level path "teleports" via a very long aqueduct. */
|
||||||
if (attempt > 0) pf.RestrictSearch(high_level_path);
|
if (attempt > 0) pf.RestrictSearch(high_level_path_span);
|
||||||
|
|
||||||
/* Find best path. */
|
/* Find best path. */
|
||||||
path_found = pf.FindPath(v);
|
path_found = pf.FindPath(v);
|
||||||
|
@ -245,6 +281,12 @@ public:
|
||||||
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
|
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
|
||||||
if (!path_found) return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
|
if (!path_found) return CreateRandomPath(v, path_cache, SHIP_LOST_PATH_LENGTH);
|
||||||
|
|
||||||
|
/* Return early when only searching for the closest depot tile. */
|
||||||
|
if (find_closest_depot) {
|
||||||
|
tile = is_intermediate_destination ? pf.GetShipDepotDestination(high_level_path.back()) : node->GetTile();
|
||||||
|
return INVALID_TRACKDIR;
|
||||||
|
}
|
||||||
|
|
||||||
/* Return only the path within the current water region if an intermediate destination was returned. If not, cache the entire path
|
/* Return only the path within the current water region if an intermediate destination was returned. If not, cache the entire path
|
||||||
* to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
|
* to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
|
||||||
* caching the full path the ship can get stuck in a loop. */
|
* caching the full path the ship can get stuck in a loop. */
|
||||||
|
@ -254,7 +296,7 @@ public:
|
||||||
while (node->parent) {
|
while (node->parent) {
|
||||||
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
|
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
|
||||||
|
|
||||||
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path, node_water_patch) != high_level_path.end();
|
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path_span, node_water_patch) != high_level_path_span.end();
|
||||||
const bool add_full_path = !is_intermediate_destination && node_water_patch != end_water_patch;
|
const bool add_full_path = !is_intermediate_destination && node_water_patch != end_water_patch;
|
||||||
|
|
||||||
/* The cached path must always lead to a region patch that's on the high level path.
|
/* The cached path must always lead to a region patch that's on the high level path.
|
||||||
|
@ -303,6 +345,7 @@ public:
|
||||||
{
|
{
|
||||||
bool path_found = false;
|
bool path_found = false;
|
||||||
ShipPathCache dummy_cache;
|
ShipPathCache dummy_cache;
|
||||||
|
TileIndex tile = v->tile;
|
||||||
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
||||||
|
|
||||||
if (trackdir == nullptr) {
|
if (trackdir == nullptr) {
|
||||||
|
@ -310,17 +353,44 @@ public:
|
||||||
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
|
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
|
||||||
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
|
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
|
||||||
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(reverse_dir);
|
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(reverse_dir);
|
||||||
(void)ChooseShipTrack(v, v->tile, forward_dirs, reverse_dirs, path_found, dummy_cache, best_origin_dir);
|
(void)ChooseShipTrack(v, tile, forward_dirs, reverse_dirs, 0, path_found, dummy_cache, best_origin_dir);
|
||||||
return path_found && best_origin_dir == reverse_dir;
|
return path_found && best_origin_dir == reverse_dir;
|
||||||
} else {
|
} else {
|
||||||
/* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */
|
/* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */
|
||||||
const DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
|
const DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
|
||||||
const TrackdirBits reverse_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry));
|
const TrackdirBits reverse_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry));
|
||||||
(void)ChooseShipTrack(v, v->tile, TRACKDIR_BIT_NONE, reverse_dirs, path_found, dummy_cache, best_origin_dir);
|
(void)ChooseShipTrack(v, tile, TRACKDIR_BIT_NONE, reverse_dirs, 0, path_found, dummy_cache, best_origin_dir);
|
||||||
*trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs);
|
*trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the best depot for a ship.
|
||||||
|
* @param v Ship
|
||||||
|
* @param max_penalty maximum pathfinder cost.
|
||||||
|
* @return FindDepotData with the best depot tile, cost and whether to reverse.
|
||||||
|
*/
|
||||||
|
static inline FindDepotData FindNearestDepot(const Ship *v, int max_penalty)
|
||||||
|
{
|
||||||
|
FindDepotData depot;
|
||||||
|
|
||||||
|
bool path_found = false;
|
||||||
|
ShipPathCache dummy_cache;
|
||||||
|
TileIndex tile = INVALID_TILE;
|
||||||
|
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
||||||
|
const bool search_both_ways = max_penalty == 0;
|
||||||
|
const Trackdir forward_dir = v->GetVehicleTrackdir();
|
||||||
|
const Trackdir reverse_dir = ReverseTrackdir(forward_dir);
|
||||||
|
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(forward_dir);
|
||||||
|
const TrackdirBits reverse_dirs = search_both_ways ? TrackdirToTrackdirBits(reverse_dir) : TRACKDIR_BIT_NONE;
|
||||||
|
(void)ChooseShipTrack(v, tile, forward_dirs, reverse_dirs, max_penalty, path_found, dummy_cache, best_origin_dir);
|
||||||
|
if (path_found) {
|
||||||
|
assert(tile != INVALID_TILE);
|
||||||
|
depot.tile = tile;
|
||||||
|
}
|
||||||
|
return depot;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Cost Provider module of YAPF for ships. */
|
/** Cost Provider module of YAPF for ships. */
|
||||||
|
@ -333,6 +403,11 @@ public:
|
||||||
typedef typename Types::NodeList::Item Node; ///< this will be our node type.
|
typedef typename Types::NodeList::Item Node; ///< this will be our node type.
|
||||||
typedef typename Node::Key Key; ///< key to hash tables.
|
typedef typename Node::Key Key; ///< key to hash tables.
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int max_cost;
|
||||||
|
|
||||||
|
CYapfCostShipT() : max_cost(0) {}
|
||||||
|
|
||||||
/** to access inherited path finder */
|
/** to access inherited path finder */
|
||||||
Tpf &Yapf()
|
Tpf &Yapf()
|
||||||
{
|
{
|
||||||
|
@ -340,6 +415,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
inline void SetMaxCost(int cost)
|
||||||
|
{
|
||||||
|
this->max_cost = cost;
|
||||||
|
}
|
||||||
|
|
||||||
inline int CurveCost(Trackdir td1, Trackdir td2)
|
inline int CurveCost(Trackdir td1, Trackdir td2)
|
||||||
{
|
{
|
||||||
assert(IsValidTrackdir(td1));
|
assert(IsValidTrackdir(td1));
|
||||||
|
@ -384,6 +464,10 @@ public:
|
||||||
uint8_t speed_frac = (GetEffectiveWaterClass(n.GetTile()) == WATER_CLASS_SEA) ? svi->ocean_speed_frac : svi->canal_speed_frac;
|
uint8_t speed_frac = (GetEffectiveWaterClass(n.GetTile()) == WATER_CLASS_SEA) ? svi->ocean_speed_frac : svi->canal_speed_frac;
|
||||||
if (speed_frac > 0) c += YAPF_TILE_LENGTH * (1 + tf->tiles_skipped) * speed_frac / (256 - speed_frac);
|
if (speed_frac > 0) c += YAPF_TILE_LENGTH * (1 + tf->tiles_skipped) * speed_frac / (256 - speed_frac);
|
||||||
|
|
||||||
|
/* Finish if we already exceeded the maximum path cost (i.e. when
|
||||||
|
* searching for the nearest depot). */
|
||||||
|
if (this->max_cost > 0 && (n.parent->cost + c) > this->max_cost) return false;
|
||||||
|
|
||||||
/* Apply it. */
|
/* Apply it. */
|
||||||
n.cost = n.parent->cost + c;
|
n.cost = n.parent->cost + c;
|
||||||
return true;
|
return true;
|
||||||
|
@ -422,7 +506,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
|
||||||
{
|
{
|
||||||
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
Trackdir best_origin_dir = INVALID_TRACKDIR;
|
||||||
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
|
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
|
||||||
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, path_found, path_cache, best_origin_dir);
|
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, 0, path_found, path_cache, best_origin_dir);
|
||||||
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
|
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,3 +514,8 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir)
|
||||||
{
|
{
|
||||||
return CYapfShip::CheckShipReverse(v, trackdir);
|
return CYapfShip::CheckShipReverse(v, trackdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty)
|
||||||
|
{
|
||||||
|
return CYapfShip::FindNearestDepot(v, max_penalty);
|
||||||
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Key dest;
|
Key dest;
|
||||||
|
bool any_ship_depot = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void SetDestination(const WaterRegionPatchDesc &water_region_patch)
|
void SetDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||||
|
@ -126,18 +127,32 @@ public:
|
||||||
this->dest.Set(water_region_patch);
|
this->dest.Set(water_region_patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetAnyShipDepotDestination()
|
||||||
|
{
|
||||||
|
this->any_ship_depot = true;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile)
|
||||||
|
{
|
||||||
|
return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner);
|
||||||
|
};
|
||||||
|
|
||||||
Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline bool PfDetectDestination(Node &n) const
|
inline bool PfDetectDestination(Node &n)
|
||||||
{
|
{
|
||||||
|
if (this->any_ship_depot) {
|
||||||
|
return GetTileInWaterRegionPatch(n.key.water_region_patch, this->detect_ship_depot) != INVALID_TILE;
|
||||||
|
}
|
||||||
|
|
||||||
return n.key == this->dest;
|
return n.key == this->dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool PfCalcEstimate(Node &n)
|
inline bool PfCalcEstimate(Node &n)
|
||||||
{
|
{
|
||||||
if (this->PfDetectDestination(n)) {
|
if (this->any_ship_depot || this->PfDetectDestination(n)) {
|
||||||
n.estimate = n.cost;
|
n.estimate = n.cost;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -218,6 +233,31 @@ public:
|
||||||
assert(!path.empty());
|
assert(!path.empty());
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<WaterRegionPatchDesc> FindShipDepotRegionPath(const Ship *v)
|
||||||
|
{
|
||||||
|
const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(v->tile);
|
||||||
|
|
||||||
|
/* We reserve 4 nodes (patches) per water region. The vast majority of water regions have 1 or 2 regions so this should be a pretty
|
||||||
|
* safe limit. We cap the limit at 65536 which is at a region size of 16x16 is equivalent to one node per region for a 4096x4096 map. */
|
||||||
|
Tpf pf(std::min(static_cast<int>(Map::Size() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES));
|
||||||
|
pf.AddOrigin(start_water_region_patch);
|
||||||
|
pf.SetAnyShipDepotDestination();
|
||||||
|
|
||||||
|
/* Find best path. */
|
||||||
|
if (!pf.FindPath(v)) return {}; // Path not found.
|
||||||
|
|
||||||
|
std::vector<WaterRegionPatchDesc> path;
|
||||||
|
Node *node = pf.GetBestNode();
|
||||||
|
while (node != nullptr) {
|
||||||
|
path.push_back(node->key.water_region_patch);
|
||||||
|
node = node->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!path.empty());
|
||||||
|
std::ranges::reverse(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Cost Provider of YAPF for water regions. */
|
/** Cost Provider of YAPF for water regions. */
|
||||||
|
@ -296,5 +336,8 @@ struct CYapfRegionWater : CYapfT<CYapfRegion_TypesT<CYapfRegionWater, CRegionNod
|
||||||
*/
|
*/
|
||||||
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
|
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
|
||||||
{
|
{
|
||||||
|
const bool find_closest_depot = start_tile == INVALID_TILE;
|
||||||
|
|
||||||
|
if (find_closest_depot) return CYapfRegionWater::FindShipDepotRegionPath(v);
|
||||||
return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
|
return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,6 @@
|
||||||
struct Ship;
|
struct Ship;
|
||||||
|
|
||||||
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length);
|
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length);
|
||||||
|
std::vector<WaterRegionPatchDesc> YapfFindShipDepotRegionPath(const Ship *v);
|
||||||
|
|
||||||
#endif /* YAPF_SHIP_REGIONS_H */
|
#endif /* YAPF_SHIP_REGIONS_H */
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#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"
|
||||||
|
@ -39,13 +38,8 @@
|
||||||
|
|
||||||
#include "table/strings.h"
|
#include "table/strings.h"
|
||||||
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the effective #WaterClass for a ship travelling on a tile.
|
* Determine the effective #WaterClass for a ship travelling on a tile.
|
||||||
* @param tile Tile of interest
|
* @param tile Tile of interest
|
||||||
|
@ -150,55 +144,13 @@ 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)
|
||||||
{
|
{
|
||||||
const int max_region_distance = (max_distance / WATER_REGION_EDGE_LENGTH) + 1;
|
const TileIndex tile = v->tile;
|
||||||
|
if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) return Depot::GetByTile(tile);
|
||||||
|
|
||||||
static std::unordered_set<int> visited_patch_hashes;
|
FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance);
|
||||||
static std::deque<WaterRegionPatchDesc> patches_to_search;
|
|
||||||
visited_patch_hashes.clear();
|
|
||||||
patches_to_search.clear();
|
|
||||||
|
|
||||||
/* Step 1: find a set of reachable Water Region Patches using BFS. */
|
if (sfdd.tile == INVALID_TILE) return nullptr;
|
||||||
const WaterRegionPatchDesc start_patch = GetWaterRegionPatchInfo(v->tile);
|
return Depot::GetByTile(sfdd.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 neighbours of the current patch to the search queue. */
|
|
||||||
VisitWaterRegionPatchCallback visit_func = [&](const WaterRegionPatchDesc &water_region_patch) {
|
|
||||||
/* Note that we check the max distance per axis, not the total distance. */
|
|
||||||
if (std::abs(water_region_patch.x - start_patch.x) > max_region_distance ||
|
|
||||||
std::abs(water_region_patch.y - start_patch.y) > max_region_distance) return;
|
|
||||||
|
|
||||||
const int hash = CalculateWaterRegionPatchHash(water_region_patch);
|
|
||||||
if (visited_patch_hashes.count(hash) == 0) {
|
|
||||||
visited_patch_hashes.insert(hash);
|
|
||||||
patches_to_search.push_back(water_region_patch);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
VisitWaterRegionPatchNeighbours(current_node, visit_func);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 2: Find the closest depot within the reachable Water Region Patches. */
|
|
||||||
const Depot *best_depot = nullptr;
|
|
||||||
uint best_dist_sq = std::numeric_limits<uint>::max();
|
|
||||||
for (const Depot *depot : Depot::Iterate()) {
|
|
||||||
const TileIndex tile = depot->xy;
|
|
||||||
if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) {
|
|
||||||
const uint dist_sq = DistanceSquare(tile, v->tile);
|
|
||||||
if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance &&
|
|
||||||
visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) {
|
|
||||||
best_dist_sq = dist_sq;
|
|
||||||
best_depot = depot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return best_depot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CheckIfShipNeedsService(Vehicle *v)
|
static void CheckIfShipNeedsService(Vehicle *v)
|
||||||
|
@ -209,7 +161,7 @@ static void CheckIfShipNeedsService(Vehicle *v)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint max_distance = _settings_game.pf.yapf.maximum_go_to_depot_penalty / YAPF_TILE_LENGTH;
|
uint max_distance = _settings_game.pf.yapf.maximum_go_to_depot_penalty;
|
||||||
|
|
||||||
const Depot *depot = FindClosestShipDepot(v, max_distance);
|
const Depot *depot = FindClosestShipDepot(v, max_distance);
|
||||||
|
|
||||||
|
@ -956,7 +908,7 @@ CommandCost CmdBuildShip(DoCommandFlags flags, TileIndex tile, const Engine *e,
|
||||||
|
|
||||||
ClosestDepot Ship::FindClosestDepot()
|
ClosestDepot Ship::FindClosestDepot()
|
||||||
{
|
{
|
||||||
const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE);
|
const Depot *depot = FindClosestShipDepot(this, 0);
|
||||||
if (depot == nullptr) return ClosestDepot();
|
if (depot == nullptr) return ClosestDepot();
|
||||||
|
|
||||||
return ClosestDepot(depot->xy, depot->index);
|
return ClosestDepot(depot->xy, depot->index);
|
||||||
|
|
Loading…
Reference in New Issue