SVN: 13840
diff --git a/docs/landscape.html b/docs/landscape.html
index 9a92ba9..07ac847 100644
--- a/docs/landscape.html
+++ b/docs/landscape.html
@@ -376,30 +376,40 @@
- - m2 bit 6: Signal 0 and 1: set = semaphore signals, clear = light signals
- - m2 bit 2: Signal 2 and 3: set = semaphore signals, clear = light signals
- - m2 bits 5..4: type of signal 0 and 1 (same values as m2 bits 1..0)
- - m2 bits 1..0: type of signal 2 and 3
+
- m2 bit 7: Signal 0 and 1: set = semaphore signals, clear = light signals
+ - m2 bit 3: Signal 2 and 3: set = semaphore signals, clear = light signals
+ - m2 bits 6..4: type of signal 0 and 1 (same values as m2 bits 2..0)
+ - m2 bits 2..0: type of signal 2 and 3
- 00: |
+ 000: |
normal signals |
- 01: |
+ 001: |
pre-signals |
- 10: |
+ 010: |
exit-signals |
- 11: |
+ 011: |
combo-signals |
+
+
+ 100: |
+ pbs signals |
+
+
+
+ 101: |
+ no-entry signals |
+
@@ -407,6 +417,39 @@
- m4 bits 7..4: bit clear = signal 3..0 shows red
+ m2 bits 8..10: track reserved for pbs
+
+
+ 0 |
+ not reserved |
+
+
+ 1 |
+ X direction |
+
+
+ 2 |
+ Y direction |
+
+
+ 3 |
+ north corner (W-E) |
+
+
+ 4 |
+ south corner (W-E) |
+
+
+ 5 |
+ west corner (N-S) |
+
+
+ 6 |
+ east corner (N-S) |
+
+
+
+ m2 bit 11: opposite track is reserved, too
m5 bit 7 set, bit 6 clear: checkpoint
@@ -425,6 +468,7 @@
+ m5 bit 4: pbs reservation state
m5 bit 7 set, bit 6 set: railway depot
@@ -452,6 +496,7 @@
+ m5 bit 4: pbs reservation state
m6 bits 7..6 : Possibility of a bridge above, in the direction specified
@@ -582,6 +627,7 @@
m4 bit 5: set if crossing lights are on
m4 bits 4..0: owner of the road type 0 (normal road)
+ m5 bit 4: pbs reservation state
@@ -898,6 +944,7 @@
m6 bits 5..3: the station type (rail, airport, truck, bus, oilrig, dock, buoy)
m6 bit 2: 1 when a drive through road stop is built over a town owned road, otherwise 0
+ m6 bit 2: pbs reservation state for railway stations
m6 bits 1..0 : Tropic zone definition
m7: animation frame
@@ -1370,6 +1417,7 @@
m3 bits 3..0: track type for railway
m3 bits 2..0: present road types for road
m4 bit 7 set = on snow or desert
+ m5 bit 4: pbs reservation state for railway
m5 bits 7 clear: tunnel entrance/exit
m5 bit 7 set: bridge ramp
diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html
index 291447f..ed57349 100644
--- a/docs/landscape_grid.html
+++ b/docs/landscape_grid.html
@@ -90,7 +90,7 @@ the array so you can quickly see what is used and what is not.
rail |
XXXX XXXX |
~~~X XXXX |
- OOOO OOOO OOOO OOOO |
+ OOOO XXXX OOOO OOOO |
OOOO ~~XX |
OOOO XXXX |
XXXX XXXX |
@@ -101,7 +101,7 @@ the array so you can quickly see what is used and what is not.
rail with signals |
-inherit- |
-inherit- |
- OOOO OOOO OXXX OXXX |
+ OOOO XXXX XXXX XXXX |
XXXX ~~XX |
XXXX XXXX |
-inherit- |
@@ -115,7 +115,7 @@ the array so you can quickly see what is used and what is not.
OOOO OOOO OOOO OOOO |
OOOO ~~XX |
OOOO XXXX |
- XXOO OOXX |
+ XXOX OXXX |
XXOO OOXX |
OOOO OOOO |
@@ -126,7 +126,7 @@ the array so you can quickly see what is used and what is not.
XXXX XXXX XXXX XXXX |
OOOO ~~XX |
OOOO XXXX |
- XXOO OOOX |
+ XXOX OOOX |
XXOO OOXX |
OOOO OOOO |
@@ -149,7 +149,7 @@ the array so you can quickly see what is used and what is not.
-inherit- |
XXXX ~~XX |
OXXX XXXX |
- XXOO XXXX |
+ XXOX XXXX |
XXOO OOXX |
-inherit- |
@@ -197,7 +197,7 @@ the array so you can quickly see what is used and what is not.
XXXX ~~XX |
XXXX XXXX |
XXXX XXXX |
- OOXX XOXX |
+ OOXX XXXX |
XXXX XXXX |
@@ -310,7 +310,7 @@ the array so you can quickly see what is used and what is not.
OOOO OOOO OOOO OOOO |
OOOO ~XXX |
XOOO OOOO |
- XOOO ~XXX |
+ XOOX ~XXX |
XXOO OOXX |
OOOO OOOO |
@@ -321,7 +321,7 @@ the array so you can quickly see what is used and what is not.
OOOO OOOO XXXX OOOO |
OOOO ~XXX |
XOOO OOOO |
- XOOO ~XXX |
+ XOOX ~XXX |
XXOO OOXX |
OOOO OOOO |
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 2d4f9c0..b19198b 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -664,6 +664,10 @@
>
+
+
@@ -1264,6 +1268,10 @@
>
+
+
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index 4b33a7f..3523d11 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -661,6 +661,10 @@
>
+
+
@@ -1261,6 +1265,10 @@
>
+
+
diff --git a/source.list b/source.list
index 8624afe..a46e52a 100644
--- a/source.list
+++ b/source.list
@@ -57,6 +57,7 @@ os_timer.cpp
ottdres.rc
#end
pathfind.cpp
+pbs.cpp
players.cpp
queue.cpp
rail.cpp
@@ -241,6 +242,7 @@ order_base.h
order_func.h
order_type.h
pathfind.h
+pbs.h
player_base.h
player_face.h
player_func.h
diff --git a/src/debug.cpp b/src/debug.cpp
index 44e1607..3b6980e 100644
--- a/src/debug.cpp
+++ b/src/debug.cpp
@@ -31,6 +31,7 @@ int _debug_freetype_level;
int _debug_sl_level;
int _debug_station_level;
int _debug_gamelog_level;
+int _debug_pbs_level;
struct DebugLevel {
@@ -56,6 +57,7 @@ struct DebugLevel {
DEBUG_LEVEL(sl),
DEBUG_LEVEL(station),
DEBUG_LEVEL(gamelog),
+ DEBUG_LEVEL(pbs),
};
#undef DEBUG_LEVEL
diff --git a/src/debug.h b/src/debug.h
index 7648732..51be8fa 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -48,6 +48,7 @@
extern int _debug_sl_level;
extern int _debug_station_level;
extern int _debug_gamelog_level;
+ extern int _debug_pbs_level;
void CDECL debug(const char *dbg, ...);
#endif /* NO_DEBUG_MESSAGES */
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 3b0b9dd..7e2c032 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -987,6 +987,7 @@ STR_CANT_SHARE_ORDER_LIST :{WHITE}Can't sh
STR_CANT_COPY_ORDER_LIST :{WHITE}Can't copy order list...
STR_END_OF_SHARED_ORDERS :{SETX 10}- - End of Shared Orders - -
+STR_TRAIN_IS_STUCK :{WHITE}Train {COMMA} can't find a path to continue.
STR_TRAIN_IS_LOST :{WHITE}Train {COMMA} is lost.
STR_TRAIN_IS_UNPROFITABLE :{WHITE}Train {COMMA}'s profit last year was {CURRENCY}
STR_EURO_INTRODUCE :{BLACK}{BIGFONT}European Monetary Union!{}{}The Euro is introduced as the sole currency for everyday transactions in your country!
@@ -1135,6 +1136,7 @@ STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_MAGLEV :Maglev
STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_FIRST :First available
STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_LAST :Last available
STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_MOST_USED :Most used
+STR_CONFIG_PATCHES_SHOW_TRACK_RESERVATION :{LTBLUE}Show reserved tracks: {ORANGE}{STRING1}
STR_CONFIG_PATCHES_ALWAYS_BUILD_INFRASTRUCTURE :{LTBLUE}Show building tools when no suitable vehicles are available: {ORANGE}{STRING1}
STR_CONFIG_PATCHES_MAX_TRAINS :{LTBLUE}Max trains per player: {ORANGE}{STRING1}
@@ -1170,6 +1172,14 @@ STR_CONFIG_PATCHES_ALLOW_SHARES :{LTBLUE}Allow b
STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY :{LTBLUE}When dragging, place signals every: {ORANGE}{STRING1} tile(s)
STR_CONFIG_PATCHES_SEMAPHORE_BUILD_BEFORE_DATE :{LTBLUE}Automatically build semaphores before: {ORANGE}{STRING1}
STR_CONFIG_PATCHES_ENABLE_SIGNAL_GUI :{LTBLUE}Enable the signal GUI: {ORANGE}{STRING1}
+STR_CONFIG_PATCHES_DEFAULT_SIGNAL_TYPE :{LTBLUE}Signal type to build by default: {ORANGE}{STRING1}
+STR_CONFIG_PATCHES_DEFAULT_SIGNAL_NORMAL :Normal
+STR_CONFIG_PATCHES_DEFAULT_SIGNAL_PBS :Advanced
+STR_CONFIG_PATCHES_DEFAULT_SIGNAL_PBSOWAY :One-way advanced
+STR_CONFIG_PATCHES_CYCLE_SIGNAL_TYPES :{LTBLUE}Cycle through signal types: {ORANGE}{STRING1}
+STR_CONFIG_PATCHES_CYCLE_SIGNAL_NORMAL :Normal only
+STR_CONFIG_PATCHES_CYCLE_SIGNAL_PBS :Advanced only
+STR_CONFIG_PATCHES_CYCLE_SIGNAL_ALL :All
STR_CONFIG_PATCHES_TOWN_LAYOUT_INVALID :{WHITE}The town layout "no more roads" isn't valid in the scenario editor
STR_CONFIG_PATCHES_TOWN_LAYOUT :{LTBLUE}Select town-road layout: {ORANGE}{STRING1}
@@ -1676,12 +1686,23 @@ STR_RAILROAD_TRACK_WITH_NORMAL_SIGNALS :Railway track w
STR_RAILROAD_TRACK_WITH_PRESIGNALS :Railway track with pre-signals
STR_RAILROAD_TRACK_WITH_EXITSIGNALS :Railway track with exit-signals
STR_RAILROAD_TRACK_WITH_COMBOSIGNALS :Railway track with combo-signals
+STR_RAILROAD_TRACK_WITH_PBSSIGNALS :Railway track with advanced signals
+STR_RAILROAD_TRACK_WITH_NOENTRYSIGNALS :Railway track with one-way advanced signals
STR_RAILROAD_TRACK_WITH_NORMAL_PRESIGNALS :Railway track with normal and pre-signals
STR_RAILROAD_TRACK_WITH_NORMAL_EXITSIGNALS :Railway track with normal and exit-signals
STR_RAILROAD_TRACK_WITH_NORMAL_COMBOSIGNALS :Railway track with normal and combo-signals
+STR_RAILROAD_TRACK_WITH_NORMAL_PBSSIGNALS :Railway track with normal and advanced signals
+STR_RAILROAD_TRACK_WITH_NORMAL_NOENTRYSIGNALS :Railway track with normal and one-way advanced signals
STR_RAILROAD_TRACK_WITH_PRE_EXITSIGNALS :Railway track with pre- and exit-signals
STR_RAILROAD_TRACK_WITH_PRE_COMBOSIGNALS :Railway track with pre- and combo-signals
+STR_RAILROAD_TRACK_WITH_PRE_PBSSIGNALS :Railway track with pre- and advanced signals
+STR_RAILROAD_TRACK_WITH_PRE_NOENTRYSIGNALS :Railway track with pre- and one-way advanced signals
STR_RAILROAD_TRACK_WITH_EXIT_COMBOSIGNALS :Railway track with exit- and combo-signals
+STR_RAILROAD_TRACK_WITH_EXIT_PBSSIGNALS :Railway track with exit- and advanced signals
+STR_RAILROAD_TRACK_WITH_EXIT_NOENTRYSIGNALS :Railway track with exit- and one-way advanced signals
+STR_RAILROAD_TRACK_WITH_COMBO_PBSSIGNALS :Railway track with combo- and advanced signals
+STR_RAILROAD_TRACK_WITH_COMBO_NOENTRYSIGNALS :Railway track with combo- and one-way advanced signals
+STR_RAILROAD_TRACK_WITH_PBS_NOENTRYSIGNALS :Railway track with advanced and one-way advanced signals
STR_MUST_REMOVE_RAILWAY_STATION_FIRST :{WHITE}Must remove railway station first
@@ -2883,6 +2904,7 @@ STR_TRAIN_STOPPING_VEL :{RED}Stopping,
STR_INCOMPATIBLE_RAIL_TYPES :Incompatible rail types
STR_TRAIN_NO_POWER :{RED}No power
STR_TRAIN_START_NO_CATENARY :This track lacks catenary, so the train can't start
+STR_TRAIN_STUCK :{ORANGE}Waiting for free path
STR_NEW_VEHICLE_NOW_AVAILABLE :{BLACK}{BIGFONT}New {STRING} now available!
STR_NEW_VEHICLE_TYPE :{BLACK}{BIGFONT}{ENGINE}
@@ -3577,10 +3599,14 @@ STR_BUILD_SIGNAL_SEMAPHORE_NORM_TIP :{BLACK}Standard
STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TIP :{BLACK}Entry-Signal (semaphore){}Green as long as there is one or more green exit-signal from the following section of track. Otherwise it shows red.
STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TIP :{BLACK}Exit-Signal (semaphore){}Behaves in the same way as a normal signal but is necessary to trigger the correct colour on entry & combo pre-signals.
STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TIP :{BLACK}Combo-Signal (semaphore){}The combo signal simply acts as both an entry and exit signal. This allows you to build large "trees" of presignals.
+STR_BUILD_SIGNAL_SEMAPHORE_PBS_TIP :{BLACK}Advanced Signal (semaphore){}An advanced signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. Advanced signals can be passed from the backside.
+STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TIP :{BLACK}One-way Advanced Signal (semaphore){}An advanced signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. One-way signals cannot be passed from the backside.
STR_BUILD_SIGNAL_ELECTRIC_NORM_TIP :{BLACK}Standard Signal (electric){}Signals are necessary to keep trains from crashing on railway networks with more than one train.
STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TIP :{BLACK}Entry-Signal (electric){}Green as long as there is one or more green exit-signal from the following section of track. Otherwise it shows red.
STR_BUILD_SIGNAL_ELECTRIC_EXIT_TIP :{BLACK}Exit-Signal (electric){}Behaves in the same way as a normal signal but is necessary to trigger the correct colour on entry & combo pre-signals.
STR_BUILD_SIGNAL_ELECTRIC_COMBO_TIP :{BLACK}Combo-Signal (electric){}The combo signal simply acts as both an entry and exit signal. This allows you to build large "trees" of presignals.
+STR_BUILD_SIGNAL_ELECTRIC_PBS_TIP :{BLACK}Advanced Signal (electric){}An advanced signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. Advanced signals can be passed from the backside.
+STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TIP :{BLACK}One-way Advanced Signal (electric){}An advanced signal allows more than one train to enter a signal block at the same time, if the train can reserve a path to a safe stopping point. One-way signals cannot be passed from the backside.
STR_SIGNAL_CONVERT_TIP :{BLACK}Signal Convert{}When selected, clicking an existing signal will convert it to the selected signal type and variant, CTRL-click will toggle the existing variant.
STR_DRAG_SIGNALS_DENSITY_TIP :{BLACK}Dragging signal density
STR_DRAG_SIGNALS_DENSITY_DECREASE_TIP :{BLACK}Decrease dragging signal density
diff --git a/src/misc/dbg_helpers.cpp b/src/misc/dbg_helpers.cpp
index 6db02e8..1c34e25 100644
--- a/src/misc/dbg_helpers.cpp
+++ b/src/misc/dbg_helpers.cpp
@@ -49,7 +49,7 @@ CStrA ValueStr(DiagDirection dd)
/** SignalType short names. */
static const char* signal_type_names[] = {
- "NORMAL", "ENTRY", "EXIT", "COMBO",
+ "NORMAL", "ENTRY", "EXIT", "COMBO", "PBS", "NOENTRY",
};
/** Return name of given SignalType. */
diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp
index 00ccd5c..c0a3b82 100644
--- a/src/misc_gui.cpp
+++ b/src/misc_gui.cpp
@@ -59,13 +59,13 @@ static bool _savegame_sort_dirty;
static const Widget _land_info_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
-{ WWT_CAPTION, RESIZE_NONE, 14, 11, 279, 0, 13, STR_01A3_LAND_AREA_INFORMATION, STR_018C_WINDOW_TITLE_DRAG_THIS},
-{ WWT_PANEL, RESIZE_BOTTOM, 14, 0, 279, 14, 99, 0x0, STR_NULL},
+{ WWT_CAPTION, RESIZE_NONE, 14, 11, 299, 0, 13, STR_01A3_LAND_AREA_INFORMATION, STR_018C_WINDOW_TITLE_DRAG_THIS},
+{ WWT_PANEL, RESIZE_BOTTOM, 14, 0, 299, 14, 99, 0x0, STR_NULL},
{ WIDGETS_END},
};
static const WindowDesc _land_info_desc = {
- WDP_AUTO, WDP_AUTO, 280, 100, 280, 100,
+ WDP_AUTO, WDP_AUTO, 300, 100, 300, 100,
WC_LAND_INFO, WC_NONE,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
_land_info_widgets,
@@ -91,7 +91,7 @@ public:
for (uint i = 0; i < LAND_INFO_CENTERED_LINES; i++) {
if (StrEmpty(this->landinfo_data[i])) break;
- DoDrawStringCentered(140, y, this->landinfo_data[i], i == 0 ? TC_LIGHT_BLUE : TC_FROMSTRING);
+ DoDrawStringCentered(150, y, this->landinfo_data[i], i == 0 ? TC_LIGHT_BLUE : TC_FROMSTRING);
y += i == 0 ? 16 : 12;
}
@@ -99,7 +99,7 @@ public:
if (!StrEmpty(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])) {
SetDParamStr(0, this->landinfo_data[LAND_INFO_MULTICENTER_LINE]);
- DrawStringMultiCenter(140, y, STR_JUST_RAW_STRING, this->width - 4);
+ DrawStringMultiCenter(150, y, STR_JUST_RAW_STRING, this->width - 4);
}
}
diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp
index 52a8c34..1e16d41 100644
--- a/src/newgrf_station.cpp
+++ b/src/newgrf_station.cpp
@@ -26,6 +26,7 @@
#include "animated_tile_func.h"
#include "functions.h"
#include "tunnelbridge_map.h"
+#include "rail_map.h"
#include "table/sprites.h"
#include "table/strings.h"
@@ -405,7 +406,12 @@ static uint32 StationGetVariable(const ResolverObject *object, byte variable, by
case 0x42: return GetTerrainType(tile) | (GetRailType(tile) << 8);
case 0x43: return st->owner; // Station owner
- case 0x44: return 2; // PBS status
+ case 0x44:
+ if (IsTileType(tile, MP_RAILWAY) && IsRailWaypoint(tile)) {
+ return GetWaypointReservation(tile) ? 7 : 4;
+ } else {
+ return GetRailwayStationReservation(tile) ? 7 : 4; // PBS status
+ }
case 0x45:
if (!HasBit(_svc.valid, 2)) { _svc.v45 = GetRailContinuationInfo(tile); SetBit(_svc.valid, 2); }
return _svc.v45;
diff --git a/src/npf.cpp b/src/npf.cpp
index e4498bb..d5f431b 100644
--- a/src/npf.cpp
+++ b/src/npf.cpp
@@ -24,6 +24,7 @@
#include "vehicle_base.h"
#include "settings_type.h"
#include "tunnelbridge.h"
+#include "pbs.h"
static AyStar _npf_aystar;
@@ -221,6 +222,23 @@ static uint NPFSlopeCost(AyStarNode* current)
* there is only one level of steepness... */
}
+static uint NPFReservedTrackCost(AyStarNode *current)
+{
+ TileIndex tile = current->tile;
+ TrackBits track = TrackToTrackBits(TrackdirToTrack((Trackdir)current->direction));
+ TrackBits res = GetReservedTrackbits(tile);
+
+ if (NPFGetFlag(current, NPF_FLAG_3RD_SIGNAL) || ((res & track) == TRACK_BIT_NONE && !TracksOverlap(res | track))) return 0;
+
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ DiagDirection exitdir = TrackdirToExitdir((Trackdir)current->direction);
+ if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(exitdir)) {
+ return _settings_game.pf.npf.npf_rail_pbs_cross_penalty * (GetTunnelBridgeLength(tile, GetOtherTunnelBridgeEnd(tile)) + 1);
+ }
+ }
+ return _settings_game.pf.npf.npf_rail_pbs_cross_penalty;
+}
+
/**
* Mark tiles by mowing the grass when npf debug level >= 1.
* Will not work for multiplayer games, since it can (will) cause desyncs.
@@ -354,30 +372,46 @@ static int32 NPFRailPathCost(AyStar* as, AyStarNode* current, OpenListNode* pare
/* Determine extra costs */
/* Check for signals */
- if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir)) {
- /* Ordinary track with signals */
- if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_RED) {
- /* Signal facing us is red */
- if (!NPFGetFlag(current, NPF_FLAG_SEEN_SIGNAL)) {
- /* Penalize the first signal we
- * encounter, if it is red */
-
- /* Is this a presignal exit or combo? */
- SignalType sigtype = GetSignalType(tile, TrackdirToTrack(trackdir));
- if (sigtype == SIGTYPE_EXIT || sigtype == SIGTYPE_COMBO) {
- /* Penalise exit and combo signals differently (heavier) */
- cost += _settings_game.pf.npf.npf_rail_firstred_exit_penalty;
+ if (IsTileType(tile, MP_RAILWAY)) {
+ if (HasSignalOnTrackdir(tile, trackdir)) {
+ /* Ordinary track with signals */
+ if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_RED) {
+ /* Signal facing us is red */
+ if (!NPFGetFlag(current, NPF_FLAG_SEEN_SIGNAL)) {
+ /* Penalize the first signal we
+ * encounter, if it is red */
+
+ /* Is this a presignal exit or combo? */
+ SignalType sigtype = GetSignalType(tile, TrackdirToTrack(trackdir));
+ if (!IsPbsSignal(sigtype)) {
+ if (sigtype == SIGTYPE_EXIT || sigtype == SIGTYPE_COMBO) {
+ /* Penalise exit and combo signals differently (heavier) */
+ cost += _settings_game.pf.npf.npf_rail_firstred_exit_penalty;
+ } else {
+ cost += _settings_game.pf.npf.npf_rail_firstred_penalty;
+ }
+ }
+ }
+ /* Record the state of this signal */
+ NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_RED, true);
+ } else {
+ /* Record the state of this signal */
+ NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_RED, false);
+ }
+ if (NPFGetFlag(current, NPF_FLAG_SEEN_SIGNAL)) {
+ if (NPFGetFlag(current, NPF_FLAG_2ND_SIGNAL)) {
+ NPFSetFlag(current, NPF_FLAG_3RD_SIGNAL, true);
} else {
- cost += _settings_game.pf.npf.npf_rail_firstred_penalty;
+ NPFSetFlag(current, NPF_FLAG_2ND_SIGNAL, true);
}
+ } else {
+ NPFSetFlag(current, NPF_FLAG_SEEN_SIGNAL, true);
}
- /* Record the state of this signal */
- NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_RED, true);
- } else {
- /* Record the state of this signal */
- NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_RED, false);
}
- NPFSetFlag(current, NPF_FLAG_SEEN_SIGNAL, true);
+
+ if (HasPbsSignalOnTrackdir(tile, ReverseTrackdir(trackdir)) && !NPFGetFlag(current, NPF_FLAG_3RD_SIGNAL)) {
+ cost += _settings_game.pf.npf.npf_rail_pbs_signal_back_penalty;
+ }
}
/* Penalise the tile if it is a target tile and the last signal was
@@ -406,7 +440,7 @@ static int32 NPFRailPathCost(AyStar* as, AyStarNode* current, OpenListNode* pare
}
/* Check for occupied track */
- //TODO
+ cost += NPFReservedTrackCost(current);
NPFMarkTile(tile);
DEBUG(npf, 4, "Calculating G for: (%d, %d). Result: %d", TileX(current->tile), TileY(current->tile), cost);
@@ -422,6 +456,17 @@ static int32 NPFFindDepot(AyStar* as, OpenListNode *current)
AYSTAR_FOUND_END_NODE : AYSTAR_DONE;
}
+/** Find any safe and free tile. */
+static int32 NPFFindSafeTile(AyStar *as, OpenListNode *current)
+{
+ const Vehicle *v = ((NPFFindStationOrTileData*)as->user_target)->v;
+
+ return
+ IsSafeWaitingPosition(v, current->path.node.tile, (Trackdir)current->path.node.direction, true, _settings_game.pf.forbid_90_deg) &&
+ IsWaitingPositionFree(v, current->path.node.tile, (Trackdir)current->path.node.direction, _settings_game.pf.forbid_90_deg) ?
+ AYSTAR_FOUND_END_NODE : AYSTAR_DONE;
+}
+
/* Will find a station identified using the NPFFindStationOrTileData */
static int32 NPFFindStationOrTile(AyStar* as, OpenListNode *current)
{
@@ -441,9 +486,48 @@ static int32 NPFFindStationOrTile(AyStar* as, OpenListNode *current)
}
}
-/* To be called when current contains the (shortest route to) the target node.
+/**
+ * Find the node containing the first signal on the path.
+ *
+ * If the first signal is on the very first two tiles of the path,
+ * the second signal is returnd. If no suitable signal is present, the
+ * last node of the path is returned.
+ */
+static const PathNode* FindSafePosition(PathNode *path, const Vehicle *v)
+{
+ /* If there is no signal, reserve the whole path. */
+ PathNode *sig = path;
+
+ for(; path->parent != NULL; path = path->parent) {
+ if (IsSafeWaitingPosition(v, path->node.tile, (Trackdir)path->node.direction, true, _settings_game.pf.forbid_90_deg)) {
+ sig = path;
+ }
+ }
+
+ return sig;
+}
+
+/**
+ * Lift the reservation of the tiles from @p start till @p end, excluding @p end itself.
+ */
+static void ClearPathReservation(const PathNode *start, const PathNode *end)
+{
+ bool first_run = true;
+ for (; start != end; start = start->parent) {
+ if (IsRailwayStationTile(start->node.tile) && first_run) {
+ SetRailwayStationPlatformReservation(start->node.tile, TrackdirToExitdir((Trackdir)start->node.direction), false);
+ } else {
+ UnreserveRailTrack(start->node.tile, TrackdirToTrack((Trackdir)start->node.direction));
+ }
+ first_run = false;
+ }
+}
+
+/**
+ * To be called when @p current contains the (shortest route to) the target node.
* Will fill the contents of the NPFFoundTargetData using
- * AyStarNode[NPF_TRACKDIR_CHOICE].
+ * AyStarNode[NPF_TRACKDIR_CHOICE]. If requested, path reservation
+ * is done here.
*/
static void NPFSaveTargetData(AyStar* as, OpenListNode* current)
{
@@ -452,6 +536,40 @@ static void NPFSaveTargetData(AyStar* as, OpenListNode* current)
ftd->best_path_dist = current->g;
ftd->best_bird_dist = 0;
ftd->node = current->path.node;
+ ftd->res_okay = false;
+
+ if (as->user_target != NULL && ((NPFFindStationOrTileData*)as->user_target)->reserve_path && as->user_data[NPF_TYPE] == TRANSPORT_RAIL) {
+ /* Path reservation is requested. */
+ const Vehicle *v = ((NPFFindStationOrTileData*)as->user_target)->v;
+
+ const PathNode *target = FindSafePosition(¤t->path, v);
+ ftd->node = target->node;
+
+ /* If the target is a station skip to platform end. */
+ if (IsRailwayStationTile(target->node.tile)) {
+ DiagDirection dir = TrackdirToExitdir((Trackdir)target->node.direction);
+ uint len = GetStationByTile(target->node.tile)->GetPlatformLength(target->node.tile, dir);
+ TileIndex end_tile = TILE_ADD(target->node.tile, (len - 1) * TileOffsByDiagDir(dir));
+
+ /* Update only end tile, trackdir of a station stays the same. */
+ ftd->node.tile = end_tile;
+ if (!IsWaitingPositionFree(v, end_tile, (Trackdir)target->node.direction, _settings_game.pf.forbid_90_deg)) return;
+ SetRailwayStationPlatformReservation(target->node.tile, dir, true);
+ SetRailwayStationReservation(target->node.tile, false);
+ } else {
+ if (!IsWaitingPositionFree(v, target->node.tile, (Trackdir)target->node.direction, _settings_game.pf.forbid_90_deg)) return;
+ }
+
+ for (const PathNode *cur = target; cur->parent != NULL; cur = cur->parent) {
+ if (!TryReserveRailTrack(cur->node.tile, TrackdirToTrack((Trackdir)cur->node.direction))) {
+ /* Reservation failed, undo. */
+ ClearPathReservation(target, cur);
+ return;
+ }
+ }
+
+ ftd->res_okay = true;
+ }
}
/**
@@ -713,16 +831,28 @@ static void NPFFollowTrack(AyStar* aystar, OpenListNode* current)
}
}
+ if (NPFGetFlag(¤t->path.node, NPF_FLAG_IGNORE_RESERVED)) {
+ /* Mask out any reserved tracks. */
+ TrackBits reserved = GetReservedTrackbits(dst_tile);
+ trackdirbits &= ~TrackBitsToTrackdirBits(reserved);
+
+ uint bits = TrackdirBitsToTrackBits(trackdirbits);
+ int i;
+ FOR_EACH_SET_BIT(i, bits) {
+ if (TracksOverlap(reserved | TrackToTrackBits((Track)i))) trackdirbits &= ~TrackToTrackdirBits((Track)i);
+ }
+ }
+
/* Enumerate possible track */
uint i = 0;
while (trackdirbits != 0) {
Trackdir dst_trackdir = RemoveFirstTrackdir(&trackdirbits);
DEBUG(npf, 5, "Expanded into trackdir: %d, remaining trackdirs: 0x%X", dst_trackdir, trackdirbits);
- /* Check for oneway signal against us */
+ /* Tile with signals? */
if (IsTileType(dst_tile, MP_RAILWAY) && GetRailTileType(dst_tile) == RAIL_TILE_SIGNALS) {
- if (HasSignalOnTrackdir(dst_tile, ReverseTrackdir(dst_trackdir)) && !HasSignalOnTrackdir(dst_tile, dst_trackdir))
- /* if one way signal not pointing towards us, stop going in this direction. */
+ if (HasSignalOnTrackdir(dst_tile, ReverseTrackdir(dst_trackdir)) && !HasSignalOnTrackdir(dst_tile, dst_trackdir) && IsOnewaySignal(dst_tile, TrackdirToTrack(dst_trackdir)))
+ /* If there's a one-way signal not pointing towards us, stop going in this direction. */
break;
}
{
@@ -782,7 +912,9 @@ static NPFFoundTargetData NPFRouteInternal(AyStarNode* start1, bool ignore_start
/* Initialize result */
result.best_bird_dist = (uint)-1;
result.best_path_dist = (uint)-1;
- result.best_trackdir = INVALID_TRACKDIR;
+ result.best_trackdir = INVALID_TRACKDIR;
+ result.node.tile = INVALID_TILE;
+ result.res_okay = false;
_npf_aystar.user_path = &result;
/* Initialize target */
@@ -868,7 +1000,7 @@ NPFFoundTargetData NPFRouteToDepotTrialError(TileIndex tile, Trackdir trackdir,
*/
Queue depots;
int r;
- NPFFoundTargetData best_result = {(uint)-1, (uint)-1, INVALID_TRACKDIR, {INVALID_TILE, 0, {0, 0}}};
+ NPFFoundTargetData best_result = {(uint)-1, (uint)-1, INVALID_TRACKDIR, {INVALID_TILE, 0, {0, 0}}, false};
NPFFoundTargetData result;
NPFFindStationOrTileData target;
AyStarNode start;
@@ -956,6 +1088,30 @@ NPFFoundTargetData NPFRouteToDepotTrialError(TileIndex tile, Trackdir trackdir,
return best_result;
}
+NPFFoundTargetData NPFRouteToSafeTile(const Vehicle *v, TileIndex tile, Trackdir trackdir, bool override_railtype)
+{
+ assert(v->type == VEH_TRAIN);
+
+ NPFFindStationOrTileData fstd;
+ fstd.v = v;
+ fstd.reserve_path = true;
+
+ AyStarNode start1;
+ start1.tile = tile;
+ /* We set this in case the target is also the start tile, we will just
+ * return a not found then */
+ start1.user_data[NPF_TRACKDIR_CHOICE] = INVALID_TRACKDIR;
+ start1.direction = trackdir;
+ NPFSetFlag(&start1, NPF_FLAG_IGNORE_RESERVED, true);
+
+ RailTypes railtypes = v->u.rail.compatible_railtypes;
+ if (override_railtype) railtypes |= GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes;
+
+ /* perform a breadth first search. Target is NULL,
+ * since we are just looking for any safe tile...*/
+ return NPFRouteInternal(&start1, true, NULL, false, &fstd, NPFFindSafeTile, NPFCalcZero, TRANSPORT_RAIL, 0, v->owner, railtypes, 0);
+}
+
void InitializeNPF()
{
static bool first_init = true;
@@ -973,7 +1129,7 @@ void InitializeNPF()
_npf_aystar.max_search_nodes = _settings_game.pf.npf.npf_max_search_nodes;
}
-void NPFFillWithOrderData(NPFFindStationOrTileData* fstd, Vehicle* v)
+void NPFFillWithOrderData(NPFFindStationOrTileData *fstd, Vehicle *v, bool reserve_path)
{
/* Ships don't really reach their stations, but the tile in front. So don't
* save the station id for ships. For roadvehs we don't store it either,
@@ -989,4 +1145,6 @@ void NPFFillWithOrderData(NPFFindStationOrTileData* fstd, Vehicle* v)
fstd->dest_coords = v->dest_tile;
fstd->station_index = INVALID_STATION;
}
+ fstd->reserve_path = reserve_path;
+ fstd->v = v;
}
diff --git a/src/npf.h b/src/npf.h
index 5a70b6b..bf19b62 100644
--- a/src/npf.h
+++ b/src/npf.h
@@ -45,6 +45,8 @@ enum {
struct NPFFindStationOrTileData {
TileIndex dest_coords; ///< An indication of where the station is, for heuristic purposes, or the target tile
StationID station_index; ///< station index we're heading for, or INVALID_STATION when we're heading for a tile
+ bool reserve_path; ///< Indicates whether the found path should be reserved
+ const Vehicle* v; ///< The vehicle we are pathfinding for
};
/* Indices into AyStar.userdata[] */
@@ -64,9 +66,13 @@ enum {
/* Flags for AyStarNode.userdata[NPF_NODE_FLAGS]. Use NPFGetBit() and NPFGetBit() to use them. */
enum NPFNodeFlag {
NPF_FLAG_SEEN_SIGNAL, ///< Used to mark that a signal was seen on the way, for rail only
+ NPF_FLAG_2ND_SIGNAL, ///< Used to mark that two signals were seen, rail only
+ NPF_FLAG_3RD_SIGNAL, ///< Used to mark that three signals were seen, rail only
NPF_FLAG_REVERSE, ///< Used to mark that this node was reached from the second start node, if applicable
NPF_FLAG_LAST_SIGNAL_RED, ///< Used to mark that the last signal on this path was red
NPF_FLAG_IGNORE_START_TILE, ///< Used to mark that the start tile is invalid, and searching should start from the second tile on
+ NPF_FLAG_TARGET_RESERVED, ///< Used to mark that the possible reservation target is already reserved
+ NPF_FLAG_IGNORE_RESERVED, ///< Used to mark that reserved tiles should be considered impassable
};
/* Meant to be stored in AyStar.userpath */
@@ -75,6 +81,7 @@ struct NPFFoundTargetData {
uint best_path_dist; ///< The shortest path. Is (uint)-1 if no path is found
Trackdir best_trackdir; ///< The trackdir that leads to the shortest path/closest birds dist
AyStarNode node; ///< The node within the target the search led us to
+ bool res_okay; ///< True if a path reservation could be made
};
/* These functions below are _not_ re-entrant, in favor of speed! */
@@ -105,7 +112,13 @@ NPFFoundTargetData NPFRouteToDepotBreadthFirstTwoWay(TileIndex tile1, Trackdir t
* of choices and accurate heuristics, such as water. */
NPFFoundTargetData NPFRouteToDepotTrialError(TileIndex tile, Trackdir trackdir, bool ignore_start_tile, TransportType type, uint sub_type, Owner owner, RailTypes railtypes);
-void NPFFillWithOrderData(NPFFindStationOrTileData* fstd, Vehicle* v);
+/**
+ * Search for any safe tile using a breadth first search and try to reserve a path.
+ */
+NPFFoundTargetData NPFRouteToSafeTile(const Vehicle *v, TileIndex tile, Trackdir trackdir,bool override_railtype);
+
+
+void NPFFillWithOrderData(NPFFindStationOrTileData *fstd, Vehicle *v, bool reserve_path = false);
/*
diff --git a/src/openttd.cpp b/src/openttd.cpp
index 29c230c..bec7050 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -2454,6 +2454,46 @@ bool AfterLoadGame()
}
}
+ /* Move the signal variant back up one bit for PBS. We don't convert the old PBS
+ * format here, as an old layout wouldn't work properly anyway. To be safe, we
+ * clear any possible PBS reservations as well. */
+ if (CheckSavegameVersion(100)) {
+ for (TileIndex t = 0; t < map_size; t++) {
+ switch (GetTileType(t)) {
+ case MP_RAILWAY:
+ if (HasSignals(t)) {
+ /* move the signal variant */
+ SetSignalVariant(t, TRACK_UPPER, HasBit(_m[t].m2, 2) ? SIG_SEMAPHORE : SIG_ELECTRIC);
+ SetSignalVariant(t, TRACK_LOWER, HasBit(_m[t].m2, 6) ? SIG_SEMAPHORE : SIG_ELECTRIC);
+ ClrBit(_m[t].m2, 2);
+ ClrBit(_m[t].m2, 6);
+ }
+
+ /* Clear PBS reservation on track */
+ if (IsRailDepot(t) ||IsRailWaypoint(t)) {
+ SetWaypointReservation(t, false);
+ } else {
+ SetTrackReservation(t, TRACK_BIT_NONE);
+ }
+ break;
+
+ case MP_ROAD: /* Clear PBS reservation on crossing */
+ if (IsLevelCrossing(t)) SetCrossingReservation(t, false);
+ break;
+
+ case MP_STATION: /* Clear PBS reservation on station */
+ if (IsRailwayStation(t)) SetRailwayStationReservation(t, false);
+ break;
+
+ case MP_TUNNELBRIDGE: /* Clear PBS reservation on tunnels/birdges */
+ if (GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) SetTunnelBridgeReservation(t, false);
+ break;
+
+ default: break;
+ }
+ }
+ }
+
GamelogPrintDebug(1);
return InitializeWindowsAndCaches();
diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp
index df84553..07154ee 100644
--- a/src/order_cmd.cpp
+++ b/src/order_cmd.cpp
@@ -1667,7 +1667,7 @@ static bool OrderConditionCompare(OrderConditionComparator occ, int variable, in
* @param v the vehicle to update
* @return index of next order to jump to, or INVALID_VEH_ORDER_ID to use the next order
*/
-static VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v)
+VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v)
{
if (order->GetType() != OT_CONDITIONAL) return INVALID_VEH_ORDER_ID;
@@ -1693,7 +1693,7 @@ static VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle
* @param order the order the vehicle currently has
* @param v the vehicle to update
*/
-static bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0)
+bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth)
{
switch (order->GetType()) {
case OT_GOTO_STATION:
diff --git a/src/order_func.h b/src/order_func.h
index be2ceea..1e7c051 100644
--- a/src/order_func.h
+++ b/src/order_func.h
@@ -37,6 +37,8 @@ bool VehicleHasDepotOrders(const Vehicle *v);
void CheckOrders(const Vehicle*);
void DeleteVehicleOrders(Vehicle *v);
bool ProcessOrders(Vehicle *v);
+bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0);
+VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v);
void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int y, bool selected, bool timetable, int width);
diff --git a/src/pathfind.cpp b/src/pathfind.cpp
index 9757b09..e6a27df 100644
--- a/src/pathfind.cpp
+++ b/src/pathfind.cpp
@@ -674,7 +674,7 @@ start_at:
if (HasSignals(tile)) {
if (!HasSignalOnTrackdir(tile, track)) {
/* if one way signal not pointing towards us, stop going in this direction => End of rail segment. */
- if (HasSignalOnTrackdir(tile, ReverseTrackdir(track))) {
+ if (HasSignalOnTrackdir(tile, ReverseTrackdir(track)) && IsOnewaySignal(tile, TrackdirToTrack(track))) {
bits = TRACK_BIT_NONE;
break;
}
diff --git a/src/pbs.cpp b/src/pbs.cpp
new file mode 100644
index 0000000..c2afed3
--- /dev/null
+++ b/src/pbs.cpp
@@ -0,0 +1,351 @@
+/* $Id$ */
+
+/** @file pbs.cpp */
+#include "stdafx.h"
+#include "openttd.h"
+#include "pbs.h"
+#include "rail_map.h"
+#include "road_map.h"
+#include "station_map.h"
+#include "tunnelbridge_map.h"
+#include "functions.h"
+#include "debug.h"
+#include "direction_func.h"
+#include "settings_type.h"
+#include "road_func.h"
+#include "vehicle_base.h"
+#include "vehicle_func.h"
+#include "yapf/follow_track.hpp"
+
+/**
+ * Get the reserved trackbits for any tile, regardless of type.
+ * @param t the tile
+ * @return the reserved trackbits. TRACK_BIT_NONE on nothing reserved or
+ * a tile without rail.
+ */
+TrackBits GetReservedTrackbits(TileIndex t)
+{
+ switch (GetTileType(t)) {
+ case MP_RAILWAY:
+ if (IsRailWaypoint(t) || IsRailDepot(t)) return GetRailWaypointReservation(t);
+ if (IsPlainRailTile(t)) return GetTrackReservation(t);
+ break;
+
+ case MP_ROAD:
+ if (IsLevelCrossing(t)) return GetRailCrossingReservation(t);
+ break;
+
+ case MP_STATION:
+ if (IsRailwayStation(t)) return GetRailStationReservation(t);
+ break;
+
+ case MP_TUNNELBRIDGE:
+ if (GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) return GetRailTunnelBridgeReservation(t);
+ break;
+
+ default:
+ break;
+ }
+ return TRACK_BIT_NONE;
+}
+
+/**
+ * Set the reservation for a complete station platform.
+ * @pre IsRailwayStationTile(start)
+ * @param start starting tile of the platform
+ * @param dir the direction in which to follow the platform
+ * @param b the state the reservation should be set to
+ */
+void SetRailwayStationPlatformReservation(TileIndex start, DiagDirection dir, bool b)
+{
+ TileIndex tile = start;
+ TileIndexDiff diff = TileOffsByDiagDir(dir);
+
+ assert(IsRailwayStationTile(start));
+ assert(GetRailStationAxis(start) == DiagDirToAxis(dir));
+
+ do {
+ SetRailwayStationReservation(tile, b);
+ MarkTileDirtyByTile(tile);
+ tile = TILE_ADD(tile, diff);
+ } while (IsCompatibleTrainStationTile(tile, start));
+}
+
+/**
+ * Try to reserve a specific track on a tile
+ * @param tile the tile
+ * @param t the track
+ * @return true if reservation was successfull, i.e. the track was
+ * free and didn't cross any other reserved tracks.
+ */
+bool TryReserveRailTrack(TileIndex tile, Track t)
+{
+ assert((GetTileTrackStatus(tile, TRANSPORT_RAIL, 0) & TrackToTrackBits(t)) != 0);
+
+ if (_settings_client.gui.show_track_reservation) {
+ MarkTileDirtyByTile(tile);
+ }
+
+ switch (GetTileType(tile)) {
+ case MP_RAILWAY:
+ if (IsPlainRailTile(tile)) return TryReserveTrack(tile, t);
+ if (IsRailWaypoint(tile) || IsRailDepot(tile)) {
+ if (!GetWaypointReservation(tile)) {
+ SetWaypointReservation(tile, true);
+ MarkTileDirtyByTile(tile);
+ return true;
+ }
+ }
+ break;
+
+ case MP_ROAD:
+ if (IsLevelCrossing(tile) && !GetCrossingReservation(tile)) {
+ SetCrossingReservation(tile, true);
+ BarCrossing(tile);
+ MarkTileDirtyByTile(tile);
+ return true;
+ }
+ break;
+
+ case MP_STATION:
+ if (IsRailwayStation(tile) && !GetRailwayStationReservation(tile)) {
+ SetRailwayStationReservation(tile, true);
+ MarkTileDirtyByTile(tile);
+ return true;
+ }
+ break;
+
+ case MP_TUNNELBRIDGE:
+ if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && !GetRailTunnelBridgeReservation(tile)) {
+ SetTunnelBridgeReservation(tile, true);
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+/**
+ * Lift the reservation of a specific track on a tile
+ * @param tile the tile
+ * @param t the track
+ */
+ void UnreserveRailTrack(TileIndex tile, Track t)
+{
+ assert((GetTileTrackStatus(tile, TRANSPORT_RAIL, 0) & TrackToTrackBits(t)) != 0);
+
+ if (_settings_client.gui.show_track_reservation) {
+ MarkTileDirtyByTile(tile);
+ }
+
+ switch (GetTileType(tile)) {
+ case MP_RAILWAY:
+ if (IsRailWaypoint(tile) || IsRailDepot(tile)) {
+ SetWaypointReservation(tile, false);
+ MarkTileDirtyByTile(tile);
+ }
+ if (IsPlainRailTile(tile)) UnreserveTrack(tile, t);
+ break;
+
+ case MP_ROAD:
+ if (IsLevelCrossing(tile)) {
+ SetCrossingReservation(tile, false);
+ UpdateLevelCrossing(tile);
+ }
+ break;
+
+ case MP_STATION:
+ if (IsRailwayStation(tile)) {
+ SetRailwayStationReservation(tile, false);
+ MarkTileDirtyByTile(tile);
+ }
+ break;
+
+ case MP_TUNNELBRIDGE:
+ if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL) SetTunnelBridgeReservation(tile, false);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/** Follow a reservation starting from a specific tile to the end. */
+static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Trackdir trackdir, bool ignore_oneway = false)
+{
+ /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */
+ CFollowTrackRail ft(o, rts);
+ while (ft.Follow(tile, trackdir)) {
+ TrackdirBits reserved = (TrackdirBits)(ft.m_new_td_bits & (GetReservedTrackbits(ft.m_new_tile) * 0x101));
+
+ /* No reservation --> path end found */
+ if (reserved == TRACKDIR_BIT_NONE) break;
+
+ /* Can't have more than one reserved trackdir */
+ Trackdir new_trackdir = FindFirstTrackdir(reserved);
+
+ /* One-way signal against us. The reservation can't be ours as it is not
+ * a safe position from our direction and we can never pass the signal. */
+ if (!ignore_oneway && HasOnewaySignalBlockingTrackdir(ft.m_new_tile, new_trackdir)) break;
+
+ tile = ft.m_new_tile;
+ trackdir = new_trackdir;
+
+ /* Depot tile? Can't continue. */
+ if (IsRailDepotTile(tile)) break;
+ /* Non-pbs signal? Reservation can't continue. */
+ if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) break;
+ }
+
+ return PBSTileInfo(tile, trackdir, false);
+}
+
+/**
+ * Follow a train reservation to the last tile.
+ *
+ * @param v the vehicle
+ * @returns The last tile of the reservation or the current train tile if no reservation present.
+ */
+PBSTileInfo FollowTrainReservation(const Vehicle *v)
+{
+ assert(v->type == VEH_TRAIN);
+
+ TileIndex tile = v->tile;
+ Trackdir trackdir = GetVehicleTrackdir(v);
+
+ if (IsRailDepotTile(tile) && !GetRailDepotReservation(tile)) return PBSTileInfo(tile, trackdir, false);
+
+ PBSTileInfo res = FollowReservation(v->owner, GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes, tile, trackdir);
+ res.okay = IsSafeWaitingPosition(v, res.tile, res.trackdir, true, _settings_game.pf.forbid_90_deg);
+ return res;
+}
+
+/** Callback for VehicleFromPos to find a train on a specific track. */
+static void *FindTrainOnTrackEnum(Vehicle *v, void *data)
+{
+ PBSTileInfo info = *(PBSTileInfo *)data;
+
+ if (v->type == VEH_TRAIN && !(v->vehstatus & VS_CRASHED) && HasBit((TrackBits)v->u.rail.track, TrackdirToTrack(info.trackdir))) return v;
+
+ return NULL;
+}
+
+/**
+ * Find the train which has reserved a specific path.
+ *
+ * @param tile A tile on the path.
+ * @param track A reserved track on the tile.
+ * @return The vehicle holding the reservation or NULL if the path is stray.
+ */
+Vehicle *GetTrainForReservation(TileIndex tile, Track track)
+{
+ assert(HasReservedTracks(tile, TrackToTrackBits(track)));
+ Trackdir trackdir = TrackToTrackdir(track);
+
+ RailTypes rts = GetRailTypeInfo(GetTileRailType(tile))->compatible_railtypes;
+
+ /* Follow the path from tile to both ends, one of the end tiles should
+ * have a train on it. We need FollowReservation to ignore one-way signals
+ * here, as one of the two search directions will be the "wrong" way. */
+ for (int i = 0; i < 2; ++i, trackdir = ReverseTrackdir(trackdir)) {
+ PBSTileInfo dest = FollowReservation(GetTileOwner(tile), rts, tile, trackdir, true);
+
+ Vehicle *v = (Vehicle *)VehicleFromPos(dest.tile, &dest, FindTrainOnTrackEnum);
+ if (v != NULL) return v->First();
+
+ /* Special case for stations: check the whole platform for a vehicle. */
+ if (IsRailwayStationTile(dest.tile)) {
+ TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(dest.trackdir)));
+ for (TileIndex st_tile = dest.tile + diff; IsCompatibleTrainStationTile(st_tile, dest.tile); st_tile += diff) {
+ v = (Vehicle *)VehicleFromPos(st_tile, &dest, FindTrainOnTrackEnum);
+ if (v != NULL) return v->First();
+ }
+ }
+
+ /* Special case for bridges/tunnels: check the other end as well. */
+ if (IsTileType(dest.tile, MP_TUNNELBRIDGE)) {
+ v = (Vehicle *)VehicleFromPos(GetOtherTunnelBridgeEnd(dest.tile), &dest, FindTrainOnTrackEnum);
+ if (v != NULL) return v->First();
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Determine whether a certain track on a tile is a safe position to end a path.
+ *
+ * @param v the vehicle to test for
+ * @param tile The tile
+ * @param trackdir The trackdir to test
+ * @param include_line_end Should end-of-line tiles be considered safe?
+ * @param forbid_90def Don't allow trains to make 90 degree turns
+ * @return True if it is a safe position
+ */
+bool IsSafeWaitingPosition(const Vehicle *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg)
+{
+ if (IsRailDepotTile(tile)) return true;
+
+ if (IsTileType(tile, MP_RAILWAY)) {
+ /* For non-pbs signals, stop on the signal tile. */
+ if (HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return true;
+ }
+
+ /* Check next tile. For perfomance reasons, we check for 90 degree turns ourself. */
+ CFollowTrackRail ft(v, GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes);
+
+ /* End of track? */
+ if (!ft.Follow(tile, trackdir)) {
+ /* Last tile of a terminus station is a safe position. */
+ if (include_line_end) return true;
+ }
+
+ /* Check for reachable tracks. */
+ ft.m_new_td_bits &= DiagdirReachesTrackdirs(ft.m_exitdir);
+ if (ft.m_new_td_bits == TRACKDIR_BIT_NONE) return include_line_end;
+ if (forbid_90deg) ft.m_new_td_bits &= ~TrackdirCrossesTrackdirs(trackdir);
+
+ if (ft.m_new_td_bits != TRACKDIR_BIT_NONE && KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE) {
+ /* PBS signal on next trackdir? Safe position. */
+ if (HasPbsSignalOnTrackdir(ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits))) return true;
+ }
+
+ return false;
+}
+
+/**
+ * Check if a safe position is free.
+ *
+ * @param v the vehicle to test for
+ * @param tile The tile
+ * @param trackdir The trackdir to test
+ * @param forbid_90def Don't allow trains to make 90 degree turns
+ * @return True if the position is free
+ */
+bool IsWaitingPositionFree(const Vehicle *v, TileIndex tile, Trackdir trackdir, bool forbid_90deg)
+{
+ Track track = TrackdirToTrack(trackdir);
+ TrackBits reserved = GetReservedTrackbits(tile);
+
+ /* Tile reserved? Can never be a free waiting position. */
+ if (TrackOverlapsTracks(reserved, track)) return false;
+
+ /* Not reserved and depot or not a pbs signal -> free. */
+ if (IsRailDepotTile(tile)) return true;
+ if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, track))) return true;
+
+ /* Check the next tile, if it's a PBS signal, it has to be free as well. */
+ CFollowTrackRail ft(v, GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes);
+
+ if (!ft.Follow(tile, trackdir)) return true;
+
+ /* Check for reachable tracks. */
+ ft.m_new_td_bits &= DiagdirReachesTrackdirs(ft.m_exitdir);
+ if (forbid_90deg) ft.m_new_td_bits &= ~TrackdirCrossesTrackdirs(trackdir);
+
+ return !HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits));
+}
diff --git a/src/pbs.h b/src/pbs.h
new file mode 100644
index 0000000..8a6a553
--- /dev/null
+++ b/src/pbs.h
@@ -0,0 +1,48 @@
+/* $Id$ */
+
+/** @file pbs.h PBS support routines */
+
+#ifndef PBS_H
+#define PBS_H
+
+#include "tile_type.h"
+#include "direction_type.h"
+#include "track_type.h"
+#include "vehicle_type.h"
+
+TrackBits GetReservedTrackbits(TileIndex t);
+
+void SetRailwayStationPlatformReservation(TileIndex start, DiagDirection dir, bool b);
+
+bool TryReserveRailTrack(TileIndex tile, Track t);
+void UnreserveRailTrack(TileIndex tile, Track t);
+
+/** This struct contains information about the end of a reserved path. */
+struct PBSTileInfo {
+ TileIndex tile; ///< Tile the path ends, INVALID_TILE if no valid path was found.
+ Trackdir trackdir; ///< The reserved trackdir on the tile.
+ bool okay; ///< True if tile is a safe wairing position, false otherwise.
+
+ PBSTileInfo() : tile(INVALID_TILE), trackdir(INVALID_TRACKDIR), okay(false) {}
+ PBSTileInfo(TileIndex _t, Trackdir _td, bool _okay) : tile(_t), trackdir(_td), okay(_okay) {}
+};
+
+PBSTileInfo FollowTrainReservation(const Vehicle *v);
+bool IsSafeWaitingPosition(const Vehicle *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg = false);
+bool IsWaitingPositionFree(const Vehicle *v, TileIndex tile, Trackdir trackdir, bool forbid_90deg = false);
+
+Vehicle *GetTrainForReservation(TileIndex tile, Track track);
+
+/**
+ * Check whether some of tracks is reserved on a tile.
+ *
+ * @param tile the tile
+ * @param tracks the tracks to test
+ * @return true if at least on of tracks is reserved
+ */
+static inline bool HasReservedTracks(TileIndex tile, TrackBits tracks)
+{
+ return (GetReservedTrackbits(tile) & tracks) != TRACK_BIT_NONE;
+}
+
+#endif /* PBS_H */
diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp
index 38e8cf7..a869e19 100644
--- a/src/rail_cmd.cpp
+++ b/src/rail_cmd.cpp
@@ -46,6 +46,8 @@
#include "functions.h"
#include "elrail_func.h"
#include "oldpool_func.h"
+#include "pbs.h"
+#include "core/smallvec_type.hpp"
#include "table/sprites.h"
#include "table/strings.h"
@@ -458,6 +460,8 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32
* so do not call GetTileOwner(tile) in any case here */
Owner owner = INVALID_OWNER;
+ Vehicle *v = NULL;
+
switch (GetTileType(tile)) {
case MP_ROAD: {
if (!IsLevelCrossing(tile) ||
@@ -468,6 +472,10 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32
}
if (flags & DC_EXEC) {
+ if (HasReservedTracks(tile, trackbit)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
owner = GetTileOwner(tile);
MakeRoadNormal(tile, GetCrossingRoadBits(tile), GetRoadTypes(tile), GetTownIndex(tile), GetRoadOwner(tile, ROADTYPE_ROAD), GetRoadOwner(tile, ROADTYPE_TRAM), GetRoadOwner(tile, ROADTYPE_HWAY));
}
@@ -492,6 +500,10 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32
cost.AddCost(DoCommand(tile, track, 0, flags, CMD_REMOVE_SIGNALS));
if (flags & DC_EXEC) {
+ if (HasReservedTracks(tile, trackbit)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
owner = GetTileOwner(tile);
present ^= trackbit;
if (present == 0) {
@@ -504,6 +516,7 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32
}
} else {
SetTrackBits(tile, present);
+ SetTrackReservation(tile, GetTrackReservation(tile) & present);
}
}
break;
@@ -530,6 +543,8 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32
AddTrackToSignalBuffer(tile, track, owner);
YapfNotifyTrackLayoutChange(tile, track);
}
+
+ if (v != NULL) TryPathReserve(v, true);
}
return cost;
@@ -793,8 +808,10 @@ CommandCost CmdBuildTrainDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p
* - p1 = (bit 0-2) - track-orientation, valid values: 0-5 (Track enum)
* - p1 = (bit 3) - 1 = override signal/semaphore, or pre/exit/combo signal or (for bit 7) toggle variant (CTRL-toggle)
* - p1 = (bit 4) - 0 = signals, 1 = semaphores
- * - p1 = (bit 5-6) - type of the signal, for valid values see enum SignalType in rail_map.h
- * - p1 = (bit 7) - convert the present signal type and variant
+ * - p1 = (bit 5-7) - type of the signal, for valid values see enum SignalType in rail_map.h
+ * - p1 = (bit 8) - convert the present signal type and variant
+ * - p1 = (bit 9-11)- start cycle from this signal type
+ * - p1 = (bit 12-14)-wrap around after this signal type
* @param p2 used for CmdBuildManySignals() to copy direction of first signal
* TODO: p2 should be replaced by two bits for "along" and "against" the track.
*/
@@ -803,10 +820,14 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, uint32 flags, uint32 p1, uint32
Track track = (Track)GB(p1, 0, 3);
bool ctrl_pressed = HasBit(p1, 3); // was the CTRL button pressed
SignalVariant sigvar = (ctrl_pressed ^ HasBit(p1, 4)) ? SIG_SEMAPHORE : SIG_ELECTRIC; // the signal variant of the new signal
- SignalType sigtype = (SignalType)GB(p1, 5, 2); // the signal type of the new signal
- bool convert_signal = HasBit(p1, 7); // convert button pressed
+ SignalType sigtype = (SignalType)GB(p1, 5, 3); // the signal type of the new signal
+ bool convert_signal = HasBit(p1, 8); // convert button pressed
+ SignalType cycle_start = (SignalType)GB(p1, 9, 3);
+ SignalType cycle_stop = (SignalType)GB(p1, 12, 3);
CommandCost cost;
+ if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
+
if (!ValParamTrackOrientation(track) || !IsTileType(tile, MP_RAILWAY) || !EnsureNoTrainOnTrack(tile, track))
return CMD_ERROR;
@@ -868,7 +889,7 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, uint32 flags, uint32 p1, uint32
if (p2 == 0) {
if (!HasSignalOnTrack(tile, track)) {
/* build new signals */
- SetPresentSignals(tile, GetPresentSignals(tile) | SignalOnTrack(track));
+ SetPresentSignals(tile, GetPresentSignals(tile) | (IsPbsSignal(sigtype) ? KillFirstBit(SignalOnTrack(track)) : SignalOnTrack(track)));
SetSignalType(tile, track, sigtype);
SetSignalVariant(tile, track, sigvar);
} else {
@@ -882,13 +903,21 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, uint32 flags, uint32 p1, uint32
/* convert the present signal to the chosen type and variant */
SetSignalType(tile, track, sigtype);
SetSignalVariant(tile, track, sigvar);
+ if (IsPbsSignal(sigtype) && (GetPresentSignals(tile) & SignalOnTrack(track)) == SignalOnTrack(track)) {
+ SetPresentSignals(tile, (GetPresentSignals(tile) & ~SignalOnTrack(track)) | KillFirstBit(SignalOnTrack(track)));
+ }
}
} else if (ctrl_pressed) {
- /* cycle between normal -> pre -> exit -> combo -> ... */
- sigtype = GetSignalType(tile, track);
+ /* cycle between cycle_start and cycle_end */
+ sigtype = (SignalType)(GetSignalType(tile, track) + 1);
- SetSignalType(tile, track, sigtype == SIGTYPE_COMBO ? SIGTYPE_NORMAL : (SignalType)(sigtype + 1));
+ if (sigtype < cycle_start || sigtype > cycle_stop) sigtype = cycle_start;
+
+ SetSignalType(tile, track, sigtype);
+ if (IsPbsSignal(sigtype) && (GetPresentSignals(tile) & SignalOnTrack(track)) == SignalOnTrack(track)) {
+ SetPresentSignals(tile, (GetPresentSignals(tile) & ~SignalOnTrack(track)) | KillFirstBit(SignalOnTrack(track)));
+ }
} else {
/* cycle the signal side: both -> left -> right -> both -> ... */
CycleSignalSide(tile, track);
@@ -899,8 +928,13 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, uint32 flags, uint32 p1, uint32
* direction of the first signal given as parameter by CmdBuildManySignals */
SetPresentSignals(tile, (GetPresentSignals(tile) & ~SignalOnTrack(track)) | (p2 & SignalOnTrack(track)));
SetSignalVariant(tile, track, sigvar);
+ SetSignalType(tile, track, sigtype);
}
+ if (IsPbsSignal(sigtype)) {
+ uint mask = GetPresentSignals(tile) & SignalOnTrack(track);
+ SetSignalStates(tile, (GetSignalStates(tile) & ~mask) | ((HasBit(GetTrackReservation(tile), track) ? (uint)-1 : 0) & mask));
+ }
MarkTileDirtyByTile(tile);
AddTrackToSignalBuffer(tile, track, _current_player);
YapfNotifyTrackLayoutChange(tile, track);
@@ -974,6 +1008,7 @@ static bool CheckSignalAutoFill(TileIndex &tile, Trackdir &trackdir, int &signal
* - p2 = (bit 4) - 0 = signals, 1 = semaphores
* - p2 = (bit 5) - 0 = build, 1 = remove signals
* - p2 = (bit 6) - 0 = selected stretch, 1 = auto fill
+ * - p2 = (bit 7- 9) - default signal type
* - p2 = (bit 24-31) - user defined signals_density
*/
static CommandCost CmdSignalTrackHelper(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
@@ -1011,6 +1046,9 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, uint32 flags, uint32 p1,
/* Autofill must start on a valid track to be able to avoid loops */
if (autofill && !HasTrack(tile, track)) return CMD_ERROR;
+ SignalType sigtype = (SignalType)GB(p2, 7, 3);
+ if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
+
/* copy the signal-style of the first rail-piece if existing */
if (HasSignals(tile)) {
signals = GetPresentSignals(tile) & SignalOnTrack(track);
@@ -1018,8 +1056,10 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, uint32 flags, uint32 p1,
/* copy signal/semaphores style (independent of CTRL) */
semaphores = GetSignalVariant(tile, track) != SIG_ELECTRIC;
+
+ sigtype = GetSignalType(tile, track);
} else { // no signals exist, drag a two-way signal stretch
- signals = SignalOnTrack(track);
+ signals = IsPbsSignal(sigtype) ? SignalAlongTrackdir(trackdir) : SignalOnTrack(track);
}
byte signal_dir = 0;
@@ -1041,6 +1081,7 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, uint32 flags, uint32 p1,
uint32 p1 = GB(TrackdirToTrack(trackdir), 0, 3);
SB(p1, 3, 1, mode);
SB(p1, 4, 1, semaphores);
+ SB(p1, 5, 3, sigtype);
/* Pick the correct orientation for the track direction */
signals = 0;
@@ -1090,6 +1131,7 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, uint32 flags, uint32 p1,
* - p2 = (bit 4) - 0 = signals, 1 = semaphores
* - p2 = (bit 5) - 0 = build, 1 = remove signals
* - p2 = (bit 6) - 0 = selected stretch, 1 = auto fill
+ * - p2 = (bit 7- 9) - default signal type
* - p2 = (bit 24-31) - user defined signals_density
* @see CmdSignalTrackHelper
*/
@@ -1153,6 +1195,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, uint32 flags, uint32 p1, uint3
* - p2 = (bit 4) - 0 = signals, 1 = semaphores
* - p2 = (bit 5) - 0 = build, 1 = remove signals
* - p2 = (bit 6) - 0 = selected stretch, 1 = auto fill
+ * - p2 = (bit 7- 9) - default signal type
* - p2 = (bit 24-31) - user defined signals_density
* @see CmdSignalTrackHelper
*/
@@ -1230,11 +1273,24 @@ CommandCost CmdConvertRail(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
/* Trying to convert other's rail */
if (!CheckTileOwnership(tile)) continue;
+ SmallVector vehicles_affected;
+
/* Vehicle on the tile when not converting Rail <-> ElRail
* Tunnels and bridges have special check later */
if (tt != MP_TUNNELBRIDGE) {
if (!IsCompatibleRail(type, totype) && !EnsureNoVehicleOnGround(tile)) continue;
if (flags & DC_EXEC) { // we can safely convert, too
+ TrackBits reserved = GetReservedTrackbits(tile);
+ Track track;
+ while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
+ Vehicle *v = GetTrainForReservation(tile, track);
+ if (v != NULL && !HasPowerOnRail(v->u.rail.railtype, totype)) {
+ /* No power on new rail type, reroute. */
+ FreeTrainTrackReservation(v);
+ *vehicles_affected.Append() = v;
+ }
+ }
+
SetRailType(tile, totype);
MarkTileDirtyByTile(tile);
/* update power of train engines on this tile */
@@ -1291,14 +1347,20 @@ CommandCost CmdConvertRail(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
GetVehicleTunnelBridge(tile, endtile) != NULL) continue;
if (flags & DC_EXEC) {
+ Track track = DiagDirToDiagTrack(GetTunnelBridgeDirection(tile));
+ if (GetTunnelBridgeReservation(tile)) {
+ Vehicle *v = GetTrainForReservation(tile, track);
+ if (v != NULL) {
+ FreeTrainTrackReservation(v);
+ *vehicles_affected.Append() = v;
+ }
+ }
SetRailType(tile, totype);
SetRailType(endtile, totype);
VehicleFromPos(tile, NULL, &UpdateTrainPowerProc);
VehicleFromPos(endtile, NULL, &UpdateTrainPowerProc);
- Track track = DiagDirToDiagTrack(GetTunnelBridgeDirection(tile));
-
YapfNotifyTrackLayoutChange(tile, track);
YapfNotifyTrackLayoutChange(endtile, track);
@@ -1324,6 +1386,10 @@ CommandCost CmdConvertRail(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
cost.AddCost(RailConvertCost(type, totype));
break;
}
+
+ for (uint i = 0; i < vehicles_affected.Length(); ++i) {
+ TryPathReserve(vehicles_affected[i], true);
+ }
}
}
@@ -1342,11 +1408,18 @@ static CommandCost RemoveTrainDepot(TileIndex tile, uint32 flags)
/* read variables before the depot is removed */
DiagDirection dir = GetRailDepotDirection(tile);
Owner owner = GetTileOwner(tile);
+ Vehicle *v = NULL;
+
+ if (GetDepotReservation(tile)) {
+ v = GetTrainForReservation(tile, DiagDirToDiagTrack(dir));
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
DoClearSquare(tile);
delete GetDepotByTile(tile);
AddSideToSignalBuffer(tile, dir, owner);
YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir));
+ if (v != NULL) TryPathReserve(v, true);
}
return CommandCost(EXPENSES_CONSTRUCTION, _price.remove_train_depot);
@@ -1452,7 +1525,7 @@ static void DrawSingleSignal(TileIndex tile, Track track, byte condition, uint i
sprite = SPR_ORIGINAL_SIGNALS_BASE + image + condition;
} else {
/* All other signals are picked from add on sprites. */
- sprite = SPR_SIGNALS_BASE + (type - 1) * 16 + variant * 64 + image + condition;
+ sprite = SPR_SIGNALS_BASE + (type - 1) * 16 + variant * 64 + image + condition + (type > SIGTYPE_LAST_NOPBS ? 64 : 0);
}
AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, GetSaveSlopeZ(x, y, track));
@@ -1688,6 +1761,29 @@ static void DrawTrackBits(TileInfo* ti, TrackBits track)
if (track & TRACK_BIT_RIGHT) DrawGroundSprite(rti->base_sprites.single_e, PAL_NONE);
}
+ /* PBS debugging, draw reserved tracks darker */
+ if (_settings_client.gui.show_track_reservation) {
+ TrackBits pbs = GetTrackReservation(ti->tile);
+ if (pbs & TRACK_BIT_X) {
+ if (ti->tileh == SLOPE_FLAT || ti->tileh == SLOPE_ELEVATED) {
+ DrawGroundSprite(rti->base_sprites.single_y, PALETTE_CRASH);
+ } else {
+ DrawGroundSprite(_track_sloped_sprites[ti->tileh - 1] + rti->base_sprites.single_sloped - 20, PALETTE_CRASH);
+ }
+ }
+ if (pbs & TRACK_BIT_Y) {
+ if (ti->tileh == SLOPE_FLAT || ti->tileh == SLOPE_ELEVATED) {
+ DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH);
+ } else {
+ DrawGroundSprite(_track_sloped_sprites[ti->tileh - 1] + rti->base_sprites.single_sloped - 20, PALETTE_CRASH);
+ }
+ }
+ if (pbs & TRACK_BIT_UPPER) AddSortableSpriteToDraw(rti->base_sprites.single_n, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + (ti->tileh & SLOPE_N ? 8 : 0));
+ if (pbs & TRACK_BIT_LOWER) AddSortableSpriteToDraw(rti->base_sprites.single_s, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + (ti->tileh & SLOPE_S ? 8 : 0));
+ if (pbs & TRACK_BIT_LEFT) AddSortableSpriteToDraw(rti->base_sprites.single_w, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + (ti->tileh & SLOPE_W ? 8 : 0));
+ if (pbs & TRACK_BIT_RIGHT) AddSortableSpriteToDraw(rti->base_sprites.single_e, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + (ti->tileh & SLOPE_E ? 8 : 0));
+ }
+
if (IsValidCorner(halftile_corner)) {
DrawFoundation(ti, HalftileFoundation(halftile_corner));
@@ -1702,6 +1798,11 @@ static void DrawTrackBits(TileInfo* ti, TrackBits track)
default: break;
}
DrawGroundSprite(image, pal, &(_halftile_sub_sprite[halftile_corner]));
+
+ if (_settings_client.gui.show_track_reservation && IsSteepSlope(ti->tileh) && HasReservedTracks(ti->tile, CornerToTrackBits(halftile_corner))) {
+ static const byte _corner_to_track_sprite[] = {3, 1, 2, 0};
+ AddSortableSpriteToDraw(_corner_to_track_sprite[halftile_corner] + rti->base_sprites.single_n, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + 16);
+ }
}
}
@@ -1846,6 +1947,11 @@ default_waypoint:
DrawGroundSprite(image, PAL_NONE);
+ /* PBS debugging, draw reserved tracks darker */
+ if (_settings_client.gui.show_track_reservation && GetWaypointReservation(ti->tile)) {
+ DrawGroundSprite(GetWaypointAxis(ti->tile) == AXIS_X ? rti->base_sprites.single_y : rti->base_sprites.single_x, PALETTE_CRASH);
+ }
+
if (HasCatenaryDrawn(GetRailType(ti->tile))) DrawCatenary(ti);
foreach_draw_tile_seq(dtss, dts->seq) {
@@ -2147,12 +2253,13 @@ static TrackStatus GetTileTrackStatus_Track(TileIndex tile, TransportType mode,
b &= a;
- /* When signals are not present (in neither
- * direction), we pretend them to be green. (So if
- * signals are only one way, the other way will
- * implicitely become `red' */
- if ((a & 0xC) == 0) b |= 0xC;
- if ((a & 0x3) == 0) b |= 0x3;
+ /* When signals are not present (in neither direction),
+ * we pretend them to be green. Otherwise, it depends on
+ * the signal type. For signals that are only active from
+ * one side, we set the missing signals explicitely to
+ * `green'. Otherwise, they implicitely become `red'. */
+ if (!IsOnewaySignal(tile, TRACK_UPPER) || (a & SignalOnTrack(TRACK_UPPER)) == 0) b |= ~a & SignalOnTrack(TRACK_UPPER);
+ if (!IsOnewaySignal(tile, TRACK_LOWER) || (a & SignalOnTrack(TRACK_LOWER)) == 0) b |= ~a & SignalOnTrack(TRACK_LOWER);
if ((b & 0x8) == 0) red_signals |= (TRACKDIR_BIT_LEFT_N | TRACKDIR_BIT_X_NE | TRACKDIR_BIT_Y_SE | TRACKDIR_BIT_UPPER_E);
if ((b & 0x4) == 0) red_signals |= (TRACKDIR_BIT_LEFT_S | TRACKDIR_BIT_X_SW | TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_UPPER_W);
@@ -2197,30 +2304,54 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
break;
case RAIL_TILE_SIGNALS: {
- const StringID signal_type[4][4] = {
+ const StringID signal_type[6][6] = {
{
STR_RAILROAD_TRACK_WITH_NORMAL_SIGNALS,
STR_RAILROAD_TRACK_WITH_NORMAL_PRESIGNALS,
STR_RAILROAD_TRACK_WITH_NORMAL_EXITSIGNALS,
- STR_RAILROAD_TRACK_WITH_NORMAL_COMBOSIGNALS
+ STR_RAILROAD_TRACK_WITH_NORMAL_COMBOSIGNALS,
+ STR_RAILROAD_TRACK_WITH_NORMAL_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_NORMAL_NOENTRYSIGNALS
},
{
STR_RAILROAD_TRACK_WITH_NORMAL_PRESIGNALS,
STR_RAILROAD_TRACK_WITH_PRESIGNALS,
STR_RAILROAD_TRACK_WITH_PRE_EXITSIGNALS,
- STR_RAILROAD_TRACK_WITH_PRE_COMBOSIGNALS
+ STR_RAILROAD_TRACK_WITH_PRE_COMBOSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PRE_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PRE_NOENTRYSIGNALS
},
{
STR_RAILROAD_TRACK_WITH_NORMAL_EXITSIGNALS,
STR_RAILROAD_TRACK_WITH_PRE_EXITSIGNALS,
STR_RAILROAD_TRACK_WITH_EXITSIGNALS,
- STR_RAILROAD_TRACK_WITH_EXIT_COMBOSIGNALS
+ STR_RAILROAD_TRACK_WITH_EXIT_COMBOSIGNALS,
+ STR_RAILROAD_TRACK_WITH_EXIT_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_EXIT_NOENTRYSIGNALS
},
{
STR_RAILROAD_TRACK_WITH_NORMAL_COMBOSIGNALS,
STR_RAILROAD_TRACK_WITH_PRE_COMBOSIGNALS,
STR_RAILROAD_TRACK_WITH_EXIT_COMBOSIGNALS,
- STR_RAILROAD_TRACK_WITH_COMBOSIGNALS
+ STR_RAILROAD_TRACK_WITH_COMBOSIGNALS,
+ STR_RAILROAD_TRACK_WITH_COMBO_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_COMBO_NOENTRYSIGNALS
+ },
+ {
+ STR_RAILROAD_TRACK_WITH_NORMAL_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PRE_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_EXIT_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_COMBO_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PBSSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PBS_NOENTRYSIGNALS
+ },
+ {
+ STR_RAILROAD_TRACK_WITH_NORMAL_NOENTRYSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PRE_NOENTRYSIGNALS,
+ STR_RAILROAD_TRACK_WITH_EXIT_NOENTRYSIGNALS,
+ STR_RAILROAD_TRACK_WITH_COMBO_NOENTRYSIGNALS,
+ STR_RAILROAD_TRACK_WITH_PBS_NOENTRYSIGNALS,
+ STR_RAILROAD_TRACK_WITH_NOENTRYSIGNALS
}
};
diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp
index 178287d..9b7b87d 100644
--- a/src/rail_gui.cpp
+++ b/src/rail_gui.cpp
@@ -51,6 +51,9 @@ static bool _convert_signal_button; ///< convert signal button in the s
static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI)
static SignalType _cur_signal_type; ///< set the signal type (for signal GUI)
+/* Map _patches.default_signal_type to the corresponding signal type */
+static const SignalType _default_signal_type[] = {SIGTYPE_NORMAL, SIGTYPE_PBS, SIGTYPE_PBS_ONEWAY};
+
static struct {
byte orientation; ///< Currently selected rail station orientation
byte numtracks; ///< Currently selected number of tracks in station (if not \c dragdrop )
@@ -216,6 +219,9 @@ static void GenericPlaceSignals(TileIndex tile)
} else {
const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
+ /* Map _patches.cycle_signal_types to the lower and upper allowed signal type. */
+ static const uint cycle_bounds[] = {SIGTYPE_NORMAL | (SIGTYPE_LAST_NOPBS << 3), SIGTYPE_PBS | (SIGTYPE_LAST << 3), SIGTYPE_NORMAL | (SIGTYPE_LAST << 3)};
+
/* various bitstuffed elements for CmdBuildSingleSignal() */
uint32 p1 = track;
@@ -223,13 +229,15 @@ static void GenericPlaceSignals(TileIndex tile)
/* signal GUI is used */
SB(p1, 3, 1, _ctrl_pressed);
SB(p1, 4, 1, _cur_signal_variant);
- SB(p1, 5, 2, _cur_signal_type);
- SB(p1, 7, 1, _convert_signal_button);
+ SB(p1, 5, 3, _cur_signal_type);
+ SB(p1, 8, 1, _convert_signal_button);
+ SB(p1, 9, 6, cycle_bounds[_settings_client.gui.cycle_signal_types]);
} else {
SB(p1, 3, 1, _ctrl_pressed);
SB(p1, 4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
- SB(p1, 5, 2, SIGTYPE_NORMAL);
- SB(p1, 7, 1, 0);
+ SB(p1, 5, 3, _default_signal_type[_settings_client.gui.default_signal_type]);
+ SB(p1, 8, 1, 0);
+ SB(p1, 9, 6, cycle_bounds[_settings_client.gui.cycle_signal_types]);
}
DoCommandP(tile, p1, 0, CcPlaySound1E, CMD_BUILD_SIGNALS |
@@ -547,11 +555,13 @@ static void HandleAutoSignalPlacement()
SB(p2, 3, 1, 0);
SB(p2, 4, 1, _cur_signal_variant);
SB(p2, 6, 1, _ctrl_pressed);
+ SB(p2, 7, 3, _cur_signal_type);
SB(p2, 24, 8, _settings_client.gui.drag_signals_density);
} else {
SB(p2, 3, 1, 0);
SB(p2, 4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
SB(p2, 6, 1, _ctrl_pressed);
+ SB(p2, 7, 3, _default_signal_type[_settings_client.gui.default_signal_type]);
SB(p2, 24, 8, _settings_client.gui.drag_signals_density);
}
@@ -1350,10 +1360,14 @@ enum BuildSignalWidgets {
BSW_SEMAPHORE_ENTRY,
BSW_SEMAPHORE_EXIT,
BSW_SEMAPHORE_COMBO,
+ BSW_SEMAPHORE_PBS,
+ BSW_SEMAPHORE_PBS_OWAY,
BSW_ELECTRIC_NORM,
BSW_ELECTRIC_ENTRY,
BSW_ELECTRIC_EXIT,
BSW_ELECTRIC_COMBO,
+ BSW_ELECTRIC_PBS,
+ BSW_ELECTRIC_PBS_OWAY,
BSW_CONVERT,
BSW_DRAG_SIGNALS_DENSITY,
BSW_DRAG_SIGNALS_DENSITY_DECREASE,
@@ -1400,10 +1414,14 @@ public:
this->DrawSignalSprite(BSW_SEMAPHORE_ENTRY, SPR_IMG_SIGNAL_SEMAPHORE_ENTRY, -1, 13); // xsize of sprite + 1 == 10
this->DrawSignalSprite(BSW_SEMAPHORE_EXIT, SPR_IMG_SIGNAL_SEMAPHORE_EXIT, 0, 12); // xsize of sprite + 1 == 9
this->DrawSignalSprite(BSW_SEMAPHORE_COMBO, SPR_IMG_SIGNAL_SEMAPHORE_COMBO, 0, 12); // xsize of sprite + 1 == 9
+ this->DrawSignalSprite(BSW_SEMAPHORE_PBS, SPR_IMG_SIGNAL_SEMAPHORE_PBS, 0, 12); // xsize of sprite + 1 == 9
+ this->DrawSignalSprite(BSW_SEMAPHORE_PBS_OWAY, SPR_IMG_SIGNAL_SEMAPHORE_PBSOWAY, -1, 13); // xsize of sprite + 1 == 10
this->DrawSignalSprite(BSW_ELECTRIC_NORM, SPR_IMG_SIGNAL_ELECTRIC_NORM, -1, 4);
this->DrawSignalSprite(BSW_ELECTRIC_ENTRY, SPR_IMG_SIGNAL_ELECTRIC_ENTRY, -2, 6);
this->DrawSignalSprite(BSW_ELECTRIC_EXIT, SPR_IMG_SIGNAL_ELECTRIC_EXIT, -2, 6);
this->DrawSignalSprite(BSW_ELECTRIC_COMBO, SPR_IMG_SIGNAL_ELECTRIC_COMBO, -2, 6);
+ this->DrawSignalSprite(BSW_ELECTRIC_PBS, SPR_IMG_SIGNAL_ELECTRIC_PBS, -1, 4);
+ this->DrawSignalSprite(BSW_ELECTRIC_PBS_OWAY,SPR_IMG_SIGNAL_ELECTRIC_OBSOWAY,-2, 6);
/* Draw dragging signal density value in the BSW_DRAG_SIGNALS_DENSITY widget */
SetDParam(0, _settings_client.gui.drag_signals_density);
@@ -1419,13 +1437,17 @@ public:
case BSW_SEMAPHORE_ENTRY:
case BSW_SEMAPHORE_EXIT:
case BSW_SEMAPHORE_COMBO:
+ case BSW_SEMAPHORE_PBS:
+ case BSW_SEMAPHORE_PBS_OWAY:
case BSW_ELECTRIC_NORM:
case BSW_ELECTRIC_ENTRY:
case BSW_ELECTRIC_EXIT:
case BSW_ELECTRIC_COMBO:
+ case BSW_ELECTRIC_PBS:
+ case BSW_ELECTRIC_PBS_OWAY:
this->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? BSW_ELECTRIC_NORM : BSW_SEMAPHORE_NORM) + _cur_signal_type);
- _cur_signal_type = (SignalType)((uint)((widget - BSW_SEMAPHORE_NORM) % (SIGTYPE_COMBO + 1)));
+ _cur_signal_type = (SignalType)((uint)((widget - BSW_SEMAPHORE_NORM) % (SIGTYPE_LAST + 1)));
_cur_signal_variant = widget >= BSW_ELECTRIC_NORM ? SIG_ELECTRIC : SIG_SEMAPHORE;
break;
@@ -1457,29 +1479,33 @@ public:
/** Widget definition of the build signal window */
static const Widget _signal_builder_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // BSW_CLOSEBOX
-{ WWT_CAPTION, RESIZE_NONE, 7, 11, 109, 0, 13, STR_SIGNAL_SELECTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, // BSW_CAPTION
+{ WWT_CAPTION, RESIZE_NONE, 7, 11, 152, 0, 13, STR_SIGNAL_SELECTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, // BSW_CAPTION
{ WWT_PANEL, RESIZE_NONE, 7, 0, 21, 14, 40, STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_NORM_TIP}, // BSW_SEMAPHORE_NORM
{ WWT_PANEL, RESIZE_NONE, 7, 22, 43, 14, 40, STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TIP}, // BSW_SEMAPHORE_ENTRY
{ WWT_PANEL, RESIZE_NONE, 7, 44, 65, 14, 40, STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TIP}, // BSW_SEMAPHORE_EXIT
{ WWT_PANEL, RESIZE_NONE, 7, 66, 87, 14, 40, STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TIP}, // BSW_SEMAPHORE_COMBO
+{ WWT_PANEL, RESIZE_NONE, 7, 88, 109, 14, 40, STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TIP}, // BSW_SEMAPHORE_PBS
+{ WWT_PANEL, RESIZE_NONE, 7, 109, 130, 14, 40, STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TIP},// BSW_SEMAPHORE_PBS_OWAY
{ WWT_PANEL, RESIZE_NONE, 7, 0, 21, 41, 67, STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TIP}, // BSW_ELECTRIC_NORM
{ WWT_PANEL, RESIZE_NONE, 7, 22, 43, 41, 67, STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TIP}, // BSW_ELECTRIC_ENTRY
{ WWT_PANEL, RESIZE_NONE, 7, 44, 65, 41, 67, STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TIP}, // BSW_ELECTRIC_EXIT
{ WWT_PANEL, RESIZE_NONE, 7, 66, 87, 41, 67, STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TIP}, // BSW_ELECTRIC_COMBO
+{ WWT_PANEL, RESIZE_NONE, 7, 88, 109, 41, 67, STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_TIP}, // BSW_ELECTRIC_PBS
+{ WWT_PANEL, RESIZE_NONE, 7, 109, 130, 41, 67, STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TIP},// BSW_ELECTRIC_PBS_OWAY
-{ WWT_IMGBTN, RESIZE_NONE, 7, 88, 109, 14, 40, SPR_IMG_SIGNAL_CONVERT, STR_SIGNAL_CONVERT_TIP}, // BSW_CONVERT
-{ WWT_PANEL, RESIZE_NONE, 7, 88, 109, 41, 67, STR_NULL, STR_DRAG_SIGNALS_DENSITY_TIP}, // BSW_DRAG_SIGNALS_DENSITY
-{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 90, 98, 54, 65, SPR_ARROW_LEFT, STR_DRAG_SIGNALS_DENSITY_DECREASE_TIP}, // BSW_DRAG_SIGNALS_DENSITY_DECREASE
-{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 99, 107, 54, 65, SPR_ARROW_RIGHT, STR_DRAG_SIGNALS_DENSITY_INCREASE_TIP}, // BSW_DRAG_SIGNALS_DENSITY_INCREASE
+{ WWT_IMGBTN, RESIZE_NONE, 7, 131, 152, 14, 40, SPR_IMG_SIGNAL_CONVERT, STR_SIGNAL_CONVERT_TIP}, // BSW_CONVERT
+{ WWT_PANEL, RESIZE_NONE, 7, 131, 152, 41, 67, STR_NULL, STR_DRAG_SIGNALS_DENSITY_TIP}, // BSW_DRAG_SIGNALS_DENSITY
+{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 133, 141, 54, 65, SPR_ARROW_LEFT, STR_DRAG_SIGNALS_DENSITY_DECREASE_TIP}, // BSW_DRAG_SIGNALS_DENSITY_DECREASE
+{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 142, 150, 54, 65, SPR_ARROW_RIGHT, STR_DRAG_SIGNALS_DENSITY_INCREASE_TIP}, // BSW_DRAG_SIGNALS_DENSITY_INCREASE
{ WIDGETS_END},
};
/** Signal selection window description */
static const WindowDesc _signal_builder_desc = {
- WDP_AUTO, WDP_AUTO, 110, 68, 110, 68,
+ WDP_AUTO, WDP_AUTO, 153, 68, 153, 68,
WC_BUILD_SIGNAL, WC_BUILD_TOOLBAR,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
_signal_builder_widgets,
@@ -1775,6 +1801,6 @@ void InitializeRailGUI()
SetDefaultRailGui();
_convert_signal_button = false;
- _cur_signal_type = SIGTYPE_NORMAL;
+ _cur_signal_type = _default_signal_type[_settings_client.gui.default_signal_type];
ResetSignalVariant();
}
diff --git a/src/rail_map.h b/src/rail_map.h
index 8c436d0..ff9fcbb 100644
--- a/src/rail_map.h
+++ b/src/rail_map.h
@@ -224,19 +224,141 @@ static inline WaypointID GetWaypointIndex(TileIndex t)
return (WaypointID)_m[t].m2;
}
+
+/**
+ * Returns the reserved track bits of the tile
+ * @pre IsPlainRailTile(t)
+ * @param t the tile to query
+ * @return the track bits
+ */
+static inline TrackBits GetTrackReservation(TileIndex t)
+{
+ assert(IsPlainRailTile(t));
+ byte track_b = GB(_m[t].m2, 8, 3);
+ Track track = (Track)(track_b - 1); // map array saves Track+1
+ if (track_b == 0) return TRACK_BIT_NONE;
+ return (TrackBits)(TrackToTrackBits(track) | (HasBit(_m[t].m2, 11) ? TrackToTrackBits(TrackToOppositeTrack(track)) : 0));
+}
+
+/**
+ * Sets the reserved track bits of the tile
+ * @pre IsPlainRailTile(t) && !TracksOverlap(b)
+ * @param t the tile to change
+ * @param b the track bits
+ */
+static inline void SetTrackReservation(TileIndex t, TrackBits b)
+{
+ assert(IsPlainRailTile(t));
+ assert(b != INVALID_TRACK_BIT);
+ assert(!TracksOverlap(b));
+ Track track = RemoveFirstTrack(&b);
+ SB(_m[t].m2, 8, 3, track == INVALID_TRACK ? 0 : track+1);
+ SB(_m[t].m2, 11, 1, (byte)(b != TRACK_BIT_NONE));
+}
+
+/**
+ * Try to reserve a specific track on a tile
+ * @pre IsPlainRailTile(t) && HasTrack(tile, t)
+ * @param tile the tile
+ * @param t the rack to reserve
+ * @return true if successful
+ */
+static inline bool TryReserveTrack(TileIndex tile, Track t)
+{
+ assert(HasTrack(tile, t));
+ TrackBits bits = TrackToTrackBits(t);
+ TrackBits res = GetTrackReservation(tile);
+ if ((res & bits) != TRACK_BIT_NONE) return false; // already reserved
+ res |= bits;
+ if (TracksOverlap(res)) return false; // crossing reservation present
+ SetTrackReservation(tile, res);
+ return true;
+}
+
+/**
+ * Lift the reservation of a specific track on a tile
+ * @pre IsPlainRailTile(t) && HasTrack(tile, t)
+ * @param tile the tile
+ * @param t the track to free
+ */
+static inline void UnreserveTrack(TileIndex tile, Track t)
+{
+ assert(HasTrack(tile, t));
+ TrackBits res = GetTrackReservation(tile);
+ res &= ~TrackToTrackBits(t);
+ SetTrackReservation(tile, res);
+}
+
+/**
+ * Get the reservation state of the waypoint or depot
+ * @note Works for both waypoints and rail depots
+ * @pre IsRailWaypoint(t) || IsRailDepot(t)
+ * @param t the waypoint/depot tile
+ * @return reservation state
+ */
+static inline bool GetWaypointReservation(TileIndex t)
+{
+ assert(IsRailWaypoint(t) || IsRailDepot(t));
+ return HasBit(_m[t].m5, 4);
+}
+
+/**
+ * Set the reservation state of the waypoint or depot
+ * @note Works for both waypoints and rail depots
+ * @pre IsRailWaypoint(t) || IsRailDepot(t)
+ * @param t the waypoint/depot tile
+ * @param b the reservation state
+ */
+static inline void SetWaypointReservation(TileIndex t, bool b)
+{
+ assert(IsRailWaypoint(t) || IsRailDepot(t));
+ SB(_m[t].m5, 4, 1, (byte)b);
+}
+
+#define GetDepotReservation GetWaypointReservation
+#define SetDepotReservation SetWaypointReservation
+
+/**
+ * Get the reserved track bits for a waypoint
+ * @pre IsRailWaypoint(t)
+ * @param t the tile
+ * @return reserved track bits
+ */
+static inline TrackBits GetRailWaypointReservation(TileIndex t)
+{
+ return GetWaypointReservation(t) ? GetRailWaypointBits(t) : TRACK_BIT_NONE;
+}
+
+/**
+ * Get the reserved track bits for a depot
+ * @pre IsRailDepot(t)
+ * @param t the tile
+ * @return reserved track bits
+ */
+static inline TrackBits GetRailDepotReservation(TileIndex t)
+{
+ return GetWaypointReservation(t) ? TrackToTrackBits(GetRailDepotTrack(t)) : TRACK_BIT_NONE;
+}
+
+
+static inline bool IsPbsSignal(SignalType s)
+{
+ return s == SIGTYPE_PBS || s == SIGTYPE_PBS_ONEWAY;
+}
+
static inline SignalType GetSignalType(TileIndex t, Track track)
{
assert(GetRailTileType(t) == RAIL_TILE_SIGNALS);
byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 4 : 0;
- return (SignalType)GB(_m[t].m2, pos, 2);
+ return (SignalType)GB(_m[t].m2, pos, 3);
}
static inline void SetSignalType(TileIndex t, Track track, SignalType s)
{
assert(GetRailTileType(t) == RAIL_TILE_SIGNALS);
byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 4 : 0;
- SB(_m[t].m2, pos, 2, s);
- if (track == INVALID_TRACK) SB(_m[t].m2, 4, 2, s);
+ SB(_m[t].m2, pos, 3, s);
+ if (track == INVALID_TRACK) SB(_m[t].m2, 4, 3, s);
}
static inline bool IsPresignalEntry(TileIndex t, Track track)
@@ -249,27 +371,33 @@ static inline bool IsPresignalExit(TileIndex t, Track track)
return GetSignalType(t, track) == SIGTYPE_EXIT || GetSignalType(t, track) == SIGTYPE_COMBO;
}
+/** One-way signals can't be passed the 'wrong' way. */
+static inline bool IsOnewaySignal(TileIndex t, Track track)
+{
+ return GetSignalType(t, track) != SIGTYPE_PBS;
+}
+
static inline void CycleSignalSide(TileIndex t, Track track)
{
byte sig;
byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 4 : 6;
sig = GB(_m[t].m3, pos, 2);
- if (--sig == 0) sig = 3;
+ if (--sig == 0) sig = IsPbsSignal(GetSignalType(t, track)) ? 2 : 3;
SB(_m[t].m3, pos, 2, sig);
}
static inline SignalVariant GetSignalVariant(TileIndex t, Track track)
{
- byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 6 : 2;
+ byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 7 : 3;
return (SignalVariant)GB(_m[t].m2, pos, 1);
}
static inline void SetSignalVariant(TileIndex t, Track track, SignalVariant v)
{
- byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 6 : 2;
+ byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 7 : 3;
SB(_m[t].m2, pos, 1, v);
- if (track == INVALID_TRACK) SB(_m[t].m2, 6, 1, v);
+ if (track == INVALID_TRACK) SB(_m[t].m2, 7, 1, v);
}
/** These are states in which a signal can be. Currently these are only two, so
@@ -396,6 +524,34 @@ static inline void SetSignalStateByTrackdir(TileIndex tile, Trackdir trackdir, S
}
}
+/**
+ * Is a pbs signal present along the trackdir?
+ * @param tile the tile to check
+ * @param td the trackdir to check
+ */
+static inline bool HasPbsSignalOnTrackdir(TileIndex tile, Trackdir td)
+{
+ return
+ IsTileType(tile, MP_RAILWAY) &&
+ HasSignalOnTrackdir(tile, td) &&
+ IsPbsSignal(GetSignalType(tile, TrackdirToTrack(td)));
+}
+
+/**
+ * Is a one-way signal blocking the trackdir? A one-way signal on the
+ * trackdir against will block, but signals on both trackdirs won't.
+ * @param tile the tile to check
+ * @param td the trackdir to check
+ */
+static inline bool HasOnewaySignalBlockingTrackdir(TileIndex tile, Trackdir td)
+{
+ return
+ IsTileType(tile, MP_RAILWAY) &&
+ HasSignalOnTrackdir(tile, ReverseTrackdir(td)) &&
+ !HasSignalOnTrackdir(tile, td) &&
+ IsOnewaySignal(tile, TrackdirToTrack(td));
+}
+
/**
* Return the rail type of tile, or INVALID_RAILTYPE if this is no rail tile.
diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp
index 8af34a4..ba06817 100644
--- a/src/road_cmd.cpp
+++ b/src/road_cmd.cpp
@@ -344,7 +344,10 @@ static CommandCost RemoveRoad(TileIndex tile, uint32 flags, RoadBits pieces, Roa
if (flags & DC_EXEC) {
RoadTypes rts = GetRoadTypes(tile) & ComplementRoadTypes(RoadTypeToRoadTypes(rt));
if (rts == ROADTYPES_NONE) {
- MakeRailNormal(tile, GetTileOwner(tile), GetCrossingRailBits(tile), GetRailType(tile));
+ TrackBits tracks = GetCrossingRailBits(tile);
+ bool reserved = GetCrossingReservation(tile);
+ MakeRailNormal(tile, GetTileOwner(tile), tracks, GetRailType(tile));
+ if (reserved) SetTrackReservation(tile, tracks);
} else {
SetRoadTypes(tile, rts);
}
@@ -562,7 +565,9 @@ CommandCost CmdBuildRoad(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (flags & DC_EXEC) {
YapfNotifyTrackLayoutChange(tile, FindFirstTrack(GetTrackBits(tile)));
/* Always add road to the roadtypes (can't draw without it) */
+ bool reserved = HasBit(GetTrackReservation(tile), AxisToTrack(OtherAxis(roaddir)));
MakeRoadCrossing(tile, _current_player, _current_player, _current_player, GetTileOwner(tile), roaddir, GetRailType(tile), RoadTypeToRoadTypes(rt) | ROADTYPES_ROAD, p2);
+ SetCrossingReservation(tile, reserved);
UpdateLevelCrossing(tile, false);
MarkTileDirtyByTile(tile);
}
@@ -1176,6 +1181,12 @@ static void DrawTile_Road(TileInfo *ti)
}
DrawGroundSprite(image, pal);
+
+ /* PBS debugging, draw reserved tracks darker */
+ if (_settings_client.gui.show_track_reservation && GetCrossingReservation(ti->tile)) {
+ DrawGroundSprite(GetCrossingRoadAxis(ti->tile) == AXIS_Y ? GetRailTypeInfo(GetRailType(ti->tile))->base_sprites.single_y : GetRailTypeInfo(GetRailType(ti->tile))->base_sprites.single_x, PALETTE_CRASH);
+ }
+
if (HasTileRoadType(ti->tile, ROADTYPE_TRAM)) {
DrawGroundSprite(SPR_TRAMWAY_OVERLAY + (GetCrossingRoadAxis(ti->tile) ^ 1), pal);
DrawTramCatenary(ti, GetCrossingRoadBits(ti->tile));
diff --git a/src/road_map.h b/src/road_map.h
index 756e3fc..b8a7b2f 100644
--- a/src/road_map.h
+++ b/src/road_map.h
@@ -253,6 +253,43 @@ static inline TrackBits GetCrossingRailBits(TileIndex tile)
return AxisToTrackBits(GetCrossingRailAxis(tile));
}
+
+/**
+ * Get the reservation state of the rail crossing
+ * @pre IsLevelCrossingTile(t)
+ * @param t the crossing tile
+ * @return reservation state
+ */
+static inline bool GetCrossingReservation(TileIndex t)
+{
+ assert(IsLevelCrossingTile(t));
+ return HasBit(_m[t].m5, 4);
+}
+
+/**
+ * Set the reservation state of the rail crossing
+ * @note Works for both waypoints and rail depots
+ * @pre IsLevelCrossingTile(t)
+ * @param t the crossing tile
+ * @param b the reservation state
+ */
+static inline void SetCrossingReservation(TileIndex t, bool b)
+{
+ assert(IsLevelCrossingTile(t));
+ SB(_m[t].m5, 4, 1, (byte)b & 1);
+}
+
+/**
+ * Get the reserved track bits for a rail crossing
+ * @pre IsLevelCrossingTile(t)
+ * @param t the tile
+ * @return reserved track bits
+ */
+static inline TrackBits GetRailCrossingReservation(TileIndex t)
+{
+ return GetCrossingReservation(t) ? GetCrossingRailBits(t) : TRACK_BIT_NONE;
+}
+
static inline bool IsCrossingBarred(TileIndex t)
{
assert(IsLevelCrossing(t));
diff --git a/src/saveload.cpp b/src/saveload.cpp
index 640ebcc..a05302a 100644
--- a/src/saveload.cpp
+++ b/src/saveload.cpp
@@ -36,7 +36,7 @@
#include "table/strings.h"
-extern const uint16 SAVEGAME_VERSION = 99;
+extern const uint16 SAVEGAME_VERSION = 100;
SavegameType _savegame_type; ///< type of savegame we are loading
diff --git a/src/settings.cpp b/src/settings.cpp
index 32eb487..61b511b 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -1690,6 +1690,10 @@ const SettingDesc _patch_settings[] = {
SDT_VAR(GameSettings, pf.wait_twoway_signal, SLE_UINT8, 0, 0, 41, 2, 100, 0, STR_NULL, NULL),
SDT_CONDLISTO(GameSettings, economy.town_noise_population, 3, SLE_UINT16, 96, SL_MAX_VERSION, 0,D0, "800,2000,4000", STR_NULL, NULL, CheckNoiseToleranceLevel),
+ SDT_CONDVAR(GameSettings, pf.wait_for_pbs_path, SLE_UINT8,100, SL_MAX_VERSION, 0, 0, 30, 2, 255, 0, STR_NULL, NULL),
+ SDT_CONDBOOL(GameSettings, pf.reserve_paths, 100, SL_MAX_VERSION, 0, 0, false, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.path_backoff_interval, SLE_UINT8,100, SL_MAX_VERSION, 0, 0, 20, 1, 255, 0, STR_NULL, NULL),
+
SDT_VAR(GameSettings, pf.opf.pf_maxlength, SLE_UINT16, 0, 0, 4096, 64, 65535, 0, STR_NULL, NULL),
SDT_VAR(GameSettings, pf.opf.pf_maxdepth, SLE_UINT8, 0, 0, 48, 4, 255, 0, STR_NULL, NULL),
@@ -1701,6 +1705,8 @@ const SettingDesc _patch_settings[] = {
SDT_VAR(GameSettings, pf.npf.npf_rail_slope_penalty, SLE_UINT, 0, 0, ( 1 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL),
SDT_VAR(GameSettings, pf.npf.npf_rail_curve_penalty, SLE_UINT, 0, 0, 1, 0, 100000, 0, STR_NULL, NULL),
SDT_VAR(GameSettings, pf.npf.npf_rail_depot_reverse_penalty, SLE_UINT, 0, 0, ( 50 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.npf.npf_rail_pbs_cross_penalty, SLE_UINT,100, SL_MAX_VERSION, 0, 0, ( 3 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.npf.npf_rail_pbs_signal_back_penalty, SLE_UINT,100, SL_MAX_VERSION, 0, 0, ( 15 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL),
SDT_VAR(GameSettings, pf.npf.npf_buoy_penalty, SLE_UINT, 0, 0, ( 2 * NPF_TILE_LENGTH), 0, 100000, 0, STR_NULL, NULL),
SDT_VAR(GameSettings, pf.npf.npf_water_curve_penalty, SLE_UINT, 0, 0, (NPF_TILE_LENGTH / 4), 0, 100000, 0, STR_NULL, NULL),
SDT_VAR(GameSettings, pf.npf.npf_road_curve_penalty, SLE_UINT, 0, 0, 1, 0, 100000, 0, STR_NULL, NULL),
@@ -1715,9 +1721,9 @@ const SettingDesc _patch_settings[] = {
SDT_CONDVAR(GameSettings, pf.yapf.rail_firstred_exit_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 100 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_lastred_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 10 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_lastred_exit_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 100 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
- SDT_CONDVAR(GameSettings, pf.yapf.rail_station_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 30 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.yapf.rail_station_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 10 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_slope_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 2 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
- SDT_CONDVAR(GameSettings, pf.yapf.rail_curve45_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 1 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.yapf.rail_curve45_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 3 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_curve90_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 6 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_depot_reverse_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 50 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_crossing_penalty, SLE_UINT, 28, SL_MAX_VERSION, 0, 0, 3 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
@@ -1725,6 +1731,10 @@ const SettingDesc _patch_settings[] = {
SDT_CONDVAR(GameSettings, pf.yapf.rail_look_ahead_signal_p0, SLE_INT, 28, SL_MAX_VERSION, 0, 0, 500, -1000000, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_look_ahead_signal_p1, SLE_INT, 28, SL_MAX_VERSION, 0, 0, -100, -1000000, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_look_ahead_signal_p2, SLE_INT, 28, SL_MAX_VERSION, 0, 0, 5, -1000000, 1000000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.yapf.rail_pbs_cross_penalty, SLE_UINT,100, SL_MAX_VERSION, 0, 0, 3 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.yapf.rail_pbs_station_penalty, SLE_UINT,100, SL_MAX_VERSION, 0, 0, 8 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.yapf.rail_pbs_signal_back_penalty, SLE_UINT,100, SL_MAX_VERSION, 0, 0, 15 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
+ SDT_CONDVAR(GameSettings, pf.yapf.rail_doubleslip_penalty, SLE_UINT,100, SL_MAX_VERSION, 0, 0, 1 * YAPF_TILE_LENGTH, 0, 1000000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_longer_platform_penalty, SLE_UINT, 33, SL_MAX_VERSION, 0, 0, 8 * YAPF_TILE_LENGTH, 0, 20000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_longer_platform_per_tile_penalty, SLE_UINT, 33, SL_MAX_VERSION, 0, 0, 0 * YAPF_TILE_LENGTH, 0, 20000, 0, STR_NULL, NULL),
SDT_CONDVAR(GameSettings, pf.yapf.rail_shorter_platform_penalty, SLE_UINT, 33, SL_MAX_VERSION, 0, 0, 40 * YAPF_TILE_LENGTH, 0, 20000, 0, STR_NULL, NULL),
@@ -1789,6 +1799,9 @@ const SettingDesc _patch_settings[] = {
SDTC_BOOL(gui.bridge_pillars, S, 0, true, STR_NULL, NULL),
SDTC_BOOL(gui.auto_euro, S, 0, true, STR_NULL, NULL),
SDTC_VAR(gui.news_message_timeout, SLE_UINT8, S, 0, 2, 1, 255, 0, STR_NULL, NULL),
+ SDTC_BOOL(gui.show_track_reservation, S, 0, false, STR_CONFIG_PATCHES_SHOW_TRACK_RESERVATION, RedrawScreen),
+ SDTC_VAR(gui.default_signal_type, SLE_UINT8, S, MS, 0, 0, 2, 1, STR_CONFIG_PATCHES_DEFAULT_SIGNAL_TYPE, NULL),
+ SDTC_VAR(gui.cycle_signal_types, SLE_UINT8, S, MS, 0, 0, 2, 1, STR_CONFIG_PATCHES_CYCLE_SIGNAL_TYPES, NULL),
#ifdef ENABLE_NETWORK
SDTC_VAR(network.sync_freq, SLE_UINT16,C|S,NO, 100, 0, 100, 0, STR_NULL, NULL),
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index b6ddb42..9ad6ba0 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -613,6 +613,7 @@ static const char *_patches_ui[] = {
"gui.timetable_in_ticks",
"gui.default_rail_type",
"gui.always_build_infrastructure",
+ "gui.show_track_reservation",
};
static const char *_patches_construction[] = {
@@ -626,6 +627,8 @@ static const char *_patches_construction[] = {
"gui.drag_signals_density",
"game_creation.oil_refinery_limit",
"gui.semaphore_build_before",
+ "gui.default_signal_type",
+ "gui.cycle_signal_types",
};
static const char *_patches_stations[] = {
diff --git a/src/settings_type.h b/src/settings_type.h
index 0926db6..c7341cd 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -79,6 +79,9 @@ struct GUISettings {
int16 autorenew_months; ///< how many months from EOL of vehicles should autorenew trigger for new companies?
int32 autorenew_money; ///< how much money before autorenewing for new companies?
byte news_message_timeout; ///< how much longer than the news message "age" should we keep the message in the history
+ bool show_track_reservation; ///< highlight reserved tracks.
+ uint8 default_signal_type; ///< the signal type to build by default.
+ uint8 cycle_signal_types; ///< what signal types to cycle with the build signal tool.
};
/** Settings related to currency/unit systems. */
@@ -182,6 +185,8 @@ struct NPFSettings {
uint32 npf_rail_slope_penalty; ///< the penalty for sloping upwards
uint32 npf_rail_curve_penalty; ///< the penalty for curves
uint32 npf_rail_depot_reverse_penalty; ///< the penalty for reversing in depots
+ uint32 npf_rail_pbs_cross_penalty; ///< the penalty for crossing a reserved rail track
+ uint32 npf_rail_pbs_signal_back_penalty; ///< the penalty for passing a pbs signal from the backside
uint32 npf_buoy_penalty; ///< the penalty for going over (through) a buoy
uint32 npf_water_curve_penalty; ///< the penalty for curves
uint32 npf_road_curve_penalty; ///< the penalty for curves
@@ -215,6 +220,10 @@ struct YAPFSettings {
int32 rail_look_ahead_signal_p0; ///< constant in polynomial penalty function
int32 rail_look_ahead_signal_p1; ///< constant in polynomial penalty function
int32 rail_look_ahead_signal_p2; ///< constant in polynomial penalty function
+ uint32 rail_pbs_cross_penalty; ///< penalty for crossing a reserved tile
+ uint32 rail_pbs_station_penalty; ///< penalty for crossing a reserved station tile
+ uint32 rail_pbs_signal_back_penalty; ///< penalty for passing a pbs signal from the backside
+ uint32 rail_doubleslip_penalty; ///< penalty for passing a double slip switch
uint32 rail_longer_platform_penalty; ///< penalty for longer station platform than train
uint32 rail_longer_platform_per_tile_penalty; ///< penalty for longer station platform than train (per tile)
@@ -235,6 +244,10 @@ struct PathfinderSettings {
byte wait_oneway_signal; ///< waitingtime in days before a oneway signal
byte wait_twoway_signal; ///< waitingtime in days before a twoway signal
+ bool reserve_paths; ///< always reserve paths regardless of signal type.
+ byte wait_for_pbs_path; ///< how long to wait for a path reservation.
+ byte path_backoff_interval; ///< ticks between checks for a free path.
+
OPFSettings opf; ///< pathfinder settings for the old pathfinder
NPFSettings npf; ///< pathfinder settings for the new pathfinder
YAPFSettings yapf; ///< pathfinder settings for the yet another pathfinder
diff --git a/src/signal.cpp b/src/signal.cpp
index f0622b7..ca6ba79 100644
--- a/src/signal.cpp
+++ b/src/signal.cpp
@@ -256,6 +256,7 @@ enum SigFlags {
SF_GREEN = 1 << 3, ///< green exitsignal found
SF_GREEN2 = 1 << 4, ///< two or more green exits found
SF_FULL = 1 << 5, ///< some of buffers was full, do not continue
+ SF_PBS = 1 << 6, ///< pbs signal found
};
DECLARE_ENUM_AS_BIT_SET(SigFlags)
@@ -323,13 +324,19 @@ static SigFlags ExploreSegment(Owner owner)
Trackdir trackdir = (Trackdir)FindFirstBit((tracks * 0x101) & _enterdir_to_trackdirbits[enterdir]);
Trackdir reversedir = ReverseTrackdir(trackdir);
/* add (tile, reversetrackdir) to 'to-be-updated' set when there is
- * ANY signal in REVERSE direction
+ * ANY conventional signal in REVERSE direction
* (if it is a presignal EXIT and it changes, it will be added to 'to-be-done' set later) */
if (HasSignalOnTrackdir(tile, reversedir)) {
- if (!_tbuset.Add(tile, reversedir)) return flags | SF_FULL;
+ if (IsPbsSignal(sig)) {
+ flags |= SF_PBS;
+ } else if (!_tbuset.Add(tile, reversedir)) {
+ return flags | SF_FULL;
+ }
}
+ if (HasSignalOnTrackdir(tile, trackdir) && !IsOnewaySignal(tile, track)) flags |= SF_PBS;
+
/* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */
- if (!(flags & SF_GREEN2) && (sig & SIGTYPE_EXIT) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit
+ if (!(flags & SF_GREEN2) && IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit
if (flags & SF_EXIT) flags |= SF_EXIT2; // found two (or more) exits
flags |= SF_EXIT; // found at least one exit - allow for compiler optimizations
if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit
@@ -337,6 +344,7 @@ static SigFlags ExploreSegment(Owner owner)
flags |= SF_GREEN;
}
}
+
continue;
}
}
@@ -434,13 +442,13 @@ static void UpdateSignalsAroundSegment(SigFlags flags)
newstate = SIGNAL_STATE_RED;
}
} else { // entry, at least one exit, no green exit
- if (sig & SIGTYPE_ENTRY && (flags & SF_EXIT && !(flags & SF_GREEN))) newstate = SIGNAL_STATE_RED;
+ if (IsPresignalEntry(tile, TrackdirToTrack(trackdir)) && flags & SF_EXIT && !(flags & SF_GREEN)) newstate = SIGNAL_STATE_RED;
}
}
/* only when the state changes */
if (newstate != GetSignalStateByTrackdir(tile, trackdir)) {
- if (sig & SIGTYPE_EXIT) {
+ if (IsPresignalExit(tile, TrackdirToTrack(trackdir))) {
/* for pre-signal exits, add block to the global set */
DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir));
_globset.Add(tile, exitdir); // do not check for full global set, first update all signals
@@ -533,8 +541,10 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
if (first) {
first = false;
- if ((flags & SF_TRAIN) || (flags & SF_EXIT && !(flags & SF_GREEN)) || (flags & SF_FULL)) {
- /* SIGSEG_FREE is set by default */
+ /* SIGSEG_FREE is set by default */
+ if (flags & SF_PBS) {
+ state = SIGSEG_PBS;
+ } else if (flags & SF_TRAIN || (flags & SF_EXIT && !(flags & SF_GREEN)) || flags & SF_FULL) {
state = SIGSEG_FULL;
}
}
diff --git a/src/signal_func.h b/src/signal_func.h
index 7dcf3fd..7e35168 100644
--- a/src/signal_func.h
+++ b/src/signal_func.h
@@ -45,6 +45,7 @@ static inline byte SignalOnTrack(Track track)
enum SigSegState {
SIGSEG_FREE, ///< Free and has no pre-signal exits or at least one green exit
SIGSEG_FULL, ///< Occupied by a train
+ SIGSEG_PBS, ///< Segment is a PBS segment
};
SigSegState UpdateSignalsOnSegment(TileIndex tile, DiagDirection side, Owner owner);
diff --git a/src/signal_type.h b/src/signal_type.h
index 5dccff6..0bcd107 100644
--- a/src/signal_type.h
+++ b/src/signal_type.h
@@ -14,10 +14,14 @@ enum SignalVariant {
/** Type of signal, i.e. how does the signal behave? */
enum SignalType {
- SIGTYPE_NORMAL = 0, ///< normal signal
- SIGTYPE_ENTRY = 1, ///< presignal block entry
- SIGTYPE_EXIT = 2, ///< presignal block exit
- SIGTYPE_COMBO = 3 ///< presignal inter-block
+ SIGTYPE_NORMAL = 0, ///< normal signal
+ SIGTYPE_ENTRY = 1, ///< presignal block entry
+ SIGTYPE_EXIT = 2, ///< presignal block exit
+ SIGTYPE_COMBO = 3, ///< presignal inter-block
+ SIGTYPE_PBS = 4, ///< normal pbs signal
+ SIGTYPE_PBS_ONEWAY = 5, ///< no-entry signal
+ SIGTYPE_LAST = SIGTYPE_PBS_ONEWAY,
+ SIGTYPE_LAST_NOPBS = SIGTYPE_COMBO
};
diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp
index 67afd5a..c635d9f 100644
--- a/src/station_cmd.cpp
+++ b/src/station_cmd.cpp
@@ -44,6 +44,7 @@
#include "animated_tile_func.h"
#include "elrail_func.h"
#include "newgrf.h"
+#include "core/smallvec_type.hpp"
#include "table/sprites.h"
#include "table/strings.h"
@@ -1019,11 +1020,20 @@ CommandCost CmdBuildRailroadStation(TileIndex tile_org, uint32 flags, uint32 p1,
numtracks_orig = numtracks;
+ SmallVector affected_vehicles;
do {
TileIndex tile = tile_org;
int w = plat_len;
do {
byte layout = *layout_ptr++;
+ if (IsRailwayStationTile(tile) && GetRailwayStationReservation(tile)) {
+ Vehicle *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile)));
+ if (v != NULL) {
+ FreeTrainTrackReservation(v);
+ *affected_vehicles.Append() = v;
+ }
+ }
+
MakeRailStation(tile, st->owner, st->index, axis, layout & ~1, (RailType)GB(p2, 0, 4));
SetCustomStationSpecIndex(tile, specindex);
SetStationTileRandomBits(tile, GB(Random(), 0, 4));
@@ -1048,6 +1058,10 @@ CommandCost CmdBuildRailroadStation(TileIndex tile_org, uint32 flags, uint32 p1,
tile_org += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta
} while (--numtracks);
+ for (uint i = 0; i < affected_vehicles.Length(); ++i) {
+ TryPathReserve(affected_vehicles[i], true);
+ }
+
st->MarkTilesDirty(false);
UpdateStationVirtCoordDirty(st);
UpdateStationAcceptance(st, false);
@@ -1173,6 +1187,12 @@ CommandCost CmdRemoveFromRailroadStation(TileIndex tile, uint32 flags, uint32 p1
uint specindex = GetCustomStationSpecIndex(tile2);
Track track = GetRailStationTrack(tile2);
Owner owner = GetTileOwner(tile2);
+ Vehicle *v = NULL;
+
+ if (GetRailwayStationReservation(tile2)) {
+ v = GetTrainForReservation(tile2, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
DoClearSquare(tile2);
st->rect.AfterRemoveTile(st, tile2);
@@ -1188,6 +1208,8 @@ CommandCost CmdRemoveFromRailroadStation(TileIndex tile, uint32 flags, uint32 p1
st->MarkTilesDirty(false);
UpdateStationSignCoord(st);
+ if (v != NULL) TryPathReserve(v, true);
+
/* if we deleted the whole station, delete the train facility. */
if (st->train_tile == 0) {
st->facilities &= ~FACIL_TRAIN;
@@ -1236,9 +1258,15 @@ static CommandCost RemoveRailroadStation(Station *st, TileIndex tile, uint32 fla
/* read variables before the station tile is removed */
Track track = GetRailStationTrack(tile);
Owner owner = GetTileOwner(tile); // _current_player can be OWNER_WATER
+ Vehicle *v = NULL;
+ if (GetRailwayStationReservation(tile)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
DoClearSquare(tile);
AddTrackToSignalBuffer(tile, track, owner);
YapfNotifyTrackLayoutChange(tile, track);
+ if (v != NULL) TryPathReserve(v, true);
}
}
tile += TileDiffXY(1, 0);
@@ -2215,6 +2243,12 @@ static void DrawTile_Station(TileInfo *ti)
image += total_offset;
}
DrawGroundSprite(image, HasBit(image, PALETTE_MODIFIER_COLOR) ? palette : PAL_NONE);
+
+ /* PBS debugging, draw reserved tracks darker */
+ if (_settings_client.gui.show_track_reservation && IsRailwayStation(ti->tile) && GetRailwayStationReservation(ti->tile)) {
+ const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
+ DrawGroundSprite(GetRailStationAxis(ti->tile) == AXIS_X ? rti->base_sprites.single_y : rti->base_sprites.single_x, PALETTE_CRASH);
+ }
}
if (IsRailwayStation(ti->tile) && HasCatenaryDrawn(GetRailType(ti->tile)) && IsStationTileElectrifiable(ti->tile)) DrawCatenary(ti);
diff --git a/src/station_map.h b/src/station_map.h
index 7422a32..d456972 100644
--- a/src/station_map.h
+++ b/src/station_map.h
@@ -204,6 +204,41 @@ static inline bool IsCompatibleTrainStationTile(TileIndex t1, TileIndex t2)
!IsStationTileBlocked(t1);
}
+/**
+ * Get the reservation state of the rail station
+ * @pre IsRailwayStationTile(t)
+ * @param t the station tile
+ * @return reservation state
+ */
+static inline bool GetRailwayStationReservation(TileIndex t)
+{
+ assert(IsRailwayStationTile(t));
+ return HasBit(_m[t].m6, 2);
+}
+
+/**
+ * Set the reservation state of the rail station
+ * @pre IsRailwayStationTile(t)
+ * @param t the station tile
+ * @param b the reservation state
+ */
+static inline void SetRailwayStationReservation(TileIndex t, bool b)
+{
+ assert(IsRailwayStationTile(t));
+ SB(_m[t].m6, 2, 1, (byte)b & 1);
+}
+
+/**
+ * Get the reserved track bits for a waypoint
+ * @pre IsRailwayStationTile(t)
+ * @param t the tile
+ * @return reserved track bits
+ */
+static inline TrackBits GetRailStationReservation(TileIndex t)
+{
+ return GetRailwayStationReservation(t) ? AxisToTrackBits(GetRailStationAxis(t)) : TRACK_BIT_NONE;
+}
+
static inline DiagDirection GetDockDirection(TileIndex t)
{
@@ -277,6 +312,7 @@ static inline void MakeRailStation(TileIndex t, Owner o, StationID sid, Axis a,
{
MakeStation(t, o, sid, STATION_RAIL, section + a);
SetRailType(t, rt);
+ SetRailwayStationReservation(t, false);
}
static inline void MakeRoadStop(TileIndex t, Owner o, StationID sid, RoadStopType rst, RoadTypes rt, DiagDirection d)
diff --git a/src/table/sprites.h b/src/table/sprites.h
index 678831f..c23d73f 100644
--- a/src/table/sprites.h
+++ b/src/table/sprites.h
@@ -1297,15 +1297,19 @@ enum Sprites {
SPR_IMG_RAIL_STATION = 1298,
SPR_IMG_RAIL_SIGNALS = 1291,
- SPR_IMG_SIGNAL_ELECTRIC_NORM = 1287,
- SPR_IMG_SIGNAL_ELECTRIC_ENTRY = SPR_SIGNALS_BASE + 12,
- SPR_IMG_SIGNAL_ELECTRIC_EXIT = SPR_SIGNALS_BASE + 28,
- SPR_IMG_SIGNAL_ELECTRIC_COMBO = SPR_SIGNALS_BASE + 44,
- SPR_IMG_SIGNAL_SEMAPHORE_NORM = SPR_SIGNALS_BASE + 60,
- SPR_IMG_SIGNAL_SEMAPHORE_ENTRY = SPR_SIGNALS_BASE + 76,
- SPR_IMG_SIGNAL_SEMAPHORE_EXIT = SPR_SIGNALS_BASE + 92,
- SPR_IMG_SIGNAL_SEMAPHORE_COMBO = SPR_SIGNALS_BASE + 108,
- SPR_IMG_SIGNAL_CONVERT = SPR_OPENTTD_BASE + 135,
+ SPR_IMG_SIGNAL_ELECTRIC_NORM = 1287,
+ SPR_IMG_SIGNAL_ELECTRIC_ENTRY = SPR_SIGNALS_BASE + 12,
+ SPR_IMG_SIGNAL_ELECTRIC_EXIT = SPR_SIGNALS_BASE + 28,
+ SPR_IMG_SIGNAL_ELECTRIC_COMBO = SPR_SIGNALS_BASE + 44,
+ SPR_IMG_SIGNAL_ELECTRIC_PBS = SPR_SIGNALS_BASE + 124,
+ SPR_IMG_SIGNAL_ELECTRIC_OBSOWAY = SPR_SIGNALS_BASE + 140,
+ SPR_IMG_SIGNAL_SEMAPHORE_NORM = SPR_SIGNALS_BASE + 60,
+ SPR_IMG_SIGNAL_SEMAPHORE_ENTRY = SPR_SIGNALS_BASE + 76,
+ SPR_IMG_SIGNAL_SEMAPHORE_EXIT = SPR_SIGNALS_BASE + 92,
+ SPR_IMG_SIGNAL_SEMAPHORE_COMBO = SPR_SIGNALS_BASE + 108,
+ SPR_IMG_SIGNAL_SEMAPHORE_PBS = SPR_SIGNALS_BASE + 188,
+ SPR_IMG_SIGNAL_SEMAPHORE_PBSOWAY = SPR_SIGNALS_BASE + 204,
+ SPR_IMG_SIGNAL_CONVERT = SPR_OPENTTD_BASE + 135,
SPR_IMG_TUNNEL_RAIL = 2430,
SPR_IMG_TUNNEL_MONO = 2431,
diff --git a/src/track_func.h b/src/track_func.h
index e659121..706de20 100644
--- a/src/track_func.h
+++ b/src/track_func.h
@@ -198,6 +198,21 @@ static inline bool IsValidTrackdir(Trackdir trackdir)
*/
/**
+ * Find the opposite track to a given track.
+ *
+ * TRACK_LOWER -> TRACK_UPPER and vice versa, likewise for left/right.
+ * TRACK_X is mapped to TRACK_Y and reversed.
+ *
+ * @param t the track to convert
+ * @return the opposite track
+ */
+static inline Track TrackToOppositeTrack(Track t)
+{
+ assert(t != INVALID_TRACK);
+ return (Track)(t ^ 1);
+}
+
+/**
* Maps a trackdir to the reverse trackdir.
*
* Returns the reverse trackdir of a Trackdir value. The reverse trackdir
@@ -575,6 +590,19 @@ static inline bool TracksOverlap(TrackBits bits)
}
/**
+ * Check if a given track is contained within or overlaps some other tracks.
+ *
+ * @param tracks Tracks to be testet against
+ * @param track The track to test
+ * @return true if the track is already in the tracks or overlaps the tracks.
+ */
+static inline bool TrackOverlapsTracks(TrackBits tracks, Track track)
+{
+ if (HasBit(tracks, track)) return true;
+ return TracksOverlap(tracks | TrackToTrackBits(track));
+}
+
+/**
* Checks whether the trackdir means that we are reversing.
* @param dir the trackdir to check
* @return true if it is a reversing road trackdir
diff --git a/src/train.h b/src/train.h
index 7f7cb61..27e2329 100644
--- a/src/train.h
+++ b/src/train.h
@@ -273,6 +273,9 @@ int CheckTrainStoppedInDepot(const Vehicle *v);
void UpdateTrainAcceleration(Vehicle* v);
void CheckTrainsLengths();
+void FreeTrainTrackReservation(const Vehicle *v, TileIndex origin = INVALID_TILE, Trackdir orig_td = INVALID_TRACKDIR);
+bool TryPathReserve(Vehicle *v, bool mark_as_stuck = false, bool first_tile_okay = false);
+
/**
* This class 'wraps' Vehicle; you do not actually instantiate this class.
* You create a Vehicle using AllocateVehicle, so it is added to the pool
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index 8cfa6a9..d2723d2 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -33,6 +33,7 @@
#include "newgrf_text.h"
#include "direction_func.h"
#include "yapf/yapf.h"
+#include "yapf/follow_track.hpp"
#include "cargotype.h"
#include "group.h"
#include "table/sprites.h"
@@ -53,13 +54,17 @@
#include "effectvehicle_func.h"
#include "gamelog.h"
#include "network/network.h"
+#include "pbs.h"
#include "table/strings.h"
#include "table/train_cmd.h"
+static Track ChooseTrainTrack(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *got_reservation, bool mark_stuck);
static bool TrainCheckIfLineEnds(Vehicle *v);
static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image);
static TileIndex TrainApproachingCrossingTile(const Vehicle *v);
+static void CheckIfTrainNeedsService(Vehicle *v);
+static void CheckNextTrainTile(Vehicle *v);
static const byte _vehicle_initial_x_fract[4] = {10, 8, 4, 8};
static const byte _vehicle_initial_y_fract[4] = { 8, 4, 8, 10};
@@ -1613,10 +1618,27 @@ static inline void SetLastSpeed(Vehicle *v, int spd)
}
}
-static void SwapTrainFlags(byte *swap_flag1, byte *swap_flag2)
+/** Mark a train as stuck and stop it if it isn't stopped right now. */
+static void MarkTrainAsStuck(Vehicle *v)
{
- byte flag1 = *swap_flag1;
- byte flag2 = *swap_flag2;
+ if (!HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) {
+ /* It is the first time the problem occured, set the "train stuck" flag. */
+ SetBit(v->u.rail.flags, VRF_TRAIN_STUCK);
+ v->load_unload_time_rem = 0;
+
+ /* Stop train */
+ v->cur_speed = 0;
+ v->subspeed = 0;
+ SetLastSpeed(v, 0);
+
+ InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH);
+ }
+}
+
+static void SwapTrainFlags(uint16 *swap_flag1, uint16 *swap_flag2)
+{
+ uint16 flag1 = *swap_flag1;
+ uint16 flag2 = *swap_flag2;
/* Clear the flags */
ClrBit(*swap_flag1, VRF_GOINGUP);
@@ -1750,8 +1772,8 @@ void UpdateLevelCrossing(TileIndex tile, bool sound)
{
assert(IsLevelCrossingTile(tile));
- /* train on crossing || train approaching crossing */
- bool new_state = VehicleFromPos(tile, NULL, &TrainOnTileEnum) || TrainApproachingCrossing(tile);
+ /* train on crossing || train approaching crossing || reserved */
+ bool new_state = VehicleFromPos(tile, NULL, &TrainOnTileEnum) || TrainApproachingCrossing(tile) || GetCrossingReservation(tile);
if (new_state != IsCrossingBarred(tile)) {
if (new_state && sound) {
@@ -1871,6 +1893,10 @@ static void ReverseTrainDirection(Vehicle *v)
InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
}
+ /* Clear path reservation in front. */
+ assert(IsFrontEngine(v));
+ FreeTrainTrackReservation(v);
+
/* Check if we were approaching a rail/road-crossing */
TileIndex crossing = TrainApproachingCrossingTile(v);
@@ -1907,6 +1933,27 @@ static void ReverseTrainDirection(Vehicle *v)
/* maybe we are approaching crossing now, after reversal */
crossing = TrainApproachingCrossingTile(v);
if (crossing != INVALID_TILE) MaybeBarCrossingWithSound(crossing);
+
+ /* If we are inside a depot after reversing, don't bother with path reserving. */
+ if (v->u.rail.track & TRACK_BIT_DEPOT) return;
+
+ /* TrainExitDir does not always produce the desired dir for depots and
+ * tunnels/bridges that is needed for UpdateSignalsOnSegment. */
+ DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track);
+ if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR;
+
+ if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) {
+ /* If we are currently on a tile with conventional signals, we can't treat the
+ * current tile as a safe tile or we would enter a PBS block without a reservation. */
+ bool first_tile_okay = !(IsTileType(v->tile, MP_RAILWAY)
+ && HasSignalOnTrackdir(v->tile, GetVehicleTrackdir(v))
+ && !IsPbsSignal(GetSignalType(v->tile, FindFirstTrack(v->u.rail.track))));
+
+ if (TryPathReserve(v, true, first_tile_okay)) {
+ /* Do a look-ahead now in case our current tile was already a safe tile. */
+ CheckNextTrainTile(v);
+ }
+ }
}
/** Reverse train.
@@ -2114,14 +2161,17 @@ static TrainFindDepotData FindClosestTrainDepot(Vehicle *v, int max_distance)
tfdd.best_length = UINT_MAX;
tfdd.reverse = false;
- TileIndex tile = v->tile;
- if (IsRailDepotTile(tile)) {
- tfdd.tile = tile;
+ PBSTileInfo origin = FollowTrainReservation(v);
+ if (IsRailDepotTile(origin.tile)) {
+ tfdd.tile = origin.tile;
tfdd.best_length = 0;
return tfdd;
}
- switch (_settings_game.pf.pathfinder_for_trains) {
+ uint8 pathfinder = _settings_game.pf.pathfinder_for_trains;
+ if ((_settings_game.pf.reserve_paths || HasReservedTracks(v->tile, v->u.rail.track)) && pathfinder == VPF_NTP) pathfinder = VPF_NPF;
+
+ switch (pathfinder) {
case VPF_YAPF: { /* YAPF */
bool found = YapfFindNearestRailDepotTwoWay(v, max_distance, NPF_INFINITE_PENALTY, &tfdd.tile, &tfdd.reverse);
tfdd.best_length = found ? max_distance / 2 : UINT_MAX; // some fake distance or NOT_FOUND
@@ -2150,12 +2200,12 @@ static TrainFindDepotData FindClosestTrainDepot(Vehicle *v, int max_distance)
case VPF_NTP: { /* NTP */
/* search in the forward direction first. */
DiagDirection i = TrainExitDir(v->direction, v->u.rail.track);
- NewTrainPathfind(tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd);
+ NewTrainPathfind(v->tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd);
if (tfdd.best_length == UINT_MAX){
tfdd.reverse = true;
/* search in backwards direction */
i = TrainExitDir(ReverseDir(v->direction), v->u.rail.track);
- NewTrainPathfind(tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd);
+ NewTrainPathfind(v->tile, 0, v->u.rail.compatible_railtypes, i, (NTPEnumProc*)NtpCallbFindDepot, &tfdd);
}
} break;
}
@@ -2303,6 +2353,44 @@ void Train::PlayLeaveStationSound() const
SndPlayVehicleFx(sfx[RailVehInfo(engtype)->engclass], this);
}
+/** Check if the train is on the last reserved tile and try to extend the path then. */
+static void CheckNextTrainTile(Vehicle *v)
+{
+ /* Don't do any look-ahead if path_backoff_interval is 255. */
+ if (_settings_game.pf.path_backoff_interval == 255) return;
+
+ /* Exit if we reached our destination or are inside a depot. */
+ if ((v->tile == v->dest_tile && !v->current_order.IsType(OT_GOTO_STATION)) || v->u.rail.track & TRACK_BIT_DEPOT) return;
+ /* Exit if we are on a station tile and are going to stop. */
+ if (IsRailwayStationTile(v->tile) && v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile))) return;
+ /* Exit if the current order doesn't have a destination, but the train has orders. */
+ if ((v->current_order.IsType(OT_NOTHING) || v->current_order.IsType(OT_LEAVESTATION)) && v->num_orders > 0) return;
+
+ Trackdir td = GetVehicleTrackdir(v);
+
+ /* On a tile with a red non-pbs signal, don't look ahead. */
+ if (IsTileType(v->tile, MP_RAILWAY) && HasSignalOnTrackdir(v->tile, td) &&
+ !IsPbsSignal(GetSignalType(v->tile, TrackdirToTrack(td))) &&
+ GetSignalStateByTrackdir(v->tile, td) == SIGNAL_STATE_RED) return;
+
+ CFollowTrackRail ft(v);
+ if (!ft.Follow(v->tile, td)) return;
+
+ if (!HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits))) {
+ /* Next tile is not reserved. */
+ if (KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE) {
+ if (HasPbsSignalOnTrackdir(ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits))) {
+ /* If the next tile is a PBS signal, try to make a reservation. */
+ TrackBits tracks = TrackdirBitsToTrackBits(ft.m_new_td_bits);
+ if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) {
+ tracks &= ~TrackCrossesTracks(TrackdirToTrack(ft.m_old_td));
+ }
+ ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, tracks, false, NULL, false);
+ }
+ }
+ }
+}
+
static bool CheckTrainStayInDepot(Vehicle *v)
{
/* bail out if not all wagons are in the same depot or not in a depot at all */
@@ -2317,7 +2405,10 @@ static bool CheckTrainStayInDepot(Vehicle *v)
return true;
}
+ SigSegState seg_state;
+
if (v->u.rail.force_proceed == 0) {
+ /* force proceed was not pressed */
if (++v->load_unload_time_rem < 37) {
InvalidateWindowClasses(WC_TRAINS_LIST);
return true;
@@ -2325,12 +2416,27 @@ static bool CheckTrainStayInDepot(Vehicle *v)
v->load_unload_time_rem = 0;
- if (UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner) == SIGSEG_FULL) {
+ seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner);
+ if (seg_state == SIGSEG_FULL || GetDepotReservation(v->tile)) {
+ /* Full and no PBS signal in block or depot reserved, can't exit. */
InvalidateWindowClasses(WC_TRAINS_LIST);
return true;
}
+ } else {
+ seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner);
}
+ /* Only leave when we can reserve a path to our destination. */
+ if (seg_state == SIGSEG_PBS && !TryPathReserve(v) && v->u.rail.force_proceed == 0) {
+ /* No path and no force proceed. */
+ InvalidateWindowClasses(WC_TRAINS_LIST);
+ MarkTrainAsStuck(v);
+ return true;
+ }
+
+ SetDepotReservation(v->tile, true);
+ if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile);
+
VehicleServiceInDepot(v);
InvalidateWindowClasses(WC_TRAINS_LIST);
v->PlayLeaveStationSound();
@@ -2351,6 +2457,85 @@ static bool CheckTrainStayInDepot(Vehicle *v)
return false;
}
+/** Clear the reservation of a tile that was just left by a wagon on track_dir. */
+static void ClearPathReservation(TileIndex tile, Trackdir track_dir)
+{
+ DiagDirection dir = TrackdirToExitdir(track_dir);
+
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ /* Are we just leaving a tunnel/bridge? */
+ if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) {
+ TileIndex end = GetOtherTunnelBridgeEnd(tile);
+
+ SetTunnelBridgeReservation(tile, false);
+ SetTunnelBridgeReservation(end, false);
+
+ if (_settings_client.gui.show_track_reservation) {
+ MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(end);
+ }
+ }
+ } else if (IsRailwayStationTile(tile)) {
+ TileIndex new_tile = TileAddByDiagDir(tile, dir);
+ /* If the new tile is not a further tile of the same station, we
+ * clear the reservation for the whole platform. */
+ if (!IsCompatibleTrainStationTile(new_tile, tile)) {
+ SetRailwayStationPlatformReservation(tile, ReverseDiagDir(dir), false);
+ }
+ } else {
+ /* Any other tile */
+ UnreserveRailTrack(tile, TrackdirToTrack(track_dir));
+ }
+}
+
+/** Free the reserved path in front of a vehicle. */
+void FreeTrainTrackReservation(const Vehicle *v, TileIndex origin, Trackdir orig_td)
+{
+ assert(IsFrontEngine(v));
+
+ TileIndex tile = origin != INVALID_TILE ? origin : v->tile;
+ Trackdir td = orig_td != INVALID_TRACKDIR ? orig_td : GetVehicleTrackdir(v);
+ bool free_tile = tile != v->tile || !(IsRailwayStationTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE));
+
+ /* Don't free reservation if it's not ours. */
+ if (TracksOverlap(GetReservedTrackbits(tile) | TrackToTrackBits(TrackdirToTrack(td)))) return;
+
+ CFollowTrackRail ft(v, GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes);
+ while (ft.Follow(tile, td)) {
+ tile = ft.m_new_tile;
+ TrackdirBits bits = (TrackdirBits)(ft.m_new_td_bits & (GetReservedTrackbits(tile) * 0x101));
+ td = RemoveFirstTrackdir(&bits);
+ assert(bits == TRACKDIR_BIT_NONE);
+
+ if (!IsValidTrackdir(td)) break;
+
+ if (IsTileType(tile, MP_RAILWAY)) {
+ if (HasSignalOnTrackdir(tile, td) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(td)))) {
+ /* Conventional signal along trackdir: remove reservation and stop. */
+ UnreserveRailTrack(tile, TrackdirToTrack(td));
+ break;
+ }
+ if (HasPbsSignalOnTrackdir(tile, td)) {
+ if (GetSignalStateByTrackdir(tile, td) == SIGNAL_STATE_RED) {
+ /* Red PBS signal? Can't be our reservation, would be green then. */
+ break;
+ } else {
+ /* Turn the signal back to red. */
+ SetSignalStateByTrackdir(tile, td, SIGNAL_STATE_RED);
+ MarkTileDirtyByTile(tile);
+ }
+ } else if (HasSignalOnTrackdir(tile, ReverseTrackdir(td)) && IsOnewaySignal(tile, TrackdirToTrack(td))) {
+ break;
+ }
+ }
+
+ /* Don't free first station/bridge/tunnel if we are on it. */
+ if (free_tile || (!ft.m_is_station && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(tile, td);
+
+ free_tile = true;
+ }
+}
+
/** Check for station tiles */
struct TrainTrackFollowerData {
TileIndex dest_coords;
@@ -2416,25 +2601,34 @@ static const byte _search_directions[6][4] = {
static const byte _pick_track_table[6] = {1, 3, 2, 2, 0, 0};
-/* choose a track */
-static Track ChooseTrainTrack(Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks)
+/**
+ * Perform pathfinding for a train.
+ *
+ * @param v The train
+ * @param tile The tile the train is about to enter
+ * @param enterdir Diagonal direction the train is coming from
+ * @param tracks Usable tracks on the new tile
+ * @param path_not_found [out] Set to false if the pathfinder couldn't find a way to the destination
+ * @param do_track_reservation
+ * @param dest [out]
+ * @return The best track the train should follow
+ */
+static Track DoTrainPathfind(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found, bool do_track_reservation, PBSTileInfo *dest)
{
Track best_track;
- /* pathfinders are able to tell that route was only 'guessed' */
- bool path_not_found = false;
#ifdef PF_BENCHMARK
TIC()
#endif
- assert((tracks & ~TRACK_BIT_MASK) == 0);
+ if (path_not_found) *path_not_found = false;
- /* quick return in case only one possible track is available */
- if (KillFirstBit(tracks) == TRACK_BIT_NONE) return FindFirstTrack(tracks);
+ uint8 pathfinder = _settings_game.pf.pathfinder_for_trains;
+ if (do_track_reservation && pathfinder == VPF_NTP) pathfinder = VPF_NPF;
- switch (_settings_game.pf.pathfinder_for_trains) {
+ switch (pathfinder) {
case VPF_YAPF: { /* YAPF */
- Trackdir trackdir = YapfChooseRailTrack(v, tile, enterdir, tracks, &path_not_found);
+ Trackdir trackdir = YapfChooseRailTrack(v, tile, enterdir, tracks, path_not_found, do_track_reservation, dest);
if (trackdir != INVALID_TRACKDIR) {
best_track = TrackdirToTrack(trackdir);
} else {
@@ -2446,12 +2640,18 @@ static Track ChooseTrainTrack(Vehicle *v, TileIndex tile, DiagDirection enterdir
void *perf = NpfBeginInterval();
NPFFindStationOrTileData fstd;
- NPFFillWithOrderData(&fstd, v);
- /* The enterdir for the new tile, is the exitdir for the old tile */
- Trackdir trackdir = GetVehicleTrackdir(v);
- assert(trackdir != INVALID_TRACKDIR);
+ NPFFillWithOrderData(&fstd, v, do_track_reservation);
- NPFFoundTargetData ftd = NPFRouteToStationOrTile(tile - TileOffsByDiagDir(enterdir), trackdir, true, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes);
+ PBSTileInfo origin = FollowTrainReservation(v);
+ assert(IsValidTrackdir(origin.trackdir));
+
+ NPFFoundTargetData ftd = NPFRouteToStationOrTile(origin.tile, origin.trackdir, true, &fstd, TRANSPORT_RAIL, 0, v->owner, v->u.rail.compatible_railtypes);
+
+ if (dest != NULL) {
+ dest->tile = ftd.node.tile;
+ dest->trackdir = (Trackdir)ftd.node.direction;
+ dest->okay = ftd.res_okay;
+ }
if (ftd.best_trackdir == INVALID_TRACKDIR) {
/* We are already at our target. Just do something
@@ -2463,7 +2663,7 @@ static Track ChooseTrainTrack(Vehicle *v, TileIndex tile, DiagDirection enterdir
* the direction we need to take to get there, if ftd.best_bird_dist is not 0,
* we did not find our target, but ftd.best_trackdir contains the direction leading
* to the tile closest to our target. */
- if (ftd.best_bird_dist != 0) path_not_found = true;
+ if (ftd.best_bird_dist != 0 && path_not_found != NULL) *path_not_found = true;
/* Discard enterdir information, making it a normal track */
best_track = TrackdirToTrack(ftd.best_trackdir);
}
@@ -2488,7 +2688,7 @@ static Track ChooseTrainTrack(Vehicle *v, TileIndex tile, DiagDirection enterdir
v->u.rail.compatible_railtypes, enterdir, (NTPEnumProc*)NtpCallbFindStation, &fd);
/* check whether the path was found or only 'guessed' */
- if (fd.best_bird_dist != 0) path_not_found = true;
+ if (fd.best_bird_dist != 0 && path_not_found != NULL) *path_not_found = true;
if (fd.best_track == INVALID_TRACKDIR) {
/* blaha */
@@ -2502,38 +2702,402 @@ static Track ChooseTrainTrack(Vehicle *v, TileIndex tile, DiagDirection enterdir
} break;
}
- /* handle "path not found" state */
- if (path_not_found) {
- /* PF didn't find the route */
- if (!HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) {
- /* it is first time the problem occurred, set the "path not found" flag */
- SetBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION);
- /* and notify user about the event */
- if (_settings_client.gui.lost_train_warn && v->owner == _local_player) {
- SetDParam(0, v->unitnumber);
- AddNewsItem(
- STR_TRAIN_IS_LOST,
- NS_ADVICE,
- v->index,
- 0);
- }
+#ifdef PF_BENCHMARK
+ TOC("PF time = ", 1)
+#endif
+
+ return best_track;
+}
+
+/**
+ * Extend a train path as far as possible. Stops on encountering a safe tile,
+ * another reservation or a track choice.
+ * @return INVALID_TILE indicates that the reservation failed.
+ */
+static PBSTileInfo ExtendTrainReservation(const Vehicle *v, TrackBits *new_tracks, DiagDirection *enterdir)
+{
+ bool no_90deg_turns = _settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg;
+ PBSTileInfo origin = FollowTrainReservation(v);
+
+ CFollowTrackRail ft(v);
+
+ TileIndex tile = origin.tile;
+ Trackdir cur_td = origin.trackdir;
+ while (ft.Follow(tile, cur_td)) {
+ if (KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE) {
+ /* Possible signal tile. */
+ if (HasOnewaySignalBlockingTrackdir(ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits))) break;
}
+
+ if (no_90deg_turns) {
+ ft.m_new_td_bits &= ~TrackdirCrossesTrackdirs(ft.m_old_td);
+ if (ft.m_new_td_bits == TRACKDIR_BIT_NONE) break;
+ }
+
+ /* Station, depot or waypoint are a possible target. */
+ bool target_seen = ft.m_is_station || (IsTileType(ft.m_new_tile, MP_RAILWAY) && !IsPlainRailTile(ft.m_new_tile));
+ if (target_seen || KillFirstBit(ft.m_new_td_bits) != TRACKDIR_BIT_NONE) {
+ /* Choice found or possible target encountered.
+ * On finding a possible target, we need to stop and let the pathfinder handle the
+ * remaining path. This is because we don't know if this target is in one of our
+ * orders, so we might cause pathfinding to fail later on if we find a choice.
+ * This failure would cause a bogous call to TryReserveSafePath which might reserve
+ * a wrong path not leading to our next destination. */
+ if (HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(TrackdirReachesTrackdirs(ft.m_old_td)))) break;
+
+ /* If we did skip some tiles, backtrack to the first skipped tile so the pathfinder
+ * actually starts its search at the first unreserved tile. */
+ if (ft.m_tiles_skipped != 0) ft.m_new_tile -= TileOffsByDiagDir(ft.m_exitdir) * ft.m_tiles_skipped;
+
+ /* Choice found, path valid but not okay. Save info about the choice tile as well. */
+ if (new_tracks) *new_tracks = TrackdirBitsToTrackBits(ft.m_new_td_bits);
+ if (enterdir) *enterdir = ft.m_exitdir;
+ return PBSTileInfo(ft.m_new_tile, ft.m_old_td, false);
+ }
+
+ tile = ft.m_new_tile;
+ cur_td = FindFirstTrackdir(ft.m_new_td_bits);
+
+ if (IsSafeWaitingPosition(v, tile, cur_td, true, no_90deg_turns)) {
+ bool wp_free = IsWaitingPositionFree(v, tile, cur_td, no_90deg_turns);
+ if (!(wp_free && TryReserveRailTrack(tile, TrackdirToTrack(cur_td)))) break;
+ /* Safe position is all good, path valid and okay. */
+ return PBSTileInfo(tile, cur_td, true);
+ }
+
+ if (!TryReserveRailTrack(tile, TrackdirToTrack(cur_td))) break;
+ }
+
+ if (ft.m_err == CFollowTrackRail::EC_OWNER && ft.m_err == CFollowTrackRail::EC_NO_WAY) {
+ /* End of line, path valid and okay. */
+ return PBSTileInfo(ft.m_old_tile, ft.m_old_td, true);
+ }
+
+ /* Sorry, can't reserve path, back out. */
+ tile = origin.tile;
+ cur_td = origin.trackdir;
+ TileIndex stopped = ft.m_old_tile;
+ Trackdir stopped_td = ft.m_old_td;
+ while (tile != stopped || cur_td != stopped_td) {
+ if (!ft.Follow(tile, cur_td)) break;
+
+ if (no_90deg_turns) {
+ ft.m_new_td_bits &= ~TrackdirCrossesTrackdirs(ft.m_old_td);
+ assert(ft.m_new_td_bits != TRACKDIR_BIT_NONE);
+ }
+ assert(KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE);
+
+ tile = ft.m_new_tile;
+ cur_td = FindFirstTrackdir(ft.m_new_td_bits);
+
+ UnreserveRailTrack(tile, TrackdirToTrack(cur_td));
+ }
+
+ /* Path invalid. */
+ return PBSTileInfo();
+}
+
+/**
+ * Try to reserve any path to a safe tile, ignoring the vehicle's destination.
+ * Safe tiles are tiles in front of a signal, depots and station tiles at end of line.
+ *
+ * @param v The vehicle.
+ * @param tile The tile the search should start from.
+ * @param td The trackdir the search should start from.
+ * @param override_tailtype Whether all physically compatible railtypes should be followed.
+ * @return True if a path to a safe stopping tile could be reserved.
+ */
+static bool TryReserveSafeTrack(const Vehicle* v, TileIndex tile, Trackdir td, bool override_tailtype)
+{
+ if (_settings_game.pf.pathfinder_for_trains == VPF_YAPF) {
+ return YapfRailFindNearestSafeTile(v, tile, td, override_tailtype);
} else {
- /* route found, is the train marked with "path not found" flag? */
- if (HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) {
- /* clear the flag as the PF's problem was solved */
- ClrBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION);
- /* can we also delete the "News" item somehow? */
+ return NPFRouteToSafeTile(v, tile, td, override_tailtype).res_okay;
+ }
+}
+
+/**
+ * Query the next order after a certain order index.
+ *
+ * @param v The vehicle
+ * @param order_index [in/out] Index of the current order, returns index of the chosen order
+ * @return Pointer to the order or NULL if the order chain was completely followed
+ * without finding a suitable order.
+ */
+static const Order* GetNextTrainOrder(const Vehicle *v, VehicleOrderID *order_index)
+{
+ ++(*order_index);
+
+ do {
+ /* Wrap around. */
+ if (*order_index >= v->num_orders) *order_index = 0;
+
+ Order *order = GetVehicleOrder(v, *order_index);
+ assert(order != NULL);
+
+ switch (order->GetType()) {
+ case OT_GOTO_DEPOT:
+ /* Skip service in depot orders when the train doesn't need service. */
+ if ((order->GetDepotOrderType() & ODTFB_SERVICE) && !v->NeedsServicing()) break;
+ case OT_GOTO_STATION:
+ case OT_GOTO_WAYPOINT:
+ return order;
+ case OT_CONDITIONAL: {
+ VehicleOrderID next = ProcessConditionalOrder(order, v);
+ if (next != INVALID_VEH_ORDER_ID) {
+ *order_index = next;
+ continue;
+ }
+ break;
+ }
+ default:
+ break;
}
+
+ ++(*order_index);
+ } while (*order_index != v->cur_order_index);
+
+ return NULL;
+}
+
+/* choose a track */
+static Track ChooseTrainTrack(Vehicle* v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *got_reservation, bool mark_stuck)
+{
+ Track best_track = INVALID_TRACK;
+ bool do_track_reservation = _settings_game.pf.reserve_paths || force_res;
+ bool changed_signal = false;
+
+ assert((tracks & ~TRACK_BIT_MASK) == 0);
+
+ if (got_reservation != NULL) *got_reservation = false;
+
+ /* Don't use tracks here as the setting to forbid 90 deg turns might have been switched between reservation and now. */
+ TrackBits res_tracks = (TrackBits)(GetReservedTrackbits(tile) & DiagdirReachesTracks(enterdir));
+ /* Do we have a suitable reserved track? */
+ if (res_tracks != TRACK_BIT_NONE) return FindFirstTrack(res_tracks);
+
+ /* Quick return in case only one possible track is available */
+ if (KillFirstBit(tracks) == TRACK_BIT_NONE) {
+ Track track = FindFirstTrack(tracks);
+ /* We need to check for signals only here, as a junction tile can't have signals. */
+ if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir))) {
+ do_track_reservation = true;
+ changed_signal = true;
+ SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir), SIGNAL_STATE_GREEN);
+ } else if (!do_track_reservation) {
+ return track;
+ }
+ best_track = track;
}
-#ifdef PF_BENCHMARK
- TOC("PF time = ", 1)
-#endif
+ PBSTileInfo res_dest(tile, INVALID_TRACKDIR, false);
+ DiagDirection dest_enterdir = enterdir;
+ if (do_track_reservation) {
+ /* Check if the train needs service here, so it has a chance to always find a depot.
+ * Also check if the current order is a service order so we don't reserve a path to
+ * the destination but instead to the next one if service isn't needed. */
+ CheckIfTrainNeedsService(v);
+ if (v->current_order.IsType(OT_DUMMY) || v->current_order.IsType(OT_CONDITIONAL) || v->current_order.IsType(OT_GOTO_DEPOT)) ProcessOrders(v);
+
+ res_dest = ExtendTrainReservation(v, &tracks, &dest_enterdir);
+ if (res_dest.tile == INVALID_TILE) {
+ /* Reservation failed? */
+ if (mark_stuck) MarkTrainAsStuck(v);
+ if (changed_signal) SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(best_track, enterdir), SIGNAL_STATE_RED);
+ return FindFirstTrack(tracks);
+ }
+ }
+
+ if (res_dest.tile != INVALID_TILE && !res_dest.okay) {
+ /* Pathfinders are able to tell that route was only 'guessed'. */
+ bool path_not_found = false;
+ TileIndex new_tile = res_dest.tile;
+
+ Track next_track = DoTrainPathfind(v, new_tile, dest_enterdir, tracks, &path_not_found, do_track_reservation, &res_dest);
+ if (new_tile == tile) best_track = next_track;
+
+ /* handle "path not found" state */
+ if (path_not_found) {
+ /* PF didn't find the route */
+ if (!HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) {
+ /* it is first time the problem occurred, set the "path not found" flag */
+ SetBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION);
+ /* and notify user about the event */
+ if (_settings_client.gui.lost_train_warn && v->owner == _local_player) {
+ SetDParam(0, v->unitnumber);
+ AddNewsItem(
+ STR_TRAIN_IS_LOST,
+ NS_ADVICE,
+ v->index,
+ 0);
+ }
+ }
+ } else {
+ /* route found, is the train marked with "path not found" flag? */
+ if (HasBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION)) {
+ /* clear the flag as the PF's problem was solved */
+ ClrBit(v->u.rail.flags, VRF_NO_PATH_TO_DESTINATION);
+ /* can we also delete the "News" item somehow? */
+ }
+ }
+ }
+
+ /* No track reservation requested -> finished. */
+ if (!do_track_reservation) return best_track;
+
+ /* A path was found, but could not be reserved. */
+ if (res_dest.tile != INVALID_TILE && !res_dest.okay) {
+ if (mark_stuck) MarkTrainAsStuck(v);
+ assert(IsFrontEngine(v));
+ FreeTrainTrackReservation(v);
+ return best_track;
+ }
+
+ /* No possible reservation target found, we are probably lost. */
+ if (res_dest.tile == INVALID_TILE) {
+ /* Try to find any safe destination. */
+ PBSTileInfo origin = FollowTrainReservation(v);
+ if (TryReserveSafeTrack(v, origin.tile, origin.trackdir, false)) {
+ TrackBits res = GetReservedTrackbits(tile) & DiagdirReachesTracks(enterdir);
+ best_track = FindFirstTrack(res);
+ TryReserveRailTrack(v->tile, TrackdirToTrack(GetVehicleTrackdir(v)));
+ if (got_reservation != NULL) *got_reservation = true;
+ if (changed_signal) MarkTileDirtyByTile(tile);
+ } else {
+ assert(IsFrontEngine(v));
+ FreeTrainTrackReservation(v);
+ if (mark_stuck) MarkTrainAsStuck(v);
+ }
+ return best_track;
+ }
+
+ if (got_reservation != NULL) *got_reservation = true;
+
+ /* Reservation target found and free, check if it is safe. */
+ Order cur_order = v->current_order;
+ TileIndex cur_dest_tile = v->dest_tile;
+ VehicleOrderID order_index = v->cur_order_index;
+ while (!IsSafeWaitingPosition(v, res_dest.tile, res_dest.trackdir, true, _settings_game.pf.forbid_90_deg)) {
+ /* Extend reservation until we have found a safe position. */
+ DiagDirection exitdir = TrackdirToExitdir(res_dest.trackdir);
+ TileIndex next_tile = TileAddByDiagDir(res_dest.tile, exitdir);
+ TrackBits reachable = TrackdirBitsToTrackBits((TrackdirBits)(GetTileTrackStatus(next_tile, TRANSPORT_RAIL, 0))) & DiagdirReachesTracks(exitdir);
+ if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) {
+ reachable &= ~TrackCrossesTracks(TrackdirToTrack(res_dest.trackdir));
+ }
+
+ /* Get next order with destination. */
+ const Order *order = GetNextTrainOrder(v, &order_index);
+ if (order != NULL) {
+ v->current_order = *order;
+ UpdateOrderDest(v, order);
+ PBSTileInfo cur_dest;
+ DoTrainPathfind(v, next_tile, exitdir, reachable, NULL, true, &cur_dest);
+ if (cur_dest.tile != INVALID_TILE) {
+ res_dest = cur_dest;
+ if (res_dest.okay) continue;
+ /* Path found, but could not be reserved. */
+ assert(IsFrontEngine(v));
+ FreeTrainTrackReservation(v);
+ if (mark_stuck) MarkTrainAsStuck(v);
+ if (got_reservation != NULL) *got_reservation = false;
+ changed_signal = false;
+ break;
+ }
+ }
+ /* No order or no safe position found, try any position. */
+ if (!TryReserveSafeTrack(v, res_dest.tile, res_dest.trackdir, true)) {
+ assert(IsFrontEngine(v));
+ FreeTrainTrackReservation(v);
+ if (mark_stuck) MarkTrainAsStuck(v);
+ if (got_reservation != NULL) *got_reservation = false;
+ changed_signal = false;
+ }
+ break;
+ }
+ v->current_order = cur_order;
+ v->dest_tile = cur_dest_tile;
+
+ TryReserveRailTrack(v->tile, TrackdirToTrack(GetVehicleTrackdir(v)));
+
+ if (changed_signal) MarkTileDirtyByTile(tile);
return best_track;
}
+/**
+ * Try to reserve a path to a safe position.
+ *
+ * @param v The vehicle
+ * @return True if a path could be reserved
+ */
+bool TryPathReserve(Vehicle *v, bool first_tile_okay, bool mark_as_stuck)
+{
+ assert(v->type == VEH_TRAIN && IsFrontEngine(v));
+
+ /* We have to handle depots specially as the track follower won't look
+ * at the depot tile itself but starts from the next tile. If we are still
+ * inside the depot, a depot reservation can never be ours. */
+ if (v->u.rail.track & TRACK_BIT_DEPOT) {
+ if (GetDepotReservation(v->tile)) {
+ if (mark_as_stuck) MarkTrainAsStuck(v);
+ return false;
+ } else {
+ /* Depot not reserved, but the next tile might be. */
+ TileIndex next_tile = TileAddByDiagDir(v->tile, GetRailDepotDirection(v->tile));
+ if (HasReservedTracks(next_tile, DiagdirReachesTracks(GetRailDepotDirection(v->tile)))) return false;
+ }
+ }
+
+ /* Special check if we are in front of a two-sided conventional signal. */
+ DiagDirection dir = TrainExitDir(v->direction, v->u.rail.track);
+ TileIndex next_tile = TileAddByDiagDir(v->tile, dir);
+ if (IsTileType(next_tile, MP_RAILWAY) && HasReservedTracks(next_tile, DiagdirReachesTracks(dir))) {
+ /* Can have only one reserved trackdir. */
+ Trackdir td = FindFirstTrackdir((TrackdirBits)(GetReservedTrackbits(next_tile) * 0x101 & DiagdirReachesTrackdirs(dir)));
+ if (HasSignalOnTrackdir(next_tile, td) && HasSignalOnTrackdir(next_tile, ReverseTrackdir(td)) &&
+ !IsPbsSignal(GetSignalType(next_tile, TrackdirToTrack(td)))) {
+ /* Signal already reserved, is not ours. */
+ if (mark_as_stuck) MarkTrainAsStuck(v);
+ return false;
+ }
+ }
+
+ PBSTileInfo origin = FollowTrainReservation(v);
+ /* If we have a reserved path and the path ends at a safe tile, we are finished already. */
+ if (origin.okay && (v->tile != origin.tile || first_tile_okay)) {
+ /* Can't be stuck then. */
+ if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH);
+ ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK);
+ return true;
+ }
+
+ /* If we are in a depot, tentativly reserve the depot. */
+ if (v->u.rail.track & TRACK_BIT_DEPOT) {
+ SetDepotReservation(v->tile, true);
+ if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile);
+ }
+
+ DiagDirection exitdir = TrackdirToExitdir(origin.trackdir);
+ TileIndex new_tile = TileAddByDiagDir(origin.tile, exitdir);
+ TrackBits reachable = TrackdirBitsToTrackBits((TrackdirBits)GetTileTrackStatus(new_tile, TRANSPORT_RAIL, 0) & DiagdirReachesTrackdirs(exitdir));
+
+ if (_settings_game.pf.pathfinder_for_trains != VPF_NTP && _settings_game.pf.forbid_90_deg) reachable &= ~TrackCrossesTracks(TrackdirToTrack(origin.trackdir));
+
+ bool res_made = false;
+ ChooseTrainTrack(v, new_tile, exitdir, reachable, true, &res_made, mark_as_stuck);
+
+ if (!res_made) {
+ /* Free the depot reservation as well. */
+ if (v->u.rail.track & TRACK_BIT_DEPOT) SetDepotReservation(v->tile, false);
+ return false;
+ }
+
+ if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH);
+ ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK);
+ return true;
+}
+
static bool CheckReverseTrain(Vehicle *v)
{
@@ -2669,7 +3233,7 @@ static int UpdateTrainSpeed(Vehicle *v)
{
uint accel;
- if (v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_REVERSING)) {
+ if (v->vehstatus & VS_STOPPED || HasBit(v->u.rail.flags, VRF_REVERSING) || HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) {
if (_settings_game.vehicle.realistic_acceleration) {
accel = GetTrainAcceleration(v, AM_BRAKE) * 2;
} else {
@@ -2841,14 +3405,18 @@ static inline void AffectSpeedByZChange(Vehicle *v, byte old_z)
}
}
-static void TrainMovedChangeSignals(TileIndex tile, DiagDirection dir)
+static bool TrainMovedChangeSignals(TileIndex tile, DiagDirection dir)
{
if (IsTileType(tile, MP_RAILWAY) &&
GetRailTileType(tile) == RAIL_TILE_SIGNALS) {
TrackdirBits tracks = TrackBitsToTrackdirBits(GetTrackBits(tile)) & DiagdirReachesTrackdirs(dir);
Trackdir trackdir = FindFirstTrackdir(tracks);
- UpdateSignalsOnSegment(tile, TrackdirToExitdir(trackdir), GetTileOwner(tile));
+ if (UpdateSignalsOnSegment(tile, TrackdirToExitdir(trackdir), GetTileOwner(tile)) == SIGSEG_PBS && HasSignalOnTrackdir(tile, trackdir)) {
+ /* A PBS block with a non-PBS signal facing us? */
+ if (!IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return true;
+ }
}
+ return false;
}
@@ -2856,6 +3424,21 @@ static void SetVehicleCrashed(Vehicle *v)
{
if (v->u.rail.crash_anim_pos != 0) return;
+ /* Free a possible path reservation and try to mark all tiles occupied by the train reserved. */
+ if (IsFrontEngine(v)) {
+ /* Remove all reservations, also the ones currently under the train
+ * and any railway station paltform reservation. */
+ FreeTrainTrackReservation(v);
+ for (const Vehicle *u = v; u != NULL; u = u->Next()) {
+ ClearPathReservation(u->tile, GetVehicleTrackdir(u));
+ }
+ /* Try to reserve all tiles directly under the train, but not the whole
+ * railway station platform or both tunnel/bridge ends. */
+ for (const Vehicle *u = v; u != NULL; u = u->Next()) {
+ TryReserveRailTrack(u->tile, TrackdirToTrack(GetVehicleTrackdir(u)));
+ }
+ }
+
/* we may need to update crossing we were approaching */
TileIndex crossing = TrainApproachingCrossingTile(v);
@@ -3011,6 +3594,10 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET);
return;
}
+ if (v->Next() == NULL && IsRailDepotTile(v->tile) && HasBit(r, VETS_ENTERED_WORMHOLE)) {
+ SetDepotReservation(v->tile, false);
+ if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile);
+ }
if (v->current_order.IsType(OT_LEAVESTATION)) {
v->current_order.Free();
@@ -3050,14 +3637,17 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
if (prev == NULL) {
/* Currently the locomotive is active. Determine which one of the
* available tracks to choose */
- chosen_track = TrackToTrackBits(ChooseTrainTrack(v, gp.new_tile, enterdir, bits));
- assert(chosen_track & bits);
+ chosen_track = TrackToTrackBits(ChooseTrainTrack(v, gp.new_tile, enterdir, bits, false, NULL, true));
+ assert(chosen_track & (bits | GetReservedTrackbits(gp.new_tile)));
/* Check if it's a red signal and that force proceed is not clicked. */
if (red_signals & chosen_track && v->u.rail.force_proceed == 0) {
/* In front of a red signal */
Trackdir i = FindFirstTrackdir(trackdirbits);
+ /* Don't handle stuck trains here. */
+ if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) return;
+
if (!HasSignalOnTrackdir(gp.new_tile, ReverseTrackdir(i))) {
v->cur_speed = 0;
v->subspeed = 0;
@@ -3077,6 +3667,13 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
if (VehicleFromPos(o_tile, &exitdir, &CheckVehicleAtSignal) == NULL) return;
}
}
+
+ /* If we would reverse but are currently in a PBS block and
+ * reversing of stuck trains is disabled, don't reverse. */
+ if (_settings_game.pf.wait_for_pbs_path == 255 && UpdateSignalsOnSegment(v->tile, enterdir, v->owner) == SIGSEG_PBS) {
+ v->load_unload_time_rem = 0;
+ return;
+ }
goto reverse_train_direction;
}
} else {
@@ -3109,9 +3706,17 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
goto invalid_rail;
}
- if (IsFrontEngine(v)) v->load_unload_time_rem = 0;
-
if (!HasBit(r, VETS_ENTERED_WORMHOLE)) {
+ Track track = FindFirstTrack(chosen_track);
+ Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir);
+ if (IsFrontEngine(v) && HasPbsSignalOnTrackdir(gp.new_tile, tdir)) {
+ SetSignalStateByTrackdir(gp.new_tile, tdir, SIGNAL_STATE_RED);
+ MarkTileDirtyByTile(gp.new_tile);
+ }
+
+ /* Clear any track reservation when the last vehicle leaves the tile */
+ if (v->Next() == NULL) ClearPathReservation(v->tile, GetVehicleTrackdir(v));
+
v->tile = gp.new_tile;
if (GetTileRailType(gp.new_tile) != GetTileRailType(gp.old_tile)) {
@@ -3129,6 +3734,17 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
if (prev == NULL) AffectSpeedByDirChange(v, chosen_dir);
v->direction = chosen_dir;
+
+ if (IsFrontEngine(v)) {
+ v->load_unload_time_rem = 0;
+
+ /* If we are approching a crossing that is reserved, play the sound now. */
+ TileIndex crossing = TrainApproachingCrossingTile(v);
+ if (crossing != INVALID_TILE && GetCrossingReservation(crossing)) SndPlayTileFx(SND_0E_LEVEL_CROSSING, crossing);
+
+ /* Always try to extend the reservation when entering a tile. */
+ CheckNextTrainTile(v);
+ }
}
} else {
/* In a tunnel or on a bridge
@@ -3139,7 +3755,10 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
min(v->cur_speed, GetBridgeSpec(GetBridgeType(v->tile))->speed);
}
- if (!IsTileType(gp.new_tile, MP_TUNNELBRIDGE) || !HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) {
+ if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) {
+ /* Perform look-ahead on tunnel exit. */
+ if (IsFrontEngine(v)) CheckNextTrainTile(v);
+ } else {
v->x_pos = gp.x;
v->y_pos = gp.y;
VehiclePositionChanged(v);
@@ -3164,7 +3783,21 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
}
if (update_signals_crossing) {
- if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir);
+ if (IsFrontEngine(v)) {
+ if (TrainMovedChangeSignals(gp.new_tile, enterdir)) {
+ /* We are entering a block with PBS signals right now, but
+ * not through a PBS signal. This means we don't have a
+ * reservation right now. As a conventional signal will only
+ * ever be green if no other train is in the block, getting
+ * a path should always be possible. If the player built
+ * such a strange network that it is not possible, the train
+ * will be marked as stuck and the player has to deal with
+ * the problem. */
+ if (!(HasReservedTracks(gp.new_tile, v->u.rail.track) ||
+ TryReserveRailTrack(gp.new_tile, FindFirstTrack(v->u.rail.track))) ||
+ !TryPathReserve(v)) MarkTrainAsStuck(v);
+ }
+ }
/* Signals can only change when the first
* (above) or the last vehicle moves. */
@@ -3173,6 +3806,9 @@ static void TrainController(Vehicle *v, Vehicle *nomove, bool update_image)
if (IsLevelCrossingTile(gp.old_tile)) UpdateLevelCrossing(gp.old_tile);
}
}
+
+ /* Do not check on every tick to save some computing time. */
+ if (IsFrontEngine(v) && v->tick_counter % _settings_game.pf.path_backoff_interval == 0) CheckNextTrainTile(v);
}
return;
@@ -3223,6 +3859,12 @@ static void DeleteLastWagon(Vehicle *v)
MarkSingleVehicleDirty(v);
+ /* Clear a possible path reservation */
+ if ((IsFrontEngine(v) && !(v->u.rail.track & TRACK_BIT_DEPOT))
+ || ((v->u.rail.track & ~TRACK_BIT_MASK) == TRACK_BIT_NONE && (v->tile != u->tile || (u->u.rail.track & ~TRACK_BIT_MASK) != TRACK_BIT_NONE))) {
+ if (HasReservedTracks(v->tile, v->u.rail.track)) UnreserveRailTrack(v->tile, TrackBitsToTrack(v->u.rail.track));
+ }
+
/* 'v' shouldn't be accessed after it has been deleted */
TrackBits track = v->u.rail.track;
TileIndex tile = v->tile;
@@ -3498,7 +4140,11 @@ static void TrainLocoHandler(Vehicle *v, bool mode)
return;
}
- if (v->u.rail.force_proceed != 0) v->u.rail.force_proceed--;
+ if (v->u.rail.force_proceed != 0) {
+ v->u.rail.force_proceed--;
+ ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK);
+ InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH);
+ }
/* train is broken down? */
if (v->breakdown_ctr != 0) {
@@ -3516,6 +4162,7 @@ static void TrainLocoHandler(Vehicle *v, bool mode)
/* exit if train is stopped */
if (v->vehstatus & VS_STOPPED && v->cur_speed == 0) return;
+ bool valid_order = v->current_order.IsValid() && v->current_order.GetType() != OT_CONDITIONAL;
if (ProcessOrders(v) && CheckReverseTrain(v)) {
v->load_unload_time_rem = 0;
v->cur_speed = 0;
@@ -3532,6 +4179,43 @@ static void TrainLocoHandler(Vehicle *v, bool mode)
if (!mode) HandleLocomotiveSmokeCloud(v);
+ /* We had no order but have an order now, do look ahead. */
+ if (!valid_order && v->current_order.IsValid()) {
+ CheckNextTrainTile(v);
+ }
+
+ /* Handle stuck trains. */
+ if (!mode && HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) {
+ ++v->load_unload_time_rem;
+
+ /* Should we try reversing this tick if still stuck? */
+ bool turn_around = v->load_unload_time_rem % (_settings_game.pf.wait_for_pbs_path * DAY_TICKS) == 0 && _settings_game.pf.wait_for_pbs_path < 255;
+
+ if (!turn_around && v->load_unload_time_rem % _settings_game.pf.path_backoff_interval != 0 && v->u.rail.force_proceed == 0) return;
+ if (!TryPathReserve(v)) {
+ /* Still stuck. */
+ if (turn_around) ReverseTrainDirection(v);
+
+ if (HasBit(v->u.rail.flags, VRF_TRAIN_STUCK) && v->load_unload_time_rem > 2 * _settings_game.pf.wait_for_pbs_path * DAY_TICKS) {
+ /* Show message to player. */
+ if (_settings_client.gui.lost_train_warn && v->owner == _local_player) {
+ SetDParam(0, v->unitnumber);
+ AddNewsItem(
+ STR_TRAIN_IS_STUCK,
+ NS_ADVICE,
+ v->index,
+ 0);
+ }
+ v->load_unload_time_rem = 0;
+ }
+ /* Exit if force proceed not pressed, else reset stuck flag anyway. */
+ if (v->u.rail.force_proceed == 0) return;
+ ClrBit(v->u.rail.flags, VRF_TRAIN_STUCK);
+ v->load_unload_time_rem = 0;
+ InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, VVW_WIDGET_START_STOP_VEH);
+ }
+ }
+
int j = UpdateTrainSpeed(v);
/* we need to invalidate the widget if we are stopping from 'Stopping 0 km/h' to 'Stopped' */
diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp
index 84c7329..08588e4 100644
--- a/src/tunnelbridge_cmd.cpp
+++ b/src/tunnelbridge_cmd.cpp
@@ -630,8 +630,15 @@ static CommandCost DoClearTunnel(TileIndex tile, uint32 flags)
if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL) {
/* We first need to request values before calling DoClearSquare */
DiagDirection dir = GetTunnelBridgeDirection(tile);
+ Track track = DiagDirToDiagTrack(dir);
Owner owner = GetTileOwner(tile);
+ Vehicle *v = NULL;
+ if (GetTunnelBridgeReservation(tile)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
+
DoClearSquare(tile);
DoClearSquare(endtile);
@@ -639,9 +646,10 @@ static CommandCost DoClearTunnel(TileIndex tile, uint32 flags)
AddSideToSignalBuffer(tile, ReverseDiagDir(dir), owner);
AddSideToSignalBuffer(endtile, dir, owner);
- Track track = DiagDirToDiagTrack(dir);
YapfNotifyTrackLayoutChange(tile, track);
YapfNotifyTrackLayoutChange(endtile, track);
+
+ if (v != NULL) TryPathReserve(v);
} else {
DoClearSquare(tile);
DoClearSquare(endtile);
@@ -689,6 +697,12 @@ static CommandCost DoClearBridge(TileIndex tile, uint32 flags)
bool rail = GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL;
Owner owner = GetTileOwner(tile);
uint height = GetBridgeHeight(tile);
+ Vehicle *v = NULL;
+
+ if (rail && GetTunnelBridgeReservation(tile)) {
+ v = GetTrainForReservation(tile, DiagDirToDiagTrack(direction));
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
DoClearSquare(tile);
DoClearSquare(endtile);
@@ -710,6 +724,8 @@ static CommandCost DoClearBridge(TileIndex tile, uint32 flags)
Track track = DiagDirToDiagTrack(direction);
YapfNotifyTrackLayoutChange(tile, track);
YapfNotifyTrackLayoutChange(endtile, track);
+
+ if (v != NULL) TryPathReserve(v, true);
}
}
@@ -887,6 +903,13 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
image += tunnelbridge_direction * 2;
DrawGroundSprite(image, PAL_NONE);
+
+ /* PBS debugging, draw reserved tracks darker */
+ if (_settings_client.gui.show_track_reservation && (transport_type == TRANSPORT_RAIL && GetTunnelBridgeReservation(ti->tile))) {
+ const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
+ DrawGroundSprite(DiagDirToAxis(tunnelbridge_direction) == AXIS_X ? rti->base_sprites.single_y : rti->base_sprites.single_x, PALETTE_CRASH);
+ }
+
if (transport_type == TRANSPORT_ROAD) {
RoadTypes rts = GetRoadTypes(ti->tile);
@@ -963,6 +986,15 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
/* Bridge heads are drawn solid no matter how invisibility/transparency is set */
AddSortableSpriteToDraw(psid->sprite, psid->pal, ti->x, ti->y, 16, 16, ti->tileh == SLOPE_FLAT ? 0 : 8, ti->z);
+ if (_settings_client.gui.show_track_reservation && transport_type == TRANSPORT_RAIL && GetTunnelBridgeReservation(ti->tile)) {
+ const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
+ if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
+ AddSortableSpriteToDraw(DiagDirToAxis(tunnelbridge_direction) == AXIS_X ? rti->base_sprites.single_y : rti->base_sprites.single_x, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + 8);
+ } else {
+ AddSortableSpriteToDraw(rti->base_sprites.single_sloped + tunnelbridge_direction, PALETTE_CRASH, ti->x, ti->y, 16, 16, 8, ti->z);
+ }
+ }
+
if (transport_type == TRANSPORT_ROAD) {
RoadTypes rts = GetRoadTypes(ti->tile);
diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h
index bdc7099..afcfb58 100644
--- a/src/tunnelbridge_map.h
+++ b/src/tunnelbridge_map.h
@@ -11,6 +11,7 @@
#include "bridge_map.h"
#include "tunnel_map.h"
#include "transport_type.h"
+#include "track_func.h"
/**
@@ -80,4 +81,42 @@ static inline TileIndex GetOtherTunnelBridgeEnd(TileIndex t)
return IsTunnel(t) ? GetOtherTunnelEnd(t) : GetOtherBridgeEnd(t);
}
+
+/**
+ * Get the reservation state of the rail tunnel/bridge
+ * @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
+ * @param t the tile
+ * @return reservation state
+ */
+static inline bool GetTunnelBridgeReservation(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ assert(GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL);
+ return HasBit(_m[t].m5, 4);
+}
+
+/**
+ * Set the reservation state of the rail tunnel/bridge
+ * @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
+ * @param t the tile
+ * @param b the reservation state
+ */
+static inline void SetTunnelBridgeReservation(TileIndex t, bool b)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ assert(GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL);
+ SB(_m[t].m5, 4, 1, (byte)b & 1);
+}
+
+/**
+ * Get the reserved track bits for a rail tunnel/bridge
+ * @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
+ * @param t the tile
+ * @return reserved track bits
+ */
+static inline TrackBits GetRailTunnelBridgeReservation(TileIndex t)
+{
+ return GetTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
+}
+
#endif /* TUNNELBRIDGE_MAP_H */
diff --git a/src/vehicle.cpp b/src/vehicle.cpp
index 46866fa..0d0699e 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -2072,7 +2072,8 @@ static const SaveLoad _train_desc[] = {
SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, railtype), SLE_UINT8),
SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, track), SLE_UINT8),
- SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, flags), SLE_UINT8, 2, SL_MAX_VERSION),
+ SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, flags), SLE_FILE_U8 | SLE_VAR_U16, 2, 99),
+ SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, flags), SLE_UINT16,100, SL_MAX_VERSION),
SLE_CONDNULL(2, 2, 59),
SLE_CONDNULL(2, 2, 19),
diff --git a/src/vehicle_base.h b/src/vehicle_base.h
index 0defc95..440f30d 100644
--- a/src/vehicle_base.h
+++ b/src/vehicle_base.h
@@ -115,7 +115,7 @@ struct VehicleRail {
* 0xffff == not in train */
EngineID first_engine;
- byte flags;
+ uint16 flags;
TrackBitsByte track;
byte force_proceed;
RailTypeByte railtype;
@@ -143,6 +143,9 @@ enum VehicleRailFlags {
/* used for vehicle var 0xFE bit 8 (toggled each time the train is reversed, accurate for first vehicle only) */
VRF_TOGGLE_REVERSE = 7,
+
+ /* used to mark a train that can't get a path reservation */
+ VRF_TRAIN_STUCK = 8,
};
struct VehicleAir {
diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp
index 894b3d7..904f511 100644
--- a/src/vehicle_gui.cpp
+++ b/src/vehicle_gui.cpp
@@ -1895,6 +1895,8 @@ struct VehicleViewWindow : Window {
} else { // no train
str = STR_8861_STOPPED;
}
+ } else if (v->type == VEH_TRAIN && HasBit(v->u.rail.flags, VRF_TRAIN_STUCK)) {
+ str = STR_TRAIN_STUCK;
} else { // vehicle is in a "normal" state, show current order
switch (v->current_order.GetType()) {
case OT_GOTO_STATION: {
diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp
index dd90530..872d49b 100644
--- a/src/water_cmd.cpp
+++ b/src/water_cmd.cpp
@@ -866,7 +866,10 @@ static void FloodVehicle(Vehicle *v)
switch (v->type) {
default: NOT_REACHED();
case VEH_TRAIN:
- if (IsFrontEngine(v)) pass += 4; // driver
+ if (IsFrontEngine(v)) {
+ pass += 4; // driver
+ FreeTrainTrackReservation(v);
+ }
v->u.rail.crash_anim_pos = 4000; // max 4440, disappear pretty fast
InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
break;
diff --git a/src/waypoint.cpp b/src/waypoint.cpp
index 6dd30cf..5aefc91 100644
--- a/src/waypoint.cpp
+++ b/src/waypoint.cpp
@@ -33,6 +33,8 @@
#include "newgrf_station.h"
#include "oldpool_func.h"
#include "viewport_func.h"
+#include "pbs.h"
+#include "train.h"
#include "table/strings.h"
@@ -249,7 +251,9 @@ CommandCost CmdBuildTrainWaypoint(TileIndex tile, uint32 flags, uint32 p1, uint3
const StationSpec* statspec;
+ bool reserved = HasBit(GetTrackReservation(tile), AxisToTrack(axis));
MakeRailWaypoint(tile, GetTileOwner(tile), axis, GetRailType(tile), wp->index);
+ SetWaypointReservation(tile, reserved);
MarkTileDirtyByTile(tile);
statspec = GetCustomStationSpec(STAT_CLASS_WAYP, p1);
@@ -318,14 +322,23 @@ CommandCost RemoveTrainWaypoint(TileIndex tile, uint32 flags, bool justremove)
wp->deleted = 30; // let it live for this many days before we do the actual deletion.
RedrawWaypointSign(wp);
+ Vehicle *v = NULL;
if (justremove) {
- MakeRailNormal(tile, GetTileOwner(tile), GetRailWaypointBits(tile), GetRailType(tile));
+ TrackBits tracks = GetRailWaypointBits(tile);
+ bool reserved = GetWaypointReservation(tile);
+ MakeRailNormal(tile, GetTileOwner(tile), tracks, GetRailType(tile));
+ if (reserved) SetTrackReservation(tile, tracks);
MarkTileDirtyByTile(tile);
} else {
+ if (GetWaypointReservation(tile)) {
+ v = GetTrainForReservation(tile, track);
+ if (v != NULL) FreeTrainTrackReservation(v);
+ }
DoClearSquare(tile);
AddTrackToSignalBuffer(tile, track, owner);
}
YapfNotifyTrackLayoutChange(tile, track);
+ if (v != NULL) TryPathReserve(v, true);
}
return CommandCost(EXPENSES_CONSTRUCTION, _price.remove_train_depot);
diff --git a/src/yapf/follow_track.hpp b/src/yapf/follow_track.hpp
index d3c9c45..c642b20 100644
--- a/src/yapf/follow_track.hpp
+++ b/src/yapf/follow_track.hpp
@@ -10,8 +10,8 @@
/** Track follower helper template class (can serve pathfinders and vehicle
* controllers). See 6 different typedefs below for 3 different transport
- * types w/ of w/o 90-deg turns allowed */
-template
+ * types w/ or w/o 90-deg turns allowed */
+template
struct CFollowTrackT
{
enum ErrorCode {
@@ -20,9 +20,11 @@ struct CFollowTrackT
EC_RAIL_TYPE,
EC_90DEG,
EC_NO_WAY,
+ EC_RESERVED,
};
const Vehicle *m_veh; ///< moving vehicle
+ Owner m_veh_owner; ///< owner of the vehicle
TileIndex m_old_tile; ///< the origin (vehicle moved from) before move
Trackdir m_old_td; ///< the trackdir (the vehicle was on) before move
TileIndex m_new_tile; ///< the new tile (the vehicle has entered)
@@ -34,16 +36,30 @@ struct CFollowTrackT
int m_tiles_skipped; ///< number of skipped tunnel or station tiles
ErrorCode m_err;
CPerformanceTimer *m_pPerf;
+ RailTypes m_railtypes;
- FORCEINLINE CFollowTrackT(const Vehicle *v = NULL, CPerformanceTimer* pPerf = NULL)
+ FORCEINLINE CFollowTrackT(const Vehicle *v = NULL, RailTypes railtype_override = INVALID_RAILTYPES, CPerformanceTimer *pPerf = NULL)
{
- Init(v, pPerf);
+ Init(v, railtype_override, pPerf);
}
- FORCEINLINE void Init(const Vehicle *v, CPerformanceTimer* pPerf)
+ FORCEINLINE CFollowTrackT(Owner o, RailTypes railtype_override = INVALID_RAILTYPES, CPerformanceTimer *pPerf = NULL)
+ {
+ m_veh = NULL;
+ Init(o, railtype_override, pPerf);
+ }
+
+ FORCEINLINE void Init(const Vehicle *v, RailTypes railtype_override, CPerformanceTimer *pPerf)
{
assert(!IsRailTT() || (v != NULL && v->type == VEH_TRAIN));
m_veh = v;
+ Init(v != NULL ? v->owner : INVALID_OWNER, railtype_override == INVALID_RAILTYPES ? v->u.rail.compatible_railtypes : railtype_override, pPerf);
+ }
+
+ FORCEINLINE void Init(Owner o, RailTypes railtype_override, CPerformanceTimer *pPerf)
+ {
+ assert((!IsRoadTT() || m_veh != NULL) && (!IsRailTT() || railtype_override != INVALID_RAILTYPES));
+ m_veh_owner = o;
m_pPerf = pPerf;
// don't worry, all is inlined so compiler should remove unnecessary initializations
m_new_tile = INVALID_TILE;
@@ -52,6 +68,7 @@ struct CFollowTrackT
m_is_station = m_is_bridge = m_is_tunnel = false;
m_tiles_skipped = 0;
m_err = EC_NONE;
+ m_railtypes = railtype_override;
}
FORCEINLINE static TransportType TT() {return Ttr_type_;}
@@ -60,6 +77,7 @@ struct CFollowTrackT
FORCEINLINE bool IsTram() {return IsRoadTT() && HasBit(m_veh->u.road.compatible_roadtypes, ROADTYPE_TRAM);}
FORCEINLINE static bool IsRoadTT() {return TT() == TRANSPORT_ROAD;}
FORCEINLINE static bool Allow90degTurns() {return T90deg_turns_allowed_;}
+ FORCEINLINE static bool MaskReservedTracks() {return Tmask_reserved_tracks;}
/** Tests if a tile is a road tile with a single tramtrack (tram can reverse) */
FORCEINLINE DiagDirection GetSingleTramBit(TileIndex tile)
@@ -79,12 +97,12 @@ struct CFollowTrackT
/** main follower routine. Fills all members and return true on success.
* Otherwise returns false if track can't be followed. */
- FORCEINLINE bool Follow(TileIndex old_tile, Trackdir old_td)
+ inline bool Follow(TileIndex old_tile, Trackdir old_td)
{
m_old_tile = old_tile;
m_old_td = old_td;
m_err = EC_NONE;
- assert(((TrackStatusToTrackdirBits(GetTileTrackStatus(m_old_tile, TT(), m_veh->u.road.compatible_roadtypes)) & TrackdirToTrackdirBits(m_old_td)) != 0) ||
+ assert(((TrackStatusToTrackdirBits(GetTileTrackStatus(m_old_tile, TT(), m_veh ? m_veh->u.road.compatible_roadtypes : 0)) & TrackdirToTrackdirBits(m_old_td)) != 0) ||
(GetSingleTramBit(m_old_tile) != INVALID_DIAGDIR)); // Disable the assertion for single tram bits
m_exitdir = TrackdirToExitdir(m_old_td);
if (ForcedReverse()) return true;
@@ -104,6 +122,21 @@ struct CFollowTrackT
return false;
}
}
+ if (MaskReservedTracks()) {
+ TrackBits reserved = GetReservedTrackbits(m_new_tile);
+ /* Mask already reserved trackdirs. */
+ m_new_td_bits &= ~TrackBitsToTrackdirBits(reserved);
+ /* Mask out all trackdirs that conflict with the reservation. */
+ uint bits = (uint)TrackdirBitsToTrackBits(m_new_td_bits);
+ int i;
+ FOR_EACH_SET_BIT(i, bits) {
+ if (TracksOverlap(reserved | TrackToTrackBits((Track)i))) m_new_td_bits &= ~TrackToTrackdirBits((Track)i);
+ }
+ if (m_new_td_bits == TRACKDIR_BIT_NONE) {
+ m_err = EC_RESERVED;
+ return false;
+ }
+ }
return true;
}
@@ -153,7 +186,7 @@ protected:
if (IsRailTT() && GetTileType(m_new_tile) == MP_RAILWAY && IsPlainRailTile(m_new_tile)) {
m_new_td_bits = (TrackdirBits)(GetTrackBits(m_new_tile) * 0x101);
} else {
- m_new_td_bits = TrackStatusToTrackdirBits(GetTileTrackStatus(m_new_tile, TT(), m_veh->u.road.compatible_roadtypes));
+ m_new_td_bits = TrackStatusToTrackdirBits(GetTileTrackStatus(m_new_tile, TT(), m_veh != NULL ? m_veh->u.road.compatible_roadtypes : 0));
if (m_new_td_bits == 0) {
/* GetTileTrackStatus() returns 0 for single tram bits.
@@ -233,7 +266,7 @@ protected:
return false;
}
// don't try to enter other player's depots
- if (GetTileOwner(m_new_tile) != m_veh->owner) {
+ if (GetTileOwner(m_new_tile) != m_veh_owner) {
m_err = EC_OWNER;
return false;
}
@@ -247,7 +280,7 @@ protected:
}
// rail transport is possible only on tiles with the same owner as vehicle
- if (IsRailTT() && GetTileOwner(m_new_tile) != m_veh->owner) {
+ if (IsRailTT() && GetTileOwner(m_new_tile) != m_veh_owner) {
// different owner
m_err = EC_NO_WAY;
return false;
@@ -256,7 +289,7 @@ protected:
// rail transport is possible only on compatible rail types
if (IsRailTT()) {
RailType rail_type = GetTileRailType(m_new_tile);
- if (!HasBit(m_veh->u.rail.compatible_railtypes, rail_type)) {
+ if (!HasBit(m_railtypes, rail_type)) {
// incompatible rail type
m_err = EC_RAIL_TYPE;
return false;
@@ -380,4 +413,7 @@ typedef CFollowTrackT CFollowTrackWaterNo90;
typedef CFollowTrackT CFollowTrackRoadNo90;
typedef CFollowTrackT CFollowTrackRailNo90;
+typedef CFollowTrackT CFollowTrackFreeRail;
+typedef CFollowTrackT CFollowTrackFreeRailNo90;
+
#endif /* FOLLOW_TRACK_HPP */
diff --git a/src/yapf/yapf.h b/src/yapf/yapf.h
index ca400a7..45ba94e 100644
--- a/src/yapf/yapf.h
+++ b/src/yapf/yapf.h
@@ -8,6 +8,7 @@
#include "../debug.h"
#include "../depot_type.h"
#include "../direction_type.h"
+#include "../pbs.h"
/** Finds the best path for given ship.
* @param v the ship that needs to find a path
@@ -32,9 +33,11 @@ Trackdir YapfChooseRoadTrack(const Vehicle *v, TileIndex tile, DiagDirection ent
* @param enterdir diagonal direction which the RV will enter this new tile from
* @param tracks available trackdirs on the new tile (to choose from)
* @param path_not_found [out] true is returned if no path can be found (returned Trackdir is only a 'guess')
+ * @param reserve_track indicates whether YAPF should try to reserve the found path
+ * @param target [out] the target tile of the reservation, free is set to true if path was reserved
* @return the best trackdir for next turn or INVALID_TRACKDIR if the path could not be found
*/
-Trackdir YapfChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found);
+Trackdir YapfChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found, bool reserve_track, PBSTileInfo *target);
/** Used by RV multistop feature to find the nearest road stop that has a free slot.
* @param v RV (its current tile will be the origin)
@@ -63,6 +66,17 @@ bool YapfFindNearestRailDepotTwoWay(const Vehicle *v, int max_distance, int reve
/** Returns true if it is better to reverse the train before leaving station */
bool YapfCheckReverseTrain(const Vehicle* v);
+/**
+ * Try to extend the reserved path of a train to the nearest safe tile.
+ *
+ * @param v The train that needs to find a safe tile.
+ * @param tile Last tile of the current reserved path.
+ * @param td Last trackdir of the current reserved path.
+ * @param override_railtype Should all physically compabtible railtypes be searched, even if the vehicle can't on them on it own?
+ * @return True if the path could be extended to a safe tile.
+ */
+bool YapfRailFindNearestSafeTile(const Vehicle *v, TileIndex tile, Trackdir td, bool override_railtype);
+
/** Use this function to notify YAPF that track layout (or signal configuration) has change */
void YapfNotifyTrackLayoutChange(TileIndex tile, Track track);
diff --git a/src/yapf/yapf_costrail.hpp b/src/yapf/yapf_costrail.hpp
index abd4210..c19353f 100644
--- a/src/yapf/yapf_costrail.hpp
+++ b/src/yapf/yapf_costrail.hpp
@@ -5,6 +5,7 @@
#ifndef YAPF_COSTRAIL_HPP
#define YAPF_COSTRAIL_HPP
+#include "../pbs.h"
template
class CYapfCostRailT
@@ -104,6 +105,16 @@ public:
return cost;
}
+ FORCEINLINE int SwitchCost(TileIndex tile1, TileIndex tile2, DiagDirection exitdir)
+ {
+ if (IsTileType(tile1, MP_RAILWAY) && IsTileType(tile2, MP_RAILWAY)) {
+ bool t1 = KillFirstBit(GetTrackBits(tile1) & DiagdirReachesTracks(ReverseDiagDir(exitdir))) != TRACK_BIT_NONE;
+ bool t2 = KillFirstBit(GetTrackBits(tile2) & DiagdirReachesTracks(exitdir)) != TRACK_BIT_NONE;
+ if (t1 && t2) return Yapf().PfGetSettings().rail_doubleslip_penalty;
+ }
+ return 0;
+ }
+
/** Return one tile cost (base cost + level crossing penalty). */
FORCEINLINE int OneTileCost(TileIndex& tile, Trackdir trackdir)
{
@@ -128,6 +139,19 @@ public:
return cost;
}
+ /** The cost for reserved tiles, including skipped ones. */
+ FORCEINLINE int ReservationCost(Node& n, TileIndex& tile, Trackdir trackdir, int skipped)
+ {
+ if (n.m_num_signals_passed >= m_sig_look_ahead_costs.Size() / 2) return 0;
+
+ if (TrackOverlapsTracks(GetReservedTrackbits(tile), TrackdirToTrack(trackdir))) {
+ int cost = IsRailwayStationTile(tile) ? Yapf().PfGetSettings().rail_pbs_station_penalty : Yapf().PfGetSettings().rail_pbs_cross_penalty;
+ if (!IsDiagonalTrackdir(trackdir)) cost = (cost * YAPF_TILE_CORNER_LENGTH) / YAPF_TILE_LENGTH;
+ return cost * (skipped + 1);
+ }
+ return 0;
+ }
+
int SignalCost(Node& n, TileIndex tile, Trackdir trackdir)
{
int cost = 0;
@@ -136,53 +160,61 @@ public:
if (IsTileType(tile, MP_RAILWAY)) {
bool has_signal_against = HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir));
bool has_signal_along = HasSignalOnTrackdir(tile, trackdir);
- if (has_signal_against && !has_signal_along) {
+ if (has_signal_against && !has_signal_along && IsOnewaySignal(tile, TrackdirToTrack(trackdir))) {
// one-way signal in opposite direction
n.m_segment->m_end_segment_reason |= ESRB_DEAD_END;
- } else if (has_signal_along) {
- SignalState sig_state = GetSignalStateByTrackdir(tile, trackdir);
- // cache the look-ahead polynomial constant only if we didn't pass more signals than the look-ahead limit is
- int look_ahead_cost = (n.m_num_signals_passed < m_sig_look_ahead_costs.Size()) ? m_sig_look_ahead_costs.Data()[n.m_num_signals_passed] : 0;
- if (sig_state != SIGNAL_STATE_RED) {
- // green signal
- n.flags_u.flags_s.m_last_signal_was_red = false;
- // negative look-ahead red-signal penalties would cause problems later, so use them as positive penalties for green signal
- if (look_ahead_cost < 0) {
- // add its negation to the cost
- cost -= look_ahead_cost;
- }
- } else {
- // we have a red signal in our direction
- // was it first signal which is two-way?
- if (Yapf().TreatFirstRedTwoWaySignalAsEOL() && n.flags_u.flags_s.m_choice_seen && has_signal_against && n.m_num_signals_passed == 0) {
- // yes, the first signal is two-way red signal => DEAD END
- n.m_segment->m_end_segment_reason |= ESRB_DEAD_END;
- Yapf().m_stopped_on_first_two_way_signal = true;
- return -1;
- }
- SignalType sig_type = GetSignalType(tile, TrackdirToTrack(trackdir));
- n.m_last_red_signal_type = sig_type;
- n.flags_u.flags_s.m_last_signal_was_red = true;
-
- // look-ahead signal penalty
- if (look_ahead_cost > 0) {
- // add the look ahead penalty only if it is positive
- cost += look_ahead_cost;
- }
+ } else {
+ if (has_signal_along) {
+ SignalState sig_state = GetSignalStateByTrackdir(tile, trackdir);
+ // cache the look-ahead polynomial constant only if we didn't pass more signals than the look-ahead limit is
+ int look_ahead_cost = (n.m_num_signals_passed < m_sig_look_ahead_costs.Size()) ? m_sig_look_ahead_costs.Data()[n.m_num_signals_passed] : 0;
+ if (sig_state != SIGNAL_STATE_RED) {
+ // green signal
+ n.flags_u.flags_s.m_last_signal_was_red = false;
+ // negative look-ahead red-signal penalties would cause problems later, so use them as positive penalties for green signal
+ if (look_ahead_cost < 0) {
+ // add its negation to the cost
+ cost -= look_ahead_cost;
+ }
+ } else {
+ SignalType sig_type = GetSignalType(tile, TrackdirToTrack(trackdir));
+ // we have a red signal in our direction
+ // was it first signal which is two-way?
+ if (!IsPbsSignal(sig_type) && Yapf().TreatFirstRedTwoWaySignalAsEOL() && n.flags_u.flags_s.m_choice_seen && has_signal_against && n.m_num_signals_passed == 0) {
+ // yes, the first signal is two-way red signal => DEAD END
+ n.m_segment->m_end_segment_reason |= ESRB_DEAD_END;
+ Yapf().m_stopped_on_first_two_way_signal = true;
+ return -1;
+ }
+ n.m_last_red_signal_type = sig_type;
+ n.flags_u.flags_s.m_last_signal_was_red = true;
+
+ // look-ahead signal penalty
+ if (!IsPbsSignal(sig_type) && look_ahead_cost > 0) {
+ // add the look ahead penalty only if it is positive
+ cost += look_ahead_cost;
+ }
- // special signal penalties
- if (n.m_num_signals_passed == 0) {
- switch (sig_type) {
- case SIGTYPE_COMBO:
- case SIGTYPE_EXIT: cost += Yapf().PfGetSettings().rail_firstred_exit_penalty; break; // first signal is red pre-signal-exit
- case SIGTYPE_NORMAL:
- case SIGTYPE_ENTRY: cost += Yapf().PfGetSettings().rail_firstred_penalty; break;
- };
+ // special signal penalties
+ if (n.m_num_signals_passed == 0) {
+ switch (sig_type) {
+ case SIGTYPE_COMBO:
+ case SIGTYPE_EXIT: cost += Yapf().PfGetSettings().rail_firstred_exit_penalty; break; // first signal is red pre-signal-exit
+ case SIGTYPE_NORMAL:
+ case SIGTYPE_ENTRY: cost += Yapf().PfGetSettings().rail_firstred_penalty; break;
+ default: break;
+ };
+ }
}
+
+ n.m_num_signals_passed++;
+ n.m_segment->m_last_signal_tile = tile;
+ n.m_segment->m_last_signal_td = trackdir;
+ }
+
+ if (has_signal_against && IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) {
+ cost += n.m_num_signals_passed < Yapf().PfGetSettings().rail_look_ahead_max_signals ? Yapf().PfGetSettings().rail_pbs_signal_back_penalty : 0;
}
- n.m_num_signals_passed++;
- n.m_segment->m_last_signal_tile = tile;
- n.m_segment->m_last_signal_td = trackdir;
}
}
return cost;
@@ -271,7 +303,7 @@ public:
EndSegmentReasonBits end_segment_reason = ESRB_NONE;
- TrackFollower tf_local(v, &Yapf().m_perf_ts_cost);
+ TrackFollower tf_local(v, Yapf().GetCompatibleRailTypes(), &Yapf().m_perf_ts_cost);
if (!has_parent) {
/* We will jump to the middle of the cost calculator assuming that segment cache is not used. */
@@ -283,6 +315,7 @@ public:
for (;;) {
/* Transition cost (cost of the move from previous tile) */
transition_cost = Yapf().CurveCost(prev.td, cur.td);
+ transition_cost += Yapf().SwitchCost(prev.tile, cur.tile, TrackdirToExitdir(prev.td));
/* First transition cost counts against segment entry cost, other transitions
* inside segment will come to segment cost (and will be cached) */
@@ -327,6 +360,9 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th
/* Slope cost. */
segment_cost += Yapf().SlopeCost(cur.tile, cur.td);
+ /* Reserved tiles. */
+ segment_cost += Yapf().ReservationCost(n, cur.tile, cur.td, tf->m_tiles_skipped);
+
/* Signal cost (routine can modify segment data). */
segment_cost += Yapf().SignalCost(n, cur.tile, cur.td);
end_segment_reason = segment.m_end_segment_reason;
@@ -351,6 +387,11 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th
} else if (cur.tile_type == MP_RAILWAY && IsRailWaypoint(cur.tile)) {
/* Waypoint is also a good reason to finish. */
end_segment_reason |= ESRB_WAYPOINT;
+ } else if (TrackFollower::MaskReservedTracks() && cur.tile_type == MP_RAILWAY) {
+ /* Searching for a safe tile? */
+ if (HasSignalOnTrackdir(cur.tile, cur.td) && !IsPbsSignal(GetSignalType(cur.tile, TrackdirToTrack(cur.td)))) {
+ end_segment_reason |= ESRB_SAFE_TILE;
+ }
}
/* Apply min/max speed penalties only when inside the look-ahead radius. Otherwise
@@ -373,7 +414,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th
/* Move to the next tile/trackdir. */
tf = &tf_local;
- tf_local.Init(v, &Yapf().m_perf_ts_cost);
+ tf_local.Init(v, Yapf().GetCompatibleRailTypes(), &Yapf().m_perf_ts_cost);
if (!tf_local.Follow(cur.tile, cur.td)) {
assert(tf_local.m_err != TrackFollower::EC_NONE);
@@ -383,6 +424,10 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th
} else {
end_segment_reason |= ESRB_DEAD_END;
}
+
+ if (TrackFollower::MaskReservedTracks() && tf_local.m_err != TrackFollower::EC_90DEG) {
+ end_segment_reason |= ESRB_SAFE_TILE;
+ }
break;
}
@@ -396,6 +441,11 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th
/* Gather the next tile/trackdir/tile_type/rail_type. */
TILE next(tf_local.m_new_tile, (Trackdir)FindFirstBit2x64(tf_local.m_new_td_bits));
+ if (TrackFollower::MaskReservedTracks() && HasPbsSignalOnTrackdir(next.tile, next.td)) {
+ /* Possible safe tile. */
+ end_segment_reason |= ESRB_SAFE_TILE;
+ }
+
/* Check the next tile for the rail type. */
if (next.rail_type != cur.rail_type) {
/* Segment must consist from the same rail_type tiles. */
diff --git a/src/yapf/yapf_destrail.hpp b/src/yapf/yapf_destrail.hpp
index aba4094..9790758 100644
--- a/src/yapf/yapf_destrail.hpp
+++ b/src/yapf/yapf_destrail.hpp
@@ -11,15 +11,21 @@ protected:
RailTypes m_compatible_railtypes;
public:
- void SetDestination(const Vehicle* v)
+ void SetDestination(const Vehicle *v, bool override_rail_type = false)
{
m_compatible_railtypes = v->u.rail.compatible_railtypes;
+ if (override_rail_type) m_compatible_railtypes |= GetRailTypeInfo(v->u.rail.railtype)->compatible_railtypes;
}
bool IsCompatibleRailType(RailType rt)
{
return HasBit(m_compatible_railtypes, rt);
}
+
+ RailTypes GetCompatibleRailTypes() const
+ {
+ return m_compatible_railtypes;
+ }
};
template
@@ -57,6 +63,41 @@ public:
};
template
+class CYapfDestinationAnySafeTileRailT
+ : public CYapfDestinationRailBase
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+ /// to access inherited path finder
+ Tpf& Yapf() {return *static_cast(this);}
+
+ /// Called by YAPF to detect if node ends in the desired destination
+ FORCEINLINE bool PfDetectDestination(Node& n)
+ {
+ return PfDetectDestination(n.GetLastTile(), n.GetLastTrackdir());
+ }
+
+ /// Called by YAPF to detect if node ends in the desired destination
+ FORCEINLINE bool PfDetectDestination(TileIndex tile, Trackdir td)
+ {
+ return
+ IsSafeWaitingPosition(Yapf().GetVehicle(), tile, td, true, Types::TrackFollower::Allow90degTurns()) &&
+ IsWaitingPositionFree(Yapf().GetVehicle(), tile, td, Types::TrackFollower::Allow90degTurns());
+ }
+
+ /** 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. */
+ FORCEINLINE bool PfCalcEstimate(Node& n)
+ {
+ n.m_estimate = n.m_cost;
+ return true;
+ }
+};
+
+template
class CYapfDestinationTileOrStationRailT
: public CYapfDestinationRailBase
{
diff --git a/src/yapf/yapf_node_rail.hpp b/src/yapf/yapf_node_rail.hpp
index 9745ff3..192bce2 100644
--- a/src/yapf/yapf_node_rail.hpp
+++ b/src/yapf/yapf_node_rail.hpp
@@ -39,6 +39,7 @@ enum EndSegmentReason {
ESR_DEPOT, ///< stop in the depot (could be a target next time)
ESR_WAYPOINT, ///< waypoint encountered (could be a target next time)
ESR_STATION, ///< station encountered (could be a target next time)
+ ESR_SAFE_TILE, ///< safe waiting position found (could be a target)
/* The following reasons are used only internally by PfCalcCost().
* They should not be found in the cached segment. */
@@ -62,6 +63,7 @@ enum EndSegmentReasonBits {
ESRB_DEPOT = 1 << ESR_DEPOT,
ESRB_WAYPOINT = 1 << ESR_WAYPOINT,
ESRB_STATION = 1 << ESR_STATION,
+ ESRB_SAFE_TILE = 1 << ESR_SAFE_TILE,
ESRB_PATH_TOO_LONG = 1 << ESR_PATH_TOO_LONG,
ESRB_FIRST_TWO_WAY_RED = 1 << ESR_FIRST_TWO_WAY_RED,
@@ -71,10 +73,10 @@ enum EndSegmentReasonBits {
/* Additional (composite) values. */
/* What reasons mean that the target can be found and needs to be detected. */
- ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION,
+ ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE,
/* What reasons can be stored back into cached segment. */
- ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION,
+ ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE,
/* Reasons to abort pathfinding in this direction. */
ESRB_ABORT_PF_MASK = ESRB_DEAD_END | ESRB_PATH_TOO_LONG | ESRB_INFINITE_LOOP | ESRB_FIRST_TWO_WAY_RED,
@@ -177,6 +179,37 @@ struct CYapfRailNodeT
FORCEINLINE Trackdir GetLastTrackdir() const {assert(m_segment != NULL); return m_segment->m_last_td;}
FORCEINLINE void SetLastTileTrackdir(TileIndex tile, Trackdir td) {assert(m_segment != NULL); m_segment->m_last_tile = tile; m_segment->m_last_td = td;}
+ template
+ bool IterateTiles(const Vehicle *v, Tpf &yapf, Tbase &obj, bool (Tfunc::*func)(TileIndex, Trackdir)) const
+ {
+ typename Tbase::TrackFollower ft(v, yapf.GetCompatibleRailTypes());
+ TileIndex cur = base::GetTile();
+ Trackdir cur_td = base::GetTrackdir();
+
+ while (cur != GetLastTile() || cur_td != GetLastTrackdir()) {
+ if (!((obj.*func)(cur, cur_td))) return false;
+
+ ft.Follow(cur, cur_td);
+ cur = ft.m_new_tile;
+ assert(KillFirstBit(ft.m_new_td_bits) == TRACKDIR_BIT_NONE);
+ cur_td = FindFirstTrackdir(ft.m_new_td_bits);
+
+ /* Did we skip tiles because of a station? */
+ if (ft.m_is_station && ft.m_tiles_skipped > 0) {
+ TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(cur_td));
+ TileIndex tile = TILE_ADD(cur, -diff * ft.m_tiles_skipped);
+
+ /* Call func for all tiles in between. */
+ for (int i = 0; i < ft.m_tiles_skipped; ++i) {
+ if (!(obj.*func)(tile, cur_td)) return false;
+ tile = TILE_ADD(tile, diff);
+ }
+ }
+ }
+
+ return (obj.*func)(cur, cur_td);
+ }
+
void Dump(DumpTarget &dmp) const
{
base::Dump(dmp);
diff --git a/src/yapf/yapf_rail.cpp b/src/yapf/yapf_rail.cpp
index 7d7d4ee..e4a29d7 100644
--- a/src/yapf/yapf_rail.cpp
+++ b/src/yapf/yapf_rail.cpp
@@ -9,6 +9,7 @@
#include "yapf_costrail.hpp"
#include "yapf_destrail.hpp"
#include "../vehicle_func.h"
+#include "../pbs.h"
#define DEBUG_YAPF_CACHE 0
@@ -16,7 +17,115 @@ int _total_pf_time_us = 0;
+template
+class CYapfReserveTrack
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::TrackFollower TrackFollower;
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+
+protected:
+ /// to access inherited pathfinder
+ FORCEINLINE Tpf& Yapf() {return *static_cast(this);}
+
+private:
+ TileIndex m_res_dest; ///< The reservation target tile
+ Trackdir m_res_dest_td; ///< The reservation target trackdir
+ Node *m_res_node; ///< The reservation target node
+ TileIndex m_res_fail_tile; ///< The tile where the reservation failed
+ Trackdir m_res_fail_td; ///< The trackdir where the reservation failed
+
+ bool FindSafePositionProc(TileIndex tile, Trackdir td)
+ {
+ if (IsSafeWaitingPosition(Yapf().GetVehicle(), tile, td, true, !TrackFollower::Allow90degTurns())) {
+ m_res_dest = tile;
+ m_res_dest_td = td;
+ return false; // Stop iterating segment
+ }
+ return true;
+ }
+
+ bool ReserveSingleTrack(TileIndex tile, Trackdir td)
+ {
+ if (!TryReserveRailTrack(tile, TrackdirToTrack(td))) {
+ /* Tile couldn't be reserved, undo. */
+ m_res_fail_tile = tile;
+ m_res_fail_td = td;
+ return false;
+ }
+ /* YAPF can sometimes skip parts of a station, so make sure we
+ * always reserve the whole platform. */
+ if (IsRailwayStationTile(tile)) SetRailwayStationPlatformReservation(tile, TrackdirToExitdir(ReverseTrackdir(td)), true);
+ return tile != m_res_dest;
+ }
+
+ bool UnreserveSingleTrack(TileIndex tile, Trackdir td)
+ {
+ if (tile != m_res_fail_tile || td != m_res_fail_td) UnreserveRailTrack(tile, TrackdirToTrack(td));
+ return tile != m_res_dest && (tile != m_res_fail_tile || td != m_res_fail_td);
+ }
+
+public:
+ /** Set the target to where the reservation should be extended. */
+ inline void SetReservationTarget(Node *node, TileIndex tile, Trackdir td)
+ {
+ m_res_node = node;
+ m_res_dest = tile;
+ m_res_dest_td = td;
+ }
+
+ /** Check the node for a possible reservation target. */
+ inline void FindSafePositionOnNode(Node *node)
+ {
+ assert(node->m_parent != NULL);
+
+ /* We will never pass more than two signals, no need to check for a safe tile. */
+ if (node->m_parent->m_num_signals_passed >= 2) return;
+
+ if (!node->IterateTiles(Yapf().GetVehicle(), Yapf(), *this, &CYapfReserveTrack::FindSafePositionProc)) {
+ m_res_node = node;
+ }
+ }
+
+ /** Try to reserve the path till the reservation target. */
+ bool TryReservePath(PBSTileInfo *target)
+ {
+ m_res_fail_tile = INVALID_TILE;
+ if (target != NULL) {
+ target->tile = m_res_dest;
+ target->trackdir = m_res_dest_td;
+ target->okay = false;
+ }
+
+ /* Don't bother if the target is reserved. */
+ if (!IsWaitingPositionFree(Yapf().GetVehicle(), m_res_dest, m_res_dest_td)) return false;
+
+ for (Node *node = m_res_node; node->m_parent != NULL; node = node->m_parent) {
+ node->IterateTiles(Yapf().GetVehicle(), Yapf(), *this, &CYapfReserveTrack::ReserveSingleTrack);
+ if (m_res_fail_tile != INVALID_TILE) {
+ /* Reservation failed, undo. */
+ Node *fail_node = m_res_node;
+ TileIndex stop_tile = m_res_fail_tile;
+ do {
+ /* If this is the node that failed, stop at the failed tile. */
+ m_res_fail_tile = fail_node == node ? stop_tile : INVALID_TILE;
+ fail_node->IterateTiles(Yapf().GetVehicle(), Yapf(), *this, &CYapfReserveTrack::UnreserveSingleTrack);
+ } while (fail_node != node && (fail_node = fail_node->m_parent) != NULL);
+
+ return false;
+ }
+ }
+
+ if (target != NULL) target->okay = true;
+
+ if (Yapf().CanUseGlobalCache(*m_res_node))
+ YapfNotifyTrackLayoutChange(INVALID_TILE, INVALID_TRACK);
+
+ return true;
+ }
+};
template
class CYapfFollowAnyDepotRailT
@@ -95,7 +204,89 @@ public:
};
template
-class CYapfFollowRailT
+class CYapfFollowAnySafeTileRailT : protected CYapfReserveTrack
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::TrackFollower TrackFollower;
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+protected:
+ /// to access inherited path finder
+ FORCEINLINE Tpf& Yapf() {return *static_cast(this);}
+
+public:
+ /** Called by YAPF to move from the given node to the next tile. For each
+ * reachable trackdir on the new tile creates new node, initializes it
+ * and adds it to the open list by calling Yapf().AddNewNode(n) */
+ inline void PfFollowNode(Node& old_node)
+ {
+ TrackFollower F(Yapf().GetVehicle(), Yapf().GetCompatibleRailTypes());
+ if (F.Follow(old_node.GetLastTile(), old_node.GetLastTrackdir()))
+ Yapf().AddMultipleNodes(&old_node, F);
+ }
+
+ /** Return debug report character to identify the transportation type */
+ FORCEINLINE char TransportTypeChar() const {return 't';}
+
+ static bool stFindNearestSafeTile(const Vehicle *v, TileIndex t1, Trackdir td, bool override_railtype)
+ {
+ /* Create pathfinder instance */
+ Tpf pf1;
+#if !DEBUG_YAPF_CACHE
+ bool result1 = pf1.FindNearestSafeTile(v, t1, td, override_railtype, false);
+
+#else
+ bool result2 = pf1.FindNearestSafeTile(v, t1, td, override_railtype, true);
+ Tpf pf2;
+ pf2.DisableCache(true);
+ bool result1 = pf2.FindNearestSafeTile(v, t1, td, override_railtype, false);
+ if (result1 != result2) {
+ DEBUG(yapf, 0, "CACHE ERROR: FindSafeTile() = [%s, %s]", result2 ? "T" : "F", result1 ? "T" : "F");
+ DumpTarget dmp1, dmp2;
+ pf1.DumpBase(dmp1);
+ pf2.DumpBase(dmp2);
+ FILE *f1 = fopen("C:\\yapf1.txt", "wt");
+ FILE *f2 = fopen("C:\\yapf2.txt", "wt");
+ fwrite(dmp1.m_out.Data(), 1, dmp1.m_out.Size(), f1);
+ fwrite(dmp2.m_out.Data(), 1, dmp2.m_out.Size(), f2);
+ fclose(f1);
+ fclose(f2);
+ }
+#endif
+
+ return result1;
+ }
+
+ bool FindNearestSafeTile(const Vehicle *v, TileIndex t1, Trackdir td, bool override_railtype, bool dont_reserve)
+ {
+ /* Set origin and destination. */
+ Yapf().SetOrigin(t1, td);
+ Yapf().SetDestination(v, override_railtype);
+
+ bool bFound = Yapf().FindPath(v);
+ if (!bFound) return false;
+
+ /* Found a destination, set as reservation target. */
+ Node *pNode = Yapf().GetBestNode();
+ this->SetReservationTarget(pNode, pNode->GetLastTile(), pNode->GetLastTrackdir());
+
+ /* Walk through the path back to the origin. */
+ Node* pPrev = NULL;
+ while (pNode->m_parent != NULL) {
+ pPrev = pNode;
+ pNode = pNode->m_parent;
+
+ this->FindSafePositionOnNode(pPrev);
+ }
+
+ return dont_reserve || this->TryReservePath(NULL);
+ }
+};
+
+template
+class CYapfFollowRailT : protected CYapfReserveTrack
{
public:
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
@@ -121,16 +312,18 @@ public:
/// return debug report character to identify the transportation type
FORCEINLINE char TransportTypeChar() const {return 't';}
- static Trackdir stChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found)
+ static Trackdir stChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found, bool reserve_track, PBSTileInfo *target)
{
// create pathfinder instance
Tpf pf1;
- Trackdir result1 = pf1.ChooseRailTrack(v, tile, enterdir, tracks, path_not_found);
+#if !DEBUG_YAPF_CACHE
+ Trackdir result1 = pf1.ChooseRailTrack(v, tile, enterdir, tracks, path_not_found, reserve_track, target);
-#if DEBUG_YAPF_CACHE
+#else
+ Trackdir result1 = pf1.ChooseRailTrack(v, tile, enterdir, tracks, path_not_found, false, NULL);
Tpf pf2;
pf2.DisableCache(true);
- Trackdir result2 = pf2.ChooseRailTrack(v, tile, enterdir, tracks, path_not_found);
+ Trackdir result2 = pf2.ChooseRailTrack(v, tile, enterdir, tracks, path_not_found, reserve_track, target);
if (result1 != result2) {
DEBUG(yapf, 0, "CACHE ERROR: ChooseRailTrack() = [%d, %d]", result1, result2);
DumpTarget dmp1, dmp2;
@@ -148,10 +341,13 @@ public:
return result1;
}
- FORCEINLINE Trackdir ChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found)
+ FORCEINLINE Trackdir ChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found, bool reserve_track, PBSTileInfo *target)
{
+ if (target != NULL) target->tile = INVALID_TILE;
+
// set origin and destination nodes
- Yapf().SetOrigin(v->tile, GetVehicleTrackdir(v), INVALID_TILE, INVALID_TRACKDIR, 1, true);
+ PBSTileInfo origin = FollowTrainReservation(v);
+ Yapf().SetOrigin(origin.tile, origin.trackdir, INVALID_TILE, INVALID_TRACKDIR, 1, true);
Yapf().SetDestination(v);
// find the best path
@@ -166,17 +362,23 @@ public:
Trackdir next_trackdir = INVALID_TRACKDIR;
Node *pNode = Yapf().GetBestNode();
if (pNode != NULL) {
+ // reserve till end of path
+ this->SetReservationTarget(pNode, pNode->GetLastTile(), pNode->GetLastTrackdir());
+
// path was found or at least suggested
// walk through the path back to the origin
Node* pPrev = NULL;
while (pNode->m_parent != NULL) {
pPrev = pNode;
pNode = pNode->m_parent;
+
+ this->FindSafePositionOnNode(pPrev);
}
// return trackdir from the best origin node (one of start nodes)
Node& best_next_node = *pPrev;
- assert(best_next_node.GetTile() == tile);
next_trackdir = best_next_node.GetTrackdir();
+
+ if (reserve_track && path_found) this->TryReservePath(target);
}
return next_trackdir;
}
@@ -246,11 +448,14 @@ struct CYapfRail2 : CYapfT > {};
struct CYapfAnyDepotRail2 : CYapfT > {};
+struct CYapfAnySafeTileRail1 : CYapfT > {};
+struct CYapfAnySafeTileRail2 : CYapfT > {};
-Trackdir YapfChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found)
+
+Trackdir YapfChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool *path_not_found, bool reserve_track, PBSTileInfo *target)
{
// default is YAPF type 2
- typedef Trackdir (*PfnChooseRailTrack)(const Vehicle*, TileIndex, DiagDirection, TrackBits, bool*);
+ typedef Trackdir (*PfnChooseRailTrack)(const Vehicle*, TileIndex, DiagDirection, TrackBits, bool*, bool, PBSTileInfo*);
PfnChooseRailTrack pfnChooseRailTrack = &CYapfRail1::stChooseRailTrack;
// check if non-default YAPF type needed
@@ -258,7 +463,7 @@ Trackdir YapfChooseRailTrack(const Vehicle *v, TileIndex tile, DiagDirection ent
pfnChooseRailTrack = &CYapfRail2::stChooseRailTrack; // Trackdir, forbid 90-deg
}
- Trackdir td_ret = pfnChooseRailTrack(v, tile, enterdir, tracks, path_not_found);
+ Trackdir td_ret = pfnChooseRailTrack(v, tile, enterdir, tracks, path_not_found, reserve_track, target);
return td_ret;
}
@@ -330,11 +535,8 @@ bool YapfFindNearestRailDepotTwoWay(const Vehicle *v, int max_distance, int reve
const Vehicle *last_veh = GetLastVehicleInChain(v);
- TileIndex tile = v->tile;
+ PBSTileInfo origin = FollowTrainReservation(v);
TileIndex last_tile = last_veh->tile;
-
- // their trackdirs
- Trackdir td = GetVehicleTrackdir(v);
Trackdir td_rev = ReverseTrackdir(GetVehicleTrackdir(last_veh));
typedef bool (*PfnFindNearestDepotTwoWay)(const Vehicle*, TileIndex, Trackdir, TileIndex, Trackdir, int, int, TileIndex*, bool*);
@@ -345,10 +547,23 @@ bool YapfFindNearestRailDepotTwoWay(const Vehicle *v, int max_distance, int reve
pfnFindNearestDepotTwoWay = &CYapfAnyDepotRail2::stFindNearestDepotTwoWay; // Trackdir, forbid 90-deg
}
- bool ret = pfnFindNearestDepotTwoWay(v, tile, td, last_tile, td_rev, max_distance, reverse_penalty, depot_tile, reversed);
+ bool ret = pfnFindNearestDepotTwoWay(v, origin.tile, origin.trackdir, last_tile, td_rev, max_distance, reverse_penalty, depot_tile, reversed);
return ret;
}
+bool YapfRailFindNearestSafeTile(const Vehicle *v, TileIndex tile, Trackdir td, bool override_railtype)
+{
+ typedef bool (*PfnFindNearestSafeTile)(const Vehicle*, TileIndex, Trackdir, bool);
+ PfnFindNearestSafeTile pfnFindNearestSafeTile = CYapfAnySafeTileRail1::stFindNearestSafeTile;
+
+ /* check if non-default YAPF type needed */
+ if (_settings_game.pf.forbid_90_deg) {
+ pfnFindNearestSafeTile = &CYapfAnySafeTileRail2::stFindNearestSafeTile;
+ }
+
+ return pfnFindNearestSafeTile(v, tile, td, override_railtype);
+}
+
/** if any track changes, this counter is incremented - that will invalidate segment cost cache */
int CSegmentCostCacheBase::s_rail_change_counter = 0;