diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h index 186986ce57..ee0d08ae7f 100644 --- a/src/pathfinder/yapf/yapf.h +++ b/src/pathfinder/yapf/yapf.h @@ -20,11 +20,10 @@ /** * Finds the best path for given ship using YAPF. * @param v the ship that needs to find a path - * @param tile the tile to find the path from (should be next tile the ship is about to enter) * @param path_found [out] Whether a path has been found (true) or has been guessed (false) - * @return the best trackdir for next turn or INVALID_TRACK if the path could not be found + * @return the best trackdir for next turn. This includes potential reverse directions. */ -Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache); +Trackdir YapfShipChooseTrack(const Ship *v, bool &path_found, ShipPathCache &path_cache); /** * Returns true if it is better to reverse the ship before leaving depot using YAPF. @@ -32,7 +31,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP * @param trackdir [out] the best of all possible reversed trackdirs * @return true if reversing is better */ -bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir); +bool YapfShipCheckReverse(const Ship *v, Trackdir &trackdir); /** * Finds the best path for given road vehicle using YAPF. diff --git a/src/pathfinder/yapf/yapf_common.hpp b/src/pathfinder/yapf/yapf_common.hpp index 58e78150a2..38dc21562c 100644 --- a/src/pathfinder/yapf/yapf_common.hpp +++ b/src/pathfinder/yapf/yapf_common.hpp @@ -25,7 +25,9 @@ public: protected: TileIndex origin_tile; ///< origin tile - TrackdirBits origin_trackdirs; ///< origin trackdir mask + TrackdirBits origin_forward_trackdirs; ///< trackdirs considered for forward motion + TrackdirBits origin_reverse_trackdirs; ///< trackdirs considered for reversal + int reverse_penalty; ///< Cost penalty added for the reverse directions /** to access inherited path finder */ inline Tpf &Yapf() @@ -35,21 +37,29 @@ protected: public: /** Set origin tile / trackdir mask */ - void SetOrigin(TileIndex tile, TrackdirBits trackdirs) + void SetOrigin(TileIndex tile, TrackdirBits forward_trackdirs, TrackdirBits reverse_trackdirs = TRACKDIR_BIT_NONE, int reverse_penalty = 0) { + assert((forward_trackdirs & reverse_trackdirs) == TRACKDIR_BIT_NONE); // Forward dirs can't also be reverse dirs and vice versa this->origin_tile = tile; - this->origin_trackdirs = trackdirs; + this->origin_forward_trackdirs = forward_trackdirs; + this->origin_reverse_trackdirs = reverse_trackdirs; + this->reverse_penalty = reverse_penalty; } /** Called when YAPF needs to place origin nodes into open list */ void PfSetStartupNodes() { - bool is_choice = (KillFirstBit(this->origin_trackdirs) != TRACKDIR_BIT_NONE); - for (TrackdirBits tdb = this->origin_trackdirs; tdb != TRACKDIR_BIT_NONE; tdb = KillFirstBit(tdb)) { - Trackdir td = (Trackdir)FindFirstBit(tdb); - Node &n1 = Yapf().CreateNewNode(); - n1.Set(nullptr, this->origin_tile, td, is_choice); - Yapf().AddStartupNode(n1); + const bool is_choice = CountBits(this->origin_forward_trackdirs | this->origin_reverse_trackdirs) > 1; + for (Trackdir td : SetTrackdirBitIterator(this->origin_forward_trackdirs)) { + Node &node = Yapf().CreateNewNode(); + node.Set(nullptr, this->origin_tile, td, is_choice); + Yapf().AddStartupNode(node); + } + for (Trackdir td : SetTrackdirBitIterator(this->origin_reverse_trackdirs)) { + Node &node = Yapf().CreateNewNode(); + node.Set(nullptr, this->origin_tile, td, is_choice); + node.cost = reverse_penalty; + Yapf().AddStartupNode(node); } } }; diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 4552c0141b..d9702fba98 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -20,7 +20,7 @@ constexpr int NUMBER_OR_WATER_REGIONS_LOOKAHEAD = 4; constexpr int MAX_SHIP_PF_NODES = (NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1) * WATER_REGION_NUMBER_OF_TILES * 4; // 4 possible exit dirs per tile. - +constexpr int SHIP_REVERSE_PENALTY = 10 * YAPF_TILE_LENGTH; constexpr int SHIP_LOST_PATH_LENGTH = 8; // The length of the (aimless) path assigned when a ship is lost. template @@ -178,7 +178,7 @@ public: return FindFirstTrackdir(trackdirs); } - /** Returns a random tile/trackdir that can be reached from the current tile/trackdir, or tile/INVALID_TRACK if none is available. */ + /** Returns a random tile/trackdir that can be reached from the current tile/trackdir, or tile/INVALID_TRACKDIR if none is available. */ static std::pair GetRandomFollowUpTileTrackdir(const Ship *v, TileIndex tile, Trackdir dir) { TrackFollower follower(v); @@ -198,10 +198,10 @@ public: for (int i = 0; i < path_length; ++i) { tile_dir = GetRandomFollowUpTileTrackdir(v, tile_dir.first, tile_dir.second); if (tile_dir.second == INVALID_TRACKDIR) break; - path_cache.push_back(tile_dir.second); + path_cache.emplace_back(tile_dir.second); } - if (path_cache.empty()) return INVALID_TRACKDIR; + if (path_cache.empty()) return ReverseTrackdir(v->GetVehicleTrackdir()); /* Reverse the path so we can take from the end. */ std::reverse(std::begin(path_cache), std::end(path_cache)); @@ -211,10 +211,15 @@ public: return result; } - static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs, - bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir) + static Trackdir ChooseShipTrack(const Ship *v, TrackdirBits forward_dirs, TrackdirBits reverse_dirs, + bool &path_found, ShipPathCache &path_cache) { - const std::vector high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1); + assert(HasBit(forward_dirs, v->GetVehicleTrackdir())); + assert(HasBit(reverse_dirs, ReverseTrackdir(v->GetVehicleTrackdir()))); + assert((forward_dirs & reverse_dirs) == TRACKDIR_BIT_NONE); + assert((TrackdirReachesTrackdirs(v->GetVehicleTrackdir()) & reverse_dirs) == TRACKDIR_BIT_NONE); + + const std::vector high_level_path = YapfShipFindWaterRegionPath(v, 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. */ @@ -228,7 +233,7 @@ public: Tpf pf(MAX_SHIP_PF_NODES); /* Set origin and destination nodes */ - pf.SetOrigin(v->tile, forward_dirs | reverse_dirs); + pf.SetOrigin(v->tile, forward_dirs, reverse_dirs, SHIP_REVERSE_PENALTY); 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()); @@ -249,17 +254,16 @@ public: * 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. */ const WaterRegionPatchDesc end_water_patch = GetWaterRegionPatchInfo(node->GetTile()); - assert(GetWaterRegionPatchInfo(tile) == high_level_path.front()); - const WaterRegionPatchDesc start_water_patch = high_level_path.front(); + assert(GetWaterRegionPatchInfo(v->tile) == high_level_path.front()); + const WaterRegionPatchDesc start_patch = high_level_path.front(); 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 add_full_path = !is_intermediate_destination && node_water_patch != end_water_patch; + const WaterRegionPatchDesc current_patch = GetWaterRegionPatchInfo(node->parent->GetTile()); + const bool current_patch_on_high_level_path = std::ranges::find(high_level_path, current_patch) != high_level_path.end(); + const bool add_full_path = !is_intermediate_destination && current_patch != end_water_patch; /* The cached path must always lead to a region patch that's on the high level path. * This is what can happen when that's not the case https://github.com/OpenTTD/OpenTTD/issues/12176. */ - if (add_full_path || !node_water_patch_on_high_level_path || node_water_patch == start_water_patch) { + if (add_full_path || !current_patch_on_high_level_path || current_patch == start_patch) { path_cache.push_back(node->GetTrackdir()); } else { path_cache.clear(); @@ -268,11 +272,10 @@ public: } assert(node->GetTile() == v->tile); - /* Return INVALID_TRACKDIR to trigger a ship reversal if that is the best option. */ - best_origin_dir = node->GetTrackdir(); - if ((TrackdirToTrackdirBits(best_origin_dir) & forward_dirs) == TRACKDIR_BIT_NONE) { + /* Return a reverse direction if that is the best option. */ + if (HasBit(reverse_dirs, node->GetTrackdir())) { path_cache.clear(); - return INVALID_TRACKDIR; + return node->GetTrackdir(); } /* A empty path means we are already at the destination. The pathfinder shouldn't have been called at all. @@ -284,42 +287,12 @@ public: path_cache.pop_back(); /* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */ - if (start_water_patch == end_water_patch) path_cache.clear(); + if (start_patch == end_water_patch) path_cache.clear(); return result; } - return INVALID_TRACKDIR; - } - - /** - * Check whether a ship should reverse to reach its destination. - * Called when leaving depot. - * @param v Ship. - * @param trackdir [out] the best of all possible reversed trackdirs. - * @return true if the reverse direction is better. - */ - static bool CheckShipReverse(const Ship *v, Trackdir *trackdir) - { - bool path_found = false; - ShipPathCache dummy_cache; - Trackdir best_origin_dir = INVALID_TRACKDIR; - - if (trackdir == nullptr) { - /* The normal case, typically called when ships leave a dock. */ - 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); - 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); - *trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs); - return true; - } + NOT_REACHED(); } }; @@ -424,16 +397,27 @@ struct CYapfShip : CYapfTmax_search_nodes = max_nodes; } }; -/** Ship controller helper - path finder invoker. */ -Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache) +Trackdir YapfShipChooseTrack(const Ship *v, bool &path_found, ShipPathCache &path_cache) { - 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); - return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK; + /* This is always called when a ship is about to exit a tile, hence we add up to three reverse directions. */ + const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir()); + const TrackdirBits all_water_dirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0)); + const TrackdirBits reverse_dirs = all_water_dirs & DiagdirReachesTrackdirs(ReverseDiagDir(TrackdirToExitdir(v->GetVehicleTrackdir()))); + + const Trackdir trackdir = CYapfShip::ChooseShipTrack(v, forward_dirs, reverse_dirs, path_found, path_cache); + assert(trackdir != INVALID_TRACKDIR); + return trackdir; } -bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir) +bool YapfShipCheckReverse(const Ship *v, Trackdir &trackdir) { - return CYapfShip::CheckShipReverse(v, trackdir); + /* It is not clear where the ship is within its current track, so we only add the forward (current) and reverse direction. */ + const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir()); + const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(ReverseTrackdir(v->GetVehicleTrackdir())); + + bool path_found = false; + ShipPathCache dummy_cache; + trackdir = CYapfShip::ChooseShipTrack(v, forward_dirs, reverse_dirs, path_found, dummy_cache); + assert(trackdir != INVALID_TRACKDIR); + return path_found && HasBit(reverse_dirs, trackdir); } diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 68970d58b5..bf552504ef 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -367,7 +367,7 @@ static Vehicle *EnsureNoMovingShipProc(Vehicle *v, void *) return v->type == VEH_SHIP && v->cur_speed != 0 ? v : nullptr; } -static bool CheckReverseShip(const Ship *v, Trackdir *trackdir = nullptr) +static bool CheckReverseShip(const Ship *v, Trackdir &trackdir) { /* Ask pathfinder for best direction */ return YapfShipCheckReverse(v, trackdir); @@ -405,7 +405,8 @@ static bool CheckShipLeaveDepot(Ship *v) TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour); TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour); if (north_tracks && south_tracks) { - if (CheckReverseShip(v)) north_tracks = TRACK_BIT_NONE; + Trackdir dummy_trackdir; + if (CheckReverseShip(v, dummy_trackdir)) north_tracks = TRACK_BIT_NONE; } if (north_tracks) { @@ -487,54 +488,39 @@ static void ShipArrivesAt(const Vehicle *v, Station *st) * Runs the pathfinder to choose a track to continue along. * * @param v Ship to navigate - * @param tile Tile, the ship is about to enter - * @param tracks Available track choices on \a tile - * @return Track to choose, or INVALID_TRACK when to reverse. + * @param trackdirs Available trackdir choices on the vehicle's type + * @return Trackdir to choose */ -static Track ChooseShipTrack(Ship *v, TileIndex tile, TrackBits tracks) +static Trackdir ChooseShipTrack(Ship *v, TrackdirBits trackdirs) { bool path_found = true; - Track track; + Trackdir trackdir; if (v->dest_tile == 0) { /* No destination, don't invoke pathfinder. */ - track = TrackBitsToTrack(v->state); - if (!IsDiagonalTrack(track)) track = TrackToOppositeTrack(track); - if (!HasBit(tracks, track)) track = FindFirstTrack(tracks); + trackdir = FindFirstTrackdir(trackdirs); + if (trackdir == INVALID_TRACKDIR) trackdir = ReverseTrackdir(v->GetVehicleTrackdir()); path_found = false; } else { /* Attempt to follow cached path. */ if (!v->path.empty()) { - track = TrackdirToTrack(v->path.back().trackdir); + trackdir = v->path.back().trackdir; - if (HasBit(tracks, track)) { + if (HasBit(trackdirs, trackdir)) { v->path.pop_back(); /* HandlePathfindResult() is not called here because this is not a new pathfinder result. */ - return track; + return trackdir; } /* Cached path is invalid so continue with pathfinder. */ v->path.clear(); } - track = YapfShipChooseTrack(v, tile, path_found, v->path); + trackdir = YapfShipChooseTrack(v, path_found, v->path); } v->HandlePathfindingResult(path_found); - return track; -} - -/** - * Get the available water tracks on a tile for a ship entering a tile. - * @param tile The tile about to enter. - * @param dir The entry direction. - * @return The available trackbits on the next tile. - */ -static inline TrackBits GetAvailShipTracks(TileIndex tile, DiagDirection dir) -{ - TrackBits tracks = GetTileShipTrackStatus(tile) & DiagdirReachesTracks(dir); - - return tracks; + return trackdir; } /** Structure for ship sub-coordinate data for moving into a new tile via a Diagdir onto a Track. */ @@ -704,7 +690,8 @@ static void ShipController(Ship *v) if (v->vehstatus.Test(VehState::Stopped)) return; - if (ProcessOrders(v) && CheckReverseShip(v)) return ReverseShip(v); + Trackdir best_reverse_dir = INVALID_TRACKDIR; + if (ProcessOrders(v) && CheckReverseShip(v, best_reverse_dir)) return ReverseShipIntoTrackdir(v, best_reverse_dir); v->HandleLoading(); @@ -792,19 +779,14 @@ static void ShipController(Ship *v) const DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); assert(diagdir != INVALID_DIAGDIR); - const TrackBits tracks = GetAvailShipTracks(gp.new_tile, diagdir); - if (tracks == TRACK_BIT_NONE) { - Trackdir trackdir = INVALID_TRACKDIR; - CheckReverseShip(v, &trackdir); - if (trackdir == INVALID_TRACKDIR) return ReverseShip(v); - return ReverseShipIntoTrackdir(v, trackdir); - } + const TrackdirBits forward_trackdirs = TrackBitsToTrackdirBits(GetTileShipTrackStatus(gp.new_tile)) & DiagdirReachesTrackdirs(diagdir); - /* Choose a direction, and continue if we find one */ - const Track track = ChooseShipTrack(v, gp.new_tile, tracks); - if (track == INVALID_TRACK) return ReverseShip(v); + /* Choose a direction, which might require a reversal */ + const Trackdir new_trackdir = ChooseShipTrack(v, forward_trackdirs); + assert(new_trackdir != INVALID_TRACKDIR); + if (!HasBit(forward_trackdirs, new_trackdir)) return ReverseShipIntoTrackdir(v, new_trackdir); - const ShipSubcoordData &b = _ship_subcoord[diagdir][track]; + const ShipSubcoordData &b = _ship_subcoord[diagdir][TrackdirToTrack(new_trackdir)]; gp.x = (gp.x & ~0xF) | b.x_subcoord; gp.y = (gp.y & ~0xF) | b.y_subcoord; @@ -815,7 +797,7 @@ static void ShipController(Ship *v) if (!HasBit(r, VETS_ENTERED_WORMHOLE)) { v->tile = gp.new_tile; - v->state = TrackToTrackBits(track); + v->state = TrackToTrackBits(TrackdirToTrack(new_trackdir)); /* Update ship cache when the water class changes. Aqueducts are always canals. */ if (GetEffectiveWaterClass(gp.old_tile) != GetEffectiveWaterClass(gp.new_tile)) v->UpdateCache();