From 5bee2c4ab8205d8fa836e113a1cbb28ff5e3b816 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:10:50 +0000 Subject: [PATCH] Fix #12193: [YAPF] Don't use the entire docking area when computing closest station tile Modified CYapfDestinationTileWaterT class to accept multiple destinations. In this manner, the estimate cost is calculated for each of the destination tiles and the shortest estimate is returned. Some adaptation was necessary to make this possible to work for both the high and low level pathfinder and the existing functions, ChooseShipTrack and its FindWaterRegionPath brother and CheckShipReverse. --- src/pathfinder/pathfinder_func.h | 34 ++++++++++ src/pathfinder/yapf/yapf_ship.cpp | 80 +++++++++++++---------- src/pathfinder/yapf/yapf_ship_regions.cpp | 20 ++---- src/pathfinder/yapf/yapf_ship_regions.h | 2 +- 4 files changed, 86 insertions(+), 50 deletions(-) diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h index 444b100ce7..2271d60490 100644 --- a/src/pathfinder/pathfinder_func.h +++ b/src/pathfinder/pathfinder_func.h @@ -12,6 +12,40 @@ #include "../tile_cmd.h" #include "../waypoint_base.h" +#include "../ship.h" + +/** + * Creates a list containing possible destination tiles for a ship. + * @param v The ship + * return Vector of tiles filled with all possible destinations. + */ +inline std::vector GetShipDestinationTiles(const Ship *v) +{ + std::vector dest_tiles; + + if (v->current_order.IsType(OT_GOTO_STATION)) { + const StationID station = v->current_order.GetDestination(); + + const BaseStation *st = BaseStation::Get(station); + TileArea ta; + st->GetTileArea(&ta, STATION_DOCK); + /* If the dock station is (temporarily) not present, use the station sign to drive near the station. */ + if (ta.tile == INVALID_TILE) { + dest_tiles.push_back(st->xy); + } else { + for (const TileIndex &docking_tile : ta) { + if (!IsDockingTile(docking_tile) || !IsShipDestinationTile(docking_tile, station)) continue; + dest_tiles.push_back(docking_tile); + } + } + } else { + dest_tiles.push_back(v->dest_tile); + } + + assert(!dest_tiles.empty()); + + return dest_tiles; +} /** * Calculates the tile of given station that is closest to a given tile diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 57d5e9d877..6ede829ebc 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -34,25 +34,21 @@ public: typedef typename Node::Key Key; ///< key to hash tables. protected: - TileIndex m_destTile; - TrackdirBits m_destTrackdirs; - StationID m_destStation; + std::span m_destTiles; + StationID m_destStation; bool m_has_intermediate_dest = false; TileIndex m_intermediate_dest_tile; WaterRegionPatchDesc m_intermediate_dest_region_patch; public: - void SetDestination(const Ship *v) + void SetDestination(const Ship *v, const std::span dest_tiles) { + m_destTiles = dest_tiles; if (v->current_order.IsType(OT_GOTO_STATION)) { m_destStation = v->current_order.GetDestination(); - m_destTile = CalcClosestStationTile(m_destStation, v->tile, STATION_DOCK); - m_destTrackdirs = INVALID_TRACKDIR_BIT; } else { m_destStation = INVALID_STATION; - m_destTile = v->dest_tile; - m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_WATER, 0)); } } @@ -74,11 +70,8 @@ public: /** Called by YAPF to detect if node ends in the desired destination. */ inline bool PfDetectDestination(Node &n) { - return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td); - } + const TileIndex tile = n.m_segment_last_tile; - inline bool PfDetectDestinationTile(TileIndex tile, Trackdir trackdir) - { if (m_has_intermediate_dest) { /* GetWaterRegionInfo is much faster than GetWaterRegionPatchInfo so we try that first. */ if (GetWaterRegionInfo(tile) != m_intermediate_dest_region_patch) return false; @@ -87,23 +80,14 @@ public: if (m_destStation != INVALID_STATION) return IsDockingTile(tile) && IsShipDestinationTile(tile, m_destStation); - return tile == m_destTile && ((m_destTrackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE); + assert(m_destTiles.size() == 1); + return tile == m_destTiles.front(); } - /** - * 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::m_estimate. - */ - inline bool PfCalcEstimate(Node &n) + static inline int CalcEstimate(Node &n, TileIndex destination_tile) { - const TileIndex destination_tile = m_has_intermediate_dest ? m_intermediate_dest_tile : m_destTile; - 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 (PfDetectDestination(n)) { - n.m_estimate = n.m_cost; - return true; - } TileIndex tile = n.m_segment_last_tile; DiagDirection exitdir = TrackdirToExitdir(n.m_segment_last_td); @@ -116,8 +100,33 @@ public: int dmin = std::min(dx, dy); int dxy = abs(dx - dy); int d = dmin * YAPF_TILE_CORNER_LENGTH + (dxy - 1) * (YAPF_TILE_LENGTH / 2); - n.m_estimate = n.m_cost + d; - assert(n.m_estimate >= n.m_parent->m_estimate); + int estimate = n.m_cost + d; + assert(estimate >= n.m_parent->m_estimate); + return estimate; + } + + /** + * 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::m_estimate. + */ + inline bool PfCalcEstimate(Node &n) + { + if (PfDetectDestination(n)) { + n.m_estimate = n.m_cost; + return true; + } + + int shortest_estimate = std::numeric_limits::max(); + if (m_has_intermediate_dest) { + shortest_estimate = CalcEstimate(n, m_intermediate_dest_tile); + } else { + for (const TileIndex &destination_tile : m_destTiles) { + int estimate = CalcEstimate(n, destination_tile); + if (estimate < shortest_estimate) shortest_estimate = estimate; + } + } + + n.m_estimate = shortest_estimate; return true; } }; @@ -210,10 +219,10 @@ 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, const std::span dest_tiles, 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); + const std::vector high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1, dest_tiles); 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. */ @@ -228,7 +237,7 @@ public: /* Set origin and destination nodes */ pf.SetOrigin(v->tile, forward_dirs | reverse_dirs); - pf.SetDestination(v); + pf.SetDestination(v, dest_tiles); 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()); @@ -296,9 +305,10 @@ public: * Called when leaving depot. * @param v Ship. * @param trackdir [out] the best of all possible reversed trackdirs. + * @param dest_tiles List of destination tiles. * @return true if the reverse direction is better. */ - static bool CheckShipReverse(const Ship *v, Trackdir *trackdir) + static bool CheckShipReverse(const Ship *v, Trackdir *trackdir, const std::span dest_tiles) { bool path_found = false; ShipPathCache dummy_cache; @@ -309,13 +319,13 @@ 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, v->tile, forward_dirs, reverse_dirs, dest_tiles, 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, v->tile, TRACKDIR_BIT_NONE, reverse_dirs, dest_tiles, path_found, dummy_cache, best_origin_dir); *trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs); return true; } @@ -426,13 +436,15 @@ struct CYapfShip : CYapfT dest_tiles = GetShipDestinationTiles(v); 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, dest_tiles, path_found, path_cache, best_origin_dir); return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK; } bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir) { - return CYapfShip::CheckShipReverse(v, trackdir); + std::vector dest_tiles = GetShipDestinationTiles(v); + return CYapfShip::CheckShipReverse(v, trackdir, dest_tiles); } diff --git a/src/pathfinder/yapf/yapf_ship_regions.cpp b/src/pathfinder/yapf/yapf_ship_regions.cpp index fa54c2ec0e..719d137343 100644 --- a/src/pathfinder/yapf/yapf_ship_regions.cpp +++ b/src/pathfinder/yapf/yapf_ship_regions.cpp @@ -188,7 +188,7 @@ public: inline char TransportTypeChar() const { return '^'; } - static std::vector FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length) + static std::vector FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length, const std::span dest_tiles) { const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(start_tile); @@ -197,18 +197,7 @@ public: Tpf pf(std::min(static_cast(Map::Size() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES)); pf.SetDestination(start_water_region_patch); - if (v->current_order.IsType(OT_GOTO_STATION)) { - DestinationID station_id = v->current_order.GetDestination(); - const BaseStation *station = BaseStation::Get(station_id); - TileArea tile_area; - station->GetTileArea(&tile_area, STATION_DOCK); - for (const auto &tile : tile_area) { - if (IsDockingTile(tile) && IsShipDestinationTile(tile, station_id)) { - pf.AddOrigin(GetWaterRegionPatchInfo(tile)); - } - } - } else { - TileIndex tile = v->dest_tile; + for (const TileIndex &tile : dest_tiles) { pf.AddOrigin(GetWaterRegionPatchInfo(tile)); } @@ -305,9 +294,10 @@ struct CYapfRegionWater : CYapfT YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length) +std::vector YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length, const std::span dest_tiles) { - return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length); + return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length, dest_tiles); } diff --git a/src/pathfinder/yapf/yapf_ship_regions.h b/src/pathfinder/yapf/yapf_ship_regions.h index 8b75773cc8..a5023e6dc8 100644 --- a/src/pathfinder/yapf/yapf_ship_regions.h +++ b/src/pathfinder/yapf/yapf_ship_regions.h @@ -16,6 +16,6 @@ struct Ship; -std::vector YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length); +std::vector YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length, const std::span dest_tiles); #endif /* YAPF_SHIP_REGIONS_H */