// // 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/user_config.hpp" #include "items/network_item_manager.hpp" #include "items/powerup_manager.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/player_controller.hpp" #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/capture_the_flag.hpp" #include "modes/linear_world.hpp" #include "network/crypto.hpp" #include "network/event.hpp" #include "network/game_setup.hpp" #include "network/network_config.hpp" #include "network/network_player_profile.hpp" #include "network/peer_vote.hpp" #include "network/protocol_manager.hpp" #include "network/protocols/connect_to_peer.hpp" #include "network/protocols/game_protocol.hpp" #include "network/protocols/game_events_protocol.hpp" #include "network/race_event_manager.hpp" #include "network/server_config.hpp" #include "network/stk_host.hpp" #include "network/stk_peer.hpp" #include "online/online_profile.hpp" #include "online/request_manager.hpp" #include "race/race_manager.hpp" #include "states_screens/online/networking_lobby.hpp" #include "states_screens/race_result_gui.hpp" #include "tracks/check_manager.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/log.hpp" #include "utils/random_generator.hpp" #include "utils/time.hpp" #include #include #include /** 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" -> "WAITING_FOR_START_GAME" [label="If LAN game"] "INIT_WAN" -> "GETTING_PUBLIC_ADDRESS" [label="GetPublicAddress protocol callback"] "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"] "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) { std::vector all_k = kart_properties_manager->getKartsInGroup("standard"); std::vector all_t = track_manager->getTracksInGroup("standard"); std::vector all_arenas = track_manager->getArenasInGroup("standard", false); std::vector all_soccers = track_manager->getArenasInGroup("standard", true); all_t.insert(all_t.end(), all_arenas.begin(), all_arenas.end()); all_t.insert(all_t.end(), all_soccers.begin(), all_soccers.end()); for (int kart : all_k) { const KartProperties* kp = kart_properties_manager->getKartById(kart); if (!kp->isAddon()) m_official_kts.first.insert(kp->getIdent()); } for (int track : all_t) { Track* t = track_manager->getTrack(track); if (!t->isAddon()) m_official_kts.second.insert(t->getIdent()); } m_rs_state.store(RS_NONE); m_last_success_poll_time.store(StkTime::getRealTimeMs() + 30000); 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; m_save_server_config = true; updateBanList(); if (ServerConfig::m_ranked) { Log::info("ServerLobby", "This server will submit ranking scores to " "the STK addons server. Don't bother hosting one without the " "corresponding permissions, as they would be rejected."); } m_result_ns = getNetworkString(); m_result_ns->setSynchronous(true); m_items_complete_state = new BareNetworkString(); m_server_id_online.store(0); m_difficulty.store(ServerConfig::m_server_difficulty); m_game_mode.store(ServerConfig::m_server_mode); m_default_vote = new PeerVote(); } // ServerLobby //----------------------------------------------------------------------------- /** Destructor. */ ServerLobby::~ServerLobby() { if (NetworkConfig::get()->isNetworking() && NetworkConfig::get()->isWAN()) { unregisterServer(true/*now*/); } delete m_result_ns; delete m_items_complete_state; if (m_save_server_config) ServerConfig::writeServerConfigToDisk(); delete m_default_vote; } // ~ServerLobby //----------------------------------------------------------------------------- /** Called whenever server is reset or game mode is changed. */ void ServerLobby::updateTracksForMode() { auto all_t = track_manager->getAllTrackIdentifiers(); if (all_t.size() >= 65536) all_t.resize(65535); m_available_kts.second = { all_t.begin(), all_t.end() }; RaceManager::MinorRaceModeType m = ServerConfig::getLocalGameMode(m_game_mode.load()).first; switch (m) { case RaceManager::MINOR_MODE_NORMAL_RACE: case RaceManager::MINOR_MODE_TIME_TRIAL: case RaceManager::MINOR_MODE_FOLLOW_LEADER: { auto it = m_available_kts.second.begin(); while (it != m_available_kts.second.end()) { Track* t = track_manager->getTrack(*it); if (t->isArena() || t->isSoccer() || t->isInternal()) { it = m_available_kts.second.erase(it); } else it++; } break; } case RaceManager::MINOR_MODE_FREE_FOR_ALL: case RaceManager::MINOR_MODE_CAPTURE_THE_FLAG: { auto it = m_available_kts.second.begin(); while (it != m_available_kts.second.end()) { Track* t = track_manager->getTrack(*it); if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_CAPTURE_THE_FLAG) { if (!t->isCTF() || t->isInternal()) { it = m_available_kts.second.erase(it); } else it++; } else { if (!t->isArena() || t->isInternal()) { it = m_available_kts.second.erase(it); } else it++; } } break; } case RaceManager::MINOR_MODE_SOCCER: { auto it = m_available_kts.second.begin(); while (it != m_available_kts.second.end()) { Track* t = track_manager->getTrack(*it); if (!t->isSoccer() || t->isInternal()) { it = m_available_kts.second.erase(it); } else it++; } break; } default: assert(false); break; } } // updateTracksForMode //----------------------------------------------------------------------------- void ServerLobby::setup() { LobbyProtocol::setup(); m_battle_hit_capture_limit = 0; m_battle_time_limit = 0.0f; m_item_seed = 0; m_winner_peer_id = 0; m_client_starting_time = 0; auto players = STKHost::get()->getPlayersForNewGame(); if (m_game_setup->isGrandPrix() && !m_game_setup->isGrandPrixStarted()) { for (auto player : players) player->resetGrandPrixData(); } if (!m_game_setup->isGrandPrix() || !m_game_setup->isGrandPrixStarted()) { for (auto player : players) player->setKartName(""); } StateManager::get()->resetActivePlayers(); // We use maximum 16bit unsigned limit auto all_k = kart_properties_manager->getAllAvailableKarts(); if (all_k.size() >= 65536) all_k.resize(65535); if (ServerConfig::m_live_players) m_available_kts.first = m_official_kts.first; else m_available_kts.first = { all_k.begin(), all_k.end() }; updateTracksForMode(); m_server_has_loaded_world.store(false); // Initialise the data structures to detect if all clients and // the server are ready: resetPeersReady(); m_timeout.store(std::numeric_limits::max()); m_server_started_at = m_server_delay = 0; Log::info("ServerLobby", "Resetting the server to its initial state."); } // setup //----------------------------------------------------------------------------- bool ServerLobby::notifyEvent(Event* event) { assert(m_game_setup); // assert that the setup exists if (event->getType() != EVENT_TYPE_MESSAGE) return false; NetworkString &data = event->data(); assert(data.size()); // message not empty uint8_t message_type; message_type = data.getUInt8(); Log::info("ServerLobby", "Synchronous message of type %d received.", message_type); switch (message_type) { case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break; case LE_LIVE_JOIN: liveJoinRequest(event); break; case LE_CLIENT_LOADED_WORLD: finishedLoadingLiveJoinClient(event); break; case LE_KART_INFO: handleKartInfo(event); break; case LE_CLIENT_BACK_LOBBY: clientInGameWantsToBackLobby(event); break; default: Log::error("ServerLobby", "Unknown message of type %d - ignored.", message_type); break; } // switch message_type return true; } // notifyEvent //----------------------------------------------------------------------------- void ServerLobby::handleChat(Event* event) { if (!checkDataSize(event, 1) || !ServerConfig::m_chat) return; // Update so that the peer is not kicked event->getPeer()->updateLastActivity(); const bool sender_in_game = event->getPeer()->isWaitingForGame(); core::stringw message; event->data().decodeString16(&message); if (message.size() > 0) { NetworkString* chat = getNetworkString(); chat->setSynchronous(true); chat->addUInt8(LE_CHAT).encodeString16(message); const bool game_started = m_state.load() != WAITING_FOR_START_GAME; STKHost::get()->sendPacketToAllPeersWith( [game_started, sender_in_game](STKPeer* p) { if (!p->isValidated()) return false; if (game_started) { if (p->isWaitingForGame() && !sender_in_game) return false; if (!p->isWaitingForGame() && sender_in_game) return false; } return true; }, chat); delete chat; } } // handleChat //----------------------------------------------------------------------------- void ServerLobby::changeTeam(Event* event) { if (!ServerConfig::m_team_choosing || !race_manager->teamEnabled()) return; if (!checkDataSize(event, 1)) return; NetworkString& data = event->data(); uint8_t local_id = data.getUInt8(); auto& player = event->getPeer()->getPlayerProfiles().at(local_id); auto red_blue = STKHost::get()->getAllPlayersTeamInfo(); // At most 7 players on each team (for live join) if (player->getTeam() == KART_TEAM_BLUE) { if (red_blue.first >= 7) return; player->setTeam(KART_TEAM_RED); } else { if (red_blue.second >= 7) return; player->setTeam(KART_TEAM_BLUE); } updatePlayerList(); } // changeTeam //----------------------------------------------------------------------------- void ServerLobby::kickHost(Event* event) { if (m_server_owner.lock() != event->getPeerSP()) return; if (!checkDataSize(event, 4)) return; NetworkString& data = event->data(); uint32_t host_id = data.getUInt32(); std::shared_ptr peer = STKHost::get()->findPeerByHostId(host_id); if (peer) peer->kick(); } // kickHost //----------------------------------------------------------------------------- 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 of type %d received.", message_type); switch(message_type) { case LE_CONNECTION_REQUESTED: connectionRequested(event); break; case LE_KART_SELECTION: kartSelectionRequested(event); break; case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break; case LE_VOTE: handlePlayerVote(event); break; case LE_KICK_HOST: kickHost(event); break; case LE_CHANGE_TEAM: changeTeam(event); break; case LE_REQUEST_BEGIN: startSelection(event); break; case LE_CHAT: handleChat(event); break; case LE_CONFIG_SERVER: handleServerConfiguration(event); break; case LE_CHANGE_HANDICAP: changeHandicap(event); break; case LE_CLIENT_BACK_LOBBY: clientSelectingAssetsWantsToBackLobby(event); break; default: 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 //----------------------------------------------------------------------------- /** Create the server id file to let the graphics server client connect. */ void ServerLobby::createServerIdFile() { std::string sid = NetworkConfig::get()->getServerIdFile(); if (!sid.empty() && !m_has_created_server_id_file) { std::fstream fs; sid += StringUtils::toString(m_server_id_online.load()) + "_" + StringUtils::toString(STKHost::get()->getPrivatePort()); fs.open(sid, std::ios::out); fs.close(); m_has_created_server_id_file = true; } } // createServerIdFile //----------------------------------------------------------------------------- /** Find out the public IP server or poll STK server asynchronously. */ void ServerLobby::asynchronousUpdate() { if (m_rs_state.load() == RS_ASYNC_RESET) { resetVotingTime(); resetServer(); m_rs_state.store(RS_NONE); } // Check if server owner has left updateServerOwner(); if (ServerConfig::m_ranked && m_state.load() == WAITING_FOR_START_GAME) clearDisconnectedRankedPlayer(); if (allowJoinedPlayersWaiting() || (m_game_setup->isGrandPrix() && m_state.load() == WAITING_FOR_START_GAME)) { // Only poll the STK server if this is a WAN server. if (NetworkConfig::get()->isWAN()) checkIncomingConnectionRequests(); handlePendingConnection(); } if (NetworkConfig::get()->isWAN() && allowJoinedPlayersWaiting() && m_server_recovering.expired() && StkTime::getRealTimeMs() > m_last_success_poll_time.load() + 30000) { Log::warn("ServerLobby", "Trying auto server recovery."); registerServer(false/*now*/); } switch (m_state.load()) { case SET_PUBLIC_ADDRESS: { // 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. if (NetworkConfig::get()->isLAN()) { m_state = WAITING_FOR_START_GAME; STKHost::get()->startListening(); createServerIdFile(); return; } STKHost::get()->setPublicAddress(); if (STKHost::get()->getPublicAddress().isUnset()) { m_state = ERROR_LEAVE; } else { m_server_address = STKHost::get()->getPublicAddress(); STKHost::get()->startListening(); m_state = REGISTER_SELF_ADDRESS; } break; } case REGISTER_SELF_ADDRESS: { if (m_game_setup->isGrandPrixStarted() || m_registered_for_once_only) { m_state = WAITING_FOR_START_GAME; break; } // Register this server with the STK server. This will block // this thread, because there is no need for the protocol manager // to react to any requests before the server is registered. if (registerServer(true/*now*/)) { if (allowJoinedPlayersWaiting()) m_registered_for_once_only = true; m_state = WAITING_FOR_START_GAME; createServerIdFile(); } else { m_state = ERROR_LEAVE; } break; } case WAITING_FOR_START_GAME: { if (ServerConfig::m_owner_less) { unsigned players = 0; STKHost::get()->updatePlayers(&players); if (((int)players >= ServerConfig::m_min_start_game_players || m_game_setup->isGrandPrixStarted()) && m_timeout.load() == std::numeric_limits::max()) { m_timeout.store((int64_t)StkTime::getRealTimeMs() + (int64_t) (ServerConfig::m_start_game_counter * 1000.0f)); } else if ((int)players < ServerConfig::m_min_start_game_players && !m_game_setup->isGrandPrixStarted()) { resetPeersReady(); if (m_timeout.load() != std::numeric_limits::max()) updatePlayerList(); m_timeout.store(std::numeric_limits::max()); } if (m_timeout.load() < (int64_t)StkTime::getRealTimeMs() || (checkPeersReady() && (int)players >= ServerConfig::m_min_start_game_players)) { resetPeersReady(); startSelection(); return; } } break; } case ERROR_LEAVE: { requestTerminate(); m_state = EXITING; STKHost::get()->requestShutdown(); break; } case WAIT_FOR_WORLD_LOADED: { // For WAIT_FOR_WORLD_LOADED and SELECTING make sure there are enough // players to start next game, otherwise exiting and let main thread // reset if (m_end_voting_period.load() == 0) return; unsigned player_in_game = 0; STKHost::get()->updatePlayers(&player_in_game); // Reset lobby will be done in main thread if ((player_in_game == 1 && ServerConfig::m_ranked) || player_in_game == 0) { resetVotingTime(); return; } // m_server_has_loaded_world is set by main thread with atomic write if (m_server_has_loaded_world.load() == false) return; if (!checkPeersReady()) return; // Reset for next state usage resetPeersReady(); configPeersStartTime(); break; } case SELECTING: { if (m_end_voting_period.load() == 0) return; unsigned player_in_game = 0; STKHost::get()->updatePlayers(&player_in_game); if ((player_in_game == 1 && ServerConfig::m_ranked) || player_in_game == 0) { resetVotingTime(); return; } PeerVote winner_vote; m_winner_peer_id = std::numeric_limits::max(); bool go_on_race = false; if (ServerConfig::m_track_voting) go_on_race = handleAllVotes(&winner_vote, &m_winner_peer_id); else if (m_game_setup->isGrandPrixStarted() || isVotingOver()) { winner_vote = *m_default_vote; go_on_race = true; } if (go_on_race) { *m_default_vote = winner_vote; m_item_seed = (uint32_t)StkTime::getTimeSinceEpoch(); ItemManager::updateRandomSeed(m_item_seed); m_game_setup->setRace(winner_vote); auto players = STKHost::get()->getPlayersForNewGame(); m_game_setup->sortPlayersForGrandPrix(players); m_game_setup->sortPlayersForGame(players); for (unsigned i = 0; i < players.size(); i++) { std::shared_ptr& player = players[i]; std::shared_ptr peer = player->getPeer(); if (peer) peer->clearAvailableKartIDs(); } for (unsigned i = 0; i < players.size(); i++) { std::shared_ptr& player = players[i]; std::shared_ptr peer = player->getPeer(); if (peer) peer->addAvailableKartID(i); } getHitCaptureLimit(); // Add placeholder players for live join addLiveJoinPlaceholder(players); // If player chose random / hasn't chose any kart for (unsigned i = 0; i < players.size(); i++) { if (players[i]->getKartName().empty()) { RandomGenerator rg; std::set::iterator it = m_available_kts.first.begin(); std::advance(it, rg.get((int)m_available_kts.first.size())); players[i]->setKartName(*it); } } NetworkString* load_world_message = getLoadWorldMessage(players, false/*live_join*/); m_game_setup->setHitCaptureTime(m_battle_hit_capture_limit, m_battle_time_limit); uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks( ServerConfig::m_flag_return_timeout); race_manager->setFlagReturnTicks(flag_return_time); uint16_t flag_deactivated_time = (uint16_t)stk_config->time2Ticks( ServerConfig::m_flag_deactivated_time); race_manager->setFlagDeactivatedTicks(flag_deactivated_time); configRemoteKart(players, 0); // Reset for next state usage resetPeersReady(); m_state = LOAD_WORLD; sendMessageToPeers(load_world_message); delete load_world_message; } break; } default: break; } } // asynchronousUpdate //----------------------------------------------------------------------------- void ServerLobby::encodePlayers(BareNetworkString* bns, std::vector >& players) const { bns->addUInt8((uint8_t)players.size()); for (unsigned i = 0; i < players.size(); i++) { std::shared_ptr& player = players[i]; bns->encodeString(player->getName()) .addUInt32(player->getHostId()) .addFloat(player->getDefaultKartColor()) .addUInt32(player->getOnlineId()) .addUInt8(player->getPerPlayerDifficulty()) .addUInt8(player->getLocalPlayerId()) .addUInt8( race_manager->teamEnabled() ? player->getTeam() : KART_TEAM_NONE) .encodeString(player->getCountryId()); bns->encodeString(player->getKartName()); } } // encodePlayers //----------------------------------------------------------------------------- NetworkString* ServerLobby::getLoadWorldMessage( std::vector >& players, bool live_join) const { NetworkString* load_world_message = getNetworkString(); load_world_message->setSynchronous(true); load_world_message->addUInt8(LE_LOAD_WORLD); load_world_message->addUInt32(m_winner_peer_id); m_default_vote->encode(load_world_message); load_world_message->addUInt8(live_join ? 1 : 0); encodePlayers(load_world_message, players); load_world_message->addUInt32(m_item_seed); if (race_manager->isBattleMode()) { load_world_message->addUInt32(m_battle_hit_capture_limit) .addFloat(m_battle_time_limit); uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks( ServerConfig::m_flag_return_timeout); load_world_message->addUInt16(flag_return_time); uint16_t flag_deactivated_time = (uint16_t)stk_config->time2Ticks( ServerConfig::m_flag_deactivated_time); load_world_message->addUInt16(flag_deactivated_time); } return load_world_message; } // getLoadWorldMessage //----------------------------------------------------------------------------- /** Returns true if server can be live joined or spectating */ bool ServerLobby::canLiveJoinNow() const { bool live_join = ServerConfig::m_live_players && worldIsActive(); if (!live_join) return false; if (race_manager->modeHasLaps()) { // No spectate when fastest kart is nearly finish, because if there // is endcontroller the spectating remote may not be knowing this // on time LinearWorld* w = dynamic_cast(World::getWorld()); if (!w) return false; AbstractKart* fastest_kart = NULL; for (unsigned i = 0; i < w->getNumKarts(); i++) { fastest_kart = w->getKartAtPosition(i + 1); if (fastest_kart && !fastest_kart->isEliminated()) break; } if (!fastest_kart) return false; float progress = w->getOverallDistance( fastest_kart->getWorldKartId()) / (Track::getCurrentTrack()->getTrackLength() * (float)race_manager->getNumLaps()); if (progress > 0.9f) return false; } return live_join; } // canLiveJoinNow //----------------------------------------------------------------------------- /** Returns true if world is active for clients to live join, spectate or * going back to lobby live */ bool ServerLobby::worldIsActive() const { return World::getWorld() && RaceEventManager::getInstance()->isRunning() && !RaceEventManager::getInstance()->isRaceOver() && World::getWorld()->getPhase() == WorldStatus::RACE_PHASE; } // worldIsActive //----------------------------------------------------------------------------- /** \ref STKPeer peer will be reset back to the lobby with reason * \ref BackLobbyReason blr */ void ServerLobby::rejectLiveJoin(STKPeer* peer, BackLobbyReason blr) { NetworkString* reset = getNetworkString(2); reset->setSynchronous(true); reset->addUInt8(LE_BACK_LOBBY).addUInt8(blr); peer->sendPacket(reset, /*reliable*/true); delete reset; updatePlayerList(); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); m_game_setup->addServerInfo(server_info); peer->sendPacket(server_info, /*reliable*/true); delete server_info; } // rejectLiveJoin //----------------------------------------------------------------------------- /** This message is like kartSelectionRequested, but it will send the peer * load world message if he can join the current started game. */ void ServerLobby::liveJoinRequest(Event* event) { STKPeer* peer = event->getPeer(); const NetworkString& data = event->data(); if (!canLiveJoinNow()) { rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); return; } bool spectator = data.getUInt8() == 1; if (race_manager->modeHasLaps() && !spectator) { // No live join for linear race rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); return; } peer->clearAvailableKartIDs(); if (!spectator) { setPlayerKarts(data, peer); std::vector used_id; for (unsigned i = 0; i < peer->getPlayerProfiles().size(); i++) { int id = getReservedId(peer->getPlayerProfiles()[i], i); if (id == -1) break; used_id.push_back(id); } if (used_id.size() != peer->getPlayerProfiles().size()) { for (unsigned i = 0; i < peer->getPlayerProfiles().size(); i++) peer->getPlayerProfiles()[i]->setKartName(""); for (unsigned i = 0; i < used_id.size(); i++) { RemoteKartInfo& rki = race_manager->getKartInfo(used_id[i]); rki.makeReserved(); } Log::info("ServerLobby", "Too many players (%d) try to live join", (int)peer->getPlayerProfiles().size()); rejectLiveJoin(peer, BLR_NO_PLACE_FOR_LIVE_JOIN); return; } for (int id : used_id) { Log::info("ServerLobby", "%s live joining with reserved kart id %d.", peer->getAddress().toString().c_str(), id); peer->addAvailableKartID(id); } } else { Log::info("ServerLobby", "%s spectating now.", peer->getAddress().toString().c_str()); } std::vector > players = getLivePlayers(); NetworkString* load_world_message = getLoadWorldMessage(players, true/*live_join*/); peer->sendPacket(load_world_message, true/*reliable*/); delete load_world_message; peer->updateLastActivity(); } // liveJoinRequest //----------------------------------------------------------------------------- /** Get a list of current ingame players for live join or spectate. */ std::vector > ServerLobby::getLivePlayers() const { std::vector > players; for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { const RemoteKartInfo& rki = race_manager->getKartInfo(i); std::shared_ptr player = rki.getNetworkPlayerProfile().lock(); if (!player) { if (race_manager->modeHasLaps()) { player = std::make_shared( nullptr, rki.getPlayerName(), std::numeric_limits::max(), rki.getDefaultKartColor(), rki.getOnlineId(), rki.getDifficulty(), rki.getLocalPlayerId(), KART_TEAM_NONE, rki.getCountryId()); player->setKartName(rki.getKartName()); } else { player = NetworkPlayerProfile::getReservedProfile( race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL ? KART_TEAM_NONE : rki.getKartTeam()); } } players.push_back(player); } return players; } // getLivePlayers //----------------------------------------------------------------------------- /** Decide where to put the live join player depends on his team and game mode. */ int ServerLobby::getReservedId(std::shared_ptr& p, unsigned local_id) const { const bool is_ffa = race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL; int red_count = 0; int blue_count = 0; for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { RemoteKartInfo& rki = race_manager->getKartInfo(i); if (rki.isReserved()) continue; bool disconnected = rki.disconnected(); if (race_manager->getKartInfo(i).getKartTeam() == KART_TEAM_RED && !disconnected) red_count++; else if (race_manager->getKartInfo(i).getKartTeam() == KART_TEAM_BLUE && !disconnected) blue_count++; } KartTeam target_team = red_count > blue_count ? KART_TEAM_BLUE : KART_TEAM_RED; for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { RemoteKartInfo& rki = race_manager->getKartInfo(i); std::shared_ptr player = rki.getNetworkPlayerProfile().lock(); if (!player) { if (is_ffa) { rki.copyFrom(p, local_id); return i; } if (ServerConfig::m_team_choosing) { if ((p->getTeam() == KART_TEAM_RED && rki.getKartTeam() == KART_TEAM_RED) || (p->getTeam() == KART_TEAM_BLUE && rki.getKartTeam() == KART_TEAM_BLUE)) { rki.copyFrom(p, local_id); return i; } } else { if (rki.getKartTeam() == target_team) { p->setTeam(target_team); rki.copyFrom(p, local_id); return i; } } } } return -1; } // getReservedId //----------------------------------------------------------------------------- /** Finally put the kart in the world and inform client the current world * status, (including current confirmed item state, kart scores...) */ void ServerLobby::finishedLoadingLiveJoinClient(Event* event) { std::shared_ptr peer = event->getPeerSP(); if (!canLiveJoinNow()) { rejectLiveJoin(peer.get(), BLR_NO_GAME_FOR_LIVE_JOIN); return; } bool live_joined_in_time = true; for (const int id : peer->getAvailableKartIDs()) { const RemoteKartInfo& rki = race_manager->getKartInfo(id); if (rki.isReserved()) { live_joined_in_time = false; break; } } if (!live_joined_in_time) { Log::warn("ServerLobby", "%s can't live-join in time.", peer->getAddress().toString().c_str()); rejectLiveJoin(peer.get(), BLR_NO_GAME_FOR_LIVE_JOIN); return; } World* w = World::getWorld(); assert(w); // Give 3 seconds for all peers to get new kart info m_last_live_join_util_ticks = w->getTicksSinceStart() + stk_config->time2Ticks(3.0f); uint64_t live_join_start_time = STKHost::get()->getNetworkTimer(); live_join_start_time -= m_server_delay; live_join_start_time += 3000; bool spectator = false; for (const int id : peer->getAvailableKartIDs()) { World::getWorld()->addReservedKart(id); const RemoteKartInfo& rki = race_manager->getKartInfo(id); addLiveJoiningKart(id, rki, m_last_live_join_util_ticks); Log::info("ServerLobby", "%s succeeded live-joining with kart id %d.", peer->getAddress().toString().c_str(), id); } if (peer->getAvailableKartIDs().empty()) { Log::info("ServerLobby", "%s spectating succeeded.", peer->getAddress().toString().c_str()); spectator = true; } const uint8_t cc = (uint8_t)CheckManager::get()->getCheckStructureCount(); NetworkString* ns = getNetworkString(10); ns->setSynchronous(true); ns->addUInt8(LE_LIVE_JOIN_ACK).addUInt64(m_client_starting_time) .addUInt8(cc).addUInt64(live_join_start_time) .addUInt32(m_last_live_join_util_ticks); NetworkItemManager* nim = dynamic_cast(ItemManager::get()); assert(nim); nim->saveCompleteState(ns); nim->addLiveJoinPeer(peer); w->saveCompleteState(ns); if (race_manager->supportsLiveJoining()) { // Only needed in non-racing mode as no need players can added after // starting of race std::vector > players = getLivePlayers(); encodePlayers(ns, players); } m_peers_ready[peer] = false; peer->setWaitingForGame(false); peer->setSpectator(spectator); peer->sendPacket(ns, true/*reliable*/); delete ns; updatePlayerList(); peer->updateLastActivity(); } // finishedLoadingLiveJoinClient //----------------------------------------------------------------------------- /** Simple finite state machine. Once this * is known, register the server and its address with the stk server so that * client can find it. */ void ServerLobby::update(int ticks) { World* w = World::getWorld(); bool world_started = m_state.load() >= WAIT_FOR_WORLD_LOADED && m_state.load() <= RACING && m_server_has_loaded_world.load(); bool all_players_in_world_disconnected = (w != NULL && world_started); int sec = ServerConfig::m_kick_idle_player_seconds; if (world_started) { for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { RemoteKartInfo& rki = race_manager->getKartInfo(i); std::shared_ptr player = rki.getNetworkPlayerProfile().lock(); if (player) { if (w) all_players_in_world_disconnected = false; } else continue; auto peer = player->getPeer(); if (!peer) continue; if (peer->idleForSeconds() > 60 && w && w->getKart(i)->isEliminated()) { // Remove loading world too long (60 seconds) live join peer Log::info("ServerLobby", "%s hasn't live-joined within" " 60 seconds, remove it.", peer->getAddress().toString().c_str()); rki.makeReserved(); continue; } if (sec > 0 && peer->idleForSeconds() > sec && !peer->isDisconnected() && NetworkConfig::get()->isWAN()) { if (w && w->getKart(i)->hasFinishedRace()) continue; Log::info("ServerLobby", "%s %s has been idle for more than" " %d seconds, kick.", peer->getAddress().toString().c_str(), StringUtils::wideToUtf8(rki.getPlayerName()).c_str(), sec); peer->kick(); } } } if (w) setGameStartedProgress(w->getGameStartedProgress()); else resetGameStartedProgress(); if (w && w->getPhase() == World::RACE_PHASE) { storePlayingTrack(track_manager->getTrackIndexByIdent( race_manager->getTrackName())); } else storePlayingTrack(-1); // Reset server to initial state if no more connected players if (m_rs_state.load() == RS_WAITING) { if ((RaceEventManager::getInstance() && !RaceEventManager::getInstance()->protocolStopped()) || !GameProtocol::emptyInstance()) return; RaceResultGUI::getInstance()->backToLobby(); m_rs_state.store(RS_ASYNC_RESET); } STKHost::get()->updatePlayers(); if (m_rs_state.load() == RS_NONE && (m_state.load() > WAITING_FOR_START_GAME || m_game_setup->isGrandPrixStarted()) && (STKHost::get()->getPlayersInGame() == 0 || all_players_in_world_disconnected)) { if (RaceEventManager::getInstance() && RaceEventManager::getInstance()->isRunning()) { // Send a notification to all players who may have start live join // or spectate to go back to lobby NetworkString* back_to_lobby = getNetworkString(2); back_to_lobby->setSynchronous(true); back_to_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); sendMessageToPeersInServer(back_to_lobby, /*reliable*/true); delete back_to_lobby; RaceEventManager::getInstance()->stop(); RaceEventManager::getInstance()->getProtocol()->requestTerminate(); GameProtocol::lock()->requestTerminate(); } resetVotingTime(); m_game_setup->stopGrandPrix(); m_rs_state.store(RS_WAITING); return; } if (m_rs_state.load() != RS_NONE) return; // Reset for ranked server if in kart / track selection has only 1 player if (ServerConfig::m_ranked && m_state.load() == SELECTING && STKHost::get()->getPlayersInGame() == 1) { NetworkString* back_lobby = getNetworkString(2); back_lobby->setSynchronous(true); back_lobby->addUInt8(LE_BACK_LOBBY) .addUInt8(BLR_ONE_PLAYER_IN_RANKED_MATCH); sendMessageToPeers(back_lobby, /*reliable*/true); delete back_lobby; resetVotingTime(); m_game_setup->stopGrandPrix(); m_rs_state.store(RS_ASYNC_RESET); } handlePlayerDisconnection(); switch (m_state.load()) { case SET_PUBLIC_ADDRESS: case REGISTER_SELF_ADDRESS: case WAITING_FOR_START_GAME: case WAIT_FOR_WORLD_LOADED: case WAIT_FOR_RACE_STARTED: { // Waiting for asynchronousUpdate 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 RACING: if (World::getWorld() && RaceEventManager::getInstance()->isRunning()) { checkRaceFinished(); } break; case WAIT_FOR_RACE_STOPPED: if (!RaceEventManager::getInstance()->protocolStopped() || !GameProtocol::emptyInstance()) return; // This will go back to lobby in server (and exit the current race) RaceResultGUI::getInstance()->backToLobby(); // Reset for next state usage resetPeersReady(); // Set the delay before the server forces all clients to exit the race // result screen and go back to the lobby m_timeout.store((int64_t)StkTime::getRealTimeMs() + 15000); m_state = RESULT_DISPLAY; sendMessageToPeers(m_result_ns, /*reliable*/ true); Log::info("ServerLobby", "End of game message sent"); break; case RESULT_DISPLAY: if (checkPeersReady() || (int64_t)StkTime::getRealTimeMs() > m_timeout.load()) { // Send a notification to all clients to exit // the race result screen NetworkString* back_to_lobby = getNetworkString(2); back_to_lobby->setSynchronous(true); back_to_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); sendMessageToPeersInServer(back_to_lobby, /*reliable*/true); delete back_to_lobby; m_rs_state.store(RS_ASYNC_RESET); } break; case ERROR_LEAVE: case EXITING: break; } } // update //----------------------------------------------------------------------------- /** 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'. */ bool ServerLobby::registerServer(bool now) { while (now && !m_server_unregistered.expired()) StkTime::sleep(1); // ======================================================================== class RegisterServerRequest : public Online::XMLRequest { private: std::weak_ptr m_server_lobby; protected: virtual void afterOperation() { Online::XMLRequest::afterOperation(); const XMLNode* result = getXMLData(); std::string rec_success; auto sl = m_server_lobby.lock(); if (!sl) return; if (result->get("success", &rec_success) && rec_success == "yes") { const XMLNode* server = result->getNode("server"); assert(server); const XMLNode* server_info = server->getNode("server-info"); assert(server_info); unsigned server_id_online = 0; server_info->get("id", &server_id_online); assert(server_id_online != 0); bool is_official = false; server_info->get("official", &is_official); if (!is_official && ServerConfig::m_ranked) { Log::fatal("ServerLobby", "You don't have permission to " "host a ranked server."); } Log::info("ServerLobby", "Server %d is now online.", server_id_online); sl->m_server_id_online.store(server_id_online); sl->m_last_success_poll_time.store(StkTime::getRealTimeMs()); return; } Log::error("ServerLobby", "%s", StringUtils::wideToUtf8(getInfo()).c_str()); // For auto server recovery wait 3 seconds for next try // This sleep only the request manager thread if (manageMemory()) StkTime::sleep(3000); } public: RegisterServerRequest(bool now, std::shared_ptr sl) : XMLRequest(!now/*manage memory*/), m_server_lobby(sl) {} }; // RegisterServerRequest RegisterServerRequest *request = new RegisterServerRequest(now, std::dynamic_pointer_cast(shared_from_this())); NetworkConfig::get()->setServerDetails(request, "create"); request->addParameter("address", m_server_address.getIP() ); request->addParameter("port", m_server_address.getPort() ); request->addParameter("private_port", STKHost::get()->getPrivatePort() ); request->addParameter("name", m_game_setup->getServerNameUtf8()); request->addParameter("max_players", ServerConfig::m_server_max_players); int difficulty = m_difficulty.load(); request->addParameter("difficulty", difficulty); int game_mode = m_game_mode.load(); request->addParameter("game_mode", game_mode); const std::string& pw = ServerConfig::m_private_server_password; request->addParameter("password", (unsigned)(!pw.empty())); request->addParameter("version", (unsigned)ServerConfig::m_server_version); Log::info("ServerLobby", "Public server address %s", m_server_address.toString().c_str()); if (now) { request->executeNow(); delete request; if (m_server_id_online.load() == 0) return false; } else { request->queue(); m_server_recovering = request->observeExistence(); } return true; } // registerServer //----------------------------------------------------------------------------- /** Unregister this server (i.e. its public address) with the STK server, * currently when karts enter kart selection screen it will be done or quit * stk. */ void ServerLobby::unregisterServer(bool now) { Online::XMLRequest* request = new Online::XMLRequest(!now/*manage memory*/); m_server_unregistered = request->observeExistence(); NetworkConfig::get()->setServerDetails(request, "stop"); request->addParameter("address", m_server_address.getIP()); request->addParameter("port", m_server_address.getPort()); Log::info("ServerLobby", "Unregister server address %s", m_server_address.toString().c_str()); // No need to check for result as server will be auto-cleared anyway // when no polling is done if (now) { request->executeNow(); delete request; } else request->queue(); } // unregisterServer //----------------------------------------------------------------------------- /** Instructs all clients to start the kart selection. If event is NULL, * the command comes from the owner less server. */ void ServerLobby::startSelection(const Event *event) { if (event != NULL) { if (m_state != WAITING_FOR_START_GAME) { Log::warn("ServerLobby", "Received startSelection while being in state %d.", m_state.load()); return; } if (ServerConfig::m_owner_less) { m_peers_ready.at(event->getPeerSP()) = !m_peers_ready.at(event->getPeerSP()); updatePlayerList(); return; } if (event->getPeerSP() != m_server_owner.lock()) { Log::warn("ServerLobby", "Client %d is not authorised to start selection.", event->getPeer()->getHostId()); return; } } if (!ServerConfig::m_owner_less && ServerConfig::m_team_choosing && race_manager->teamEnabled()) { auto red_blue = STKHost::get()->getAllPlayersTeamInfo(); if ((red_blue.first == 0 || red_blue.second == 0) && red_blue.first + red_blue.second != 1) { Log::warn("ServerLobby", "Bad team choosing."); if (event) { NetworkString* bt = getNetworkString(); bt->setSynchronous(true); bt->addUInt8(LE_BAD_TEAM); event->getPeer()->sendPacket(bt, true/*reliable*/); delete bt; } return; } } // Remove karts / tracks from server that are not supported on all clients std::set karts_erase, tracks_erase; auto peers = STKHost::get()->getPeers(); for (auto peer : peers) { if (!peer->isValidated() || peer->isWaitingForGame()) continue; peer->eraseServerKarts(m_available_kts.first, karts_erase); peer->eraseServerTracks(m_available_kts.second, tracks_erase); } for (const std::string& kart_erase : karts_erase) { m_available_kts.first.erase(kart_erase); } for (const std::string& track_erase : tracks_erase) { m_available_kts.second.erase(track_erase); } if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL) { unsigned max_player = 0; STKHost::get()->updatePlayers(&max_player); auto it = m_available_kts.second.begin(); while (it != m_available_kts.second.end()) { Track* t = track_manager->getTrack(*it); if (t->getMaxArenaPlayers() < max_player) { it = m_available_kts.second.erase(it); } else it++; } } // Default vote use only official tracks to prevent network AI cannot // finish some bad wip / addons tracks std::set official_tracks = m_official_kts.second; std::set::iterator it = official_tracks.begin(); while (it != official_tracks.end()) { if (m_available_kts.second.find(*it) == m_available_kts.second.end()) { it = official_tracks.erase(it); } else it++; } if (official_tracks.empty()) { Log::error("ServerLobby", "No official tracks for playing!"); return; } RandomGenerator rg; it = official_tracks.begin(); std::advance(it, rg.get((int)official_tracks.size())); m_default_vote->m_track_name = *it; switch (race_manager->getMinorMode()) { case RaceManager::MINOR_MODE_NORMAL_RACE: case RaceManager::MINOR_MODE_TIME_TRIAL: case RaceManager::MINOR_MODE_FOLLOW_LEADER: { Track* t = track_manager->getTrack(*it); assert(t); m_default_vote->m_num_laps = t->getDefaultNumberOfLaps(); m_default_vote->m_reverse = rg.get(2) == 0; break; } case RaceManager::MINOR_MODE_FREE_FOR_ALL: { m_default_vote->m_num_laps = 0; m_default_vote->m_reverse = rg.get(2) == 0; break; } case RaceManager::MINOR_MODE_CAPTURE_THE_FLAG: { m_default_vote->m_num_laps = 0; m_default_vote->m_reverse = 0; break; } case RaceManager::MINOR_MODE_SOCCER: { if (m_game_setup->isSoccerGoalTarget()) { m_default_vote->m_num_laps = (uint8_t)(UserConfigParams::m_num_goals); if (m_default_vote->m_num_laps > 10) m_default_vote->m_num_laps = (uint8_t)5; } else { m_default_vote->m_num_laps = (uint8_t)(UserConfigParams::m_soccer_time_limit); if (m_default_vote->m_num_laps > 15) m_default_vote->m_num_laps = (uint8_t)7; } m_default_vote->m_reverse = rg.get(2) == 0; break; } default: assert(false); break; } if (!allowJoinedPlayersWaiting()) { ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION); if (NetworkConfig::get()->isWAN()) { unregisterServer(false/*now*/); } } startVotingPeriod(ServerConfig::m_voting_timeout); NetworkString *ns = getNetworkString(1); // Start selection - must be synchronous since the receiver pushes // a new screen, which must be done from the main thread. ns->setSynchronous(true); ns->addUInt8(LE_START_SELECTION) .addFloat(ServerConfig::m_voting_timeout) .addUInt8(m_game_setup->isGrandPrixStarted() ? 1 : 0) .addUInt8(ServerConfig::m_auto_game_time_ratio > 0.0f ? 1 : 0) .addUInt8(ServerConfig::m_track_voting ? 1 : 0); const auto& all_k = m_available_kts.first; const auto& all_t = m_available_kts.second; ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size()); for (const std::string& kart : all_k) { ns->encodeString(kart); } for (const std::string& track : all_t) { ns->encodeString(track); } sendMessageToPeers(ns, /*reliable*/true); delete ns; m_state = SELECTING; if (!allowJoinedPlayersWaiting()) { // 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 ul(m_keys_mutex); m_keys.clear(); ul.unlock(); } // Will be changed after the first vote received m_timeout.store(std::numeric_limits::max()); } // 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 uint64_t POLL_INTERVAL = 5000; static uint64_t last_poll_time = 0; if (StkTime::getRealTimeMs() < last_poll_time + POLL_INTERVAL || StkTime::getRealTimeMs() > m_last_success_poll_time.load() + 30000 || m_server_id_online.load() == 0) return; // Keep the port open, it can be sent to anywhere as we will send to the // correct peer later in ConnectToPeer. if (ServerConfig::m_firewalled_server) { BareNetworkString data; data.addUInt8(0); STKHost::get()->sendRawPacket(data, STKHost::get()->getStunAddress()); } // Now poll the stk server last_poll_time = StkTime::getRealTimeMs(); // ======================================================================== class PollServerRequest : public Online::XMLRequest { private: std::weak_ptr m_server_lobby; protected: virtual void afterOperation() { Online::XMLRequest::afterOperation(); const XMLNode* result = getXMLData(); std::string success; if (!result->get("success", &success) || success != "yes") { Log::error("ServerLobby", "Poll server request failed: %s", StringUtils::wideToUtf8(getInfo()).c_str()); return; } // Now start a ConnectToPeer protocol for each connection request const XMLNode * users_xml = result->getNode("users"); std::map keys; auto sl = m_server_lobby.lock(); if (!sl) return; sl->m_last_success_poll_time.store(StkTime::getRealTimeMs()); if (sl->m_state.load() != WAITING_FOR_START_GAME && !sl->allowJoinedPlayersWaiting()) { sl->replaceKeys(keys); return; } sl->removeExpiredPeerConnection(); for (unsigned int i = 0; i < users_xml->getNumNodes(); i++) { uint32_t addr, id; uint16_t port; users_xml->getNode(i)->get("ip", &addr); users_xml->getNode(i)->get("port", &port); users_xml->getNode(i)->get("id", &id); users_xml->getNode(i)->get("aes-key", &keys[id].m_aes_key); users_xml->getNode(i)->get("aes-iv", &keys[id].m_aes_iv); users_xml->getNode(i)->get("username", &keys[id].m_name); keys[id].m_tried = false; if (ServerConfig::m_firewalled_server) { TransportAddress peer_addr(addr, port); std::string peer_addr_str = peer_addr.toString(); if (sl->m_pending_peer_connection.find(peer_addr_str) != sl->m_pending_peer_connection.end()) { continue; } std::make_shared(peer_addr)->requestStart(); sl->addPeerConnection(peer_addr_str); } } sl->replaceKeys(keys); } public: PollServerRequest(std::shared_ptr sl) : XMLRequest(true), m_server_lobby(sl) { m_disable_sending_log = true; } }; // PollServerRequest // ======================================================================== PollServerRequest* request = new PollServerRequest( std::dynamic_pointer_cast(shared_from_this())); NetworkConfig::get()->setServerDetails(request, "poll-connection-requests"); const TransportAddress &addr = STKHost::get()->getPublicAddress(); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort()); request->addParameter("current-players", STKHost::get()->getTotalPlayers()); request->addParameter("game-started", m_state.load() == WAITING_FOR_START_GAME ? 0 : 1); Track* current_track = getPlayingTrack(); if (current_track) request->addParameter("current-track", current_track->getIdent()); request->queue(); } // 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; Log::info("ServerLobby", "The game is considered finished."); // notify the network world that it is stopped RaceEventManager::getInstance()->stop(); // stop race protocols before going back to lobby (end race) RaceEventManager::getInstance()->getProtocol()->requestTerminate(); GameProtocol::lock()->requestTerminate(); // Save race result before delete the world m_result_ns->clear(); m_result_ns->addUInt8(LE_RACE_FINISHED); if (m_game_setup->isGrandPrix()) { // fastest lap int fastest_lap = static_cast(World::getWorld())->getFastestLapTicks(); m_result_ns->addUInt32(fastest_lap); m_result_ns->encodeString(static_cast(World::getWorld()) ->getFastestLapKartName()); // all gp tracks m_result_ns->addUInt8((uint8_t)m_game_setup->getTotalGrandPrixTracks()) .addUInt8((uint8_t)m_game_setup->getAllTracks().size()); for (const std::string& gp_track : m_game_setup->getAllTracks()) m_result_ns->encodeString(gp_track); // each kart score and total time m_result_ns->addUInt8((uint8_t)race_manager->getNumPlayers()); for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { int last_score = race_manager->getKartScore(i); int cur_score = last_score; float overall_time = race_manager->getOverallTime(i); if (auto player = race_manager->getKartInfo(i).getNetworkPlayerProfile().lock()) { last_score = player->getScore(); cur_score += last_score; overall_time = overall_time + player->getOverallTime(); player->setScore(cur_score); player->setOverallTime(overall_time); } m_result_ns->addUInt32(last_score).addUInt32(cur_score) .addFloat(overall_time); } } else if (race_manager->modeHasLaps()) { int fastest_lap = static_cast(World::getWorld())->getFastestLapTicks(); m_result_ns->addUInt32(fastest_lap); m_result_ns->encodeString(static_cast(World::getWorld()) ->getFastestLapKartName()); } if (ServerConfig::m_ranked) { computeNewRankings(); submitRankingsToAddons(); } m_state.store(WAIT_FOR_RACE_STOPPED); } // checkRaceFinished //----------------------------------------------------------------------------- /** Compute the new player's rankings used in ranked servers */ void ServerLobby::computeNewRankings() { // No ranking for battle mode if (!race_manager->modeHasLaps()) return; // Using a vector of vector, it would be possible to fill // all j < i v[i][j] with -v[j][i] // Would this be worth it ? std::vector scores_change; std::vector new_scores; unsigned player_count = race_manager->getNumPlayers(); for (unsigned i = 0; i < player_count; i++) { const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); new_scores.push_back(m_scores.at(id)); new_scores[i] += distributeBasePoints(id); } // First, update the number of ranked races for (unsigned i = 0; i < player_count; i++) { const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); m_num_ranked_races.at(id)++; } // Now compute points exchanges for (unsigned i = 0; i < player_count; i++) { scores_change.push_back(0.0); World* w = World::getWorld(); assert(w); double player1_scores = new_scores[i]; // If the player has quitted before the race end, // the value will be incorrect, but it will not be used double player1_time = race_manager->getKartRaceTime(i); double player1_factor = computeRankingFactor(race_manager->getKartInfo(i).getOnlineId()); double player1_handicap = ( w->getKart(i)->getPerPlayerDifficulty() == PLAYER_DIFFICULTY_HANDICAP ) ? HANDICAP_OFFSET : 0; for (unsigned j = 0; j < player_count; j++) { // Don't compare a player with himself if (i == j) continue; double result = 0.0; double expected_result = 0.0; double ranking_importance = 0.0; double max_time = 0.0; // No change between two quitting players if (w->getKart(i)->isEliminated() && w->getKart(j)->isEliminated()) continue; double player2_scores = new_scores[j]; double player2_time = race_manager->getKartRaceTime(j); double player2_handicap = ( w->getKart(j)->getPerPlayerDifficulty() == PLAYER_DIFFICULTY_HANDICAP ) ? HANDICAP_OFFSET : 0; // Compute the result and race ranking importance double player_factors = std::min(player1_factor, computeRankingFactor( race_manager->getKartInfo(j).getOnlineId())); double mode_factor = getModeFactor(); if (w->getKart(i)->isEliminated()) { result = 0.0; player1_time = player2_time; // for getTimeSpread max_time = MAX_SCALING_TIME; } else if (w->getKart(j)->isEliminated()) { result = 1.0; player2_time = player1_time; max_time = MAX_SCALING_TIME; } else { // If time difference > 2,5% ; the result is 1 or 0 // Otherwise, it is averaged between 0 and 1. if (player1_time <= player2_time) { result = (player2_time - player1_time) / (player1_time / 20.0); result = std::min(1.0, 0.5 + result); } else { result = (player1_time - player2_time) / (player2_time / 20.0); result = std::max(0.0, 0.5 - result); } max_time = std::min(std::max(player1_time, player2_time), MAX_SCALING_TIME); } ranking_importance = mode_factor * scalingValueForTime(max_time) * player_factors; // Compute the expected result using an ELO-like function double diff = player2_scores - player1_scores; if (!w->getKart(i)->isEliminated() && !w->getKart(j)->isEliminated()) diff += player1_handicap - player2_handicap; double uncertainty = std::max(getUncertaintySpread(race_manager->getKartInfo(i).getOnlineId()), getUncertaintySpread(race_manager->getKartInfo(j).getOnlineId()) ); expected_result = 1.0/ (1.0 + std::pow(10.0, diff / ( BASE_RANKING_POINTS / 2.0 * getModeSpread() * getTimeSpread(std::min(player1_time, player2_time)) * uncertainty ))); // Compute the ranking change scores_change[i] += ranking_importance * (result - expected_result); } } // Don't merge it in the main loop as new_scores value are used there for (unsigned i = 0; i < player_count; i++) { new_scores[i] += scores_change[i]; const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); m_scores.at(id) = new_scores[i]; if (m_scores.at(id) > m_max_scores.at(id)) m_max_scores.at(id) = m_scores.at(id); } } // computeNewRankings //----------------------------------------------------------------------------- /** Compute the ranking factor, used to make top rankings more stable * and to allow new players to faster get to an appropriate ranking */ double ServerLobby::computeRankingFactor(uint32_t online_id) { double max_points = m_max_scores.at(online_id); unsigned num_races = m_num_ranked_races.at(online_id); if (max_points >= (BASE_RANKING_POINTS * 2.0)) return 0.6; else if (max_points >= (BASE_RANKING_POINTS * 1.75) || num_races > 500) return 0.7; else if (max_points >= (BASE_RANKING_POINTS * 1.5) || num_races > 250) return 0.8; else if (max_points >= (BASE_RANKING_POINTS * 1.25) || num_races > 100) return 1.0; // The base ranking points are not distributed all at once // So it's not guaranteed a player reach them else if (max_points >= (BASE_RANKING_POINTS) || num_races > 50) return 1.2; else return 1.5; } // computeRankingFactor //----------------------------------------------------------------------------- /** Returns the mode race importance factor, * used to make ranking move slower in more random modes. */ double ServerLobby::getModeFactor() { if (race_manager->isTimeTrialMode()) return 1.0; return 0.7; } // getModeFactor //----------------------------------------------------------------------------- /** Returns the mode spread factor, used so that a similar difference in * skill will result in a similar ranking difference in more random modes. */ double ServerLobby::getModeSpread() { if (race_manager->isTimeTrialMode()) return 1.0; //TODO: the value used here for normal races is a wild guess. // When hard data to the spread tendencies of time-trial // and normal mode becomes available, update this to make // the spreads more comparable return 1.5; } // getModeSpread //----------------------------------------------------------------------------- /** Returns the time spread factor. * Short races are more random, so the expected result changes depending * on race duration. */ double ServerLobby::getTimeSpread(double time) { return sqrt(120.0 / time); } // getTimeSpread //----------------------------------------------------------------------------- /** Returns the uncertainty spread factor. * The ranking of new players is not yet reliable, * so weight the expected results twoards 0.5 by using a > 1 spread */ double ServerLobby::getUncertaintySpread(uint32_t online_id) { unsigned num_races = m_num_ranked_races.at(online_id); if (num_races <= 60) return 0.5 + (4.0/sqrt(num_races+3)); else return 1.0; } // getUncertaintySpread //----------------------------------------------------------------------------- /** Compute the scaling value of a given time * This is linear to race duration, getTimeSpread takes care * of expecting a more random result in shorter races. */ double ServerLobby::scalingValueForTime(double time) { return time * MAX_POINTS_PER_SECOND; } // scalingValueForTime //----------------------------------------------------------------------------- /** Manages the distribution of the base points. * Gives half of the points progressively * by smaller and smaller chuncks from race 1 to 60. * The race count is incremented after this is called, so num_races * is between 0 and 59. * The first half is distributed when the player enters * for the first time in a ranked server. */ double ServerLobby::distributeBasePoints(uint32_t online_id) { unsigned num_races = m_num_ranked_races.at(online_id); if (num_races < 60) { return BASE_RANKING_POINTS / 8000.0 * std::max((96u - num_races), 41u); } else return 0.0; } // distributeBasePoints //----------------------------------------------------------------------------- /** Called when a client disconnects. * \param event The disconnect event. */ void ServerLobby::clientDisconnected(Event* event) { auto players_on_peer = event->getPeer()->getPlayerProfiles(); if (players_on_peer.empty()) 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()) .addUInt32(event->getPeer()->getHostId()); for (auto p : players_on_peer) { std::string name = StringUtils::wideToUtf8(p->getName()); msg->encodeString(name); Log::info("ServerLobby", "%s disconnected", name.c_str()); } // 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 //----------------------------------------------------------------------------- void ServerLobby::clearDisconnectedRankedPlayer() { for (auto it = m_ranked_players.begin(); it != m_ranked_players.end();) { if (it->second.expired()) { const uint32_t id = it->first; m_scores.erase(id); m_max_scores.erase(id); m_num_ranked_races.erase(id); it = m_ranked_players.erase(it); } else { it++; } } } // clearDisconnectedRankedPlayer //----------------------------------------------------------------------------- void ServerLobby::connectionRequested(Event* event) { std::shared_ptr peer = event->getPeerSP(); NetworkString& data = event->data(); if (!checkDataSize(event, 14)) return; peer->cleanPlayerProfiles(); // can we add the player ? if (!allowJoinedPlayersWaiting() && (m_state.load() != WAITING_FOR_START_GAME || m_game_setup->isGrandPrixStarted())) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BUSY); // send only to the peer that made the request and disconect it now peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: selection started"); return; } // Check server version int version = data.getUInt32(); if (version < stk_config->m_min_server_version || version > stk_config->m_max_server_version) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(RR_INCOMPATIBLE_DATA); peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: wrong server version"); return; } std::string user_version; data.decodeString(&user_version); event->getPeer()->setUserVersion(user_version); unsigned list_caps = data.getUInt16(); std::vector caps; for (unsigned i = 0; i < list_caps; i++) { std::string cap; data.decodeString(&cap); caps.push_back(cap); } event->getPeer()->setClientCapabilities(caps); std::set client_karts, client_tracks; const unsigned kart_num = data.getUInt16(); const unsigned track_num = data.getUInt16(); for (unsigned i = 0; i < kart_num; i++) { std::string kart; data.decodeString(&kart); client_karts.insert(kart); } for (unsigned i = 0; i < track_num; i++) { std::string track; data.decodeString(&track); client_tracks.insert(track); } // Drop this player if he doesn't have at least 1 kart / track the same // as server float okt = 0.0f; float ott = 0.0f; for (auto& client_kart : client_karts) { if (m_official_kts.first.find(client_kart) != m_official_kts.first.end()) okt += 1.0f; } okt = okt / (float)m_official_kts.first.size(); for (auto& client_track : client_tracks) { if (m_official_kts.second.find(client_track) != m_official_kts.second.end()) ott += 1.0f; } ott = ott / (float)m_official_kts.second.size(); std::set karts_erase, tracks_erase; for (const std::string& server_kart : m_available_kts.first) { if (client_karts.find(server_kart) == client_karts.end()) { karts_erase.insert(server_kart); } } for (const std::string& server_track : m_available_kts.second) { if (client_tracks.find(server_track) == client_tracks.end()) { tracks_erase.insert(server_track); } } if (karts_erase.size() == m_available_kts.first.size() || tracks_erase.size() == m_available_kts.second.size() || okt < ServerConfig::m_official_karts_threshold || ott < ServerConfig::m_official_tracks_threshold) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(RR_INCOMPATIBLE_DATA); peer->cleanPlayerProfiles(); peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player has incompatible karts / tracks."); return; } // Save available karts and tracks from clients in STKPeer so if this peer // disconnects later in lobby it won't affect current players peer->setAvailableKartsTracks(client_karts, client_tracks); unsigned player_count = data.getUInt8(); uint32_t online_id = 0; uint32_t encrypted_size = 0; online_id = data.getUInt32(); encrypted_size = data.getUInt32(); bool is_banned = isBannedForIP(peer->getAddress()); if (online_id != 0 && !is_banned) { if (m_online_id_ban_list.find(online_id) != m_online_id_ban_list.end() && (uint32_t)StkTime::getTimeSinceEpoch() < m_online_id_ban_list.at(online_id)) { is_banned = true; } } if (is_banned) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BANNED); // For future we can say the reason here // and use encodeString instead message->addUInt8(0); peer->cleanPlayerProfiles(); peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: banned"); return; } unsigned total_players = 0; STKHost::get()->updatePlayers(NULL, NULL, &total_players); if (total_players + player_count > (unsigned)ServerConfig::m_server_max_players) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_TOO_MANY_PLAYERS); peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: too many players"); return; } // Reject non-valiated player joinning if WAN server and not disabled // encforement of validation, unless it's player from localhost or lan // And no duplicated online id or split screen players in ranked server std::set all_online_ids = STKHost::get()->getAllPlayerOnlineIds(); bool duplicated_ranked_player = all_online_ids.find(online_id) != all_online_ids.end(); if (((encrypted_size == 0 || online_id == 0) && !(peer->getAddress().isPublicAddressLocalhost() || peer->getAddress().isLAN()) && NetworkConfig::get()->isWAN() && ServerConfig::m_validating_player) || (ServerConfig::m_strict_players && (player_count != 1 || online_id == 0 || duplicated_ranked_player))) { 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"); return; } if (encrypted_size != 0) { m_pending_connection[peer] = std::make_pair(online_id, BareNetworkString(data.getCurrentData(), encrypted_size)); } else { core::stringw online_name; if (online_id > 0) data.decodeStringW(&online_name); handleUnencryptedConnection(peer, data, online_id, online_name); } } // connectionRequested //----------------------------------------------------------------------------- void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, BareNetworkString& data, uint32_t online_id, const core::stringw& online_name) { if (data.size() < 2) return; // Check for password std::string password; data.decodeString(&password); const std::string& server_pw = ServerConfig::m_private_server_password; if (password != server_pw) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(RR_INCORRECT_PASSWORD); peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: incorrect password"); return; } // Check again for duplicated player in ranked server std::set all_online_ids = STKHost::get()->getAllPlayerOnlineIds(); bool duplicated_ranked_player = all_online_ids.find(online_id) != all_online_ids.end(); if (ServerConfig::m_ranked && duplicated_ranked_player) { 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"); return; } unsigned player_count = data.getUInt8(); auto red_blue = STKHost::get()->getAllPlayersTeamInfo(); for (unsigned i = 0; i < player_count; i++) { core::stringw name; data.decodeStringW(&name); if (name.empty()) name = L"unnamed"; float default_kart_color = data.getFloat(); PerPlayerDifficulty per_player_difficulty = (PerPlayerDifficulty)data.getUInt8(); auto player = std::make_shared (peer, i == 0 && !online_name.empty() ? online_name : name, peer->getHostId(), default_kart_color, i == 0 ? online_id : 0, per_player_difficulty, (uint8_t)i, KART_TEAM_NONE, ""/* reserved for country id */); if (ServerConfig::m_team_choosing) { KartTeam cur_team = KART_TEAM_NONE; if (red_blue.first > red_blue.second) { cur_team = KART_TEAM_BLUE; red_blue.second++; } else { cur_team = KART_TEAM_RED; red_blue.first++; } player->setTeam(cur_team); } peer->addPlayer(player); } peer->setValidated(); // send a message to the one that asked to connect NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); m_game_setup->addServerInfo(server_info); 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 float auto_start_timer = 0.0f; if (m_timeout.load() == std::numeric_limits::max()) auto_start_timer = std::numeric_limits::max(); else { auto_start_timer = (m_timeout.load() - (int64_t)StkTime::getRealTimeMs()) / 1000.0f; } message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt32(peer->getHostId()) .addUInt32(ServerConfig::m_server_version); // Reserved for future to supply list of network capabilities message_ack->addUInt16(0); message_ack->addFloat(auto_start_timer) .addUInt32(ServerConfig::m_state_frequency) .addUInt8(ServerConfig::m_chat ? 1 : 0); peer->setSpectator(false); if (game_started) { peer->setWaitingForGame(true); updatePlayerList(); peer->sendPacket(message_ack); delete message_ack; } else { peer->setWaitingForGame(false); m_peers_ready[peer] = false; for (std::shared_ptr& npp : peer->getPlayerProfiles()) { Log::info("ServerLobby", "New player %s with online id %u from %s with %s.", StringUtils::wideToUtf8(npp->getName()).c_str(), npp->getOnlineId(), peer->getAddress().toString().c_str(), peer->getUserVersion().c_str()); } updatePlayerList(); peer->sendPacket(message_ack); delete message_ack; if (ServerConfig::m_ranked) { getRankingForPlayer(peer->getPlayerProfiles()[0]); } } } // handleUnencryptedConnection //----------------------------------------------------------------------------- /** 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) { 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)(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()); std::shared_ptr p = profile->getPeer(); uint8_t boolean_combine = 0; if (p && p->isWaitingForGame()) boolean_combine |= 1; if (p && p->isSpectator()) boolean_combine |= (1 << 1); if (p && m_server_owner_id.load() == p->getHostId()) boolean_combine |= (1 << 2); if (ServerConfig::m_owner_less && !game_started && m_peers_ready.find(p) != m_peers_ready.end() && m_peers_ready.at(p)) boolean_combine |= (1 << 3); pl->addUInt8(boolean_combine); pl->addUInt8(profile->getPerPlayerDifficulty()); if (ServerConfig::m_team_choosing && race_manager->teamEnabled()) pl->addUInt8(profile->getTeam()); else pl->addUInt8(KART_TEAM_NONE); pl->encodeString(profile->getCountryId()); } // 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() < WAITING_FOR_START_GAME || m_state.load() > RESULT_DISPLAY || ServerConfig::m_owner_less) return; if (!m_server_owner.expired()) return; auto peers = STKHost::get()->getPeers(); if (peers.empty()) return; std::sort(peers.begin(), peers.end(), [](const std::shared_ptr a, const std::shared_ptr b)->bool { return a->getHostId() < b->getHostId(); }); std::shared_ptr owner; for (auto peer: peers) { // Only 127.0.0.1 can be server owner in case of graphics-client-server if (peer->isValidated() && (NetworkConfig::get()->getServerIdFile().empty() || peer->getAddress().getIP() == 0x7f000001)) { owner = peer; break; } } if (owner) { NetworkString* ns = getNetworkString(); ns->setSynchronous(true); ns->addUInt8(LE_SERVER_OWNERSHIP); owner->sendPacket(ns); delete ns; m_server_owner = owner; m_server_owner_id.store(owner->getHostId()); updatePlayerList(); } } // updateServerOwner //----------------------------------------------------------------------------- /*! \brief Called when a player asks to select karts. * \param event : Event providing the information. */ void ServerLobby::kartSelectionRequested(Event* event) { if (m_state != SELECTING || m_game_setup->isGrandPrixStarted()) { Log::warn("ServerLobby", "Received kart selection while in state %d.", m_state.load()); return; } if (!checkDataSize(event, 1) || event->getPeer()->getPlayerProfiles().empty()) return; const NetworkString& data = event->data(); STKPeer* peer = event->getPeer(); setPlayerKarts(data, peer); } // kartSelectionRequested //----------------------------------------------------------------------------- /*! \brief Called when a player votes for track(s), it will auto correct client * data if it sends some invalid data. * \param event : Event providing the information. */ void ServerLobby::handlePlayerVote(Event* event) { if (m_state != SELECTING || !ServerConfig::m_track_voting) { Log::warn("ServerLobby", "Received track vote while in state %d.", m_state.load()); return; } if (!checkDataSize(event, 4) || event->getPeer()->getPlayerProfiles().empty() || event->getPeer()->isWaitingForGame()) return; if (isVotingOver()) return; NetworkString& data = event->data(); PeerVote vote(data); Log::debug("ServerLobby", "Vote from client: host %d, track %s, laps %d, reverse %d.", event->getPeer()->getHostId(), vote.m_track_name.c_str(), vote.m_num_laps, vote.m_reverse); Track* t = track_manager->getTrack(vote.m_track_name); if (!t) { vote.m_track_name = *m_available_kts.second.begin(); t = track_manager->getTrack(vote.m_track_name); assert(t); } // Remove / adjust any invalid settings if (race_manager->modeHasLaps()) { if (ServerConfig::m_auto_game_time_ratio > 0.0f) { vote.m_num_laps = (uint8_t)(fmaxf(1.0f, (float)t->getDefaultNumberOfLaps() * ServerConfig::m_auto_game_time_ratio)); } else if (vote.m_num_laps == 0 || vote.m_num_laps > 20) vote.m_num_laps = (uint8_t)3; if (!t->reverseAvailable() && vote.m_reverse) vote.m_reverse = false; } else if (race_manager->isSoccerMode()) { if (m_game_setup->isSoccerGoalTarget()) { if (ServerConfig::m_auto_game_time_ratio > 0.0f) { vote.m_num_laps = (uint8_t)(ServerConfig::m_auto_game_time_ratio * UserConfigParams::m_num_goals); } else if (vote.m_num_laps > 10) vote.m_num_laps = (uint8_t)5; } else { if (ServerConfig::m_auto_game_time_ratio > 0.0f) { vote.m_num_laps = (uint8_t)(ServerConfig::m_auto_game_time_ratio * UserConfigParams::m_soccer_time_limit); } else if (vote.m_num_laps > 15) vote.m_num_laps = (uint8_t)7; } } else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL) { vote.m_num_laps = 0; } else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_CAPTURE_THE_FLAG) { vote.m_num_laps = 0; vote.m_reverse = false; } // Store vote: vote.m_player_name = event->getPeer()->getPlayerProfiles()[0]->getName(); addVote(event->getPeer()->getHostId(), vote); // Now inform all clients about the vote NetworkString other = NetworkString(PROTOCOL_LOBBY_ROOM); other.setSynchronous(true); other.addUInt8(LE_VOTE); other.addUInt32(event->getPeer()->getHostId()); vote.encode(&other); sendMessageToPeers(&other); } // handlePlayerVote // ---------------------------------------------------------------------------- /** Select the track to be used based on all votes being received. * \param winner_vote The PeerVote that was picked. * \param winner_peer_id The host id of winner (unchanged if no vote). * \return True if race can go on, otherwise wait. */ bool ServerLobby::handleAllVotes(PeerVote* winner_vote, uint32_t* winner_peer_id) { // Determine majority agreement when 35% of voting time remains, // reserve some time for kart selection so it's not 50% if (getRemainingVotingTime() / getMaxVotingTime() > 0.35f) { return false; } // First remove all votes from disconnected hosts auto it = m_peers_votes.begin(); while (it != m_peers_votes.end()) { auto peer = STKHost::get()->findPeerByHostId(it->first); if (peer == nullptr) { it = m_peers_votes.erase(it); } else it++; } if (m_peers_votes.empty()) { if (isVotingOver()) { *winner_vote = *m_default_vote; return true; } return false; } // Count number of players float cur_players = 0.0f; auto peers = STKHost::get()->getPeers(); for (auto peer : peers) { if (peer->hasPlayerProfiles() && !peer->isWaitingForGame()) cur_players += 1.0f; } std::string top_track = m_default_vote->m_track_name; int top_laps = m_default_vote->m_num_laps; bool top_reverse = m_default_vote->m_reverse; std::map tracks; std::map laps; std::map reverses; // Ratio to determine majority agreement float tracks_rate = 0.0f; float laps_rate = 0.0f; float reverses_rate = 0.0f; RandomGenerator rg; for (auto& p : m_peers_votes) { auto track_vote = tracks.find(p.second.m_track_name); if (track_vote == tracks.end()) tracks[p.second.m_track_name] = 1; else track_vote->second++; auto lap_vote = laps.find(p.second.m_num_laps); if (lap_vote == laps.end()) laps[p.second.m_num_laps] = 1; else lap_vote->second++; auto reverse_vote = reverses.find(p.second.m_reverse); if (reverse_vote == reverses.end()) reverses[p.second.m_reverse] = 1; else reverse_vote->second++; } unsigned vote = 0; auto track_vote = tracks.begin(); // rg.get(2) == 0 will allow not always the "less" in map get picked for (auto c_vote = tracks.begin(); c_vote != tracks.end(); c_vote++) { if (c_vote->second > vote || (c_vote->second >= vote && rg.get(2) == 0)) { vote = c_vote->second; track_vote = c_vote; } } if (track_vote != tracks.end()) { top_track = track_vote->first; tracks_rate = float(track_vote->second) / cur_players; } vote = 0; auto lap_vote = laps.begin(); for (auto c_vote = laps.begin(); c_vote != laps.end(); c_vote++) { if (c_vote->second > vote || (c_vote->second >= vote && rg.get(2) == 0)) { vote = c_vote->second; lap_vote = c_vote; } } if (lap_vote != laps.end()) { top_laps = lap_vote->first; laps_rate = float(lap_vote->second) / cur_players; } vote = 0; auto reverse_vote = reverses.begin(); for (auto c_vote = reverses.begin(); c_vote != reverses.end(); c_vote++) { if (c_vote->second > vote || (c_vote->second >= vote && rg.get(2) == 0)) { vote = c_vote->second; reverse_vote = c_vote; } } if (reverse_vote != reverses.end()) { top_reverse = reverse_vote->first; reverses_rate = float(reverse_vote->second) / cur_players; } // End early if there is majority agreement which is all entries rate > 0.5 it = m_peers_votes.begin(); if (tracks_rate > 0.5f && laps_rate > 0.5f && reverses_rate > 0.5f) { while (it != m_peers_votes.end()) { if (it->second.m_track_name == top_track && it->second.m_num_laps == top_laps && it->second.m_reverse == top_reverse) break; else it++; } if (it == m_peers_votes.end()) { Log::warn("ServerLobby", "Missing track %s from majority.", top_track.c_str()); it = m_peers_votes.begin(); } *winner_peer_id = it->first; *winner_vote = it->second; return true; } else if (isVotingOver()) { // Pick the best lap (or soccer goal / time) from only the top track // if no majority agreement from all int diff = std::numeric_limits::max(); auto closest_lap = m_peers_votes.begin(); while (it != m_peers_votes.end()) { if (it->second.m_track_name == top_track && std::abs((int)it->second.m_num_laps - top_laps) < diff) { closest_lap = it; diff = std::abs((int)it->second.m_num_laps - top_laps); } else it++; } *winner_peer_id = closest_lap->first; *winner_vote = closest_lap->second; return true; } return false; } // handleAllVotes // ---------------------------------------------------------------------------- void ServerLobby::getHitCaptureLimit() { int hit_capture_limit = std::numeric_limits::max(); float time_limit = 0.0f; if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_CAPTURE_THE_FLAG) { if (ServerConfig::m_capture_limit > 0) hit_capture_limit = ServerConfig::m_capture_limit; if (ServerConfig::m_time_limit_ctf > 0) time_limit = (float)ServerConfig::m_time_limit_ctf; } else { if (ServerConfig::m_hit_limit > 0) hit_capture_limit = ServerConfig::m_hit_limit; if (ServerConfig::m_time_limit_ffa > 0.0f) time_limit = (float)ServerConfig::m_time_limit_ffa; } m_battle_hit_capture_limit = hit_capture_limit; m_battle_time_limit = time_limit; } // getHitCaptureLimit // ---------------------------------------------------------------------------- /** 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() { for (auto p : m_peers_ready) { if (auto peer = p.first.lock()) peer->updateLastActivity(); } m_server_has_loaded_world.store(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) { std::shared_ptr peer = event->getPeerSP(); peer->updateLastActivity(); m_peers_ready.at(peer) = true; Log::info("ServerLobby", "Peer %d has finished loading world at %lf", peer->getHostId(), StkTime::getRealTime()); } // finishedLoadingWorldClient //----------------------------------------------------------------------------- /** 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) { if (m_rs_state.load() == RS_ASYNC_RESET || m_state.load() != RESULT_DISPLAY) return; std::shared_ptr peer = event->getPeerSP(); m_peers_ready.at(peer) = true; } // playerFinishedResult //----------------------------------------------------------------------------- void ServerLobby::updateBanList() { m_ip_ban_list.clear(); m_online_id_ban_list.clear(); for (auto& ban : ServerConfig::m_server_ip_ban_list) { if (ban.first == "0.0.0.0/0" || (uint32_t)StkTime::getTimeSinceEpoch() > ban.second) continue; uint32_t netbits = 0; std::vector ip_and_netbits = StringUtils::split(ban.first, '/'); if (ip_and_netbits.size() != 2 || !StringUtils::fromString(ip_and_netbits[1], netbits) || netbits > 32) { Log::error("STKHost", "Wrong CIDR: %s", ban.first.c_str()); continue; } TransportAddress addr(ip_and_netbits[0]); if (addr.getIP() == 0) { Log::error("STKHost", "Wrong CIDR: %s", ban.first.c_str()); continue; } uint32_t mask = ~((1 << (32 - netbits)) - 1); uint32_t ip_start = addr.getIP() & mask; uint32_t ip_end = (addr.getIP() & mask) | ~mask; m_ip_ban_list[ip_start] = std::make_tuple(ip_end, ban.first, ban.second); } std::map final_ip_ban_list; for (auto it = m_ip_ban_list.begin(); it != m_ip_ban_list.end();) { auto next_itr = std::next(it); if (next_itr != m_ip_ban_list.end() && next_itr->first <= std::get<0>(it->second)) { Log::warn("ServerLobby", "%s overlaps %s, removing the first one.", std::get<1>(next_itr->second).c_str(), std::get<1>(it->second).c_str()); m_ip_ban_list.erase(next_itr); continue; } final_ip_ban_list[std::get<1>(it->second)] = ServerConfig::m_server_ip_ban_list.at(std::get<1>(it->second)); it++; } ServerConfig::m_server_ip_ban_list = final_ip_ban_list; // Default guided entry ServerConfig::m_server_ip_ban_list["0.0.0.0/0"] = 0; std::map final_online_id_ban_list; for (auto& ban : ServerConfig::m_server_online_id_ban_list) { if (ban.first == 0 || (uint32_t)StkTime::getTimeSinceEpoch() > ban.second) continue; m_online_id_ban_list[ban.first] = ban.second; final_online_id_ban_list[ban.first] = ServerConfig::m_server_online_id_ban_list.at(ban.first); } ServerConfig::m_server_online_id_ban_list = final_online_id_ban_list; ServerConfig::m_server_online_id_ban_list[0] = 0; } // updateBanList //----------------------------------------------------------------------------- bool ServerLobby::waitingForPlayers() const { if (m_game_setup->isGrandPrix() && m_game_setup->isGrandPrixStarted()) return false; return m_state.load() >= WAITING_FOR_START_GAME; } // waitingForPlayers //----------------------------------------------------------------------------- void ServerLobby::handlePendingConnection() { std::lock_guard lock(m_keys_mutex); for (auto it = m_pending_connection.begin(); it != m_pending_connection.end();) { auto peer = it->first.lock(); if (!peer) { it = m_pending_connection.erase(it); } else { const uint32_t online_id = it->second.first; auto key = m_keys.find(online_id); if (key != m_keys.end() && key->second.m_tried == false) { try { if (decryptConnectionRequest(peer, it->second.second, key->second.m_aes_key, key->second.m_aes_iv, online_id, key->second.m_name)) { it = m_pending_connection.erase(it); m_keys.erase(online_id); continue; } else key->second.m_tried = true; } catch (std::exception& e) { Log::error("ServerLobby", "handlePendingConnection error: %s", e.what()); key->second.m_tried = true; } } it++; } } } // handlePendingConnection //----------------------------------------------------------------------------- bool ServerLobby::decryptConnectionRequest(std::shared_ptr peer, BareNetworkString& data, const std::string& key, const std::string& iv, uint32_t online_id, const core::stringw& online_name) { auto crypto = std::unique_ptr(new Crypto( Crypto::decode64(key), Crypto::decode64(iv))); if (crypto->decryptConnectionRequest(data)) { peer->setCrypto(std::move(crypto)); Log::info("ServerLobby", "%s validated", StringUtils::wideToUtf8(online_name).c_str()); handleUnencryptedConnection(peer, data, online_id, online_name); return true; } return false; } // decryptConnectionRequest //----------------------------------------------------------------------------- void ServerLobby::getRankingForPlayer(std::shared_ptr p) { Online::XMLRequest* request = new Online::XMLRequest(); NetworkConfig::get()->setUserDetails(request, "get-ranking"); const uint32_t id = p->getOnlineId(); request->addParameter("id", id); request->executeNow(); const XMLNode* result = request->getXMLData(); std::string rec_success; // Default result double score = 2000.0; double max_score = 2000.0; unsigned num_races = 0; if (result->get("success", &rec_success)) { if (rec_success == "yes") { result->get("scores", &score); result->get("max-scores", &max_score); result->get("num-races-done", &num_races); } else { Log::error("ServerLobby", "No ranking info found."); } } else { Log::error("ServerLobby", "No ranking info found."); } m_ranked_players[id] = p; m_scores[id] = score; m_max_scores[id] = max_score; m_num_ranked_races[id] = num_races; delete request; } // getRankingForPlayer //----------------------------------------------------------------------------- void ServerLobby::submitRankingsToAddons() { // No ranking for battle mode if (!race_manager->modeHasLaps()) return; // ======================================================================== class SumbitRankingRequest : public Online::XMLRequest { public: SumbitRankingRequest(uint32_t online_id, double scores, double max_scores, unsigned num_races) : XMLRequest(true) { addParameter("id", online_id); addParameter("scores", scores); addParameter("max-scores", max_scores); addParameter("num-races-done", num_races); } virtual void afterOperation() { Online::XMLRequest::afterOperation(); const XMLNode* result = getXMLData(); std::string rec_success; if (!(result->get("success", &rec_success) && rec_success == "yes")) { Log::error("ServerLobby", "Failed to submit scores."); } } }; // UpdatePlayerRankingRequest // ======================================================================== for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); SumbitRankingRequest* request = new SumbitRankingRequest (id, m_scores.at(id), m_max_scores.at(id), m_num_ranked_races.at(id)); NetworkConfig::get()->setUserDetails(request, "submit-ranking"); Log::info("ServerLobby", "Submiting ranking for %s (%d) : %lf, %lf %d", StringUtils::wideToUtf8( race_manager->getKartInfo(i).getPlayerName()).c_str(), id, m_scores.at(id), m_max_scores.at(id), m_num_ranked_races.at(id)); request->queue(); } } // submitRankingsToAddons //----------------------------------------------------------------------------- /** This function is called when all clients have loaded the world and * are therefore ready to start the race. It determine the start time in * network timer for client and server based on pings and then switches state * to WAIT_FOR_RACE_STARTED. */ void ServerLobby::configPeersStartTime() { uint32_t max_ping = 0; const unsigned max_ping_from_peers = ServerConfig::m_max_ping; for (auto p : m_peers_ready) { auto peer = p.first.lock(); if (!peer) continue; if (peer->getAveragePing() > max_ping_from_peers) { Log::warn("ServerLobby", "Peer %s cannot catch up with max ping %d.", peer->getAddress().toString().c_str(), max_ping); continue; } max_ping = std::max(peer->getAveragePing(), max_ping); } if (ServerConfig::m_live_players && race_manager->supportsLiveJoining()) { Log::info("ServerLobby", "Max ping to ServerConfig::m_max_ping for " "live joining."); max_ping = ServerConfig::m_max_ping; } // Start up time will be after 2500ms, so even if this packet is sent late // (due to packet loss), the start time will still ahead of current time uint64_t start_time = STKHost::get()->getNetworkTimer() + (uint64_t)2500; powerup_manager->setRandomSeed(start_time); NetworkString* ns = getNetworkString(10); ns->setSynchronous(true); ns->addUInt8(LE_START_RACE).addUInt64(start_time); const uint8_t cc = (uint8_t)CheckManager::get()->getCheckStructureCount(); ns->addUInt8(cc); *ns += *m_items_complete_state; m_client_starting_time = start_time; sendMessageToPeers(ns, /*reliable*/true); const unsigned jitter_tolerance = ServerConfig::m_jitter_tolerance; Log::info("ServerLobby", "Max ping from peers: %d, jitter tolerance: %d", max_ping, jitter_tolerance); // Delay server for max ping / 2 from peers and jitter tolerance. m_server_delay = (uint64_t)(max_ping / 2) + (uint64_t)jitter_tolerance; start_time += m_server_delay; m_server_started_at = start_time; delete ns; m_state = WAIT_FOR_RACE_STARTED; World::getWorld()->setPhase(WorldStatus::SERVER_READY_PHASE); joinStartGameThread(); m_start_game_thread = std::thread([start_time, this]() { const uint64_t cur_time = STKHost::get()->getNetworkTimer(); assert(start_time > cur_time); int sleep_time = (int)(start_time - cur_time); Log::info("ServerLobby", "Start game after %dms", sleep_time); std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time)); Log::info("ServerLobby", "Started at %lf", StkTime::getRealTime()); m_state.store(RACING); }); } // configPeersStartTime //----------------------------------------------------------------------------- bool ServerLobby::allowJoinedPlayersWaiting() const { return !m_game_setup->isGrandPrix(); } // allowJoinedPlayersWaiting //----------------------------------------------------------------------------- void ServerLobby::addWaitingPlayersToGame() { auto all_profiles = STKHost::get()->getAllPlayerProfiles(); for (auto& profile : all_profiles) { auto peer = profile->getPeer(); if (!peer || !peer->isValidated()) continue; peer->setWaitingForGame(false); peer->setSpectator(false); if (m_peers_ready.find(peer) == m_peers_ready.end()) { m_peers_ready[peer] = false; Log::info("ServerLobby", "New player %s with online id %u from %s with %s.", StringUtils::wideToUtf8(profile->getName()).c_str(), profile->getOnlineId(), peer->getAddress().toString().c_str(), peer->getUserVersion().c_str()); } uint32_t online_id = profile->getOnlineId(); if (ServerConfig::m_ranked && (m_ranked_players.find(online_id) == m_ranked_players.end() || (m_ranked_players.find(online_id) != m_ranked_players.end() && m_ranked_players.at(online_id).expired()))) { getRankingForPlayer(peer->getPlayerProfiles()[0]); } } } // addWaitingPlayersToGame //----------------------------------------------------------------------------- void ServerLobby::resetServer() { addWaitingPlayersToGame(); resetPeersReady(); 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(); m_state = NetworkConfig::get()->isLAN() ? WAITING_FOR_START_GAME : REGISTER_SELF_ADDRESS; } // resetServer //----------------------------------------------------------------------------- bool ServerLobby::isBannedForIP(const TransportAddress& addr) const { uint32_t ip_decimal = addr.getIP(); auto lb = m_ip_ban_list.lower_bound(addr.getIP()); bool is_banned = false; if (lb != m_ip_ban_list.end() && ip_decimal >= lb->first/*ip_start*/) { if (ip_decimal <= std::get<0>(lb->second)/*ip_end*/ && (uint32_t)StkTime::getTimeSinceEpoch() < std::get<2>(lb->second)) is_banned = true; } else if (lb != m_ip_ban_list.begin()) { lb--; if (ip_decimal>= lb->first/*ip_start*/ && ip_decimal <= std::get<0>(lb->second)/*ip_end*/ && (uint32_t)StkTime::getTimeSinceEpoch() < std::get<2>(lb->second)) is_banned = true; } if (is_banned) { Log::info("ServerLobby", "%s is banned by CIDR %s", addr.toString(false/*show_port*/).c_str(), std::get<1>(lb->second).c_str()); } return is_banned; } // isBannedForIP //----------------------------------------------------------------------------- float ServerLobby::getStartupBoostOrPenaltyForKart(uint32_t ping, unsigned kart_id) { AbstractKart* k = World::getWorld()->getKart(kart_id); if (k->getStartupBoost() != 0.0f) return k->getStartupBoost(); uint64_t now = STKHost::get()->getNetworkTimer(); uint64_t client_time = now - ping / 2; uint64_t server_time = client_time + m_server_delay; int ticks = stk_config->time2Ticks( (float)(server_time - m_server_started_at) / 1000.0f); if (ticks < stk_config->time2Ticks(1.0f)) { PlayerController* pc = dynamic_cast(k->getController()); pc->displayPenaltyWarning(); return -1.0f; } float f = k->getStartupBoostFromStartTicks(ticks); k->setStartupBoost(f); return f; } // getStartupBoostOrPenaltyForKart //----------------------------------------------------------------------------- /*! \brief Called when the server owner request to change game mode or * difficulty. * \param event : Event providing the information. * * Format of the data : * Byte 0 1 2 * ----------------------------------------------- * Size | 1 | 1 | 1 | * Data | difficulty | game mode | soccer goal target | * ----------------------------------------------- */ void ServerLobby::handleServerConfiguration(Event* event) { if (m_state != WAITING_FOR_START_GAME) { Log::warn("ServerLobby", "Received handleServerConfiguration while being in state %d.", m_state.load()); return; } if (!ServerConfig::m_server_configurable) { Log::warn("ServerLobby", "server-configurable is not enabled."); return; } if (event->getPeerSP() != m_server_owner.lock()) { Log::warn("ServerLobby", "Client %d is not authorised to config server.", event->getPeer()->getHostId()); return; } NetworkString& data = event->data(); int new_difficulty = data.getUInt8(); int new_game_mode = data.getUInt8(); bool new_soccer_goal_target = data.getUInt8() == 1; auto modes = ServerConfig::getLocalGameMode(new_game_mode); if (modes.second == RaceManager::MAJOR_MODE_GRAND_PRIX) { Log::warn("ServerLobby", "Grand prix is used for new mode."); return; } race_manager->setMinorMode(modes.first); race_manager->setMajorMode(modes.second); race_manager->setDifficulty(RaceManager::Difficulty(new_difficulty)); m_game_setup->resetExtraServerInfo(); if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER) m_game_setup->setSoccerGoalTarget(new_soccer_goal_target); if (NetworkConfig::get()->isWAN() && (m_difficulty.load() != new_difficulty || m_game_mode.load() != new_game_mode)) { Log::info("ServerLobby", "Updating server info with new " "difficulty: %d, game mode: %d to stk-addons.", new_difficulty, new_game_mode); Online::XMLRequest* request = new Online::XMLRequest(true/*manage_memory*/); NetworkConfig::get()->setServerDetails(request, "update-config"); request->addParameter("address", m_server_address.getIP()); request->addParameter("port", m_server_address.getPort()); request->addParameter("new-difficulty", new_difficulty); request->addParameter("new-game-mode", new_game_mode); request->queue(); } m_difficulty.store(new_difficulty); m_game_mode.store(new_game_mode); updateTracksForMode(); auto peers = STKHost::get()->getPeers(); for (auto& peer : peers) { auto assets = peer->getClientAssets(); if (!peer->isValidated() || assets.second.empty()) continue; std::set tracks_erase; for (const std::string& server_track : m_available_kts.second) { if (assets.second.find(server_track) == assets.second.end()) { tracks_erase.insert(server_track); } } if (tracks_erase.size() == m_available_kts.second.size()) { NetworkString *message = getNetworkString(2); message->setSynchronous(true); message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(RR_INCOMPATIBLE_DATA); peer->cleanPlayerProfiles(); peer->sendPacket(message, true/*reliable*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player has incompatible tracks for new game mode."); } } 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; updatePlayerList(); } // handleServerConfiguration //----------------------------------------------------------------------------- /*! \brief Called when a player want to change his handicap * \param event : Event providing the information. * * Format of the data : * Byte 0 1 * ---------------------------------- * Size | 1 | 1 | * Data | local player id | new handicap | * ---------------------------------- */ void ServerLobby::changeHandicap(Event* event) { NetworkString& data = event->data(); if (m_state.load() != WAITING_FOR_START_GAME && !event->getPeer()->isWaitingForGame()) { Log::warn("ServerLobby", "Set handicap at wrong time."); return; } uint8_t local_id = data.getUInt8(); auto& player = event->getPeer()->getPlayerProfiles().at(local_id); uint8_t difficulty_id = data.getUInt8(); if (difficulty_id >= PLAYER_DIFFICULTY_COUNT) { Log::warn("ServerLobby", "Wrong handicap %d.", difficulty_id); return; } PerPlayerDifficulty d = (PerPlayerDifficulty)difficulty_id; player->setPerPlayerDifficulty(d); updatePlayerList(); } // changeHandicap //----------------------------------------------------------------------------- /** Update and see if any player disconnects, if so eliminate the kart in * world, so this function must be called in main thread. */ void ServerLobby::handlePlayerDisconnection() const { if (!World::getWorld() || World::getWorld()->getPhase() < WorldStatus::MUSIC_PHASE) { return; } int red_count = 0; int blue_count = 0; unsigned total = 0; for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { RemoteKartInfo& rki = race_manager->getKartInfo(i); if (rki.isReserved()) continue; bool disconnected = rki.disconnected(); if (race_manager->getKartInfo(i).getKartTeam() == KART_TEAM_RED && !disconnected) red_count++; else if (race_manager->getKartInfo(i).getKartTeam() == KART_TEAM_BLUE && !disconnected) blue_count++; if (!disconnected) { total++; continue; } else rki.makeReserved(); AbstractKart* k = World::getWorld()->getKart(i); if (!k->isEliminated() && !k->hasFinishedRace()) { CaptureTheFlag* ctf = dynamic_cast (World::getWorld()); if (ctf) ctf->loseFlagForKart(k->getWorldKartId()); World::getWorld()->eliminateKart(i, false/*notify_of_elimination*/); k->setPosition( World::getWorld()->getCurrentNumKarts() + 1); k->finishedRace(World::getWorld()->getTime(), true/*from_server*/); } } // If live players is enabled, don't end the game if unfair team if (!ServerConfig::m_live_players && total != 1 && World::getWorld()->hasTeam() && (red_count == 0 || blue_count == 0)) World::getWorld()->setUnfairTeam(true); } // handlePlayerDisconnection //----------------------------------------------------------------------------- /** Add reserved players for live join later if required. */ void ServerLobby::addLiveJoinPlaceholder( std::vector >& players) const { if (!ServerConfig::m_live_players || !race_manager->supportsLiveJoining()) return; if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL) { Track* t = track_manager->getTrack(m_game_setup->getCurrentTrack()); assert(t); int max_players = std::min((int)ServerConfig::m_server_max_players, (int)t->getMaxArenaPlayers()); int add_size = max_players - (int)players.size(); assert(add_size >= 0); for (int i = 0; i < add_size; i++) { players.push_back( NetworkPlayerProfile::getReservedProfile(KART_TEAM_NONE)); } } else { // CTF or soccer, reserve at most 7 players on each team int red_count = 0; int blue_count = 0; for (unsigned i = 0; i < players.size(); i++) { if (players[i]->getTeam() == KART_TEAM_RED) red_count++; else blue_count++; } red_count = red_count >= 7 ? 0 : 7 - red_count; blue_count = blue_count >= 7 ? 0 : 7 - blue_count; for (int i = 0; i < red_count; i++) { players.push_back( NetworkPlayerProfile::getReservedProfile(KART_TEAM_RED)); } for (int i = 0; i < blue_count; i++) { players.push_back( NetworkPlayerProfile::getReservedProfile(KART_TEAM_BLUE)); } } } // addLiveJoinPlaceholder //----------------------------------------------------------------------------- void ServerLobby::setPlayerKarts(const NetworkString& ns, STKPeer* peer) const { unsigned player_count = ns.getUInt8(); for (unsigned i = 0; i < player_count; i++) { std::string kart; ns.decodeString(&kart); if (m_available_kts.first.find(kart) == m_available_kts.first.end()) { continue; } else { peer->getPlayerProfiles()[i]->setKartName(kart); } } } // setPlayerKarts //----------------------------------------------------------------------------- /** Tell the client \ref RemoteKartInfo of a player when some player joining * live. */ void ServerLobby::handleKartInfo(Event* event) { World* w = World::getWorld(); if (!w) return; STKPeer* peer = event->getPeer(); const NetworkString& data = event->data(); uint8_t kart_id = data.getUInt8(); if (kart_id > race_manager->getNumPlayers()) return; AbstractKart* k = w->getKart(kart_id); int live_join_util_ticks = k->getLiveJoinUntilTicks(); const RemoteKartInfo& rki = race_manager->getKartInfo(kart_id); NetworkString* ns = getNetworkString(1); ns->setSynchronous(true); ns->addUInt8(LE_KART_INFO).addUInt32(live_join_util_ticks) .addUInt8(kart_id) .encodeString(rki.getPlayerName()) .addUInt32(rki.getHostId()).addFloat(rki.getDefaultKartColor()) .addUInt32(rki.getOnlineId()).addUInt8(rki.getDifficulty()) .addUInt8((uint8_t)rki.getLocalPlayerId()) .encodeString(rki.getKartName()).encodeString(rki.getCountryId()); peer->sendPacket(ns, true/*reliable*/); delete ns; } // handleKartInfo //----------------------------------------------------------------------------- /** Client if currently in-game (including spectator) wants to go back to * lobby. */ void ServerLobby::clientInGameWantsToBackLobby(Event* event) { World* w = World::getWorld(); std::shared_ptr peer = event->getPeerSP(); if (!w || !worldIsActive() || peer->isWaitingForGame()) { Log::warn("ServerLobby", "%s try to leave the game at wrong time.", peer->getAddress().toString().c_str()); return; } for (const int id : peer->getAvailableKartIDs()) { RemoteKartInfo& rki = race_manager->getKartInfo(id); if (rki.getHostId() == peer->getHostId()) { Log::info("ServerLobby", "%s left the game with kart id %d.", peer->getAddress().toString().c_str(), id); rki.setNetworkPlayerProfile( std::shared_ptr()); } else { Log::error("ServerLobby", "%s doesn't exist anymore in server.", peer->getAddress().toString().c_str()); } } NetworkItemManager* nim = dynamic_cast(ItemManager::get()); assert(nim); nim->erasePeerInGame(peer); m_peers_ready.erase(peer); peer->setWaitingForGame(true); peer->setSpectator(false); NetworkString* reset = getNetworkString(2); reset->setSynchronous(true); reset->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); peer->sendPacket(reset, /*reliable*/true); delete reset; updatePlayerList(); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); m_game_setup->addServerInfo(server_info); peer->sendPacket(server_info, /*reliable*/true); delete server_info; } // clientInGameWantsToBackLobby //----------------------------------------------------------------------------- /** Client if currently select assets wants to go back to lobby. */ void ServerLobby::clientSelectingAssetsWantsToBackLobby(Event* event) { std::shared_ptr peer = event->getPeerSP(); if (m_state.load() != SELECTING || peer->isWaitingForGame()) { Log::warn("ServerLobby", "%s try to leave selecting assets at wrong time.", peer->getAddress().toString().c_str()); return; } m_peers_ready.erase(peer); peer->setWaitingForGame(true); peer->setSpectator(false); NetworkString* reset = getNetworkString(2); reset->setSynchronous(true); reset->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); peer->sendPacket(reset, /*reliable*/true); delete reset; updatePlayerList(); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); m_game_setup->addServerInfo(server_info); peer->sendPacket(server_info, /*reliable*/true); delete server_info; } // clientSelectingAssetsWantsToBackLobby //----------------------------------------------------------------------------- void ServerLobby::saveInitialItems() { m_items_complete_state->getBuffer().clear(); m_items_complete_state->reset(); NetworkItemManager* nim = dynamic_cast(ItemManager::get()); assert(nim); nim->saveCompleteState(m_items_complete_state); } // saveInitialItems