diff --git a/src/pbs.cpp b/src/pbs.cpp index bb83ffb189..b29bf340af 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -14,6 +14,7 @@ #include "pathfinder/follow_track.hpp" #include "tracerestrict.h" #include "newgrf_newsignals.h" +#include "train_speed_adaptation.h" #include "safeguards.h" @@ -408,6 +409,14 @@ static uint16 GetTrainSpeedLimitForRailtype(const Train *v, RailType rt, TileInd return speed; } +static void AddSignalToLookAhead(const Train *v, TrainReservationLookAhead *lookahead, uint16 signal_speed, uint16 signal_flags, TileIndex signal_tile, uint16 signal_track, int offset, int16 z_pos) +{ + lookahead->AddSignal(signal_speed, offset, z_pos, signal_flags); + if (_settings_game.vehicle.train_speed_adaptation) { + lookahead->AddSpeedAdaptation(signal_tile, signal_track, offset, z_pos); + } +} + /** Follow a reservation starting from a specific tile to the end. */ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Trackdir trackdir, FollowReservationFlags flags, const Train *v, TrainReservationLookAhead *lookahead) { @@ -589,7 +598,7 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra const uint16 signal_flags = GetTunnelBridgeSignalStyle(tile) << 8; /* Entrance signal */ - lookahead->AddSignal(signal_speed, 0, z, signal_flags); + AddSignalToLookAhead(v, lookahead, signal_speed, signal_flags, tile, TrackdirToTrack(trackdir), 0, z); update_z(tile, trackdir, false); @@ -603,7 +612,8 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra 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, signal_flags); + const int signal_z = chunnel ? LookaheadTileHeightForChunnel(length, i * spacing) : z; + AddSignalToLookAhead(v, lookahead, signal_speed, signal_flags, tile, 0x100 + i, offset, signal_z); } /* Exit signal */ @@ -617,7 +627,7 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra if (signal_speed == 0 || (bridge_speed != 0 && bridge_speed < signal_speed)) signal_speed = bridge_speed; } - lookahead->AddSignal(signal_speed, end_offset, z, signal_flags); + AddSignalToLookAhead(v, lookahead, signal_speed, signal_flags, end, FindFirstTrack(GetAcrossTunnelBridgeTrackBits(end)), end_offset, z); lookahead->SetNextExtendPositionIfUnset(); } else { @@ -672,7 +682,7 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra SetBit(signal_flags, TRSLAI_COMBINED); UpdateLookaheadCombinedNormalShuntSignalDeferred(tile, trackdir, lookahead->RealEndPosition()); } - lookahead->AddSignal(signal_speed, 0, z, signal_flags); + AddSignalToLookAhead(v, lookahead, signal_speed, signal_flags, tile, TrackdirToTrack(trackdir), 0, z); lookahead->SetNextExtendPositionIfUnset(); } } @@ -1073,7 +1083,8 @@ void TryCreateLookAheadForTrainInTunnelBridge(Train *t) int offset = -(int)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, signal_flags); + const int signal_z = HasBit(t->lookahead->flags, TRLF_CHUNNEL) ? LookaheadTileHeightForChunnel(length, i * spacing) : z; + AddSignalToLookAhead(t, t->lookahead.get(), signal_speed, signal_flags, t->tile, 0x100 + i, offset, signal_z); } /* Exit signal */ @@ -1086,7 +1097,7 @@ void TryCreateLookAheadForTrainInTunnelBridge(Train *t) if (signal_speed == 0 || (bridge_speed != 0 && bridge_speed < signal_speed)) signal_speed = bridge_speed; } - t->lookahead->AddSignal(signal_speed, end_offset, z, signal_flags); + AddSignalToLookAhead(t, t->lookahead.get(), signal_speed, signal_flags, end, FindFirstTrack(GetAcrossTunnelBridgeTrackBits(end)), end_offset, z); t->lookahead->SetNextExtendPositionIfUnset(); } @@ -1174,6 +1185,8 @@ void FillTrainReservationLookAhead(Train *v) if (v->lookahead == nullptr) return; } + int32 old_reservation_end_position = 0; + if (v->lookahead == nullptr) { v->lookahead.reset(new TrainReservationLookAhead()); v->lookahead->current_position = 0; @@ -1205,6 +1218,7 @@ void FillTrainReservationLookAhead(Train *v) v->lookahead->reservation_end_position += station_offset_tiles * TILE_SIZE; } } else { + old_reservation_end_position = v->lookahead->reservation_end_position; tile = v->lookahead->reservation_end_tile; trackdir = v->lookahead->reservation_end_trackdir; if (IsTunnelBridgeSignalSimulationEntranceTile(tile) && TrackdirEntersTunnelBridge(tile, trackdir)) { @@ -1263,6 +1277,24 @@ void FillTrainReservationLookAhead(Train *v) FlushDeferredDetermineCombineNormalShuntMode(v); SetTrainReservationLookaheadEnd(v); + + if (_settings_game.vehicle.train_speed_adaptation && v->signal_speed_restriction > 0 && v->lookahead->reservation_end_position > old_reservation_end_position) { + for (const TrainReservationLookAheadItem &item : v->lookahead->items) { + if (item.type == TRLIT_SPEED_ADAPTATION && item.end + 1 >= old_reservation_end_position && item.end + 1 < v->lookahead->reservation_end_position) { + uint16 signal_speed = GetLowestSpeedTrainAdaptationSpeedAtSignal(item.data_id, item.data_aux); + + if (signal_speed == 0) { + /* unrestricted signal ahead, remove current speed adaptation */ + v->signal_speed_restriction = 0; + break; + } + if (signal_speed > v->signal_speed_restriction) { + /* signal ahead with higher speed, increase current speed adaptation */ + v->signal_speed_restriction = signal_speed; + } + } + } + } } /** diff --git a/src/pbs.h b/src/pbs.h index a4bda0b964..cb1bef5fa3 100644 --- a/src/pbs.h +++ b/src/pbs.h @@ -57,6 +57,7 @@ enum TrainReservationLookAheadItemType : byte { TRLIT_SPEED_RESTRICTION = 3, ///< Speed restriction TRLIT_SIGNAL = 4, ///< Signal TRLIT_CURVE_SPEED = 5, ///< Curve speed limit + TRLIT_SPEED_ADAPTATION = 6, ///< Train speed adaptation ahead }; enum TrainReservationSignalLookAheadItemFlags { @@ -70,9 +71,11 @@ struct TrainReservationLookAheadItem { int32 start; int32 end; int16 z_pos; - uint16 data_id; + /* gap: 2 bytes */ + uint32 data_id; uint16 data_aux; TrainReservationLookAheadItemType type; + /* gap: 1 byte */ }; struct TrainReservationLookAheadCurve { @@ -145,6 +148,12 @@ struct TrainReservationLookAhead { this->items.push_back({ end + offset, end + offset, z_pos, target_speed, 0, TRLIT_CURVE_SPEED }); } + void AddSpeedAdaptation(TileIndex signal_tile, uint16 signal_track, int offset, int16 z_pos) + { + int end = this->RealEndPosition(); + this->items.push_back({ end + offset, end + offset, z_pos, signal_tile, signal_track, TRLIT_SPEED_ADAPTATION }); + } + void SetNextExtendPosition(); void SetNextExtendPositionIfUnset() diff --git a/src/sl/extended_ver_sl.cpp b/src/sl/extended_ver_sl.cpp index dd4f4cedea..8071493939 100644 --- a/src/sl/extended_ver_sl.cpp +++ b/src/sl/extended_ver_sl.cpp @@ -157,7 +157,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, 9, 9, "realistic_train_braking", nullptr, nullptr, "VLKA" }, + { XSLFI_REALISTIC_TRAIN_BRAKING, XSCF_NULL, 10, 10, "realistic_train_braking", nullptr, nullptr, "VLKA" }, { XSLFI_INFLATION_FIXED_DATES, XSCF_IGNORABLE_ALL, 1, 1, "inflation_fixed_dates", nullptr, nullptr, nullptr }, { XSLFI_WATER_FLOODING, XSCF_NULL, 2, 2, "water_flooding", nullptr, nullptr, nullptr }, { XSLFI_MORE_HOUSES, XSCF_NULL, 2, 2, "more_houses", nullptr, nullptr, nullptr }, diff --git a/src/sl/vehicle_sl.cpp b/src/sl/vehicle_sl.cpp index 31878ab98c..ba1c0c7136 100644 --- a/src/sl/vehicle_sl.cpp +++ b/src/sl/vehicle_sl.cpp @@ -1513,7 +1513,8 @@ const SaveLoadTable GetVehicleLookAheadItemDescription() 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_CONDVAR_X(TrainReservationLookAheadItem, data_id, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 0, 9)), + SLE_CONDVAR_X(TrainReservationLookAheadItem, data_id, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 10)), SLE_CONDVAR_X(TrainReservationLookAheadItem, data_aux, SLE_UINT16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 9)), SLE_VAR(TrainReservationLookAheadItem, type, SLE_UINT8), }; diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 40db7b9aee..c5ecce0782 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -401,6 +401,15 @@ class NIHVehicle : public NIHelper { if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) print_braking_speed(item.start, item.data_id, item.z_pos); break; + case TRLIT_SPEED_ADAPTATION: { + TileIndex tile = item.data_id; + uint16 td = item.data_aux; + b += seprintf(b, lastof(buffer), "speed adaptation: tile: %X, trackdir: %X", tile, td); + if (item.end + 1 < l.reservation_end_position) { + b += seprintf(b, lastof(buffer), " --> %u", GetLowestSpeedTrainAdaptationSpeedAtSignal(tile, td)); + } + break; + } } output.print(buffer); } @@ -1411,17 +1420,17 @@ class NIHSignals : public NIHelper { DumpTunnelBridgeSignalsInfo(buffer, lastof(buffer), index, output); } if (_settings_game.vehicle.train_speed_adaptation) { - for (const auto &it : _signal_speeds) { - if (it.first.signal_tile == index) { - char *b = buffer + seprintf(buffer, lastof(buffer), "Speed adaptation: Track: %X, last dir: %X --> speed: %u", - it.first.signal_track, it.first.last_passing_train_dir, it.second.train_speed); - if (it.second.IsOutOfDate()) { - b += seprintf(b, lastof(buffer), ", expired"); - } else { - b += seprintf(b, lastof(buffer), ", expires in %u ticks", (uint)(it.second.time_stamp - _scaled_date_ticks)); - } - output.print(buffer); + SignalSpeedKey speed_key = { index, 0, (Trackdir)0 }; + for (auto iter = _signal_speeds.lower_bound(speed_key); iter != _signal_speeds.end() && iter->first.signal_tile == index; ++iter) { + const auto &it = *iter; + char *b = buffer + seprintf(buffer, lastof(buffer), "Speed adaptation: Track: %X, last dir: %X --> speed: %u", + it.first.signal_track, it.first.last_passing_train_dir, it.second.train_speed); + if (it.second.IsOutOfDate()) { + b += seprintf(b, lastof(buffer), ", expired"); + } else { + b += seprintf(b, lastof(buffer), ", expires in %u ticks", (uint)(it.second.time_stamp - _scaled_date_ticks)); } + output.print(buffer); } } } diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index c954add1ab..416abef8ed 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -81,7 +81,7 @@ enum ChooseTrainTrackFlags { }; DECLARE_ENUM_AS_BIT_SET(ChooseTrainTrackFlags) -std::unordered_map _signal_speeds(1 << 16); +btree::btree_map _signal_speeds; static void TryLongReserveChooseTrainTrackFromReservationEnd(Train *v, bool no_reserve_vehicle_tile = false); static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, ChooseTrainTrackFlags flags, bool *p_got_reservation, ChooseTrainTrackLookAheadState lookahead_state = {}); @@ -966,6 +966,9 @@ static void ApplyLookAheadItem(const Train *v, const TrainReservationLookAheadIt case TRLIT_CURVE_SPEED: if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) limit_speed(item.start, item.data_id, item.z_pos); break; + + case TRLIT_SPEED_ADAPTATION: + break; } } @@ -7566,27 +7569,70 @@ void SetSignalTrainAdaptationSpeed(const Train *v, TileIndex tile, uint16 track) _signal_speeds[speed_key] = speed_value; } -void ApplySignalTrainAdaptationSpeed(Train *v, TileIndex tile, uint16 track) +static uint16 GetTrainAdaptationSpeed(TileIndex tile, uint16 track, Trackdir last_passing_train_dir) { SignalSpeedKey speed_key = { speed_key.signal_tile = tile, speed_key.signal_track = track, - speed_key.last_passing_train_dir = v->GetVehicleTrackdir() + speed_key.last_passing_train_dir = last_passing_train_dir }; const auto found_speed_restriction = _signal_speeds.find(speed_key); if (found_speed_restriction != _signal_speeds.end()) { if (found_speed_restriction->second.IsOutOfDate()) { _signal_speeds.erase(found_speed_restriction); - v->signal_speed_restriction = 0; + return 0; } else { - v->signal_speed_restriction = std::max(25, found_speed_restriction->second.train_speed); + return std::max(25, found_speed_restriction->second.train_speed); } } else { - v->signal_speed_restriction = 0; + return 0; } } +void ApplySignalTrainAdaptationSpeed(Train *v, TileIndex tile, uint16 track) +{ + uint16 speed = GetTrainAdaptationSpeed(tile, track, v->GetVehicleTrackdir()); + + if (speed > 0 && v->lookahead != nullptr) { + for (const TrainReservationLookAheadItem &item : v->lookahead->items) { + if (item.type == TRLIT_SPEED_ADAPTATION && item.end + 1 < v->lookahead->reservation_end_position) { + uint16 signal_speed = GetLowestSpeedTrainAdaptationSpeedAtSignal(item.data_id, item.data_aux); + + if (signal_speed == 0) { + /* unrestricted signal ahead, disregard speed adaptation at earlier signal */ + v->signal_speed_restriction = 0; + return; + } + if (signal_speed > speed) { + /* signal ahead with higher speed adaptation speed, override speed adaptation at earlier signal */ + speed = signal_speed; + } + } + } + } + + v->signal_speed_restriction = speed; +} + +uint16 GetLowestSpeedTrainAdaptationSpeedAtSignal(TileIndex tile, uint16 track) +{ + uint16 lowest_speed = 0; + + SignalSpeedKey speed_key = { tile, track, (Trackdir)0 }; + for (auto iter = _signal_speeds.lower_bound(speed_key); iter != _signal_speeds.end() && iter->first.signal_tile == tile && iter->first.signal_track == track;) { + if (iter->second.IsOutOfDate()) { + iter = _signal_speeds.erase(iter); + } else { + uint16 adapt_speed = std::max(25, iter->second.train_speed); + if (lowest_speed == 0 || adapt_speed < lowest_speed) lowest_speed = adapt_speed; + ++iter; + } + } + + return lowest_speed; +} + uint16 Train::GetMaxWeight() const { uint16 weight = CargoSpec::Get(this->cargo_type)->WeightOfNUnitsInTrain(this->GetEngine()->DetermineCapacity(this)); diff --git a/src/train_speed_adaptation.h b/src/train_speed_adaptation.h index 7affd34b26..706140614e 100644 --- a/src/train_speed_adaptation.h +++ b/src/train_speed_adaptation.h @@ -14,8 +14,7 @@ #include "date_func.h" #include "track_type.h" #include "tile_type.h" - -#include +#include "3rdparty/cpp-btree/btree_map.h" struct SignalSpeedKey { TileIndex signal_tile; @@ -28,6 +27,11 @@ struct SignalSpeedKey { signal_track == other.signal_track && last_passing_train_dir == other.last_passing_train_dir; } + + bool operator<(const SignalSpeedKey& other) const + { + return std::tie(this->signal_tile, this->signal_track, this->last_passing_train_dir) < std::tie(other.signal_tile, other.signal_track, other.last_passing_train_dir); + } }; struct SignalSpeedValue { @@ -41,21 +45,11 @@ struct SignalSpeedValue { } }; -struct SignalSpeedKeyHashFunc { - std::size_t operator() (const SignalSpeedKey &key) const - { - const std::size_t h1 = std::hash()(key.signal_tile); - const std::size_t h2 = std::hash()(key.last_passing_train_dir); - const std::size_t h3 = std::hash()(key.signal_track); - - return (h1 ^ h2) ^ h3; - } -}; - -extern std::unordered_map _signal_speeds; +extern btree::btree_map _signal_speeds; struct Train; void SetSignalTrainAdaptationSpeed(const Train *v, TileIndex tile, uint16 track); void ApplySignalTrainAdaptationSpeed(Train *v, TileIndex tile, uint16 track); +uint16 GetLowestSpeedTrainAdaptationSpeedAtSignal(TileIndex tile, uint16 track); #endif /* TRAIN_SPEED_ADAPTATION_H */