From e0c1662fa5ee3e6ac7c4053620b57cfd3e4df5ca Mon Sep 17 00:00:00 2001 From: lotodore Date: Sat, 6 Dec 2008 21:10:46 +0000 Subject: [PATCH] Work on timeout of kick votes. --- docs/net_protocol.txt | 8 +- src/game_defs.h | 7 ++ src/gamedata.h | 13 +-- src/net/common/netpacket.cpp | 44 +++++++++- src/net/common/servergamethread.cpp | 129 +++++++++++++++++++--------- src/net/netpacket.h | 9 +- src/net/servergamethread.h | 8 +- 7 files changed, 163 insertions(+), 55 deletions(-) diff --git a/docs/net_protocol.txt b/docs/net_protocol.txt index f7846c41..4d38d037 100644 --- a/docs/net_protocol.txt +++ b/docs/net_protocol.txt @@ -907,12 +907,18 @@ Server Notification: End Kick Player Petition +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | # Votes against Kicking | # Votes in favour of Kicking | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Vote Result | Reserved | + | Vote Result | End Reason | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Vote Result: 0: Player not 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 diff --git a/src/game_defs.h b/src/game_defs.h index bd4b366b..ba6a9748 100644 --- a/src/game_defs.h +++ b/src/game_defs.h @@ -89,6 +89,13 @@ enum DenyVoteReason 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 { BUTTON_NONE = 0, diff --git a/src/gamedata.h b/src/gamedata.h index f49a4bdf..e5c407ad 100644 --- a/src/gamedata.h +++ b/src/gamedata.h @@ -91,15 +91,16 @@ struct StartData struct VoteKickData { VoteKickData() - : petitionId(0), kickPlayerId(0), initialNumVotesToKick(0), - numVotesInFavourOfKicking(0), numVotesAgainstKicking(0) {} + : petitionId(0), kickPlayerId(0), numVotesToKick(0), + numVotesInFavourOfKicking(0), numVotesAgainstKicking(0), timeLimitSec(0) {} unsigned petitionId; unsigned kickPlayerId; - unsigned initialNumVotesToKick; - unsigned numVotesInFavourOfKicking; - unsigned numVotesAgainstKicking; + int numVotesToKick; + int numVotesInFavourOfKicking; + int numVotesAgainstKicking; + unsigned timeLimitSec; boost::timers::portable::microsec_timer voteTimer; - std::list votedPlayerIds; + PlayerIdList votedPlayerIds; }; #endif diff --git a/src/net/common/netpacket.cpp b/src/net/common/netpacket.cpp index df07cc36..6dfcba9a 100644 --- a/src/net/common/netpacket.cpp +++ b/src/net/common/netpacket.cpp @@ -136,6 +136,12 @@ using namespace std; #define NET_VOTE_KICK_DENIED_IMPOSSIBLE 0x0002 #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 #define NET_TIMEOUT_NO_DATA_RECEIVED 0x0000 #define NET_TIMEOUT_INACTIVE_GAME 0x0001 @@ -589,7 +595,7 @@ struct GCC_PACKED NetPacketEndKickPlayerPetitionData u_int16_t numVotesAgainstKicking; u_int16_t numVotesInFavourOfKicking; u_int16_t voteResult; - u_int16_t reserved; + u_int16_t endReason; }; struct GCC_PACKED StatisticsData @@ -4855,6 +4861,24 @@ NetPacketEndKickPlayerPetition::SetData(const NetPacketEndKickPlayerPetition::Da tmpData->numVotesInFavourOfKicking = htons(inData.numVotesInFavourOfKicking); 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(GetRawData()); } @@ -4868,6 +4892,24 @@ NetPacketEndKickPlayerPetition::GetData(NetPacketEndKickPlayerPetition::Data &ou outData.numVotesAgainstKicking = ntohs(tmpData->numVotesAgainstKicking); outData.numVotesInFavourOfKicking = ntohs(tmpData->numVotesInFavourOfKicking); 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 * diff --git a/src/net/common/servergamethread.cpp b/src/net/common/servergamethread.cpp index 2d86cb38..239ee3fd 100644 --- a/src/net/common/servergamethread.cpp +++ b/src/net/common/servergamethread.cpp @@ -31,6 +31,9 @@ #include + +#define SERVER_CHECK_VOTE_KICK_INTERVAL_MSEC 500 + using namespace std; @@ -171,14 +174,70 @@ ServerGameThread::RemovePlayerLoop() void ServerGameThread::VoteKickLoop() { -/* boost::mutex::scoped_lock lock(m_voteKickMapMutex); - // Check the list whether the player is already being kicked. - VoteKickMap::iterator i = m_voteKickMap.begin(); - VoteKickMap::iterator end = m_voteKickMap.end(); - while (i != end) + // Do not call the vote kick action all the time. + // Check the timer. + // The action will also be initiated after a player left. + if (m_voteKickActionTimer.elapsed().total_milliseconds() >= SERVER_CHECK_VOTE_KICK_INTERVAL_MSEC) + 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 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(endPetition.get())->SetData(endPetitionData); + SendToAllPlayers(endPetition, SessionData::Game); + } + } } void @@ -248,44 +307,36 @@ ServerGameThread::InternalAskVoteKick(SessionWrapper byWhom, unsigned playerIdWh size_t numPlayers = GetCurNumberOfPlayers(); if (numPlayers > 2) { - // Lock the vote kick list. - boost::mutex::scoped_lock lock(m_voteKickMapMutex); - // Check the list whether the player is already being kicked. - 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) + // Lock the vote kick data. + boost::mutex::scoped_lock lock(m_voteKickDataMutex); + if (!m_voteKickData) { // Initiate a vote kick. unsigned playerIdByWhom = byWhom.playerData->GetUniqueId(); - boost::shared_ptr voteData(new VoteKickData); - voteData->petitionId = m_curPetitionId++; - voteData->kickPlayerId = playerIdWho; - voteData->initialNumVotesToKick = static_cast(ceil(numPlayers / 3. * 2.)); + m_voteKickData.reset(new VoteKickData); + m_voteKickData->petitionId = m_curPetitionId++; + m_voteKickData->kickPlayerId = playerIdWho; + m_voteKickData->numVotesToKick = static_cast(ceil(numPlayers / 3. * 2.)); + m_voteKickData->timeLimitSec = timeoutSec; // Consider first vote. - voteData->numVotesInFavourOfKicking = 1; - voteData->votedPlayerIds.push_back(playerIdByWhom); - m_voteKickMap.insert(VoteKickMap::value_type(voteData->petitionId, voteData)); - lock.unlock(); // Do not block the list longer than necessary. + m_voteKickData->numVotesInFavourOfKicking = 1; + m_voteKickData->votedPlayerIds.push_back(playerIdByWhom); boost::shared_ptr startPetition(new NetPacketStartKickPlayerPetition); NetPacketStartKickPlayerPetition::Data startPetitionData; - startPetitionData.petitionId = voteData->petitionId; + startPetitionData.petitionId = m_voteKickData->petitionId; startPetitionData.proposingPlayerId = playerIdByWhom; - startPetitionData.kickPlayerId = voteData->kickPlayerId; - startPetitionData.kickTimeoutSec = timeoutSec; - startPetitionData.numVotesNeededToKick = voteData->initialNumVotesToKick; + startPetitionData.kickPlayerId = m_voteKickData->kickPlayerId; + startPetitionData.kickTimeoutSec = m_voteKickData->timeLimitSec; + startPetitionData.numVotesNeededToKick = m_voteKickData->numVotesToKick; + lock.unlock(); // Do not block the data longer than necessary. + static_cast(startPetition.get())->SetData(startPetitionData); SendToAllPlayers(startPetition, SessionData::Game); } 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); } } @@ -312,22 +363,20 @@ ServerGameThread::InternalVoteKick(unsigned petitionId, KickVote vote) { if (IsRunning()) { - boost::mutex::scoped_lock lock(m_voteKickMapMutex); - VoteKickMap::iterator pos = m_voteKickMap.find(petitionId); - if (pos != m_voteKickMap.end()) + boost::mutex::scoped_lock lock(m_voteKickDataMutex); + if (m_voteKickData->petitionId == petitionId) { - boost::shared_ptr curData(pos->second); if (vote == KICK_VOTE_IN_FAVOUR) { - curData->numVotesInFavourOfKicking++; - if (curData->numVotesInFavourOfKicking >= curData->initialNumVotesToKick) + m_voteKickData->numVotesInFavourOfKicking++; + if (m_voteKickData->numVotesInFavourOfKicking >= m_voteKickData->numVotesToKick) { // Perform kick. - InternalKickPlayer(curData->kickPlayerId); + InternalKickPlayer(m_voteKickData->kickPlayerId); } } else - curData->numVotesAgainstKicking++; + m_voteKickData->numVotesAgainstKicking++; // TODO abort if no longer possible. // TODO remove deprecated list entries. // TODO error handling. diff --git a/src/net/netpacket.h b/src/net/netpacket.h index 9fd06572..11a5edea 100644 --- a/src/net/netpacket.h +++ b/src/net/netpacket.h @@ -1337,10 +1337,11 @@ class NetPacketEndKickPlayerPetition : public NetPacket public: struct Data { - u_int32_t petitionId; - u_int16_t numVotesAgainstKicking; - u_int16_t numVotesInFavourOfKicking; - bool playerKicked; + u_int32_t petitionId; + u_int16_t numVotesAgainstKicking; + u_int16_t numVotesInFavourOfKicking; + bool playerKicked; + EndPetitionReason endReason; }; NetPacketEndKickPlayerPetition(); diff --git a/src/net/servergamethread.h b/src/net/servergamethread.h index a19d1fab..e2f77469 100644 --- a/src/net/servergamethread.h +++ b/src/net/servergamethread.h @@ -77,12 +77,12 @@ public: protected: typedef std::deque SessionQueue; - typedef std::map > VoteKickMap; // Main function of the thread. virtual void Main(); void RemovePlayerLoop(); void VoteKickLoop(); + void VoteKickAction(); void InternalStartGame(); void ResetGame(); @@ -145,8 +145,8 @@ private: unsigned m_adminPlayerId; mutable boost::mutex m_adminPlayerIdMutex; - VoteKickMap m_voteKickMap; - mutable boost::mutex m_voteKickMapMutex; + boost::shared_ptr m_voteKickData; + mutable boost::mutex m_voteKickDataMutex; ServerLobbyThread &m_lobbyThread; boost::shared_ptr m_receiver; @@ -166,6 +166,8 @@ private: boost::timers::portable::microsec_timer m_stateTimer; unsigned m_stateTimerFlag; + boost::timers::portable::microsec_timer m_voteKickActionTimer; + friend class AbstractServerGameStateReceiving; friend class AbstractServerGameStateRunning; friend class AbstractServerGameStateTimer;