Separated out byte swapping into its own header.

Simplified data serialisation of game actions.
Moved the flags away from parameters.
GameAction base now serialises mandatory data such as flags and player.
Split some functions from network in order to move command processing to the end of tick.
This commit is contained in:
ZehM4tt 2017-07-15 11:24:08 +02:00 committed by Michał Janiszewski
parent 60e72e6dbc
commit 1b2a61c6ba
15 changed files with 492 additions and 227 deletions

View File

@ -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))
{

View File

@ -21,27 +21,51 @@
typedef IGameAction *(*GameActionFactory)();
using GameActionCallback = std::function<void(GameActionResult)>;
template<uint32 _TYPE, uint16 _FLAGS>
template<uint32 _TYPE, uint16 _ACTFLAGS>
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<typename T>
static GameActionFactory Register()

View File

@ -19,6 +19,7 @@
#include <functional>
#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<IGameAction&>(*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;
};

View File

@ -41,23 +41,12 @@ public:
sint16 z;
uint8 direction;
void Deserialise(IStream * stream) override
void Serialise(DataSerialiser& stream) override
{
x = stream->ReadValue<sint16>();
y = stream->ReadValue<sint16>();
z = stream->ReadValue<sint16>();
direction = stream->ReadValue<uint8>();
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;

View File

@ -30,17 +30,12 @@ struct SetParkEntranceFeeAction : public GameAction<GAME_COMMAND_SET_PARK_ENTRAN
public:
money16 Fee;
void Deserialise(IStream * stream) override
void Serialise(DataSerialiser& stream) override
{
Fee = stream->ReadValue<money16>();
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);

View File

@ -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<typename T>
DataSerialiser& operator<<(T& data)
{
if (_isSaving)
DataSerializerTraits<std::remove_const<T>::type>::encode(_activeStream, data);
else
DataSerializerTraits<std::remove_const<T>::type>::decode(_activeStream, data);
return *this;
}
};

View File

@ -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<typename T>
struct DataSerializerTraits {
static void encode(IStream *stream, const T& v) = delete;
static void decode(IStream *stream, T& val) = delete;
};
template<typename T>
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<uint8> : public DataSerializerTraitsIntegral<uint8> {};
template<>
struct DataSerializerTraits<sint8> : public DataSerializerTraitsIntegral<sint8> {};
template<>
struct DataSerializerTraits<uint16> : public DataSerializerTraitsIntegral<uint16> {};
template<>
struct DataSerializerTraits<sint16> : public DataSerializerTraitsIntegral<sint16> {};
template<>
struct DataSerializerTraits<uint32> : public DataSerializerTraitsIntegral<uint32> {};
template<>
struct DataSerializerTraits<sint32> : public DataSerializerTraitsIntegral<sint32> {};

View File

@ -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 <size_t size>
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 <typename T>
static T ByteSwapBE(const T& value)
{
return ByteSwapT<sizeof(T)>::SwapBE(value);
}
#endif

View File

@ -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++;

View File

@ -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();

View File

@ -57,7 +57,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\resources\resource.h" />
<ClInclude Include="**\*.h" />
<ClInclude Include="core\DataSerialiser.h" />
<ClInclude Include="**\*.hpp" />
</ItemGroup>
<ItemGroup>
@ -67,4 +67,4 @@
<Image Include="..\..\resources\logo\icon.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>

View File

@ -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<NetworkPacket> 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<NetworkPacket> 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<NetworkPacket> 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<uint32>();
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<uint32>();
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() {}

View File

@ -21,6 +21,7 @@
#include <memory>
#include <vector>
#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

View File

@ -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 <size_t size>
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 <typename T>
static T ByteSwapBE(const T& value)
{
return ByteSwapT<sizeof(T)>::SwapBE(value);
}
#endif

View File

@ -104,7 +104,9 @@ public:
uint32 GetServerTick();
uint8 GetPlayerID();
void Update();
void Flush();
void ProcessGameCommandQueue();
void EnqueueGameAction(const IGameAction *action);
std::vector<std::unique_ptr<NetworkPlayer>>::iterator GetPlayerIteratorByID(uint8 id);
NetworkPlayer* GetPlayerByID(uint8 id);
std::vector<std::unique_ptr<NetworkGroup>>::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);