1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-13 19:12:32 +01:00

Add normalisation support to remove gaps.

This commit is contained in:
Matt
2018-12-11 08:13:47 +01:00
parent b6c2dca849
commit df0cc72887
5 changed files with 236 additions and 39 deletions

View File

@@ -485,13 +485,25 @@ int32_t game_do_command_p(
// Second call to actually perform the operation // Second call to actually perform the operation
new_game_command_table[command](eax, ebx, ecx, edx, esi, edi, ebp); new_game_command_table[command](eax, ebx, ecx, edx, esi, edi, ebp);
if (replayManager != nullptr && replayManager->IsRecording() && (flags & GAME_COMMAND_FLAG_APPLY) if (replayManager != nullptr)
&& (flags & GAME_COMMAND_FLAG_GHOST) == 0 && (flags & GAME_COMMAND_FLAG_5) == 0)
{ {
int32_t callback = game_command_callback_get_index(game_command_callback); bool recordCommand = false;
bool commandExecutes = (flags & GAME_COMMAND_FLAG_APPLY) && (flags & GAME_COMMAND_FLAG_GHOST) == 0
&& (flags & GAME_COMMAND_FLAG_5) == 0;
replayManager->AddGameCommand( if (replayManager->IsRecording() && commandExecutes)
gCurrentTicks, *eax, original_ebx, *ecx, original_edx, original_esi, original_edi, original_ebp, callback); recordCommand = true;
else if (replayManager->IsNormalising() && commandExecutes && (flags & GAME_COMMAND_FLAG_REPLAY) != 0)
recordCommand = true;
if (recordCommand)
{
int32_t callback = game_command_callback_get_index(game_command_callback);
replayManager->AddGameCommand(
gCurrentTicks, *eax, original_ebx, *ecx, original_edx, original_esi, original_edi, original_ebp,
callback);
}
} }
// Do the callback (required for multiplayer to work correctly), but only for top level commands // Do the callback (required for multiplayer to work correctly), but only for top level commands

View File

@@ -25,6 +25,8 @@
namespace OpenRCT2 namespace OpenRCT2
{ {
// NOTE: This is currently very close to what the network version uses.
// Should be refactored once the old game commands are gone.
struct ReplayCommand struct ReplayCommand
{ {
ReplayCommand() = default; ReplayCommand() = default;
@@ -75,10 +77,12 @@ namespace OpenRCT2
{ {
MemoryStream parkData; MemoryStream parkData;
std::string name; // Name of play std::string name; // Name of play
uint32_t timeRecorded; // Posix Time. uint64_t timeRecorded; // Posix Time.
uint32_t tickStart; // First tick of replay. uint32_t tickStart; // First tick of replay.
uint32_t tickEnd; // Last tick of replay. uint32_t tickEnd; // Last tick of replay.
std::multiset<ReplayCommand> commands; std::multiset<ReplayCommand> commands;
std::vector<std::pair<uint32_t, rct_sprite_checksum>> checksums;
int32_t checksumIndex;
}; };
class ReplayManager final : public IReplayManager class ReplayManager final : public IReplayManager
@@ -88,6 +92,7 @@ namespace OpenRCT2
NONE = 0, NONE = 0,
RECORDING, RECORDING,
PLAYING, PLAYING,
NORMALISATION,
}; };
public: public:
@@ -105,11 +110,16 @@ namespace OpenRCT2
return _mode == ReplayMode::RECORDING; return _mode == ReplayMode::RECORDING;
} }
virtual bool IsNormalising() const override
{
return _mode == ReplayMode::NORMALISATION;
}
virtual void AddGameCommand( virtual void AddGameCommand(
uint32_t tick, uint32_t eax, uint32_t ebx, uint32_t ecx, uint32_t edx, uint32_t esi, uint32_t edi, uint32_t ebp, uint32_t tick, uint32_t eax, uint32_t ebx, uint32_t ecx, uint32_t edx, uint32_t esi, uint32_t edi, uint32_t ebp,
uint8_t callback) override uint8_t callback) override
{ {
if (_current == nullptr) if (_currentRecording == nullptr)
return; return;
uint32_t args[7]; uint32_t args[7];
@@ -121,12 +131,15 @@ namespace OpenRCT2
args[5] = edi; args[5] = edi;
args[6] = ebp; args[6] = ebp;
_current->commands.emplace(gCurrentTicks, args, callback, _commandId++); _currentRecording->commands.emplace(gCurrentTicks, args, callback, _commandId++);
// Force a checksum record the next tick.
_nextChecksumTick = tick + 1;
} }
virtual void AddGameAction(uint32_t tick, const GameAction* action) override virtual void AddGameAction(uint32_t tick, const GameAction* action) override
{ {
if (_current == nullptr) if (_currentRecording == nullptr)
return; return;
MemoryStream stream; MemoryStream stream;
@@ -140,17 +153,35 @@ namespace OpenRCT2
DataSerialiser dsIn(false, stream); DataSerialiser dsIn(false, stream);
ga->Serialise(dsIn); ga->Serialise(dsIn);
_current->commands.emplace(gCurrentTicks, std::move(ga), _commandId++); _currentRecording->commands.emplace(gCurrentTicks, std::move(ga), _commandId++);
// Force a checksum record the next tick.
_nextChecksumTick = tick + 1;
} }
void AddChecksum(uint32_t tick, rct_sprite_checksum&& checksum)
{
_currentRecording->checksums.emplace_back(std::make_pair(tick, checksum));
}
// Function runs each Tick.
virtual void Update() override virtual void Update() override
{ {
if (_mode == ReplayMode::NONE) if (_mode == ReplayMode::NONE)
return; return;
if ((_mode == ReplayMode::RECORDING || _mode == ReplayMode::NORMALISATION) && gCurrentTicks == _nextChecksumTick)
{
rct_sprite_checksum checksum = sprite_checksum();
AddChecksum(gCurrentTicks, std::move(checksum));
// Reset.
_nextChecksumTick = 0;
}
if (_mode == ReplayMode::RECORDING) if (_mode == ReplayMode::RECORDING)
{ {
if (gCurrentTicks >= _current->tickEnd) if (gCurrentTicks >= _currentRecording->tickEnd)
{ {
StopRecording(); StopRecording();
return; return;
@@ -158,13 +189,36 @@ namespace OpenRCT2
} }
else if (_mode == ReplayMode::PLAYING) else if (_mode == ReplayMode::PLAYING)
{ {
CheckState();
ReplayCommands(); ReplayCommands();
// If we run out of commands we can stop the replay and checked all checksums we can stop.
if (_currentReplay->commands.empty() && _currentReplay->checksumIndex >= _currentReplay->checksums.size())
{
StopPlayback();
return;
}
}
else if (_mode == ReplayMode::NORMALISATION)
{
ReplayCommands();
// If we run out of commands we can just stop
if (_currentReplay->commands.empty() && _nextChecksumTick == 0)
{
StopPlayback();
StopRecording();
// Reset mode, in normalisation nothing will set it.
_mode = ReplayMode::NONE;
return;
}
} }
} }
virtual bool StartRecording(const std::string& name, uint32_t maxTicks /*= 0xFFFFFFFF*/) override virtual bool StartRecording(const std::string& name, uint32_t maxTicks /*= 0xFFFFFFFF*/) override
{ {
if (_mode != ReplayMode::NONE) if (_mode != ReplayMode::NONE && _mode != ReplayMode::NORMALISATION)
return false; return false;
auto replayData = std::make_unique<ReplayRecordData>(); auto replayData = std::make_unique<ReplayRecordData>();
@@ -184,22 +238,26 @@ namespace OpenRCT2
s6exporter->Export(); s6exporter->Export();
s6exporter->SaveGame(&replayData->parkData); s6exporter->SaveGame(&replayData->parkData);
_mode = ReplayMode::RECORDING; if (_mode != ReplayMode::NORMALISATION)
_current = std::move(replayData); _mode = ReplayMode::RECORDING;
_currentRecording = std::move(replayData);
return true; return true;
} }
virtual bool StopRecording() override virtual bool StopRecording() override
{ {
if (_mode != ReplayMode::RECORDING) if (_mode != ReplayMode::RECORDING && _mode != ReplayMode::NORMALISATION)
return false; return false;
_currentRecording->tickEnd = gCurrentTicks;
DataSerialiser serialiser(true); DataSerialiser serialiser(true);
Serialise(serialiser, *_current); Serialise(serialiser, *_currentRecording);
char replayName[512] = {}; char replayName[512] = {};
snprintf(replayName, sizeof(replayName), "replay_%s_%d.sv6r", _current->name.c_str(), 0); snprintf(replayName, sizeof(replayName), "%s.sv6r", _currentRecording->name.c_str());
std::string outPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::REPLAY); std::string outPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::REPLAY);
std::string outFile = Path::Combine(outPath, replayName); std::string outFile = Path::Combine(outPath, replayName);
@@ -213,14 +271,20 @@ namespace OpenRCT2
fclose(fp); fclose(fp);
} }
//_current.reset(); // When normalizing the output we don't touch the mode.
_mode = ReplayMode::NONE; if (_mode != ReplayMode::NORMALISATION)
_mode = ReplayMode::NONE;
_currentRecording.reset();
return true; return true;
} }
virtual bool StartPlayback(const std::string& file) override virtual bool StartPlayback(const std::string& file) override
{ {
if (_mode != ReplayMode::NONE && _mode != ReplayMode::NORMALISATION)
return false;
auto replayData = std::make_unique<ReplayRecordData>(); auto replayData = std::make_unique<ReplayRecordData>();
if (!ReadReplayData(file, *replayData)) if (!ReadReplayData(file, *replayData))
@@ -235,19 +299,47 @@ namespace OpenRCT2
gCurrentTicks = replayData->tickStart; gCurrentTicks = replayData->tickStart;
_current = std::move(replayData); _currentReplay = std::move(replayData);
_mode = ReplayMode::PLAYING; _currentReplay->checksumIndex = 0;
if (_mode != ReplayMode::NORMALISATION)
_mode = ReplayMode::PLAYING;
return true; return true;
} }
virtual bool StopPlayback() override virtual bool StopPlayback() override
{ {
if (_mode != ReplayMode::PLAYING) if (_mode != ReplayMode::PLAYING && _mode != ReplayMode::NORMALISATION)
return false; return false;
_current.reset(); // When normalizing the output we don't touch the mode.
_mode = ReplayMode::NONE; if (_mode != ReplayMode::NORMALISATION)
{
_mode = ReplayMode::NONE;
}
_currentReplay.reset();
return true;
}
virtual bool NormaliseReplay(const std::string& file, const std::string& outFile) override
{
_mode = ReplayMode::NORMALISATION;
if (StartPlayback(file) == false)
{
return false;
}
if (StartRecording(outFile, 0xFFFFFFFF) == false)
{
StopPlayback();
return false;
}
_nextReplayTick = gCurrentTicks + 1;
return true; return true;
} }
@@ -310,7 +402,7 @@ namespace OpenRCT2
} }
std::string outPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::REPLAY); std::string outPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::REPLAY);
std::string outFile = Path::Combine(outPath, file); std::string outFile = Path::Combine(outPath, fileName);
bool loaded = false; bool loaded = false;
if (ReadReplayFromFile(outFile, stream)) if (ReadReplayFromFile(outFile, stream))
@@ -405,25 +497,72 @@ namespace OpenRCT2
} }
} }
uint32_t countChecksums = (uint32_t)data.checksums.size();
serialiser << countChecksums;
if (serialiser.IsLoading())
{
data.checksums.resize(countChecksums);
}
for (uint32_t i = 0; i < countChecksums; i++)
{
serialiser << data.checksums[i].first;
serialiser << data.checksums[i].second.raw;
}
return true; return true;
} }
void CheckState()
{
int32_t checksumIndex = _currentReplay->checksumIndex;
if (checksumIndex >= _currentReplay->checksums.size())
return;
const auto& savedChecksum = _currentReplay->checksums[checksumIndex];
if (_currentReplay->checksums[checksumIndex].first == gCurrentTicks)
{
rct_sprite_checksum checksum = sprite_checksum();
if (savedChecksum.second.ToString() != checksum.ToString())
{
// Detected different game state.
log_info(
"Different sprite checksum at tick %u ; Saved: %s, Current: %s", gCurrentTicks,
savedChecksum.second.ToString().c_str(), checksum.ToString().c_str());
}
else
{
// Good state.
log_info(
"Good state at tick %u ; Saved: %s, Current: %s", gCurrentTicks,
savedChecksum.second.ToString().c_str(), checksum.ToString().c_str());
}
_currentReplay->checksumIndex++;
}
}
void ReplayCommands() void ReplayCommands()
{ {
auto& replayQueue = _current->commands; auto& replayQueue = _currentReplay->commands;
// If we run out of commands we can stop the replay.
if (replayQueue.empty())
{
StopPlayback();
return;
}
while (replayQueue.begin() != replayQueue.end()) while (replayQueue.begin() != replayQueue.end())
{ {
const ReplayCommand& command = (*replayQueue.begin()); const ReplayCommand& command = (*replayQueue.begin());
if (command.tick != gCurrentTicks)
break; if (_mode == ReplayMode::PLAYING)
{
// If this is a normal playback wait for the correct tick.
if (command.tick != gCurrentTicks)
break;
}
else if (_mode == ReplayMode::NORMALISATION)
{
if (gCurrentTicks != _nextReplayTick)
break;
_nextReplayTick = gCurrentTicks + 1;
}
if (command.action != nullptr) if (command.action != nullptr)
{ {
@@ -446,8 +585,11 @@ namespace OpenRCT2
private: private:
ReplayMode _mode = ReplayMode::NONE; ReplayMode _mode = ReplayMode::NONE;
std::unique_ptr<ReplayRecordData> _current; std::unique_ptr<ReplayRecordData> _currentRecording;
std::unique_ptr<ReplayRecordData> _currentReplay;
uint32_t _commandId = 0; uint32_t _commandId = 0;
uint32_t _nextChecksumTick = 0;
uint32_t _nextReplayTick = 0;
}; };
std::unique_ptr<IReplayManager> CreateReplayManager() std::unique_ptr<IReplayManager> CreateReplayManager()

View File

@@ -25,9 +25,10 @@ namespace OpenRCT2
virtual ~IReplayManager() = default; virtual ~IReplayManager() = default;
virtual void Update() = 0; virtual void Update() = 0;
virtual bool IsReplaying() const = 0;
virtual bool IsReplaying() const = 0;
virtual bool IsRecording() const = 0; virtual bool IsRecording() const = 0;
virtual bool IsNormalising() const = 0;
// NOTE: Will become obsolete eventually once all game actions are done. // NOTE: Will become obsolete eventually once all game actions are done.
virtual void AddGameCommand( virtual void AddGameCommand(
@@ -41,6 +42,8 @@ namespace OpenRCT2
virtual bool StartPlayback(const std::string& file) = 0; virtual bool StartPlayback(const std::string& file) = 0;
virtual bool StopPlayback() = 0; virtual bool StopPlayback() = 0;
virtual bool NormaliseReplay(const std::string& inputFile, const std::string& outputFile) = 0;
}; };
std::unique_ptr<IReplayManager> CreateReplayManager(); std::unique_ptr<IReplayManager> CreateReplayManager();

View File

@@ -201,7 +201,7 @@ namespace GameActions
uint32_t flags = action->GetFlags(); uint32_t flags = action->GetFlags();
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager(); auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
if (replayManager != nullptr && replayManager->IsReplaying()) if (replayManager != nullptr && (replayManager->IsReplaying() || replayManager->IsNormalising()))
{ {
// We only accept replay commands as long the replay is active. // We only accept replay commands as long the replay is active.
if ((flags & GAME_COMMAND_FLAG_REPLAY) == 0) if ((flags & GAME_COMMAND_FLAG_REPLAY) == 0)
@@ -282,7 +282,15 @@ namespace GameActions
} }
else if (network_get_mode() == NETWORK_MODE_NONE) else if (network_get_mode() == NETWORK_MODE_NONE)
{ {
if (replayManager != nullptr && replayManager->IsRecording()) bool recordAction = false;
if (replayManager)
{
if (replayManager->IsRecording())
recordAction = true;
else if (replayManager->IsNormalising() && (flags & GAME_COMMAND_FLAG_REPLAY) != 0)
recordAction = true; // In normalisation we only feed back actions issued by the replay manager.
}
if (recordAction)
{ {
replayManager->AddGameAction(gCurrentTicks, action); replayManager->AddGameAction(gCurrentTicks, action);
} }

View File

@@ -1438,6 +1438,36 @@ static int32_t cc_replay_stop(InteractiveConsole& console, const utf8** argv, in
return 0; return 0;
} }
static int32_t cc_replay_normalise(InteractiveConsole& console, const utf8** argv, int32_t argc)
{
if (network_get_mode() != NETWORK_MODE_NONE)
{
console.WriteFormatLine("This command is currently not supported in multiplayer mode.");
return 0;
}
if (argc < 2)
{
console.WriteFormatLine("Parameters required <replay_input> <replay_output>");
return 0;
}
std::string inputFile = argv[0];
std::string outputFile = argv[1];
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
if (replayManager != nullptr)
{
if (replayManager->NormaliseReplay(inputFile, outputFile))
{
console.WriteFormatLine("Stopped replay");
return 1;
}
}
return 0;
}
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4702) // unreachable code #pragma warning(disable : 4702) // unreachable code
static int32_t cc_abort( static int32_t cc_abort(
@@ -1561,6 +1591,8 @@ static constexpr const console_command console_command_table[] = {
{ "replay_stoprecord", cc_replay_stoprecord, "Stops recording a new replay.", "replay_stoprecord"}, { "replay_stoprecord", cc_replay_stoprecord, "Stops recording a new replay.", "replay_stoprecord"},
{ "replay_start", cc_replay_start, "Starts a replay", "replay_start <name>"}, { "replay_start", cc_replay_start, "Starts a replay", "replay_start <name>"},
{ "replay_stop", cc_replay_stop, "Stops the replay", "replay_stop"}, { "replay_stop", cc_replay_stop, "Stops the replay", "replay_stop"},
{ "replay_normalise", cc_replay_normalise, "Normalises the replay to remove all gaps", "replay_normalise <input file> <output file>"},
}; };
// clang-format on // clang-format on