From 0630cc6f711b28d4a34b43d04270ba9f3b7589cb Mon Sep 17 00:00:00 2001 From: Matt <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 17 Oct 2025 00:05:30 +0300 Subject: [PATCH] Fix #20486: Multiplayer desync when placing track designs without any scenery. (#25337) * Fix #20486: Placing track designs with scenery disabled desyncs * Remove pointless error logging, too much log spam * Don't entirely disable all scenery when one element is obstructed * Update changelog.txt * Bump up network version * clang-format --- distribution/changelog.txt | 2 ++ src/openrct2-ui/windows/InstallTrack.cpp | 2 +- src/openrct2-ui/windows/TrackDesignPlace.cpp | 16 ++++++---- src/openrct2-ui/windows/TrackList.cpp | 2 +- .../actions/SmallSceneryRemoveAction.cpp | 2 -- src/openrct2/actions/TrackDesignAction.cpp | 8 +++-- src/openrct2/actions/TrackDesignAction.h | 3 +- src/openrct2/ride/TrackDesign.cpp | 31 ++++++++++--------- src/openrct2/ride/TrackDesign.h | 5 +-- 9 files changed, 41 insertions(+), 30 deletions(-) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index dbcea55308..414306aac9 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,6 +1,8 @@ 0.4.28 (in development) ------------------------------------------------------------------------ - Change: [#25089] Peep actions and animations that cause them to stop moving no longer trigger when they are on a level crossing. +- Change: [#25337] Placing track designs with scenery that is obstructed no longer disables all of the scenery. +- Fix: [#20486] Multiplayer desync when placing track designs without any scenery. - Fix: [#22779, #25330] Incorrect queue paths in Nevermore Park and Six Flags Holland scenarios (bug in the original scenarios). - Fix: [#25190] Inserting a block brake while a coaster is simulating will cause the simulation to behave strangely. - Fix: [#25272] Text colour dropdown in the Banner window is too narrow, resulting in truncated labels. diff --git a/src/openrct2-ui/windows/InstallTrack.cpp b/src/openrct2-ui/windows/InstallTrack.cpp index 357204b6de..f3aa90db43 100644 --- a/src/openrct2-ui/windows/InstallTrack.cpp +++ b/src/openrct2-ui/windows/InstallTrack.cpp @@ -356,7 +356,7 @@ namespace OpenRCT2::Ui::Windows private: void UpdatePreview() { - TrackDesignDrawPreview(*_trackDesign, _trackDesignPreviewPixels.data()); + TrackDesignDrawPreview(*_trackDesign, _trackDesignPreviewPixels.data(), !gTrackDesignSceneryToggle); } void InstallTrackDesign() diff --git a/src/openrct2-ui/windows/TrackDesignPlace.cpp b/src/openrct2-ui/windows/TrackDesignPlace.cpp index 4cac2be601..70a780a9fb 100644 --- a/src/openrct2-ui/windows/TrackDesignPlace.cpp +++ b/src/openrct2-ui/windows/TrackDesignPlace.cpp @@ -203,7 +203,8 @@ namespace OpenRCT2::Ui::Windows if (trackLoc == _placementLoc) { TrackDesignPreviewDrawOutlines( - tds, *_trackDesign, RideGetTemporaryForPreview(), { mapCoords, 0, _currentTrackPieceDirection }); + tds, *_trackDesign, RideGetTemporaryForPreview(), { mapCoords, 0, _currentTrackPieceDirection }, + !gTrackDesignSceneryToggle); return; } @@ -216,7 +217,7 @@ namespace OpenRCT2::Ui::Windows if (res.Error == GameActions::Status::Ok) { // Valid location found. Place the ghost at the location. - auto tdAction = GameActions::TrackDesignAction(trackLoc, *_trackDesign); + auto tdAction = GameActions::TrackDesignAction(trackLoc, *_trackDesign, !gTrackDesignSceneryToggle); tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); tdAction.SetCallback([&](const GameActions::GameAction*, const GameActions::Result* result) { if (result->Error == GameActions::Status::Ok) @@ -240,7 +241,8 @@ namespace OpenRCT2::Ui::Windows invalidateWidget(WIDX_PRICE); } - TrackDesignPreviewDrawOutlines(tds, *_trackDesign, RideGetTemporaryForPreview(), trackLoc); + TrackDesignPreviewDrawOutlines( + tds, *_trackDesign, RideGetTemporaryForPreview(), trackLoc, !gTrackDesignSceneryToggle); } void onToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override @@ -285,7 +287,8 @@ namespace OpenRCT2::Ui::Windows return; } - auto tdAction = GameActions::TrackDesignAction({ trackLoc, _currentTrackPieceDirection }, *_trackDesign); + auto tdAction = GameActions::TrackDesignAction( + { trackLoc, _currentTrackPieceDirection }, *_trackDesign, !gTrackDesignSceneryToggle); tdAction.SetCallback([&](const GameActions::GameAction*, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) { @@ -381,7 +384,8 @@ namespace OpenRCT2::Ui::Windows { if (_hasPlacementGhost) { - auto tdAction = GameActions::TrackDesignAction({ _placementGhostLoc }, *_trackDesign); + auto tdAction = GameActions::TrackDesignAction( + { _placementGhostLoc }, *_trackDesign, !gTrackDesignSceneryToggle); tdAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); auto res = GameActions::Execute(&tdAction, getGameState()); if (res.Error != GameActions::Status::Ok) @@ -728,7 +732,7 @@ namespace OpenRCT2::Ui::Windows for (int32_t i = 0; i < 7; i++, loc.z += kCoordsZStep) { auto tdAction = GameActions::TrackDesignAction( - CoordsXYZD{ loc.x, loc.y, loc.z, _currentTrackPieceDirection }, *_trackDesign); + CoordsXYZD{ loc.x, loc.y, loc.z, _currentTrackPieceDirection }, *_trackDesign, !gTrackDesignSceneryToggle); tdAction.SetFlags(newFlags); res = GameActions::Query(&tdAction, getGameState()); diff --git a/src/openrct2-ui/windows/TrackList.cpp b/src/openrct2-ui/windows/TrackList.cpp index ebcff9bdca..7dca9b78d1 100644 --- a/src/openrct2-ui/windows/TrackList.cpp +++ b/src/openrct2-ui/windows/TrackList.cpp @@ -201,7 +201,7 @@ namespace OpenRCT2::Ui::Windows _loadedTrackDesign = TrackDesignImport(path.c_str()); if (_loadedTrackDesign != nullptr) { - TrackDesignDrawPreview(*_loadedTrackDesign, _trackDesignPreviewPixels.data()); + TrackDesignDrawPreview(*_loadedTrackDesign, _trackDesignPreviewPixels.data(), !gTrackDesignSceneryToggle); return true; } return false; diff --git a/src/openrct2/actions/SmallSceneryRemoveAction.cpp b/src/openrct2/actions/SmallSceneryRemoveAction.cpp index 0ffa5cba6e..5117a3cfd5 100644 --- a/src/openrct2/actions/SmallSceneryRemoveAction.cpp +++ b/src/openrct2/actions/SmallSceneryRemoveAction.cpp @@ -67,7 +67,6 @@ namespace OpenRCT2::GameActions auto* entry = OpenRCT2::ObjectManager::GetObjectEntry(_sceneryType); if (entry == nullptr) { - LOG_ERROR("Invalid small scenery type %u", _sceneryType); return Result(Status::InvalidParameters, STR_CANT_REMOVE_THIS, STR_INVALID_SELECTION_OF_OBJECTS); } @@ -103,7 +102,6 @@ namespace OpenRCT2::GameActions TileElement* tileElement = FindSceneryElement(); if (tileElement == nullptr) { - LOG_ERROR("Small scenery of type %u not found at x = %d, y = %d, z = &d", _sceneryType, _loc.x, _loc.y, _loc.z); return Result(Status::InvalidParameters, STR_CANT_REMOVE_THIS, STR_INVALID_SELECTION_OF_OBJECTS); } diff --git a/src/openrct2/actions/TrackDesignAction.cpp b/src/openrct2/actions/TrackDesignAction.cpp index c5d286584d..e602e8b5b7 100644 --- a/src/openrct2/actions/TrackDesignAction.cpp +++ b/src/openrct2/actions/TrackDesignAction.cpp @@ -27,9 +27,10 @@ namespace OpenRCT2::GameActions { - TrackDesignAction::TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td) + TrackDesignAction::TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td, bool placeScenery) : _loc(location) , _td(td) + , _placeScenery(placeScenery) { } @@ -50,6 +51,7 @@ namespace OpenRCT2::GameActions stream << DS_TAG(_loc); _td.Serialise(stream); + stream << DS_TAG(_placeScenery); } Result TrackDesignAction::Query(GameState_t& gameState) const @@ -94,7 +96,7 @@ namespace OpenRCT2::GameActions return Result(Status::Unknown, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_ERR_RIDE_NOT_FOUND); } - bool placeScenery = true; + bool placeScenery = _placeScenery; uint32_t flags = 0; if (GetFlags() & GAME_COMMAND_FLAG_GHOST) @@ -169,7 +171,7 @@ namespace OpenRCT2::GameActions // Query first, this is required again to determine if scenery is available. uint32_t flags = GetFlags() & ~GAME_COMMAND_FLAG_APPLY; - bool placeScenery = true; + bool placeScenery = _placeScenery; auto queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc); if (_trackDesignPlaceStateSceneryUnavailable) diff --git a/src/openrct2/actions/TrackDesignAction.h b/src/openrct2/actions/TrackDesignAction.h index aa68836a7b..1b5acfa888 100644 --- a/src/openrct2/actions/TrackDesignAction.h +++ b/src/openrct2/actions/TrackDesignAction.h @@ -19,10 +19,11 @@ namespace OpenRCT2::GameActions private: CoordsXYZD _loc; TrackDesign _td; + bool _placeScenery{ false }; public: TrackDesignAction() = default; - TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td); + TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td, bool placeScenery); void AcceptParameters(GameActionParameterVisitor&) final; diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index 5e81770772..cf6051d15c 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -340,7 +340,8 @@ ResultWithMessage TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, con } } - TrackDesignPreviewDrawOutlines(tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection }); + TrackDesignPreviewDrawOutlines( + tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection }, false); // Resave global vars for scenery reasons. tds.origin = startPos; @@ -454,7 +455,8 @@ ResultWithMessage TrackDesign::CreateTrackDesignMaze(TrackDesignState& tds, cons // Save global vars as they are still used by scenery???? int32_t startZ = tds.origin.z; - TrackDesignPreviewDrawOutlines(tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection }); + TrackDesignPreviewDrawOutlines( + tds, *this, RideGetTemporaryForPreview(), { 4096, 4096, 0, _currentTrackPieceDirection }, false); tds.origin = { startLoc.x, startLoc.y, startZ }; gMapSelectFlags.unset(MapSelectFlag::enableConstruct); @@ -1299,11 +1301,17 @@ static GameActions::Result TrackDesignPlaceAllScenery( auto placementRes = TrackDesignPlaceSceneryElement(tds, mapCoord, mode, scenery, rotation, origin.z); if (placementRes.Error != GameActions::Status::Ok) { - // Allow operation to fail when its removing ghosts. if (tds.placeOperation != TrackPlaceOperation::removeGhost) { + // Allow operation to fail when its removing ghosts. return placementRes; } + + if (placementRes.Error == GameActions::Status::NoClearance) + { + // Some scenery might be obstructed, don't abort the entire operation. + continue; + } } cost += placementRes.Cost; } @@ -1768,11 +1776,6 @@ static GameActions::Result TrackDesignPlaceVirtual( tds.previewMax = coords; tds.placeSceneryZ = 0; - if (gTrackDesignSceneryToggle) - { - tds.placeScenery = false; - } - // NOTE: We need to save this, in networked games this would affect all clients otherwise. auto savedRideId = _currentRideIndex; auto savedTrackPieceDirection = _currentTrackPieceDirection; @@ -1840,9 +1843,10 @@ void TrackDesignPreviewRemoveGhosts(const TrackDesign& td, Ride& ride, const Coo TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::removeGhost, true, ride, coords); } -void TrackDesignPreviewDrawOutlines(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords) +void TrackDesignPreviewDrawOutlines( + TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords, bool placeScenery) { - TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::drawOutlines, true, ride, coords); + TrackDesignPlaceVirtual(tds, td, TrackPlaceOperation::drawOutlines, placeScenery, ride, coords); } static int32_t TrackDesignGetZPlacement(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords) @@ -1888,7 +1892,7 @@ static money64 TrackDesignCreateRide(int32_t type, int32_t subType, int32_t flag * cost = edi */ static bool TrackDesignPlacePreview( - TrackDesignState& tds, const TrackDesign& td, Ride** outRide, TrackDesignGameStateData& gameStateData) + TrackDesignState& tds, const TrackDesign& td, Ride** outRide, TrackDesignGameStateData& gameStateData, bool placeScenery) { *outRide = nullptr; gameStateData.flags = 0; @@ -1948,7 +1952,6 @@ static bool TrackDesignPlacePreview( z += 16 - tds.placeSceneryZ; - bool placeScenery = true; if (_trackDesignPlaceStateSceneryUnavailable) { placeScenery = false; @@ -2073,7 +2076,7 @@ bool TrackDesignSceneryElement::operator!=(const TrackDesignSceneryElement& rhs) * * rct2: 0x006D1EF0 */ -void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels) +void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels, bool placeScenery) { StashMap(); TrackDesignPreviewClearMap(); @@ -2087,7 +2090,7 @@ void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels) Ride* ride; TrackDesignGameStateData updatedGameStateData = td.gameStateData; - if (!TrackDesignPlacePreview(tds, td, &ride, updatedGameStateData)) + if (!TrackDesignPlacePreview(tds, td, &ride, updatedGameStateData, !gTrackDesignSceneryToggle)) { std::fill_n(pixels, kTrackPreviewImageSize * 4, 0x00); UnstashMap(); diff --git a/src/openrct2/ride/TrackDesign.h b/src/openrct2/ride/TrackDesign.h index d4d327a510..9738492bbb 100644 --- a/src/openrct2/ride/TrackDesign.h +++ b/src/openrct2/ride/TrackDesign.h @@ -240,13 +240,14 @@ void TrackDesignMirror(TrackDesign& td); OpenRCT2::GameActions::Result TrackDesignPlace( const TrackDesign& td, uint32_t flags, bool placeScenery, Ride& ride, const CoordsXYZD& coords); void TrackDesignPreviewRemoveGhosts(const TrackDesign& td, Ride& ride, const CoordsXYZD& coords); -void TrackDesignPreviewDrawOutlines(TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords); +void TrackDesignPreviewDrawOutlines( + TrackDesignState& tds, const TrackDesign& td, Ride& ride, const CoordsXYZD& coords, bool placeScenery); int32_t TrackDesignGetZPlacement(const TrackDesign& td, Ride& ride, const CoordsXYZD& coords); /////////////////////////////////////////////////////////////////////////////// // Track design preview /////////////////////////////////////////////////////////////////////////////// -void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels); +void TrackDesignDrawPreview(TrackDesign& td, uint8_t* pixels, bool placeScenery); /////////////////////////////////////////////////////////////////////////////// // Track design saving