diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 9af2e8d1c3..1348b92e2e 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -389,7 +389,7 @@ int32_t game_do_command_p( flags = *ebx; auto* replayManager = GetContext()->GetReplayManager(); - if (replayManager != nullptr && replayManager->IsReplaying()) + if (replayManager->IsReplaying()) { // We only accept replay commands as long the replay is active. if ((flags & GAME_COMMAND_FLAG_REPLAY) == 0) diff --git a/src/openrct2/ReplayManager.cpp b/src/openrct2/ReplayManager.cpp index fc824bb342..5e567fab1d 100644 --- a/src/openrct2/ReplayManager.cpp +++ b/src/openrct2/ReplayManager.cpp @@ -23,6 +23,7 @@ #include "rct2/S6Exporter.h" #include "world/Park.h" +#include #include namespace OpenRCT2 @@ -146,16 +147,7 @@ namespace OpenRCT2 if (_currentRecording == nullptr) return; - MemoryStream stream; - DataSerialiser dsOut(true, stream); - action->Serialise(dsOut); - - std::unique_ptr ga = GameActions::Create(action->GetType()); - ga->SetCallback(action->GetCallback()); - - stream.SetPosition(0); - DataSerialiser dsIn(false, stream); - ga->Serialise(dsIn); + auto ga = GameActions::Clone(action); _currentRecording->commands.emplace(gCurrentTicks, std::move(ga), _commandId++); @@ -202,6 +194,8 @@ namespace OpenRCT2 else if (_mode == ReplayMode::PLAYING) { #ifndef DISABLE_NETWORK + // If the network is disabled we will only get a dummy hash which will cause + // false positives during replay. CheckState(); #endif ReplayCommands(); @@ -230,7 +224,7 @@ namespace OpenRCT2 } } - virtual bool StartRecording(const std::string& name, uint32_t maxTicks /*= 0xFFFFFFFF*/) override + virtual bool StartRecording(const std::string& name, uint32_t maxTicks /*= k_MaxReplayTicks*/) override { if (_mode != ReplayMode::NONE && _mode != ReplayMode::NORMALISATION) return false; @@ -238,10 +232,10 @@ namespace OpenRCT2 auto replayData = std::make_unique(); replayData->name = name; replayData->tickStart = gCurrentTicks; - if (maxTicks != 0xFFFFFFFF) + if (maxTicks != k_MaxReplayTicks) replayData->tickEnd = gCurrentTicks + maxTicks; else - replayData->tickEnd = 0xFFFFFFFF; + replayData->tickEnd = k_MaxReplayTicks; auto context = GetContext(); auto& objManager = context->GetObjectManager(); @@ -253,31 +247,10 @@ namespace OpenRCT2 s6exporter->SaveGame(&replayData->parkData); replayData->spriteSpatialData.Write(gSpriteSpatialIndex, sizeof(gSpriteSpatialIndex)); + replayData->timeRecorded = std::chrono::seconds(std::time(nullptr)).count(); DataSerialiser parkParams(true, replayData->parkParams); - parkParams << _guestGenerationProbability; - parkParams << _suggestedGuestMaximum; - parkParams << gCheatsSandboxMode; - parkParams << gCheatsDisableClearanceChecks; - parkParams << gCheatsDisableSupportLimits; - parkParams << gCheatsDisableTrainLengthLimit; - parkParams << gCheatsEnableChainLiftOnAllTrack; - parkParams << gCheatsShowAllOperatingModes; - parkParams << gCheatsShowVehiclesFromOtherTrackTypes; - parkParams << gCheatsFastLiftHill; - parkParams << gCheatsDisableBrakesFailure; - parkParams << gCheatsDisableAllBreakdowns; - parkParams << gCheatsBuildInPauseMode; - parkParams << gCheatsIgnoreRideIntensity; - parkParams << gCheatsDisableVandalism; - parkParams << gCheatsDisableLittering; - parkParams << gCheatsNeverendingMarketing; - parkParams << gCheatsFreezeWeather; - parkParams << gCheatsDisablePlantAging; - parkParams << gCheatsAllowArbitraryRideTypeChanges; - parkParams << gCheatsDisableRideValueAging; - parkParams << gConfigGeneral.show_real_names_of_guests; - parkParams << gCheatsIgnoreResearchStatus; + SerialiseParkParameters(parkParams); if (_mode != ReplayMode::NORMALISATION) _mode = ReplayMode::RECORDING; @@ -304,6 +277,8 @@ namespace OpenRCT2 std::string outPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::REPLAY); std::string outFile = Path::Combine(outPath, replayName); + bool result = true; + FILE* fp = fopen(outFile.c_str(), "wb"); if (fp) { @@ -311,6 +286,13 @@ namespace OpenRCT2 fwrite(stream.GetData(), 1, stream.GetLength(), fp); fclose(fp); + + result = true; + } + else + { + log_error("Unable to write to file '%s'", outFile.c_str()); + result = false; } // When normalizing the output we don't touch the mode. @@ -319,7 +301,7 @@ namespace OpenRCT2 _currentRecording.reset(); - return true; + return result; } virtual bool StartPlayback(const std::string& file) override @@ -393,7 +375,7 @@ namespace OpenRCT2 return false; } - if (StartRecording(outFile, 0xFFFFFFFF) == false) + if (StartRecording(outFile, k_MaxReplayTicks) == false) { StopPlayback(); return false; @@ -434,8 +416,10 @@ namespace OpenRCT2 bool TranslateDeprecatedGameCommands(ReplayRecordData& data) { - for (auto&& replayCommand : data.commands) + for (auto it = data.commands.begin(); it != data.commands.end(); it++) { + const ReplayCommand& replayCommand = *it; + if (replayCommand.action == nullptr) { // Check if we can create a game action with the command id. @@ -444,13 +428,18 @@ namespace OpenRCT2 { // Convert ReplayCommand converted; + converted.commandIndex = replayCommand.commandIndex; + if (!ConvertDeprecatedGameCommand(replayCommand, converted)) { return false; } - data.commands.erase(replayCommand); - data.commands.emplace(std::move(converted)); + // Remove deprecated command. + data.commands.erase(it); + + // Insert new game action, iterator points to the replaced element. + it = data.commands.emplace(std::move(converted)); } } } @@ -474,38 +463,22 @@ namespace OpenRCT2 sprite_position_tween_reset(); + Guard::Assert(sizeof(gSpriteSpatialIndex) >= data.spriteSpatialData.GetLength()); + + // In case the sprite limit will be increased we keep the unused fields cleared. + std::fill_n(gSpriteSpatialIndex, std::size(gSpriteSpatialIndex), SPRITE_INDEX_NULL); std::memcpy(gSpriteSpatialIndex, data.spriteSpatialData.GetData(), data.spriteSpatialData.GetLength()); + // Load all map global variables. DataSerialiser parkParams(false, data.parkParams); - parkParams << _guestGenerationProbability; - parkParams << _suggestedGuestMaximum; - parkParams << gCheatsSandboxMode; - parkParams << gCheatsDisableClearanceChecks; - parkParams << gCheatsDisableSupportLimits; - parkParams << gCheatsDisableTrainLengthLimit; - parkParams << gCheatsEnableChainLiftOnAllTrack; - parkParams << gCheatsShowAllOperatingModes; - parkParams << gCheatsShowVehiclesFromOtherTrackTypes; - parkParams << gCheatsFastLiftHill; - parkParams << gCheatsDisableBrakesFailure; - parkParams << gCheatsDisableAllBreakdowns; - parkParams << gCheatsBuildInPauseMode; - parkParams << gCheatsIgnoreRideIntensity; - parkParams << gCheatsDisableVandalism; - parkParams << gCheatsDisableLittering; - parkParams << gCheatsNeverendingMarketing; - parkParams << gCheatsFreezeWeather; - parkParams << gCheatsDisablePlantAging; - parkParams << gCheatsAllowArbitraryRideTypeChanges; - parkParams << gCheatsDisableRideValueAging; - parkParams << gConfigGeneral.show_real_names_of_guests; - parkParams << gCheatsIgnoreResearchStatus; + SerialiseParkParameters(parkParams); game_load_init(); fix_invalid_vehicle_sprite_sizes(); } - catch (const std::exception&) + catch (const std::exception& ex) { + log_error("Exception: %s", ex.what()); return false; } return true; @@ -520,10 +493,10 @@ namespace OpenRCT2 char buffer[128]; while (feof(fp) == false) { - size_t read = fread(buffer, 1, 128, fp); - if (read == 0) + size_t numBytesRead = fread(buffer, 1, 128, fp); + if (numBytesRead == 0) break; - stream.Write(buffer, read); + stream.Write(buffer, numBytesRead); } fclose(fp); @@ -571,6 +544,35 @@ namespace OpenRCT2 return true; } + bool SerialiseParkParameters(DataSerialiser& serialiser) + { + serialiser << _guestGenerationProbability; + serialiser << _suggestedGuestMaximum; + serialiser << gCheatsSandboxMode; + serialiser << gCheatsDisableClearanceChecks; + serialiser << gCheatsDisableSupportLimits; + serialiser << gCheatsDisableTrainLengthLimit; + serialiser << gCheatsEnableChainLiftOnAllTrack; + serialiser << gCheatsShowAllOperatingModes; + serialiser << gCheatsShowVehiclesFromOtherTrackTypes; + serialiser << gCheatsFastLiftHill; + serialiser << gCheatsDisableBrakesFailure; + serialiser << gCheatsDisableAllBreakdowns; + serialiser << gCheatsBuildInPauseMode; + serialiser << gCheatsIgnoreRideIntensity; + serialiser << gCheatsDisableVandalism; + serialiser << gCheatsDisableLittering; + serialiser << gCheatsNeverendingMarketing; + serialiser << gCheatsFreezeWeather; + serialiser << gCheatsDisablePlantAging; + serialiser << gCheatsAllowArbitraryRideTypeChanges; + serialiser << gCheatsDisableRideValueAging; + serialiser << gConfigGeneral.show_real_names_of_guests; + serialiser << gCheatsIgnoreResearchStatus; + + return true; + } + bool SerialiseCommand(DataSerialiser& serialiser, ReplayCommand& command) { serialiser << command.tick; @@ -673,7 +675,7 @@ namespace OpenRCT2 if (_currentReplay->checksums[checksumIndex].first == gCurrentTicks) { rct_sprite_checksum checksum = sprite_checksum(); - if (savedChecksum.second.ToString() != checksum.ToString()) + if (savedChecksum.second.raw != checksum.raw) { // Detected different game state. log_verbose( @@ -724,8 +726,6 @@ namespace OpenRCT2 GameAction* action = command.action.get(); action->SetFlags(action->GetFlags() | GAME_COMMAND_FLAG_REPLAY); - Guard::Assert(action != nullptr); - GameActionResult::Ptr result = GameActions::Execute(action); if (result->Error == GA_ERROR::OK) { @@ -744,7 +744,7 @@ namespace OpenRCT2 } // Focus camera on event. - if (isPositionValid && static_cast(gCommandPosition.x) != 0x8000) + if (isPositionValid && gCommandPosition.x != LOCATION_NULL) { auto* mainWindow = window_get_main(); if (mainWindow != nullptr) diff --git a/src/openrct2/ReplayManager.h b/src/openrct2/ReplayManager.h index 8714d30ca4..760ae6fa91 100644 --- a/src/openrct2/ReplayManager.h +++ b/src/openrct2/ReplayManager.h @@ -19,6 +19,8 @@ struct GameAction; namespace OpenRCT2 { + static constexpr uint32_t k_MaxReplayTicks = 0xFFFFFFFF; + interface IReplayManager { public: @@ -37,7 +39,7 @@ namespace OpenRCT2 = 0; virtual void AddGameAction(uint32_t tick, const GameAction* action) = 0; - virtual bool StartRecording(const std::string& name, uint32_t maxTicks = 0xFFFFFFFF) = 0; + virtual bool StartRecording(const std::string& name, uint32_t maxTicks = k_MaxReplayTicks) = 0; virtual bool StopRecording() = 0; virtual bool StartPlayback(const std::string& file) = 0; diff --git a/src/openrct2/actions/GameAction.cpp b/src/openrct2/actions/GameAction.cpp index 665e1f9a3c..3d523e06f9 100644 --- a/src/openrct2/actions/GameAction.cpp +++ b/src/openrct2/actions/GameAction.cpp @@ -94,6 +94,25 @@ namespace GameActions return std::unique_ptr(result); } + GameAction::Ptr Clone(const GameAction* action) + { + std::unique_ptr ga = GameActions::Create(action->GetType()); + ga->SetCallback(action->GetCallback()); + + // Serialise action data into stream. + DataSerialiser dsOut(true); + action->Serialise(dsOut); + + // Serialise into new action. + MemoryStream& stream = dsOut.GetStream(); + stream.SetPosition(0); + + DataSerialiser dsIn(false, stream); + ga->Serialise(dsIn); + + return ga; + } + static bool CheckActionInPausedMode(uint32_t actionFlags) { if (gGamePaused == 0) diff --git a/src/openrct2/actions/GameAction.h b/src/openrct2/actions/GameAction.h index fc93403510..5cecd7b41a 100644 --- a/src/openrct2/actions/GameAction.h +++ b/src/openrct2/actions/GameAction.h @@ -243,6 +243,7 @@ namespace GameActions void Register(); bool IsValidId(uint32_t id); GameAction::Ptr Create(uint32_t id); + GameAction::Ptr Clone(const GameAction* action); GameActionResult::Ptr Query(const GameAction* action); GameActionResult::Ptr Execute(const GameAction* action); GameActionFactory Register(uint32_t id, GameActionFactory action); diff --git a/src/openrct2/interface/InteractiveConsole.cpp b/src/openrct2/interface/InteractiveConsole.cpp index 13bc693e9e..e5e0ef73ad 100644 --- a/src/openrct2/interface/InteractiveConsole.cpp +++ b/src/openrct2/interface/InteractiveConsole.cpp @@ -1348,20 +1348,19 @@ static int32_t cc_replay_startrecord(InteractiveConsole& console, const utf8** a } std::string name = argv[0]; - uint32_t maxTicks = 0xFFFFFFFF; + + // If ticks are specified by user use that otherwise maximum ticks specified by const. + uint32_t maxTicks = OpenRCT2::k_MaxReplayTicks; if (argc >= 2) { maxTicks = atol(argv[1]); } auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); - if (replayManager != nullptr) + if (replayManager->StartRecording(name, maxTicks)) { - if (replayManager->StartRecording(name, maxTicks)) - { - console.WriteFormatLine("Replay recording start"); - return 1; - } + console.WriteFormatLine("Replay recording start"); + return 1; } return 0; @@ -1376,13 +1375,10 @@ static int32_t cc_replay_stoprecord(InteractiveConsole& console, const utf8** ar } auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); - if (replayManager != nullptr) + if (replayManager->StopRecording()) { - if (replayManager->StopRecording()) - { - console.WriteFormatLine("Replay recording stopped"); - return 1; - } + console.WriteFormatLine("Replay recording stopped"); + return 1; } return 0; @@ -1405,13 +1401,10 @@ static int32_t cc_replay_start(InteractiveConsole& console, const utf8** argv, i std::string name = argv[0]; auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); - if (replayManager != nullptr) + if (replayManager->StartPlayback(name)) { - if (replayManager->StartPlayback(name)) - { - console.WriteFormatLine("Started replay"); - return 1; - } + console.WriteFormatLine("Started replay"); + return 1; } return 0; @@ -1426,13 +1419,10 @@ static int32_t cc_replay_stop(InteractiveConsole& console, const utf8** argv, in } auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); - if (replayManager != nullptr) + if (replayManager->StopPlayback()) { - if (replayManager->StopPlayback()) - { - console.WriteFormatLine("Stopped replay"); - return 1; - } + console.WriteFormatLine("Stopped replay"); + return 1; } return 0; @@ -1456,13 +1446,10 @@ static int32_t cc_replay_normalise(InteractiveConsole& console, const utf8** arg std::string outputFile = argv[1]; auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); - if (replayManager != nullptr) + if (replayManager->NormaliseReplay(inputFile, outputFile)) { - if (replayManager->NormaliseReplay(inputFile, outputFile)) - { - console.WriteFormatLine("Stopped replay"); - return 1; - } + console.WriteFormatLine("Stopped replay"); + return 1; } return 0; diff --git a/src/openrct2/paint/Painter.cpp b/src/openrct2/paint/Painter.cpp index 2827c95e39..0f047c14ec 100644 --- a/src/openrct2/paint/Painter.cpp +++ b/src/openrct2/paint/Painter.cpp @@ -59,20 +59,17 @@ void Painter::Paint(IDrawingEngine& de) } auto* replayManager = GetContext()->GetReplayManager(); - if (replayManager != nullptr) - { - const char* text = nullptr; + const char* text = nullptr; - if (replayManager->IsReplaying()) - text = "Replaying..."; - else if (replayManager->IsRecording()) - text = "Recording..."; - else if (replayManager->IsNormalising()) - text = "Normalising..."; + if (replayManager->IsReplaying()) + text = "Replaying..."; + else if (replayManager->IsRecording()) + text = "Recording..."; + else if (replayManager->IsNormalising()) + text = "Normalising..."; - if (text != nullptr) - PaintReplayNotice(dpi, text); - } + if (text != nullptr) + PaintReplayNotice(dpi, text); if (gConfigGeneral.show_fps) {