1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-23 07:43:01 +01:00

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
This commit is contained in:
Matt
2025-10-17 00:05:30 +03:00
committed by GitHub
parent f3f97018cb
commit 0630cc6f71
9 changed files with 41 additions and 30 deletions

View File

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

View File

@@ -356,7 +356,7 @@ namespace OpenRCT2::Ui::Windows
private:
void UpdatePreview()
{
TrackDesignDrawPreview(*_trackDesign, _trackDesignPreviewPixels.data());
TrackDesignDrawPreview(*_trackDesign, _trackDesignPreviewPixels.data(), !gTrackDesignSceneryToggle);
}
void InstallTrackDesign()

View File

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

View File

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

View File

@@ -67,7 +67,6 @@ namespace OpenRCT2::GameActions
auto* entry = OpenRCT2::ObjectManager::GetObjectEntry<SmallSceneryEntry>(_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);
}

View File

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

View File

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

View File

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

View File

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