mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 11:03:00 +01:00
Gamestate snapshots (#8819)
* Add initial interface. * Implement move operator in MemoryStream * Add pod array serialisation traits. * Add push_back with move semantics to CircularBuffer * Initial implementation of GameStateSnapshots * Add GameStateSnapshots to Context. * Add mp_desync console command. * Compare sprite data and fill change list. * Minor changes. * Proof of concept. * Calculate offset instead of using offsetof * Implement game state difference detection * Update mp_desync console command. * Fix identification of sprite remove/add. * Fix crash when only one peep in park when using mp_desync * Output state differences into user directory desync folder. * Add desync debugging as an option. * Add information to network status when a desync report was created. * Cast to proper type for %llu. * Update xcode project * Add more information to the diffed data. * Remove client-only relevant fields. * Cleanup. * Add better name output for misc sprites * Add srand0 and tick information to the output * Bump up network version * Cleanup * Set desync_debugging to false as default * Apply suggestions
This commit is contained in:
@@ -450,6 +450,7 @@
|
||||
C6E415511FAFD6DC00D4A52A /* RideConstruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6E415501FAFD6DB00D4A52A /* RideConstruction.cpp */; };
|
||||
C6E96E361E0408B40076A04F /* libzip.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C6E96E351E0408B40076A04F /* libzip.dylib */; };
|
||||
C6E96E371E040E040076A04F /* libzip.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C6E96E351E0408B40076A04F /* libzip.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
C9C630B62235A22D009AD16E /* GameStateSnapshots.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9C630B52235A22C009AD16E /* GameStateSnapshots.cpp */; };
|
||||
D41B73EF1C2101890080A7B9 /* libcurl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73EE1C2101890080A7B9 /* libcurl.tbd */; };
|
||||
D41B741D1C210A7A0080A7B9 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B741C1C210A7A0080A7B9 /* libiconv.tbd */; };
|
||||
D41B74731C2125E50080A7B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D41B74721C2125E50080A7B9 /* Assets.xcassets */; };
|
||||
@@ -1390,6 +1391,8 @@
|
||||
C9C630BB2235A7F9009AD16E /* WaterLowerAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = WaterLowerAction.hpp; sourceTree = "<group>"; };
|
||||
C9C630BC2235A7F9009AD16E /* WaterRaiseAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = WaterRaiseAction.hpp; sourceTree = "<group>"; };
|
||||
C9C630BD2235A9C6009AD16E /* FootpathPlaceFromTrackAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FootpathPlaceFromTrackAction.hpp; sourceTree = "<group>"; };
|
||||
C9C630B42235A22C009AD16E /* GameStateSnapshots.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GameStateSnapshots.h; sourceTree = "<group>"; };
|
||||
C9C630B52235A22C009AD16E /* GameStateSnapshots.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameStateSnapshots.cpp; sourceTree = "<group>"; };
|
||||
D41B73EE1C2101890080A7B9 /* libcurl.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurl.tbd; path = usr/lib/libcurl.tbd; sourceTree = SDKROOT; };
|
||||
D41B741C1C210A7A0080A7B9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
|
||||
D41B74721C2125E50080A7B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = distribution/macos/Assets.xcassets; sourceTree = SOURCE_ROOT; };
|
||||
@@ -2512,6 +2515,8 @@
|
||||
F76C83551EC4E7CC00FA49E2 /* libopenrct2 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C9C630B52235A22C009AD16E /* GameStateSnapshots.cpp */,
|
||||
C9C630B42235A22C009AD16E /* GameStateSnapshots.h */,
|
||||
4C358E5021C445F700ADE6BC /* ReplayManager.cpp */,
|
||||
4C358E5121C445F700ADE6BC /* ReplayManager.h */,
|
||||
C6352B871F477032006CCEE3 /* actions */,
|
||||
@@ -3972,6 +3977,7 @@
|
||||
C68878C220289B710084B384 /* DrawLineShader.cpp in Sources */,
|
||||
F76C888B1EC5324E00FA49E2 /* Ui.cpp in Sources */,
|
||||
C685E51A1F8907850090598F /* Staff.cpp in Sources */,
|
||||
C9C630B62235A22D009AD16E /* GameStateSnapshots.cpp in Sources */,
|
||||
F76C888C1EC5324E00FA49E2 /* UiContext.cpp in Sources */,
|
||||
C666EE7D1F37ACB10061AA04 /* TitleMenu.cpp in Sources */,
|
||||
93F6004C213DD7DD00EEB83E /* TerrainSurfaceObject.cpp in Sources */,
|
||||
|
||||
@@ -3765,6 +3765,7 @@ STR_6314 :{WINDOW_COLOUR_2}Dest: {BLACK}{INT32}, {INT32} tolerance {INT32}
|
||||
STR_6315 :{WINDOW_COLOUR_2}Pathfind Goal: {BLACK}{INT32}, {INT32}, {INT32} dir {INT32}
|
||||
STR_6316 :{WINDOW_COLOUR_2}Pathfind history:
|
||||
STR_6317 :{BLACK}{INT32}, {INT32}, {INT32} dir {INT32}
|
||||
STR_6318 :Network desync detected.{NEWLINE}Log file: {STRING}
|
||||
|
||||
#############
|
||||
# Scenarios #
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "FileClassifier.h"
|
||||
#include "Game.h"
|
||||
#include "GameState.h"
|
||||
#include "GameStateSnapshots.h"
|
||||
#include "Input.h"
|
||||
#include "Intro.h"
|
||||
#include "OpenRCT2.h"
|
||||
@@ -92,6 +93,7 @@ namespace OpenRCT2
|
||||
std::unique_ptr<ITrackDesignRepository> _trackDesignRepository;
|
||||
std::unique_ptr<IScenarioRepository> _scenarioRepository;
|
||||
std::unique_ptr<IReplayManager> _replayManager;
|
||||
std::unique_ptr<IGameStateSnapshots> _gameStateSnapshots;
|
||||
#ifdef __ENABLE_DISCORD__
|
||||
std::unique_ptr<DiscordService> _discordService;
|
||||
#endif
|
||||
@@ -211,6 +213,11 @@ namespace OpenRCT2
|
||||
return _replayManager.get();
|
||||
}
|
||||
|
||||
IGameStateSnapshots* GetGameStateSnapshots() override
|
||||
{
|
||||
return _gameStateSnapshots.get();
|
||||
}
|
||||
|
||||
int32_t GetDrawingEngineType() override
|
||||
{
|
||||
return _drawingEngineType;
|
||||
@@ -340,6 +347,7 @@ namespace OpenRCT2
|
||||
_trackDesignRepository = CreateTrackDesignRepository(_env);
|
||||
_scenarioRepository = CreateScenarioRepository(_env);
|
||||
_replayManager = CreateReplayManager();
|
||||
_gameStateSnapshots = CreateGameStateSnapshots();
|
||||
#ifdef __ENABLE_DISCORD__
|
||||
_discordService = std::make_unique<DiscordService>();
|
||||
#endif
|
||||
@@ -994,6 +1002,7 @@ namespace OpenRCT2
|
||||
DIRID::THEME,
|
||||
DIRID::SEQUENCE,
|
||||
DIRID::REPLAY,
|
||||
DIRID::LOG_DESYNCS,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ interface IObjectRepository;
|
||||
interface IScenarioRepository;
|
||||
interface IStream;
|
||||
interface ITrackDesignRepository;
|
||||
interface IGameStateSnapshots;
|
||||
|
||||
class Intent;
|
||||
struct rct_window;
|
||||
@@ -110,6 +111,7 @@ namespace OpenRCT2
|
||||
virtual ITrackDesignRepository* GetTrackDesignRepository() abstract;
|
||||
virtual IScenarioRepository* GetScenarioRepository() abstract;
|
||||
virtual IReplayManager* GetReplayManager() abstract;
|
||||
virtual IGameStateSnapshots* GetGameStateSnapshots() abstract;
|
||||
virtual int32_t GetDrawingEngineType() abstract;
|
||||
virtual Drawing::IDrawingEngine* GetDrawingEngine() abstract;
|
||||
virtual Paint::Painter* GetPainter() abstract;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Context.h"
|
||||
#include "Editor.h"
|
||||
#include "FileClassifier.h"
|
||||
#include "GameStateSnapshots.h"
|
||||
#include "Input.h"
|
||||
#include "OpenRCT2.h"
|
||||
#include "ParkImporter.h"
|
||||
@@ -847,6 +848,9 @@ void game_load_init()
|
||||
{
|
||||
rct_window* mainWindow;
|
||||
|
||||
IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
|
||||
snapshots->Reset();
|
||||
|
||||
gScreenFlags = SCREEN_FLAGS_PLAYING;
|
||||
audio_stop_all_music_and_sounds();
|
||||
if (!gLoadKeepWindowsOpen)
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
#include "Context.h"
|
||||
#include "Editor.h"
|
||||
#include "Game.h"
|
||||
#include "GameStateSnapshots.h"
|
||||
#include "Input.h"
|
||||
#include "OpenRCT2.h"
|
||||
#include "ReplayManager.h"
|
||||
#include "config/Config.h"
|
||||
#include "interface/Screenshot.h"
|
||||
#include "localisation/Date.h"
|
||||
#include "localisation/Localisation.h"
|
||||
@@ -238,13 +240,30 @@ void GameState::UpdateLogic()
|
||||
|
||||
if (network_get_mode() == NETWORK_MODE_SERVER)
|
||||
{
|
||||
if (network_gamestate_snapshots_enabled())
|
||||
{
|
||||
CreateStateSnapshot();
|
||||
}
|
||||
|
||||
// Send current tick out.
|
||||
network_send_tick();
|
||||
}
|
||||
else if (network_get_mode() == NETWORK_MODE_CLIENT)
|
||||
{
|
||||
// Check desync.
|
||||
network_check_desynchronization();
|
||||
bool desynced = network_check_desynchronisation();
|
||||
if (desynced)
|
||||
{
|
||||
// If desync debugging is enabled and we are still connected request the specific game state from server.
|
||||
if (network_gamestate_snapshots_enabled() && network_get_status() == NETWORK_STATUS_CONNECTED)
|
||||
{
|
||||
// Create snapshot from this tick so we can compare it later
|
||||
// as we won't pause the game on this event.
|
||||
CreateStateSnapshot();
|
||||
|
||||
network_request_gamestate_snapshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
date_update();
|
||||
@@ -311,3 +330,12 @@ void GameState::UpdateLogic()
|
||||
gScenarioTicks++;
|
||||
gSavedAge++;
|
||||
}
|
||||
|
||||
void GameState::CreateStateSnapshot()
|
||||
{
|
||||
IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
|
||||
|
||||
auto& snapshot = snapshots->CreateSnapshot();
|
||||
snapshots->Capture(snapshot);
|
||||
snapshots->LinkSnapshot(snapshot, gCurrentTicks, scenario_rand_state().s0);
|
||||
}
|
||||
|
||||
@@ -42,5 +42,8 @@ namespace OpenRCT2
|
||||
void InitAll(int32_t mapSize);
|
||||
void Update();
|
||||
void UpdateLogic();
|
||||
|
||||
private:
|
||||
void CreateStateSnapshot();
|
||||
};
|
||||
} // namespace OpenRCT2
|
||||
|
||||
578
src/openrct2/GameStateSnapshots.cpp
Normal file
578
src/openrct2/GameStateSnapshots.cpp
Normal file
@@ -0,0 +1,578 @@
|
||||
#include "GameStateSnapshots.h"
|
||||
|
||||
#include "core/CircularBuffer.h"
|
||||
#include "peep/Peep.h"
|
||||
#include "world/Sprite.h"
|
||||
|
||||
static constexpr size_t MaximumGameStateSnapshots = 32;
|
||||
static constexpr uint32_t InvalidTick = 0xFFFFFFFF;
|
||||
|
||||
struct GameStateSnapshot_t
|
||||
{
|
||||
GameStateSnapshot_t& operator=(GameStateSnapshot_t&& mv)
|
||||
{
|
||||
tick = mv.tick;
|
||||
storedSprites = std::move(mv.storedSprites);
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint32_t tick = InvalidTick;
|
||||
uint32_t srand0 = 0;
|
||||
|
||||
MemoryStream storedSprites;
|
||||
MemoryStream parkParameters;
|
||||
|
||||
void SerialiseSprites(rct_sprite* sprites, const size_t numSprites, bool saving)
|
||||
{
|
||||
const bool loading = !saving;
|
||||
|
||||
storedSprites.SetPosition(0);
|
||||
DataSerialiser ds(saving, storedSprites);
|
||||
|
||||
std::vector<uint32_t> indexTable;
|
||||
indexTable.reserve(numSprites);
|
||||
|
||||
uint32_t numSavedSprites = 0;
|
||||
|
||||
if (saving)
|
||||
{
|
||||
for (size_t i = 0; i < numSprites; i++)
|
||||
{
|
||||
if (sprites[i].generic.sprite_identifier == SPRITE_IDENTIFIER_NULL)
|
||||
continue;
|
||||
indexTable.push_back((uint32_t)i);
|
||||
}
|
||||
numSavedSprites = (uint32_t)indexTable.size();
|
||||
}
|
||||
|
||||
ds << numSavedSprites;
|
||||
|
||||
if (loading)
|
||||
{
|
||||
indexTable.resize(numSavedSprites);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < numSavedSprites; i++)
|
||||
{
|
||||
ds << indexTable[i];
|
||||
|
||||
const uint32_t spriteIdx = indexTable[i];
|
||||
rct_sprite& sprite = sprites[spriteIdx];
|
||||
|
||||
ds << sprite.generic.sprite_identifier;
|
||||
|
||||
switch (sprite.generic.sprite_identifier)
|
||||
{
|
||||
case SPRITE_IDENTIFIER_VEHICLE:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_vehicle)]>(sprite.vehicle);
|
||||
break;
|
||||
case SPRITE_IDENTIFIER_PEEP:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(Peep)]>(sprite.peep);
|
||||
break;
|
||||
case SPRITE_IDENTIFIER_LITTER:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_litter)]>(sprite.litter);
|
||||
break;
|
||||
case SPRITE_IDENTIFIER_MISC:
|
||||
{
|
||||
ds << sprite.generic.type;
|
||||
switch (sprite.generic.type)
|
||||
{
|
||||
case SPRITE_MISC_MONEY_EFFECT:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_money_effect)]>(sprite.money_effect);
|
||||
break;
|
||||
case SPRITE_MISC_BALLOON:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_balloon)]>(sprite.balloon);
|
||||
break;
|
||||
case SPRITE_MISC_DUCK:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_duck)]>(sprite.duck);
|
||||
break;
|
||||
case SPRITE_MISC_JUMPING_FOUNTAIN_WATER:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_jumping_fountain)]>(sprite.jumping_fountain);
|
||||
break;
|
||||
case SPRITE_MISC_STEAM_PARTICLE:
|
||||
ds << reinterpret_cast<uint8_t(&)[sizeof(rct_steam_particle)]>(sprite.steam_particle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GameStateSnapshots : public IGameStateSnapshots
|
||||
{
|
||||
virtual void Reset() override final
|
||||
{
|
||||
_snapshots.clear();
|
||||
}
|
||||
|
||||
virtual GameStateSnapshot_t& CreateSnapshot() override final
|
||||
{
|
||||
auto snapshot = std::make_unique<GameStateSnapshot_t>();
|
||||
_snapshots.push_back(std::move(snapshot));
|
||||
|
||||
return *_snapshots.back();
|
||||
}
|
||||
|
||||
virtual void LinkSnapshot(GameStateSnapshot_t& snapshot, uint32_t tick, uint32_t srand0) override final
|
||||
{
|
||||
snapshot.tick = tick;
|
||||
snapshot.srand0 = srand0;
|
||||
}
|
||||
|
||||
virtual void Capture(GameStateSnapshot_t& snapshot) override final
|
||||
{
|
||||
snapshot.SerialiseSprites(get_sprite(0), MAX_SPRITES, true);
|
||||
|
||||
// log_info("Snapshot size: %u bytes\n", (uint32_t)snapshot.storedSprites.GetLength());
|
||||
}
|
||||
|
||||
virtual const GameStateSnapshot_t* GetLinkedSnapshot(uint32_t tick) const override final
|
||||
{
|
||||
for (size_t i = 0; i < _snapshots.size(); i++)
|
||||
{
|
||||
if (_snapshots[i]->tick == tick)
|
||||
return _snapshots[i].get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void SerialiseSnapshot(GameStateSnapshot_t& snapshot, DataSerialiser& ds) const override final
|
||||
{
|
||||
ds << snapshot.tick;
|
||||
ds << snapshot.srand0;
|
||||
ds << snapshot.storedSprites;
|
||||
ds << snapshot.parkParameters;
|
||||
}
|
||||
|
||||
std::vector<rct_sprite> BuildSpriteList(GameStateSnapshot_t& snapshot) const
|
||||
{
|
||||
std::vector<rct_sprite> spriteList;
|
||||
spriteList.resize(MAX_SPRITES);
|
||||
|
||||
for (auto& sprite : spriteList)
|
||||
{
|
||||
// By default they don't exist.
|
||||
sprite.generic.sprite_identifier = SPRITE_IDENTIFIER_NULL;
|
||||
}
|
||||
|
||||
snapshot.SerialiseSprites(spriteList.data(), MAX_SPRITES, false);
|
||||
|
||||
return spriteList;
|
||||
}
|
||||
|
||||
#define COMPARE_FIELD(struc, field) \
|
||||
if (std::memcmp(&spriteBase.field, &spriteCmp.field, sizeof(struc::field)) != 0) \
|
||||
{ \
|
||||
uint64_t valA = 0; \
|
||||
uint64_t valB = 0; \
|
||||
std::memcpy(&valA, &spriteBase.field, sizeof(struc::field)); \
|
||||
std::memcpy(&valB, &spriteCmp.field, sizeof(struc::field)); \
|
||||
uintptr_t offset = reinterpret_cast<uintptr_t>(&spriteBase.field) - reinterpret_cast<uintptr_t>(&spriteBase); \
|
||||
changeData.diffs.push_back( \
|
||||
GameStateSpriteChange_t::Diff_t{ (size_t)offset, sizeof(struc::field), #struc, #field, valA, valB }); \
|
||||
}
|
||||
|
||||
void CompareSpriteDataCommon(
|
||||
const rct_sprite_common& spriteBase, const rct_sprite_common& spriteCmp, GameStateSpriteChange_t& changeData) const
|
||||
{
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_identifier);
|
||||
COMPARE_FIELD(rct_sprite_common, type);
|
||||
COMPARE_FIELD(rct_sprite_common, next_in_quadrant);
|
||||
COMPARE_FIELD(rct_sprite_common, next);
|
||||
COMPARE_FIELD(rct_sprite_common, previous);
|
||||
COMPARE_FIELD(rct_sprite_common, linked_list_type_offset);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_index);
|
||||
COMPARE_FIELD(rct_sprite_common, flags);
|
||||
COMPARE_FIELD(rct_sprite_common, x);
|
||||
COMPARE_FIELD(rct_sprite_common, y);
|
||||
COMPARE_FIELD(rct_sprite_common, z);
|
||||
/* Only relevant for rendering, does not affect game state.
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_width);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_height_negative);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_height_positive);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_left);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_top);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_right);
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_bottom);
|
||||
*/
|
||||
COMPARE_FIELD(rct_sprite_common, sprite_direction);
|
||||
}
|
||||
|
||||
void CompareSpriteDataPeep(const Peep& spriteBase, const Peep& spriteCmp, GameStateSpriteChange_t& changeData) const
|
||||
{
|
||||
COMPARE_FIELD(Peep, name_string_idx);
|
||||
COMPARE_FIELD(Peep, next_x);
|
||||
COMPARE_FIELD(Peep, next_y);
|
||||
COMPARE_FIELD(Peep, next_z);
|
||||
COMPARE_FIELD(Peep, next_flags);
|
||||
COMPARE_FIELD(Peep, outside_of_park);
|
||||
COMPARE_FIELD(Peep, state);
|
||||
COMPARE_FIELD(Peep, sub_state);
|
||||
COMPARE_FIELD(Peep, sprite_type);
|
||||
COMPARE_FIELD(Peep, type);
|
||||
COMPARE_FIELD(Peep, no_of_rides);
|
||||
COMPARE_FIELD(Peep, tshirt_colour);
|
||||
COMPARE_FIELD(Peep, trousers_colour);
|
||||
COMPARE_FIELD(Peep, destination_x);
|
||||
COMPARE_FIELD(Peep, destination_y);
|
||||
COMPARE_FIELD(Peep, destination_tolerance);
|
||||
COMPARE_FIELD(Peep, var_37);
|
||||
COMPARE_FIELD(Peep, energy);
|
||||
COMPARE_FIELD(Peep, energy_target);
|
||||
COMPARE_FIELD(Peep, happiness);
|
||||
COMPARE_FIELD(Peep, happiness_target);
|
||||
COMPARE_FIELD(Peep, nausea);
|
||||
COMPARE_FIELD(Peep, nausea_target);
|
||||
COMPARE_FIELD(Peep, hunger);
|
||||
COMPARE_FIELD(Peep, thirst);
|
||||
COMPARE_FIELD(Peep, toilet);
|
||||
COMPARE_FIELD(Peep, mass);
|
||||
COMPARE_FIELD(Peep, time_to_consume);
|
||||
COMPARE_FIELD(Peep, intensity);
|
||||
COMPARE_FIELD(Peep, nausea_tolerance);
|
||||
COMPARE_FIELD(Peep, window_invalidate_flags);
|
||||
COMPARE_FIELD(Peep, paid_on_drink);
|
||||
for (int i = 0; i < PEEP_MAX_THOUGHTS; i++)
|
||||
{
|
||||
COMPARE_FIELD(Peep, ride_types_been_on[i]);
|
||||
}
|
||||
COMPARE_FIELD(Peep, item_extra_flags);
|
||||
COMPARE_FIELD(Peep, photo2_ride_ref);
|
||||
COMPARE_FIELD(Peep, photo3_ride_ref);
|
||||
COMPARE_FIELD(Peep, photo4_ride_ref);
|
||||
COMPARE_FIELD(Peep, current_ride);
|
||||
COMPARE_FIELD(Peep, current_ride_station);
|
||||
COMPARE_FIELD(Peep, current_train);
|
||||
COMPARE_FIELD(Peep, time_to_sitdown);
|
||||
COMPARE_FIELD(Peep, special_sprite);
|
||||
COMPARE_FIELD(Peep, action_sprite_type);
|
||||
COMPARE_FIELD(Peep, next_action_sprite_type);
|
||||
COMPARE_FIELD(Peep, action_sprite_image_offset);
|
||||
COMPARE_FIELD(Peep, action);
|
||||
COMPARE_FIELD(Peep, action_frame);
|
||||
COMPARE_FIELD(Peep, step_progress);
|
||||
COMPARE_FIELD(Peep, next_in_queue);
|
||||
COMPARE_FIELD(Peep, maze_last_edge);
|
||||
COMPARE_FIELD(Peep, interaction_ride_index);
|
||||
COMPARE_FIELD(Peep, time_in_queue);
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
COMPARE_FIELD(Peep, rides_been_on[i]);
|
||||
}
|
||||
COMPARE_FIELD(Peep, id);
|
||||
COMPARE_FIELD(Peep, cash_in_pocket);
|
||||
COMPARE_FIELD(Peep, cash_spent);
|
||||
COMPARE_FIELD(Peep, time_in_park);
|
||||
COMPARE_FIELD(Peep, rejoin_queue_timeout);
|
||||
COMPARE_FIELD(Peep, previous_ride);
|
||||
COMPARE_FIELD(Peep, previous_ride_time_out);
|
||||
for (int i = 0; i < PEEP_MAX_THOUGHTS; i++)
|
||||
{
|
||||
COMPARE_FIELD(Peep, thoughts[i]);
|
||||
}
|
||||
COMPARE_FIELD(Peep, path_check_optimisation);
|
||||
COMPARE_FIELD(Peep, guest_heading_to_ride_id);
|
||||
COMPARE_FIELD(Peep, staff_orders);
|
||||
COMPARE_FIELD(Peep, photo1_ride_ref);
|
||||
COMPARE_FIELD(Peep, peep_flags);
|
||||
COMPARE_FIELD(Peep, pathfind_goal);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
COMPARE_FIELD(Peep, pathfind_history[i]);
|
||||
}
|
||||
COMPARE_FIELD(Peep, no_action_frame_num);
|
||||
COMPARE_FIELD(Peep, litter_count);
|
||||
COMPARE_FIELD(Peep, time_on_ride);
|
||||
COMPARE_FIELD(Peep, disgusting_count);
|
||||
COMPARE_FIELD(Peep, paid_to_enter);
|
||||
COMPARE_FIELD(Peep, paid_on_rides);
|
||||
COMPARE_FIELD(Peep, paid_on_food);
|
||||
COMPARE_FIELD(Peep, paid_on_souvenirs);
|
||||
COMPARE_FIELD(Peep, no_of_food);
|
||||
COMPARE_FIELD(Peep, no_of_drinks);
|
||||
COMPARE_FIELD(Peep, no_of_souvenirs);
|
||||
COMPARE_FIELD(Peep, vandalism_seen);
|
||||
COMPARE_FIELD(Peep, voucher_type);
|
||||
COMPARE_FIELD(Peep, voucher_arguments);
|
||||
COMPARE_FIELD(Peep, surroundings_thought_timeout);
|
||||
COMPARE_FIELD(Peep, angriness);
|
||||
COMPARE_FIELD(Peep, time_lost);
|
||||
COMPARE_FIELD(Peep, days_in_queue);
|
||||
COMPARE_FIELD(Peep, balloon_colour);
|
||||
COMPARE_FIELD(Peep, umbrella_colour);
|
||||
COMPARE_FIELD(Peep, hat_colour);
|
||||
COMPARE_FIELD(Peep, favourite_ride);
|
||||
COMPARE_FIELD(Peep, favourite_ride_rating);
|
||||
COMPARE_FIELD(Peep, item_standard_flags);
|
||||
}
|
||||
|
||||
void CompareSpriteDataVehicle(
|
||||
const rct_vehicle& spriteBase, const rct_vehicle& spriteCmp, GameStateSpriteChange_t& changeData) const
|
||||
{
|
||||
COMPARE_FIELD(rct_vehicle, vehicle_sprite_type);
|
||||
COMPARE_FIELD(rct_vehicle, bank_rotation);
|
||||
COMPARE_FIELD(rct_vehicle, remaining_distance);
|
||||
COMPARE_FIELD(rct_vehicle, velocity);
|
||||
COMPARE_FIELD(rct_vehicle, acceleration);
|
||||
COMPARE_FIELD(rct_vehicle, ride);
|
||||
COMPARE_FIELD(rct_vehicle, vehicle_type);
|
||||
COMPARE_FIELD(rct_vehicle, colours);
|
||||
COMPARE_FIELD(rct_vehicle, track_progress);
|
||||
COMPARE_FIELD(rct_vehicle, track_direction);
|
||||
COMPARE_FIELD(rct_vehicle, track_x);
|
||||
COMPARE_FIELD(rct_vehicle, track_y);
|
||||
COMPARE_FIELD(rct_vehicle, track_z);
|
||||
COMPARE_FIELD(rct_vehicle, next_vehicle_on_train);
|
||||
COMPARE_FIELD(rct_vehicle, prev_vehicle_on_ride);
|
||||
COMPARE_FIELD(rct_vehicle, next_vehicle_on_ride);
|
||||
COMPARE_FIELD(rct_vehicle, var_44);
|
||||
COMPARE_FIELD(rct_vehicle, mass);
|
||||
COMPARE_FIELD(rct_vehicle, update_flags);
|
||||
COMPARE_FIELD(rct_vehicle, swing_sprite);
|
||||
COMPARE_FIELD(rct_vehicle, current_station);
|
||||
COMPARE_FIELD(rct_vehicle, swinging_car_var_0);
|
||||
COMPARE_FIELD(rct_vehicle, var_4E);
|
||||
COMPARE_FIELD(rct_vehicle, status);
|
||||
COMPARE_FIELD(rct_vehicle, sub_state);
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
COMPARE_FIELD(rct_vehicle, peep[i]);
|
||||
}
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
COMPARE_FIELD(rct_vehicle, peep_tshirt_colours[i]);
|
||||
}
|
||||
COMPARE_FIELD(rct_vehicle, num_seats);
|
||||
COMPARE_FIELD(rct_vehicle, num_peeps);
|
||||
COMPARE_FIELD(rct_vehicle, next_free_seat);
|
||||
COMPARE_FIELD(rct_vehicle, restraints_position);
|
||||
COMPARE_FIELD(rct_vehicle, spin_speed);
|
||||
COMPARE_FIELD(rct_vehicle, sound2_flags);
|
||||
COMPARE_FIELD(rct_vehicle, spin_sprite);
|
||||
COMPARE_FIELD(rct_vehicle, sound1_id);
|
||||
COMPARE_FIELD(rct_vehicle, sound1_volume);
|
||||
COMPARE_FIELD(rct_vehicle, sound2_id);
|
||||
COMPARE_FIELD(rct_vehicle, sound2_volume);
|
||||
COMPARE_FIELD(rct_vehicle, sound_vector_factor);
|
||||
COMPARE_FIELD(rct_vehicle, cable_lift_target);
|
||||
COMPARE_FIELD(rct_vehicle, speed);
|
||||
COMPARE_FIELD(rct_vehicle, powered_acceleration);
|
||||
COMPARE_FIELD(rct_vehicle, var_C4);
|
||||
COMPARE_FIELD(rct_vehicle, animation_frame);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
COMPARE_FIELD(rct_vehicle, pad_C6[i]);
|
||||
}
|
||||
COMPARE_FIELD(rct_vehicle, var_C8);
|
||||
COMPARE_FIELD(rct_vehicle, var_CA);
|
||||
COMPARE_FIELD(rct_vehicle, scream_sound_id);
|
||||
COMPARE_FIELD(rct_vehicle, var_CD);
|
||||
COMPARE_FIELD(rct_vehicle, num_laps);
|
||||
COMPARE_FIELD(rct_vehicle, brake_speed);
|
||||
COMPARE_FIELD(rct_vehicle, lost_time_out);
|
||||
COMPARE_FIELD(rct_vehicle, vertical_drop_countdown);
|
||||
COMPARE_FIELD(rct_vehicle, var_D3);
|
||||
COMPARE_FIELD(rct_vehicle, mini_golf_current_animation);
|
||||
COMPARE_FIELD(rct_vehicle, mini_golf_flags);
|
||||
COMPARE_FIELD(rct_vehicle, ride_subtype);
|
||||
COMPARE_FIELD(rct_vehicle, colours_extended);
|
||||
COMPARE_FIELD(rct_vehicle, seat_rotation);
|
||||
COMPARE_FIELD(rct_vehicle, target_seat_rotation);
|
||||
}
|
||||
|
||||
void CompareSpriteDataLitter(
|
||||
const rct_litter& spriteBase, const rct_litter& spriteCmp, GameStateSpriteChange_t& changeData) const
|
||||
{
|
||||
COMPARE_FIELD(rct_litter, creationTick);
|
||||
}
|
||||
|
||||
void CompareSpriteData(const rct_sprite& spriteBase, const rct_sprite& spriteCmp, GameStateSpriteChange_t& changeData) const
|
||||
{
|
||||
CompareSpriteDataCommon(spriteBase.generic, spriteCmp.generic, changeData);
|
||||
if (spriteBase.generic.sprite_identifier == spriteCmp.generic.sprite_identifier)
|
||||
{
|
||||
switch (spriteBase.generic.sprite_identifier)
|
||||
{
|
||||
case SPRITE_IDENTIFIER_PEEP:
|
||||
CompareSpriteDataPeep(spriteBase.peep, spriteCmp.peep, changeData);
|
||||
break;
|
||||
case SPRITE_IDENTIFIER_VEHICLE:
|
||||
CompareSpriteDataVehicle(spriteBase.vehicle, spriteCmp.vehicle, changeData);
|
||||
break;
|
||||
case SPRITE_IDENTIFIER_LITTER:
|
||||
CompareSpriteDataLitter(spriteBase.litter, spriteCmp.litter, changeData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual GameStateCompareData_t Compare(const GameStateSnapshot_t& base, const GameStateSnapshot_t& cmp) const override final
|
||||
{
|
||||
GameStateCompareData_t res;
|
||||
res.tick = base.tick;
|
||||
res.srand0Left = base.srand0;
|
||||
res.srand0Right = cmp.srand0;
|
||||
|
||||
std::vector<rct_sprite> spritesBase = BuildSpriteList(const_cast<GameStateSnapshot_t&>(base));
|
||||
std::vector<rct_sprite> spritesCmp = BuildSpriteList(const_cast<GameStateSnapshot_t&>(cmp));
|
||||
|
||||
for (uint32_t i = 0; i < (uint32_t)spritesBase.size(); i++)
|
||||
{
|
||||
GameStateSpriteChange_t changeData;
|
||||
changeData.spriteIndex = i;
|
||||
|
||||
const rct_sprite& spriteBase = spritesBase[i];
|
||||
const rct_sprite& spriteCmp = spritesCmp[i];
|
||||
|
||||
changeData.spriteIdentifier = spriteBase.generic.sprite_identifier;
|
||||
changeData.miscIdentifier = spriteBase.generic.type;
|
||||
|
||||
if (spriteBase.generic.sprite_identifier == SPRITE_IDENTIFIER_NULL
|
||||
&& spriteCmp.generic.sprite_identifier != SPRITE_IDENTIFIER_NULL)
|
||||
{
|
||||
// Sprite was added.
|
||||
changeData.changeType = GameStateSpriteChange_t::ADDED;
|
||||
changeData.spriteIdentifier = spriteCmp.generic.sprite_identifier;
|
||||
}
|
||||
else if (
|
||||
spriteBase.generic.sprite_identifier != SPRITE_IDENTIFIER_NULL
|
||||
&& spriteCmp.generic.sprite_identifier == SPRITE_IDENTIFIER_NULL)
|
||||
{
|
||||
// Sprite was removed.
|
||||
changeData.changeType = GameStateSpriteChange_t::REMOVED;
|
||||
changeData.spriteIdentifier = spriteBase.generic.sprite_identifier;
|
||||
}
|
||||
else if (
|
||||
spriteBase.generic.sprite_identifier == SPRITE_IDENTIFIER_NULL
|
||||
&& spriteCmp.generic.sprite_identifier == SPRITE_IDENTIFIER_NULL)
|
||||
{
|
||||
// Do nothing.
|
||||
changeData.changeType = GameStateSpriteChange_t::EQUAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
CompareSpriteData(spriteBase, spriteCmp, changeData);
|
||||
if (changeData.diffs.size() == 0)
|
||||
{
|
||||
changeData.changeType = GameStateSpriteChange_t::EQUAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
changeData.changeType = GameStateSpriteChange_t::MODIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
res.spriteChanges.push_back(changeData);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const char* GetSpriteIdentifierName(uint32_t spriteIdentifier, uint8_t miscIdentifier)
|
||||
{
|
||||
switch (spriteIdentifier)
|
||||
{
|
||||
case SPRITE_IDENTIFIER_NULL:
|
||||
return "Null";
|
||||
case SPRITE_IDENTIFIER_PEEP:
|
||||
return "Peep";
|
||||
case SPRITE_IDENTIFIER_VEHICLE:
|
||||
return "Vehicle";
|
||||
case SPRITE_IDENTIFIER_LITTER:
|
||||
return "Litter";
|
||||
case SPRITE_IDENTIFIER_MISC:
|
||||
switch (miscIdentifier)
|
||||
{
|
||||
case SPRITE_MISC_STEAM_PARTICLE:
|
||||
return "Misc: Steam Particle";
|
||||
case SPRITE_MISC_MONEY_EFFECT:
|
||||
return "Misc: Money effect";
|
||||
case SPRITE_MISC_CRASHED_VEHICLE_PARTICLE:
|
||||
return "Misc: Crash Vehicle Particle";
|
||||
case SPRITE_MISC_EXPLOSION_CLOUD:
|
||||
return "Misc: Explosion Cloud";
|
||||
case SPRITE_MISC_CRASH_SPLASH:
|
||||
return "Misc: Crash Splash";
|
||||
case SPRITE_MISC_EXPLOSION_FLARE:
|
||||
return "Misc: Explosion Flare";
|
||||
case SPRITE_MISC_JUMPING_FOUNTAIN_WATER:
|
||||
return "Misc: Jumping fountain water";
|
||||
case SPRITE_MISC_BALLOON:
|
||||
return "Misc: Balloon";
|
||||
case SPRITE_MISC_DUCK:
|
||||
return "Misc: Duck";
|
||||
case SPRITE_MISC_JUMPING_FOUNTAIN_SNOW:
|
||||
return "Misc: Jumping fountain snow";
|
||||
}
|
||||
return "Misc";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
virtual bool LogCompareDataToFile(const std::string& fileName, const GameStateCompareData_t& cmpData) const override
|
||||
{
|
||||
std::string outputBuffer;
|
||||
char tempBuffer[1024] = {};
|
||||
|
||||
snprintf(tempBuffer, sizeof(tempBuffer), "tick: %08X\n", cmpData.tick);
|
||||
outputBuffer += tempBuffer;
|
||||
|
||||
snprintf(
|
||||
tempBuffer, sizeof(tempBuffer), "srand0 left = %08X, srand0 right = %08X\n", cmpData.srand0Left,
|
||||
cmpData.srand0Right);
|
||||
outputBuffer += tempBuffer;
|
||||
|
||||
for (auto& change : cmpData.spriteChanges)
|
||||
{
|
||||
if (change.changeType == GameStateSpriteChange_t::EQUAL)
|
||||
continue;
|
||||
|
||||
const char* typeName = GetSpriteIdentifierName(change.spriteIdentifier, change.miscIdentifier);
|
||||
|
||||
if (change.changeType == GameStateSpriteChange_t::ADDED)
|
||||
{
|
||||
snprintf(tempBuffer, sizeof(tempBuffer), "Sprite added (%s), index: %u\n", typeName, change.spriteIndex);
|
||||
outputBuffer += tempBuffer;
|
||||
}
|
||||
else if (change.changeType == GameStateSpriteChange_t::REMOVED)
|
||||
{
|
||||
snprintf(tempBuffer, sizeof(tempBuffer), "Sprite removed (%s), index: %u\n", typeName, change.spriteIndex);
|
||||
outputBuffer += tempBuffer;
|
||||
}
|
||||
else if (change.changeType == GameStateSpriteChange_t::MODIFIED)
|
||||
{
|
||||
snprintf(
|
||||
tempBuffer, sizeof(tempBuffer), "Sprite modifications (%s), index: %u\n", typeName, change.spriteIndex);
|
||||
outputBuffer += tempBuffer;
|
||||
for (auto& diff : change.diffs)
|
||||
{
|
||||
snprintf(
|
||||
tempBuffer, sizeof(tempBuffer),
|
||||
" %s::%s, len = %u, offset = %u, left = 0x%.16llX, right = 0x%.16llX\n", diff.structname,
|
||||
diff.fieldname, (uint32_t)diff.length, (uint32_t)diff.offset, (unsigned long long)diff.valueA,
|
||||
(unsigned long long)diff.valueB);
|
||||
outputBuffer += tempBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FILE* fp = fopen(fileName.c_str(), "wt");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
fputs(outputBuffer.c_str(), fp);
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CircularBuffer<std::unique_ptr<GameStateSnapshot_t>, MaximumGameStateSnapshots> _snapshots;
|
||||
};
|
||||
|
||||
std::unique_ptr<IGameStateSnapshots> CreateGameStateSnapshots()
|
||||
{
|
||||
return std::make_unique<GameStateSnapshots>();
|
||||
}
|
||||
108
src/openrct2/GameStateSnapshots.h
Normal file
108
src/openrct2/GameStateSnapshots.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2019 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "core/DataSerialiser.h"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
struct GameStateSnapshot_t;
|
||||
|
||||
struct GameStateSpriteChange_t
|
||||
{
|
||||
enum
|
||||
{
|
||||
REMOVED,
|
||||
ADDED,
|
||||
MODIFIED,
|
||||
EQUAL
|
||||
};
|
||||
|
||||
struct Diff_t
|
||||
{
|
||||
size_t offset;
|
||||
size_t length;
|
||||
const char* structname;
|
||||
const char* fieldname;
|
||||
uint64_t valueA;
|
||||
uint64_t valueB;
|
||||
};
|
||||
|
||||
uint8_t changeType;
|
||||
uint8_t spriteIdentifier;
|
||||
uint8_t miscIdentifier;
|
||||
uint32_t spriteIndex;
|
||||
|
||||
std::vector<Diff_t> diffs;
|
||||
};
|
||||
|
||||
struct GameStateCompareData_t
|
||||
{
|
||||
uint32_t tick;
|
||||
uint32_t srand0Left;
|
||||
uint32_t srand0Right;
|
||||
std::vector<GameStateSpriteChange_t> spriteChanges;
|
||||
};
|
||||
|
||||
/*
|
||||
* Interface to create and capture game states. It only allows to have 32 active snapshots
|
||||
* the oldest snapshot will be removed from the buffer. Never store the snapshot pointer
|
||||
* as it may become invalid at any time when a snapshot is created, rather Link the snapshot
|
||||
* to a specific tick which can be obtained by that later again assuming its still valid.
|
||||
*/
|
||||
interface IGameStateSnapshots
|
||||
{
|
||||
virtual ~IGameStateSnapshots() = default;
|
||||
|
||||
/*
|
||||
* Removes all existing entries and starts over.
|
||||
*/
|
||||
virtual void Reset() = 0;
|
||||
|
||||
/*
|
||||
* Creates a new empty snapshot, oldest snapshot will be removed.
|
||||
*/
|
||||
virtual GameStateSnapshot_t& CreateSnapshot() = 0;
|
||||
|
||||
/*
|
||||
* Links the snapshot to a specific game tick.
|
||||
*/
|
||||
virtual void LinkSnapshot(GameStateSnapshot_t & snapshot, uint32_t tick, uint32_t srand0) = 0;
|
||||
|
||||
/*
|
||||
* This will fill the snapshot with the current game state in a compact form.
|
||||
*/
|
||||
virtual void Capture(GameStateSnapshot_t & snapshot) = 0;
|
||||
|
||||
/*
|
||||
* Returns the snapshot for a given tick in the history, nullptr if not found.
|
||||
*/
|
||||
virtual const GameStateSnapshot_t* GetLinkedSnapshot(uint32_t tick) const = 0;
|
||||
|
||||
/*
|
||||
* Serialisation of GameStateSnapshot_t
|
||||
*/
|
||||
virtual void SerialiseSnapshot(GameStateSnapshot_t & snapshot, DataSerialiser & serialiser) const = 0;
|
||||
|
||||
/*
|
||||
* Compares two states resulting GameStateCompareData_t with all mismatches stored.
|
||||
*/
|
||||
virtual GameStateCompareData_t Compare(const GameStateSnapshot_t& base, const GameStateSnapshot_t& cmp) const = 0;
|
||||
|
||||
/*
|
||||
* Writes the GameStateCompareData_t into the specified file as readable text.
|
||||
*/
|
||||
virtual bool LogCompareDataToFile(const std::string& fileName, const GameStateCompareData_t& cmpData) const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IGameStateSnapshots> CreateGameStateSnapshots();
|
||||
@@ -221,6 +221,7 @@ const char * PlatformEnvironment::DirectoryNamesOpenRCT2[] =
|
||||
"track", // TRACK
|
||||
"heightmap", // HEIGHTMAP
|
||||
"replay", // REPLAY
|
||||
"desyncs", // DESYNCS
|
||||
};
|
||||
|
||||
const char * PlatformEnvironment::FileNames[] =
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace OpenRCT2
|
||||
TRACK, // Contains track designs.
|
||||
HEIGHTMAP, // Contains heightmap data.
|
||||
REPLAY, // Contains recorded replays.
|
||||
LOG_DESYNCS, // Contains desync reports.
|
||||
};
|
||||
|
||||
enum class PATHID
|
||||
|
||||
@@ -395,6 +395,7 @@ namespace Config
|
||||
model->log_chat = reader->GetBoolean("log_chat", false);
|
||||
model->log_server_actions = reader->GetBoolean("log_server_actions", false);
|
||||
model->pause_server_if_no_clients = reader->GetBoolean("pause_server_if_no_clients", false);
|
||||
model->desync_debugging = reader->GetBoolean("desync_debugging", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,6 +421,7 @@ namespace Config
|
||||
writer->WriteBoolean("log_chat", model->log_chat);
|
||||
writer->WriteBoolean("log_server_actions", model->log_server_actions);
|
||||
writer->WriteBoolean("pause_server_if_no_clients", model->pause_server_if_no_clients);
|
||||
writer->WriteBoolean("desync_debugging", model->desync_debugging);
|
||||
}
|
||||
|
||||
static void ReadNotifications(IIniReader* reader)
|
||||
|
||||
@@ -156,6 +156,7 @@ struct NetworkConfiguration
|
||||
bool log_chat;
|
||||
bool log_server_actions;
|
||||
bool pause_server_if_no_clients;
|
||||
bool desync_debugging;
|
||||
};
|
||||
|
||||
struct NotificationConfiguration
|
||||
|
||||
@@ -104,6 +104,34 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void push_back(value_type&& val)
|
||||
{
|
||||
if (_size == 0)
|
||||
{
|
||||
_elements[_head] = std::move(val);
|
||||
_tail = _head;
|
||||
_size++;
|
||||
}
|
||||
else if (_size != capacity())
|
||||
{
|
||||
_tail++;
|
||||
if (_tail == capacity())
|
||||
_tail = 0;
|
||||
_size++;
|
||||
_elements[_tail] = std::move(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
_head++;
|
||||
if (_head == capacity())
|
||||
_head = 0;
|
||||
_tail++;
|
||||
if (_tail == capacity())
|
||||
_tail = 0;
|
||||
_elements[_tail] = std::move(val);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _head = 0;
|
||||
size_t _tail = 0;
|
||||
|
||||
@@ -253,6 +253,64 @@ template<> struct DataSerializerTraits<MemoryStream>
|
||||
}
|
||||
};
|
||||
|
||||
template<typename _Ty, size_t _Size> struct DataSerializerTraitsPODArray
|
||||
{
|
||||
static void encode(IStream* stream, const _Ty (&val)[_Size])
|
||||
{
|
||||
uint16_t len = (uint16_t)_Size;
|
||||
uint16_t swapped = ByteSwapBE(len);
|
||||
stream->Write(&swapped);
|
||||
|
||||
DataSerializerTraits<uint8_t> s;
|
||||
for (auto&& sub : val)
|
||||
{
|
||||
s.encode(stream, sub);
|
||||
}
|
||||
}
|
||||
static void decode(IStream* stream, _Ty (&val)[_Size])
|
||||
{
|
||||
uint16_t len;
|
||||
stream->Read(&len);
|
||||
len = ByteSwapBE(len);
|
||||
|
||||
if (len != _Size)
|
||||
throw std::runtime_error("Invalid size, can't decode");
|
||||
|
||||
DataSerializerTraits<_Ty> s;
|
||||
for (auto&& sub : val)
|
||||
{
|
||||
s.decode(stream, sub);
|
||||
}
|
||||
}
|
||||
static void log(IStream* stream, const _Ty (&val)[_Size])
|
||||
{
|
||||
stream->Write("{", 1);
|
||||
DataSerializerTraits<_Ty> s;
|
||||
for (auto&& sub : val)
|
||||
{
|
||||
s.log(stream, sub);
|
||||
stream->Write("; ", 2);
|
||||
}
|
||||
stream->Write("}", 1);
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t _Size> struct DataSerializerTraits<uint8_t[_Size]> : public DataSerializerTraitsPODArray<uint8_t, _Size>
|
||||
{
|
||||
};
|
||||
|
||||
template<size_t _Size> struct DataSerializerTraits<uint16_t[_Size]> : public DataSerializerTraitsPODArray<uint16_t, _Size>
|
||||
{
|
||||
};
|
||||
|
||||
template<size_t _Size> struct DataSerializerTraits<uint32_t[_Size]> : public DataSerializerTraitsPODArray<uint32_t, _Size>
|
||||
{
|
||||
};
|
||||
|
||||
template<size_t _Size> struct DataSerializerTraits<uint64_t[_Size]> : public DataSerializerTraitsPODArray<uint64_t, _Size>
|
||||
{
|
||||
};
|
||||
|
||||
template<typename _Ty, size_t _Size> struct DataSerializerTraits<std::array<_Ty, _Size>>
|
||||
{
|
||||
static void encode(IStream* stream, const std::array<_Ty, _Size>& val)
|
||||
|
||||
@@ -49,6 +49,11 @@ MemoryStream::MemoryStream(const void* data, size_t dataSize)
|
||||
{
|
||||
}
|
||||
|
||||
MemoryStream::MemoryStream(MemoryStream&& mv)
|
||||
{
|
||||
*this = std::move(mv);
|
||||
}
|
||||
|
||||
MemoryStream::~MemoryStream()
|
||||
{
|
||||
if (_access & MEMORY_ACCESS::OWNER)
|
||||
@@ -60,6 +65,21 @@ MemoryStream::~MemoryStream()
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
MemoryStream& MemoryStream::operator=(MemoryStream&& mv)
|
||||
{
|
||||
_access = mv._access;
|
||||
_dataCapacity = mv._dataCapacity;
|
||||
_data = mv._data;
|
||||
_position = mv._position;
|
||||
|
||||
mv._data = nullptr;
|
||||
mv._position = nullptr;
|
||||
mv._dataCapacity = 0;
|
||||
mv._dataSize = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const void* MemoryStream::GetData() const
|
||||
{
|
||||
return _data;
|
||||
|
||||
@@ -34,11 +34,14 @@ private:
|
||||
public:
|
||||
MemoryStream() = default;
|
||||
MemoryStream(const MemoryStream& copy);
|
||||
MemoryStream(MemoryStream&& mv);
|
||||
explicit MemoryStream(size_t capacity);
|
||||
MemoryStream(void* data, size_t dataSize, uint8_t access = MEMORY_ACCESS::READ);
|
||||
MemoryStream(const void* data, size_t dataSize);
|
||||
virtual ~MemoryStream();
|
||||
|
||||
MemoryStream& operator=(MemoryStream&& mv);
|
||||
|
||||
const void* GetData() const;
|
||||
void* GetDataCopy() const;
|
||||
void* TakeData();
|
||||
|
||||
@@ -1549,6 +1549,66 @@ static int32_t cc_replay_normalise(InteractiveConsole& console, const arguments_
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t cc_mp_desync(InteractiveConsole& console, const arguments_t& argv)
|
||||
{
|
||||
int32_t desyncType = 0;
|
||||
if (argv.size() >= 1)
|
||||
{
|
||||
desyncType = atoi(argv[0].c_str());
|
||||
}
|
||||
|
||||
std::vector<rct_sprite*> peeps;
|
||||
std::vector<rct_sprite*> vehicles;
|
||||
|
||||
for (int i = 0; i < MAX_SPRITES; i++)
|
||||
{
|
||||
rct_sprite* sprite = get_sprite(i);
|
||||
if (sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_NULL)
|
||||
continue;
|
||||
|
||||
if (sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_PEEP)
|
||||
peeps.push_back(sprite);
|
||||
else if (sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_VEHICLE)
|
||||
vehicles.push_back(sprite);
|
||||
}
|
||||
|
||||
switch (desyncType)
|
||||
{
|
||||
case 0: // Peep t-shirts.
|
||||
{
|
||||
if (peeps.empty())
|
||||
{
|
||||
console.WriteFormatLine("No peeps");
|
||||
}
|
||||
else
|
||||
{
|
||||
rct_sprite* sprite = peeps[0];
|
||||
if (peeps.size() > 1)
|
||||
sprite = peeps[util_rand() % peeps.size() - 1];
|
||||
sprite->peep.tshirt_colour = util_rand() & 0xFF;
|
||||
invalidate_sprite_0(sprite);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: // Remove random peep.
|
||||
{
|
||||
if (peeps.empty())
|
||||
{
|
||||
console.WriteFormatLine("No peep removed");
|
||||
}
|
||||
else
|
||||
{
|
||||
rct_sprite* sprite = peeps[0];
|
||||
if (peeps.size() > 1)
|
||||
sprite = peeps[util_rand() % peeps.size() - 1];
|
||||
sprite->AsPeep()->Remove();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4702) // unreachable code
|
||||
static int32_t cc_abort([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
||||
@@ -1670,6 +1730,7 @@ static constexpr const console_command console_command_table[] = {
|
||||
{ "replay_start", cc_replay_start, "Starts a replay", "replay_start <name>"},
|
||||
{ "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>"},
|
||||
{ "mp_desync", cc_mp_desync, "Forces a multiplayer desync", "cc_mp_desync [desync_type, 0 = Random t-shirt color on random peep, 1 = Remove random peep ]"},
|
||||
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -3934,6 +3934,7 @@ enum
|
||||
STR_DOWNLOADING_OBJECTS = 6303,
|
||||
|
||||
STR_SHORTCUT_OPEN_SCENERY_PICKER = 6304,
|
||||
|
||||
STR_MULTITHREADING = 6305,
|
||||
STR_MULTITHREADING_TIP = 6306,
|
||||
|
||||
@@ -3952,6 +3953,8 @@ enum
|
||||
STR_PEEP_DEBUG_PATHFIND_HISTORY = 6316,
|
||||
STR_PEEP_DEBUG_PATHFIND_HISTORY_ITEM = 6317,
|
||||
|
||||
STR_DESYNC_REPORT = 6318,
|
||||
|
||||
// Have to include resource strings (from scenarios and objects) for the time being now that language is partially working
|
||||
STR_COUNT = 32768
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../Context.h"
|
||||
#include "../Game.h"
|
||||
#include "../GameStateSnapshots.h"
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../PlatformEnvironment.h"
|
||||
#include "../actions/LoadOrQuitAction.hpp"
|
||||
@@ -31,12 +32,16 @@
|
||||
// This string specifies which version of network stream current build uses.
|
||||
// It is used for making sure only compatible builds get connected, even within
|
||||
// single OpenRCT2 version.
|
||||
#define NETWORK_STREAM_VERSION "24"
|
||||
#define NETWORK_STREAM_VERSION "25"
|
||||
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
|
||||
|
||||
static Peep* _pickup_peep = nullptr;
|
||||
static int32_t _pickup_peep_old_x = LOCATION_NULL;
|
||||
|
||||
// General chunk size is 63 KiB, this can not be any larger because the packet size is encoded
|
||||
// with uint16_t and needs some spare room for other data in the packet.
|
||||
static constexpr uint32_t CHUNK_SIZE = 1024 * 63;
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
|
||||
# include "../Cheats.h"
|
||||
@@ -137,7 +142,9 @@ public:
|
||||
void SendPacketToClients(NetworkPacket& packet, bool front = false, bool gameCmd = false);
|
||||
bool CheckSRAND(uint32_t tick, uint32_t srand0);
|
||||
bool IsDesynchronised();
|
||||
void CheckDesynchronizaton();
|
||||
bool CheckDesynchronizaton();
|
||||
void RequestStateSnapshot();
|
||||
NetworkServerState_t GetServerState() const;
|
||||
void KickPlayer(int32_t playerId);
|
||||
void SetPassword(const char* password);
|
||||
void ShutdownClient();
|
||||
@@ -160,6 +167,8 @@ public:
|
||||
void AppendServerLog(const std::string& s);
|
||||
void CloseServerLog();
|
||||
|
||||
void Client_Send_RequestGameState(uint32_t tick);
|
||||
|
||||
void Client_Send_TOKEN();
|
||||
void Client_Send_AUTH(
|
||||
const std::string& name, const std::string& password, const std::string& pubkey, const std::vector<uint8_t>& signature);
|
||||
@@ -309,7 +318,6 @@ private:
|
||||
uint16_t listening_port = 0;
|
||||
SOCKET_STATUS _lastConnectStatus = SOCKET_STATUS_CLOSED;
|
||||
uint32_t last_ping_sent_time = 0;
|
||||
uint32_t server_tick = 0;
|
||||
uint8_t player_id = 0;
|
||||
std::list<std::unique_ptr<NetworkConnection>> client_connection_list;
|
||||
std::multiset<GameCommand> game_command_queue;
|
||||
@@ -317,7 +325,8 @@ private:
|
||||
std::string _host;
|
||||
uint16_t _port = 0;
|
||||
std::string _password;
|
||||
bool _desynchronised = false;
|
||||
NetworkServerState_t _serverState;
|
||||
MemoryStream _serverGameState;
|
||||
uint32_t server_connect_time = 0;
|
||||
uint8_t default_group = 0;
|
||||
uint32_t _commandId;
|
||||
@@ -336,6 +345,7 @@ private:
|
||||
private:
|
||||
std::vector<void (Network::*)(NetworkConnection& connection, NetworkPacket& packet)> client_command_handlers;
|
||||
std::vector<void (Network::*)(NetworkConnection& connection, NetworkPacket& packet)> server_command_handlers;
|
||||
void Server_Handle_REQUEST_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Server_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Server_Client_Joined(const char* name, const std::string& keyhash, NetworkConnection& connection);
|
||||
@@ -361,6 +371,7 @@ private:
|
||||
void Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Server_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Client_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet);
|
||||
void Server_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket& packet);
|
||||
|
||||
uint8_t* save_for_network(size_t& out_size, const std::vector<const ObjectRepositoryItem*>& objects) const;
|
||||
@@ -397,6 +408,7 @@ Network::Network()
|
||||
client_command_handlers[NETWORK_COMMAND_GAMEINFO] = &Network::Client_Handle_GAMEINFO;
|
||||
client_command_handlers[NETWORK_COMMAND_TOKEN] = &Network::Client_Handle_TOKEN;
|
||||
client_command_handlers[NETWORK_COMMAND_OBJECTS] = &Network::Client_Handle_OBJECTS;
|
||||
client_command_handlers[NETWORK_COMMAND_GAMESTATE] = &Network::Client_Handle_GAMESTATE;
|
||||
server_command_handlers.resize(NETWORK_COMMAND_MAX, nullptr);
|
||||
server_command_handlers[NETWORK_COMMAND_AUTH] = &Network::Server_Handle_AUTH;
|
||||
server_command_handlers[NETWORK_COMMAND_CHAT] = &Network::Server_Handle_CHAT;
|
||||
@@ -406,6 +418,7 @@ Network::Network()
|
||||
server_command_handlers[NETWORK_COMMAND_GAMEINFO] = &Network::Server_Handle_GAMEINFO;
|
||||
server_command_handlers[NETWORK_COMMAND_TOKEN] = &Network::Server_Handle_TOKEN;
|
||||
server_command_handlers[NETWORK_COMMAND_OBJECTS] = &Network::Server_Handle_OBJECTS;
|
||||
server_command_handlers[NETWORK_COMMAND_REQUEST_GAMESTATE] = &Network::Server_Handle_REQUEST_GAMESTATE;
|
||||
|
||||
_chat_log_fs << std::unitbuf;
|
||||
_server_log_fs << std::unitbuf;
|
||||
@@ -532,6 +545,8 @@ bool Network::BeginClient(const std::string& host, uint16_t port)
|
||||
_serverConnection = std::make_unique<NetworkConnection>();
|
||||
_serverConnection->Socket = CreateTcpSocket();
|
||||
_serverConnection->Socket->ConnectAsync(host, port);
|
||||
_serverState.gamestateSnapshotsEnabled = false;
|
||||
|
||||
status = NETWORK_STATUS_CONNECTING;
|
||||
_lastConnectStatus = SOCKET_STATUS_CLOSED;
|
||||
_clientMapLoaded = false;
|
||||
@@ -664,6 +679,8 @@ bool Network::BeginServer(uint16_t port, const std::string& address)
|
||||
|
||||
status = NETWORK_STATUS_CONNECTED;
|
||||
listening_port = port;
|
||||
_serverState.gamestateSnapshotsEnabled = gConfigNetwork.desync_debugging;
|
||||
|
||||
if (gConfigNetwork.advertise)
|
||||
{
|
||||
_advertiser = CreateServerAdvertiser(listening_port);
|
||||
@@ -703,7 +720,7 @@ int32_t Network::GetAuthStatus()
|
||||
|
||||
uint32_t Network::GetServerTick()
|
||||
{
|
||||
return server_tick;
|
||||
return _serverState.tick;
|
||||
}
|
||||
|
||||
uint8_t Network::GetPlayerID()
|
||||
@@ -997,7 +1014,10 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0)
|
||||
_serverTickData.erase(itTickData);
|
||||
|
||||
if (storedTick.srand0 != srand0)
|
||||
{
|
||||
log_info("Srand0 mismatch, client = %08X, server = %08X", srand0, storedTick.srand0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!storedTick.spriteHash.empty())
|
||||
{
|
||||
@@ -1005,6 +1025,7 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0)
|
||||
std::string clientSpriteHash = checksum.ToString();
|
||||
if (clientSpriteHash != storedTick.spriteHash)
|
||||
{
|
||||
log_info("Sprite hash mismatch, client = %s, server = %s", clientSpriteHash.c_str(), storedTick.spriteHash.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1014,15 +1035,17 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0)
|
||||
|
||||
bool Network::IsDesynchronised()
|
||||
{
|
||||
return gNetwork._desynchronised;
|
||||
return _serverState.state == NETWORK_SERVER_STATE_DESYNCED;
|
||||
}
|
||||
|
||||
void Network::CheckDesynchronizaton()
|
||||
bool Network::CheckDesynchronizaton()
|
||||
{
|
||||
// Check synchronisation
|
||||
if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, scenario_rand_state().s0))
|
||||
if (GetMode() == NETWORK_MODE_CLIENT && _serverState.state != NETWORK_SERVER_STATE_DESYNCED
|
||||
&& !CheckSRAND(gCurrentTicks, scenario_rand_state().s0))
|
||||
{
|
||||
_desynchronised = true;
|
||||
_serverState.state = NETWORK_SERVER_STATE_DESYNCED;
|
||||
_serverState.desyncTick = gCurrentTicks;
|
||||
|
||||
char str_desync[256];
|
||||
format_string(str_desync, 256, STR_MULTIPLAYER_DESYNC, nullptr);
|
||||
@@ -1035,7 +1058,23 @@ void Network::CheckDesynchronizaton()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Network::RequestStateSnapshot()
|
||||
{
|
||||
log_info("Requesting game state for tick %u", _serverState.desyncTick);
|
||||
|
||||
Client_Send_RequestGameState(_serverState.desyncTick);
|
||||
}
|
||||
|
||||
NetworkServerState_t Network::GetServerState() const
|
||||
{
|
||||
return _serverState;
|
||||
}
|
||||
|
||||
void Network::KickPlayer(int32_t playerId)
|
||||
@@ -1403,6 +1442,20 @@ void Network::CloseServerLog()
|
||||
_server_log_fs.close();
|
||||
}
|
||||
|
||||
void Network::Client_Send_RequestGameState(uint32_t tick)
|
||||
{
|
||||
if (_serverState.gamestateSnapshotsEnabled == false)
|
||||
{
|
||||
log_verbose("Server does not store a gamestate history");
|
||||
return;
|
||||
}
|
||||
|
||||
log_verbose("Requesting gamestate from server for tick %u", tick);
|
||||
std::unique_ptr<NetworkPacket> packet(NetworkPacket::Allocate());
|
||||
*packet << (uint32_t)NETWORK_COMMAND_REQUEST_GAMESTATE << tick;
|
||||
_serverConnection->QueuePacket(std::move(packet));
|
||||
}
|
||||
|
||||
void Network::Client_Send_TOKEN()
|
||||
{
|
||||
log_verbose("requesting token");
|
||||
@@ -1531,7 +1584,7 @@ void Network::Server_Send_MAP(NetworkConnection* connection)
|
||||
}
|
||||
return;
|
||||
}
|
||||
size_t chunksize = 65000;
|
||||
size_t chunksize = CHUNK_SIZE;
|
||||
for (size_t i = 0; i < out_size; i += chunksize)
|
||||
{
|
||||
size_t datasize = std::min(chunksize, out_size - i);
|
||||
@@ -1784,6 +1837,8 @@ void Network::Server_Send_GAMEINFO(NetworkConnection& connection)
|
||||
json_object_set_new(obj, "provider", jsonProvider);
|
||||
|
||||
packet->WriteString(json_dumps(obj, 0));
|
||||
*packet << _serverState.gamestateSnapshotsEnabled;
|
||||
|
||||
json_decref(obj);
|
||||
# endif
|
||||
connection.QueuePacket(std::move(packet));
|
||||
@@ -2313,6 +2368,48 @@ void Network::Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket&
|
||||
Client_Send_AUTH(gConfigNetwork.player_name.c_str(), password, pubkey.c_str(), signature);
|
||||
}
|
||||
|
||||
void Network::Server_Handle_REQUEST_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet)
|
||||
{
|
||||
uint32_t tick;
|
||||
packet >> tick;
|
||||
|
||||
if (_serverState.gamestateSnapshotsEnabled == false)
|
||||
{
|
||||
// Ignore this if this is off.
|
||||
return;
|
||||
}
|
||||
|
||||
IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
|
||||
|
||||
const GameStateSnapshot_t* snapshot = snapshots->GetLinkedSnapshot(tick);
|
||||
if (snapshot)
|
||||
{
|
||||
MemoryStream snapshotMemory;
|
||||
DataSerialiser ds(true, snapshotMemory);
|
||||
|
||||
snapshots->SerialiseSnapshot(const_cast<GameStateSnapshot_t&>(*snapshot), ds);
|
||||
|
||||
uint32_t bytesSent = 0;
|
||||
uint32_t length = (uint32_t)snapshotMemory.GetLength();
|
||||
while (bytesSent < length)
|
||||
{
|
||||
uint32_t dataSize = CHUNK_SIZE;
|
||||
if (bytesSent + dataSize > snapshotMemory.GetLength())
|
||||
{
|
||||
dataSize = snapshotMemory.GetLength() - bytesSent;
|
||||
}
|
||||
|
||||
std::unique_ptr<NetworkPacket> gameStateChunk(NetworkPacket::Allocate());
|
||||
*gameStateChunk << (uint32_t)NETWORK_COMMAND_GAMESTATE << tick << length << bytesSent << dataSize;
|
||||
gameStateChunk->Write((const uint8_t*)snapshotMemory.GetData() + bytesSent, dataSize);
|
||||
|
||||
connection.QueuePacket(std::move(gameStateChunk));
|
||||
|
||||
bytesSent += dataSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Network::Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet)
|
||||
{
|
||||
uint32_t auth_status;
|
||||
@@ -2436,6 +2533,73 @@ void Network::Client_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket
|
||||
Client_Send_OBJECTS(requested_objects);
|
||||
}
|
||||
|
||||
void Network::Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet)
|
||||
{
|
||||
uint32_t tick;
|
||||
uint32_t totalSize;
|
||||
uint32_t offset;
|
||||
uint32_t dataSize;
|
||||
|
||||
packet >> tick >> totalSize >> offset >> dataSize;
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
// Reset
|
||||
_serverGameState = MemoryStream();
|
||||
}
|
||||
|
||||
_serverGameState.SetPosition(offset);
|
||||
|
||||
const uint8_t* data = packet.Read(dataSize);
|
||||
_serverGameState.Write(data, dataSize);
|
||||
|
||||
log_verbose("Received Game State %.02f%%", ((float)_serverGameState.GetLength() / (float)totalSize) * 100.0f);
|
||||
|
||||
if (_serverGameState.GetLength() == totalSize)
|
||||
{
|
||||
_serverGameState.SetPosition(0);
|
||||
DataSerialiser ds(false, _serverGameState);
|
||||
|
||||
IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
|
||||
|
||||
GameStateSnapshot_t& serverSnapshot = snapshots->CreateSnapshot();
|
||||
snapshots->SerialiseSnapshot(serverSnapshot, ds);
|
||||
|
||||
const GameStateSnapshot_t* desyncSnapshot = snapshots->GetLinkedSnapshot(tick);
|
||||
if (desyncSnapshot)
|
||||
{
|
||||
GameStateCompareData_t cmpData = snapshots->Compare(serverSnapshot, *desyncSnapshot);
|
||||
|
||||
std::string outputPath = GetContext()->GetPlatformEnvironment()->GetDirectoryPath(
|
||||
DIRBASE::USER, DIRID::LOG_DESYNCS);
|
||||
|
||||
platform_ensure_directory_exists(outputPath.c_str());
|
||||
|
||||
char uniqueFileName[128] = {};
|
||||
snprintf(
|
||||
uniqueFileName, sizeof(uniqueFileName), "desync_%llu_%u.txt",
|
||||
(long long unsigned)platform_get_datetime_now_utc(), tick);
|
||||
|
||||
std::string outputFile = Path::Combine(outputPath, uniqueFileName);
|
||||
|
||||
if (snapshots->LogCompareDataToFile(outputFile, cmpData))
|
||||
{
|
||||
log_info("Wrote desync report to '%s'", outputFile.c_str());
|
||||
|
||||
uint8_t args[32]{};
|
||||
set_format_arg_on(args, 0, char*, uniqueFileName);
|
||||
|
||||
char str_desync[1024];
|
||||
format_string(str_desync, sizeof(str_desync), STR_DESYNC_REPORT, args);
|
||||
|
||||
auto intent = Intent(WC_NETWORK_STATUS);
|
||||
intent.putExtra(INTENT_EXTRA_MESSAGE, std::string{ str_desync });
|
||||
context_open_intent(&intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Network::Server_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket& packet)
|
||||
{
|
||||
uint32_t size;
|
||||
@@ -2644,9 +2808,10 @@ void Network::Client_Handle_MAP([[maybe_unused]] NetworkConnection& connection,
|
||||
game_load_init();
|
||||
game_command_queue.clear();
|
||||
_serverTickData.clear();
|
||||
server_tick = gCurrentTicks;
|
||||
_serverState.tick = gCurrentTicks;
|
||||
_serverTickData.clear();
|
||||
// window_network_status_open("Loaded new map from network");
|
||||
_desynchronised = false;
|
||||
_serverState.state = NETWORK_SERVER_STATE_OK;
|
||||
_clientMapLoaded = true;
|
||||
gFirstTimeSaving = true;
|
||||
|
||||
@@ -2977,11 +3142,13 @@ void Network::Client_Handle_TICK([[maybe_unused]] NetworkConnection& connection,
|
||||
{
|
||||
uint32_t srand0;
|
||||
uint32_t flags;
|
||||
packet >> server_tick >> srand0 >> flags;
|
||||
uint32_t serverTick;
|
||||
|
||||
packet >> serverTick >> srand0 >> flags;
|
||||
|
||||
ServerTickData_t tickData;
|
||||
tickData.srand0 = srand0;
|
||||
tickData.tick = server_tick;
|
||||
tickData.tick = serverTick;
|
||||
|
||||
if (flags & NETWORK_TICK_FLAG_CHECKSUMS)
|
||||
{
|
||||
@@ -2998,7 +3165,8 @@ void Network::Client_Handle_TICK([[maybe_unused]] NetworkConnection& connection,
|
||||
_serverTickData.erase(_serverTickData.begin());
|
||||
}
|
||||
|
||||
_serverTickData.emplace(server_tick, tickData);
|
||||
_serverState.tick = serverTick;
|
||||
_serverTickData.emplace(serverTick, tickData);
|
||||
}
|
||||
|
||||
void Network::Client_Handle_PLAYERINFO([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
|
||||
@@ -3154,6 +3322,7 @@ static std::string json_stdstring_value(const json_t* string)
|
||||
void Network::Client_Handle_GAMEINFO([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
|
||||
{
|
||||
const char* jsonString = packet.ReadString();
|
||||
packet >> _serverState.gamestateSnapshotsEnabled;
|
||||
|
||||
json_error_t error;
|
||||
json_t* root = json_loads(jsonString, 0, &error);
|
||||
@@ -3234,11 +3403,16 @@ bool network_is_desynchronised()
|
||||
return gNetwork.IsDesynchronised();
|
||||
}
|
||||
|
||||
void network_check_desynchronization()
|
||||
bool network_check_desynchronisation()
|
||||
{
|
||||
return gNetwork.CheckDesynchronizaton();
|
||||
}
|
||||
|
||||
void network_request_gamestate_snapshot()
|
||||
{
|
||||
return gNetwork.RequestStateSnapshot();
|
||||
}
|
||||
|
||||
void network_send_tick()
|
||||
{
|
||||
gNetwork.Server_Send_TICK();
|
||||
@@ -3995,6 +4169,16 @@ NetworkStats_t network_get_stats()
|
||||
return gNetwork.GetStats();
|
||||
}
|
||||
|
||||
NetworkServerState_t network_get_server_state()
|
||||
{
|
||||
return gNetwork.GetServerState();
|
||||
}
|
||||
|
||||
bool network_gamestate_snapshots_enabled()
|
||||
{
|
||||
return network_get_server_state().gamestateSnapshotsEnabled;
|
||||
}
|
||||
|
||||
#else
|
||||
int32_t network_get_mode()
|
||||
{
|
||||
@@ -4022,7 +4206,15 @@ bool network_is_desynchronised()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void network_check_desynchronization()
|
||||
bool network_gamestate_snapshots_enabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool network_check_desynchronisation()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void network_request_gamestate_snapshot()
|
||||
{
|
||||
}
|
||||
void network_enqueue_game_action(const GameAction* action)
|
||||
@@ -4240,4 +4432,8 @@ NetworkStats_t network_get_stats()
|
||||
{
|
||||
return NetworkStats_t{};
|
||||
}
|
||||
NetworkServerState_t network_get_server_state()
|
||||
{
|
||||
return NetworkServerState_t{};
|
||||
}
|
||||
#endif /* DISABLE_NETWORK */
|
||||
|
||||
@@ -66,12 +66,29 @@ enum NETWORK_COMMAND
|
||||
NETWORK_COMMAND_OBJECTS,
|
||||
NETWORK_COMMAND_GAME_ACTION,
|
||||
NETWORK_COMMAND_PLAYERINFO,
|
||||
NETWORK_COMMAND_REQUEST_GAMESTATE,
|
||||
NETWORK_COMMAND_GAMESTATE,
|
||||
NETWORK_COMMAND_MAX,
|
||||
NETWORK_COMMAND_INVALID = -1
|
||||
};
|
||||
|
||||
static_assert(NETWORK_COMMAND::NETWORK_COMMAND_GAMEINFO == 9, "Master server expects this to be 9");
|
||||
|
||||
enum NETWORK_SERVER_STATE
|
||||
{
|
||||
NETWORK_SERVER_STATE_OK,
|
||||
NETWORK_SERVER_STATE_DESYNCED,
|
||||
};
|
||||
|
||||
struct NetworkServerState_t
|
||||
{
|
||||
NETWORK_SERVER_STATE state = NETWORK_SERVER_STATE_OK;
|
||||
uint32_t desyncTick = 0;
|
||||
uint32_t tick = 0;
|
||||
uint32_t srand0 = 0;
|
||||
bool gamestateSnapshotsEnabled = false;
|
||||
};
|
||||
|
||||
// Structure is used for networking specific fields with meaning,
|
||||
// this structure can be used in combination with DataSerialiser
|
||||
// to provide extra details with template specialization.
|
||||
|
||||
@@ -39,8 +39,10 @@ int32_t network_begin_server(int32_t port, const std::string& address);
|
||||
int32_t network_get_mode();
|
||||
int32_t network_get_status();
|
||||
bool network_is_desynchronised();
|
||||
void network_check_desynchronization();
|
||||
bool network_check_desynchronisation();
|
||||
void network_request_gamestate_snapshot();
|
||||
void network_send_tick();
|
||||
bool network_gamestate_snapshots_enabled();
|
||||
void network_update();
|
||||
void network_process_pending();
|
||||
void network_flush();
|
||||
@@ -106,3 +108,4 @@ const utf8* network_get_server_provider_website();
|
||||
std::string network_get_version();
|
||||
|
||||
NetworkStats_t network_get_stats();
|
||||
NetworkServerState_t network_get_server_state();
|
||||
|
||||
Reference in New Issue
Block a user