diff --git a/data/commands.xml b/data/commands.xml index 40ae715f758..7dec51574fe 100644 --- a/data/commands.xml +++ b/data/commands.xml @@ -1223,6 +1223,13 @@ here later. For now, please refer to the code for the strings being used. --> permissions="UP_HAMMER" state-scope="SS_ALWAYS" /> + (); - if (sl && - sl->getCurrentState() >= ServerLobby::WAITING_FOR_START_GAME) + if (sl && sl->isPastRegistrationPhase()) { m_port = STKHost::get()->getPrivatePort(); m_server_online_id = sl->getServerIdOnline(); diff --git a/src/network/protocol.cpp b/src/network/protocol.cpp index 0a97e0d0dda..09726300200 100644 --- a/src/network/protocol.cpp +++ b/src/network/protocol.cpp @@ -24,11 +24,7 @@ #include "network/stk_peer.hpp" /** \brief Constructor - * Sets the basic protocol parameters, as the callback object and the - * protocol type. - * \param callback_object The callback object that will be used by the - * protocol. Protocols that do not use callback objects must set - * it to NULL. + * Sets the protocol type. * \param type The type of the protocol. */ Protocol::Protocol(ProtocolType type) diff --git a/src/network/protocol.hpp b/src/network/protocol.hpp index aed9b98b468..a3d6b8ec45a 100644 --- a/src/network/protocol.hpp +++ b/src/network/protocol.hpp @@ -51,32 +51,9 @@ enum ProtocolType }; // ProtocolType // ---------------------------------------------------------------------------- -/** \enum ProtocolState - * \brief Defines the three states that a protocol can have. - */ -enum ProtocolState -{ - PROTOCOL_STATE_INITIALISING, //!< The protocol is waiting to be started - PROTOCOL_STATE_RUNNING, //!< The protocol is being updated everytime. - PROTOCOL_STATE_PAUSED, //!< The protocol is paused. - PROTOCOL_STATE_TERMINATED //!< The protocol is terminated/does not exist. -}; // ProtocolState class Protocol; -// ============================================================================* -/** \class CallbackObject - * \brief Class that must be inherited to pass objects to protocols. - */ -class CallbackObject -{ -public: - CallbackObject() {} - virtual ~CallbackObject() {} - - virtual void callback(Protocol *protocol) = 0; -}; // CallbackObject - // ============================================================================ /** \class Protocol * \brief Abstract class used to define the global protocol functions. @@ -96,7 +73,7 @@ class Protocol : public std::enable_shared_from_this, /** True if this protocol should receive connection events. */ bool m_handle_connections; - /** TRue if this protocol should recceiver disconnection events. */ + /** True if this protocol should receive disconnection events. */ bool m_handle_disconnections; public: Protocol(ProtocolType type); diff --git a/src/network/protocol_manager.cpp b/src/network/protocol_manager.cpp index faa8a319779..56c01a4d127 100644 --- a/src/network/protocol_manager.cpp +++ b/src/network/protocol_manager.cpp @@ -87,9 +87,7 @@ std::shared_ptr ProtocolManager::createInstance() auto sl = LobbyProtocol::get(); if (sl) { - ServerLobby::ServerState ss = sl->getCurrentState(); - if (!(ss >= ServerLobby::WAIT_FOR_WORLD_LOADED && - ss <= ServerLobby::RACING)) + if (sl->canIgnoreControllerEvents()) { delete event_top; continue; @@ -297,7 +295,7 @@ bool ProtocolManager::OneProtocolType::notifyEvent(Event *event) if (m_protocols.empty()) return false; // Either all protocols of a certain type handle connects, or none. - // So we tet only one of them + // So we test only one of them if (event->getType() == EVENT_TYPE_CONNECTED && !m_protocols[0]->handleConnects()) return false; if (event->getType() == EVENT_TYPE_DISCONNECTED && diff --git a/src/network/protocols/client_lobby.hpp b/src/network/protocols/client_lobby.hpp index 5f50dd8b0de..52ad0033076 100644 --- a/src/network/protocols/client_lobby.hpp +++ b/src/network/protocols/client_lobby.hpp @@ -168,8 +168,6 @@ class ClientLobby : public LobbyProtocol virtual void setup() OVERRIDE; virtual void update(int ticks) OVERRIDE; virtual void asynchronousUpdate() OVERRIDE {} - virtual bool allPlayersReady() const OVERRIDE - { return m_state.load() >= RACING; } bool waitingForServerRespond() const { return m_state.load() == REQUESTING_CONNECTION; } bool isLobbyReady() const { return !m_first_connect; } diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 1052eac9602..2597bc3b287 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -612,6 +612,7 @@ void CommandManager::initCommands() applyFunctionIfPossible("network", &CM::process_net); applyFunctionIfPossible("everynet", &CM::process_everynet); applyFunctionIfPossible("temp", &CM::process_temp250318); + applyFunctionIfPossible("room", &CM::process_room); applyFunctionIfPossible("addondownloadprogress", &CM::special); applyFunctionIfPossible("stopaddondownload", &CM::special); @@ -1355,8 +1356,7 @@ void CommandManager::process_spectate(Context& context) } if (value >= 1) { - if (getLobby()->isChildProcess() && - getLobby()->isClientServerHost(acting_peer)) + if (getLobby()->isChildProcess() && getLobby()->isClientServerHost(acting_peer)) { context.say("Graphical client server cannot spectate"); return; @@ -3877,6 +3877,49 @@ void CommandManager::process_temp250318(Context& context) } // process_temp250318 // ======================================================================== +void CommandManager::process_room(Context& context) +{ + auto& argv = context.m_argv; + auto acting_peer = context.actingPeer(); + + if (argv.size() < 3) + { + error(context); + return; + } + + if (!validate(context, 1, TFT_PRESENT_USERS, false, false)) + return; + + std::string player_name = argv[1]; + std::shared_ptr player_peer = STKHost::get()->findPeerByName( + StringUtils::utf8ToWide(player_name)); + if (player_name.empty() || !player_peer) + { + error(context); + return; + } + + int room = -2; + + if (!StringUtils::parseString(argv[2], &room) || room < -1 || room >= 256) + { + error(context); + return; + } + + player_peer->setRoomNumber(room); + + auto npp = player_peer->getMainProfile(); + context.say(StringUtils::insertValues("Moved player %s to room %d", + getLobby()->encodeProfileNameForPeer(npp, acting_peer.get()).c_str(), + room + )); + + getLobby()->updatePlayerList(); +} // process_room +// ======================================================================== + void CommandManager::special(Context& context) { auto command = context.command(); diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 848f2e5fc5d..fc78f02ff52 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -370,6 +370,7 @@ class CommandManager: public LobbyContextComponent void process_countteams(Context& context); void process_net(Context& context); void process_everynet(Context& context); + void process_room(Context& context); // Temporary command void process_temp250318(Context& context); diff --git a/src/network/protocols/lobby_protocol.hpp b/src/network/protocols/lobby_protocol.hpp index 2426a8e5c40..5bb02a1f02d 100644 --- a/src/network/protocols/lobby_protocol.hpp +++ b/src/network/protocols/lobby_protocol.hpp @@ -149,7 +149,6 @@ class LobbyProtocol : public Protocol virtual void update(int ticks) = 0; virtual void finishedLoadingWorld() = 0; virtual void loadWorld(); - virtual bool allPlayersReady() const = 0; virtual bool isRacing() const = 0; void startVotingPeriod(float max_time); float getRemainingVotingTime(); diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp new file mode 100644 index 00000000000..8591bd31245 --- /dev/null +++ b/src/network/protocols/playing_room.cpp @@ -0,0 +1,2384 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// 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/playing_room.hpp" + +#include "utils/log.hpp" +#include "network/server_config.hpp" +#include "network/stk_host.hpp" +#include "network/stk_peer.hpp" +#include "network/network_player_profile.hpp" +#include "network/network_string.hpp" +#include "utils/game_info.hpp" +#include "items/network_item_manager.hpp" +#include "network/game_setup.hpp" +#include "utils/lobby_asset_manager.hpp" +#include "config/stk_config.hpp" +#include "utils/lobby_settings.hpp" +#include "karts/abstract_kart.hpp" +#include "modes/linear_world.hpp" +#include "modes/world.hpp" +#include "modes/free_for_all.hpp" +#include "tracks/track_manager.hpp" +#include "network/peer_vote.hpp" +#include "network/protocol_manager.hpp" +#include "tracks/check_manager.hpp" +#include "network/socket_address.hpp" +#include "network/race_event_manager.hpp" +#include "tracks/track.hpp" +#include "network/event.hpp" +#include "network/protocols/command_permissions.hpp" +#include "utils/tournament.hpp" +#include "utils/crown_manager.hpp" +#include "utils/communication.hpp" +#include "network/protocols/server_lobby.hpp" +#include "network/protocols/command_manager.hpp" +#include "utils/team_manager.hpp" +#include "utils/lobby_queues.hpp" +#include "utils/kart_elimination.hpp" +#include "network/network_config.hpp" +#include "utils/lobby_gp_manager.hpp" +#include "network/protocols/game_protocol.hpp" +#include "utils/hit_processor.hpp" +#include "modes/capture_the_flag.hpp" +#include "utils/chat_manager.hpp" +#include "network/protocols/game_events_protocol.hpp" + +namespace +{ + void encodePlayers(BareNetworkString* bns, + std::vector >& players, + const std::shared_ptr& decorator) + { + bns->addUInt8((uint8_t)players.size()); + for (unsigned i = 0; i < players.size(); i++) + { + std::shared_ptr& player = players[i]; + bns->encodeString(player->getDecoratedName(decorator)) + .addUInt32(player->getHostId()) + .addFloat(player->getDefaultKartColor()) + .addUInt32(player->getOnlineId()) + .addUInt8(player->getHandicap()) + .addUInt8(player->getLocalPlayerId()) + .addUInt8(player->getTeam()) + .encodeString(player->getCountryCode()); + bns->encodeString(player->getKartName()); + } + } // encodePlayers + //------------------------------------------------------------------------- + + /** Returns true if world is active for clients to live join, spectate or + * going back to lobby live + */ + bool worldIsActive() + { + return World::getWorld() && RaceEventManager::get()->isRunning() && + !RaceEventManager::get()->isRaceOver() && + World::getWorld()->getPhase() == WorldStatus::RACE_PHASE; + } // worldIsActive + //------------------------------------------------------------------------- + + NetworkString* getNetworkString(size_t capacity = 16) + { + return new NetworkString(PROTOCOL_LOBBY_ROOM, capacity); + } // getNetworkString + //------------------------------------------------------------------------- + + /** Get a list of current ingame players for live join or spectate. + */ + std::vector > getLivePlayers() + { + std::vector > players; + for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) + { + const RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); + std::shared_ptr player = + rki.getNetworkPlayerProfile().lock(); + if (!player) + { + if (RaceManager::get()->modeHasLaps()) + { + player = std::make_shared( + nullptr, rki.getPlayerName(), + std::numeric_limits::max(), + rki.getDefaultKartColor(), + rki.getOnlineId(), rki.getHandicap(), + rki.getLocalPlayerId(), KART_TEAM_NONE, + rki.getCountryCode()); + player->setKartName(rki.getKartName()); + } + else + { + player = NetworkPlayerProfile::getReservedProfile( + RaceManager::get()->getMinorMode() == + RaceManager::MINOR_MODE_FREE_FOR_ALL ? + KART_TEAM_NONE : rki.getKartTeam()); + } + } + players.push_back(player); + } + return players; + } // getLivePlayers + //------------------------------------------------------------------------- + +} // namespace +//============================================================================= + +PlayingRoom::PlayingRoom() +{ + m_lobby_players.store(0); + m_current_ai_count.store(0); + m_rs_state.store(RS_NONE); + m_server_owner_id.store(-1); + m_play_state = WAITING_FOR_START_GAME; + m_items_complete_state = new BareNetworkString(); + m_difficulty.store(ServerConfig::m_server_difficulty); + m_game_mode.store(ServerConfig::m_server_mode); + m_reset_to_default_mode_later.store(false); + + m_game_info = {}; +} // PlayingRoom +//----------------------------------------------------------------------------- + +PlayingRoom::~PlayingRoom() +{ + delete m_items_complete_state; +} // ~PlayingRoom +//----------------------------------------------------------------------------- + +[[deprecated("STKHost and GameSetup are used in a room method.")]] +void PlayingRoom::setup() +{ + m_item_seed = 0; + m_client_starting_time = 0; + m_ai_count = 0; + + auto players = STKHost::get()->getPlayersForNewGame(); + auto game_setup = getGameSetupFromCtx(); + if (game_setup->isGrandPrix() && !game_setup->isGrandPrixStarted()) + { + for (auto player : players) + player->resetGrandPrixData(); + } + if (!game_setup->isGrandPrix() || !game_setup->isGrandPrixStarted()) + { + for (auto player : players) + player->setKartName(""); + } + if (auto ai = m_ai_peer.lock()) + { + for (auto player : ai->getPlayerProfiles()) + player->setKartName(""); + } + for (auto ai : m_ai_profiles) + ai->setKartName(""); + + updateMapsForMode(); + + m_server_has_loaded_world.store(false); + // Initialise the data structures to detect if all clients and + // the server are ready: + resetPeersReady(); + setInfiniteTimeout(); + m_server_started_at = m_server_delay = 0; + m_game_info = {}; + + Log::info("PlayingRoom", "Resetting room to its initial state."); +} // PlayingRoom::setup() +//----------------------------------------------------------------------------- + +void PlayingRoom::updateRoom(int ticks) +{ + World* w = World::getWorld(); + bool world_started = m_play_state.load() >= WAIT_FOR_WORLD_LOADED && + m_play_state.load() <= RACING && m_server_has_loaded_world.load(); + bool all_players_in_world_disconnected = (w != NULL && world_started); + int sec = getSettings()->getKickIdlePlayerSeconds(); + if (world_started) + { + for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) + { + RemoteKartInfo& rki = RaceManager::get()->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 (!peer->isAIPeer() && + sec > 0 && peer->idleForSeconds() > sec && + !peer->isDisconnected() && NetworkConfig::get()->isWAN()) + { + if (w && w->getKart(i)->hasFinishedRace()) + continue; + // Don't kick in game GUI server host so he can idle in game + if (getLobby()->isChildClientServerHost(peer)) + continue; + Log::info("ServerLobby", "%s %s has been idle ingame for more than" + " %d seconds, kick.", + peer->getAddress().toString().c_str(), + StringUtils::wideToUtf8(rki.getPlayerName()).c_str(), sec); + peer->kick(); + } + if (getHitProcessor()->isAntiTrollActive() && !peer->isAIPeer()) + { + // for all human players + // if they troll, kick them + LinearWorld *lin_world = dynamic_cast(w); + if (lin_world) { + // check warn level for each player + switch(lin_world->getWarnLevel(i)) + { + case 0: + break; + case 1: + { + Comm::sendStringToPeer(peer, getSettings()->getTrollWarnMsg()); + std::string player_name = peer->getMainName(); + Log::info("ServerLobby-AntiTroll", "Sent WARNING to %s", player_name.c_str()); + break; + } + default: + { + std::string player_name = peer->getMainName(); + Log::info("ServerLobby-AntiTroll", "KICKING %s", player_name.c_str()); + peer->kick(); + break; + } + } + } + } + getHitProcessor()->punishSwatterHits(); + } + } + if (m_play_state.load() == WAITING_FOR_START_GAME) { + sec = getSettings()->getKickIdleLobbyPlayerSeconds(); + auto peers = STKHost::get()->getPeers(); + for (auto peer: peers) + { + if (!peer->isAIPeer() && + sec > 0 && peer->idleForSeconds() > sec && + !peer->isDisconnected() && NetworkConfig::get()->isWAN()) + { + // Don't kick in game GUI server host so he can idle in the lobby + if (getLobby()->isChildClientServerHost(peer)) + continue; + std::string peer_name = ""; + if (peer->hasPlayerProfiles()) + peer_name = peer->getMainName().c_str(); + Log::info("ServerLobby", "%s %s has been idle on the server for " + "more than %d seconds, kick.", + peer->getAddress().toString().c_str(), peer_name.c_str(), sec); + peer->kick(); + } + } + } + + if (w) + setGameStartedProgress(w->getGameStartedProgress()); + else + resetGameStartedProgress(); + + if (w && w->getPhase() == World::RACE_PHASE) + storePlayingTrack(RaceManager::get()->getTrackName()); + else + storePlayingTrack(""); + + // Reset server to initial state if no more connected players + if (m_rs_state.load() == RS_WAITING) + { + if ((RaceEventManager::get() && + !RaceEventManager::get()->protocolStopped()) || + !GameProtocol::emptyInstance()) + return; + + exitGameState(); + m_rs_state.store(RS_ASYNC_RESET); + } + + STKHost::get()->updatePlayers(); + if (m_rs_state.load() == RS_NONE && + (m_play_state.load() > WAITING_FOR_START_GAME/* || + m_game_setup->isGrandPrixStarted()*/) && + (STKHost::get()->getPlayersInGame() == 0 || + all_players_in_world_disconnected)) + { + if (RaceEventManager::get() && + RaceEventManager::get()->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); + Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); + delete back_to_lobby; + + RaceEventManager::get()->stop(); + RaceEventManager::get()->getProtocol()->requestTerminate(); + GameProtocol::lock()->requestTerminate(); + } + else if (auto ai = m_ai_peer.lock()) + { + // Reset AI peer for empty server, which will delete world + NetworkString* back_to_lobby = getNetworkString(2); + back_to_lobby->setSynchronous(true); + back_to_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); + ai->sendPacket(back_to_lobby, PRM_RELIABLE); + delete back_to_lobby; + } + if (all_players_in_world_disconnected) + m_game_setup->cancelOneRace(); + resetVotingTime(); + // m_game_setup->cancelOneRace(); + //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 (getSettings()->isRanked() && + m_play_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); + Comm::sendMessageToPeers(back_lobby, PRM_RELIABLE); + delete back_lobby; + resetVotingTime(); + // m_game_setup->cancelOneRace(); + //m_game_setup->stopGrandPrix(); + m_rs_state.store(RS_ASYNC_RESET); + } + + handlePlayerDisconnection(); + + switch (m_play_state.load()) + { + 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("ServerLobby", "Starting the race loading."); + // This will create the world instance, i.e. load track and karts + loadWorld(); + getGPManager()->updateWorldScoring(); + getSettings()->updateWorldSettings(m_game_info); + m_play_state = WAIT_FOR_WORLD_LOADED; + break; + case RACING: + if (World::getWorld() && RaceEventManager::get() && + RaceEventManager::get()->isRunning()) + { + checkRaceFinished(); + } + break; + case WAIT_FOR_RACE_STOPPED: + if (!RaceEventManager::get()->protocolStopped() || + !GameProtocol::emptyInstance()) + return; + + // This will go back to lobby in server (and exit the current race) + exitGameState(); + // 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 + setTimeoutFromNow(15); + m_play_state = RESULT_DISPLAY; + Comm::sendMessageToPeers(m_result_ns, PRM_RELIABLE); + delete m_result_ns; + Log::info("ServerLobby", "End of game message sent"); + break; + case RESULT_DISPLAY: + if (checkPeersReady(true/*ignore_ai_peer*/, AFTER_GAME) || + isTimeoutExpired()) + { + // 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); + Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); + delete back_to_lobby; + m_rs_state.store(RS_ASYNC_RESET); + } + break; + } +} // updateRoom +//----------------------------------------------------------------------------- + +[[deprecated("Ranking should not be handled by a single room.")]] +void PlayingRoom::asynchronousUpdateRoom() +{ + if (m_rs_state.load() == RS_ASYNC_RESET) + { + resetVotingTime(); + resetServer(); + m_rs_state.store(RS_NONE); + } + + getChatManager()->clearAllExpiredWeakPtrs(); + + // Check if server owner has left + updateServerOwner(); + + // Should be done in SL. + if (getSettings()->isRanked() && m_play_state.load() == WAITING_FOR_START_GAME) + m_ranking->cleanup(); + + // Should be done in SL. + if (!getSettings()->isLegacyGPMode() || m_play_state.load() == WAITING_FOR_START_GAME) + { + // Only poll the STK server if server has been registered. + if (m_server_id_online.load() != 0 && + m_play_state.load() != REGISTER_SELF_ADDRESS) + checkIncomingConnectionRequests(); + handlePendingConnection(); + } + + // Should be done in SL. + if (m_server_id_online.load() != 0 && + !getSettings()->isLegacyGPMode() && + StkTime::getMonoTimeMs() > m_last_unsuccess_poll_time && + StkTime::getMonoTimeMs() > m_last_success_poll_time.load() + 30000) + { + Log::warn("ServerLobby", "Trying auto server recovery."); + // For auto server recovery wait 3 seconds for next try + m_last_unsuccess_poll_time = StkTime::getMonoTimeMs() + 3000; + registerServer(false/*first_time*/); + } + + switch (m_play_state.load()) + { + case WAITING_FOR_START_GAME: + { + if (getCrownManager()->isOwnerLess()) + { + // Ensure that a game can auto-start if the server meets the config's starting limit or if it's already full. + int starting_limit = std::min((int)getSettings()->getMinStartGamePlayers(), (int)getSettings()->getServerMaxPlayers()); + unsigned current_max_players_in_game = getSettings()->getCurrentMaxPlayersInGame(); + if (current_max_players_in_game > 0) // 0 here means it's not the limit + starting_limit = std::min(starting_limit, (int)current_max_players_in_game); + + unsigned players = 0; + STKHost::get()->updatePlayers(&players); + if (((int)players >= starting_limit || + getGameSetupFromCtx()->isGrandPrixStarted()) && + isInfiniteTimeout()) + { + if (getSettings()->getStartGameCounter() >= -1e-5) + { + setTimeoutFromNow(getSettings()->getStartGameCounter()); + } + else + { + setInfiniteTimeout(); + } + } + else if ((int)players < starting_limit && + !getGameSetupFromCtx()->isGrandPrixStarted()) + { + resetPeersReady(); + if (!isInfiniteTimeout()) + updatePlayerList(); + + setInfiniteTimeout(); + } + bool forbid_starting = false; + if (isTournament() && getTournament()->forbidStarting()) + forbid_starting = true; + + bool timer_finished = (!forbid_starting && isTimeoutExpired()); + bool players_ready = (checkPeersReady(true/*ignore_ai_peer*/, BEFORE_SELECTION) + && (int)players >= starting_limit); + + if (timer_finished || (players_ready && !getSettings()->isCooldown())) + { + resetPeersReady(); + startSelection(); + return; + } + } + 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 + + // maybe this is not the best place for this? + getHitProcessor()->resetLastHits(); + + 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 && getSettings()->isRanked()) || + 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( + getSettings()->hasAiHandling() && m_ai_count == 0/*ignore_ai_peer*/, LOADING_WORLD)) + 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 && getSettings()->isRanked()) || + player_in_game == 0) + { + resetVotingTime(); + return; + } + + PeerVote winner_vote; + getSettings()->resetWinnerPeerId(); + bool go_on_race = false; + if (getSettings()->hasTrackVoting()) + go_on_race = handleAllVotes(&winner_vote); + else if (/*m_game_setup->isGrandPrixStarted() || */isVotingOver()) + { + winner_vote = getSettings()->getDefaultVote(); + go_on_race = true; + } + if (go_on_race) + { + getSettings()->applyRestrictionsOnWinnerVote(&winner_vote); + getSettings()->setDefaultVote(winner_vote); + m_item_seed = (uint32_t)StkTime::getTimeSinceEpoch(); + ItemManager::updateRandomSeed(m_item_seed); + float extra_seconds = 0.0f; + if (isTournament()) + extra_seconds = getTournament()->getExtraSeconds(); + m_game_setup->setRace(winner_vote, extra_seconds); + + // For spectators that don't have the track, remember their + // spectate mode and don't load the track + std::string track_name = winner_vote.m_track_name; + if (isTournament()) + getTournament()->fillNextArena(track_name); + + auto peers = STKHost::get()->getPeers(); + std::map, + AlwaysSpectateMode> previous_spectate_mode; + for (auto peer : peers) + { + if (peer->alwaysSpectate() && (!peer->alwaysSpectateForReal() || + peer->getClientAssets().second.count(track_name) == 0)) + { + previous_spectate_mode[peer] = peer->getAlwaysSpectate(); + peer->setAlwaysSpectate(ASM_NONE); + peer->setWaitingForGame(true); + m_peers_ready.erase(peer); + } + } + bool has_always_on_spectators = false; + auto players = STKHost::get() + ->getPlayersForNewGame(&has_always_on_spectators); + for (auto& p: previous_spectate_mode) + if (p.first) + p.first->setAlwaysSpectate(p.second); + auto ai_instance = m_ai_peer.lock(); + if (supportsAI()) + { + if (ai_instance) + { + auto ai_profiles = ai_instance->getPlayerProfiles(); + if (m_ai_count > 0) + { + ai_profiles.resize(m_ai_count); + players.insert(players.end(), ai_profiles.begin(), + ai_profiles.end()); + } + } + else if (!m_ai_profiles.empty()) + { + players.insert(players.end(), m_ai_profiles.begin(), + m_ai_profiles.end()); + } + } + m_game_setup->sortPlayersForGrandPrix(players, getSettings()->isGPGridShuffled()); + 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); + } + getSettings()->getLobbyHitCaptureLimit(); + + // 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++) + { + std::string current_kart = players[i]->getKartName(); + if (!players[i]->getPeer().get()) + continue; + if (getQueues()->areKartFiltersIgnoringKarts()) + current_kart = ""; + std::string name = StringUtils::wideToUtf8(players[i]->getName()); + // Note 1: setKartName also resets KartData, and should be called + // only if current kart name is not suitable. + // Note 2: filters only support standard karts for now, so GKFBKC + // cannot return an addon; when addons are supported, this part of + // code will also have to provide kart data (or setKartName has to + // set the correct hitbox itself). + std::string new_kart = getAssetManager()->getKartForBadKartChoice( + players[i]->getPeer(), name, current_kart); + if (new_kart != current_kart) + { + // Filters only support standard karts for now, but when they + // start supporting addons, probably type should not be empty + players[i]->setKartName(new_kart); + KartData kart_data; + setKartDataProperly(kart_data, new_kart, players[i], ""); + players[i]->setKartData(kart_data); + } + } + + auto& stk_config = STKConfig::get(); + + NetworkString* load_world_message = getLoadWorldMessage(players, + false/*live_join*/); + m_game_setup->setHitCaptureTime(getSettings()->getBattleHitCaptureLimit(), + getSettings()->getBattleTimeLimit()); + uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks( + getSettings()->getFlagReturnTimeout()); + RaceManager::get()->setHitProcessor(getHitProcessor()); + RaceManager::get()->setFlagReturnTicks(flag_return_time); + if (getSettings()->isRecordingReplays() && getSettings()->hasConsentOnReplays() && + (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL || + RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE)) + RaceManager::get()->setRecordRace(true); + uint16_t flag_deactivated_time = (uint16_t)STKConfig::get()->time2Ticks( + getSettings()->getFlagDeactivatedTime()); + RaceManager::get()->setFlagDeactivatedTicks(flag_deactivated_time); + configRemoteKart(players, 0); + + // Reset for next state usage + resetPeersReady(); + + m_play_state = LOAD_WORLD; + Comm::sendMessageToPeers(load_world_message); + // updatePlayerList so the in lobby players (if any) can see always + // spectators join the game + if (has_always_on_spectators || !previous_spectate_mode.empty()) + updatePlayerList(); + delete load_world_message; + + if (RaceManager::get()->getMinorMode() == + RaceManager::MINOR_MODE_SOCCER) + { + for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) + { + if (auto player = + RaceManager::get()->getKartInfo(i).getNetworkPlayerProfile().lock()) + { + std::string username = StringUtils::wideToUtf8(player->getName()); + if (username.empty()) + continue; + Log::info("ServerLobby", "SoccerMatchLog: There is a player %s.", + username.c_str()); + } + } + } + m_game_info = std::make_shared(); + m_game_info->setContext(m_lobby_context.get()); + m_game_info->fillFromRaceManager(); + } + break; + } + default: + break; + } +} // asynchronousUpdateRoom +//----------------------------------------------------------------------------- + +/** Calls the corresponding method from LobbyAssetManager + * whenever server is reset or game mode is changed. */ +[[deprecated("Asset managers should be separate for different rooms.")]] +void PlayingRoom::updateMapsForMode() +{ + getAssetManager()->updateMapsForMode( + ServerConfig::getLocalGameMode(m_game_mode.load()).first + ); +} // updateMapsForMode +//----------------------------------------------------------------------------- + +NetworkString* PlayingRoom::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); + getSettings()->encodeDefaultVote(load_world_message); + load_world_message->addUInt8(live_join ? 1 : 0); + encodePlayers(load_world_message, players, getLobby()->getNameDecorator()); + load_world_message->addUInt32(m_item_seed); + if (RaceManager::get()->isBattleMode()) + { + auto& stk_config = STKConfig::get(); + + load_world_message->addUInt32(getSettings()->getBattleHitCaptureLimit()) + .addFloat(getSettings()->getBattleTimeLimit()); + uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks( + getSettings()->getFlagReturnTimeout()); + load_world_message->addUInt16(flag_return_time); + uint16_t flag_deactivated_time = (uint16_t)stk_config->time2Ticks( + getSettings()->getFlagDeactivatedTime()); + load_world_message->addUInt16(flag_deactivated_time); + } + for (unsigned i = 0; i < players.size(); i++) + players[i]->getKartData().encode(load_world_message); + return load_world_message; +} // getLoadWorldMessage + +//----------------------------------------------------------------------------- +/** Returns true if server can be live joined or spectating + */ +bool PlayingRoom::canLiveJoinNow() const +{ + bool live_join = getSettings()->isLivePlayers() && worldIsActive(); + if (!live_join) + return false; + if (RaceManager::get()->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 leader_distance = w->getOverallDistance( + fastest_kart->getWorldKartId()); + float total_distance = + Track::getCurrentTrack()->getTrackLength() * + (float)RaceManager::get()->getNumLaps(); + // standard version uses (leader_distance / total_distance > 0.9f) + // TODO: allow switching + if (total_distance - leader_distance < 250.0) + return false; + } + return live_join; +} // canLiveJoinNow + +//----------------------------------------------------------------------------- +/** \ref STKPeer peer will be reset back to the lobby with reason + * \ref BackLobbyReason blr + */ +void PlayingRoom::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr) +{ + NetworkString* reset = getNetworkString(2); + reset->setSynchronous(true); + reset->addUInt8(LE_BACK_LOBBY).addUInt8(blr); + peer->sendPacket(reset, PRM_RELIABLE); + delete reset; + + getLobby()->updatePlayerList(); + + NetworkString* server_info = getNetworkString(); + server_info->setSynchronous(true); + server_info->addUInt8(LE_SERVER_INFO); + getGameSetupFromCtx()->addServerInfo(server_info); + peer->sendPacket(server_info, PRM_RELIABLE); + delete server_info; + + peer->updateLastActivity(); +} // rejectLiveJoin + +//----------------------------------------------------------------------------- +/** This message is like kartSelectionRequested, but it will send the peer + * load world message if he can join the current started game. + */ +void PlayingRoom::liveJoinRequest(Event* event) +{ + // I moved some data getters ahead of some returns. This should be fine + // in general, but you know what caused it if smth goes wrong. + + std::shared_ptr peer = event->getPeerSP(); + const NetworkString& data = event->data(); + bool spectator = data.getUInt8() == 1; + + if (!canLiveJoinNow()) + { + rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); + return; + } + if (RaceManager::get()->modeHasLaps() && !spectator) + { + // No live join for linear race + rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); + return; + } + + peer->clearAvailableKartIDs(); + if (!spectator) + { + auto& spectators_by_limit = getCrownManager()->getSpectatorsByLimit(); + 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()) || + (spectators_by_limit.find(peer) != spectators_by_limit.end())) + { + for (unsigned i = 0; i < peer->getPlayerProfiles().size(); i++) + peer->getPlayerProfiles()[i]->setKartName(""); + for (unsigned i = 0; i < used_id.size(); i++) + { + RemoteKartInfo& rki = RaceManager::get()->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, PRM_RELIABLE); + delete load_world_message; + peer->updateLastActivity(); +} // liveJoinRequest + +//----------------------------------------------------------------------------- +/** Finally put the kart in the world and inform client the current world + * status, (including current confirmed item state, kart scores...) + */ +void PlayingRoom::finishedLoadingLiveJoinClient(Event* event) +{ + std::shared_ptr peer = event->getPeerSP(); + if (!canLiveJoinNow()) + { + rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); + return; + } + bool live_joined_in_time = true; + for (const int id : peer->getAvailableKartIDs()) + { + const RemoteKartInfo& rki = RaceManager::get()->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, BLR_NO_GAME_FOR_LIVE_JOIN); + return; + } + World* w = World::getWorld(); + assert(w); + + uint64_t live_join_start_time = STKHost::get()->getNetworkTimer(); + + auto& stk_config = STKConfig::get(); + + // Instead of using getTicksSinceStart we caculate the current world ticks + // only from network timer, because if the server hangs in between the + // world ticks may not be up to date + // 2000 is the time for ready set, remove 3 ticks after for minor + // correction (make it more looks like getTicksSinceStart if server has no + // hang + int cur_world_ticks = stk_config->time2Ticks( + (live_join_start_time - m_server_started_at - 2000) / 1000.f) - 3; + // Give 3 seconds for all peers to get new kart info + m_last_live_join_util_ticks = + cur_world_ticks + stk_config->time2Ticks(3.0f); + live_join_start_time -= m_server_delay; + live_join_start_time += 3000; + + bool spectator = false; + for (const int id : peer->getAvailableKartIDs()) + { + const RemoteKartInfo& rki = RaceManager::get()->getKartInfo(id); + int points = 0; + + if (m_game_info) + points = m_game_info->onLiveJoinedPlayer(id, rki, w); + else + Log::warn("ServerLobby", "GameInfo is not accessible??"); + + // If the mode is not battle/CTF, points are 0. + // I assume it's fine like that for now + World::getWorld()->addReservedKart(id, points); + 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)Track::getCurrentTrack()->getCheckManager()->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 + (Track::getCurrentTrack()->getItemManager()); + assert(nim); + nim->saveCompleteState(ns); + nim->addLiveJoinPeer(peer); + + w->saveCompleteState(ns, peer); + if (RaceManager::get()->supportsLiveJoining()) + { + // Only needed in non-racing mode as no need players can added after + // starting of race + std::vector > players = + getLivePlayers(); + encodePlayers(ns, players, getLobby()->getNameDecorator()); + for (unsigned i = 0; i < players.size(); i++) + players[i]->getKartData().encode(ns); + } + + m_peers_ready[peer] = false; + peer->setWaitingForGame(false); + peer->setSpectator(spectator); + + peer->sendPacket(ns, PRM_RELIABLE); + delete ns; + getLobby()->updatePlayerList(); + peer->updateLastActivity(); +} // finishedLoadingLiveJoinClient + +//----------------------------------------------------------------------------- +/** Instructs all clients to start the kart selection. If event is NULL, + * the command comes from the owner less server. + */ +void PlayingRoom::startSelection(const Event *event) +{ + bool need_to_update = false; + bool cooldown = getSettings()->isCooldown(); + + if (event != NULL) + { + if (m_play_state.load() != WAITING_FOR_START_GAME) + { + Log::warn("ServerLobby", + "Received startSelection while being in state %d.", + m_play_state.load()); + return; + } + if (getCrownManager()->isSleepingServer()) + { + Log::warn("ServerLobby", + "An attempt to start a race on a sleeping server."); + return; + } + auto peer = event->getPeerSP(); + if (getCrownManager()->isOwnerLess()) + { + if (!getSettings()->isAllowedToStart()) + { + Comm::sendStringToPeer(peer, "Starting the game is forbidden by server owner"); + return; + } + if (!getCrownManager()->canRace(peer)) + { + Comm::sendStringToPeer(peer, "You cannot play so pressing ready has no action"); + return; + } + else + { + m_peers_ready.at(event->getPeerSP()) = + !m_peers_ready.at(event->getPeerSP()); + getLobby()->updatePlayerList(); + return; + } + } + if (!getSettings()->isAllowedToStart()) + { + Comm::sendStringToPeer(peer, "Starting the game is forbidden by server owner"); + return; + } + if (!hasHostRights(peer)) + { + auto argv = getCommandManager()->getCurrentArgv(); + if (argv.empty() || argv[0] != "start") { + Log::warn("ServerLobby", + "Client %d is not authorised to start selection.", + event->getPeer()->getHostId()); + return; + } + } + if (cooldown) + { + Comm::sendStringToPeer(peer, "Starting the game is forbidden by server cooldown"); + return; + } + } else { + // Even if cooldown is bigger than server timeout, start happens upon + // timeout expiration. If all players clicked before timeout, no start + // happens - it's handled in another place + if (!getSettings()->isAllowedToStart()) + { + // Produce no log spam + return; + } + } + + if (!getCrownManager()->isOwnerLess() && getSettings()->hasTeamChoosing() && + !getSettings()->hasFreeTeams() && RaceManager::get()->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, PRM_RELIABLE); + delete bt; + } + return; + } + } + + unsigned max_player = 0; + STKHost::get()->updatePlayers(&max_player); + auto peers = STKHost::get()->getPeers(); + std::set> always_spectate_peers; + + // Set late coming player to spectate if too many players + auto& spectators_by_limit = getCrownManager()->getSpectatorsByLimit(); + if (spectators_by_limit.size() == peers.size()) + { + // produce no log spam for now + // Log::error("ServerLobby", "Too many players and cannot set " + // "spectate for late coming players!"); + return; + } + for (auto &peer : spectators_by_limit) + { + peer->setAlwaysSpectate(ASM_FULL); + peer->setWaitingForGame(true); + always_spectate_peers.insert(peer); + } + + // Remove karts / tracks from server that are not supported on all clients + std::vector> erasingPeers; + bool has_peer_plays_game = false; + for (auto peer : peers) + { + if (!peer->isValidated() || peer->isWaitingForGame()) + continue; + bool can_race = getCrownManager()->canRace(peer); + if (!can_race && !peer->alwaysSpectate()) + { + peer->setAlwaysSpectate(ASM_FULL); + peer->setWaitingForGame(true); + m_peers_ready.erase(peer); + need_to_update = true; + always_spectate_peers.insert(peer); + continue; + } + else if (peer->alwaysSpectate()) + { + always_spectate_peers.insert(peer); + continue; + } + // I might introduce an extra use for a peer that leaves at the same moment. Please investigate later. + erasingPeers.push_back(peer); + if (!peer->isAIPeer()) + has_peer_plays_game = true; + } + + // kimden thinks if someone wants to race he should disable spectating + // // Disable always spectate peers if no players join the game + if (!has_peer_plays_game) + { + if (event) + { + // inside if to not produce log spam for ownerless + Log::warn("ServerLobby", + "An attempt to start a game while no one can play."); + Comm::sendStringToPeer(event->getPeerSP(), "No one can play!"); + } + addWaitingPlayersToGame(); + return; + // for (std::shared_ptr peer : always_spectate_peers) + // peer->setAlwaysSpectate(ASM_NONE); + // always_spectate_peers.clear(); + } + else + { + // We make those always spectate peer waiting for game so it won't + // be able to vote, this will be reset in STKHost::getPlayersForNewGame + // This will also allow a correct number of in game players for max + // arena players handling + for (std::shared_ptr peer : always_spectate_peers) + peer->setWaitingForGame(true); + } + + getAssetManager()->eraseAssetsWithPeers(erasingPeers); + + max_player = 0; + STKHost::get()->updatePlayers(&max_player); + if (auto ai = m_ai_peer.lock()) + { + if (supportsAI()) + { + unsigned total_ai_available = + (unsigned)ai->getPlayerProfiles().size(); + m_ai_count = max_player > total_ai_available ? + 0 : total_ai_available - max_player + 1; + // Disable ai peer for this game + if (m_ai_count == 0) + ai->setValidated(false); + else + ai->setValidated(true); + } + else + { + ai->setValidated(false); + m_ai_count = 0; + } + } + else + m_ai_count = 0; + + if (!getAssetManager()->tryApplyingMapFilters()) + { + Log::error("ServerLobby", "No tracks for playing!"); + return; + } + + getSettings()->initializeDefaultVote(); + + getLobby()->unregisterServerForLegacyGPMode(); + + startVotingPeriod(getSettings()->getVotingTimeout()); + + peers = STKHost::get()->getPeers(); + for (auto& peer: peers) + { + if (peer->isDisconnected() && !peer->isValidated()) + continue; + if (!getCrownManager()->canRace(peer) || peer->isWaitingForGame()) + continue; // they are handled below + + 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(getSettings()->getVotingTimeout()) + .addUInt8(/*getGameSetupFromCtx()->isGrandPrixStarted() ? 1 : */0) + .addUInt8((!getSettings()->hasNoLapRestrictions() ? 1 : 0)) + .addUInt8(getSettings()->hasTrackVoting() ? 1 : 0); + + + std::set all_k = peer->getClientAssets().first; + std::string username = peer->getMainName(); + // std::string username = StringUtils::wideToUtf8(profile->getName()); + getAssetManager()->applyAllKartFilters(username, all_k); + + if (!getKartElimination()->getRemainingParticipants().empty() && getKartElimination()->getRemainingParticipants().count(username) == 0) + { + if (all_k.count(getKartElimination()->getKart())) + all_k = {getKartElimination()->getKart()}; + else + all_k = {}; + } + + getAssetManager()->encodePlayerKartsAndCommonMaps(ns, all_k); + + peer->sendPacket(ns, PRM_RELIABLE); + delete ns; + + if (getQueues()->areKartFiltersIgnoringKarts()) + Comm::sendStringToPeer(peer, "The server will ignore your kart choice"); + } + + m_play_state = SELECTING; + if (need_to_update || !always_spectate_peers.empty()) + { + NetworkString* back_lobby = getNetworkString(2); + back_lobby->setSynchronous(true); + back_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_SPECTATING_NEXT_GAME); + STKHost::get()->sendPacketToAllPeersWith( + [always_spectate_peers](std::shared_ptr peer) + { + return always_spectate_peers.find(peer) != + always_spectate_peers.end(); + }, back_lobby, PRM_RELIABLE); + delete back_lobby; + getLobby()->updatePlayerList(); + } + + getLobby()->dropPendingConnectionsForLegacyGPMode(); + + // Will be changed after the first vote received + setInfiniteTimeout(); + + getGPManager()->onStartSelection(); + + getCommandManager()->onStartSelection(); +} // startSelection + +//----------------------------------------------------------------------------- +/*! \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 PlayingRoom::handlePlayerVote(Event* event) +{ + if (m_play_state != SELECTING || !getSettings()->hasTrackVoting()) + { + Log::warn("ServerLobby", "Received track vote while in state %d.", + m_play_state.load()); + return; + } + + if (!checkDataSize(event, 4) || + event->getPeer()->getPlayerProfiles().empty() || + event->getPeer()->isWaitingForGame()) + return; + + if (isVotingOver()) return; + + if (!canVote(event->getPeerSP())) 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 = TrackManager::get()->getTrack(vote.m_track_name); + if (!t) + { + vote.m_track_name = getAssetManager()->getAnyMapForVote(); + t = TrackManager::get()->getTrack(vote.m_track_name); + assert(t); + } + + // Remove / adjust any invalid settings + if (isTournament()) + { + getTournament()->applyRestrictionsOnVote(&vote); + } + else + { + getSettings()->applyRestrictionsOnVote(&vote, t); + } + + // Store vote: + vote.m_player_name = event->getPeer()->getMainProfile()->getName(); + addVote(event->getPeer()->getHostId(), vote); + + // After adding the vote, show decorated name instead + vote.m_player_name = event->getPeer()->getMainProfile()->getDecoratedName(getLobby()->getNameDecorator()); + + // 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); + Comm::sendMessageToPeers(&other); + +} // handlePlayerVote + +//----------------------------------------------------------------------------- +/** 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 PlayingRoom::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 PlayingRoom::playerFinishedResult(Event *event) +{ + if (m_rs_state.load() == RS_ASYNC_RESET || + m_play_state.load() != RESULT_DISPLAY) + return; + std::shared_ptr peer = event->getPeerSP(); + m_peers_ready.at(peer) = true; +} // playerFinishedResult + +//----------------------------------------------------------------------------- +/** Tell the client \ref RemoteKartInfo of a player when some player joining + * live. + */ +void PlayingRoom::handleKartInfo(Event* event) +{ + World* w = World::getWorld(); + if (!w) + return; + + std::shared_ptr peer = event->getPeerSP(); + const NetworkString& data = event->data(); + uint8_t kart_id = data.getUInt8(); + if (kart_id > RaceManager::get()->getNumPlayers()) + return; + + AbstractKart* k = w->getKart(kart_id); + int live_join_util_ticks = k->getLiveJoinUntilTicks(); + + const RemoteKartInfo& rki = RaceManager::get()->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.getHandicap()) + .addUInt8((uint8_t)rki.getLocalPlayerId()) + .encodeString(rki.getKartName()).encodeString(rki.getCountryCode()); + if (peer->getClientCapabilities().find("real_addon_karts") != + peer->getClientCapabilities().end()) + rki.getKartData().encode(ns); + peer->sendPacket(ns, PRM_RELIABLE); + delete ns; + + FreeForAll* ffa_world = dynamic_cast(World::getWorld()); + if (ffa_world) + ffa_world->notifyAboutScoreIfNonzero(kart_id); +} // handleKartInfo + +//----------------------------------------------------------------------------- +/** Client if currently in-game (including spectator) wants to go back to + * lobby. + */ +void PlayingRoom::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; + } + + if (getLobby()->isChildClientServerHost(event->getPeer())) + { + // For child server the remaining client cannot go on player when the + // server owner quited the game (because the world will be deleted), so + // we reset all players + auto pm = ProtocolManager::lock(); + if (RaceEventManager::get()) + { + RaceEventManager::get()->stop(); + pm->findAndTerminate(PROTOCOL_GAME_EVENTS); + } + auto gp = GameProtocol::lock(); + if (gp) + { + auto lock = gp->acquireWorldDeletingMutex(); + pm->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS); + exitGameState(); + } + else + exitGameState(); + NetworkString* back_to_lobby = getNetworkString(2); + back_to_lobby->setSynchronous(true); + back_to_lobby->addUInt8(LE_BACK_LOBBY) + .addUInt8(BLR_SERVER_ONWER_QUITED_THE_GAME); + Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); + delete back_to_lobby; + m_rs_state.store(RS_ASYNC_RESET); + return; + } + + if (m_game_info) + m_game_info->saveDisconnectingPeerInfo(peer); + else + Log::warn("ServerLobby", "GameInfo is not accessible??"); + + for (const int id : peer->getAvailableKartIDs()) + { + RemoteKartInfo& rki = RaceManager::get()->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 + (Track::getCurrentTrack()->getItemManager()); + 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, PRM_RELIABLE); + delete reset; + getLobby()->updatePlayerList(); + NetworkString* server_info = getNetworkString(); + server_info->setSynchronous(true); + server_info->addUInt8(LE_SERVER_INFO); + getGameSetupFromCtx()->addServerInfo(server_info); + peer->sendPacket(server_info, PRM_RELIABLE); + delete server_info; + + peer->updateLastActivity(); +} // clientInGameWantsToBackLobby + +//----------------------------------------------------------------------------- +/** Client if currently select assets wants to go back to lobby. + */ +void PlayingRoom::clientSelectingAssetsWantsToBackLobby(Event* event) +{ + std::shared_ptr peer = event->getPeerSP(); + + if (m_play_state.load() != SELECTING || peer->isWaitingForGame()) + { + Log::warn("ServerLobby", + "%s try to leave selecting assets at wrong time.", + peer->getAddress().toString().c_str()); + return; + } + + if (getLobby()->isChildClientServerHost(event->getPeerSP())) + { + NetworkString* back_to_lobby = getNetworkString(2); + back_to_lobby->setSynchronous(true); + back_to_lobby->addUInt8(LE_BACK_LOBBY) + .addUInt8(BLR_SERVER_ONWER_QUITED_THE_GAME); + Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); + delete back_to_lobby; + resetVotingTime(); + resetServer(); + m_rs_state.store(RS_NONE); + 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, PRM_RELIABLE); + delete reset; + getLobby()->updatePlayerList(); + NetworkString* server_info = getNetworkString(); + server_info->setSynchronous(true); + server_info->addUInt8(LE_SERVER_INFO); + getGameSetupFromCtx()->addServerInfo(server_info); + peer->sendPacket(server_info, PRM_RELIABLE); + delete server_info; + + peer->updateLastActivity(); +} // clientSelectingAssetsWantsToBackLobby + +//----------------------------------------------------------------------------- +void PlayingRoom::saveInitialItems(std::shared_ptr nim) +{ + m_items_complete_state->getBuffer().clear(); + m_items_complete_state->reset(); + nim->saveCompleteState(m_items_complete_state); +} // saveInitialItems + +//----------------------------------------------------------------------------- +bool PlayingRoom::supportsAI() +{ + return getGameMode() == 3 || getGameMode() == 4; +} // supportsAI + +//----------------------------------------------------------------------------- +bool PlayingRoom::checkPeersReady(bool ignore_ai_peer, SelectionPhase phase) +{ + bool all_ready = true; + bool someone_races = false; + for (auto p : m_peers_ready) + { + auto peer = p.first.lock(); + if (!peer) + continue; + if (phase == BEFORE_SELECTION && peer->alwaysSpectate()) + continue; + if (phase == AFTER_GAME && peer->isSpectator()) + continue; + if (ignore_ai_peer && peer->isAIPeer()) + continue; + if (phase == BEFORE_SELECTION && !getCrownManager()->canRace(peer)) + continue; + someone_races = true; + all_ready = all_ready && p.second; + if (!all_ready) + return false; + } + return someone_races; +} // checkPeersReady + +//----------------------------------------------------------------------------- + +void PlayingRoom::resetToDefaultSettings() +{ + if (getSettings()->isServerConfigurable() && !getSettings()->isPreservingMode()) + { + if (m_play_state == WAITING_FOR_START_GAME) + handleServerConfiguration(NULL); + else + m_reset_to_default_mode_later.store(true); + } + + getSettings()->onResetToDefaultSettings(); +} // resetToDefaultSettings +//----------------------------------------------------------------------------- + +bool PlayingRoom::canVote(std::shared_ptr peer) const +{ + if (!peer || peer->getPlayerProfiles().empty()) + return false; + + if (!isTournament()) + return true; + + return getTournament()->canVote(peer); +} // canVote +//----------------------------------------------------------------------------- + +bool PlayingRoom::hasHostRights(std::shared_ptr peer) const +{ + if (!peer || peer->getPlayerProfiles().empty()) + return false; + + if (peer == m_server_owner.lock()) + return true; + + if (peer->hammerLevel() > 0) + return true; + + if (isTournament()) + return getTournament()->hasHostRights(peer); + + return false; +} // hasHostRights +//----------------------------------------------------------------------------- + +int PlayingRoom::getPermissions(std::shared_ptr peer) const +{ + int mask = 0; + if (!peer) + return mask; + bool isSpectator = (peer->alwaysSpectate()); + if (isSpectator) + { + mask |= CommandPermissions::PE_SPECTATOR; + mask |= CommandPermissions::PE_VOTED_SPECTATOR; + } + else + { + mask |= CommandPermissions::PE_USUAL; + mask |= CommandPermissions::PE_VOTED_NORMAL; + } + if (peer == m_server_owner.lock()) + { + mask |= CommandPermissions::PE_CROWNED; + if (getCrownManager()->hasOnlyHostRiding()) + mask |= CommandPermissions::PE_SINGLE; + } + int hammer_level = peer->hammerLevel(); + if (hammer_level >= 1) + { + mask |= CommandPermissions::PE_HAMMER; + if (hammer_level >= 2) + mask |= CommandPermissions::PE_MANIPULATOR; + } + else if (getTournament() && getTournament()->hasHammerRights(peer)) + { + mask |= CommandPermissions::PE_HAMMER; + } + return mask; +} // getPermissions +//----------------------------------------------------------------------------- + +int PlayingRoom::getCurrentStateScope() +{ + auto state = m_play_state.load(); + + if (state < WAITING_FOR_START_GAME || state > RESULT_DISPLAY) + return 0; + + if (state == WAITING_FOR_START_GAME) + return CommandManager::StateScope::SS_LOBBY; + + return CommandManager::StateScope::SS_INGAME; +} // getCurrentStateScope +//----------------------------------------------------------------------------- +/*! \brief Called when a player asks to select karts. + * \param event : Event providing the information. + */ +void PlayingRoom::kartSelectionRequested(Event* event) +{ + if (m_play_state != SELECTING /*|| m_game_setup->isGrandPrixStarted()*/) + { + Log::warn("PlayingRoom", "Received kart selection while in state %d.", + m_play_state.load()); + return; + } + + if (!checkDataSize(event, 1) || + event->getPeer()->getPlayerProfiles().empty()) + return; + + const NetworkString& data = event->data(); + std::shared_ptr peer = event->getPeerSP(); + setPlayerKarts(data, peer); +} // kartSelectionRequested + +//----------------------------------------------------------------------------- +void PlayingRoom::setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const +{ + unsigned player_count = ns.getUInt8(); + player_count = std::min(player_count, (unsigned)peer->getPlayerProfiles().size()); + for (unsigned i = 0; i < player_count; i++) + { + std::string kart; + ns.decodeString(&kart); + std::string username = StringUtils::wideToUtf8( + peer->getPlayerProfiles()[i]->getName()); + if (getKartElimination()->isEliminated(username)) + { + peer->getPlayerProfiles()[i]->setKartName(getKartElimination()->getKart()); + continue; + } + std::string current_kart = kart; + if (kart.find("randomkart") != std::string::npos || + (kart.find("addon_") == std::string::npos && + !getAssetManager()->isKartAvailable(kart))) + { + current_kart = ""; + } + if (getQueues()->areKartFiltersIgnoringKarts()) + current_kart = ""; + std::string name = StringUtils::wideToUtf8(peer->getPlayerProfiles()[i]->getName()); + peer->getPlayerProfiles()[i]->setKartName( + getAssetManager()->getKartForBadKartChoice(peer, name, current_kart)); + } + if (peer->getClientCapabilities().find("real_addon_karts") == + peer->getClientCapabilities().end() || ns.size() == 0) + return; + for (unsigned i = 0; i < player_count; i++) + { + KartData kart_data(ns); + std::string type = kart_data.m_kart_type; + auto& player = peer->getPlayerProfiles()[i]; + const std::string& kart_id = player->getKartName(); + setKartDataProperly(kart_data, kart_id, player, type); + } +} // setPlayerKarts + +//----------------------------------------------------------------------------- +/** Decide where to put the live join player depends on his team and game mode. + */ +int PlayingRoom::getReservedId(std::shared_ptr& p, + unsigned local_id) +{ + const bool is_ffa = + RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL; + int red_count = 0; + int blue_count = 0; + for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) + { + RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); + if (rki.isReserved()) + continue; + bool disconnected = rki.disconnected(); + if (RaceManager::get()->getKartInfo(i).getKartTeam() == KART_TEAM_RED && + !disconnected) + red_count++; + else if (RaceManager::get()->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 < RaceManager::get()->getNumPlayers(); i++) + { + RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); + std::shared_ptr player = + rki.getNetworkPlayerProfile().lock(); + if (!player) + { + if (is_ffa) + { + rki.copyFrom(p, local_id); + return i; + } + if (getSettings()->hasTeamChoosing()) + { + 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) + { + getTeamManager()->setTeamInLobby(p, target_team); + rki.copyFrom(p, local_id); + return i; + } + } + } + } + return -1; +} // getReservedId + +//----------------------------------------------------------------------------- + +void PlayingRoom::setKartDataProperly(KartData& kart_data, const std::string& kart_name, + std::shared_ptr player, + const std::string& type) const +{ + // This should set kart data for kart name at least in the following cases: + // 1. useTuxHitboxAddon() is true + // 2. kart_name is installed on the server + // (for addons; standard karts are not processed here it seems) + // 3. kart type is fine + // Maybe I'm mistaken and then it should be fixed. + // I extracted this into a separate function because if kart_name is set + // by the server (for random addon kart, or due to a filter), kart data + // has to be set in another place than default one. + if (NetworkConfig::get()->useTuxHitboxAddon() && + StringUtils::startsWith(kart_name, "addon_") && + kart_properties_manager->hasKartTypeCharacteristic(type)) + { + const KartProperties* real_addon = + kart_properties_manager->getKart(kart_name); + if (getSettings()->usesRealAddonKarts() && real_addon) + { + kart_data = KartData(real_addon); + } + else + { + const KartProperties* tux_kp = + kart_properties_manager->getKart("tux"); + kart_data = KartData(tux_kp); + kart_data.m_kart_type = type; + } + player->setKartData(kart_data); + } +} // setKartDataProperly +//----------------------------------------------------------------------------- +void PlayingRoom::addWaitingPlayersToGame() +{ + auto all_profiles = STKHost::get()->getAllPlayerProfiles(); + for (auto& profile : all_profiles) + { + auto peer = profile->getPeer(); + if (!peer || !peer->isValidated()) + continue; + + peer->resetAlwaysSpectateFull(); + peer->setWaitingForGame(false); + peer->setSpectator(false); + if (m_peers_ready.find(peer) == m_peers_ready.end()) + { + m_peers_ready[peer] = false; + if (!getSettings()->hasSqlManagement()) + { + 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()); + } + } + getLobby()->addWaitingPlayersToRanking(profile); + } + // Re-activiate the ai + if (auto ai = m_ai_peer.lock()) + ai->setValidated(true); +} // addWaitingPlayersToGame + +//----------------------------------------------------------------------------- + +void PlayingRoom::resetServer() +{ + addWaitingPlayersToGame(); + resetPeersReady(); + updatePlayerList(true/*update_when_reset_server*/); + + NetworkString* server_info = getNetworkString(); + server_info->setSynchronous(true); + server_info->addUInt8(LE_SERVER_INFO); + getGameSetupFromCtx()->addServerInfo(server_info); + Comm::sendMessageToPeersInServer(server_info); + delete server_info; + + for (auto p : m_peers_ready) + { + if (auto peer = p.first.lock()) + peer->updateLastActivity(); + } + + setup(); + + // kimden: Before, the state was unified and it was set to REGISTER_SELF_ADDRESS + // for WAN server, and to WAITING_FOR_START_GAME for LAN. For now, I'm not really + // sure why. Some issues might arise. + + m_play_state = WAITING_FOR_START_GAME; + getLobby()->resetServerToRSA(); + + // The above also means I always call handleServerConfiguration() now, + // and not only for LAN servers. + if (m_reset_to_default_mode_later.exchange(false)) + handleServerConfiguration(NULL); + + updatePlayerList(); +} // resetServer +//----------------------------------------------------------------------------- + +/** Update and see if any player disconnects, if so eliminate the kart in + * world, so this function must be called in main thread. + */ +void PlayingRoom::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 < RaceManager::get()->getNumPlayers(); i++) + { + RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); + if (rki.isReserved()) + continue; + bool disconnected = rki.disconnected(); + if (RaceManager::get()->getKartInfo(i).getKartTeam() == KART_TEAM_RED && + !disconnected) + red_count++; + else if (RaceManager::get()->getKartInfo(i).getKartTeam() == + KART_TEAM_BLUE && !disconnected) + blue_count++; + + if (!disconnected) + { + total++; + continue; + } + + if (m_game_info) + m_game_info->saveDisconnectingIdInfo(i); + else + Log::warn("ServerLobby", "GameInfo is not accessible??"); + + 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*/); + if (getSettings()->isRanked()) + { + // Handle disconnection earlier to prevent cheating by joining + // another ranked server + // Real score will be submitted later in computeNewRankings + const uint32_t id = + RaceManager::get()->getKartInfo(i).getOnlineId(); + RankingEntry penalized = m_ranking->getTemporaryPenalizedScores(id); + auto request = std::make_shared + (penalized, + RaceManager::get()->getKartInfo(i).getCountryCode()); + NetworkConfig::get()->setUserDetails(request, + "submit-ranking"); + request->queue(); + } + 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 (!getSettings()->isLivePlayers() && + 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 PlayingRoom::addLiveJoinPlaceholder( + std::vector >& players) const +{ + if (!getSettings()->isLivePlayers() || !RaceManager::get()->supportsLiveJoining()) + return; + if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL) + { + Track* t = TrackManager::get()->getTrack(getGameSetupFromCtx()->getCurrentTrack()); + assert(t); + int max_players = std::min((int)getSettings()->getServerMaxPlayers(), + (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 +//----------------------------------------------------------------------------- + +/** 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 PlayingRoom::configPeersStartTime() +{ + uint32_t max_ping = 0; + const unsigned max_ping_from_peers = getSettings()->getMaxPing(); + bool peer_exceeded_max_ping = false; + for (auto p : m_peers_ready) + { + auto peer = p.first.lock(); + // Spectators don't send input so we don't need to delay for them + if (!peer || peer->alwaysSpectate()) + 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); + peer_exceeded_max_ping = true; + continue; + } + max_ping = std::max(peer->getAveragePing(), max_ping); + } + if ((getSettings()->hasHighPingWorkaround() && peer_exceeded_max_ping) || + (getSettings()->isLivePlayers() && RaceManager::get()->supportsLiveJoining())) + { + Log::info("ServerLobby", "Max ping to ServerConfig::m_max_ping for " + "live joining or high ping workaround."); + max_ping = getSettings()->getMaxPing(); + } + // 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)Track::getCurrentTrack()->getCheckManager()->getCheckStructureCount(); + ns->addUInt8(cc); + *ns += *m_items_complete_state; + m_client_starting_time = start_time; + Comm::sendMessageToPeers(ns, PRM_RELIABLE); + + const unsigned jitter_tolerance = getSettings()->getJitterTolerance(); + 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_play_state = WAIT_FOR_RACE_STARTED; + + World::getWorld()->setPhase(WorldStatus::SERVER_READY_PHASE); + // Different stk process thread may have different stk host + STKHost* stk_host = STKHost::get(); + joinStartGameThread(); + m_start_game_thread = std::thread([start_time, stk_host, this]() + { + const uint64_t cur_time = stk_host->getNetworkTimer(); + assert(start_time > cur_time); + int sleep_time = (int)(start_time - cur_time); + //Log::info("ServerLobby", "Start game after %dms", sleep_time); + StkTime::sleep(sleep_time); + //Log::info("ServerLobby", "Started at %lf", StkTime::getRealTime()); + m_play_state.store(RACING); + }); +} // configPeersStartTime +//----------------------------------------------------------------------------- + +void PlayingRoom::updateServerOwner(bool force) +{ + ServerInitState state = m_init_state.load(); + if (state != RUNNING) + return; + + if (getCrownManager()->isOwnerLess()) + return; + + if (!force && !m_server_owner.expired()) + return; + + auto peers = STKHost::get()->getPeers(); + + if (m_process_type != PT_MAIN) + { + auto id = m_client_server_host_id.load(); + for (unsigned i = 0; i < peers.size(); ) + { + const auto& peer = peers[i]; + if (peer->getHostId() != id) + { + std::swap(peers[i], peers.back()); + peers.pop_back(); + continue; + } + ++i; + } + } + + for (unsigned i = 0; i < peers.size(); ) + { + const auto& peer = peers[i]; + if (!peer->isValidated() || peer->isAIPeer()) + { + std::swap(peers[i], peers.back()); + peers.pop_back(); + continue; + } + ++i; + } + + if (peers.empty()) + return; + + std::shared_ptr owner = getCrownManager()->getFirstInCrownOrder(peers); + if (m_server_owner.expired() || m_server_owner.lock() != 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 +//----------------------------------------------------------------------------- +/** 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 PlayingRoom::checkRaceFinished() +{ + assert(RaceEventManager::get()->isRunning()); + assert(World::getWorld()); + if (!RaceEventManager::get()->isRaceOver()) return; + + if (isTournament()) + getTournament()->onRaceFinished(); + + if (RaceManager::get()->getMinorMode() == + RaceManager::MINOR_MODE_SOCCER) + Log::info("ServerLobby", "SoccerMatchLog: The game is considered finished."); + else + Log::info("ServerLobby", "The game is considered finished."); + // notify the network world that it is stopped + RaceEventManager::get()->stop(); + RaceManager::get()->resetHitProcessor(); + + // stop race protocols before going back to lobby (end race) + RaceEventManager::get()->getProtocol()->requestTerminate(); + GameProtocol::lock()->requestTerminate(); + + // Save race result before delete the world + m_result_ns = getNetworkString(); + m_result_ns->setSynchronous(true); + m_result_ns->addUInt8(LE_RACE_FINISHED); + std::vector gp_changes; + if (m_game_setup->isGrandPrix()) + { + getGPManager()->updateGPScores(gp_changes, m_result_ns); + } + else if (RaceManager::get()->modeHasLaps()) + { + int fastest_lap = + static_cast(World::getWorld())->getFastestLapTicks(); + m_result_ns->addUInt32(fastest_lap); + m_result_ns->encodeString(static_cast(World::getWorld()) + ->getFastestLapKartName()); + } + + uint8_t ranking_changes_indication = 0; + if (getSettings()->isRanked() && RaceManager::get()->modeHasLaps()) + ranking_changes_indication = 1; + if (m_game_setup->isGrandPrix()) + ranking_changes_indication = 1; + m_result_ns->addUInt8(ranking_changes_indication); + + if (getKartElimination()->isEnabled()) + { + std::string msg = getKartElimination()->onRaceFinished(); + if (!msg.empty()) + Comm::sendStringToAllPeers(msg); + } + + if (getSettings()->isStoringResults()) + { + if (m_game_info) + m_game_info->fillAndStoreResults(); + else + Log::warn("ServerLobby", "GameInfo is not accessible??"); + } + + if (getSettings()->isRanked()) + { + computeNewRankings(m_result_ns); + submitRankingsToAddons(); + } + else if (m_game_setup->isGrandPrix()) + { + unsigned player_count = RaceManager::get()->getNumPlayers(); + m_result_ns->addUInt8((uint8_t)player_count); + for (unsigned i = 0; i < player_count; i++) + { + m_result_ns->addFloat(gp_changes[i]); + } + } + m_play_state.store(WAIT_FOR_RACE_STOPPED); + + getAssetManager()->gameFinishedOn(RaceManager::get()->getTrackName()); + + getQueues()->popOnRaceFinished(); +} // checkRaceFinished + +//----------------------------------------------------------------------------- +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +//----------------------------------------------------------------------------- + +void PlayingRoom::setTimeoutFromNow(int seconds) +{ + m_timeout.store((int64_t)StkTime::getMonoTimeMs() + + (int64_t)(seconds * 1000.0f)); +} // setTimeoutFromNow +//----------------------------------------------------------------------------- + +void PlayingRoom::setInfiniteTimeout() +{ + m_timeout.store(std::numeric_limits::max()); +} // setInfiniteTimeout +//----------------------------------------------------------------------------- + +bool PlayingRoom::isInfiniteTimeout() const +{ + return m_timeout.load() == std::numeric_limits::max(); +} // isInfiniteTimeout +//----------------------------------------------------------------------------- + +bool PlayingRoom::isTimeoutExpired() const +{ + return m_timeout.load() < (int64_t)StkTime::getMonoTimeMs(); +} // isTimeoutExpired +//----------------------------------------------------------------------------- + +float PlayingRoom::getTimeUntilExpiration() const +{ + int64_t timeout = m_timeout.load(); + if (timeout == std::numeric_limits::max()) + return std::numeric_limits::max(); + + return (timeout - (int64_t)StkTime::getMonoTimeMs()) / 1000.0f; +} // getTimeUntilExpiration +//----------------------------------------------------------------------------- + +void PlayingRoom::onSpectatorStatusChange(const std::shared_ptr& peer) +{ + auto state = m_play_state.load(); + if (state >= ServerPlayState::SELECTING && state < ServerPlayState::RACING) + { + erasePeerReady(peer); + peer->setWaitingForGame(true); + } +} // onSpectatorStatusChange +//----------------------------------------------------------------------------- diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp new file mode 100644 index 00000000000..f72cc65a53f --- /dev/null +++ b/src/network/protocols/playing_room.hpp @@ -0,0 +1,209 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// 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. + +#ifndef PLAYING_ROOM_HPP +#define PLAYING_ROOM_HPP + +#include "network/server_enums.hpp" +#include "utils/lobby_context.hpp" +#include "network/packet_types.hpp" +#include "network/kart_data.hpp" +#include "network/protocols/lobby_protocol.hpp" + +#include +#include +#include +#include +#include + +class STKPeer; +class NetworkPlayerProfile; +class NetworkString; +class BareNetworkString; +class GameInfo; +class NetworkItemManager; + + +class PlayingRoom: public LobbyProtocol, public LobbyContextUser +{ +private: + std::atomic m_play_state; + + std::atomic m_rs_state; + + /** Hold the next connected peer for server owner if current one expired + * (disconnected). */ + std::weak_ptr m_server_owner; + + /** AI peer which holds the list of reserved AI for dedicated server. */ + std::weak_ptr m_ai_peer; + + /** AI profiles for all-in-one graphical client server, this will be a + * fixed count thorough the live time of server, which its value is + * configured in NetworkConfig. */ + std::vector > m_ai_profiles; + + std::atomic m_server_owner_id; + + /** Keeps track of the server state. */ + std::atomic_bool m_server_has_loaded_world; + + /** Counts how many peers have finished loading the world. */ + std::map, bool, + std::owner_less > > m_peers_ready; + + /** Timeout counter for various state. */ + std::atomic m_timeout; + + /* Saved the last game result */ + NetworkString* m_result_ns; + + /* Used to make sure clients are having same item list at start */ + BareNetworkString* m_items_complete_state; + + std::atomic m_difficulty; + + std::atomic m_game_mode; + + std::atomic m_lobby_players; + + std::atomic m_current_ai_count; + + uint64_t m_server_started_at; + + uint64_t m_server_delay; + + unsigned m_item_seed; + + uint64_t m_client_starting_time; + + // Calculated before each game started + unsigned m_ai_count; + + std::shared_ptr m_game_info; + + std::atomic m_reset_to_default_mode_later; + + std::pair m_game_progress; + + // SL (as LobbyProtocol) has a separate mutex for it. However, PR + // is not doing anything for synchronization for now... + std::string m_playing_track; + +private: + void resetPeersReady() + { + for (auto it = m_peers_ready.begin(); it != m_peers_ready.end();) + { + if (it->first.expired()) + { + it = m_peers_ready.erase(it); + } + else + { + it->second = false; + it++; + } + } + } + +private: + void updateMapsForMode(); + NetworkString* getLoadWorldMessage( + std::vector >& players, + bool live_join) const; + + void rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr); + bool canLiveJoinNow() const; + bool canVote(std::shared_ptr peer) const; + bool hasHostRights(std::shared_ptr peer) const; + bool checkPeersReady(bool ignore_ai_peer, SelectionPhase phase); + bool supportsAI(); + void setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const; + int getReservedId(std::shared_ptr& p, + unsigned local_id); + void resetServer(); + void addWaitingPlayersToGame(); + void configPeersStartTime(); + void checkRaceFinished(); + +public: + PlayingRoom(); + ~PlayingRoom(); + void setup(); + void updateRoom(int ticks); + void asynchronousUpdateRoom(); + ServerPlayState getCurrentPlayState() const { return m_play_state.load(); } + bool isRacing() const { return m_play_state.load() == RACING; } + int getDifficulty() const { return m_difficulty.load(); } + int getGameMode() const { return m_game_mode.load(); } + int getLobbyPlayers() const { return m_lobby_players.load(); } + bool isAIProfile(const std::shared_ptr& npp) const + { + return std::find(m_ai_profiles.begin(), m_ai_profiles.end(), npp) != + m_ai_profiles.end(); + } + void erasePeerReady(std::shared_ptr peer) + { m_peers_ready.erase(peer); } + bool isWorldPicked() const { return m_play_state.load() >= LOAD_WORLD; } + bool isWorldFinished() const { return m_play_state.load() >= RESULT_DISPLAY; } + bool isStateAtLeastRacing() const { return m_play_state.load() >= RACING; } + std::shared_ptr getGameInfo() const { return m_game_info; } + std::shared_ptr getServerOwner() const + { return m_server_owner.lock(); } + bool isWaitingForStartGame() const + { return m_play_state.load() == WAITING_FOR_START_GAME; } + + void handlePlayerDisconnection() const; + void addLiveJoinPlaceholder( + std::vector >& players) const; + +public: // were public before and SL doesn't call them + + void setTimeoutFromNow(int seconds); + void setInfiniteTimeout(); + bool isInfiniteTimeout() const; + bool isTimeoutExpired() const; + float getTimeUntilExpiration() const; + void onSpectatorStatusChange(const std::shared_ptr& peer); + int getPermissions(std::shared_ptr peer) const; + int getCurrentStateScope(); + void resetToDefaultSettings(); + void saveInitialItems(std::shared_ptr nim); + void setKartDataProperly(KartData& kart_data, const std::string& kart_name, + std::shared_ptr player, + const std::string& type) const; + +public: // SL needs to call them + void playerFinishedResult(Event *event); + void liveJoinRequest(Event* event); + void finishedLoadingLiveJoinClient(Event *event); + void handleKartInfo(Event* event); + void clientInGameWantsToBackLobby(Event* event); + void finishedLoadingWorldClient(Event *event); + void clientSelectingAssetsWantsToBackLobby(Event* event); + void handlePlayerVote(Event *event); + void startSelection(const Event *event=NULL); // was public already + void kartSelectionRequested(Event* event); + +public: // Was initially private but then I made them public + + void updateServerOwner(bool force = false); +}; + +#endif // PLAYING_ROOM_HPP \ No newline at end of file diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 2b47cd2b50c..532bdba7c07 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -39,6 +39,7 @@ #include "network/protocols/game_protocol.hpp" #include "network/protocols/game_events_protocol.hpp" #include "network/protocols/ranking.hpp" +#include "network/protocols/playing_room.hpp" #include "network/race_event_manager.hpp" #include "network/requests.hpp" #include "network/server_config.hpp" @@ -77,74 +78,6 @@ // Helper functions. namespace { - void encodePlayers(BareNetworkString* bns, - std::vector >& players, - const std::shared_ptr& decorator) - { - bns->addUInt8((uint8_t)players.size()); - for (unsigned i = 0; i < players.size(); i++) - { - std::shared_ptr& player = players[i]; - bns->encodeString(player->getDecoratedName(decorator)) - .addUInt32(player->getHostId()) - .addFloat(player->getDefaultKartColor()) - .addUInt32(player->getOnlineId()) - .addUInt8(player->getHandicap()) - .addUInt8(player->getLocalPlayerId()) - .addUInt8(player->getTeam()) - .encodeString(player->getCountryCode()); - bns->encodeString(player->getKartName()); - } - } // encodePlayers - //------------------------------------------------------------------------- - /** Returns true if world is active for clients to live join, spectate or - * going back to lobby live - */ - bool worldIsActive() - { - return World::getWorld() && RaceEventManager::get()->isRunning() && - !RaceEventManager::get()->isRaceOver() && - World::getWorld()->getPhase() == WorldStatus::RACE_PHASE; - } // worldIsActive - - //------------------------------------------------------------------------- - /** Get a list of current ingame players for live join or spectate. - */ - std::vector > getLivePlayers() - { - std::vector > players; - for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) - { - const RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); - std::shared_ptr player = - rki.getNetworkPlayerProfile().lock(); - if (!player) - { - if (RaceManager::get()->modeHasLaps()) - { - player = std::make_shared( - nullptr, rki.getPlayerName(), - std::numeric_limits::max(), - rki.getDefaultKartColor(), - rki.getOnlineId(), rki.getHandicap(), - rki.getLocalPlayerId(), KART_TEAM_NONE, - rki.getCountryCode()); - player->setKartName(rki.getKartName()); - } - else - { - player = NetworkPlayerProfile::getReservedProfile( - RaceManager::get()->getMinorMode() == - RaceManager::MINOR_MODE_FREE_FOR_ALL ? - KART_TEAM_NONE : rki.getKartTeam()); - } - } - players.push_back(player); - } - return players; - } // getLivePlayers - //------------------------------------------------------------------------- - void getClientAssetsFromNetworkString(const NetworkString& ns, std::set& client_karts, std::set& client_maps) @@ -216,7 +149,8 @@ namespace ServerLobby::ServerLobby() : LobbyProtocol() { m_client_server_host_id.store(0); - m_lobby_players.store(0); + + m_init_state = SET_PUBLIC_ADDRESS; m_lobby_context = std::make_shared(this, (bool)ServerConfig::m_soccer_tournament); m_lobby_context->setup(); @@ -224,15 +158,10 @@ ServerLobby::ServerLobby() : LobbyProtocol() m_game_setup->setContext(m_context); m_context->setGameSetup(m_game_setup); - m_current_ai_count.store(0); - - m_rs_state.store(RS_NONE); m_last_success_poll_time.store(StkTime::getMonoTimeMs() + 30000); m_last_unsuccess_poll_time = StkTime::getMonoTimeMs(); - m_server_owner_id.store(-1); m_registered_for_once_only = false; setHandleDisconnections(true); - m_state = SET_PUBLIC_ADDRESS; if (ServerConfig::m_ranked) { Log::info("ServerLobby", "This server will submit ranking scores to " @@ -242,16 +171,16 @@ ServerLobby::ServerLobby() : LobbyProtocol() m_ranking = std::make_shared(); } m_name_decorator = std::make_shared(); - 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_reset_to_default_mode_later.store(false); - m_game_info = {}; + m_rooms.emplace_back(); + + for (auto& room: m_rooms) + room->setContext(m_context); } // ServerLobby //----------------------------------------------------------------------------- + /** Destructor. */ ServerLobby::~ServerLobby() @@ -261,7 +190,9 @@ ServerLobby::~ServerLobby() // For child process the request manager will keep on running unregisterServer(m_process_type == PT_MAIN ? true : false/*now*/); } - delete m_items_complete_state; + for (auto& room: m_rooms) + room.reset(); + m_rooms.clear(); if (getSettings()->isSavingServerConfig()) ServerConfig::writeServerConfigToDisk(); @@ -279,57 +210,18 @@ void ServerLobby::initServerStatsTable() #endif } // initServerStatsTable -//----------------------------------------------------------------------------- -/** Calls the corresponding method from LobbyAssetManager - * whenever server is reset or game mode is changed. */ -void ServerLobby::updateMapsForMode() -{ - getAssetManager()->updateMapsForMode( - ServerConfig::getLocalGameMode(m_game_mode.load()).first - ); -} // updateMapsForMode - //----------------------------------------------------------------------------- void ServerLobby::setup() { LobbyProtocol::setup(); - m_item_seed = 0; - m_client_starting_time = 0; - m_ai_count = 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(""); - } - if (auto ai = m_ai_peer.lock()) - { - for (auto player : ai->getPlayerProfiles()) - player->setKartName(""); - } - for (auto ai : m_ai_profiles) - ai->setKartName(""); + for (auto& room: m_rooms) + room->setup(); StateManager::get()->resetActivePlayers(); getAssetManager()->onServerSetup(); getSettings()->onServerSetup(); - updateMapsForMode(); - m_server_has_loaded_world.store(false); - - // Initialise the data structures to detect if all clients and - // the server are ready: - resetPeersReady(); - setInfiniteTimeout(); - m_server_started_at = m_server_delay = 0; getCommandManager()->onServerSetup(); - m_game_info = {}; - Log::info("ServerLobby", "Resetting the server to its initial state."); } // setup @@ -346,13 +238,16 @@ bool ServerLobby::notifyEvent(Event* event) message_type = data.getUInt8(); Log::info("ServerLobby", "Synchronous message of type %d received.", message_type); + + auto& room = getRoomForPeer(event->getPeerSP()); + 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; + case LE_RACE_FINISHED_ACK: room->playerFinishedResult(event); break; + case LE_LIVE_JOIN: room->liveJoinRequest(event); break; + case LE_CLIENT_LOADED_WORLD: room->finishedLoadingLiveJoinClient(event); break; + case LE_KART_INFO: room->handleKartInfo(event); break; + case LE_CLIENT_BACK_LOBBY: room->clientInGameWantsToBackLobby(event); break; default: Log::error("ServerLobby", "Unknown message of type %d - ignored.", message_type); @@ -440,20 +335,24 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) message_type = data.getUInt8(); Log::info("ServerLobby", "Message of type %d received.", message_type); + + auto room = getRoomForPeer(event->getPeerSP()); + 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_KART_SELECTION: room->kartSelectionRequested(event); break; + case LE_CLIENT_LOADED_WORLD: + room->finishedLoadingWorldClient(event); break; + case LE_VOTE: room->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_REQUEST_BEGIN: room->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; + room->clientSelectingAssetsWantsToBackLobby(event); break; case LE_REPORT_PLAYER: writePlayerReport(event); break; case LE_ASSETS_UPDATE: handleAssets(event); break; case LE_COMMAND: handleServerCommand(event); break; @@ -614,1377 +513,195 @@ void ServerLobby::writePlayerReport(Event* event) /** 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); - } - - getChatManager()->clearAllExpiredWeakPtrs(); - #ifdef ENABLE_SQLITE3 pollDatabase(); #endif - // Check if server owner has left - updateServerOwner(); - - if (getSettings()->isRanked() && m_state.load() == WAITING_FOR_START_GAME) - m_ranking->cleanup(); - - if (!getSettings()->isLegacyGPMode() || m_state.load() == WAITING_FOR_START_GAME) - { - // Only poll the STK server if server has been registered. - if (m_server_id_online.load() != 0 && - m_state.load() != REGISTER_SELF_ADDRESS) - checkIncomingConnectionRequests(); - handlePendingConnection(); - } - - if (m_server_id_online.load() != 0 && - !getSettings()->isLegacyGPMode() && - StkTime::getMonoTimeMs() > m_last_unsuccess_poll_time && - StkTime::getMonoTimeMs() > m_last_success_poll_time.load() + 30000) - { - Log::warn("ServerLobby", "Trying auto server recovery."); - // For auto server recovery wait 3 seconds for next try - m_last_unsuccess_poll_time = StkTime::getMonoTimeMs() + 3000; - registerServer(false/*first_time*/); - } - - 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; - if (m_reset_to_default_mode_later.exchange(false)) - handleServerConfiguration(NULL); + for (auto& room: m_rooms) + room->asynchronousUpdateRoom(); - updatePlayerList(); - STKHost::get()->startListening(); - return; - } - auto ip_type = NetworkConfig::get()->getIPType(); - // Set the IPv6 address first for possible IPv6 only server - if (isIPv6Socket() && ip_type >= NetworkConfig::IP_V6) - { - STKHost::get()->setPublicAddress(AF_INET6); - } - if (ip_type == NetworkConfig::IP_V4 || - ip_type == NetworkConfig::IP_DUAL_STACK) - { - STKHost::get()->setPublicAddress(AF_INET); - } - if (STKHost::get()->getPublicAddress().isUnset() && - STKHost::get()->getPublicIPv6Address().empty()) - { - m_state = ERROR_LEAVE; - } - else - { - STKHost::get()->startListening(); - m_state = REGISTER_SELF_ADDRESS; - } - break; - } - case REGISTER_SELF_ADDRESS: + switch (m_init_state.load()) { - if (m_game_setup->isGrandPrixStarted() || m_registered_for_once_only) - { - m_state = WAITING_FOR_START_GAME; - if (m_reset_to_default_mode_later.exchange(false)) - handleServerConfiguration(NULL); - - updatePlayerList(); - 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 (m_server_registering.expired() && m_server_id_online.load() == 0) - registerServer(true/*first_time*/); - - if (m_server_registering.expired()) + case SET_PUBLIC_ADDRESS: { - // Finished registering server - if (m_server_id_online.load() != 0) + // In case of LAN we don't need our public address or register with the + // STK server, so we can directly go to the accepting clients state. + if (NetworkConfig::get()->isLAN()) { - // For non grand prix server we only need to register to stk - // addons once - if (!getSettings()->isLegacyGPMode()) - m_registered_for_once_only = true; - m_state = WAITING_FOR_START_GAME; + m_init_state = RUNNING; if (m_reset_to_default_mode_later.exchange(false)) handleServerConfiguration(NULL); updatePlayerList(); - } - } - break; - } - case WAITING_FOR_START_GAME: - { - if (getCrownManager()->isOwnerLess()) - { - // Ensure that a game can auto-start if the server meets the config's starting limit or if it's already full. - int starting_limit = std::min((int)getSettings()->getMinStartGamePlayers(), (int)getSettings()->getServerMaxPlayers()); - unsigned current_max_players_in_game = getSettings()->getCurrentMaxPlayersInGame(); - if (current_max_players_in_game > 0) // 0 here means it's not the limit - starting_limit = std::min(starting_limit, (int)current_max_players_in_game); - - unsigned players = 0; - STKHost::get()->updatePlayers(&players); - if (((int)players >= starting_limit || - m_game_setup->isGrandPrixStarted()) && - isInfiniteTimeout()) - { - if (getSettings()->getStartGameCounter() >= -1e-5) - { - setTimeoutFromNow(getSettings()->getStartGameCounter()); - } - else - { - setInfiniteTimeout(); - } - } - else if ((int)players < starting_limit && - !m_game_setup->isGrandPrixStarted()) - { - resetPeersReady(); - if (!isInfiniteTimeout()) - updatePlayerList(); - - setInfiniteTimeout(); - } - bool forbid_starting = false; - if (isTournament() && getTournament()->forbidStarting()) - forbid_starting = true; - - bool timer_finished = (!forbid_starting && isTimeoutExpired()); - bool players_ready = (checkPeersReady(true/*ignore_ai_peer*/, BEFORE_SELECTION) - && (int)players >= starting_limit); - - if (timer_finished || (players_ready && !getSettings()->isCooldown())) - { - resetPeersReady(); - startSelection(); + STKHost::get()->startListening(); 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 - - // maybe this is not the best place for this? - getHitProcessor()->resetLastHits(); - - 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 && getSettings()->isRanked()) || - 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( - getSettings()->hasAiHandling() && m_ai_count == 0/*ignore_ai_peer*/, LOADING_WORLD)) - 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 && getSettings()->isRanked()) || - player_in_game == 0) - { - resetVotingTime(); - return; - } - - PeerVote winner_vote; - getSettings()->resetWinnerPeerId(); - bool go_on_race = false; - if (getSettings()->hasTrackVoting()) - go_on_race = handleAllVotes(&winner_vote); - else if (/*m_game_setup->isGrandPrixStarted() || */isVotingOver()) - { - winner_vote = getSettings()->getDefaultVote(); - go_on_race = true; - } - if (go_on_race) - { - getSettings()->applyRestrictionsOnWinnerVote(&winner_vote); - getSettings()->setDefaultVote(winner_vote); - m_item_seed = (uint32_t)StkTime::getTimeSinceEpoch(); - ItemManager::updateRandomSeed(m_item_seed); - float extra_seconds = 0.0f; - if (isTournament()) - extra_seconds = getTournament()->getExtraSeconds(); - m_game_setup->setRace(winner_vote, extra_seconds); - - // For spectators that don't have the track, remember their - // spectate mode and don't load the track - std::string track_name = winner_vote.m_track_name; - if (isTournament()) - getTournament()->fillNextArena(track_name); - - auto peers = STKHost::get()->getPeers(); - std::map, - AlwaysSpectateMode> previous_spectate_mode; - for (auto peer : peers) + auto ip_type = NetworkConfig::get()->getIPType(); + // Set the IPv6 address first for possible IPv6 only server + if (isIPv6Socket() && ip_type >= NetworkConfig::IP_V6) { - if (peer->alwaysSpectate() && (!peer->alwaysSpectateForReal() || - peer->getClientAssets().second.count(track_name) == 0)) - { - previous_spectate_mode[peer] = peer->getAlwaysSpectate(); - peer->setAlwaysSpectate(ASM_NONE); - peer->setWaitingForGame(true); - m_peers_ready.erase(peer); - } + STKHost::get()->setPublicAddress(AF_INET6); } - bool has_always_on_spectators = false; - auto players = STKHost::get() - ->getPlayersForNewGame(&has_always_on_spectators); - for (auto& p: previous_spectate_mode) - if (p.first) - p.first->setAlwaysSpectate(p.second); - auto ai_instance = m_ai_peer.lock(); - if (supportsAI()) + if (ip_type == NetworkConfig::IP_V4 || + ip_type == NetworkConfig::IP_DUAL_STACK) { - if (ai_instance) - { - auto ai_profiles = ai_instance->getPlayerProfiles(); - if (m_ai_count > 0) - { - ai_profiles.resize(m_ai_count); - players.insert(players.end(), ai_profiles.begin(), - ai_profiles.end()); - } - } - else if (!m_ai_profiles.empty()) - { - players.insert(players.end(), m_ai_profiles.begin(), - m_ai_profiles.end()); - } + STKHost::get()->setPublicAddress(AF_INET); } - m_game_setup->sortPlayersForGrandPrix(players, getSettings()->isGPGridShuffled()); - m_game_setup->sortPlayersForGame(players); - for (unsigned i = 0; i < players.size(); i++) + if (STKHost::get()->getPublicAddress().isUnset() && + STKHost::get()->getPublicIPv6Address().empty()) { - std::shared_ptr& player = players[i]; - std::shared_ptr peer = player->getPeer(); - if (peer) - peer->clearAvailableKartIDs(); + m_init_state = ERROR_LEAVE; } - for (unsigned i = 0; i < players.size(); i++) + else { - std::shared_ptr& player = players[i]; - std::shared_ptr peer = player->getPeer(); - if (peer) - peer->addAvailableKartID(i); + STKHost::get()->startListening(); + m_init_state = REGISTER_SELF_ADDRESS; } - getSettings()->getLobbyHitCaptureLimit(); - - // 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++) + break; + } + case REGISTER_SELF_ADDRESS: + { + if (m_game_setup->isGrandPrixStarted() || m_registered_for_once_only) { - std::string current_kart = players[i]->getKartName(); - if (!players[i]->getPeer().get()) - continue; - if (getQueues()->areKartFiltersIgnoringKarts()) - current_kart = ""; - std::string name = StringUtils::wideToUtf8(players[i]->getName()); - // Note 1: setKartName also resets KartData, and should be called - // only if current kart name is not suitable. - // Note 2: filters only support standard karts for now, so GKFBKC - // cannot return an addon; when addons are supported, this part of - // code will also have to provide kart data (or setKartName has to - // set the correct hitbox itself). - std::string new_kart = getAssetManager()->getKartForBadKartChoice( - players[i]->getPeer(), name, current_kart); - if (new_kart != current_kart) - { - // Filters only support standard karts for now, but when they - // start supporting addons, probably type should not be empty - players[i]->setKartName(new_kart); - KartData kart_data; - setKartDataProperly(kart_data, new_kart, players[i], ""); - players[i]->setKartData(kart_data); - } - } + m_init_state = RUNNING; + if (m_reset_to_default_mode_later.exchange(false)) + handleServerConfiguration(NULL); - auto& stk_config = STKConfig::get(); - - NetworkString* load_world_message = getLoadWorldMessage(players, - false/*live_join*/); - m_game_setup->setHitCaptureTime(getSettings()->getBattleHitCaptureLimit(), - getSettings()->getBattleTimeLimit()); - uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks( - getSettings()->getFlagReturnTimeout()); - RaceManager::get()->setHitProcessor(getHitProcessor()); - RaceManager::get()->setFlagReturnTicks(flag_return_time); - if (getSettings()->isRecordingReplays() && getSettings()->hasConsentOnReplays() && - (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL || - RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE)) - RaceManager::get()->setRecordRace(true); - uint16_t flag_deactivated_time = (uint16_t)STKConfig::get()->time2Ticks( - getSettings()->getFlagDeactivatedTime()); - RaceManager::get()->setFlagDeactivatedTicks(flag_deactivated_time); - configRemoteKart(players, 0); - - // Reset for next state usage - resetPeersReady(); - - m_state = LOAD_WORLD; - Comm::sendMessageToPeers(load_world_message); - // updatePlayerList so the in lobby players (if any) can see always - // spectators join the game - if (has_always_on_spectators || !previous_spectate_mode.empty()) updatePlayerList(); - delete load_world_message; + 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 (m_server_registering.expired() && m_server_id_online.load() == 0) + registerServer(true/*first_time*/); - if (RaceManager::get()->getMinorMode() == - RaceManager::MINOR_MODE_SOCCER) + if (m_server_registering.expired()) { - for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) + // Finished registering server + if (m_server_id_online.load() != 0) { - if (auto player = - RaceManager::get()->getKartInfo(i).getNetworkPlayerProfile().lock()) - { - std::string username = StringUtils::wideToUtf8(player->getName()); - if (username.empty()) - continue; - Log::info("ServerLobby", "SoccerMatchLog: There is a player %s.", - username.c_str()); - } + // For non grand prix server we only need to register to stk + // addons once + if (!getSettings()->isLegacyGPMode()) + m_registered_for_once_only = true; + + m_init_state = RUNNING; + if (m_reset_to_default_mode_later.exchange(false)) + handleServerConfiguration(NULL); + + updatePlayerList(); } } - m_game_info = std::make_shared(); - m_game_info->setContext(m_lobby_context.get()); - m_game_info->fillFromRaceManager(); + break; } - break; - } - default: - break; - } - -} // asynchronousUpdate - -//----------------------------------------------------------------------------- -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); - getSettings()->encodeDefaultVote(load_world_message); - load_world_message->addUInt8(live_join ? 1 : 0); - encodePlayers(load_world_message, players, m_name_decorator); - load_world_message->addUInt32(m_item_seed); - if (RaceManager::get()->isBattleMode()) - { - auto& stk_config = STKConfig::get(); - - load_world_message->addUInt32(getSettings()->getBattleHitCaptureLimit()) - .addFloat(getSettings()->getBattleTimeLimit()); - uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks( - getSettings()->getFlagReturnTimeout()); - load_world_message->addUInt16(flag_return_time); - uint16_t flag_deactivated_time = (uint16_t)stk_config->time2Ticks( - getSettings()->getFlagDeactivatedTime()); - load_world_message->addUInt16(flag_deactivated_time); - } - for (unsigned i = 0; i < players.size(); i++) - players[i]->getKartData().encode(load_world_message); - return load_world_message; -} // getLoadWorldMessage - -//----------------------------------------------------------------------------- -/** Returns true if server can be live joined or spectating - */ -bool ServerLobby::canLiveJoinNow() const -{ - bool live_join = getSettings()->isLivePlayers() && worldIsActive(); - if (!live_join) - return false; - if (RaceManager::get()->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++) + case ERROR_LEAVE: { - fastest_kart = w->getKartAtPosition(i + 1); - if (fastest_kart && !fastest_kart->isEliminated()) - break; + requestTerminate(); + m_init_state = EXITING; + STKHost::get()->requestShutdown(); + break; } - if (!fastest_kart) - return false; - float leader_distance = w->getOverallDistance( - fastest_kart->getWorldKartId()); - float total_distance = - Track::getCurrentTrack()->getTrackLength() * - (float)RaceManager::get()->getNumLaps(); - // standard version uses (leader_distance / total_distance > 0.9f) - // TODO: allow switching - if (total_distance - leader_distance < 250.0) - return false; + default: + break; } - return live_join; -} // canLiveJoinNow - +} // asynchronousUpdate //----------------------------------------------------------------------------- -/** \ref STKPeer peer will be reset back to the lobby with reason - * \ref BackLobbyReason blr +/** 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::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr) -{ - NetworkString* reset = getNetworkString(2); - reset->setSynchronous(true); - reset->addUInt8(LE_BACK_LOBBY).addUInt8(blr); - peer->sendPacket(reset, PRM_RELIABLE); - delete reset; - updatePlayerList(); +// kimden: Sounds like a thing to not be done here..... - NetworkString* server_info = getNetworkString(); - server_info->setSynchronous(true); - server_info->addUInt8(LE_SERVER_INFO); - m_game_setup->addServerInfo(server_info); - peer->sendPacket(server_info, PRM_RELIABLE); - delete server_info; +void ServerLobby::update(int ticks) +{ + for (auto& room: m_rooms) + room->updateRoom(ticks); - peer->updateLastActivity(); -} // rejectLiveJoin + setGameStartedProgress(m_rooms[0]->getGameStartedProgress()); + storePlayingTrack(m_rooms[0]->getPlayingTrackIdent()); +} // update //----------------------------------------------------------------------------- -/** This message is like kartSelectionRequested, but it will send the peer - * load world message if he can join the current started game. +/** Register this server (i.e. its public address) with the STK server + * so that clients can find it. It blocks till a response from the + * stk server is received (this function is executed from the + * ProtocolManager thread). The information about this client is added + * to the table 'server'. */ -void ServerLobby::liveJoinRequest(Event* event) +void ServerLobby::registerServer(bool first_time) { - // I moved some data getters ahead of some returns. This should be fine - // in general, but you know what caused it if smth goes wrong. - - std::shared_ptr peer = event->getPeerSP(); - const NetworkString& data = event->data(); - bool spectator = data.getUInt8() == 1; + auto request = std::make_shared( + std::dynamic_pointer_cast(shared_from_this()), first_time); + NetworkConfig::get()->setServerDetails(request, "create"); + const SocketAddress& addr = STKHost::get()->getPublicAddress(); + request->addParameter("address", addr.getIP() ); + request->addParameter("port", addr.getPort() ); + request->addParameter("private_port", + STKHost::get()->getPrivatePort() ); + request->addParameter("name", m_game_setup->getServerNameUtf8()); + request->addParameter("max_players", getSettings()->getServerMaxPlayers()); + 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 = getSettings()->getPrivateServerPassword(); + request->addParameter("password", (unsigned)(!pw.empty())); + request->addParameter("version", (unsigned)ServerConfig::m_server_version); - if (!canLiveJoinNow()) + bool ipv6_only = addr.isUnset(); + if (!ipv6_only) { - rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); - return; + Log::info("ServerLobby", "Public IPv4 server address %s", + addr.toString().c_str()); } - if (RaceManager::get()->modeHasLaps() && !spectator) + if (!STKHost::get()->getPublicIPv6Address().empty()) { - // No live join for linear race - rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); - return; + request->addParameter("address_ipv6", + STKHost::get()->getPublicIPv6Address()); + Log::info("ServerLobby", "Public IPv6 server address %s", + STKHost::get()->getPublicIPv6Address().c_str()); } + request->queue(); + m_server_registering = request; +} // registerServer - peer->clearAvailableKartIDs(); - if (!spectator) - { - auto& spectators_by_limit = getCrownManager()->getSpectatorsByLimit(); - 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()) || - (spectators_by_limit.find(peer) != spectators_by_limit.end())) - { - for (unsigned i = 0; i < peer->getPlayerProfiles().size(); i++) - peer->getPlayerProfiles()[i]->setKartName(""); - for (unsigned i = 0; i < used_id.size(); i++) - { - RemoteKartInfo& rki = RaceManager::get()->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; - } +//----------------------------------------------------------------------------- +/** 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, std::weak_ptr sl) +{ + auto request = std::make_shared(sl); + NetworkConfig::get()->setServerDetails(request, "stop"); - 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); - } + const SocketAddress& addr = STKHost::get()->getPublicAddress(); + request->addParameter("address", addr.getIP()); + request->addParameter("port", addr.getPort()); + bool ipv6_only = addr.isUnset(); + if (!ipv6_only) + { + Log::info("ServerLobby", "Unregister server address %s", + addr.toString().c_str()); } else { - Log::info("ServerLobby", "%s spectating now.", - peer->getAddress().toString().c_str()); + Log::info("ServerLobby", "Unregister server address %s", + STKHost::get()->getValidPublicAddress().c_str()); } - std::vector > players = - getLivePlayers(); - NetworkString* load_world_message = getLoadWorldMessage(players, - true/*live_join*/); - peer->sendPacket(load_world_message, PRM_RELIABLE); - delete load_world_message; - peer->updateLastActivity(); -} // liveJoinRequest - -//----------------------------------------------------------------------------- -/** 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 bool is_ffa = - RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL; - int red_count = 0; - int blue_count = 0; - for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) - { - RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); - if (rki.isReserved()) - continue; - bool disconnected = rki.disconnected(); - if (RaceManager::get()->getKartInfo(i).getKartTeam() == KART_TEAM_RED && - !disconnected) - red_count++; - else if (RaceManager::get()->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 < RaceManager::get()->getNumPlayers(); i++) - { - RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); - std::shared_ptr player = - rki.getNetworkPlayerProfile().lock(); - if (!player) - { - if (is_ffa) - { - rki.copyFrom(p, local_id); - return i; - } - if (getSettings()->hasTeamChoosing()) - { - 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) - { - getTeamManager()->setTeamInLobby(p, 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, BLR_NO_GAME_FOR_LIVE_JOIN); - return; - } - bool live_joined_in_time = true; - for (const int id : peer->getAvailableKartIDs()) - { - const RemoteKartInfo& rki = RaceManager::get()->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, BLR_NO_GAME_FOR_LIVE_JOIN); - return; - } - World* w = World::getWorld(); - assert(w); - - uint64_t live_join_start_time = STKHost::get()->getNetworkTimer(); - - auto& stk_config = STKConfig::get(); - - // Instead of using getTicksSinceStart we caculate the current world ticks - // only from network timer, because if the server hangs in between the - // world ticks may not be up to date - // 2000 is the time for ready set, remove 3 ticks after for minor - // correction (make it more looks like getTicksSinceStart if server has no - // hang - int cur_world_ticks = stk_config->time2Ticks( - (live_join_start_time - m_server_started_at - 2000) / 1000.f) - 3; - // Give 3 seconds for all peers to get new kart info - m_last_live_join_util_ticks = - cur_world_ticks + stk_config->time2Ticks(3.0f); - live_join_start_time -= m_server_delay; - live_join_start_time += 3000; - - bool spectator = false; - for (const int id : peer->getAvailableKartIDs()) - { - const RemoteKartInfo& rki = RaceManager::get()->getKartInfo(id); - int points = 0; - - if (m_game_info) - points = m_game_info->onLiveJoinedPlayer(id, rki, w); - else - Log::warn("ServerLobby", "GameInfo is not accessible??"); - - // If the mode is not battle/CTF, points are 0. - // I assume it's fine like that for now - World::getWorld()->addReservedKart(id, points); - 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)Track::getCurrentTrack()->getCheckManager()->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 - (Track::getCurrentTrack()->getItemManager()); - assert(nim); - nim->saveCompleteState(ns); - nim->addLiveJoinPeer(peer); - - w->saveCompleteState(ns, peer); - if (RaceManager::get()->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_name_decorator); - for (unsigned i = 0; i < players.size(); i++) - players[i]->getKartData().encode(ns); - } - - m_peers_ready[peer] = false; - peer->setWaitingForGame(false); - peer->setSpectator(spectator); - - peer->sendPacket(ns, PRM_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 = getSettings()->getKickIdlePlayerSeconds(); - if (world_started) - { - for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) - { - RemoteKartInfo& rki = RaceManager::get()->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 (!peer->isAIPeer() && - sec > 0 && peer->idleForSeconds() > sec && - !peer->isDisconnected() && NetworkConfig::get()->isWAN()) - { - if (w && w->getKart(i)->hasFinishedRace()) - continue; - // Don't kick in game GUI server host so he can idle in game - if (m_process_type == PT_CHILD && - peer->getHostId() == m_client_server_host_id.load()) - continue; - Log::info("ServerLobby", "%s %s has been idle ingame for more than" - " %d seconds, kick.", - peer->getAddress().toString().c_str(), - StringUtils::wideToUtf8(rki.getPlayerName()).c_str(), sec); - peer->kick(); - } - if (getHitProcessor()->isAntiTrollActive() && !peer->isAIPeer()) - { - // for all human players - // if they troll, kick them - LinearWorld *lin_world = dynamic_cast(w); - if (lin_world) { - // check warn level for each player - switch(lin_world->getWarnLevel(i)) - { - case 0: - break; - case 1: - { - Comm::sendStringToPeer(peer, getSettings()->getTrollWarnMsg()); - std::string player_name = peer->getMainName(); - Log::info("ServerLobby-AntiTroll", "Sent WARNING to %s", player_name.c_str()); - break; - } - default: - { - std::string player_name = peer->getMainName(); - Log::info("ServerLobby-AntiTroll", "KICKING %s", player_name.c_str()); - peer->kick(); - break; - } - } - } - } - getHitProcessor()->punishSwatterHits(); - } - } - if (m_state.load() == WAITING_FOR_START_GAME) { - sec = getSettings()->getKickIdleLobbyPlayerSeconds(); - auto peers = STKHost::get()->getPeers(); - for (auto peer: peers) - { - if (!peer->isAIPeer() && - sec > 0 && peer->idleForSeconds() > sec && - !peer->isDisconnected() && NetworkConfig::get()->isWAN()) - { - // Don't kick in game GUI server host so he can idle in the lobby - if (m_process_type == PT_CHILD && - peer->getHostId() == m_client_server_host_id.load()) - continue; - std::string peer_name = ""; - if (peer->hasPlayerProfiles()) - peer_name = peer->getMainName().c_str(); - Log::info("ServerLobby", "%s %s has been idle on the server for " - "more than %d seconds, kick.", - peer->getAddress().toString().c_str(), peer_name.c_str(), sec); - peer->kick(); - } - } - } - if (w) - setGameStartedProgress(w->getGameStartedProgress()); - else - resetGameStartedProgress(); - - if (w && w->getPhase() == World::RACE_PHASE) - { - storePlayingTrack(RaceManager::get()->getTrackName()); - } - else - storePlayingTrack(""); - - // Reset server to initial state if no more connected players - if (m_rs_state.load() == RS_WAITING) - { - if ((RaceEventManager::get() && - !RaceEventManager::get()->protocolStopped()) || - !GameProtocol::emptyInstance()) - return; - - exitGameState(); - 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::get() && - RaceEventManager::get()->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); - Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); - delete back_to_lobby; - - RaceEventManager::get()->stop(); - RaceEventManager::get()->getProtocol()->requestTerminate(); - GameProtocol::lock()->requestTerminate(); - } - else if (auto ai = m_ai_peer.lock()) - { - // Reset AI peer for empty server, which will delete world - NetworkString* back_to_lobby = getNetworkString(2); - back_to_lobby->setSynchronous(true); - back_to_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); - ai->sendPacket(back_to_lobby, PRM_RELIABLE); - delete back_to_lobby; - } - if (all_players_in_world_disconnected) - m_game_setup->cancelOneRace(); - resetVotingTime(); - // m_game_setup->cancelOneRace(); - //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 (getSettings()->isRanked() && - 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); - Comm::sendMessageToPeers(back_lobby, PRM_RELIABLE); - delete back_lobby; - resetVotingTime(); - // m_game_setup->cancelOneRace(); - //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("ServerLobby", "Starting the race loading."); - // This will create the world instance, i.e. load track and karts - loadWorld(); - getGPManager()->updateWorldScoring(); - getSettings()->updateWorldSettings(m_game_info); - m_state = WAIT_FOR_WORLD_LOADED; - break; - case RACING: - if (World::getWorld() && RaceEventManager::get() && - RaceEventManager::get()->isRunning()) - { - checkRaceFinished(); - } - break; - case WAIT_FOR_RACE_STOPPED: - if (!RaceEventManager::get()->protocolStopped() || - !GameProtocol::emptyInstance()) - return; - - // This will go back to lobby in server (and exit the current race) - exitGameState(); - // 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 - setTimeoutFromNow(15); - m_state = RESULT_DISPLAY; - Comm::sendMessageToPeers(m_result_ns, PRM_RELIABLE); - delete m_result_ns; - Log::info("ServerLobby", "End of game message sent"); - break; - case RESULT_DISPLAY: - if (checkPeersReady(true/*ignore_ai_peer*/, AFTER_GAME) || - isTimeoutExpired()) - { - // 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); - Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); - 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'. - */ -void ServerLobby::registerServer(bool first_time) -{ - auto request = std::make_shared( - std::dynamic_pointer_cast(shared_from_this()), first_time); - NetworkConfig::get()->setServerDetails(request, "create"); - const SocketAddress& addr = STKHost::get()->getPublicAddress(); - request->addParameter("address", addr.getIP() ); - request->addParameter("port", addr.getPort() ); - request->addParameter("private_port", - STKHost::get()->getPrivatePort() ); - request->addParameter("name", m_game_setup->getServerNameUtf8()); - request->addParameter("max_players", getSettings()->getServerMaxPlayers()); - 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 = getSettings()->getPrivateServerPassword(); - request->addParameter("password", (unsigned)(!pw.empty())); - request->addParameter("version", (unsigned)ServerConfig::m_server_version); - - bool ipv6_only = addr.isUnset(); - if (!ipv6_only) - { - Log::info("ServerLobby", "Public IPv4 server address %s", - addr.toString().c_str()); - } - if (!STKHost::get()->getPublicIPv6Address().empty()) - { - request->addParameter("address_ipv6", - STKHost::get()->getPublicIPv6Address()); - Log::info("ServerLobby", "Public IPv6 server address %s", - STKHost::get()->getPublicIPv6Address().c_str()); - } - request->queue(); - m_server_registering = request; -} // 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, std::weak_ptr sl) -{ - auto request = std::make_shared(sl); - NetworkConfig::get()->setServerDetails(request, "stop"); - - const SocketAddress& addr = STKHost::get()->getPublicAddress(); - request->addParameter("address", addr.getIP()); - request->addParameter("port", addr.getPort()); - bool ipv6_only = addr.isUnset(); - if (!ipv6_only) - { - Log::info("ServerLobby", "Unregister server address %s", - addr.toString().c_str()); - } - else - { - Log::info("ServerLobby", "Unregister server address %s", - STKHost::get()->getValidPublicAddress().c_str()); - } - - // No need to check for result as server will be auto-cleared anyway - // when no polling is done - if (now) - { - request->executeNow(); - } - 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) -{ - bool need_to_update = false; - bool cooldown = getSettings()->isCooldown(); - - if (event != NULL) - { - if (m_state.load() != WAITING_FOR_START_GAME) - { - Log::warn("ServerLobby", - "Received startSelection while being in state %d.", - m_state.load()); - return; - } - if (getCrownManager()->isSleepingServer()) - { - Log::warn("ServerLobby", - "An attempt to start a race on a sleeping server."); - return; - } - auto peer = event->getPeerSP(); - if (getCrownManager()->isOwnerLess()) - { - if (!getSettings()->isAllowedToStart()) - { - Comm::sendStringToPeer(peer, "Starting the game is forbidden by server owner"); - return; - } - if (!getCrownManager()->canRace(peer)) - { - Comm::sendStringToPeer(peer, "You cannot play so pressing ready has no action"); - return; - } - else - { - m_peers_ready.at(event->getPeerSP()) = - !m_peers_ready.at(event->getPeerSP()); - updatePlayerList(); - return; - } - } - if (!getSettings()->isAllowedToStart()) - { - Comm::sendStringToPeer(peer, "Starting the game is forbidden by server owner"); - return; - } - if (!hasHostRights(peer)) - { - auto argv = getCommandManager()->getCurrentArgv(); - if (argv.empty() || argv[0] != "start") { - Log::warn("ServerLobby", - "Client %d is not authorised to start selection.", - event->getPeer()->getHostId()); - return; - } - } - if (cooldown) - { - Comm::sendStringToPeer(peer, "Starting the game is forbidden by server cooldown"); - return; - } - } else { - // Even if cooldown is bigger than server timeout, start happens upon - // timeout expiration. If all players clicked before timeout, no start - // happens - it's handled in another place - if (!getSettings()->isAllowedToStart()) - { - // Produce no log spam - return; - } - } - - if (!getCrownManager()->isOwnerLess() && getSettings()->hasTeamChoosing() && - !getSettings()->hasFreeTeams() && RaceManager::get()->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, PRM_RELIABLE); - delete bt; - } - return; - } - } - - unsigned max_player = 0; - STKHost::get()->updatePlayers(&max_player); - auto peers = STKHost::get()->getPeers(); - std::set> always_spectate_peers; - - // Set late coming player to spectate if too many players - auto& spectators_by_limit = getCrownManager()->getSpectatorsByLimit(); - if (spectators_by_limit.size() == peers.size()) - { - // produce no log spam for now - // Log::error("ServerLobby", "Too many players and cannot set " - // "spectate for late coming players!"); - return; - } - for (auto &peer : spectators_by_limit) - { - peer->setAlwaysSpectate(ASM_FULL); - peer->setWaitingForGame(true); - always_spectate_peers.insert(peer); - } - - // Remove karts / tracks from server that are not supported on all clients - std::vector> erasingPeers; - bool has_peer_plays_game = false; - for (auto peer : peers) - { - if (!peer->isValidated() || peer->isWaitingForGame()) - continue; - bool can_race = getCrownManager()->canRace(peer); - if (!can_race && !peer->alwaysSpectate()) - { - peer->setAlwaysSpectate(ASM_FULL); - peer->setWaitingForGame(true); - m_peers_ready.erase(peer); - need_to_update = true; - always_spectate_peers.insert(peer); - continue; - } - else if (peer->alwaysSpectate()) - { - always_spectate_peers.insert(peer); - continue; - } - // I might introduce an extra use for a peer that leaves at the same moment. Please investigate later. - erasingPeers.push_back(peer); - if (!peer->isAIPeer()) - has_peer_plays_game = true; - } - - // kimden thinks if someone wants to race he should disable spectating - // // Disable always spectate peers if no players join the game - if (!has_peer_plays_game) - { - if (event) - { - // inside if to not produce log spam for ownerless - Log::warn("ServerLobby", - "An attempt to start a game while no one can play."); - Comm::sendStringToPeer(event->getPeerSP(), "No one can play!"); - } - addWaitingPlayersToGame(); - return; - // for (std::shared_ptr peer : always_spectate_peers) - // peer->setAlwaysSpectate(ASM_NONE); - // always_spectate_peers.clear(); - } - else - { - // We make those always spectate peer waiting for game so it won't - // be able to vote, this will be reset in STKHost::getPlayersForNewGame - // This will also allow a correct number of in game players for max - // arena players handling - for (std::shared_ptr peer : always_spectate_peers) - peer->setWaitingForGame(true); - } - - getAssetManager()->eraseAssetsWithPeers(erasingPeers); - - max_player = 0; - STKHost::get()->updatePlayers(&max_player); - if (auto ai = m_ai_peer.lock()) + // No need to check for result as server will be auto-cleared anyway + // when no polling is done + if (now) { - if (supportsAI()) - { - unsigned total_ai_available = - (unsigned)ai->getPlayerProfiles().size(); - m_ai_count = max_player > total_ai_available ? - 0 : total_ai_available - max_player + 1; - // Disable ai peer for this game - if (m_ai_count == 0) - ai->setValidated(false); - else - ai->setValidated(true); - } - else - { - ai->setValidated(false); - m_ai_count = 0; - } + request->executeNow(); } else - m_ai_count = 0; - - if (!getAssetManager()->tryApplyingMapFilters()) - { - Log::error("ServerLobby", "No tracks for playing!"); - return; - } - - getSettings()->initializeDefaultVote(); - - if (getSettings()->isLegacyGPMode()) - { - ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION); - if (m_server_id_online.load() != 0) - { - unregisterServer(false/*now*/, - std::dynamic_pointer_cast(shared_from_this())); - } - } - - startVotingPeriod(getSettings()->getVotingTimeout()); - - peers = STKHost::get()->getPeers(); - for (auto& peer: peers) - { - if (peer->isDisconnected() && !peer->isValidated()) - continue; - if (!getCrownManager()->canRace(peer) || peer->isWaitingForGame()) - continue; // they are handled below - - 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(getSettings()->getVotingTimeout()) - .addUInt8(/*m_game_setup->isGrandPrixStarted() ? 1 : */0) - .addUInt8((!getSettings()->hasNoLapRestrictions() ? 1 : 0)) - .addUInt8(getSettings()->hasTrackVoting() ? 1 : 0); - - - std::set all_k = peer->getClientAssets().first; - std::string username = peer->getMainName(); - // std::string username = StringUtils::wideToUtf8(profile->getName()); - getAssetManager()->applyAllKartFilters(username, all_k); - - if (!getKartElimination()->getRemainingParticipants().empty() && getKartElimination()->getRemainingParticipants().count(username) == 0) - { - if (all_k.count(getKartElimination()->getKart())) - all_k = {getKartElimination()->getKart()}; - else - all_k = {}; - } - - getAssetManager()->encodePlayerKartsAndCommonMaps(ns, all_k); - - peer->sendPacket(ns, PRM_RELIABLE); - delete ns; - - if (getQueues()->areKartFiltersIgnoringKarts()) - Comm::sendStringToPeer(peer, "The server will ignore your kart choice"); - } - - m_state = SELECTING; - if (need_to_update || !always_spectate_peers.empty()) - { - NetworkString* back_lobby = getNetworkString(2); - back_lobby->setSynchronous(true); - back_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_SPECTATING_NEXT_GAME); - STKHost::get()->sendPacketToAllPeersWith( - [always_spectate_peers](std::shared_ptr peer) - { - return always_spectate_peers.find(peer) != - always_spectate_peers.end(); - }, back_lobby, PRM_RELIABLE); - delete back_lobby; - updatePlayerList(); - } - - if (getSettings()->isLegacyGPMode()) - { - // 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 - setInfiniteTimeout(); - - getGPManager()->onStartSelection(); + request->queue(); - getCommandManager()->onStartSelection(); -} // startSelection +} // unregisterServer //----------------------------------------------------------------------------- /** Query the STK server for connection requests. For each connection request @@ -2035,94 +752,6 @@ void ServerLobby::checkIncomingConnectionRequests() } // 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::get()->isRunning()); - assert(World::getWorld()); - if (!RaceEventManager::get()->isRaceOver()) return; - - if (isTournament()) - getTournament()->onRaceFinished(); - - if (RaceManager::get()->getMinorMode() == - RaceManager::MINOR_MODE_SOCCER) - Log::info("ServerLobby", "SoccerMatchLog: The game is considered finished."); - else - Log::info("ServerLobby", "The game is considered finished."); - // notify the network world that it is stopped - RaceEventManager::get()->stop(); - RaceManager::get()->resetHitProcessor(); - - // stop race protocols before going back to lobby (end race) - RaceEventManager::get()->getProtocol()->requestTerminate(); - GameProtocol::lock()->requestTerminate(); - - // Save race result before delete the world - m_result_ns = getNetworkString(); - m_result_ns->setSynchronous(true); - m_result_ns->addUInt8(LE_RACE_FINISHED); - std::vector gp_changes; - if (m_game_setup->isGrandPrix()) - { - getGPManager()->updateGPScores(gp_changes, m_result_ns); - } - else if (RaceManager::get()->modeHasLaps()) - { - int fastest_lap = - static_cast(World::getWorld())->getFastestLapTicks(); - m_result_ns->addUInt32(fastest_lap); - m_result_ns->encodeString(static_cast(World::getWorld()) - ->getFastestLapKartName()); - } - - uint8_t ranking_changes_indication = 0; - if (getSettings()->isRanked() && RaceManager::get()->modeHasLaps()) - ranking_changes_indication = 1; - if (m_game_setup->isGrandPrix()) - ranking_changes_indication = 1; - m_result_ns->addUInt8(ranking_changes_indication); - - if (getKartElimination()->isEnabled()) - { - std::string msg = getKartElimination()->onRaceFinished(); - if (!msg.empty()) - Comm::sendStringToAllPeers(msg); - } - - if (getSettings()->isStoringResults()) - { - if (m_game_info) - m_game_info->fillAndStoreResults(); - else - Log::warn("ServerLobby", "GameInfo is not accessible??"); - } - - if (getSettings()->isRanked()) - { - computeNewRankings(m_result_ns); - submitRankingsToAddons(); - } - else if (m_game_setup->isGrandPrix()) - { - unsigned player_count = RaceManager::get()->getNumPlayers(); - m_result_ns->addUInt8((uint8_t)player_count); - for (unsigned i = 0; i < player_count; i++) - { - m_result_ns->addFloat(gp_changes[i]); - } - } - m_state.store(WAIT_FOR_RACE_STOPPED); - - getAssetManager()->gameFinishedOn(RaceManager::get()->getTrackName()); - - getQueues()->popOnRaceFinished(); -} // checkRaceFinished - //----------------------------------------------------------------------------- /** Compute the new player's rankings used in ranked servers @@ -2314,8 +943,7 @@ bool ServerLobby::handleAssetsAndAddonScores(std::shared_ptr peer, peer->setAvailableKartsTracks(client_karts, client_maps); peer->setAddonsScores(addons_scores); - if (m_process_type == PT_CHILD && - peer->getHostId() == m_client_server_host_id.load()) + if (isChildClientServerHost(peer)) { // Update child process addons list too so player can choose later getAssetManager()->updateAddons(); @@ -2879,6 +1507,10 @@ void ServerLobby::updatePlayerList(bool update_when_reset_server) for (int i = angry_host; i > 0; --i) profile_name = StringUtils::utf32ToWide({0x1F528}) + profile_name; + profile_name = StringUtils::utf8ToWide( + StringUtils::insertValues("<%s> ", (int)profile->getPeer()->getRoomNumber() + )) + profile_name; + std::string prefix = ""; for (const std::string& category: getTeamManager()->getVisibleCategoriesForPlayer(utf8_profile_name)) { @@ -2942,155 +1574,8 @@ void ServerLobby::updatePlayerList(bool update_when_reset_server) }, pl); delete pl; } // updatePlayerList -//----------------------------------------------------------------------------- - -void ServerLobby::updateServerOwner(bool force) -{ - ServerState state = m_state.load(); - if (state < WAITING_FOR_START_GAME || state > RESULT_DISPLAY) - return; - - if (getCrownManager()->isOwnerLess()) - return; - - if (!force && !m_server_owner.expired()) - return; - - auto peers = STKHost::get()->getPeers(); - - if (m_process_type != PT_MAIN) - { - auto id = m_client_server_host_id.load(); - for (unsigned i = 0; i < peers.size(); ) - { - const auto& peer = peers[i]; - if (peer->getHostId() != id) - { - std::swap(peers[i], peers.back()); - peers.pop_back(); - continue; - } - ++i; - } - } - - for (unsigned i = 0; i < peers.size(); ) - { - const auto& peer = peers[i]; - if (!peer->isValidated() || peer->isAIPeer()) - { - std::swap(peers[i], peers.back()); - peers.pop_back(); - continue; - } - ++i; - } - - if (peers.empty()) - return; - - std::shared_ptr owner = getCrownManager()->getFirstInCrownOrder(peers); - if (m_server_owner.expired() || m_server_owner.lock() != 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(); - std::shared_ptr peer = event->getPeerSP(); - 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 || !getSettings()->hasTrackVoting()) - { - 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; - - if (!canVote(event->getPeerSP())) 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 = TrackManager::get()->getTrack(vote.m_track_name); - if (!t) - { - vote.m_track_name = getAssetManager()->getAnyMapForVote(); - t = TrackManager::get()->getTrack(vote.m_track_name); - assert(t); - } - - // Remove / adjust any invalid settings - if (isTournament()) - { - getTournament()->applyRestrictionsOnVote(&vote); - } - else - { - getSettings()->applyRestrictionsOnVote(&vote, t); - } - - // Store vote: - vote.m_player_name = event->getPeer()->getMainProfile()->getName(); - addVote(event->getPeer()->getHostId(), vote); - - // After adding the vote, show decorated name instead - vote.m_player_name = event->getPeer()->getMainProfile()->getDecoratedName(m_name_decorator); - - // 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); - Comm::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). @@ -3148,41 +1633,16 @@ void ServerLobby::finishedLoadingWorld() 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 - //----------------------------------------------------------------------------- bool ServerLobby::waitingForPlayers() const { if (getSettings()->isLegacyGPMode() && getSettings()->isLegacyGPModeStarted()) return false; - return m_state.load() >= WAITING_FOR_START_GAME; -} // waitingForPlayers + return m_init_state.load() >= RUNNING; +} // waitingForPlayers //----------------------------------------------------------------------------- + void ServerLobby::handlePendingConnection() { std::lock_guard lock(m_keys_mutex); @@ -3263,185 +1723,45 @@ void ServerLobby::getRankingForPlayer(std::shared_ptr p) bool success = false; if (result->get("success", &rec_success)) if (rec_success == "yes") - success = true; - - if (!success) - { - Log::error("ServerLobby", "No ranking info found for player %s.", - StringUtils::wideToUtf8(p->getName()).c_str()); - // Kick the player to avoid his score being reset in case - // connection to stk addons is broken - auto peer = p->getPeer(); - if (peer) - { - peer->kick(); - return; - } - } - m_ranking->fill(id, result, p); -} // getRankingForPlayer - -//----------------------------------------------------------------------------- -void ServerLobby::submitRankingsToAddons() -{ - // No ranking for battle mode - if (!RaceManager::get()->modeHasLaps()) - return; - - for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) - { - const uint32_t id = RaceManager::get()->getKartInfo(i).getOnlineId(); - const RankingEntry& scores = m_ranking->getScores(id); - auto request = std::make_shared - (scores, RaceManager::get()->getKartInfo(i).getCountryCode()); - NetworkConfig::get()->setUserDetails(request, "submit-ranking"); - Log::info("ServerLobby", "Submitting ranking for %s (%d) : %lf, %lf %d", - StringUtils::wideToUtf8( - RaceManager::get()->getKartInfo(i).getPlayerName()).c_str(), id, - scores.score, scores.max_score, scores.races); - 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 = getSettings()->getMaxPing(); - bool peer_exceeded_max_ping = false; - for (auto p : m_peers_ready) - { - auto peer = p.first.lock(); - // Spectators don't send input so we don't need to delay for them - if (!peer || peer->alwaysSpectate()) - 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); - peer_exceeded_max_ping = true; - continue; - } - max_ping = std::max(peer->getAveragePing(), max_ping); - } - if ((getSettings()->hasHighPingWorkaround() && peer_exceeded_max_ping) || - (getSettings()->isLivePlayers() && RaceManager::get()->supportsLiveJoining())) - { - Log::info("ServerLobby", "Max ping to ServerConfig::m_max_ping for " - "live joining or high ping workaround."); - max_ping = getSettings()->getMaxPing(); - } - // 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)Track::getCurrentTrack()->getCheckManager()->getCheckStructureCount(); - ns->addUInt8(cc); - *ns += *m_items_complete_state; - m_client_starting_time = start_time; - Comm::sendMessageToPeers(ns, PRM_RELIABLE); - - const unsigned jitter_tolerance = getSettings()->getJitterTolerance(); - 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); - // Different stk process thread may have different stk host - STKHost* stk_host = STKHost::get(); - joinStartGameThread(); - m_start_game_thread = std::thread([start_time, stk_host, this]() - { - const uint64_t cur_time = stk_host->getNetworkTimer(); - assert(start_time > cur_time); - int sleep_time = (int)(start_time - cur_time); - //Log::info("ServerLobby", "Start game after %dms", sleep_time); - StkTime::sleep(sleep_time); - //Log::info("ServerLobby", "Started at %lf", StkTime::getRealTime()); - m_state.store(RACING); - }); -} // configPeersStartTime - -//----------------------------------------------------------------------------- -void ServerLobby::addWaitingPlayersToGame() -{ - auto all_profiles = STKHost::get()->getAllPlayerProfiles(); - for (auto& profile : all_profiles) - { - auto peer = profile->getPeer(); - if (!peer || !peer->isValidated()) - continue; - - peer->resetAlwaysSpectateFull(); - peer->setWaitingForGame(false); - peer->setSpectator(false); - if (m_peers_ready.find(peer) == m_peers_ready.end()) - { - m_peers_ready[peer] = false; - if (!getSettings()->hasSqlManagement()) - { - 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 (getSettings()->isRanked() && !m_ranking->has(online_id)) + success = true; + + if (!success) + { + Log::error("ServerLobby", "No ranking info found for player %s.", + StringUtils::wideToUtf8(p->getName()).c_str()); + // Kick the player to avoid his score being reset in case + // connection to stk addons is broken + auto peer = p->getPeer(); + if (peer) { - getRankingForPlayer(peer->getMainProfile()); + peer->kick(); + return; } } - // Re-activiate the ai - if (auto ai = m_ai_peer.lock()) - ai->setValidated(true); -} // addWaitingPlayersToGame + m_ranking->fill(id, result, p); +} // getRankingForPlayer //----------------------------------------------------------------------------- -void ServerLobby::resetServer() +void ServerLobby::submitRankingsToAddons() { - 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); - Comm::sendMessageToPeersInServer(server_info); - delete server_info; + // No ranking for battle mode + if (!RaceManager::get()->modeHasLaps()) + return; - for (auto p : m_peers_ready) + for (unsigned i = 0; i < RaceManager::get()->getNumPlayers(); i++) { - if (auto peer = p.first.lock()) - peer->updateLastActivity(); + const uint32_t id = RaceManager::get()->getKartInfo(i).getOnlineId(); + const RankingEntry& scores = m_ranking->getScores(id); + auto request = std::make_shared + (scores, RaceManager::get()->getKartInfo(i).getCountryCode()); + NetworkConfig::get()->setUserDetails(request, "submit-ranking"); + Log::info("ServerLobby", "Submitting ranking for %s (%d) : %lf, %lf %d", + StringUtils::wideToUtf8( + RaceManager::get()->getKartInfo(i).getPlayerName()).c_str(), id, + scores.score, scores.max_score, scores.races); + request->queue(); } - - setup(); - m_state = NetworkConfig::get()->isLAN() ? - WAITING_FOR_START_GAME : REGISTER_SELF_ADDRESS; - - if (m_state.load() == WAITING_FOR_START_GAME) - if (m_reset_to_default_mode_later.exchange(false)) - handleServerConfiguration(NULL); - - updatePlayerList(); -} // resetServer +} // submitRankingsToAddons //----------------------------------------------------------------------------- void ServerLobby::testBannedForIP(std::shared_ptr peer) const @@ -3676,528 +1996,134 @@ void ServerLobby::handleServerConfiguration(std::shared_ptr peer, auto peers = STKHost::get()->getPeers(); for (auto& peer : peers) { - auto assets = peer->getClientAssets(); - if (!peer->isValidated() || assets.second.empty()) // this check will fail hard when I introduce vavriable limits - continue; - - if (getAssetManager()->checkIfNoCommonMaps(assets)) - { - NetworkString *message = getNetworkString(2); - message->setSynchronous(true); - message->addUInt8(LE_CONNECTION_REFUSED) - .addUInt8(RR_INCOMPATIBLE_DATA); - peer->cleanPlayerProfiles(); - peer->sendPacket(message, PRM_RELIABLE); - peer->reset(); - delete message; - Log::verbose("ServerLobby", - "Player has incompatible tracks for new game mode."); - } - } - - if (teams_before ^ teams_after) - { - if (teams_after) - { - int final_number, players_number; - getTeamManager()->clearTeams(); - getTeamManager()->assignRandomTeams(2, &final_number, &players_number); - } - else - { - getTeamManager()->clearTemporaryTeams(); - } - for (auto& peer : STKHost::get()->getPeers()) - { - if (teams_before) - peer->resetNoTeamSpectate(); - for (auto &profile: peer->getPlayerProfiles()) - { - // updates KartTeam - getTeamManager()->setTemporaryTeamInLobby(profile, profile->getTemporaryTeam()); - } - } - } - - NetworkString* server_info = getNetworkString(); - server_info->setSynchronous(true); - server_info->addUInt8(LE_SERVER_INFO); - m_game_setup->addServerInfo(server_info); - Comm::sendMessageToPeers(server_info); - delete server_info; - - updatePlayerList(); - - if (getKartElimination()->isEnabled() && - RaceManager::get()->getMinorMode() != RaceManager::MINOR_MODE_NORMAL_RACE && - RaceManager::get()->getMinorMode() != RaceManager::MINOR_MODE_TIME_TRIAL) - { - getKartElimination()->disable(); - Comm::sendStringToAllPeers("Gnu Elimination is disabled because of non-racing mode"); - } -} // handleServerConfiguration -//----------------------------------------------------------------------------- -/*! \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 (event != NULL && event->getPeerSP() != m_server_owner.lock()) - { - Log::warn("ServerLobby", - "Client %d is not authorised to config server.", - event->getPeer()->getHostId()); - return; - } - int new_difficulty = getSettings()->getServerDifficulty(); - int new_game_mode = getSettings()->getServerMode(); - bool new_soccer_goal_target = getSettings()->isSoccerGoalTargetInConfig(); - if (event != NULL) - { - NetworkString& data = event->data(); - new_difficulty = data.getUInt8(); - new_game_mode = data.getUInt8(); - new_soccer_goal_target = data.getUInt8() == 1; - } - handleServerConfiguration( - (event ? event->getPeerSP() : std::shared_ptr()), - new_difficulty, new_game_mode, new_soccer_goal_target); -} // 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 handicap_id = data.getUInt8(); - if (handicap_id >= HANDICAP_COUNT) - { - Log::warn("ServerLobby", "Wrong handicap %d.", handicap_id); - return; - } - HandicapLevel h = (HandicapLevel)handicap_id; - player->setHandicap(h); - 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 < RaceManager::get()->getNumPlayers(); i++) - { - RemoteKartInfo& rki = RaceManager::get()->getKartInfo(i); - if (rki.isReserved()) - continue; - bool disconnected = rki.disconnected(); - if (RaceManager::get()->getKartInfo(i).getKartTeam() == KART_TEAM_RED && - !disconnected) - red_count++; - else if (RaceManager::get()->getKartInfo(i).getKartTeam() == - KART_TEAM_BLUE && !disconnected) - blue_count++; - - if (!disconnected) - { - total++; - continue; - } - - if (m_game_info) - m_game_info->saveDisconnectingIdInfo(i); - else - Log::warn("ServerLobby", "GameInfo is not accessible??"); - - 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*/); - if (getSettings()->isRanked()) - { - // Handle disconnection earlier to prevent cheating by joining - // another ranked server - // Real score will be submitted later in computeNewRankings - const uint32_t id = - RaceManager::get()->getKartInfo(i).getOnlineId(); - RankingEntry penalized = m_ranking->getTemporaryPenalizedScores(id); - auto request = std::make_shared - (penalized, - RaceManager::get()->getKartInfo(i).getCountryCode()); - NetworkConfig::get()->setUserDetails(request, - "submit-ranking"); - request->queue(); - } - 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 (!getSettings()->isLivePlayers() && - 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 (!getSettings()->isLivePlayers() || !RaceManager::get()->supportsLiveJoining()) - return; - if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL) - { - Track* t = TrackManager::get()->getTrack(m_game_setup->getCurrentTrack()); - assert(t); - int max_players = std::min((int)getSettings()->getServerMaxPlayers(), - (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, std::shared_ptr peer) const -{ - unsigned player_count = ns.getUInt8(); - player_count = std::min(player_count, (unsigned)peer->getPlayerProfiles().size()); - for (unsigned i = 0; i < player_count; i++) - { - std::string kart; - ns.decodeString(&kart); - std::string username = StringUtils::wideToUtf8( - peer->getPlayerProfiles()[i]->getName()); - if (getKartElimination()->isEliminated(username)) - { - peer->getPlayerProfiles()[i]->setKartName(getKartElimination()->getKart()); - continue; - } - std::string current_kart = kart; - if (kart.find("randomkart") != std::string::npos || - (kart.find("addon_") == std::string::npos && - !getAssetManager()->isKartAvailable(kart))) - { - current_kart = ""; - } - if (getQueues()->areKartFiltersIgnoringKarts()) - current_kart = ""; - std::string name = StringUtils::wideToUtf8(peer->getPlayerProfiles()[i]->getName()); - peer->getPlayerProfiles()[i]->setKartName( - getAssetManager()->getKartForBadKartChoice(peer, name, current_kart)); - } - if (peer->getClientCapabilities().find("real_addon_karts") == - peer->getClientCapabilities().end() || ns.size() == 0) - return; - for (unsigned i = 0; i < player_count; i++) - { - KartData kart_data(ns); - std::string type = kart_data.m_kart_type; - auto& player = peer->getPlayerProfiles()[i]; - const std::string& kart_id = player->getKartName(); - setKartDataProperly(kart_data, kart_id, player, type); - } -} // 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; - - std::shared_ptr peer = event->getPeerSP(); - const NetworkString& data = event->data(); - uint8_t kart_id = data.getUInt8(); - if (kart_id > RaceManager::get()->getNumPlayers()) - return; - - AbstractKart* k = w->getKart(kart_id); - int live_join_util_ticks = k->getLiveJoinUntilTicks(); - - const RemoteKartInfo& rki = RaceManager::get()->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.getHandicap()) - .addUInt8((uint8_t)rki.getLocalPlayerId()) - .encodeString(rki.getKartName()).encodeString(rki.getCountryCode()); - if (peer->getClientCapabilities().find("real_addon_karts") != - peer->getClientCapabilities().end()) - rki.getKartData().encode(ns); - peer->sendPacket(ns, PRM_RELIABLE); - delete ns; - - - FreeForAll* ffa_world = dynamic_cast(World::getWorld()); - if (ffa_world) - ffa_world->notifyAboutScoreIfNonzero(kart_id); -} // 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; - } - - if (m_process_type == PT_CHILD && - event->getPeer()->getHostId() == m_client_server_host_id.load()) - { - // For child server the remaining client cannot go on player when the - // server owner quited the game (because the world will be deleted), so - // we reset all players - auto pm = ProtocolManager::lock(); - if (RaceEventManager::get()) - { - RaceEventManager::get()->stop(); - pm->findAndTerminate(PROTOCOL_GAME_EVENTS); - } - auto gp = GameProtocol::lock(); - if (gp) + auto assets = peer->getClientAssets(); + if (!peer->isValidated() || assets.second.empty()) // this check will fail hard when I introduce vavriable limits + continue; + + if (getAssetManager()->checkIfNoCommonMaps(assets)) { - auto lock = gp->acquireWorldDeletingMutex(); - pm->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS); - exitGameState(); + NetworkString *message = getNetworkString(2); + message->setSynchronous(true); + message->addUInt8(LE_CONNECTION_REFUSED) + .addUInt8(RR_INCOMPATIBLE_DATA); + peer->cleanPlayerProfiles(); + peer->sendPacket(message, PRM_RELIABLE); + peer->reset(); + delete message; + Log::verbose("ServerLobby", + "Player has incompatible tracks for new game mode."); } - else - exitGameState(); - NetworkString* back_to_lobby = getNetworkString(2); - back_to_lobby->setSynchronous(true); - back_to_lobby->addUInt8(LE_BACK_LOBBY) - .addUInt8(BLR_SERVER_ONWER_QUITED_THE_GAME); - Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); - delete back_to_lobby; - m_rs_state.store(RS_ASYNC_RESET); - return; } - if (m_game_info) - m_game_info->saveDisconnectingPeerInfo(peer); - else - Log::warn("ServerLobby", "GameInfo is not accessible??"); - - for (const int id : peer->getAvailableKartIDs()) + if (teams_before ^ teams_after) { - RemoteKartInfo& rki = RaceManager::get()->getKartInfo(id); - if (rki.getHostId() == peer->getHostId()) + if (teams_after) { - Log::info("ServerLobby", "%s left the game with kart id %d.", - peer->getAddress().toString().c_str(), id); - rki.setNetworkPlayerProfile( - std::shared_ptr()); + int final_number, players_number; + getTeamManager()->clearTeams(); + getTeamManager()->assignRandomTeams(2, &final_number, &players_number); } else { - Log::error("ServerLobby", "%s doesn't exist anymore in server.", - peer->getAddress().toString().c_str()); + getTeamManager()->clearTemporaryTeams(); + } + for (auto& peer : STKHost::get()->getPeers()) + { + if (teams_before) + peer->resetNoTeamSpectate(); + for (auto &profile: peer->getPlayerProfiles()) + { + // updates KartTeam + getTeamManager()->setTemporaryTeamInLobby(profile, profile->getTemporaryTeam()); + } } } - NetworkItemManager* nim = dynamic_cast - (Track::getCurrentTrack()->getItemManager()); - 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, PRM_RELIABLE); - 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, PRM_RELIABLE); + Comm::sendMessageToPeers(server_info); delete server_info; - peer->updateLastActivity(); -} // clientInGameWantsToBackLobby + updatePlayerList(); + if (getKartElimination()->isEnabled() && + RaceManager::get()->getMinorMode() != RaceManager::MINOR_MODE_NORMAL_RACE && + RaceManager::get()->getMinorMode() != RaceManager::MINOR_MODE_TIME_TRIAL) + { + getKartElimination()->disable(); + Comm::sendStringToAllPeers("Gnu Elimination is disabled because of non-racing mode"); + } +} // handleServerConfiguration //----------------------------------------------------------------------------- -/** Client if currently select assets wants to go back to lobby. +/*! \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::clientSelectingAssetsWantsToBackLobby(Event* event) +void ServerLobby::handleServerConfiguration(Event* event) { - std::shared_ptr peer = event->getPeerSP(); - - if (m_state.load() != SELECTING || peer->isWaitingForGame()) + if (event != NULL && event->getPeerSP() != m_server_owner.lock()) { Log::warn("ServerLobby", - "%s try to leave selecting assets at wrong time.", - peer->getAddress().toString().c_str()); + "Client %d is not authorised to config server.", + event->getPeer()->getHostId()); return; } - - if (m_process_type == PT_CHILD && - event->getPeer()->getHostId() == m_client_server_host_id.load()) + int new_difficulty = getSettings()->getServerDifficulty(); + int new_game_mode = getSettings()->getServerMode(); + bool new_soccer_goal_target = getSettings()->isSoccerGoalTargetInConfig(); + if (event != NULL) { - NetworkString* back_to_lobby = getNetworkString(2); - back_to_lobby->setSynchronous(true); - back_to_lobby->addUInt8(LE_BACK_LOBBY) - .addUInt8(BLR_SERVER_ONWER_QUITED_THE_GAME); - Comm::sendMessageToPeersInServer(back_to_lobby, PRM_RELIABLE); - delete back_to_lobby; - resetVotingTime(); - resetServer(); - m_rs_state.store(RS_NONE); - return; + NetworkString& data = event->data(); + new_difficulty = data.getUInt8(); + new_game_mode = data.getUInt8(); + new_soccer_goal_target = data.getUInt8() == 1; } - - 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, PRM_RELIABLE); - 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, PRM_RELIABLE); - delete server_info; - - peer->updateLastActivity(); -} // clientSelectingAssetsWantsToBackLobby - -//----------------------------------------------------------------------------- -void ServerLobby::saveInitialItems(std::shared_ptr nim) -{ - m_items_complete_state->getBuffer().clear(); - m_items_complete_state->reset(); - nim->saveCompleteState(m_items_complete_state); -} // saveInitialItems - -//----------------------------------------------------------------------------- -bool ServerLobby::supportsAI() -{ - return getGameMode() == 3 || getGameMode() == 4; -} // supportsAI + handleServerConfiguration( + (event ? event->getPeerSP() : std::shared_ptr()), + new_difficulty, new_game_mode, new_soccer_goal_target); +} // handleServerConfiguration //----------------------------------------------------------------------------- -bool ServerLobby::checkPeersReady(bool ignore_ai_peer, SelectionPhase phase) +/*! \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) { - bool all_ready = true; - bool someone_races = false; - for (auto p : m_peers_ready) + NetworkString& data = event->data(); + if (m_state.load() != WAITING_FOR_START_GAME && + !event->getPeer()->isWaitingForGame()) { - auto peer = p.first.lock(); - if (!peer) - continue; - if (phase == BEFORE_SELECTION && peer->alwaysSpectate()) - continue; - if (phase == AFTER_GAME && peer->isSpectator()) - continue; - if (ignore_ai_peer && peer->isAIPeer()) - continue; - if (phase == BEFORE_SELECTION && !getCrownManager()->canRace(peer)) - continue; - someone_races = true; - all_ready = all_ready && p.second; - if (!all_ready) - return false; + 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 handicap_id = data.getUInt8(); + if (handicap_id >= HANDICAP_COUNT) + { + Log::warn("ServerLobby", "Wrong handicap %d.", handicap_id); + return; } - return someone_races; -} // checkPeersReady + HandicapLevel h = (HandicapLevel)handicap_id; + player->setHandicap(h); + updatePlayerList(); +} // changeHandicap //----------------------------------------------------------------------------- void ServerLobby::handleServerCommand(Event* event) @@ -4211,20 +2137,6 @@ void ServerLobby::handleServerCommand(Event* event) } // handleServerCommand //----------------------------------------------------------------------------- -void ServerLobby::resetToDefaultSettings() -{ - if (getSettings()->isServerConfigurable() && !getSettings()->isPreservingMode()) - { - if (m_state == WAITING_FOR_START_GAME) - handleServerConfiguration(NULL); - else - m_reset_to_default_mode_later.store(true); - } - - getSettings()->onResetToDefaultSettings(); -} // resetToDefaultSettings -//----------------------------------------------------------------------------- - void ServerLobby::writeOwnReport(std::shared_ptr reporter, std::shared_ptr reporting, const std::string& info) { #ifdef ENABLE_SQLITE3 @@ -4274,73 +2186,6 @@ std::string ServerLobby::encodeProfileNameForPeer( } // encodeProfileNameForPeer //----------------------------------------------------------------------------- -bool ServerLobby::canVote(std::shared_ptr peer) const -{ - if (!peer || peer->getPlayerProfiles().empty()) - return false; - - if (!isTournament()) - return true; - - return getTournament()->canVote(peer); -} // canVote -//----------------------------------------------------------------------------- - -bool ServerLobby::hasHostRights(std::shared_ptr peer) const -{ - if (!peer || peer->getPlayerProfiles().empty()) - return false; - - if (peer == m_server_owner.lock()) - return true; - - if (peer->hammerLevel() > 0) - return true; - - if (isTournament()) - return getTournament()->hasHostRights(peer); - - return false; -} // hasHostRights -//----------------------------------------------------------------------------- - -int ServerLobby::getPermissions(std::shared_ptr peer) const -{ - int mask = 0; - if (!peer) - return mask; - bool isSpectator = (peer->alwaysSpectate()); - if (isSpectator) - { - mask |= CommandPermissions::PE_SPECTATOR; - mask |= CommandPermissions::PE_VOTED_SPECTATOR; - } - else - { - mask |= CommandPermissions::PE_USUAL; - mask |= CommandPermissions::PE_VOTED_NORMAL; - } - if (peer == m_server_owner.lock()) - { - mask |= CommandPermissions::PE_CROWNED; - if (getCrownManager()->hasOnlyHostRiding()) - mask |= CommandPermissions::PE_SINGLE; - } - int hammer_level = peer->hammerLevel(); - if (hammer_level >= 1) - { - mask |= CommandPermissions::PE_HAMMER; - if (hammer_level >= 2) - mask |= CommandPermissions::PE_MANIPULATOR; - } - else if (getTournament() && getTournament()->hasHammerRights(peer)) - { - mask |= CommandPermissions::PE_HAMMER; - } - return mask; -} // getPermissions -//----------------------------------------------------------------------------- - bool ServerLobby::writeOnePlayerReport(std::shared_ptr reporter, const std::string& table, const std::string& info) { @@ -4414,41 +2259,6 @@ bool ServerLobby::isSoccerGoalTarget() const } // isSoccerGoalTarget //----------------------------------------------------------------------------- -void ServerLobby::setKartDataProperly(KartData& kart_data, const std::string& kart_name, - std::shared_ptr player, - const std::string& type) const -{ - // This should set kart data for kart name at least in the following cases: - // 1. useTuxHitboxAddon() is true - // 2. kart_name is installed on the server - // (for addons; standard karts are not processed here it seems) - // 3. kart type is fine - // Maybe I'm mistaken and then it should be fixed. - // I extracted this into a separate function because if kart_name is set - // by the server (for random addon kart, or due to a filter), kart data - // has to be set in another place than default one. - if (NetworkConfig::get()->useTuxHitboxAddon() && - StringUtils::startsWith(kart_name, "addon_") && - kart_properties_manager->hasKartTypeCharacteristic(type)) - { - const KartProperties* real_addon = - kart_properties_manager->getKart(kart_name); - if (getSettings()->usesRealAddonKarts() && real_addon) - { - kart_data = KartData(real_addon); - } - else - { - const KartProperties* tux_kp = - kart_properties_manager->getKart("tux"); - kart_data = KartData(tux_kp); - kart_data.m_kart_type = type; - } - player->setKartData(kart_data); - } -} // setKartDataProperly -//----------------------------------------------------------------------------- - bool ServerLobby::playerReportsTableExists() const { #ifdef ENABLE_SQLITE3 @@ -4477,66 +2287,149 @@ bool ServerLobby::isLegacyGPMode() const } // isLegacyGPMode //----------------------------------------------------------------------------- -int ServerLobby::getCurrentStateScope() -{ - auto state = m_state.load(); - if (state < WAITING_FOR_START_GAME - || state > RESULT_DISPLAY) - return 0; - if (state == WAITING_FOR_START_GAME) - return CommandManager::StateScope::SS_LOBBY; - return CommandManager::StateScope::SS_INGAME; -} // getCurrentStateScope -//----------------------------------------------------------------------------- - bool ServerLobby::isClientServerHost(const std::shared_ptr& peer) { return peer->getHostId() == m_client_server_host_id.load(); } // isClientServerHost //----------------------------------------------------------------------------- -void ServerLobby::setTimeoutFromNow(int seconds) +void ServerLobby::unregisterServerForLegacyGPMode() +{ + if (getSettings()->isLegacyGPMode()) + { + ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION); + if (m_server_id_online.load() != 0) + { + unregisterServer(false/*now*/, + std::dynamic_pointer_cast(shared_from_this())); + } + } +} // unregisterServerForLegacyGPMode +//----------------------------------------------------------------------------- + +void ServerLobby::dropPendingConnectionsForLegacyGPMode() +{ + if (getSettings()->isLegacyGPMode()) + { + // 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(); + } +} // dropPendingConnectionsForLegacyGPMode +//----------------------------------------------------------------------------- + +void ServerLobby::addWaitingPlayersToRanking( + const std::shared_ptr& profile) +{ + auto peer = profile->getPeer(); + + // technically it's already checked... + if (!peer || !peer->isValidated()) + return; + + uint32_t online_id = profile->getOnlineId(); + if (getSettings()->isRanked() && !m_ranking->has(online_id)) + { + getRankingForPlayer(peer->getMainProfile()); + } +} // addWaitingPlayersToRanking +//----------------------------------------------------------------------------- + +bool ServerLobby::canIgnoreControllerEvents() const +{ + for (auto& room: m_rooms) + { + ServerPlayState state = room->getCurrentPlayState(); + if (state >= ServerPlayState::WAIT_FOR_WORLD_LOADED && + state <= ServerPlayState::RACING) + { + return false; + } + } + + return true; +} // canIgnoreControllerEvents +//----------------------------------------------------------------------------- + +bool ServerLobby::isPeerInGame(const std::shared_ptr& peer) const { - m_timeout.store((int64_t)StkTime::getMonoTimeMs() + - (int64_t)(seconds * 1000.0f)); -} // setTimeoutFromNow + auto room = getRoomForPeer(peer); + if (!room) + { + Log::warn("ServerLobby", "isPeerInGame: no room for peer??"); + return false; + } + + return room->getCurrentPlayState() > ServerPlayState::SELECTING + && !peer->isWaitingForGame(); +} // isPeerInGame //----------------------------------------------------------------------------- -void ServerLobby::setInfiniteTimeout() +bool ServerLobby::hasAnyGameStarted() const { - m_timeout.store(std::numeric_limits::max()); -} // setInfiniteTimeout + for (auto& room: m_rooms) + if (room->getCurrentPlayState() != ServerPlayState::WAITING_FOR_START_GAME) + return true; + + return false; +} // hasAnyGameStarted //----------------------------------------------------------------------------- -bool ServerLobby::isInfiniteTimeout() const +bool ServerLobby::isPastRegistrationPhase() const { - return m_timeout.load() == std::numeric_limits::max(); -} // isInfiniteTimeout + return getCurrentInitState() >= ServerInitState::RUNNING; +} // isPastRegistrationPhase //----------------------------------------------------------------------------- -bool ServerLobby::isTimeoutExpired() const +std::shared_ptr ServerLobby::getRoomForPeer(const std::shared_ptr& peer) const { - return m_timeout.load() < (int64_t)StkTime::getMonoTimeMs(); -} // isTimeoutExpired + if (!peer) + { + Log::warn("ServerLobby", "getRoomForPeer: no peer??"); + return {}; + } + + return getRoom(peer->getRoomNumber()); +} // getRoomForPeer //----------------------------------------------------------------------------- -float ServerLobby::getTimeUntilExpiration() const +std::shared_ptr ServerLobby::getRoom(int idx) const { - int64_t timeout = m_timeout.load(); - if (timeout == std::numeric_limits::max()) - return std::numeric_limits::max(); + if (idx < 0 || idx >= m_rooms.size()) + { + Log::warn("ServerLobby", "getRoom: invalid room number %d of %d", idx, m_rooms.size()); + return {}; + } - return (timeout - (int64_t)StkTime::getMonoTimeMs()) / 1000.0f; -} // getTimeUntilExpiration + return m_rooms[idx]; +} // getRoom //----------------------------------------------------------------------------- -void ServerLobby::onSpectatorStatusChange(const std::shared_ptr& peer) +int ServerLobby::getPermissions(std::shared_ptr peer) const { - auto state = m_state.load(); - if (state >= ServerLobby::SELECTING && state < ServerLobby::RACING) + auto room = getRoomForPeer(peer); + if (!room) { - erasePeerReady(peer); - peer->setWaitingForGame(true); + Log::warn("ServerLobby", "getPermissions: invalid room, cannot get permissions for peer"); + return PE_NONE; } -} // onSpectatorStatusChange + + return room->getPermissions(peer); +} // SL::getPermissions +//----------------------------------------------------------------------------- + +void ServerLobby::resetServerToRSA() +{ + if (!NetworkConfig::get()->isLAN()) + m_init_state.store(REGISTER_SELF_ADDRESS); + // I'm not sure I should do anything otherwise (setting state to RUNNING?) + +} // resetServerToRSA //----------------------------------------------------------------------------- diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 44728e6d4de..09ef9202812 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -22,6 +22,7 @@ #include "karts/controller/player_controller.hpp" #include "network/protocols/lobby_protocol.hpp" #include "network/requests.hpp" // only needed in header as long as KeyData is there +#include "network/server_enums.hpp" #include "utils/cpp2011.hpp" #include "utils/hourglass_reason.hpp" #include "utils/lobby_context.hpp" @@ -61,6 +62,9 @@ class Tournament; enum AlwaysSpectateMode: uint8_t; struct GameInfo; +// class PlayingRoom; +#include "network/protocols/playing_room.hpp" + namespace Online { class Request; @@ -68,76 +72,20 @@ namespace Online class ServerLobby : public LobbyProtocol, public LobbyContextUser { -public: - /* The state for a small finite state machine. */ - enum ServerState : unsigned int - { - SET_PUBLIC_ADDRESS, // Waiting to receive its public ip address - REGISTER_SELF_ADDRESS, // Register with STK online server - WAITING_FOR_START_GAME, // In lobby, waiting for (auto) start game - SELECTING, // kart, track, ... selection started - LOAD_WORLD, // Server starts loading world - WAIT_FOR_WORLD_LOADED, // Wait for clients and server to load world - WAIT_FOR_RACE_STARTED, // Wait for all clients to have started the race - RACING, // racing - WAIT_FOR_RACE_STOPPED, // Wait server for stopping all race protocols - RESULT_DISPLAY, // Show result screen - ERROR_LEAVE, // shutting down server - EXITING - }; - - enum SelectionPhase: unsigned int - { - BEFORE_SELECTION = 0, - LOADING_WORLD = 1, - AFTER_GAME = 2, - }; private: + std::atomic m_init_state; + #ifdef ENABLE_SQLITE3 void pollDatabase(); #endif - std::atomic m_state; - - /* The state used in multiple threads when reseting server. */ - enum ResetState : unsigned int - { - RS_NONE, // Default state - RS_WAITING, // Waiting for reseting finished - RS_ASYNC_RESET // Finished reseting server in main thread, now async thread - }; - - std::atomic m_rs_state; - - /** Hold the next connected peer for server owner if current one expired - * (disconnected). */ - std::weak_ptr m_server_owner; - - /** AI peer which holds the list of reserved AI for dedicated server. */ - std::weak_ptr m_ai_peer; - - /** AI profiles for all-in-one graphical client server, this will be a - * fixed count thorough the live time of server, which its value is - * configured in NetworkConfig. */ - std::vector > m_ai_profiles; - - std::atomic m_server_owner_id; - - /** Keeps track of the server state. */ - std::atomic_bool m_server_has_loaded_world; + std::vector> m_rooms; bool m_registered_for_once_only; - /** Counts how many peers have finished loading the world. */ - std::map, bool, - std::owner_less > > m_peers_ready; - std::weak_ptr m_server_registering; - /** Timeout counter for various state. */ - std::atomic m_timeout; - std::mutex m_keys_mutex; std::map m_keys; @@ -148,28 +96,14 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::map m_pending_peer_connection; - /* Saved the last game result */ - NetworkString* m_result_ns; - - /* Used to make sure clients are having same item list at start */ - BareNetworkString* m_items_complete_state; - std::atomic m_server_id_online; std::atomic m_client_server_host_id; - std::atomic m_difficulty; - - std::atomic m_game_mode; - - std::atomic m_lobby_players; - - std::atomic m_current_ai_count; - std::atomic m_last_success_poll_time; - uint64_t m_last_unsuccess_poll_time, m_server_started_at, m_server_delay; - + uint64_t m_last_unsuccess_poll_time; + // Other units previously used in ServerLobby std::shared_ptr m_lobby_context; @@ -177,28 +111,65 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr m_name_decorator; - unsigned m_item_seed; +private: + +#define Reworking "This method was moved to PlayingRoom but is still used for ServerLobby. This has to be changed," + +public: + ServerInitState getCurrentInitState() const { return m_init_state.load(); } + + [[deprecated(Reworking)]] + virtual bool isRacing() const OVERRIDE { return m_rooms[0]->isRacing(); } + + [[deprecated(Reworking)]] + int getDifficulty() const { return m_rooms[0]->getDifficulty(); } + + [[deprecated(Reworking)]] + int getGameMode() const { return m_rooms[0]->getGameMode(); } + + [[deprecated(Reworking)]] + int getLobbyPlayers() const { return m_rooms[0]->getLobbyPlayers(); } - uint64_t m_client_starting_time; + [[deprecated(Reworking)]] + bool isAIProfile(const std::shared_ptr& npp) const + { + return m_rooms[0]->isAIProfile(npp); + } + + [[deprecated(Reworking)]] + bool isWorldPicked() const { return m_rooms[0]->isWorldPicked(); } + + [[deprecated(Reworking)]] + bool isWorldFinished() const { return m_rooms[0]->isWorldFinished(); } + + [[deprecated(Reworking)]] + bool isStateAtLeastRacing() const { return m_rooms[0]->isStateAtLeastRacing(); } + + [[deprecated(Reworking)]] + std::shared_ptr getGameInfo() const { return m_rooms[0]->getGameInfo(); } + + [[deprecated(Reworking)]] + std::shared_ptr getServerOwner() const + { return m_rooms[0]->getServerOwner(); } - // Calculated before each game started - unsigned m_ai_count; + void doErrorLeave() { m_init_state.store(ERROR_LEAVE); } - std::shared_ptr m_game_info; + [[deprecated(Reworking)]] + bool isWaitingForStartGame() const { return m_rooms[0]->isWaitingForStartGame(); } - std::atomic m_reset_to_default_mode_later; +private: // connection management void clientDisconnected(Event* event); void connectionRequested(Event* event); // kart selection - void kartSelectionRequested(Event* event); + // void kartSelectionRequested(Event* event); // Track(s) votes - void handlePlayerVote(Event *event); - void playerFinishedResult(Event *event); + // void handlePlayerVote(Event *event); + // void playerFinishedResult(Event *event); void registerServer(bool first_time); - void finishedLoadingWorldClient(Event *event); - void finishedLoadingLiveJoinClient(Event *event); + // void finishedLoadingWorldClient(Event *event); + // void finishedLoadingLiveJoinClient(Event *event); void kickHost(Event* event); void changeTeam(Event* event); void handleChat(Event* event); @@ -207,7 +178,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser public: // I'll see if it should be private later void updatePlayerList(bool update_when_reset_server = false); - void updateServerOwner(bool force = false); + // void updateServerOwner(bool force = false); private: void handleServerConfiguration(Event* event); @@ -217,23 +188,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser int difficulty, int mode, bool soccer_goal_target); private: - void updateMapsForMode(); - bool checkPeersReady(bool ignore_ai_peer, SelectionPhase phase); - void resetPeersReady() - { - for (auto it = m_peers_ready.begin(); it != m_peers_ready.end();) - { - if (it->first.expired()) - { - it = m_peers_ready.erase(it); - } - else - { - it->second = false; - it++; - } - } - } + // bool checkPeersReady(bool ignore_ai_peer, SelectionPhase phase); void handlePendingConnection(); void handleUnencryptedConnection(std::shared_ptr peer, BareNetworkString& data, @@ -252,18 +207,15 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void getRankingForPlayer(std::shared_ptr p); void submitRankingsToAddons(); void computeNewRankings(NetworkString* ns); - void checkRaceFinished(); - void configPeersStartTime(); - void resetServer(); - void addWaitingPlayersToGame(); + // void checkRaceFinished(); + // void configPeersStartTime(); + // void resetServer(); + // void addWaitingPlayersToGame(); void changeHandicap(Event* event); - void handlePlayerDisconnection() const; - void addLiveJoinPlaceholder( - std::vector >& players) const; - NetworkString* getLoadWorldMessage( - std::vector >& players, - bool live_join) const; - void setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const; + // void handlePlayerDisconnection() const; + // void addLiveJoinPlaceholder( + // std::vector >& players) const; + // void setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const; bool handleAssets(Event* event); bool handleAssetsAndAddonScores(std::shared_ptr peer, @@ -271,27 +223,26 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser const std::set& client_maps); void handleServerCommand(Event* event); - void liveJoinRequest(Event* event); - void rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr); - bool canLiveJoinNow() const; - int getReservedId(std::shared_ptr& p, - unsigned local_id); - void handleKartInfo(Event* event); - void clientInGameWantsToBackLobby(Event* event); - void clientSelectingAssetsWantsToBackLobby(Event* event); + // void rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr); + // bool canLiveJoinNow() const; + // int getReservedId(std::shared_ptr& p, + // unsigned local_id); + // void handleKartInfo(Event* event); + // void clientInGameWantsToBackLobby(Event* event); + // void clientSelectingAssetsWantsToBackLobby(Event* event); void kickPlayerWithReason(std::shared_ptr peer, const char* reason) const; void testBannedForIP(std::shared_ptr peer) const; void testBannedForIPv6(std::shared_ptr peer) const; void testBannedForOnlineId(std::shared_ptr peer, uint32_t online_id) const; void getMessagesFromHost(std::shared_ptr peer, int online_id); void writePlayerReport(Event* event); - bool supportsAI(); + // bool supportsAI(); void initTournamentPlayers(); public: void changeLimitForTournament(bool goal_target); private: - bool canVote(std::shared_ptr peer) const; - bool hasHostRights(std::shared_ptr peer) const; + // bool canVote(std::shared_ptr peer) const; + // bool hasHostRights(std::shared_ptr peer) const; public: ServerLobby(); @@ -303,31 +254,19 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser virtual void update(int ticks) OVERRIDE; virtual void asynchronousUpdate() OVERRIDE; - void startSelection(const Event *event=NULL); + // void startSelection(const Event *event=NULL); void checkIncomingConnectionRequests(); void finishedLoadingWorld() OVERRIDE; - ServerState getCurrentState() const { return m_state.load(); } void updateBanList(); bool waitingForPlayers() const; - virtual bool allPlayersReady() const OVERRIDE - { return m_state.load() >= WAIT_FOR_RACE_STARTED; } - virtual bool isRacing() const OVERRIDE { return m_state.load() == RACING; } float getStartupBoostOrPenaltyForKart(uint32_t ping, unsigned kart_id); - int getDifficulty() const { return m_difficulty.load(); } - int getGameMode() const { return m_game_mode.load(); } - int getLobbyPlayers() const { return m_lobby_players.load(); } - void saveInitialItems(std::shared_ptr nim); + // void saveInitialItems(std::shared_ptr nim); void saveIPBanTable(const SocketAddress& addr); void listBanTable(); void initServerStatsTable(); - bool isAIProfile(const std::shared_ptr& npp) const - { - return std::find(m_ai_profiles.begin(), m_ai_profiles.end(), npp) != - m_ai_profiles.end(); - } uint32_t getServerIdOnline() const { return m_server_id_online; } void setClientServerHostId(uint32_t id) { m_client_server_host_id = id; } - void resetToDefaultSettings(); + // void resetToDefaultSettings(); void writeOwnReport(std::shared_ptr reporter, std::shared_ptr reporting, const std::string& info); bool writeOnePlayerReport(std::shared_ptr reporter, const std::string& table, @@ -342,7 +281,16 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr npp, STKPeer* peer); - int getPermissions(std::shared_ptr peer) const; + std::shared_ptr getNameDecorator() { return m_name_decorator; } + + void unregisterServerForLegacyGPMode(); + + void dropPendingConnectionsForLegacyGPMode(); + + void addWaitingPlayersToRanking( + const std::shared_ptr& npp); + + // int getPermissions(std::shared_ptr peer) const; bool isSoccerGoalTarget() const; #ifdef ENABLE_SQLITE3 @@ -350,53 +298,49 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::string& direction, int laps); #endif - void erasePeerReady(std::shared_ptr peer) - { m_peers_ready.erase(peer); } bool areKartFiltersIgnoringKarts() const; - void setKartDataProperly(KartData& kart_data, const std::string& kart_name, - std::shared_ptr player, - const std::string& type) const; + // void setKartDataProperly(KartData& kart_data, const std::string& kart_name, + // std::shared_ptr player, + // const std::string& type) const; bool playerReportsTableExists() const; void sendServerInfoToEveryone() const; - bool isWorldPicked() const { return m_state.load() >= LOAD_WORLD; } - bool isWorldFinished() const { return m_state.load() >= RESULT_DISPLAY; } - bool isStateAtLeastRacing() const { return m_state.load() >= RACING; } bool isLegacyGPMode() const; - int getCurrentStateScope(); + // int getCurrentStateScope(); bool isChildProcess() { return m_process_type == PT_CHILD; } bool isClientServerHost(const std::shared_ptr& peer); + bool isChildClientServerHost(const std::shared_ptr& peer) + { + return isChildProcess() && isClientServerHost(peer); + } - void setTimeoutFromNow(int seconds); - void setInfiniteTimeout(); - bool isInfiniteTimeout() const; - bool isTimeoutExpired() const; - float getTimeUntilExpiration() const; - void onSpectatorStatusChange(const std::shared_ptr& peer); + bool canIgnoreControllerEvents() const; + bool isPeerInGame(const std::shared_ptr& peer) const; + bool hasAnyGameStarted() const; + bool isPastRegistrationPhase() const; + + std::shared_ptr getRoomForPeer(const std::shared_ptr& peer) const; + std::shared_ptr getRoom(int idx) const; + + int getPermissions(std::shared_ptr peer) const; + void resetServerToRSA(); //------------------------------------------------------------------------- // More auxiliary functions //------------------------------------------------------------------------- - std::shared_ptr getGameInfo() const { return m_game_info; } - std::shared_ptr getServerOwner() const - { return m_server_owner.lock(); } - // Functions that arose from requests separation void setServerOnlineId(uint32_t id) { m_server_id_online.store(id); } void resetSuccessPollTime() { m_last_success_poll_time.store(StkTime::getMonoTimeMs()); } - void doErrorLeave() { m_state.store(ERROR_LEAVE); } bool hasPendingPeerConnection(const std::string& peer_addr_str) const { return m_pending_peer_connection.find(peer_addr_str) != m_pending_peer_connection.end(); } - bool isWaitingForStartGame() const - { return m_state.load() == WAITING_FOR_START_GAME; } void addPeerConnection(const std::string& addr_str) { diff --git a/src/network/server_enums.hpp b/src/network/server_enums.hpp new file mode 100644 index 00000000000..5343c3f26de --- /dev/null +++ b/src/network/server_enums.hpp @@ -0,0 +1,59 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2025 kimden +// +// 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. + +#ifndef SERVER_ENUMS_HPP +#define SERVER_ENUMS_HPP + +/* The state for a small finite state machine. */ +enum ServerPlayState : unsigned int +{ + WAITING_FOR_START_GAME, // In lobby, waiting for (auto) start game + SELECTING, // kart, track, ... selection started + LOAD_WORLD, // Server starts loading world + WAIT_FOR_WORLD_LOADED, // Wait for clients and server to load world + WAIT_FOR_RACE_STARTED, // Wait for all clients to have started the race + RACING, // racing + WAIT_FOR_RACE_STOPPED, // Wait server for stopping all race protocols + RESULT_DISPLAY, // Show result screen +}; + +enum ServerInitState : unsigned int +{ + SET_PUBLIC_ADDRESS, // Waiting to receive its public ip address + REGISTER_SELF_ADDRESS, // Register with STK online server + RUNNING, // Normal functioning + ERROR_LEAVE, // shutting down server + EXITING, +}; + +enum SelectionPhase: unsigned int +{ + BEFORE_SELECTION = 0, + LOADING_WORLD = 1, + AFTER_GAME = 2, +}; + +/* The state used in multiple threads when reseting server. */ +enum ResetState : unsigned int +{ + RS_NONE, // Default state + RS_WAITING, // Waiting for reseting finished + RS_ASYNC_RESET // Finished reseting server in main thread, now async thread +}; + +#endif // SERVER_ENUMS_HPP \ No newline at end of file diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index c378114da14..6a95d50017a 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -898,11 +898,8 @@ void STKHost::mainLoop(ProcessType pt) { player_name = p.second->getMainName(); } - const bool peer_not_in_game = - sl->getCurrentState() <= ServerLobby::SELECTING - || p.second->isWaitingForGame(); if (ServerConfig::m_kick_high_ping_players && - !p.second->isDisconnected() && peer_not_in_game) + !p.second->isDisconnected() && !sl->isPeerInGame(p.second)) { Log::info("STKHost", "%s %s with ping %d is higher" " than %d ms when not in game, kick.", @@ -1265,9 +1262,7 @@ void STKHost::handleDirectSocketRequest(Network* direct_socket, s.addUInt8((uint8_t)sl->getDifficulty()); s.addUInt8((uint8_t)sl->getGameMode()); s.addUInt8(!pw.empty()); - s.addUInt8((uint8_t) - (sl->getCurrentState() == ServerLobby::WAITING_FOR_START_GAME ? - 0 : 1)); + s.addUInt8((uint8_t)(sl->hasAnyGameStarted() ? 1 : 0)); s.encodeString(sl->getPlayingTrackIdent()); direct_socket->sendRawPacket(s, sender); } // if message is server-requested diff --git a/src/network/stk_peer.cpp b/src/network/stk_peer.cpp index 4b1a978376c..78cc234f472 100644 --- a/src/network/stk_peer.cpp +++ b/src/network/stk_peer.cpp @@ -58,6 +58,7 @@ STKPeer::STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id) m_last_message.store(0); m_angry_host.store(false); m_consecutive_messages = 0; + m_room_number.store(0); } // STKPeer //----------------------------------------------------------------------------- diff --git a/src/network/stk_peer.hpp b/src/network/stk_peer.hpp index ebed54e3d92..7223a38e92a 100644 --- a/src/network/stk_peer.hpp +++ b/src/network/stk_peer.hpp @@ -133,6 +133,8 @@ class STKPeer : public NoCopy std::string m_user_version; + std::atomic_int8_t m_room_number; + /** List of client capabilities set when connecting it, to determine * features available in same version. */ std::set m_client_capabilities; @@ -387,6 +389,12 @@ class STKPeer : public NoCopy // ------------------------------------------------------------------------ std::string getMainName() const; // ------------------------------------------------------------------------ + int8_t getRoomNumber() { return m_room_number.load(); } + // ------------------------------------------------------------------------ + void setRoomNumber(int8_t idx) { m_room_number.store(idx); } + // ------------------------------------------------------------------------ + void resetRoomNumber() { m_room_number.store(0); } + // ------------------------------------------------------------------------ }; // STKPeer #endif // STK_PEER_HPP