diff --git a/src/lang/english.txt b/src/lang/english.txt index 43fdd0ff71..89fe889a67 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1576,6 +1576,8 @@ STR_CONFIG_SETTING_MIN_YEARS_FOR_SHARES :Minimum company STR_CONFIG_SETTING_MIN_YEARS_FOR_SHARES_HELPTEXT :Set the minimum age of a company for others to be able to buy and sell shares from them. 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} @@ -2731,8 +2733,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 81cec7bb6a..8cb09db131 100644 --- a/src/lang/russian.txt +++ b/src/lang/russian.txt @@ -1726,6 +1726,8 @@ STR_CONFIG_SETTING_MIN_YEARS_FOR_SHARES :Мин. воз 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 ку ки ок} @@ -2909,8 +2911,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 5bc9ab03cd..385688b2cd 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -370,7 +370,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; } @@ -378,7 +378,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 133f596437..5a879a45dc 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]); @@ -1247,6 +1296,7 @@ static bool CheckSignalAutoFill(TileIndex &tile, Trackdir &trackdir, int &signal return true; case MP_TUNNELBRIDGE: { + if (!remove && HasWormholeSignals(tile)) return false; TileIndex orig_tile = tile; // backup old value if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false; @@ -1358,7 +1408,8 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin bool had_success = false; for (;;) { /* 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); SB(p1, 3, 1, mode); SB(p1, 4, 1, semaphores); @@ -1394,13 +1445,20 @@ static CommandCost CmdSignalTrackHelper(TileIndex tile, DoCommandFlag flags, uin /* Collect cost. */ if (!test_only) { - /* Be user-friendly and try placing signals as much as possible */ - if (ret.Succeeded()) { - had_success = true; - total_cost.AddCost(ret); - last_used_ctr = last_suitable_ctr; - last_suitable_tile = INVALID_TILE; + /* Be user-friendly and try placing signals as much as possible */ + if (ret.Succeeded()) { + had_success = true; + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if ((!autofill && GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) || + (autofill && GetTunnelBridgeDirection(tile) != TrackdirToExitdir(trackdir))) { + total_cost.AddCost(ret); + } } 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. */ if (ret.GetErrorMessage() != STR_ERROR_THERE_IS_NO_RAILROAD_TRACK || last_error.GetErrorMessage() == INVALID_STRING_ID) { @@ -1471,22 +1529,48 @@ CommandCost CmdBuildSignalTrack(TileIndex tile, DoCommandFlag flags, uint32 p1, CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Track track = Extract(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); @@ -1522,7 +1606,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 d2a22dff4a..2bd6ba2cbf 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1461,6 +1461,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.enable_signal_gui")); construction->Add(new SettingEntry("gui.persistent_buildingtools")); construction->Add(new SettingEntry("gui.quick_goto")); diff --git a/src/settings_type.h b/src/settings_type.h index 3c325a0cc0..a1975c8c3b 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -317,6 +317,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 d6795e3b5c..6a7eca6b64 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 @@ -374,17 +382,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; @@ -492,7 +522,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.ini b/src/table/settings.ini index 1c68e6b731..896725475f 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -542,6 +542,20 @@ str = STR_CONFIG_SETTING_MAX_TUNNEL_LENGTH strhelp = STR_CONFIG_SETTING_MAX_TUNNEL_LENGTH_HELPTEXT 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 = SL_MIN_VERSION +cat = SC_BASIC + # construction.longbridges [SDT_NULL] length = 1 diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 3b8230e5fa..354a38c094 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1859,6 +1859,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); @@ -2193,6 +2204,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. @@ -2208,7 +2255,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); @@ -2222,6 +2270,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); @@ -3088,6 +3137,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. @@ -3273,6 +3415,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); @@ -3325,6 +3484,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 e71440fc2e..d73b93c951 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 62d3c14b2d..fa029dcf4d 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 */