Import signals on tunnels and bridges patch
http://www.tt-forums.net/viewtopic.php?p=1140215#p1140215
This commit is contained in:

committed by
Jonathan G Rennison

parent
4dc65dd9b5
commit
59b0b18aa2
@@ -1527,6 +1527,8 @@ STR_CONFIG_SETTING_ALLOW_SHARES :Allow buying sh
|
|||||||
STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :When enabled, allow buying and selling of company shares. Shares will only be available for companies reaching a certain age
|
STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :When enabled, allow buying and selling of company shares. Shares will only be available for companies reaching a certain age
|
||||||
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Percentage of leg profit to pay in feeder systems: {STRING2}
|
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_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 :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_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}
|
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} tile{P 0 "" s}
|
||||||
@@ -2662,8 +2664,10 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot
|
|||||||
# Industries come directly from their industry names
|
# Industries come directly from their industry names
|
||||||
|
|
||||||
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Railway tunnel
|
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_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_SUSPENSION_STEEL :Steel suspension rail bridge
|
||||||
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Steel girder rail bridge
|
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Steel girder rail bridge
|
||||||
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Steel cantilever rail bridge
|
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Steel cantilever rail bridge
|
||||||
|
@@ -1679,6 +1679,8 @@ STR_CONFIG_SETTING_ALLOW_SHARES :Разреши
|
|||||||
STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :Разрешает торговлю акциями транспортных компаний. Акции выпускаются компаниями через некоторое время после основания.
|
STR_CONFIG_SETTING_ALLOW_SHARES_HELPTEXT :Разрешает торговлю акциями транспортных компаний. Акции выпускаются компаниями через некоторое время после основания.
|
||||||
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Процент дохода, начисляемый при частичной перевозке: {STRING}
|
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE :Процент дохода, начисляемый при частичной перевозке: {STRING}
|
||||||
STR_CONFIG_SETTING_FEEDER_PAYMENT_SHARE_HELPTEXT :Процент прибыли, начисляемый транспорту за частичную перевозку груза.
|
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 :При перетаскивании ставить сигналы каждые: {STRING}
|
||||||
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Настройка периодичности расстановки сигналов методом перетаскивания. Сигналы будут устанавливаться до первого встреченного препятствия (пересечения или другого сигнала).
|
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_HELPTEXT :Настройка периодичности расстановки сигналов методом перетаскивания. Сигналы будут устанавливаться до первого встреченного препятствия (пересечения или другого сигнала).
|
||||||
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} клет{P ку ки ок}
|
STR_CONFIG_SETTING_DRAG_SIGNALS_DENSITY_VALUE :{COMMA} клет{P ку ки ок}
|
||||||
@@ -2842,8 +2844,10 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Верфь
|
|||||||
# Industries come directly from their industry names
|
# Industries come directly from their industry names
|
||||||
|
|
||||||
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Железнодорожный туннель
|
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Железнодорожный туннель
|
||||||
|
STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_SIGNAL :Железнодорожный туннель с симуляцией светофоров
|
||||||
STR_LAI_TUNNEL_DESCRIPTION_ROAD :Автомобильный туннель
|
STR_LAI_TUNNEL_DESCRIPTION_ROAD :Автомобильный туннель
|
||||||
|
|
||||||
|
STR_LAI_BRIDGE_DESCRIPTION_RAILROAD_SIGNAL :Железнодорожный мост с симуляцией светофоров
|
||||||
STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Стальной висячий ж/д мост
|
STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Стальной висячий ж/д мост
|
||||||
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Стальной балочный ж/д мост
|
STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Стальной балочный ж/д мост
|
||||||
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Стальной консольный ж/д мост
|
STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :Стальной консольный ж/д мост
|
||||||
|
@@ -353,7 +353,7 @@ protected:
|
|||||||
if (IsTunnel(m_new_tile)) {
|
if (IsTunnel(m_new_tile)) {
|
||||||
if (!m_is_tunnel) {
|
if (!m_is_tunnel) {
|
||||||
DiagDirection tunnel_enterdir = GetTunnelBridgeDirection(m_new_tile);
|
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;
|
m_err = EC_NO_WAY;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -361,7 +361,7 @@ protected:
|
|||||||
} else { // IsBridge(m_new_tile)
|
} else { // IsBridge(m_new_tile)
|
||||||
if (!m_is_bridge) {
|
if (!m_is_bridge) {
|
||||||
DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile);
|
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;
|
m_err = EC_NO_WAY;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
122
src/rail_cmd.cpp
122
src/rail_cmd.cpp
@@ -1012,9 +1012,12 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
|
|||||||
if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
|
if (sigtype > SIGTYPE_LAST) return CMD_ERROR;
|
||||||
if (cycle_start > cycle_stop || cycle_stop > 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 */
|
/* You can only build signals on plain rail tiles or tunnel/bridges, and the selected track must exist */
|
||||||
if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) ||
|
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
|
||||||
!HasTrack(tile, track)) {
|
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);
|
return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
|
||||||
}
|
}
|
||||||
/* Protect against invalid signal copying */
|
/* Protect against invalid signal copying */
|
||||||
@@ -1023,6 +1026,53 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
|
|||||||
CommandCost ret = CheckTileOwnership(tile);
|
CommandCost ret = CheckTileOwnership(tile);
|
||||||
if (ret.Failed()) return ret;
|
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) */
|
/* 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);
|
if (TracksOverlap(GetTrackBits(tile))) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK);
|
||||||
|
|
||||||
@@ -1032,7 +1082,6 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1,
|
|||||||
/* you can not convert a signal if no signal is on track */
|
/* 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);
|
if (convert_signal && !HasSignalOnTrack(tile, track)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
|
||||||
|
|
||||||
CommandCost cost;
|
|
||||||
if (!HasSignalOnTrack(tile, track)) {
|
if (!HasSignalOnTrack(tile, track)) {
|
||||||
/* build new signals */
|
/* build new signals */
|
||||||
cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]);
|
cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]);
|
||||||
@@ -1190,6 +1239,7 @@ static bool CheckSignalAutoFill(TileIndex &tile, Trackdir &trackdir, int &signal
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MP_TUNNELBRIDGE: {
|
case MP_TUNNELBRIDGE: {
|
||||||
|
if (!remove && HasWormholeSignals(tile)) return false;
|
||||||
TileIndex orig_tile = tile; // backup old value
|
TileIndex orig_tile = tile; // backup old value
|
||||||
|
|
||||||
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false;
|
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false;
|
||||||
@@ -1301,7 +1351,8 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin
|
|||||||
bool had_success = false;
|
bool had_success = false;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
/* only build/remove signals with the specified density */
|
/* only build/remove signals with the specified density */
|
||||||
if (remove || minimise_gaps || signal_ctr % signal_density == 0) {
|
|
||||||
|
if (remove || minimise_gaps || signal_ctr % signal_density == 0 || IsTileType(tile, MP_TUNNELBRIDGE)) {
|
||||||
uint32 p1 = GB(TrackdirToTrack(trackdir), 0, 3);
|
uint32 p1 = GB(TrackdirToTrack(trackdir), 0, 3);
|
||||||
SB(p1, 3, 1, mode);
|
SB(p1, 3, 1, mode);
|
||||||
SB(p1, 4, 1, semaphores);
|
SB(p1, 4, 1, semaphores);
|
||||||
@@ -1337,13 +1388,20 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin
|
|||||||
|
|
||||||
/* Collect cost. */
|
/* Collect cost. */
|
||||||
if (!test_only) {
|
if (!test_only) {
|
||||||
/* Be user-friendly and try placing signals as much as possible */
|
/* Be user-friendly and try placing signals as much as possible */
|
||||||
if (ret.Succeeded()) {
|
if (ret.Succeeded()) {
|
||||||
had_success = true;
|
had_success = true;
|
||||||
total_cost.AddCost(ret);
|
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
|
||||||
last_used_ctr = last_suitable_ctr;
|
if ((!autofill && GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) ||
|
||||||
last_suitable_tile = INVALID_TILE;
|
(autofill && GetTunnelBridgeDirection(tile) != TrackdirToExitdir(trackdir))) {
|
||||||
|
total_cost.AddCost(ret);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
total_cost.AddCost(ret);
|
||||||
|
}
|
||||||
|
last_used_ctr = last_suitable_ctr;
|
||||||
|
last_suitable_tile = INVALID_TILE;
|
||||||
|
} else {
|
||||||
/* The "No railway" error is the least important one. */
|
/* The "No railway" error is the least important one. */
|
||||||
if (ret.GetErrorMessage() != STR_ERROR_THERE_IS_NO_RAILROAD_TRACK ||
|
if (ret.GetErrorMessage() != STR_ERROR_THERE_IS_NO_RAILROAD_TRACK ||
|
||||||
last_error.GetErrorMessage() == INVALID_STRING_ID) {
|
last_error.GetErrorMessage() == INVALID_STRING_ID) {
|
||||||
@@ -1414,22 +1472,48 @@ CommandCost CmdBuildSignalTrack(TileIndex tile, DoCommandFlag flags, uint32 p1,
|
|||||||
CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
|
CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
|
||||||
{
|
{
|
||||||
Track track = Extract<Track, 0, 3>(p1);
|
Track track = Extract<Track, 0, 3>(p1);
|
||||||
|
Money cost = _price[PR_CLEAR_SIGNALS];
|
||||||
|
|
||||||
if (!ValParamTrackOrientation(track) || !IsPlainRailTile(tile) || !HasTrack(tile, track)) {
|
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
|
||||||
return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
|
TileIndex end = GetOtherTunnelBridgeEnd(tile);
|
||||||
}
|
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
|
||||||
if (!HasSignalOnTrack(tile, track)) {
|
if (!HasWormholeSignals(tile)) return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS);
|
||||||
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 */
|
/* Only water can remove signals from anyone */
|
||||||
if (_current_company != OWNER_WATER) {
|
if (_current_company != OWNER_WATER) {
|
||||||
CommandCost ret = CheckTileOwnership(tile);
|
|
||||||
if (ret.Failed()) return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do it? */
|
/* Do it? */
|
||||||
if (flags & DC_EXEC) {
|
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 = NULL;
|
Train *v = NULL;
|
||||||
if (HasReservedTracks(tile, TrackToTrackBits(track))) {
|
if (HasReservedTracks(tile, TrackToTrackBits(track))) {
|
||||||
v = GetTrainForReservation(tile, track);
|
v = GetTrainForReservation(tile, track);
|
||||||
@@ -1465,7 +1549,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1
|
|||||||
MarkTileDirtyByTile(tile);
|
MarkTileDirtyByTile(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_SIGNALS]);
|
return CommandCost(EXPENSES_CONSTRUCTION, cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1529,6 +1529,7 @@ static SettingsContainer &GetSettingsTree()
|
|||||||
SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
|
SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
|
||||||
{
|
{
|
||||||
construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
|
construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
|
||||||
|
construction->Add(new SettingEntry("construction.simulated_wormhole_signals"));
|
||||||
construction->Add(new SettingEntry("gui.enable_signal_gui"));
|
construction->Add(new SettingEntry("gui.enable_signal_gui"));
|
||||||
construction->Add(new SettingEntry("gui.persistent_buildingtools"));
|
construction->Add(new SettingEntry("gui.persistent_buildingtools"));
|
||||||
construction->Add(new SettingEntry("gui.quick_goto"));
|
construction->Add(new SettingEntry("gui.quick_goto"));
|
||||||
|
@@ -311,6 +311,7 @@ struct ConstructionSettings {
|
|||||||
bool freeform_edges; ///< allow terraforming the tiles at the map edges
|
bool freeform_edges; ///< allow terraforming the tiles at the map edges
|
||||||
uint8 extra_tree_placement; ///< (dis)allow building extra trees in-game
|
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
|
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?
|
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?
|
uint16 terraform_frame_burst; ///< how many tile heights may, over a short period, be terraformed?
|
||||||
|
@@ -197,6 +197,14 @@ static Vehicle *TrainOnTileEnum(Vehicle *v, void *)
|
|||||||
return v;
|
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
|
* Perform some operations before adding data into Todo set
|
||||||
@@ -376,17 +384,39 @@ static SigFlags ExploreSegment(Owner owner)
|
|||||||
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue;
|
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue;
|
||||||
DiagDirection dir = GetTunnelBridgeDirection(tile);
|
DiagDirection dir = GetTunnelBridgeDirection(tile);
|
||||||
|
|
||||||
if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
|
if (HasWormholeSignals(tile)) {
|
||||||
if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
|
if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
|
||||||
enterdir = dir;
|
if (!(flags & SF_TRAIN) && IsTunnelBridgeExit(tile)) { // tunnel entrence is ignored
|
||||||
exitdir = ReverseDiagDir(dir);
|
if (HasVehicleOnPos(GetOtherTunnelBridgeEnd(tile), &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
|
||||||
tile += TileOffsByDiagDir(exitdir); // just skip to next tile
|
if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, &tile, &TrainInWormholeTileEnum)) flags |= SF_TRAIN;
|
||||||
} else { // NOT incoming from the wormhole!
|
}
|
||||||
if (ReverseDiagDir(enterdir) != dir) continue;
|
enterdir = dir;
|
||||||
if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
|
exitdir = ReverseDiagDir(dir);
|
||||||
tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
|
tile += TileOffsByDiagDir(exitdir); // just skip to next tile
|
||||||
enterdir = INVALID_DIAGDIR;
|
} else { // NOT incoming from the wormhole!
|
||||||
exitdir = INVALID_DIAGDIR;
|
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, NULL, &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, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
|
||||||
|
tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
|
||||||
|
enterdir = INVALID_DIAGDIR;
|
||||||
|
exitdir = INVALID_DIAGDIR;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -494,7 +524,9 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
|
|||||||
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
|
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
|
||||||
assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
|
assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
|
||||||
_tbdset.Add(tile, INVALID_DIAGDIR); // we can safely start from wormhole centre
|
_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;
|
break;
|
||||||
|
|
||||||
case MP_RAILWAY:
|
case MP_RAILWAY:
|
||||||
|
@@ -538,6 +538,20 @@ str = STR_CONFIG_SETTING_MAX_TUNNEL_LENGTH
|
|||||||
strhelp = STR_CONFIG_SETTING_MAX_TUNNEL_LENGTH_HELPTEXT
|
strhelp = STR_CONFIG_SETTING_MAX_TUNNEL_LENGTH_HELPTEXT
|
||||||
strval = STR_CONFIG_SETTING_TILE_LENGTH
|
strval = STR_CONFIG_SETTING_TILE_LENGTH
|
||||||
|
|
||||||
|
[SDT_VAR]
|
||||||
|
base = GameSettings
|
||||||
|
var = construction.simulated_wormhole_signals
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = 0
|
||||||
|
def = 2
|
||||||
|
min = 1
|
||||||
|
max = 16
|
||||||
|
str = STR_CONFIG_SETTING_SIMULATE_SIGNALS
|
||||||
|
strval = STR_CONFIG_SETTING_SIMULATE_SIGNALS_VALUE
|
||||||
|
proc = RedrawScreen
|
||||||
|
from = 0
|
||||||
|
cat = SC_BASIC
|
||||||
|
|
||||||
# construction.longbridges
|
# construction.longbridges
|
||||||
[SDT_NULL]
|
[SDT_NULL]
|
||||||
length = 1
|
length = 1
|
||||||
|
@@ -1855,6 +1855,17 @@ void ReverseTrainDirection(Train *v)
|
|||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/* TrainExitDir does not always produce the desired dir for depots and
|
/* TrainExitDir does not always produce the desired dir for depots and
|
||||||
* tunnels/bridges that is needed for UpdateSignalsOnSegment. */
|
* tunnels/bridges that is needed for UpdateSignalsOnSegment. */
|
||||||
DiagDirection dir = TrainExitDir(v->direction, v->track);
|
DiagDirection dir = TrainExitDir(v->direction, v->track);
|
||||||
@@ -2191,6 +2202,42 @@ static bool CheckTrainStayInDepot(Train *v)
|
|||||||
return false;
|
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.
|
* Clear the reservation of \a tile that was just left by a wagon on \a track_dir.
|
||||||
* @param v %Train owning the reservation.
|
* @param v %Train owning the reservation.
|
||||||
@@ -2206,7 +2253,8 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
|
|||||||
if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) {
|
if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) {
|
||||||
TileIndex end = GetOtherTunnelBridgeEnd(tile);
|
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. */
|
/* Free the reservation only if no other train is on the tiles. */
|
||||||
SetTunnelBridgeReservation(tile, false);
|
SetTunnelBridgeReservation(tile, false);
|
||||||
SetTunnelBridgeReservation(end, false);
|
SetTunnelBridgeReservation(end, false);
|
||||||
@@ -2216,6 +2264,7 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
|
|||||||
MarkTileDirtyByTile(end);
|
MarkTileDirtyByTile(end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (HasWormholeSignals(tile)) HandleLastTunnelBridgeSignals(tile, end, dir, free);
|
||||||
}
|
}
|
||||||
} else if (IsRailStationTile(tile)) {
|
} else if (IsRailStationTile(tile)) {
|
||||||
TileIndex new_tile = TileAddByDiagDir(tile, dir);
|
TileIndex new_tile = TileAddByDiagDir(tile, dir);
|
||||||
@@ -3083,6 +3132,99 @@ static Vehicle *CheckTrainAtSignal(Vehicle *v, void *data)
|
|||||||
return t;
|
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 != 0) 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 != 0) 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.
|
* Move a vehicle chain one movement stop forwards.
|
||||||
* @param v First vehicle to move.
|
* @param v First vehicle to move.
|
||||||
@@ -3268,6 +3410,23 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
|
|||||||
goto invalid_rail;
|
goto invalid_rail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HasWormholeSignals(gp.new_tile)) {
|
||||||
|
/* If red signal stop. */
|
||||||
|
if (v->IsFrontEngine() && v->force_proceed == 0) {
|
||||||
|
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)) {
|
if (!HasBit(r, VETS_ENTERED_WORMHOLE)) {
|
||||||
Track track = FindFirstTrack(chosen_track);
|
Track track = FindFirstTrack(chosen_track);
|
||||||
Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir);
|
Trackdir tdir = TrackDirectionToTrackdir(track, chosen_dir);
|
||||||
@@ -3320,6 +3479,64 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 == 0 && 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 = 0;
|
||||||
|
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)) {
|
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. */
|
/* Perform look-ahead on tunnel exit. */
|
||||||
if (v->IsFrontEngine()) {
|
if (v->IsFrontEngine()) {
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
#include "date_func.h"
|
#include "date_func.h"
|
||||||
#include "clear_func.h"
|
#include "clear_func.h"
|
||||||
#include "vehicle_func.h"
|
#include "vehicle_func.h"
|
||||||
|
#include "vehicle_gui.h"
|
||||||
#include "sound_func.h"
|
#include "sound_func.h"
|
||||||
#include "tunnelbridge.h"
|
#include "tunnelbridge.h"
|
||||||
#include "cheat_type.h"
|
#include "cheat_type.h"
|
||||||
@@ -1103,6 +1104,103 @@ static void DrawBridgeTramBits(int x, int y, int z, int offset, bool overlay, bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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.
|
* Draws a tunnel of bridge tile.
|
||||||
* For tunnels, this is rather simple, as you only need to draw the entrance.
|
* For tunnels, this is rather simple, as you only need to draw the entrance.
|
||||||
@@ -1212,6 +1310,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, 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);
|
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);
|
DrawBridgeMiddle(ti);
|
||||||
} else { // IsBridge(ti->tile)
|
} else { // IsBridge(ti->tile)
|
||||||
const PalSpriteID *psid;
|
const PalSpriteID *psid;
|
||||||
@@ -1310,6 +1411,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Draw signals for bridge. */
|
||||||
|
if (HasWormholeSignals(ti->tile)) DrawTunnelBridgeRampSignal(ti);
|
||||||
|
|
||||||
DrawBridgeMiddle(ti);
|
DrawBridgeMiddle(ti);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1448,6 +1552,9 @@ void DrawBridgeMiddle(const TileInfo *ti)
|
|||||||
if (HasCatenaryDrawn(GetRailType(rampsouth))) {
|
if (HasCatenaryDrawn(GetRailType(rampsouth))) {
|
||||||
DrawCatenaryOnBridge(ti);
|
DrawCatenaryOnBridge(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 */
|
/* draw roof, the component of the bridge which is logically between the vehicle and the camera */
|
||||||
@@ -1536,9 +1643,9 @@ static void GetTileDesc_TunnelBridge(TileIndex tile, TileDesc *td)
|
|||||||
TransportType tt = GetTunnelBridgeTransportType(tile);
|
TransportType tt = GetTunnelBridgeTransportType(tile);
|
||||||
|
|
||||||
if (IsTunnel(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)
|
} 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);
|
td->owner[0] = GetTileOwner(tile);
|
||||||
|
|
||||||
@@ -1607,6 +1714,26 @@ static void TileLoop_TunnelBridge(TileIndex tile)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool ClickTile_TunnelBridge(TileIndex tile)
|
||||||
|
{
|
||||||
|
/* Show vehicles found in tunnel. */
|
||||||
|
if (IsTunnelTile(tile)) {
|
||||||
|
int count = 0;
|
||||||
|
const Train *t;
|
||||||
|
TileIndex tile_end = GetOtherTunnelBridgeEnd(tile);
|
||||||
|
FOR_ALL_TRAINS(t) {
|
||||||
|
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)
|
static TrackStatus GetTileTrackStatus_TunnelBridge(TileIndex tile, TransportType mode, uint sub_mode, DiagDirection side)
|
||||||
{
|
{
|
||||||
TransportType transport_type = GetTunnelBridgeTransportType(tile);
|
TransportType transport_type = GetTunnelBridgeTransportType(tile);
|
||||||
@@ -1859,7 +1986,7 @@ extern const TileTypeProcs _tile_type_tunnelbridge_procs = {
|
|||||||
NULL, // add_accepted_cargo_proc
|
NULL, // add_accepted_cargo_proc
|
||||||
GetTileDesc_TunnelBridge, // get_tile_desc_proc
|
GetTileDesc_TunnelBridge, // get_tile_desc_proc
|
||||||
GetTileTrackStatus_TunnelBridge, // get_tile_track_status_proc
|
GetTileTrackStatus_TunnelBridge, // get_tile_track_status_proc
|
||||||
NULL, // click_tile_proc
|
ClickTile_TunnelBridge, // click_tile_proc
|
||||||
NULL, // animate_tile_proc
|
NULL, // animate_tile_proc
|
||||||
TileLoop_TunnelBridge, // tile_loop_proc
|
TileLoop_TunnelBridge, // tile_loop_proc
|
||||||
ChangeTileOwner_TunnelBridge, // change_tile_owner_proc
|
ChangeTileOwner_TunnelBridge, // change_tile_owner_proc
|
||||||
|
@@ -121,4 +121,98 @@ static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t)
|
|||||||
return HasTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
|
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 */
|
#endif /* TUNNELBRIDGE_MAP_H */
|
||||||
|
Reference in New Issue
Block a user