summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErich Eckner <git@eckner.net>2018-11-21 13:43:12 +0100
committerErich Eckner <git@eckner.net>2021-11-23 22:52:03 +0100
commit0465c0a378324835012faecc55a7d0a84b37e70c (patch)
tree478818f8072322cd88ed321c8663d2e532fc5ce6
parentad7bdeb73a99970ea8e7de99dc20131b29fb1b9e (diff)
downloadopenttd-signaltunnel.tar.xz
whateversignaltunnel
-rw-r--r--src/lang/english.txt5
-rw-r--r--src/lang/russian.txt4
-rw-r--r--src/pathfinder/follow_track.hpp4
-rw-r--r--src/rail_cmd.cpp100
-rw-r--r--src/settings_gui.cpp1
-rw-r--r--src/settings_type.h1
-rw-r--r--src/signal.cpp56
-rw-r--r--src/table/settings/game_settings.ini14
-rw-r--r--src/train_cmd.cpp219
-rw-r--r--src/tunnelbridge_cmd.cpp134
-rw-r--r--src/tunnelbridge_map.h94
11 files changed, 601 insertions, 31 deletions
diff --git a/src/lang/english.txt b/src/lang/english.txt
index becf8d35c..8645b5cd1 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1812,6 +1812,9 @@ STR_CONFIG_SETTING_MIN_YEARS_FOR_SHARES_HELPTEXT :Set the minimum
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Percentage of leg profit to pay in feeder systems: {STRING2}
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE_HELPTEXT :Percentage of income given to the intermediate legs in feeder systems, giving more control over the income
+STR_CONFIG_SETTING_SIMULATE_SIGNALS :Simulate signals in tunnels, bridges every: {STRING2}
+STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE :{COMMA} tile{P 0 "" s}
+
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY :When dragging, place signals every: {STRING2}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Set the distance at which signals will be built on a track up to the next obstacle (signal, junction), if signals are dragged
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} tile{P 0 "" s}
@@ -3005,8 +3008,10 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot
# Industries come directly from their industry names
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Railway tunnel
+STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL :Railway tunnel with signal simulation
STR_LAI_TUNNEL_DESCRIPTION_ROAD :Road tunnel
+STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL :Railway bridge with signal simulation
STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Steel suspension rail bridge
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Steel girder rail bridge
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Steel cantilever rail bridge
diff --git a/src/lang/russian.txt b/src/lang/russian.txt
index 534be43c1..658e41ca2 100644
--- a/src/lang/russian.txt
+++ b/src/lang/russian.txt
@@ -1963,6 +1963,8 @@ STR_CONFIG_SETTING_MIN_YEARS_FOR_SHARES_HELPTEXT :Минимал
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Процент дохода, начисляемый при частичной перевозке: {STRING}
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE_HELPTEXT :Процент прибыли, начисляемый транспорту за частичную перевозку груза.
+STR_CONFIG_SETTING_SIMULATE_SIGNALS :Симуляция светофоров в туннелях и на мостах каждые: {STRING}
+STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE :{COMMA} клет{P ку ки ок}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY :При перетаскивании ставить сигналы каждые: {STRING}
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Настройка периодичности расстановки сигналов методом перетаскивания. Сигналы будут устанавливаться до первого встреченного препятствия (пересечения или другого сигнала).
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} клет{P ку ки ок}
@@ -3180,8 +3182,10 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Верфь
# Industries come directly from their industry names
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Железнодорожный туннель
+STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL :Железнодорожный туннель с симуляцией светофоров
STR_LAI_TUNNEL_DESCRIPTION_ROAD :Автомобильный туннель
+STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL :Железнодорожный мост с симуляцией светофоров
STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Стальной висячий ж/д мост
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Стальной балочный ж/д мост
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Стальной консольный ж/д мост
diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp
index a9a51da68..5558c0226 100644
--- a/src/pathfinder/follow_track.hpp
+++ b/src/pathfinder/follow_track.hpp
@@ -350,7 +350,7 @@ protected:
if (IsTunnel(m_new_tile)) {
if (!m_is_tunnel) {
DiagDirection tunnel_enterdir = GetTunnelBridgeDirection(m_new_tile);
- if (tunnel_enterdir != m_exitdir) {
+ if (tunnel_enterdir != m_exitdir || IsTunnelBridgeExit(m_new_tile)) {
m_err = EC_NO_WAY;
return false;
}
@@ -358,7 +358,7 @@ protected:
} else { // IsBridge(m_new_tile)
if (!m_is_bridge) {
DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile);
- if (ramp_enderdir != m_exitdir) {
+ if (ramp_enderdir != m_exitdir || IsTunnelBridgeExit(m_new_tile)) {
m_err = EC_NO_WAY;
return false;
}
diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp
index 4c34f1ca8..e71f562ac 100644
--- a/src/rail_cmd.cpp
+++ b/src/rail_cmd.cpp
@@ -1069,9 +1069,12 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
if (cycle_start > cycle_stop || cycle_stop > SIGTYPE_LAST) return CMD_ERROR;
- /* You can only build signals on plain rail tiles, and the selected track must exist */
- if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) ||
- !HasTrack(tile, track)) {
+ /* You can only build signals on plain rail tiles or tunnel/bridges, and the selected track must exist */
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return CMD_ERROR;
+ CommandCost ret = EnsureNoTrainOnTrack(GetOtherTunnelBridgeEnd(tile), track);
+ //if (ret.Failed()) return ret;
+ } else if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
}
/* Protect against invalid signal copying */
@@ -1080,6 +1083,53 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
CommandCost ret = CheckTileOwnership(tile);
if (ret.Failed()) return ret;
+ CommandCost cost;
+ /* handle signals simulation on tunnel/bridge. */
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ TileIndex tile_exit = GetOtherTunnelBridgeEnd(tile);
+ cost = CommandCost();
+ if (!HasWormholeSignals(tile)) { // toggle signal zero costs.
+ if (p2 != 12) cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS] * ((GetTunnelBridgeLength(tile, tile_exit) + 4) >> 2)); // minimal 1
+ }
+ if (flags & DC_EXEC) {
+ if (p2 == 0 && HasWormholeSignals(tile)){ // Toggle signal if already signals present.
+ if (IsTunnelBridgeEntrance (tile)) {
+ ClrBitTunnelBridgeSignal(tile);
+ ClrBitTunnelBridgeExit(tile_exit);
+ SetBitTunnelBridgeExit(tile);
+ SetBitTunnelBridgeSignal(tile_exit);
+ } else {
+ ClrBitTunnelBridgeSignal(tile_exit);
+ ClrBitTunnelBridgeExit(tile);
+ SetBitTunnelBridgeExit(tile_exit);
+ SetBitTunnelBridgeSignal(tile);
+ }
+ } else{
+ /* Create one direction tunnel/bridge if required. */
+ if (p2 == 0) {
+ SetBitTunnelBridgeSignal(tile);
+ SetBitTunnelBridgeExit(tile_exit);
+ } else if (p2 == 4 || p2 == 8) {
+ DiagDirection tbdir = GetTunnelBridgeDirection(tile);
+ /* If signal only on one side build accoringly one-way tunnel/bridge. */
+ if ((p2 == 8 && (tbdir == DIAGDIR_NE || tbdir == DIAGDIR_SE)) ||
+ (p2 == 4 && (tbdir == DIAGDIR_SW || tbdir == DIAGDIR_NW))) {
+ SetBitTunnelBridgeSignal(tile);
+ SetBitTunnelBridgeExit(tile_exit);
+ } else {
+ SetBitTunnelBridgeSignal(tile_exit);
+ SetBitTunnelBridgeExit(tile);
+ }
+ }
+ }
+ MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(tile_exit);
+ AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company);
+ YapfNotifyTrackLayoutChange(tile, track);
+ }
+ return cost;
+ }
+
/* See if this is a valid track combination for signals (no overlap) */
if (TracksOverlap(GetTrackBits(tile))) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK);
@@ -1089,7 +1139,6 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
/* you can not convert a signal if no signal is on track */
if (convert_signal && !HasSignalOnTrack(tile, track)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
- CommandCost cost;
if (!HasSignalOnTrack(tile, track)) {
/* build new signals */
cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]);
@@ -1243,6 +1292,7 @@ static bool AdvanceSignalAutoFill(TileIndex &tile, Trackdir &trackdir, bool remo
break;
case MP_TUNNELBRIDGE: {
+ if (!remove && HasWormholeSignals(tile)) return false;
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false;
if (GetTunnelBridgeDirection(tile) != TrackdirToExitdir(trackdir)) return false;
break;
@@ -1501,22 +1551,48 @@ CommandCost CmdBuildSignalTrack(TileIndex tile, DoCommandFlag flags, uint32 p1,
CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
{
Track track = Extract<Track, 0, 3>(p1);
+ Money cost = _price[PR_CLEAR_SIGNALS];
- if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
- return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
- }
- if (!HasSignalOnTrack(tile, track)) {
- return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
+ if (IsTileType(tile, MP_TUNNELBRIDGE)) {
+ TileIndex end = GetOtherTunnelBridgeEnd(tile);
+ if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
+ if (!HasWormholeSignals(tile)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
+
+ cost *= ((GetTunnelBridgeLength(tile, end) + 4) >> 2);
+
+ CommandCost ret = EnsureNoTrainOnTrack(GetOtherTunnelBridgeEnd(tile), track);
+ //if (ret.Failed()) return ret;
+ } else {
+ if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
+ return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
+ }
+ if (!HasSignalOnTrack(tile, track)) {
+ return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
+ }
+ CommandCost ret = EnsureNoTrainOnTrack(tile, track);
+ //if (ret.Failed()) return ret;
}
/* Only water can remove signals from anyone */
if (_current_company != OWNER_WATER) {
- CommandCost ret = CheckTileOwnership(tile);
- if (ret.Failed()) return ret;
}
/* Do it? */
if (flags & DC_EXEC) {
+
+ if (HasWormholeSignals(tile)) { // handle tunnel/bridge signals.
+ TileIndex end = GetOtherTunnelBridgeEnd(tile);
+ ClrBitTunnelBridgeExit(tile);
+ ClrBitTunnelBridgeExit(end);
+ ClrBitTunnelBridgeSignal(tile);
+ ClrBitTunnelBridgeSignal(end);
+ _m[tile].m2 = 0;
+ _m[end].m2 = 0;
+ MarkTileDirtyByTile(tile);
+ MarkTileDirtyByTile(end);
+ return CommandCost(EXPENSES_CONSTRUCTION, cost);
+ }
+
Train *v = nullptr;
if (HasReservedTracks(tile, TrackToTrackBits(track))) {
v = GetTrainForReservation(tile, track);
@@ -1552,7 +1628,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1
MarkTileDirtyByTile(tile);
}
- return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_SIGNALS]);
+ return CommandCost(EXPENSES_CONSTRUCTION, cost);
}
/**
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index f99ef8f3e..a913884da 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -1625,6 +1625,7 @@ static SettingsContainer &GetSettingsTree()
SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
{
construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
+ construction->Add(new SettingEntry("construction.simulated_wormhole_signals"));
construction->Add(new SettingEntry("gui.persistent_buildingtools"));
construction->Add(new SettingEntry("gui.quick_goto"));
construction->Add(new SettingEntry("gui.default_rail_type"));
diff --git a/src/settings_type.h b/src/settings_type.h
index 9da2655d6..d37f5e18d 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -345,6 +345,7 @@ struct ConstructionSettings {
bool freeform_edges; ///< allow terraforming the tiles at the map edges
uint8 extra_tree_placement; ///< (dis)allow building extra trees in-game
uint8 command_pause_level; ///< level/amount of commands that can't be executed while paused
+ byte simulated_wormhole_signals; ///< simulate signals in tunnel
uint32 terraform_per_64k_frames; ///< how many tile heights may, over a long period, be terraformed per 65536 frames?
uint16 terraform_frame_burst; ///< how many tile heights may, over a short period, be terraformed?
diff --git a/src/signal.cpp b/src/signal.cpp
index 329b1b05d..e058edf5e 100644
--- a/src/signal.cpp
+++ b/src/signal.cpp
@@ -195,6 +195,14 @@ static Vehicle *TrainOnTileEnum(Vehicle *v, void *)
return v;
}
+/** Check whether there is a train only on ramp. */
+static Vehicle *TrainInWormholeTileEnum(Vehicle *v, void *data)
+{
+ /* Only look for front engine or last wagon. */
+ if (v->type != VEH_TRAIN || (v->Previous() != NULL && v->Next() != NULL)) return NULL;
+ if (*(TileIndex *)data != TileVirtXY(v->x_pos, v->y_pos)) return NULL;
+ return v;
+}
/**
* Perform some operations before adding data into Todo set
@@ -372,17 +380,39 @@ static SigFlags ExploreSegment(Owner owner)
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue;
DiagDirection dir = GetTunnelBridgeDirection(tile);
- if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN;
- enterdir = dir;
- exitdir = ReverseDiagDir(dir);
- tile += TileOffsByDiagDir(exitdir); // just skip to next tile
- } else { // NOT incoming from the wormhole!
- if (ReverseDiagDir(enterdir) != dir) continue;
- if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN;
- tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
- enterdir = INVALID_DIAGDIR;
- exitdir = INVALID_DIAGDIR;
+ if (HasWormholeSignals(tile)) {
+ if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
+ if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) { // tunnel entrence is ignored
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ }
+ enterdir = dir;
+ exitdir = ReverseDiagDir(dir);
+ tile += TileOffsByDiagDir(exitdir); // just skip to next tile
+ } else { // NOT incoming from the wormhole!
+ if (ReverseDiagDir(enterdir) != dir) continue;
+ if (!(flags & SF_TRAIN)) {
+ if (HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) {
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
+ }
+ }
+ continue;
+ }
+ } else {
+ if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
+ if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ enterdir = dir;
+ exitdir = ReverseDiagDir(dir);
+ tile += TileOffsByDiagDir(exitdir); // just skip to next tile
+ } else { // NOT incoming from the wormhole!
+ if (ReverseDiagDir(enterdir) != dir) continue;
+ if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN;
+ tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
+ enterdir = INVALID_DIAGDIR;
+ exitdir = INVALID_DIAGDIR;
+ }
+
}
}
break;
@@ -490,7 +520,9 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
_tbdset.Add(tile, INVALID_DIAGDIR); // we can safely start from wormhole centre
- _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
+ if (!HasWormholeSignals(tile)) { // Don't worry with other side of tunnel.
+ _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
+ }
break;
case MP_RAILWAY:
diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini
index 79fac7513..503863c96 100644
--- a/src/table/settings/game_settings.ini
+++ b/src/table/settings/game_settings.ini
@@ -406,6 +406,20 @@ min = 5
max = 800
to = SLV_120
+[SDT_VAR]
+base = GameSettings
+var = construction.simulated_wormhole_signals
+type = SLE_UINT8
+flags = SF_NONE
+def = 2
+min = 1
+max = 16
+str = STR_CONFIG_SETTING_SIMULATE_SIGNALS
+strval = STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE
+proc = RedrawScreen
+from = SL_MIN_VERSION
+cat = SC_BASIC
+
## These were once in the "gui" section, but they really are related to orders.
[SDTC_BOOL]
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index f5eb32d7f..5b9830b02 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -1876,6 +1876,17 @@ void ReverseTrainDirection(Train *v)
return;
}
+ /* We are inside tunnel/bidge with signals, reversing will close the entrance. */
+ if (HasWormholeSignals(v->tile)) {
+ /* Flip signal on tunnel entrance tile red. */
+ SetBitTunnelBridgeExit(v->tile);
+ MarkTileDirtyByTile(v->tile);
+ /* Clear counters. */
+ v->wait_counter = 0;
+ v->load_unload_ticks = 0;
+ return;
+ }
+
/* VehicleExitDir does not always produce the desired dir for depots and
* tunnels/bridges that is needed for UpdateSignalsOnSegment. */
DiagDirection dir = VehicleExitDir(v->direction, v->track);
@@ -2210,6 +2221,42 @@ static bool CheckTrainStayInDepot(Train *v)
return false;
}
+static void HandleLastTunnelBridgeSignals(TileIndex tile, TileIndex end, DiagDirection dir, bool free)
+{
+ if (IsBridge(end) && _m[end].m2 > 0){
+ /* Clearing last bridge signal. */
+ uint16 m = _m[end].m2;
+ byte i = 15;
+ while((m & 0x8000) == 0 && --i > 0) m <<= 1;
+ ClrBit(_m[end].m2, i);
+
+ uint x = TileX(end)* TILE_SIZE;
+ uint y = TileY(end)* TILE_SIZE;
+ uint distance = (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals) * ++i;
+ switch (dir) {
+ default: NOT_REACHED();
+ case DIAGDIR_NE: MarkTileDirtyByTile(TileVirtXY(x - distance, y)); break;
+ case DIAGDIR_SE: MarkTileDirtyByTile(TileVirtXY(x, y + distance)); break;
+ case DIAGDIR_SW: MarkTileDirtyByTile(TileVirtXY(x + distance, y)); break;
+ case DIAGDIR_NW: MarkTileDirtyByTile(TileVirtXY(x, y - distance)); break;
+ }
+ MarkTileDirtyByTile(tile);
+ }
+ if (free) {
+ /* Open up the wormhole and clear m2. */
+ _m[tile].m2 = 0;
+ _m[end].m2 = 0;
+
+ if (IsTunnelBridgeWithSignRed(end)) {
+ ClrBitTunnelBridgeExit(end);
+ if (!_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(end);
+ } else if (IsTunnelBridgeWithSignRed(tile)) {
+ ClrBitTunnelBridgeExit(tile);
+ if (!_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(tile);
+ }
+ }
+}
+
/**
* Clear the reservation of \a tile that was just left by a wagon on \a track_dir.
* @param v %Train owning the reservation.
@@ -2225,7 +2272,8 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) {
TileIndex end = GetOtherTunnelBridgeEnd(tile);
- if (TunnelBridgeIsFree(tile, end, v).Succeeded()) {
+ bool free = TunnelBridgeIsFree(tile, end, v).Succeeded();
+ if (free) {
/* Free the reservation only if no other train is on the tiles. */
SetTunnelBridgeReservation(tile, false);
SetTunnelBridgeReservation(end, false);
@@ -2239,6 +2287,7 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
}
}
}
+ if (HasWormholeSignals(tile)) HandleLastTunnelBridgeSignals(tile, end, dir, free);
}
} else if (IsRailStationTile(tile)) {
TileIndex new_tile = TileAddByDiagDir(tile, dir);
@@ -3105,6 +3154,99 @@ static Vehicle *CheckTrainAtSignal(Vehicle *v, void *data)
return t;
}
+/** Find train in front and keep distance between trains in tunnel/bridge. */
+static Vehicle *FindSpaceBetweenTrainsEnum(Vehicle *v, void *data)
+{
+ /* Don't look at wagons between front and back of train. */
+ if (v->type != VEH_TRAIN || (v->Previous() != NULL && v->Next() != NULL)) return NULL;
+
+ const Vehicle *u = (Vehicle*)data;
+ int32 a, b = 0;
+
+ switch (u->direction) {
+ default: NOT_REACHED();
+ case DIR_NE: a = u->x_pos; b = v->x_pos; break;
+ case DIR_SE: a = v->y_pos; b = u->y_pos; break;
+ case DIR_SW: a = v->x_pos; b = u->x_pos; break;
+ case DIR_NW: a = u->y_pos; b = v->y_pos; break;
+ }
+
+ if (a > b && a <= (b + (int)(Train::From(u)->wait_counter)) + (int)(TILE_SIZE)) return v;
+ return NULL;
+}
+
+static bool IsToCloseBehindTrain(Vehicle *v, TileIndex tile, bool check_endtile)
+{
+ Train *t = (Train *)v;
+
+ if (t->force_proceed != TFP_NONE) return false;
+
+ if (HasVehicleOnPos(t->tile, v, &FindSpaceBetweenTrainsEnum)) {
+ /* Revert train if not going with tunnel direction. */
+ if (DirToDiagDir(t->direction) != GetTunnelBridgeDirection(t->tile)) {
+ v->cur_speed = 0;
+ ToggleBit(t->flags, VRF_REVERSING);
+ }
+ return true;
+ }
+ /* Cover blind spot at end of tunnel bridge. */
+ if (check_endtile){
+ if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(t->tile), v, &FindSpaceBetweenTrainsEnum)) {
+ /* Revert train if not going with tunnel direction. */
+ if (DirToDiagDir(t->direction) != GetTunnelBridgeDirection(t->tile)) {
+ v->cur_speed = 0;
+ ToggleBit(t->flags, VRF_REVERSING);
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/** Simulate signals in tunnel - bridge. */
+static bool CheckTrainStayInWormHole(Train *t, TileIndex tile)
+{
+ if (t->force_proceed != TFP_NONE) return false;
+
+ /* When not exit reverse train. */
+ if (!IsTunnelBridgeExit(tile)) {
+ t->cur_speed = 0;
+ ToggleBit(t->flags, VRF_REVERSING);
+ return true;
+ }
+ SigSegState seg_state = _settings_game.pf.reserve_paths ? SIGSEG_PBS : UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, t->owner);
+ if (seg_state == SIGSEG_FULL || (seg_state == SIGSEG_PBS && !TryPathReserve(t))) {
+ t->cur_speed = 0;
+ return true;
+ }
+
+ return false;
+}
+
+static void HandleSignalBehindTrain(Train *v, uint signal_number)
+{
+ TileIndex tile;
+ switch (v->direction) {
+ default: NOT_REACHED();
+ case DIR_NE: tile = TileVirtXY(v->x_pos + (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals), v->y_pos); break;
+ case DIR_SE: tile = TileVirtXY(v->x_pos, v->y_pos - (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals) ); break;
+ case DIR_SW: tile = TileVirtXY(v->x_pos - (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals), v->y_pos); break;
+ case DIR_NW: tile = TileVirtXY(v->x_pos, v->y_pos + (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals)); break;
+ }
+
+ if(tile == v->tile) {
+ /* Flip signal on ramp. */
+ if (IsTunnelBridgeWithSignRed(tile)) {
+ ClrBitTunnelBridgeExit(tile);
+ MarkTileDirtyByTile(tile);
+ }
+ } else if (IsBridge(v->tile) && signal_number <= 16) {
+ ClrBit(_m[v->tile].m2, signal_number);
+ MarkTileDirtyByTile(tile);
+ }
+}
+
/**
* Move a vehicle chain one movement stop forwards.
* @param v First vehicle to move.
@@ -3290,6 +3432,23 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
goto invalid_rail;
}
+ if (HasWormholeSignals(gp.new_tile)) {
+ /* If red signal stop. */
+ if (v->IsFrontEngine() && v->force_proceed == TFP_NONE) {
+ if (IsTunnelBridgeWithSignRed(gp.new_tile)) {
+ v->cur_speed = 0;
+ return false;
+ }
+ if (IsTunnelBridgeExit(gp.new_tile)) {
+ v->cur_speed = 0;
+ goto invalid_rail;
+ }
+ /* Flip signal on tunnel entrance tile red. */
+ SetBitTunnelBridgeExit(gp.new_tile);
+ MarkTileDirtyByTile(gp.new_tile);
+ }
+ }
+
if (!HasBit(r, VETS_ENTERED_WORMHOLE)) {
Track track = FindFirstTrack(chosen_track);
Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir);
@@ -3342,6 +3501,64 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
}
}
} else {
+ /* Handle signal simulation on tunnel/bridge. */
+ TileIndex old_tile = TileVirtXY(v->x_pos, v->y_pos);
+ if (old_tile != gp.new_tile && HasWormholeSignals(v->tile) && (v->IsFrontEngine() || v->Next() == NULL)){
+ if (old_tile == v->tile) {
+ if (v->IsFrontEngine() && v->force_proceed == TFP_NONE && IsTunnelBridgeExit(v->tile)) goto invalid_rail;
+ /* Entered wormhole set counters. */
+ v->wait_counter = (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals) - TILE_SIZE;
+ v->load_unload_ticks = 0;
+ }
+
+ uint distance = v->wait_counter;
+ bool leaving = false;
+ if (distance == 0) v->wait_counter = (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals);
+
+ if (v->IsFrontEngine()) {
+ /* Check if track in front is free and see if we can leave wormhole. */
+ int z = GetSlopePixelZ(gp.x, gp.y) - v->z_pos;
+ if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && !(abs(z) > 2)) {
+ if (CheckTrainStayInWormHole(v, gp.new_tile)) return false;
+ leaving = true;
+ } else {
+ if (IsToCloseBehindTrain(v, gp.new_tile, distance == 0)) {
+ if (distance == 0) v->wait_counter = 0;
+ v->cur_speed = 0;
+ return false;
+ }
+ /* flip signal in front to red on bridges*/
+ if (distance == 0 && v->load_unload_ticks <= 15 && IsBridge(v->tile)){
+ SetBit(_m[v->tile].m2, v->load_unload_ticks);
+ MarkTileDirtyByTile(gp.new_tile);
+ }
+ }
+ }
+ if (v->Next() == NULL) {
+ if (v->load_unload_ticks > 0 && v->load_unload_ticks <= 16 && distance == (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals) - TILE_SIZE) HandleSignalBehindTrain(v, v->load_unload_ticks - 2);
+ if (old_tile == v->tile) {
+ /* We left ramp into wormhole. */
+ v->x_pos = gp.x;
+ v->y_pos = gp.y;
+ UpdateSignalsOnSegment(old_tile, INVALID_DIAGDIR, v->owner);
+ }
+ }
+ if (distance == 0) v->load_unload_ticks++;
+ v->wait_counter -= TILE_SIZE;
+
+ if (leaving) { // Reset counters.
+ v->force_proceed = TFP_NONE;
+ v->wait_counter = 0;
+ v->load_unload_ticks = 0;
+ v->x_pos = gp.x;
+ v->y_pos = gp.y;
+ v->UpdatePosition();
+ v->UpdateViewport(false, false);
+ UpdateSignalsOnSegment(gp.new_tile, INVALID_DIAGDIR, v->owner);
+ continue;
+ }
+ }
+
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 (v->IsFrontEngine()) {
diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp
index 5181dfdca..e9df7e23b 100644
--- a/src/tunnelbridge_cmd.cpp
+++ b/src/tunnelbridge_cmd.cpp
@@ -28,6 +28,7 @@
#include "date_func.h"
#include "clear_func.h"
#include "vehicle_func.h"
+#include "vehicle_gui.h"
#include "sound_func.h"
#include "tunnelbridge.h"
#include "cheat_type.h"
@@ -1259,6 +1260,103 @@ static void DrawBridgeRoadBits(TileIndex head_tile, int x, int y, int z, int off
}
}
+/* Draws a signal on tunnel / bridge entrance tile. */
+static void DrawTunnelBridgeRampSignal(const TileInfo *ti)
+{
+ bool side = (_settings_game.vehicle.road_side != 0) &&_settings_game.construction.train_signal_side;
+
+ static const Point SignalPositions[2][4] = {
+ { /* X X Y Y Signals on the left side */
+ {13, 3}, { 2, 13}, { 3, 4}, {13, 14}
+ }, {/* X X Y Y Signals on the right side */
+ {14, 13}, { 3, 3}, {13, 2}, { 3, 13}
+ }
+ };
+
+ uint position;
+ DiagDirection dir = GetTunnelBridgeDirection(ti->tile);
+
+ switch (dir) {
+ default: NOT_REACHED();
+ case DIAGDIR_NE: position = 0; break;
+ case DIAGDIR_SE: position = 2; break;
+ case DIAGDIR_SW: position = 1; break;
+ case DIAGDIR_NW: position = 3; break;
+ }
+
+ uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][position].x;
+ uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][position].y;
+ uint z = ti->z;
+
+ if (ti->tileh != SLOPE_FLAT && IsBridge(ti->tile)) z += 8; // sloped bridge head
+ SignalVariant variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
+
+ SpriteID sprite;
+ if (variant == SIG_ELECTRIC) {
+ /* Normal electric signals are picked from original sprites. */
+ sprite = SPR_ORIGINAL_SIGNALS_BASE + ((position << 1) + IsTunnelBridgeWithSignGreen(ti->tile));
+ } else {
+ /* All other signals are picked from add on sprites. */
+ sprite = SPR_SIGNALS_BASE + ((SIGTYPE_NORMAL - 1) * 16 + variant * 64 + (position << 1) + IsTunnelBridgeWithSignGreen(ti->tile));
+ }
+
+ AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR);
+}
+
+/* Draws a signal on tunnel / bridge entrance tile. */
+static void DrawBrigeSignalOnMiddelPart(const TileInfo *ti, TileIndex bridge_start_tile, uint z)
+{
+
+ uint bridge_signal_position = 0;
+ int m2_position = 0;
+
+ uint bridge_section = GetTunnelBridgeLength(ti->tile, bridge_start_tile) + 1;
+
+ while (bridge_signal_position <= bridge_section) {
+ bridge_signal_position += _settings_game.construction.simulated_wormhole_signals;
+ if (bridge_signal_position == bridge_section) {
+ bool side = (_settings_game.vehicle.road_side != 0) && _settings_game.construction.train_signal_side;
+
+ static const Point SignalPositions[2][4] = {
+ { /* X X Y Y Signals on the left side */
+ {11, 3}, { 4, 13}, { 3, 4}, {11, 13}
+ }, {/* X X Y Y Signals on the right side */
+ {11, 13}, { 4, 3}, {13, 4}, { 3, 11}
+ }
+ };
+
+ uint position;
+
+ switch (GetTunnelBridgeDirection(bridge_start_tile)) {
+ default: NOT_REACHED();
+ case DIAGDIR_NE: position = 0; break;
+ case DIAGDIR_SE: position = 2; break;
+ case DIAGDIR_SW: position = 1; break;
+ case DIAGDIR_NW: position = 3; break;
+ }
+
+ uint x = TileX(ti->tile) * TILE_SIZE + SignalPositions[side][position].x;
+ uint y = TileY(ti->tile) * TILE_SIZE + SignalPositions[side][position].y;
+ z += 5;
+
+ SignalVariant variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
+
+ SpriteID sprite;
+
+ if (variant == SIG_ELECTRIC) {
+ /* Normal electric signals are picked from original sprites. */
+ sprite = SPR_ORIGINAL_SIGNALS_BASE + ((position << 1) + !HasBit(_m[bridge_start_tile].m2, m2_position));
+ } else {
+ /* All other signals are picked from add on sprites. */
+ sprite = SPR_SIGNALS_BASE + ((SIGTYPE_NORMAL - 1) * 16 + variant * 64 + (position << 1) + !HasBit(_m[bridge_start_tile].m2, m2_position));
+ }
+
+ AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR);
+ }
+ m2_position++;
+ }
+}
+
/**
* Draws a tunnel of bridge tile.
* For tunnels, this is rather simple, as you only need to draw the entrance.
@@ -1390,6 +1488,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x, ti->y, BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x + BB_data[4], ti->y + BB_data[5], BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
+ /* Draw signals for tunnel. */
+ if (IsTunnelBridgeEntrance(ti->tile)) DrawTunnelBridgeRampSignal(ti);
+
DrawBridgeMiddle(ti);
} else { // IsBridge(ti->tile)
const PalSpriteID *psid;
@@ -1495,6 +1596,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
}
}
+ /* Draw signals for bridge. */
+ if (HasWormholeSignals(ti->tile)) DrawTunnelBridgeRampSignal(ti);
+
DrawBridgeMiddle(ti);
}
}
@@ -1636,6 +1740,9 @@ void DrawBridgeMiddle(const TileInfo *ti)
if (HasRailCatenaryDrawn(GetRailType(rampsouth))) {
DrawRailCatenaryOnBridge(ti);
}
+ if (HasWormholeSignals(rampsouth)) {
+ IsTunnelBridgeExit(rampsouth) ? DrawBrigeSignalOnMiddelPart(ti, rampnorth, z): DrawBrigeSignalOnMiddelPart(ti, rampsouth, z);
+ }
}
/* draw roof, the component of the bridge which is logically between the vehicle and the camera */
@@ -1709,9 +1816,9 @@ static void GetTileDesc_TunnelBridge(TileIndex tile, TileDesc *td)
TransportType tt = GetTunnelBridgeTransportType(tile);
if (IsTunnel(tile)) {
- td->str = (tt == TRANSPORT_RAIL) ? STR_LAI_TUNNEL_DESCRIPTION_RAILROAD : STR_LAI_TUNNEL_DESCRIPTION_ROAD;
+ td->str = (tt == TRANSPORT_RAIL) ? HasWormholeSignals(tile) ? STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL : STR_LAI_TUNNEL_DESCRIPTION_RAILROAD : STR_LAI_TUNNEL_DESCRIPTION_ROAD;
} else { // IsBridge(tile)
- td->str = (tt == TRANSPORT_WATER) ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : GetBridgeSpec(GetBridgeType(tile))->transport_name[tt];
+ td->str = (tt == TRANSPORT_WATER) ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : HasWormholeSignals(tile) ? STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL : GetBridgeSpec(GetBridgeType(tile))->transport_name[tt];
}
td->owner[0] = GetTileOwner(tile);
@@ -1794,6 +1901,25 @@ static void TileLoop_TunnelBridge(TileIndex tile)
}
}
+static bool ClickTile_TunnelBridge(TileIndex tile)
+{
+ /* Show vehicles found in tunnel. */
+ if (IsTunnelTile(tile)) {
+ int count = 0;
+ TileIndex tile_end = GetOtherTunnelBridgeEnd(tile);
+ for (Train *t : Train::Iterate()) {
+ if (!t->IsFrontEngine()) continue;
+ if (tile == t->tile || tile_end == t->tile) {
+ ShowVehicleViewWindow(t);
+ count++;
+ }
+ if (count > 19) break; // no more than 20 windows open
+ }
+ if (count > 0) return true;
+ }
+ return false;
+}
+
static TrackStatus GetTileTrackStatus_TunnelBridge(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side)
{
TransportType transport_type = GetTunnelBridgeTransportType(tile);
@@ -2047,8 +2173,8 @@ extern const TileTypeProcs _tile_type_tunnelbridge_procs = {
nullptr, // add_accepted_cargo_proc
GetTileDesc_TunnelBridge, // get_tile_desc_proc
GetTileTrackStatus_TunnelBridge, // get_tile_track_status_proc
- nullptr, // click_tile_proc
- nullptr, // animate_tile_proc
+ ClickTile_TunnelBridge, // click_tile_proc
+ nullptr, // animate_tile_proc
TileLoop_TunnelBridge, // tile_loop_proc
ChangeTileOwner_TunnelBridge, // change_tile_owner_proc
nullptr, // add_produced_cargo_proc
diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h
index 62d3c14b2..fa029dcf4 100644
--- a/src/tunnelbridge_map.h
+++ b/src/tunnelbridge_map.h
@@ -119,4 +119,98 @@ static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t)
return HasTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
}
+/**
+ * Declare tunnel/bridge with signal simulation.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void SetBitTunnelBridgeSignal(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ SetBit(_m[t].m5, 5);
+}
+
+/**
+ * Remove tunnel/bridge with signal simulation.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void ClrBitTunnelBridgeSignal(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ ClrBit(_m[t].m5, 5);
+}
+
+/**
+ * Declare tunnel/bridge exit.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void SetBitTunnelBridgeExit(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ SetBit(_m[t].m5, 6);
+}
+
+/**
+ * Remove tunnel/bridge exit declaration.
+ * @param t the tunnel/bridge tile.
+ */
+static inline void ClrBitTunnelBridgeExit(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ ClrBit(_m[t].m5, 6);
+}
+
+/**
+ * Is this a tunnel/bridge pair with signal simulation?
+ * On tunnel/bridge pair minimal one of the two bits is set.
+ * @param t the tile that might be a tunnel/bridge.
+ * @return true if and only if this tile is a tunnel/bridge with signal simulation.
+ */
+static inline bool HasWormholeSignals(TileIndex t)
+{
+ return IsTileType(t, MP_TUNNELBRIDGE) && (HasBit(_m[t].m5, 5) || HasBit(_m[t].m5, 6)) ;
+}
+
+/**
+ * Is this a tunnel/bridge with sign on green?
+ * @param t the tile that might be a tunnel/bridge with sign set green.
+ * @pre IsTileType(t, MP_TUNNELBRIDGE)
+ * @return true if and only if this tile is a tunnel/bridge entrance.
+ */
+static inline bool IsTunnelBridgeWithSignGreen(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return HasBit(_m[t].m5, 5) && !HasBit(_m[t].m5, 6);
+}
+
+static inline bool IsTunnelBridgeWithSignRed(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return HasBit(_m[t].m5, 5) && HasBit(_m[t].m5, 6);
+}
+
+/**
+ * Is this a tunnel/bridge entrance tile with signal?
+ * Tunnel bridge signal simulation has allways bit 5 on at entrance.
+ * @param t the tile that might be a tunnel/bridge.
+ * @return true if and only if this tile is a tunnel/bridge entrance.
+ */
+static inline bool IsTunnelBridgeEntrance(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return HasBit(_m[t].m5, 5) ;
+}
+
+/**
+ * Is this a tunnel/bridge exit?
+ * @param t the tile that might be a tunnel/bridge.
+ * @return true if and only if this tile is a tunnel/bridge exit.
+ */
+static inline bool IsTunnelBridgeExit(TileIndex t)
+{
+ assert(IsTileType(t, MP_TUNNELBRIDGE));
+ return !HasBit(_m[t].m5, 5) && HasBit(_m[t].m5, 6);
+}
+
+
+
#endif /* TUNNELBRIDGE_MAP_H */