Add feature: realistic train braking

Add setting to select train braking model.
pull/213/head
Jonathan G Rennison 3 years ago
parent 2b02318c7e
commit ed0ffb6220

@ -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);

@ -59,7 +59,7 @@ template <> struct EnumPropsT<Direction> : MakeEnumPropsT<Direction, byte, DIR_B
* modulo DIR_END or use the #ChangeDirDiff(DirDiff, DirDiff) function.
* @see ChangeDirDiff(DirDiff, DirDiff)
*/
enum DirDiff {
enum DirDiff : byte {
DIRDIFF_SAME = 0, ///< Both directions faces to the same direction
DIRDIFF_45RIGHT = 1, ///< Angle of 45 degrees right
DIRDIFF_90RIGHT = 2, ///< Angle of 90 degrees right

@ -105,6 +105,7 @@ void GroundVehicle<T, Type>::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<T, Type>::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 <class T, VehicleType Type>
int GroundVehicle<T, Type>::GetAcceleration()
GroundVehicleAcceleration GroundVehicle<T, Type>::GetAcceleration()
{
/* Templated class used for function calls for performance reasons. */
const T *v = T::From(this);
@ -172,6 +173,8 @@ int GroundVehicle<T, Type>::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<T, Type>::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<T, Type>::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<T, Type>::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<T, Type>::GetAcceleration()
}
}
return accel;
return { accel, braking_accel };
} else {
return ClampToI32(min(-force - resistance, -10000) / mass);
return { braking_accel, braking_accel };
}
}

@ -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<T, Type> {
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);
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(spd);
int scaled_spd = this->GetAdvanceSpeed(this->cur_speed);
scaled_spd += this->progress;
this->progress = 0; // set later in *Handler or *Controller

@ -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);

@ -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...

@ -1626,10 +1626,13 @@ void CheckCaches(bool force_check, std::function<void(const char *)> 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' : '-');
}

@ -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).

@ -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));
}

@ -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;
}

@ -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;
}
}

@ -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<TrainReservationLookAheadItem> items;
std::deque<TrainReservationLookAheadCurve> 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.

@ -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<Train *> 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<Train *> 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<Train *> 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);
}
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));
}
}
}
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));
}
}
}
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<Train *> 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);

@ -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<NWidgetStacked>(WID_BS_SEMAPHORE_PROG_SEL)->SetDisplayedPlane(_settings_client.gui.show_progsig_ui ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_PROG_SEL)->SetDisplayedPlane(_settings_client.gui.show_progsig_ui ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(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<NWidgetStacked>(WID_BS_SEMAPHORE_ENTRY_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_ENTRY_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_EXIT_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_EXIT_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_COMBO_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_COMBO_SEL)->SetDisplayedPlane(this->presig_ui_shown ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_PROG_SEL)->SetDisplayedPlane(show_progsig ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_PROG_SEL)->SetDisplayedPlane(show_progsig ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(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(),

@ -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);
}

@ -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));

@ -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) {

@ -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
};

@ -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

@ -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},
};

@ -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;

@ -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"));

@ -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

@ -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;
}

@ -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)
{

@ -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<T *> &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<T *> &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;

@ -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);

@ -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

@ -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
/* cached max. speed / acceleration data */
int cached_max_curve_speed; ///< max consist speed limited by curves
byte user_def_data; ///< Cached property 0x25. Can be set by Callback 0x36.
/* 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
/* cached max. speed / acceleration data */
int cached_max_curve_speed; ///< max consist speed limited by curves
byte user_def_data; ///< Cached property 0x25. Can be set by Callback 0x36.
};
/**
@ -106,6 +110,8 @@ struct Train FINAL : public GroundVehicle<Train, VEH_TRAIN> {
/* Link between the two ends of a multiheaded engine */
Train *other_multiheaded_part;
std::unique_ptr<TrainReservationLookAhead> lookahead;
uint32 flags;
uint16 crash_anim_pos; ///< Crash animation counter.
@ -159,6 +165,12 @@ struct Train FINAL : public GroundVehicle<Train, VEH_TRAIN> {
void UpdateAcceleration();
struct MaxSpeedInfo {
int strict_max_speed;
int advisory_max_speed;
};
MaxSpeedInfo GetCurrentMaxSpeedInfo() const;
int GetCurrentMaxSpeed() const;
/**

File diff suppressed because it is too large Load Diff

@ -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<Train *> 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];

@ -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);

@ -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;
}
}

@ -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);

@ -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 {

@ -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.

@ -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

Loading…
Cancel
Save