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/water_regions.cpp b/src/pathfinder/water_regions.cpp index 4ef9694af1..e9d3b8d15f 100644 --- a/src/pathfinder/water_regions.cpp +++ b/src/pathfinder/water_regions.cpp @@ -430,3 +430,83 @@ void PrintWaterRegionDebugInfo(TileIndex tile) { GetUpdatedWaterRegion(tile).PrintDebugInfo(); } + +/** + * Tests the provided callback function on all tiles of the water patch of the region + * 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 true if it passes the callback test, or false if the callback failed. + */ +bool TestTileInWaterRegionPatch(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 true; + } + + 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 4e35f7090c..6599d6adda 100644 --- a/src/pathfinder/water_regions.h +++ b/src/pathfinder/water_regions.h @@ -64,4 +64,8 @@ void AllocateWaterRegions(); void PrintWaterRegionDebugInfo(TileIndex tile); +using TestTileIndexCallBack = std::function; +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.h b/src/pathfinder/yapf/yapf.h index 186986ce57..c86624a55a 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -34,6 +34,17 @@ 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) + * @param may_reverse whether the ship is allowed to reverse + * @return the data about the depot + */ +FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse); + /** * 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..8a675024eb 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 std::span high_level_path) + { + return FindClosestEnteringTile(high_level_path, 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) : 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,45 @@ 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. + * @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, bool may_reverse) + { + FindDepotData depot; + + bool path_found = false; + ShipPathCache dummy_cache; + TileIndex tile = INVALID_TILE; + Trackdir best_origin_dir = INVALID_TRACKDIR; + 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); + 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 +404,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 +416,11 @@ public: } public: + inline void SetMaxCost(int cost) + { + this->max_cost = cost; + } + inline int CurveCost(Trackdir td1, Trackdir td2) { assert(IsValidTrackdir(td1)); @@ -384,6 +465,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 +507,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 +515,8 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir) { return CYapfShip::CheckShipReverse(v, trackdir); } + +FindDepotData YapfShipFindNearestDepot(const Ship *v, int max_penalty, bool may_reverse) +{ + return CYapfShip::FindNearestDepot(v, max_penalty, may_reverse); +} diff --git a/src/pathfinder/yapf/yapf_ship_regions.cpp b/src/pathfinder/yapf/yapf_ship_regions.cpp index 0a1a831a05..a469fae280 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 TestTileInWaterRegionPatch(n.key.water_region_patch, this->detect_ship_depot); + } + 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/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 956d730252..56bcfb4d0b 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 @@ -148,57 +142,15 @@ 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 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, may_reverse); - /* 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); @@ -954,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, MAX_SHIP_DEPOT_SEARCH_DISTANCE); + 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 4d3428e9f5..9ccb236884 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2194,7 +2194,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 4765b07c41..cd614e96c3 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; }