From d50aec65de62da8cb7ad147232439d4b25a2901c Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sat, 24 May 2025 22:36:49 +0400 Subject: [PATCH 01/12] Room number for peer + command + update player list --- src/network/protocols/command_manager.cpp | 44 +++++++++++++++++++++++ src/network/protocols/command_manager.hpp | 1 + src/network/protocols/server_lobby.cpp | 4 +++ src/network/stk_peer.cpp | 1 + src/network/stk_peer.hpp | 8 +++++ 5 files changed, 58 insertions(+) diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 1052eac9602..0275e753eaf 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); @@ -3877,6 +3878,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/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 2b47cd2b50c..af766297083 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -2879,6 +2879,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)) { 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..961804d2d75 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(-1); } + // ------------------------------------------------------------------------ }; // STKPeer #endif // STK_PEER_HPP From 73ba27ef37d2a5dc3142494c41a6079361b375ed Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sun, 25 May 2025 03:24:53 +0400 Subject: [PATCH 02/12] Forgot commands.xml --- data/commands.xml | 7 +++++++ 1 file changed, 7 insertions(+) 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" /> + Date: Sun, 25 May 2025 13:53:59 +0400 Subject: [PATCH 03/12] Some code movements that don't compile [skip ci] --- src/network/protocols/server_lobby.hpp | 271 +++++++++++++++++-------- 1 file changed, 191 insertions(+), 80 deletions(-) diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 44728e6d4de..b911fca2dba 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -66,7 +66,7 @@ namespace Online class Request; } -class ServerLobby : public LobbyProtocol, public LobbyContextUser +class PlayingLobby { public: /* The state for a small finite state machine. */ @@ -86,20 +86,6 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser EXITING }; - enum SelectionPhase: unsigned int - { - BEFORE_SELECTION = 0, - LOADING_WORLD = 1, - AFTER_GAME = 2, - }; -private: - -#ifdef ENABLE_SQLITE3 - void pollDatabase(); -#endif - - std::atomic m_state; - /* The state used in multiple threads when reseting server. */ enum ResetState : unsigned int { @@ -108,6 +94,9 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser RS_ASYNC_RESET // Finished reseting server in main thread, now async thread }; +private: + std::atomic m_state; + std::atomic m_rs_state; /** Hold the next connected peer for server owner if current one expired @@ -127,37 +116,19 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser /** Keeps track of the server state. */ std::atomic_bool m_server_has_loaded_world; - 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; - - std::map, - std::pair, - std::owner_less > > m_pending_connection; - - 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; @@ -166,10 +137,107 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::atomic m_current_ai_count; - std::atomic m_last_success_poll_time; + uint64_t m_server_started_at; + + uint64_t m_server_delay; - uint64_t m_last_unsuccess_poll_time, m_server_started_at, 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; + +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++; + } + } + } + +public: + ServerState getCurrentState() const { return m_state.load(); } + virtual bool allPlayersReady() const OVERRIDE + { return m_state.load() >= WAIT_FOR_RACE_STARTED; } + virtual bool isRacing() const OVERRIDE { return m_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_state.load() >= LOAD_WORLD; } + bool isWorldFinished() const { return m_state.load() >= RESULT_DISPLAY; } + bool isStateAtLeastRacing() const { return m_state.load() >= RACING; } + std::shared_ptr getGameInfo() const { return m_game_info; } + std::shared_ptr getServerOwner() const + { return m_server_owner.lock(); } + void doErrorLeave() { m_state.store(ERROR_LEAVE); } + bool isWaitingForStartGame() const + { return m_state.load() == WAITING_FOR_START_GAME; } + + void doErrorLeave() { m_state.store(ERROR_LEAVE); } + bool isWaitingForStartGame() const + { return m_state.load() == WAITING_FOR_START_GAME; } +}; + +class ServerLobby : public LobbyProtocol, public LobbyContextUser +{ +public: + enum SelectionPhase: unsigned int + { + BEFORE_SELECTION = 0, + LOADING_WORLD = 1, + AFTER_GAME = 2, + }; +private: + +#ifdef ENABLE_SQLITE3 + void pollDatabase(); +#endif + + std::vector m_rooms; + + bool m_registered_for_once_only; + + std::weak_ptr m_server_registering; + + std::mutex m_keys_mutex; + + std::map m_keys; + + std::map, + std::pair, + std::owner_less > > m_pending_connection; + + std::map m_pending_peer_connection; + + std::atomic m_server_id_online; + + std::atomic m_client_server_host_id; + + std::atomic m_last_success_poll_time; + + uint64_t m_last_unsuccess_poll_time; + // Other units previously used in ServerLobby std::shared_ptr m_lobby_context; @@ -177,16 +245,98 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr m_name_decorator; - unsigned m_item_seed; +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++; + } + } + } - uint64_t m_client_starting_time; +public: + ServerState getCurrentState() const { return m_state.load(); } + virtual bool allPlayersReady() const OVERRIDE + { return m_state.load() >= WAIT_FOR_RACE_STARTED; } + virtual bool isRacing() const OVERRIDE { return m_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_state.load() >= LOAD_WORLD; } + bool isWorldFinished() const { return m_state.load() >= RESULT_DISPLAY; } + bool isStateAtLeastRacing() const { return m_state.load() >= RACING; } + std::shared_ptr getGameInfo() const { return m_game_info; } + std::shared_ptr getServerOwner() const + { return m_server_owner.lock(); } + void doErrorLeave() { m_state.store(ERROR_LEAVE); } + bool isWaitingForStartGame() const + { return m_state.load() == WAITING_FOR_START_GAME; } + + void doErrorLeave() { m_state.store(ERROR_LEAVE); } + bool isWaitingForStartGame() const + { return m_state.load() == WAITING_FOR_START_GAME; } +}; - // Calculated before each game started - unsigned m_ai_count; +class ServerLobby : public LobbyProtocol, public LobbyContextUser +{ +public: + enum SelectionPhase: unsigned int + { + BEFORE_SELECTION = 0, + LOADING_WORLD = 1, + AFTER_GAME = 2, + }; +private: - std::shared_ptr m_game_info; +#ifdef ENABLE_SQLITE3 + void pollDatabase(); +#endif - std::atomic m_reset_to_default_mode_later; + std::vector m_rooms; + + bool m_registered_for_once_only; + + std::weak_ptr m_server_registering; + + std::mutex m_keys_mutex; + + std::map m_keys; + + std::map, + std::pair, + std::owner_less > > m_pending_connection; + + std::map m_pending_peer_connection; + + std::atomic m_server_id_online; + + std::atomic m_client_server_host_id; + + std::atomic m_last_success_poll_time; + + uint64_t m_last_unsuccess_poll_time; + + // Other units previously used in ServerLobby + std::shared_ptr m_lobby_context; + + std::shared_ptr m_ranking; + + std::shared_ptr m_name_decorator; // connection management void clientDisconnected(Event* event); @@ -219,21 +369,6 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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++; - } - } - } void handlePendingConnection(); void handleUnencryptedConnection(std::shared_ptr peer, BareNetworkString& data, @@ -306,25 +441,13 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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 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(); @@ -350,8 +473,6 @@ 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, @@ -362,9 +483,6 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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(); bool isChildProcess() { return m_process_type == PT_CHILD; } @@ -382,21 +500,14 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser // 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) { From 837d34c7e7912b85fcbf74be5cef90c392abe437 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:50:48 +0400 Subject: [PATCH 04/12] Started moving things to PlayingLobby [skip ci] --- src/network/child_loop.cpp | 2 +- src/network/protocol_manager.cpp | 6 +- src/network/protocols/client_lobby.hpp | 2 - src/network/protocols/lobby_protocol.hpp | 1 - src/network/protocols/server_lobby.cpp | 164 +++++++++------- src/network/protocols/server_lobby.hpp | 231 ++++++++++------------- 6 files changed, 206 insertions(+), 200 deletions(-) diff --git a/src/network/child_loop.cpp b/src/network/child_loop.cpp index ef95e7fe526..6727559b43f 100644 --- a/src/network/child_loop.cpp +++ b/src/network/child_loop.cpp @@ -126,7 +126,7 @@ void ChildLoop::run() { auto sl = LobbyProtocol::get(); if (sl && - sl->getCurrentState() >= ServerLobby::WAITING_FOR_START_GAME) + sl->getCurrentState() >= ServerState::WAITING_FOR_START_GAME) { m_port = STKHost::get()->getPrivatePort(); m_server_online_id = sl->getServerIdOnline(); diff --git a/src/network/protocol_manager.cpp b/src/network/protocol_manager.cpp index faa8a319779..1814233eb6e 100644 --- a/src/network/protocol_manager.cpp +++ b/src/network/protocol_manager.cpp @@ -87,9 +87,9 @@ 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)) + ServerState ss = sl->getCurrentState(); + if (!(ss >= ServerState::WAIT_FOR_WORLD_LOADED && + ss <= ServerState::RACING)) { delete event_top; continue; 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/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/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index af766297083..00d13c96266 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -174,6 +174,74 @@ namespace // We use max priority for all server requests to avoid downloading of addons // icons blocking the poll request in all-in-one graphical client server +PlayingLobby::PlayingLobby() +{ + m_lobby_players.store(0); + m_current_ai_count.store(0); + m_rs_state.store(RS_NONE); + m_server_owner_id.store(-1); + m_state = SET_PUBLIC_ADDRESS; + 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 = {}; +} // PlayingLobby +//----------------------------------------------------------------------------- + +PlayingLobby::~PlayingLobby() +{ + delete m_items_complete_state; +} // ~PlayingLobby +//----------------------------------------------------------------------------- + +[[deprecated("STKHost and GameSetup are used in a room method.")]] +void PlayingLobby::setup() +{ + m_item_seed = 0; + m_client_starting_time = 0; + m_ai_count = 0; + + auto players = STKHost::get()->getPlayersForNewGame(); + auto game_setup = getGameSetup(); + 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("PlayingLobby", "Resetting room to its initial state."); +} // PlayingLobby::setup() +//----------------------------------------------------------------------------- + + + + + + /** This is the central game setup protocol running in the server. It is * mostly a finite state machine. Note that all nodes in ellipses and light * grey background are actual states; nodes in boxes and white background @@ -216,7 +284,6 @@ namespace ServerLobby::ServerLobby() : LobbyProtocol() { m_client_server_host_id.store(0); - m_lobby_players.store(0); m_lobby_context = std::make_shared(this, (bool)ServerConfig::m_soccer_tournament); m_lobby_context->setup(); @@ -224,15 +291,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 +304,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 +323,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(); @@ -282,7 +346,8 @@ void ServerLobby::initServerStatsTable() //----------------------------------------------------------------------------- /** Calls the corresponding method from LobbyAssetManager * whenever server is reset or game mode is changed. */ -void ServerLobby::updateMapsForMode() +[[deprecated("Asset managers should be separate for different rooms.")]] +void PlayingLobby::updateMapsForMode() { getAssetManager()->updateMapsForMode( ServerConfig::getLocalGameMode(m_game_mode.load()).first @@ -293,43 +358,14 @@ void ServerLobby::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 @@ -1004,7 +1040,7 @@ void ServerLobby::asynchronousUpdate() } // asynchronousUpdate //----------------------------------------------------------------------------- -NetworkString* ServerLobby::getLoadWorldMessage( +NetworkString* PlayingLobby::getLoadWorldMessage( std::vector >& players, bool live_join) const { @@ -1036,7 +1072,7 @@ NetworkString* ServerLobby::getLoadWorldMessage( //----------------------------------------------------------------------------- /** Returns true if server can be live joined or spectating */ -bool ServerLobby::canLiveJoinNow() const +bool PlayingLobby::canLiveJoinNow() const { bool live_join = getSettings()->isLivePlayers() && worldIsActive(); if (!live_join) @@ -1075,7 +1111,7 @@ bool ServerLobby::canLiveJoinNow() const /** \ref STKPeer peer will be reset back to the lobby with reason * \ref BackLobbyReason blr */ -void ServerLobby::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr) +void PlayingLobby::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr) { NetworkString* reset = getNetworkString(2); reset->setSynchronous(true); @@ -1088,7 +1124,7 @@ void ServerLobby::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); - m_game_setup->addServerInfo(server_info); + getGameSetup()->addServerInfo(server_info); peer->sendPacket(server_info, PRM_RELIABLE); delete server_info; @@ -1099,7 +1135,7 @@ void ServerLobby::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason /** This message is like kartSelectionRequested, but it will send the peer * load world message if he can join the current started game. */ -void ServerLobby::liveJoinRequest(Event* event) +void PlayingLobby::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. @@ -4164,7 +4200,7 @@ void ServerLobby::clientSelectingAssetsWantsToBackLobby(Event* event) } // clientSelectingAssetsWantsToBackLobby //----------------------------------------------------------------------------- -void ServerLobby::saveInitialItems(std::shared_ptr nim) +void PlayingLobby::saveInitialItems(std::shared_ptr nim) { m_items_complete_state->getBuffer().clear(); m_items_complete_state->reset(); @@ -4172,13 +4208,13 @@ void ServerLobby::saveInitialItems(std::shared_ptr nim) } // saveInitialItems //----------------------------------------------------------------------------- -bool ServerLobby::supportsAI() +bool PlayingLobby::supportsAI() { return getGameMode() == 3 || getGameMode() == 4; } // supportsAI //----------------------------------------------------------------------------- -bool ServerLobby::checkPeersReady(bool ignore_ai_peer, SelectionPhase phase) +bool PlayingLobby::checkPeersReady(bool ignore_ai_peer, SelectionPhase phase) { bool all_ready = true; bool someone_races = false; @@ -4215,7 +4251,7 @@ void ServerLobby::handleServerCommand(Event* event) } // handleServerCommand //----------------------------------------------------------------------------- -void ServerLobby::resetToDefaultSettings() +void PlayingLobby::resetToDefaultSettings() { if (getSettings()->isServerConfigurable() && !getSettings()->isPreservingMode()) { @@ -4278,7 +4314,7 @@ std::string ServerLobby::encodeProfileNameForPeer( } // encodeProfileNameForPeer //----------------------------------------------------------------------------- -bool ServerLobby::canVote(std::shared_ptr peer) const +bool PlayingLobby::canVote(std::shared_ptr peer) const { if (!peer || peer->getPlayerProfiles().empty()) return false; @@ -4290,7 +4326,7 @@ bool ServerLobby::canVote(std::shared_ptr peer) const } // canVote //----------------------------------------------------------------------------- -bool ServerLobby::hasHostRights(std::shared_ptr peer) const +bool PlayingLobby::hasHostRights(std::shared_ptr peer) const { if (!peer || peer->getPlayerProfiles().empty()) return false; @@ -4308,7 +4344,7 @@ bool ServerLobby::hasHostRights(std::shared_ptr peer) const } // hasHostRights //----------------------------------------------------------------------------- -int ServerLobby::getPermissions(std::shared_ptr peer) const +int PlayingLobby::getPermissions(std::shared_ptr peer) const { int mask = 0; if (!peer) @@ -4481,7 +4517,7 @@ bool ServerLobby::isLegacyGPMode() const } // isLegacyGPMode //----------------------------------------------------------------------------- -int ServerLobby::getCurrentStateScope() +int PlayingLobby::getCurrentStateScope() { auto state = m_state.load(); if (state < WAITING_FOR_START_GAME @@ -4499,32 +4535,32 @@ bool ServerLobby::isClientServerHost(const std::shared_ptr& peer) } // isClientServerHost //----------------------------------------------------------------------------- -void ServerLobby::setTimeoutFromNow(int seconds) +void PlayingLobby::setTimeoutFromNow(int seconds) { m_timeout.store((int64_t)StkTime::getMonoTimeMs() + (int64_t)(seconds * 1000.0f)); } // setTimeoutFromNow //----------------------------------------------------------------------------- -void ServerLobby::setInfiniteTimeout() +void PlayingLobby::setInfiniteTimeout() { m_timeout.store(std::numeric_limits::max()); } // setInfiniteTimeout //----------------------------------------------------------------------------- -bool ServerLobby::isInfiniteTimeout() const +bool PlayingLobby::isInfiniteTimeout() const { return m_timeout.load() == std::numeric_limits::max(); } // isInfiniteTimeout //----------------------------------------------------------------------------- -bool ServerLobby::isTimeoutExpired() const +bool PlayingLobby::isTimeoutExpired() const { return m_timeout.load() < (int64_t)StkTime::getMonoTimeMs(); } // isTimeoutExpired //----------------------------------------------------------------------------- -float ServerLobby::getTimeUntilExpiration() const +float PlayingLobby::getTimeUntilExpiration() const { int64_t timeout = m_timeout.load(); if (timeout == std::numeric_limits::max()) @@ -4534,10 +4570,10 @@ float ServerLobby::getTimeUntilExpiration() const } // getTimeUntilExpiration //----------------------------------------------------------------------------- -void ServerLobby::onSpectatorStatusChange(const std::shared_ptr& peer) +void PlayingLobby::onSpectatorStatusChange(const std::shared_ptr& peer) { auto state = m_state.load(); - if (state >= ServerLobby::SELECTING && state < ServerLobby::RACING) + if (state >= ServerState::SELECTING && state < ServerState::RACING) { erasePeerReady(peer); peer->setWaitingForGame(true); diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index b911fca2dba..9c67dc74daf 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -66,25 +66,33 @@ namespace Online class Request; } -class PlayingLobby +/* 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, +}; + +class PlayingLobby: 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 - }; /* The state used in multiple threads when reseting server. */ enum ResetState : unsigned int @@ -169,11 +177,26 @@ class PlayingLobby } } +private: + void updateMapsForMode(); + NetworkString* getLoadWorldMessage( + std::vector >& players, + bool live_join) const; + + void liveJoinRequest(Event* event); + 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(); + public: + PlayingLobby(); + ~PlayingLobby(); + void setup(); ServerState getCurrentState() const { return m_state.load(); } - virtual bool allPlayersReady() const OVERRIDE - { return m_state.load() >= WAIT_FOR_RACE_STARTED; } - virtual bool isRacing() const OVERRIDE { return m_state.load() == RACING; } + bool isRacing() const { return m_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(); } @@ -193,28 +216,29 @@ class PlayingLobby void doErrorLeave() { m_state.store(ERROR_LEAVE); } bool isWaitingForStartGame() const { return m_state.load() == WAITING_FOR_START_GAME; } - - void doErrorLeave() { m_state.store(ERROR_LEAVE); } - bool isWaitingForStartGame() const - { return m_state.load() == WAITING_FOR_START_GAME; } +public: + + 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); }; class ServerLobby : public LobbyProtocol, public LobbyContextUser { -public: - enum SelectionPhase: unsigned int - { - BEFORE_SELECTION = 0, - LOADING_WORLD = 1, - AFTER_GAME = 2, - }; private: #ifdef ENABLE_SQLITE3 void pollDatabase(); #endif - std::vector m_rooms; + std::vector> m_rooms; bool m_registered_for_once_only; @@ -246,97 +270,58 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr m_name_decorator; 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++; - } - } - } -public: - ServerState getCurrentState() const { return m_state.load(); } - virtual bool allPlayersReady() const OVERRIDE - { return m_state.load() >= WAIT_FOR_RACE_STARTED; } - virtual bool isRacing() const OVERRIDE { return m_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_state.load() >= LOAD_WORLD; } - bool isWorldFinished() const { return m_state.load() >= RESULT_DISPLAY; } - bool isStateAtLeastRacing() const { return m_state.load() >= RACING; } - std::shared_ptr getGameInfo() const { return m_game_info; } - std::shared_ptr getServerOwner() const - { return m_server_owner.lock(); } - void doErrorLeave() { m_state.store(ERROR_LEAVE); } - bool isWaitingForStartGame() const - { return m_state.load() == WAITING_FOR_START_GAME; } - - void doErrorLeave() { m_state.store(ERROR_LEAVE); } - bool isWaitingForStartGame() const - { return m_state.load() == WAITING_FOR_START_GAME; } -}; +#define Reworking "This method was moved to PlayingRoom but is still used for ServerLobby. This has to be changed," -class ServerLobby : public LobbyProtocol, public LobbyContextUser -{ public: - enum SelectionPhase: unsigned int - { - BEFORE_SELECTION = 0, - LOADING_WORLD = 1, - AFTER_GAME = 2, - }; -private: + [[deprecated(Reworking)]] + ServerState getCurrentState() const { return m_rooms[0]->getCurrentState(); } -#ifdef ENABLE_SQLITE3 - void pollDatabase(); -#endif + [[deprecated(Reworking)]] + virtual bool isRacing() const OVERRIDE { return m_rooms[0]->isRacing(); } - std::vector m_rooms; + [[deprecated(Reworking)]] + int getDifficulty() const { return m_rooms[0]->getDifficulty(); } - bool m_registered_for_once_only; + [[deprecated(Reworking)]] + int getGameMode() const { return m_rooms[0]->getGameMode(); } - std::weak_ptr m_server_registering; - - std::mutex m_keys_mutex; + [[deprecated(Reworking)]] + int getLobbyPlayers() const { return m_rooms[0]->getLobbyPlayers(); } - std::map m_keys; + [[deprecated(Reworking)]] + bool isAIProfile(const std::shared_ptr& npp) const + { + return m_rooms[0]->isAIProfile(npp); + } - std::map, - std::pair, - std::owner_less > > m_pending_connection; + [[deprecated(Reworking)]] + bool isWorldPicked() const { return m_rooms[0]->isWorldPicked(); } - std::map m_pending_peer_connection; + [[deprecated(Reworking)]] + bool isWorldFinished() const { return m_rooms[0]->isWorldFinished(); } - std::atomic m_server_id_online; + [[deprecated(Reworking)]] + bool isStateAtLeastRacing() const { return m_rooms[0]->isStateAtLeastRacing(); } - std::atomic m_client_server_host_id; + [[deprecated(Reworking)]] + std::shared_ptr getGameInfo() const { return m_rooms[0]->getGameInfo(); } - std::atomic m_last_success_poll_time; + [[deprecated(Reworking)]] + std::shared_ptr getServerOwner() const + { return m_rooms[0]->getServerOwner(); } - uint64_t m_last_unsuccess_poll_time; - - // Other units previously used in ServerLobby - std::shared_ptr m_lobby_context; + [[deprecated(Reworking)]] + void doErrorLeave() + { + for (auto& room: m_rooms) + room->doErrorLeave(); + } - std::shared_ptr m_ranking; + [[deprecated(Reworking)]] + bool isWaitingForStartGame() const { return m_rooms[0]->isWaitingForStartGame(); } - std::shared_ptr m_name_decorator; +private: // connection management void clientDisconnected(Event* event); @@ -367,8 +352,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); + // bool checkPeersReady(bool ignore_ai_peer, SelectionPhase phase); void handlePendingConnection(); void handleUnencryptedConnection(std::shared_ptr peer, BareNetworkString& data, @@ -395,9 +379,6 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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; bool handleAssets(Event* event); @@ -406,9 +387,8 @@ 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; + // void rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr); + // bool canLiveJoinNow() const; int getReservedId(std::shared_ptr& p, unsigned local_id); void handleKartInfo(Event* event); @@ -420,13 +400,13 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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(); @@ -444,13 +424,13 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void updateBanList(); bool waitingForPlayers() const; float getStartupBoostOrPenaltyForKart(uint32_t ping, unsigned kart_id); - void saveInitialItems(std::shared_ptr nim); + // void saveInitialItems(std::shared_ptr nim); void saveIPBanTable(const SocketAddress& addr); void listBanTable(); void initServerStatsTable(); 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, @@ -465,7 +445,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr npp, STKPeer* peer); - int getPermissions(std::shared_ptr peer) const; + // int getPermissions(std::shared_ptr peer) const; bool isSoccerGoalTarget() const; #ifdef ENABLE_SQLITE3 @@ -484,18 +464,11 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void sendServerInfoToEveryone() const; bool isLegacyGPMode() const; - int getCurrentStateScope(); + // int getCurrentStateScope(); bool isChildProcess() { return m_process_type == PT_CHILD; } bool isClientServerHost(const std::shared_ptr& peer); - void setTimeoutFromNow(int seconds); - void setInfiniteTimeout(); - bool isInfiniteTimeout() const; - bool isTimeoutExpired() const; - float getTimeUntilExpiration() const; - void onSpectatorStatusChange(const std::shared_ptr& peer); - //------------------------------------------------------------------------- // More auxiliary functions //------------------------------------------------------------------------- From 91d0ebc854c0e19b229a49670de4ef57f082ff94 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Tue, 10 Jun 2025 01:16:41 +0400 Subject: [PATCH 05/12] Rename, move to own file, call room functions for events [skip ci] --- src/network/protocol_manager.cpp | 2 +- src/network/protocols/command_manager.cpp | 3 +- src/network/protocols/playing_room.cpp | 1209 +++++++++++++++++++++ src/network/protocols/playing_room.hpp | 184 ++++ src/network/protocols/server_lobby.cpp | 1187 +------------------- src/network/protocols/server_lobby.hpp | 188 +--- src/network/server_enums.hpp | 54 + src/network/stk_host.cpp | 4 +- src/network/stk_peer.hpp | 2 +- 9 files changed, 1488 insertions(+), 1345 deletions(-) create mode 100644 src/network/protocols/playing_room.cpp create mode 100644 src/network/protocols/playing_room.hpp create mode 100644 src/network/server_enums.hpp diff --git a/src/network/protocol_manager.cpp b/src/network/protocol_manager.cpp index 1814233eb6e..526aaa07b13 100644 --- a/src/network/protocol_manager.cpp +++ b/src/network/protocol_manager.cpp @@ -297,7 +297,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/command_manager.cpp b/src/network/protocols/command_manager.cpp index 0275e753eaf..2597bc3b287 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -1356,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; diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp new file mode 100644 index 00000000000..baf728a434c --- /dev/null +++ b/src/network/protocols/playing_room.cpp @@ -0,0 +1,1209 @@ +// +// 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" + +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 + //------------------------------------------------------------------------- +} // 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_state = SET_PUBLIC_ADDRESS; + 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() +//----------------------------------------------------------------------------- + +/** 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, 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 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; + + 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, 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 + +//----------------------------------------------------------------------------- +/** 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_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()) + { + 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(); + + 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(/*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_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(); + + 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_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 + +//----------------------------------------------------------------------------- +/** 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_state.load() != RESULT_DISPLAY) + return; + std::shared_ptr peer = event->getPeerSP(); + m_peers_ready.at(peer) = true; +} // playerFinishedResult + +//----------------------------------------------------------------------------- +bool PlayingRoom::waitingForPlayers() const +{ + if (getSettings()->isLegacyGPMode() && getSettings()->isLegacyGPModeStarted()) + return false; + return m_state.load() >= WAITING_FOR_START_GAME; +} // waitingForPlayers + +//----------------------------------------------------------------------------- +/** 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 (m_process_type == PT_CHILD && isClientServerHost(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; + 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_state.load() != SELECTING || peer->isWaitingForGame()) + { + Log::warn("ServerLobby", + "%s try to leave selecting assets at wrong time.", + peer->getAddress().toString().c_str()); + return; + } + + if (m_process_type == PT_CHILD && isClientServerHost(event->getPeer())) + { + 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; + 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_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_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 +//----------------------------------------------------------------------------- +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +/// ... +//----------------------------------------------------------------------------- + +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_state.load(); + if (state >= ServerState::SELECTING && state < ServerState::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..c327693f99e --- /dev/null +++ b/src/network/protocols/playing_room.hpp @@ -0,0 +1,184 @@ +// +// 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 +#include +#include +#include +#include + +class STKPeer; +class NetworkPlayerProfile; +class NetworkString; +class BareNetworkString; +class GameInfo; +class NetworkItemManager; + + +class PlayingRoom: public LobbyContextUser +{ +public: + +private: + std::atomic m_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; + +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(); + +public: + PlayingRoom(); + ~PlayingRoom(); + void setup(); + ServerState getCurrentState() const { return m_state.load(); } + bool isRacing() const { return m_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_state.load() >= LOAD_WORLD; } + bool isWorldFinished() const { return m_state.load() >= RESULT_DISPLAY; } + bool isStateAtLeastRacing() const { return m_state.load() >= RACING; } + std::shared_ptr getGameInfo() const { return m_game_info; } + std::shared_ptr getServerOwner() const + { return m_server_owner.lock(); } + void doErrorLeave() { m_state.store(ERROR_LEAVE); } + bool isWaitingForStartGame() const + { return m_state.load() == WAITING_FOR_START_GAME; } + +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); + bool waitingForPlayers() 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 +}; + +#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 00d13c96266..3cb03475e01 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,37 +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() @@ -174,74 +144,6 @@ namespace // We use max priority for all server requests to avoid downloading of addons // icons blocking the poll request in all-in-one graphical client server -PlayingLobby::PlayingLobby() -{ - m_lobby_players.store(0); - m_current_ai_count.store(0); - m_rs_state.store(RS_NONE); - m_server_owner_id.store(-1); - m_state = SET_PUBLIC_ADDRESS; - 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 = {}; -} // PlayingLobby -//----------------------------------------------------------------------------- - -PlayingLobby::~PlayingLobby() -{ - delete m_items_complete_state; -} // ~PlayingLobby -//----------------------------------------------------------------------------- - -[[deprecated("STKHost and GameSetup are used in a room method.")]] -void PlayingLobby::setup() -{ - m_item_seed = 0; - m_client_starting_time = 0; - m_ai_count = 0; - - auto players = STKHost::get()->getPlayersForNewGame(); - auto game_setup = getGameSetup(); - 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("PlayingLobby", "Resetting room to its initial state."); -} // PlayingLobby::setup() -//----------------------------------------------------------------------------- - - - - - - /** This is the central game setup protocol running in the server. It is * mostly a finite state machine. Note that all nodes in ellipses and light * grey background are actual states; nodes in boxes and white background @@ -343,17 +245,6 @@ void ServerLobby::initServerStatsTable() #endif } // initServerStatsTable -//----------------------------------------------------------------------------- -/** 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 PlayingLobby::updateMapsForMode() -{ - getAssetManager()->updateMapsForMode( - ServerConfig::getLocalGameMode(m_game_mode.load()).first - ); -} // updateMapsForMode - //----------------------------------------------------------------------------- void ServerLobby::setup() { @@ -382,13 +273,17 @@ bool ServerLobby::notifyEvent(Event* event) message_type = data.getUInt8(); Log::info("ServerLobby", "Synchronous message of type %d received.", message_type); + + auto& peer = event->getPeerSP(); + auto& room = m_rooms[peer->getRoomNumber()]; + 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); @@ -476,20 +371,25 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) message_type = data.getUInt8(); Log::info("ServerLobby", "Message of type %d received.", message_type); + + auto& peer = event->getPeerSP(); + auto& room = m_rooms[peer->getRoomNumber()]; + 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_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; @@ -1038,176 +938,6 @@ void ServerLobby::asynchronousUpdate() } } // asynchronousUpdate - -//----------------------------------------------------------------------------- -NetworkString* PlayingLobby::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 PlayingLobby::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 PlayingLobby::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(); - - NetworkString* server_info = getNetworkString(); - server_info->setSynchronous(true); - server_info->addUInt8(LE_SERVER_INFO); - getGameSetup()->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 PlayingLobby::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 - //----------------------------------------------------------------------------- /** Decide where to put the live join player depends on his team and game mode. */ @@ -1271,116 +1001,6 @@ int ServerLobby::getReservedId(std::shared_ptr& p, 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 @@ -1428,8 +1048,7 @@ void ServerLobby::update(int ticks) 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()) + if (m_process_type == PT_CHILD && isClientServerHost(peer)) continue; Log::info("ServerLobby", "%s %s has been idle ingame for more than" " %d seconds, kick.", @@ -1478,8 +1097,7 @@ void ServerLobby::update(int ticks) !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()) + if (m_process_type == PT_CHILD && isClientServerHost(peer)) continue; std::string peer_name = ""; if (peer->hasPlayerProfiles()) @@ -1724,304 +1342,6 @@ void ServerLobby::unregisterServer(bool now, std::weak_ptr sl) } // 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()) - { - 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(); - - 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(); - - getCommandManager()->onStartSelection(); -} // startSelection - //----------------------------------------------------------------------------- /** Query the STK server for connection requests. For each connection request * start a ConnectToPeer protocol. @@ -2350,8 +1670,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 (m_process_type == PT_CHILD && isClientServerHost(peer)) { // Update child process addons list too so player can choose later getAssetManager()->updateAddons(); @@ -3065,71 +2384,6 @@ void ServerLobby::kartSelectionRequested(Event* event) 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. @@ -3188,40 +2442,6 @@ 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 - //----------------------------------------------------------------------------- void ServerLobby::handlePendingConnection() { @@ -4016,229 +3236,6 @@ void ServerLobby::setPlayerKarts(const NetworkString& ns, 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 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; - 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(); -} // clientInGameWantsToBackLobby - -//----------------------------------------------------------------------------- -/** Client if currently select assets wants to go back to lobby. - */ -void ServerLobby::clientSelectingAssetsWantsToBackLobby(Event* event) -{ - std::shared_ptr peer = event->getPeerSP(); - - if (m_state.load() != SELECTING || peer->isWaitingForGame()) - { - Log::warn("ServerLobby", - "%s try to leave selecting assets at wrong time.", - peer->getAddress().toString().c_str()); - return; - } - - if (m_process_type == PT_CHILD && - event->getPeer()->getHostId() == m_client_server_host_id.load()) - { - 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; - 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 PlayingLobby::saveInitialItems(std::shared_ptr nim) -{ - m_items_complete_state->getBuffer().clear(); - m_items_complete_state->reset(); - nim->saveCompleteState(m_items_complete_state); -} // saveInitialItems - -//----------------------------------------------------------------------------- -bool PlayingLobby::supportsAI() -{ - return getGameMode() == 3 || getGameMode() == 4; -} // supportsAI - -//----------------------------------------------------------------------------- -bool PlayingLobby::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 ServerLobby::handleServerCommand(Event* event) { @@ -4251,20 +3248,6 @@ void ServerLobby::handleServerCommand(Event* event) } // handleServerCommand //----------------------------------------------------------------------------- -void PlayingLobby::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 @@ -4314,73 +3297,6 @@ std::string ServerLobby::encodeProfileNameForPeer( } // encodeProfileNameForPeer //----------------------------------------------------------------------------- -bool PlayingLobby::canVote(std::shared_ptr peer) const -{ - if (!peer || peer->getPlayerProfiles().empty()) - return false; - - if (!isTournament()) - return true; - - return getTournament()->canVote(peer); -} // canVote -//----------------------------------------------------------------------------- - -bool PlayingLobby::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 PlayingLobby::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) { @@ -4517,66 +3433,7 @@ bool ServerLobby::isLegacyGPMode() const } // isLegacyGPMode //----------------------------------------------------------------------------- -int PlayingLobby::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 PlayingLobby::setTimeoutFromNow(int seconds) -{ - m_timeout.store((int64_t)StkTime::getMonoTimeMs() + - (int64_t)(seconds * 1000.0f)); -} // setTimeoutFromNow -//----------------------------------------------------------------------------- - -void PlayingLobby::setInfiniteTimeout() -{ - m_timeout.store(std::numeric_limits::max()); -} // setInfiniteTimeout -//----------------------------------------------------------------------------- - -bool PlayingLobby::isInfiniteTimeout() const -{ - return m_timeout.load() == std::numeric_limits::max(); -} // isInfiniteTimeout -//----------------------------------------------------------------------------- - -bool PlayingLobby::isTimeoutExpired() const -{ - return m_timeout.load() < (int64_t)StkTime::getMonoTimeMs(); -} // isTimeoutExpired -//----------------------------------------------------------------------------- - -float PlayingLobby::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 PlayingLobby::onSpectatorStatusChange(const std::shared_ptr& peer) -{ - auto state = m_state.load(); - if (state >= ServerState::SELECTING && state < ServerState::RACING) - { - erasePeerReady(peer); - peer->setWaitingForGame(true); - } -} // onSpectatorStatusChange -//----------------------------------------------------------------------------- diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 9c67dc74daf..a2585816c0b 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,175 +62,14 @@ class Tournament; enum AlwaysSpectateMode: uint8_t; struct GameInfo; +// class PlayingRoom; +#include "network/protocols/playing_room.hpp" + namespace Online { class Request; } -/* 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, -}; - -class PlayingLobby: public LobbyContextUser -{ -public: - - /* 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 - }; - -private: - std::atomic m_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; - -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 liveJoinRequest(Event* event); - 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(); - -public: - PlayingLobby(); - ~PlayingLobby(); - void setup(); - ServerState getCurrentState() const { return m_state.load(); } - bool isRacing() const { return m_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_state.load() >= LOAD_WORLD; } - bool isWorldFinished() const { return m_state.load() >= RESULT_DISPLAY; } - bool isStateAtLeastRacing() const { return m_state.load() >= RACING; } - std::shared_ptr getGameInfo() const { return m_game_info; } - std::shared_ptr getServerOwner() const - { return m_server_owner.lock(); } - void doErrorLeave() { m_state.store(ERROR_LEAVE); } - bool isWaitingForStartGame() const - { return m_state.load() == WAITING_FOR_START_GAME; } -public: - - 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); -}; - class ServerLobby : public LobbyProtocol, public LobbyContextUser { private: @@ -238,7 +78,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void pollDatabase(); #endif - std::vector> m_rooms; + std::vector> m_rooms; bool m_registered_for_once_only; @@ -329,11 +169,11 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser // kart selection 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); @@ -391,9 +231,9 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser // 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 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; @@ -418,11 +258,11 @@ 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; void updateBanList(); - bool waitingForPlayers() const; + // bool waitingForPlayers() const; float getStartupBoostOrPenaltyForKart(uint32_t ping, unsigned kart_id); // void saveInitialItems(std::shared_ptr nim); void saveIPBanTable(const SocketAddress& addr); diff --git a/src/network/server_enums.hpp b/src/network/server_enums.hpp new file mode 100644 index 00000000000..436ed6fd948 --- /dev/null +++ b/src/network/server_enums.hpp @@ -0,0 +1,54 @@ +// +// 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 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, +}; + +/* 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..3f56e930ddc 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -899,7 +899,7 @@ void STKHost::mainLoop(ProcessType pt) player_name = p.second->getMainName(); } const bool peer_not_in_game = - sl->getCurrentState() <= ServerLobby::SELECTING + sl->getCurrentState() <= ServerState::SELECTING || p.second->isWaitingForGame(); if (ServerConfig::m_kick_high_ping_players && !p.second->isDisconnected() && peer_not_in_game) @@ -1266,7 +1266,7 @@ void STKHost::handleDirectSocketRequest(Network* direct_socket, s.addUInt8((uint8_t)sl->getGameMode()); s.addUInt8(!pw.empty()); s.addUInt8((uint8_t) - (sl->getCurrentState() == ServerLobby::WAITING_FOR_START_GAME ? + (sl->getCurrentState() == ServerState::WAITING_FOR_START_GAME ? 0 : 1)); s.encodeString(sl->getPlayingTrackIdent()); direct_socket->sendRawPacket(s, sender); diff --git a/src/network/stk_peer.hpp b/src/network/stk_peer.hpp index 961804d2d75..7223a38e92a 100644 --- a/src/network/stk_peer.hpp +++ b/src/network/stk_peer.hpp @@ -393,7 +393,7 @@ class STKPeer : public NoCopy // ------------------------------------------------------------------------ void setRoomNumber(int8_t idx) { m_room_number.store(idx); } // ------------------------------------------------------------------------ - void resetRoomNumber() { m_room_number.store(-1); } + void resetRoomNumber() { m_room_number.store(0); } // ------------------------------------------------------------------------ }; // STKPeer From 8c1d698606a73ba4caf3de257cc544cd780bf13d Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:12:55 +0400 Subject: [PATCH 06/12] More movements outside of ServerLobby [skip ci] --- src/network/protocols/playing_room.cpp | 320 +++++++++++++++++++++--- src/network/protocols/playing_room.hpp | 9 + src/network/protocols/server_lobby.cpp | 322 ++++--------------------- src/network/protocols/server_lobby.hpp | 27 ++- 4 files changed, 369 insertions(+), 309 deletions(-) diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp index baf728a434c..30bc9177c63 100644 --- a/src/network/protocols/playing_room.cpp +++ b/src/network/protocols/playing_room.cpp @@ -46,6 +46,14 @@ #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" namespace { @@ -80,6 +88,51 @@ namespace 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 //============================================================================= @@ -165,7 +218,7 @@ NetworkString* PlayingRoom::getLoadWorldMessage( 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); + encodePlayers(load_world_message, players, getLobby()->getNameDecorator()); load_world_message->addUInt32(m_item_seed); if (RaceManager::get()->isBattleMode()) { @@ -235,7 +288,7 @@ void PlayingRoom::rejectLiveJoin(std::shared_ptr peer, BackLobbyReason peer->sendPacket(reset, PRM_RELIABLE); delete reset; - updatePlayerList(); + getLobby()->updatePlayerList(); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); @@ -419,7 +472,7 @@ void PlayingRoom::finishedLoadingLiveJoinClient(Event* event) // starting of race std::vector > players = getLivePlayers(); - encodePlayers(ns, players, m_name_decorator); + encodePlayers(ns, players, getLobby()->getNameDecorator()); for (unsigned i = 0; i < players.size(); i++) players[i]->getKartData().encode(ns); } @@ -430,7 +483,7 @@ void PlayingRoom::finishedLoadingLiveJoinClient(Event* event) peer->sendPacket(ns, PRM_RELIABLE); delete ns; - updatePlayerList(); + getLobby()->updatePlayerList(); peer->updateLastActivity(); } // finishedLoadingLiveJoinClient @@ -475,7 +528,7 @@ void PlayingRoom::startSelection(const Event *event) { m_peers_ready.at(event->getPeerSP()) = !m_peers_ready.at(event->getPeerSP()); - updatePlayerList(); + getLobby()->updatePlayerList(); return; } } @@ -641,15 +694,7 @@ void PlayingRoom::startSelection(const Event *event) 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())); - } - } + getLobby()->unregisterServerForLegacyGPMode(); startVotingPeriod(getSettings()->getVotingTimeout()); @@ -707,22 +752,10 @@ void PlayingRoom::startSelection(const Event *event) always_spectate_peers.end(); }, back_lobby, PRM_RELIABLE); delete back_lobby; - updatePlayerList(); + getLobby()->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(); - } + getLobby()->dropPendingConnectionsForLegacyGPMode(); // Will be changed after the first vote received setInfiniteTimeout(); @@ -785,7 +818,7 @@ void PlayingRoom::handlePlayerVote(Event* event) addVote(event->getPeer()->getHostId(), vote); // After adding the vote, show decorated name instead - vote.m_player_name = event->getPeer()->getMainProfile()->getDecoratedName(m_name_decorator); + vote.m_player_name = event->getPeer()->getMainProfile()->getDecoratedName(getLobby()->getNameDecorator()); // Now inform all clients about the vote NetworkString other = NetworkString(PROTOCOL_LOBBY_ROOM); @@ -952,7 +985,7 @@ void PlayingRoom::clientInGameWantsToBackLobby(Event* event) reset->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); peer->sendPacket(reset, PRM_RELIABLE); delete reset; - updatePlayerList(); + getLobby()->updatePlayerList(); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); @@ -1001,7 +1034,7 @@ void PlayingRoom::clientSelectingAssetsWantsToBackLobby(Event* event) reset->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE); peer->sendPacket(reset, PRM_RELIABLE); delete reset; - updatePlayerList(); + getLobby()->updatePlayerList(); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); @@ -1145,6 +1178,231 @@ int PlayingRoom::getCurrentStateScope() 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_state != SELECTING /*|| m_game_setup->isGrandPrixStarted()*/) + { + Log::warn("PlayingRoom", "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 + +//----------------------------------------------------------------------------- +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(); + 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 + //----------------------------------------------------------------------------- /// ... /// ... diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp index c327693f99e..f9152f4a0ba 100644 --- a/src/network/protocols/playing_room.hpp +++ b/src/network/protocols/playing_room.hpp @@ -128,6 +128,11 @@ class PlayingRoom: public LobbyContextUser 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(); public: PlayingRoom(); @@ -168,6 +173,9 @@ class PlayingRoom: public LobbyContextUser void resetToDefaultSettings(); void saveInitialItems(std::shared_ptr nim); bool waitingForPlayers() const; + 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); @@ -179,6 +187,7 @@ class PlayingRoom: public LobbyContextUser void clientSelectingAssetsWantsToBackLobby(Event* event); void handlePlayerVote(Event *event); void startSelection(const Event *event=NULL); // was public already + void kartSelectionRequested(Event* event); }; #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 3cb03475e01..350ee3852a6 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -78,43 +78,6 @@ // Helper functions. namespace { - /** 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) @@ -274,7 +237,7 @@ bool ServerLobby::notifyEvent(Event* event) Log::info("ServerLobby", "Synchronous message of type %d received.", message_type); - auto& peer = event->getPeerSP(); + auto peer = event->getPeerSP(); auto& room = m_rooms[peer->getRoomNumber()]; switch (message_type) @@ -372,13 +335,13 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) Log::info("ServerLobby", "Message of type %d received.", message_type); - auto& peer = event->getPeerSP(); + auto peer = event->getPeerSP(); auto& room = m_rooms[peer->getRoomNumber()]; switch(message_type) { case LE_CONNECTION_REQUESTED: connectionRequested(event); break; - case LE_KART_SELECTION: kartSelectionRequested(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; @@ -938,69 +901,6 @@ void ServerLobby::asynchronousUpdate() } } // asynchronousUpdate -//----------------------------------------------------------------------------- -/** 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 - //----------------------------------------------------------------------------- /** Simple finite state machine. Once this * is known, register the server and its address with the stk server so that @@ -2362,28 +2262,6 @@ void ServerLobby::updateServerOwner(bool force) 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 - // ---------------------------------------------------------------------------- /** Select the track to be used based on all votes being received. * \param winner_vote The PeerVote that was picked. @@ -2636,73 +2514,6 @@ void ServerLobby::configPeersStartTime() }); } // 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)) - { - getRankingForPlayer(peer->getMainProfile()); - } - } - // Re-activiate the ai - if (auto ai = m_ai_peer.lock()) - ai->setValidated(true); -} // addWaitingPlayersToGame - -//----------------------------------------------------------------------------- -void ServerLobby::resetServer() -{ - addWaitingPlayersToGame(); - resetPeersReady(); - updatePlayerList(true/*update_when_reset_server*/); - NetworkString* server_info = getNetworkString(); - server_info->setSynchronous(true); - server_info->addUInt8(LE_SERVER_INFO); - m_game_setup->addServerInfo(server_info); - Comm::sendMessageToPeersInServer(server_info); - delete server_info; - - for (auto p : m_peers_ready) - { - if (auto peer = p.first.lock()) - peer->updateLastActivity(); - } - - 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 - //----------------------------------------------------------------------------- void ServerLobby::testBannedForIP(std::shared_ptr peer) const { @@ -3194,48 +3005,6 @@ void ServerLobby::addLiveJoinPlaceholder( } } // 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 - //----------------------------------------------------------------------------- void ServerLobby::handleServerCommand(Event* event) { @@ -3370,41 +3139,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 @@ -3437,3 +3171,53 @@ bool ServerLobby::isClientServerHost(const std::shared_ptr& peer) { return peer->getHostId() == m_client_server_host_id.load(); } // isClientServerHost +//----------------------------------------------------------------------------- + +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 +//----------------------------------------------------------------------------- diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index a2585816c0b..cba028d771e 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -167,7 +167,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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); @@ -213,13 +213,13 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void computeNewRankings(NetworkString* ns); void checkRaceFinished(); void configPeersStartTime(); - void resetServer(); - void addWaitingPlayersToGame(); + // void resetServer(); + // void addWaitingPlayersToGame(); void changeHandicap(Event* event); void handlePlayerDisconnection() const; void addLiveJoinPlaceholder( std::vector >& players) const; - void setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const; + // void setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const; bool handleAssets(Event* event); bool handleAssetsAndAddonScores(std::shared_ptr peer, @@ -229,8 +229,8 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void handleServerCommand(Event* event); // void rejectLiveJoin(std::shared_ptr peer, BackLobbyReason blr); // bool canLiveJoinNow() const; - int getReservedId(std::shared_ptr& p, - unsigned local_id); + // int getReservedId(std::shared_ptr& p, + // unsigned local_id); // void handleKartInfo(Event* event); // void clientInGameWantsToBackLobby(Event* event); // void clientSelectingAssetsWantsToBackLobby(Event* event); @@ -285,6 +285,15 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr npp, STKPeer* peer); + 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; @@ -295,9 +304,9 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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; From 5ddb81ea894b609be2d3f896f5c09508ee78bea2 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:02:33 +0400 Subject: [PATCH 07/12] Remove unused stuff from Protocol [skip ci] --- src/network/protocol.cpp | 6 +----- src/network/protocol.hpp | 25 +------------------------ 2 files changed, 2 insertions(+), 29 deletions(-) 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); From 9a65c786216372085ef204a023240a98dbfcb1aa Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Sun, 15 Jun 2025 19:45:22 +0400 Subject: [PATCH 08/12] Split ServerState into two [skip ci] --- src/network/child_loop.cpp | 3 +- src/network/protocol_manager.cpp | 4 +- src/network/protocols/playing_room.cpp | 56 ++++++------ src/network/protocols/playing_room.hpp | 19 ++--- src/network/protocols/server_lobby.cpp | 113 +++++++++++++++++++++++-- src/network/protocols/server_lobby.hpp | 24 ++++-- src/network/server_enums.hpp | 13 ++- src/network/stk_host.cpp | 9 +- 8 files changed, 170 insertions(+), 71 deletions(-) diff --git a/src/network/child_loop.cpp b/src/network/child_loop.cpp index 6727559b43f..f7a5cd0a8d7 100644 --- a/src/network/child_loop.cpp +++ b/src/network/child_loop.cpp @@ -125,8 +125,7 @@ void ChildLoop::run() if (m_port == 0 && STKHost::existHost()) { auto sl = LobbyProtocol::get(); - if (sl && - sl->getCurrentState() >= ServerState::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_manager.cpp b/src/network/protocol_manager.cpp index 526aaa07b13..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) { - ServerState ss = sl->getCurrentState(); - if (!(ss >= ServerState::WAIT_FOR_WORLD_LOADED && - ss <= ServerState::RACING)) + if (sl->canIgnoreControllerEvents()) { delete event_top; continue; diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp index 30bc9177c63..aee74e90649 100644 --- a/src/network/protocols/playing_room.cpp +++ b/src/network/protocols/playing_room.cpp @@ -132,7 +132,6 @@ namespace } // getLivePlayers //------------------------------------------------------------------------- - } // namespace //============================================================================= @@ -142,7 +141,7 @@ PlayingRoom::PlayingRoom() m_current_ai_count.store(0); m_rs_state.store(RS_NONE); m_server_owner_id.store(-1); - m_state = SET_PUBLIC_ADDRESS; + 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); @@ -498,11 +497,11 @@ void PlayingRoom::startSelection(const Event *event) if (event != NULL) { - if (m_state.load() != WAITING_FOR_START_GAME) + if (m_play_state.load() != WAITING_FOR_START_GAME) { Log::warn("ServerLobby", "Received startSelection while being in state %d.", - m_state.load()); + m_play_state.load()); return; } if (getCrownManager()->isSleepingServer()) @@ -739,7 +738,7 @@ void PlayingRoom::startSelection(const Event *event) Comm::sendStringToPeer(peer, "The server will ignore your kart choice"); } - m_state = SELECTING; + m_play_state = SELECTING; if (need_to_update || !always_spectate_peers.empty()) { NetworkString* back_lobby = getNetworkString(2); @@ -772,10 +771,10 @@ void PlayingRoom::startSelection(const Event *event) */ void PlayingRoom::handlePlayerVote(Event* event) { - if (m_state != SELECTING || !getSettings()->hasTrackVoting()) + if (m_play_state != SELECTING || !getSettings()->hasTrackVoting()) { Log::warn("ServerLobby", "Received track vote while in state %d.", - m_state.load()); + m_play_state.load()); return; } @@ -850,20 +849,12 @@ void PlayingRoom::finishedLoadingWorldClient(Event *event) void PlayingRoom::playerFinishedResult(Event *event) { if (m_rs_state.load() == RS_ASYNC_RESET || - m_state.load() != RESULT_DISPLAY) + m_play_state.load() != RESULT_DISPLAY) return; std::shared_ptr peer = event->getPeerSP(); m_peers_ready.at(peer) = true; } // playerFinishedResult -//----------------------------------------------------------------------------- -bool PlayingRoom::waitingForPlayers() const -{ - if (getSettings()->isLegacyGPMode() && getSettings()->isLegacyGPModeStarted()) - return false; - return m_state.load() >= WAITING_FOR_START_GAME; -} // waitingForPlayers - //----------------------------------------------------------------------------- /** Tell the client \ref RemoteKartInfo of a player when some player joining * live. @@ -1003,7 +994,7 @@ void PlayingRoom::clientSelectingAssetsWantsToBackLobby(Event* event) { std::shared_ptr peer = event->getPeerSP(); - if (m_state.load() != SELECTING || peer->isWaitingForGame()) + if (m_play_state.load() != SELECTING || peer->isWaitingForGame()) { Log::warn("ServerLobby", "%s try to leave selecting assets at wrong time.", @@ -1091,7 +1082,7 @@ void PlayingRoom::resetToDefaultSettings() { if (getSettings()->isServerConfigurable() && !getSettings()->isPreservingMode()) { - if (m_state == WAITING_FOR_START_GAME) + if (m_play_state == WAITING_FOR_START_GAME) handleServerConfiguration(NULL); else m_reset_to_default_mode_later.store(true); @@ -1170,7 +1161,7 @@ int PlayingRoom::getPermissions(std::shared_ptr peer) const int PlayingRoom::getCurrentStateScope() { - auto state = m_state.load(); + auto state = m_play_state.load(); if (state < WAITING_FOR_START_GAME || state > RESULT_DISPLAY) return 0; @@ -1184,10 +1175,10 @@ int PlayingRoom::getCurrentStateScope() */ void PlayingRoom::kartSelectionRequested(Event* event) { - if (m_state != SELECTING /*|| m_game_setup->isGrandPrixStarted()*/) + if (m_play_state != SELECTING /*|| m_game_setup->isGrandPrixStarted()*/) { Log::warn("PlayingRoom", "Received kart selection while in state %d.", - m_state.load()); + m_play_state.load()); return; } @@ -1374,11 +1365,13 @@ void PlayingRoom::addWaitingPlayersToGame() } // 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); @@ -1393,16 +1386,21 @@ void PlayingRoom::resetServer() } 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); + // 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 - //----------------------------------------------------------------------------- /// ... /// ... @@ -1457,8 +1455,8 @@ float PlayingRoom::getTimeUntilExpiration() const void PlayingRoom::onSpectatorStatusChange(const std::shared_ptr& peer) { - auto state = m_state.load(); - if (state >= ServerState::SELECTING && state < ServerState::RACING) + auto state = m_play_state.load(); + if (state >= ServerPlayState::SELECTING && state < ServerPlayState::RACING) { erasePeerReady(peer); peer->setWaitingForGame(true); diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp index f9152f4a0ba..d569aa22c60 100644 --- a/src/network/protocols/playing_room.hpp +++ b/src/network/protocols/playing_room.hpp @@ -22,6 +22,7 @@ #include "network/server_enums.hpp" #include "utils/lobby_context.hpp" #include "network/packet_types.hpp" +#include "network/kart_data.hpp" #include #include @@ -39,10 +40,8 @@ class NetworkItemManager; class PlayingRoom: public LobbyContextUser { -public: - private: - std::atomic m_state; + std::atomic m_play_state; std::atomic m_rs_state; @@ -138,8 +137,8 @@ class PlayingRoom: public LobbyContextUser PlayingRoom(); ~PlayingRoom(); void setup(); - ServerState getCurrentState() const { return m_state.load(); } - bool isRacing() const { return m_state.load() == RACING; } + 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(); } @@ -150,15 +149,14 @@ class PlayingRoom: public LobbyContextUser } void erasePeerReady(std::shared_ptr peer) { m_peers_ready.erase(peer); } - 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 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(); } - void doErrorLeave() { m_state.store(ERROR_LEAVE); } bool isWaitingForStartGame() const - { return m_state.load() == WAITING_FOR_START_GAME; } + { return m_play_state.load() == WAITING_FOR_START_GAME; } public: // were public before and SL doesn't call them @@ -172,7 +170,6 @@ class PlayingRoom: public LobbyContextUser int getCurrentStateScope(); void resetToDefaultSettings(); void saveInitialItems(std::shared_ptr nim); - bool waitingForPlayers() const; void setKartDataProperly(KartData& kart_data, const std::string& kart_name, std::shared_ptr player, const std::string& type) const; diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 350ee3852a6..9c442f29449 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -150,6 +150,8 @@ ServerLobby::ServerLobby() : LobbyProtocol() { m_client_server_host_id.store(0); + m_init_state = SET_PUBLIC_ADDRESS; + m_lobby_context = std::make_shared(this, (bool)ServerConfig::m_soccer_tournament); m_lobby_context->setup(); m_context = m_lobby_context.get(); @@ -237,8 +239,7 @@ bool ServerLobby::notifyEvent(Event* event) Log::info("ServerLobby", "Synchronous message of type %d received.", message_type); - auto peer = event->getPeerSP(); - auto& room = m_rooms[peer->getRoomNumber()]; + auto& room = getRoomForPeer(event->getPeerSP()); switch (message_type) { @@ -335,8 +336,7 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) Log::info("ServerLobby", "Message of type %d received.", message_type); - auto peer = event->getPeerSP(); - auto& room = m_rooms[peer->getRoomNumber()]; + auto& room = getRoomForPeer(event->getPeerSP()); switch(message_type) { @@ -2205,8 +2205,8 @@ void ServerLobby::updatePlayerList(bool update_when_reset_server) void ServerLobby::updateServerOwner(bool force) { - ServerState state = m_state.load(); - if (state < WAITING_FOR_START_GAME || state > RESULT_DISPLAY) + ServerInitState state = m_init_state.load(); + if (state != RUNNING) return; if (getCrownManager()->isOwnerLess()) @@ -2321,6 +2321,15 @@ void ServerLobby::finishedLoadingWorld() } // finishedLoadingWorld; //----------------------------------------------------------------------------- +bool ServerLobby::waitingForPlayers() const +{ + if (getSettings()->isLegacyGPMode() && getSettings()->isLegacyGPModeStarted()) + return false; + + return m_init_state.load() >= RUNNING; +} // waitingForPlayers +//----------------------------------------------------------------------------- + void ServerLobby::handlePendingConnection() { std::lock_guard lock(m_keys_mutex); @@ -3221,3 +3230,95 @@ void ServerLobby::addWaitingPlayersToRanking( } } // 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 +{ + auto room = getRoomForPeer(peer); + if (!room) + { + Log::warn("ServerLobby", "isPeerInGame: no room for peer??"); + return false; + } + + return room->getCurrentPlayState() > ServerPlayState::SELECTING + && !peer->isWaitingForGame(); +} // isPeerInGame +//----------------------------------------------------------------------------- + +bool ServerLobby::hasAnyGameStarted() const +{ + for (auto& room: m_rooms) + if (room->getCurrentPlayState() != ServerPlayState::WAITING_FOR_START_GAME) + return true; + + return false; +} // hasAnyGameStarted +//----------------------------------------------------------------------------- + +bool ServerLobby::isPastRegistrationPhase() const +{ + return getCurrentInitState() >= ServerInitState::RUNNING; +} // isPastRegistrationPhase +//----------------------------------------------------------------------------- + +std::shared_ptr ServerLobby::getRoomForPeer(const std::shared_ptr& peer) const +{ + if (!peer) + { + Log::warn("ServerLobby", "getRoomForPeer: no peer??"); + return {}; + } + + return getRoom(peer->getRoomNumber()); +} // getRoomForPeer +//----------------------------------------------------------------------------- + +std::shared_ptr ServerLobby::getRoom(int idx) const +{ + if (idx < 0 || idx >= m_rooms.size()) + { + Log::warn("ServerLobby", "getRoom: invalid room number %d of %d", idx, m_rooms.size()); + return {}; + } + + return m_rooms[idx]; +} // getRoom +//----------------------------------------------------------------------------- + +int ServerLobby::getPermissions(std::shared_ptr peer) const +{ + auto room = getRoomForPeer(peer); + if (!room) + { + Log::warn("ServerLobby", "getPermissions: invalid room, cannot get permissions for peer"); + return PE_NONE; + } + + 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 cba028d771e..35f31f2932e 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -74,6 +74,8 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser { private: + std::atomic m_init_state; + #ifdef ENABLE_SQLITE3 void pollDatabase(); #endif @@ -114,8 +116,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser #define Reworking "This method was moved to PlayingRoom but is still used for ServerLobby. This has to be changed," public: - [[deprecated(Reworking)]] - ServerState getCurrentState() const { return m_rooms[0]->getCurrentState(); } + ServerInitState getCurrentInitState() const { return m_init_state.load(); } [[deprecated(Reworking)]] virtual bool isRacing() const OVERRIDE { return m_rooms[0]->isRacing(); } @@ -151,12 +152,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser std::shared_ptr getServerOwner() const { return m_rooms[0]->getServerOwner(); } - [[deprecated(Reworking)]] - void doErrorLeave() - { - for (auto& room: m_rooms) - room->doErrorLeave(); - } + void doErrorLeave() { m_init_state.store(ERROR_LEAVE); } [[deprecated(Reworking)]] bool isWaitingForStartGame() const { return m_rooms[0]->isWaitingForStartGame(); } @@ -262,7 +258,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void checkIncomingConnectionRequests(); void finishedLoadingWorld() OVERRIDE; void updateBanList(); - // bool waitingForPlayers() const; + bool waitingForPlayers() const; float getStartupBoostOrPenaltyForKart(uint32_t ping, unsigned kart_id); // void saveInitialItems(std::shared_ptr nim); void saveIPBanTable(const SocketAddress& addr); @@ -317,6 +313,16 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser bool isChildProcess() { return m_process_type == PT_CHILD; } bool isClientServerHost(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 diff --git a/src/network/server_enums.hpp b/src/network/server_enums.hpp index 436ed6fd948..5343c3f26de 100644 --- a/src/network/server_enums.hpp +++ b/src/network/server_enums.hpp @@ -20,10 +20,8 @@ #define SERVER_ENUMS_HPP /* The state for a small finite state machine. */ -enum ServerState : unsigned int +enum ServerPlayState : 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 @@ -32,8 +30,15 @@ enum ServerState : unsigned int 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 + EXITING, }; enum SelectionPhase: unsigned int diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index 3f56e930ddc..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() <= ServerState::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() == ServerState::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 From 5167032e426fa7838b29f8c990f0d6376d3e29ba Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Mon, 16 Jun 2025 01:27:10 +0400 Subject: [PATCH 09/12] Move two functions from SL to PR --- src/network/protocols/playing_room.cpp | 129 +++++++++++++++++++++++ src/network/protocols/playing_room.hpp | 4 + src/network/protocols/server_lobby.cpp | 136 ++----------------------- src/network/protocols/server_lobby.hpp | 6 +- 4 files changed, 143 insertions(+), 132 deletions(-) diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp index aee74e90649..b1f6a91d9a0 100644 --- a/src/network/protocols/playing_room.cpp +++ b/src/network/protocols/playing_room.cpp @@ -1402,6 +1402,135 @@ void PlayingRoom::resetServer() 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(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 +//----------------------------------------------------------------------------- /// ... /// ... /// ... diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp index d569aa22c60..c027a0fcb6d 100644 --- a/src/network/protocols/playing_room.hpp +++ b/src/network/protocols/playing_room.hpp @@ -158,6 +158,10 @@ class PlayingRoom: public LobbyContextUser 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); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 9c442f29449..5ede59c29cf 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -906,6 +906,9 @@ void ServerLobby::asynchronousUpdate() * is known, register the server and its address with the stk server so that * client can find it. */ + +// kimden: Sounds like a thing to not be done here..... + void ServerLobby::update(int ticks) { World* w = World::getWorld(); @@ -1009,11 +1012,14 @@ void ServerLobby::update(int ticks) } } } + + // kimden: ok, this really belongs to SL if (w) setGameStartedProgress(w->getGameStartedProgress()); else resetGameStartedProgress(); + // kimden: ok, this really belongs to SL if (w && w->getPhase() == World::RACE_PHASE) { storePlayingTrack(RaceManager::get()->getTrackName()); @@ -1021,6 +1027,7 @@ void ServerLobby::update(int ticks) else storePlayingTrack(""); + // kimden: I don't know // Reset server to initial state if no more connected players if (m_rs_state.load() == RS_WAITING) { @@ -2885,135 +2892,6 @@ void ServerLobby::changeHandicap(Event* event) 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::handleServerCommand(Event* event) { diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 35f31f2932e..55323c061a5 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -212,9 +212,9 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser // void resetServer(); // void addWaitingPlayersToGame(); void changeHandicap(Event* event); - void handlePlayerDisconnection() const; - void addLiveJoinPlaceholder( - std::vector >& players) const; + // void handlePlayerDisconnection() const; + // void addLiveJoinPlaceholder( + // std::vector >& players) const; // void setPlayerKarts(const NetworkString& ns, std::shared_ptr peer) const; bool handleAssets(Event* event); From 24e16330f0143f83f0c4d60eb11cc4859c2f51dd Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Tue, 17 Jun 2025 00:46:27 +0400 Subject: [PATCH 10/12] Kind of split update and asyncUpdate [skip ci] --- src/network/protocols/playing_room.cpp | 714 +++++++++++++++++++++- src/network/protocols/playing_room.hpp | 18 + src/network/protocols/server_lobby.cpp | 809 ++----------------------- src/network/protocols/server_lobby.hpp | 4 +- 4 files changed, 795 insertions(+), 750 deletions(-) diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp index b1f6a91d9a0..d2b14e25338 100644 --- a/src/network/protocols/playing_room.cpp +++ b/src/network/protocols/playing_room.cpp @@ -54,6 +54,9 @@ #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" namespace { @@ -198,6 +201,575 @@ void PlayingRoom::setup() } // PlayingRoom::setup() //----------------------------------------------------------------------------- +void PlayingRoom::updateRoom(int ticks) +{ + World* w = World::getWorld(); + bool world_started = m_play_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 && isClientServerHost(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_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 && isClientServerHost(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) + m_game_progress = w->getGameStartedProgress(); + else + m_game_progress = { std::numeric_limits::max(), std::numeric_limits::max() }; + + if (w && w->getPhase() == World::RACE_PHASE) + m_playing_track = (RaceManager::get()->getTrackName()); + else + m_playing_track = ""; + + // 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_play_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; + } +} // 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_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.")]] @@ -890,7 +1462,6 @@ void PlayingRoom::handleKartInfo(Event* event) peer->sendPacket(ns, PRM_RELIABLE); delete ns; - FreeForAll* ffa_world = dynamic_cast(World::getWorld()); if (ffa_world) ffa_world->notifyAboutScoreIfNonzero(kart_id); @@ -1162,11 +1733,13 @@ int PlayingRoom::getPermissions(std::shared_ptr peer) const int PlayingRoom::getCurrentStateScope() { auto state = m_play_state.load(); - if (state < WAITING_FOR_START_GAME - || state > RESULT_DISPLAY) + + 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 //----------------------------------------------------------------------------- @@ -1492,7 +2065,7 @@ void PlayingRoom::addLiveJoinPlaceholder( return; if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL) { - Track* t = TrackManager::get()->getTrack(m_game_setup->getCurrentTrack()); + Track* t = TrackManager::get()->getTrack(getGameSetupFromCtx()->getCurrentTrack()); assert(t); int max_players = std::min((int)getSettings()->getServerMaxPlayers(), (int)t->getMaxArenaPlayers()); @@ -1531,6 +2104,139 @@ void PlayingRoom::addLiveJoinPlaceholder( } } // 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 +//----------------------------------------------------------------------------- /// ... /// ... /// ... diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp index c027a0fcb6d..9df23ff141a 100644 --- a/src/network/protocols/playing_room.hpp +++ b/src/network/protocols/playing_room.hpp @@ -98,6 +98,12 @@ class PlayingRoom: public LobbyContextUser 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() { @@ -132,11 +138,14 @@ class PlayingRoom: public LobbyContextUser unsigned local_id); void resetServer(); void addWaitingPlayersToGame(); + void configPeersStartTime(); 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(); } @@ -162,6 +171,11 @@ class PlayingRoom: public LobbyContextUser void addLiveJoinPlaceholder( std::vector >& players) const; + std::pair getGameStartedProgress() const + { return m_game_progress; } + + std::string getPlayingTrack() const { return m_playing_track; } + public: // were public before and SL doesn't call them void setTimeoutFromNow(int seconds); @@ -189,6 +203,10 @@ class PlayingRoom: public LobbyContextUser 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 5ede59c29cf..ca51df75598 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -513,393 +513,98 @@ 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*/); - } + for (auto& room: m_rooms) + room->asynchronousUpdateRoom(); - switch (m_state.load()) - { - case SET_PUBLIC_ADDRESS: + switch (m_init_state.load()) { - // 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()) + case SET_PUBLIC_ADDRESS: { - m_state = WAITING_FOR_START_GAME; - if (m_reset_to_default_mode_later.exchange(false)) - handleServerConfiguration(NULL); - - 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: - { - 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()) - { - // 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; + case ERROR_LEAVE: + { + requestTerminate(); + m_init_state = EXITING; + STKHost::get()->requestShutdown(); + break; + } + default: + break; } - } // asynchronousUpdate //----------------------------------------------------------------------------- /** Simple finite state machine. Once this @@ -911,262 +616,11 @@ void ServerLobby::asynchronousUpdate() 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 && isClientServerHost(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_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 && isClientServerHost(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(); - } - } - } - - // kimden: ok, this really belongs to SL - if (w) - setGameStartedProgress(w->getGameStartedProgress()); - else - resetGameStartedProgress(); - - // kimden: ok, this really belongs to SL - if (w && w->getPhase() == World::RACE_PHASE) - { - storePlayingTrack(RaceManager::get()->getTrackName()); - } - else - storePlayingTrack(""); - - // kimden: I don't know - // 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; + for (auto& room: m_rooms) + room->updateRoom(ticks); - // 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; - } + setGameStartedProgress(m_rooms[0]->getGameStartedProgress()); + storePlayingTrack(m_rooms[0]->getPlayingTrack()); } // update //----------------------------------------------------------------------------- @@ -2208,68 +1662,8 @@ void ServerLobby::updatePlayerList(bool update_when_reset_server) }, pl); delete pl; } // updatePlayerList -//----------------------------------------------------------------------------- - -void ServerLobby::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 - // ---------------------------------------------------------------------------- + /** 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). @@ -2457,79 +1851,6 @@ void ServerLobby::submitRankingsToAddons() } } // 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::testBannedForIP(std::shared_ptr peer) const { diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 55323c061a5..092a826743d 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -178,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); @@ -208,7 +208,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void submitRankingsToAddons(); void computeNewRankings(NetworkString* ns); void checkRaceFinished(); - void configPeersStartTime(); + // void configPeersStartTime(); // void resetServer(); // void addWaitingPlayersToGame(); void changeHandicap(Event* event); From e33ff229f0047a2af7077a4cd77b3d3afa2e9f73 Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:40:29 +0400 Subject: [PATCH 11/12] Minor fixes, also make PR a LobbyProtocol for some reason --- src/network/protocols/playing_room.cpp | 32 +++++++++++--------------- src/network/protocols/playing_room.hpp | 8 ++----- src/network/protocols/server_lobby.cpp | 6 ++--- src/network/protocols/server_lobby.hpp | 5 ++++ 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp index d2b14e25338..d0f0bcd969f 100644 --- a/src/network/protocols/playing_room.cpp +++ b/src/network/protocols/playing_room.cpp @@ -57,6 +57,7 @@ #include "utils/hit_processor.hpp" #include "modes/capture_the_flag.hpp" #include "utils/chat_manager.hpp" +#include "network/protocols/game_events_protocol.hpp" namespace { @@ -205,7 +206,7 @@ void PlayingRoom::updateRoom(int ticks) { World* w = World::getWorld(); bool world_started = m_play_state.load() >= WAIT_FOR_WORLD_LOADED && - m_state.load() <= RACING && m_server_has_loaded_world.load(); + 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) @@ -243,7 +244,7 @@ void PlayingRoom::updateRoom(int ticks) 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 && isClientServerHost(peer)) + if (getLobby()->isChildClientServerHost(peer)) continue; Log::info("ServerLobby", "%s %s has been idle ingame for more than" " %d seconds, kick.", @@ -282,7 +283,7 @@ void PlayingRoom::updateRoom(int ticks) getHitProcessor()->punishSwatterHits(); } } - if (m_state.load() == WAITING_FOR_START_GAME) { + if (m_play_state.load() == WAITING_FOR_START_GAME) { sec = getSettings()->getKickIdleLobbyPlayerSeconds(); auto peers = STKHost::get()->getPeers(); for (auto peer: peers) @@ -292,7 +293,7 @@ void PlayingRoom::updateRoom(int ticks) !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 && isClientServerHost(peer)) + if (getLobby()->isChildClientServerHost(peer)) continue; std::string peer_name = ""; if (peer->hasPlayerProfiles()) @@ -306,14 +307,14 @@ void PlayingRoom::updateRoom(int ticks) } if (w) - m_game_progress = w->getGameStartedProgress(); + setGameStartedProgress(w->getGameStartedProgress()); else - m_game_progress = { std::numeric_limits::max(), std::numeric_limits::max() }; + resetGameStartedProgress(); if (w && w->getPhase() == World::RACE_PHASE) - m_playing_track = (RaceManager::get()->getTrackName()); + storePlayingTrack(RaceManager::get()->getTrackName()); else - m_playing_track = ""; + storePlayingTrack(""); // Reset server to initial state if no more connected players if (m_rs_state.load() == RS_WAITING) @@ -329,7 +330,7 @@ void PlayingRoom::updateRoom(int ticks) STKHost::get()->updatePlayers(); if (m_rs_state.load() == RS_NONE && - (m_state.load() > WAITING_FOR_START_GAME/* || + (m_play_state.load() > WAITING_FOR_START_GAME/* || m_game_setup->isGrandPrixStarted()*/) && (STKHost::get()->getPlayersInGame() == 0 || all_players_in_world_disconnected)) @@ -372,7 +373,7 @@ void PlayingRoom::updateRoom(int ticks) // Reset for ranked server if in kart / track selection has only 1 player if (getSettings()->isRanked() && - m_state.load() == SELECTING && + m_play_state.load() == SELECTING && STKHost::get()->getPlayersInGame() == 1) { NetworkString* back_lobby = getNetworkString(2); @@ -391,8 +392,6 @@ void PlayingRoom::updateRoom(int ticks) switch (m_play_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: @@ -450,9 +449,6 @@ void PlayingRoom::updateRoom(int ticks) m_rs_state.store(RS_ASYNC_RESET); } break; - case ERROR_LEAVE: - case EXITING: - break; } } // updateRoom //----------------------------------------------------------------------------- @@ -769,7 +765,6 @@ void PlayingRoom::asynchronousUpdateRoom() } // 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.")]] @@ -780,6 +775,7 @@ void PlayingRoom::updateMapsForMode() ); } // updateMapsForMode //----------------------------------------------------------------------------- + NetworkString* PlayingRoom::getLoadWorldMessage( std::vector >& players, bool live_join) const @@ -1483,7 +1479,7 @@ void PlayingRoom::clientInGameWantsToBackLobby(Event* event) return; } - if (m_process_type == PT_CHILD && isClientServerHost(event->getPeer())) + 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 @@ -1573,7 +1569,7 @@ void PlayingRoom::clientSelectingAssetsWantsToBackLobby(Event* event) return; } - if (m_process_type == PT_CHILD && isClientServerHost(event->getPeer())) + if (getLobby()->isChildClientServerHost(event->getPeerSP())) { NetworkString* back_to_lobby = getNetworkString(2); back_to_lobby->setSynchronous(true); diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp index 9df23ff141a..9a835a66e3d 100644 --- a/src/network/protocols/playing_room.hpp +++ b/src/network/protocols/playing_room.hpp @@ -23,6 +23,7 @@ #include "utils/lobby_context.hpp" #include "network/packet_types.hpp" #include "network/kart_data.hpp" +#include "network/protocols/lobby_protocol.hpp" #include #include @@ -38,7 +39,7 @@ class GameInfo; class NetworkItemManager; -class PlayingRoom: public LobbyContextUser +class PlayingRoom: public LobbyProtocol, public LobbyContextUser { private: std::atomic m_play_state; @@ -171,11 +172,6 @@ class PlayingRoom: public LobbyContextUser void addLiveJoinPlaceholder( std::vector >& players) const; - std::pair getGameStartedProgress() const - { return m_game_progress; } - - std::string getPlayingTrack() const { return m_playing_track; } - public: // were public before and SL doesn't call them void setTimeoutFromNow(int seconds); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index ca51df75598..507e3138e79 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -336,7 +336,7 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) Log::info("ServerLobby", "Message of type %d received.", message_type); - auto& room = getRoomForPeer(event->getPeerSP()); + auto room = getRoomForPeer(event->getPeerSP()); switch(message_type) { @@ -620,7 +620,7 @@ void ServerLobby::update(int ticks) room->updateRoom(ticks); setGameStartedProgress(m_rooms[0]->getGameStartedProgress()); - storePlayingTrack(m_rooms[0]->getPlayingTrack()); + storePlayingTrack(m_rooms[0]->getPlayingTrackIdent()); } // update //----------------------------------------------------------------------------- @@ -1031,7 +1031,7 @@ bool ServerLobby::handleAssetsAndAddonScores(std::shared_ptr peer, peer->setAvailableKartsTracks(client_karts, client_maps); peer->setAddonsScores(addons_scores); - if (m_process_type == PT_CHILD && isClientServerHost(peer)) + if (isChildClientServerHost(peer)) { // Update child process addons list too so player can choose later getAssetManager()->updateAddons(); diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 092a826743d..e13bb05b891 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -313,6 +313,11 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser 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); + } + bool canIgnoreControllerEvents() const; bool isPeerInGame(const std::shared_ptr& peer) const; bool hasAnyGameStarted() const; From 6b47b30830fb5b174bb6081ddc9fe687f4d44a8a Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Wed, 9 Jul 2025 01:41:14 +0400 Subject: [PATCH 12/12] Move one function from SL to PR, minor renames [skip ci] --- src/network/protocols/playing_room.cpp | 94 +++++++++++++++++++++++++- src/network/protocols/playing_room.hpp | 1 + src/network/protocols/server_lobby.cpp | 88 ------------------------ src/network/protocols/server_lobby.hpp | 2 +- 4 files changed, 93 insertions(+), 92 deletions(-) diff --git a/src/network/protocols/playing_room.cpp b/src/network/protocols/playing_room.cpp index d0f0bcd969f..8591bd31245 100644 --- a/src/network/protocols/playing_room.cpp +++ b/src/network/protocols/playing_room.cpp @@ -409,7 +409,7 @@ void PlayingRoom::updateRoom(int ticks) loadWorld(); getGPManager()->updateWorldScoring(); getSettings()->updateWorldSettings(m_game_info); - m_state = WAIT_FOR_WORLD_LOADED; + m_play_state = WAIT_FOR_WORLD_LOADED; break; case RACING: if (World::getWorld() && RaceEventManager::get() && @@ -430,7 +430,7 @@ void PlayingRoom::updateRoom(int ticks) // 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; + m_play_state = RESULT_DISPLAY; Comm::sendMessageToPeers(m_result_ns, PRM_RELIABLE); delete m_result_ns; Log::info("ServerLobby", "End of game message sent"); @@ -729,7 +729,7 @@ void PlayingRoom::asynchronousUpdateRoom() // Reset for next state usage resetPeersReady(); - m_state = LOAD_WORLD; + 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 @@ -2232,6 +2232,94 @@ void PlayingRoom::updateServerOwner(bool force) 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 + //----------------------------------------------------------------------------- /// ... /// ... diff --git a/src/network/protocols/playing_room.hpp b/src/network/protocols/playing_room.hpp index 9a835a66e3d..f72cc65a53f 100644 --- a/src/network/protocols/playing_room.hpp +++ b/src/network/protocols/playing_room.hpp @@ -140,6 +140,7 @@ class PlayingRoom: public LobbyProtocol, public LobbyContextUser void resetServer(); void addWaitingPlayersToGame(); void configPeersStartTime(); + void checkRaceFinished(); public: PlayingRoom(); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 507e3138e79..532bdba7c07 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -752,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 diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index e13bb05b891..09ef9202812 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -207,7 +207,7 @@ class ServerLobby : public LobbyProtocol, public LobbyContextUser void getRankingForPlayer(std::shared_ptr p); void submitRankingsToAddons(); void computeNewRankings(NetworkString* ns); - void checkRaceFinished(); + // void checkRaceFinished(); // void configPeersStartTime(); // void resetServer(); // void addWaitingPlayersToGame();