// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2013-2015 SuperTuxKart-Team // // This program 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. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "network/protocols/server_lobby.hpp" #include "config/player_manager.hpp" #include "config/user_config.hpp" #include "modes/world.hpp" #include "network/event.hpp" #include "network/network_config.hpp" #include "network/network_player_profile.hpp" #include "network/protocols/get_public_address.hpp" #include "network/protocols/connect_to_peer.hpp" #include "network/protocols/latency_protocol.hpp" #include "network/protocol_manager.hpp" #include "network/race_event_manager.hpp" #include "network/stk_host.hpp" #include "network/stk_peer.hpp" #include "online/online_profile.hpp" #include "online/request_manager.hpp" #include "states_screens/networking_lobby.hpp" #include "states_screens/race_result_gui.hpp" #include "states_screens/waiting_for_others.hpp" #include "utils/log.hpp" #include "utils/random_generator.hpp" #include "utils/time.hpp" /** This is the central game setup protocol running in the server. It is * mostly a finite state machine. Note that all nodes in ellipses and light * grey background are actual states; nodes in boxes and white background * are functions triggered from a state or triggering potentially a state * change. \dot digraph interaction { node [shape=box]; "Server Constructor"; "playerTrackVote"; "connectionRequested"; "signalRaceStartToClients"; "startedRaceOnClient"; "loadWorld"; node [shape=ellipse,style=filled,color=lightgrey]; "Server Constructor" -> "INIT_WAN" [label="If WAN game"] "Server Constructor" -> "ACCEPTING_CLIENTS" [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"] "SELECTING" -> "SELECTING" [label="Client selects kart, #laps, ..."] "SELECTING" -> "playerTrackVote" [label="Client selected track"] "playerTrackVote" -> "SELECTING" [label="Not all clients have selected"] "playerTrackVote" -> "LOAD_WORLD" [label="All clients have selected; signal load_world to clients"] "LOAD_WORLD" -> "loadWorld" "loadWorld" -> "WAIT_FOR_WORLD_LOADED" "WAIT_FOR_WORLD_LOADED" -> "WAIT_FOR_WORLD_LOADED" [label="Client or server loaded world"] "WAIT_FOR_WORLD_LOADED" -> "signalRaceStartToClients" [label="All clients and server ready"] "signalRaceStartToClients" -> "WAIT_FOR_RACE_STARTED" "WAIT_FOR_RACE_STARTED" -> "startedRaceOnClient" [label="Client has started race"] "startedRaceOnClient" -> "WAIT_FOR_RACE_STARTED" [label="Not all clients have started"] "startedRaceOnClient" -> "DELAY_SERVER" [label="All clients have started"] "DELAY_SERVER" -> "DELAY_SERVER" [label="Not done waiting"] "DELAY_SERVER" -> "RACING" [label="Server starts race now"] } \enddot * It starts with detecting the public ip address and port of this * host (GetPublicAddress). */ ServerLobby::ServerLobby() : LobbyProtocol(NULL) { setHandleDisconnections(true); } // ServerLobby //----------------------------------------------------------------------------- /** Destructor. */ ServerLobby::~ServerLobby() { } // ~ServerLobby //----------------------------------------------------------------------------- void ServerLobby::setup() { m_game_setup = STKHost::get()->setupNewGame(); m_game_setup->setNumLocalPlayers(0); // no local players on a server m_next_player_id.setAtomic(0); // In case of LAN we don't need our public address or register with the // STK server, so we can directly go to the accepting clients state. m_state = NetworkConfig::get()->isLAN() ? ACCEPTING_CLIENTS : INIT_WAN; m_selection_enabled = false; m_current_protocol = NULL; Log::info("ServerLobby", "Starting the protocol."); // Initialise the data structures to detect if all clients and // the server are ready: m_player_states.clear(); m_client_ready_count.setAtomic(0); m_server_has_loaded_world = false; const std::vector &players = m_game_setup->getPlayers(); for (unsigned int i = 0; i < players.size(); i++) { m_player_states[players[i]->getGlobalPlayerId()] = false; } } // setup //----------------------------------------------------------------------------- bool ServerLobby::notifyEventAsynchronous(Event* event) { assert(m_game_setup); // assert that the setup exists if (event->getType() == EVENT_TYPE_MESSAGE) { NetworkString &data = event->data(); assert(data.size()); // message not empty uint8_t message_type; message_type = data.getUInt8(); Log::info("ServerLobby", "Message received with type %d.", message_type); switch(message_type) { case LE_CONNECTION_REQUESTED: connectionRequested(event); break; case LE_REQUEST_BEGIN: startSelection(event); break; case LE_KART_SELECTION: kartSelectionRequested(event); break; case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break; case LE_STARTED_RACE: startedRaceOnClient(event); break; case LE_VOTE_MAJOR: playerMajorVote(event); break; case LE_VOTE_RACE_COUNT: playerRaceCountVote(event); break; case LE_VOTE_MINOR: playerMinorVote(event); break; case LE_VOTE_TRACK: playerTrackVote(event); break; case LE_VOTE_REVERSE: playerReversedVote(event); break; case LE_VOTE_LAPS: playerLapsVote(event); break; case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break; } // switch } // if (event->getType() == EVENT_TYPE_MESSAGE) else if (event->getType() == EVENT_TYPE_DISCONNECTED) { clientDisconnected(event); } // if (event->getType() == EVENT_TYPE_DISCONNECTED) return true; } // notifyEventAsynchronous //----------------------------------------------------------------------------- /** Simple finite state machine. First get the public ip address. Once this * is known, register the server and its address with the stk server so that * client can find it. */ void ServerLobby::update(float dt) { switch (m_state) { case INIT_WAN: // Start the protocol to find the public ip address. m_current_protocol = new GetPublicAddress(this); m_current_protocol->requestStart(); m_state = GETTING_PUBLIC_ADDRESS; // The callback from GetPublicAddress will wake this protocol up requestPause(); break; case GETTING_PUBLIC_ADDRESS: { Log::debug("ServerLobby", "Public address known."); // Free GetPublicAddress protocol delete m_current_protocol; // Register this server with the STK server. This will block // this thread, but there is no need for the protocol manager // to react to any requests before the server is registered. registerServer(); Log::info("ServerLobby", "Server registered."); m_state = ACCEPTING_CLIENTS; } break; case ACCEPTING_CLIENTS: { // Only poll the STK server if this is a WAN server. if(NetworkConfig::get()->isWAN()) checkIncomingConnectionRequests(); break; } case SELECTING: // The function playerTrackVote will trigger the next state // once all track votes have been received. break; case LOAD_WORLD: Log::info("ServerLobbyRoom", "Starting the race loading."); // This will create the world instance, i.e. load track and karts loadWorld(); m_state = WAIT_FOR_WORLD_LOADED; break; case WAIT_FOR_WORLD_LOADED: // Note that m_server_has_loaded_world is called by the main thread // (same a the thread updating this protocol) m_client_ready_count.lock(); if (m_server_has_loaded_world && m_client_ready_count.getData() == m_game_setup->getPlayerCount()) { signalRaceStartToClients(); m_server_delay = 0.02f; m_client_ready_count.getData() = 0; } m_client_ready_count.unlock(); // Initialise counter again, to wait for all clients to indicate that // they have started the race/ break; case WAIT_FOR_RACE_STARTED: // The function finishedLoadingWorldClient() will trigger the // next state. break; case DELAY_SERVER: m_server_delay -= dt; if (m_server_delay < 0) { m_state = RACING; World::getWorld()->setReadyToRace(); } break; case RACING: if (World::getWorld() && RaceEventManager::getInstance()->isRunning()) { checkRaceFinished(); } break; case RESULT_DISPLAY: if(StkTime::getRealTime() > m_timeout) { // Send a notification to all clients to exit // the race result screen NetworkString *exit_result_screen = getNetworkString(1); exit_result_screen->setSynchronous(true); exit_result_screen->addUInt8(LE_EXIT_RESULT); sendMessageToPeersChangingToken(exit_result_screen, /*reliable*/true); delete exit_result_screen; m_state = ACCEPTING_CLIENTS; RaceResultGUI::getInstance()->backToLobby(); // notify the network world that it is stopped RaceEventManager::getInstance()->stop(); // stop race protocols findAndTerminateProtocol(PROTOCOL_CONTROLLER_EVENTS); findAndTerminateProtocol(PROTOCOL_KART_UPDATE); findAndTerminateProtocol(PROTOCOL_GAME_EVENTS); } break; case DONE: m_state = EXITING; requestTerminate(); break; case EXITING: break; } } // update //----------------------------------------------------------------------------- /** Callback when the GetPublicAddress terminates. It will unpause this * protocol, which triggers the next state of the finite state machine. */ void ServerLobby::callback(Protocol *protocol) { requestUnpause(); } // callback //----------------------------------------------------------------------------- /** Register this server (i.e. its public address) with the STK server * so that clients can find it. It blocks till a response from the * stk server is received (this function is executed from the * ProtocolManager thread). The information about this client is added * to the table 'server'. */ void ServerLobby::registerServer() { Online::XMLRequest *request = new Online::XMLRequest(); const TransportAddress& addr = NetworkConfig::get()->getMyAddress(); PlayerManager::setUserDetails(request, "create", Online::API::SERVER_PATH); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort() ); request->addParameter("private_port", NetworkConfig::get()->getServerPort() ); request->addParameter("name", NetworkConfig::get()->getServerName() ); request->addParameter("max_players", UserConfigParams::m_server_max_players ); Log::info("RegisterServer", "Showing addr %s", addr.toString().c_str()); request->executeNow(); const XMLNode * result = request->getXMLData(); std::string rec_success; if (result->get("success", &rec_success) && rec_success == "yes") { Log::info("RegisterServer", "Server is now online."); STKHost::get()->setRegistered(true); } else { irr::core::stringc error(request->getInfo().c_str()); Log::error("RegisterServer", "%s", error.c_str()); STKHost::get()->setErrorMessage(_("Failed to register server: %s", error.c_str())); } } // registerServer //----------------------------------------------------------------------------- /** This function is called when all clients have loaded the world and * are therefore ready to start the race. It signals to all clients * to start the race and then switches state to DELAY_SERVER. */ void ServerLobby::signalRaceStartToClients() { const std::vector &peers = STKHost::get()->getPeers(); NetworkString *ns = getNetworkString(1); ns->addUInt8(LE_START_RACE); sendMessageToPeersChangingToken(ns, /*reliable*/true); delete ns; m_state = WAIT_FOR_RACE_STARTED; } // startGame //----------------------------------------------------------------------------- /** Instructs all clients to start the kart selection. If event is not NULL, * the command comes from a client (which needs to be authorised). */ void ServerLobby::startSelection(const Event *event) { if (m_state != ACCEPTING_CLIENTS) { Log::warn("ServerLobby", "Received startSelection while being in state %d", m_state); return; } if(event && !event->getPeer()->isAuthorised()) { Log::warn("ServerLobby", "Client %lx is not authorised to start selection.", event->getPeer()); return; } const std::vector &peers = STKHost::get()->getPeers(); NetworkString *ns = getNetworkString(1); // start selection ns->addUInt8(LE_START_SELECTION); sendMessageToPeersChangingToken(ns, /*reliable*/true); delete ns; m_selection_enabled = true; m_state = SELECTING; WaitingForOthersScreen::getInstance()->push(); Protocol *p = new LatencyProtocol(); p->requestStart(); Log::info("LobbyProtocol", "LatencyProtocol started."); } // startSelection //----------------------------------------------------------------------------- /** Query the STK server for connection requests. For each connection request * start a ConnectToPeer protocol. */ void ServerLobby::checkIncomingConnectionRequests() { // First poll every 5 seconds. Return if no polling needs to be done. const float POLL_INTERVAL = 5.0f; static double last_poll_time = 0; if (StkTime::getRealTime() < last_poll_time + POLL_INTERVAL) return; // Now poll the stk server last_poll_time = StkTime::getRealTime(); Online::XMLRequest* request = new Online::XMLRequest(); PlayerManager::setUserDetails(request, "poll-connection-requests", Online::API::SERVER_PATH); const TransportAddress &addr = NetworkConfig::get()->getMyAddress(); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort()); request->executeNow(); assert(request->isDone()); const XMLNode *result = request->getXMLData(); std::string success; if (!result->get("success", &success) || success != "yes") { Log::error("ServerLobby", "Cannot retrieve the list."); return; } // Now start a ConnectToPeer protocol for each connection request const XMLNode * users_xml = result->getNode("users"); uint32_t id = 0; for (unsigned int i = 0; i < users_xml->getNumNodes(); i++) { users_xml->getNode(i)->get("id", &id); Log::debug("ServerLobby", "User with id %d wants to connect.", id); Protocol *p = new ConnectToPeer(id); p->requestStart(); } delete request; } // checkIncomingConnectionRequests //----------------------------------------------------------------------------- /** Checks if the race is finished, and if so informs the clients and switches * to state RESULT_DISPLAY, during which the race result gui is shown and all * clients can click on 'continue'. */ void ServerLobby::checkRaceFinished() { assert(RaceEventManager::getInstance()->isRunning()); assert(World::getWorld()); if(!RaceEventManager::getInstance()->isRaceOver()) return; m_player_ready_counter = 0; // Set the delay before the server forces all clients to exit the race // result screen and go back to the lobby m_timeout = (float)(StkTime::getRealTime()+15.0f); m_state = RESULT_DISPLAY; // calculate karts ranks : int num_karts = race_manager->getNumberOfKarts(); std::vector karts_results; std::vector karts_times; for (int j = 0; j < num_karts; j++) { float kart_time = race_manager->getKartRaceTime(j); for (unsigned int i = 0; i < karts_times.size(); i++) { if (kart_time < karts_times[i]) { karts_times.insert(karts_times.begin() + i, kart_time); karts_results.insert(karts_results.begin() + i, j); break; } } } const std::vector &peers = STKHost::get()->getPeers(); NetworkString *total = getNetworkString(1 + karts_results.size()); total->setSynchronous(true); total->addUInt8(LE_RACE_FINISHED); for (unsigned int i = 0; i < karts_results.size(); i++) { total->addUInt8(karts_results[i]); // kart pos = i+1 Log::info("ServerLobby", "Kart %d finished #%d", karts_results[i], i + 1); } sendMessageToPeersChangingToken(total, /*reliable*/ true); delete total; Log::info("ServerLobby", "End of game message sent"); } // checkRaceFinished //----------------------------------------------------------------------------- /** Called when a client disconnects. * \param event The disconnect event. */ void ServerLobby::clientDisconnected(Event* event) { std::vector players_on_host = event->getPeer()->getAllPlayerProfiles(); NetworkString *msg = getNetworkString(2); msg->addUInt8(LE_PLAYER_DISCONNECTED); for(unsigned int i=0; iaddUInt8(players_on_host[i]->getGlobalPlayerId()); Log::info("ServerLobby", "Player disconnected : id %d", players_on_host[i]->getGlobalPlayerId()); m_game_setup->removePlayer(players_on_host[i]); } sendMessageToPeersChangingToken(msg, /*reliable*/true); // Remove the profile from the peer (to avoid double free) STKHost::get()->removePeer(event->getPeer()); delete msg; } // clientDisconnected //----------------------------------------------------------------------------- /*! \brief Called when a player asks for a connection. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 * --------------------- * Size | 1 |1| | * Data | 4 |n| player name | * --------------------- */ void ServerLobby::connectionRequested(Event* event) { STKPeer* peer = event->getPeer(); const NetworkString &data = event->data(); // can we add the player ? if (m_game_setup->getPlayerCount() >= NetworkConfig::get()->getMaxPlayers() || m_state!=ACCEPTING_CLIENTS ) { NetworkString *message = getNetworkString(2); // Len, error code: 2 = busy, 0 = too many players message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(m_state!=ACCEPTING_CLIENTS ? 2 : 0); // send only to the peer that made the request peer->sendPacket(message); delete message; Log::verbose("ServerLobby", "Player refused"); return; } // Connection accepted. // ==================== std::string name_u8; int len = data.decodeString(&name_u8); core::stringw name = StringUtils::utf8ToWide(name_u8); std::string password; data.decodeString(&password); bool is_authorised = (password==NetworkConfig::get()->getPassword()); // Get the unique global ID for this player. m_next_player_id.lock(); m_next_player_id.getData()++; int new_player_id = m_next_player_id.getData(); m_next_player_id.unlock(); if(m_game_setup->getLocalMasterID()==0) m_game_setup->setLocalMaster(new_player_id); // The host id has already been incremented when the peer // was added, so it is the right id now. int new_host_id = STKHost::get()->getNextHostId(); // Notify everybody that there is a new player // ------------------------------------------- NetworkString *message = getNetworkString(3+1+name_u8.size()); // size of id -- id -- size of local id -- local id; message->addUInt8(LE_NEW_PLAYER_CONNECTED).addUInt8(new_player_id) .addUInt8(new_host_id).encodeString(name_u8); STKHost::get()->sendPacketExcept(peer, message); delete message; // Now answer to the peer that just connected // ------------------------------------------ RandomGenerator token_generator; // use 4 random numbers because rand_max is probably 2^15-1. uint32_t token = (uint32_t)((token_generator.get(RAND_MAX) & 0xff) << 24 | (token_generator.get(RAND_MAX) & 0xff) << 16 | (token_generator.get(RAND_MAX) & 0xff) << 8 | (token_generator.get(RAND_MAX) & 0xff)); peer->setClientServerToken(token); peer->setAuthorised(is_authorised); peer->setHostId(new_host_id); const std::vector &players = m_game_setup->getPlayers(); // send a message to the one that asked to connect // Estimate 10 as average name length NetworkString *message_ack = getNetworkString(4 + players.size() * (2+10)); // connection success -- size of token -- token message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt8(new_player_id) .addUInt8(new_host_id).addUInt8(is_authorised); // Add all players so that this user knows (this new player is only added // to the list of players later, so the new player's info is not included) for (unsigned int i = 0; i < players.size(); i++) { message_ack->addUInt8(players[i]->getGlobalPlayerId()) .addUInt8(players[i]->getHostId()) .encodeString(players[i]->getName()); } peer->sendPacket(message_ack); delete message_ack; NetworkPlayerProfile* profile = new NetworkPlayerProfile(name, new_player_id, new_host_id); m_game_setup->addPlayer(profile); NetworkingLobby::getInstance()->addPlayer(profile); Log::verbose("ServerLobby", "New player."); } // connectionRequested //----------------------------------------------------------------------------- /*! \brief Called when a player asks to select a kart. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 2 * ---------------------------------------------- * Size | 1 | 1 | N | * Data |player id | N (kart name size) | kart name | * ---------------------------------------------- */ void ServerLobby::kartSelectionRequested(Event* event) { if(m_state!=SELECTING) { Log::warn("Server", "Received kart selection while in state %d.", m_state); return; } if (!checkDataSize(event, 1)) return; const NetworkString &data = event->data(); STKPeer* peer = event->getPeer(); uint8_t player_id = data.getUInt8(); std::string kart_name; data.decodeString(&kart_name); // check if selection is possible if (!m_selection_enabled) { NetworkString *answer = getNetworkString(2); // selection still not started answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(2); peer->sendPacket(answer); delete answer; return; } // check if somebody picked that kart if (!m_game_setup->isKartAvailable(kart_name)) { NetworkString *answer = getNetworkString(2); // kart is already taken answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(0); peer->sendPacket(answer); delete answer; return; } // check if this kart is authorized if (!m_game_setup->isKartAllowed(kart_name)) { NetworkString *answer = getNetworkString(2); // kart is not authorized answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(1); peer->sendPacket(answer); delete answer; return; } // send a kart update to everyone NetworkString *answer = getNetworkString(3+kart_name.size()); // This message must be handled synchronously on the client. answer->setSynchronous(true); // kart update (3), 1, race id answer->addUInt8(LE_KART_SELECTION_UPDATE).addUInt8(player_id) .encodeString(kart_name); sendMessageToPeersChangingToken(answer); delete answer; m_game_setup->setPlayerKart(player_id, kart_name); } // kartSelectionRequested //----------------------------------------------------------------------------- /*! \brief Called when a player votes for a major race mode. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 * ------------------------------- * Size | 1 | 4 | * Data | player-id | major mode vote | * ------------------------------- */ void ServerLobby::playerMajorVote(Event* event) { if (!checkDataSize(event, 5)) return; NetworkString &data = event->data(); uint8_t player_id = data.getUInt8(); uint32_t major = data.getUInt32(); m_game_setup->getRaceConfig()->setPlayerMajorVote(player_id, major); // Send the vote to everybody (including the sender) NetworkString *other = getNetworkString(6); other->addUInt8(LE_VOTE_MAJOR).addUInt8(player_id).addUInt32(major); sendMessageToPeersChangingToken(other); delete other; } // playerMajorVote //----------------------------------------------------------------------------- /** \brief Called when a player votes for the number of races in a GP. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 * --------------------------- * Size | 1 | 4 | * Data | player-id | races count | * --------------------------- */ void ServerLobby::playerRaceCountVote(Event* event) { if (!checkDataSize(event, 1)) return; NetworkString &data = event->data(); uint8_t player_id = data.getUInt8(); uint8_t race_count = data.getUInt8(); m_game_setup->getRaceConfig()->setPlayerRaceCountVote(player_id, race_count); // Send the vote to everybody (including the sender) NetworkString *other = getNetworkString(3); other->addUInt8(LE_VOTE_RACE_COUNT).addUInt8(player_id) .addUInt8(race_count); sendMessageToPeersChangingToken(other); delete other; } // playerRaceCountVote //----------------------------------------------------------------------------- /*! \brief Called when a player votes for a minor race mode. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 * ------------------------------- * Size | 1 | 4 | * Data | player-id | minor mode vote | * ------------------------------- */ void ServerLobby::playerMinorVote(Event* event) { if (!checkDataSize(event, 1)) return; NetworkString &data = event->data(); uint8_t player_id = data.getUInt8(); uint32_t minor = data.getUInt32(); m_game_setup->getRaceConfig()->setPlayerMinorVote(player_id, minor); // Send the vote to everybody (including the sender) NetworkString *other = getNetworkString(3); other->addUInt8(LE_VOTE_MINOR).addUInt8(player_id).addUInt8(minor); sendMessageToPeersChangingToken(other); delete other; } // playerMinorVote //----------------------------------------------------------------------------- /*! \brief Called when a player votes for a track. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 2 3 * -------------------------------------------------- * Size | 1 | 1 | 1 | N | * Data | player id | track number (gp) | N | track name | * -------------------------------------------------- */ void ServerLobby::playerTrackVote(Event* event) { if (!checkDataSize(event, 3)) return; NetworkString &data = event->data(); uint8_t player_id = data.getUInt8(); // As which track this track should be used, e.g. 1st track: Sandtrack // 2nd track Mathclass, ... uint8_t track_number = data.getUInt8(); std::string track_name; int N = data.decodeString(&track_name); m_game_setup->getRaceConfig()->setPlayerTrackVote(player_id, track_name, track_number); // Send the vote to everybody (including the sender) NetworkString *other = getNetworkString(3+1+data.size()); other->addUInt8(LE_VOTE_TRACK).addUInt8(player_id).addUInt8(track_number) .encodeString(track_name); sendMessageToPeersChangingToken(other); delete other; // Check if we received all information if (m_game_setup->getRaceConfig()->getNumTrackVotes() == m_game_setup->getPlayerCount()) { // Inform clients to start loading the world NetworkString *ns = getNetworkString(1); ns->setSynchronous(true); ns->addUInt8(LE_LOAD_WORLD); sendMessageToPeersChangingToken(ns, /*reliable*/true); delete ns; m_state = LOAD_WORLD; // Server can now load world } } // playerTrackVote //----------------------------------------------------------------------------- /*! \brief Called when a player votes for the reverse mode of a race * \param event : Event providing the information. * * Format of the data : * Byte 0 1 2 * -------------------------------------------- * Size | 1 | 1 | 1 | * Data | player id | reversed | track number (gp) | * -------------------------------------------- */ void ServerLobby::playerReversedVote(Event* event) { if (!checkDataSize(event, 3)) return; NetworkString &data = event->data(); uint8_t player_id = data.getUInt8(); uint8_t reverse = data.getUInt8(); uint8_t nb_track = data.getUInt8(); m_game_setup->getRaceConfig()->setPlayerReversedVote(player_id, reverse!=0, nb_track); // Send the vote to everybody (including the sender) NetworkString *other = getNetworkString(4); other->addUInt8(LE_VOTE_REVERSE).addUInt8(player_id).addUInt8(reverse) .addUInt8(nb_track); sendMessageToPeersChangingToken(other); delete other; } // playerReversedVote //----------------------------------------------------------------------------- /*! \brief Called when a player votes for a major race mode. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 2 * ---------------------------------------- * Size | 1 | 1 | 1 | * Data | player id | laps | track number (gp) | * ---------------------------------------- */ void ServerLobby::playerLapsVote(Event* event) { if (!checkDataSize(event, 2)) return; NetworkString &data = event->data(); uint8_t player_id = data.getUInt8(); uint8_t lap_count = data.getUInt8(); uint8_t track_nb = data.getUInt8(); m_game_setup->getRaceConfig()->setPlayerLapsVote(player_id, lap_count, track_nb); NetworkString *other = getNetworkString(4); other->addUInt8(LE_VOTE_LAPS).addUInt8(player_id).addUInt8(lap_count) .addUInt8(track_nb); sendMessageToPeersChangingToken(other); delete other; } // playerLapsVote // ---------------------------------------------------------------------------- /** Called from the RaceManager of the server when the world is loaded. Marks * the server to be ready to start the race. */ void ServerLobby::finishedLoadingWorld() { m_server_has_loaded_world = true; } // finishedLoadingWorld; //----------------------------------------------------------------------------- /** Called when a client notifies the server that it has loaded the world. * When all clients and the server are ready, the race can be started. */ void ServerLobby::finishedLoadingWorldClient(Event *event) { if (!checkDataSize(event, 1)) return; const NetworkString &data = event->data(); uint8_t player_count = data.getUInt8(); m_client_ready_count.lock(); for (unsigned int i = 0; i < player_count; i++) { uint8_t player_id = data.getUInt8(); if (m_player_states[player_id]) { Log::error("ServerLobbyProtocol", "Player %d send more than one ready message.", player_id); m_client_ready_count.unlock(); return; } m_player_states[player_id] = true; m_client_ready_count.getData()++; Log::info("ServerLobbyeProtocol", "Player %d is ready (%d/%d).", player_id, m_client_ready_count.getData(), m_game_setup->getPlayerCount()); } m_client_ready_count.unlock(); } // finishedLoadingWorldClient //----------------------------------------------------------------------------- /** Called when a notification from a client is received that the client has * started the race. Once all clients have informed the server that they * have started the race, the server can start. This makes sure that the * server's local time is behind all clients by (at least) their latency, * which in turn means that when the server simulates local time T all * messages from clients at their local time T should have arrived at * the server, which creates smoother play experience. */ void ServerLobby::startedRaceOnClient(Event *event) { m_client_ready_count.lock(); Log::verbose("ServerLobby", "Host %d has started race.", event->getPeer()->getHostId()); m_client_ready_count.getData()++; if (m_client_ready_count.getData() == m_game_setup->getPlayerCount()) { m_state = DELAY_SERVER; terminateLatencyProtocol(); } m_client_ready_count.unlock(); } // startedRaceOnClient //----------------------------------------------------------------------------- /** Called when a client clicks on 'ok' on the race result screen. * If all players have clicked on 'ok', go back to the lobby. */ void ServerLobby::playerFinishedResult(Event *event) { m_player_ready_counter++; if(m_player_ready_counter == STKHost::get()->getPeerCount()) { // We can't trigger the world/race exit here, since this is called // from the protocol manager thread. So instead we force the timeout // to get triggered (which is done from the main thread): m_timeout = 0; } } // playerFinishedResult //-----------------------------------------------------------------------------