4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file pbs.cpp PBS support routines */
13 #include "viewport_func.h"
14 #include "vehicle_func.h"
15 #include "newgrf_station.h"
16 #include "pathfinder/follow_track.hpp"
17 #include "tracerestrict.h"
18 #include "logic_signals.h"
20 #include "safeguards.h"
23 * Get the reserved trackbits for any tile, regardless of type.///
25 * @return the reserved trackbits. TRACK_BIT_NONE on nothing reserved or
26 * a tile without rail.
28 TrackBits
GetReservedTrackbits(TileIndex t
)
30 switch (GetTileType(t
)) {
32 if (IsRailDepot(t
)) return GetDepotReservationTrackBits(t
);
33 if (IsPlainRail(t
)) return GetRailReservationTrackBits(t
);
37 if (IsLevelCrossing(t
)) return GetCrossingReservationTrackBits(t
);
41 if (HasStationRail(t
)) return GetStationReservationTrackBits(t
);
45 if (GetTunnelBridgeTransportType(t
) == TRANSPORT_RAIL
) return GetTunnelBridgeReservationTrackBits(t
);
51 return TRACK_BIT_NONE
;
55 * Set the reservation for a complete station platform.
56 * @pre IsRailStationTile(start)
57 * @param start starting tile of the platform
58 * @param dir the direction in which to follow the platform
59 * @param b the state the reservation should be set to
61 void SetRailStationPlatformReservation(TileIndex start
, DiagDirection dir
, bool b
)
63 TileIndex tile
= start
;
64 TileIndexDiff diff
= TileOffsByDiagDir(dir
);
66 assert(IsRailStationTile(start
));
67 assert(GetRailStationAxis(start
) == DiagDirToAxis(dir
));
70 SetRailStationReservation(tile
, b
);
71 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
72 tile
= TILE_ADD(tile
, diff
);
73 } while (IsCompatibleTrainStationTile(tile
, start
));
77 * Try to reserve a specific track on a tile
78 * This also sets PBS signals to green if reserving through the facing track direction
79 * @param tile the tile
81 * @param trigger_stations whether to call station randomisation trigger
82 * @return \c true if reservation was successful, i.e. the track was
83 * free and didn't cross any other reserved tracks.
85 bool TryReserveRailTrackdir(TileIndex tile
, Trackdir td
, bool trigger_stations
)
87 bool success
= TryReserveRailTrack(tile
, TrackdirToTrack(td
), trigger_stations
);
88 if (success
&& HasPbsSignalOnTrackdir(tile
, td
)) {
89 SetSignalStateByTrackdir(tile
, td
, SIGNAL_STATE_GREEN
);
90 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
92 /* notify logic signals of the state change */
93 SignalStateChanged(tile
, TrackdirToTrack(td
), 1);
99 * Try to reserve a specific track on a tile
100 * @param tile the tile
102 * @param trigger_stations whether to call station randomisation trigger
103 * @return \c true if reservation was successful, i.e. the track was
104 * free and didn't cross any other reserved tracks.
106 bool TryReserveRailTrack(TileIndex tile
, Track t
, bool trigger_stations
)
108 assert((GetTileTrackStatus(tile
, TRANSPORT_RAIL
, 0) & TrackToTrackBits(t
)) != 0);
110 if (_settings_client
.gui
.show_track_reservation
) {
111 /* show the reserved rail if needed */
112 if (IsBridgeTile(tile
)) {
113 MarkBridgeDirty(tile
);
115 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
119 switch (GetTileType(tile
)) {
121 if (IsPlainRail(tile
)) return TryReserveTrack(tile
, t
);
122 if (IsRailDepot(tile
)) {
123 if (!HasDepotReservation(tile
)) {
124 SetDepotReservation(tile
, true);
125 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
); // some GRFs change their appearance when tile is reserved
132 if (IsLevelCrossing(tile
) && !HasCrossingReservation(tile
)) {
133 SetCrossingReservation(tile
, true);
134 UpdateLevelCrossing(tile
, false);
140 if (HasStationRail(tile
) && !HasStationReservation(tile
)) {
141 SetRailStationReservation(tile
, true);
142 if (trigger_stations
&& IsRailStation(tile
)) TriggerStationRandomisation(nullptr, tile
, SRT_PATH_RESERVATION
);
143 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
); // some GRFs need redraw after reserving track
148 case MP_TUNNELBRIDGE
:
149 if (GetTunnelBridgeTransportType(tile
) == TRANSPORT_RAIL
&& !GetTunnelBridgeReservationTrackBits(tile
)) {
150 SetTunnelBridgeReservation(tile
, true);
151 if (IsTunnelBridgeSignalSimulationExit(tile
) && IsTunnelBridgePBS(tile
)) SetTunnelBridgeSignalState(tile
, SIGNAL_STATE_GREEN
);
152 MarkTileDirtyByTile(tile
);
164 * Lift the reservation of a specific trackdir on a tile
165 * This also sets PBS signals to red if unreserving through the facing track direction
166 * @param tile the tile
169 void UnreserveRailTrackdir(TileIndex tile
, Trackdir td
)
171 if (HasPbsSignalOnTrackdir(tile
, td
)) {
172 SetSignalStateByTrackdir(tile
, td
, SIGNAL_STATE_RED
);
173 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
175 /* notify logic signals of the state change */
176 SignalStateChanged(tile
, TrackdirToTrack(td
), 1);
178 UnreserveRailTrack(tile
, TrackdirToTrack(td
));
182 * Lift the reservation of a specific track on a tile
183 * @param tile the tile
186 void UnreserveRailTrack(TileIndex tile
, Track t
)
188 assert((GetTileTrackStatus(tile
, TRANSPORT_RAIL
, 0) & TrackToTrackBits(t
)) != 0);
190 if (_settings_client
.gui
.show_track_reservation
) {
191 if (IsBridgeTile(tile
)) {
192 MarkBridgeDirty(tile
);
194 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
198 switch (GetTileType(tile
)) {
200 if (IsRailDepot(tile
)) {
201 SetDepotReservation(tile
, false);
202 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
205 if (IsPlainRail(tile
)) UnreserveTrack(tile
, t
);
209 if (IsLevelCrossing(tile
)) {
210 SetCrossingReservation(tile
, false);
211 UpdateLevelCrossing(tile
);
216 if (HasStationRail(tile
)) {
217 SetRailStationReservation(tile
, false);
218 MarkTileDirtyByTile(tile
, ZOOM_LVL_DRAW_MAP
);
222 case MP_TUNNELBRIDGE
:
223 if (GetTunnelBridgeTransportType(tile
) == TRANSPORT_RAIL
) {
224 SetTunnelBridgeReservation(tile
, false);
225 if (IsTunnelBridgeSignalSimulationExit(tile
) && IsTunnelBridgePBS(tile
)) SetTunnelBridgeSignalState(tile
, SIGNAL_STATE_RED
);
226 MarkTileDirtyByTile(tile
);
236 /** Follow a reservation starting from a specific tile to the end. */
237 static PBSTileInfo
FollowReservation(Owner o
, RailTypes rts
, TileIndex tile
, Trackdir trackdir
, bool ignore_oneway
= false)
239 TileIndex start_tile
= tile
;
240 Trackdir start_trackdir
= trackdir
;
241 bool first_loop
= true;
243 /* Start track not reserved? This can happen if two trains
244 * are on the same tile. The reservation on the next tile
245 * is not ours in this case, so exit. */
246 if (!HasReservedTracks(tile
, TrackToTrackBits(TrackdirToTrack(trackdir
)))) return PBSTileInfo(tile
, trackdir
, false);
248 /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */
249 CFollowTrackRail
ft(o
, rts
);
250 while (ft
.Follow(tile
, trackdir
)) {
251 TrackdirBits reserved
= ft
.m_new_td_bits
& TrackBitsToTrackdirBits(GetReservedTrackbits(ft
.m_new_tile
));
253 /* No reservation --> path end found */
254 if (reserved
== TRACKDIR_BIT_NONE
) {
255 if (ft
.m_is_station
) {
256 /* Check skipped station tiles as well, maybe our reservation ends inside the station. */
257 TileIndexDiff diff
= TileOffsByDiagDir(ft
.m_exitdir
);
258 while (ft
.m_tiles_skipped
-- > 0) {
259 ft
.m_new_tile
-= diff
;
260 if (HasStationReservation(ft
.m_new_tile
)) {
261 tile
= ft
.m_new_tile
;
262 trackdir
= DiagDirToDiagTrackdir(ft
.m_exitdir
);
270 /* Can't have more than one reserved trackdir */
271 Trackdir new_trackdir
= FindFirstTrackdir(reserved
);
273 /* One-way signal against us. The reservation can't be ours as it is not
274 * a safe position from our direction and we can never pass the signal. */
275 if (!ignore_oneway
&& HasOnewaySignalBlockingTrackdir(ft
.m_new_tile
, new_trackdir
)) break;
277 tile
= ft
.m_new_tile
;
278 trackdir
= new_trackdir
;
281 /* Update the start tile after we followed the track the first
282 * time. This is necessary because the track follower can skip
283 * tiles (in stations for example) which means that we might
284 * never visit our original starting tile again. */
286 start_trackdir
= trackdir
;
289 /* Loop encountered? */
290 if (tile
== start_tile
&& trackdir
== start_trackdir
) break;
292 /* Depot tile? Can't continue. */
293 if (IsRailDepotTile(tile
)) break;
294 /* Non-pbs signal? Reservation can't continue. */
295 if (IsTileType(tile
, MP_RAILWAY
) && HasSignalOnTrackdir(tile
, trackdir
) && !IsPbsSignal(GetSignalType(tile
, TrackdirToTrack(trackdir
)))) break;
296 if (IsTileType(tile
, MP_TUNNELBRIDGE
) && IsTunnelBridgeWithSignalSimulation(tile
)) break;
299 return PBSTileInfo(tile
, trackdir
, false);
303 * Helper struct for finding the best matching vehicle on a specific track.
305 struct FindTrainOnTrackInfo
{
306 PBSTileInfo res
; ///< Information about the track.
307 Train
*best
; ///< The currently "best" vehicle we have found.
309 /** Init the best location to nullptr always! */
310 FindTrainOnTrackInfo() : best(nullptr) {}
313 /** Callback for Has/FindVehicleOnPos to find a train on a specific track. */
314 static Vehicle
*FindTrainOnTrackEnum(Vehicle
*v
, void *data
)
316 FindTrainOnTrackInfo
*info
= (FindTrainOnTrackInfo
*)data
;
318 if (v
->type
!= VEH_TRAIN
|| (v
->vehstatus
& VS_CRASHED
)) return nullptr;
320 Train
*t
= Train::From(v
);
321 if (t
->track
== TRACK_BIT_WORMHOLE
) {
322 /* Do not find trains inside signalled bridge/tunnels.
323 * Trains on the ramp/entrance itself are found though.
325 if (IsTileType(info
->res
.tile
, MP_TUNNELBRIDGE
) && IsTunnelBridgeWithSignalSimulation(info
->res
.tile
) && info
->res
.tile
!= TileVirtXY(t
->x_pos
, t
->y_pos
)) {
329 if (t
->track
== TRACK_BIT_WORMHOLE
|| HasBit((TrackBits
)t
->track
, TrackdirToTrack(info
->res
.trackdir
))) {
332 /* ALWAYS return the lowest ID (anti-desync!) */
333 if (info
->best
== nullptr || t
->index
< info
->best
->index
) info
->best
= t
;
341 * Follow a train reservation to the last tile.
343 * @param v the vehicle
344 * @param train_on_res Is set to a train we might encounter
345 * @returns The last tile of the reservation or the current train tile if no reservation present.
347 PBSTileInfo
FollowTrainReservation(const Train
*v
, Vehicle
**train_on_res
)
349 assert(v
->type
== VEH_TRAIN
);
351 TileIndex tile
= v
->tile
;
352 Trackdir trackdir
= v
->GetVehicleTrackdir();
354 if (IsRailDepotTile(tile
) && !GetDepotReservationTrackBits(tile
)) return PBSTileInfo(tile
, trackdir
, false);
356 FindTrainOnTrackInfo ftoti
;
357 ftoti
.res
= FollowReservation(v
->owner
, GetRailTypeInfo(v
->railtype
)->compatible_railtypes
, tile
, trackdir
);
358 ftoti
.res
.okay
= IsSafeWaitingPosition(v
, ftoti
.res
.tile
, ftoti
.res
.trackdir
, true, _settings_game
.pf
.forbid_90_deg
);
359 if (train_on_res
!= nullptr) {
360 FindVehicleOnPos(ftoti
.res
.tile
, &ftoti
, FindTrainOnTrackEnum
);
361 if (ftoti
.best
!= nullptr) *train_on_res
= ftoti
.best
->First();
362 if (*train_on_res
== nullptr && IsRailStationTile(ftoti
.res
.tile
)) {
363 /* The target tile is a rail station. The track follower
364 * has stopped on the last platform tile where we haven't
365 * found a train. Also check all previous platform tiles
366 * for a possible train. */
367 TileIndexDiff diff
= TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti
.res
.trackdir
)));
368 for (TileIndex st_tile
= ftoti
.res
.tile
+ diff
; *train_on_res
== nullptr && IsCompatibleTrainStationTile(st_tile
, ftoti
.res
.tile
); st_tile
+= diff
) {
369 FindVehicleOnPos(st_tile
, &ftoti
, FindTrainOnTrackEnum
);
370 if (ftoti
.best
!= nullptr) *train_on_res
= ftoti
.best
->First();
373 if (*train_on_res
== nullptr && IsTileType(ftoti
.res
.tile
, MP_TUNNELBRIDGE
) && !IsTunnelBridgeWithSignalSimulation(ftoti
.res
.tile
)) {
374 /* The target tile is a bridge/tunnel, also check the other end tile. */
375 FindVehicleOnPos(GetOtherTunnelBridgeEnd(ftoti
.res
.tile
), &ftoti
, FindTrainOnTrackEnum
);
376 if (ftoti
.best
!= nullptr) *train_on_res
= ftoti
.best
->First();
383 * Find the train which has reserved a specific path.
385 * @param tile A tile on the path.
386 * @param track A reserved track on the tile.
387 * @return The vehicle holding the reservation or nullptr if the path is stray.
389 Train
*GetTrainForReservation(TileIndex tile
, Track track
)
391 assert(HasReservedTracks(tile
, TrackToTrackBits(track
)));
392 Trackdir trackdir
= TrackToTrackdir(track
);
394 RailTypes rts
= GetRailTypeInfo(GetTileRailType(tile
))->compatible_railtypes
;
396 /* Follow the path from tile to both ends, one of the end tiles should
397 * have a train on it. We need FollowReservation to ignore one-way signals
398 * here, as one of the two search directions will be the "wrong" way. */
399 for (int i
= 0; i
< 2; ++i
, trackdir
= ReverseTrackdir(trackdir
)) {
400 /* If the tile has a one-way block signal in the current trackdir, skip the
401 * search in this direction as the reservation can't come from this side.*/
402 if (HasOnewaySignalBlockingTrackdir(tile
, ReverseTrackdir(trackdir
)) && !HasPbsSignalOnTrackdir(tile
, trackdir
)) continue;
404 FindTrainOnTrackInfo ftoti
;
405 ftoti
.res
= FollowReservation(GetTileOwner(tile
), rts
, tile
, trackdir
, true);
407 FindVehicleOnPos(ftoti
.res
.tile
, &ftoti
, FindTrainOnTrackEnum
);
408 if (ftoti
.best
!= nullptr) return ftoti
.best
;
410 /* Special case for stations: check the whole platform for a vehicle. */
411 if (IsRailStationTile(ftoti
.res
.tile
)) {
412 TileIndexDiff diff
= TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti
.res
.trackdir
)));
413 for (TileIndex st_tile
= ftoti
.res
.tile
+ diff
; IsCompatibleTrainStationTile(st_tile
, ftoti
.res
.tile
); st_tile
+= diff
) {
414 FindVehicleOnPos(st_tile
, &ftoti
, FindTrainOnTrackEnum
);
415 if (ftoti
.best
!= nullptr) return ftoti
.best
;
419 /* Special case for bridges/tunnels: check the other end as well. */
420 if (IsTileType(ftoti
.res
.tile
, MP_TUNNELBRIDGE
)) {
421 FindVehicleOnPos(GetOtherTunnelBridgeEnd(ftoti
.res
.tile
), &ftoti
, FindTrainOnTrackEnum
);
422 if (ftoti
.best
!= nullptr) return ftoti
.best
;
430 * This is called to retrieve the previous signal, as required
431 * This is not run all the time as it is somewhat expensive and most restrictions will not test for the previous signal
433 static TileIndex
IsSafeWaitingPositionTraceRestrictPreviousSignalCallback(const Train
*v
, const void *)
435 if (IsRailDepotTile(v
->tile
)) {
439 // scan forwards from vehicle position, for the case that train is waiting at/approaching PBS signal
441 TileIndex tile
= v
->tile
;
442 Trackdir trackdir
= v
->GetVehicleTrackdir();
444 CFollowTrackRail
ft(v
);
447 if (IsTileType(tile
, MP_RAILWAY
) && HasSignalOnTrackdir(tile
, trackdir
)) {
448 if (HasPbsSignalOnTrackdir(tile
, trackdir
)) {
452 // wrong type of signal
457 // advance to next tile
458 if (!ft
.Follow(tile
, trackdir
)) {
463 if (KillFirstBit(ft
.m_new_td_bits
) != TRACKDIR_BIT_NONE
) {
464 // reached a junction tile
468 tile
= ft
.m_new_tile
;
469 trackdir
= FindFirstTrackdir(ft
.m_new_td_bits
);
474 * Determine whether a certain track on a tile is a safe position to end a path.
476 * @param v the vehicle to test for
477 * @param tile The tile
478 * @param trackdir The trackdir to test
479 * @param include_line_end Should end-of-line tiles be considered safe?
480 * @param forbid_90deg Don't allow trains to make 90 degree turns
481 * @return True if it is a safe position
483 bool IsSafeWaitingPosition(const Train
*v
, TileIndex tile
, Trackdir trackdir
, bool include_line_end
, bool forbid_90deg
)
485 if (IsRailDepotTile(tile
)) return true;
487 if (IsTileType(tile
, MP_RAILWAY
)) {
488 /* For non-pbs signals, stop on the signal tile. */
489 if (HasSignalOnTrackdir(tile
, trackdir
) && !IsPbsSignal(GetSignalType(tile
, TrackdirToTrack(trackdir
)))) return true;
492 if (IsTileType(tile
, MP_TUNNELBRIDGE
) && GetTunnelBridgeTransportType(tile
) == TRANSPORT_RAIL
) {
493 if (IsTunnelBridgeSignalSimulationEntrance(tile
)) {
498 /* Check next tile. For performance reasons, we check for 90 degree turns ourself. */
499 CFollowTrackRail
ft(v
, GetRailTypeInfo(v
->railtype
)->compatible_railtypes
);
502 if (!ft
.Follow(tile
, trackdir
)) {
503 /* Last tile of a terminus station is a safe position. */
504 if (include_line_end
) return true;
507 /* Check for reachable tracks. */
508 ft
.m_new_td_bits
&= DiagdirReachesTrackdirs(ft
.m_exitdir
);
509 if (forbid_90deg
) ft
.m_new_td_bits
&= ~TrackdirCrossesTrackdirs(trackdir
);
510 if (ft
.m_new_td_bits
== TRACKDIR_BIT_NONE
) return include_line_end
;
512 if (ft
.m_new_td_bits
!= TRACKDIR_BIT_NONE
&& KillFirstBit(ft
.m_new_td_bits
) == TRACKDIR_BIT_NONE
) {
513 Trackdir td
= FindFirstTrackdir(ft
.m_new_td_bits
);
515 /* PBS signal on next trackdir? Conditionally safe position. */
516 if (HasPbsSignalOnTrackdir(ft
.m_new_tile
, td
)) {
517 if (IsRestrictedSignal(ft
.m_new_tile
)) {
518 const TraceRestrictProgram
*prog
= GetExistingTraceRestrictProgram(ft
.m_new_tile
, TrackdirToTrack(td
));
519 if (prog
&& prog
->actions_used_flags
& TRPAUF_RESERVE_THROUGH
) {
520 TraceRestrictProgramResult out
;
521 prog
->Execute(v
, TraceRestrictProgramInput(ft
.m_new_tile
, td
, &IsSafeWaitingPositionTraceRestrictPreviousSignalCallback
, nullptr), out
);
522 if (out
.flags
& TRPRF_RESERVE_THROUGH
) {
530 /* One-way PBS signal against us? Safe if end-of-line is allowed. */
531 if (IsTileType(ft
.m_new_tile
, MP_RAILWAY
) && HasSignalOnTrackdir(ft
.m_new_tile
, ReverseTrackdir(td
)) &&
532 GetSignalType(ft
.m_new_tile
, TrackdirToTrack(td
)) == SIGTYPE_PBS_ONEWAY
) {
533 return include_line_end
;
536 if (IsTileType(ft
.m_new_tile
, MP_TUNNELBRIDGE
) && GetTunnelBridgeTransportType(ft
.m_new_tile
) == TRANSPORT_RAIL
&&
537 IsTunnelBridgeSignalSimulationExit(ft
.m_new_tile
) && IsTunnelBridgePBS(ft
.m_new_tile
)) {
538 return include_line_end
;
546 * Check if a safe position is free.
548 * @param v the vehicle to test for
549 * @param tile The tile
550 * @param trackdir The trackdir to test
551 * @param forbid_90deg Don't allow trains to make 90 degree turns
552 * @return True if the position is free
554 bool IsWaitingPositionFree(const Train
*v
, TileIndex tile
, Trackdir trackdir
, bool forbid_90deg
)
556 Track track
= TrackdirToTrack(trackdir
);
557 TrackBits reserved
= GetReservedTrackbits(tile
);
559 /* Tile reserved? Can never be a free waiting position. */
560 if (TrackOverlapsTracks(reserved
, track
)) return false;
562 /* Not reserved and depot or not a pbs signal -> free. */
563 if (IsRailDepotTile(tile
)) return true;
564 if (IsTileType(tile
, MP_RAILWAY
) && HasSignalOnTrackdir(tile
, trackdir
) && !IsPbsSignal(GetSignalType(tile
, track
))) return true;
565 if (IsTileType(tile
, MP_TUNNELBRIDGE
) && GetTunnelBridgeTransportType(tile
) == TRANSPORT_RAIL
&& IsTunnelBridgeSignalSimulationEntrance(tile
)) return true;
567 /* Check the next tile, if it's a PBS signal, it has to be free as well. */
568 CFollowTrackRail
ft(v
, GetRailTypeInfo(v
->railtype
)->compatible_railtypes
);
570 if (!ft
.Follow(tile
, trackdir
)) return true;
572 /* Check for reachable tracks. */
573 ft
.m_new_td_bits
&= DiagdirReachesTrackdirs(ft
.m_exitdir
);
574 if (forbid_90deg
) ft
.m_new_td_bits
&= ~TrackdirCrossesTrackdirs(trackdir
);
576 return !HasReservedTracks(ft
.m_new_tile
, TrackdirBitsToTrackBits(ft
.m_new_td_bits
));