mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-06 06:32:56 +01:00
Merge pull request #8806 from ZehMatt/mp-ga-cooldown
Implement rate limiting times for game actions.
This commit is contained in:
@@ -188,6 +188,15 @@ public:
|
||||
return const_cast<GameAction&>(*this).Serialise(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to specify the wait time in milliseconds the player is required to wait before
|
||||
* being able to execute it again.
|
||||
*/
|
||||
virtual uint32_t GetCooldownTime() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the result of the game action without changing the game state.
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t GetCooldownTime() const override
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
|
||||
void Serialise(DataSerialiser & stream) override
|
||||
{
|
||||
GameAction::Serialise(stream);
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t GetCooldownTime() const override
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
uint16_t GetActionFlags() const override
|
||||
{
|
||||
return GameAction::GetActionFlags();
|
||||
|
||||
@@ -203,6 +203,7 @@ public:
|
||||
std::string ServerProviderWebsite;
|
||||
|
||||
private:
|
||||
void DecayCooldown(NetworkPlayer* player);
|
||||
void CloseConnection();
|
||||
|
||||
bool ProcessConnection(NetworkConnection& connection);
|
||||
@@ -314,6 +315,8 @@ private:
|
||||
uint8_t default_group = 0;
|
||||
uint32_t _commandId;
|
||||
uint32_t _actionId;
|
||||
uint32_t _lastUpdateTime = 0;
|
||||
uint32_t _currentDeltaTime = 0;
|
||||
std::string _chatLogPath;
|
||||
std::string _chatLogFilenameFormat = "%Y%m%d-%H%M%S.txt";
|
||||
std::string _serverLogPath;
|
||||
@@ -451,6 +454,21 @@ void Network::Close()
|
||||
}
|
||||
}
|
||||
|
||||
void Network::DecayCooldown(NetworkPlayer* player)
|
||||
{
|
||||
if (player == nullptr)
|
||||
return; // No valid connection yet.
|
||||
|
||||
for (auto it = std::begin(player->CooldownTime); it != std::end(player->CooldownTime);)
|
||||
{
|
||||
it->second -= _currentDeltaTime;
|
||||
if (it->second <= 0)
|
||||
it = player->CooldownTime.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void Network::CloseConnection()
|
||||
{
|
||||
if (mode == NETWORK_MODE_CLIENT)
|
||||
@@ -671,6 +689,11 @@ void Network::Update()
|
||||
{
|
||||
_closeLock = true;
|
||||
|
||||
// Update is not necessarily called per game tick, maintain our own delta time
|
||||
uint32_t ticks = platform_get_ticks();
|
||||
_currentDeltaTime = std::max<uint32_t>(ticks - _lastUpdateTime, 1);
|
||||
_lastUpdateTime = ticks;
|
||||
|
||||
switch (GetMode())
|
||||
{
|
||||
case NETWORK_MODE_SERVER:
|
||||
@@ -716,6 +739,7 @@ void Network::UpdateServer()
|
||||
}
|
||||
else
|
||||
{
|
||||
DecayCooldown((*it)->Player);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
@@ -2737,58 +2761,29 @@ void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPa
|
||||
uint32_t tick;
|
||||
uint32_t actionType;
|
||||
|
||||
if (!connection.Player)
|
||||
NetworkPlayer* player = connection.Player;
|
||||
if (player == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
packet >> tick >> actionType;
|
||||
|
||||
// tick count is different by time last_action_time is set, keep same value
|
||||
// Don't let clients send pause or quit
|
||||
if (actionType == GAME_COMMAND_TOGGLE_PAUSE || actionType == GAME_COMMAND_LOAD_OR_QUIT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player's group permission allows command to run
|
||||
uint32_t ticks = platform_get_ticks();
|
||||
NetworkGroup* group = GetGroupByID(connection.Player->Group);
|
||||
if (!group || !group->CanPerformCommand(actionType))
|
||||
if (group == nullptr || group->CanPerformCommand(actionType) == false)
|
||||
{
|
||||
Server_Send_SHOWERROR(connection, STR_CANT_DO_THIS, STR_PERMISSION_DENIED);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 (actionType == 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.
|
||||
ticks > connection.Player->LastPlaceSceneryTime)
|
||||
{
|
||||
if (!(group->CanPerformCommand(MISC_COMMAND_TOGGLE_SCENERY_CLUSTER)))
|
||||
{
|
||||
Server_Send_SHOWERROR(connection, STR_CANT_DO_THIS, STR_NETWORK_ACTION_RATE_LIMIT_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 (actionType == 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.
|
||||
ticks > connection.Player->LastDemolishRideTime)
|
||||
{
|
||||
Server_Send_SHOWERROR(connection, STR_CANT_DO_THIS, STR_NETWORK_ACTION_RATE_LIMIT_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Don't let clients send pause or quit
|
||||
else if (actionType == GAME_COMMAND_TOGGLE_PAUSE || actionType == GAME_COMMAND_LOAD_OR_QUIT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Run game command, and if it is successful send to clients
|
||||
// Create and enqueue the action.
|
||||
GameAction::Ptr ga = GameActions::Create(actionType);
|
||||
if (ga == nullptr)
|
||||
{
|
||||
@@ -2798,6 +2793,26 @@ void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPa
|
||||
return;
|
||||
}
|
||||
|
||||
// Player who is hosting is not affected by cooldowns.
|
||||
if ((player->Flags & NETWORK_PLAYER_FLAG_ISSERVER) == 0)
|
||||
{
|
||||
auto cooldownIt = player->CooldownTime.find(actionType);
|
||||
if (cooldownIt != std::end(player->CooldownTime))
|
||||
{
|
||||
if (cooldownIt->second > 0)
|
||||
{
|
||||
Server_Send_SHOWERROR(connection, STR_CANT_DO_THIS, STR_NETWORK_ACTION_RATE_LIMIT_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cooldownTime = ga->GetCooldownTime();
|
||||
if (cooldownTime > 0)
|
||||
{
|
||||
player->CooldownTime[actionType] = cooldownTime;
|
||||
}
|
||||
}
|
||||
|
||||
DataSerialiser stream(false);
|
||||
size_t size = packet.Size - packet.BytesRead;
|
||||
stream.GetStream().WriteArray(packet.Read(size), size);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../world/Sprite.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class NetworkPacket;
|
||||
|
||||
@@ -36,7 +37,7 @@ public:
|
||||
std::string KeyHash;
|
||||
uint32_t LastDemolishRideTime = 0;
|
||||
uint32_t LastPlaceSceneryTime = 0;
|
||||
|
||||
std::unordered_map<uint32_t, int32_t> CooldownTime;
|
||||
NetworkPlayer() = default;
|
||||
|
||||
void SetName(const std::string& name);
|
||||
|
||||
Reference in New Issue
Block a user