From 0b44deedd0e24387fb56a5f8e8817eeeb004f4ca Mon Sep 17 00:00:00 2001 From: Samu Date: Mon, 1 Oct 2018 22:40:30 +0100 Subject: [PATCH 1/3] 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. --- src/pathfinder/water_regions.cpp | 21 ++++ src/pathfinder/water_regions.h | 3 + src/pathfinder/yapf/yapf.h | 10 ++ src/pathfinder/yapf/yapf_ship.cpp | 113 +++++++++++++++++++--- src/pathfinder/yapf/yapf_ship_regions.cpp | 47 ++++++++- src/pathfinder/yapf/yapf_ship_regions.h | 1 + src/ship_cmd.cpp | 62 ++---------- 7 files changed, 188 insertions(+), 69 deletions(-) diff --git a/src/pathfinder/water_regions.cpp b/src/pathfinder/water_regions.cpp index 60e1c9b723..88fb21493f 100644 --- a/src/pathfinder/water_regions.cpp +++ b/src/pathfinder/water_regions.cpp @@ -430,3 +430,24 @@ void PrintWaterRegionDebugInfo(TileIndex tile) { 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; +} diff --git a/src/pathfinder/water_regions.h b/src/pathfinder/water_regions.h index 4e35f7090c..bb457bc183 100644 --- a/src/pathfinder/water_regions.h +++ b/src/pathfinder/water_regions.h @@ -64,4 +64,7 @@ void AllocateWaterRegions(); void PrintWaterRegionDebugInfo(TileIndex tile); +using TestTileIndexCallBack = std::function; +TileIndex GetTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback); + #endif /* WATER_REGIONS_H */ diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h index 186986ce57..88f38334f0 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -34,6 +34,16 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP */ 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. * @param v the RV that needs to find a path diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index a47df5abb3..06307b54bb 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -36,6 +36,7 @@ protected: TileIndex dest_tile; TrackdirBits dest_trackdirs; StationID dest_station; + bool any_ship_depot = false; bool has_intermediate_dest = false; TileIndex intermediate_dest_tile; @@ -55,6 +56,11 @@ public: } } + void SetAnyShipDepotDestination() + { + this->any_ship_depot = true; + } + void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch) { this->has_intermediate_dest = true; @@ -69,10 +75,16 @@ protected: return *static_cast(this); } + TestTileIndexCallBack detect_ship_depot = [&](const TileIndex tile) + { + return IsShipDepotTile(tile) && GetShipDepotPart(tile) == DEPOT_PART_NORTH && IsTileOwner(tile, Yapf().GetVehicle()->owner); + }; + public: /** Called by YAPF to detect if node ends in the desired destination. */ 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); } @@ -89,6 +101,11 @@ public: 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 * 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_y_offs[] = { 0, 1, 0, -1 }; - if (this->PfDetectDestination(n)) { + if (this->any_ship_depot || this->PfDetectDestination(n)) { n.estimate = n.cost; return true; } @@ -158,7 +175,7 @@ public: } /** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */ - inline void RestrictSearch(const std::vector &path) + inline void RestrictSearch(const std::span &path) { this->water_region_corridor.clear(); for (const WaterRegionPatchDesc &path_entry : path) this->water_region_corridor.push_back(path_entry); @@ -211,16 +228,20 @@ public: 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) { - const std::vector high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1); + std::vector high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1); if (high_level_path.empty()) { path_found = false; /* 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); } + 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. * 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. */ @@ -229,13 +250,28 @@ public: /* Set origin and destination nodes */ pf.SetOrigin(v->tile, forward_dirs | reverse_dirs); - pf.SetDestination(v); - const bool is_intermediate_destination = static_cast(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1; - if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back()); + if (find_closest_depot) { + pf.SetAnyShipDepotDestination(); + } else { + pf.SetDestination(v); + } + pf.SetMaxCost(max_penalty); + + const std::span high_level_path_span(high_level_path.data(), std::min(high_level_path.size(), NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1)); + const bool is_intermediate_destination = static_cast(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 * 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. */ 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. */ 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 * 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. */ @@ -254,7 +296,7 @@ public: while (node->parent) { 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; /* 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; ShipPathCache dummy_cache; + TileIndex tile = v->tile; Trackdir best_origin_dir = INVALID_TRACKDIR; if (trackdir == nullptr) { @@ -310,17 +353,44 @@ public: const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir()); const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir()); 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; } else { /* 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 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); 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. */ @@ -333,6 +403,11 @@ public: typedef typename Types::NodeList::Item Node; ///< this will be our node type. typedef typename Node::Key Key; ///< key to hash tables. +protected: + int max_cost; + + CYapfCostShipT() : max_cost(0) {} + /** to access inherited path finder */ Tpf &Yapf() { @@ -340,6 +415,11 @@ public: } public: + inline void SetMaxCost(int cost) + { + this->max_cost = cost; + } + inline int CurveCost(Trackdir td1, Trackdir td2) { 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; 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. */ n.cost = n.parent->cost + c; return true; @@ -422,7 +506,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP { Trackdir best_origin_dir = INVALID_TRACKDIR; 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; } @@ -430,3 +514,8 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir) { return CYapfShip::CheckShipReverse(v, trackdir); } + +FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty) +{ + return CYapfShip::FindNearestDepot(v, max_penalty); +} diff --git a/src/pathfinder/yapf/yapf_ship_regions.cpp b/src/pathfinder/yapf/yapf_ship_regions.cpp index 0a1a831a05..1991321475 100644 --- a/src/pathfinder/yapf/yapf_ship_regions.cpp +++ b/src/pathfinder/yapf/yapf_ship_regions.cpp @@ -119,6 +119,7 @@ public: protected: Key dest; + bool any_ship_depot = false; public: void SetDestination(const WaterRegionPatchDesc &water_region_patch) @@ -126,18 +127,32 @@ public: this->dest.Set(water_region_patch); } + void SetAnyShipDepotDestination() + { + this->any_ship_depot = true; + } + 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(this); } 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; } inline bool PfCalcEstimate(Node &n) { - if (this->PfDetectDestination(n)) { + if (this->any_ship_depot || this->PfDetectDestination(n)) { n.estimate = n.cost; return true; } @@ -218,6 +233,31 @@ public: assert(!path.empty()); return path; } + + static std::vector 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(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 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. */ @@ -296,5 +336,8 @@ struct CYapfRegionWater : CYapfT 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); } diff --git a/src/pathfinder/yapf/yapf_ship_regions.h b/src/pathfinder/yapf/yapf_ship_regions.h index dc488754b4..318c867769 100644 --- a/src/pathfinder/yapf/yapf_ship_regions.h +++ b/src/pathfinder/yapf/yapf_ship_regions.h @@ -16,5 +16,6 @@ struct Ship; std::vector YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length); +std::vector YapfFindShipDepotRegionPath(const Ship *v); #endif /* YAPF_SHIP_REGIONS_H */ diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 956d730252..7a67ad0569 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -17,7 +17,6 @@ #include "station_base.h" #include "newgrf_engine.h" #include "pathfinder/yapf/yapf.h" -#include "pathfinder/yapf/yapf_ship_regions.h" #include "newgrf_sound.h" #include "spritecache.h" #include "strings_func.h" @@ -39,13 +38,8 @@ #include "table/strings.h" -#include - #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. * @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) { - 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 visited_patch_hashes; - static std::deque patches_to_search; - visited_patch_hashes.clear(); - patches_to_search.clear(); + FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance); - /* 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 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::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; + if (sfdd.tile == INVALID_TILE) return nullptr; + return Depot::GetByTile(sfdd.tile); } static void CheckIfShipNeedsService(Vehicle *v) @@ -209,7 +161,7 @@ static void CheckIfShipNeedsService(Vehicle *v) 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); @@ -956,7 +908,7 @@ CommandCost CmdBuildShip(DoCommandFlags flags, TileIndex tile, const Engine *e, ClosestDepot Ship::FindClosestDepot() { - const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE); + const Depot *depot = FindClosestShipDepot(this, 0); if (depot == nullptr) return ClosestDepot(); return ClosestDepot(depot->xy, depot->index); From 7df9426000ebdcb4c029095cf5a0b8ec2747e4f3 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:37:48 +0100 Subject: [PATCH 2/3] Change: Find nearest depot to entry edge in intermediate region When using an intermediate region, find the depot closest to the edge where it entered the region from. --- src/pathfinder/water_regions.cpp | 69 +++++++++++++++++++++-- src/pathfinder/water_regions.h | 3 +- src/pathfinder/yapf/yapf_ship.cpp | 6 +- src/pathfinder/yapf/yapf_ship_regions.cpp | 2 +- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/pathfinder/water_regions.cpp b/src/pathfinder/water_regions.cpp index 88fb21493f..1393438194 100644 --- a/src/pathfinder/water_regions.cpp +++ b/src/pathfinder/water_regions.cpp @@ -433,12 +433,12 @@ void PrintWaterRegionDebugInfo(TileIndex tile) /** * 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. + * and returns true on 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. + * @return true if it passes the callback test, or false if the callback failed. */ -TileIndex GetTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback) +bool TestTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback) { const WaterRegion region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y); @@ -446,8 +446,67 @@ TileIndex GetTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_pat for (const TileIndex tile : region) { if (region.GetLabel(tile) != water_region_patch.label || !callback(tile)) continue; - return tile; + return true; } - return INVALID_TILE; + return false; +} + +/** + * Tests the provided callback function on all tiles of the current water patch of the region, collects the + * tiles which passed the callback and returns the tile closest to the edge from where the region is entered from. + * @param high_level_path A span containing at least current and parent water patches. + * @param callback The test function that will be called for each tile in the water patch. + * @return The tile closest to the edge from where it came from that passed the callback test, or INVALID_TILE if no tile passed. + */ +TileIndex FindClosestEnteringTile(const std::span high_level_path, TestTileIndexCallBack &callback) +{ + assert(high_level_path.size() > 1); + + const WaterRegionPatchDesc ¤t_water_region_patch = high_level_path.back(); + const WaterRegion current_region = GetUpdatedWaterRegion(current_water_region_patch.x, current_water_region_patch.y); + + /* Check if the current region has a tile which passes the callback test. */ + std::vector tile_list; + for (const TileIndex tile : current_region) { + if (current_region.GetLabel(tile) != current_water_region_patch.label || !callback(tile)) continue; + + /* We collect the tiles when we know which region we came from for further evaluation. */ + tile_list.push_back(tile); + } + + /* If there aren't any tiles that passed the callback, return with an invalid tile. */ + if (tile_list.empty()) return INVALID_TILE; + + /* If there's only one, just return it. */ + if (tile_list.size() == 1) return tile_list.front(); + + const TileIndex top_tile = current_region.begin(); + const TileIndex bot_tile = TileAddXY(top_tile, WATER_REGION_EDGE_LENGTH - 1, WATER_REGION_EDGE_LENGTH - 1); + + /* Get the side from which the current region is entered from. */ + const WaterRegionPatchDesc &parent_water_region_patch = high_level_path[high_level_path.size() - 2]; + const WaterRegion parent_region = GetUpdatedWaterRegion(parent_water_region_patch.x, parent_water_region_patch.y); + const DiagDirection side = DiagdirBetweenTiles(top_tile, parent_region.begin()); + + /* Depending on the side, determine which corner tile to use to extract their x or y coordinates. */ + const bool is_at_top = side == DIAGDIR_NE || side == DIAGDIR_NW; + const TileIndex edge_tile = is_at_top ? top_tile : bot_tile; + const bool is_axis_x = DiagDirToAxis(side) == AXIS_X; + const int x_or_y_edge = is_axis_x ? TileX(edge_tile) : TileY(edge_tile); + + /* With more than one tile passing the callback, calculate the tile that is closest to the edge from whence it came. */ + TileIndex best_tile = INVALID_TILE; + int best_dist = WATER_REGION_EDGE_LENGTH; + for (const TileIndex &tile : tile_list) { + const int x_or_y_tile = is_axis_x ? TileX(tile) : TileY(tile); + const int dist_to_edge = std::abs(x_or_y_tile - x_or_y_edge); + assert(dist_to_edge < WATER_REGION_EDGE_LENGTH); + if (dist_to_edge >= best_dist) continue; + + best_dist = dist_to_edge; + best_tile = tile; + } + + return best_tile; } diff --git a/src/pathfinder/water_regions.h b/src/pathfinder/water_regions.h index bb457bc183..6599d6adda 100644 --- a/src/pathfinder/water_regions.h +++ b/src/pathfinder/water_regions.h @@ -65,6 +65,7 @@ void AllocateWaterRegions(); void PrintWaterRegionDebugInfo(TileIndex tile); using TestTileIndexCallBack = std::function; -TileIndex GetTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback); +bool TestTileInWaterRegionPatch(const WaterRegionPatchDesc &water_region_patch, TestTileIndexCallBack &callback); +TileIndex FindClosestEnteringTile(const std::span high_level_path, TestTileIndexCallBack &callback); #endif /* WATER_REGIONS_H */ diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 06307b54bb..9704bd913b 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -101,9 +101,9 @@ public: return tile == this->dest_tile && ((this->dest_trackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE); } - inline TileIndex GetShipDepotDestination(const WaterRegionPatchDesc &water_region_patch) + inline TileIndex GetShipDepotDestination(const std::span high_level_path) { - return GetTileInWaterRegionPatch(water_region_patch, this->detect_ship_depot); + return FindClosestEnteringTile(high_level_path, this->detect_ship_depot); } /** @@ -283,7 +283,7 @@ public: /* 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(); + tile = is_intermediate_destination ? pf.GetShipDepotDestination(high_level_path) : node->GetTile(); return INVALID_TRACKDIR; } diff --git a/src/pathfinder/yapf/yapf_ship_regions.cpp b/src/pathfinder/yapf/yapf_ship_regions.cpp index 1991321475..a469fae280 100644 --- a/src/pathfinder/yapf/yapf_ship_regions.cpp +++ b/src/pathfinder/yapf/yapf_ship_regions.cpp @@ -144,7 +144,7 @@ public: inline bool PfDetectDestination(Node &n) { if (this->any_ship_depot) { - return GetTileInWaterRegionPatch(n.key.water_region_patch, this->detect_ship_depot) != INVALID_TILE; + return TestTileInWaterRegionPatch(n.key.water_region_patch, this->detect_ship_depot); } return n.key == this->dest; From 8031d68d015ceefb3b4a5f19c840a11bb3abf115 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:33:17 +0000 Subject: [PATCH 3/3] Change: Ships may reverse on find closest depot order Ships may be allowed to reverse when: - manually ordered to find the closest ship depot. - executing an updated order to find the closest ship depot after departing from a station. --- src/aircraft.h | 2 +- src/aircraft_cmd.cpp | 2 +- src/order_cmd.cpp | 9 +++++---- src/order_func.h | 2 +- src/pathfinder/yapf/yapf.h | 3 ++- src/pathfinder/yapf/yapf_ship.cpp | 9 +++++---- src/roadveh.h | 2 +- src/roadveh_cmd.cpp | 2 +- src/ship.h | 2 +- src/ship_cmd.cpp | 8 ++++---- src/train.h | 2 +- src/train_cmd.cpp | 2 +- src/vehicle.cpp | 2 +- src/vehicle_base.h | 3 ++- 14 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/aircraft.h b/src/aircraft.h index d3b9336f1f..44a2bac7bb 100644 --- a/src/aircraft.h +++ b/src/aircraft.h @@ -110,7 +110,7 @@ struct Aircraft final : public SpecializedVehicle { uint Crash(bool flooded = false) override; TileIndex GetOrderStationLocation(StationID station) override; TileIndex GetCargoTile() const override { return this->First()->tile; } - ClosestDepot FindClosestDepot() override; + ClosestDepot FindClosestDepot(bool may_reverse = false) override; /** * Check if the aircraft type is a normal flying device; eg diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index aac97c84c9..7627a83874 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -396,7 +396,7 @@ CommandCost CmdBuildAircraft(DoCommandFlags flags, TileIndex tile, const Engine } -ClosestDepot Aircraft::FindClosestDepot() +ClosestDepot Aircraft::FindClosestDepot([[maybe_unused]] bool may_reverse) { const Station *st = GetTargetAirportIfValid(this); /* If the station is not a valid airport or if it has no hangars */ diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 7b4b675d3c..9f54034734 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1896,10 +1896,11 @@ VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v) * Update the vehicle's destination tile from an order. * @param order the order the vehicle currently has * @param v the vehicle to update + * @param may_reverse Whether the vehicle is allowed to reverse when executing the updated order. * @param conditional_depth the depth (amount of steps) to go with conditional orders. This to prevent infinite loops. * @param pbs_look_ahead Whether we are forecasting orders for pbs reservations in advance. If true, the order indices must not be modified. */ -bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool pbs_look_ahead) +bool UpdateOrderDest(Vehicle *v, const Order *order, bool may_reverse, int conditional_depth, bool pbs_look_ahead) { if (conditional_depth > v->GetNumOrders()) { v->current_order.Free(); @@ -1926,7 +1927,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool if (v->dest_tile == 0 && TimerGameEconomy::date_fract != (v->index % Ticks::DAY_TICKS)) break; /* We need to search for the nearest depot (hangar). */ - ClosestDepot closest_depot = v->FindClosestDepot(); + ClosestDepot closest_depot = v->FindClosestDepot(may_reverse); if (closest_depot.found) { /* PBS reservations cannot reverse */ @@ -2018,7 +2019,7 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool } v->current_order = *order; - return UpdateOrderDest(v, order, conditional_depth + 1, pbs_look_ahead); + return UpdateOrderDest(v, order, may_reverse, conditional_depth + 1, pbs_look_ahead); } /** @@ -2116,7 +2117,7 @@ bool ProcessOrders(Vehicle *v) break; } - return UpdateOrderDest(v, order) && may_reverse; + return UpdateOrderDest(v, order, may_reverse) && may_reverse; } /** diff --git a/src/order_func.h b/src/order_func.h index ff7d865f1a..2df86d342b 100644 --- a/src/order_func.h +++ b/src/order_func.h @@ -20,7 +20,7 @@ void InvalidateVehicleOrder(const Vehicle *v, int data); void CheckOrders(const Vehicle*); void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true); bool ProcessOrders(Vehicle *v); -bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false); +bool UpdateOrderDest(Vehicle *v, const Order *order, bool may_reverse = false, int conditional_depth = 0, bool pbs_look_ahead = false); VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v); uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0); diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h index 88f38334f0..c86624a55a 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -40,9 +40,10 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir); * @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) + * @param may_reverse whether the ship is allowed to reverse * @return the data about the depot */ -FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty); +FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse); /** * Finds the best path for given road vehicle using YAPF. diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 9704bd913b..8a675024eb 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -369,9 +369,10 @@ public: * Find the best depot for a ship. * @param v Ship * @param max_penalty maximum pathfinder cost. + * @param may_reverse whether the ship is allowed to reverse. * @return FindDepotData with the best depot tile, cost and whether to reverse. */ - static inline FindDepotData FindNearestDepot(const Ship *v, int max_penalty) + static inline FindDepotData FindNearestDepot(const Ship *v, int max_penalty, bool may_reverse) { FindDepotData depot; @@ -379,7 +380,7 @@ public: ShipPathCache dummy_cache; TileIndex tile = INVALID_TILE; Trackdir best_origin_dir = INVALID_TRACKDIR; - const bool search_both_ways = max_penalty == 0; + const bool search_both_ways = may_reverse && max_penalty == 0; const Trackdir forward_dir = v->GetVehicleTrackdir(); const Trackdir reverse_dir = ReverseTrackdir(forward_dir); const TrackdirBits forward_dirs = TrackdirToTrackdirBits(forward_dir); @@ -515,7 +516,7 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir) return CYapfShip::CheckShipReverse(v, trackdir); } -FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty) +FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse) { - return CYapfShip::FindNearestDepot(v, max_penalty); + return CYapfShip::FindNearestDepot(v, max_penalty, may_reverse); } diff --git a/src/roadveh.h b/src/roadveh.h index 56aae919c4..00df046dc5 100644 --- a/src/roadveh.h +++ b/src/roadveh.h @@ -132,7 +132,7 @@ struct RoadVehicle final : public GroundVehicle { uint Crash(bool flooded = false) override; Trackdir GetVehicleTrackdir() const override; TileIndex GetOrderStationLocation(StationID station) override; - ClosestDepot FindClosestDepot() override; + ClosestDepot FindClosestDepot(bool may_reverse = false) override; bool IsBus() const; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index e3564f0991..904d08039a 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -346,7 +346,7 @@ static FindDepotData FindClosestRoadDepot(const RoadVehicle *v, int max_distance return YapfRoadVehicleFindNearestDepot(v, max_distance); } -ClosestDepot RoadVehicle::FindClosestDepot() +ClosestDepot RoadVehicle::FindClosestDepot([[maybe_unused]] bool may_reverse) { FindDepotData rfdd = FindClosestRoadDepot(this, 0); if (rfdd.best_length == UINT_MAX) return ClosestDepot(); diff --git a/src/ship.h b/src/ship.h index c4789bf954..cae564d009 100644 --- a/src/ship.h +++ b/src/ship.h @@ -57,7 +57,7 @@ struct Ship final : public SpecializedVehicle { void OnNewEconomyDay() override; Trackdir GetVehicleTrackdir() const override; TileIndex GetOrderStationLocation(StationID station) override; - ClosestDepot FindClosestDepot() override; + ClosestDepot FindClosestDepot(bool may_reverse = false) override; void UpdateCache(); void SetDestTile(TileIndex tile) override; }; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 7a67ad0569..56bcfb4d0b 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -142,12 +142,12 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri result->Set(_ship_sprites[spritenum] + direction); } -static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance) +static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance, bool may_reverse = false) { const TileIndex tile = v->tile; if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) return Depot::GetByTile(tile); - FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance); + FindDepotData sfdd = YapfShipFindNearestDepot(Ship::From(v), max_distance, may_reverse); if (sfdd.tile == INVALID_TILE) return nullptr; return Depot::GetByTile(sfdd.tile); @@ -906,9 +906,9 @@ CommandCost CmdBuildShip(DoCommandFlags flags, TileIndex tile, const Engine *e, return CommandCost(); } -ClosestDepot Ship::FindClosestDepot() +ClosestDepot Ship::FindClosestDepot(bool may_reverse) { - const Depot *depot = FindClosestShipDepot(this, 0); + const Depot *depot = FindClosestShipDepot(this, 0, may_reverse); if (depot == nullptr) return ClosestDepot(); return ClosestDepot(depot->xy, depot->index); diff --git a/src/train.h b/src/train.h index b7ff1a6581..4ce7c7ebd6 100644 --- a/src/train.h +++ b/src/train.h @@ -129,7 +129,7 @@ struct Train final : public GroundVehicle { uint Crash(bool flooded = false) override; Trackdir GetVehicleTrackdir() const override; TileIndex GetOrderStationLocation(StationID station) override; - ClosestDepot FindClosestDepot() override; + ClosestDepot FindClosestDepot(bool may_reverse = false) override; void ReserveTrackUnderConsist() const; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index ab2472af15..7a0194b707 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2176,7 +2176,7 @@ static FindDepotData FindClosestTrainDepot(Train *v, int max_distance) return YapfTrainFindNearestDepot(v, max_distance); } -ClosestDepot Train::FindClosestDepot() +ClosestDepot Train::FindClosestDepot([[maybe_unused]] bool may_reverse) { FindDepotData tfdd = FindClosestTrainDepot(this, 0); if (tfdd.best_length == UINT_MAX) return ClosestDepot(); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index e688e19c6e..04b46ce069 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2583,7 +2583,7 @@ CommandCost Vehicle::SendToDepot(DoCommandFlags flags, DepotCommandFlags command return CommandCost(); } - ClosestDepot closest_depot = this->FindClosestDepot(); + ClosestDepot closest_depot = this->FindClosestDepot(true); static const StringID no_depot[] = {STR_ERROR_UNABLE_TO_FIND_ROUTE_TO, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT, STR_ERROR_CAN_T_SEND_AIRCRAFT_TO_HANGAR}; if (!closest_depot.found) return CommandCost(no_depot[this->type]); diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 58c311e804..6eb0317f8f 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -792,9 +792,10 @@ public: /** * Find the closest depot for this vehicle and tell us the location, * DestinationID and whether we should reverse. + * @param may_reverse Whether the vehicle is allowed to reverse. * @return A structure with information about the closest depot, if found. */ - virtual ClosestDepot FindClosestDepot() { return {}; } + virtual ClosestDepot FindClosestDepot([[maybe_unused]] bool may_reverse = false) { return {}; } virtual void SetDestTile(TileIndex tile) { this->dest_tile = tile; }