Add support for joining server with ongoing game

This commit is contained in:
Benau 2018-08-31 16:27:32 +08:00
parent 8a579d9542
commit 2199679ac2
15 changed files with 438 additions and 141 deletions

View File

@ -48,14 +48,20 @@ void GameSetup::update(bool remove_disconnected_players)
{
return npp.expired();
}), m_players.end());
m_connected_players_count.store((uint32_t)m_players.size());
return;
}
if (!World::getWorld() ||
World::getWorld()->getPhase() < WorldStatus::MUSIC_PHASE)
{
m_connected_players_count.store((uint32_t)m_players.size());
return;
}
lock.unlock();
if (!World::getWorld() ||
World::getWorld()->getPhase() < WorldStatus::MUSIC_PHASE)
return;
int red_count = 0;
int blue_count = 0;
unsigned total = 0;
for (uint8_t i = 0; i < (uint8_t)m_players.size(); i++)
{
bool disconnected = m_players[i].expired();
@ -67,7 +73,10 @@ void GameSetup::update(bool remove_disconnected_players)
blue_count++;
if (!disconnected)
{
total++;
continue;
}
AbstractKart* k = World::getWorld()->getKart(i);
if (!k->isEliminated())
{
@ -82,6 +91,8 @@ void GameSetup::update(bool remove_disconnected_players)
STKHost::get()->sendPacketToAllPeers(&p, true);
}
}
m_connected_players_count.store(total);
if (m_players.size() != 1 && World::getWorld()->hasTeam() &&
(red_count == 0 || blue_count == 0))
World::getWorld()->setUnfairTeam(true);

View File

@ -24,6 +24,7 @@
#include "network/remote_kart_info.hpp"
#include <atomic>
#include <cassert>
#include <memory>
#include <mutex>
@ -58,10 +59,13 @@ private:
float m_battle_time_limit;
std::atomic<uint32_t> m_connected_players_count;
public:
// ------------------------------------------------------------------------
GameSetup()
{
m_connected_players_count.store(0);
m_extra_server_info = -1;
reset();
}
@ -99,11 +103,7 @@ public:
} // getConnectedPlayers
// ------------------------------------------------------------------------
/** Returns the number of connected players. */
unsigned getPlayerCount()
{
std::lock_guard<std::mutex> lock(m_players_mutex);
return (unsigned)m_players.size();
}
unsigned getPlayerCount() { return m_connected_players_count.load(); }
// ------------------------------------------------------------------------
void setRace(const std::string& track, unsigned laps, bool reverse)
{

View File

@ -111,9 +111,9 @@ void Protocol::requestTerminate()
} // requestTerminate
// ----------------------------------------------------------------------------
/** Sends a message to all peers, encrypt the message if needed.
* The message is composed of a 1-byte message (usually the message type)
* followed by the actual message.
/** Sends a message to all validated peers in game, encrypt the message if
* needed. The message is composed of a 1-byte message (usually the message
* type) followed by the actual message.
* \param message The actual message content.
*/
void Protocol::sendMessageToPeers(NetworkString *message, bool reliable)
@ -121,6 +121,18 @@ void Protocol::sendMessageToPeers(NetworkString *message, bool reliable)
STKHost::get()->sendPacketToAllPeers(message, reliable);
} // sendMessageToPeers
// ----------------------------------------------------------------------------
/** Sends a message to all validated peers in server, encrypt the message if
* needed. The message is composed of a 1-byte message (usually the message
* type) followed by the actual message.
* \param message The actual message content.
*/
void Protocol::sendMessageToPeersInServer(NetworkString* message,
bool reliable)
{
STKHost::get()->sendPacketToAllPeersInServer(message, reliable);
} // sendMessageToPeersInServer
// ----------------------------------------------------------------------------
/** Sends a message from a client to the server.
*/

View File

@ -126,6 +126,8 @@ public:
NetworkString* getNetworkString(size_t capacity = 16);
bool checkDataSize(Event* event, unsigned int minimum_size);
void sendMessageToPeers(NetworkString *message, bool reliable = true);
void sendMessageToPeersInServer(NetworkString *message,
bool reliable = true);
void sendToServer(NetworkString *message, bool reliable = true);
void requestStart();
void requestPause();

View File

@ -72,6 +72,7 @@ engine.
ClientLobby::ClientLobby(const TransportAddress& a, std::shared_ptr<Server> s)
: LobbyProtocol(NULL)
{
m_waiting_for_game.store(false);
m_state.store(NONE);
m_server_address = a;
m_server = s;
@ -626,6 +627,16 @@ void ClientLobby::updatePlayerList(Event* event)
{
if (!checkDataSize(event, 1)) return;
NetworkString& data = event->data();
bool waiting = data.getUInt8() == 1;
if (m_waiting_for_game.load() && !waiting)
{
// The waiting game finished
NetworkingLobby::getInstance()
->addMoreServerInfo(L"--------------------");
SFXManager::get()->quickSound("wee");
}
m_waiting_for_game.store(waiting);
unsigned player_count = data.getUInt8();
std::vector<std::tuple<uint32_t, uint32_t, uint32_t, core::stringw,
int, KartTeam> > players;
@ -637,9 +648,13 @@ void ClientLobby::updatePlayerList(Event* event)
std::get<1>(pl) = data.getUInt32();
std::get<2>(pl) = data.getUInt8();
data.decodeStringW(&std::get<3>(pl));
bool is_peer_waiting_for_game = data.getUInt8() == 1;
bool is_peer_server_owner = data.getUInt8() == 1;
// icon to be used, see NetworkingLobby::loadedFromFile
std::get<4>(pl) = data.getUInt8() == 1 /*if server owner*/ ? 0 :
std::get<4>(pl) = is_peer_server_owner ? 0 :
std::get<1>(pl) != 0 /*if online account*/ ? 1 : 2;
if (waiting && !is_peer_waiting_for_game)
std::get<4>(pl) = 3;
PerPlayerDifficulty d = (PerPlayerDifficulty)data.getUInt8();
if (d == PLAYER_DIFFICULTY_HANDICAP)
std::get<3>(pl) = _("%s (handicapped)", std::get<3>(pl));

View File

@ -71,6 +71,8 @@ private:
EXITING
};
std::atomic_bool m_waiting_for_game;
/** The state of the finite state machine. */
std::atomic<ClientState> m_state;
@ -100,13 +102,13 @@ public:
virtual void finishedLoadingWorld() OVERRIDE;
virtual void setup() OVERRIDE;
virtual void update(int ticks) OVERRIDE;
virtual bool waitingForPlayers() const OVERRIDE
{ return m_state.load() == CONNECTED; }
virtual void asynchronousUpdate() OVERRIDE {}
virtual bool allPlayersReady() const OVERRIDE
{ return m_state.load() >= RACING; }
bool waitingForServerRespond() const
{ return m_state.load() == REQUESTING_CONNECTION; }
bool isLobbyReady() const { return m_state.load() == CONNECTED; }
bool isWaitingForGame() const { return m_waiting_for_game.load(); }
virtual bool isRacing() const OVERRIDE { return m_state.load() == RACING; }
};

View File

@ -125,7 +125,6 @@ public:
virtual void update(int ticks) = 0;
virtual void finishedLoadingWorld() = 0;
virtual void loadWorld();
virtual bool waitingForPlayers() const = 0;
virtual bool allPlayersReady() const = 0;
virtual bool isRacing() const = 0;
GameSetup* getGameSetup() const { return m_game_setup; }

View File

@ -61,12 +61,12 @@
node [shape=ellipse,style=filled,color=lightgrey];
"Server Constructor" -> "INIT_WAN" [label="If WAN game"]
"Server Constructor" -> "ACCEPTING_CLIENTS" [label="If LAN game"]
"Server Constructor" -> "WAITING_FOR_START_GAME" [label="If LAN game"]
"INIT_WAN" -> "GETTING_PUBLIC_ADDRESS" [label="GetPublicAddress protocol callback"]
"GETTING_PUBLIC_ADDRESS" -> "ACCEPTING_CLIENTS" [label="Register server"]
"ACCEPTING_CLIENTS" -> "connectionRequested" [label="Client connection request"]
"connectionRequested" -> "ACCEPTING_CLIENTS"
"ACCEPTING_CLIENTS" -> "SELECTING" [label="Start race from authorised client"]
"GETTING_PUBLIC_ADDRESS" -> "WAITING_FOR_START_GAME" [label="Register server"]
"WAITING_FOR_START_GAME" -> "connectionRequested" [label="Client connection request"]
"connectionRequested" -> "WAITING_FOR_START_GAME"
"WAITING_FOR_START_GAME" -> "SELECTING" [label="Start race from authorised client"]
"SELECTING" -> "SELECTING" [label="Client selects kart, #laps, ..."]
"SELECTING" -> "playerTrackVote" [label="Client selected track"]
"playerTrackVote" -> "SELECTING" [label="Not all clients have selected"]
@ -90,7 +90,9 @@
*/
ServerLobby::ServerLobby() : LobbyProtocol(NULL)
{
m_waiting_players_counts.store(0);
m_server_owner_id.store(-1);
m_registered_for_once_only = false;
m_has_created_server_id_file = false;
setHandleDisconnections(true);
m_state = SET_PUBLIC_ADDRESS;
@ -253,7 +255,19 @@ void ServerLobby::handleChat(Event* event)
NetworkString* chat = getNetworkString();
chat->setSynchronous(true);
chat->addUInt8(LE_CHAT).encodeString16(message);
sendMessageToPeers(chat, /*reliable*/true);
// After game is started, only send to waiting player
const bool game_started = m_state.load() != WAITING_FOR_START_GAME;
STKHost::get()->sendPacketToAllPeersWith([game_started]
(STKPeer* p)
{
if (!p->isValidated())
return false;
if (!game_started)
return true;
if (!p->isWaitingForGame() && game_started)
return false;
return true;
}, chat);
delete chat;
}
} // handleChat
@ -341,6 +355,17 @@ void ServerLobby::asynchronousUpdate()
// Check if server owner has left
updateServerOwner();
if (allowJoinedPlayersWaiting() || (m_game_setup->isGrandPrix() &&
m_state.load() == WAITING_FOR_START_GAME))
{
updateWaitingPlayers();
clearDisconnectedRankedPlayer();
// Only poll the STK server if this is a WAN server.
if (NetworkConfig::get()->isWAN())
checkIncomingConnectionRequests();
handlePendingConnection();
}
switch (m_state.load())
{
case SET_PUBLIC_ADDRESS:
@ -349,7 +374,7 @@ void ServerLobby::asynchronousUpdate()
// STK server, so we can directly go to the accepting clients state.
if (NetworkConfig::get()->isLAN())
{
m_state = ACCEPTING_CLIENTS;
m_state = WAITING_FOR_START_GAME;
STKHost::get()->startListening();
createServerIdFile();
return;
@ -369,9 +394,9 @@ void ServerLobby::asynchronousUpdate()
}
case REGISTER_SELF_ADDRESS:
{
if (m_game_setup->isGrandPrixStarted())
if (m_game_setup->isGrandPrixStarted() || m_registered_for_once_only)
{
m_state = ACCEPTING_CLIENTS;
m_state = WAITING_FOR_START_GAME;
break;
}
// Register this server with the STK server. This will block
@ -379,7 +404,9 @@ void ServerLobby::asynchronousUpdate()
// to react to any requests before the server is registered.
if (registerServer())
{
m_state = ACCEPTING_CLIENTS;
if (allowJoinedPlayersWaiting())
m_registered_for_once_only = true;
m_state = WAITING_FOR_START_GAME;
createServerIdFile();
}
else
@ -388,12 +415,12 @@ void ServerLobby::asynchronousUpdate()
}
break;
}
case ACCEPTING_CLIENTS:
case WAITING_FOR_START_GAME:
{
if (NetworkConfig::get()->isOwnerLess())
{
auto players = m_game_setup->getPlayers();
if (((float)players.size() >=
float player_size = (float)m_game_setup->getPlayerCount();
if ((player_size >=
(float)NetworkConfig::get()->getMaxPlayers() *
UserConfigParams::m_start_game_threshold ||
m_game_setup->isGrandPrixStarted()) &&
@ -403,7 +430,7 @@ void ServerLobby::asynchronousUpdate()
(int64_t)
(UserConfigParams::m_start_game_counter * 1000.0f));
}
else if ((float)players.size() <
else if (player_size <
(float)NetworkConfig::get()->getMaxPlayers() *
UserConfigParams::m_start_game_threshold &&
!m_game_setup->isGrandPrixStarted())
@ -416,11 +443,6 @@ void ServerLobby::asynchronousUpdate()
return;
}
}
clearDisconnectedRankedPlayer();
// Only poll the STK server if this is a WAN server.
if (NetworkConfig::get()->isWAN())
checkIncomingConnectionRequests();
handlePendingConnection();
break;
}
case ERROR_LEAVE:
@ -538,14 +560,12 @@ void ServerLobby::update(int ticks)
RaceResultGUI::getInstance()->backToLobby();
std::lock_guard<std::mutex> lock(m_connection_mutex);
m_game_setup->stopGrandPrix();
m_state = NetworkConfig::get()->isLAN() ?
ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS;
setup();
resetServer();
}
if ((m_state.load() > ACCEPTING_CLIENTS ||
if ((m_state.load() > WAITING_FOR_START_GAME ||
m_game_setup->isGrandPrixStarted()) &&
STKHost::get()->getPeerCount() == 0 &&
m_game_setup->getPlayerCount() == 0 &&
NetworkConfig::get()->getServerIdFile().empty())
{
if (RaceEventManager::getInstance() &&
@ -571,29 +591,20 @@ void ServerLobby::update(int ticks)
delete exit_result_screen;
std::lock_guard<std::mutex> lock(m_connection_mutex);
m_game_setup->stopGrandPrix();
m_state = NetworkConfig::get()->isLAN() ?
ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS;
updatePlayerList(true/*force_update*/);
NetworkString* server_info = getNetworkString();
server_info->setSynchronous(true);
server_info->addUInt8(LE_SERVER_INFO);
m_game_setup->addServerInfo(server_info);
sendMessageToPeers(server_info);
delete server_info;
setup();
resetServer();
}
if (m_game_setup)
{
// Remove disconnected players if in these two states
m_game_setup->update(m_state.load() == ACCEPTING_CLIENTS ||
m_game_setup->update(m_state.load() == WAITING_FOR_START_GAME ||
m_state.load() == SELECTING);
}
switch (m_state.load())
{
case SET_PUBLIC_ADDRESS:
case REGISTER_SELF_ADDRESS:
case ACCEPTING_CLIENTS:
case WAITING_FOR_START_GAME:
case WAIT_FOR_WORLD_LOADED:
case WAIT_FOR_RACE_STARTED:
{
@ -645,16 +656,7 @@ void ServerLobby::update(int ticks)
sendMessageToPeers(exit_result_screen, /*reliable*/true);
delete exit_result_screen;
std::lock_guard<std::mutex> lock(m_connection_mutex);
m_state = NetworkConfig::get()->isLAN() ?
ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS;
updatePlayerList(true/*force_update*/);
NetworkString* server_info = getNetworkString();
server_info->setSynchronous(true);
server_info->addUInt8(LE_SERVER_INFO);
m_game_setup->addServerInfo(server_info);
sendMessageToPeers(server_info);
delete server_info;
setup();
resetServer();
}
break;
case ERROR_LEAVE:
@ -748,7 +750,7 @@ void ServerLobby::startSelection(const Event *event)
{
if (event != NULL)
{
if (m_state != ACCEPTING_CLIENTS)
if (m_state != WAITING_FOR_START_GAME)
{
Log::warn("ServerLobby",
"Received startSelection while being in state %d",
@ -791,10 +793,13 @@ void ServerLobby::startSelection(const Event *event)
}
}
ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION);
if (NetworkConfig::get()->isWAN())
if (!allowJoinedPlayersWaiting())
{
unregisterServer(false/*now*/);
ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION);
if (NetworkConfig::get()->isWAN() )
{
unregisterServer(false/*now*/);
}
}
NetworkString *ns = getNetworkString(1);
@ -809,7 +814,7 @@ void ServerLobby::startSelection(const Event *event)
auto peers = STKHost::get()->getPeers();
for (auto peer : peers)
{
if (!peer->isValidated())
if (!peer->isValidated() || peer->isWaitingForGame())
continue;
peer->eraseServerKarts(m_available_kts.first, karts_erase);
peer->eraseServerTracks(m_available_kts.second, tracks_erase);
@ -855,16 +860,19 @@ void ServerLobby::startSelection(const Event *event)
delete ns;
m_state = SELECTING;
// Drop all pending players and keys
for (auto& p : m_pending_connection)
if (!allowJoinedPlayersWaiting())
{
if (auto peer = p.first.lock())
peer->disconnect();
// Drop all pending players and keys if doesn't allow joinning-waiting
for (auto& p : m_pending_connection)
{
if (auto peer = p.first.lock())
peer->disconnect();
}
m_pending_connection.clear();
std::unique_lock<std::mutex> ul(m_keys_mutex);
m_keys.clear();
ul.unlock();
}
m_pending_connection.clear();
std::unique_lock<std::mutex> ul(m_keys_mutex);
m_keys.clear();
ul.unlock();
// Will be changed after the first vote received
m_timeout.store(std::numeric_limits<int64_t>::max());
@ -916,7 +924,8 @@ void ServerLobby::checkIncomingConnectionRequests()
const XMLNode * users_xml = result->getNode("users");
std::map<uint32_t, KeyData> keys;
auto sl = m_server_lobby.lock();
if (!sl || sl->m_state.load() != ACCEPTING_CLIENTS)
if (!sl || (sl->m_state.load() != WAITING_FOR_START_GAME &&
!sl->allowJoinedPlayersWaiting()))
return;
for (unsigned int i = 0; i < users_xml->getNumNodes(); i++)
@ -956,7 +965,10 @@ void ServerLobby::checkIncomingConnectionRequests()
const TransportAddress &addr = STKHost::get()->getPublicAddress();
request->addParameter("address", addr.getIP() );
request->addParameter("port", addr.getPort());
request->addParameter("current-players", m_game_setup->getPlayerCount());
request->addParameter("current-players",
m_game_setup->getPlayerCount() + m_waiting_players_counts.load());
request->addParameter("game-started",
m_state.load() == WAITING_FOR_START_GAME ? 0 : 1);
request->queue();
} // checkIncomingConnectionRequests
@ -1236,6 +1248,8 @@ void ServerLobby::clientDisconnected(Event* event)
return;
NetworkString* msg = getNetworkString(2);
const bool waiting_peer_disconnected =
event->getPeer()->isWaitingForGame();
msg->setSynchronous(true);
msg->addUInt8(LE_PLAYER_DISCONNECTED);
msg->addUInt8((uint8_t)players_on_peer.size());
@ -1245,7 +1259,17 @@ void ServerLobby::clientDisconnected(Event* event)
msg->encodeString(name);
Log::info("ServerLobby", "%s disconnected", name.c_str());
}
sendMessageToPeers(msg, /*reliable*/true);
// Don't show waiting peer disconnect message to in game player
STKHost::get()->sendPacketToAllPeersWith([waiting_peer_disconnected]
(STKPeer* p)
{
if (!p->isValidated())
return false;
if (!p->isWaitingForGame() && waiting_peer_disconnected)
return false;
return true;
}, msg);
updatePlayerList();
delete msg;
} // clientDisconnected
@ -1281,8 +1305,9 @@ void ServerLobby::connectionRequested(Event* event)
peer->cleanPlayerProfiles();
// can we add the player ?
if (m_state != ACCEPTING_CLIENTS ||
m_game_setup->isGrandPrixStarted())
if (!allowJoinedPlayersWaiting() &&
(m_state.load() != WAITING_FOR_START_GAME ||
m_game_setup->isGrandPrixStarted()))
{
NetworkString *message = getNetworkString(2);
message->setSynchronous(true);
@ -1394,7 +1419,8 @@ void ServerLobby::connectionRequested(Event* event)
return;
}
if (m_game_setup->getPlayerCount() + player_count >
if (m_game_setup->getPlayerCount() + player_count +
m_waiting_players_counts.load() >
NetworkConfig::get()->getMaxPlayers())
{
NetworkString *message = getNetworkString(2);
@ -1415,7 +1441,8 @@ void ServerLobby::connectionRequested(Event* event)
peer->getAddress().isLAN()) &&
NetworkConfig::get()->isWAN() &&
NetworkConfig::get()->onlyValidatedPlayers()) ||
((player_count != 1 || m_scores.find(online_id) != m_scores.end()) &&
((player_count != 1 || online_id == 0 ||
m_scores.find(online_id) != m_scores.end()) &&
NetworkConfig::get()->isRankedServer()))
{
NetworkString* message = getNetworkString(2);
@ -1507,6 +1534,7 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr<STKPeer> peer,
peer->sendPacket(server_info);
delete server_info;
const bool game_started = m_state.load() != WAITING_FOR_START_GAME;
NetworkString* message_ack = getNetworkString(4);
message_ack->setSynchronous(true);
// connection success -- return the host id of peer
@ -1520,39 +1548,70 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr<STKPeer> peer,
}
message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt32(peer->getHostId())
.addUInt32(NetworkConfig::m_server_version).addFloat(auto_start_timer);
peer->sendPacket(message_ack);
delete message_ack;
m_peers_ready[peer] = false;
for (std::shared_ptr<NetworkPlayerProfile> npp : peer->getPlayerProfiles())
if (game_started)
{
m_game_setup->addPlayer(npp);
Log::info("ServerLobby", "New player %s with online id %u from %s.",
StringUtils::wideToUtf8(npp->getName()).c_str(),
npp->getOnlineId(), peer->getAddress().toString().c_str());
peer->setWaitingForGame(true);
for (auto& p : peer->getPlayerProfiles())
m_waiting_players.push_back(p);
updatePlayerList();
peer->sendPacket(message_ack);
delete message_ack;
}
updatePlayerList();
if (NetworkConfig::get()->isRankedServer())
else
{
getRankingForPlayer(peer->getPlayerProfiles()[0]);
peer->setWaitingForGame(false);
m_peers_ready[peer] = false;
for (std::shared_ptr<NetworkPlayerProfile>& npp :
peer->getPlayerProfiles())
{
m_game_setup->addPlayer(npp);
Log::info("ServerLobby",
"New player %s with online id %u from %s.",
StringUtils::wideToUtf8(npp->getName()).c_str(),
npp->getOnlineId(), peer->getAddress().toString().c_str());
}
updatePlayerList();
peer->sendPacket(message_ack);
delete message_ack;
if (NetworkConfig::get()->isRankedServer())
{
getRankingForPlayer(peer->getPlayerProfiles()[0]);
}
}
} // handleUnencryptedConnection
//-----------------------------------------------------------------------------
void ServerLobby::updatePlayerList(bool force_update)
/** Called when any players change their setting (team for example), or
* connection / disconnection, it will use the game_started parameter to
* determine if this should be send to all peers in server or just in game.
* \param update_when_reset_server If true, this message will be sent to
* all peers.
*/
void ServerLobby::updatePlayerList(bool update_when_reset_server)
{
if (m_state.load() > ACCEPTING_CLIENTS && !force_update)
const bool game_started = m_state.load() != WAITING_FOR_START_GAME &&
!update_when_reset_server;
// No need to update player list (for started grand prix currently)
if (!allowJoinedPlayersWaiting() &&
m_state.load() > WAITING_FOR_START_GAME && !update_when_reset_server)
return;
auto all_profiles = STKHost::get()->getAllPlayerProfiles();
NetworkString* pl = getNetworkString();
pl->setSynchronous(true);
pl->addUInt8(LE_UPDATE_PLAYER_LIST).addUInt8((uint8_t)all_profiles.size());
pl->addUInt8(LE_UPDATE_PLAYER_LIST)
.addUInt8((uint8_t)(game_started ? 1 : 0))
.addUInt8((uint8_t)all_profiles.size());
for (auto profile : all_profiles)
{
pl->addUInt32(profile->getHostId()).addUInt32(profile->getOnlineId())
.addUInt8(profile->getLocalPlayerId())
.encodeString(profile->getName());
pl->addUInt8((uint8_t)
(profile->getPeer()->isWaitingForGame() ? 1 : 0));
uint8_t server_owner = 0;
if (m_server_owner_id.load() == profile->getPeer()->getHostId())
server_owner = 1;
@ -1564,14 +1623,24 @@ void ServerLobby::updatePlayerList(bool force_update)
else
pl->addUInt8(KART_TEAM_NONE);
}
sendMessageToPeers(pl);
// Don't send this message to in-game players
STKHost::get()->sendPacketToAllPeersWith([game_started]
(STKPeer* p)
{
if (!p->isValidated())
return false;
if (!p->isWaitingForGame() && game_started)
return false;
return true;
}, pl);
delete pl;
} // updatePlayerList
//-----------------------------------------------------------------------------
void ServerLobby::updateServerOwner()
{
if (m_state.load() < ACCEPTING_CLIENTS ||
if (m_state.load() < WAITING_FOR_START_GAME ||
m_state.load() > RESULT_DISPLAY ||
NetworkConfig::get()->isOwnerLess())
return;
@ -1660,7 +1729,8 @@ void ServerLobby::playerVote(Event* event)
}
if (!checkDataSize(event, 4) ||
event->getPeer()->getPlayerProfiles().empty())
event->getPeer()->getPlayerProfiles().empty() ||
event->getPeer()->isWaitingForGame())
return;
// Check if first vote, if so start counter
@ -1714,7 +1784,7 @@ std::tuple<std::string, uint8_t, bool, bool> ServerLobby::handleVote()
auto peers = STKHost::get()->getPeers();
for (auto peer : peers)
{
if (peer->hasPlayerProfiles())
if (peer->hasPlayerProfiles() && !peer->isWaitingForGame())
cur_players += 1.0f;
}
if (cur_players == 0.0f)
@ -1887,8 +1957,9 @@ void ServerLobby::updateBanList()
//-----------------------------------------------------------------------------
bool ServerLobby::waitingForPlayers() const
{
return m_state.load() == ACCEPTING_CLIENTS &&
!m_game_setup->isGrandPrixStarted();
if (m_game_setup->isGrandPrix() && m_game_setup->isGrandPrixStarted())
return false;
return m_state.load() >= WAITING_FOR_START_GAME;
} // waitingForPlayers
//-----------------------------------------------------------------------------
@ -2094,3 +2165,86 @@ void ServerLobby::configPeersStartTime()
m_state.store(RACING);
});
} // configPeersStartTime
//-----------------------------------------------------------------------------
bool ServerLobby::allowJoinedPlayersWaiting() const
{
return !m_game_setup->isGrandPrix();
} // allowJoinedPlayersWaiting
//-----------------------------------------------------------------------------
void ServerLobby::updateWaitingPlayers()
{
// addWaitingPlayersToGame below will be called by main thread in case
// resetServer
std::lock_guard<std::mutex> lock(m_connection_mutex);
m_waiting_players.erase(std::remove_if(
m_waiting_players.begin(), m_waiting_players.end(), []
(const std::weak_ptr<NetworkPlayerProfile>& npp)->bool
{
return npp.expired();
}), m_waiting_players.end());
m_waiting_players_counts.store((uint32_t)m_waiting_players.size());
} // updateWaitingPlayers
//-----------------------------------------------------------------------------
void ServerLobby::addWaitingPlayersToGame()
{
if (m_waiting_players.empty())
return;
for (auto& p : m_waiting_players)
{
auto npp = p.lock();
if (!npp)
continue;
auto peer = npp->getPeer();
assert(peer);
uint32_t online_id = npp->getOnlineId();
if (NetworkConfig::get()->isRankedServer())
{
if (m_scores.find(online_id) != m_scores.end())
{
NetworkString* message = getNetworkString(2);
message->setSynchronous(true);
message->addUInt8(LE_CONNECTION_REFUSED)
.addUInt8(RR_INVALID_PLAYER);
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
peer->reset();
delete message;
Log::verbose("ServerLobby", "Player refused: invalid player");
continue;
}
}
peer->setWaitingForGame(false);
m_peers_ready[peer] = false;
m_game_setup->addPlayer(npp);
Log::info("ServerLobby", "New player %s with online id %u from %s.",
StringUtils::wideToUtf8(npp->getName()).c_str(),
npp->getOnlineId(), peer->getAddress().toString().c_str());
if (NetworkConfig::get()->isRankedServer())
{
getRankingForPlayer(peer->getPlayerProfiles()[0]);
}
}
m_waiting_players.clear();
m_waiting_players_counts.store(0);
} // addWaitingPlayersToGame
//-----------------------------------------------------------------------------
void ServerLobby::resetServer()
{
addWaitingPlayersToGame();
m_state = NetworkConfig::get()->isLAN() ?
WAITING_FOR_START_GAME : REGISTER_SELF_ADDRESS;
updatePlayerList(true/*update_when_reset_server*/);
NetworkString* server_info = getNetworkString();
server_info->setSynchronous(true);
server_info->addUInt8(LE_SERVER_INFO);
m_game_setup->addServerInfo(server_info);
sendMessageToPeersInServer(server_info);
delete server_info;
setup();
} // resetServer

View File

@ -46,7 +46,7 @@ public:
{
SET_PUBLIC_ADDRESS, // Waiting to receive its public ip address
REGISTER_SELF_ADDRESS, // Register with STK online server
ACCEPTING_CLIENTS, // In lobby, accepting clients
WAITING_FOR_START_GAME, // In lobby, waiting for (auto) start game
SELECTING, // kart, track, ... selection started
LOAD_WORLD, // Server starts loading world
WAIT_FOR_WORLD_LOADED, // Wait for clients and server to load world
@ -136,6 +136,12 @@ private:
NetworkString* m_result_ns;
std::vector<std::weak_ptr<NetworkPlayerProfile> > m_waiting_players;
std::atomic<uint32_t> m_waiting_players_counts;
bool m_registered_for_once_only;
// connection management
void clientDisconnected(Event* event);
void connectionRequested(Event* event);
@ -151,7 +157,7 @@ private:
void handleChat(Event* event);
void unregisterServer(bool now);
void createServerIdFile();
void updatePlayerList(bool force_update = false);
void updatePlayerList(bool update_when_reset_server = false);
void updateServerOwner();
bool checkPeersReady() const
{
@ -212,6 +218,9 @@ private:
void sendBadConnectionMessageToPeer(std::shared_ptr<STKPeer> p);
std::pair<int, float> getHitCaptureLimit(float num_karts);
void configPeersStartTime();
void updateWaitingPlayers();
void resetServer();
void addWaitingPlayersToGame();
public:
ServerLobby();
virtual ~ServerLobby();
@ -227,10 +236,13 @@ public:
void finishedLoadingWorld() OVERRIDE;
ServerState getCurrentState() const { return m_state.load(); }
void updateBanList();
virtual bool waitingForPlayers() const OVERRIDE;
bool waitingForPlayers() const;
uint32_t getWaitingPlayersCount() const
{ return m_waiting_players_counts.load(); }
virtual bool allPlayersReady() const OVERRIDE
{ return m_state.load() >= WAIT_FOR_RACE_STARTED; }
virtual bool isRacing() const OVERRIDE { return m_state.load() == RACING; }
bool allowJoinedPlayersWaiting() const;
}; // class ServerLobby

View File

@ -738,7 +738,7 @@ void STKHost::mainLoop()
std::unique_lock<std::mutex> peer_lock(m_peers_mutex);
const float timeout = UserConfigParams::m_validation_timeout;
bool need_ping = false;
if (sl && !sl->isRacing() &&
if (sl && (!sl->isRacing() || sl->allowJoinedPlayersWaiting()) &&
last_ping_time < StkTime::getRealTimeMs())
{
// If not racing, send an reliable packet at the same rate with
@ -749,6 +749,7 @@ void STKHost::mainLoop()
need_ping = true;
}
ENetPacket* packet = NULL;
if (need_ping)
{
m_peer_pings.getData().clear();
@ -772,22 +773,25 @@ void STKHost::mainLoop()
ECT_DISCONNECT);
}
}
BareNetworkString ping_packet;
uint64_t network_timer = getNetworkTimer();
ping_packet.addUInt64(network_timer);
ping_packet.addUInt8((uint8_t)m_peer_pings.getData().size());
for (auto& p : m_peer_pings.getData())
ping_packet.addUInt32(p.first).addUInt32(p.second);
ping_packet.getBuffer().insert(
ping_packet.getBuffer().begin(), g_ping_packet.begin(),
g_ping_packet.end());
packet = enet_packet_create(ping_packet.getData(),
ping_packet.getTotalSize(), ENET_PACKET_FLAG_RELIABLE);
}
for (auto it = m_peers.begin(); it != m_peers.end();)
{
if (need_ping)
if (need_ping &&
(!sl->allowJoinedPlayersWaiting() ||
!sl->isRacing() || it->second->isWaitingForGame()))
{
BareNetworkString ping_packet;
uint64_t network_timer = getNetworkTimer();
ping_packet.addUInt64(network_timer);
ping_packet.addUInt8((uint8_t)m_peer_pings.getData().size());
for (auto& p : m_peer_pings.getData())
ping_packet.addUInt32(p.first).addUInt32(p.second);
ping_packet.getBuffer().insert(
ping_packet.getBuffer().begin(), g_ping_packet.begin(),
g_ping_packet.end());
ENetPacket* packet = enet_packet_create(ping_packet.getData(),
ping_packet.getTotalSize(), ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(it->first, EVENT_CHANNEL_UNENCRYPTED, packet);
}
@ -1026,12 +1030,15 @@ void STKHost::handleDirectSocketRequest(Network* direct_socket,
s.addUInt32(NetworkConfig::m_server_version);
s.encodeString(name);
s.addUInt8(NetworkConfig::get()->getMaxPlayers());
s.addUInt8((uint8_t)sl->getGameSetup()->getPlayerCount());
s.addUInt8((uint8_t)(sl->getGameSetup()->getPlayerCount() +
sl->getWaitingPlayersCount()));
s.addUInt16(m_private_port);
s.addUInt8((uint8_t)race_manager->getDifficulty());
s.addUInt8((uint8_t)NetworkConfig::get()->getServerMode());
s.addUInt8(!NetworkConfig::get()->getPassword().empty());
s.addUInt8(0);
s.addUInt8((uint8_t)
(sl->getCurrentState() == ServerLobby::WAITING_FOR_START_GAME ?
0 : 1));
direct_socket->sendRawPacket(s, sender);
} // if message is server-requested
else if (command == connection_cmd)
@ -1123,7 +1130,22 @@ bool STKHost::isConnectedTo(const TransportAddress& peer)
} // isConnectedTo
//-----------------------------------------------------------------------------
/** Sends data to all peers
/** Sends data to all validated peers currently in server
* \param data Data to sent.
* \param reliable If the data should be sent reliable or now.
*/
void STKHost::sendPacketToAllPeersInServer(NetworkString *data, bool reliable)
{
std::lock_guard<std::mutex> lock(m_peers_mutex);
for (auto p : m_peers)
{
if (p.second->isValidated())
p.second->sendPacket(data, reliable);
}
} // sendPacketToAllPeersInServer
//-----------------------------------------------------------------------------
/** Sends data to all validated peers currently in game
* \param data Data to sent.
* \param reliable If the data should be sent reliable or now.
*/
@ -1132,13 +1154,13 @@ void STKHost::sendPacketToAllPeers(NetworkString *data, bool reliable)
std::lock_guard<std::mutex> lock(m_peers_mutex);
for (auto p : m_peers)
{
if (p.second->isValidated())
if (p.second->isValidated() && !p.second->isWaitingForGame())
p.second->sendPacket(data, reliable);
}
} // sendPacketExcept
} // sendPacketToAllPeers
//-----------------------------------------------------------------------------
/** Sends data to all peers except the specified one.
/** Sends data to all validated peers except the specified currently in game
* \param peer Peer which will not receive the message.
* \param data Data to sent.
* \param reliable If the data should be sent reliable or now.
@ -1150,13 +1172,32 @@ void STKHost::sendPacketExcept(STKPeer* peer, NetworkString *data,
for (auto p : m_peers)
{
STKPeer* stk_peer = p.second.get();
if (!stk_peer->isSamePeer(peer) && p.second->isValidated())
if (!stk_peer->isSamePeer(peer) && p.second->isValidated() &&
!p.second->isWaitingForGame())
{
stk_peer->sendPacket(data, reliable);
}
}
} // sendPacketExcept
//-----------------------------------------------------------------------------
/** Sends data to peers with custom rule
* \param predicate boolean function for peer to predicate whether to send
* \param data Data to sent.
* \param reliable If the data should be sent reliable or now.
*/
void STKHost::sendPacketToAllPeersWith(std::function<bool(STKPeer*)> predicate,
NetworkString* data, bool reliable)
{
std::lock_guard<std::mutex> lock(m_peers_mutex);
for (auto p : m_peers)
{
STKPeer* stk_peer = p.second.get();
if (predicate(stk_peer))
stk_peer->sendPacket(data, reliable);
}
} // sendPacketToAllPeersWith
//-----------------------------------------------------------------------------
/** Sends a message from a client to the server. */
void STKHost::sendToServer(NetworkString *data, bool reliable)

View File

@ -38,6 +38,7 @@
#include <atomic>
#include <list>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
@ -210,8 +211,14 @@ public:
//-------------------------------------------------------------------------
void shutdown();
//-------------------------------------------------------------------------
void sendPacketToAllPeersInServer(NetworkString *data,
bool reliable = true);
// ------------------------------------------------------------------------
void sendPacketToAllPeers(NetworkString *data, bool reliable = true);
// ------------------------------------------------------------------------
void sendPacketToAllPeersWith(std::function<bool(STKPeer*)> predicate,
NetworkString* data, bool reliable = true);
// ------------------------------------------------------------------------
/** Returns true if this client instance is allowed to control the server.
* It will auto transfer ownership if previous server owner disconnected.
*/

View File

@ -40,6 +40,7 @@ STKPeer::STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id)
m_connected_time = StkTime::getRealTimeMs();
m_validated.store(false);
m_average_ping.store(0);
m_waiting_for_game.store(true);
} // STKPeer
//-----------------------------------------------------------------------------

View File

@ -64,6 +64,9 @@ protected:
/** True if this peer is validated by server. */
std::atomic_bool m_validated;
/** True if this peer is waiting for game. */
std::atomic_bool m_waiting_for_game;
/** Host id of this peer. */
uint32_t m_host_id;
@ -170,6 +173,11 @@ public:
uint32_t getAveragePing() const { return m_average_ping.load(); }
// ------------------------------------------------------------------------
ENetPeer* getENetPeer() const { return m_enet_peer; }
// ------------------------------------------------------------------------
void setWaitingForGame(bool val) { m_waiting_for_game.store(val); }
// ------------------------------------------------------------------------
bool isWaitingForGame() const { return m_waiting_for_game.load(); }
}; // STKPeer
#endif // STK_PEER_HPP

View File

@ -37,7 +37,7 @@
#include "io/file_manager.hpp"
#include "network/network_config.hpp"
#include "network/protocols/connect_to_server.hpp"
#include "network/protocols/lobby_protocol.hpp"
#include "network/protocols/client_lobby.hpp"
#include "network/server.hpp"
#include "network/stk_host.hpp"
#include "states_screens/state_manager.hpp"
@ -115,9 +115,12 @@ void NetworkingLobby::loadedFromFile()
(file_manager->getAsset(FileManager::GUI, "difficulty_medium.png"));
video::ITexture* icon_3 = irr_driver->getTexture
(file_manager->getAsset(FileManager::GUI, "main_help.png"));
video::ITexture* icon_4 = irr_driver->getTexture
(file_manager->getAsset(FileManager::GUI, "hourglass.png"));
m_icon_bank->addTextureAsSprite(icon_1);
m_icon_bank->addTextureAsSprite(icon_2);
m_icon_bank->addTextureAsSprite(icon_3);
m_icon_bank->addTextureAsSprite(icon_4);
const int screen_width = irr_driver->getFrameSize().Width;
m_icon_bank->setScale(screen_width > 1280 ? 0.4f : 0.25f);
} // loadedFromFile
@ -136,7 +139,9 @@ void NetworkingLobby::init()
{
Screen::init();
m_player_names.clear();
m_allow_change_team = false;
m_has_auto_start_in_server = false;
m_ping_update_timer = 0.0f;
m_cur_starting_timer = m_start_threshold = m_start_timeout =
m_server_max_player = std::numeric_limits<float>::max();
@ -210,8 +215,43 @@ void NetworkingLobby::onUpdate(float delta)
{
if (NetworkConfig::get()->isServer())
return;
if (m_timeout_message->isVisible() && m_player_list)
m_ping_update_timer += delta;
if (m_player_list && m_ping_update_timer > 2.0f)
{
m_ping_update_timer = 0.0f;
updatePlayerPings();
}
//I18N: In the networking lobby, display ping when connected
const uint32_t ping = STKHost::get()->getClientPingToServer();
if (ping != 0)
m_header->setText(_("Lobby (ping: %dms)", ping), false);
auto cl = LobbyProtocol::get<ClientLobby>();
if (cl && cl->isWaitingForGame())
{
m_start_button->setVisible(false);
m_exit_widget->setVisible(true);
m_timeout_message->setVisible(true);
//I18N: In the networking lobby, show when player is required to wait
//before the current game finish
core::stringw msg = _("Please wait for current game to be finished.");
m_timeout_message->setText(msg, true);
core::stringw total_msg;
for (auto& string : m_server_info)
{
total_msg += string;
total_msg += L"\n";
}
m_text_bubble->setText(total_msg, true);
m_cur_starting_timer = std::numeric_limits<float>::max();
return;
}
if (m_has_auto_start_in_server && m_player_list)
{
m_timeout_message->setVisible(true);
float cur_player = (float)(m_player_list->getItemCount());
if (cur_player >= m_server_max_player * m_start_threshold &&
m_cur_starting_timer == std::numeric_limits<float>::max())
@ -242,6 +282,10 @@ void NetworkingLobby::onUpdate(float delta)
m_timeout_message->setText(msg, true);
}
}
else
{
m_timeout_message->setVisible(false);
}
if (m_state == LS_ADD_PLAYERS)
{
@ -259,8 +303,7 @@ void NetworkingLobby::onUpdate(float delta)
m_start_button->setVisible(false);
m_exit_widget->setVisible(true);
auto lp = LobbyProtocol::get<LobbyProtocol>();
if (!lp || !lp->waitingForPlayers())
if (!cl || !cl->isLobbyReady())
{
core::stringw connect_msg;
if (m_joined_server)
@ -287,20 +330,10 @@ void NetworkingLobby::onUpdate(float delta)
m_text_bubble->setText(total_msg, true);
}
m_ping_update_timer += delta;
if (m_player_list && m_ping_update_timer > 2.0f)
{
m_ping_update_timer = 0.0f;
updatePlayerPings();
}
if (STKHost::get()->isAuthorisedToControl())
{
m_start_button->setVisible(true);
}
//I18N: In the networking lobby, display ping when connected
const uint32_t ping = STKHost::get()->getClientPingToServer();
if (ping != 0)
m_header->setText(_("Lobby (ping: %dms)", ping), false);
} // onUpdate
@ -508,7 +541,7 @@ void NetworkingLobby::initAutoStartTimer(bool grand_prix_started,
if (start_threshold == 0.0f || start_timeout == 0.0f)
return;
m_timeout_message->setVisible(true);
m_has_auto_start_in_server = true;
m_start_threshold = grand_prix_started ? 0.0f : start_threshold;
m_start_timeout = start_timeout;
m_server_max_player = (float)server_max_player;

View File

@ -74,7 +74,7 @@ private:
float m_cur_starting_timer, m_start_threshold, m_start_timeout,
m_server_max_player;
bool m_allow_change_team;
bool m_allow_change_team, m_has_auto_start_in_server;
GUIEngine::IconButtonWidget* m_back_widget;
GUIEngine::LabelWidget* m_header;