diff --git a/CMakeLists.txt b/CMakeLists.txt
index cb9253ef5a..dc3fad1b08 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -76,9 +76,9 @@ set(OPENMSX_VERSION "1.1.0")
set(OPENMSX_URL "https://github.com/OpenRCT2/OpenMusic/releases/download/v${OPENMSX_VERSION}/openmusic.zip")
set(OPENMSX_SHA1 "8f0cf6b2fd4727e91b0d4062e7f199a43d15e777")
-set(REPLAYS_VERSION "0.0.75")
+set(REPLAYS_VERSION "0.0.76")
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip")
-set(REPLAYS_SHA1 "D1701450AE0FE84B144236243A925801B67D92ED")
+set(REPLAYS_SHA1 "AE5808DE726D27F5311731677A20C96A8FF9101F")
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.")
option(WITH_TESTS "Build tests")
diff --git a/distribution/changelog.txt b/distribution/changelog.txt
index 343512f178..cb62155f52 100644
--- a/distribution/changelog.txt
+++ b/distribution/changelog.txt
@@ -30,6 +30,7 @@
- Improved: [#19463] Added ‘W’ and ‘Y’ with circumflex to sprite font (for Welsh).
- Improved: [#19549] Enable large address awareness for 32 bit Windows builds allowing to use 4 GiB of virtual memory.
- Improved: [#19668] Decreased the minimum map size from 13 to 3.
+- Improved: [#19683] The delays for ride ratings to appear has been reduced drastically.
- Change: [#19018] Renamed actions to fit the naming scheme.
- Change: [#19091] [Plugin] Add game action information to callback arguments of custom actions.
- Change: [#19233] Reduce lift speed minimum and maximum values for “Classic Wooden Coaster”.
diff --git a/openrct2.proj b/openrct2.proj
index b3202b4ded..b6515e9a5c 100644
--- a/openrct2.proj
+++ b/openrct2.proj
@@ -51,8 +51,8 @@
64EF7E0B7785602C91AEC66F005C035B05A2133B
https://github.com/OpenRCT2/OpenMusic/releases/download/v1.1.0/openmusic.zip
8f0cf6b2fd4727e91b0d4062e7f199a43d15e777
- https://github.com/OpenRCT2/replays/releases/download/v0.0.75/replays.zip
- D1701450AE0FE84B144236243A925801B67D92ED
+ https://github.com/OpenRCT2/replays/releases/download/v0.0.76/replays.zip
+ AE5808DE726D27F5311731677A20C96A8FF9101F
diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp
index 35a13838b1..3bf494f91b 100644
--- a/src/openrct2/network/NetworkBase.cpp
+++ b/src/openrct2/network/NetworkBase.cpp
@@ -43,7 +43,7 @@
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
-#define NETWORK_STREAM_VERSION "12"
+#define NETWORK_STREAM_VERSION "13"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp
index a30fe38b1a..53d6421f68 100644
--- a/src/openrct2/park/ParkFile.cpp
+++ b/src/openrct2/park/ParkFile.cpp
@@ -536,7 +536,25 @@ namespace OpenRCT2
cs.ReadWrite(gGrassSceneryTileLoopPosition);
cs.ReadWrite(gWidePathTileLoopPosition);
- ReadWriteRideRatingCalculationData(cs, gRideRatingUpdateState);
+ auto& rideRatings = RideRatingGetUpdateStates();
+ if (os.GetHeader().TargetVersion >= 21)
+ {
+ cs.ReadWriteArray(rideRatings, [this, &cs](RideRatingUpdateState& calcData) {
+ ReadWriteRideRatingCalculationData(cs, calcData);
+ return true;
+ });
+ }
+ else
+ {
+ // Only single state was stored prior to version 20.
+ if (os.GetMode() == OrcaStream::Mode::READING)
+ {
+ // Since we read only one state ensure the rest is reset.
+ RideRatingResetUpdateStates();
+ }
+ auto& rideRatingUpdateState = rideRatings[0];
+ ReadWriteRideRatingCalculationData(cs, rideRatingUpdateState);
+ }
if (os.GetHeader().TargetVersion >= 14)
{
diff --git a/src/openrct2/park/ParkFile.h b/src/openrct2/park/ParkFile.h
index 4c7e1bc042..69fe23f2b5 100644
--- a/src/openrct2/park/ParkFile.h
+++ b/src/openrct2/park/ParkFile.h
@@ -9,10 +9,10 @@ struct ObjectRepositoryItem;
namespace OpenRCT2
{
// Current version that is saved.
- constexpr uint32_t PARK_FILE_CURRENT_VERSION = 20;
+ constexpr uint32_t PARK_FILE_CURRENT_VERSION = 21;
// The minimum version that is forwards compatible with the current version.
- constexpr uint32_t PARK_FILE_MIN_VERSION = 19;
+ constexpr uint32_t PARK_FILE_MIN_VERSION = 21;
// The minimum version that is backwards compatible with the current version.
// If this is increased beyond 0, uncomment the checks in ParkFile.cpp and Context.cpp!
diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp
index 76fe31644a..9ee12d3c7c 100644
--- a/src/openrct2/rct2/S6Importer.cpp
+++ b/src/openrct2/rct2/S6Importer.cpp
@@ -1097,7 +1097,10 @@ namespace RCT2
void ImportRideRatingsCalcData()
{
const auto& src = _s6.RideRatingsCalcData;
- auto& dst = gRideRatingUpdateState;
+ // S6 has only one state, ensure we reset all states before reading the first one.
+ RideRatingResetUpdateStates();
+ auto& rideRatingStates = RideRatingGetUpdateStates();
+ auto& dst = rideRatingStates[0];
dst = {};
dst.Proximity = { src.ProximityX, src.ProximityY, src.ProximityZ };
dst.ProximityStart = { src.ProximityStartX, src.ProximityStartY, src.ProximityStartZ };
diff --git a/src/openrct2/ride/RideRatings.cpp b/src/openrct2/ride/RideRatings.cpp
index c6afa920a3..4ccdeb90f3 100644
--- a/src/openrct2/ride/RideRatings.cpp
+++ b/src/openrct2/ride/RideRatings.cpp
@@ -77,7 +77,12 @@ struct ShelteredEights
uint8_t TotalShelteredEighths;
};
-RideRatingUpdateState gRideRatingUpdateState;
+static RideRatingUpdateStates gRideRatingUpdateStates;
+
+// Amount of updates allowed per updating state on the current tick.
+// The total amount would be MaxRideRatingSubSteps * RideRatingMaxUpdateStates which
+// would be currently 80, this is the worst case of sub-steps and may break out earlier.
+static constexpr size_t MaxRideRatingUpdateSubSteps = 20;
static void ride_ratings_update_state(RideRatingUpdateState& state);
static void ride_ratings_update_state_0(RideRatingUpdateState& state);
@@ -90,9 +95,21 @@ static void ride_ratings_begin_proximity_loop(RideRatingUpdateState& state);
static void RideRatingsCalculate(RideRatingUpdateState& state, Ride& ride);
static void RideRatingsCalculateValue(Ride& ride);
static void ride_ratings_score_close_proximity(RideRatingUpdateState& state, TileElement* inputTileElement);
-
static void ride_ratings_add(RatingTuple* rating, int32_t excitement, int32_t intensity, int32_t nausea);
+RideRatingUpdateStates& RideRatingGetUpdateStates()
+{
+ return gRideRatingUpdateStates;
+}
+
+void RideRatingResetUpdateStates()
+{
+ RideRatingUpdateState nullState{};
+ nullState.State = RIDE_RATINGS_STATE_FIND_NEXT_RIDE;
+
+ std::fill(gRideRatingUpdateStates.begin(), gRideRatingUpdateStates.end(), nullState);
+}
+
/**
* This is a small hack function to keep calling the ride rating processor until
* the given ride's ratings have been calculated. Whatever is currently being
@@ -124,9 +141,17 @@ void RideRatingsUpdateAll()
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
return;
- // NOTE: With the new save format more than one ride can be updated at once, but this has not yet been implemented.
- // The SV6 format could store only a single state.
- ride_ratings_update_state(gRideRatingUpdateState);
+ for (auto& updateState : gRideRatingUpdateStates)
+ {
+ for (size_t i = 0; i < MaxRideRatingUpdateSubSteps; ++i)
+ {
+ ride_ratings_update_state(updateState);
+
+ // We need to abort the loop if the state machine requested to find the next ride.
+ if (updateState.State == RIDE_RATINGS_STATE_FIND_NEXT_RIDE)
+ break;
+ }
+ }
}
static void ride_ratings_update_state(RideRatingUpdateState& state)
@@ -154,6 +179,42 @@ static void ride_ratings_update_state(RideRatingUpdateState& state)
}
}
+static bool RideRatingIsUpdatingRide(RideId id)
+{
+ return std::any_of(gRideRatingUpdateStates.begin(), gRideRatingUpdateStates.end(), [id](auto& state) {
+ return state.CurrentRide == id && state.State != RIDE_RATINGS_STATE_FIND_NEXT_RIDE;
+ });
+}
+
+static bool ShouldSkipRatingCalculation(const Ride& ride)
+{
+ // Skip anything that isn't a real ride.
+ if (ride.GetClassification() != RideClassification::Ride)
+ {
+ return true;
+ }
+
+ // Skip rides that are closed.
+ if (ride.status == RideStatus::Closed)
+ {
+ return true;
+ }
+
+ // Skip anything that is already updating.
+ if (RideRatingIsUpdatingRide(ride.id))
+ {
+ return true;
+ }
+
+ // Skip rides that have a fixed rating.
+ if (ride.lifecycle_flags & RIDE_LIFECYCLE_FIXED_RATINGS)
+ {
+ return true;
+ }
+
+ return false;
+}
+
static RideId GetNextRideToUpdate(RideId currentRide)
{
auto rm = GetRideManager();
@@ -161,14 +222,31 @@ static RideId GetNextRideToUpdate(RideId currentRide)
{
return RideId::GetNull();
}
- // Skip all empty ride ids
- auto nextRide = std::next(rm.get(currentRide));
- // If at end, loop around
- if (nextRide == rm.end())
+
+ auto it = rm.get(currentRide);
+ if (it == rm.end())
{
- nextRide = rm.begin();
+ // Start at the beginning, ride is missing.
+ it = rm.begin();
}
- return (*nextRide).id;
+ else
+ {
+ it = std::next(it);
+ }
+
+ // Filter out rides to avoid wasting a tick to find the next ride.
+ while (it != rm.end() && ShouldSkipRatingCalculation(*it))
+ {
+ it++;
+ }
+
+ // If we reached the end of the list we start over,
+ // in case the next ride doesn't pass the filter function it will
+ // look for the next matching ride in the next tick.
+ if (it == rm.end())
+ it = rm.begin();
+
+ return (*it).id;
}
/**
@@ -185,11 +263,11 @@ static void ride_ratings_update_state_0(RideRatingUpdateState& state)
state.CurrentRide = {};
}
- auto nextRideId = GetNextRideToUpdate(state.CurrentRide);
- auto nextRide = GetRide(nextRideId);
- if (nextRide != nullptr && nextRide->status != RideStatus::Closed
- && !(nextRide->lifecycle_flags & RIDE_LIFECYCLE_FIXED_RATINGS))
+ const auto nextRideId = GetNextRideToUpdate(state.CurrentRide);
+ const auto* nextRide = GetRide(nextRideId);
+ if (nextRide != nullptr && !ShouldSkipRatingCalculation(*nextRide))
{
+ Guard::Assert(!RideRatingIsUpdatingRide(nextRideId));
state.State = RIDE_RATINGS_STATE_INITIALISE;
}
state.CurrentRide = nextRideId;
diff --git a/src/openrct2/ride/RideRatings.h b/src/openrct2/ride/RideRatings.h
index 7ca932121d..3ed0481744 100644
--- a/src/openrct2/ride/RideRatings.h
+++ b/src/openrct2/ride/RideRatings.h
@@ -54,7 +54,12 @@ struct RideRatingUpdateState
uint16_t StationFlags;
};
-extern RideRatingUpdateState gRideRatingUpdateState;
+static constexpr size_t RideRatingMaxUpdateStates = 4;
+
+using RideRatingUpdateStates = std::array;
+
+RideRatingUpdateStates& RideRatingGetUpdateStates();
+void RideRatingResetUpdateStates();
void RideRatingsUpdateRide(const Ride& ride);
void RideRatingsUpdateAll();