diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 1129777c5d..f038229e13 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -5,6 +5,7 @@ - Improved: [#23404] Folders are now paired with an icon in the load/save window. - Improved: [#23431] Opaque water and Corkscrew Roller Coaster boosters now show up if RCT1 isn’t linked. - Change: [#23413] The max number of park entrance objects has been raised to 255. +- Fix: [#1122] Trains spawned on a cable lift hill will fall down and crash (original bug). - Fix: [#22742, #22793] In game console does not handle format tokens properly. - Fix: [#23286] Currency formatted incorrectly in the in game console. - Fix: [#23348] Console set commands don't print output properly. diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index 0f88380536..5128ea832b 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -50,7 +50,7 @@ using namespace OpenRCT2; // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -constexpr uint8_t kNetworkStreamVersion = 2; +constexpr uint8_t kNetworkStreamVersion = 3; const std::string kNetworkStreamID = std::string(OPENRCT2_VERSION) + "-" + std::to_string(kNetworkStreamVersion); diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp index 637f91d112..6566f74846 100644 --- a/src/openrct2/ride/Ride.cpp +++ b/src/openrct2/ride/Ride.cpp @@ -3666,6 +3666,23 @@ ResultWithMessage Ride::CreateVehicles(const CoordsXYE& element, bool isApplying */ void Ride::MoveTrainsToBlockBrakes(const CoordsXYZ& firstBlockPosition, TrackElement& firstBlock) { + // If the ride has a cable lift, we don't want to fetch the cable lift element and the block preceding it + TrackElement* cableLiftTileElement = nullptr; + TrackElement* cableLiftPreviousBlock = nullptr; + if (lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT_HILL_COMPONENT_USED) + { + cableLiftTileElement = MapGetTrackElementAt(CableLiftLoc); + if (cableLiftTileElement != nullptr) + { + cableLiftTileElement = MapGetTrackElementAt(CableLiftLoc); + if (cableLiftTileElement != nullptr) + { + CoordsXYZ location = CableLiftLoc; + cableLiftPreviousBlock = TrackGetPreviousBlock(location, reinterpret_cast(cableLiftTileElement)); + } + } + } + for (int32_t i = 0; i < NumTrains; i++) { auto train = GetEntity(vehicles[i]); @@ -3695,6 +3712,14 @@ void Ride::MoveTrainsToBlockBrakes(const CoordsXYZ& firstBlockPosition, TrackEle break; } + // Setting the first block before the cable lift to the same state as the cable lift ensures that any train which + // would be placed on the cable lift will instead stop on the block before it. As there can only be one cable lift + // per ride and there must always be at least one block left free, there will be enough blocks remaining. This fixes + // the bug in #1122. + if (cableLiftTileElement != nullptr && cableLiftPreviousBlock != nullptr) + { + cableLiftPreviousBlock->SetBrakeClosed(cableLiftTileElement->IsBrakeClosed()); + } firstBlock.SetBrakeClosed(true); for (Vehicle* car = train; car != nullptr; car = GetEntity(car->next_vehicle_on_train)) { @@ -3722,6 +3747,12 @@ void Ride::MoveTrainsToBlockBrakes(const CoordsXYZ& firstBlockPosition, TrackEle } } } + + // After all trains are in position, set the block preceding the cable lift to open. + if (cableLiftPreviousBlock != nullptr) + { + cableLiftPreviousBlock->SetBrakeClosed(false); + } } /** diff --git a/src/openrct2/ride/Track.cpp b/src/openrct2/ride/Track.cpp index 68257bb405..58a658fc8f 100644 --- a/src/openrct2/ride/Track.cpp +++ b/src/openrct2/ride/Track.cpp @@ -523,6 +523,58 @@ void TrackGetFront(const CoordsXYE& input, CoordsXYE* output) *output = lastTrack; } +TrackElement* TrackGetPreviousBlock(CoordsXYZ& location, TileElement* tileElement) +{ + CoordsXYZ startLocation = location; + TrackBeginEnd trackBeginEnd, slowTrackBeginEnd; + TileElement slowTileElement = *tileElement; + bool counter = true; + CoordsXY slowLocation = location; + do + { + if (!TrackBlockGetPrevious({ location, tileElement }, &trackBeginEnd)) + { + return nullptr; + } + if (trackBeginEnd.begin_x == startLocation.x && trackBeginEnd.begin_y == startLocation.y + && tileElement == trackBeginEnd.begin_element) + { + return nullptr; + } + + location.x = trackBeginEnd.end_x; + location.y = trackBeginEnd.end_y; + location.z = trackBeginEnd.begin_z; + tileElement = trackBeginEnd.begin_element; + + // #2081: prevent infinite loop + counter = !counter; + if (counter) + { + TrackBlockGetPrevious({ slowLocation, &slowTileElement }, &slowTrackBeginEnd); + slowLocation.x = slowTrackBeginEnd.end_x; + slowLocation.y = slowTrackBeginEnd.end_y; + slowTileElement = *(slowTrackBeginEnd.begin_element); + if (slowLocation == location && slowTileElement.GetBaseZ() == tileElement->GetBaseZ() + && slowTileElement.GetType() == tileElement->GetType() + && slowTileElement.GetDirection() == tileElement->GetDirection()) + { + return nullptr; + } + } + } while (!(trackBeginEnd.begin_element->AsTrack()->IsBlockStart())); + + // Get the start of the track block instead of the end + location = { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z }; + auto trackOrigin = MapGetTrackElementAtOfTypeSeq(location, trackBeginEnd.begin_element->AsTrack()->GetTrackType(), 0); + if (trackOrigin == nullptr) + { + return nullptr; + } + + return trackOrigin->AsTrack(); +} + TrackRoll TrackGetActualBank(TileElement* tileElement, TrackRoll bank) { auto ride = GetRide(tileElement->AsTrack()->GetRideIndex()); diff --git a/src/openrct2/ride/Track.h b/src/openrct2/ride/Track.h index a58e60c88e..f3c1fd4fec 100644 --- a/src/openrct2/ride/Track.h +++ b/src/openrct2/ride/Track.h @@ -702,6 +702,8 @@ bool TrackCircuitIteratorsMatch(const TrackCircuitIterator* firstIt, const Track void TrackGetBack(const CoordsXYE& input, CoordsXYE* output); void TrackGetFront(const CoordsXYE& input, CoordsXYE* output); +TrackElement* TrackGetPreviousBlock(CoordsXYZ& location, TileElement* tileElement); + bool TrackElementIsCovered(OpenRCT2::TrackElemType trackElementType); OpenRCT2::TrackElemType UncoverTrackElement(OpenRCT2::TrackElemType trackElementType); bool TrackTypeIsStation(OpenRCT2::TrackElemType trackType);