2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file yapf_ship.cpp Implementation of YAPF for ships. */
10 #include "../../stdafx.h"
11 #include "../../ship.h"
12 #include "../../industry.h"
13 #include "../../vehicle_func.h"
16 #include "yapf_node_ship.hpp"
17 #include "yapf_ship_regions.h"
18 #include "../water_regions.h"
20 #include "../../safeguards.h"
22 constexpr int NUMBER_OR_WATER_REGIONS_LOOKAHEAD
= 4;
23 constexpr int MAX_SHIP_PF_NODES
= (NUMBER_OR_WATER_REGIONS_LOOKAHEAD
+ 1) * WATER_REGION_NUMBER_OF_TILES
* 4; // 4 possible exit dirs per tile.
25 constexpr int SHIP_LOST_PATH_LENGTH
= 8; // The length of the (aimless) path assigned when a ship is lost.
27 template <class Types
>
28 class CYapfDestinationTileWaterT
31 typedef typename
Types::Tpf Tpf
; ///< the pathfinder class (derived from THIS class).
32 typedef typename
Types::TrackFollower TrackFollower
;
33 typedef typename
Types::NodeList::Titem Node
; ///< this will be our node type.
34 typedef typename
Node::Key Key
; ///< key to hash tables.
38 TrackdirBits m_destTrackdirs
;
39 StationID m_destStation
;
41 bool m_has_intermediate_dest
= false;
42 TileIndex m_intermediate_dest_tile
;
43 WaterRegionPatchDesc m_intermediate_dest_region_patch
;
46 void SetDestination(const Ship
*v
)
48 if (v
->current_order
.IsType(OT_GOTO_STATION
)) {
49 m_destStation
= v
->current_order
.GetDestination();
50 m_destTile
= CalcClosestStationTile(m_destStation
, v
->tile
, STATION_DOCK
);
51 m_destTrackdirs
= INVALID_TRACKDIR_BIT
;
53 m_destStation
= INVALID_STATION
;
54 m_destTile
= v
->dest_tile
;
55 m_destTrackdirs
= TrackStatusToTrackdirBits(GetTileTrackStatus(v
->dest_tile
, TRANSPORT_WATER
, 0));
59 void SetIntermediateDestination(const WaterRegionPatchDesc
&water_region_patch
)
61 m_has_intermediate_dest
= true;
62 m_intermediate_dest_tile
= GetWaterRegionCenterTile(water_region_patch
);
63 m_intermediate_dest_region_patch
= water_region_patch
;
67 /** To access inherited path finder. */
70 return *static_cast<Tpf
*>(this);
74 /** Called by YAPF to detect if node ends in the desired destination. */
75 inline bool PfDetectDestination(Node
&n
)
77 return PfDetectDestinationTile(n
.m_segment_last_tile
, n
.m_segment_last_td
);
80 inline bool PfDetectDestinationTile(TileIndex tile
, Trackdir trackdir
)
82 if (m_has_intermediate_dest
) {
83 /* GetWaterRegionInfo is much faster than GetWaterRegionPatchInfo so we try that first. */
84 if (GetWaterRegionInfo(tile
) != m_intermediate_dest_region_patch
) return false;
85 return GetWaterRegionPatchInfo(tile
) == m_intermediate_dest_region_patch
;
88 if (m_destStation
!= INVALID_STATION
) return IsDockingTile(tile
) && IsShipDestinationTile(tile
, m_destStation
);
90 return tile
== m_destTile
&& ((m_destTrackdirs
& TrackdirToTrackdirBits(trackdir
)) != TRACKDIR_BIT_NONE
);
94 * Called by YAPF to calculate cost estimate. Calculates distance to the destination
95 * adds it to the actual cost from origin and stores the sum to the Node::m_estimate.
97 inline bool PfCalcEstimate(Node
&n
)
99 const TileIndex destination_tile
= m_has_intermediate_dest
? m_intermediate_dest_tile
: m_destTile
;
101 static const int dg_dir_to_x_offs
[] = { -1, 0, 1, 0 };
102 static const int dg_dir_to_y_offs
[] = { 0, 1, 0, -1 };
103 if (PfDetectDestination(n
)) {
104 n
.m_estimate
= n
.m_cost
;
108 TileIndex tile
= n
.m_segment_last_tile
;
109 DiagDirection exitdir
= TrackdirToExitdir(n
.m_segment_last_td
);
110 int x1
= 2 * TileX(tile
) + dg_dir_to_x_offs
[(int)exitdir
];
111 int y1
= 2 * TileY(tile
) + dg_dir_to_y_offs
[(int)exitdir
];
112 int x2
= 2 * TileX(destination_tile
);
113 int y2
= 2 * TileY(destination_tile
);
114 int dx
= abs(x1
- x2
);
115 int dy
= abs(y1
- y2
);
116 int dmin
= std::min(dx
, dy
);
117 int dxy
= abs(dx
- dy
);
118 int d
= dmin
* YAPF_TILE_CORNER_LENGTH
+ (dxy
- 1) * (YAPF_TILE_LENGTH
/ 2);
119 n
.m_estimate
= n
.m_cost
+ d
;
120 assert(n
.m_estimate
>= n
.m_parent
->m_estimate
);
125 /** Node Follower module of YAPF for ships */
126 template <class Types
>
127 class CYapfFollowShipT
130 typedef typename
Types::Tpf Tpf
; ///< the pathfinder class (derived from THIS class).
131 typedef typename
Types::TrackFollower TrackFollower
;
132 typedef typename
Types::NodeList::Titem Node
; ///< this will be our node type.
133 typedef typename
Node::Key Key
; ///< key to hash tables.
136 /** to access inherited path finder */
139 return *static_cast<Tpf
*>(this);
142 std::vector
<WaterRegionDesc
> m_water_region_corridor
;
146 * Called by YAPF to move from the given node to the next tile. For each
147 * reachable trackdir on the new tile creates new node, initializes it
148 * and adds it to the open list by calling Yapf().AddNewNode(n)
150 inline void PfFollowNode(Node
&old_node
)
152 TrackFollower
F(Yapf().GetVehicle());
153 if (F
.Follow(old_node
.m_key
.m_tile
, old_node
.m_key
.m_td
)) {
154 if (m_water_region_corridor
.empty()
155 || std::find(m_water_region_corridor
.begin(), m_water_region_corridor
.end(),
156 GetWaterRegionInfo(F
.m_new_tile
)) != m_water_region_corridor
.end()) {
157 Yapf().AddMultipleNodes(&old_node
, F
);
162 /** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */
163 inline void RestrictSearch(const std::vector
<WaterRegionPatchDesc
> &path
)
165 m_water_region_corridor
.clear();
166 for (const WaterRegionPatchDesc
&path_entry
: path
) m_water_region_corridor
.push_back(path_entry
);
169 /** Return debug report character to identify the transportation type. */
170 inline char TransportTypeChar() const
175 /** Returns a random trackdir out of a set of trackdirs. */
176 static Trackdir
GetRandomTrackdir(TrackdirBits trackdirs
)
178 const int strip_amount
= RandomRange(CountBits(trackdirs
));
179 for (int s
= 0; s
< strip_amount
; ++s
) RemoveFirstTrackdir(&trackdirs
);
180 return FindFirstTrackdir(trackdirs
);
183 /** Returns a random tile/trackdir that can be reached from the current tile/trackdir, or tile/INVALID_TRACK if none is available. */
184 static std::pair
<TileIndex
, Trackdir
> GetRandomFollowUpTileTrackdir(const Ship
*v
, TileIndex tile
, Trackdir dir
)
186 TrackFollower
follower(v
);
187 if (follower
.Follow(tile
, dir
)) {
188 TrackdirBits dirs
= follower
.m_new_td_bits
;
189 const TrackdirBits dirs_without_90_degree
= dirs
& ~TrackdirCrossesTrackdirs(dir
);
190 if (dirs_without_90_degree
!= TRACKDIR_BIT_NONE
) dirs
= dirs_without_90_degree
;
191 return { follower
.m_new_tile
, GetRandomTrackdir(dirs
) };
193 return { follower
.m_new_tile
, INVALID_TRACKDIR
};
196 /** Creates a random path, avoids 90 degree turns. */
197 static Trackdir
CreateRandomPath(const Ship
*v
, ShipPathCache
&path_cache
, int path_length
)
199 std::pair
<TileIndex
, Trackdir
> tile_dir
= { v
->tile
, v
->GetVehicleTrackdir()};
200 for (int i
= 0; i
< path_length
; ++i
) {
201 tile_dir
= GetRandomFollowUpTileTrackdir(v
, tile_dir
.first
, tile_dir
.second
);
202 if (tile_dir
.second
== INVALID_TRACKDIR
) break;
203 path_cache
.push_back(tile_dir
.second
);
206 if (path_cache
.empty()) return INVALID_TRACKDIR
;
208 const Trackdir result
= path_cache
.front();
209 path_cache
.pop_front();
213 static Trackdir
ChooseShipTrack(const Ship
*v
, TileIndex tile
, TrackdirBits forward_dirs
, TrackdirBits reverse_dirs
,
214 bool &path_found
, ShipPathCache
&path_cache
, Trackdir
&best_origin_dir
)
216 const std::vector
<WaterRegionPatchDesc
> high_level_path
= YapfShipFindWaterRegionPath(v
, tile
, NUMBER_OR_WATER_REGIONS_LOOKAHEAD
+ 1);
217 if (high_level_path
.empty()) {
219 /* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
220 return CreateRandomPath(v
, path_cache
, SHIP_LOST_PATH_LENGTH
);
223 /* Try one time without restricting the search area, which generally results in better and more natural looking paths.
224 * However the pathfinder can hit the node limit in certain situations such as long aqueducts or maze-like terrain.
225 * If that happens we run the pathfinder again, but restricted only to the regions provided by the region pathfinder. */
226 for (int attempt
= 0; attempt
< 2; ++attempt
) {
227 Tpf
pf(MAX_SHIP_PF_NODES
);
229 /* Set origin and destination nodes */
230 pf
.SetOrigin(v
->tile
, forward_dirs
| reverse_dirs
);
231 pf
.SetDestination(v
);
232 const bool is_intermediate_destination
= static_cast<int>(high_level_path
.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD
+ 1;
233 if (is_intermediate_destination
) pf
.SetIntermediateDestination(high_level_path
.back());
235 /* Restrict the search area to prevent the low level pathfinder from expanding too many nodes. This can happen
236 * when the terrain is very "maze-like" or when the high level path "teleports" via a very long aqueduct. */
237 if (attempt
> 0) pf
.RestrictSearch(high_level_path
);
239 /* Find best path. */
240 path_found
= pf
.FindPath(v
);
241 Node
*node
= pf
.GetBestNode();
242 if (attempt
== 0 && !path_found
) continue; // Try again with restricted search area.
244 /* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
245 if (!path_found
) return CreateRandomPath(v
, path_cache
, SHIP_LOST_PATH_LENGTH
);
247 /* Return only the path within the current water region if an intermediate destination was returned. If not, cache the entire path
248 * to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
249 * caching the full path the ship can get stuck in a loop. */
250 const WaterRegionPatchDesc end_water_patch
= GetWaterRegionPatchInfo(node
->GetTile());
251 assert(GetWaterRegionPatchInfo(tile
) == high_level_path
.front());
252 const WaterRegionPatchDesc start_water_patch
= high_level_path
.front();
253 while (node
->m_parent
) {
254 const WaterRegionPatchDesc node_water_patch
= GetWaterRegionPatchInfo(node
->GetTile());
256 const bool node_water_patch_on_high_level_path
= std::find(high_level_path
.begin(), high_level_path
.end(), node_water_patch
) != high_level_path
.end();
257 const bool add_full_path
= !is_intermediate_destination
&& node_water_patch
!= end_water_patch
;
259 /* The cached path must always lead to a region patch that's on the high level path.
260 * This is what can happen when that's not the case https://github.com/OpenTTD/OpenTTD/issues/12176. */
261 if (add_full_path
|| !node_water_patch_on_high_level_path
|| node_water_patch
== start_water_patch
) {
262 path_cache
.push_front(node
->GetTrackdir());
266 node
= node
->m_parent
;
268 assert(node
->GetTile() == v
->tile
);
270 /* Return INVALID_TRACKDIR to trigger a ship reversal if that is the best option. */
271 best_origin_dir
= node
->GetTrackdir();
272 if ((TrackdirToTrackdirBits(best_origin_dir
) & forward_dirs
) == TRACKDIR_BIT_NONE
) {
274 return INVALID_TRACKDIR
;
277 /* A empty path means we are already at the destination. The pathfinder shouldn't have been called at all.
278 * Return a random reachable trackdir to hopefully nudge the ship out of this strange situation. */
279 if (path_cache
.empty()) return CreateRandomPath(v
, path_cache
, 1);
281 /* Take out the last trackdir as the result. */
282 const Trackdir result
= path_cache
.front();
283 path_cache
.pop_front();
285 /* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */
286 if (start_water_patch
== end_water_patch
) path_cache
.clear();
291 return INVALID_TRACKDIR
;
295 * Check whether a ship should reverse to reach its destination.
296 * Called when leaving depot.
298 * @param trackdir [out] the best of all possible reversed trackdirs.
299 * @return true if the reverse direction is better.
301 static bool CheckShipReverse(const Ship
*v
, Trackdir
*trackdir
)
303 bool path_found
= false;
304 ShipPathCache dummy_cache
;
305 Trackdir best_origin_dir
= INVALID_TRACKDIR
;
307 if (trackdir
== nullptr) {
308 /* The normal case, typically called when ships leave a dock. */
309 const Trackdir reverse_dir
= ReverseTrackdir(v
->GetVehicleTrackdir());
310 const TrackdirBits forward_dirs
= TrackdirToTrackdirBits(v
->GetVehicleTrackdir());
311 const TrackdirBits reverse_dirs
= TrackdirToTrackdirBits(reverse_dir
);
312 (void)ChooseShipTrack(v
, v
->tile
, forward_dirs
, reverse_dirs
, path_found
, dummy_cache
, best_origin_dir
);
313 return path_found
&& best_origin_dir
== reverse_dir
;
315 /* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */
316 const DiagDirection entry
= ReverseDiagDir(VehicleExitDir(v
->direction
, v
->state
));
317 const TrackdirBits reverse_dirs
= DiagdirReachesTrackdirs(entry
) & TrackStatusToTrackdirBits(GetTileTrackStatus(v
->tile
, TRANSPORT_WATER
, 0, entry
));
318 (void)ChooseShipTrack(v
, v
->tile
, TRACKDIR_BIT_NONE
, reverse_dirs
, path_found
, dummy_cache
, best_origin_dir
);
319 *trackdir
= path_found
&& best_origin_dir
!= INVALID_TRACKDIR
? best_origin_dir
: GetRandomTrackdir(reverse_dirs
);
325 /** Cost Provider module of YAPF for ships. */
326 template <class Types
>
330 typedef typename
Types::Tpf Tpf
; ///< the pathfinder class (derived from THIS class).
331 typedef typename
Types::TrackFollower TrackFollower
;
332 typedef typename
Types::NodeList::Titem Node
; ///< this will be our node type.
333 typedef typename
Node::Key Key
; ///< key to hash tables.
335 /** to access inherited path finder */
338 return *static_cast<Tpf
*>(this);
342 inline int CurveCost(Trackdir td1
, Trackdir td2
)
344 assert(IsValidTrackdir(td1
));
345 assert(IsValidTrackdir(td2
));
347 if (HasTrackdir(TrackdirCrossesTrackdirs(td1
), td2
)) {
348 /* 90-deg curve penalty. */
349 return Yapf().PfGetSettings().ship_curve90_penalty
;
350 } else if (td2
!= NextTrackdir(td1
)) {
351 /* 45-deg curve penalty. */
352 return Yapf().PfGetSettings().ship_curve45_penalty
;
357 static Vehicle
*CountShipProc(Vehicle
*v
, void *data
)
359 uint
*count
= (uint
*)data
;
360 /* Ignore other vehicles (aircraft) and ships inside depot. */
361 if (v
->type
== VEH_SHIP
&& (v
->vehstatus
& VS_HIDDEN
) == 0) (*count
)++;
367 * Called by YAPF to calculate the cost from the origin to the given node.
368 * Calculates only the cost of given node, adds it to the parent node cost
369 * and stores the result into Node::m_cost member.
371 inline bool PfCalcCost(Node
&n
, const TrackFollower
*tf
)
373 /* Base tile cost depending on distance. */
374 int c
= IsDiagonalTrackdir(n
.GetTrackdir()) ? YAPF_TILE_LENGTH
: YAPF_TILE_CORNER_LENGTH
;
375 /* Additional penalty for curves. */
376 c
+= CurveCost(n
.m_parent
->GetTrackdir(), n
.GetTrackdir());
378 if (IsDockingTile(n
.GetTile())) {
379 /* Check docking tile for occupancy. */
381 HasVehicleOnPos(n
.GetTile(), &count
, &CountShipProc
);
382 c
+= count
* 3 * YAPF_TILE_LENGTH
;
385 /* Skipped tile cost for aqueducts. */
386 c
+= YAPF_TILE_LENGTH
* tf
->m_tiles_skipped
;
388 /* Ocean/canal speed penalty. */
389 const ShipVehicleInfo
*svi
= ShipVehInfo(Yapf().GetVehicle()->engine_type
);
390 uint8_t speed_frac
= (GetEffectiveWaterClass(n
.GetTile()) == WATER_CLASS_SEA
) ? svi
->ocean_speed_frac
: svi
->canal_speed_frac
;
391 if (speed_frac
> 0) c
+= YAPF_TILE_LENGTH
* (1 + tf
->m_tiles_skipped
) * speed_frac
/ (256 - speed_frac
);
394 n
.m_cost
= n
.m_parent
->m_cost
+ c
;
400 * Config struct of YAPF for ships.
401 * Defines all 6 base YAPF modules as classes providing services for CYapfBaseT.
403 template <class Tpf_
, class Ttrack_follower
, class Tnode_list
>
404 struct CYapfShip_TypesT
406 typedef CYapfShip_TypesT
<Tpf_
, Ttrack_follower
, Tnode_list
> Types
; ///< Shortcut for this struct type.
407 typedef Tpf_ Tpf
; ///< Pathfinder type.
408 typedef Ttrack_follower TrackFollower
; ///< Track follower helper class.
409 typedef Tnode_list NodeList
;
410 typedef Ship VehicleType
;
412 /** Pathfinder components (modules). */
413 typedef CYapfBaseT
<Types
> PfBase
; ///< Base pathfinder class.
414 typedef CYapfFollowShipT
<Types
> PfFollow
; ///< Node follower.
415 typedef CYapfOriginTileT
<Types
> PfOrigin
; ///< Origin provider.
416 typedef CYapfDestinationTileWaterT
<Types
> PfDestination
; ///< Destination/distance provider.
417 typedef CYapfSegmentCostCacheNoneT
<Types
> PfCache
; ///< Segment cost cache provider.
418 typedef CYapfCostShipT
<Types
> PfCost
; ///< Cost provider.
421 struct CYapfShip
: CYapfT
<CYapfShip_TypesT
<CYapfShip
, CFollowTrackWater
, CShipNodeListExitDir
> >
423 explicit CYapfShip(int max_nodes
) { m_max_search_nodes
= max_nodes
; }
426 /** Ship controller helper - path finder invoker. */
427 Track
YapfShipChooseTrack(const Ship
*v
, TileIndex tile
, bool &path_found
, ShipPathCache
&path_cache
)
429 Trackdir best_origin_dir
= INVALID_TRACKDIR
;
430 const TrackdirBits origin_dirs
= TrackdirToTrackdirBits(v
->GetVehicleTrackdir());
431 const Trackdir td_ret
= CYapfShip::ChooseShipTrack(v
, tile
, origin_dirs
, TRACKDIR_BIT_NONE
, path_found
, path_cache
, best_origin_dir
);
432 return (td_ret
!= INVALID_TRACKDIR
) ? TrackdirToTrack(td_ret
) : INVALID_TRACK
;
435 bool YapfShipCheckReverse(const Ship
*v
, Trackdir
*trackdir
)
437 return CYapfShip::CheckShipReverse(v
, trackdir
);