Work on timeout of kick votes.
This commit is contained in:
@@ -907,12 +907,18 @@ Server Notification: End Kick Player Petition
|
|||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| # Votes against Kicking | # Votes in favour of Kicking |
|
| # Votes against Kicking | # Votes in favour of Kicking |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| Vote Result | Reserved |
|
| Vote Result | End Reason |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
Vote Result:
|
Vote Result:
|
||||||
0: Player not kicked.
|
0: Player not kicked.
|
||||||
1: Player kicked.
|
1: Player kicked.
|
||||||
|
|
||||||
|
End Reason:
|
||||||
|
0: Enough votes to determine result.
|
||||||
|
1: Not enough players left to vote.
|
||||||
|
2: Player to be kicked left.
|
||||||
|
3: Petition timed out.
|
||||||
|
|
||||||
|
|
||||||
Server Notification: Statistics Changed
|
Server Notification: Statistics Changed
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ enum DenyVoteReason
|
|||||||
VOTE_DENIED_OTHER_REASON
|
VOTE_DENIED_OTHER_REASON
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum EndPetitionReason
|
||||||
|
{
|
||||||
|
PETITION_END_ENOUGH_VOTES = 0,
|
||||||
|
PETITION_END_NOT_ENOUGH_PLAYERS,
|
||||||
|
PETITION_END_PLAYER_LEFT,
|
||||||
|
PETITION_END_TIMEOUT
|
||||||
|
};
|
||||||
|
|
||||||
enum Button {
|
enum Button {
|
||||||
BUTTON_NONE = 0,
|
BUTTON_NONE = 0,
|
||||||
|
|||||||
+7
-6
@@ -91,15 +91,16 @@ struct StartData
|
|||||||
struct VoteKickData
|
struct VoteKickData
|
||||||
{
|
{
|
||||||
VoteKickData()
|
VoteKickData()
|
||||||
: petitionId(0), kickPlayerId(0), initialNumVotesToKick(0),
|
: petitionId(0), kickPlayerId(0), numVotesToKick(0),
|
||||||
numVotesInFavourOfKicking(0), numVotesAgainstKicking(0) {}
|
numVotesInFavourOfKicking(0), numVotesAgainstKicking(0), timeLimitSec(0) {}
|
||||||
unsigned petitionId;
|
unsigned petitionId;
|
||||||
unsigned kickPlayerId;
|
unsigned kickPlayerId;
|
||||||
unsigned initialNumVotesToKick;
|
int numVotesToKick;
|
||||||
unsigned numVotesInFavourOfKicking;
|
int numVotesInFavourOfKicking;
|
||||||
unsigned numVotesAgainstKicking;
|
int numVotesAgainstKicking;
|
||||||
|
unsigned timeLimitSec;
|
||||||
boost::timers::portable::microsec_timer voteTimer;
|
boost::timers::portable::microsec_timer voteTimer;
|
||||||
std::list<unsigned> votedPlayerIds;
|
PlayerIdList votedPlayerIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ using namespace std;
|
|||||||
#define NET_VOTE_KICK_DENIED_IMPOSSIBLE 0x0002
|
#define NET_VOTE_KICK_DENIED_IMPOSSIBLE 0x0002
|
||||||
#define NET_VOTE_KICK_DENIED_OTHER_REASON 0xFFFF
|
#define NET_VOTE_KICK_DENIED_OTHER_REASON 0xFFFF
|
||||||
|
|
||||||
|
// Reasons why a petition to kick a player was ended.
|
||||||
|
#define NET_PETITION_END_ENOUGH_VOTES 0x0000
|
||||||
|
#define NET_PETITION_END_NOT_ENOUGH_PLAYERS 0x0001
|
||||||
|
#define NET_PETITION_END_PLAYER_LEFT 0x0002
|
||||||
|
#define NET_PETITION_END_TIMEOUT 0x0003
|
||||||
|
|
||||||
// Reasons for timeout warning
|
// Reasons for timeout warning
|
||||||
#define NET_TIMEOUT_NO_DATA_RECEIVED 0x0000
|
#define NET_TIMEOUT_NO_DATA_RECEIVED 0x0000
|
||||||
#define NET_TIMEOUT_INACTIVE_GAME 0x0001
|
#define NET_TIMEOUT_INACTIVE_GAME 0x0001
|
||||||
@@ -589,7 +595,7 @@ struct GCC_PACKED NetPacketEndKickPlayerPetitionData
|
|||||||
u_int16_t numVotesAgainstKicking;
|
u_int16_t numVotesAgainstKicking;
|
||||||
u_int16_t numVotesInFavourOfKicking;
|
u_int16_t numVotesInFavourOfKicking;
|
||||||
u_int16_t voteResult;
|
u_int16_t voteResult;
|
||||||
u_int16_t reserved;
|
u_int16_t endReason;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GCC_PACKED StatisticsData
|
struct GCC_PACKED StatisticsData
|
||||||
@@ -4855,6 +4861,24 @@ NetPacketEndKickPlayerPetition::SetData(const NetPacketEndKickPlayerPetition::Da
|
|||||||
tmpData->numVotesInFavourOfKicking = htons(inData.numVotesInFavourOfKicking);
|
tmpData->numVotesInFavourOfKicking = htons(inData.numVotesInFavourOfKicking);
|
||||||
tmpData->voteResult = htons(inData.playerKicked ? 1 : 0);
|
tmpData->voteResult = htons(inData.playerKicked ? 1 : 0);
|
||||||
|
|
||||||
|
switch (inData.endReason)
|
||||||
|
{
|
||||||
|
case PETITION_END_ENOUGH_VOTES:
|
||||||
|
tmpData->endReason = htons(NET_PETITION_END_ENOUGH_VOTES);
|
||||||
|
break;
|
||||||
|
case PETITION_END_NOT_ENOUGH_PLAYERS:
|
||||||
|
tmpData->endReason = htons(NET_PETITION_END_NOT_ENOUGH_PLAYERS);
|
||||||
|
break;
|
||||||
|
case PETITION_END_PLAYER_LEFT:
|
||||||
|
tmpData->endReason = htons(NET_PETITION_END_PLAYER_LEFT);
|
||||||
|
break;
|
||||||
|
case PETITION_END_TIMEOUT:
|
||||||
|
tmpData->endReason = htons(NET_PETITION_END_TIMEOUT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw NetException(__FILE__, __LINE__, ERR_SOCK_INVALID_PACKET, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Check the packet - just in case.
|
// Check the packet - just in case.
|
||||||
Check(GetRawData());
|
Check(GetRawData());
|
||||||
}
|
}
|
||||||
@@ -4868,6 +4892,24 @@ NetPacketEndKickPlayerPetition::GetData(NetPacketEndKickPlayerPetition::Data &ou
|
|||||||
outData.numVotesAgainstKicking = ntohs(tmpData->numVotesAgainstKicking);
|
outData.numVotesAgainstKicking = ntohs(tmpData->numVotesAgainstKicking);
|
||||||
outData.numVotesInFavourOfKicking = ntohs(tmpData->numVotesInFavourOfKicking);
|
outData.numVotesInFavourOfKicking = ntohs(tmpData->numVotesInFavourOfKicking);
|
||||||
outData.playerKicked = ntohs(tmpData->voteResult) == 1;
|
outData.playerKicked = ntohs(tmpData->voteResult) == 1;
|
||||||
|
|
||||||
|
switch (ntohs(tmpData->endReason))
|
||||||
|
{
|
||||||
|
case NET_PETITION_END_ENOUGH_VOTES:
|
||||||
|
outData.endReason = PETITION_END_ENOUGH_VOTES;
|
||||||
|
break;
|
||||||
|
case NET_PETITION_END_NOT_ENOUGH_PLAYERS:
|
||||||
|
outData.endReason = PETITION_END_NOT_ENOUGH_PLAYERS;
|
||||||
|
break;
|
||||||
|
case NET_PETITION_END_PLAYER_LEFT:
|
||||||
|
outData.endReason = PETITION_END_PLAYER_LEFT;
|
||||||
|
break;
|
||||||
|
case NET_PETITION_END_TIMEOUT:
|
||||||
|
outData.endReason = PETITION_END_TIMEOUT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw NetException(__FILE__, __LINE__, ERR_SOCK_INVALID_PACKET, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NetPacketEndKickPlayerPetition *
|
const NetPacketEndKickPlayerPetition *
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
|
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
#define SERVER_CHECK_VOTE_KICK_INTERVAL_MSEC 500
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
@@ -171,14 +174,70 @@ ServerGameThread::RemovePlayerLoop()
|
|||||||
void
|
void
|
||||||
ServerGameThread::VoteKickLoop()
|
ServerGameThread::VoteKickLoop()
|
||||||
{
|
{
|
||||||
/* boost::mutex::scoped_lock lock(m_voteKickMapMutex);
|
// Do not call the vote kick action all the time.
|
||||||
// Check the list whether the player is already being kicked.
|
// Check the timer.
|
||||||
VoteKickMap::iterator i = m_voteKickMap.begin();
|
// The action will also be initiated after a player left.
|
||||||
VoteKickMap::iterator end = m_voteKickMap.end();
|
if (m_voteKickActionTimer.elapsed().total_milliseconds() >= SERVER_CHECK_VOTE_KICK_INTERVAL_MSEC)
|
||||||
while (i != end)
|
VoteKickAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ServerGameThread::VoteKickAction()
|
||||||
|
{
|
||||||
|
// Check whether someone should be kicked, or whether a vote kick should be aborted.
|
||||||
|
// Only one vote kick can be active at a time.
|
||||||
|
boost::mutex::scoped_lock lock(m_voteKickDataMutex);
|
||||||
|
if (m_voteKickData)
|
||||||
{
|
{
|
||||||
++i;
|
const PlayerIdList playerIds(GetPlayerIdList());
|
||||||
}*/
|
// 1. Several players left the game, so a kick is no longer possible.
|
||||||
|
int votesRequiredToKick = m_voteKickData->numVotesToKick - m_voteKickData->numVotesInFavourOfKicking;
|
||||||
|
int playersAllowedToVote = 0;
|
||||||
|
// We need to count the number of players which are still allowed to vote.
|
||||||
|
PlayerIdList::const_iterator player_i = playerIds.begin();
|
||||||
|
PlayerIdList::const_iterator player_end = playerIds.end();
|
||||||
|
while (player_i != player_end)
|
||||||
|
{
|
||||||
|
if (find(m_voteKickData->votedPlayerIds.begin(), m_voteKickData->votedPlayerIds.end(), *player_i) == m_voteKickData->votedPlayerIds.end())
|
||||||
|
playersAllowedToVote++;
|
||||||
|
++player_i;
|
||||||
|
}
|
||||||
|
bool abortPetition = false;
|
||||||
|
EndPetitionReason reason = PETITION_END_ENOUGH_VOTES;
|
||||||
|
|
||||||
|
if (votesRequiredToKick > playersAllowedToVote)
|
||||||
|
{
|
||||||
|
reason = PETITION_END_NOT_ENOUGH_PLAYERS;
|
||||||
|
abortPetition = true;
|
||||||
|
}
|
||||||
|
// 2. The kick has become invalid because the player to be kicked left.
|
||||||
|
else if (find(playerIds.begin(), playerIds.end(), m_voteKickData->kickPlayerId) == playerIds.end())
|
||||||
|
{
|
||||||
|
reason = PETITION_END_PLAYER_LEFT;
|
||||||
|
abortPetition = true;
|
||||||
|
}
|
||||||
|
// 3. A kick request timed out (because not everyone voted).
|
||||||
|
else if (m_voteKickData->voteTimer.elapsed().total_seconds() >= m_voteKickData->timeLimitSec)
|
||||||
|
{
|
||||||
|
reason = PETITION_END_TIMEOUT;
|
||||||
|
abortPetition = true;
|
||||||
|
}
|
||||||
|
if (abortPetition)
|
||||||
|
{
|
||||||
|
boost::shared_ptr<NetPacket> endPetition(new NetPacketEndKickPlayerPetition);
|
||||||
|
NetPacketEndKickPlayerPetition::Data endPetitionData;
|
||||||
|
endPetitionData.petitionId = m_voteKickData->petitionId;
|
||||||
|
endPetitionData.numVotesAgainstKicking = m_voteKickData->numVotesAgainstKicking;
|
||||||
|
endPetitionData.numVotesInFavourOfKicking = m_voteKickData->numVotesInFavourOfKicking;
|
||||||
|
endPetitionData.playerKicked = false;
|
||||||
|
endPetitionData.endReason = reason;
|
||||||
|
m_voteKickData.reset();
|
||||||
|
lock.unlock(); // Do not block the data longer than necessary.
|
||||||
|
|
||||||
|
static_cast<NetPacketEndKickPlayerPetition *>(endPetition.get())->SetData(endPetitionData);
|
||||||
|
SendToAllPlayers(endPetition, SessionData::Game);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -248,44 +307,36 @@ ServerGameThread::InternalAskVoteKick(SessionWrapper byWhom, unsigned playerIdWh
|
|||||||
size_t numPlayers = GetCurNumberOfPlayers();
|
size_t numPlayers = GetCurNumberOfPlayers();
|
||||||
if (numPlayers > 2)
|
if (numPlayers > 2)
|
||||||
{
|
{
|
||||||
// Lock the vote kick list.
|
// Lock the vote kick data.
|
||||||
boost::mutex::scoped_lock lock(m_voteKickMapMutex);
|
boost::mutex::scoped_lock lock(m_voteKickDataMutex);
|
||||||
// Check the list whether the player is already being kicked.
|
if (!m_voteKickData)
|
||||||
VoteKickMap::const_iterator i = m_voteKickMap.begin();
|
|
||||||
VoteKickMap::const_iterator end = m_voteKickMap.end();
|
|
||||||
while (i != end)
|
|
||||||
{
|
|
||||||
if (i->second->kickPlayerId == playerIdWho)
|
|
||||||
break;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
if (i == end)
|
|
||||||
{
|
{
|
||||||
// Initiate a vote kick.
|
// Initiate a vote kick.
|
||||||
unsigned playerIdByWhom = byWhom.playerData->GetUniqueId();
|
unsigned playerIdByWhom = byWhom.playerData->GetUniqueId();
|
||||||
boost::shared_ptr<VoteKickData> voteData(new VoteKickData);
|
m_voteKickData.reset(new VoteKickData);
|
||||||
voteData->petitionId = m_curPetitionId++;
|
m_voteKickData->petitionId = m_curPetitionId++;
|
||||||
voteData->kickPlayerId = playerIdWho;
|
m_voteKickData->kickPlayerId = playerIdWho;
|
||||||
voteData->initialNumVotesToKick = static_cast<unsigned>(ceil(numPlayers / 3. * 2.));
|
m_voteKickData->numVotesToKick = static_cast<int>(ceil(numPlayers / 3. * 2.));
|
||||||
|
m_voteKickData->timeLimitSec = timeoutSec;
|
||||||
// Consider first vote.
|
// Consider first vote.
|
||||||
voteData->numVotesInFavourOfKicking = 1;
|
m_voteKickData->numVotesInFavourOfKicking = 1;
|
||||||
voteData->votedPlayerIds.push_back(playerIdByWhom);
|
m_voteKickData->votedPlayerIds.push_back(playerIdByWhom);
|
||||||
m_voteKickMap.insert(VoteKickMap::value_type(voteData->petitionId, voteData));
|
|
||||||
lock.unlock(); // Do not block the list longer than necessary.
|
|
||||||
|
|
||||||
boost::shared_ptr<NetPacket> startPetition(new NetPacketStartKickPlayerPetition);
|
boost::shared_ptr<NetPacket> startPetition(new NetPacketStartKickPlayerPetition);
|
||||||
NetPacketStartKickPlayerPetition::Data startPetitionData;
|
NetPacketStartKickPlayerPetition::Data startPetitionData;
|
||||||
startPetitionData.petitionId = voteData->petitionId;
|
startPetitionData.petitionId = m_voteKickData->petitionId;
|
||||||
startPetitionData.proposingPlayerId = playerIdByWhom;
|
startPetitionData.proposingPlayerId = playerIdByWhom;
|
||||||
startPetitionData.kickPlayerId = voteData->kickPlayerId;
|
startPetitionData.kickPlayerId = m_voteKickData->kickPlayerId;
|
||||||
startPetitionData.kickTimeoutSec = timeoutSec;
|
startPetitionData.kickTimeoutSec = m_voteKickData->timeLimitSec;
|
||||||
startPetitionData.numVotesNeededToKick = voteData->initialNumVotesToKick;
|
startPetitionData.numVotesNeededToKick = m_voteKickData->numVotesToKick;
|
||||||
|
lock.unlock(); // Do not block the data longer than necessary.
|
||||||
|
|
||||||
static_cast<NetPacketStartKickPlayerPetition *>(startPetition.get())->SetData(startPetitionData);
|
static_cast<NetPacketStartKickPlayerPetition *>(startPetition.get())->SetData(startPetitionData);
|
||||||
SendToAllPlayers(startPetition, SessionData::Game);
|
SendToAllPlayers(startPetition, SessionData::Game);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lock.unlock(); // Do not block the list longer than necessary.
|
lock.unlock(); // Do not block the data longer than necessary.
|
||||||
InternalDenyAskVoteKick(byWhom, playerIdWho, KICK_DENIED_OTHER_IN_PROGRESS);
|
InternalDenyAskVoteKick(byWhom, playerIdWho, KICK_DENIED_OTHER_IN_PROGRESS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,22 +363,20 @@ ServerGameThread::InternalVoteKick(unsigned petitionId, KickVote vote)
|
|||||||
{
|
{
|
||||||
if (IsRunning())
|
if (IsRunning())
|
||||||
{
|
{
|
||||||
boost::mutex::scoped_lock lock(m_voteKickMapMutex);
|
boost::mutex::scoped_lock lock(m_voteKickDataMutex);
|
||||||
VoteKickMap::iterator pos = m_voteKickMap.find(petitionId);
|
if (m_voteKickData->petitionId == petitionId)
|
||||||
if (pos != m_voteKickMap.end())
|
|
||||||
{
|
{
|
||||||
boost::shared_ptr<VoteKickData> curData(pos->second);
|
|
||||||
if (vote == KICK_VOTE_IN_FAVOUR)
|
if (vote == KICK_VOTE_IN_FAVOUR)
|
||||||
{
|
{
|
||||||
curData->numVotesInFavourOfKicking++;
|
m_voteKickData->numVotesInFavourOfKicking++;
|
||||||
if (curData->numVotesInFavourOfKicking >= curData->initialNumVotesToKick)
|
if (m_voteKickData->numVotesInFavourOfKicking >= m_voteKickData->numVotesToKick)
|
||||||
{
|
{
|
||||||
// Perform kick.
|
// Perform kick.
|
||||||
InternalKickPlayer(curData->kickPlayerId);
|
InternalKickPlayer(m_voteKickData->kickPlayerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
curData->numVotesAgainstKicking++;
|
m_voteKickData->numVotesAgainstKicking++;
|
||||||
// TODO abort if no longer possible.
|
// TODO abort if no longer possible.
|
||||||
// TODO remove deprecated list entries.
|
// TODO remove deprecated list entries.
|
||||||
// TODO error handling.
|
// TODO error handling.
|
||||||
|
|||||||
+5
-4
@@ -1337,10 +1337,11 @@ class NetPacketEndKickPlayerPetition : public NetPacket
|
|||||||
public:
|
public:
|
||||||
struct Data
|
struct Data
|
||||||
{
|
{
|
||||||
u_int32_t petitionId;
|
u_int32_t petitionId;
|
||||||
u_int16_t numVotesAgainstKicking;
|
u_int16_t numVotesAgainstKicking;
|
||||||
u_int16_t numVotesInFavourOfKicking;
|
u_int16_t numVotesInFavourOfKicking;
|
||||||
bool playerKicked;
|
bool playerKicked;
|
||||||
|
EndPetitionReason endReason;
|
||||||
};
|
};
|
||||||
|
|
||||||
NetPacketEndKickPlayerPetition();
|
NetPacketEndKickPlayerPetition();
|
||||||
|
|||||||
@@ -77,12 +77,12 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
typedef std::deque<SessionWrapper> SessionQueue;
|
typedef std::deque<SessionWrapper> SessionQueue;
|
||||||
typedef std::map<unsigned, boost::shared_ptr<VoteKickData> > VoteKickMap;
|
|
||||||
|
|
||||||
// Main function of the thread.
|
// Main function of the thread.
|
||||||
virtual void Main();
|
virtual void Main();
|
||||||
void RemovePlayerLoop();
|
void RemovePlayerLoop();
|
||||||
void VoteKickLoop();
|
void VoteKickLoop();
|
||||||
|
void VoteKickAction();
|
||||||
|
|
||||||
void InternalStartGame();
|
void InternalStartGame();
|
||||||
void ResetGame();
|
void ResetGame();
|
||||||
@@ -145,8 +145,8 @@ private:
|
|||||||
unsigned m_adminPlayerId;
|
unsigned m_adminPlayerId;
|
||||||
mutable boost::mutex m_adminPlayerIdMutex;
|
mutable boost::mutex m_adminPlayerIdMutex;
|
||||||
|
|
||||||
VoteKickMap m_voteKickMap;
|
boost::shared_ptr<VoteKickData> m_voteKickData;
|
||||||
mutable boost::mutex m_voteKickMapMutex;
|
mutable boost::mutex m_voteKickDataMutex;
|
||||||
|
|
||||||
ServerLobbyThread &m_lobbyThread;
|
ServerLobbyThread &m_lobbyThread;
|
||||||
boost::shared_ptr<ReceiverHelper> m_receiver;
|
boost::shared_ptr<ReceiverHelper> m_receiver;
|
||||||
@@ -166,6 +166,8 @@ private:
|
|||||||
boost::timers::portable::microsec_timer m_stateTimer;
|
boost::timers::portable::microsec_timer m_stateTimer;
|
||||||
unsigned m_stateTimerFlag;
|
unsigned m_stateTimerFlag;
|
||||||
|
|
||||||
|
boost::timers::portable::microsec_timer m_voteKickActionTimer;
|
||||||
|
|
||||||
friend class AbstractServerGameStateReceiving;
|
friend class AbstractServerGameStateReceiving;
|
||||||
friend class AbstractServerGameStateRunning;
|
friend class AbstractServerGameStateRunning;
|
||||||
friend class AbstractServerGameStateTimer;
|
friend class AbstractServerGameStateTimer;
|
||||||
|
|||||||
Reference in New Issue
Block a user