Network serialiser for entities (#14541)

* Start a network serialiser for entities

will be used only for checksums and replay diffs

* Continue work

* Use the new serailser for checksums

* Use new serialiser for replays

* keep compilers happy

* Try create checksum stream

* Fix compiling

* Split off class into seperate file

* Update Xcode project

* Increment network version

* Fix pragma mistake

* Fix none network builds

* Update replays

* Improve ChecksumStream and use FNV internally

* Small cleanups

* satisfy compilers

* Revert change of checksum size to simplfy rerecording

* Zero initialise data

* Fix serialiser

* Update replays again

Co-authored-by: Michael Steenbeek <m.o.steenbeek@gmail.com>
Co-authored-by: Matt <m.moninger.h@gmail.com>
This commit is contained in:
Duncan 2021-05-09 19:12:44 +01:00 committed by GitHub
parent c63e072974
commit d46e4a9bb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 602 additions and 71 deletions

View File

@ -50,9 +50,9 @@ set(OBJECTS_VERSION "1.0.21")
set(OBJECTS_URL "https://github.com/OpenRCT2/objects/releases/download/v${OBJECTS_VERSION}/objects.zip") set(OBJECTS_URL "https://github.com/OpenRCT2/objects/releases/download/v${OBJECTS_VERSION}/objects.zip")
set(OBJECTS_SHA1 "c38af45d51a6e440386180feacf76c64720b6ac5") set(OBJECTS_SHA1 "c38af45d51a6e440386180feacf76c64720b6ac5")
set(REPLAYS_VERSION "0.0.39") set(REPLAYS_VERSION "0.0.42")
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip") set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip")
set(REPLAYS_SHA1 "8AF797661D87394FBE1A059375D82632094290FB") set(REPLAYS_SHA1 "C60FC1D6526DB8EDDF4AFBB0EE52A4A0D561646E")
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.") option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.")
option(WITH_TESTS "Build tests") option(WITH_TESTS "Build tests")

View File

@ -60,6 +60,8 @@
4C8BB68525533DB9005C8830 /* ZoomLevel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C8BB68425533DB9005C8830 /* ZoomLevel.cpp */; }; 4C8BB68525533DB9005C8830 /* ZoomLevel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C8BB68425533DB9005C8830 /* ZoomLevel.cpp */; };
4C91FD5F25AE476700CA5DA4 /* MusicObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C91FD5D25AE476700CA5DA4 /* MusicObject.cpp */; }; 4C91FD5F25AE476700CA5DA4 /* MusicObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C91FD5D25AE476700CA5DA4 /* MusicObject.cpp */; };
4C91FD6225AE483700CA5DA4 /* RideAudio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C91FD6025AE483600CA5DA4 /* RideAudio.cpp */; }; 4C91FD6225AE483700CA5DA4 /* RideAudio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C91FD6025AE483600CA5DA4 /* RideAudio.cpp */; };
4CA23D64263C91D800077AA1 /* ChecksumStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA23D62263C91D700077AA1 /* ChecksumStream.cpp */; };
4CA23DB2263C920900077AA1 /* Entity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA23DB1263C920900077AA1 /* Entity.cpp */; };
4CA39E512513F8A00094066B /* RTL.ICU.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA39E4E2513F8A00094066B /* RTL.ICU.cpp */; }; 4CA39E512513F8A00094066B /* RTL.ICU.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA39E4E2513F8A00094066B /* RTL.ICU.cpp */; };
4CA39E522513F8A00094066B /* RTL.FriBidi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA39E502513F8A00094066B /* RTL.FriBidi.cpp */; }; 4CA39E522513F8A00094066B /* RTL.FriBidi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA39E502513F8A00094066B /* RTL.FriBidi.cpp */; };
4CB1375621C2E9F80029FCDA /* SimulateCommands.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CB1375521C2E9F80029FCDA /* SimulateCommands.cpp */; }; 4CB1375621C2E9F80029FCDA /* SimulateCommands.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CB1375521C2E9F80029FCDA /* SimulateCommands.cpp */; };
@ -1152,6 +1154,11 @@
4C93F1B71F8E185600A9330D /* NewsItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewsItem.h; sourceTree = "<group>"; }; 4C93F1B71F8E185600A9330D /* NewsItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewsItem.h; sourceTree = "<group>"; };
4C93F1B81F8E185600A9330D /* Research.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Research.cpp; sourceTree = "<group>"; }; 4C93F1B81F8E185600A9330D /* Research.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Research.cpp; sourceTree = "<group>"; };
4C93F1B91F8E185600A9330D /* Research.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Research.h; sourceTree = "<group>"; }; 4C93F1B91F8E185600A9330D /* Research.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Research.h; sourceTree = "<group>"; };
4CA23D62263C91D700077AA1 /* ChecksumStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ChecksumStream.cpp; sourceTree = "<group>"; };
4CA23D63263C91D700077AA1 /* ChecksumStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChecksumStream.h; sourceTree = "<group>"; };
4CA23DAF263C920900077AA1 /* Entity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Entity.h; sourceTree = "<group>"; };
4CA23DB0263C920900077AA1 /* EntityList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EntityList.h; sourceTree = "<group>"; };
4CA23DB1263C920900077AA1 /* Entity.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Entity.cpp; sourceTree = "<group>"; };
4CA39E4E2513F8A00094066B /* RTL.ICU.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RTL.ICU.cpp; sourceTree = "<group>"; }; 4CA39E4E2513F8A00094066B /* RTL.ICU.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RTL.ICU.cpp; sourceTree = "<group>"; };
4CA39E4F2513F8A00094066B /* RTL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTL.h; sourceTree = "<group>"; }; 4CA39E4F2513F8A00094066B /* RTL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTL.h; sourceTree = "<group>"; };
4CA39E502513F8A00094066B /* RTL.FriBidi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RTL.FriBidi.cpp; sourceTree = "<group>"; }; 4CA39E502513F8A00094066B /* RTL.FriBidi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RTL.FriBidi.cpp; sourceTree = "<group>"; };
@ -2433,6 +2440,8 @@
F76C83781EC4E7CC00FA49E2 /* core */ = { F76C83781EC4E7CC00FA49E2 /* core */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CA23D62263C91D700077AA1 /* ChecksumStream.cpp */,
4CA23D63263C91D700077AA1 /* ChecksumStream.h */,
2A5354EA22099C7200A5440F /* CircularBuffer.h */, 2A5354EA22099C7200A5440F /* CircularBuffer.h */,
F76C83791EC4E7CC00FA49E2 /* Collections.hpp */, F76C83791EC4E7CC00FA49E2 /* Collections.hpp */,
F76C837A1EC4E7CC00FA49E2 /* Console.cpp */, F76C837A1EC4E7CC00FA49E2 /* Console.cpp */,
@ -3066,6 +3075,9 @@
4C7B54202007646A00A52E21 /* Climate.cpp */, 4C7B54202007646A00A52E21 /* Climate.cpp */,
4C7B54212007646A00A52E21 /* Climate.h */, 4C7B54212007646A00A52E21 /* Climate.h */,
4C7B54222007646A00A52E21 /* Duck.cpp */, 4C7B54222007646A00A52E21 /* Duck.cpp */,
4CA23DB1263C920900077AA1 /* Entity.cpp */,
4CA23DAF263C920900077AA1 /* Entity.h */,
4CA23DB0263C920900077AA1 /* EntityList.h */,
4C7B54232007646A00A52E21 /* Entrance.cpp */, 4C7B54232007646A00A52E21 /* Entrance.cpp */,
4C7B54242007646A00A52E21 /* Entrance.h */, 4C7B54242007646A00A52E21 /* Entrance.h */,
4C7B54252007646A00A52E21 /* Footpath.cpp */, 4C7B54252007646A00A52E21 /* Footpath.cpp */,
@ -3774,6 +3786,7 @@
4C8BB67925533D4C005C8830 /* FileStream.cpp in Sources */, 4C8BB67925533D4C005C8830 /* FileStream.cpp in Sources */,
933CBDBD20CB1BA900134678 /* ViewportInteraction.cpp in Sources */, 933CBDBD20CB1BA900134678 /* ViewportInteraction.cpp in Sources */,
C685E51B1F8907850090598F /* Guest.cpp in Sources */, C685E51B1F8907850090598F /* Guest.cpp in Sources */,
4CA23DB2263C920900077AA1 /* Entity.cpp in Sources */,
C64644F91F3FA4120026AC2D /* EditorInventionsList.cpp in Sources */, C64644F91F3FA4120026AC2D /* EditorInventionsList.cpp in Sources */,
C68878C720289B710084B384 /* OpenGLShaderProgram.cpp in Sources */, C68878C720289B710084B384 /* OpenGLShaderProgram.cpp in Sources */,
4CA39E512513F8A00094066B /* RTL.ICU.cpp in Sources */, 4CA39E512513F8A00094066B /* RTL.ICU.cpp in Sources */,
@ -3839,6 +3852,7 @@
4C8BB67C25533D59005C8830 /* JobPool.cpp in Sources */, 4C8BB67C25533D59005C8830 /* JobPool.cpp in Sources */,
01C6F0C222FD519E0057E2F7 /* TrackImporter.cpp in Sources */, 01C6F0C222FD519E0057E2F7 /* TrackImporter.cpp in Sources */,
4C8BB68125533D65005C8830 /* StringBuilder.cpp in Sources */, 4C8BB68125533D65005C8830 /* StringBuilder.cpp in Sources */,
4CA23D64263C91D800077AA1 /* ChecksumStream.cpp in Sources */,
C666EE761F37ACB10061AA04 /* Options.cpp in Sources */, C666EE761F37ACB10061AA04 /* Options.cpp in Sources */,
4CB991CC25CEE54500C692B4 /* Shortcuts.cpp in Sources */, 4CB991CC25CEE54500C692B4 /* Shortcuts.cpp in Sources */,
C666EE6E1F37ACB10061AA04 /* CustomCurrency.cpp in Sources */, C666EE6E1F37ACB10061AA04 /* CustomCurrency.cpp in Sources */,

View File

@ -48,8 +48,8 @@
<TitleSequencesSha1>304d13a126c15bf2c86ff13b81a2f2cc1856ac8d</TitleSequencesSha1> <TitleSequencesSha1>304d13a126c15bf2c86ff13b81a2f2cc1856ac8d</TitleSequencesSha1>
<ObjectsUrl>https://github.com/OpenRCT2/objects/releases/download/v1.0.21/objects.zip</ObjectsUrl> <ObjectsUrl>https://github.com/OpenRCT2/objects/releases/download/v1.0.21/objects.zip</ObjectsUrl>
<ObjectsSha1>c38af45d51a6e440386180feacf76c64720b6ac5</ObjectsSha1> <ObjectsSha1>c38af45d51a6e440386180feacf76c64720b6ac5</ObjectsSha1>
<ReplaysUrl>https://github.com/OpenRCT2/replays/releases/download/v0.0.39/replays.zip</ReplaysUrl> <ReplaysUrl>https://github.com/OpenRCT2/replays/releases/download/v0.0.42/replays.zip</ReplaysUrl>
<ReplaysSha1>8AF797661D87394FBE1A059375D82632094290FB</ReplaysSha1> <ReplaysSha1>C60FC1D6526DB8EDDF4AFBB0EE52A4A0D561646E</ReplaysSha1>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1795,18 +1795,18 @@ void window_guest_thoughts_paint(rct_window* w, rct_drawpixelinfo* dpi)
DrawTextBasic(dpi, screenCoords, STR_GUEST_RECENT_THOUGHTS_LABEL); DrawTextBasic(dpi, screenCoords, STR_GUEST_RECENT_THOUGHTS_LABEL);
screenCoords.y += 10; screenCoords.y += 10;
for (rct_peep_thought* thought = peep->Thoughts; thought < &peep->Thoughts[PEEP_MAX_THOUGHTS]; ++thought) for (const auto& thought : peep->Thoughts)
{ {
if (thought->type == PeepThoughtType::None) if (thought.type == PeepThoughtType::None)
return; return;
if (thought->freshness == 0) if (thought.freshness == 0)
continue; continue;
int32_t width = window_guest_thoughts_widgets[WIDX_PAGE_BACKGROUND].right int32_t width = window_guest_thoughts_widgets[WIDX_PAGE_BACKGROUND].right
- window_guest_thoughts_widgets[WIDX_PAGE_BACKGROUND].left - 8; - window_guest_thoughts_widgets[WIDX_PAGE_BACKGROUND].left - 8;
auto ft = Formatter(); auto ft = Formatter();
peep_thought_set_format_args(thought, ft); peep_thought_set_format_args(&thought, ft);
screenCoords.y += DrawTextWrapped(dpi, screenCoords, width, STR_BLACK_STRING, ft, { FontSpriteBase::SMALL }); screenCoords.y += DrawTextWrapped(dpi, screenCoords, width, STR_BLACK_STRING, ft, { FontSpriteBase::SMALL });
// If this is the last visible line end drawing. // If this is the last visible line end drawing.

View File

@ -82,30 +82,31 @@ struct GameStateSnapshot_t
switch (sprite.misc.Type) switch (sprite.misc.Type)
{ {
case EntityType::Vehicle: case EntityType::Vehicle:
ds << reinterpret_cast<uint8_t(&)[sizeof(Vehicle)]>(sprite.vehicle); reinterpret_cast<Vehicle&>(sprite).Serialise(ds);
break; break;
case EntityType::Guest: case EntityType::Guest:
ds << reinterpret_cast<uint8_t(&)[sizeof(Guest)]>(sprite.peep); reinterpret_cast<Guest&>(sprite).Serialise(ds);
break; break;
case EntityType::Staff: case EntityType::Staff:
ds << reinterpret_cast<uint8_t(&)[sizeof(Staff)]>(sprite.peep); reinterpret_cast<Staff&>(sprite).Serialise(ds);
break; break;
case EntityType::Litter: case EntityType::Litter:
ds << reinterpret_cast<uint8_t(&)[sizeof(Litter)]>(sprite.litter); reinterpret_cast<Litter&>(sprite).Serialise(ds);
break; break;
case EntityType::MoneyEffect: case EntityType::MoneyEffect:
ds << reinterpret_cast<uint8_t(&)[sizeof(MoneyEffect)]>(sprite.money_effect); reinterpret_cast<MoneyEffect&>(sprite).Serialise(ds);
break; break;
case EntityType::Balloon: case EntityType::Balloon:
ds << reinterpret_cast<uint8_t(&)[sizeof(Balloon)]>(sprite.balloon); reinterpret_cast<Balloon&>(sprite).Serialise(ds);
break; break;
case EntityType::Duck: case EntityType::Duck:
ds << reinterpret_cast<uint8_t(&)[sizeof(Duck)]>(sprite.duck); reinterpret_cast<Duck&>(sprite).Serialise(ds);
break; break;
case EntityType::JumpingFountain: case EntityType::JumpingFountain:
ds << reinterpret_cast<uint8_t(&)[sizeof(JumpingFountain)]>(sprite.jumping_fountain); reinterpret_cast<JumpingFountain&>(sprite).Serialise(ds);
break; break;
case EntityType::SteamParticle: case EntityType::SteamParticle:
reinterpret_cast<SteamParticle&>(sprite).Serialise(ds);
ds << reinterpret_cast<uint8_t(&)[sizeof(SteamParticle)]>(sprite.steam_particle); ds << reinterpret_cast<uint8_t(&)[sizeof(SteamParticle)]>(sprite.steam_particle);
break; break;
case EntityType::Null: case EntityType::Null:

View File

@ -0,0 +1,47 @@
/*****************************************************************************
* Copyright (c) 2014-2021 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.
*****************************************************************************/
#include "ChecksumStream.h"
#include "Endianness.h"
#include <cstddef>
namespace OpenRCT2
{
#ifndef DISABLE_NETWORK
ChecksumStream::ChecksumStream(std::array<std::byte, 20>& buf)
: _checksum(buf)
{
uint64_t* hash = reinterpret_cast<uint64_t*>(_checksum.data());
*hash = Seed;
}
void ChecksumStream::Write(const void* buffer, uint64_t length)
{
uint64_t* hash = reinterpret_cast<uint64_t*>(_checksum.data());
for (size_t i = 0; i < length; i += sizeof(uint64_t))
{
const auto maxLen = std::min<size_t>(sizeof(uint64_t), length - i);
uint64_t temp{};
std::memcpy(&temp, reinterpret_cast<const std::byte*>(buffer) + i, maxLen);
// Always use value as little endian, most common systems are little.
# if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
temp = ByteSwapBE(temp);
# endif
*hash ^= temp;
*hash *= Prime;
}
}
#endif
} // namespace OpenRCT2

View File

@ -0,0 +1,110 @@
/*****************************************************************************
* Copyright (c) 2014-2021 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 "IStream.hpp"
namespace OpenRCT2
{
/**
* A stream for checksumming a stream of data
*/
class ChecksumStream final : public IStream
{
// FIXME: Move the checksum implementation out.
std::array<std::byte, 20>& _checksum;
static constexpr uint64_t Seed = 0xcbf29ce484222325ULL;
static constexpr uint64_t Prime = 0x00000100000001B3ULL;
public:
ChecksumStream(std::array<std::byte, 20>& buf);
virtual ~ChecksumStream() = default;
const void* GetData() const override
{
return _checksum.data();
};
///////////////////////////////////////////////////////////////////////////
// ISteam methods
///////////////////////////////////////////////////////////////////////////
bool CanRead() const override
{
return false;
}
bool CanWrite() const override
{
return true;
}
uint64_t GetLength() const override
{
return _checksum.size();
}
uint64_t GetPosition() const override
{
return 0;
}
void SetPosition(uint64_t position) override
{
}
void Seek(int64_t offset, int32_t origin) override
{
}
void Read(void* buffer, uint64_t length) override
{
}
void Write(const void* buffer, uint64_t length) override;
void Write1(const void* buffer) override
{
Write<1>(buffer);
}
void Write2(const void* buffer) override
{
Write<2>(buffer);
}
void Write4(const void* buffer) override
{
Write<4>(buffer);
}
void Write8(const void* buffer) override
{
Write<8>(buffer);
}
void Write16(const void* buffer) override
{
Write<16>(buffer);
}
template<size_t N> void Write(const void* buffer)
{
Write(buffer, N);
}
uint64_t TryRead(void* buffer, uint64_t length) override
{
return 0;
}
};
} // namespace OpenRCT2

View File

@ -302,7 +302,7 @@ template<typename _Ty, size_t _Size> struct DataSerializerTraitsPODArray
uint16_t swapped = ByteSwapBE(len); uint16_t swapped = ByteSwapBE(len);
stream->Write(&swapped); stream->Write(&swapped);
DataSerializerTraits<uint8_t> s; DataSerializerTraits<_Ty> s;
for (auto&& sub : val) for (auto&& sub : val)
{ {
s.encode(stream, sub); s.encode(stream, sub);
@ -584,6 +584,34 @@ template<> struct DataSerializerTraits_t<CoordsXYZD>
stream->Write(msg, strlen(msg)); stream->Write(msg, strlen(msg));
} }
}; };
template<> struct DataSerializerTraits_t<rct12_xyzd8>
{
static void encode(OpenRCT2::IStream* stream, const rct12_xyzd8& coord)
{
stream->WriteValue(ByteSwapBE(coord.x));
stream->WriteValue(ByteSwapBE(coord.y));
stream->WriteValue(ByteSwapBE(coord.z));
stream->WriteValue(ByteSwapBE(coord.direction));
}
static void decode(OpenRCT2::IStream* stream, rct12_xyzd8& coord)
{
auto x = ByteSwapBE(stream->ReadValue<uint8_t>());
auto y = ByteSwapBE(stream->ReadValue<uint8_t>());
auto z = ByteSwapBE(stream->ReadValue<uint8_t>());
auto d = ByteSwapBE(stream->ReadValue<uint8_t>());
coord = rct12_xyzd8{ x, y, z, d };
}
static void log(OpenRCT2::IStream* stream, const rct12_xyzd8& coord)
{
char msg[128] = {};
snprintf(
msg, sizeof(msg), "rct12_xyzd8(x = %d, y = %d, z = %d, direction = %d)", coord.x, coord.y, coord.z,
coord.direction);
stream->Write(msg, strlen(msg));
}
};
template<> struct DataSerializerTraits_t<NetworkCheatType_t> template<> struct DataSerializerTraits_t<NetworkCheatType_t>
{ {
@ -793,3 +821,49 @@ template<> struct DataSerializerTraits_t<ObjectEntryDescriptor>
stream->Write(msg, strlen(msg)); stream->Write(msg, strlen(msg));
} }
}; };
template<> struct DataSerializerTraits_t<IntensityRange>
{
static void encode(OpenRCT2::IStream* stream, const IntensityRange& val)
{
uint8_t temp = uint8_t(val);
stream->Write(&temp);
}
static void decode(OpenRCT2::IStream* stream, IntensityRange& val)
{
auto temp = stream->ReadValue<uint8_t>();
val = IntensityRange(temp);
}
static void log(OpenRCT2::IStream* stream, const IntensityRange& val)
{
char msg[128] = {};
snprintf(msg, sizeof(msg), "IntensityRange(min = %d, max = %d)", val.GetMinimum(), val.GetMaximum());
stream->Write(msg, strlen(msg));
}
};
template<> struct DataSerializerTraits_t<rct_peep_thought>
{
static void encode(OpenRCT2::IStream* stream, const rct_peep_thought& val)
{
stream->Write(&val.type);
stream->Write(&val.item);
stream->Write(&val.freshness);
stream->Write(&val.fresh_timeout);
}
static void decode(OpenRCT2::IStream* stream, rct_peep_thought& val)
{
stream->Read(&val.type);
stream->Read(&val.item);
stream->Read(&val.freshness);
stream->Read(&val.fresh_timeout);
}
static void log(OpenRCT2::IStream* stream, const rct_peep_thought& val)
{
char msg[128] = {};
snprintf(
msg, sizeof(msg), "rct_peep_thought(type = %d, item = %d, freshness = %d, freshtimeout = %d)",
static_cast<int32_t>(val.type), val.item, val.freshness, val.fresh_timeout);
stream->Write(msg, strlen(msg));
}
};

View File

@ -147,6 +147,7 @@
<ClInclude Include="config\IniReader.hpp" /> <ClInclude Include="config\IniReader.hpp" />
<ClInclude Include="config\IniWriter.hpp" /> <ClInclude Include="config\IniWriter.hpp" />
<ClInclude Include="Context.h" /> <ClInclude Include="Context.h" />
<ClInclude Include="core\ChecksumStream.h" />
<ClInclude Include="core\CircularBuffer.h" /> <ClInclude Include="core\CircularBuffer.h" />
<ClInclude Include="core\Collections.hpp" /> <ClInclude Include="core\Collections.hpp" />
<ClInclude Include="core\Console.hpp" /> <ClInclude Include="core\Console.hpp" />
@ -437,6 +438,8 @@
<ClInclude Include="windows\tile_inspector.h" /> <ClInclude Include="windows\tile_inspector.h" />
<ClInclude Include="world\Banner.h" /> <ClInclude Include="world\Banner.h" />
<ClInclude Include="world\Climate.h" /> <ClInclude Include="world\Climate.h" />
<ClInclude Include="world\Entity.h" />
<ClInclude Include="world\EntityList.h" />
<ClInclude Include="world\Entrance.h" /> <ClInclude Include="world\Entrance.h" />
<ClInclude Include="world\Footpath.h" /> <ClInclude Include="world\Footpath.h" />
<ClInclude Include="world\Fountain.h" /> <ClInclude Include="world\Fountain.h" />
@ -569,6 +572,7 @@
<ClCompile Include="config\IniReader.cpp" /> <ClCompile Include="config\IniReader.cpp" />
<ClCompile Include="config\IniWriter.cpp" /> <ClCompile Include="config\IniWriter.cpp" />
<ClCompile Include="Context.cpp" /> <ClCompile Include="Context.cpp" />
<ClCompile Include="core\ChecksumStream.cpp" />
<ClCompile Include="core\Console.cpp" /> <ClCompile Include="core\Console.cpp" />
<ClCompile Include="core\Crypt.CNG.cpp" /> <ClCompile Include="core\Crypt.CNG.cpp" />
<ClCompile Include="core\Crypt.OpenSSL.cpp" /> <ClCompile Include="core\Crypt.OpenSSL.cpp" />
@ -854,6 +858,7 @@
<ClCompile Include="world\Banner.cpp" /> <ClCompile Include="world\Banner.cpp" />
<ClCompile Include="world\Climate.cpp" /> <ClCompile Include="world\Climate.cpp" />
<ClCompile Include="world\Duck.cpp" /> <ClCompile Include="world\Duck.cpp" />
<ClCompile Include="world\Entity.cpp" />
<ClCompile Include="world\Entrance.cpp" /> <ClCompile Include="world\Entrance.cpp" />
<ClCompile Include="world\Footpath.cpp" /> <ClCompile Include="world\Footpath.cpp" />
<ClCompile Include="world\Fountain.cpp" /> <ClCompile Include="world\Fountain.cpp" />
@ -875,4 +880,4 @@
<ClCompile Include="world\Wall.cpp" /> <ClCompile Include="world\Wall.cpp" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project> </Project>

View File

@ -36,7 +36,7 @@
// This string specifies which version of network stream current build uses. // This string specifies which version of network stream current build uses.
// It is used for making sure only compatible builds get connected, even within // It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version. // single OpenRCT2 version.
#define NETWORK_STREAM_VERSION "13" #define NETWORK_STREAM_VERSION "14"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
static Peep* _pickup_peep = nullptr; static Peep* _pickup_peep = nullptr;

View File

@ -6950,7 +6950,7 @@ Guest* Guest::Generate(const CoordsXYZ& coords)
peep->PathCheckOptimisation = 0; peep->PathCheckOptimisation = 0;
peep->InteractionRideIndex = RIDE_ID_NULL; peep->InteractionRideIndex = RIDE_ID_NULL;
peep->PreviousRide = RIDE_ID_NULL; peep->PreviousRide = RIDE_ID_NULL;
peep->Thoughts->type = PeepThoughtType::None; peep->Thoughts[0].type = PeepThoughtType::None;
peep->WindowInvalidateFlags = 0; peep->WindowInvalidateFlags = 0;
uint8_t intensityHighest = (scenario_rand() & 0x7) + 3; uint8_t intensityHighest = (scenario_rand() & 0x7) + 3;

View File

@ -1410,7 +1410,7 @@ Direction peep_pathfind_choose_direction(const TileCoordsXYZ& loc, Peep* peep)
peep->PathfindGoal.direction = 0; peep->PathfindGoal.direction = 0;
// Clear pathfinding history // Clear pathfinding history
std::fill_n(reinterpret_cast<uint8_t*>(peep->PathfindHistory), sizeof(peep->PathfindHistory), 0xFF); std::fill(std::begin(peep->PathfindHistory), std::end(peep->PathfindHistory), rct12_xyzd8{ 0xFF, 0xFF, 0xFF, 0xFF });
#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
if (_pathFindDebug) if (_pathFindDebug)
{ {

View File

@ -50,6 +50,8 @@ constexpr auto PEEP_CLEARANCE_HEIGHT = 4 * COORDS_Z_STEP;
class Formatter; class Formatter;
struct TileElement; struct TileElement;
struct Ride; struct Ride;
class DataSerialiser;
namespace GameActions namespace GameActions
{ {
class Result; class Result;
@ -649,7 +651,7 @@ struct Peep : SpriteBase
int8_t RejoinQueueTimeout; // whilst waiting for a free vehicle (or pair) in the entrance int8_t RejoinQueueTimeout; // whilst waiting for a free vehicle (or pair) in the entrance
ride_id_t PreviousRide; ride_id_t PreviousRide;
uint16_t PreviousRideTimeOut; uint16_t PreviousRideTimeOut;
rct_peep_thought Thoughts[PEEP_MAX_THOUGHTS]; std::array<rct_peep_thought, PEEP_MAX_THOUGHTS> Thoughts;
uint8_t PathCheckOptimisation; // see peep.checkForPath uint8_t PathCheckOptimisation; // see peep.checkForPath
union union
{ {
@ -664,7 +666,7 @@ struct Peep : SpriteBase
ride_id_t Photo1RideRef; ride_id_t Photo1RideRef;
uint32_t PeepFlags; uint32_t PeepFlags;
rct12_xyzd8 PathfindGoal; rct12_xyzd8 PathfindGoal;
rct12_xyzd8 PathfindHistory[4]; std::array<rct12_xyzd8, 4> PathfindHistory;
uint8_t WalkingFrameNum; uint8_t WalkingFrameNum;
// 0x3F Litter Count split into lots of 3 with time, 0xC0 Time since last recalc // 0x3F Litter Count split into lots of 3 with time, 0xC0 Time since last recalc
uint8_t LitterCount; uint8_t LitterCount;
@ -818,6 +820,7 @@ public:
void RemoveItem(ShopItem item); void RemoveItem(ShopItem item);
void GiveItem(ShopItem item); void GiveItem(ShopItem item);
bool HasItem(ShopItem peepItem) const; bool HasItem(ShopItem peepItem) const;
void Serialise(DataSerialiser& stream);
private: private:
void UpdateRide(); void UpdateRide();
@ -885,6 +888,7 @@ public:
bool CanIgnoreWideFlag(const CoordsXYZ& staffPos, TileElement* path) const; bool CanIgnoreWideFlag(const CoordsXYZ& staffPos, TileElement* path) const;
static void ResetStats(); static void ResetStats();
void Serialise(DataSerialiser& stream);
private: private:
void UpdatePatrolling(); void UpdatePatrolling();

View File

@ -28,6 +28,7 @@ using track_type_t = uint16_t;
struct Ride; struct Ride;
struct rct_ride_entry; struct rct_ride_entry;
class DataSerialiser;
struct GForces struct GForces
{ {
@ -266,6 +267,7 @@ struct Vehicle : SpriteBase
update_flags |= flag; update_flags |= flag;
} }
void ApplyMass(int16_t appliedMass); void ApplyMass(int16_t appliedMass);
void Serialise(DataSerialiser& stream);
private: private:
bool SoundCanPlay() const; bool SoundCanPlay() const;

View File

@ -0,0 +1,296 @@
/*****************************************************************************
* Copyright (c) 2014-2021 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.
*****************************************************************************/
#include "Entity.h"
#include "../core/DataSerialiser.h"
#include "../peep/Peep.h"
#include "../ride/Vehicle.h"
#include "Sprite.h"
static void EntityBaseSerialise(SpriteBase& base, DataSerialiser& stream)
{
stream << base.Type;
stream << base.sprite_index;
stream << base.x;
stream << base.y;
stream << base.z;
stream << base.sprite_direction;
}
void Litter::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << SubType;
stream << creationTick;
}
void Balloon::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
stream << popped;
stream << time_to_move;
stream << colour;
}
void Duck::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
stream << target_x;
stream << target_y;
stream << state;
}
void MoneyEffect::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
stream << MoveDelay;
stream << NumMovements;
stream << Vertical;
stream << Value;
stream << OffsetX;
stream << Wiggle;
}
void VehicleCrashParticle::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
stream << time_to_live;
stream << colour;
stream << crashed_sprite_base;
stream << velocity_x;
stream << velocity_y;
stream << velocity_z;
stream << acceleration_x;
stream << acceleration_y;
stream << acceleration_z;
}
void ExplosionFlare::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
}
void ExplosionCloud::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
}
void CrashSplashParticle::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
}
void SteamParticle::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
stream << time_to_move;
}
void JumpingFountain::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << frame;
stream << FountainType;
stream << NumTicksAlive;
stream << FountainFlags;
stream << TargetX;
stream << TargetY;
stream << Iteration;
}
static void PeepBaseSerialise(Peep& base, DataSerialiser& stream)
{
EntityBaseSerialise(base, stream);
if (stream.IsLoading())
{
base.Name = nullptr;
}
stream << base.NextLoc;
stream << base.NextFlags;
stream << base.State;
stream << base.SubState;
stream << base.SpriteType;
stream << base.TshirtColour;
stream << base.TrousersColour;
stream << base.DestinationX;
stream << base.DestinationY;
stream << base.DestinationTolerance;
stream << base.Var37;
stream << base.Energy;
stream << base.EnergyTarget;
stream << base.Mass;
// stream << base.WindowInvalidateFlags;
stream << base.CurrentRide;
stream << base.CurrentRideStation;
stream << base.CurrentTrain;
stream << base.CurrentCar;
stream << base.CurrentSeat;
stream << base.SpecialSprite;
stream << base.ActionSpriteType;
stream << base.NextActionSpriteType;
stream << base.ActionSpriteImageOffset;
stream << base.Action;
stream << base.ActionFrame;
stream << base.StepProgress;
stream << base.PeepDirection;
stream << base.InteractionRideIndex;
stream << base.Id;
stream << base.PathCheckOptimisation;
stream << base.PathfindGoal;
stream << base.PathfindHistory;
stream << base.WalkingFrameNum;
stream << base.PeepFlags;
}
void Guest::Serialise(DataSerialiser& stream)
{
PeepBaseSerialise(*this, stream);
stream << GuestNumRides;
stream << GuestNextInQueue;
stream << ParkEntryTime;
stream << GuestHeadingToRideId;
stream << GuestIsLostCountdown;
stream << GuestTimeOnRide;
stream << PaidToEnter;
stream << PaidOnRides;
stream << PaidOnFood;
stream << PaidOnDrink;
stream << PaidOnSouvenirs;
stream << OutsideOfPark;
stream << Happiness;
stream << HappinessTarget;
stream << Nausea;
stream << NauseaTarget;
stream << Hunger;
stream << Thirst;
stream << Toilet;
stream << TimeToConsume;
stream << Intensity;
stream << NauseaTolerance;
stream << RideTypesBeenOn;
stream << TimeInQueue;
stream << RidesBeenOn;
stream << CashInPocket;
stream << CashSpent;
stream << Photo1RideRef;
stream << Photo2RideRef;
stream << Photo3RideRef;
stream << Photo4RideRef;
stream << RejoinQueueTimeout;
stream << PreviousRide;
stream << PreviousRideTimeOut;
stream << Thoughts;
stream << LitterCount;
stream << DisgustingCount;
stream << AmountOfFood;
stream << AmountOfDrinks;
stream << AmountOfSouvenirs;
stream << VandalismSeen;
stream << VoucherType;
stream << VoucherRideId;
stream << SurroundingsThoughtTimeout;
stream << Angriness;
stream << TimeLost;
stream << DaysInQueue;
stream << BalloonColour;
stream << UmbrellaColour;
stream << HatColour;
stream << FavouriteRide;
stream << FavouriteRideRating;
stream << ItemFlags;
}
void Staff::Serialise(DataSerialiser& stream)
{
PeepBaseSerialise(*this, stream);
stream << AssignedStaffType;
stream << MechanicTimeSinceCall;
stream << HireDate;
stream << StaffId;
stream << StaffOrders;
stream << StaffMowingTimeout;
stream << StaffLawnsMown;
stream << StaffGardensWatered;
stream << StaffLitterSwept;
stream << StaffBinsEmptied;
}
void Vehicle::Serialise(DataSerialiser& stream)
{
EntityBaseSerialise(*this, stream);
stream << SubType;
stream << vehicle_sprite_type;
stream << bank_rotation;
stream << remaining_distance;
stream << velocity;
stream << acceleration;
stream << ride;
stream << vehicle_type;
stream << colours;
stream << track_progress;
stream << TrackTypeAndDirection;
stream << TrackLocation;
stream << next_vehicle_on_train;
stream << prev_vehicle_on_ride;
stream << next_vehicle_on_ride;
stream << var_44;
stream << mass;
stream << update_flags;
stream << SwingSprite;
stream << current_station;
stream << SwingPosition;
stream << SwingSpeed;
stream << status;
stream << sub_state;
stream << peep;
stream << peep_tshirt_colours;
stream << num_seats;
stream << num_peeps;
stream << next_free_seat;
stream << restraints_position;
stream << spin_speed;
stream << sound2_flags;
stream << spin_sprite;
stream << sound1_id;
stream << sound1_volume;
stream << sound2_id;
stream << sound2_volume;
stream << sound_vector_factor;
stream << var_C0;
stream << speed;
stream << powered_acceleration;
stream << dodgems_collision_direction;
stream << animation_frame;
stream << var_C8;
stream << var_CA;
stream << scream_sound_id;
stream << TrackSubposition;
stream << var_CE;
stream << var_CF;
stream << lost_time_out;
stream << vertical_drop_countdown;
stream << var_D3;
stream << mini_golf_current_animation;
stream << mini_golf_flags;
stream << ride_subtype;
stream << colours_extended;
stream << seat_rotation;
stream << target_seat_rotation;
stream << BoatLocation;
stream << IsCrashedVehicle;
}

View File

@ -13,6 +13,8 @@
#include "Map.h" #include "Map.h"
#include "SpriteBase.h" #include "SpriteBase.h"
class DataSerialiser;
enum class JumpingFountainType : uint8_t enum class JumpingFountainType : uint8_t
{ {
Water, Water,
@ -31,6 +33,7 @@ struct JumpingFountain : MiscEntity
uint16_t Iteration; uint16_t Iteration;
void Update(); void Update();
static void StartAnimation(JumpingFountainType newType, const CoordsXY& newLoc, const TileElement* tileElement); static void StartAnimation(JumpingFountainType newType, const CoordsXY& newLoc, const TileElement* tileElement);
void Serialise(DataSerialiser& stream);
private: private:
JumpingFountainType GetType() const; JumpingFountainType GetType() const;

View File

@ -13,8 +13,11 @@
#include "../Game.h" #include "../Game.h"
#include "../OpenRCT2.h" #include "../OpenRCT2.h"
#include "../audio/audio.h" #include "../audio/audio.h"
#include "../core/ChecksumStream.h"
#include "../core/Crypt.h" #include "../core/Crypt.h"
#include "../core/DataSerialiser.h"
#include "../core/Guard.hpp" #include "../core/Guard.hpp"
#include "../core/MemoryStream.h"
#include "../interface/Viewport.h" #include "../interface/Viewport.h"
#include "../localisation/Date.h" #include "../localisation/Date.h"
#include "../localisation/Localisation.h" #include "../localisation/Localisation.h"
@ -136,7 +139,7 @@ std::string rct_sprite_checksum::ToString() const
for (auto b : raw) for (auto b : raw)
{ {
char buf[3]; char buf[3];
snprintf(buf, 3, "%02x", b); snprintf(buf, 3, "%02x", static_cast<int32_t>(b));
result.append(buf); result.append(buf);
} }
@ -275,64 +278,26 @@ void reset_sprite_spatial_index()
#ifndef DISABLE_NETWORK #ifndef DISABLE_NETWORK
template<typename T> void ComputeChecksumForEntityType(Crypt::HashAlgorithm<20>* _entityHashAlg) template<typename T> void NetworkSerialseEntityType(DataSerialiser& ds)
{ {
for (auto* ent : EntityList<T>()) for (auto* ent : EntityList<T>())
{ {
T copy = *ent; ent->Serialise(ds);
// Only required for rendering/invalidation, has no meaning to the game state.
copy.sprite_left = copy.sprite_right = copy.sprite_top = copy.sprite_bottom = 0;
copy.sprite_width = copy.sprite_height_negative = copy.sprite_height_positive = 0;
if constexpr (std::is_base_of_v<Peep, T>)
{
// Name is pointer and will not be the same across clients
copy.Name = {};
// We set this to 0 because as soon the client selects a guest the window will remove the
// invalidation flags causing the sprite checksum to be different than on server, the flag does not
// affect game state.
copy.WindowInvalidateFlags = 0;
}
_entityHashAlg->Update(&copy, sizeof(copy));
} }
} }
template<typename... T> void ComputeChecksumForEntityTypes(Crypt::HashAlgorithm<20>* _entityHashAlg) template<typename... T> void NetworkSerialiseEntityTypes(DataSerialiser& ds)
{ {
(ComputeChecksumForEntityType<T>(_entityHashAlg), ...); (NetworkSerialseEntityType<T>(ds), ...);
} }
rct_sprite_checksum sprite_checksum() rct_sprite_checksum sprite_checksum()
{ {
using namespace Crypt; rct_sprite_checksum checksum{};
// TODO Remove statics, should be one of these per sprite manager / OpenRCT2 context. OpenRCT2::ChecksumStream ms(checksum.raw);
// Alternatively, make a new class for this functionality. DataSerialiser ds(true, ms);
static std::unique_ptr<HashAlgorithm<20>> _spriteHashAlg; NetworkSerialiseEntityTypes<Guest, Staff, Vehicle, Litter>(ds);
rct_sprite_checksum checksum;
try
{
if (_spriteHashAlg == nullptr)
{
_spriteHashAlg = CreateSHA1();
}
_spriteHashAlg->Clear();
ComputeChecksumForEntityTypes<Guest, Staff, Vehicle, Litter>(_spriteHashAlg.get());
checksum.raw = _spriteHashAlg->Finish();
}
catch (std::exception& e)
{
log_error("sprite_checksum failed: %s", e.what());
throw;
}
return checksum; return checksum;
} }

View File

@ -18,12 +18,14 @@
#include "SpriteBase.h" #include "SpriteBase.h"
enum LitterType : uint8_t; enum LitterType : uint8_t;
class DataSerialiser;
struct Litter : SpriteBase struct Litter : SpriteBase
{ {
static constexpr auto cEntityType = EntityType::Litter; static constexpr auto cEntityType = EntityType::Litter;
LitterType SubType; LitterType SubType;
uint32_t creationTick; uint32_t creationTick;
void Serialise(DataSerialiser& stream);
}; };
struct Balloon : MiscEntity struct Balloon : MiscEntity
@ -36,6 +38,7 @@ struct Balloon : MiscEntity
void Update(); void Update();
void Pop(); void Pop();
void Press(); void Press();
void Serialise(DataSerialiser& stream);
}; };
struct Duck : MiscEntity struct Duck : MiscEntity
@ -57,6 +60,7 @@ struct Duck : MiscEntity
uint32_t GetFrameImage(int32_t direction) const; uint32_t GetFrameImage(int32_t direction) const;
bool IsFlying(); bool IsFlying();
void Remove(); void Remove();
void Serialise(DataSerialiser& stream);
private: private:
void UpdateFlyToWater(); void UpdateFlyToWater();
@ -80,6 +84,7 @@ struct MoneyEffect : MiscEntity
static void Create(money32 value, const CoordsXYZ& loc); static void Create(money32 value, const CoordsXYZ& loc);
void Update(); void Update();
std::pair<rct_string_id, money32> GetStringId() const; std::pair<rct_string_id, money32> GetStringId() const;
void Serialise(DataSerialiser& stream);
}; };
struct VehicleCrashParticle : MiscEntity struct VehicleCrashParticle : MiscEntity
@ -96,24 +101,28 @@ struct VehicleCrashParticle : MiscEntity
int32_t acceleration_z; int32_t acceleration_z;
void Update(); void Update();
void Serialise(DataSerialiser& stream);
}; };
struct ExplosionFlare : MiscEntity struct ExplosionFlare : MiscEntity
{ {
static constexpr auto cEntityType = EntityType::ExplosionFlare; static constexpr auto cEntityType = EntityType::ExplosionFlare;
void Update(); void Update();
void Serialise(DataSerialiser& stream);
}; };
struct ExplosionCloud : MiscEntity struct ExplosionCloud : MiscEntity
{ {
static constexpr auto cEntityType = EntityType::ExplosionCloud; static constexpr auto cEntityType = EntityType::ExplosionCloud;
void Update(); void Update();
void Serialise(DataSerialiser& stream);
}; };
struct CrashSplashParticle : MiscEntity struct CrashSplashParticle : MiscEntity
{ {
static constexpr auto cEntityType = EntityType::CrashSplash; static constexpr auto cEntityType = EntityType::CrashSplash;
void Update(); void Update();
void Serialise(DataSerialiser& stream);
}; };
struct SteamParticle : MiscEntity struct SteamParticle : MiscEntity
@ -122,6 +131,7 @@ struct SteamParticle : MiscEntity
uint16_t time_to_move; uint16_t time_to_move;
void Update(); void Update();
void Serialise(DataSerialiser& stream);
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
@ -154,7 +164,7 @@ assert_struct_size(rct_sprite, 0x200);
struct rct_sprite_checksum struct rct_sprite_checksum
{ {
std::array<uint8_t, 20> raw; std::array<std::byte, 20> raw;
std::string ToString() const; std::string ToString() const;
}; };