diff --git a/src/openrct2/actions/GameAction.cpp b/src/openrct2/actions/GameAction.cpp index 716f0bf679..0c0a57d852 100644 --- a/src/openrct2/actions/GameAction.cpp +++ b/src/openrct2/actions/GameAction.cpp @@ -82,12 +82,12 @@ namespace GameActions return false; } - GameActionResult Query(const IGameAction * action, uint32 flags) + GameActionResult Query(const IGameAction * action) { Guard::ArgumentNotNull(action); GameActionResult result; - uint16 actionFlags = action->GetFlags(); + uint16 actionFlags = action->GetActionFlags(); if (!CheckActionInPausedMode(actionFlags)) { result.Error = GA_ERROR::GAME_PAUSED; @@ -95,7 +95,7 @@ namespace GameActions } else { - result = action->Query(flags); + result = action->Query(); if (result.Error == GA_ERROR::OK) { if (!CheckActionAffordability(&result)) @@ -109,35 +109,43 @@ namespace GameActions return result; } - GameActionResult Execute(const IGameAction * action, uint32 flags, GameActionCallback callback) + GameActionResult Execute(IGameAction * action) { log_info("[%s] GameAction::Execute\n", network_get_mode() == NETWORK_MODE_CLIENT ? "cl" : "sv"); Guard::ArgumentNotNull(action); - uint16 actionFlags = action->GetFlags(); - GameActionResult result = Query(action, flags); + uint16 actionFlags = action->GetActionFlags(); + uint32 flags = action->GetFlags(); + + GameActionResult result = Query(action); if (result.Error == GA_ERROR::OK) { // Networked games send actions to the server to be run - if (network_get_mode() != NETWORK_MODE_NONE) + if (network_get_mode() == NETWORK_MODE_CLIENT) { - // Action has come from server. + // As a client we have to wait or send it first. if (!(actionFlags & GA_FLAGS::CLIENT_ONLY) && !(flags & GAME_COMMAND_FLAG_GHOST) && !(flags & GAME_COMMAND_FLAG_NETWORKED)) { - network_send_game_action(action, flags); - if (network_get_mode() == NETWORK_MODE_CLIENT) { - // Client sent the command to the server, do not run it locally, just return. It will run when server sends it - //game_command_callback = 0; - // Decrement nest count - //gGameCommandNestLevel--; - return result; - } + network_send_game_action(action); + + return result; + } + } + else if (network_get_mode() == NETWORK_MODE_SERVER) + { + // If player is the server it would execute right away as where clients execute the commands + // at the beginning of the frame, so we have to put them into the queue. + if (!(actionFlags & GA_FLAGS::CLIENT_ONLY) && !(flags & GAME_COMMAND_FLAG_GHOST) && !(flags & GAME_COMMAND_FLAG_NETWORKED)) + { + network_enqueue_game_action(action); + + return result; } } // Execute the action, changing the game state - result = action->Execute(flags); + result = action->Execute(); // Update money balance if (!(gParkFlags & PARK_FLAGS_NO_MONEY) && result.Cost != 0) @@ -148,10 +156,13 @@ namespace GameActions if (!(actionFlags & GA_FLAGS::CLIENT_ONLY)) { - if (network_get_mode() == NETWORK_MODE_SERVER) + if (network_get_mode() == NETWORK_MODE_SERVER && result.Error == GA_ERROR::OK) { - // network_set_player_last_action(network_get_player_index(network_get_current_player_id()), command); - // network_add_player_money_spent(network_get_current_player_id(), cost); + uint32 playerId = action->GetPlayer(); + + network_set_player_last_action(network_get_player_index(playerId), action->GetType()); + if(result.Cost != 0) + network_add_player_money_spent(playerId, result.Cost); } } @@ -163,10 +174,12 @@ namespace GameActions } // Call callback for asynchronous events + /* if (callback != nullptr) { callback(result); } + */ if (result.Error != GA_ERROR::OK && !(flags & GAME_COMMAND_FLAG_GHOST)) { diff --git a/src/openrct2/actions/GameAction.h b/src/openrct2/actions/GameAction.h index 2be2ecf19b..ddd7f289a2 100644 --- a/src/openrct2/actions/GameAction.h +++ b/src/openrct2/actions/GameAction.h @@ -21,27 +21,51 @@ typedef IGameAction *(*GameActionFactory)(); using GameActionCallback = std::function; -template +template struct GameAction : public IGameAction { public: constexpr static uint32 Type = _TYPE; - constexpr static uint16 Flags = _FLAGS; + constexpr static uint16 ActionFlags = _ACTFLAGS; private: - uint32 _playerId; + uint32 _playerId; // Callee + uint32 _flags; // GAME_COMMAND_FLAGS public: - GameAction() : _playerId(0) + GameAction() : _playerId(0), _flags(0) { } + virtual void SetPlayer(uint32 playerId) override + { + _playerId = playerId; + } + + virtual uint32 GetPlayer() const override + { + return _playerId; + } + /** * Gets the GA_FLAGS flags that are enabled for this game action. */ - virtual uint16 GetFlags() const override + virtual uint16 GetActionFlags() const override { - return Flags; + return ActionFlags; + } + + /** + * Currently used for GAME_COMMAND_FLAGS, needs refactoring once everything is replaced. + */ + virtual uint32 GetFlags() const override + { + return _flags; + } + + virtual uint32 SetFlags(uint32 flags) override + { + return _flags = flags; } virtual uint32 GetType() const override @@ -49,41 +73,29 @@ public: return Type; } - /** - * Reads the game action directly from the given stream. Used for - * sending across the network in multiplayer. - */ - virtual void Deserialise(IStream * stream) override + virtual void Serialise(DataSerialiser& stream) { - stream->Read(&_playerId); - } - - /** - * Writes the game action directly to the given stream. Used for - * sending across the network in multiplayer. - */ - virtual void Serialise(IStream * stream) const override - { - stream->Write(&_playerId); + stream << _flags; + stream << _playerId; } /** * Query the result of the game action without changing the game state. */ - virtual GameActionResult Query(uint32 flags = 0) const override abstract; + virtual GameActionResult Query() const abstract; /** * Apply the game action and change the game state. */ - virtual GameActionResult Execute(uint32 flags = 0) const override abstract; + virtual GameActionResult Execute() const abstract; }; namespace GameActions { GameActionFactory Register(uint32 id, GameActionFactory action); IGameAction * Create(uint32 id); -GameActionResult Query(const IGameAction * action, uint32 flags = 0); -GameActionResult Execute(const IGameAction * action, uint32 flags = 0, GameActionCallback callback = nullptr); +GameActionResult Query(const IGameAction * action); +GameActionResult Execute(IGameAction * action); template static GameActionFactory Register() diff --git a/src/openrct2/actions/IGameAction.h b/src/openrct2/actions/IGameAction.h index 0e764bd824..153afe7b65 100644 --- a/src/openrct2/actions/IGameAction.h +++ b/src/openrct2/actions/IGameAction.h @@ -19,6 +19,7 @@ #include #include "../common.h" #include "../core/IStream.hpp" +#include "../core/DataSerialiser.h" extern "C" { @@ -84,30 +85,42 @@ public: /** * Gets the GA_FLAGS flags that are enabled for this game action. */ - virtual uint16 GetFlags() const abstract; + virtual uint16 GetActionFlags() const abstract; + + /** + * Currently used for GAME_COMMAND_FLAGS, needs refactoring once everything is replaced. + */ + virtual uint32 GetFlags() const abstract; + virtual uint32 SetFlags(uint32 flags) abstract; virtual uint32 GetType() const abstract; - /** - * Reads the game action directly from the given stream. Used for - * sending across the network in multiplayer. - */ - virtual void Deserialise(IStream * stream) abstract; /** - * Writes the game action directly to the given stream. Used for + * Gets/Sets player who owns this action, 0 if server or local client. + */ + virtual void SetPlayer(uint32 playerId) abstract; + virtual uint32 GetPlayer() const abstract; + + /** + * Writes or reads the game action directly to the given stream. Used for * sending across the network in multiplayer. */ - virtual void Serialise(IStream * stream) const abstract; + virtual void Serialise(DataSerialiser& stream) abstract; + + // Helper function, allows const Objects to still serialize into DataSerialiser while being const. + void Serialise(DataSerialiser& stream) const + { + return const_cast(*this).Serialise(stream); + } /** * Query the result of the game action without changing the game state. */ - virtual GameActionResult Query(uint32 flags = 0) const abstract; + virtual GameActionResult Query() const abstract; /** * Apply the game action and change the game state. */ - virtual GameActionResult Execute(uint32 flags = 0) const abstract; - virtual ~IGameAction() {}; + virtual GameActionResult Execute() const abstract; }; diff --git a/src/openrct2/actions/PlaceParkEntranceAction.cpp b/src/openrct2/actions/PlaceParkEntranceAction.cpp index 5f896b1ca2..99badbfed1 100644 --- a/src/openrct2/actions/PlaceParkEntranceAction.cpp +++ b/src/openrct2/actions/PlaceParkEntranceAction.cpp @@ -41,23 +41,12 @@ public: sint16 z; uint8 direction; - void Deserialise(IStream * stream) override + void Serialise(DataSerialiser& stream) override { - x = stream->ReadValue(); - y = stream->ReadValue(); - z = stream->ReadValue(); - direction = stream->ReadValue(); + stream << x << y << z << direction; } - void Serialise(IStream * stream) const override - { - stream->WriteValue(x); - stream->WriteValue(y); - stream->WriteValue(z); - stream->WriteValue(direction); - } - - GameActionResult Query(uint32 flags = 0) const override + GameActionResult Query() const override { if (!(gScreenFlags & SCREEN_FLAGS_EDITOR) && !gCheatsSandboxMode) { @@ -129,8 +118,10 @@ public: return GameActionResult(); } - GameActionResult Execute(uint32 flags = 0) const override + GameActionResult Execute() const override { + uint32 flags = GetFlags(); + gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE; gCommandPosition.x = x; @@ -257,12 +248,15 @@ extern "C" money32 park_entrance_place_ghost(sint32 x, sint32 y, sint32 z, sint32 direction) { park_entrance_remove_ghost(); + auto gameAction = PlaceParkEntranceAction(); gameAction.x = x; gameAction.y = y; gameAction.z = z; gameAction.direction = direction; - auto result = GameActions::Execute(&gameAction, GAME_COMMAND_FLAG_GHOST); + gameAction.SetFlags(GAME_COMMAND_FLAG_GHOST); + + auto result = GameActions::Execute(&gameAction); if (result.Error == GA_ERROR::OK) { gParkEntranceGhostPosition.x = x; diff --git a/src/openrct2/actions/SetParkEntranceFeeAction.cpp b/src/openrct2/actions/SetParkEntranceFeeAction.cpp index ab25ef2317..f6bd328b2e 100644 --- a/src/openrct2/actions/SetParkEntranceFeeAction.cpp +++ b/src/openrct2/actions/SetParkEntranceFeeAction.cpp @@ -30,17 +30,12 @@ struct SetParkEntranceFeeAction : public GameActionReadValue(); + stream << Fee; } - void Serialise(IStream * stream) const override - { - stream->WriteValue(Fee); - } - - GameActionResult Query(uint32 flags = 0) const override + GameActionResult Query() const override { bool noMoney = (gParkFlags & PARK_FLAGS_NO_MONEY) != 0; bool forceFreeEntry = (gParkFlags & PARK_FLAGS_PARK_FREE_ENTRY) && !gCheatsUnlockAllPrices; @@ -55,7 +50,7 @@ public: return GameActionResult(); } - GameActionResult Execute(uint32 flags = 0) const override + GameActionResult Execute() const override { gParkEntranceFee = Fee; window_invalidate_by_class(WC_PARK_INFORMATION); diff --git a/src/openrct2/core/DataSerialiser.h b/src/openrct2/core/DataSerialiser.h new file mode 100644 index 0000000000..5e3ea6a9f2 --- /dev/null +++ b/src/openrct2/core/DataSerialiser.h @@ -0,0 +1,63 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#pragma once + +#include "DataSerialiserTraits.h" + +class DataSerialiser +{ +private: + MemoryStream _stream; + MemoryStream *_activeStream; + bool _isSaving; + +public: + DataSerialiser(bool isSaving) : _isSaving(isSaving) + { + _activeStream = &_stream; + } + + DataSerialiser(bool isSaving, MemoryStream& stream) : _isSaving(isSaving) + { + _activeStream = &stream; + } + + bool IsSaving() const + { + return _isSaving; + } + + bool IsLoading() const + { + return !_isSaving; + } + + MemoryStream& GetStream() + { + return _stream; + } + + template + DataSerialiser& operator<<(T& data) + { + if (_isSaving) + DataSerializerTraits::type>::encode(_activeStream, data); + else + DataSerializerTraits::type>::decode(_activeStream, data); + return *this; + } +}; \ No newline at end of file diff --git a/src/openrct2/core/DataSerialiserTraits.h b/src/openrct2/core/DataSerialiserTraits.h new file mode 100644 index 0000000000..ecb12a0529 --- /dev/null +++ b/src/openrct2/core/DataSerialiserTraits.h @@ -0,0 +1,60 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Endianness.h" +#include "MemoryStream.h" + +template +struct DataSerializerTraits { + static void encode(IStream *stream, const T& v) = delete; + static void decode(IStream *stream, T& val) = delete; +}; + +template +struct DataSerializerTraitsIntegral +{ + static void encode(IStream *stream, const T& val) + { + T temp = ByteSwapBE(val); + stream->Write(&temp); + } + static void decode(IStream *stream, T& val) + { + T temp; + stream->Read(&temp); + val = ByteSwapBE(temp); + } +}; + +template<> +struct DataSerializerTraits : public DataSerializerTraitsIntegral {}; + +template<> +struct DataSerializerTraits : public DataSerializerTraitsIntegral {}; + +template<> +struct DataSerializerTraits : public DataSerializerTraitsIntegral {}; + +template<> +struct DataSerializerTraits : public DataSerializerTraitsIntegral {}; + +template<> +struct DataSerializerTraits : public DataSerializerTraitsIntegral {}; + +template<> +struct DataSerializerTraits : public DataSerializerTraitsIntegral {}; diff --git a/src/openrct2/core/Endianness.h b/src/openrct2/core/Endianness.h new file mode 100644 index 0000000000..bcefd87e00 --- /dev/null +++ b/src/openrct2/core/Endianness.h @@ -0,0 +1,62 @@ +#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" + +#ifdef __cplusplus + +template +struct ByteSwapT { }; + +template <> +struct ByteSwapT<1> +{ + static uint8 SwapBE(uint8 value) + { + return value; + } +}; + +template <> +struct ByteSwapT<2> +{ + static uint16 SwapBE(uint16 value) + { + return (uint16)((value << 8) | (value >> 8)); + } +}; + +template <> +struct ByteSwapT<4> +{ + static uint32 SwapBE(uint32 value) + { + return (uint32)(((value << 24) | + ((value << 8) & 0x00FF0000) | + ((value >> 8) & 0x0000FF00) | + (value >> 24))); + } +}; + +template +static T ByteSwapBE(const T& value) +{ + return ByteSwapT::SwapBE(value); +} + +#endif \ No newline at end of file diff --git a/src/openrct2/game.c b/src/openrct2/game.c index fc42c04b36..518211215f 100644 --- a/src/openrct2/game.c +++ b/src/openrct2/game.c @@ -383,6 +383,10 @@ void game_update() void game_logic_update() { + gScreenAge++; + if (gScreenAge == 0) + gScreenAge--; + network_update(); if (network_get_mode() == NETWORK_MODE_CLIENT && network_get_status() == NETWORK_STATUS_CONNECTED && network_get_authstatus() == NETWORK_AUTH_OK) { @@ -393,14 +397,17 @@ void game_logic_update() } } - // Separated out processing commands in network_update which could call scenario_rand where gInUpdateCode is false. - // All commands that are received are first queued and then executed where gInUpdateCode is set to true. - network_process_game_commands(); - - gScreenAge++; - if (gScreenAge == 0) - gScreenAge--; - + if (network_get_mode() == NETWORK_MODE_SERVER) + { + // Send current tick out. + network_send_tick(); + } + else if (network_get_mode() == NETWORK_MODE_CLIENT) + { + // Check desync. + network_check_desynchronization(); + } + sub_68B089(); scenario_update(); climate_update(); @@ -445,6 +452,12 @@ void game_logic_update() gLastAutoSaveUpdate = platform_get_ticks(); } + // Separated out processing commands in network_update which could call scenario_rand where gInUpdateCode is false. + // All commands that are received are first queued and then executed where gInUpdateCode is set to true. + network_process_game_commands(); + + network_flush(); + gCurrentTicks++; gScenarioTicks++; gSavedAge++; diff --git a/src/openrct2/game.h b/src/openrct2/game.h index f2d293bbd3..04790acb4c 100644 --- a/src/openrct2/game.h +++ b/src/openrct2/game.h @@ -164,7 +164,9 @@ void game_reduce_game_speed(); void game_create_windows(); void game_update(); +bool game_logic_begin(); void game_logic_update(); +void game_logic_finish(); void reset_all_sprite_quadrant_placements(); void update_palette_effects(); diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index b883a94b2d..da019e5be8 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -57,7 +57,7 @@ - + @@ -67,4 +67,4 @@ - + \ No newline at end of file diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index f75670d9d4..5de2cc4edd 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -94,6 +94,7 @@ Network::Network() status = NETWORK_STATUS_NONE; last_tick_sent_time = 0; last_ping_sent_time = 0; + _commandIndex = 0; client_command_handlers.resize(NETWORK_COMMAND_MAX, 0); client_command_handlers[NETWORK_COMMAND_AUTH] = &Network::Client_Handle_AUTH; client_command_handlers[NETWORK_COMMAND_MAP] = &Network::Client_Handle_MAP; @@ -213,6 +214,8 @@ bool Network::BeginClient(const char* host, uint16 port) mode = NETWORK_MODE_CLIENT; + log_info("Connecting to %s:%u\n", host, port); + assert(server_connection->Socket == nullptr); server_connection->Socket = CreateTcpSocket(); server_connection->Socket->ConnectAsync(host, port); @@ -403,6 +406,21 @@ void Network::Update() } } +void Network::Flush() +{ + if (GetMode() == NETWORK_MODE_CLIENT) + { + server_connection->SendQueuedPackets(); + } + else + { + for (auto& it : client_connection_list) + { + it->SendQueuedPackets(); + } + } +} + void Network::UpdateServer() { auto it = client_connection_list.begin(); @@ -416,9 +434,6 @@ void Network::UpdateServer() } uint32 ticks = platform_get_ticks(); - if (ticks > last_tick_sent_time + 25) { - Server_Send_TICK(); - } if (ticks > last_ping_sent_time + 3000) { Server_Send_PING(); Server_Send_PINGLIST(); @@ -622,9 +637,25 @@ bool Network::CheckSRAND(uint32 tick, uint32 srand0) return false; } } + return true; } +void Network::CheckDesynchronizaton() +{ + // Check synchronisation + if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, gScenarioSrand0)) { + _desynchronised = true; + + char str_desync[256]; + format_string(str_desync, 256, STR_MULTIPLAYER_DESYNC, NULL); + window_network_status_open(str_desync, NULL); + if (!gConfigNetwork.stay_connected) { + Close(); + } + } +} + void Network::KickPlayer(sint32 playerId) { for(auto it = client_connection_list.begin(); it != client_connection_list.end(); it++) { @@ -1116,29 +1147,40 @@ void Network::Server_Send_GAMECMD(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx SendPacketToClients(*packet, false, true); } -void Network::Client_Send_GAME_ACTION(const IGameAction *action, uint32 flags = 0) +void Network::Client_Send_GAME_ACTION(const IGameAction *action) { std::unique_ptr packet(NetworkPacket::Allocate()); - *packet << (uint32)NETWORK_COMMAND_GAME_ACTION << (uint32)gCurrentTicks << action->GetType() << flags; - MemoryStream stream; - action->Serialise(&stream); - packet->Write((uint8*)stream.GetData(), stream.GetLength()); + + DataSerialiser stream(true); + action->Serialise(stream); + + *packet << (uint32)NETWORK_COMMAND_GAME_ACTION << (uint32)gCurrentTicks << action->GetType() << stream; + server_connection->QueuePacket(std::move(packet)); } -void Network::Server_Send_GAME_ACTION(const IGameAction *action, uint32 flags = 0) +void Network::Server_Send_GAME_ACTION(const IGameAction *action) { std::unique_ptr packet(NetworkPacket::Allocate()); - *packet << (uint32)NETWORK_COMMAND_GAME_ACTION << (uint32)gCurrentTicks << action->GetType() << gNetwork.GetPlayerID() << flags; - MemoryStream stream; - action->Serialise(&stream); - packet->Write((uint8*)stream.GetData(), stream.GetLength()); + + DataSerialiser stream(true); + action->Serialise(stream); + + *packet << (uint32)NETWORK_COMMAND_GAME_ACTION << (uint32)gCurrentTicks << action->GetType() << stream; + SendPacketToClients(*packet); } void Network::Server_Send_TICK() { - last_tick_sent_time = platform_get_ticks(); + uint32 ticks = platform_get_ticks(); + if (ticks < last_tick_sent_time + 25) + { + return; + } + + last_tick_sent_time = ticks; + std::unique_ptr packet(NetworkPacket::Allocate()); *packet << (uint32)NETWORK_COMMAND_TICK << (uint32)gCurrentTicks << (uint32)gScenarioSrand0; uint32 flags = 0; @@ -1359,20 +1401,26 @@ void Network::ProcessGameCommandQueue() if (game_command_queue.begin()->tick != gCurrentTicks) break; } - if (gc.actionType != UINT32_MAX) { - IGameAction * action = GameActions::Create(gc.actionType); - uint32 flags = gc.parameters->ReadValue(); - action->Deserialise(gc.parameters); - GameActionResult result = GameActions::Execute(action, flags | GAME_COMMAND_FLAG_NETWORKED); - if (result.Error != GA_ERROR::OK) + + if (gc.action != nullptr) { + + IGameAction *action = gc.action; + action->SetFlags(action->GetFlags() | GAME_COMMAND_FLAG_NETWORKED); + + Guard::Assert(action != nullptr); + + GameActionResult result = GameActions::Execute(action); + if (result.Error == GA_ERROR::OK) { game_commands_processed_this_tick++; NetworkPlayer* player = GetPlayerByID(gc.playerid); if (player) { - player->LastAction = NetworkActions::FindCommand(gc.actionType); + player->LastAction = NetworkActions::FindCommand(action->GetType()); player->LastActionTime = platform_get_ticks(); player->AddMoneySpent(result.Cost); } + + Server_Send_GAME_ACTION(action); } } else { @@ -1415,26 +1463,22 @@ void Network::ProcessGameCommandQueue() } game_command_queue.erase(game_command_queue.begin()); } +} - // Check synchronisation - if (mode == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, gScenarioSrand0)) { - _desynchronised = true; +void Network::EnqueueGameAction(const IGameAction *action) +{ + MemoryStream stream; + DataSerialiser dsOut(true, stream); + action->Serialise(dsOut); - char str_desync[256]; - format_string(str_desync, 256, STR_MULTIPLAYER_DESYNC, nullptr); - window_network_status_open(str_desync, nullptr); - if (!gConfigNetwork.stay_connected) { - Close(); - } - } + IGameAction *ga = GameActions::Create(action->GetType()); + stream.SetPosition(0); + DataSerialiser dsIn(false, stream); + ga->Serialise(dsIn); - if (mode == NETWORK_MODE_SERVER) - { - for (const auto& it : client_connection_list) - { - it->SendQueuedPackets(); - } - } + GameCommand gc(gCurrentTicks, ga); + gc.commandIndex = _commandIndex++; + game_command_queue.insert(gc); } void Network::AddClient(ITcpSocket * socket) @@ -2056,38 +2100,51 @@ void Network::Client_Handle_GAMECMD(NetworkConnection& connection, NetworkPacket packet >> tick >> args[0] >> args[1] >> args[2] >> args[3] >> args[4] >> args[5] >> args[6] >> playerid >> callback; GameCommand gc(tick, args, playerid, callback); + gc.commandIndex = _commandIndex++; game_command_queue.insert(gc); } + void Network::Client_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPacket& packet) { uint32 tick; uint32 type; - uint8 playerid; - packet >> tick >> type >> playerid; + packet >> tick >> type; + MemoryStream stream; size_t size = packet.Size - packet.BytesRead; stream.WriteArray(packet.Read(size), size); stream.SetPosition(0); - GameCommand gc(tick, type, stream, playerid); + + DataSerialiser ds(false, stream); + + IGameAction *action = GameActions::Create(type); + if (!action) + { + // TODO: Handle error. + } + action->Serialise(ds); + + GameCommand gc(tick, action); + gc.commandIndex = _commandIndex++; game_command_queue.insert(gc); } void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPacket& packet) { uint32 tick; - uint32 commandType; + uint32 type; if (!connection.Player) { return; } - packet >> tick >> commandType; + packet >> tick >> type; //tick count is different by time last_action_time is set, keep same value // Check if player's group permission allows command to run uint32 ticks = platform_get_ticks(); NetworkGroup* group = GetGroupByID(connection.Player->Group); - if (!group || (group && !group->CanPerformCommand(commandType))) { + if (!group || (group && !group->CanPerformCommand(type))) { Server_Send_SHOWERROR(connection, STR_CANT_DO_THIS, STR_PERMISSION_DENIED); return; } @@ -2095,7 +2152,7 @@ void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPa // In case someone modifies the code / memory to enable cluster build, // require a small delay in between placing scenery to provide some security, as // cluster mode is a for loop that runs the place_scenery code multiple times. - if (commandType == GAME_COMMAND_PLACE_SCENERY) { + if (type == GAME_COMMAND_PLACE_SCENERY) { if ( ticks - connection.Player->LastPlaceSceneryTime < ACTION_COOLDOWN_TIME_PLACE_SCENERY && // Incase platform_get_ticks() wraps after ~49 days, ignore larger logged times. @@ -2109,7 +2166,7 @@ void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPa } // This is to prevent abuse of demolishing rides. Anyone that is not the server // host will have to wait a small amount of time in between deleting rides. - else if (commandType == GAME_COMMAND_DEMOLISH_RIDE) { + else if (type == GAME_COMMAND_DEMOLISH_RIDE) { if ( ticks - connection.Player->LastDemolishRideTime < ACTION_COOLDOWN_TIME_DEMOLISH_RIDE && // Incase platform_get_ticks()() wraps after ~49 days, ignore larger logged times. @@ -2120,39 +2177,31 @@ void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPa } } // Don't let clients send pause or quit - else if (commandType == GAME_COMMAND_TOGGLE_PAUSE || - commandType == GAME_COMMAND_LOAD_OR_QUIT + else if (type == GAME_COMMAND_TOGGLE_PAUSE || + type == GAME_COMMAND_LOAD_OR_QUIT ) { return; } - // Set this to reference inside of game command functions - game_command_playerid = connection.Player->Id; // Run game command, and if it is successful send to clients - auto ga = GameActions::Create(commandType); - MemoryStream stream; - size_t size = packet.Size - packet.BytesRead; - stream.WriteArray(packet.Read(size), size); - stream.SetPosition(0); - uint32 flags = stream.ReadValue(); - ga->Deserialise(&stream); - auto result = GameActions::Execute(ga, GAME_COMMAND_FLAG_NETWORKED | flags); - if (result.Error != GA_ERROR::OK) { - return; - } + IGameAction *ga = GameActions::Create(type); + if (!ga) + { + // TODO: Handle error. + } - connection.Player->LastAction = NetworkActions::FindCommand(commandType); - connection.Player->LastActionTime = platform_get_ticks(); - connection.Player->AddMoneySpent(result.Cost); + DataSerialiser stream(false); + size_t size = packet.Size - packet.BytesRead; + stream.GetStream().WriteArray(packet.Read(size), size); + stream.GetStream().SetPosition(0); - if (commandType == GAME_COMMAND_PLACE_SCENERY) { - connection.Player->LastPlaceSceneryTime = connection.Player->LastActionTime; - } - else if (commandType == GAME_COMMAND_DEMOLISH_RIDE) { - connection.Player->LastDemolishRideTime = connection.Player->LastActionTime; - } + ga->Serialise(stream); + // Set player to sender, should be 0 if sent from client. + ga->SetPlayer(connection.Player->Id); - Server_Send_GAME_ACTION(ga, flags); + GameCommand gc(tick, ga); + gc.commandIndex = _commandIndex++; + game_command_queue.insert(gc); } void Network::Server_Handle_GAMECMD(NetworkConnection& connection, NetworkPacket& packet) @@ -2216,6 +2265,7 @@ void Network::Server_Handle_GAMECMD(NetworkConnection& connection, NetworkPacket } GameCommand gc = GameCommand(tick, args, playerid, callback); + gc.commandIndex = _commandIndex++; game_command_queue.insert(gc); } @@ -2441,6 +2491,11 @@ void network_process_game_commands() gNetwork.ProcessGameCommandQueue(); } +void network_flush() +{ + gNetwork.Flush(); +} + sint32 network_get_mode() { return gNetwork.GetMode(); @@ -2451,6 +2506,16 @@ sint32 network_get_status() return gNetwork.GetStatus(); } +void network_check_desynchronization() +{ + return gNetwork.CheckDesynchronizaton(); +} + +void network_send_tick() +{ + gNetwork.Server_Send_TICK(); +} + sint32 network_get_authstatus() { return gNetwork.GetAuthStatus(); @@ -3002,18 +3067,23 @@ void network_send_chat(const char* text) } } -void network_send_game_action(const IGameAction *action, uint32 flags = 0) +void network_send_game_action(const IGameAction *action) { switch (gNetwork.GetMode()) { case NETWORK_MODE_SERVER: - gNetwork.Server_Send_GAME_ACTION(action, flags); + gNetwork.Server_Send_GAME_ACTION(action); break; case NETWORK_MODE_CLIENT: - gNetwork.Client_Send_GAME_ACTION(action, flags); + gNetwork.Client_Send_GAME_ACTION(action); break; } } +void network_enqueue_game_action(const IGameAction *action) +{ + gNetwork.EnqueueGameAction(action); +} + void network_send_gamecmd(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 esi, uint32 edi, uint32 ebp, uint8 callback) { switch (gNetwork.GetMode()) { @@ -3104,7 +3174,7 @@ sint32 network_get_status() { return NETWORK_STATUS_NONE; } sint32 network_get_authstatus() { return NETWORK_AUTH_NONE; } uint32 network_get_server_tick() { return gCurrentTicks; } void network_send_gamecmd(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 esi, uint32 edi, uint32 ebp, uint8 callback) {} -void network_send_game_action(const IGameAction *action, uint32 flags = 0) {} +void network_send_game_action(const IGameAction *action) {} void network_send_map() {} void network_update() {} void network_process_game_commands() {} diff --git a/src/openrct2/network/NetworkPacket.h b/src/openrct2/network/NetworkPacket.h index 683720e890..717be49017 100644 --- a/src/openrct2/network/NetworkPacket.h +++ b/src/openrct2/network/NetworkPacket.h @@ -21,6 +21,7 @@ #include #include #include "NetworkTypes.h" +#include "../core/DataSerialiser.h" #include "../common.h" class NetworkPacket final @@ -68,6 +69,12 @@ public: Data->insert(Data->end(), bytes, bytes + sizeof(value)); return *this; } + + NetworkPacket& operator<<(DataSerialiser& data) + { + Write((const uint8_t*)data.GetStream().GetData(), data.GetStream().GetLength()); + return *this; + } }; #endif diff --git a/src/openrct2/network/NetworkTypes.h b/src/openrct2/network/NetworkTypes.h index b27bcd11c5..180fc18ad2 100644 --- a/src/openrct2/network/NetworkTypes.h +++ b/src/openrct2/network/NetworkTypes.h @@ -17,6 +17,7 @@ #pragma once #include "../common.h" +#include "../core/Endianness.h" enum NETWORK_AUTH { @@ -54,46 +55,3 @@ enum NETWORK_COMMAND NETWORK_COMMAND_MAX, NETWORK_COMMAND_INVALID = -1 }; - -#ifdef __cplusplus - -template -struct ByteSwapT { }; - -template <> -struct ByteSwapT<1> -{ - static uint8 SwapBE(uint8 value) - { - return value; - } -}; - -template <> -struct ByteSwapT<2> -{ - static uint16 SwapBE(uint16 value) - { - return (uint16)((value << 8) | (value >> 8)); - } -}; - -template <> -struct ByteSwapT<4> -{ - static uint32 SwapBE(uint32 value) - { - return (uint32)(((value << 24) | - ((value << 8) & 0x00FF0000) | - ((value >> 8) & 0x0000FF00) | - (value >> 24))); - } -}; - -template -static T ByteSwapBE(const T& value) -{ - return ByteSwapT::SwapBE(value); -} - -#endif diff --git a/src/openrct2/network/network.h b/src/openrct2/network/network.h index 15e559af48..bba5b51242 100644 --- a/src/openrct2/network/network.h +++ b/src/openrct2/network/network.h @@ -104,7 +104,9 @@ public: uint32 GetServerTick(); uint8 GetPlayerID(); void Update(); + void Flush(); void ProcessGameCommandQueue(); + void EnqueueGameAction(const IGameAction *action); std::vector>::iterator GetPlayerIteratorByID(uint8 id); NetworkPlayer* GetPlayerByID(uint8 id); std::vector>::iterator GetGroupIteratorByID(uint8 id); @@ -112,6 +114,7 @@ public: static const char* FormatChat(NetworkPlayer* fromplayer, const char* text); void SendPacketToClients(NetworkPacket& packet, bool front = false, bool gameCmd = false); bool CheckSRAND(uint32 tick, uint32 srand0); + void CheckDesynchronizaton(); void KickPlayer(sint32 playerId); void SetPassword(const char* password); void ShutdownClient(); @@ -144,8 +147,8 @@ public: void Server_Send_CHAT(const char* text); void Client_Send_GAMECMD(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 esi, uint32 edi, uint32 ebp, uint8 callback); void Server_Send_GAMECMD(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 esi, uint32 edi, uint32 ebp, uint8 playerid, uint8 callback); - void Client_Send_GAME_ACTION(const IGameAction *action, uint32 flags); - void Server_Send_GAME_ACTION(const IGameAction *action, uint32 flags); + void Client_Send_GAME_ACTION(const IGameAction *action); + void Server_Send_GAME_ACTION(const IGameAction *action); void Server_Send_TICK(); void Server_Send_PLAYERLIST(); void Client_Send_PING(); @@ -194,26 +197,21 @@ private: GameCommand(uint32 t, uint32* args, uint8 p, uint8 cb) { tick = t; eax = args[0]; ebx = args[1]; ecx = args[2]; edx = args[3]; esi = args[4]; edi = args[5]; ebp = args[6]; playerid = p; callback = cb; - actionType = 0xFFFFFFFF; + action = nullptr; } - GameCommand(uint32 t, uint32 aType, MemoryStream const &stream, uint8 p) + GameCommand(uint32 t, IGameAction *ga) { - tick = t; playerid = p; actionType = aType; - // Note this will leak memory. Do something about this - parameters = new MemoryStream(stream); + tick = t; + action = ga; } GameCommand(const GameCommand &source) { tick = source.tick; playerid = source.playerid; - actionType = source.actionType; + action = source.action; callback = source.callback; - if (actionType != 0xFFFFFFFF) - { - parameters = new MemoryStream(*source.parameters); - } - else + if (action == nullptr) { eax = source.eax; ebx = source.ebx; @@ -227,17 +225,16 @@ private: ~GameCommand() { - delete parameters; } uint32 tick; uint32 eax, ebx, ecx, edx, esi, edi, ebp; - uint32 actionType = 0xFFFFFFFF; - MemoryStream *parameters = nullptr; + IGameAction *action; uint8 playerid; uint8 callback; + uint32 commandIndex; bool operator<(const GameCommand& comp) const { - return tick < comp.tick; + return tick < comp.tick && commandIndex < comp.commandIndex; } }; @@ -266,6 +263,7 @@ private: uint32 server_connect_time = 0; uint8 default_group = 0; uint32 game_commands_processed_this_tick = 0; + uint32 _commandIndex; std::string _chatLogPath; std::string _chatLogFilenameFormat = "%Y%m%d-%H%M%S.txt"; std::string _serverLogPath; @@ -323,8 +321,12 @@ sint32 network_begin_server(sint32 port, const char* address); sint32 network_get_mode(); sint32 network_get_status(); +void network_check_desynchronization(); +void network_send_tick(); void network_update(); void network_process_game_commands(); +void network_flush(); + sint32 network_get_authstatus(); uint32 network_get_server_tick(); uint8 network_get_current_player_id(); @@ -364,7 +366,8 @@ sint32 network_get_pickup_peep_old_x(uint8 playerid); void network_send_map(); void network_send_chat(const char* text); void network_send_gamecmd(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 esi, uint32 edi, uint32 ebp, uint8 callback); -void network_send_game_action(const IGameAction *action, uint32 flags); +void network_send_game_action(const IGameAction *action); +void network_enqueue_game_action(const IGameAction *action); void network_send_password(const char* password); void network_set_password(const char* password);