diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 924cc326f1..29f6f71c73 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -3005,6 +3005,35 @@ DEF_CONSOLE_CMD(ConFramerateWindow) return true; } +DEF_CONSOLE_CMD(ConFindNonRealisticBrakingSignal) +{ + if (argc == 0) { + IConsoleHelp("Find next signal tile which prevents enabling of realitic braking"); + return true; + } + + for (TileIndex t = 0; t < MapSize(); t++) { + if (IsTileType(t, MP_RAILWAY) && GetRailTileType(t) == RAIL_TILE_SIGNALS) { + uint signals = GetPresentSignals(t); + if ((signals & 0x3) & ((signals & 0x3) - 1) || (signals & 0xC) & ((signals & 0xC) - 1)) { + /* Signals in both directions */ + ScrollMainWindowToTile(t); + SetRedErrorSquare(t); + return true; + } + if (((signals & 0x3) && IsSignalTypeUnsuitableForRealisticBraking(GetSignalType(t, TRACK_LOWER))) || + ((signals & 0xC) && IsSignalTypeUnsuitableForRealisticBraking(GetSignalType(t, TRACK_UPPER)))) { + /* Banned signal types present */ + ScrollMainWindowToTile(t); + SetRedErrorSquare(t); + return true; + } + } + } + + return true; +} + /******************************* * console command registration *******************************/ @@ -3156,6 +3185,8 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("fps", ConFramerate); IConsoleCmdRegister("fps_wnd", ConFramerateWindow); + IConsoleCmdRegister("find_non_realistic_braking_signal", ConFindNonRealisticBrakingSignal); + IConsoleCmdRegister("getfulldate", ConGetFullDate, nullptr, true); IConsoleCmdRegister("dump_command_log", ConDumpCommandLog, nullptr, true); IConsoleCmdRegister("dump_desync_msgs", ConDumpDesyncMsgLog, nullptr, true); diff --git a/src/direction_type.h b/src/direction_type.h index 55e430e8cd..71f253edde 100644 --- a/src/direction_type.h +++ b/src/direction_type.h @@ -59,7 +59,7 @@ template <> struct EnumPropsT : MakeEnumPropsT::CargoChanged() for (T *u = T::From(this); u != nullptr; u = u->Next()) { uint32 current_weight = u->GetWeight(); + if (Type == VEH_TRAIN) Train::From(u)->tcache.cached_veh_weight = current_weight; weight += current_weight; /* Slope steepness is in percent, result in N. */ u->gcache.cached_slope_resistance = current_weight * u->GetSlopeSteepness() * 100; @@ -123,10 +124,10 @@ void GroundVehicle::CargoChanged() /** * Calculates the acceleration of the vehicle under its current conditions. - * @return Current acceleration of the vehicle. + * @return Current upper and lower bounds of acceleration of the vehicle. */ template -int GroundVehicle::GetAcceleration() +GroundVehicleAcceleration GroundVehicle::GetAcceleration() { /* Templated class used for function calls for performance reasons. */ const T *v = T::From(this); @@ -172,6 +173,8 @@ int GroundVehicle::GetAcceleration() /* This value allows to know if the vehicle is accelerating or braking. */ AccelStatus mode = v->GetAccelerationStatus(); + const int braking_power = power; + /* handle breakdown power reduction */ uint32 max_te = this->gcache.cached_max_te; // [N] if (Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) { @@ -185,18 +188,22 @@ int GroundVehicle::GetAcceleration() /* Constructued from power, with need to multiply by 18 and assuming * low speed, it needs to be a 64 bit integer too. */ int64 force; + int64 braking_force; if (speed > 0) { if (!maglev) { /* Conversion factor from km/h to m/s is 5/18 to get [N] in the end. */ force = power * 18 / (speed * 5); + braking_force = force; if (mode == AS_ACCEL && force > (int)max_te) force = max_te; } else { force = power / 25; + braking_force = force; } } else { /* "Kickoff" acceleration. */ force = (mode == AS_ACCEL && !maglev) ? min(max_te, power) : power; force = max(force, (mass * 8) + resistance); + braking_force = force; } /* If power is 0 because of a breakdown, we make the force 0 if accelerating */ @@ -204,6 +211,15 @@ int GroundVehicle::GetAcceleration() force = 0; } + if (power != braking_power) { + if (!maglev && speed > 0) { + /* Conversion factor from km/h to m/s is 5/18 to get [N] in the end. */ + braking_force = braking_power * 18 / (speed * 5); + } else { + braking_force = braking_power / 25; + } + } + /* Calculate the breakdown chance */ if (_settings_game.vehicle.improved_breakdowns) { assert(this->gcache.cached_max_track_speed > 0); @@ -225,9 +241,23 @@ int GroundVehicle::GetAcceleration() this->breakdown_chance_factor = max(breakdown_factor >> 16, (uint64)5); } + int braking_accel; + if (Type == VEH_TRAIN && _settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + /* Assume that every part of a train is braked, not just the engine. + * Exceptionally heavy freight trains should still have a sensible braking distance. + * The total braking force is generally larger than the total tractive force. */ + braking_accel = ClampToI32((-braking_force - resistance - (this->gcache.cached_total_length * 300)) / mass); + + /* Defensive driving: prevent ridiculously fast deceleration. + * -130 corresponds to a braking distance of about 6.2 tiles from 160 km/h. */ + braking_accel = max(braking_accel, -130); + } else { + braking_accel = ClampToI32(min(-braking_force - resistance, -10000) / mass); + } + if (mode == AS_ACCEL) { /* Easy way out when there is no acceleration. */ - if (force == resistance) return 0; + if (force == resistance) return { 0, braking_accel }; /* When we accelerate, make sure we always keep doing that, even when * the excess force is more than the mass. Otherwise a vehicle going @@ -256,9 +286,9 @@ int GroundVehicle::GetAcceleration() } } - return accel; + return { accel, braking_accel }; } else { - return ClampToI32(min(-force - resistance, -10000) / mass); + return { braking_accel, braking_accel }; } } diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp index b289fb5f9e..74a733cabe 100644 --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -56,6 +56,11 @@ enum GroundVehicleFlags { GVF_CHUNNEL_BIT = 3, ///< Vehicle may currently be in a chunnel. (Cached track information for inclination changes) }; +struct GroundVehicleAcceleration { + int acceleration; + int braking; +}; + /** * Base class for all vehicles that move through ground. * @@ -95,7 +100,7 @@ struct GroundVehicle : public SpecializedVehicle { void CalculatePower(uint32& power, uint32& max_te, bool breakdowns) const; - int GetAcceleration(); + GroundVehicleAcceleration GetAcceleration(); /** * Common code executed for crashed ground vehicles @@ -428,13 +433,19 @@ protected: * @param accel The acceleration we would like to give this vehicle. * @param min_speed The minimum speed here, in vehicle specific units. * @param max_speed The maximum speed here, in vehicle specific units. + * @param advisory_max_speed The advisory maximum speed here, in vehicle specific units. * @return Distance to drive. */ - inline uint DoUpdateSpeed(uint accel, int min_speed, int max_speed) + inline uint DoUpdateSpeed(GroundVehicleAcceleration accel, int min_speed, int max_speed, int advisory_max_speed) { - uint spd = this->subspeed + accel; + const byte initial_subspeed = this->subspeed; + uint spd = this->subspeed + accel.acceleration; this->subspeed = (byte)spd; + if (!(Type == VEH_TRAIN && _settings_game.vehicle.train_braking_model == TBM_REALISTIC)) { + max_speed = min(max_speed, advisory_max_speed); + } + int tempmax = max_speed; /* When we are going faster than the maximum speed, reduce the speed @@ -456,6 +467,10 @@ protected: } if (this->cur_speed > max_speed) { + if (Type == VEH_TRAIN && _settings_game.vehicle.train_braking_model == TBM_REALISTIC && accel.braking >= 0) { + extern void TrainBrakesOverheatedBreakdown(Vehicle *v); + TrainBrakesOverheatedBreakdown(this); + } tempmax = max(this->cur_speed - (this->cur_speed / 10) - 1, max_speed); } @@ -464,9 +479,32 @@ protected: * threshold for some reason. That makes acceleration fail and assertions * happen in Clamp. So make it explicit that min_speed overrules the maximum * speed by explicit ordering of min and max. */ - this->cur_speed = spd = max(min(this->cur_speed + ((int)spd >> 8), tempmax), min_speed); + int tempspeed = min(this->cur_speed + ((int)spd >> 8), tempmax); - int scaled_spd = this->GetAdvanceSpeed(spd); + if (Type == VEH_TRAIN && _settings_game.vehicle.train_braking_model == TBM_REALISTIC && tempspeed > advisory_max_speed && accel.braking != accel.acceleration) { + spd = initial_subspeed + accel.braking; + int braking_speed = this->cur_speed + ((int)spd >> 8); + if (braking_speed >= advisory_max_speed) { + if (braking_speed > tempmax) { + if (Type == VEH_TRAIN && _settings_game.vehicle.train_braking_model == TBM_REALISTIC && accel.braking >= 0) { + extern void TrainBrakesOverheatedBreakdown(Vehicle *v); + TrainBrakesOverheatedBreakdown(this); + } + tempspeed = tempmax; + this->subspeed = 0; + } else { + tempspeed = braking_speed; + this->subspeed = (byte)spd; + } + } else { + tempspeed = advisory_max_speed; + this->subspeed = 0; + } + } + + this->cur_speed = max(tempspeed, min_speed); + + int scaled_spd = this->GetAdvanceSpeed(this->cur_speed); scaled_spd += this->progress; this->progress = 0; // set later in *Handler or *Controller diff --git a/src/infrastructure.cpp b/src/infrastructure.cpp index 24da02f873..0b048b5338 100644 --- a/src/infrastructure.cpp +++ b/src/infrastructure.cpp @@ -205,7 +205,7 @@ static void FixAllReservations() * detect this by first finding the end of the reservation, * then switch sharing on and try again. If these two ends differ, * unreserve the path, switch sharing off and try to reserve a new path */ - PBSTileInfo end_tile_info = FollowTrainReservation(v); + PBSTileInfo end_tile_info = FollowTrainReservation(v, nullptr, FTRF_IGNORE_LOOKAHEAD); /* first do a quick test to determine whether the next tile has any reservation at all */ TileIndex next_tile = end_tile_info.tile + TileOffsByDiagDir(TrackdirToExitdir(end_tile_info.trackdir)); @@ -214,7 +214,7 @@ static void FixAllReservations() /* change sharing setting temporarily */ _settings_game.economy.infrastructure_sharing[VEH_TRAIN] = true; - PBSTileInfo end_tile_info2 = FollowTrainReservation(v); + PBSTileInfo end_tile_info2 = FollowTrainReservation(v, nullptr, FTRF_IGNORE_LOOKAHEAD); /* if these two reservation ends differ, unreserve the path and try to reserve a new path */ if (end_tile_info.tile != end_tile_info2.tile || end_tile_info.trackdir != end_tile_info2.trackdir) { FreeTrainTrackReservation(v); diff --git a/src/lang/english.txt b/src/lang/english.txt index c153ccce25..296e202739 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -869,6 +869,7 @@ STR_NEWS_TRAIN_IS_STUCK :{WHITE}{VEHICLE STR_NEWS_VEHICLE_IS_LOST :{WHITE}{VEHICLE} is lost STR_NEWS_VEHICLE_IS_UNPROFITABLE :{WHITE}{VEHICLE}'s profit last year was {CURRENCY_LONG} STR_NEWS_AIRCRAFT_DEST_TOO_FAR :{WHITE}{VEHICLE} can't get to the next destination because it is out of range +STR_NEWS_TRAIN_OVERSHOT_STATION :{WHITE}{VEHICLE} failed to stop at {STRING1} due to excessive speed STR_NEWS_ORDER_REFIT_FAILED :{WHITE}{VEHICLE} stopped because an ordered refit failed STR_NEWS_VEHICLE_AUTORENEW_FAILED :{WHITE}Autorenew failed on {VEHICLE}{}{STRING} @@ -1166,6 +1167,10 @@ STR_CONFIG_SETTING_NONE :None STR_CONFIG_SETTING_ORIGINAL :Original STR_CONFIG_SETTING_REALISTIC :Realistic +STR_CONFIG_SETTING_TRAIN_BRAKING_REALISTIC :Realistic {PUSH_COLOUR}{RED}(Expert){POP_COLOUR} + +STR_CONFIG_SETTING_REALISTIC_BRAKING_SIGNALS_NOT_ALLOWED :{WHITE}Realistic braking can't be enabled. At least one pre-signal or two-way signal is present. + STR_CONFIG_SETTING_HORIZONTAL_POS_LEFT :Left STR_CONFIG_SETTING_HORIZONTAL_POS_CENTER :Centre STR_CONFIG_SETTING_HORIZONTAL_POS_RIGHT :Right @@ -1219,6 +1224,8 @@ STR_CONFIG_SETTING_SMOKE_AMOUNT :Amount of vehic STR_CONFIG_SETTING_SMOKE_AMOUNT_HELPTEXT :Set how much smoke or how many sparks are emitted by vehicles STR_CONFIG_SETTING_TRAIN_ACCELERATION_MODEL :Train acceleration model: {STRING2} STR_CONFIG_SETTING_TRAIN_ACCELERATION_MODEL_HELPTEXT :Select the physics model for train acceleration. The "original" model penalises slopes equally for all vehicles. The "realistic" model penalises slopes and curves depending on various properties of the consist, like length and tractive effort +STR_CONFIG_SETTING_TRAIN_BRAKING_MODEL :Train braking model: {STRING2} +STR_CONFIG_SETTING_TRAIN_BRAKING_MODEL_HELPTEXT :Select the physics model for train braking. The "original" model allows trains to stop instantly. In the "realistic" model, trains have a stopping distance and will reserve ahead accordingly, trains cannot stop instantly.{}{}The "realistic" model has many implications for signalling and track layout design, and is therefore an advanced feature which may not be suitable for beginners. In particular pre-signals and two-way signals are not permitted, and PBS is used for all signalling. STR_CONFIG_SETTING_ROAD_VEHICLE_ACCELERATION_MODEL :Road vehicle acceleration model: {STRING2} STR_CONFIG_SETTING_ROAD_VEHICLE_ACCELERATION_MODEL_HELPTEXT :Select the physics model for road vehicle acceleration. The "original" model penalises slopes equally for all vehicles. The "realistic" model penalises slopes depending on various properties of the engine, for example 'tractive effort' STR_CONFIG_SETTING_TRAIN_SLOPE_STEEPNESS :Slope steepness for trains: {STRING2} @@ -4641,6 +4648,7 @@ STR_BREAKDOWN_TYPE_EM_STOP :Emergency stop STR_BREAKDOWN_TYPE_LOW_SPEED :Limited to {VELOCITY} STR_BREAKDOWN_TYPE_LOW_POWER :{COMMA}% Power STR_BREAKDOWN_TYPE_HIT_RV :Collided with road vehicle +STR_BREAKDOWN_TYPE_BRAKES_OVERHEATED :Brakes overheated STR_BREAKDOWN_TYPE_DEPOT :Heading to {STATION} Hangar for repairs STR_BREAKDOWN_TYPE_LANDING :Heading to {STATION} for emergency landing STR_ERROR_TRAIN_TOO_HEAVY :{WHITE}{VEHICLE} is too heavy @@ -5674,6 +5682,8 @@ STR_ERROR_NO_VEHICLES_AVAILABLE_YET_EXPLANATION :{WHITE}Start a STR_ERROR_CANT_PURCHASE_OTHER_COMPANY_DEPOT :{WHITE}Depot is owned by another company, and building vehicles there is not allowed. STR_ERROR_DEPOT_HAS_WRONG_RAIL_TYPE :{WHITE}Depot cannot be used to build trains with this rail type. +STR_ERROR_CANNOT_MODIFY_TRACK_TRAIN_APPROACHING :{WHITE}Moving train approaching... + # Specific vehicle errors STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL :{WHITE}Can't make train pass signal at danger... STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN :{WHITE}Can't reverse direction of train... diff --git a/src/openttd.cpp b/src/openttd.cpp index 5b64b8b0f6..0f3419c723 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -1626,10 +1626,13 @@ void CheckCaches(bool force_check, std::function log) print_gv_cache_diff("train", gro_cache[length], Train::From(u)->gcache); } if (memcmp(&tra_cache[length], &Train::From(u)->tcache, sizeof(TrainCache)) != 0) { - CCLOGV("train cache mismatch: %c%c%c%c%c", + CCLOGV("train cache mismatch: %c%c%c%c%c%c%c%c", tra_cache[length].cached_override != Train::From(u)->tcache.cached_override ? 'o' : '-', tra_cache[length].cached_tilt != Train::From(u)->tcache.cached_tilt ? 't' : '-', tra_cache[length].cached_num_engines != Train::From(u)->tcache.cached_num_engines ? 'e' : '-', + tra_cache[length].cached_veh_weight != Train::From(u)->tcache.cached_veh_weight ? 'w' : '-', + tra_cache[length].cached_uncapped_decel != Train::From(u)->tcache.cached_uncapped_decel ? 'D' : '-', + tra_cache[length].cached_deceleration != Train::From(u)->tcache.cached_deceleration ? 'd' : '-', tra_cache[length].user_def_data != Train::From(u)->tcache.user_def_data ? 'u' : '-', tra_cache[length].cached_max_curve_speed != Train::From(u)->tcache.cached_max_curve_speed ? 'c' : '-'); } diff --git a/src/order_base.h b/src/order_base.h index 49185c34bd..cac7953694 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -178,6 +178,15 @@ public: return IsType(OT_GOTO_WAYPOINT) || IsType(OT_GOTO_DEPOT) || IsType(OT_GOTO_STATION); } + /** + * Is this an order with a BaseStation destination? + * @return True if the type is either #OT_IMPLICIT, #OT_GOTO_STATION or #OT_GOTO_WAYPOINT. + */ + inline bool IsBaseStationOrder() const + { + return IsType(OT_IMPLICIT) || IsType(OT_GOTO_STATION) || IsType(OT_GOTO_WAYPOINT); + } + /** * Gets the destination of this order. * @pre IsType(OT_GOTO_WAYPOINT) || IsType(OT_GOTO_DEPOT) || IsType(OT_GOTO_STATION). diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 3341169325..244d86fc4c 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -3049,7 +3049,7 @@ bool Order::ShouldStopAtStation(const Vehicle *v, StationID station, bool waypoi bool is_dest_station = this->IsType(OT_GOTO_STATION) && this->dest == station; return (!this->IsType(OT_GOTO_DEPOT) || (this->GetDepotOrderType() & ODTFB_PART_OF_ORDERS) != 0) && - v->last_station_visited != station && // Do stop only when we've not just been there + (v == nullptr || v->last_station_visited != station) && // Do stop only when we've not just been there /* Finally do stop when there is no non-stop flag set for this type of station. */ !(this->GetNonStopType() & (is_dest_station ? ONSF_NO_STOP_AT_DESTINATION_STATION : ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS)); } diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 8d176ec802..ddf5779568 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -215,7 +215,7 @@ private: if (IsRailDepotTile(v->tile)) { candidate_tile = v->tile; - } else if (v->track & TRACK_BIT_WORMHOLE && IsTileType(v->tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(v->tile) && IsTunnelBridgePBS(v->tile)) { + } else if (v->track & TRACK_BIT_WORMHOLE && IsTileType(v->tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(v->tile) && IsTunnelBridgeEffectivelyPBS(v->tile)) { candidate_tile = v->tile; } diff --git a/src/pbs.cpp b/src/pbs.cpp index a33355fc1b..7cf078f021 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -237,7 +237,13 @@ void UnreserveRailTrack(TileIndex tile, Track t) } else { UnreserveRailBridgeHeadTrack(tile, t); } - if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgePBS(tile) && IsTrackAcrossTunnelBridge(tile, t)) SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgeEffectivelyPBS(tile) && IsTrackAcrossTunnelBridge(tile, t)) { + if (IsTunnelBridgePBS(tile)) { + SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + } else { + UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, GetTileOwner(tile)); + } + } MarkBridgeOrTunnelDirtyOnReservationChange(tile, VMDF_NOT_MAP_MODE); } break; @@ -247,9 +253,93 @@ void UnreserveRailTrack(TileIndex tile, Track t) } } +/** Flags for FollowReservation */ +enum FollowReservationFlags { + FRF_NONE = 0, ///< No flags + FRF_IGNORE_ONEWAY = 0x01, ///< Ignore one way signals in the opposite direction + FRF_TB_EXIT_FREE = 0x02, ///< Exit of starting tunnel/bridge is free +}; +DECLARE_ENUM_AS_BIT_SET(FollowReservationFlags) + +static void CheckCurveLookAhead(const Train *v, TrainReservationLookAhead *lookahead, int end_position, int z) +{ + while (!lookahead->curves.empty() && lookahead->curves.front().position < end_position - v->gcache.cached_total_length) { + lookahead->curves.pop_front(); + } + + static const int absolute_max_speed = UINT16_MAX; + int max_speed = absolute_max_speed; + + int curvecount[2] = {0, 0}; + + /* first find the curve speed limit */ + int numcurve = 0; + int sum = 0; + int pos = 0; + int lastpos = -1; + const Train *u = v->Last(); + int veh_offset = v->gcache.cached_total_length - u->gcache.cached_veh_length; + for (const TrainReservationLookAheadCurve &curve : lookahead->curves) { + int delta = end_position - curve.position; + while (veh_offset > delta && u->Previous() != nullptr) { + veh_offset -= u->gcache.cached_veh_length; + pos++; + u = u->Previous(); + } + + if (curve.dir_diff == DIRDIFF_45LEFT) curvecount[0]++; + if (curve.dir_diff == DIRDIFF_45RIGHT) curvecount[1]++; + if (curve.dir_diff == DIRDIFF_45LEFT || curve.dir_diff == DIRDIFF_45RIGHT) { + if (lastpos != -1) { + numcurve++; + sum += pos - lastpos; + if (pos - lastpos == 1 && max_speed > 88) { + max_speed = 88; + } + } + lastpos = pos; + } + + /* if we have a 90 degree turn, fix the speed limit to 60 */ + if (curve.dir_diff == DIRDIFF_90LEFT || curve.dir_diff == DIRDIFF_90RIGHT) { + max_speed = 61; + } + } + + if (numcurve > 0 && max_speed > 88) { + if (curvecount[0] == 1 && curvecount[1] == 1) { + max_speed = absolute_max_speed; + } else { + sum /= numcurve; + max_speed = 232 - (13 - Clamp(sum, 1, 12)) * (13 - Clamp(sum, 1, 12)); + } + } + + if (max_speed != absolute_max_speed) { + /* Apply the engine's rail type curve speed advantage, if it slowed by curves */ + const RailtypeInfo *rti = GetRailTypeInfo(v->railtype); + max_speed += (max_speed / 2) * rti->curve_speed; + + if (v->tcache.cached_tilt) { + /* Apply max_speed bonus of 20% for a tilting train */ + max_speed += max_speed / 5; + } + + lookahead->AddCurveSpeedLimit(max_speed, 4, z); + } +} + +static int LookaheadTileHeightForChunnel(int length, int offset) +{ + if (offset == 0) return 0; + if (offset < 3) return -1 * TILE_HEIGHT; + if (offset < length - 3) return -2 * TILE_HEIGHT; + if (offset < length) return -1 * TILE_HEIGHT; + return 0; +} /** Follow a reservation starting from a specific tile to the end. */ -static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Trackdir trackdir, bool ignore_oneway = false) +static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Trackdir trackdir, FollowReservationFlags flags, const Train *v, TrainReservationLookAhead *lookahead) { TileIndex start_tile = tile; Trackdir start_trackdir = trackdir; @@ -258,11 +348,99 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra /* Start track not reserved? This can happen if two trains * are on the same tile. The reservation on the next tile * is not ours in this case, so exit. */ - if (!HasReservedTracks(tile, TrackToTrackBits(TrackdirToTrack(trackdir)))) return PBSTileInfo(tile, trackdir, false); + if (!(flags & FRF_TB_EXIT_FREE) && !HasReservedTracks(tile, TrackToTrackBits(TrackdirToTrack(trackdir)))) return PBSTileInfo(tile, trackdir, false); + + RailType rt = INVALID_RAILTYPE; + Direction dir = INVALID_DIR; + int z = 0; + auto update_z = [&](TileIndex t, Trackdir td, bool force) { + if (force || TrackdirToTrack(td) == TRACK_X || TrackdirToTrack(td) == TRACK_Y) { + if (IsBridgeTile(t) && TrackdirToExitdir(td) == GetTunnelBridgeDirection(t)) { + z = GetBridgePixelHeight(t); + } else { + int x = (TileX(t) * TILE_SIZE) + 8; + int y = (TileY(t) * TILE_SIZE) + 8; + if (!IsTunnelTile(tile)) { + switch (TrackdirToExitdir(td)) { + case DIAGDIR_NE: x -= 8; break; + case DIAGDIR_SE: y += 7; break; + case DIAGDIR_SW: x += 7; break; + case DIAGDIR_NW: y -= 8; break; + default: NOT_REACHED(); + } + } + z = GetSlopePixelZ(x, y); + } + } + }; + + if (lookahead != nullptr) { + rt = GetRailTypeByTrack(tile, TrackdirToTrack(trackdir)); + dir = TrackdirToDirection(trackdir); + update_z(tile, trackdir, true); + } + + auto check_rail_type = [&](TileIndex t, Trackdir td, int offset) { + RailType new_rt = GetRailTypeByTrack(t, TrackdirToTrack(td)); + if (new_rt != rt) { + uint16 rail_speed = GetRailTypeInfo(new_rt)->max_speed; + if (rail_speed > 0) lookahead->AddTrackSpeedLimit(rail_speed, offset, 4, z); + rt = new_rt; + } + }; + + auto check_direction = [&](Direction new_dir, int offset, TileIndex tile) { + if (dir == new_dir) return; + DirDiff dirdiff = DirDifference(dir, new_dir); + int end = lookahead->RealEndPosition() + 4; + lookahead->curves.push_back({ end + offset, dirdiff }); + dir = new_dir; + CheckCurveLookAhead(v, lookahead, end + offset, z); + }; /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */ CFollowTrackRail ft(o, rts); - while (ft.Follow(tile, trackdir)) { + auto check_tunnel_bridge = [&]() -> bool { + if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && IsTunnelBridgeWithSignalSimulation(tile) && TrackdirEntersTunnelBridge(tile, trackdir)) { + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && IsTunnelBridgeSignalSimulationEntrance(tile)) { + TileIndex end = GetOtherTunnelBridgeEnd(tile); + if (HasAcrossTunnelBridgeReservation(end) && GetTunnelBridgeExitSignalState(end) == SIGNAL_STATE_GREEN && + ((flags & FRF_TB_EXIT_FREE) || TunnelBridgeIsFree(tile, end, nullptr, true).Succeeded())) { + /* skip far end */ + if (lookahead != nullptr) { + lookahead->reservation_end_position += (DistanceManhattan(tile, end) - 1) * TILE_SIZE; + } + Trackdir end_trackdir = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(end)), ReverseDiagDir(GetTunnelBridgeDirection(end))); + if (lookahead != nullptr) { + if ((flags & FRF_TB_EXIT_FREE) && GetTunnelBridgeLength(tile, end) > 1) { + /* middle part of bridge is in wormhole direction */ + dir = DiagDirToDir(GetTunnelBridgeDirection(tile)); + } + check_direction(TrackdirToDirection(end_trackdir), 0, end); + lookahead->reservation_end_position += (IsDiagonalTrackdir(end_trackdir) ? 16 : 8); + update_z(end, end_trackdir, false); + } + tile = end; + trackdir = end_trackdir; + return true; + } + } + if ((flags & FRF_IGNORE_ONEWAY) && _settings_game.vehicle.train_braking_model == TBM_REALISTIC && IsTunnelBridgeSignalSimulationExit(tile) && + GetTunnelBridgeExitSignalState(tile) == SIGNAL_STATE_GREEN) { + TileIndex end = GetOtherTunnelBridgeEnd(tile); + if (HasAcrossTunnelBridgeReservation(end) && TunnelBridgeIsFree(tile, end, nullptr, true).Succeeded()) { + /* skip far end */ + tile = end; + trackdir = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(tile)), ReverseDiagDir(GetTunnelBridgeDirection(tile))); + return true; + } + } + return false; + } + return true; + }; + while (check_tunnel_bridge() && ft.Follow(tile, trackdir)) { + flags &= ~FRF_TB_EXIT_FREE; TrackdirBits reserved = ft.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedTrackbits(ft.m_new_tile)); /* No reservation --> path end found */ @@ -273,6 +451,10 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra while (ft.m_tiles_skipped-- > 0) { ft.m_new_tile -= diff; if (HasStationReservation(ft.m_new_tile)) { + if (lookahead != nullptr) { + lookahead->AddStation(1 + ft.m_tiles_skipped, GetStationIndex(ft.m_new_tile), z); + lookahead->reservation_end_position += (1 + ft.m_tiles_skipped) * TILE_SIZE; + } tile = ft.m_new_tile; trackdir = DiagDirToDiagTrackdir(ft.m_exitdir); break; @@ -287,11 +469,117 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra /* One-way signal against us. The reservation can't be ours as it is not * a safe position from our direction and we can never pass the signal. */ - if (!ignore_oneway && HasOnewaySignalBlockingTrackdir(ft.m_new_tile, new_trackdir)) break; + if (!(flags & FRF_IGNORE_ONEWAY) && HasOnewaySignalBlockingTrackdir(ft.m_new_tile, new_trackdir)) break; tile = ft.m_new_tile; trackdir = new_trackdir; + if (lookahead != nullptr) { + if (ft.m_tiles_skipped > 0) { + DiagDirection skip_dir = ReverseDiagDir(TrackdirToExitdir(ReverseTrackdir(trackdir))); + check_direction(DiagDirToDir(skip_dir), 0, tile); + } + if (ft.m_is_station) { + if (ft.m_tiles_skipped > 0) { + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(trackdir)); + TileIndex start = tile - (diff * ft.m_tiles_skipped); + for (int i = 0; i < ft.m_tiles_skipped; i++) { + check_rail_type(start, trackdir, i * TILE_SIZE); + start += diff; + } + } + check_rail_type(tile, trackdir, ft.m_tiles_skipped * TILE_SIZE); + lookahead->AddStation(1 + ft.m_tiles_skipped, GetStationIndex(ft.m_new_tile), z); + } else { + check_rail_type(tile, trackdir, 0); + } + check_direction(TrackdirToDirection(trackdir), ft.m_tiles_skipped * TILE_SIZE, tile); + if (IsTileType(tile, MP_TUNNELBRIDGE) && TrackdirEntersTunnelBridge(tile, trackdir)) { + uint16 bridge_speed = 0; + if (IsBridge(tile)) { + bridge_speed = GetBridgeSpec(GetBridgeType(tile))->speed; + lookahead->AddTrackSpeedLimit(bridge_speed, 0, 8, z); + } + const int start_offset = (IsDiagonalTrackdir(trackdir) ? 16 : 8); + const TileIndex end = GetOtherTunnelBridgeEnd(tile); + const int length = GetTunnelBridgeLength(tile, end); + if (IsTunnelBridgeSignalSimulationEntrance(tile)) { + const int spacing = GetTunnelBridgeSignalSimulationSpacing(tile); + const int signals = length / spacing; + + uint16 signal_speed = GetRailTypeInfo(rt)->max_speed; + if (signal_speed == 0 || (lookahead->speed_restriction != 0 && lookahead->speed_restriction < signal_speed)) signal_speed = lookahead->speed_restriction; + if (signal_speed == 0 || (bridge_speed != 0 && bridge_speed < signal_speed)) signal_speed = bridge_speed; + + /* Entrance signal */ + lookahead->AddSignal(signal_speed, 0, z); + + update_z(tile, trackdir, false); + + if (length > 1) { + check_direction(DiagDirToDir(GetTunnelBridgeDirection(tile)), start_offset, tile); + } + + bool chunnel = IsTunnel(tile) && Tunnel::GetByTile(tile)->is_chunnel; + + /* Middle signals */ + int offset = start_offset - TILE_SIZE; + for (int i = 0; i < signals; i++) { + offset += TILE_SIZE * spacing; + lookahead->AddSignal(signal_speed, offset, chunnel ? LookaheadTileHeightForChunnel(length, i * spacing) : z); + } + + /* Exit signal */ + const int end_offset = start_offset + (TILE_SIZE * length) /* + ((DiagDirToDiagTrackBits(GetTunnelBridgeDirection(end)) & GetTunnelBridgeTrackBits(end)) ? 16 : 8)*/; + lookahead->AddSignal(signal_speed, end_offset, z); + } else { + update_z(tile, trackdir, false); + if (length > 1) { + check_direction(DiagDirToDir(GetTunnelBridgeDirection(tile)), start_offset, tile); + } + } + } + + if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrack(tile, TrackdirToTrack(trackdir))) { + TraceRestrictProgramActionsUsedFlags au_flags; + if (HasSignalOnTrackdir(tile, trackdir)) { + /* Passing through a signal from the front side */ + au_flags = TRPAUF_SPEED_RESTRICTION; + } else { + /* Passing through a signal from the rear side */ + au_flags = TRPAUF_SPEED_RESTRICTION | TRPAUF_REVERSE; + } + uint16 speed_restriction = lookahead->speed_restriction; + if (v != nullptr) { + const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(tile, TrackdirToTrack(trackdir)); + if (prog && prog->actions_used_flags & au_flags) { + TraceRestrictProgramResult out; + TraceRestrictProgramInput input(tile, trackdir, nullptr, nullptr); + prog->Execute(v, input, out); + if (out.flags & TRPRF_REVERSE && au_flags & TRPAUF_REVERSE) { + lookahead->AddReverse(z); + } + if (out.flags & TRPRF_SPEED_RETRICTION_SET) { + lookahead->AddSpeedRestriction(out.speed_restriction, z); + if (out.speed_restriction != 0 && (speed_restriction == 0 || out.speed_restriction < speed_restriction)) { + /* lower of the speed restrictions before or after the signal */ + speed_restriction = out.speed_restriction; + } + } + } + } + if (!(au_flags & TRPAUF_REVERSE)) { + /* Passing through a signal from the front side */ + uint16 signal_speed = GetRailTypeInfo(rt)->max_speed; + if (signal_speed == 0 || (speed_restriction != 0 && speed_restriction < signal_speed)) signal_speed = speed_restriction; + lookahead->AddSignal(signal_speed, 0, z); + } + } + + lookahead->reservation_end_position += (IsDiagonalTrackdir(trackdir) ? 16 : 8) + (ft.m_tiles_skipped * 16); + update_z(tile, trackdir, false); + } + if (first_loop) { /* Update the start tile after we followed the track the first * time. This is necessary because the track follower can skip @@ -305,12 +593,16 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra if (tile == start_tile && trackdir == start_trackdir) break; } /* Depot tile? Can't continue. */ - if (IsRailDepotTile(tile)) break; + if (IsRailDepotTile(tile)) { + if (lookahead != nullptr) SetBit(lookahead->flags, TRLF_DEPOT_END); + break; + } /* Non-pbs signal? Reservation can't continue. */ if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) break; - if (IsTileType(tile, MP_TUNNELBRIDGE) && IsTunnelBridgeWithSignalSimulation(tile) && IsTrackAcrossTunnelBridge(tile, TrackdirToTrack(trackdir))) break; } + if (lookahead != nullptr) lookahead->reservation_end_z = z; + return PBSTileInfo(tile, trackdir, false); } @@ -352,6 +644,7 @@ static Vehicle *FindTrainOnTrackEnum(Vehicle *v, void *data) return nullptr; } + /** * Follow a train reservation to the last tile. * @@ -359,17 +652,33 @@ static Vehicle *FindTrainOnTrackEnum(Vehicle *v, void *data) * @param train_on_res Is set to a train we might encounter * @returns The last tile of the reservation or the current train tile if no reservation present. */ -PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res) +PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res, FollowTrainReservationFlags flags) { assert(v->type == VEH_TRAIN); - TileIndex tile = v->tile; - Trackdir trackdir = v->GetVehicleTrackdir(); + TileIndex tile; + Trackdir trackdir; + + if (!(flags & FTRF_IGNORE_LOOKAHEAD) && _settings_game.vehicle.train_braking_model == TBM_REALISTIC && v->lookahead != nullptr) { + tile = v->lookahead->reservation_end_tile; + trackdir = v->lookahead->reservation_end_trackdir; + if (HasBit(v->lookahead->flags, TRLF_TB_EXIT_FREE)) { + TileIndex exit_tile = GetOtherTunnelBridgeEnd(tile); + if (GetTunnelBridgeExitSignalState(exit_tile) == SIGNAL_STATE_GREEN && HasAcrossTunnelBridgeReservation(exit_tile)) { + tile = exit_tile; + DiagDirection exit_dir = ReverseDiagDir(GetTunnelBridgeDirection(exit_tile)); + trackdir = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(exit_tile)), exit_dir); + } + } + } else { + tile = v->tile; + trackdir = v->GetVehicleTrackdir(); + } if (IsRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false); FindTrainOnTrackInfo ftoti; - ftoti.res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->all_compatible_railtypes, tile, trackdir); + ftoti.res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->all_compatible_railtypes, tile, trackdir, FRF_NONE, v, nullptr); ftoti.res.okay = IsSafeWaitingPosition(v, ftoti.res.tile, ftoti.res.trackdir, true, _settings_game.pf.forbid_90_deg); if (train_on_res != nullptr) { FindVehicleOnPos(ftoti.res.tile, VEH_TRAIN, &ftoti, FindTrainOnTrackEnum); @@ -394,6 +703,240 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res) return ftoti.res; } +void ApplyAvailableFreeTunnelBridgeTiles(TrainReservationLookAhead *lookahead, int free_tiles, TileIndex tile, TileIndex end) +{ + SB(lookahead->flags, TRLF_TB_EXIT_FREE, 1, free_tiles == INT_MAX ? 1 : 0); + if (free_tiles == INT_MAX) { + /* whole tunnel/bridge is empty */ + if (unlikely(end == INVALID_TILE)) end = GetOtherTunnelBridgeEnd(tile); + free_tiles = DistanceManhattan(tile, end) - 1; + } else { + if (free_tiles > 0) { + int spacing = GetTunnelBridgeSignalSimulationSpacing(tile); + free_tiles = (((free_tiles - 1) / spacing) * spacing) - 1; + } else { + free_tiles = -1; + } + } + lookahead->reservation_end_position += ((free_tiles - lookahead->tunnel_bridge_reserved_tiles) * TILE_SIZE); + lookahead->tunnel_bridge_reserved_tiles = free_tiles; + if (HasBit(lookahead->flags, TRLF_CHUNNEL)) { + if (unlikely(end == INVALID_TILE)) end = GetOtherTunnelBridgeEnd(tile); + lookahead->reservation_end_z = LookaheadTileHeightForChunnel(GetTunnelBridgeLength(tile, end), free_tiles + 1); + } +} + +void FillLookAheadCurveDataFromTrainPosition(Train *t) +{ + TileIndex tile = TileVirtXY(t->x_pos, t->y_pos); + Direction dir = t->direction; + int32 current_pos = t->lookahead->reservation_end_position + 4 - ((dir & 1) ? 16 : 8); + for (Train *u = t->Next(); u != nullptr; u = u->Next()) { + TileIndex cur_tile = TileVirtXY(u->x_pos, u->y_pos); + if (cur_tile == tile) continue; + tile = cur_tile; + if (u->direction != dir) { + DirDiff dirdiff = DirDifference(u->direction, dir); + t->lookahead->curves.push_front({ current_pos, dirdiff }); + dir = u->direction; + } + current_pos -= ((dir & 1) ? 16 : 8); + } +} + +static int ScanTrainPositionForLookAheadStation(Train *t, TileIndex start_tile) +{ + StationID prev = INVALID_STATION; + int offset = 0; + int start_offset_tiles = 0; + TileIndex cur_tile = start_tile; + for (const Train *u = t; u != nullptr; u = u->Next()) { + if (u != t) { + TileIndex u_tile = TileVirtXY(u->x_pos, u->y_pos); + if (u_tile != cur_tile) { + offset += (IsDiagonalTrackdir(u->GetVehicleTrackdir()) ? 16 : 8); + cur_tile = u_tile; + } + } + if (HasStationTileRail(u->tile)) { + StationID current = GetStationIndex(u->tile); + if (current != prev) { + /* Train is in a station, add that to the lookahead */ + TileIndex tile = u->tile; + Trackdir trackdir = u->GetVehicleTrackdir(); + + RailType rt = GetRailTypeByTrack(tile, TrackdirToTrack(trackdir)); + int z = GetTileMaxPixelZ(tile); + + DiagDirection forward_dir = TrackdirToExitdir(trackdir); + TileIndexDiff diff = TileOffsByDiagDir(forward_dir); + uint forward_length = BaseStation::GetByTile(tile)->GetPlatformLength(tile, forward_dir); + uint reverse_length = BaseStation::GetByTile(tile)->GetPlatformLength(tile, ReverseDiagDir(forward_dir)); + + if (u == t) { + for (uint i = 1; i < forward_length; i++) { + /* Check for mid platform rail type change */ + RailType new_rt = GetRailTypeByTrack(tile + (i * diff), TrackdirToTrack(trackdir)); + if (new_rt != rt) { + uint16 rail_speed = GetRailTypeInfo(new_rt)->max_speed; + if (rail_speed > 0) t->lookahead->AddTrackSpeedLimit(rail_speed, (i - 1) * TILE_SIZE, 4, z); + rt = new_rt; + } + } + start_offset_tiles = forward_length - 1; + } + + t->lookahead->AddStation(forward_length - 1, current, z); + t->lookahead->items.back().start -= offset + (reverse_length * TILE_SIZE); + t->lookahead->items.back().end -= offset; + + prev = current; + } + } else { + prev = INVALID_STATION; + } + if (!HasBit(u->flags, VRF_BEYOND_PLATFORM_END)) break; + } + return start_offset_tiles; +} + +void TryCreateLookAheadForTrainInTunnelBridge(Train *t) +{ + DiagDirection tb_dir = GetTunnelBridgeDirection(t->tile); + if (DirToDiagDirAlongAxis(t->direction, DiagDirToAxis(tb_dir)) == tb_dir) { + /* going in the right direction, allocate a new lookahead */ + t->lookahead.reset(new TrainReservationLookAhead()); + t->lookahead->reservation_end_tile = t->tile; + t->lookahead->reservation_end_trackdir = TrackExitdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(t->tile)), GetTunnelBridgeDirection(t->tile)); + t->lookahead->reservation_end_z = t->z_pos; + t->lookahead->current_position = 0; + t->lookahead->tunnel_bridge_reserved_tiles = DistanceManhattan(t->tile, TileVirtXY(t->x_pos, t->y_pos)); + t->lookahead->reservation_end_position = GetTileMarginInFrontOfTrain(t); + t->lookahead->flags = 0; + t->lookahead->speed_restriction = t->speed_restriction; + if (IsTunnel(t->tile) && Tunnel::GetByTile(t->tile)->is_chunnel) SetBit(t->lookahead->flags, TRLF_CHUNNEL); + + if (IsTunnelBridgeSignalSimulationEntrance(t->tile)) { + uint16 bridge_speed = IsBridge(t->tile) ? GetBridgeSpec(GetBridgeType(t->tile))->speed : 0; + const int length = GetTunnelBridgeLength(t->tile, GetOtherTunnelBridgeEnd(t->tile)); + const int spacing = GetTunnelBridgeSignalSimulationSpacing(t->tile); + const int signals = length / spacing; + + uint16 signal_speed = GetRailTypeInfo(GetRailTypeByTrack(t->tile, TrackdirToTrack(t->lookahead->reservation_end_trackdir)))->max_speed; + if (signal_speed == 0 || (t->speed_restriction != 0 && t->speed_restriction < signal_speed)) signal_speed = t->speed_restriction; + if (signal_speed == 0 || (bridge_speed != 0 && bridge_speed < signal_speed)) signal_speed = bridge_speed; + + int z = IsBridge(t->tile) ? GetBridgeHeight(t->tile) : GetTilePixelZ(t->tile); + + /* Middle signals */ + int offset = -TILE_SIZE; + for (int i = 0; i < signals; i++) { + offset += TILE_SIZE * spacing; + t->lookahead->AddSignal(signal_speed, offset, HasBit(t->lookahead->flags, TRLF_CHUNNEL) ? LookaheadTileHeightForChunnel(length, i * spacing) : z); + } + + /* Exit signal */ + const int end_offset = TILE_SIZE * length; + t->lookahead->AddSignal(signal_speed, end_offset, z); + } + + FillLookAheadCurveDataFromTrainPosition(t); + TileIndex end = GetOtherTunnelBridgeEnd(t->tile); + int raw_free_tiles = GetAvailableFreeTilesInSignalledTunnelBridgeWithStartOffset(t->tile, end, t->lookahead->tunnel_bridge_reserved_tiles + 1); + ApplyAvailableFreeTunnelBridgeTiles(t->lookahead.get(), raw_free_tiles, t->tile, end); + ScanTrainPositionForLookAheadStation(t, TileVirtXY(t->x_pos, t->y_pos)); + } +} + +void FillTrainReservationLookAhead(Train *v) +{ + TileIndex tile; + Trackdir trackdir; + + if (v->lookahead == nullptr && (v->track & TRACK_BIT_WORMHOLE)) { + TryCreateLookAheadForTrainInTunnelBridge(v); + if (v->lookahead == nullptr) return; + } + + if (v->lookahead == nullptr) { + v->lookahead.reset(new TrainReservationLookAhead()); + v->lookahead->current_position = 0; + + /* Special case, if called from TrainController, + * v->tile, v->track and v->direction can be updated to the new tile, + * but v->x_pos and v->y_pos can still use the cordinates on the old tile, + * GetTileMarginInFrontOfTrain could erroneously return -5 if the old and + * new directions don't match. */ + v->lookahead->reservation_end_position = max(GetTileMarginInFrontOfTrain(v), -4); + + v->lookahead->tunnel_bridge_reserved_tiles = 0; + v->lookahead->flags = 0; + v->lookahead->speed_restriction = v->speed_restriction; + FillLookAheadCurveDataFromTrainPosition(v); + tile = v->tile; + trackdir = v->GetVehicleTrackdir(); + TileIndex virt_tile = TileVirtXY(v->x_pos, v->y_pos); + if (tile != virt_tile) { + v->lookahead->reservation_end_position += (IsDiagonalDirection(v->direction) ? 16 : 8); + } + int station_offset_tiles = ScanTrainPositionForLookAheadStation(v, tile); + if (station_offset_tiles > 0) { + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(trackdir)); + tile += station_offset_tiles * diff; + v->lookahead->reservation_end_position += station_offset_tiles * TILE_SIZE; + } + } else { + tile = v->lookahead->reservation_end_tile; + trackdir = v->lookahead->reservation_end_trackdir; + if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && IsTunnelBridgeSignalSimulationEntrance(tile) && TrackdirEntersTunnelBridge(tile, trackdir)) { + TileIndex end = GetOtherTunnelBridgeEnd(tile); + int raw_free_tiles; + if (HasBit(v->lookahead->flags, TRLF_TB_EXIT_FREE)) { + raw_free_tiles = INT_MAX; + } else { + raw_free_tiles = GetAvailableFreeTilesInSignalledTunnelBridgeWithStartOffset(tile, end, v->lookahead->tunnel_bridge_reserved_tiles + 1); + ApplyAvailableFreeTunnelBridgeTiles(v->lookahead.get(), raw_free_tiles, tile, end); + } + if (!(HasAcrossTunnelBridgeReservation(end) && GetTunnelBridgeExitSignalState(end) == SIGNAL_STATE_GREEN && raw_free_tiles == INT_MAX)) { + /* do not attempt to follow through a signalled tunnel/bridge if it is not empty or the far end is not reserved */ + return; + } + } + } + + if (IsRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return; + + FollowReservationFlags flags = FRF_NONE; + if (HasBit(v->lookahead->flags, TRLF_TB_EXIT_FREE)) flags |= FRF_TB_EXIT_FREE; + PBSTileInfo res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->all_compatible_railtypes, tile, trackdir, flags, v, v->lookahead.get()); + + if (IsTunnelBridgeWithSignalSimulation(res.tile) && TrackdirEntersTunnelBridge(res.tile, res.trackdir)) { + SB(v->lookahead->flags, TRLF_CHUNNEL, 1, (IsTunnel(res.tile) && Tunnel::GetByTile(res.tile)->is_chunnel) ? 1 : 0); + if (v->lookahead->current_position < v->lookahead->reservation_end_position - ((int)TILE_SIZE * (1 + v->lookahead->tunnel_bridge_reserved_tiles))) { + /* Vehicle is not itself in this tunnel/bridge, scan how much is available */ + TileIndex end = INVALID_TILE; + int free_tiles; + if (GetTunnelBridgeEntranceSignalState(res.tile) == SIGNAL_STATE_GREEN) { + end = GetOtherTunnelBridgeEnd(res.tile); + free_tiles = GetAvailableFreeTilesInSignalledTunnelBridge(res.tile, end, res.tile); + } else { + free_tiles = -1; + } + ApplyAvailableFreeTunnelBridgeTiles(v->lookahead.get(), free_tiles, res.tile, end); + } + } else { + ClrBit(v->lookahead->flags, TRLF_TB_EXIT_FREE); + ClrBit(v->lookahead->flags, TRLF_CHUNNEL); + if (v->lookahead->tunnel_bridge_reserved_tiles != 0) { + v->lookahead->reservation_end_position -= (v->lookahead->tunnel_bridge_reserved_tiles * (int)TILE_SIZE); + v->lookahead->tunnel_bridge_reserved_tiles = 0; + } + } + + v->lookahead->reservation_end_tile = res.tile; + v->lookahead->reservation_end_trackdir = res.trackdir; +} + /** * Find the train which has reserved a specific path. * @@ -417,7 +960,7 @@ Train *GetTrainForReservation(TileIndex tile, Track track) if (HasOnewaySignalBlockingTrackdir(tile, ReverseTrackdir(trackdir)) && !HasPbsSignalOnTrackdir(tile, trackdir)) continue; FindTrainOnTrackInfo ftoti; - ftoti.res = FollowReservation(GetTileOwner(tile), rts, tile, trackdir, true); + ftoti.res = FollowReservation(GetTileOwner(tile), rts, tile, trackdir, FRF_IGNORE_ONEWAY, nullptr, nullptr); FindVehicleOnPos(ftoti.res.tile, VEH_TRAIN, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) return ftoti.best; @@ -431,9 +974,16 @@ Train *GetTrainForReservation(TileIndex tile, Track track) } } - /* Special case for bridges/tunnels: check the other end as well. */ if (IsTileType(ftoti.res.tile, MP_TUNNELBRIDGE) && IsTrackAcrossTunnelBridge(ftoti.res.tile, TrackdirToTrack(ftoti.res.trackdir))) { - FindVehicleOnPos(GetOtherTunnelBridgeEnd(ftoti.res.tile), VEH_TRAIN, &ftoti, FindTrainOnTrackEnum); + if (IsTunnelBridgeWithSignalSimulation(ftoti.res.tile)) { + /* Special case for signalled bridges/tunnels: find best train on bridge/tunnel if exit reserved. */ + if (IsTunnelBridgeSignalSimulationExit(ftoti.res.tile) && !(IsTunnelBridgeEffectivelyPBS(ftoti.res.tile) && GetTunnelBridgeExitSignalState(ftoti.res.tile) == SIGNAL_STATE_RED)) { + ftoti.best = GetTrainClosestToTunnelBridgeEnd(ftoti.res.tile, GetOtherTunnelBridgeEnd(ftoti.res.tile)); + } + } else { + /* Special case for bridges/tunnels: check the other end as well. */ + FindVehicleOnPos(GetOtherTunnelBridgeEnd(ftoti.res.tile), VEH_TRAIN, &ftoti, FindTrainOnTrackEnum); + } if (ftoti.best != nullptr) return ftoti.best; } } @@ -441,6 +991,40 @@ Train *GetTrainForReservation(TileIndex tile, Track track) return nullptr; } +CommandCost CheckTrainReservationPreventsTrackModification(TileIndex tile, Track track) +{ + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + return CheckTrainReservationPreventsTrackModification(GetTrainForReservation(tile, track)); + } + return CommandCost(); +} + +CommandCost CheckTrainReservationPreventsTrackModification(const Train *v) +{ + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && v != nullptr && (v->cur_speed > 0 || !(v->vehstatus & (VS_STOPPED | VS_CRASHED)))) { + return_cmd_error(STR_ERROR_CANNOT_MODIFY_TRACK_TRAIN_APPROACHING); + } + return CommandCost(); +} + +static Vehicle *TrainInTunnelBridgePreventsTrackModificationEnum(Vehicle *v, void *) +{ + if (CheckTrainReservationPreventsTrackModification(Train::From(v)->First()).Failed()) return v; + + return nullptr; +} + +CommandCost CheckTrainInTunnelBridgePreventsTrackModification(TileIndex start, TileIndex end) +{ + if (_settings_game.vehicle.train_braking_model != TBM_REALISTIC) return CommandCost(); + + if (HasVehicleOnPos(start, VEH_TRAIN, nullptr, &TrainInTunnelBridgePreventsTrackModificationEnum) || + HasVehicleOnPos(end, VEH_TRAIN, nullptr, &TrainInTunnelBridgePreventsTrackModificationEnum)) { + return_cmd_error(STR_ERROR_CANNOT_MODIFY_TRACK_TRAIN_APPROACHING); + } + return CommandCost(); +} + /** * This is called to retrieve the previous signal, as required * This is not run all the time as it is somewhat expensive and most restrictions will not test for the previous signal @@ -450,7 +1034,7 @@ TileIndex VehiclePosTraceRestrictPreviousSignalCallback(const Train *v, const vo if (IsRailDepotTile(v->tile)) { return v->tile; } - if (v->track & TRACK_BIT_WORMHOLE && IsTileType(v->tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(v->tile) && IsTunnelBridgePBS(v->tile)) { + if (v->track & TRACK_BIT_WORMHOLE && IsTileType(v->tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(v->tile) && IsTunnelBridgeEffectivelyPBS(v->tile)) { return v->tile; } @@ -507,7 +1091,7 @@ bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bo if (HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return true; } - if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && IsTrackAcrossTunnelBridge(tile, TrackdirToTrack(trackdir))) { + if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && IsTrackAcrossTunnelBridge(tile, TrackdirToTrack(trackdir))) { if (IsTunnelBridgeSignalSimulationEntrance(tile)) { return true; } @@ -550,7 +1134,7 @@ bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bo } if (IsTileType(ft.m_new_tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(ft.m_new_tile) == TRANSPORT_RAIL && IsTrackAcrossTunnelBridge(ft.m_new_tile, TrackdirToTrack(td)) && - IsTunnelBridgeSignalSimulationExitOnly(ft.m_new_tile) && IsTunnelBridgePBS(ft.m_new_tile)) { + IsTunnelBridgeSignalSimulationExitOnly(ft.m_new_tile) && IsTunnelBridgeEffectivelyPBS(ft.m_new_tile)) { return include_line_end; } } diff --git a/src/pbs.h b/src/pbs.h index 1c2992400a..4bc82f64fc 100644 --- a/src/pbs.h +++ b/src/pbs.h @@ -49,11 +49,108 @@ struct PBSWaitingPositionRestrictedSignalInfo { Trackdir trackdir = INVALID_TRACKDIR; }; -PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res = nullptr); +enum TrainReservationLookAheadItemType : byte { + TRLIT_STATION = 0, ///< Station/waypoint + TRLIT_REVERSE = 1, ///< Reverse behind signal + TRLIT_TRACK_SPEED = 2, ///< Track or bridge speed limit + TRLIT_SPEED_RESTRICTION = 3, ///< Speed restriction + TRLIT_SIGNAL = 4, ///< Signal + TRLIT_CURVE_SPEED = 5, ///< Curve speed limit +}; + +struct TrainReservationLookAheadItem { + int32 start; + int32 end; + int16 z_pos; + uint16 data_id; + TrainReservationLookAheadItemType type; +}; + +struct TrainReservationLookAheadCurve { + int32 position; + DirDiff dir_diff; +}; + +enum TrainReservationLookAheadFlags { + TRLF_TB_EXIT_FREE = 0, ///< Reservation ends at signalled tunnel/bridge entrance and the corresponding exit is free, but may not be reserved + TRLF_DEPOT_END = 1, ///< Reservation ends at a depot + TRLF_APPLY_ADVISORY = 2, ///< Apply advisory speed limit on next iteration + TRLF_CHUNNEL = 3, ///< Reservation ends at a signalled chunnel entrance +}; + +struct TrainReservationLookAhead { + TileIndex reservation_end_tile; ///< Tile the reservation ends. + Trackdir reservation_end_trackdir; ///< The reserved trackdir on the end tile. + int32 current_position; ///< Current position of the train on the reservation + int32 reservation_end_position; ///< Position of the end of the reservation + int16 reservation_end_z; ///< The z coordinate of the reservation end + int16 tunnel_bridge_reserved_tiles; ///< How many tiles a reservation into the tunnel/bridge currently extends into the wormhole + uint16 flags; ///< Flags (TrainReservationLookAheadFlags) + uint16 speed_restriction; + std::deque items; + std::deque curves; + + int32 RealEndPosition() const + { + return this->reservation_end_position - (this->tunnel_bridge_reserved_tiles * TILE_SIZE); + } + + void AddStation(int tiles, StationID id, int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end, end + (((int)TILE_SIZE) * tiles), z_pos, id, TRLIT_STATION }); + } + + void AddReverse(int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end, end, z_pos, 0, TRLIT_REVERSE }); + } + + void AddTrackSpeedLimit(uint16 speed, int offset, int duration, int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end + offset, end + offset + duration, z_pos, speed, TRLIT_TRACK_SPEED }); + } + + void AddSpeedRestriction(uint16 speed, int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end, end, z_pos, speed, TRLIT_SPEED_RESTRICTION }); + this->speed_restriction = speed; + } + + void AddSignal(uint16 target_speed, int offset, int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end + offset, end + offset, z_pos, target_speed, TRLIT_SIGNAL }); + } + + void AddCurveSpeedLimit(uint16 target_speed, int offset, int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end + offset, end + offset, z_pos, target_speed, TRLIT_CURVE_SPEED }); + } +}; + +/** Flags for FollowTrainReservation */ +enum FollowTrainReservationFlags { + FTRF_NONE = 0, ///< No flags + FTRF_IGNORE_LOOKAHEAD = 0x01, ///< No use of cached lookahead +}; +DECLARE_ENUM_AS_BIT_SET(FollowTrainReservationFlags) + +PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res = nullptr, FollowTrainReservationFlags flags = FTRF_NONE); +void ApplyAvailableFreeTunnelBridgeTiles(TrainReservationLookAhead *lookahead, int free_tiles, TileIndex tile, TileIndex end); +void TryCreateLookAheadForTrainInTunnelBridge(Train *t); +void FillTrainReservationLookAhead(Train *v); bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg = false); bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bool forbid_90deg = false, PBSWaitingPositionRestrictedSignalInfo *restricted_signal_info = nullptr); Train *GetTrainForReservation(TileIndex tile, Track track); +CommandCost CheckTrainReservationPreventsTrackModification(TileIndex tile, Track track); +CommandCost CheckTrainReservationPreventsTrackModification(const Train *v); +CommandCost CheckTrainInTunnelBridgePreventsTrackModification(TileIndex start, TileIndex end); /** * Check whether some of tracks is reserved on a tile. diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 2cf1cbfd35..f952c4b370 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -849,11 +849,16 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, cost.AddCost(RailClearCost(GetRailType(tile))); - if (flags & DC_EXEC) { - if (HasReservedTracks(tile, trackbit)) { - v = GetTrainForReservation(tile, track); - if (v != nullptr) FreeTrainTrackReservation(v); + if (HasReservedTracks(tile, trackbit)) { + v = GetTrainForReservation(tile, track); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; } + } + + if (flags & DC_EXEC) { + if (v != nullptr) FreeTrainTrackReservation(v); owner = GetTileOwner(tile); Company::Get(owner)->infrastructure.rail[GetRailType(tile)] -= LEVELCROSSING_TRACKBIT_FACTOR; @@ -890,11 +895,16 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, cost.AddCost(DoCommand(tile, track, 0, flags, CMD_REMOVE_SIGNALS)); } - if (flags & DC_EXEC) { - if (HasReservedTracks(tile, trackbit)) { - v = GetTrainForReservation(tile, track); - if (v != nullptr) FreeTrainTrackReservation(v); + if (HasReservedTracks(tile, trackbit)) { + v = GetTrainForReservation(tile, track); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; } + } + + if (flags & DC_EXEC) { + if (v != nullptr) FreeTrainTrackReservation(v); owner = GetTileOwner(tile); @@ -958,16 +968,21 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, } if (ret.Failed()) return ret; + if (HasReservedTracks(tile, trackbit)) { + v = GetTrainForReservation(tile, track); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + } + } + cost.AddCost(RailClearCost(GetTileRailTypeByTrackBit(tile, trackbit))); if (flags & DC_EXEC) { SubtractRailTunnelBridgeInfrastructure(tile, other_end); owner = GetTileOwner(tile); - if (HasReservedTracks(tile, trackbit)) { - v = GetTrainForReservation(tile, track); - if (v != nullptr) FreeTrainTrackReservation(v); - } + if (v != nullptr) FreeTrainTrackReservation(v); if (future == TRACK_BIT_HORZ || future == TRACK_BIT_VERT) { // Changing to two separate tracks with separate rail types @@ -1346,6 +1361,8 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, uint which_signals = GB(p1, 9, 6); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && IsSignalTypeUnsuitableForRealisticBraking(sigtype)) return CMD_ERROR; + /* 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; @@ -1414,15 +1431,30 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, SetTunnelBridgeEntranceSignalState(t, SIGNAL_STATE_GREEN); SetTunnelBridgeSignalSimulationExit(t); }; + + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + for (TileIndex t : { tile, tile_exit }) { + if (HasAcrossTunnelBridgeReservation(t)) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(t, FindFirstTrack(GetAcrossTunnelBridgeReservationTrackBits(t))); + if (ret.Failed()) return ret; + } + } + } + if (flags & DC_EXEC) { Company * const c = Company::Get(GetTileOwner(tile)); - Train *re_reserve_train = nullptr; + std::vector re_reserve_trains; if (IsTunnelBridgeWithSignalSimulation(tile)) { c->infrastructure.signal -= GetTunnelBridgeSignalSimulationSignalCount(tile, tile_exit); } else { - if (HasAcrossTunnelBridgeReservation(tile)) { - re_reserve_train = GetTrainForReservation(tile, FindFirstTrack(GetAcrossTunnelBridgeReservationTrackBits(tile))); - if (re_reserve_train != nullptr) FreeTrainTrackReservation(re_reserve_train); + for (TileIndex t : { tile, tile_exit }) { + if (HasAcrossTunnelBridgeReservation(t)) { + Train *re_reserve_train = GetTrainForReservation(t, FindFirstTrack(GetAcrossTunnelBridgeReservationTrackBits(t))); + if (re_reserve_train != nullptr) { + FreeTrainTrackReservation(re_reserve_train); + re_reserve_trains.push_back(re_reserve_train); + } + } } } if (!p2_active && IsTunnelBridgeWithSignalSimulation(tile)) { // Toggle signal if already signals present. @@ -1481,8 +1513,8 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, if (!IsTunnelBridgePBS(tile)) remove_pbs_bidi(); } } - if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgePBS(tile) && !HasAcrossTunnelBridgeReservation(tile)) SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); - if (IsTunnelBridgeSignalSimulationExit(tile_exit) && IsTunnelBridgePBS(tile_exit) && !HasAcrossTunnelBridgeReservation(tile_exit)) SetTunnelBridgeExitSignalState(tile_exit, SIGNAL_STATE_RED); + if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgeEffectivelyPBS(tile) && !HasAcrossTunnelBridgeReservation(tile)) SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + if (IsTunnelBridgeSignalSimulationExit(tile_exit) && IsTunnelBridgeEffectivelyPBS(tile_exit) && !HasAcrossTunnelBridgeReservation(tile_exit)) SetTunnelBridgeExitSignalState(tile_exit, SIGNAL_STATE_RED); MarkBridgeOrTunnelDirty(tile); AddSideToSignalBuffer(tile, INVALID_DIAGDIR, GetTileOwner(tile)); AddSideToSignalBuffer(tile_exit, INVALID_DIAGDIR, GetTileOwner(tile)); @@ -1490,7 +1522,7 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, YapfNotifyTrackLayoutChange(tile_exit, track); if (IsTunnelBridgeWithSignalSimulation(tile)) c->infrastructure.signal += GetTunnelBridgeSignalSimulationSignalCount(tile, tile_exit); DirtyCompanyInfrastructureWindows(GetTileOwner(tile)); - if (re_reserve_train != nullptr) { + for (Train *re_reserve_train : re_reserve_trains) { ReReserveTrainPath(re_reserve_train); } } @@ -1530,16 +1562,20 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, } } - if (flags & DC_EXEC) { - Train *v = nullptr; - /* The new/changed signal could block our path. As this can lead to - * stale reservations, we clear the path reservation here and try - * to redo it later on. */ - if (HasReservedTracks(tile, TrackToTrackBits(track))) { - v = GetTrainForReservation(tile, track); - if (v != nullptr) FreeTrainTrackReservation(v); + Train *v = nullptr; + /* The new/changed signal could block our path. As this can lead to + * stale reservations, we clear the path reservation here and try + * to redo it later on. */ + if (HasReservedTracks(tile, TrackToTrackBits(track))) { + v = GetTrainForReservation(tile, track); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + if (flags & DC_EXEC) FreeTrainTrackReservation(v); } + } + if (flags & DC_EXEC) { if (!HasSignals(tile)) { /* there are no signals at all on this tile yet */ SetHasSignals(tile, true); @@ -1555,7 +1591,7 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, if (p2 == 0) { if (!HasSignalOnTrack(tile, track)) { /* build new signals */ - SetPresentSignals(tile, GetPresentSignals(tile) | (IsPbsSignal(sigtype) ? KillFirstBit(SignalOnTrack(track)) : SignalOnTrack(track))); + SetPresentSignals(tile, GetPresentSignals(tile) | ((IsPbsSignal(sigtype) || _settings_game.vehicle.train_braking_model == TBM_REALISTIC) ? KillFirstBit(SignalOnTrack(track)) : SignalOnTrack(track))); SetSignalType(tile, track, sigtype); SetSignalVariant(tile, track, sigvar); while (num_dir_cycle-- > 0) CycleSignalSide(tile, track); @@ -1569,8 +1605,9 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, sigtype = GetSignalType(tile, track); } else { /* convert the present signal to the chosen type and variant */ - if (IsPresignalProgrammable(tile, track)) + if (IsPresignalProgrammable(tile, track)) { FreeSignalProgram(SignalReference(tile, track)); + } SetSignalType(tile, track, sigtype); SetSignalVariant(tile, track, sigvar); if (IsPbsSignal(sigtype) && (GetPresentSignals(tile) & SignalOnTrack(track)) == SignalOnTrack(track)) { @@ -1584,7 +1621,9 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, if(IsProgrammableSignal(sigtype)) FreeSignalProgram(SignalReference(tile, track)); - sigtype = NextSignalType(sigtype, which_signals); + do { + sigtype = NextSignalType(sigtype, which_signals); + } while (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && IsSignalTypeUnsuitableForRealisticBraking(sigtype)); SetSignalType(tile, track, sigtype); if (IsPbsSignal(sigtype) && (GetPresentSignals(tile) & SignalOnTrack(track)) == SignalOnTrack(track)) { @@ -1613,7 +1652,7 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, Company::Get(GetTileOwner(tile))->infrastructure.signal += CountBits(GetPresentSignals(tile)); DirtyCompanyInfrastructureWindows(GetTileOwner(tile)); - if (IsPbsSignal(sigtype)) { + if (IsPbsSignalNonExtended(sigtype) || (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasBit(GetRailReservationTrackBits(tile), track))) { /* PBS signals should show red unless they are on reserved tiles without a train. */ uint mask = GetPresentSignals(tile) & SignalOnTrack(track); SetSignalStates(tile, (GetSignalStates(tile) & ~mask) | ((HasBit(GetRailReservationTrackBits(tile), track) && EnsureNoVehicleOnGround(tile).Succeeded() ? UINT_MAX : 0) & mask)); @@ -1956,54 +1995,60 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1 if (ret.Failed()) return ret; } - /* Do it? */ - if (flags & DC_EXEC) { - - if (IsTunnelBridgeWithSignalSimulation(tile)) { // handle tunnel/bridge signals. - TileIndex end = GetOtherTunnelBridgeEnd(tile); - std::vector re_reserve_trains; - auto check_reservation = [&](TileIndex t) { - if (HasAcrossTunnelBridgeReservation(t)) { - Train *v = GetTrainForReservation(t, FindFirstTrack(GetAcrossTunnelBridgeReservationTrackBits(t))); - if (v != nullptr) { + if (IsTunnelBridgeWithSignalSimulation(tile)) { // handle tunnel/bridge signals. + TileIndex end = GetOtherTunnelBridgeEnd(tile); + std::vector re_reserve_trains; + for (TileIndex t : { tile, end }) { + if (HasAcrossTunnelBridgeReservation(t)) { + Train *v = GetTrainForReservation(t, FindFirstTrack(GetAcrossTunnelBridgeReservationTrackBits(t))); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + if (flags & DC_EXEC) { FreeTrainTrackReservation(v); re_reserve_trains.push_back(v); } } - }; - check_reservation(tile); - check_reservation(end); - Company::Get(GetTileOwner(tile))->infrastructure.signal -= GetTunnelBridgeSignalSimulationSignalCount(tile, end); - ClearBridgeTunnelSignalSimulation(end, tile); - ClearBridgeTunnelSignalSimulation(tile, end); - MarkBridgeOrTunnelDirty(tile); - AddSideToSignalBuffer(tile, INVALID_DIAGDIR, GetTileOwner(tile)); - AddSideToSignalBuffer(end, INVALID_DIAGDIR, GetTileOwner(tile)); - YapfNotifyTrackLayoutChange(tile, track); - YapfNotifyTrackLayoutChange(end, track); - DirtyCompanyInfrastructureWindows(GetTileOwner(tile)); - for (Train *v : re_reserve_trains) { - ReReserveTrainPath(v); } - return CommandCost(EXPENSES_CONSTRUCTION, cost); } + Company::Get(GetTileOwner(tile))->infrastructure.signal -= GetTunnelBridgeSignalSimulationSignalCount(tile, end); + ClearBridgeTunnelSignalSimulation(end, tile); + ClearBridgeTunnelSignalSimulation(tile, end); + MarkBridgeOrTunnelDirty(tile); + AddSideToSignalBuffer(tile, INVALID_DIAGDIR, GetTileOwner(tile)); + AddSideToSignalBuffer(end, INVALID_DIAGDIR, GetTileOwner(tile)); + YapfNotifyTrackLayoutChange(tile, track); + YapfNotifyTrackLayoutChange(end, track); + DirtyCompanyInfrastructureWindows(GetTileOwner(tile)); + for (Train *v : re_reserve_trains) { + ReReserveTrainPath(v); + } + return CommandCost(EXPENSES_CONSTRUCTION, cost); + } - Train *v = nullptr; - if (HasReservedTracks(tile, TrackToTrackBits(track))) { - v = GetTrainForReservation(tile, track); - } else if (IsPbsSignal(GetSignalType(tile, track))) { - /* PBS signal, might be the end of a path reservation. */ - Trackdir td = TrackToTrackdir(track); - for (int i = 0; v == nullptr && i < 2; i++, td = ReverseTrackdir(td)) { - /* Only test the active signal side. */ - if (!HasSignalOnTrackdir(tile, ReverseTrackdir(td))) continue; - TileIndex next = TileAddByDiagDir(tile, TrackdirToExitdir(td)); - TrackBits tracks = TrackdirBitsToTrackBits(TrackdirReachesTrackdirs(td)); - if (HasReservedTracks(next, tracks)) { - v = GetTrainForReservation(next, TrackBitsToTrack(GetReservedTrackbits(next) & tracks)); - } + Train *v = nullptr; + if (HasReservedTracks(tile, TrackToTrackBits(track))) { + v = GetTrainForReservation(tile, track); + } else if (IsPbsSignal(GetSignalType(tile, track))) { + /* PBS signal, might be the end of a path reservation. */ + Trackdir td = TrackToTrackdir(track); + for (int i = 0; v == nullptr && i < 2; i++, td = ReverseTrackdir(td)) { + /* Only test the active signal side. */ + if (!HasSignalOnTrackdir(tile, ReverseTrackdir(td))) continue; + TileIndex next = TileAddByDiagDir(tile, TrackdirToExitdir(td)); + TrackBits tracks = TrackdirBitsToTrackBits(TrackdirReachesTrackdirs(td)); + if (HasReservedTracks(next, tracks)) { + v = GetTrainForReservation(next, TrackBitsToTrack(GetReservedTrackbits(next) & tracks)); } } + } + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + } + + /* Do it? */ + if (flags & DC_EXEC) { Company::Get(GetTileOwner(tile))->infrastructure.signal -= CountBits(GetPresentSignals(tile)); CheckRemoveSignal(tile, track); SetPresentSignals(tile, GetPresentSignals(tile) & ~SignalOnTrack(track)); @@ -2196,16 +2241,35 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 std::vector vehicles_affected; - auto find_train_reservations = [&vehicles_affected, &totype](TileIndex tile, TrackBits reserved) { + auto find_train_reservations = [&vehicles_affected, &totype, &flags](TileIndex tile, TrackBits reserved) -> CommandCost { + if (!(flags & DC_EXEC) && _settings_game.vehicle.train_braking_model != TBM_REALISTIC) { + /* Nothing to do */ + return CommandCost(); + } Track track; while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) { Train *v = GetTrainForReservation(tile, track); + bool check_train = false; if (v != nullptr && !HasPowerOnRail(v->railtype, totype)) { + check_train = true; + } else if (v != nullptr && _settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + RailType original = GetRailTypeByTrack(tile, track); + if ((uint)(GetRailTypeInfo(original)->max_speed - 1) > (uint)(GetRailTypeInfo(totype)->max_speed - 1)) { + check_train = true; + } + } + if (check_train) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + /* No power on new rail type, reroute. */ - FreeTrainTrackReservation(v); - vehicles_affected.push_back(v); + if (flags & DC_EXEC) { + FreeTrainTrackReservation(v); + vehicles_affected.push_back(v); + } } } + return CommandCost(); }; auto yapf_notify_track_change = [](TileIndex tile, TrackBits tracks) { @@ -2224,9 +2288,9 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 continue; } } + CommandCost ret = find_train_reservations(tile, GetReservedTrackbits(tile)); + if (ret.Failed()) return ret; if (flags & DC_EXEC) { // we can safely convert, too - find_train_reservations(tile, GetReservedTrackbits(tile)); - /* Update the company infrastructure counters. */ if (!IsRailStationTile(tile) || !IsStationTileBlocked(tile)) { Company *c = Company::Get(GetTileOwner(tile)); @@ -2316,12 +2380,18 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 if (raw_secondary_type != INVALID_RAILTYPE) cost.AddCost(RailConvertCost(raw_secondary_type, totype)); if (end_secondary_type != INVALID_RAILTYPE) cost.AddCost(RailConvertCost(end_secondary_type, totype)); + CommandCost ret = find_train_reservations(tile, GetTunnelBridgeReservationTrackBits(tile)); + if (ret.Failed()) return ret; + ret = find_train_reservations(endtile, GetTunnelBridgeReservationTrackBits(endtile)); + if (ret.Failed()) return ret; + if ((uint)(GetRailTypeInfo(type)->max_speed - 1) > (uint)(GetRailTypeInfo(totype)->max_speed - 1)) { + ret = CheckTrainInTunnelBridgePreventsTrackModification(tile, endtile); + if (ret.Failed()) return ret; + } + if (flags & DC_EXEC) { SubtractRailTunnelBridgeInfrastructure(tile, endtile); - find_train_reservations(tile, GetTunnelBridgeReservationTrackBits(tile)); - find_train_reservations(endtile, GetTunnelBridgeReservationTrackBits(endtile)); - SetRailType(tile, totype); SetRailType(endtile, totype); SetSecondaryRailType(tile, totype); @@ -2384,16 +2454,23 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; + /* read variables before the depot is removed */ + DiagDirection dir = GetRailDepotDirection(tile); + + Train *v = nullptr; + if (HasDepotReservation(tile)) { + v = GetTrainForReservation(tile, DiagDirToDiagTrack(dir)); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + } + } + if (flags & DC_EXEC) { /* read variables before the depot is removed */ - DiagDirection dir = GetRailDepotDirection(tile); Owner owner = GetTileOwner(tile); - Train *v = nullptr; - if (HasDepotReservation(tile)) { - v = GetTrainForReservation(tile, DiagDirToDiagTrack(dir)); - if (v != nullptr) FreeTrainTrackReservation(v); - } + if (v != nullptr) FreeTrainTrackReservation(v); Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--; DirtyCompanyInfrastructureWindows(owner); diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index c607176fb9..ccc126eff2 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -1606,6 +1606,7 @@ private: Dimension sig_sprite_size; ///< Maximum size of signal GUI sprites. int sig_sprite_bottom_offset; ///< Maximum extent of signal GUI sprite from reference point towards bottom. bool progsig_ui_shown; ///< Whether programmable pre-signal UI is shown + bool presig_ui_shown; ///< Whether pre-signal UI is shown /** * Draw dynamic a signal-sprite in a button in the signal GUI @@ -1629,18 +1630,26 @@ private: y + this->IsWidgetLowered(widget_index)); } - void SetProgsigUiShown() { + void SetSignalUIMode() { this->progsig_ui_shown = _settings_client.gui.show_progsig_ui; - this->GetWidget(WID_BS_SEMAPHORE_PROG_SEL)->SetDisplayedPlane(_settings_client.gui.show_progsig_ui ? 0 : SZSP_NONE); - this->GetWidget(WID_BS_ELECTRIC_PROG_SEL)->SetDisplayedPlane(_settings_client.gui.show_progsig_ui ? 0 : SZSP_NONE); - this->GetWidget(WID_BS_PROGRAM_SEL)->SetDisplayedPlane(_settings_client.gui.show_progsig_ui ? 0 : 1); + this->presig_ui_shown = _settings_game.vehicle.train_braking_model != TBM_REALISTIC; + bool show_progsig = this->progsig_ui_shown && this->presig_ui_shown; + this->GetWidget(WID_BS_SEMAPHORE_ENTRY_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_ELECTRIC_ENTRY_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_SEMAPHORE_EXIT_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_ELECTRIC_EXIT_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_SEMAPHORE_COMBO_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_ELECTRIC_COMBO_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_SEMAPHORE_PROG_SEL)->SetDisplayedPlane(show_progsig ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_ELECTRIC_PROG_SEL)->SetDisplayedPlane(show_progsig ? 0 : SZSP_NONE); + this->GetWidget(WID_BS_PROGRAM_SEL)->SetDisplayedPlane(show_progsig ? 0 : 1); } public: BuildSignalWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent) { this->CreateNestedTree(); - this->SetProgsigUiShown(); + this->SetSignalUIMode(); this->FinishInitNested(TRANSPORT_RAIL); this->OnInvalidateData(); } @@ -1811,8 +1820,8 @@ public: this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1); this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20); - if (this->progsig_ui_shown != _settings_client.gui.show_progsig_ui) { - this->SetProgsigUiShown(); + if (this->progsig_ui_shown != _settings_client.gui.show_progsig_ui || this->presig_ui_shown != (_settings_game.vehicle.train_braking_model != TBM_REALISTIC)) { + this->SetSignalUIMode(); this->ReInit(); } } @@ -1827,9 +1836,15 @@ static const NWidgetPart _nested_signal_builder_widgets[] = { NWidget(NWID_VERTICAL, NC_EQUALSIZE), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP), EndContainer(), SetFill(1, 1), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_ENTRY_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_EXIT_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_COMBO_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1), + EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_PROG_SEL), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PROG), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PROG_TOOLTIP), EndContainer(), SetFill(1, 1), EndContainer(), @@ -1840,9 +1855,15 @@ static const NWidgetPart _nested_signal_builder_widgets[] = { EndContainer(), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), EndContainer(), SetFill(1, 1), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_ENTRY_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_EXIT_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_COMBO_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1), + EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_PROG_SEL), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PROG), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PROG_TOOLTIP), EndContainer(), SetFill(1, 1), EndContainer(), diff --git a/src/rail_map.h b/src/rail_map.h index 89ba7a12e8..83b427a42e 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -361,7 +361,7 @@ static inline void CycleSignalSide(TileIndex t, Track track) byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 4 : 6; sig = GB(_m[t].m3, pos, 2); - if (--sig == 0) sig = IsPbsSignal(GetSignalType(t, track)) ? 2 : 3; + if (--sig == 0) sig = (IsPbsSignal(GetSignalType(t, track)) || _settings_game.vehicle.train_braking_model == TBM_REALISTIC) ? 2 : 3; SB(_m[t].m3, pos, 2, sig); } diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 9844f5dd4c..52f185e720 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -826,6 +826,12 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec c->infrastructure.rail[GetRailType(tile)] -= LEVELCROSSING_TRACKBIT_FACTOR - 1; DirtyCompanyInfrastructureWindows(c->index); } + + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + AddTrackToSignalBuffer(tile, railtrack, GetTileOwner(tile)); + UpdateSignalsInBuffer(); + } + DeleteNewGRFInspectWindow(GSF_ROADTYPES, tile); } else { SetRoadType(tile, rtt, INVALID_ROADTYPE); @@ -1109,6 +1115,10 @@ CommandCost CmdBuildRoad(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 if (rtt == RTT_ROAD) { UpdateRoadCachedOneWayStatesAroundTile(tile); } + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + AddTrackToSignalBuffer(tile, railtrack, GetTileOwner(tile)); + UpdateSignalsInBuffer(); + } MarkTileDirtyByTile(tile); } return CommandCost(EXPENSES_CONSTRUCTION, 2 * RoadBuildCost(rt)); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 6941258857..a2bd99d4fa 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -794,13 +794,19 @@ static void RoadVehArrivesAt(const RoadVehicle *v, Station *st) */ int RoadVehicle::UpdateSpeed() { + int max_speed = this->GetCurrentMaxSpeed(); switch (_settings_game.vehicle.roadveh_acceleration_model) { default: NOT_REACHED(); - case AM_ORIGINAL: - return this->DoUpdateSpeed(this->overtaking != 0 ? 512 : 256, 0, this->GetCurrentMaxSpeed()); + case AM_ORIGINAL: { + int acceleration = this->overtaking != 0 ? 512 : 256; + return this->DoUpdateSpeed({ acceleration, acceleration }, 0, max_speed, max_speed); + } - case AM_REALISTIC: - return this->DoUpdateSpeed(this->GetAcceleration() + (this->overtaking != 0 ? 256 : 0), this->GetAccelerationStatus() == AS_BRAKE ? 0 : 4, this->GetCurrentMaxSpeed()); + case AM_REALISTIC: { + GroundVehicleAcceleration acceleration = this->GetAcceleration(); + if (this->overtaking != 0) acceleration.acceleration += 256; + return this->DoUpdateSpeed(acceleration, this->GetAccelerationStatus() == AS_BRAKE ? 0 : 4, max_speed, max_speed); + } } } @@ -1021,7 +1027,7 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) /* Can't overtake a vehicle that is moving faster than us. If the vehicle in front is * accelerating, take the maximum speed for the comparison, else the current speed. * Original acceleration always accelerates, so always use the maximum speed. */ - int u_speed = (_settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL || u->GetAcceleration() > 0) ? u->GetCurrentMaxSpeed() : u->cur_speed; + int u_speed = (_settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL || u->GetAcceleration().acceleration > 0) ? u->GetCurrentMaxSpeed() : u->cur_speed; if (u_speed >= v->GetCurrentMaxSpeed() && !(u->vehstatus & VS_STOPPED) && u->cur_speed != 0) { diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 8e9de59144..d14fecf73b 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -83,7 +83,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_TIMETABLES_START_TICKS, XSCF_NULL, 2, 2, "timetable_start_ticks", nullptr, nullptr, nullptr }, { XSLFI_TOWN_CARGO_ADJ, XSCF_IGNORABLE_UNKNOWN, 2, 2, "town_cargo_adj", nullptr, nullptr, nullptr }, { XSLFI_SIG_TUNNEL_BRIDGE, XSCF_NULL, 8, 8, "signal_tunnel_bridge", nullptr, nullptr, "XBSS" }, - { XSLFI_IMPROVED_BREAKDOWNS, XSCF_NULL, 6, 6, "improved_breakdowns", nullptr, nullptr, nullptr }, + { XSLFI_IMPROVED_BREAKDOWNS, XSCF_NULL, 7, 7, "improved_breakdowns", nullptr, nullptr, nullptr }, { XSLFI_CONSIST_BREAKDOWN_FLAG, XSCF_NULL, 1, 1, "consist_breakdown_flag", nullptr, nullptr, nullptr }, { XSLFI_TT_WAIT_IN_DEPOT, XSCF_NULL, 1, 1, "tt_wait_in_depot", nullptr, nullptr, nullptr }, { XSLFI_AUTO_TIMETABLE, XSCF_NULL, 5, 5, "auto_timetables", nullptr, nullptr, nullptr }, @@ -143,6 +143,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_ANIMATED_TILE_EXTRA, XSCF_NULL, 1, 1, "animated_tile_extra", nullptr, nullptr, nullptr }, { XSLFI_NEWGRF_INFO_EXTRA, XSCF_NULL, 1, 1, "newgrf_info_extra", nullptr, nullptr, nullptr }, { XSLFI_INDUSTRY_CARGO_ADJ, XSCF_IGNORABLE_UNKNOWN, 1, 1, "industry_cargo_adj", nullptr, nullptr, nullptr }, + { XSLFI_REALISTIC_TRAIN_BRAKING,XSCF_NULL, 1, 1, "realistic_train_braking", nullptr, nullptr, "VLKA" }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index e3767c9f0a..62ce068784 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -97,6 +97,7 @@ enum SlXvFeatureIndex { XSLFI_ANIMATED_TILE_EXTRA, ///< Animated tile extra info XSLFI_NEWGRF_INFO_EXTRA, ///< Extra NewGRF info in savegame XSLFI_INDUSTRY_CARGO_ADJ, ///< Industry cargo adjustment patch + XSLFI_REALISTIC_TRAIN_BRAKING, ///< Realistic train braking XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 66a6048007..94928a54fa 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -1148,6 +1148,9 @@ struct train_venc { GroundVehicleCache gvcache; bool cached_tilt; uint8 cached_num_engines; + uint16 cached_veh_weight; + uint16 cached_uncapped_decel; + uint8 cached_deceleration; byte user_def_data; int cached_max_curve_speed; }; @@ -1212,6 +1215,9 @@ void Save_VENC() write_gv_cache(t->gcache); SlWriteByte(t->tcache.cached_tilt); SlWriteByte(t->tcache.cached_num_engines); + SlWriteUint16(t->tcache.cached_veh_weight); + SlWriteUint16(t->tcache.cached_uncapped_decel); + SlWriteByte(t->tcache.cached_deceleration); SlWriteByte(t->tcache.user_def_data); SlWriteUint32(t->tcache.cached_max_curve_speed); } @@ -1269,6 +1275,9 @@ void Load_VENC() read_gv_cache(venc.gvcache); venc.cached_tilt = SlReadByte(); venc.cached_num_engines = SlReadByte(); + venc.cached_veh_weight = SlReadUint16(); + venc.cached_uncapped_decel = SlReadUint16(); + venc.cached_deceleration = SlReadByte(); venc.user_def_data = SlReadByte(); venc.cached_max_curve_speed = SlReadUint32(); } @@ -1352,6 +1361,9 @@ void SlProcessVENC() check_gv_cache(t->gcache, venc.gvcache, t); CheckVehicleVENCProp(t->tcache.cached_tilt, venc.cached_tilt, t, "cached_tilt"); CheckVehicleVENCProp(t->tcache.cached_num_engines, venc.cached_num_engines, t, "cached_num_engines"); + CheckVehicleVENCProp(t->tcache.cached_veh_weight, venc.cached_veh_weight, t, "cached_veh_weight"); + CheckVehicleVENCProp(t->tcache.cached_uncapped_decel, venc.cached_uncapped_decel, t, "cached_uncapped_decel"); + CheckVehicleVENCProp(t->tcache.cached_deceleration, venc.cached_deceleration, t, "cached_deceleration"); CheckVehicleVENCProp(t->tcache.user_def_data, venc.user_def_data, t, "user_def_data"); CheckVehicleVENCProp(t->tcache.cached_max_curve_speed, venc.cached_max_curve_speed, t, "cached_max_curve_speed"); } @@ -1373,9 +1385,96 @@ void SlProcessVENC() } } +const SaveLoad *GetVehicleLookAheadDescription() +{ + static const SaveLoad _vehicle_look_ahead_desc[] = { + SLE_VAR(TrainReservationLookAhead, reservation_end_tile, SLE_UINT32), + SLE_VAR(TrainReservationLookAhead, reservation_end_trackdir, SLE_UINT8), + SLE_VAR(TrainReservationLookAhead, current_position, SLE_INT32), + SLE_VAR(TrainReservationLookAhead, reservation_end_position, SLE_INT32), + SLE_VAR(TrainReservationLookAhead, reservation_end_z, SLE_INT16), + SLE_VAR(TrainReservationLookAhead, tunnel_bridge_reserved_tiles, SLE_INT16), + SLE_VAR(TrainReservationLookAhead, flags, SLE_UINT16), + SLE_VAR(TrainReservationLookAhead, speed_restriction, SLE_UINT16), + SLE_END() + }; + + return _vehicle_look_ahead_desc; +} + +const SaveLoad *GetVehicleLookAheadItemDescription() +{ + static const SaveLoad _vehicle_look_ahead_item_desc[] = { + SLE_VAR(TrainReservationLookAheadItem, start, SLE_INT32), + SLE_VAR(TrainReservationLookAheadItem, end, SLE_INT32), + SLE_VAR(TrainReservationLookAheadItem, z_pos, SLE_INT16), + SLE_VAR(TrainReservationLookAheadItem, data_id, SLE_UINT16), + SLE_VAR(TrainReservationLookAheadItem, type, SLE_UINT8), + SLE_END() + }; + + return _vehicle_look_ahead_item_desc; +} + +const SaveLoad *GetVehicleLookAheadCurveDescription() +{ + static const SaveLoad _vehicle_look_ahead_curve_desc[] = { + SLE_VAR(TrainReservationLookAheadCurve, position, SLE_INT32), + SLE_VAR(TrainReservationLookAheadCurve, dir_diff, SLE_UINT8), + SLE_END() + }; + + return _vehicle_look_ahead_curve_desc; +} + +static void RealSave_VLKA(TrainReservationLookAhead *lookahead) +{ + SlObject(lookahead, GetVehicleLookAheadDescription()); + SlWriteUint32(lookahead->items.size()); + for (TrainReservationLookAheadItem &item : lookahead->items) { + SlObject(&item, GetVehicleLookAheadItemDescription()); + } + SlWriteUint32(lookahead->curves.size()); + for (TrainReservationLookAheadCurve &curve : lookahead->curves) { + SlObject(&curve, GetVehicleLookAheadCurveDescription()); + } +} + +void Save_VLKA() +{ + for (Train *t : Train::Iterate()) { + if (t->lookahead != nullptr) { + SlSetArrayIndex(t->index); + SlAutolength((AutolengthProc*) RealSave_VLKA, t->lookahead.get()); + } + } +} + +void Load_VLKA() +{ + int index; + while ((index = SlIterateArray()) != -1) { + Train *t = Train::GetIfValid(index); + assert(t != nullptr); + t->lookahead.reset(new TrainReservationLookAhead()); + SlObject(t->lookahead.get(), GetVehicleLookAheadDescription()); + uint32 items = SlReadUint32(); + t->lookahead->items.resize(items); + for (uint i = 0; i < items; i++) { + SlObject(&t->lookahead->items[i], GetVehicleLookAheadItemDescription()); + } + uint32 curves = SlReadUint32(); + t->lookahead->curves.resize(curves); + for (uint i = 0; i < curves; i++) { + SlObject(&t->lookahead->curves[i], GetVehicleLookAheadCurveDescription()); + } + } +} + extern const ChunkHandler _veh_chunk_handlers[] = { { 'VEHS', Save_VEHS, Load_VEHS, Ptrs_VEHS, nullptr, CH_SPARSE_ARRAY}, { 'VEOX', Save_VEOX, Load_VEOX, nullptr, nullptr, CH_SPARSE_ARRAY}, { 'VESR', Save_VESR, Load_VESR, nullptr, nullptr, CH_SPARSE_ARRAY}, - { 'VENC', Save_VENC, Load_VENC, nullptr, nullptr, CH_RIFF | CH_LAST}, + { 'VENC', Save_VENC, Load_VENC, nullptr, nullptr, CH_RIFF}, + { 'VLKA', Save_VLKA, Load_VLKA, nullptr, nullptr, CH_SPARSE_ARRAY | CH_LAST}, }; diff --git a/src/settings.cpp b/src/settings.cpp index e7ddaa47f4..6489f5dcc2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -68,6 +68,8 @@ #include "string_func.h" #include "debug.h" #include "zoning.h" +#include "vehicle_func.h" +#include "scope_info.h" #include "void_map.h" #include "station_base.h" @@ -958,7 +960,10 @@ static bool UpdateConsists(int32 p1) { for (Train *t : Train::Iterate()) { /* Update the consist of all trains so the maximum speed is set correctly. */ - if (t->IsFrontEngine() || t->IsFreeWagon()) t->ConsistChanged(CCF_TRACK); + if (t->IsFrontEngine() || t->IsFreeWagon()) { + t->ConsistChanged(CCF_TRACK); + if (t->lookahead != nullptr) SetBit(t->lookahead->flags, TRLF_APPLY_ADVISORY); + } } InvalidateWindowClassesData(WC_BUILD_VEHICLE, 0); return true; @@ -1059,6 +1064,7 @@ static bool TrainAccelerationModelChanged(int32 p1) if (t->IsFrontEngine()) { t->tcache.cached_max_curve_speed = t->GetCurveSpeedLimit(); t->UpdateAcceleration(); + if (t->lookahead != nullptr) SetBit(t->lookahead->flags, TRLF_APPLY_ADVISORY); } } @@ -1070,6 +1076,86 @@ static bool TrainAccelerationModelChanged(int32 p1) return true; } +static bool TrainBrakingModelChanged(int32 p1) +{ + for (Train *t : Train::Iterate()) { + if (t->IsFrontEngine()) { + t->UpdateAcceleration(); + } + } + if (p1 == TBM_REALISTIC && (_game_mode == GM_NORMAL || _game_mode == GM_EDITOR)) { + for (TileIndex t = 0; t < MapSize(); t++) { + if (IsTileType(t, MP_RAILWAY) && GetRailTileType(t) == RAIL_TILE_SIGNALS) { + uint signals = GetPresentSignals(t); + if ((signals & 0x3) & ((signals & 0x3) - 1) || (signals & 0xC) & ((signals & 0xC) - 1)) { + /* Signals in both directions */ + ShowErrorMessage(STR_CONFIG_SETTING_REALISTIC_BRAKING_SIGNALS_NOT_ALLOWED, INVALID_STRING_ID, WL_ERROR); + return false; + } + if (((signals & 0x3) && IsSignalTypeUnsuitableForRealisticBraking(GetSignalType(t, TRACK_LOWER))) || + ((signals & 0xC) && IsSignalTypeUnsuitableForRealisticBraking(GetSignalType(t, TRACK_UPPER)))) { + /* Banned signal types present */ + ShowErrorMessage(STR_CONFIG_SETTING_REALISTIC_BRAKING_SIGNALS_NOT_ALLOWED, INVALID_STRING_ID, WL_ERROR); + return false; + } + } + } + for (TileIndex t = 0; t < MapSize(); t++) { + if (IsTileType(t, MP_RAILWAY) && GetRailTileType(t) == RAIL_TILE_SIGNALS) { + TrackBits bits = GetTrackBits(t); + do { + Track track = RemoveFirstTrack(&bits); + if (HasSignalOnTrack(t, track) && GetSignalType(t, track) == SIGTYPE_NORMAL && HasBit(GetRailReservationTrackBits(t), track)) { + if (EnsureNoTrainOnTrackBits(t, TrackToTrackBits(track)).Succeeded()) { + UnreserveTrack(t, track); + } + } + } while (bits != TRACK_BIT_NONE); + } + } + Train *v_cur = nullptr; + SCOPE_INFO_FMT([&v_cur], "TrainBrakingModelChanged: %s", scope_dumper().VehicleInfo(v_cur)); + extern bool _long_reserve_disabled; + _long_reserve_disabled = true; + for (Train *v : Train::Iterate()) { + v_cur = v; + if (!v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0 || HasBit(v->subtype, GVSF_VIRTUAL) || v->track == TRACK_BIT_DEPOT) continue; + TryPathReserve(v, v->current_order.GetType() != OT_LOADING, HasStationTileRail(v->tile)); + } + _long_reserve_disabled = false; + for (Train *v : Train::Iterate()) { + v_cur = v; + if (!v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0 || HasBit(v->subtype, GVSF_VIRTUAL) || v->track == TRACK_BIT_DEPOT) continue; + TryPathReserve(v, v->current_order.GetType() != OT_LOADING, HasStationTileRail(v->tile)); + if (v->lookahead != nullptr) SetBit(v->lookahead->flags, TRLF_APPLY_ADVISORY); + } + } else if (p1 == TBM_ORIGINAL && (_game_mode == GM_NORMAL || _game_mode == GM_EDITOR)) { + Train *v_cur = nullptr; + SCOPE_INFO_FMT([&v_cur], "TrainBrakingModelChanged: %s", scope_dumper().VehicleInfo(v_cur)); + for (Train *v : Train::Iterate()) { + v_cur = v; + if (!v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0 || HasBit(v->subtype, GVSF_VIRTUAL) || v->track == TRACK_BIT_DEPOT) { + v->lookahead.reset(); + continue; + } + if (!HasBit(v->flags, VRF_TRAIN_STUCK)) { + _settings_game.vehicle.train_braking_model = TBM_REALISTIC; + FreeTrainTrackReservation(v); + _settings_game.vehicle.train_braking_model = p1; + TryPathReserve(v, v->current_order.GetType() != OT_LOADING, HasStationTileRail(v->tile)); + } else { + v->lookahead.reset(); + } + } + } + + UpdateAllBlockSignals(); + + InvalidateWindowData(WC_BUILD_SIGNAL, 0); + + return true; +} + /** * This function updates the train acceleration cache after a steepness change. * @param p1 Callback parameter. @@ -1078,7 +1164,10 @@ static bool TrainAccelerationModelChanged(int32 p1) static bool TrainSlopeSteepnessChanged(int32 p1) { for (Train *t : Train::Iterate()) { - if (t->IsFrontEngine()) t->CargoChanged(); + if (t->IsFrontEngine()) { + t->CargoChanged(); + if (t->lookahead != nullptr) SetBit(t->lookahead->flags, TRLF_APPLY_ADVISORY); + } } return true; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 897b2ae2be..b435099d33 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1812,6 +1812,7 @@ static SettingsContainer &GetSettingsTree() SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS)); { physics->Add(new SettingEntry("vehicle.train_acceleration_model")); + physics->Add(new SettingEntry("vehicle.train_braking_model")); physics->Add(new SettingEntry("vehicle.train_slope_steepness")); physics->Add(new SettingEntry("vehicle.wagon_speed_limits")); physics->Add(new SettingEntry("vehicle.freight_trains")); diff --git a/src/settings_type.h b/src/settings_type.h index 8302ccbe16..45599f8ef4 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -538,6 +538,7 @@ struct VehicleSettings { uint8 max_train_length; ///< maximum length for trains uint8 smoke_amount; ///< amount of smoke/sparks locomotives produce uint8 train_acceleration_model; ///< realistic acceleration for trains + uint8 train_braking_model; ///< braking model for trains uint8 roadveh_acceleration_model; ///< realistic acceleration for road vehicles uint8 train_slope_steepness; ///< Steepness of hills for trains when using realistic acceleration uint8 roadveh_slope_steepness; ///< Steepness of hills for road vehicles when using realistic acceleration diff --git a/src/signal.cpp b/src/signal.cpp index b9d03d64da..38daade11a 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -313,12 +313,14 @@ static SigInfo ExploreSegment(Owner owner) if (IsRailDepot(tile)) { if (enterdir == INVALID_DIAGDIR) { // from 'inside' - train just entered or left the depot + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) info.flags |= SF_PBS; if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, VEH_TRAIN, nullptr, &TrainOnTileEnum)) info.flags |= SF_TRAIN; exitdir = GetRailDepotDirection(tile); tile += TileOffsByDiagDir(exitdir); enterdir = ReverseDiagDir(exitdir); break; } else if (enterdir == GetRailDepotDirection(tile)) { // entered a depot + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) info.flags |= SF_PBS; if (!(info.flags & SF_TRAIN) && HasVehicleOnPos(tile, VEH_TRAIN, nullptr, &TrainOnTileEnum)) info.flags |= SF_TRAIN; continue; } else { @@ -349,7 +351,7 @@ static SigInfo ExploreSegment(Owner owner) * ANY conventional signal in REVERSE direction * (if it is a presignal EXIT and it changes, it will be added to 'to-be-done' set later) */ if (HasSignalOnTrackdir(tile, reversedir)) { - if (IsPbsSignal(sig)) { + if (IsPbsSignalNonExtended(sig)) { info.flags |= SF_PBS; } else if (!_tbuset.Add(tile, reversedir)) { info.flags |= SF_FULL; @@ -515,9 +517,14 @@ static void UpdateSignalsAroundSegment(SigInfo info) Trackdir trackdir = INVALID_TRACKDIR; Track track = INVALID_TRACK; + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + if (_tbuset.Items() > 1) info.flags |= SF_PBS; + if (info.flags & SF_PBS) info.flags |= SF_TRAIN; + } + while (_tbuset.Get(&tile, &trackdir)) { if (IsTileType(tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(tile)) { - if (IsTunnelBridgePBS(tile)) continue; + if (IsTunnelBridgePBS(tile) || (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasAcrossTunnelBridgeReservation(tile))) continue; SignalState old_state = GetTunnelBridgeExitSignalState(tile); SignalState new_state = (info.flags & SF_TRAIN) ? SIGNAL_STATE_RED : SIGNAL_STATE_GREEN; if (old_state != new_state) { @@ -533,6 +540,9 @@ static void UpdateSignalsAroundSegment(SigInfo info) SignalType sig = GetSignalType(tile, track); SignalState newstate = SIGNAL_STATE_GREEN; + /* don't change signal state if tile is reserved in realistic braking mode */ + if ((_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasBit(GetRailReservationTrackBits(tile), track))) continue; + /* determine whether the new state is red */ if (info.flags & SF_TRAIN) { /* train in the segment */ @@ -704,6 +714,8 @@ static SigSegState UpdateSignalsInBuffer(Owner owner) UpdateSignalsAroundSegment(info); } + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) state = SIGSEG_PBS; + return state; } diff --git a/src/signal_func.h b/src/signal_func.h index 305ff540c4..04baf24dac 100644 --- a/src/signal_func.h +++ b/src/signal_func.h @@ -16,6 +16,8 @@ #include "direction_type.h" #include "company_type.h" #include "debug.h" +#include "settings_type.h" +#include "vehicle_type.h" /** * Maps a trackdir to the bit that stores its status in the map arrays, in the @@ -67,6 +69,12 @@ static inline bool IsComboSignal(SignalType type) /// Is a given signal type a PBS signal? static inline bool IsPbsSignal(SignalType type) +{ + return _settings_game.vehicle.train_braking_model == TBM_REALISTIC || type == SIGTYPE_PBS || type == SIGTYPE_PBS_ONEWAY; +} + +/// Is a given signal type a PBS signal? +static inline bool IsPbsSignalNonExtended(SignalType type) { return type == SIGTYPE_PBS || type == SIGTYPE_PBS_ONEWAY; } @@ -77,6 +85,12 @@ static inline bool IsProgrammableSignal(SignalType type) return type == SIGTYPE_PROG; } +/// Is this signal type unsuitable for realistic braking? +static inline bool IsSignalTypeUnsuitableForRealisticBraking(SignalType type) +{ + return type == SIGTYPE_ENTRY || type == SIGTYPE_EXIT || type == SIGTYPE_COMBO || type == SIGTYPE_PROG; +} + /// Does a given signal have a PBS sprite? static inline bool IsSignalSpritePBS(SignalType type) { diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 8d93aa1aec..ae1a6fc5b1 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -974,6 +974,8 @@ static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag fl if (HasBit(GetRailReservationTrackBits(tile_cur), track)) { Train *v = GetTrainForReservation(tile_cur, track); if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; affected_vehicles.push_back(v); } } @@ -1497,6 +1499,7 @@ CommandCost CmdBuildRailStation(TileIndex tile_org, DoCommandFlag flags, uint32 Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile))); if (v != nullptr) { affected_vehicles.push_back(v); + /* Not necessary to call CheckTrainReservationPreventsTrackModification as that is done by CheckFlatLandRailStation */ FreeTrainReservation(v); } } @@ -1707,6 +1710,18 @@ CommandCost RemoveFromRailBaseStation(TileArea ta, std::vector &affected_st if (ret.Failed()) continue; } + Train *v = nullptr; + Track track = GetRailStationTrack(tile); + if (HasStationReservation(tile)) { + v = GetTrainForReservation(tile, track); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + error.AddCost(ret); + if (ret.Failed()) continue; + if (flags & DC_EXEC) FreeTrainReservation(v); + } + } + /* If we reached here, the tile is valid so increase the quantity of tiles we will remove */ quantity++; @@ -1722,15 +1737,8 @@ CommandCost RemoveFromRailBaseStation(TileArea ta, std::vector &affected_st /* read variables before the station tile is removed */ uint specindex = GetCustomStationSpecIndex(tile); - Track track = GetRailStationTrack(tile); Owner owner = GetTileOwner(tile); RailType rt = GetRailType(tile); - Train *v = nullptr; - - if (HasStationReservation(tile)) { - v = GetTrainForReservation(tile, track); - if (v != nullptr) FreeTrainReservation(v); - } bool build_rail = keep_rail && !IsStationTileBlocked(tile); if (!build_rail && !IsStationTileBlocked(tile)) Company::Get(owner)->infrastructure.rail[rt]--; @@ -3629,8 +3637,25 @@ static VehicleEnterTileStatus VehicleEnter_Station(Vehicle *v, TileIndex tile, i stop &= TILE_SIZE - 1; if (x == stop) { + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && front->cur_speed > 15 && !(front->lookahead != nullptr && HasBit(front->lookahead->flags, TRLF_APPLY_ADVISORY))) { + /* Travelling too fast, do not stop and report overshoot to player */ + SetDParam(0, front->index); + SetDParam(1, IsRailWaypointTile(tile) ? STR_WAYPOINT_NAME : STR_STATION_NAME); + SetDParam(2, station_id); + AddNewsItem(STR_NEWS_TRAIN_OVERSHOT_STATION, NT_ADVICE, NF_INCOLOUR | NF_SMALL | NF_VEHICLE_PARAM0, + NR_VEHICLE, v->index, + NR_STATION, station_id); + for (Train *u = front; u != nullptr; u = u->Next()) { + ClrBit(u->flags, VRF_BEYOND_PLATFORM_END); + } + return VETSB_CONTINUE; + } return VETSB_ENTERED_STATION | (VehicleEnterTileStatus)(station_id << VETS_STATION_ID_OFFSET); // enter station } else if (x < stop) { + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && front->cur_speed > 30) { + /* Travelling too fast, take no action */ + return VETSB_CONTINUE; + } front->vehstatus |= VS_TRAIN_SLOWING; uint16 spd = max(0, (stop - x) * 20 - 15); if (spd < front->cur_speed) front->cur_speed = spd; diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 956801bfcd..5e020b9fc5 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -109,7 +109,8 @@ class NIHVehicle : public NIHelper { seprintf(buffer, lastof(buffer), " VirtXYTile: %X (%u x %u)", vtile, TileX(vtile), TileY(vtile)); print(buffer); } - seprintf(buffer, lastof(buffer), " Position: %X, %X, %X", v->x_pos, v->y_pos, v->z_pos); + b = buffer + seprintf(buffer, lastof(buffer), " Position: %X, %X, %X", v->x_pos, v->y_pos, v->z_pos); + if (v->type == VEH_TRAIN) seprintf(b, lastof(buffer), ", tile margin: %d", GetTileMarginInFrontOfTrain(Train::From(v))); print(buffer); if (v->IsPrimaryVehicle()) { @@ -149,9 +150,68 @@ class NIHVehicle : public NIHelper { } if (v->type == VEH_TRAIN) { const Train *t = Train::From(v); - seprintf(buffer, lastof(buffer), " Wait counter: %u, rev distance: %u, TBSN: %u, speed restriction: %u, railtype: %u, compatible_railtypes: 0x" OTTD_PRINTFHEX64, - t->wait_counter, t->reverse_distance, t->tunnel_bridge_signal_num, t->speed_restriction, t->railtype, t->compatible_railtypes); + seprintf(buffer, lastof(buffer), " T cache: tilt: %u, engines: %u, decel: %u, uncapped decel: %u", + t->tcache.cached_tilt, t->tcache.cached_num_engines, t->tcache.cached_deceleration, t->tcache.cached_uncapped_decel); print(buffer); + seprintf(buffer, lastof(buffer), " T cache: veh weight: %u, user data: %u, curve speed: %u", + t->tcache.cached_veh_weight, t->tcache.user_def_data, t->tcache.cached_max_curve_speed); + print(buffer); + seprintf(buffer, lastof(buffer), " Wait counter: %u, rev distance: %u, TBSN: %u, speed restriction: %u", + t->wait_counter, t->reverse_distance, t->tunnel_bridge_signal_num, t->speed_restriction); + print(buffer); + seprintf(buffer, lastof(buffer), " Railtype: %u, compatible_railtypes: 0x" OTTD_PRINTFHEX64, + t->railtype, t->compatible_railtypes); + print(buffer); + if (t->lookahead != nullptr) { + print (" Look ahead:"); + const TrainReservationLookAhead &l = *t->lookahead; + seprintf(buffer, lastof(buffer), " Position: current: %d, end: %d, remaining: %d", l.current_position, l.reservation_end_position, l.reservation_end_position - l.current_position); + print(buffer); + seprintf(buffer, lastof(buffer), " Reservation ends at %X (%u x %u), trackdir: %02X, z: %d", + l.reservation_end_tile, TileX(l.reservation_end_tile), TileY(l.reservation_end_tile), l.reservation_end_trackdir, l.reservation_end_z); + print(buffer); + b = buffer + seprintf(buffer, lastof(buffer), " TB reserved tiles: %d, flags:", l.tunnel_bridge_reserved_tiles); + if (HasBit(l.flags, TRLF_TB_EXIT_FREE)) b += seprintf(b, lastof(buffer), "x"); + if (HasBit(l.flags, TRLF_DEPOT_END)) b += seprintf(b, lastof(buffer), "d"); + if (HasBit(l.flags, TRLF_APPLY_ADVISORY)) b += seprintf(b, lastof(buffer), "a"); + if (HasBit(l.flags, TRLF_CHUNNEL)) b += seprintf(b, lastof(buffer), "c"); + print(buffer); + + seprintf(buffer, lastof(buffer), " Items: %u", (uint)l.items.size()); + print(buffer); + for (const TrainReservationLookAheadItem &item : l.items) { + b = buffer + seprintf(buffer, lastof(buffer), " Start: %d (dist: %d), end: %d (dist: %d), z: %d, ", + item.start, item.start - l.current_position, item.end, item.end - l.current_position, item.z_pos); + switch (item.type) { + case TRLIT_STATION: + b += seprintf(b, lastof(buffer), "station: %u, %s", item.data_id, BaseStation::IsValidID(item.data_id) ? BaseStation::Get(item.data_id)->GetCachedName() : "[invalid]"); + break; + case TRLIT_REVERSE: + b += seprintf(b, lastof(buffer), "reverse"); + break; + case TRLIT_TRACK_SPEED: + b += seprintf(b, lastof(buffer), "track speed: %u", item.data_id); + break; + case TRLIT_SPEED_RESTRICTION: + b += seprintf(b, lastof(buffer), "speed restriction: %u", item.data_id); + break; + case TRLIT_SIGNAL: + b += seprintf(b, lastof(buffer), "signal: target speed: %u", item.data_id); + break; + case TRLIT_CURVE_SPEED: + b += seprintf(b, lastof(buffer), "curve speed: %u", item.data_id); + break; + } + print(buffer); + } + + seprintf(buffer, lastof(buffer), " Curves: %u", (uint)l.curves.size()); + print(buffer); + for (const TrainReservationLookAheadCurve &curve : l.curves) { + seprintf(buffer, lastof(buffer), " Pos: %d (dist: %d), dir diff: %d", curve.position, curve.position - l.current_position, curve.dir_diff); + print(buffer); + } + } } if (v->type == VEH_ROAD) { const RoadVehicle *rv = RoadVehicle::From(v); diff --git a/src/table/settings.ini b/src/table/settings.ini index b67d5c6975..491dd79e3f 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -18,6 +18,7 @@ static bool InvalidateTownViewWindow(int32 p1); static bool DeleteSelectStationWindow(int32 p1); static bool UpdateConsists(int32 p1); static bool TrainAccelerationModelChanged(int32 p1); +static bool TrainBrakingModelChanged(int32 p1); static bool RoadVehAccelerationModelChanged(int32 p1); static bool TrainSlopeSteepnessChanged(int32 p1); static bool RoadVehSlopeSteepnessChanged(int32 p1); @@ -108,6 +109,12 @@ static const SettingDescEnumEntry _linkgraph_mode_per_cargo[] = { { 0, STR_NULL } }; +static const SettingDescEnumEntry _train_braking_model[] = { +{ TBM_ORIGINAL, STR_CONFIG_SETTING_ORIGINAL }, +{ TBM_REALISTIC, STR_CONFIG_SETTING_TRAIN_BRAKING_REALISTIC }, +{ 0, STR_NULL } +}; + /* Some settings do not need to be synchronised when playing in multiplayer. * These include for example the GUI settings and will not be saved with the * savegame. @@ -1218,6 +1225,18 @@ strhelp = STR_CONFIG_SETTING_TRAIN_ACCELERATION_MODEL_HELPTEXT strval = STR_CONFIG_SETTING_ORIGINAL proc = TrainAccelerationModelChanged +[SDT_ENUM] +base = GameSettings +var = vehicle.train_braking_model +type = SLE_UINT8 +def = TBM_ORIGINAL +enumlist = _train_braking_model +str = STR_CONFIG_SETTING_TRAIN_BRAKING_MODEL +strhelp = STR_CONFIG_SETTING_TRAIN_BRAKING_MODEL_HELPTEXT +proc = TrainBrakingModelChanged +cat = SC_EXPERT +patxname = ""realistic_braking.vehicle.train_braking_model"" + [SDT_VAR] base = GameSettings var = vehicle.roadveh_acceleration_model diff --git a/src/train.h b/src/train.h index 25549649b3..22f17a9c47 100644 --- a/src/train.h +++ b/src/train.h @@ -18,6 +18,7 @@ #include "engine_base.h" #include "rail_map.h" #include "ground_vehicle.hpp" +#include "pbs.h" struct Train; @@ -74,7 +75,7 @@ byte FreightWagonMult(CargoID cargo); void CheckTrainsLengths(); -void FreeTrainTrackReservation(const Train *v, TileIndex origin = INVALID_TILE, Trackdir orig_td = INVALID_TRACKDIR); +void FreeTrainTrackReservation(Train *v, TileIndex origin = INVALID_TILE, Trackdir orig_td = INVALID_TRACKDIR); bool TryPathReserve(Train *v, bool mark_as_stuck = false, bool first_tile_okay = false); void DeleteVisibleTrain(Train *v); @@ -87,14 +88,17 @@ struct TrainCache { /* Cached wagon override spritegroup */ const struct SpriteGroup *cached_override; - /* cached values, recalculated on load and each time a vehicle is added to/removed from the consist. */ - bool cached_tilt; ///< train can tilt; feature provides a bonus in curves - uint8 cached_num_engines; ///< total number of engines, including rear ends of multiheaded engines - - byte user_def_data; ///< Cached property 0x25. Can be set by Callback 0x36. - /* cached max. speed / acceleration data */ - int cached_max_curve_speed; ///< max consist speed limited by curves + int cached_max_curve_speed; ///< max consist speed limited by curves + + /* cached values, recalculated on load and each time a vehicle is added to/removed from the consist. */ + bool cached_tilt; ///< train can tilt; feature provides a bonus in curves + uint8 cached_num_engines; ///< total number of engines, including rear ends of multiheaded engines + uint16 cached_veh_weight; ///< Cached individual vehicle weight + uint16 cached_uncapped_decel; ///< Uncapped cached deceleration for realistic braking lookahead purposes + uint8 cached_deceleration; ///< Cached deceleration for realistic braking lookahead purposes + + byte user_def_data; ///< Cached property 0x25. Can be set by Callback 0x36. }; /** @@ -106,6 +110,8 @@ struct Train FINAL : public GroundVehicle { /* Link between the two ends of a multiheaded engine */ Train *other_multiheaded_part; + std::unique_ptr lookahead; + uint32 flags; uint16 crash_anim_pos; ///< Crash animation counter. @@ -159,6 +165,12 @@ struct Train FINAL : public GroundVehicle { void UpdateAcceleration(); + struct MaxSpeedInfo { + int strict_max_speed; + int advisory_max_speed; + }; + MaxSpeedInfo GetCurrentMaxSpeedInfo() const; + int GetCurrentMaxSpeed() const; /** diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 50694280d7..9df38ccc1b 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -41,6 +41,7 @@ #include "engine_func.h" #include "bridge_signal_map.h" #include "scope_info.h" +#include "scope.h" #include "core/checksum_func.hpp" #include "table/strings.h" @@ -48,11 +49,29 @@ #include "safeguards.h" -static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *p_got_reservation, bool mark_stuck); +enum { + REALISTIC_BRAKING_MIN_SPEED = 5, +}; + +enum ChooseTrainTrackLookAheadStateFlags { + CTTLASF_STOP_FOUND = 0, ///< Stopping destination found + CTTLASF_REVERSE_FOUND = 1, ///< Reverse destination found + CTTLASF_NO_RES_VEH_TILE = 2, ///< Do not reserve the vehicle tile +}; + +struct ChooseTrainTrackLookAheadState { + uint order_items_start = 0; ///< Order items start for VehicleOrderSaver + uint16 flags = 0; ///< Flags + DestinationID reverse_dest = 0; ///< Reverse station ID when CTTLASF_REVERSE_FOUND is set +}; + +static void TryLongReserveChooseTrainTrackFromReservationEnd(Train *v, bool no_reserve_vehicle_tile = false); +static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *p_got_reservation, bool mark_stuck, ChooseTrainTrackLookAheadState lookahead_state = {}); static bool TrainApproachingLineEnd(Train *v, bool signal, bool reverse); static bool TrainCheckIfLineEnds(Train *v, bool reverse = true); static bool TrainCanLeaveTile(const Train *v); static inline bool CheckCompatibleRail(const Train *v, TileIndex tile, DiagDirection enterdir); +int ReversingDistanceTargetSpeed(const Train *v); bool TrainController(Train *v, Vehicle *nomove, bool reverse = true); // Also used in vehicle_sl.cpp. static TileIndex TrainApproachingCrossingTile(const Train *v); static void CheckIfTrainNeedsService(Train *v); @@ -60,6 +79,7 @@ static void CheckNextTrainTile(Train *v); TileIndex VehiclePosTraceRestrictPreviousSignalCallback(const Train *v, const void *); static void TrainEnterStation(Train *v, StationID station); static void UnreserveBridgeTunnelTile(TileIndex tile); +static bool CheckTrainStayInWormHolePathReserve(Train *t, TileIndex tile); static const byte _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; static const byte _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; @@ -326,6 +346,8 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes) u->gcache.cached_air_drag = 0; u->gcache.cached_total_length = 0; u->tcache.cached_num_engines = 0; + u->tcache.cached_deceleration = 0; + u->tcache.cached_uncapped_decel = 0; u->tcache.cached_tilt = false; u->tcache.cached_max_curve_speed = 0; } @@ -570,17 +592,328 @@ int Train::GetCurveSpeedLimit() const return max_speed; } +void AdvanceOrderIndex(const Vehicle *v, VehicleOrderID &index) +{ + int depth = 0; + + do { + /* Wrap around. */ + if (index >= v->GetNumOrders()) index = 0; + + Order *order = v->GetOrder(index); + assert(order != nullptr); + + switch (order->GetType()) { + case OT_GOTO_DEPOT: + /* Skip service in depot orders when the train doesn't need service. */ + if ((order->GetDepotOrderType() & ODTFB_SERVICE) && !v->NeedsServicing()) break; + FALLTHROUGH; + case OT_GOTO_STATION: + case OT_GOTO_WAYPOINT: + return; + case OT_CONDITIONAL: { + VehicleOrderID next = ProcessConditionalOrder(order, v, true); + if (next != INVALID_VEH_ORDER_ID) { + depth++; + index = next; + /* Don't increment next, so no break here. */ + continue; + } + break; + } + default: + break; + } + /* Don't increment inside the while because otherwise conditional + * orders can lead to an infinite loop. */ + ++index; + depth++; + } while (depth < v->GetNumOrders()); +} + +int PredictStationStoppingLocation(const Train *v, const Order *order, int station_length, DestinationID dest) +{ + /* Default to the middle of the station for stations stops that are not in + * the order list like intermediate stations when non-stop is disabled */ + OrderStopLocation osl = OSL_PLATFORM_MIDDLE; + if (order->IsType(OT_GOTO_STATION) && order->GetDestination() == dest) { + osl = order->GetStopLocation(); + } else if (order->IsType(OT_LOADING_ADVANCE) && order->GetDestination() == dest) { + osl = OSL_PLATFORM_THROUGH; + } else if (order->IsType(OT_GOTO_WAYPOINT) && order->GetDestination() == dest) { + osl = OSL_PLATFORM_FAR_END; + } + + int overhang = v->gcache.cached_total_length - station_length; + int adjust = 0; + if (osl == OSL_PLATFORM_THROUGH && overhang > 0) { + for (const Train *u = v; u != nullptr; u = u->Next()) { + /* Passengers may not be through-loaded */ + if (u->cargo_cap > 0 && IsCargoInClass(u->cargo_type, CC_PASSENGERS)) { + osl = OSL_PLATFORM_FAR_END; + break; + } + } + } + if (osl == OSL_PLATFORM_THROUGH && overhang > 0) { + /* The train is longer than the station, and we can run through the station to load/unload */ + for (const Train *u = v; u != nullptr; u = u->Next()) { + if (overhang > 0 && !u->IsArticulatedPart()) { + bool skip = true; + for (const Train *part = u; part != nullptr; part = part->HasArticulatedPart() ? part->GetNextArticulatedPart() : nullptr) { + if (part->cargo_cap != 0) { + skip = false; + break; + } + } + if (skip) { + for (const Train *part = u; part != nullptr; part = part->HasArticulatedPart() ? part->GetNextArticulatedPart() : nullptr) { + overhang -= u->gcache.cached_veh_length; + adjust += u->gcache.cached_veh_length; + } + continue; + } + } + break; + } + if (overhang < 0) adjust += overhang; + } else if (overhang >= 0) { + /* The train is longer than the station, make it stop at the far end of the platform */ + osl = OSL_PLATFORM_FAR_END; + } + + int stop; + switch (osl) { + default: NOT_REACHED(); + + case OSL_PLATFORM_NEAR_END: + stop = v->gcache.cached_total_length; + break; + + case OSL_PLATFORM_MIDDLE: + stop = station_length - (station_length - v->gcache.cached_total_length) / 2; + break; + + case OSL_PLATFORM_FAR_END: + case OSL_PLATFORM_THROUGH: + stop = station_length; + break; + } + return stop + adjust; +} + +struct TrainDecelerationStats { + int deceleration_x2; + int uncapped_deceleration_x2; + int z_pos; + const Train *t; + + TrainDecelerationStats(const Train *t) + { + this->deceleration_x2 = 2 * t->tcache.cached_deceleration; + this->uncapped_deceleration_x2 = 2 * t->tcache.cached_uncapped_decel; + if (likely(HasBit(t->vcache.cached_veh_flags, VCF_GV_ZERO_SLOPE_RESIST))) { + this->z_pos = t->z_pos; + } else { + int64 sum = 0; + for (const Train *u = t; u != nullptr; u = u->Next()) { + sum += ((int)u->z_pos * (int)u->tcache.cached_veh_weight); + } + this->z_pos = sum / t->gcache.cached_weight; + } + this->t = t; + } +}; + +static int64 GetRealisticBrakingDistanceForSpeed(const TrainDecelerationStats &stats, int start_speed, int end_speed, int z_delta) +{ + /* v^2 = u^2 + 2as */ + + auto sqr = [](int64 speed) -> int64 { return speed * speed; }; + + int64 ke_delta = sqr(start_speed) - sqr(end_speed); + + int64 dist = ke_delta / stats.deceleration_x2; + + if (z_delta < 0 && _settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) { + /* descending */ + int64 slope_dist = (ke_delta - (z_delta * 400 * _settings_game.vehicle.train_slope_steepness)) / stats.uncapped_deceleration_x2; + dist = max(dist, slope_dist); + } + return dist; +} + +static int GetRealisticBrakingSpeedForDistance(const TrainDecelerationStats &stats, int distance, int end_speed, int z_delta) +{ + /* v^2 = u^2 + 2as */ + + auto sqr = [](int64 speed) -> int64 { return speed * speed; }; + + int64 target_ke = sqr(end_speed); + int64 speed_sqr = target_ke + ((int64)stats.deceleration_x2 * (int64)distance); + + if (speed_sqr <= REALISTIC_BRAKING_MIN_SPEED * REALISTIC_BRAKING_MIN_SPEED) return REALISTIC_BRAKING_MIN_SPEED; + + if (z_delta < 0 && _settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) { + /* descending */ + int64 sloped_ke = target_ke + (z_delta * 400 * _settings_game.vehicle.train_slope_steepness); + int64 slope_speed_sqr = sloped_ke + ((int64)stats.uncapped_deceleration_x2 * (int64)distance); + if (slope_speed_sqr < speed_sqr && + _settings_game.vehicle.train_acceleration_model == AM_REALISTIC && GetRailTypeInfo(stats.t->railtype)->acceleration_type != 2) { + /* calculate speed at which braking would be sufficient */ + + uint weight = stats.t->gcache.cached_weight; + int64 power_w = stats.t->gcache.cached_power * 746ll; + int64 min_braking_force = (stats.t->gcache.cached_total_length * 300) + stats.t->gcache.cached_axle_resistance + (weight * 16); + + /* F = (7/8) * (F_min + ((power_w * 18) / (5 * v))) + * v^2 = sloped_ke + F * s / m + * let k = sloped_ke + ((7 * F_min * s) / (8 * m)) + * v^3 - k * v - (7 * 18 * power_w * s) / (5 * 8 * m) = 0 + * v^3 + p * v + q = 0 + * where: p = -k + * q = -(7 * 18 * power_w * s) / (5 * 8 * m) + * + * v = cbrt(-q / 2 + sqrt((q^2 / 4) - (k^3 / 27))) + cbrt(-q / 2 - sqrt((q^2 / 4) - (k^3 / 27))) + * let r = - q / 2 = (7 * 9 * power_w * s) / (5 * 8 * m) + * let l = k / 3 + * v = cbrt(r + sqrt(r^2 - l^3)) + cbrt(r - sqrt(r^2 - l^3)) + */ + int64 l = (sloped_ke + ((7 * min_braking_force * (int64)distance) / (8 * weight))) / 3; + int64 r = (7 * 9 * power_w * (int64)distance) / (40 * weight); + int64 sqrt_factor = (r * r) - (l * l * l); + if (sqrt_factor >= 0) { + int64 part = IntSqrt64(sqrt_factor); + int32 v_calc = IntCbrt(r + part); + int cb2 = r - part; + if (cb2 > 0) { + v_calc += IntCbrt(cb2); + } else if (cb2 < 0) { + v_calc -= IntCbrt(-cb2); + } + int64 v_calc_sq = sqr(v_calc); + if (v_calc_sq < speed_sqr) { + return max((int)REALISTIC_BRAKING_MIN_SPEED, v_calc); + } + } + } + speed_sqr = min(speed_sqr, slope_speed_sqr); + } + if (speed_sqr <= REALISTIC_BRAKING_MIN_SPEED * REALISTIC_BRAKING_MIN_SPEED) return REALISTIC_BRAKING_MIN_SPEED; + if (speed_sqr > UINT_MAX) speed_sqr = UINT_MAX; + + return IntSqrt((uint) speed_sqr); +} + +static void LimitSpeedFromLookAhead(int &max_speed, const TrainDecelerationStats &stats, int current_position, int position, int end_speed, int z_delta) +{ + if (position <= current_position) { + max_speed = min(max_speed, max(15, end_speed)); + } else if (end_speed < max_speed) { + int64 distance = GetRealisticBrakingDistanceForSpeed(stats, max_speed, end_speed, z_delta); + if (distance + current_position > position) { + /* Speed is too fast, we would overshoot */ + if (z_delta < 0 && (position - current_position) < stats.t->gcache.cached_total_length) { + /* Reduce z delta near target to compensate for target z not taking into account that z varies across the whole train */ + z_delta = (z_delta * (position - current_position)) / stats.t->gcache.cached_total_length; + } + max_speed = min(max_speed, GetRealisticBrakingSpeedForDistance(stats, position - current_position, end_speed, z_delta)); + } + } +} + +static void ApplyLookAheadItem(const Train *v, const TrainReservationLookAheadItem &item, int &max_speed, int &advisory_max_speed, + VehicleOrderID ¤t_order_index, const TrainDecelerationStats &stats, int current_position) +{ + auto limit_speed = [&](int position, int end_speed, int z) { + LimitSpeedFromLookAhead(max_speed, stats, current_position, position, end_speed, z - stats.z_pos); + advisory_max_speed = min(advisory_max_speed, max_speed); + }; + auto limit_advisory_speed = [&](int position, int end_speed, int z) { + LimitSpeedFromLookAhead(advisory_max_speed, stats, current_position, position, end_speed, z - stats.z_pos); + }; + + switch (item.type) { + case TRLIT_STATION: { + if (current_order_index < v->GetNumOrders()) { + const Order *order = v->GetOrder(current_order_index); + if (order->ShouldStopAtStation(nullptr, item.data_id, Waypoint::GetIfValid(item.data_id) != nullptr)) { + limit_advisory_speed(item.start + PredictStationStoppingLocation(v, order, item.end - item.start, item.data_id), 0, item.z_pos); + } else if (order->IsType(OT_GOTO_WAYPOINT) && order->GetDestination() == item.data_id && (order->GetWaypointFlags() & OWF_REVERSE)) { + limit_advisory_speed(item.start + v->gcache.cached_total_length, 0, item.z_pos); + } + if (order->IsBaseStationOrder() && order->GetDestination() == item.data_id) { + current_order_index++; + AdvanceOrderIndex(v, current_order_index); + uint16 max_speed = v->GetOrder(current_order_index)->GetMaxSpeed(); + if (max_speed < UINT16_MAX) limit_advisory_speed(item.start, max_speed, item.z_pos); + } + } + break; + } + + case TRLIT_REVERSE: + limit_advisory_speed(item.start + v->gcache.cached_total_length, 0, item.z_pos); + break; + + case TRLIT_TRACK_SPEED: + limit_speed(item.start, item.data_id, item.z_pos); + break; + + case TRLIT_SPEED_RESTRICTION: + if (item.data_id > 0) limit_advisory_speed(item.start, item.data_id, item.z_pos); + break; + + case TRLIT_SIGNAL: + break; + + case TRLIT_CURVE_SPEED: + if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) limit_speed(item.start, item.data_id, item.z_pos); + break; + } +} + +static void AdvanceLookAheadPosition(Train *v) +{ + v->lookahead->current_position++; + + if (v->lookahead->current_position > v->lookahead->reservation_end_position + 8) { + /* Beyond end of lookahead, delete it, it will be recreated later with a new reservation */ + v->lookahead.reset(); + return; + } + + while (!v->lookahead->items.empty() && v->lookahead->items.front().end < v->lookahead->current_position) { + if (v->lookahead->items.front().type == TRLIT_STATION) { + int trim_position = v->lookahead->current_position - 4; + for (const Train *u = v; u != nullptr; u = u->Next()) { + if (HasBit(u->flags, VRF_BEYOND_PLATFORM_END)) { + trim_position -= u->gcache.cached_veh_length; + } else { + break; + } + } + if (v->lookahead->items.front().end >= trim_position) break; + } + v->lookahead->items.pop_front(); + } +} + /** - * Calculates the maximum speed of the vehicle under its current conditions. - * @return Maximum speed of the vehicle. + * Calculates the maximum speed information of the vehicle under its current conditions. + * @return Maximum speed information of the vehicle. */ -int Train::GetCurrentMaxSpeed() const +Train::MaxSpeedInfo Train::GetCurrentMaxSpeedInfo() const { int max_speed = _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL ? this->gcache.cached_max_track_speed : - this->tcache.cached_max_curve_speed; + min(this->tcache.cached_max_curve_speed, this->gcache.cached_max_track_speed); - if (_settings_game.vehicle.train_acceleration_model == AM_REALISTIC) { + if (this->current_order.IsType(OT_LOADING_ADVANCE)) max_speed = min(max_speed, 15); + + int advisory_max_speed = max_speed; + + if (_settings_game.vehicle.train_acceleration_model == AM_REALISTIC && this->lookahead == nullptr) { Train *v_platform = const_cast(this->GetStationLoadingVehicle()); TileIndex platform_tile = v_platform->tile; if (HasStationTileRail(platform_tile)) { @@ -598,12 +931,12 @@ int Train::GetCurrentMaxSpeed() const int st_max_speed = 120; int delta_v = this->cur_speed / (distance_to_go + 1); - if (max_speed > (this->cur_speed - delta_v)) { + if (advisory_max_speed > (this->cur_speed - delta_v)) { st_max_speed = this->cur_speed - (delta_v / 10); } st_max_speed = max(st_max_speed, 25 * distance_to_go); - max_speed = min(max_speed, st_max_speed); + advisory_max_speed = min(advisory_max_speed, st_max_speed); } } } @@ -628,17 +961,48 @@ int Train::GetCurrentMaxSpeed() const } } - max_speed = min(max_speed, this->current_order.GetMaxSpeed()); + advisory_max_speed = min(advisory_max_speed, this->current_order.GetMaxSpeed()); if (HasBit(this->flags, VRF_BREAKDOWN_SPEED)) { - max_speed = min(max_speed, this->GetBreakdownSpeed()); + advisory_max_speed = min(advisory_max_speed, this->GetBreakdownSpeed()); } if (this->speed_restriction != 0) { - max_speed = min(max_speed, this->speed_restriction); + advisory_max_speed = min(advisory_max_speed, this->speed_restriction); + } + if (this->reverse_distance > 1) { + advisory_max_speed = min(advisory_max_speed, ReversingDistanceTargetSpeed(this)); } - if (this->current_order.IsType(OT_LOADING_ADVANCE)) max_speed = min(max_speed, 15); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + if (this->lookahead != nullptr) { + TrainDecelerationStats stats(this); + if (HasBit(this->lookahead->flags, TRLF_DEPOT_END)) { + LimitSpeedFromLookAhead(max_speed, stats, this->lookahead->current_position, this->lookahead->reservation_end_position - TILE_SIZE, 61, this->lookahead->reservation_end_z - stats.z_pos); + } else { + LimitSpeedFromLookAhead(max_speed, stats, this->lookahead->current_position, this->lookahead->reservation_end_position, 0, this->lookahead->reservation_end_z - stats.z_pos); + } + VehicleOrderID current_order_index = this->cur_real_order_index; + for (const TrainReservationLookAheadItem &item : this->lookahead->items) { + ApplyLookAheadItem(this, item, max_speed, advisory_max_speed, current_order_index, stats, this->lookahead->current_position); + } + if (HasBit(this->lookahead->flags, TRLF_APPLY_ADVISORY)) { + max_speed = min(max_speed, advisory_max_speed); + } + } else { + advisory_max_speed = min(advisory_max_speed, 30); + } + } - return min(max_speed, this->gcache.cached_max_track_speed); + return { max_speed, advisory_max_speed }; +} + +/** + * Calculates the maximum speed of the vehicle under its current conditions. + * @return Maximum speed of the vehicle. + */ +int Train::GetCurrentMaxSpeed() const +{ + MaxSpeedInfo info = this->GetCurrentMaxSpeedInfo(); + return min(info.strict_max_speed, info.advisory_max_speed); } /** Update acceleration of the train from the cached power and weight. */ @@ -651,6 +1015,65 @@ void Train::UpdateAcceleration() assert(weight != 0); this->acceleration = Clamp(power / weight * 4, 1, 255); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + switch (_settings_game.vehicle.train_acceleration_model) { + default: NOT_REACHED(); + case AM_ORIGINAL: + this->tcache.cached_deceleration = Clamp((this->acceleration * 7) / 2, 1, 200); + break; + + case AM_REALISTIC: { + bool maglev = this->GetAccelerationType() == 2; + int64 power_w = power * 746ll; + int64 min_braking_force = this->gcache.cached_total_length * 300; + if (!maglev) { + /* From GroundVehicle::GetAcceleration() + * force = power * 18 / (speed * 5); + * resistance += (area * this->gcache.cached_air_drag * speed * speed) / 1000; + * + * let: + * F = force + resistance + * P = power + * v = speed + * d = area * this->gcache.cached_air_drag / 1000 + * + * F = (18 P / 5 v) + d v^2 + * Minimum occurs at d F / d v = 0 + * This is v^3 = 9 P / 5 d + * If d == 0 or v > max v, evaluate at max v + */ + int evaluation_speed = this->vcache.cached_max_speed; + int area = 14; + if (this->gcache.cached_air_drag > 0) { + uint64 v_3 = 1800 * (uint64)power_w / (area * this->gcache.cached_air_drag); + evaluation_speed = min(evaluation_speed, IntCbrt(v_3)); + } + if (evaluation_speed > 0) { + min_braking_force += power_w * 18 / (evaluation_speed * 5); + min_braking_force += (area * this->gcache.cached_air_drag * evaluation_speed * evaluation_speed) / 1000; + } + + min_braking_force += this->gcache.cached_axle_resistance; + int rolling_friction = 16; // 16 is the minimum value of v->GetRollingFriction() for a moving vehicle + min_braking_force += weight * rolling_friction; + } else { + /* From GroundVehicle::GetAcceleration() + * Braking force does not decrease with speed, + * therefore air drag can be omitted. + * There is no rolling/axle drag. */ + min_braking_force += power / 25; + } + min_braking_force -= (min_braking_force >> 3); // Slightly underestimate braking for defensive driving purposes + this->tcache.cached_uncapped_decel = Clamp(min_braking_force / weight, 1, UINT16_MAX); + this->tcache.cached_deceleration = Clamp(this->tcache.cached_uncapped_decel, 1, 120); + break; + } + } + } else { + this->tcache.cached_deceleration = 0; + this->tcache.cached_uncapped_decel = 0; + } + if (_settings_game.vehicle.improved_breakdowns) { if (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) { this->breakdown_chance_factor = max(128 * 3 / (this->tcache.cached_num_engines + 2), 5); @@ -2224,6 +2647,8 @@ void ReverseTrainDirection(Train *v) /* Clear path reservation in front if train is not stuck. */ if (!HasBit(v->flags, VRF_TRAIN_STUCK) && !no_near_end_unreserve && !no_far_end_unreserve) { FreeTrainTrackReservation(v); + } else { + v->lookahead.reset(); } if ((v->track & TRACK_BIT_WORMHOLE) && IsTunnelBridgeWithSignalSimulation(v->tile)) { @@ -2344,6 +2769,7 @@ void ReverseTrainDirection(Train *v) SetTunnelBridgeEntranceSignalState(v->tile, SIGNAL_STATE_RED); MarkTileDirtyByTile(v->tile, VMDF_NOT_MAP_MODE); update_check_tunnel_bridge_signal_counters(v); + ClrBit(v->flags, VRF_TRAIN_STUCK); return; } @@ -2605,6 +3031,10 @@ static void CheckNextTrainTile(Train *v) ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, tracks, false, nullptr, false); } } + } else if (v->lookahead != nullptr && v->lookahead->reservation_end_tile == ft.m_new_tile && IsTileType(ft.m_new_tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationEntrance(ft.m_new_tile) && + v->lookahead->reservation_end_trackdir == FindFirstTrackdir(ft.m_new_td_bits)) { + /* If the lookahead ends at the next tile which is a signalled tunnel/bridge entrance, try to make a reservation. */ + TryLongReserveChooseTrainTrackFromReservationEnd(v); } } @@ -2810,7 +3240,13 @@ static void HandleLastTunnelBridgeSignals(TileIndex tile, TileIndex end, DiagDir static void UnreserveBridgeTunnelTile(TileIndex tile) { UnreserveAcrossRailTunnelBridge(tile); - if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgePBS(tile)) SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgeEffectivelyPBS(tile)) { + if (IsTunnelBridgePBS(tile)) { + SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + } else { + UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, GetTileOwner(tile)); + } + } } /** @@ -2870,27 +3306,28 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_ * @param origin %Tile to start clearing (if #INVALID_TILE, use the current tile of \a v). * @param orig_td Track direction (if #INVALID_TRACKDIR, use the track direction of \a v). */ -void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_td) +void FreeTrainTrackReservation(Train *v, TileIndex origin, Trackdir orig_td) { assert(v->IsFrontEngine()); - if (origin == INVALID_TILE && v->track & TRACK_BIT_WORMHOLE && IsTunnelBridgeWithSignalSimulation(v->tile)) { + if (origin == INVALID_TILE) v->lookahead.reset(); + + bool free_origin_tunnel_bridge = false; + + if (origin == INVALID_TILE && (v->track & TRACK_BIT_WORMHOLE) && IsTunnelBridgeWithSignalSimulation(v->tile)) { + TileIndex other_end = GetOtherTunnelBridgeEnd(v->tile); Axis axis = DiagDirToAxis(GetTunnelBridgeDirection(v->tile)); DiagDirection axial_dir = DirToDiagDirAlongAxis(v->direction, axis); - TileIndex next_tile = TileVirtXY(v->x_pos, v->y_pos) + TileOffsByDiagDir(axial_dir); - if (next_tile == v->tile || next_tile == GetOtherTunnelBridgeEnd(v->tile)) { - origin = next_tile; - if (v->track == TRACK_BIT_WORMHOLE) { - /* Train in tunnel or on bridge, so just use his direction and make an educated guess - * given the track bits on the tunnel/bridge head tile. - * If a reachable track piece is reserved, use that, otherwise use the first reachable track piece. - */ - TrackBits tracks = GetAcrossTunnelBridgeReservationTrackBits(next_tile); - if (!tracks) tracks = GetAcrossTunnelBridgeTrackBits(next_tile); - orig_td = ReverseTrackdir(TrackExitdirToTrackdir(FindFirstTrack(tracks), GetTunnelBridgeDirection(next_tile))); - } else { - orig_td = TrackDirectionToTrackdir(FindFirstTrack(v->track & TRACK_BIT_MASK), v->direction); - } + TileIndex exit = v->tile; + TileIndex entrance = other_end; + if (axial_dir == GetTunnelBridgeDirection(v->tile)) std::swap(exit, entrance); + if (GetTrainClosestToTunnelBridgeEnd(exit, entrance) == v) { + origin = exit; + TrackBits tracks = GetAcrossTunnelBridgeTrackBits(origin); + orig_td = ReverseTrackdir(TrackExitdirToTrackdir(FindFirstTrack(tracks), GetTunnelBridgeDirection(origin))); + free_origin_tunnel_bridge = true; + } else { + return; } } @@ -2913,6 +3350,14 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_t /* Do not attempt to unreserve out of a signalled tunnel/bridge entrance, as this would unreserve the reservations of another train coming in */ if (IsTunnelBridgeWithSignalSimulation(tile) && TrackdirExitsTunnelBridge(tile, td) && IsTunnelBridgeSignalSimulationEntranceOnly(tile)) return; + if (free_origin_tunnel_bridge) { + if (!HasReservedTracks(tile, TrackToTrackBits(TrackdirToTrack(td)))) return; + UnreserveRailTrack(tile, TrackdirToTrack(td)); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && !IsTunnelBridgePBS(tile)) { + UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, GetTileOwner(tile)); + } + } + CFollowTrackRail ft(v, GetRailTypeInfo(v->railtype)->all_compatible_railtypes); while (ft.Follow(tile, td)) { tile = ft.m_new_tile; @@ -2922,6 +3367,8 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_t if (!IsValidTrackdir(td)) break; + bool update_signal = false; + if (IsTileType(tile, MP_RAILWAY)) { if (HasSignalOnTrackdir(tile, td) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(td)))) { /* Conventional signal along trackdir: remove reservation and stop. */ @@ -2934,7 +3381,11 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_t break; } else { /* Turn the signal back to red. */ - SetSignalStateByTrackdir(tile, td, SIGNAL_STATE_RED); + if (GetSignalType(tile, TrackdirToTrack(td)) == SIGTYPE_NORMAL) { + update_signal = true; + } else { + SetSignalStateByTrackdir(tile, td, SIGNAL_STATE_RED); + } MarkSingleSignalDirty(tile, td); } } else if (HasSignalOnTrackdir(tile, ReverseTrackdir(td)) && IsOnewaySignal(tile, TrackdirToTrack(td))) { @@ -2948,6 +3399,7 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_t /* Don't free first station/bridge/tunnel if we are on it. */ if (free_tile || (!(ft.m_is_station && GetStationIndex(ft.m_new_tile) == station_id) && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(v, tile, td); + if (update_signal) UpdateSignalsOnSegment(tile, TrackdirToExitdir(td), GetTileOwner(tile)); free_tile = true; } @@ -3110,7 +3562,7 @@ private: Order old_order; TileIndex old_dest_tile; StationID old_last_station_visited; - VehicleOrderID index; + VehicleOrderID old_index; bool suppress_implicit_orders; public: @@ -3119,16 +3571,17 @@ public: old_order(_v->current_order), old_dest_tile(_v->dest_tile), old_last_station_visited(_v->last_station_visited), - index(_v->cur_real_order_index), + old_index(_v->cur_real_order_index), suppress_implicit_orders(HasBit(_v->gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS)) { } ~VehicleOrderSaver() { - this->v->current_order = this->old_order; + this->v->current_order = std::move(this->old_order); this->v->dest_tile = this->old_dest_tile; this->v->last_station_visited = this->old_last_station_visited; + this->v->cur_real_order_index = this->old_index; SB(this->v->gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS, 1, suppress_implicit_orders ? 1: 0); } @@ -3141,15 +3594,15 @@ public: { if (this->v->GetNumOrders() == 0) return false; - if (skip_first) ++this->index; + if (skip_first) ++this->v->cur_real_order_index; int depth = 0; do { /* Wrap around. */ - if (this->index >= this->v->GetNumOrders()) this->index = 0; + if (this->v->cur_real_order_index >= this->v->GetNumOrders()) this->v->cur_real_order_index = 0; - Order *order = this->v->GetOrder(this->index); + Order *order = this->v->GetOrder(this->v->cur_real_order_index); assert(order != nullptr); switch (order->GetType()) { @@ -3165,7 +3618,7 @@ public: VehicleOrderID next = ProcessConditionalOrder(order, this->v, true); if (next != INVALID_VEH_ORDER_ID) { depth++; - this->index = next; + this->v->cur_real_order_index = next; /* Don't increment next, so no break here. */ continue; } @@ -3176,32 +3629,238 @@ public: } /* Don't increment inside the while because otherwise conditional * orders can lead to an infinite loop. */ - ++this->index; + ++this->v->cur_real_order_index; depth++; - } while (this->index != this->v->cur_real_order_index && depth < this->v->GetNumOrders()); + } while (this->v->cur_real_order_index != this->old_index && depth < this->v->GetNumOrders()); return false; } + + void AdvanceOrdersFromVehiclePosition(ChooseTrainTrackLookAheadState &state) + { + /* If the current tile is the destination of the current order and + * a reservation was requested, advance to the next order. + * Don't advance on a depot order as depots are always safe end points + * for a path and no look-ahead is necessary. This also avoids a + * problem with depot orders not part of the order list when the + * order list itself is empty. */ + Train *v = this->v; + if (v->current_order.IsType(OT_LEAVESTATION)) { + this->SwitchToNextOrder(false); + } else if (v->current_order.IsAnyLoadingType() || (!v->current_order.IsType(OT_GOTO_DEPOT) && ( + v->current_order.IsBaseStationOrder() ? + HasStationTileRail(v->tile) && v->current_order.GetDestination() == GetStationIndex(v->tile) : + v->tile == v->dest_tile))) { + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && v->current_order.IsBaseStationOrder()) { + if (v->current_order.ShouldStopAtStation(nullptr, v->current_order.GetDestination(), v->current_order.IsType(OT_GOTO_WAYPOINT))) { + SetBit(state.flags, CTTLASF_STOP_FOUND); + } + } + if (v->current_order.IsAnyLoadingType()) SetBit(state.flags, CTTLASF_STOP_FOUND); + this->SwitchToNextOrder(true); + } + } + + void AdvanceOrdersFromLookahead(ChooseTrainTrackLookAheadState &state) + { + TrainReservationLookAhead *lookahead = this->v->lookahead.get(); + if (lookahead == nullptr) return; + + for (size_t i = state.order_items_start; i < lookahead->items.size(); i++) { + const TrainReservationLookAheadItem &item = lookahead->items[i]; + switch (item.type) { + case TRLIT_STATION: + if (this->v->current_order.IsBaseStationOrder()) { + /* we've already seen this station in the lookahead, advance current order */ + if (this->v->current_order.ShouldStopAtStation(nullptr, item.data_id, Waypoint::GetIfValid(item.data_id) != nullptr)) { + SetBit(state.flags, CTTLASF_STOP_FOUND); + } else if (this->v->current_order.IsType(OT_GOTO_WAYPOINT) && this->v->current_order.GetDestination() == item.data_id && (this->v->current_order.GetWaypointFlags() & OWF_REVERSE)) { + if (!HasBit(state.flags, CTTLASF_REVERSE_FOUND)) { + SetBit(state.flags, CTTLASF_REVERSE_FOUND); + state.reverse_dest = item.data_id; + } + } + if (this->v->current_order.GetDestination() == item.data_id) { + this->SwitchToNextOrder(true); + } + } + break; + + default: + break; + } + } + state.order_items_start = (uint)lookahead->items.size(); + } }; -static bool HasLongReservePbsSignalOnTrackdir(Train* v, TileIndex tile, Trackdir trackdir) +static bool IsReservationLookAheadLongEnough(const Train *v, const ChooseTrainTrackLookAheadState &lookahead_state) +{ + if (_settings_game.vehicle.train_braking_model != TBM_REALISTIC || v->lookahead == nullptr) return true; + + if (v->current_order.IsAnyLoadingType()) return true; + + if (HasBit(lookahead_state.flags, CTTLASF_STOP_FOUND) || HasBit(lookahead_state.flags, TRLF_DEPOT_END)) return true; + + if (v->reverse_distance > 1) { + if (v->lookahead->reservation_end_position >= v->lookahead->current_position + v->reverse_distance - 1) return true; + } + + TrainDecelerationStats stats(v); + + bool found_signal = false; + int signal_speed = 0; + int signal_position = 0; + int signal_z = 0; + + for (const TrainReservationLookAheadItem &item : v->lookahead->items) { + if (item.type == TRLIT_REVERSE) { + if (v->lookahead->reservation_end_position >= item.start + v->gcache.cached_total_length) return true; + } + if (item.type == TRLIT_STATION && HasBit(lookahead_state.flags, CTTLASF_REVERSE_FOUND) && lookahead_state.reverse_dest == item.data_id) { + if (v->lookahead->reservation_end_position >= item.start + v->gcache.cached_total_length) return true; + } + + if (found_signal) { + if (item.type == TRLIT_TRACK_SPEED || item.type == TRLIT_SPEED_RESTRICTION || item.type == TRLIT_CURVE_SPEED) { + if (item.data_id > 0) LimitSpeedFromLookAhead(signal_speed, stats, signal_position, item.start, item.data_id, item.z_pos - stats.z_pos); + } + } else if (item.type == TRLIT_SIGNAL && item.start > v->lookahead->current_position + 24) { + signal_speed = min(item.data_id > 0 ? item.data_id : UINT16_MAX, v->vcache.cached_max_speed); + signal_position = item.start; + signal_z = item.z_pos; + found_signal = true; + } + } + + if (found_signal) { + int64 distance = GetRealisticBrakingDistanceForSpeed(stats, signal_speed, 0, v->lookahead->reservation_end_z - signal_z); + if (signal_position + distance <= v->lookahead->reservation_end_position) return true; + } + + return false; +} + +static bool LookaheadWithinCurrentTunnelBridge(const Train *t) +{ + return t->lookahead->current_position >= t->lookahead->reservation_end_position - ((int)TILE_SIZE * t->lookahead->tunnel_bridge_reserved_tiles) && !HasBit(t->lookahead->flags, TRLF_TB_EXIT_FREE); +} + +static bool HasLongReservePbsSignalOnTrackdir(Train* v, TileIndex tile, Trackdir trackdir, bool default_value) { if (HasPbsSignalOnTrackdir(tile, trackdir)) { if (IsRestrictedSignal(tile)) { const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(tile, TrackdirToTrack(trackdir)); if (prog && prog->actions_used_flags & TRPAUF_LONG_RESERVE) { TraceRestrictProgramResult out; + if (default_value) out.flags |= TRPRF_LONG_RESERVE; prog->Execute(v, TraceRestrictProgramInput(tile, trackdir, &VehiclePosTraceRestrictPreviousSignalCallback, nullptr), out); - if (out.flags & TRPRF_LONG_RESERVE) { - return true; - } + return (out.flags & TRPRF_LONG_RESERVE); } } + return default_value; } return false; } +static TileIndex CheckLongReservePbsTunnelBridgeOnTrackdir(Train* v, TileIndex tile, Trackdir trackdir) +{ + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && IsTileType(tile, MP_TUNNELBRIDGE) && + GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && IsTunnelBridgeSignalSimulationEntrance(tile) && TrackdirEntersTunnelBridge(tile, trackdir)) { + + TileIndex end = GetOtherTunnelBridgeEnd(tile); + int raw_free_tiles; + if (v->lookahead != nullptr && v->lookahead->reservation_end_tile == tile && v->lookahead->reservation_end_trackdir == trackdir) { // TODO fix loop case + if (HasBit(v->lookahead->flags, TRLF_TB_EXIT_FREE)) { + raw_free_tiles = INT_MAX; + } else { + raw_free_tiles = GetAvailableFreeTilesInSignalledTunnelBridgeWithStartOffset(tile, end, v->lookahead->tunnel_bridge_reserved_tiles + 1); + ApplyAvailableFreeTunnelBridgeTiles(v->lookahead.get(), raw_free_tiles, tile, end); + } + } else { + raw_free_tiles = GetAvailableFreeTilesInSignalledTunnelBridge(tile, end, tile); + } + if (!HasAcrossTunnelBridgeReservation(end) && raw_free_tiles == INT_MAX) { + return end; + } + } + return INVALID_TILE; +} + +bool _long_reserve_disabled = false; + +static void TryLongReserveChooseTrainTrack(Train *v, TileIndex tile, Trackdir td, bool force_res, ChooseTrainTrackLookAheadState lookahead_state) +{ + if (_long_reserve_disabled) return; + + const bool long_enough = IsReservationLookAheadLongEnough(v, lookahead_state); + + // We reserved up to a unoccupied signalled tunnel/bridge, reserve past it as well. recursion + TileIndex exit_tile = long_enough ? INVALID_TILE : CheckLongReservePbsTunnelBridgeOnTrackdir(v, tile, td); + if (exit_tile != INVALID_TILE) { + CFollowTrackRail ft(v); + DiagDirection exit_dir = ReverseDiagDir(GetTunnelBridgeDirection(exit_tile)); + Trackdir exit_td = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(exit_tile)), exit_dir); + if (ft.Follow(exit_tile, exit_td)) { + const TrackBits reserved_bits = GetReservedTrackbits(ft.m_new_tile); + if ((ft.m_new_td_bits & TrackBitsToTrackdirBits(reserved_bits)) == TRACKDIR_BIT_NONE) { + /* next tile is not reserved */ + + SignalState exit_state = GetTunnelBridgeExitSignalState(exit_tile); + + /* reserve exit to make contiguous reservation */ + if (IsBridge(exit_tile)) { + TryReserveRailBridgeHead(exit_tile, FindFirstTrack(GetAcrossTunnelBridgeTrackBits(exit_tile))); + } else { + SetTunnelReservation(exit_tile, true); + } + SetTunnelBridgeExitSignalState(exit_tile, SIGNAL_STATE_GREEN); + + ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, TrackdirBitsToTrackBits(ft.m_new_td_bits), force_res, nullptr, false, lookahead_state); + + if (reserved_bits == GetReservedTrackbits(ft.m_new_tile)) { + /* next tile is still not reserved, so unreserve exit and restore signal state */ + if (IsBridge(exit_tile)) { + UnreserveRailBridgeHeadTrack(exit_tile, FindFirstTrack(GetAcrossTunnelBridgeTrackBits(exit_tile))); + } else { + SetTunnelReservation(exit_tile, false); + } + SetTunnelBridgeExitSignalState(exit_tile, exit_state); + } else { + MarkTileDirtyByTile(exit_tile, VMDF_NOT_MAP_MODE); + } + } + } + return; + } + + CFollowTrackRail ft(v); + if (ft.Follow(tile, td) && HasLongReservePbsSignalOnTrackdir(v, ft.m_new_tile, FindFirstTrackdir(ft.m_new_td_bits), !long_enough)) { + // We reserved up to a LR signal, reserve past it as well. recursion + ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, TrackdirBitsToTrackBits(ft.m_new_td_bits), force_res, nullptr, false, lookahead_state); + } +} + +static void TryLongReserveChooseTrainTrackFromReservationEnd(Train *v, bool no_reserve_vehicle_tile) +{ + PBSTileInfo origin = FollowTrainReservation(v); + if (IsRailDepotTile(origin.tile)) return; + + ChooseTrainTrackLookAheadState lookahead_state; + if (no_reserve_vehicle_tile) SetBit(lookahead_state.flags, CTTLASF_NO_RES_VEH_TILE); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + VehicleOrderSaver orders(v); + orders.AdvanceOrdersFromVehiclePosition(lookahead_state); + orders.AdvanceOrdersFromLookahead(lookahead_state); + + /* Note that this must be called before the VehicleOrderSaver destructor, above */ + TryLongReserveChooseTrainTrack(v, origin.tile, origin.trackdir, true, lookahead_state); + } else { + TryLongReserveChooseTrainTrack(v, origin.tile, origin.trackdir, true, lookahead_state); + } +} + /** * Choose a track and reserve if necessary * @@ -3214,7 +3873,7 @@ static bool HasLongReservePbsSignalOnTrackdir(Train* v, TileIndex tile, Trackdir * @param mark_stuck The train has to be marked as stuck when needed * @return The track the train should take. */ -static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *p_got_reservation, bool mark_stuck) +static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *p_got_reservation, bool mark_stuck, ChooseTrainTrackLookAheadState lookahead_state) { Track best_track = INVALID_TRACK; bool do_track_reservation = _settings_game.pf.reserve_paths || force_res; @@ -3234,7 +3893,8 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, if (KillFirstBit(tracks) == TRACK_BIT_NONE) { Track track = FindFirstTrack(tracks); /* We need to check for signals only here, as a junction tile can't have signals. */ - if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir))) { + Trackdir td = TrackEnterdirToTrackdir(track, enterdir); + if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, td)) { if (IsRestrictedSignal(tile) && v->force_proceed != TFP_SIGNAL) { const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(tile, track); if (prog && prog->actions_used_flags & (TRPAUF_WAIT_AT_PBS | TRPAUF_SLOT_ACQUIRE | TRPAUF_TRAIN_NOT_STUCK)) { @@ -3274,18 +3934,24 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, return FindFirstTrack(tracks); } if (res_dest.okay) { - CFollowTrackRail ft(v); - if (ft.Follow(res_dest.tile, res_dest.trackdir)) { - Trackdir new_td = FindFirstTrackdir(ft.m_new_td_bits); - - if (!HasLongReservePbsSignalOnTrackdir(v, ft.m_new_tile, new_td)) { - /* Got a valid reservation that ends at a safe target, quick exit. */ - if (p_got_reservation != nullptr) *p_got_reservation = true; - if (changed_signal != INVALID_TRACKDIR) MarkSingleSignalDirty(tile, changed_signal); - TryReserveRailTrack(v->tile, TrackdirToTrack(v->GetVehicleTrackdir())); - return best_track; + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) FillTrainReservationLookAhead(v); + bool long_reserve = (CheckLongReservePbsTunnelBridgeOnTrackdir(v, res_dest.tile, res_dest.trackdir) != INVALID_TILE); + if (!long_reserve) { + CFollowTrackRail ft(v); + if (ft.Follow(res_dest.tile, res_dest.trackdir)) { + Trackdir new_td = FindFirstTrackdir(ft.m_new_td_bits); + long_reserve = HasLongReservePbsSignalOnTrackdir(v, ft.m_new_tile, new_td, _settings_game.vehicle.train_braking_model == TBM_REALISTIC); } } + + if (!long_reserve) { + /* Got a valid reservation that ends at a safe target, quick exit. */ + if (p_got_reservation != nullptr) *p_got_reservation = true; + if (changed_signal != INVALID_TRACKDIR) MarkSingleSignalDirty(tile, changed_signal); + if (!HasBit(lookahead_state.flags, CTTLASF_NO_RES_VEH_TILE)) TryReserveRailTrack(v->tile, TrackdirToTrack(v->GetVehicleTrackdir())); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) FillTrainReservationLookAhead(v); + return best_track; + } } /* Check if the train needs service here, so it has a chance to always find a depot. @@ -3298,20 +3964,10 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, /* Save the current train order. The destructor will restore the old order on function exit. */ VehicleOrderSaver orders(v); - /* If the current tile is the destination of the current order and - * a reservation was requested, advance to the next order. - * Don't advance on a depot order as depots are always safe end points - * for a path and no look-ahead is necessary. This also avoids a - * problem with depot orders not part of the order list when the - * order list itself is empty. */ - if (v->current_order.IsType(OT_LEAVESTATION)) { - orders.SwitchToNextOrder(false); - } else if (v->current_order.IsAnyLoadingType() || (!v->current_order.IsType(OT_GOTO_DEPOT) && ( - v->current_order.IsType(OT_GOTO_STATION) ? - IsRailStationTile(v->tile) && v->current_order.GetDestination() == GetStationIndex(v->tile) : - v->tile == v->dest_tile))) { - orders.SwitchToNextOrder(true); + if (lookahead_state.order_items_start == 0) { + orders.AdvanceOrdersFromVehiclePosition(lookahead_state); } + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) orders.AdvanceOrdersFromLookahead(lookahead_state); if (res_dest.tile != INVALID_TILE && !res_dest.okay) { /* Pathfinders are able to tell that route was only 'guessed'. */ @@ -3342,9 +3998,10 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, if (TryReserveSafeTrack(v, path_end.tile, path_end.trackdir, false)) { TrackBits res = GetReservedTrackbits(tile) & DiagdirReachesTracks(enterdir); best_track = FindFirstTrack(res); - TryReserveRailTrack(v->tile, TrackdirToTrack(v->GetVehicleTrackdir())); + if (!HasBit(lookahead_state.flags, CTTLASF_NO_RES_VEH_TILE)) TryReserveRailTrack(v->tile, TrackdirToTrack(v->GetVehicleTrackdir())); if (p_got_reservation != nullptr) *p_got_reservation = true; if (changed_signal != INVALID_TRACKDIR) MarkSingleSignalDirty(tile, changed_signal); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) FillTrainReservationLookAhead(v); } else { FreeTrainTrackReservation(v, origin.tile, origin.trackdir); if (mark_stuck) MarkTrainAsStuck(v); @@ -3354,6 +4011,22 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, got_reservation = true; + auto check_destination_seen = [&](TileIndex tile) { + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && v->current_order.IsBaseStationOrder() && + HasStationTileRail(tile)) { + if (v->current_order.ShouldStopAtStation(nullptr, GetStationIndex(tile), IsRailWaypoint(tile))) { + SetBit(lookahead_state.flags, CTTLASF_STOP_FOUND); + } else if (v->current_order.IsType(OT_GOTO_WAYPOINT) && v->current_order.GetDestination() == GetStationIndex(tile) && (v->current_order.GetWaypointFlags() & OWF_REVERSE)) { + if (!HasBit(lookahead_state.flags, CTTLASF_REVERSE_FOUND)) { + SetBit(lookahead_state.flags, CTTLASF_REVERSE_FOUND); + lookahead_state.reverse_dest = GetStationIndex(tile); + } + } + } + }; + + check_destination_seen(res_dest.tile); + /* Reservation target found and free, check if it is safe. */ while (!IsSafeWaitingPosition(v, res_dest.tile, res_dest.trackdir, true, _settings_game.pf.forbid_90_deg)) { /* Extend reservation until we have found a safe position. */ @@ -3371,7 +4044,10 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, DoTrainPathfind(v, next_tile, exitdir, reachable, path_found, true, &cur_dest); if (cur_dest.tile != INVALID_TILE) { res_dest = cur_dest; - if (res_dest.okay) continue; + if (res_dest.okay) { + check_destination_seen(res_dest.tile); + continue; + } /* Path found, but could not be reserved. */ FreeTrainTrackReservation(v, origin.tile, origin.trackdir); if (mark_stuck) MarkTrainAsStuck(v); @@ -3391,18 +4067,17 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, } if (got_reservation) { - CFollowTrackRail ft(v); - if (ft.Follow(res_dest.tile, res_dest.trackdir)) { - Trackdir new_td = FindFirstTrackdir(ft.m_new_td_bits); - - if (HasLongReservePbsSignalOnTrackdir(v, ft.m_new_tile, new_td)) { - // We reserved up to a LR signal, reserve past it as well. recursion - ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, TrackdirBitsToTrackBits(ft.m_new_td_bits), force_res, nullptr, mark_stuck); - } + if (v->current_order.IsBaseStationOrder() && HasStationTileRail(res_dest.tile) && v->current_order.GetDestination() == GetStationIndex(res_dest.tile)) { + orders.SwitchToNextOrder(true); } + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + FillTrainReservationLookAhead(v); + if (v->lookahead != nullptr) lookahead_state.order_items_start = (uint)v->lookahead->items.size(); + } + TryLongReserveChooseTrainTrack(v, res_dest.tile, res_dest.trackdir, force_res, lookahead_state); } - TryReserveRailTrack(v->tile, TrackdirToTrack(v->GetVehicleTrackdir())); + if (!HasBit(lookahead_state.flags, CTTLASF_NO_RES_VEH_TILE)) TryReserveRailTrack(v->tile, TrackdirToTrack(v->GetVehicleTrackdir())); if (changed_signal != INVALID_TRACKDIR) MarkSingleSignalDirty(tile, changed_signal); if (p_got_reservation != nullptr) *p_got_reservation = got_reservation; @@ -3422,6 +4097,8 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) { assert(v->IsFrontEngine()); + if (v->lookahead != nullptr && HasBit(v->lookahead->flags, TRLF_DEPOT_END)) return true; + /* We have to handle depots specially as the track follower won't look * at the depot tile itself but starts from the next tile. If we are still * inside the depot, a depot reservation can never be ours. */ @@ -3438,9 +4115,36 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) if (IsTileType(v->tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExitOnly(v->tile) && TrackdirEntersTunnelBridge(v->tile, v->GetVehicleTrackdir())) { - // prevent any attempt to reserve the wrong way onto a tunnel/bridge exit + /* prevent any attempt to reserve the wrong way onto a tunnel/bridge exit */ return false; } + if (IsTunnelBridgeWithSignalSimulation(v->tile) && ((v->track & TRACK_BIT_WORMHOLE) || TrackdirEntersTunnelBridge(v->tile, v->GetVehicleTrackdir()))) { + DiagDirection tunnel_bridge_dir = GetTunnelBridgeDirection(v->tile); + Axis axis = DiagDirToAxis(tunnel_bridge_dir); + DiagDirection axial_dir = DirToDiagDirAlongAxis(v->direction, axis); + if (axial_dir == tunnel_bridge_dir) { + /* prevent use of the entrance tile for reservations when the train is already in the wormhole */ + + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + /* Initialise a lookahead if there isn't one already */ + if (v->lookahead == nullptr) TryCreateLookAheadForTrainInTunnelBridge(v); + if (v->lookahead != nullptr && !LookaheadWithinCurrentTunnelBridge(v)) { + /* Try to extend the reservation beyond the tunnel/bridge exit */ + TryLongReserveChooseTrainTrackFromReservationEnd(v, true); + } + } else { + TileIndex exit = GetOtherTunnelBridgeEnd(v->tile); + TileIndex v_pos = TileVirtXY(v->x_pos, v->y_pos); + if (v_pos != exit) { + v_pos += TileOffsByDiagDir(tunnel_bridge_dir); + } + if (v_pos == exit) { + return CheckTrainStayInWormHolePathReserve(v, exit); + } + } + return false; + } + } Vehicle *other_train = nullptr; PBSTileInfo origin = FollowTrainReservation(v, &other_train); @@ -3458,6 +4162,10 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) /* Can't be stuck then. */ if (HasBit(v->flags, VRF_TRAIN_STUCK)) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); ClrBit(v->flags, VRF_TRAIN_STUCK); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + FillTrainReservationLookAhead(v); + TryLongReserveChooseTrainTrackFromReservationEnd(v, true); + } return true; } @@ -3479,7 +4187,11 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) if (Rail90DegTurnDisallowedTilesFromDiagDir(origin.tile, new_tile, exitdir)) reachable &= ~TrackCrossesTracks(TrackdirToTrack(origin.trackdir)); bool res_made = false; - ChooseTrainTrack(v, new_tile, exitdir, reachable, true, &res_made, mark_as_stuck); + if (reachable != TRACK_BIT_NONE) { + ChooseTrainTrack(v, new_tile, exitdir, reachable, true, &res_made, mark_as_stuck); + } else if (mark_as_stuck) { + MarkTrainAsStuck(v, true); + } if (!res_made) { /* Free the depot reservation as well. */ @@ -3492,6 +4204,7 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } ClrBit(v->flags, VRF_TRAIN_STUCK); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) FillTrainReservationLookAhead(v); return true; } @@ -3556,13 +4269,18 @@ void Train::MarkDirty() */ int Train::UpdateSpeed() { + AccelStatus accel_status = this->GetAccelerationStatus(); + MaxSpeedInfo max_speed_info = this->GetCurrentMaxSpeedInfo(); + if (this->lookahead != nullptr && HasBit(this->lookahead->flags, TRLF_APPLY_ADVISORY) && this->cur_speed <= max_speed_info.strict_max_speed) { + ClrBit(this->lookahead->flags, TRLF_APPLY_ADVISORY); + } switch (_settings_game.vehicle.train_acceleration_model) { default: NOT_REACHED(); case AM_ORIGINAL: - return this->DoUpdateSpeed(this->acceleration * (this->GetAccelerationStatus() == AS_BRAKE ? -4 : 2), 0, this->GetCurrentMaxSpeed()); + return this->DoUpdateSpeed({ this->acceleration * 2, this->acceleration * -4 }, 0, max_speed_info.strict_max_speed, max_speed_info.advisory_max_speed); case AM_REALISTIC: - return this->DoUpdateSpeed(this->GetAcceleration(), this->GetAccelerationStatus() == AS_BRAKE ? 0 : 2, this->GetCurrentMaxSpeed()); + return this->DoUpdateSpeed(this->GetAcceleration(), accel_status == AS_BRAKE ? 0 : 2, max_speed_info.strict_max_speed, max_speed_info.advisory_max_speed); } } /** @@ -3680,7 +4398,7 @@ enum TrainMovedChangeSignalEnum { CHANGED_LR_PBS ///< A long reserve PBS signal }; -static TrainMovedChangeSignalEnum TrainMovedChangeSignal(Train* v, TileIndex tile, DiagDirection dir) +static TrainMovedChangeSignalEnum TrainMovedChangeSignal(Train* v, TileIndex tile, DiagDirection dir, bool front) { if (IsTileType(tile, MP_RAILWAY) && GetRailTileType(tile) == RAIL_TILE_SIGNALS) { @@ -3690,7 +4408,7 @@ static TrainMovedChangeSignalEnum TrainMovedChangeSignal(Train* v, TileIndex til /* A PBS block with a non-PBS signal facing us? */ if (!IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) return CHANGED_NORMAL_TO_PBS_BLOCK; - if (HasLongReservePbsSignalOnTrackdir(v, tile, trackdir)) return CHANGED_LR_PBS; + if (front && HasLongReservePbsSignalOnTrackdir(v, tile, trackdir, _settings_game.vehicle.train_braking_model == TBM_REALISTIC)) return CHANGED_LR_PBS; } } if (IsTileType(tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(tile) && GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) { @@ -3698,6 +4416,11 @@ static TrainMovedChangeSignalEnum TrainMovedChangeSignal(Train* v, TileIndex til return CHANGED_NORMAL_TO_PBS_BLOCK; } } + if (front && _settings_game.vehicle.train_braking_model == TBM_REALISTIC && IsTileType(tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationEntrance(tile)) { + TrackdirBits tracks = TrackBitsToTrackdirBits(GetTunnelBridgeTrackBits(tile)) & DiagdirReachesTrackdirs(dir); + Trackdir trackdir = FindFirstTrackdir(tracks); + if (CheckLongReservePbsTunnelBridgeOnTrackdir(v, tile, trackdir) != INVALID_TILE) return CHANGED_LR_PBS; + } return CHANGED_NOTHING; } @@ -3920,12 +4643,42 @@ static Vehicle *FindSpaceBetweenTrainsEnum(Vehicle *v, void *data) return nullptr; } -static bool IsTooCloseBehindTrain(Vehicle *v, TileIndex tile, uint16 distance, bool check_endtile) +static bool IsTooCloseBehindTrain(Train *t, TileIndex tile, uint16 distance, bool check_endtile) { - Train *t = Train::From(v); - if (t->force_proceed != 0) return false; + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + if (unlikely(t->lookahead == nullptr)) { + TryCreateLookAheadForTrainInTunnelBridge(t); + } + if (likely(t->lookahead != nullptr)) { + if (LookaheadWithinCurrentTunnelBridge(t)) { + /* lookahead is within tunnel/bridge */ + TileIndex end = GetOtherTunnelBridgeEnd(t->tile); + const int raw_free_tiles = GetAvailableFreeTilesInSignalledTunnelBridge(t->tile, end, tile); + ApplyAvailableFreeTunnelBridgeTiles(t->lookahead.get(), raw_free_tiles + ((raw_free_tiles != INT_MAX) ? DistanceManhattan(t->tile, tile) : 0), t->tile, end); + + if (!LookaheadWithinCurrentTunnelBridge(t)) { + /* Try to extend the reservation beyond the tunnel/bridge exit */ + TryLongReserveChooseTrainTrackFromReservationEnd(t, true); + } + + if (raw_free_tiles <= (int)(distance / TILE_SIZE)) { + /* Revert train if not going with tunnel direction. */ + DiagDirection tb_dir = GetTunnelBridgeDirection(t->tile); + if (DirToDiagDirAlongAxis(t->direction, DiagDirToAxis(tb_dir)) != tb_dir) { + SetBit(t->flags, VRF_REVERSING); + } + return true; + } + return false; + } else { + /* Try to extend the reservation beyond the tunnel/bridge exit */ + TryLongReserveChooseTrainTrackFromReservationEnd(t, true); + } + } + } + FindSpaceBetweenTrainsChecker checker; checker.distance = distance; checker.direction = DirToDiagDirAlongAxis(t->direction, DiagDirToAxis(GetTunnelBridgeDirection(t->tile))); @@ -3960,14 +4713,14 @@ static bool IsTooCloseBehindTrain(Vehicle *v, TileIndex tile, uint16 distance, b static bool CheckTrainStayInWormHolePathReserve(Train *t, TileIndex tile) { + bool mark_dirty = false; + auto guard = scope_guard([&]() { + if (mark_dirty) MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + }); + Trackdir td = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(tile)), ReverseDiagDir(GetTunnelBridgeDirection(tile))); - TileIndex veh_orig_tile = t->tile; - TrackBits veh_orig_track = t->track; - Direction veh_orig_direction = t->direction; - t->tile = tile; - t->track = TRACK_BIT_WORMHOLE; - t->direction = TrackdirToDirection(td); CFollowTrackRail ft(GetTileOwner(tile), GetRailTypeInfo(t->railtype)->all_compatible_railtypes); + if (ft.Follow(tile, td)) { TrackdirBits reserved = ft.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedTrackbits(ft.m_new_tile)); if (reserved == TRACKDIR_BIT_NONE) { @@ -3977,14 +4730,72 @@ static bool CheckTrainStayInWormHolePathReserve(Train *t, TileIndex tile) } else { SetTunnelReservation(tile, true); } - MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + mark_dirty = true; } } + + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + if (unlikely(t->lookahead == nullptr)) { + TryCreateLookAheadForTrainInTunnelBridge(t); + } + if (likely(t->lookahead != nullptr)) { + if (!HasAcrossTunnelBridgeReservation(tile)) return false; + if (t->lookahead->reservation_end_tile == t->tile && t->lookahead->reservation_end_position - t->lookahead->current_position <= (int)TILE_SIZE && !HasBit(t->lookahead->flags, TRLF_TB_EXIT_FREE)) return false; + SignalState exit_state = GetTunnelBridgeExitSignalState(tile); + SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_GREEN); + TileIndex veh_orig_tile = t->tile; + TrackBits veh_orig_track = t->track; + Direction veh_orig_direction = t->direction; + t->tile = tile; + t->track = TRACK_BIT_WORMHOLE; + t->direction = TrackdirToDirection(td); + bool ok = TryPathReserve(t); + if (!ok && (t->lookahead->reservation_end_position >= t->lookahead->current_position && t->lookahead->reservation_end_position > t->lookahead->current_position + GetTileMarginInFrontOfTrain(t))) { + /* Reservation was made previously and was valid then. + * To avoid unexpected braking due to stopping short of the lookahead end, + * just carry on even if the end is not a safe waiting point now. */ + ok = true; + } + if (ok) { + mark_dirty = true; + if (t->lookahead->reservation_end_tile == veh_orig_tile && t->lookahead->reservation_end_position - t->lookahead->current_position <= (int)TILE_SIZE) { + /* Less than a tile of lookahead, advance tile */ + t->lookahead->reservation_end_tile = tile; + t->lookahead->reservation_end_trackdir = td; + ClrBit(t->lookahead->flags, TRLF_TB_EXIT_FREE); + ClrBit(t->lookahead->flags, TRLF_CHUNNEL); + t->lookahead->reservation_end_position += (DistanceManhattan(veh_orig_tile, tile) - 1 - t->lookahead->tunnel_bridge_reserved_tiles) * (int)TILE_SIZE; + t->lookahead->reservation_end_position += IsDiagonalTrackdir(td) ? 16 : 8; + t->lookahead->tunnel_bridge_reserved_tiles = 0; + FillTrainReservationLookAhead(t); + } + /* Try to extend the reservation */ + TryLongReserveChooseTrainTrackFromReservationEnd(t); + } else { + SetTunnelBridgeExitSignalState(tile, exit_state); + } + t->tile = veh_orig_tile; + t->track = veh_orig_track; + t->direction = veh_orig_direction; + return ok; + } + } + + + TileIndex veh_orig_tile = t->tile; + TrackBits veh_orig_track = t->track; + Direction veh_orig_direction = t->direction; + t->tile = tile; + t->track = TRACK_BIT_WORMHOLE; + t->direction = TrackdirToDirection(td); bool ok = TryPathReserve(t); t->tile = veh_orig_tile; t->track = veh_orig_track; t->direction = veh_orig_direction; - if (ok && IsTunnelBridgePBS(tile)) SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_GREEN); + if (ok && IsTunnelBridgeEffectivelyPBS(tile)) { + SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_GREEN); + mark_dirty = true; + } return ok; } @@ -3998,7 +4809,7 @@ static bool CheckTrainStayInWormHole(Train *t, TileIndex tile) SetBit(t->flags, VRF_REVERSING); return true; } - SigSegState seg_state = (_settings_game.pf.reserve_paths || IsTunnelBridgePBS(tile)) ? SIGSEG_PBS : UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, t->owner); + SigSegState seg_state = (_settings_game.pf.reserve_paths || IsTunnelBridgeEffectivelyPBS(tile)) ? SIGSEG_PBS : UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, t->owner); if (seg_state != SIGSEG_PBS) { CFollowTrackRail ft(GetTileOwner(tile), GetRailTypeInfo(t->railtype)->all_compatible_railtypes); if (ft.Follow(tile, TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(tile)), ReverseDiagDir(GetTunnelBridgeDirection(tile))))) { @@ -4044,8 +4855,23 @@ static void HandleSignalBehindTrain(Train *v, int signal_number) } } -uint16 ReversingDistanceTargetSpeed(const Train *v) +inline void DecreaseReverseDistance(Train *v) { + if (v->reverse_distance > 1) { + v->reverse_distance--; + if (unlikely(v->reverse_distance == 1 && v->cur_speed > 15 && _settings_game.vehicle.train_acceleration_model == AM_REALISTIC)) { + /* Train is still moving too fast, extend the reversing point */ + v->reverse_distance++; + } + } +} + +int ReversingDistanceTargetSpeed(const Train *v) +{ + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + TrainDecelerationStats stats(v); + return GetRealisticBrakingSpeedForDistance(stats, v->reverse_distance - 1, 0, 0); + } int target_speed; if (_settings_game.vehicle.train_acceleration_model == AM_REALISTIC) { target_speed = ((v->reverse_distance - 1) * 5) / 2; @@ -4086,11 +4912,6 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) goto reverse_train_direction; } - if (v->reverse_distance > 1) { - uint16 spd = ReversingDistanceTargetSpeed(v); - if (spd < v->cur_speed) v->cur_speed = spd; - } - /* For every vehicle after and including the given vehicle */ for (prev = v->Previous(); v != nomove; prev = v, v = v->Next()) { old_direction = v->direction; @@ -4498,9 +5319,8 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) v->y_pos = gp.y; v->UpdatePosition(); v->UpdateDeltaXY(); - if (v->reverse_distance > 1) { - v->reverse_distance--; - } + DecreaseReverseDistance(v); + if (v->lookahead != nullptr) AdvanceLookAheadPosition(v); if (HasBit(v->gv_flags, GVF_CHUNNEL_BIT)) { /* update the Z position of the vehicle */ int old_z = v->UpdateInclination(false, false, true); @@ -4525,9 +5345,8 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) v->x_pos = gp.x; v->y_pos = gp.y; v->UpdatePosition(); - if (v->reverse_distance > 1) { - v->reverse_distance--; - } + DecreaseReverseDistance(v); + if (v->lookahead != nullptr) AdvanceLookAheadPosition(v); if (HasBit(v->flags, VRF_PENDING_SPEED_RESTRICTION)) { auto range = pending_speed_restriction_change_map.equal_range(v->index); if (range.first == range.second) ClrBit(v->flags, VRF_PENDING_SPEED_RESTRICTION); @@ -4560,7 +5379,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) if (update_signals_crossing) { if (v->IsFrontEngine()) { - switch (TrainMovedChangeSignal(v, gp.new_tile, enterdir)) { + switch (TrainMovedChangeSignal(v, gp.new_tile, enterdir, true)) { case CHANGED_NORMAL_TO_PBS_BLOCK: /* We are entering a block with PBS signals right now, but * not through a PBS signal. This means we don't have a @@ -4582,17 +5401,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) { /* We went past a long reserve PBS signal. Try to extend the * reservation if reserving failed at another LR signal. */ - PBSTileInfo origin = FollowTrainReservation(v); - CFollowTrackRail ft(v); - - if (ft.Follow(origin.tile, origin.trackdir)) { - Trackdir new_td = FindFirstTrackdir(ft.m_new_td_bits); - - if (HasLongReservePbsSignalOnTrackdir(v, ft.m_new_tile, new_td)) { - ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, TrackdirBitsToTrackBits(ft.m_new_td_bits), true, nullptr, false); - } - } - + TryLongReserveChooseTrainTrackFromReservationEnd(v); break; } @@ -4604,7 +5413,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) /* Signals can only change when the first * (above) or the last vehicle moves. */ if (v->Next() == nullptr) { - TrainMovedChangeSignal(v, gp.old_tile, ReverseDiagDir(enterdir)); + TrainMovedChangeSignal(v, gp.old_tile, ReverseDiagDir(enterdir), false); if (IsLevelCrossingTile(gp.old_tile)) UpdateLevelCrossing(gp.old_tile); if (IsTileType(gp.old_tile, MP_RAILWAY) && HasSignals(gp.old_tile) && IsRestrictedSignal(gp.old_tile)) { @@ -4625,7 +5434,10 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) } /* Do not check on every tick to save some computing time. */ - if (v->IsFrontEngine() && v->tick_counter % _settings_game.pf.path_backoff_interval == 0) CheckNextTrainTile(v); + if (v->IsFrontEngine() && ((v->tick_counter % _settings_game.pf.path_backoff_interval == 0) || + (v->lookahead != nullptr && v->cur_speed > 0 && v->lookahead->reservation_end_position <= v->lookahead->current_position + 12))) { + CheckNextTrainTile(v); + } } if (direction_changed) first->tcache.cached_max_curve_speed = first->GetCurveSpeedLimit(); @@ -5866,3 +6678,14 @@ void TrainRoadVehicleCrashBreakdown(Vehicle *v) t->breakdown_severity = 0; t->reliability = 0; } + +void TrainBrakesOverheatedBreakdown(Vehicle *v) +{ + Train *t = Train::From(v)->First(); + if (t->breakdown_ctr != 0) return; + t->breakdown_ctr = 2; + SetBit(t->flags, VRF_CONSIST_BREAKDOWN); + t->breakdown_delay = 255; + t->breakdown_type = BREAKDOWN_BRAKE_OVERHEAT; + t->breakdown_severity = 0; +} diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index d53bcf5fc1..e22702d798 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -402,6 +402,7 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u Owner owner; bool is_new_owner; bool is_upgrade = false; + std::vector vehicles_affected; if (IsBridgeTile(tile_start) && IsBridgeTile(tile_end) && GetOtherBridgeEnd(tile_start) == tile_end && GetTunnelBridgeTransportType(tile_start) == transport_type) { @@ -445,6 +446,26 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER); } + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && GetBridgeSpec(bridge_type)->speed < GetBridgeSpec(GetBridgeType(tile_start))->speed) { + CommandCost ret = CheckTrainInTunnelBridgePreventsTrackModification(tile_start, tile_end); + if (ret.Failed()) return ret; + for (TileIndex t : { tile_start, tile_end }) { + TrackBits reserved = GetBridgeReservationTrackBits(t); + Track track; + while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) { + Train *v = GetTrainForReservation(t, track); + if (v != nullptr) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(v); + if (ret.Failed()) return ret; + if (flags & DC_EXEC) { + FreeTrainTrackReservation(v); + vehicles_affected.push_back(v); + } + } + } + } + } + cost.AddCost((bridge_len + 1) * _price[PR_CLEAR_BRIDGE]); // The cost of clearing the current bridge. owner = GetTileOwner(tile_start); @@ -716,6 +737,9 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u Track track = AxisToTrack(direction); AddSideToSignalBuffer(tile_start, INVALID_DIAGDIR, company); YapfNotifyTrackLayoutChange(tile_start, track); + for (uint i = 0; i < vehicles_affected.size(); ++i) { + TryPathReserve(vehicles_affected[i], true); + } } /* Human players that build bridges get a selection to choose from (DC_QUERY_COST) @@ -1141,6 +1165,15 @@ static CommandCost DoClearTunnel(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; } + if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && _settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + DiagDirection dir = GetTunnelBridgeDirection(tile); + Track track = DiagDirToDiagTrack(dir); + CommandCost ret = CheckTrainReservationPreventsTrackModification(tile, track); + if (ret.Failed()) return ret; + ret = CheckTrainReservationPreventsTrackModification(endtile, track); + if (ret.Failed()) return ret; + } + /* checks if the owner is town then decrease town rating by RATING_TUNNEL_BRIDGE_DOWN_STEP until * you have a "Poor" (0) town rating */ if (IsTileOwner(tile, OWNER_TOWN) && _game_mode != GM_EDITOR) { @@ -1261,8 +1294,17 @@ static CommandCost DoClearBridge(TileIndex tile, DoCommandFlag flags) tile_tracks = GetCustomBridgeHeadTrackBits(tile); endtile_tracks = GetCustomBridgeHeadTrackBits(endtile); cost.AddCost(RailClearCost(GetRailType(tile)) * (CountBits(GetPrimaryTunnelBridgeTrackBits(tile)) + CountBits(GetPrimaryTunnelBridgeTrackBits(endtile)) - 2)); - if (GetSecondaryTunnelBridgeTrackBits(tile)) cost.AddCost(RailClearCost(GetSecondaryRailType(tile))); - if (GetSecondaryTunnelBridgeTrackBits(endtile)) cost.AddCost(RailClearCost(GetSecondaryRailType(endtile))); + for (TileIndex t : { tile, endtile }) { + if (GetSecondaryTunnelBridgeTrackBits(t)) cost.AddCost(RailClearCost(GetSecondaryRailType(t))); + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + TrackBits reserved = GetBridgeReservationTrackBits(t); + Track track; + while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) { + CommandCost ret = CheckTrainReservationPreventsTrackModification(t, track); + if (ret.Failed()) return ret; + } + } + } } Money base_cost = (GetTunnelBridgeTransportType(tile) != TRANSPORT_WATER) ? _price[PR_CLEAR_BRIDGE] : _price[PR_CLEAR_AQUEDUCT]; diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h index 7b3b595ca2..3d158898a1 100644 --- a/src/tunnelbridge_map.h +++ b/src/tunnelbridge_map.h @@ -16,6 +16,8 @@ #include "signal_type.h" #include "tunnel_map.h" #include "track_func.h" +#include "settings_type.h" +#include "vehicle_type.h" #include "core/bitmath_func.hpp" @@ -493,6 +495,11 @@ static inline bool IsTunnelBridgePBS(TileIndex t) return HasBit(_me[t].m6, 6); } +static inline bool IsTunnelBridgeEffectivelyPBS(TileIndex t) +{ + return _settings_game.vehicle.train_braking_model == TBM_REALISTIC || IsTunnelBridgePBS(t); +} + static inline void SetTunnelBridgePBS(TileIndex t, bool is_pbs) { assert_tile(IsTunnelBridgeWithSignalSimulation(t), t); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index e9e5299dfe..972fefdb9c 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -666,6 +666,125 @@ CommandCost TunnelBridgeIsFree(TileIndex tile, TileIndex endtile, const Vehicle return CommandCost(); } +struct FindTrainClosestToTunnelBridgeEndInfo { + Train *best; ///< The currently "best" vehicle we have found. + int32 best_pos; + DiagDirection direction; + + FindTrainClosestToTunnelBridgeEndInfo(DiagDirection direction) : best(nullptr), best_pos(INT32_MIN), direction(direction) {} +}; + +/** Callback for Has/FindVehicleOnPos to find a train in a signalled tunnel/bridge */ +static Vehicle *FindClosestTrainToTunnelBridgeEndEnum(Vehicle *v, void *data) +{ + FindTrainClosestToTunnelBridgeEndInfo *info = (FindTrainClosestToTunnelBridgeEndInfo *)data; + + /* Only look for train heads and tails. */ + if (v->Previous() != nullptr && v->Next() != nullptr) return nullptr; + + if ((v->vehstatus & VS_CRASHED)) return nullptr; + + Train *t = Train::From(v); + + if (!IsDiagonalDirection(t->direction)) { + /* Check for vehicles on non-across track pieces of custom bridge head */ + if ((GetAcrossTunnelBridgeTrackBits(t->tile) & t->track & TRACK_BIT_ALL) == TRACK_BIT_NONE) return nullptr; + } + + int32 pos; + switch (info->direction) { + default: NOT_REACHED(); + case DIAGDIR_NE: pos = -v->x_pos; break; // X: lower is better + case DIAGDIR_SE: pos = v->y_pos; break; // Y: higher is better + case DIAGDIR_SW: pos = v->x_pos; break; // X: higher is better + case DIAGDIR_NW: pos = -v->y_pos; break; // Y: lower is better + } + + /* ALWAYS return the lowest ID (anti-desync!) if the coordinate is the same */ + if (pos > info->best_pos || (pos == info->best_pos && t->First()->index < info->best->index)) { + info->best = t->First(); + info->best_pos = pos; + } + + return t; +} + +Train *GetTrainClosestToTunnelBridgeEnd(TileIndex tile, TileIndex other_tile) +{ + FindTrainClosestToTunnelBridgeEndInfo info(ReverseDiagDir(GetTunnelBridgeDirection(tile))); + FindVehicleOnPos(tile, VEH_TRAIN, &info, FindClosestTrainToTunnelBridgeEndEnum); + FindVehicleOnPos(other_tile, VEH_TRAIN, &info, FindClosestTrainToTunnelBridgeEndEnum); + return info.best; +} + + +struct GetAvailableFreeTilesInSignalledTunnelBridgeChecker { + DiagDirection direction; + int pos; + int lowest_seen; +}; + +static Vehicle *GetAvailableFreeTilesInSignalledTunnelBridgeEnum(Vehicle *v, void *data) +{ + /* Don't look at wagons between front and back of train. */ + if ((v->Previous() != nullptr && v->Next() != nullptr)) return nullptr; + + if (!IsDiagonalDirection(v->direction)) { + /* Check for vehicles on non-across track pieces of custom bridge head */ + if ((GetAcrossTunnelBridgeTrackBits(v->tile) & Train::From(v)->track & TRACK_BIT_ALL) == TRACK_BIT_NONE) return nullptr; + } + + GetAvailableFreeTilesInSignalledTunnelBridgeChecker *checker = (GetAvailableFreeTilesInSignalledTunnelBridgeChecker*) data; + int v_pos; + + switch (checker->direction) { + default: NOT_REACHED(); + case DIAGDIR_NE: v_pos = -v->x_pos + TILE_UNIT_MASK; break; + case DIAGDIR_SE: v_pos = v->y_pos; break; + case DIAGDIR_SW: v_pos = v->x_pos; break; + case DIAGDIR_NW: v_pos = -v->y_pos + TILE_UNIT_MASK; break; + } + if (v_pos > checker->pos && v_pos < checker->lowest_seen) { + checker->lowest_seen = v_pos; + } + + return nullptr; +} + +int GetAvailableFreeTilesInSignalledTunnelBridgeWithStartOffset(TileIndex entrance, TileIndex exit, int offset) +{ + if (offset < 0) offset = 0; + TileIndex tile = entrance; + if (offset > 0) tile += offset * TileOffsByDiagDir(GetTunnelBridgeDirection(entrance)); + int free_tiles = GetAvailableFreeTilesInSignalledTunnelBridge(entrance, exit, tile); + if (free_tiles != INT_MAX && offset > 0) free_tiles += offset; + return free_tiles; +} + +int GetAvailableFreeTilesInSignalledTunnelBridge(TileIndex entrance, TileIndex exit, TileIndex tile) +{ + GetAvailableFreeTilesInSignalledTunnelBridgeChecker checker; + checker.direction = GetTunnelBridgeDirection(entrance); + checker.lowest_seen = INT_MAX; + switch (checker.direction) { + default: NOT_REACHED(); + case DIAGDIR_NE: checker.pos = -(TileX(tile) * TILE_SIZE); break; + case DIAGDIR_SE: checker.pos = (TileY(tile) * TILE_SIZE); break; + case DIAGDIR_SW: checker.pos = (TileX(tile) * TILE_SIZE); break; + case DIAGDIR_NW: checker.pos = -(TileY(tile) * TILE_SIZE); break; + } + + FindVehicleOnPos(entrance, VEH_TRAIN, &checker, &GetAvailableFreeTilesInSignalledTunnelBridgeEnum); + FindVehicleOnPos(exit, VEH_TRAIN, &checker, &GetAvailableFreeTilesInSignalledTunnelBridgeEnum); + + if (checker.lowest_seen == INT_MAX) { + /* Remainder of bridge/tunnel is clear */ + return INT_MAX; + } + + return (checker.lowest_seen - checker.pos) / TILE_SIZE; +} + static Vehicle *EnsureNoTrainOnTrackProc(Vehicle *v, void *data) { TrackBits rail_bits = *(TrackBits *)data; @@ -1961,6 +2080,10 @@ bool Vehicle::HandleBreakdown() CheckBreakdownFlags(Train::From(this->First())); SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_STOPPED); break; + case BREAKDOWN_BRAKE_OVERHEAT: + CheckBreakdownFlags(Train::From(this->First())); + SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_STOPPED); + break; case BREAKDOWN_LOW_SPEED: CheckBreakdownFlags(Train::From(this->First())); SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_SPEED); @@ -2042,7 +2165,8 @@ bool Vehicle::HandleBreakdown() } } } - return (this->breakdown_type == BREAKDOWN_CRITICAL || this->breakdown_type == BREAKDOWN_EM_STOP || this->breakdown_type == BREAKDOWN_RV_CRASH); + return (this->breakdown_type == BREAKDOWN_CRITICAL || this->breakdown_type == BREAKDOWN_EM_STOP || + this->breakdown_type == BREAKDOWN_RV_CRASH || this->breakdown_type == BREAKDOWN_BRAKE_OVERHEAT); default: if (!this->current_order.IsType(OT_LOADING)) this->breakdown_ctr--; @@ -2209,6 +2333,7 @@ void VehicleEnterDepot(Vehicle *v) ClrBit(t->flags, VRF_TOGGLE_REVERSE); t->ConsistChanged(CCF_ARRANGE); t->reverse_distance = 0; + t->lookahead.reset(); break; } @@ -3513,7 +3638,7 @@ static void SpawnAdvancedVisualEffect(const Vehicle *v) } } -uint16 ReversingDistanceTargetSpeed(const Train *v); +int ReversingDistanceTargetSpeed(const Train *v); /** * Draw visual effects (smoke and/or sparks) for a vehicle chain. @@ -3542,13 +3667,15 @@ void Vehicle::ShowVisualEffect() const const Train *t = Train::From(this); /* For trains, do not show any smoke when: * - the train is reversing + * - the train is exceeding the max speed * - is entering a station with an order to stop there and its speed is equal to maximum station entering speed * - is approaching a reversing point and its speed is equal to maximum approach speed */ if (HasBit(t->flags, VRF_REVERSING) || + t->cur_speed > max_speed || (HasStationTileRail(t->tile) && t->IsFrontEngine() && t->current_order.ShouldStopAtStation(t, GetStationIndex(t->tile), IsRailWaypoint(t->tile)) && t->cur_speed >= max_speed) || - (t->reverse_distance >= 1 && t->cur_speed >= ReversingDistanceTargetSpeed(t))) { + (t->reverse_distance >= 1 && (int)t->cur_speed >= ReversingDistanceTargetSpeed(t))) { return; } } diff --git a/src/vehicle_func.h b/src/vehicle_func.h index c2bccf09a0..73943b3c87 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -132,6 +132,9 @@ void ViewportMapDrawVehicles(DrawPixelInfo *dpi, Viewport *vp); void ShowNewGrfVehicleError(EngineID engine, StringID part1, StringID part2, GRFBugs bug_type, bool critical); CommandCost TunnelBridgeIsFree(TileIndex tile, TileIndex endtile, const Vehicle *ignore = nullptr, bool across_only = false); +Train *GetTrainClosestToTunnelBridgeEnd(TileIndex tile, TileIndex other_tile); +int GetAvailableFreeTilesInSignalledTunnelBridge(TileIndex entrance, TileIndex exit, TileIndex tile); +int GetAvailableFreeTilesInSignalledTunnelBridgeWithStartOffset(TileIndex entrance, TileIndex exit, int offset); void DecreaseVehicleValue(Vehicle *v); void CheckVehicleBreakdown(Vehicle *v); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 86733a4517..706733aa04 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -3318,7 +3318,7 @@ public: str = STR_VEHICLE_STATUS_CRASHED; } else if (v->breakdown_ctr == 1 || (v->type == VEH_TRAIN && Train::From(v)->flags & VRF_IS_BROKEN)) { const Vehicle *w = (v->type == VEH_TRAIN) ? GetMostSeverelyBrokenEngine(Train::From(v)) : v; - if (_settings_game.vehicle.improved_breakdowns || w->breakdown_type == BREAKDOWN_RV_CRASH) { + if (_settings_game.vehicle.improved_breakdowns || w->breakdown_type == BREAKDOWN_RV_CRASH || w->breakdown_type == BREAKDOWN_BRAKE_OVERHEAT) { str = STR_VEHICLE_STATUS_BROKEN_DOWN_VEL; SetDParam(3, v->GetDisplaySpeed()); } else { diff --git a/src/vehicle_type.h b/src/vehicle_type.h index d5b59143cd..2e1a8141fd 100644 --- a/src/vehicle_type.h +++ b/src/vehicle_type.h @@ -89,6 +89,7 @@ enum BreakdownType { BREAKDOWN_LOW_SPEED = 2, ///< Lower max speed BREAKDOWN_LOW_POWER = 3, ///< Power reduction BREAKDOWN_RV_CRASH = 4, ///< Train hit road vehicle + BREAKDOWN_BRAKE_OVERHEAT = 5, ///< Train brakes overheated due to excessive slope or speed change BREAKDOWN_AIRCRAFT_SPEED = BREAKDOWN_CRITICAL, ///< Lower speed until the next airport BREAKDOWN_AIRCRAFT_DEPOT = BREAKDOWN_EM_STOP, ///< We have to visit a depot at the next airport @@ -101,6 +102,12 @@ enum AccelerationModel { AM_REALISTIC, }; +/** Train braking models. */ +enum TrainBrakingModel { + TBM_ORIGINAL, + TBM_REALISTIC, +}; + /** Visualisation contexts of vehicles and engines. */ enum EngineImageType { EIT_ON_MAP = 0x00, ///< Vehicle drawn in viewport. diff --git a/src/widgets/rail_widget.h b/src/widgets/rail_widget.h index 9518a97eb3..6637a618a8 100644 --- a/src/widgets/rail_widget.h +++ b/src/widgets/rail_widget.h @@ -97,6 +97,12 @@ enum BuildSignalWidgets { WID_BS_DRAG_SIGNALS_DENSITY_LABEL, ///< The current signal density. WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, ///< Decrease the signal density. WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, ///< Increase the signal density. + WID_BS_SEMAPHORE_ENTRY_SEL, ///< NWID_SELECTION for WID_BS_SEMAPHORE_ENTRY + WID_BS_ELECTRIC_ENTRY_SEL, ///< NWID_SELECTION for WID_BS_ELECTRIC_ENTRY + WID_BS_SEMAPHORE_EXIT_SEL, ///< NWID_SELECTION for WID_BS_SEMAPHORE_EXIT + WID_BS_ELECTRIC_EXIT_SEL, ///< NWID_SELECTION for WID_BS_ELECTRIC_EXIT + WID_BS_SEMAPHORE_COMBO_SEL, ///< NWID_SELECTION for WID_BS_SEMAPHORE_COMBO + WID_BS_ELECTRIC_COMBO_SEL, ///< NWID_SELECTION for WID_BS_ELECTRIC_COMBO WID_BS_SEMAPHORE_PROG_SEL, ///< NWID_SELECTION for WID_BS_SEMAPHORE_PROG WID_BS_ELECTRIC_PROG_SEL, ///< NWID_SELECTION for WID_BS_ELECTRIC_PROG WID_BS_PROGRAM_SEL, ///< NWID_SELECTION for WID_BS_PROGRAM