diff --git a/data/gui/License.txt b/data/gui/License.txt index 6e3474c64..adb4c4c30 100644 --- a/data/gui/License.txt +++ b/data/gui/License.txt @@ -32,6 +32,8 @@ mass.png by Auria, released under CC-0 power.png by Auria, based on https://openclipart.org/detail/193925/check-engine and https://openclipart.org/detail/144799/power-icon, released under CC-0 +crown.png by glitch, from https://openclipart.org/detail/210257/misc-game-crown, released under public domain + ==== Glass Skin by Auria, under CC-BY-SA 3+ diff --git a/data/gui/crown.png b/data/gui/crown.png new file mode 100644 index 000000000..992c9d93a Binary files /dev/null and b/data/gui/crown.png differ diff --git a/data/gui/karts_online.stkgui b/data/gui/karts_online.stkgui deleted file mode 100644 index 2d0699fac..000000000 --- a/data/gui/karts_online.stkgui +++ /dev/null @@ -1,28 +0,0 @@ - - -
- -
- - - - - - - - - - - - - - - -
- - -
diff --git a/data/gui/online/create_server.stkgui b/data/gui/online/create_server.stkgui index 9f5fce33e..67f508743 100644 --- a/data/gui/online/create_server.stkgui +++ b/data/gui/online/create_server.stkgui @@ -2,69 +2,70 @@
- - -
-
-
-
+ - - -
-
- - - -
-
- - - -
- -
diff --git a/data/gui/online/networking_lobby.stkgui b/data/gui/online/networking_lobby.stkgui index c2660c79c..957e273c3 100644 --- a/data/gui/online/networking_lobby.stkgui +++ b/data/gui/online/networking_lobby.stkgui @@ -1,7 +1,7 @@
-
+
@@ -20,7 +20,7 @@ -
+
+
diff --git a/data/gui/online/server_selection.stkgui b/data/gui/online/server_selection.stkgui index 8d89bdd96..5c19d2eb0 100644 --- a/data/gui/online/server_selection.stkgui +++ b/data/gui/online/server_selection.stkgui @@ -11,5 +11,13 @@ +
+
+ + +
+
diff --git a/data/gui/online/splitscreen_player_dialog.stkgui b/data/gui/online/splitscreen_player_dialog.stkgui new file mode 100644 index 000000000..4fbcf1973 --- /dev/null +++ b/data/gui/online/splitscreen_player_dialog.stkgui @@ -0,0 +1,46 @@ + + +
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + + + + + + + + +
+
diff --git a/data/gui/online/waiting_for_others.stkgui b/data/gui/online/waiting_for_others.stkgui deleted file mode 100644 index 5af100c2a..000000000 --- a/data/gui/online/waiting_for_others.stkgui +++ /dev/null @@ -1,11 +0,0 @@ - - -
- -
- - - -
-
diff --git a/data/gui/options_ui.stkgui b/data/gui/options_ui.stkgui index 737104b0d..61e253d67 100644 --- a/data/gui/options_ui.stkgui +++ b/data/gui/options_ui.stkgui @@ -80,6 +80,16 @@
+ + +
+
+ + +
+
+ diff --git a/data/gui/tracks.stkgui b/data/gui/tracks.stkgui index 37fd871b6..5ad274a77 100644 --- a/data/gui/tracks.stkgui +++ b/data/gui/tracks.stkgui @@ -2,7 +2,7 @@ -
+
@@ -17,5 +17,32 @@ + +
+
+ +
+
+
+
+
+
+ + +
diff --git a/sources.cmake b/sources.cmake index 3e53f13e9..d4f28ae4d 100644 --- a/sources.cmake +++ b/sources.cmake @@ -1,5 +1,5 @@ # Modify this file to change the last-modified date when you add/remove a file. -# This will then trigger a new cmake run automatically. +# This will then trigger a new cmake run automatically. file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp") file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp") file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*") diff --git a/src/config/user_config.cpp b/src/config/user_config.cpp index 788efc0dc..5c53ee2d9 100644 --- a/src/config/user_config.cpp +++ b/src/config/user_config.cpp @@ -181,146 +181,6 @@ void GroupUserConfigParam::addChild(UserConfigParam* child) m_attributes.push_back(child); } // addChild - -// ============================================================================ -template -ListUserConfigParam::ListUserConfigParam(const char* param_name, - const char* comment) -{ - m_param_name = param_name; - all_params.push_back(this); - if(comment != NULL) m_comment = comment; -} // ListUserConfigParam - -// ============================================================================ -template -ListUserConfigParam::ListUserConfigParam(const char* param_name, - const char* comment, - int nb_elements, - ...) -{ - m_param_name = param_name; - all_params.push_back(this); - if(comment != NULL) m_comment = comment; - - // add the default list - va_list arguments; - va_start ( arguments, nb_elements ); - for ( int i = 0; i < nb_elements; i++ ) - m_elements.push_back(T(va_arg ( arguments, U ))); - va_end ( arguments ); // Cleans up the list -} // ListUserConfigParam - -// ============================================================================ -template -ListUserConfigParam::ListUserConfigParam(const char* param_name, - GroupUserConfigParam* group, - const char* comment) -{ - m_param_name = param_name; - group->addChild(this); - if(comment != NULL) m_comment = comment; -} // ListUserConfigParam - -// ============================================================================ -template -ListUserConfigParam::ListUserConfigParam(const char* param_name, - GroupUserConfigParam* group, - const char* comment, - int nb_elements, - ...) -{ - m_param_name = param_name; - group->addChild(this); - if(comment != NULL) m_comment = comment; - - // add the default list - va_list arguments; - va_start ( arguments, nb_elements ); - for ( int i = 0; i < nb_elements; i++ ) - m_elements.push_back(va_arg ( arguments, T )); - va_end ( arguments ); // Cleans up the list -} // ListUserConfigParam - -// ---------------------------------------------------------------------------- -template -void ListUserConfigParam::write(std::ofstream& stream) const -{ - const int elts_amount = (int)m_elements.size(); - - // comment - if(m_comment.size() > 0) stream << " \n <" << m_param_name.c_str() << "\n"; - - stream << " Size=\"" << elts_amount << "\"\n"; - // actual elements - for (int n=0; n\n"; - stream << " \n\n"; -} // write - -// ---------------------------------------------------------------------------- - -template -void ListUserConfigParam::findYourDataInAChildOf(const XMLNode* node) -{ - const XMLNode* child = node->getNode( m_param_name ); - if (child == NULL) - { - //Log::error("User Config", "Couldn't find parameter group %s", m_param_name.c_str()); - return; - } - - int attr_count = 0; - child->get( "Size", &attr_count); - for (int n=0; nget( StringUtils::toString(n), &str); - StringUtils::fromString(str, elt); - - // check if the element is already there : - bool there = false; - for (unsigned int i = 0; i < m_elements.size(); i++) - { - if (elt == m_elements[i]) - { - there = true; - break; - } - } - if (!there) - { - m_elements.push_back(elt); - } - } - -} // findYourDataInAChildOf - -// ---------------------------------------------------------------------------- -template -void ListUserConfigParam::findYourDataInAnAttributeOf(const XMLNode* node) -{ -} // findYourDataInAnAttributeOf - -// ---------------------------------------------------------------------------- -template -void ListUserConfigParam::addElement(T element) -{ - m_elements.push_back(element); -} // findYourDataInAnAttributeOf - -// ---------------------------------------------------------------------------- -template -core::stringc ListUserConfigParam::toString() const -{ - return ""; -} // toString - // ---------------------------------------------------------------------------- template MapUserConfigParam::MapUserConfigParam(const char* param_name, @@ -334,26 +194,12 @@ MapUserConfigParam::MapUserConfigParam(const char* param_name, // ---------------------------------------------------------------------------- template MapUserConfigParam::MapUserConfigParam(const char* param_name, - const char* comment, - int nb_elements, - ...) + const char* comment, std::map default_value) { m_param_name = param_name; all_params.push_back(this); if (comment != NULL) m_comment = comment; - - // add the default list - va_list arguments; - va_start(arguments, nb_elements); - - struct pair_type { T key; U value; }; - - for (int i = 0; i < nb_elements; i++) - { - pair_type key_value_pair = va_arg(arguments, pair_type); - m_elements.insert(std::pair(key_value_pair.key, key_value_pair.value)); - } - va_end(arguments); // Cleans up the list + m_elements = default_value; } // MapUserConfigParam // ---------------------------------------------------------------------------- @@ -370,27 +216,14 @@ MapUserConfigParam::MapUserConfigParam(const char* param_name, // ---------------------------------------------------------------------------- template MapUserConfigParam::MapUserConfigParam(const char* param_name, - GroupUserConfigParam* group, - const char* comment, - int nb_elements, - ...) + GroupUserConfigParam* group, const char* comment, + std::map default_value) { m_param_name = param_name; group->addChild(this); if (comment != NULL) m_comment = comment; - // add the default list - va_list arguments; - va_start(arguments, nb_elements); - - struct pair_type { T key; U value; }; - - for (int i = 0; i < nb_elements; i++) - { - pair_type key_value_pair = va_arg(arguments, pair_type); - m_elements.insert(std::pair(key_value_pair.key, key_value_pair.value)); - } - va_end(arguments); // Cleans up the list + m_elements = default_value; } // MapUserConfigParam // ---------------------------------------------------------------------------- @@ -401,7 +234,7 @@ void MapUserConfigParam::write(std::ofstream& stream) const if (m_comment.size() > 0) stream << " \n <" << m_param_name.c_str() << "\n"; - for (const auto& kv : m_elements) + for (const auto& kv : m_elements) { stream << " " << kv.first << "=\"" << kv.second << "\"\n"; } @@ -410,7 +243,6 @@ void MapUserConfigParam::write(std::ofstream& stream) const } // write // ---------------------------------------------------------------------------- - template void MapUserConfigParam::findYourDataInAChildOf(const XMLNode* node) { diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index f2a256a26..22d65a878 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -37,6 +37,7 @@ cause an undefined game action now 6: Added stick configurations. */ +#include #include #include #include @@ -98,44 +99,6 @@ public: }; // GroupUserConfigParam // ============================================================================ -template -class ListUserConfigParam : public UserConfigParam -{ - std::vector m_elements; - -public: - ListUserConfigParam(const char* param_name, - const char* comment = NULL); - ListUserConfigParam(const char* param_name, - const char* comment, - int nb_elts, - ...); - ListUserConfigParam(const char* param_name, - GroupUserConfigParam* group, - const char* comment = NULL); - ListUserConfigParam(const char* param_name, - GroupUserConfigParam* group, - const char* comment, - int nb_elts, - ...); - - void write(std::ofstream& stream) const; - void findYourDataInAChildOf(const XMLNode* node); - void findYourDataInAnAttributeOf(const XMLNode* node); - - void addElement(T element); - - irr::core::stringc toString() const; - - operator std::vector() const - { return m_elements; } - float& operator=(const std::vector& v) - { m_elements = std::vector(v); return m_elements; } - float& operator=(const ListUserConfigParam& v) - { m_elements = std::vector(v); return m_elements; } -}; // ListUserConfigParam -typedef ListUserConfigParam StringListUserConfigParam; - template class MapUserConfigParam : public UserConfigParam { @@ -146,16 +109,14 @@ public: const char* comment = NULL); MapUserConfigParam(const char* param_name, const char* comment, - int nb_elts, - ...); + std::map default_value); MapUserConfigParam(const char* param_name, GroupUserConfigParam* group, const char* comment = NULL); MapUserConfigParam(const char* param_name, GroupUserConfigParam* group, const char* comment, - int nb_elts, - ...); + std::map default_value); void write(std::ofstream& stream) const; void findYourDataInAChildOf(const XMLNode* node); @@ -165,10 +126,18 @@ public: irr::core::stringc toString() const; - operator std::map() const + operator std::map() const { return m_elements; } + typename std::map::iterator begin() + { + return m_elements.begin(); + } + typename std::map::iterator end() + { + return m_elements.end(); + } std::map& operator=(const std::map& v) { m_elements = std::map(v); @@ -183,8 +152,9 @@ public: { return m_elements[key]; } -}; // ListUserConfigParam -typedef MapUserConfigParam IntToIntUserConfigParam; +}; // MapUserConfigParam +typedef MapUserConfigParam UIntToUIntUserConfigParam; +typedef MapUserConfigParam StringToUIntUserConfigParam; // ============================================================================ class IntUserConfigParam : public UserConfigParam { @@ -708,38 +678,25 @@ namespace UserConfigParams * can store. */ PARAM_PREFIX float m_profiler_buffer_duration PARAM_DEFAULT(20.0f); - // not saved to file - // ---- Networking - - PARAM_PREFIX IntUserConfigParam m_server_max_players - PARAM_DEFAULT( IntUserConfigParam(12, "server_max_players", - "Maximum number of players on the server.") ); - - PARAM_PREFIX StringListUserConfigParam m_stun_servers - PARAM_DEFAULT( StringListUserConfigParam("Stun_servers", "The stun servers" - " that will be used to know the public address.", - 10, - "stun.cope.es", - "stun.12connect.com", - "stun.callwithus.com", - "stun.counterpath.net", - "stun.ekiga.net", - "stun.schlund.de", - "stun.stunprotocol.org", - "stun.voip.aebc.com", - "numb.viagenie.ca", - "stun.ivao.aero") ); - - // ---- Gamemode setup - PARAM_PREFIX IntToIntUserConfigParam m_num_karts_per_gamemode - PARAM_DEFAULT(IntToIntUserConfigParam("num_karts_per_gamemode", - "The Number of karts per gamemode.", - 1, - std::make_pair(1100, 4) + PARAM_PREFIX StringToUIntUserConfigParam m_stun_list + PARAM_DEFAULT(StringToUIntUserConfigParam("stun_list", + "The stun servers that will be used to know the public address," + " LHS: server address, RHS: ping time.", + { + { "numb.viagenie.ca", 0u }, + { "stun.12connect.com", 0u }, + { "stun.callwithus.com", 0u }, + { "stun.cope.es", 0u }, + { "stun.counterpath.net", 0u }, + { "stun.ekiga.net", 0u }, + { "stun.ivao.aero", 0u }, + { "stun.schlund.de", 0u }, + { "stun.stunprotocol.org", 0u }, + { "stun.voip.aebc.com", 0u } + } )); - // ---- Network PARAM_PREFIX GroupUserConfigParam m_network_group PARAM_DEFAULT(GroupUserConfigParam("Network", "Network Settings")); PARAM_PREFIX BoolUserConfigParam m_log_packets @@ -748,24 +705,43 @@ namespace UserConfigParams PARAM_PREFIX BoolUserConfigParam m_random_ports PARAM_DEFAULT(BoolUserConfigParam(true, "randrom-ports", &m_network_group, "Use random ports for client and server connection")); + PARAM_PREFIX BoolUserConfigParam m_lobby_chat + PARAM_DEFAULT(BoolUserConfigParam(false, "lobby-chat", + &m_network_group, "Enable chatting in networking lobby, if off than " + "no chat message will be displayed from any players.")); + PARAM_PREFIX FloatUserConfigParam m_voting_timeout + PARAM_DEFAULT(FloatUserConfigParam(10.0f, "voting-timeout", + &m_network_group, "Timeout in seconds for voting tracks in server.")); + PARAM_PREFIX IntUserConfigParam m_server_max_players + PARAM_DEFAULT(IntUserConfigParam(12, "server_max_players", + &m_network_group, "Maximum number of players on the server.")); + + PARAM_PREFIX StringToUIntUserConfigParam m_server_ban_list + PARAM_DEFAULT(StringToUIntUserConfigParam("server_ban_list", + "LHS: IP in x.x.x.x format, RHS: online id, if 0 than all players " + "from this IP will be banned.", + { { "0.0.0.0", 0u } } + )); + + // ---- Gamemode setup + PARAM_PREFIX UIntToUIntUserConfigParam m_num_karts_per_gamemode + PARAM_DEFAULT(UIntToUIntUserConfigParam("num_karts_per_gamemode", + "The Number of karts per gamemode.", + { + { 0u, 4u }, + { 1002u, 5u }, + { 1100u, 4u }, + { 1101u, 4u }, + { 2000u, 4u }, + { 2001u, 4u } + } + )); // ---- Graphic Quality PARAM_PREFIX GroupUserConfigParam m_graphics_quality PARAM_DEFAULT( GroupUserConfigParam("GFX", "Graphics Quality Settings") ); - // On OSX 10.4 and before there may be driver issues with FBOs, so to be - // safe disable them by default -#ifdef __APPLE__ - #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - #define FBO_DEFAULT false - #else - #define FBO_DEFAULT true - #endif -#else -#define FBO_DEFAULT true -#endif - PARAM_PREFIX IntUserConfigParam m_particles_effects PARAM_DEFAULT( IntUserConfigParam(2, "particles-effecs", &m_graphics_quality, "Particles effects: 0 disabled, 1 only important, 2 enabled") ); diff --git a/src/guiengine/event_handler.cpp b/src/guiengine/event_handler.cpp index f1bc22d52..07f8671ba 100644 --- a/src/guiengine/event_handler.cpp +++ b/src/guiengine/event_handler.cpp @@ -375,7 +375,10 @@ void EventHandler::processGUIAction(const PlayerAction action, case PA_RESCUE: case PA_MENU_CANCEL: - if (pressedDown) GUIEngine::getStateManager()->escapePressed(); + if (pressedDown&& !isWithinATextBox()) + { + GUIEngine::getStateManager()->escapePressed(); + } break; case PA_FIRE: @@ -813,7 +816,7 @@ EventPropagation EventHandler::onGUIEvent(const SEvent& event) const int playerID = input_manager->getPlayerKeyboardID(); if (input_manager->masterPlayerOnly() && playerID != PLAYER_ID_GAME_MASTER) break; - if (!w->isFocusedForPlayer(playerID)) w->setFocusForPlayer(playerID); + if (playerID != -1 && !w->isFocusedForPlayer(playerID)) w->setFocusForPlayer(playerID); break; } diff --git a/src/guiengine/screen.hpp b/src/guiengine/screen.hpp index 3df7fc712..b4334b0ef 100644 --- a/src/guiengine/screen.hpp +++ b/src/guiengine/screen.hpp @@ -48,14 +48,9 @@ using namespace irr; */ namespace GUIEngine { -#define DEFINE_SCREEN_SINGLETON( ClassName ) \ - template<> ClassName* GUIEngine::ScreenSingleton< ClassName >::singleton = NULL - /** * \brief Declares a class to be a singleton. * Normally, all screens will be singletons. - * Note that you need to use the 'DEFINE_SCREEN_SINGLETON' macro in a . - * cpp file to actually define the instance (as this can't be done in a .h) * \ingroup guiengine */ template @@ -83,6 +78,8 @@ namespace GUIEngine } }; + template SCREEN* + ScreenSingleton::singleton = nullptr; /** * \brief Represents a single GUI screen. diff --git a/src/guiengine/skin.cpp b/src/guiengine/skin.cpp index ef90cfc1f..769c7114d 100644 --- a/src/guiengine/skin.cpp +++ b/src/guiengine/skin.cpp @@ -1675,7 +1675,7 @@ void Skin::renderSections(PtrVector* within_vector) if (widget.m_type == WTYPE_DIV) { - if (widget.m_show_bounding_box) + if (widget.m_show_bounding_box && widget.isVisible()) { if (widget.m_is_bounding_box_round) { diff --git a/src/guiengine/widget.cpp b/src/guiengine/widget.cpp index db1956f56..c2c009603 100644 --- a/src/guiengine/widget.cpp +++ b/src/guiengine/widget.cpp @@ -309,7 +309,9 @@ bool Widget::isVisible() const { if (m_element != NULL) { - assert(m_element->isVisible() == m_is_visible); + // repair mismatch + if (m_element->isVisible() != m_is_visible) + m_element->setVisible(m_is_visible); } return m_is_visible; } diff --git a/src/guiengine/widget.hpp b/src/guiengine/widget.hpp index 7c67cc2ad..4aae5f2f4 100644 --- a/src/guiengine/widget.hpp +++ b/src/guiengine/widget.hpp @@ -659,6 +659,7 @@ namespace GUIEngine /** Gets called when the widget is active and got clicked. (Only works for button widgets for now.) */ virtual void onClick() { } + virtual irr::core::dimension2di getDimension() const { return irr::core::dimension2di(m_w, m_h); } }; diff --git a/src/guiengine/widgets/CGUIEditBox.cpp b/src/guiengine/widgets/CGUIEditBox.cpp index 15f78603b..d4f06013a 100644 --- a/src/guiengine/widgets/CGUIEditBox.cpp +++ b/src/guiengine/widgets/CGUIEditBox.cpp @@ -823,6 +823,9 @@ bool CGUIEditBox::processKey(const SEvent& event) calculateScrollPos(); + if (CursorPos > (s32)Text.size()) + CursorPos = Text.size(); + #if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) switch(event.KeyInput.Key) { diff --git a/src/guiengine/widgets/check_box_widget.cpp b/src/guiengine/widgets/check_box_widget.cpp index cd0307f3d..d4275f055 100644 --- a/src/guiengine/widgets/check_box_widget.cpp +++ b/src/guiengine/widgets/check_box_widget.cpp @@ -44,6 +44,9 @@ void CheckBoxWidget::add() m_id = m_element->getID(); m_element->setTabOrder(m_id); m_element->setTabGroup(false); + + if (!m_is_visible) + m_element->setVisible(false); } // ----------------------------------------------------------------------------- EventPropagation CheckBoxWidget::transmitEvent(Widget* w, diff --git a/src/guiengine/widgets/label_widget.cpp b/src/guiengine/widgets/label_widget.cpp index a48961408..4eb56313d 100644 --- a/src/guiengine/widgets/label_widget.cpp +++ b/src/guiengine/widgets/label_widget.cpp @@ -110,6 +110,9 @@ void LabelWidget::add() if (m_scroll_speed <= 0) m_element->setNotClipped(true); + + if (!m_is_visible) + m_element->setVisible(false); } // add // ---------------------------------------------------------------------------- diff --git a/src/guiengine/widgets/player_kart_widget.hpp b/src/guiengine/widgets/player_kart_widget.hpp index 66474c857..073354da4 100644 --- a/src/guiengine/widgets/player_kart_widget.hpp +++ b/src/guiengine/widgets/player_kart_widget.hpp @@ -108,7 +108,9 @@ namespace GUIEngine // ------------------------------------------------------------------------ /** Called when players are renumbered (changes the player ID) */ void setPlayerID(const int newPlayerID); - + // ------------------------------------------------------------------------ + PlayerNameSpinner* getPlayerNameSpinner() const + { return m_player_ident_spinner; } // ------------------------------------------------------------------------ /** Returns the ID of this player */ int getPlayerID() const; diff --git a/src/guiengine/widgets/text_box_widget.cpp b/src/guiengine/widgets/text_box_widget.cpp index 9b5684488..02d32c4ea 100644 --- a/src/guiengine/widgets/text_box_widget.cpp +++ b/src/guiengine/widgets/text_box_widget.cpp @@ -63,7 +63,17 @@ public: m_listeners[n].onTextUpdated(); } } - + if (event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.Key == IRR_KEY_RETURN) + { + for (unsigned int n=0; ngetAssignMode() == DETECT_NEW) { + if (NetworkConfig::get()->isNetworking() && + NetworkConfig::get()->isAddingNetworkPlayers()) + { + // Ignore release event + if (value == 0) + return; + InputDevice *device = NULL; + if (type == Input::IT_KEYBOARD) + { + //Log::info("InputManager", "New Player Joining with Key %d", button); + device = m_device_manager->getKeyboardFromBtnID(button); + } + else if (type == Input::IT_STICKBUTTON || + type == Input::IT_STICKMOTION ) + { + device = m_device_manager->getGamePadFromIrrID(deviceID); + } + if (device && (action == PA_FIRE || action == PA_MENU_SELECT)) + { + if (!GUIEngine::ModalDialog::isADialogActive()) + new SplitscreenPlayerDialog(device); + return; + } + } + // Player is unjoining if ((player != NULL) && (action == PA_RESCUE || action == PA_MENU_CANCEL ) ) @@ -749,7 +776,7 @@ void InputManager::dispatchInput(Input::InputType type, int deviceID, if (device != NULL) { - KartSelectionScreen::getRunningInstance()->joinPlayer(device); + KartSelectionScreen::getRunningInstance()->joinPlayer(device, NULL/*player profile*/); } } return; // we're done here, ignore devices that aren't diff --git a/src/main.cpp b/src/main.cpp index 16aab420b..e0924ebae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1043,10 +1043,11 @@ int handleCmdLine() if(CommandLine::has("--network-console")) STKHost::m_enable_console = true; + std::string server_password; if (CommandLine::has("--server-password", &s)) { - core::stringw pw = StringUtils::xmlDecode(s); - NetworkConfig::get()->setPassword(StringUtils::wideToUtf8(pw)); + server_password = s; + NetworkConfig::get()->setPassword(server_password); } if (CommandLine::has("--server-id-file", &s)) @@ -1082,7 +1083,7 @@ int handleCmdLine() NetworkConfig::get()->getMaxPlayers(), 0, race_manager->getDifficulty(), NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(), - race_manager->getMajorMode()), ip); + race_manager->getMajorMode()), ip, !server_password.empty()); NetworkingLobby::getInstance()->setJoinedServer(server); STKHost::create(server); } diff --git a/src/main_loop.cpp b/src/main_loop.cpp index f505bbec0..bce8634db 100644 --- a/src/main_loop.cpp +++ b/src/main_loop.cpp @@ -41,10 +41,13 @@ #include "states_screens/state_manager.hpp" #include "utils/profiler.hpp" -#include +#ifndef WIN32 +#include +#endif MainLoop* main_loop = 0; +// ---------------------------------------------------------------------------- MainLoop::MainLoop(unsigned parent_pid) : m_abort(false), m_parent_pid(parent_pid) { @@ -314,6 +317,10 @@ void MainLoop::run() if (WaitForSingleObject(parent, 0) != WAIT_TIMEOUT) m_abort = true; } +#else + // POSIX equivalent + if (m_parent_pid != 0 && getppid() != (int)m_parent_pid) + m_abort = true; #endif m_is_last_substep = false; PROFILER_PUSH_CPU_MARKER("Main loop", 0xFF, 0x00, 0xF7); @@ -327,7 +334,7 @@ void MainLoop::run() STKHost::get()->requestedShutdown()) { SFXManager::get()->quickSound("anvil"); - core::stringw msg = _("Connection to server is lost."); + core::stringw msg = _("Server connection timed out."); if (!STKHost::get()->getErrorMessage().empty()) { msg = STKHost::get()->getErrorMessage(); diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 168cdf59f..f07255a1d 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -341,6 +341,13 @@ AbstractKart *World::createKart(const std::string &kart_ident, int index, gk = ReplayPlay::get()->getNumGhostKart(); std::shared_ptr ri = std::make_shared(); + if (global_player_id > -1 && race_manager->getKartInfo(global_player_id) + .getDefaultKartColor() > 0.0f) + { + ri->setHue(race_manager->getKartInfo(global_player_id) + .getDefaultKartColor()); + } + int position = index+1; btTransform init_pos = getStartTransform(index - gk); AbstractKart *new_kart; @@ -357,15 +364,12 @@ AbstractKart *World::createKart(const std::string &kart_ident, int index, { case RaceManager::KT_PLAYER: { - controller = new LocalPlayerController(new_kart, - - local_player_id); - - const float hue = StateManager::get()->getActivePlayer(local_player_id) - ->getConstProfile()->getDefaultKartColor(); - if (hue > 0.0f) + controller = new LocalPlayerController(new_kart, local_player_id); + const PlayerProfile* p = StateManager::get() + ->getActivePlayer(local_player_id)->getConstProfile(); + if (p && p->getDefaultKartColor() > 0.0f) { - ri->setHue(hue); + ri->setHue(p->getDefaultKartColor()); } m_num_players ++; diff --git a/src/modes/world_status.cpp b/src/modes/world_status.cpp index eabd893cf..481f5a1aa 100644 --- a/src/modes/world_status.cpp +++ b/src/modes/world_status.cpp @@ -48,7 +48,7 @@ WorldStatus::WorldStatus() m_play_track_intro_sound = UserConfigParams::m_music; m_play_ready_set_go_sounds = true; m_play_racestart_sounds = true; - m_server_is_ready = false; + m_server_is_ready.store(false); IrrlichtDevice *device = irr_driver->getDevice(); @@ -99,7 +99,7 @@ void WorldStatus::reset() // In case of a networked race the race can only start once // all protocols are up. This flag is used to wait for // a confirmation before starting the actual race. - m_server_is_ready = false; + m_server_is_ready.store(false); } // reset //----------------------------------------------------------------------------- @@ -262,7 +262,7 @@ void WorldStatus::updateTime(int ticks) // loaded the world). The server waits for a confirmation from // each client that they have started (to guarantee that the // server is running with a local time behind all clients). - if (!m_server_is_ready) return; + if (m_server_is_ready.load() == false) return; m_phase = READY_PHASE; auto cl = LobbyProtocol::get(); @@ -484,7 +484,7 @@ float WorldStatus::adjustDT(float dt) */ void WorldStatus::startReadySetGo() { - m_server_is_ready = true; + m_server_is_ready.store(true); } // startReadySetGo //----------------------------------------------------------------------------- diff --git a/src/modes/world_status.hpp b/src/modes/world_status.hpp index 16f4776bf..eb790f209 100644 --- a/src/modes/world_status.hpp +++ b/src/modes/world_status.hpp @@ -19,6 +19,7 @@ #define HEADER_WORLD_STATUS_HPP #include "utils/cpp2011.hpp" +#include class SFXBase; @@ -143,7 +144,7 @@ private: * set go' to make sure all client are actually ready to start the game. * A server on the other hand will run behind all clients, so it will * wait for all clients to indicate that they have started the race. */ - bool m_server_is_ready; + std::atomic_bool m_server_is_ready; void startEngines(); @@ -216,7 +217,7 @@ public: /** Get the ticks since start regardless of which way the clock counts */ int getTicksSinceStart() const { return m_count_up_ticks; } // ------------------------------------------------------------------------ - void setReadyToRace() { m_server_is_ready = true; } + void setReadyToRace() { m_server_is_ready.store(true); } // ------------------------------------------------------------------------ /** Sets a time by which the clock should be adjusted. Used by networking * if too many rewinds are detected. */ diff --git a/src/network/event.cpp b/src/network/event.cpp index 33803cb0a..7ad8cf3bd 100644 --- a/src/network/event.cpp +++ b/src/network/event.cpp @@ -18,7 +18,6 @@ #include "network/event.hpp" -#include "network/stk_host.hpp" #include "network/stk_peer.hpp" #include "utils/log.hpp" #include "utils/time.hpp" @@ -28,9 +27,10 @@ /** \brief Constructor * \param event : The event that needs to be translated. */ -Event::Event(ENetEvent* event) +Event::Event(ENetEvent* event, std::shared_ptr peer) { m_arrival_time = (double)StkTime::getTimeSinceEpoch(); + m_pdi = PDI_TIMEOUT; switch (event->type) { @@ -39,6 +39,7 @@ Event::Event(ENetEvent* event) break; case ENET_EVENT_TYPE_DISCONNECT: m_type = EVENT_TYPE_DISCONNECTED; + m_pdi = (PeerDisconnectInfo)event->data; break; case ENET_EVENT_TYPE_RECEIVE: m_type = EVENT_TYPE_MESSAGE; @@ -61,16 +62,7 @@ Event::Event(ENetEvent* event) enet_packet_destroy(event->packet); } - m_peer = STKHost::get()->getPeer(event->peer); - if(m_type == EVENT_TYPE_MESSAGE && m_peer->isClientServerTokenSet() && - m_data->getToken()!=m_peer->getClientServerToken() ) - { - Log::error("Event", "Received event with invalid token!"); - Log::error("Event", "HostID %d Token %d message token %d", - m_peer->getHostId(), m_peer->getClientServerToken(), - m_data->getToken()); - Log::error("Event", m_data->getLogMessage().c_str()); - } + m_peer = peer; } // Event(ENetEvent) // ---------------------------------------------------------------------------- diff --git a/src/network/event.hpp b/src/network/event.hpp index f044c636f..0b1d5be5c 100644 --- a/src/network/event.hpp +++ b/src/network/event.hpp @@ -30,6 +30,8 @@ #include "enet/enet.h" +#include + class STKPeer; /*! @@ -42,6 +44,7 @@ enum EVENT_TYPE EVENT_TYPE_DISCONNECTED,//!< A peer is disconnected EVENT_TYPE_MESSAGE //!< A message between server and client protocols }; +enum PeerDisconnectInfo : unsigned int; /*! * \class Event @@ -65,22 +68,27 @@ private: EVENT_TYPE m_type; /** Pointer to the peer that triggered that event. */ - STKPeer* m_peer; + std::shared_ptr m_peer; /** Arrivial time of the event, for timeouts. */ double m_arrival_time; + /** For disconnection event, a bit more info is provided. */ + PeerDisconnectInfo m_pdi; + public: - Event(ENetEvent* event); + Event(ENetEvent* event, std::shared_ptr peer); ~Event(); // ------------------------------------------------------------------------ /** Returns the type of this event. */ EVENT_TYPE getType() const { return m_type; } - + // ------------------------------------------------------------------------ + /** Returns the peer of this event (shared pointer). */ + std::shared_ptr getPeerSP() const { return m_peer; } // ------------------------------------------------------------------------ /** Returns the peer of this event. */ - STKPeer* getPeer() const { return m_peer; } + STKPeer* getPeer() const { return m_peer.get(); } // ------------------------------------------------------------------------ /** \brief Get a const reference to the received data. * This is empty for events like connection or disconnections. @@ -99,7 +107,8 @@ public: // ------------------------------------------------------------------------ /** Returns the arrival time of this event. */ double getArrivalTime() const { return m_arrival_time; } - + // ------------------------------------------------------------------------ + PeerDisconnectInfo getPeerDisconnectInfo() const { return m_pdi; } // ------------------------------------------------------------------------ }; // class Event diff --git a/src/network/game_setup.cpp b/src/network/game_setup.cpp index d05704607..84327cc71 100644 --- a/src/network/game_setup.cpp +++ b/src/network/game_setup.cpp @@ -18,6 +18,7 @@ #include "network/game_setup.hpp" +#include "config/player_manager.hpp" #include "karts/abstract_kart.hpp" #include "modes/world.hpp" #include "network/network_player_profile.hpp" @@ -26,202 +27,43 @@ #include "utils/log.hpp" //----------------------------------------------------------------------------- - GameSetup::GameSetup() { - m_race_config = new RaceConfig(); m_num_local_players = 0; m_local_master = 0; + m_laps = 0; + m_reverse = false; } // GameSetup //----------------------------------------------------------------------------- - -GameSetup::~GameSetup() -{ - // remove all players - for (unsigned int i = 0; i < m_players.size(); i++) - { - delete m_players[i]; - }; - m_players.clear(); - delete m_race_config; -} // ~GameSetup - -//----------------------------------------------------------------------------- - -void GameSetup::addPlayer(NetworkPlayerProfile* profile) -{ - m_players.push_back(profile); - Log::info("GameSetup", "New player in the game setup. Player id : %d.", - profile->getGlobalPlayerId()); -} // addPlayer - -//----------------------------------------------------------------------------- -/** Removed a player give his NetworkPlayerProfile. - * \param profile The NetworkPlayerProfile to remove. - * \return True if the player was found and removed, false otherwise. +/** Update and see if any player disconnects. + * \param remove_disconnected_players remove the disconnected players, + * otherwise replace with AI (when racing), so this function must be called + * in main thread. */ -bool GameSetup::removePlayer(const NetworkPlayerProfile *profile) +void GameSetup::update(bool remove_disconnected_players) { - for (unsigned int i = 0; i < m_players.size(); i++) + std::unique_lock lock(m_players_mutex); + if (remove_disconnected_players) { - if (m_players[i] == profile) - { - delete m_players[i]; - m_players.erase(m_players.begin()+i, m_players.begin()+i+1); - Log::verbose("GameSetup", - "Removed a player from the game setup. Remains %u.", - m_players.size()); - return true; - } + m_players.erase(std::remove_if(m_players.begin(), m_players.end(), [] + (const std::weak_ptr npp)->bool + { + return npp.expired(); + }), m_players.end()); + return; } - return false; + lock.unlock(); + if (!World::getWorld()) + return; } // removePlayer //----------------------------------------------------------------------------- -/** Sets the player id of the local master. - * \param player_id The id of the player who is the local master. - */ -void GameSetup::setLocalMaster(uint8_t player_id) +void GameSetup::loadWorld() { - m_local_master = player_id; -} // setLocalMaster - -//----------------------------------------------------------------------------- -/** Returns true if the player id is the local game master (used in the - * network game selection. - * \param Local player id to test. - */ -bool GameSetup::isLocalMaster(uint8_t player_id) -{ - return m_local_master == player_id; -} // isLocalMaster - -//----------------------------------------------------------------------------- -/** Sets the kart the specified player uses. - * \param player_id ID of this player (in this race). - * \param kart_name Name of the kart the player picked. - */ -void GameSetup::setPlayerKart(uint8_t player_id, const std::string &kart_name) -{ - bool found = false; - for (unsigned int i = 0; i < m_players.size(); i++) - { - if (m_players[i]->getGlobalPlayerId() == player_id) - { - m_players[i]->setKartName(kart_name); - Log::info("GameSetup::setPlayerKart", "Player %d took kart %s", - player_id, kart_name.c_str()); - found = true; - } - } - if (!found) - { - Log::info("GameSetup::setPlayerKart", "The player %d was unknown.", - player_id); - } -} // setPlayerKart - -//----------------------------------------------------------------------------- - -void GameSetup::bindKartsToProfiles() -{ - World::KartList karts = World::getWorld()->getKarts(); - - for (unsigned int i = 0; i < m_players.size(); i++) - { - Log::info("GameSetup", "Player %d has id %d and kart %s", i, - m_players[i]->getGlobalPlayerId(), - m_players[i]->getKartName().c_str()); - } - for (unsigned int i = 0; i < karts.size(); i++) - { - Log::info("GameSetup", "Kart %d has id %d and kart %s", i, - karts[i]->getWorldKartId(), karts[i]->getIdent().c_str()); - } - for (unsigned int j = 0; j < m_players.size(); j++) - { - bool found = false; - for (unsigned int i = 0 ; i < karts.size(); i++) - { - if (karts[i]->getIdent() == m_players[j]->getKartName()) - { - m_players[j]->setWorldKartID(karts[i]->getWorldKartId()); - found = true; - break; - } - } - if (!found) - { - Log::error("GameSetup", "Error while binding world kart ids to players profiles."); - } - } -} // bindKartsToProfiles - -//----------------------------------------------------------------------------- -/** \brief Get a network player profile with the specified player id. - * \param player_id : Player id in this race. - * \return The profile of the player having the specified player id, or - * NULL if no such player exists. - */ -const NetworkPlayerProfile* GameSetup::getProfile(uint8_t player_id) -{ - for (unsigned int i = 0; i < m_players.size(); i++) - { - if (m_players[i]->getGlobalPlayerId()== player_id) - { - return m_players[i]; - } - } - return NULL; -} // getProfile - -//----------------------------------------------------------------------------- -/** \brief Get a network player profile matching a kart name. - * \param kart_name : Name of the kart used by the player. - * \return The profile of the player having the kart kart_name, or NULL - * if no such network profile exists. - */ - -const NetworkPlayerProfile* GameSetup::getProfile(const std::string &kart_name) -{ - for (unsigned int i = 0; i < m_players.size(); i++) - { - if (m_players[i]->getKartName() == kart_name) - { - return m_players[i]; - } - } - return NULL; -} // getProfile(kart_name) - -//----------------------------------------------------------------------------- -/** Returns the list of all player profiles from a specified host. Note that - * this function is somewhat expensive (it loops over all network profiles - * to find the ones with the specified host id). - * \param host_id The host id which players should be collected. - * \return List of NetworkPlayerProfile pointers/ - */ -std::vector GameSetup::getAllPlayersOnHost(uint8_t host_id) -{ - std::vector result; - - for (unsigned int i = 0; i < m_players.size(); i++) - { - if (m_players[i]->getHostId() == host_id) - result.push_back(m_players[i]); - } - return result; -} // getAllPlayersOnHost - -//----------------------------------------------------------------------------- - -bool GameSetup::isKartAvailable(std::string kart_name) -{ - for (unsigned int i = 0; i < m_players.size(); i++) - { - if (m_players[i]->getKartName() == kart_name) - return false; - } - return true; -} + assert(!m_track.empty()); + // Disable accidentally unlocking of a challenge + PlayerManager::getCurrentPlayer()->setCurrentChallenge(""); + race_manager->setReverseTrack(m_reverse); + race_manager->startSingleRace(m_track, m_laps, false/*from_overworld*/); +} // loadWorld diff --git a/src/network/game_setup.hpp b/src/network/game_setup.hpp index e75ed32a8..3a81b76e7 100644 --- a/src/network/game_setup.hpp +++ b/src/network/game_setup.hpp @@ -22,16 +22,15 @@ #ifndef GAME_SETUP_HPP #define GAME_SETUP_HPP -#include "network/race_config.hpp" #include "network/remote_kart_info.hpp" -#include +#include +#include #include +#include -namespace Online { class OnlineProfile; } class NetworkPlayerProfile; - // ============================================================================ /*! \class GameSetup * \brief Used to store the needed data about the players that join a game. @@ -40,11 +39,10 @@ class NetworkPlayerProfile; class GameSetup { private: - /** Information about all connected players. */ - std::vector m_players; + mutable std::mutex m_players_mutex; - /** The race configuration. */ - RaceConfig* m_race_config; + /** Information about all connected players. */ + std::vector > m_players; /** Stores the number of local players. */ int m_num_local_players; @@ -52,26 +50,23 @@ private: /** The player id of the local game master, used in * kart selection screen. */ uint8_t m_local_master; + + std::string m_track; + + unsigned m_laps; + + bool m_reverse; + public: - GameSetup(); - virtual ~GameSetup(); - - void addPlayer(NetworkPlayerProfile* profile); //!< Add a player. - bool removePlayer(const NetworkPlayerProfile *profile); - void setPlayerKart(uint8_t player_id, const std::string &kart_name); - void bindKartsToProfiles(); //!< Sets the right world_kart_id in profiles - void setLocalMaster(uint8_t player_id); - - bool isLocalMaster(uint8_t player_id); - const NetworkPlayerProfile* getProfile(uint8_t id); - const NetworkPlayerProfile* getProfile(const std::string &kart_name); - std::vector getAllPlayersOnHost(uint8_t host_id); - - /*! \brief Used to know if a kart is available. - * \param kart_name : Name of the kart to check. - * \return True if the kart hasn't been selected yet, false elseway. - */ - bool isKartAvailable(std::string kart_name); + // ------------------------------------------------------------------------ + GameSetup(); + // ------------------------------------------------------------------------ + ~GameSetup() {} + // ------------------------------------------------------------------------ + void addPlayer(std::shared_ptr profile) + { m_players.push_back(profile); } + // ------------------------------------------------------------------------ + void update(bool remove_disconnected_players); // ------------------------------------------------------------------------ /** Sets the number of local players. */ void setNumLocalPlayers(int n) { m_num_local_players = n; } @@ -79,29 +74,47 @@ public: /** Returns the nunber of local players. */ int getNumLocalPlayers() const { return m_num_local_players; } // ------------------------------------------------------------------------ - /*! \brief Used to know if a kart is playable. - * \param kart_name : Name of the kart to check. - * \return True if the kart is playable (standard kart). - * Currently this is always true as the kart selection screen shows - * only the standard karts. - */ - bool isKartAllowed(std::string kart_name) { return true; } - // ------------------------------------------------------------------------ - /** Returns the configuration for this race. */ - RaceConfig* getRaceConfig() { return m_race_config; } - // ------------------------------------------------------------------------ - /** \brief Get the players that are in the game + /** \brief Get the players that are / were in the game * \return A vector containing pointers on the players profiles. */ - const std::vector& getPlayers() const + const std::vector >& getPlayers() const { + std::lock_guard lock(m_players_mutex); return m_players; } // getPlayers // ------------------------------------------------------------------------ + /** \brief Get the players that are in the game + * \return A vector containing pointers on the players profiles. */ + std::vector > + getConnectedPlayers() const + { + std::lock_guard lock(m_players_mutex); + std::vector > players; + for (auto player_weak : m_players) + { + if (auto player_connected = player_weak.lock()) + players.push_back(player_connected); + } + return players; + } // getConnectedPlayers + // ------------------------------------------------------------------------ /** Returns the number of connected players. */ - int getPlayerCount() { return (int)m_players.size(); } + unsigned getPlayerCount() + { + std::lock_guard lock(m_players_mutex); + return (unsigned)m_players.size(); + } // ------------------------------------------------------------------------ /** Returns the id of the local master. */ int getLocalMasterID() const { return m_local_master; } + // ------------------------------------------------------------------------ + void setRace(const std::string& track, unsigned laps, bool reverse) + { + m_track = track; + m_laps = laps; + m_reverse = reverse; + } + // ------------------------------------------------------------------------ + void loadWorld(); }; #endif // GAME_SETUP_HPP diff --git a/src/network/network_config.cpp b/src/network/network_config.cpp index 400b54d59..aa33cfded 100644 --- a/src/network/network_config.cpp +++ b/src/network/network_config.cpp @@ -23,6 +23,7 @@ NetworkConfig *NetworkConfig::m_network_config = NULL; bool NetworkConfig::m_disable_lan = false; +const uint8_t NetworkConfig::m_server_version = 1; /** \class NetworkConfig * This class is the interface between STK and the online code, particularly @@ -42,6 +43,7 @@ NetworkConfig::NetworkConfig() m_auto_connect = false; m_is_server = false; m_is_public_server = false; + m_done_adding_network_players = false; m_max_players = 4; m_cur_user_id = 0; m_cur_user_token = ""; diff --git a/src/network/network_config.hpp b/src/network/network_config.hpp index c97c5f7d8..bcd97c5f8 100644 --- a/src/network/network_config.hpp +++ b/src/network/network_config.hpp @@ -26,12 +26,17 @@ #include "race/race_manager.hpp" #include "irrString.h" +#include +#include namespace Online { class XMLRequest; } +class InputDevice; +class PlayerProfile; + class NetworkConfig { private: @@ -70,12 +75,14 @@ private: uint16_t m_client_port; /** Maximum number of players on the server. */ - int m_max_players; + unsigned m_max_players; /** True if a client should connect to the first server it finds and * immediately start a race. */ bool m_auto_connect; + bool m_done_adding_network_players; + /** If this is a server, the server name. */ irr::core::stringw m_server_name; @@ -86,6 +93,9 @@ private: /** Used by client server to determine if the child server is created. */ std::string m_server_id_file; + std::vector > m_network_players; + NetworkConfig(); public: @@ -93,6 +103,9 @@ public: * WAN code to be used when connection client and server). */ static bool m_disable_lan; + /** Server version, will be advanced if there are protocol changes. */ + static const uint8_t m_server_version; + /** Singleton get, which creates this object if necessary. */ static NetworkConfig *get() { @@ -161,13 +174,54 @@ public: void setIsWAN() { m_network_type = NETWORK_WAN; } // ------------------------------------------------------------------------ /** Set that this is not a networked game. */ - void unsetNetworking() { m_network_type = NETWORK_NONE; } + void unsetNetworking() + { + m_network_type = NETWORK_NONE; + m_password = ""; + } + // ------------------------------------------------------------------------ + const std::vector >& + getNetworkPlayers() const { return m_network_players; } + // ------------------------------------------------------------------------ + bool isAddingNetworkPlayers() const + { return !m_done_adding_network_players; } + // ------------------------------------------------------------------------ + void doneAddingNetworkPlayers() { m_done_adding_network_players = true; } + // ------------------------------------------------------------------------ + bool addNetworkPlayer(InputDevice* device, PlayerProfile* profile, bool h) + { + for (auto& p : m_network_players) + { + if (std::get<0>(p) == device) + return false; + if (std::get<1>(p) == profile) + return false; + } + m_network_players.emplace_back(device, profile, h); + return true; + } + // ------------------------------------------------------------------------ + bool playerExists(PlayerProfile* profile) const + { + for (auto& p : m_network_players) + { + if (std::get<1>(p) == profile) + return true; + } + return false; + } + // ------------------------------------------------------------------------ + void cleanNetworkPlayers() + { + m_network_players.clear(); + m_done_adding_network_players = false; + } // ------------------------------------------------------------------------ /** Sets the maximum number of players for this server. */ - void setMaxPlayers(int n) { m_max_players = n; } + void setMaxPlayers(unsigned n) { m_max_players = n; } // ------------------------------------------------------------------------ /** Returns the maximum number of players for this server. */ - int getMaxPlayers() const { return m_max_players; } + unsigned getMaxPlayers() const { return m_max_players; } // ------------------------------------------------------------------------ /** Returns if this instance is a server. */ bool isServer() const { return m_is_server; } diff --git a/src/network/network_console.cpp b/src/network/network_console.cpp index 455d78c81..4612d1823 100644 --- a/src/network/network_console.cpp +++ b/src/network/network_console.cpp @@ -16,13 +16,12 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#include "config/user_config.hpp" #include "network/network_config.hpp" #include "network/network_player_profile.hpp" #include "network/stk_host.hpp" -#include "network/protocols/client_lobby.hpp" -#include "network/protocols/server_lobby.hpp" #include "network/stk_peer.hpp" -#include "utils/log.hpp" +#include "network/protocols/server_lobby.hpp" #include "utils/time.hpp" #include "utils/vs.hpp" #include "main_loop.hpp" @@ -32,110 +31,93 @@ namespace NetworkConsole { // ---------------------------------------------------------------------------- -void kickAllPlayers(STKHost* host) +void showHelp() { - const std::vector &peers = host->getPeers(); - for (unsigned int i = 0; i < peers.size(); i++) - { - peers[i]->disconnect(); - } -} // kickAllPlayers + std::cout << "Available command:" << std::endl; + std::cout << "help, Print this." << std::endl; + std::cout << "quit, Shut down the server." << std::endl; + std::cout << "kickall, Kick all players out of STKHost." << std::endl; + std::cout << "kick #, kick # peer of STKHost." << std::endl; + std::cout << "kickban #, kick and ban # peer of STKHost." << std::endl; + std::cout << "listpeers, List all peers with host ID and IP." << std::endl; + std::cout << "listban, List IP ban list of server." << std::endl; +} // showHelp // ---------------------------------------------------------------------------- void mainLoop(STKHost* host) { VS::setThreadName("NetworkConsole"); + showHelp(); std::string str = ""; while (!host->requestedShutdown()) { getline(std::cin, str); - if (str == "quit") + std::stringstream ss(str); + int number = -1; + ss >> str >> number; + if (str == "help") + { + showHelp(); + } + else if (str == "quit") { host->requestShutdown(); } - else if (str == "kickall" && NetworkConfig::get()->isServer()) + else if (str == "kickall") { - kickAllPlayers(host); + auto peers = host->getPeers(); + for (unsigned int i = 0; i < peers.size(); i++) + { + peers[i]->kick(); + } } - else if (str == "start" && NetworkConfig::get()->isServer()) + else if (str == "kick" && number != -1 && + NetworkConfig::get()->isServer()) { - auto sl = LobbyProtocol::get(); - sl->signalRaceStartToClients(); + std::shared_ptr peer = host->findPeerByHostId(number); + if (peer) + peer->kick(); + else + std::cout << "Unknown host id: " << number << std::endl; } - else if (str == "selection" && NetworkConfig::get()->isServer()) + else if (str == "kickban" && number != -1 && + NetworkConfig::get()->isServer()) { - auto sl = LobbyProtocol::get(); - sl->startSelection(); + std::shared_ptr peer = host->findPeerByHostId(number); + if (peer) + { + peer->kick(); + UserConfigParams::m_server_ban_list + [peer->getAddress().toString(false/*show_port*/)] = 0; + LobbyProtocol::get()->updateBanList(); + } + else + std::cout << "Unknown host id: " << number << std::endl; } - else if (str == "select" && NetworkConfig::get()->isClient()) + else if (str == "listpeers") { - std::string str2; - getline(std::cin, str2); - auto clrp = LobbyProtocol::get(); - std::vector players = - host->getMyPlayerProfiles(); - // For now send a vote for each local player - for(unsigned int i=0; igetPeers(); + if (peers.empty()) + std::cout << "No peers exist" << std::endl; + for (unsigned int i = 0; i < peers.size(); i++) { - clrp->requestKartSelection(players[i]->getGlobalPlayerId(), - str2); - } // for i in players + std::cout << peers[i]->getHostId() << ": " << + peers[i]->getAddress().toString() << std::endl; + } } - else if (str == "vote" && NetworkConfig::get()->isClient()) + else if (str == "listban") { - std::cout << "Vote for ? (track/laps/reversed/major/minor/race#) :"; - std::string str2; - getline(std::cin, str2); - auto clrp = LobbyProtocol::get(); - std::vector players = - host->getMyPlayerProfiles(); - if (str2 == "track") + for (auto& ban : UserConfigParams::m_server_ban_list) { - std::cin >> str2; - // For now send a vote for each local player - for(unsigned int i=0; ivoteTrack(i, str2); + if (ban.first == "0.0.0.0") + continue; + std::cout << "IP: " << ban.first << " online id: " << + ban.second << std::endl; } - else if (str2 == "laps") - { - int cnt; - std::cin >> cnt; - for(unsigned int i=0; ivoteLaps(i, cnt); - } - else if (str2 == "reversed") - { - bool cnt; - std::cin >> cnt; - for(unsigned int i=0; ivoteReversed(i, cnt); - } - else if (str2 == "major") - { - int cnt; - std::cin >> cnt; - for(unsigned int i=0; ivoteMajor(i, cnt); - } - else if (str2 == "minor") - { - int cnt; - std::cin >> cnt; - for(unsigned int i=0; ivoteMinor(i, cnt); - } - else if (str2 == "race#") - { - int cnt; - std::cin >> cnt; - for(unsigned int i=0; ivoteRaceCount(i, cnt); - } - std::cout << "\n"; } else { - Log::info("Console", "Unknown command '%s'.", str.c_str()); + std::cout << "Unknown command: " << str << std::endl; } } // while !stop main_loop->abort(); diff --git a/src/network/network_player_profile.cpp b/src/network/network_player_profile.cpp index 303aa56c2..b00045d07 100644 --- a/src/network/network_player_profile.cpp +++ b/src/network/network_player_profile.cpp @@ -17,33 +17,9 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "network/network_player_profile.hpp" - +#include "network/network_config.hpp" #include "network/stk_host.hpp" -#include "online/online_player_profile.hpp" -/** Constructor. - * \param global_player_id A unique number assigned from the server to this - * player (though it might not be the index in the peer list). - * \param name Name of this player. - * \param global_player_id Global id of this player. - * \param host_id The id of the host the player is connected from. - */ -NetworkPlayerProfile::NetworkPlayerProfile(const irr::core::stringw &name, - int global_player_id, - int host_id ) -{ - m_global_player_id = global_player_id; - m_host_id = host_id; - m_kart_name = ""; - m_world_kart_id = 0; - m_per_player_difficulty = PLAYER_DIFFICULTY_NORMAL; - m_player_name = name; -} // BetworkPlayerProfile - -// ---------------------------------------------------------------------------- -NetworkPlayerProfile::~NetworkPlayerProfile() -{ -} // ~NetworkPlayerProfile // ---------------------------------------------------------------------------- /** Returns true if this player is local, i.e. running on this computer. This * is done by comparing the host id of this player with the host id of this @@ -51,5 +27,7 @@ NetworkPlayerProfile::~NetworkPlayerProfile() */ bool NetworkPlayerProfile::isLocalPlayer() const { - return m_host_id == STKHost::get()->getMyHostId(); -} // isLocalPlayer \ No newline at end of file + // Server never has local player atm + return NetworkConfig::get()->isClient() && + m_host_id == STKHost::get()->getMyHostId(); +} // isLocalPlayer diff --git a/src/network/network_player_profile.hpp b/src/network/network_player_profile.hpp index 13ce72121..8feb37b3d 100644 --- a/src/network/network_player_profile.hpp +++ b/src/network/network_player_profile.hpp @@ -26,10 +26,11 @@ #include "utils/types.hpp" #include "irrString.h" - +#include #include -namespace Online { class OnlineProfile; } +#include +class STKPeer; /*! \class NetworkPlayerProfile * \brief Contains the profile of a player. @@ -37,31 +38,51 @@ namespace Online { class OnlineProfile; } class NetworkPlayerProfile { private: + std::weak_ptr m_peer; + + /** The name of the player. */ + irr::core::stringw m_player_name; + + /** Host id of this player. */ + uint32_t m_host_id; + + float m_default_kart_color; + + uint32_t m_online_id; + + /** Per player difficulty. */ + PerPlayerDifficulty m_per_player_difficulty; + + /** The selected kart id. */ + std::string m_kart_name; + /** The unique id of the player for this race. The number is assigned * by the server (and it might not be the index of this player in the * peer list. */ uint8_t m_global_player_id; - /** Host id of this player. */ - uint8_t m_host_id; - - /** The selected kart id. */ - std::string m_kart_name; - - /** The name of the player. */ - irr::core::stringw m_player_name; - /** The kart id in the World class (pointer to AbstractKart). */ uint8_t m_world_kart_id; - /** Per player difficulty. */ - PerPlayerDifficulty m_per_player_difficulty; public: - NetworkPlayerProfile(const irr::core::stringw &name, - int global_player_id, int host_id); - ~NetworkPlayerProfile(); + NetworkPlayerProfile(std::shared_ptr peer, + const irr::core::stringw &name, uint32_t host_id, + float default_kart_color, uint32_t online_id, + PerPlayerDifficulty per_player_difficulty) + { + m_peer = peer; + m_player_name = name; + m_host_id = host_id; + m_default_kart_color = default_kart_color; + m_online_id = online_id; + m_per_player_difficulty = per_player_difficulty; + m_global_player_id = 0; + m_world_kart_id = 0; + } + // ------------------------------------------------------------------------ + ~NetworkPlayerProfile() {} + // ------------------------------------------------------------------------ bool isLocalPlayer() const; - // ------------------------------------------------------------------------ /** Sets the global player id of this player. */ void setGlobalPlayerId(int player_id) { m_global_player_id = player_id; } @@ -70,7 +91,7 @@ public: int getGlobalPlayerId() const { return m_global_player_id; } // ------------------------------------------------------------------------ /** Returns the host id of this player. */ - uint8_t getHostId() const { return m_host_id; } + uint32_t getHostId() const { return m_host_id; } // ------------------------------------------------------------------------ /** Sets the kart name for this player. */ void setKartName(const std::string &kart_name) { m_kart_name = kart_name; } @@ -93,6 +114,13 @@ public: /** Returns the name of this player. */ const irr::core::stringw& getName() const { return m_player_name; } // ------------------------------------------------------------------------ + float getDefaultKartColor() const { return m_default_kart_color; } + // ------------------------------------------------------------------------ + uint32_t getOnlineId() const { return m_online_id; } + // ------------------------------------------------------------------------ + bool isOfflineAccount() const { return m_online_id == 0; } + // ------------------------------------------------------------------------ + std::shared_ptr getPeer() const { return m_peer.lock(); } }; // class NetworkPlayerProfile diff --git a/src/network/protocol.cpp b/src/network/protocol.cpp index 607e9df68..17c597725 100644 --- a/src/network/protocol.cpp +++ b/src/network/protocol.cpp @@ -119,11 +119,7 @@ void Protocol::requestTerminate() void Protocol::sendMessageToPeersChangingToken(NetworkString *message, bool reliable) { - const std::vector &peers = STKHost::get()->getPeers(); - for (unsigned int i = 0; i < peers.size(); i++) - { - peers[i]->sendPacket(message, reliable); - } + STKHost::get()->sendPacketToAllPeers(message, reliable); } // sendMessageToPeersChangingToken // ---------------------------------------------------------------------------- diff --git a/src/network/protocol_manager.cpp b/src/network/protocol_manager.cpp index 2828bdb99..b42932ced 100644 --- a/src/network/protocol_manager.cpp +++ b/src/network/protocol_manager.cpp @@ -20,13 +20,12 @@ #include "network/event.hpp" #include "network/protocol.hpp" -#include "network/stk_host.hpp" -#include "network/stk_peer.hpp" #include "utils/log.hpp" #include "utils/profiler.hpp" #include "utils/time.hpp" #include "utils/vs.hpp" +#include #include #include #include @@ -229,8 +228,9 @@ void ProtocolManager::startProtocol(std::shared_ptr protocol) protocol->setup(); protocol->setState(PROTOCOL_STATE_RUNNING); opt.unlock(); + Protocol* protocol_ptr = protocol.get(); Log::info("ProtocolManager", - "A %s protocol has been started.", typeid(*protocol).name()); + "A %s protocol has been started.", typeid(*protocol_ptr).name()); // setup the protocol and notify it that it's started } // startProtocol @@ -278,9 +278,10 @@ void ProtocolManager::OneProtocolType::removeProtocol(std::shared_ptr m_protocols.getData().end(), p); if (i == m_protocols.getData().end()) { + Protocol* protocol_ptr = p.get(); Log::error("ProtocolManager", "Trying to delete protocol '%s', which was not found", - typeid(*p).name()); + typeid(*protocol_ptr).name()); } else { @@ -305,8 +306,9 @@ void ProtocolManager::terminateProtocol(std::shared_ptr protocol) opt.unlock(); protocol->setState(PROTOCOL_STATE_TERMINATED); protocol->terminated(); + Protocol* protocol_ptr = protocol.get(); Log::info("ProtocolManager", - "A %s protocol has been terminated.", typeid(*protocol).name()); + "A %s protocol has been terminated.", typeid(*protocol_ptr).name()); } // terminateProtocol // ---------------------------------------------------------------------------- @@ -502,15 +504,13 @@ void ProtocolManager::asynchronousUpdate() for (unsigned int i = 0; i < m_all_protocols.size(); i++) { OneProtocolType &opt = m_all_protocols[i]; - // The lock is likely not necessary, since this function is only - // called from the ProtocolManager thread, and this thread is also + // We don't need lock here because it can hang the GUI when connecting + // to or creating server, since this function is only called from + // the ProtocolManager thread, and this thread is also // the only one who changes the number of protocols. - // Edit: remove this lock can avoid hanging the GUI when connecting - // to or creating server, but you need to make sure async and non-async - // update in each protocol will have atomic or mutex write - //opt.lock(); + // But you need to make sure async and non-async + // update in each protocol will have atomic or mutex-protected write opt.update(0, /*async*/true); // dt does not matter, so set it to 0 - //opt.unlock(); } PROFILER_POP_CPU_MARKER(); diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 42395dbd1..600cf7fbc 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -18,22 +18,29 @@ #include "network/protocols/client_lobby.hpp" +#include "config/user_config.hpp" #include "config/player_manager.hpp" +#include "guiengine/modaldialog.hpp" +#include "guiengine/message_queue.hpp" +#include "input/device_manager.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/world_with_rank.hpp" #include "network/event.hpp" +#include "network/game_setup.hpp" #include "network/network_config.hpp" #include "network/network_player_profile.hpp" #include "network/protocol_manager.hpp" -#include "network/protocols/latency_protocol.hpp" #include "network/race_event_manager.hpp" #include "network/stk_host.hpp" #include "network/stk_peer.hpp" +#include "online/online_player_profile.hpp" #include "online/online_profile.hpp" #include "states_screens/networking_lobby.hpp" #include "states_screens/network_kart_selection.hpp" #include "states_screens/race_result_gui.hpp" #include "states_screens/state_manager.hpp" +#include "states_screens/tracks_screen.hpp" +#include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/log.hpp" @@ -61,16 +68,25 @@ ClientLobby::ClientLobby() : LobbyProtocol(NULL) { m_server_address.clear(); - m_server = NULL; setHandleDisconnections(true); } // ClientLobby //----------------------------------------------------------------------------- - ClientLobby::~ClientLobby() { + clearPlayers(); } // ClientLobby +//----------------------------------------------------------------------------- +void ClientLobby::clearPlayers() +{ + StateManager::get()->resetActivePlayers(); + input_manager->getDeviceManager()->setAssignMode(NO_ASSIGN); + input_manager->getDeviceManager()->setSinglePlayer(NULL); + input_manager->setMasterPlayerOnly(false); + input_manager->getDeviceManager()->clearLatestUsedDevice(); +} // clearPlayers + //----------------------------------------------------------------------------- /** Sets the address of the server. */ @@ -78,140 +94,16 @@ void ClientLobby::setAddress(const TransportAddress &address) { m_server_address.copy(address); } // setAddress -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- void ClientLobby::setup() { - m_game_setup = STKHost::get()->setupNewGame(); // create a new game setup + clearPlayers(); + TracksScreen::getInstance()->resetVote(); + LobbyProtocol::setup(); m_state = NONE; } // setup -//----------------------------------------------------------------------------- -/** Sends the selection of a kart from this client to the server. - * \param player_id The global player id of the voting player. - * \param kart_name Name of the selected kart. - */ -void ClientLobby::requestKartSelection(uint8_t player_id, - const std::string &kart_name) -{ - NetworkString *request = getNetworkString(3+kart_name.size()); - request->addUInt8(LE_KART_SELECTION).addUInt8(player_id) - .encodeString(kart_name); - sendToServer(request, /*reliable*/ true); - delete request; -} // requestKartSelection - -//----------------------------------------------------------------------------- -/** Sends a vote for a major vote from a client to the server. Note that even - * this client will only store the vote when it is received back from the - * server. - * \param player_id The global player id of the voting player. - * \param major Major mode voted for. - */ -void ClientLobby::voteMajor(uint8_t player_id, uint32_t major) -{ - NetworkString *request = getNetworkString(6); - request->addUInt8(LE_VOTE_MAJOR).addUInt8(player_id) - .addUInt32(major); - sendToServer(request, true); - delete request; -} // voteMajor - -//----------------------------------------------------------------------------- -/** Sends a vote for the number of tracks from a client to the server. Note - * that even this client will only store the vote when it is received back - * from the server. - * \param player_id The global player id of the voting player. - * \param count NUmber of tracks to play. - */ -void ClientLobby::voteRaceCount(uint8_t player_id, uint8_t count) -{ - NetworkString *request = getNetworkString(3); - request->addUInt8(LE_VOTE_RACE_COUNT).addUInt8(player_id).addUInt8(count); - sendToServer(request, true); - delete request; -} // voteRaceCount - -//----------------------------------------------------------------------------- -/** Sends a vote for the minor game mode from a client to the server. Note that - * even this client will only store the vote when it is received back from the - * server. - * \param player_id The global player id of the voting player. - * \param minor Voted minor mode. - */ -void ClientLobby::voteMinor(uint8_t player_id, uint32_t minor) -{ - NetworkString *request = getNetworkString(6); - request->addUInt8(LE_VOTE_MINOR).addUInt8(player_id).addUInt32(minor); - sendToServer(request, true); - delete request; -} // voteMinor - -//----------------------------------------------------------------------------- -/** Sends the vote about which track to play at which place in the list of - * tracks (like a custom GP definition). Note that even this client will only - * store the vote when it is received back from the server. - * \param player_id The global player id of the voting player. - * \param track Name of the track. - * \param At which place in the list of tracks this track should be played. - */ -void ClientLobby::voteTrack(uint8_t player_id, - const std::string &track, - uint8_t track_nb) -{ - NetworkString *request = getNetworkString(2+1+track.size()); - request->addUInt8(LE_VOTE_TRACK).addUInt8(player_id).addUInt8(track_nb) - .encodeString(track); - sendToServer(request, true); - delete request; -} // voteTrack - -//----------------------------------------------------------------------------- -/** Sends a vote if a track at a specified place in the list of all tracks - * should be played in reverse or not. Note that even this client will only - * store the vote when it is received back from the server. - * \param player_id Global player id of the voting player. - * \param reversed True if the track should be played in reverse. - * \param track_nb Index for the track to be voted on in the list of all - * tracks. - */ -void ClientLobby::voteReversed(uint8_t player_id, bool reversed, - uint8_t track_nb) -{ - NetworkString *request = getNetworkString(9); - request->addUInt8(LE_VOTE_REVERSE).addUInt8(player_id).addUInt8(reversed) - .addUInt8(track_nb); - sendToServer(request, true); - delete request; -} // voteReversed - -//----------------------------------------------------------------------------- -/** Vote for the number of laps of the specified track. Note that even this - * client will only store the vote when it is received back from the server. - * \param player_id Global player id of the voting player. - * \param laps Number of laps for the specified track. - * \param track_nb Index of the track in the list of all tracks. - */ -void ClientLobby::voteLaps(uint8_t player_id, uint8_t laps, - uint8_t track_nb) -{ - NetworkString *request = getNetworkString(10); - request->addUInt8(LE_VOTE_LAPS).addUInt8(player_id).addUInt8(laps) - .addUInt8(track_nb); - sendToServer(request, true); - delete request; -} // voteLaps - -//----------------------------------------------------------------------------- -/** Called when a client selects to exit a server. - */ -void ClientLobby::leave() -{ - m_server->disconnect(); - STKHost::get()->removePeer(m_server); - m_server_address.clear(); -} // leave - //----------------------------------------------------------------------------- /** Called from the gui when a client clicked on 'continue' on the race result * screen. It notifies the server that this client has exited the screen and @@ -226,7 +118,6 @@ void ClientLobby::doneWithResults() } // doneWithResults //----------------------------------------------------------------------------- - bool ClientLobby::notifyEvent(Event* event) { assert(m_game_setup); // assert that the setup exists @@ -239,10 +130,11 @@ bool ClientLobby::notifyEvent(Event* event) switch(message_type) { case LE_START_SELECTION: startSelection(event); break; - case LE_KART_SELECTION_UPDATE: kartSelectionUpdate(event); break; - case LE_LOAD_WORLD: loadWorld(); break; + case LE_LOAD_WORLD: addAllPlayers(event); break; case LE_RACE_FINISHED: raceFinished(event); break; case LE_EXIT_RESULT: exitResultScreen(event); break; + case LE_UPDATE_PLAYER_LIST: updatePlayerList(event); break; + case LE_CHAT: handleChat(event); break; default: return false; break; @@ -251,7 +143,6 @@ bool ClientLobby::notifyEvent(Event* event) } // notifyEvent //----------------------------------------------------------------------------- - bool ClientLobby::notifyEventAsynchronous(Event* event) { assert(m_game_setup); // assert that the setup exists @@ -265,18 +156,13 @@ bool ClientLobby::notifyEventAsynchronous(Event* event) message_type); switch(message_type) { - case LE_NEW_PLAYER_CONNECTED: newPlayer(event); break; case LE_PLAYER_DISCONNECTED : disconnectedPlayer(event); break; case LE_START_RACE: startGame(event); break; case LE_CONNECTION_REFUSED: connectionRefused(event); break; case LE_CONNECTION_ACCEPTED: connectionAccepted(event); break; - case LE_KART_SELECTION_REFUSED: kartSelectionRefused(event); break; - case LE_VOTE_MAJOR : playerMajorVote(event); break; - case LE_VOTE_RACE_COUNT: playerRaceCountVote(event); break; - case LE_VOTE_MINOR: playerMinorVote(event); break; - case LE_VOTE_TRACK: playerTrackVote(event); break; - case LE_VOTE_REVERSE: playerReversedVote(event); break; - case LE_VOTE_LAPS: playerLapsVote(event); break; + case LE_VOTE: displayPlayerVote(event); break; + case LE_SERVER_OWNERSHIP: becomingServerOwner(); break; + default: break; } // switch return true; @@ -289,6 +175,22 @@ bool ClientLobby::notifyEventAsynchronous(Event* event) // the ProtocolManager, which might already have been deleted. // So only signal that STKHost should exit, which will be tested // from the main thread. + STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/); + switch(event->getPeerDisconnectInfo()) + { + case PDI_TIMEOUT: + STKHost::get()->setErrorMessage( + _("Server connection timed out.")); + break; + case PDI_NORMAL: + STKHost::get()->setErrorMessage( + _("Server has been shut down.")); + break; + case PDI_KICK: + STKHost::get()->setErrorMessage( + _("You were kicked from the server.")); + break; + } // switch STKHost::get()->requestShutdown(); return true; } // disconnection @@ -296,7 +198,51 @@ bool ClientLobby::notifyEventAsynchronous(Event* event) } // notifyEventAsynchronous //----------------------------------------------------------------------------- +void ClientLobby::addAllPlayers(Event* event) +{ + if (!checkDataSize(event, 1)) + { + // If recieved invalid message for players leave now + STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/); + STKHost::get()->requestShutdown(); + return; + } + NetworkString& data = event->data(); + std::string track_name; + data.decodeString(&track_name); + uint8_t lap = data.getUInt8(); + uint8_t reverse = data.getUInt8(); + m_game_setup->setRace(track_name, lap, reverse == 1); + std::shared_ptr peer = event->getPeerSP(); + peer->cleanPlayerProfiles(); + + std::vector > players; + unsigned player_count = data.getUInt8(); + assert(m_game_setup->getPlayerCount() == 0); + + for (unsigned i = 0; i < player_count; i++) + { + core::stringw player_name; + data.decodeStringW(&player_name); + uint32_t host_id = data.getUInt32(); + float kart_color = data.getFloat(); + uint32_t online_id = data.getUInt32(); + PerPlayerDifficulty ppd = (PerPlayerDifficulty)data.getUInt8(); + auto player = std::make_shared(peer, player_name, + host_id, kart_color, online_id, ppd); + std::string kart_name; + data.decodeString(&kart_name); + player->setKartName(kart_name); + peer->addPlayer(player); + m_game_setup->addPlayer(player); + players.push_back(player); + } + configRemoteKart(players); + loadWorld(); +} // addAllPlayers + +//----------------------------------------------------------------------------- void ClientLobby::update(int ticks) { switch (m_state) @@ -309,20 +255,38 @@ void ClientLobby::update(int ticks) break; case LINKED: { - core::stringw name; - if(PlayerManager::getCurrentOnlineState()==PlayerProfile::OS_SIGNED_IN) - name = PlayerManager::getCurrentOnlineUserName(); - else - name = PlayerManager::getCurrentPlayer()->getName(); - - std::string name_u8 = StringUtils::wideToUtf8(name); - const std::string &password = NetworkConfig::get()->getPassword(); - NetworkString *ns = getNetworkString(6+1+name_u8.size() - +1+password.size()); - // 4 (size of id), global id - ns->addUInt8(LE_CONNECTION_REQUESTED).encodeString(name) - .encodeString(NetworkConfig::get()->getPassword()); + NetworkString *ns = getNetworkString(); + ns->addUInt8(LE_CONNECTION_REQUESTED) + .encodeString(NetworkConfig::get()->getPassword()); + assert(!NetworkConfig::get()->isAddingNetworkPlayers()); + ns->addUInt8( + (uint8_t)NetworkConfig::get()->getNetworkPlayers().size()); + // Only first player has online name and profile + bool first_player = true; + for (auto& p : NetworkConfig::get()->getNetworkPlayers()) + { + core::stringw name; + PlayerProfile* player = std::get<1>(p); + if (PlayerManager::getCurrentOnlineState() == + PlayerProfile::OS_SIGNED_IN && first_player) + { + name = PlayerManager::getCurrentOnlineUserName(); + } + else + { + name = player->getName(); + } + std::string name_u8 = StringUtils::wideToUtf8(name); + ns->encodeString(name_u8).addFloat(player->getDefaultKartColor()); + Online::OnlinePlayerProfile* opp = + dynamic_cast(player); + ns->addUInt32(first_player && opp && opp->getProfile() ? + opp->getProfile()->getID() : 0); + // Per-player handicap + ns->addUInt8(std::get<2>(p)); + first_player = false; + } auto all_k = kart_properties_manager->getAllAvailableKarts(); auto all_t = track_manager->getAllTrackIdentifiers(); if (all_k.size() >= 65536) @@ -350,14 +314,30 @@ void ClientLobby::update(int ticks) break; case KART_SELECTION: { + // In case the user opened a user info dialog + GUIEngine::ModalDialog::dismiss(); NetworkKartSelectionScreen* screen = - NetworkKartSelectionScreen::getInstance(); + NetworkKartSelectionScreen::getInstance(); screen->setAvailableKartsFromServer(m_available_karts); - screen->push(); + // In case of auto-connect, use random karts (or previous kart) from + // server and go to track selection (or grand prix later) + if (NetworkConfig::get()->isAutoConnect()) + { + input_manager->setMasterPlayerOnly(true); + for (auto& p : NetworkConfig::get()->getNetworkPlayers()) + { + StateManager::get() + ->createActivePlayer(std::get<1>(p), std::get<0>(p)); + } + input_manager->getDeviceManager()->setAssignMode(ASSIGN); + TracksScreen::getInstance()->setNetworkTracks(); + TracksScreen::getInstance()->push(); + } + else + { + screen->push(); + } m_state = SELECTING_KARTS; - - std::make_shared()->requestStart(); - Log::info("LobbyProtocol", "LatencyProtocol started."); } break; case SELECTING_KARTS: @@ -376,50 +356,36 @@ void ClientLobby::update(int ticks) } // update //----------------------------------------------------------------------------- - -/*! \brief Called when a new player is connected to the server - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * ------------------------------------- - * Size | 1 | 1 | | - * Data | player_id | hostid | player name | - * ------------------------------------- - */ -void ClientLobby::newPlayer(Event* event) +void ClientLobby::displayPlayerVote(Event* event) { - if (!checkDataSize(event, 2)) return; - const NetworkString &data = event->data(); - - uint8_t player_id = data.getUInt8(); - uint8_t host_id = data.getUInt8(); - core::stringw name; - data.decodeStringW(&name); - // FIXME need adjusting when splitscreen is used/ - if(STKHost::get()->getGameSetup()->isLocalMaster(player_id)) - { - Log::error("ClientLobby", - "The server notified me that I'm a new player in the " - "room (not normal)."); - } - else if (m_game_setup->getProfile(player_id) == NULL) - { - Log::verbose("ClientLobby", "New player connected."); - NetworkPlayerProfile* profile = - new NetworkPlayerProfile(name, player_id, host_id); - m_game_setup->addPlayer(profile); - NetworkingLobby::getInstance()->addPlayer(profile); - } - else - { - Log::error("ClientLobby", - "One of the player notified in the list is myself."); - } -} // newPlayer + if (!checkDataSize(event, 4)) return; + // Get the player name who voted + NetworkString& data = event->data(); + float timeout = data.getFloat(); + TracksScreen::getInstance()->setVoteTimeout(timeout); + std::string player_name; + data.decodeString(&player_name); + uint32_t host_id = data.getUInt32(); + player_name += ": "; + std::string track_name; + data.decodeString(&track_name); + Track* track = track_manager->getTrack(track_name); + if (!track) + Log::fatal("ClientLobby", "Missing track %s", track_name.c_str()); + core::stringw track_readable = track->getName(); + int lap = data.getUInt8(); + int rev = data.getUInt8(); + core::stringw yes = _("Yes"); + core::stringw no = _("No"); + //I18N: Vote message in network game from a player + core::stringw vote_msg = _("Track: %s,\nlaps: %d, reversed: %s", + track_readable, lap, rev == 1 ? yes : no); + vote_msg = StringUtils::utf8ToWide(player_name) + vote_msg; + TracksScreen::getInstance()->addVoteMessage(player_name + + StringUtils::toString(host_id), vote_msg); +} // displayPlayerVote //----------------------------------------------------------------------------- - /*! \brief Called when a new player is disconnected * \param event : Event providing the information. * @@ -435,97 +401,73 @@ void ClientLobby::disconnectedPlayer(Event* event) if (!checkDataSize(event, 1)) return; NetworkString &data = event->data(); - while(data.size()>0) + unsigned disconnected_player_count = data.getUInt8(); + for (unsigned i = 0; i < disconnected_player_count; i++) { - const NetworkPlayerProfile *profile = - m_game_setup->getProfile(data.getUInt8()); - if (m_game_setup->removePlayer(profile)) - { - Log::info("ClientLobby", - "Player %d removed successfully.", - profile->getGlobalPlayerId()); - } - else - { - Log::error("ClientLobby", - "The disconnected peer wasn't known."); - } - } // while + core::stringw player_name; + data.decodeStringW(&player_name); + core::stringw msg = _("%s disconnected.", player_name); + // Use the friend icon to avoid an error-like message + MessageQueue::add(MessageQueue::MT_FRIEND, msg); + } - STKHost::get()->removePeer(event->getPeer()); } // disconnectedPlayer //----------------------------------------------------------------------------- - /*! \brief Called when the server accepts the connection. * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 3 - * --------------------------------------------------------- - * Size | 1 | 1 | 1 | | - * Data | player_id| hostid | authorised |playernames* | - * --------------------------------------------------------- */ void ClientLobby::connectionAccepted(Event* event) { - // At least 3 bytes should remain now - if(!checkDataSize(event, 3)) return; + // At least 4 byte should remain now + if (!checkDataSize(event, 4)) return; NetworkString &data = event->data(); STKPeer* peer = event->getPeer(); // Accepted // ======== - Log::info("ClientLobby", - "The server accepted the connection."); + Log::info("ClientLobby", "The server accepted the connection."); - // self profile - irr::core::stringw name; - if (PlayerManager::getCurrentOnlineState() == PlayerProfile::OS_SIGNED_IN) - name = PlayerManager::getCurrentOnlineUserName(); - else - name = PlayerManager::getCurrentPlayer()->getName(); - uint8_t my_player_id = data.getUInt8(); - uint8_t my_host_id = data.getUInt8(); - uint8_t authorised = data.getUInt8(); - // Store this client's authorisation status in the peer information - // for the server. - event->getPeer()->setAuthorised(authorised!=0); - STKHost::get()->setMyHostId(my_host_id); - - NetworkPlayerProfile* profile = - new NetworkPlayerProfile(name, my_player_id, my_host_id); - STKHost::get()->getGameSetup()->setLocalMaster(my_player_id); - m_game_setup->setNumLocalPlayers(1); + STKHost::get()->setMyHostId(data.getUInt32()); + assert(!NetworkConfig::get()->isAddingNetworkPlayers()); + m_game_setup->setNumLocalPlayers((int) + NetworkConfig::get()->getNetworkPlayers().size()); // connection token uint32_t token = data.getToken(); - peer->setClientServerToken(token); - - // Add all players - // =============== - while (data.size() > 0) - { - uint8_t player_id = data.getUInt8(); - uint8_t host_id = data.getUInt8(); - irr::core::stringw name; - int bytes_read = data.decodeStringW(&name); - - NetworkPlayerProfile* profile2 = - new NetworkPlayerProfile(name, player_id, host_id); - m_game_setup->addPlayer(profile2); - // Inform the network lobby of all players so that the GUI can - // show all currently connected players. - NetworkingLobby::getInstance()->addPlayer(profile2); - } - - // Add self after other players so that player order is identical - // on server and all clients. - m_game_setup->addPlayer(profile); - NetworkingLobby::getInstance()->addPlayer(profile); - m_server = event->getPeer(); + if (!peer->isClientServerTokenSet()) + peer->setClientServerToken(token); m_state = CONNECTED; - if (NetworkConfig::get()->isAutoConnect()) +} // connectionAccepted + +//----------------------------------------------------------------------------- +void ClientLobby::updatePlayerList(Event* event) +{ + if (!checkDataSize(event, 1)) return; + NetworkString& data = event->data(); + unsigned player_count = data.getUInt8(); + std::vector > players; + for (unsigned i = 0; i < player_count; i++) + { + std::tuple pl; + std::get<0>(pl) = data.getUInt32(); + std::get<1>(pl) = data.getUInt32(); + data.decodeStringW(&std::get<2>(pl)); + // icon to be used, see NetworkingLobby::loadedFromFile + std::get<3>(pl) = data.getUInt8() == 1 /*if server owner*/ ? 0 : + std::get<1>(pl) != 0 /*if online account*/ ? 1 : 2; + players.push_back(pl); + } + NetworkingLobby::getInstance()->updatePlayers(players); +} // updatePlayerList + +//----------------------------------------------------------------------------- +void ClientLobby::becomingServerOwner() +{ + MessageQueue::add(MessageQueue::MT_GENERIC, + _("You are now the owner of server.")); + STKHost::get()->setAuthorisedToControl(true); + if (m_state == CONNECTED && NetworkConfig::get()->isAutoConnect()) { // Send a message to the server to start NetworkString start(PROTOCOL_LOBBY_ROOM); @@ -533,11 +475,24 @@ void ClientLobby::connectionAccepted(Event* event) start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN); STKHost::get()->sendToServer(&start, true); } - -} // connectionAccepted +} // becomingServerOwner //----------------------------------------------------------------------------- +void ClientLobby::handleChat(Event* event) +{ + if (!UserConfigParams::m_lobby_chat) + return; + std::string message; + event->data().decodeString(&message); + Log::info("ClientLobby", "%s", message.c_str()); + if (message.size() > 0) + { + NetworkingLobby::getInstance()->addMoreServerInfo( + StringUtils::utf8ToWide(message)); + } +} // handleChat +//----------------------------------------------------------------------------- /*! \brief Called when the server refuses the connection. * \param event : Event providing the information. * @@ -552,93 +507,35 @@ void ClientLobby::connectionRefused(Event* event) { if (!checkDataSize(event, 1)) return; const NetworkString &data = event->data(); - - switch (data.getUInt8()) // the second byte + switch ((RejectReason)data.getUInt8()) // the second byte { - case 0: - Log::info("ClientLobby", - "Connection refused : too many players."); + case RR_BUSY: + STKHost::get()->setErrorMessage( + _("Connection refused: Server is busy.")); break; - case 1: - Log::info("ClientLobby", "Connection refused : banned."); + case RR_BANNED: + STKHost::get()->setErrorMessage( + _("Connection refused: You are banned from the server.")); break; - case 2: - Log::info("ClientLobby", "Client busy."); + case RR_INCORRECT_PASSWORD: + STKHost::get()->setErrorMessage( + _("Connection refused: Server password is incorrect.")); break; - case 3: - Log::info("ClientLobby", "Having incompatible karts / tracks."); + case RR_INCOMPATIBLE_DATA: + STKHost::get()->setErrorMessage( + _("Connection refused: Game data is incompatible.")); break; - default: - Log::info("ClientLobby", "Connection refused."); + case RR_TOO_MANY_PLAYERS: + STKHost::get()->setErrorMessage( + _("Connection refused: Server is full.")); break; } + STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/); + STKHost::get()->requestShutdown(); } // connectionRefused //----------------------------------------------------------------------------- -/*! \brief Called when the server refuses the kart selection request. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 - * ---------------- - * Size | 1 | - * Data | refusal code | - * ---------------- - */ -void ClientLobby::kartSelectionRefused(Event* event) -{ - if(!checkDataSize(event, 1)) return; - - const NetworkString &data = event->data(); - - switch (data.getUInt8()) // the error code - { - case 0: - Log::info("ClientLobby", - "Kart selection refused : already taken."); - break; - case 1: - Log::info("ClientLobby", - "Kart selection refused : not available."); - break; - default: - Log::info("ClientLobby", "Kart selection refused."); - break; - } -} // kartSelectionRefused - -//----------------------------------------------------------------------------- - -/*! \brief Called when the server tells to update a player's kart. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 3 N+3 - * -------------------------------------------------- - * Size | 1 | 1 | N | - * Data | player id | N (kart name size) | kart name | - * -------------------------------------------------- - */ -void ClientLobby::kartSelectionUpdate(Event* event) -{ - if(!checkDataSize(event, 3)) return; - const NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - std::string kart_name; - data.decodeString(&kart_name); - if (!m_game_setup->isKartAvailable(kart_name)) - { - Log::error("ClientLobby", - "The updated kart is taken already."); - } - m_game_setup->setPlayerKart(player_id, kart_name); - NetworkKartSelectionScreen::getInstance()->playerSelected(player_id, - kart_name); -} // kartSelectionUpdate - -//----------------------------------------------------------------------------- - /*! \brief Called when the server broadcasts to start the race to all clients. * \param event : Event providing the information (no additional information * in this case). @@ -668,7 +565,6 @@ void ClientLobby::startingRaceNow() sendToServer(ns, /*reliable*/true); Log::verbose("ClientLobby", "StartingRaceNow at %lf", StkTime::getRealTime()); - terminateLatencyProtocol(); } // startingRaceNow //----------------------------------------------------------------------------- @@ -750,149 +646,19 @@ void ClientLobby::raceFinished(Event* event) */ void ClientLobby::exitResultScreen(Event *event) { - RaceResultGUI::getInstance()->backToLobby(); - // Will be reset to linked if connected to server, see update(float dt) - m_game_setup = STKHost::get()->setupNewGame(); - STKHost::get()->getServerPeerForClient()->unsetClientServerToken(); // stop race protocols auto pm = ProtocolManager::lock(); assert(pm); pm->findAndTerminate(PROTOCOL_CONTROLLER_EVENTS); pm->findAndTerminate(PROTOCOL_KART_UPDATE); pm->findAndTerminate(PROTOCOL_GAME_EVENTS); - m_state = NONE; + + // Will be reset to linked if connected to server, see update(float dt) + setup(); + + RaceResultGUI::getInstance()->backToLobby(); } // exitResultScreen -//----------------------------------------------------------------------------- -/*! \brief Called when a player votes for a major race mode. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * ------------------------------ - * Size | 1 | 1 | - * Data |player id | major mode vote | - * ------------------------------ - */ -void ClientLobby::playerMajorVote(Event* event) -{ - const NetworkString &data = event->data(); - if (!checkDataSize(event, 2)) - return; - uint8_t player_id = data.getUInt8(); - uint8_t mode = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerMajorVote(player_id, mode); -} // playerMajorVote - -//----------------------------------------------------------------------------- -/*! \brief Called when a player votes for the number of races in a GP. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 - * --------------------------- - * Size | 1 | 1 | - * Data | player id | races count | - * --------------------------- - */ -void ClientLobby::playerRaceCountVote(Event* event) -{ - if (!checkDataSize(event, 2)) return; - const NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t count = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerRaceCountVote(player_id, count); -} // playerRaceCountVote - -//----------------------------------------------------------------------------- -/*! \brief Called when a player votes for a minor race mode. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 - * ------------------------------- - * Size | 1 | 4 | - * Data | player id | minor mode vote | - * ------------------------------- - */ -void ClientLobby::playerMinorVote(Event* event) -{ - if (!checkDataSize(event, 2)) return; - const NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t minor = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerMinorVote(player_id, minor); -} // playerMinorVote - -//----------------------------------------------------------------------------- - -/*! \brief Called when a player votes for a track. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 3 - * -------------------------------------------------- - * Size | 1 | 1 | 1 | N | - * Data | player id | track number (gp) | N | track name | - * -------------------------------------------------- - */ -void ClientLobby::playerTrackVote(Event* event) -{ - if (!checkDataSize(event, 3)) return; - const NetworkString &data = event->data(); - std::string track_name; - uint8_t player_id = data.getUInt8(); - uint8_t number = data.getUInt8(); - int N = data.decodeString(&track_name); - m_game_setup->getRaceConfig()->setPlayerTrackVote(player_id, track_name, - number); -} // playerTrackVote - -//----------------------------------------------------------------------------- - -/*! \brief Called when a player votes for the reverse mode of a race - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * ------------------------------------------- - * Size | 1 | 1 | 1 | - * Data | player id |reversed | track number (gp) | - * ------------------------------------------- - */ -void ClientLobby::playerReversedVote(Event* event) -{ - if (!checkDataSize(event, 3)) return; - const NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t reversed = data.getUInt8(); - uint8_t number = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerReversedVote(player_id, reversed!=0, - number); -} // playerReversedVote - -//----------------------------------------------------------------------------- - -/*! \brief Called when a player votes for a major race mode. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * ---------------------------------------- - * Size | 1 | 1 | 1 | - * Data | player id | laps | track number (gp) | - * ---------------------------------------- - */ -void ClientLobby::playerLapsVote(Event* event) -{ - if (!checkDataSize(event, 3)) return; - const NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t laps = data.getUInt8(); - uint8_t number = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerLapsVote(player_id, laps, number); -} // playerLapsVote - //----------------------------------------------------------------------------- /** Callback when the world is loaded. The client will inform the server * that the players on this host are ready to start the race. It is called by @@ -900,19 +666,8 @@ void ClientLobby::playerLapsVote(Event* event) */ void ClientLobby::finishedLoadingWorld() { - assert(STKHost::get()->getPeerCount() == 1); - std::vector players = - STKHost::get()->getMyPlayerProfiles(); - NetworkString *ns = getNetworkString(2); + NetworkString* ns = getNetworkString(1); ns->addUInt8(LE_CLIENT_LOADED_WORLD); - ns->addUInt8( uint8_t(players.size()) ) ; - for (unsigned int i = 0; i < players.size(); i++) - { - ns->addUInt8(players[i]->getGlobalPlayerId()); - Log::info("ClientLobby", - "Player %d ready, notifying server.", - players[i]->getGlobalPlayerId()); - } // for i < players.size() - sendToServer(ns, /*reliable*/true); + sendToServer(ns, true); delete ns; } // finishedLoadingWorld diff --git a/src/network/protocols/client_lobby.hpp b/src/network/protocols/client_lobby.hpp index b45fa4197..06ea1037a 100644 --- a/src/network/protocols/client_lobby.hpp +++ b/src/network/protocols/client_lobby.hpp @@ -6,33 +6,26 @@ #include "utils/cpp2011.hpp" #include -class STKPeer; - class ClientLobby : public LobbyProtocol { private: - void newPlayer(Event* event); void disconnectedPlayer(Event* event); void connectionAccepted(Event* event); //!< Callback function on connection acceptation void connectionRefused(Event* event); //!< Callback function on connection refusal - void kartSelectionRefused(Event* event); - void kartSelectionUpdate(Event* event); void startGame(Event* event); void startSelection(Event* event); void raceFinished(Event* event); void exitResultScreen(Event *event); // race votes - void playerMajorVote(Event* event); - void playerRaceCountVote(Event* event); - void playerMinorVote(Event* event); - void playerTrackVote(Event* event); - void playerReversedVote(Event* event); - void playerLapsVote(Event* event); + void displayPlayerVote(Event* event); + void updatePlayerList(Event* event); + void handleChat(Event* event); + void becomingServerOwner(); + + void clearPlayers(); TransportAddress m_server_address; - STKPeer* m_server; - enum STATE { NONE, @@ -53,29 +46,18 @@ private: std::set m_available_karts; std::set m_available_tracks; + void addAllPlayers(Event* event); + public: ClientLobby(); virtual ~ClientLobby(); - - virtual void requestKartSelection(uint8_t player_id, - const std::string &kart_name) OVERRIDE; void setAddress(const TransportAddress &address); - void voteMajor(uint8_t player_id, uint32_t major); - void voteRaceCount(uint8_t player_id, uint8_t count); - void voteMinor(uint8_t player_id, uint32_t minor); - void voteTrack(uint8_t player_id, const std::string &track, - uint8_t track_nb = 0); - void voteReversed(uint8_t player_id, bool reversed, uint8_t track_nb = 0); - void voteLaps(uint8_t player_id, uint8_t laps, uint8_t track_nb = 0); void doneWithResults(); void startingRaceNow(); - void leave(); - const std::set& getAvailableKarts() const { return m_available_karts; } const std::set& getAvailableTracks() const { return m_available_tracks; } - virtual bool notifyEvent(Event* event) OVERRIDE; virtual bool notifyEventAsynchronous(Event* event) OVERRIDE; virtual void finishedLoadingWorld() OVERRIDE; diff --git a/src/network/protocols/connect_to_peer.cpp b/src/network/protocols/connect_to_peer.cpp index e20ad3083..8f4584a6c 100644 --- a/src/network/protocols/connect_to_peer.cpp +++ b/src/network/protocols/connect_to_peer.cpp @@ -38,7 +38,6 @@ ConnectToPeer::ConnectToPeer(uint32_t peer_id) : Protocol(PROTOCOL_CONNECTION) m_peer_id = peer_id; m_state = NONE; m_is_lan = false; - setHandleConnections(true); } // ConnectToPeer(peer_id) // ---------------------------------------------------------------------------- @@ -53,7 +52,6 @@ ConnectToPeer::ConnectToPeer(const TransportAddress &address) // with the state when we found the peer address. m_state = WAIT_FOR_CONNECTION; m_is_lan = true; - setHandleConnections(true); } // ConnectToPeers(TransportAddress) // ---------------------------------------------------------------------------- @@ -62,17 +60,6 @@ ConnectToPeer::~ConnectToPeer() { } // ~ConnectToPeer -// ---------------------------------------------------------------------------- -bool ConnectToPeer::notifyEventAsynchronous(Event* event) -{ - if (event->getType() == EVENT_TYPE_CONNECTED) - { - Log::debug("ConnectToPeer", "Received event notifying peer connection."); - m_state = CONNECTED; // we received a message, we are connected - } - return true; -} // notifyEventAsynchronous - // ---------------------------------------------------------------------------- /** Simple finite state machine: Start a GetPeerAddress protocol. Once the * result has been received, start a ping protocol (hoping to be able @@ -115,6 +102,14 @@ void ConnectToPeer::asynchronousUpdate() } case WAIT_FOR_CONNECTION: { + if (STKHost::get()->peerExists(m_peer_address)) + { + Log::info("ConnectToPeer", + "Peer %s has established a connection.", + m_peer_address.toString().c_str()); + m_state = DONE; + break; + } // Each 2 second for a ping or broadcast if (StkTime::getRealTime() > m_timer + 2.0) { @@ -128,13 +123,16 @@ void ConnectToPeer::asynchronousUpdate() BareNetworkString aloha(std::string("aloha_stk")); STKHost::get()->sendRawPacket(aloha, broadcast_address); - Log::info("ConnectToPeer", "Broadcast aloha sent."); + Log::verbose("ConnectToPeer", "Broadcast aloha sent."); StkTime::sleep(1); - broadcast_address.setIP(0x7f000001); // 127.0.0.1 (localhost) - broadcast_address.setPort(m_peer_address.getPort()); - STKHost::get()->sendRawPacket(aloha, broadcast_address); - Log::info("ConnectToPeer", "Broadcast aloha to self."); + if (m_peer_address.isPublicAddressLocalhost()) + { + broadcast_address.setIP(0x7f000001); // 127.0.0.1 (localhost) + broadcast_address.setPort(m_peer_address.getPort()); + STKHost::get()->sendRawPacket(aloha, broadcast_address); + Log::verbose("ConnectToPeer", "Broadcast aloha to self."); + } // 20 seconds timeout if (m_tried_connection++ > 10) @@ -142,23 +140,13 @@ void ConnectToPeer::asynchronousUpdate() // Not much we can do about if we don't receive the client // connection - it could have stopped, lost network, ... // Terminate this protocol. - Log::error("ConnectToPeer", "Time out trying to connect to %s", + Log::warn("ConnectToPeer", "Time out trying to connect to %s", m_peer_address.toString().c_str()); - requestTerminate(); + m_state = DONE; } } break; } - case CONNECTING: // waiting for the peer to connect - // If we receive a 'connected' event from enet, our - // notifyEventAsynchronous is called, which will move - // the FSM to the next state CONNECTED - break; - case CONNECTED: - { - m_state = DONE; - break; - } case DONE: m_state = EXITING; requestTerminate(); diff --git a/src/network/protocols/connect_to_peer.hpp b/src/network/protocols/connect_to_peer.hpp index d47264399..b83189731 100644 --- a/src/network/protocols/connect_to_peer.hpp +++ b/src/network/protocols/connect_to_peer.hpp @@ -23,8 +23,6 @@ #include "network/transport_address.hpp" #include "utils/cpp2011.hpp" -#include - /** One instance of this is started for every peer who tries to * connect to this server. */ @@ -55,8 +53,6 @@ protected: NONE, RECEIVED_PEER_ADDRESS, WAIT_FOR_CONNECTION, - CONNECTING, - CONNECTED, DONE, EXITING } m_state; @@ -66,7 +62,6 @@ public: ConnectToPeer(const TransportAddress &address); virtual ~ConnectToPeer(); - virtual bool notifyEventAsynchronous(Event* event) OVERRIDE; virtual void setup() OVERRIDE {} virtual void update(int ticks) OVERRIDE {} virtual void asynchronousUpdate() OVERRIDE; diff --git a/src/network/protocols/connect_to_server.cpp b/src/network/protocols/connect_to_server.cpp index af07f9b4f..b42998883 100644 --- a/src/network/protocols/connect_to_server.cpp +++ b/src/network/protocols/connect_to_server.cpp @@ -18,7 +18,9 @@ #include "network/protocols/connect_to_server.hpp" +#include "config/user_config.hpp" #include "network/event.hpp" +#include "network/network.hpp" #include "network/network_config.hpp" #include "network/protocols/get_peer_address.hpp" #include "network/protocols/hide_public_address.hpp" @@ -33,6 +35,8 @@ #include "utils/time.hpp" #include "utils/log.hpp" +#include + // ---------------------------------------------------------------------------- /** Specify server to connect to. * \param server Server to connect to (if nullptr than we use quick play). @@ -40,7 +44,11 @@ ConnectToServer::ConnectToServer(std::shared_ptr server) : Protocol(PROTOCOL_CONNECTION) { - m_server = server; + if (server) + { + m_server = server; + m_server_address.copy(m_server->getAddress()); + } setHandleConnections(true); } // ConnectToServer(server, host) @@ -78,17 +86,26 @@ void ConnectToServer::asynchronousUpdate() StkTime::sleep(1); while (!ServersManager::get()->listUpdated()) StkTime::sleep(1); - if (!ServersManager::get()->getServers().empty()) + auto servers = std::move(ServersManager::get()->getServers()); + + // Remove password protected servers + servers.erase(std::remove_if(servers.begin(), servers.end(), [] + (const std::shared_ptr a)->bool + { + return a->isPasswordProtected(); + }), servers.end()); + + if (!servers.empty()) { // For quick play we choose the server with the least player - ServersManager::get()->sortServers([] + std::sort(servers.begin(), servers.end(), [] (const std::shared_ptr a, const std::shared_ptr b)->bool { return a->getCurrentPlayers() < b->getCurrentPlayers(); }); - m_server = ServersManager::get()->getServers()[0]; - ServersManager::get()->cleanUpServers(); + m_server = servers[0]; + m_server_address.copy(m_server->getAddress()); } else { @@ -99,7 +116,11 @@ void ConnectToServer::asynchronousUpdate() m_state = EXITING; return; } + servers.clear(); } + + if (handleDirectConnect()) + return; STKHost::get()->setPublicAddress(); // Set to DONE will stop STKHost is not connected m_state = STKHost::get()->getPublicAddress().isUnset() ? @@ -115,7 +136,6 @@ void ConnectToServer::asynchronousUpdate() case GOT_SERVER_ADDRESS: { assert(m_server); - m_server_address.copy(m_server->getAddress()); Log::info("ConnectToServer", "Server's address known"); m_state = REQUESTING_CONNECTION; auto request_connection = @@ -146,11 +166,15 @@ void ConnectToServer::asynchronousUpdate() m_current_protocol = hide_address; return; } - if (m_tried_connection++ > 10) + if (m_tried_connection++ > 7) { - Log::error("ConnectToServer", "Timeout waiting for aloha"); - m_state = NetworkConfig::get()->isWAN() ? - HIDING_ADDRESS : DONE; + Log::warn("ConnectToServer", "Timeout waiting for" + " aloha, trying to connect anyway."); + m_state = CONNECTING; + // Reset timer for next usage + m_timer = 0.0; + m_tried_connection = 0; + return; } if ((!NetworkConfig::m_disable_lan && m_server_address.getIP() == @@ -183,10 +207,12 @@ void ConnectToServer::asynchronousUpdate() if (StkTime::getRealTime() > m_timer + 5.0) { m_timer = StkTime::getRealTime(); + STKHost::get()->stopListening(); STKHost::get()->connect(m_server_address); + STKHost::get()->startListening(); Log::info("ConnectToServer", "Trying to connect to %s", m_server_address.toString().c_str()); - if (m_tried_connection++ > 3) + if (m_tried_connection++ > 1) { Log::error("ConnectToServer", "Timeout connect to %s", m_server_address.toString().c_str()); @@ -201,7 +227,8 @@ void ConnectToServer::asynchronousUpdate() Log::info("ConnectToServer", "Connected"); // LAN networking does not use the stk server tables. if (NetworkConfig::get()->isWAN() && - !STKHost::get()->isClientServer()) + !STKHost::get()->isClientServer() && + !STKHost::get()->getPublicAddress().isUnset()) { auto hide_address = std::make_shared(); hide_address->requestStart(); @@ -239,7 +266,7 @@ void ConnectToServer::update(int ticks) { // lobby room protocol if we're connected only if (STKHost::get()->getPeerCount() > 0 && - STKHost::get()->getPeers()[0]->isConnected() && + STKHost::get()->getServerPeerForClient()->isConnected() && !m_server_address.isUnset()) { // Let main thread create ClientLobby for better @@ -264,6 +291,44 @@ void ConnectToServer::update(int ticks) } } // update +// ---------------------------------------------------------------------------- +bool ConnectToServer::handleDirectConnect() +{ + // Direct connection to server should only possbile if public and private + // ports of server are the same + if (NetworkConfig::get()->isWAN() && + m_server->getPrivatePort() == m_server->getAddress().getPort() && + !STKHost::get()->isClientServer()) + { + ENetEvent event; + ENetAddress ea; + ea.host = STKHost::HOST_ANY; + ea.port = STKHost::PORT_ANY; + Network* dc = new Network(/*peer_count*/1, /*channel_limit*/2, + /*max_in_bandwidth*/0, /*max_out_bandwidth*/0, &ea, + true/*change_port_if_bound*/); + assert(dc); + ENetPeer* p = dc->connectTo(m_server_address); + if (p) + { + while (enet_host_service(dc->getENetHost(), &event, 2000) != 0) + { + if (event.type == ENET_EVENT_TYPE_CONNECT) + { + Log::info("ConnectToServer", + "Direct connection to %s succeed", + m_server_address.toString().c_str()); + STKHost::get()->replaceNetwork(event, dc); + m_state = DONE; + return true; + } + } + } + delete dc; + } + return false; +} // handleDirectConnect + // ---------------------------------------------------------------------------- /** Register this client with the STK server. */ @@ -313,16 +378,14 @@ void ConnectToServer::waitingAloha(bool is_wan) { // just send a broadcast packet, the client will know our // ip address and will connect - STKHost* host = STKHost::get(); - host->stopListening(); // stop the listening - + STKHost::get()->stopListening(); // stop the listening Log::info("ConnectToServer", "Waiting broadcast message."); TransportAddress sender; // get the sender const int LEN=256; char buffer[LEN]; - int len = host->receiveRawPacket(buffer, LEN, &sender, 2000); + int len = STKHost::get()->receiveRawPacket(buffer, LEN, &sender, 2000); if(len<0) { Log::warn("ConnectToServer", @@ -333,7 +396,6 @@ void ConnectToServer::waitingAloha(bool is_wan) BareNetworkString message(buffer, len); std::string received; message.decodeString(&received); - host->startListening(); // start listening again std::string aloha("aloha_stk"); if (received==aloha) { @@ -360,7 +422,9 @@ bool ConnectToServer::notifyEventAsynchronous(Event* event) { Log::info("ConnectToServer", "The Connect To Server protocol has " "received an event notifying that he's connected to the peer."); - m_state = CONNECTED; // we received a message, we are connected + // We received a message and connected, no need to check for address + // as only 1 peer possible in client + m_state = CONNECTED; } return true; } // notifyEventAsynchronous diff --git a/src/network/protocols/connect_to_server.hpp b/src/network/protocols/connect_to_server.hpp index 71caf0db2..6d755e465 100644 --- a/src/network/protocols/connect_to_server.hpp +++ b/src/network/protocols/connect_to_server.hpp @@ -56,7 +56,7 @@ private: void registerWithSTKServer(); void waitingAloha(bool is_wan); - + bool handleDirectConnect(); public: ConnectToServer(std::shared_ptr server); virtual ~ConnectToServer(); diff --git a/src/network/protocols/game_events_protocol.cpp b/src/network/protocols/game_events_protocol.cpp index 8daaf3864..d03f0f3c7 100644 --- a/src/network/protocols/game_events_protocol.cpp +++ b/src/network/protocols/game_events_protocol.cpp @@ -8,7 +8,7 @@ #include "modes/world.hpp" #include "network/event.hpp" #include "network/game_setup.hpp" -#include "network/protocol_manager.hpp" +#include "network/network_config.hpp" #include "network/stk_host.hpp" #include "network/stk_peer.hpp" @@ -82,30 +82,23 @@ bool GameEventsProtocol::notifyEvent(Event* event) */ void GameEventsProtocol::collectedItem(Item* item, AbstractKart* kart) { - GameSetup* setup = STKHost::get()->getGameSetup(); - assert(setup); + NetworkString *ns = getNetworkString(7); + ns->setSynchronous(true); + // Item picked : send item id, powerup type and kart race id + uint8_t powerup = 0; + if (item->getType() == Item::ITEM_BANANA) + powerup = (int)(kart->getAttachment()->getType()); + else if (item->getType() == Item::ITEM_BONUS_BOX) + powerup = (((int)(kart->getPowerup()->getType()) << 4) & 0xf0) + + (kart->getPowerup()->getNum() & 0x0f); - const std::vector &peers = STKHost::get()->getPeers(); - for (unsigned int i = 0; i < peers.size(); i++) - { - NetworkString *ns = getNetworkString(7); - ns->setSynchronous(true); - // Item picked : send item id, powerup type and kart race id - uint8_t powerup = 0; - if (item->getType() == Item::ITEM_BANANA) - powerup = (int)(kart->getAttachment()->getType()); - else if (item->getType() == Item::ITEM_BONUS_BOX) - powerup = (((int)(kart->getPowerup()->getType()) << 4) & 0xf0) - + (kart->getPowerup()->getNum() & 0x0f); - - ns->addUInt8(GE_ITEM_COLLECTED).addUInt32(item->getItemId()) - .addUInt8(powerup).addUInt8(kart->getWorldKartId()); - peers[i]->sendPacket(ns, /*reliable*/true); - delete ns; - Log::info("GameEventsProtocol", - "Notified a peer that a kart collected item %d.", - (int)(kart->getPowerup()->getType())); - } + ns->addUInt8(GE_ITEM_COLLECTED).addUInt32(item->getItemId()) + .addUInt8(powerup).addUInt8(kart->getWorldKartId()); + Log::info("GameEventsProtocol", + "Notified a peer that a kart collected item %d.", + (int)(kart->getPowerup()->getType())); + STKHost::get()->sendPacketToAllPeers(ns, /*reliable*/true); + delete ns; } // collectedItem // ---------------------------------------------------------------------------- @@ -183,7 +176,7 @@ void GameEventsProtocol::clientHasStarted() * ready set go. */ void GameEventsProtocol::receivedClientHasStarted(Event *event) { - assert(NetworkConfig::get()->isServer()); +/* assert(NetworkConfig::get()->isServer()); m_count_ready_clients++; Log::verbose("GameEvent", "Host %d has started ready-set-go: %d out of %d done", @@ -196,5 +189,5 @@ void GameEventsProtocol::receivedClientHasStarted(Event *event) // SIgnal the server to start now - since it is now behind the client // times by the latency of the 'slowest' client. World::getWorld()->startReadySetGo(); - } + }*/ } // receivedClientHasStarted diff --git a/src/network/protocols/game_protocol.cpp b/src/network/protocols/game_protocol.cpp index 766a7e0cd..953cc4bde 100644 --- a/src/network/protocols/game_protocol.cpp +++ b/src/network/protocols/game_protocol.cpp @@ -22,7 +22,6 @@ #include "karts/controller/player_controller.hpp" #include "network/event.hpp" #include "network/network_config.hpp" -#include "network/network_player_profile.hpp" #include "network/game_setup.hpp" #include "network/network_config.hpp" #include "network/network_string.hpp" diff --git a/src/network/protocols/latency_protocol.cpp b/src/network/protocols/latency_protocol.cpp deleted file mode 100644 index 6e9e36967..000000000 --- a/src/network/protocols/latency_protocol.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "network/protocols/latency_protocol.hpp" - -#include "network/event.hpp" -#include "network/network_config.hpp" -#include "network/stk_host.hpp" -#include "network/stk_peer.hpp" -#include "utils/time.hpp" - -//----------------------------------------------------------------------------- -/** This protocol tries to determine the average latency between client and - * server. While this information is not used atm, it might be useful for - * the server to determine how much behind the clients it should start. - * FIXME: ATM the main thread will load the world as part of an update - * of the ProtocolManager (which updates the protocols). Since all protocols - * are locked dusing this update, the synchronisation protocol is actually - * delayed from starting while world is loading (since finding the protocol - * for a message requires the protocol lock) - causing at least two frames - * of significanlty delayed pings :( - */ -LatencyProtocol::LatencyProtocol() - : Protocol(PROTOCOL_SYNCHRONIZATION) -{ - unsigned int size = STKHost::get()->getPeerCount(); - m_pings.resize(size, std::map()); - m_successed_pings.resize(size, 0); - m_total_diff.resize(size, 0); - m_average_ping.resize(size, 0); - m_pings_count = 0; - m_last_time = 0.0; -} // LatencyProtocol - -//----------------------------------------------------------------------------- -LatencyProtocol::~LatencyProtocol() -{ -} // ~LatencyProtocol - -//----------------------------------------------------------------------------- -void LatencyProtocol::setup() -{ - Log::info("LatencyProtocol", "Ready !"); -} // setup - - //----------------------------------------------------------------------------- -/** Called when receiving a message. On the client side the message is a ping - * from the server, which is answered back. On the server the received message - * is a reply to a previous ping request. The server will keep track of - * average latency. - */ -bool LatencyProtocol::notifyEventAsynchronous(Event* event) -{ - if (event->getType() != EVENT_TYPE_MESSAGE) - return true; - if(!checkDataSize(event, 5)) return true; - - const NetworkString &data = event->data(); - uint32_t request = data.getUInt8(); - uint32_t sequence = data.getUInt32(); - - const std::vector &peers = STKHost::get()->getPeers(); - assert(peers.size() > 0); - - // Find the right peer id. The host id (i.e. each host sendings its - // host id) can not be used here, since host ids can have gaps (if a - // host should disconnect) - uint8_t peer_id = -1; - for (unsigned int i = 0; i < peers.size(); i++) - { - if (peers[i]->isSamePeer(event->getPeer())) - { - peer_id = i; - break; - } - } - - if (request) - { - // Only a client should receive a request for a ping response - assert(NetworkConfig::get()->isClient()); - NetworkString *response = getNetworkString(5); - // The '0' indicates a response to a ping request - response->addUInt8(0).addUInt32(sequence); - event->getPeer()->sendPacket(response, false); - delete response; - } - else // receive response to a ping request - { - // Only a server should receive this kind of message - assert(NetworkConfig::get()->isServer()); - if (sequence >= m_pings[peer_id].size()) - { - Log::warn("LatencyProtocol", - "The sequence# %u isn't known.", sequence); - return true; - } - double current_time = StkTime::getRealTime(); - m_total_diff[peer_id] += current_time - m_pings[peer_id][sequence]; - m_successed_pings[peer_id]++; - m_average_ping[peer_id] = - (int)((m_total_diff[peer_id]/m_successed_pings[peer_id])*1000.0); - - Log::debug("LatencyProtocol", - "Peer %d sequence %d ping %u average %u at %lf", - peer_id, sequence, - (unsigned int)((current_time - m_pings[peer_id][sequence])*1000), - m_average_ping[peer_id], - StkTime::getRealTime()); - } - return true; -} // notifyEventAsynchronous - -//----------------------------------------------------------------------------- -/** Waits for the countdown to be started. On the server the start of the - * countdown is triggered by ServerLobby::finishedLoadingWorld(), - * which is called once all clients have confirmed that they are ready to - * start. The server will send a ping request to each client once a second, - * and include the information if the countdown has started (and its current - * value). On the client the countdown is started in notifyEvenAsynchronous() - * when a server ping is received that indicates that the countdown has - * started. The measured times can be used later to estimate the latency - * between server and client. - */ -void LatencyProtocol::asynchronousUpdate() -{ - float current_time = float(StkTime::getRealTime()); - if (NetworkConfig::get()->isServer() && current_time > m_last_time+1) - { - const std::vector &peers = STKHost::get()->getPeers(); - for (unsigned int i = 0; i < peers.size(); i++) - { - NetworkString *ping_request = - getNetworkString(5); - ping_request->addUInt8(1).addUInt32((int)m_pings[i].size()); - m_pings[i] [ m_pings_count ] = current_time; - peers[i]->sendPacket(ping_request, false); - delete ping_request; - } // for i M peers - m_last_time = current_time; - m_pings_count++; - } // if current_time > m_last_time + 0.1 -} // asynchronousUpdate - diff --git a/src/network/protocols/latency_protocol.hpp b/src/network/protocols/latency_protocol.hpp deleted file mode 100644 index 92baa6caf..000000000 --- a/src/network/protocols/latency_protocol.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef LATENCY_PROTOCOL_HPP -#define LATENCY_PROTOCOL_HPP - -#include "network/protocol.hpp" -#include "utils/cpp2011.hpp" - -#include -#include - -class LatencyProtocol : public Protocol -{ -private: - std::vector > m_pings; - std::vector m_average_ping; - - /** Counts the number of pings sent. */ - uint32_t m_pings_count; - std::vector m_successed_pings; - std::vector m_total_diff; - - /** Keeps track of last time that an update was sent. */ - double m_last_time; - - -public: - LatencyProtocol(); - virtual ~LatencyProtocol(); - - virtual bool notifyEventAsynchronous(Event* event) OVERRIDE; - virtual void setup() OVERRIDE; - virtual void asynchronousUpdate() OVERRIDE; - - // ------------------------------------------------------------------------ - virtual void update(int ticks) OVERRIDE {} - -}; // class LatencyProtocol - -#endif // LATENCY_PROTOCOL_HPP diff --git a/src/network/protocols/lobby_protocol.cpp b/src/network/protocols/lobby_protocol.cpp index e1f7a444c..faa4f2807 100644 --- a/src/network/protocols/lobby_protocol.cpp +++ b/src/network/protocols/lobby_protocol.cpp @@ -22,14 +22,12 @@ #include "input/input_manager.hpp" #include "input/device_manager.hpp" #include "modes/world.hpp" +#include "network/game_setup.hpp" #include "network/network_player_profile.hpp" -#include "network/protocol_manager.hpp" #include "network/protocols/game_protocol.hpp" #include "network/protocols/game_events_protocol.hpp" -#include "network/protocols/latency_protocol.hpp" #include "network/race_event_manager.hpp" #include "network/rewind_manager.hpp" -#include "network/stk_host.hpp" #include "race/race_manager.hpp" #include "states_screens/state_manager.hpp" @@ -44,6 +42,8 @@ LobbyProtocol::LobbyProtocol(CallbackObject* callback_object) // ---------------------------------------------------------------------------- LobbyProtocol::~LobbyProtocol() { + if (m_game_setup) + delete m_game_setup; } // ~LobbyProtocol //----------------------------------------------------------------------------- @@ -64,20 +64,39 @@ void LobbyProtocol::loadWorld() // This creates the network world. RaceEventManager::getInstance()->start(); + // Make sure that if there is only a single local player this player can + // use all input devices. + StateManager::ActivePlayer *ap = race_manager->getNumLocalPlayers()>1 + ? NULL + : StateManager::get()->getActivePlayer(0); + + input_manager->getDeviceManager()->setSinglePlayer(ap); + + // Load the actual world. + m_game_setup->loadWorld(); + World::getWorld()->setNetworkWorld(true); + GameProtocol::createInstance()->requestStart(); + std::make_shared()->requestStart(); + +} // loadWorld + +// ---------------------------------------------------------------------------- +void LobbyProtocol::configRemoteKart( + const std::vector >& players) const +{ // The number of karts includes the AI karts, which are not supported atm - race_manager->setNumKarts(m_game_setup->getPlayerCount()); + race_manager->setNumKarts((int)players.size()); // Set number of global and local players. - race_manager->setNumPlayers(m_game_setup->getPlayerCount(), - m_game_setup->getNumLocalPlayers()); + race_manager->setNumPlayers((int)players.size(), + m_game_setup->getNumLocalPlayers()); // Create the kart information for the race manager: // ------------------------------------------------- - std::vector players = m_game_setup->getPlayers(); int local_player_id = 0; for (unsigned int i = 0; i < players.size(); i++) { - NetworkPlayerProfile* profile = players[i]; + std::shared_ptr profile = players[i]; bool is_local = profile->isLocalPlayer(); // All non-local players are created here. This means all players @@ -95,12 +114,13 @@ void LobbyProtocol::loadWorld() // corresponding device associated with it). RemoteKartInfo rki(is_local ? local_player_id : i - local_player_id - + STKHost::get()->getGameSetup()->getNumLocalPlayers(), + + m_game_setup->getNumLocalPlayers(), profile->getKartName(), profile->getName(), profile->getHostId(), !is_local); - rki.setGlobalPlayerId(profile->getGlobalPlayerId()); + rki.setGlobalPlayerId(i); + rki.setDefaultKartColor(profile->getDefaultKartColor()); rki.setPerPlayerDifficulty(profile->getPerPlayerDifficulty()); if (is_local) { @@ -111,29 +131,16 @@ void LobbyProtocol::loadWorld() // Inform the race manager about the data for this kart. race_manager->setPlayerKart(i, rki); } // for i in players - - // Make sure that if there is only a single local player this player can - // use all input devices. - StateManager::ActivePlayer *ap = race_manager->getNumLocalPlayers()>1 - ? NULL - : StateManager::get()->getActivePlayer(0); - - input_manager->getDeviceManager()->setSinglePlayer(ap); - Log::info("LobbyProtocol", "Player configuration ready."); +} // configRemoteKart - // Load the actual world. - m_game_setup->getRaceConfig()->loadWorld(); - World::getWorld()->setNetworkWorld(true); - GameProtocol::createInstance()->requestStart(); - std::make_shared()->requestStart(); - -} // loadWorld - -// ---------------------------------------------------------------------------- -/** Terminates the LatencyProtocol. +//----------------------------------------------------------------------------- +/** A previous GameSetup is deleted and a new one is created. + * \return Newly create GameSetup object. */ -void LobbyProtocol::terminateLatencyProtocol() +void LobbyProtocol::setup() { - ProtocolManager::lock()->findAndTerminate(PROTOCOL_SYNCHRONIZATION); -} // stopLatencyProtocol + if (m_game_setup) + delete m_game_setup; + m_game_setup = new GameSetup(); +} // setupNewGame diff --git a/src/network/protocols/lobby_protocol.hpp b/src/network/protocols/lobby_protocol.hpp index 93a69b7cc..c1ee91284 100644 --- a/src/network/protocols/lobby_protocol.hpp +++ b/src/network/protocols/lobby_protocol.hpp @@ -20,10 +20,14 @@ #define LOBBY_PROTOCOL_HPP #include "network/protocol.hpp" - -#include "network/game_setup.hpp" #include "network/network_string.hpp" +class GameSetup; +class NetworkPlayerProfile; + +#include +#include + /*! * \class LobbyProtocol * \brief Base class for both client and server lobby. The lobbies are started @@ -34,15 +38,15 @@ class LobbyProtocol : public Protocol { public: /** Lists all lobby events (LE). */ - enum - { + enum : uint8_t + { LE_CONNECTION_REQUESTED = 1, // a connection to the server LE_CONNECTION_REFUSED, // Connection to server refused LE_CONNECTION_ACCEPTED, // Connection to server accepted LE_KART_SELECTION_UPDATE, // inform client about kart selected LE_REQUEST_BEGIN, // begin of kart selection LE_KART_SELECTION_REFUSED, // Client not auth. to start selection - LE_NEW_PLAYER_CONNECTED, // inform client about new player + LE_UPDATE_PLAYER_LIST, // inform client about player list update LE_KART_SELECTION, // Player selected kart LE_PLAYER_DISCONNECTED, // Client disconnected LE_CLIENT_LOADED_WORLD, // Client finished loading world @@ -53,21 +57,30 @@ public: LE_RACE_FINISHED, // race has finished, display result LE_RACE_FINISHED_ACK, // client went back to lobby LE_EXIT_RESULT, // Force clients to exit race result screen - LE_VOTE, // Any vote (race mode, track, ...) - LE_VOTE_MAJOR, // vote of major race mode - LE_VOTE_MINOR, // vote for minor race mode - LE_VOTE_RACE_COUNT, // vote for number of tracks - LE_VOTE_TRACK, // vote for a track - LE_VOTE_REVERSE, // vote if race in reverse - LE_VOTE_LAPS, // vote number of laps + LE_VOTE, // Track vote + LE_CHAT, + LE_SERVER_OWNERSHIP, + LE_KICK_HOST + }; + + enum RejectReason : uint8_t + { + RR_BUSY = 0, + RR_BANNED = 1, + RR_INCORRECT_PASSWORD = 2, + RR_INCOMPATIBLE_DATA = 3, + RR_TOO_MANY_PLAYERS = 4 }; protected: static std::weak_ptr m_lobby; - /** The game setup. */ + /** Stores data about the online game to play. */ GameSetup* m_game_setup; + void configRemoteKart( + const std::vector >& players) const; + public: /** Creates either a client or server lobby protocol as a singleton. */ @@ -101,12 +114,7 @@ public: virtual void finishedLoadingWorld() = 0; virtual void loadWorld(); virtual bool waitingForPlayers() const = 0; - void terminateLatencyProtocol(); - virtual void requestKartSelection(uint8_t player_id, - const std::string &kart_name) - { - assert(false); // Only defined in client - }; + GameSetup* getGameSetup() const { return m_game_setup; } }; // class LobbyProtocol diff --git a/src/network/protocols/request_connection.cpp b/src/network/protocols/request_connection.cpp index 65fcf964a..d6d12f028 100644 --- a/src/network/protocols/request_connection.cpp +++ b/src/network/protocols/request_connection.cpp @@ -113,7 +113,14 @@ void RequestConnection::asynchronousUpdate() // Direct socket always listens on server discovery port server_addr.setPort(NetworkConfig::get() ->getServerDiscoveryPort()); - STKHost::get()->sendRawPacket(message, server_addr); + // Avoid possible packet loss, the connect to peer done by + // server will auto terminate if same peer from same port + // has connected already + for (int i = 0; i < 5; i++) + { + STKHost::get()->sendRawPacket(message, server_addr); + StkTime::sleep(1); + } m_state = DONE; } else diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 738b2a704..3a9dff7a0 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -22,10 +22,10 @@ #include "karts/kart_properties_manager.hpp" #include "modes/world.hpp" #include "network/event.hpp" +#include "network/game_setup.hpp" #include "network/network_config.hpp" #include "network/network_player_profile.hpp" #include "network/protocols/connect_to_peer.hpp" -#include "network/protocols/latency_protocol.hpp" #include "network/protocol_manager.hpp" #include "network/race_event_manager.hpp" #include "network/stk_host.hpp" @@ -35,12 +35,13 @@ #include "race/race_manager.hpp" #include "states_screens/networking_lobby.hpp" #include "states_screens/race_result_gui.hpp" -#include "states_screens/waiting_for_others.hpp" #include "tracks/track_manager.hpp" #include "utils/log.hpp" #include "utils/random_generator.hpp" #include "utils/time.hpp" +#include +#include #include /** This is the central game setup protocol running in the server. It is @@ -84,18 +85,10 @@ */ ServerLobby::ServerLobby() : LobbyProtocol(NULL) { + m_has_created_server_id_file = false; setHandleDisconnections(true); m_state = SET_PUBLIC_ADDRESS; - - // We use maximum 16bit unsigned limit - auto all_k = kart_properties_manager->getAllAvailableKarts(); - auto all_t = track_manager->getAllTrackIdentifiers(); - if (all_k.size() >= 65536) - all_k.resize(65535); - if (all_t.size() >= 65536) - all_t.resize(65535); - m_available_kts.getData().first = { all_k.begin(), all_k.end() }; - m_available_kts.getData().second = { all_t.begin(), all_t.end() }; + updateBanList(); } // ServerLobby //----------------------------------------------------------------------------- @@ -110,28 +103,29 @@ ServerLobby::~ServerLobby() } // ~ServerLobby //----------------------------------------------------------------------------- - void ServerLobby::setup() { + LobbyProtocol::setup(); + // We use maximum 16bit unsigned limit + auto all_k = kart_properties_manager->getAllAvailableKarts(); + auto all_t = track_manager->getAllTrackIdentifiers(); + if (all_k.size() >= 65536) + all_k.resize(65535); + if (all_t.size() >= 65536) + all_t.resize(65535); + m_available_kts.first = { all_k.begin(), all_k.end() }; + m_available_kts.second = { all_t.begin(), all_t.end() }; + m_server_registered = false; - m_game_setup = STKHost::get()->setupNewGame(); m_game_setup->setNumLocalPlayers(0); // no local players on a server - m_next_player_id.setAtomic(0); - m_selection_enabled = false; - Log::info("ServerLobby", "Starting the protocol."); // Initialise the data structures to detect if all clients and // the server are ready: - m_player_states.clear(); - m_client_ready_count.setAtomic(0); - m_server_has_loaded_world = false; - const std::vector &players = - m_game_setup->getPlayers(); - for (unsigned int i = 0; i < players.size(); i++) - { - m_player_states[players[i]->getGlobalPlayerId()] = false; - } - + m_server_has_loaded_world.store(false); + m_peers_ready.clear(); + m_peers_votes.clear(); + m_server_delay = 0.0; + Log::info("ServerLobby", "Reset server to initial state."); } // setup //----------------------------------------------------------------------------- @@ -158,7 +152,41 @@ bool ServerLobby::notifyEvent(Event* event) } // notifyEvent //----------------------------------------------------------------------------- +void ServerLobby::handleChat(Event* event) +{ + if (!event->getPeer()->hasPlayerProfiles()) + { + Log::warn("ServerLobby", "Unauthorized peer wants to chat."); + return; + } + core::stringw message; + event->data().decodeStringW(&message); + if (message.size() > 0) + { + NetworkString* chat = getNetworkString(); + chat->setSynchronous(true); + chat->addUInt8(LE_CHAT).encodeString(message); + sendMessageToPeersChangingToken(chat, /*reliable*/true); + delete chat; + } +} // handleChat +//----------------------------------------------------------------------------- +void ServerLobby::kickHost(Event* event) +{ + std::unique_lock lock(m_connection_mutex); + if (m_server_owner.lock() != event->getPeerSP()) + return; + lock.unlock(); + if (!checkDataSize(event, 4)) return; + NetworkString& data = event->data(); + uint32_t host_id = data.getUInt32(); + std::shared_ptr peer = STKHost::get()->findPeerByHostId(host_id); + if (peer) + peer->kick(); +} // kickHost + +//----------------------------------------------------------------------------- bool ServerLobby::notifyEventAsynchronous(Event* event) { assert(m_game_setup); // assert that the setup exists @@ -176,13 +204,11 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) case LE_KART_SELECTION: kartSelectionRequested(event); break; case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break; case LE_STARTED_RACE: startedRaceOnClient(event); break; - case LE_VOTE_MAJOR: playerMajorVote(event); break; - case LE_VOTE_RACE_COUNT: playerRaceCountVote(event); break; - case LE_VOTE_MINOR: playerMinorVote(event); break; - case LE_VOTE_TRACK: playerTrackVote(event); break; - case LE_VOTE_REVERSE: playerReversedVote(event); break; - case LE_VOTE_LAPS: playerLapsVote(event); break; + case LE_VOTE: playerVote(event); break; case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break; + case LE_KICK_HOST: kickHost(event); break; + case LE_CHAT: handleChat(event); break; + default: break; } // switch } // if (event->getType() == EVENT_TYPE_MESSAGE) else if (event->getType() == EVENT_TYPE_DISCONNECTED) @@ -197,12 +223,12 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) void ServerLobby::createServerIdFile() { const std::string& sid = NetworkConfig::get()->getServerIdFile(); - if (!sid.empty()) + if (!sid.empty() && !m_has_created_server_id_file) { std::fstream fs; fs.open(sid, std::ios::out); fs.close(); - NetworkConfig::get()->setServerIdFile(""); + m_has_created_server_id_file = true; } } // createServerIdFile @@ -263,11 +289,98 @@ void ServerLobby::asynchronousUpdate() STKHost::get()->requestShutdown(); break; } + case WAIT_FOR_WORLD_LOADED: + { + // m_server_has_loaded_world is set by main thread with atomic write + if (m_server_has_loaded_world.load() == false) + return; + if (!checkPeersReady()) + return; + m_state = WAIT_FOR_RACE_STARTED; + // Reset for next state usage + for (auto p : m_peers_ready) + { + p.second = false; + } + signalRaceStartToClients(); + break; + } + case WAIT_FOR_RACE_STARTED: + // The function startedRaceOnClient() will trigger the + // next state. + break; + case DELAY_SERVER: + if (m_server_delay < StkTime::getRealTime()) + { + Log::verbose("ServerLobby", "End delay at %lf", + StkTime::getRealTime()); + m_state = RACING; + World::getWorld()->setReadyToRace(); + } + break; + case SELECTING: + if (m_timeout.load() < (float)StkTime::getRealTime()) + { + auto result = handleVote(); + // Remove disconnected player (if any) one last time + m_game_setup->update(true); + auto players = m_game_setup->getConnectedPlayers(); + NetworkString* load_world = getNetworkString(); + load_world->setSynchronous(true); + load_world->addUInt8(LE_LOAD_WORLD).encodeString(std::get<0>(result)) + .addUInt8(std::get<1>(result)).addUInt8(std::get<2>(result)) + .addUInt8((uint8_t)players.size()); + for (auto player : players) + { + load_world->encodeString(player->getName()) + .addUInt32(player->getHostId()) + .addFloat(player->getDefaultKartColor()) + .addUInt32(player->getOnlineId()) + .addUInt8(player->getPerPlayerDifficulty()); + if (player->getKartName().empty()) + { + RandomGenerator rg; + std::set::iterator it = + m_available_kts.first.begin(); + std::advance(it, rg.get((int)m_available_kts.first.size())); + player->setKartName(*it); + } + load_world->encodeString(player->getKartName()); + } + // TODO: sort players for grand prix here + configRemoteKart(players); + + // Reset for next state usage + for (auto p : m_peers_ready) + { + p.second = false; + } + m_state = LOAD_WORLD; + sendMessageToPeersChangingToken(load_world); + delete load_world; + } + break; default: break; } + } // asynchronousUpdate +//----------------------------------------------------------------------------- +bool ServerLobby::checkPeersReady() const +{ + bool all_ready = true; + for (auto p : m_peers_ready) + { + if (p.first.expired()) + continue; + all_ready = all_ready && p.second; + if (!all_ready) + return false; + } + return true; +} // checkPeersReady + //----------------------------------------------------------------------------- /** Simple finite state machine. Once this * is known, register the server and its address with the stk server so that @@ -275,11 +388,22 @@ void ServerLobby::asynchronousUpdate() */ void ServerLobby::update(int ticks) { + // Check if server owner has left + updateServerOwner(); + if (m_game_setup) + { + // Remove disconnected players if in these two states + m_game_setup->update(m_state.load() == ACCEPTING_CLIENTS || + m_state.load() == SELECTING); + } switch (m_state.load()) { case SET_PUBLIC_ADDRESS: case REGISTER_SELF_ADDRESS: case ACCEPTING_CLIENTS: + case WAIT_FOR_WORLD_LOADED: + case WAIT_FOR_RACE_STARTED: + case DELAY_SERVER: { // Waiting for asynchronousUpdate break; @@ -294,33 +418,6 @@ void ServerLobby::update(int ticks) loadWorld(); m_state = WAIT_FOR_WORLD_LOADED; break; - case WAIT_FOR_WORLD_LOADED: - // Note that m_server_has_loaded_world is called by the main thread - // (same as the thread updating this protocol) - m_client_ready_count.lock(); - if (m_server_has_loaded_world && - m_client_ready_count.getData() == m_game_setup->getPlayerCount()) - { - signalRaceStartToClients(); - m_client_ready_count.getData() = 0; - } - m_client_ready_count.unlock(); - // Initialise counter again, to wait for all clients to indicate that - // they have started the race/ - break; - case WAIT_FOR_RACE_STARTED: - // The function startedRaceOnClient() will trigger the - // next state. - break; - case DELAY_SERVER: - if (m_server_delay < StkTime::getRealTime()) - { - Log::verbose("ServerLobby", "End delay at %lf", - StkTime::getRealTime()); - m_state = RACING; - World::getWorld()->setReadyToRace(); - } - break; case RACING: if (World::getWorld() && RaceEventManager::getInstance()->isRunning()) @@ -329,18 +426,8 @@ void ServerLobby::update(int ticks) } break; case RESULT_DISPLAY: - if(StkTime::getRealTime() > m_timeout) + if(StkTime::getRealTime() > m_timeout.load()) { - // Send a notification to all clients to exit - // the race result screen - NetworkString *exit_result_screen = getNetworkString(1); - exit_result_screen->setSynchronous(true); - exit_result_screen->addUInt8(LE_EXIT_RESULT); - sendMessageToPeersChangingToken(exit_result_screen, - /*reliable*/true); - delete exit_result_screen; - m_state = NetworkConfig::get()->isLAN() ? - ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS; RaceResultGUI::getInstance()->backToLobby(); // notify the network world that it is stopped RaceEventManager::getInstance()->stop(); @@ -351,6 +438,16 @@ void ServerLobby::update(int ticks) pm->findAndTerminate(PROTOCOL_KART_UPDATE); pm->findAndTerminate(PROTOCOL_GAME_EVENTS); setup(); + m_state = NetworkConfig::get()->isLAN() ? + ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS; + // Send a notification to all clients to exit + // the race result screen + NetworkString *exit_result_screen = getNetworkString(1); + exit_result_screen->setSynchronous(true); + exit_result_screen->addUInt8(LE_EXIT_RESULT); + sendMessageToPeersChangingToken(exit_result_screen, + /*reliable*/true); + delete exit_result_screen; } break; case ERROR_LEAVE: @@ -382,6 +479,11 @@ void ServerLobby::registerServer() request->addParameter("game_mode", NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(), race_manager->getMajorMode())); + request->addParameter("password", + (unsigned)(!NetworkConfig::get()->getPassword().empty())); + request->addParameter("version", + (unsigned)NetworkConfig::m_server_version); + Log::info("ServerLobby", "Public server addr %s", addr.toString().c_str()); request->executeNow(); @@ -450,12 +552,10 @@ void ServerLobby::signalRaceStartToClients() { Log::verbose("Server", "Signaling race start to clients at %lf", StkTime::getRealTime()); - const std::vector &peers = STKHost::get()->getPeers(); NetworkString *ns = getNetworkString(1); ns->addUInt8(LE_START_RACE); sendMessageToPeersChangingToken(ns, /*reliable*/true); delete ns; - m_state = WAIT_FOR_RACE_STARTED; } // startGame //----------------------------------------------------------------------------- @@ -464,12 +564,7 @@ void ServerLobby::signalRaceStartToClients() */ void ServerLobby::startSelection(const Event *event) { - if (NetworkConfig::get()->isWAN()) - { - assert(m_server_registered); - unregisterServer(); - m_server_registered = false; - } + std::lock_guard lock(m_connection_mutex); if (m_state != ACCEPTING_CLIENTS) { @@ -478,22 +573,28 @@ void ServerLobby::startSelection(const Event *event) m_state.load()); return; } - if(event && !event->getPeer()->isAuthorised()) + if (event->getPeerSP() != m_server_owner.lock()) { Log::warn("ServerLobby", "Client %lx is not authorised to start selection.", event->getPeer()); return; } - const std::vector &peers = STKHost::get()->getPeers(); + + if (NetworkConfig::get()->isWAN()) + { + assert(m_server_registered); + unregisterServer(); + m_server_registered = false; + } + NetworkString *ns = getNetworkString(1); // Start selection - must be synchronous since the receiver pushes // a new screen, which must be donefrom the main thread. ns->setSynchronous(true); ns->addUInt8(LE_START_SELECTION); - m_available_kts.lock(); - const auto& all_k = m_available_kts.getData().first; - const auto& all_t = m_available_kts.getData().second; + const auto& all_k = m_available_kts.first; + const auto& all_t = m_available_kts.second; ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size()); for (const std::string& kart : all_k) { @@ -503,19 +604,14 @@ void ServerLobby::startSelection(const Event *event) { ns->encodeString(track); } - m_available_kts.unlock(); sendMessageToPeersChangingToken(ns, /*reliable*/true); delete ns; - m_selection_enabled = true; - m_state = SELECTING; - WaitingForOthersScreen::getInstance()->push(); - - std::make_shared()->requestStart(); - Log::info("LobbyProtocol", "LatencyProtocol started."); + // Will be changed after the first vote received + m_timeout.store(std::numeric_limits::max()); } // startSelection //----------------------------------------------------------------------------- @@ -544,7 +640,7 @@ void ServerLobby::checkIncomingConnectionRequests() const TransportAddress &addr = STKHost::get()->getPublicAddress(); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort()); - request->addParameter("current_players", STKHost::get()->getPeerCount()); + request->addParameter("current_players", m_game_setup->getPlayerCount()); request->executeNow(); assert(request->isDone()); @@ -585,7 +681,7 @@ void ServerLobby::checkIncomingConnectionRequests() m_player_ready_counter = 0; // Set the delay before the server forces all clients to exit the race // result screen and go back to the lobby - m_timeout = (float)(StkTime::getRealTime()+15.0f); + m_timeout.store((float)(StkTime::getRealTime()+15.0f)); m_state = RESULT_DISPLAY; // calculate karts ranks : @@ -606,8 +702,6 @@ void ServerLobby::checkIncomingConnectionRequests() } } - const std::vector &peers = STKHost::get()->getPeers(); - NetworkString *total = getNetworkString(1 + karts_results.size()); total->setSynchronous(true); total->addUInt8(LE_RACE_FINISHED); @@ -629,25 +723,23 @@ void ServerLobby::checkIncomingConnectionRequests() */ void ServerLobby::clientDisconnected(Event* event) { - std::vector players_on_host = - event->getPeer()->getAllPlayerProfiles(); + std::lock_guard lock(m_connection_mutex); + auto players_on_peer = event->getPeer()->getPlayerProfiles(); + if (players_on_peer.empty()) + return; - NetworkString *msg = getNetworkString(2); + NetworkString* msg = getNetworkString(2); msg->addUInt8(LE_PLAYER_DISCONNECTED); - - for(unsigned int i=0; iaddUInt8((uint8_t)players_on_peer.size()); + for (auto p : players_on_peer) { - msg->addUInt8(players_on_host[i]->getGlobalPlayerId()); - Log::info("ServerLobby", "Player disconnected : id %d", - players_on_host[i]->getGlobalPlayerId()); - m_game_setup->removePlayer(players_on_host[i]); + std::string name = StringUtils::wideToUtf8(p->getName()); + msg->encodeString(name); + Log::info("ServerLobby", "%s disconnected", name.c_str()); } - sendMessageToPeersChangingToken(msg, /*reliable*/true); - // Remove the profile from the peer (to avoid double free) - STKHost::get()->removePeer(event->getPeer()); + updatePlayerList(); delete msg; - } // clientDisconnected //----------------------------------------------------------------------------- @@ -664,34 +756,103 @@ void ServerLobby::clientDisconnected(Event* event) */ void ServerLobby::connectionRequested(Event* event) { - STKPeer* peer = event->getPeer(); + std::lock_guard lock(m_connection_mutex); + std::shared_ptr peer = event->getPeerSP(); + peer->cleanPlayerProfiles(); + const NetworkString &data = event->data(); // can we add the player ? - if (m_game_setup->getPlayerCount() >= NetworkConfig::get()->getMaxPlayers() || - m_state!=ACCEPTING_CLIENTS ) + if (m_state != ACCEPTING_CLIENTS) { NetworkString *message = getNetworkString(2); - // Len, error code: 2 = busy, 0 = too many players - message->addUInt8(LE_CONNECTION_REFUSED) - .addUInt8(m_state!=ACCEPTING_CLIENTS ? 2 : 0); - - // send only to the peer that made the request + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BUSY); + // send only to the peer that made the request and disconect it now peer->sendPacket(message); + peer->reset(); delete message; - Log::verbose("ServerLobby", "Player refused"); + Log::verbose("ServerLobby", "Player refused: selection started"); + return; + } + + // Check for password + std::string password; + data.decodeString(&password); + if (password != NetworkConfig::get()->getPassword()) + { + NetworkString *message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED) + .addUInt8(RR_INCORRECT_PASSWORD); + peer->sendPacket(message); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: incorrect password"); + return; + } + + unsigned player_count = data.getUInt8(); + if (m_game_setup->getPlayerCount() + player_count > + NetworkConfig::get()->getMaxPlayers()) + { + NetworkString *message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_TOO_MANY_PLAYERS); + peer->sendPacket(message); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: too many players"); + return; + } + + for (unsigned i = 0; i < player_count; i++) + { + std::string name_u8; + data.decodeString(&name_u8); + core::stringw name = StringUtils::utf8ToWide(name_u8); + float default_kart_color = data.getFloat(); + uint32_t online_id = data.getUInt32(); + PerPlayerDifficulty per_player_difficulty = + (PerPlayerDifficulty)data.getUInt8(); + peer->addPlayer(std::make_shared + (peer, name, peer->getHostId(), default_kart_color, online_id, + per_player_difficulty)); + } + + bool is_banned = false; + auto ret = m_ban_list.find(peer->getAddress().getIP()); + if (ret != m_ban_list.end()) + { + // Ban all players + if (ret->second == 0) + { + is_banned = true; + } + else + { + for (auto& p : peer->getPlayerProfiles()) + { + if (ret->second == p->getOnlineId()) + { + is_banned = true; + break; + } + } + } + } + + if (is_banned) + { + NetworkString *message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BANNED); + peer->cleanPlayerProfiles(); + peer->sendPacket(message); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: banned"); return; } // Connection accepted. // ==================== - std::string name_u8; - data.decodeString(&name_u8); - core::stringw name = StringUtils::utf8ToWide(name_u8); - std::string password; - data.decodeString(&password); - bool is_authorised = (password==NetworkConfig::get()->getPassword()); - std::set client_karts, client_tracks; const unsigned kart_num = data.getUInt16(); const unsigned track_num = data.getUInt16(); @@ -712,14 +873,14 @@ void ServerLobby::connectionRequested(Event* event) // so that in the end the server has a list of all karts/tracks available // on all clients std::set karts_erase, tracks_erase; - for (const std::string& server_kart : m_available_kts.getData().first) + for (const std::string& server_kart : m_available_kts.first) { if (client_karts.find(server_kart) == client_karts.end()) { karts_erase.insert(server_kart); } } - for (const std::string& server_track : m_available_kts.getData().second) + for (const std::string& server_track : m_available_kts.second) { if (client_tracks.find(server_track) == client_tracks.end()) { @@ -729,341 +890,290 @@ void ServerLobby::connectionRequested(Event* event) // Drop this player if he doesn't have at least 1 kart / track the same // from server - if (karts_erase.size() == m_available_kts.getData().first.size() || - tracks_erase.size() == m_available_kts.getData().second.size()) + if (karts_erase.size() == m_available_kts.first.size() || + tracks_erase.size() == m_available_kts.second.size()) { NetworkString *message = getNetworkString(2); - message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(3); + message->addUInt8(LE_CONNECTION_REFUSED) + .addUInt8(RR_INCOMPATIBLE_DATA); + peer->cleanPlayerProfiles(); peer->sendPacket(message); + peer->reset(); delete message; Log::verbose("ServerLobby", "Player has incompatible karts / tracks"); - m_available_kts.unlock(); return; } for (const std::string& kart_erase : karts_erase) { - m_available_kts.getData().first.erase(kart_erase); + m_available_kts.first.erase(kart_erase); } for (const std::string& track_erase : tracks_erase) { - m_available_kts.getData().second.erase(track_erase); + m_available_kts.second.erase(track_erase); } - m_available_kts.unlock(); - // Get the unique global ID for this player. - m_next_player_id.lock(); - m_next_player_id.getData()++; - int new_player_id = m_next_player_id.getData(); - m_next_player_id.unlock(); - if(m_game_setup->getLocalMasterID()==0) - m_game_setup->setLocalMaster(new_player_id); - - // The host id has already been incremented when the peer - // was added, so it is the right id now. - int new_host_id = STKHost::get()->getNextHostId(); - - // Notify everybody that there is a new player - // ------------------------------------------- - NetworkString *message = getNetworkString(3+1+name_u8.size()); - // size of id -- id -- size of local id -- local id; - message->addUInt8(LE_NEW_PLAYER_CONNECTED).addUInt8(new_player_id) - .addUInt8(new_host_id).encodeString(name_u8); - STKHost::get()->sendPacketExcept(peer, message); - delete message; - - // Now answer to the peer that just connected - // ------------------------------------------ - RandomGenerator token_generator; - // use 4 random numbers because rand_max is probably 2^15-1. - uint32_t token = (uint32_t)((token_generator.get(RAND_MAX) & 0xff) << 24 | - (token_generator.get(RAND_MAX) & 0xff) << 16 | - (token_generator.get(RAND_MAX) & 0xff) << 8 | - (token_generator.get(RAND_MAX) & 0xff)); - - peer->setClientServerToken(token); - peer->setAuthorised(is_authorised); - peer->setHostId(new_host_id); - - const std::vector &players = m_game_setup->getPlayers(); - // send a message to the one that asked to connect - // Estimate 10 as average name length - NetworkString *message_ack = getNetworkString(4 + players.size() * (2+10)); - // connection success -- size of token -- token - message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt8(new_player_id) - .addUInt8(new_host_id).addUInt8(is_authorised); - // Add all players so that this user knows (this new player is only added - // to the list of players later, so the new player's info is not included) - for (unsigned int i = 0; i < players.size(); i++) + if (!peer->isClientServerTokenSet()) { - message_ack->addUInt8(players[i]->getGlobalPlayerId()) - .addUInt8(players[i]->getHostId()) - .encodeString(players[i]->getName()); + // Now answer to the peer that just connected + // ------------------------------------------ + RandomGenerator token_generator; + // use 4 random numbers because rand_max is probably 2^15-1. + uint32_t token = (uint32_t)((token_generator.get(RAND_MAX) & 0xff) << 24 | + (token_generator.get(RAND_MAX) & 0xff) << 16 | + (token_generator.get(RAND_MAX) & 0xff) << 8 | + (token_generator.get(RAND_MAX) & 0xff)); + + peer->setClientServerToken(token); } + // send a message to the one that asked to connect + NetworkString *message_ack = getNetworkString(4); + // connection success -- return the host id of peer + message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt32(peer->getHostId()); peer->sendPacket(message_ack); delete message_ack; - NetworkPlayerProfile* profile = - new NetworkPlayerProfile(name, new_player_id, new_host_id); - m_game_setup->addPlayer(profile); - NetworkingLobby::getInstance()->addPlayer(profile); - - Log::verbose("ServerLobby", "New player."); - + m_peers_ready[peer] = false; + for (std::shared_ptr npp : peer->getPlayerProfiles()) + { + m_game_setup->addPlayer(npp); + Log::info("ServerLobby", "New player %s with online id %u from %s.", + StringUtils::wideToUtf8(npp->getName()).c_str(), + npp->getOnlineId(), peer->getAddress().toString().c_str()); + } + updatePlayerList(); } // connectionRequested //----------------------------------------------------------------------------- +void ServerLobby::updatePlayerList() +{ + if (m_state.load() != ACCEPTING_CLIENTS) + return; + auto all_profiles = STKHost::get()->getAllPlayerProfiles(); + NetworkString* pl = getNetworkString(); + pl->setSynchronous(true); + pl->addUInt8(LE_UPDATE_PLAYER_LIST).addUInt8((uint8_t)all_profiles.size()); + for (auto profile : all_profiles) + { + pl->addUInt32(profile->getHostId()).addUInt32(profile->getOnlineId()) + .encodeString(profile->getName()); + uint8_t server_owner = 0; + if (m_server_owner.lock() == profile->getPeer()) + server_owner = 1; + pl->addUInt8(server_owner); + } + sendMessageToPeersChangingToken(pl); + delete pl; +} // updatePlayerList -/*! \brief Called when a player asks to select a kart. +//----------------------------------------------------------------------------- +void ServerLobby::updateServerOwner() +{ + if (m_state.load() < ACCEPTING_CLIENTS || + m_state.load() > RESULT_DISPLAY) + return; + if (!m_server_owner.expired()) + return; + auto peers = STKHost::get()->getPeers(); + if (peers.empty()) + return; + std::sort(peers.begin(), peers.end(), [](const std::shared_ptr a, + const std::shared_ptr b)->bool + { + return a->getHostId() < b->getHostId(); + }); + + std::shared_ptr owner; + std::lock_guard lock(m_connection_mutex); + // Make sure no one access the weak pointer or adding player to peers + for (auto peer: peers) + { + // Only 127.0.0.1 can be server owner in case of graphics-client-server + if (peer->hasPlayerProfiles() && + (NetworkConfig::get()->getServerIdFile().empty() || + peer->getAddress().getIP() == 0x7f000001)) + { + owner = peer; + break; + } + } + if (owner) + { + NetworkString* ns = getNetworkString(); + ns->addUInt8(LE_SERVER_OWNERSHIP); + owner->sendPacket(ns); + delete ns; + m_server_owner = owner; + updatePlayerList(); + } +} // updateServerOwner + +//----------------------------------------------------------------------------- +/*! \brief Called when a player asks to select karts. * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * ---------------------------------------------- - * Size | 1 | 1 | N | - * Data |player id | N (kart name size) | kart name | - * ---------------------------------------------- */ void ServerLobby::kartSelectionRequested(Event* event) { - if(m_state!=SELECTING) + std::lock_guard lock(m_connection_mutex); + if (m_state != SELECTING) { - Log::warn("Server", "Received kart selection while in state %d.", + Log::warn("ServerLobby", "Received kart selection while in state %d.", m_state.load()); return; } - if (!checkDataSize(event, 1)) return; + if (!checkDataSize(event, 1) || + event->getPeer()->getPlayerProfiles().empty()) + return; - const NetworkString &data = event->data(); + const NetworkString& data = event->data(); STKPeer* peer = event->getPeer(); - - uint8_t player_id = data.getUInt8(); - std::string kart_name; - data.decodeString(&kart_name); - // check if selection is possible - if (!m_selection_enabled) + unsigned player_count = data.getUInt8(); + for (unsigned i = 0; i < player_count; i++) { - NetworkString *answer = getNetworkString(2); - // selection still not started - answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(2); - peer->sendPacket(answer); - delete answer; - return; + std::string kart; + data.decodeString(&kart); + if (m_available_kts.first.find(kart) == m_available_kts.first.end()) + { + // Will be reset to a random one later + Log::debug("ServerLobby", "Player %d from peer %d chose unknown " + "kart %s, use a random one", i, peer->getHostId(), + kart.c_str()); + continue; + } + else + { + peer->getPlayerProfiles()[i]->setKartName(kart); + } + Log::verbose("ServerLobby", "Player %d from peer %d chose %s", i, + peer->getHostId(), kart.c_str()); } - // check if somebody picked that kart - if (!m_game_setup->isKartAvailable(kart_name)) - { - NetworkString *answer = getNetworkString(2); - // kart is already taken - answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(0); - peer->sendPacket(answer); - delete answer; - return; - } - // check if this kart is authorized - if (!m_game_setup->isKartAllowed(kart_name)) - { - NetworkString *answer = getNetworkString(2); - // kart is not authorized - answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(1); - peer->sendPacket(answer); - delete answer; - return; - } - - // send a kart update to everyone - NetworkString *answer = getNetworkString(3+kart_name.size()); - // This message must be handled synchronously on the client. - answer->setSynchronous(true); - // kart update (3), 1, race id - answer->addUInt8(LE_KART_SELECTION_UPDATE).addUInt8(player_id) - .encodeString(kart_name); - sendMessageToPeersChangingToken(answer); - delete answer; - m_game_setup->setPlayerKart(player_id, kart_name); } // kartSelectionRequested //----------------------------------------------------------------------------- - -/*! \brief Called when a player votes for a major race mode. +/*! \brief Called when a player votes for track(s). * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 - * ------------------------------- - * Size | 1 | 4 | - * Data | player-id | major mode vote | - * ------------------------------- */ -void ServerLobby::playerMajorVote(Event* event) +void ServerLobby::playerVote(Event* event) { - if (!checkDataSize(event, 5)) return; - - NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint32_t major = data.getUInt32(); - m_game_setup->getRaceConfig()->setPlayerMajorVote(player_id, major); - // Send the vote to everybody (including the sender) - NetworkString *other = getNetworkString(6); - other->addUInt8(LE_VOTE_MAJOR).addUInt8(player_id).addUInt32(major); - sendMessageToPeersChangingToken(other); - delete other; -} // playerMajorVote - -//----------------------------------------------------------------------------- -/** \brief Called when a player votes for the number of races in a GP. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 - * --------------------------- - * Size | 1 | 4 | - * Data | player-id | races count | - * --------------------------- - */ -void ServerLobby::playerRaceCountVote(Event* event) -{ - if (!checkDataSize(event, 1)) return; - NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t race_count = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerRaceCountVote(player_id, race_count); - // Send the vote to everybody (including the sender) - NetworkString *other = getNetworkString(3); - other->addUInt8(LE_VOTE_RACE_COUNT).addUInt8(player_id) - .addUInt8(race_count); - sendMessageToPeersChangingToken(other); - delete other; -} // playerRaceCountVote - -//----------------------------------------------------------------------------- - -/*! \brief Called when a player votes for a minor race mode. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 - * ------------------------------- - * Size | 1 | 4 | - * Data | player-id | minor mode vote | - * ------------------------------- - */ -void ServerLobby::playerMinorVote(Event* event) -{ - if (!checkDataSize(event, 1)) return; - NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint32_t minor = data.getUInt32(); - m_game_setup->getRaceConfig()->setPlayerMinorVote(player_id, minor); - - // Send the vote to everybody (including the sender) - NetworkString *other = getNetworkString(3); - other->addUInt8(LE_VOTE_MINOR).addUInt8(player_id).addUInt8(minor); - sendMessageToPeersChangingToken(other); - delete other; -} // playerMinorVote - -//----------------------------------------------------------------------------- - -/*! \brief Called when a player votes for a track. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 3 - * -------------------------------------------------- - * Size | 1 | 1 | 1 | N | - * Data | player id | track number (gp) | N | track name | - * -------------------------------------------------- - */ -void ServerLobby::playerTrackVote(Event* event) -{ - if (!checkDataSize(event, 3)) return; - NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - // As which track this track should be used, e.g. 1st track: Sandtrack - // 2nd track Mathclass, ... - uint8_t track_number = data.getUInt8(); - std::string track_name; - int N = data.decodeString(&track_name); - m_game_setup->getRaceConfig()->setPlayerTrackVote(player_id, track_name, - track_number); - // Send the vote to everybody (including the sender) - NetworkString *other = getNetworkString(3+1+data.size()); - other->addUInt8(LE_VOTE_TRACK).addUInt8(player_id).addUInt8(track_number) - .encodeString(track_name); - sendMessageToPeersChangingToken(other); - delete other; - - // Check if we received all information - if (m_game_setup->getRaceConfig()->getNumTrackVotes() == - m_game_setup->getPlayerCount()) + if (m_state != SELECTING) { - // Inform clients to start loading the world - NetworkString *ns = getNetworkString(1); - ns->setSynchronous(true); - ns->addUInt8(LE_LOAD_WORLD); - sendMessageToPeersChangingToken(ns, /*reliable*/true); - delete ns; - m_state = LOAD_WORLD; // Server can now load world + Log::warn("ServerLobby", "Received track vote while in state %d.", + m_state.load()); + return; } -} // playerTrackVote -//----------------------------------------------------------------------------- -/*! \brief Called when a player votes for the reverse mode of a race - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * -------------------------------------------- - * Size | 1 | 1 | 1 | - * Data | player id | reversed | track number (gp) | - * -------------------------------------------- - */ -void ServerLobby::playerReversedVote(Event* event) + if (!checkDataSize(event, 4) || + event->getPeer()->getPlayerProfiles().empty()) + return; + + // Check if first vote, if so start counter + if (m_timeout.load() == std::numeric_limits::max()) + { + m_timeout.store((float)StkTime::getRealTime() + + UserConfigParams::m_voting_timeout); + } + float remaining_time = m_timeout.load() - (float)StkTime::getRealTime(); + if (remaining_time < 0.0f) + { + return; + } + + NetworkString& data = event->data(); + NetworkString other = NetworkString(PROTOCOL_LOBBY_ROOM); + std::string name = StringUtils::wideToUtf8(event->getPeer() + ->getPlayerProfiles()[0]->getName()); + other.addUInt8(LE_VOTE).addFloat(m_timeout.load()).encodeString(name) + .addUInt32(event->getPeer()->getHostId()); + other += data; + + std::string track_name; + data.decodeString(&track_name); + uint8_t lap = data.getUInt8(); + uint8_t reverse = data.getUInt8(); + m_peers_votes[event->getPeerSP()] = + std::make_tuple(track_name, lap, reverse == 1); + sendMessageToPeersChangingToken(&other); + +} // playerVote + +// ---------------------------------------------------------------------------- +std::tuple ServerLobby::handleVote() { - if (!checkDataSize(event, 3)) return; + // Default settings if no votes at all + RandomGenerator rg; + std::set::iterator it = m_available_kts.second.begin(); + std::advance(it, rg.get((int)m_available_kts.second.size())); + std::string final_track = *it; + unsigned final_laps = UserConfigParams::m_num_laps; + bool final_reverse = final_track.size() % 2 == 0; - NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t reverse = data.getUInt8(); - uint8_t nb_track = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerReversedVote(player_id, - reverse!=0, nb_track); - // Send the vote to everybody (including the sender) - NetworkString *other = getNetworkString(4); - other->addUInt8(LE_VOTE_REVERSE).addUInt8(player_id).addUInt8(reverse) - .addUInt8(nb_track); - sendMessageToPeersChangingToken(other); - delete other; -} // playerReversedVote + std::map tracks; + std::map laps; + std::map reverses; + for (auto p : m_peers_votes) + { + if (p.first.expired()) + continue; + auto track_vote = tracks.find(std::get<0>(p.second)); + if (track_vote == tracks.end()) + tracks[std::get<0>(p.second)] = 1; + else + track_vote->second++; + auto lap_vote = laps.find(std::get<1>(p.second)); + if (lap_vote == laps.end()) + laps[std::get<1>(p.second)] = 1; + else + lap_vote->second++; + auto reverse_vote = reverses.find(std::get<2>(p.second)); + if (reverse_vote == reverses.end()) + reverses[std::get<2>(p.second)] = 1; + else + reverse_vote->second++; + } -//----------------------------------------------------------------------------- -/*! \brief Called when a player votes for a major race mode. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 2 - * ---------------------------------------- - * Size | 1 | 1 | 1 | - * Data | player id | laps | track number (gp) | - * ---------------------------------------- - */ -void ServerLobby::playerLapsVote(Event* event) -{ - if (!checkDataSize(event, 2)) return; - NetworkString &data = event->data(); - uint8_t player_id = data.getUInt8(); - uint8_t lap_count = data.getUInt8(); - uint8_t track_nb = data.getUInt8(); - m_game_setup->getRaceConfig()->setPlayerLapsVote(player_id, lap_count, - track_nb); - NetworkString *other = getNetworkString(4); - other->addUInt8(LE_VOTE_LAPS).addUInt8(player_id).addUInt8(lap_count) - .addUInt8(track_nb); - sendMessageToPeersChangingToken(other); - delete other; -} // playerLapsVote + unsigned vote = 0; + auto track_vote = tracks.begin(); + for (auto c_vote = tracks.begin(); c_vote != tracks.end(); c_vote++) + { + if (c_vote->second > vote) + { + vote = c_vote->second; + track_vote = c_vote; + } + } + if (track_vote != tracks.end()) + final_track = track_vote->first; + + vote = 0; + auto lap_vote = laps.begin(); + for (auto c_vote = laps.begin(); c_vote != laps.end(); c_vote++) + { + if (c_vote->second > vote) + { + vote = c_vote->second; + lap_vote = c_vote; + } + } + if (lap_vote != laps.end()) + final_laps = lap_vote->first; + + vote = 0; + auto reverse_vote = reverses.begin(); + for (auto c_vote = reverses.begin(); c_vote != reverses.end(); c_vote++) + { + if (c_vote->second > vote) + { + vote = c_vote->second; + reverse_vote = c_vote; + } + } + if (reverse_vote != reverses.end()) + final_reverse = reverse_vote->first; + + m_game_setup->setRace(final_track, final_laps, final_reverse); + return std::make_tuple(final_track, final_laps, final_reverse); +} // handleVote // ---------------------------------------------------------------------------- /** Called from the RaceManager of the server when the world is loaded. Marks @@ -1071,7 +1181,7 @@ void ServerLobby::playerLapsVote(Event* event) */ void ServerLobby::finishedLoadingWorld() { - m_server_has_loaded_world = true; + m_server_has_loaded_world.store(true); } // finishedLoadingWorld; //----------------------------------------------------------------------------- @@ -1080,30 +1190,10 @@ void ServerLobby::finishedLoadingWorld() */ void ServerLobby::finishedLoadingWorldClient(Event *event) { - if (!checkDataSize(event, 1)) return; - - const NetworkString &data = event->data(); - uint8_t player_count = data.getUInt8(); - m_client_ready_count.lock(); - for (unsigned int i = 0; i < player_count; i++) - { - uint8_t player_id = data.getUInt8(); - if (m_player_states[player_id]) - { - Log::error("ServerLobbyProtocol", - "Player %d send more than one ready message.", - player_id); - m_client_ready_count.unlock(); - return; - } - m_player_states[player_id] = true; - m_client_ready_count.getData()++; - Log::info("ServerLobbyeProtocol", "Player %d is ready (%d/%d).", - player_id, m_client_ready_count.getData(), - m_game_setup->getPlayerCount()); - } - m_client_ready_count.unlock(); - + std::shared_ptr peer = event->getPeerSP(); + m_peers_ready.at(peer) = true; + Log::info("ServerLobby", "Peer %d has finished loading world", + peer->getHostId()); } // finishedLoadingWorldClient //----------------------------------------------------------------------------- @@ -1117,20 +1207,18 @@ void ServerLobby::finishedLoadingWorldClient(Event *event) */ void ServerLobby::startedRaceOnClient(Event *event) { - m_client_ready_count.lock(); - Log::verbose("ServerLobby", "Host %d has started race at %lf.", - event->getPeer()->getHostId(), StkTime::getRealTime()); - m_client_ready_count.getData()++; - if (m_client_ready_count.getData() == m_game_setup->getPlayerCount()) + std::shared_ptr peer = event->getPeerSP(); + m_peers_ready.at(peer) = true; + Log::info("ServerLobby", "Peer %d has started race at %lf", + peer->getHostId(), StkTime::getRealTime()); + + if (checkPeersReady()) { m_state = DELAY_SERVER; - m_server_delay = StkTime::getRealTime() + 0.1f; + m_server_delay = StkTime::getRealTime() + 0.1; Log::verbose("ServerLobby", "Started delay at %lf set delay to %lf", - StkTime::getRealTime(), - m_server_delay); - terminateLatencyProtocol(); + StkTime::getRealTime(), m_server_delay); } - m_client_ready_count.unlock(); } // startedRaceOnClient //----------------------------------------------------------------------------- @@ -1145,8 +1233,19 @@ void ServerLobby::playerFinishedResult(Event *event) // We can't trigger the world/race exit here, since this is called // from the protocol manager thread. So instead we force the timeout // to get triggered (which is done from the main thread): - m_timeout = 0; + m_timeout.store(0); } } // playerFinishedResult //----------------------------------------------------------------------------- +void ServerLobby::updateBanList() +{ + std::lock_guard lock(m_connection_mutex); + m_ban_list.clear(); + for (auto& ban : UserConfigParams::m_server_ban_list) + { + if (ban.first == "0.0.0.0") + continue; + m_ban_list[TransportAddress(ban.first).getIP()] = ban.second; + } +} // updateBanList diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index c877ac22c..248fa0a2a 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -3,10 +3,15 @@ #include "network/protocols/lobby_protocol.hpp" #include "utils/cpp2011.hpp" -#include "utils/synchronised.hpp" #include +#include +#include +#include #include +#include + +class STKPeer; class ServerLobby : public LobbyProtocol { @@ -30,24 +35,24 @@ public: private: std::atomic m_state; + /** Hold the next connected peer for server owner if current one expired + * (disconnected). */ + std::weak_ptr m_server_owner; + /** Available karts and tracks for all clients, this will be initialized * with data in server first. */ - Synchronised, - std::set > > m_available_kts; - - /** Next id to assign to a peer. */ - Synchronised m_next_player_id; + std::pair, std::set > m_available_kts; /** Keeps track of the server state. */ - bool m_server_has_loaded_world; + std::atomic_bool m_server_has_loaded_world; - /** Counts how many clients have finished loading the world. */ - Synchronised m_client_ready_count; + /** Counts how many peers have finished loading the world. */ + std::map, bool, + std::owner_less > > m_peers_ready; - /** For debugging: keep track of the state (ready or not) of each player, - * to make sure no client/player reports more than once. Needs to be a - * map since the client IDs can be non-consecutive. */ - std::map m_player_states; + /** Vote from each peer. */ + std::map, std::tuple, + std::owner_less > > m_peers_votes; /** Keeps track of an artificial server delay (which makes sure that the * data from all clients has arrived when the server computes a certain @@ -55,7 +60,7 @@ private: * seconds), which is the real time at which the server should start. */ double m_server_delay; - bool m_selection_enabled; + bool m_has_created_server_id_file; /** It indicates if this server is registered with the stk server. */ std::atomic_bool m_server_registered; @@ -63,27 +68,36 @@ private: /** Counts how many players are ready to go on. */ int m_player_ready_counter; - /** Timeout counter for showing the result screen. */ - float m_timeout; + /** Timeout counter for various state. */ + std::atomic m_timeout; + + /** Lock this mutex whenever a client is connect / disconnect or + * starting race. */ + std::mutex m_connection_mutex; + + /** Ban list ip (in decimal) with online user id. */ + std::map m_ban_list; // connection management void clientDisconnected(Event* event); void connectionRequested(Event* event); // kart selection void kartSelectionRequested(Event* event); - // race votes - void playerMajorVote(Event* event); - void playerRaceCountVote(Event* event); - void playerMinorVote(Event* event); - void playerTrackVote(Event* event); - void playerReversedVote(Event* event); - void playerLapsVote(Event* event); + // Track(s) votes + void playerVote(Event *event); void playerFinishedResult(Event *event); void registerServer(); void finishedLoadingWorldClient(Event *event); void startedRaceOnClient(Event *event); + void kickHost(Event* event); + void handleChat(Event* event); void unregisterServer(); void createServerIdFile(); + void updatePlayerList(); + void updateServerOwner(); + bool checkPeersReady() const; + std::tuple handleVote(); + public: ServerLobby(); virtual ~ServerLobby(); @@ -98,8 +112,9 @@ public: void startSelection(const Event *event=NULL); void checkIncomingConnectionRequests(); void checkRaceFinished(); - void finishedLoadingWorld(); + void finishedLoadingWorld() OVERRIDE; ServerState getCurrentState() const { return m_state.load(); } + void updateBanList(); virtual bool waitingForPlayers() const OVERRIDE { return m_state.load() == ACCEPTING_CLIENTS; } diff --git a/src/network/race_config.cpp b/src/network/race_config.cpp deleted file mode 100644 index 26f57e218..000000000 --- a/src/network/race_config.cpp +++ /dev/null @@ -1,452 +0,0 @@ -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2014-2015 SuperTuxKart-Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 3 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#include "network/race_config.hpp" - -#include "config/user_config.hpp" -#include "network/network_config.hpp" -#include "race/race_manager.hpp" -#include "utils/log.hpp" - -#include - -/** \brief Gets the element with the highest count in a std::map. - * \param histogram : A pointer to the histogram. - * \return The key of type S that has the highest second value. - */ -template -S getHighestInHistogram(std::map* histogram, S default_value) -{ - if(histogram->empty()) - return default_value; - - S best_item = histogram->begin()->first; - uint8_t highest_count = histogram->begin()->second; - for (typename std::map::iterator it = histogram->begin(); - it != histogram->end(); it++) - { - if (it->second > highest_count) - { - highest_count = it->second; - best_item = it->first; - } - } - return best_item; -} // getHighestInHistogram - -//----------------------------------------------------------------------------- -//--------------------------------- TrackVote -------------------------------- -//----------------------------------------------------------------------------- -/** Constructor for a track vote. - */ -TrackVote::TrackVote() -{ - has_voted_laps = false; - has_voted_track = false; - has_voted_reversed = false; -} // TrackVote - -//----------------------------------------------------------------------------- -/** Sets that this vote is for the specified track. - */ -void TrackVote::voteTrack(const std::string &track) -{ - track_info.track = track; - has_voted_track = true; -} // voteTrack - -//----------------------------------------------------------------------------- -/** Sets if this vote is for normal or reversed driving. - * \param reversed True if this vote is for reversed racing. - */ -void TrackVote::voteReversed(bool reversed) -{ - track_info.reversed = reversed; - has_voted_reversed = true; -} // voteReversed - -//----------------------------------------------------------------------------- -/** Votes for the number of laps. - * \param laps Numger of laps. - */ -void TrackVote::voteLaps(uint8_t laps) -{ - track_info.laps = laps; - has_voted_laps = true; -} // voteLaps - -//----------------------------------------------------------------------------- -//--------------------------------- RaceVote --------------------------------- -//----------------------------------------------------------------------------- -/** Constructor for a race vote. - */ -RaceVote::RaceVote() -{ - m_has_voted_major = false; - m_has_voted_minor = false; - m_has_voted_races_count = false; - m_major_mode = 0; - m_minor_mode = 0; - m_races_count = 0; - m_tracks_vote.resize(1); -} // RaceVote - -//----------------------------------------------------------------------------- -/** Sets the selected major race vote. - */ -void RaceVote::voteMajor(uint32_t major) -{ - m_has_voted_major = true; - m_major_mode = major; -} // voteMajor - -//----------------------------------------------------------------------------- -/** Sets the vote for race count. - */ -void RaceVote::voteRaceCount(uint8_t count) -{ - m_has_voted_races_count = true; - m_races_count = count; -} // voteRaceCount - -//----------------------------------------------------------------------------- -/** Sets vote for minor race mode. - */ -void RaceVote::voteMinor(uint32_t minor) -{ - m_has_voted_minor = true; - m_minor_mode = minor; -} // voteMinor - -//----------------------------------------------------------------------------- -/** Sets a track vote. - * \param track Name of the track. - */ -void RaceVote::voteTrack(const std::string &track, uint8_t track_number) -{ - m_tracks_vote[track_number].voteTrack(track); -} // voteTrack - -//----------------------------------------------------------------------------- -/** Sets a vote for reveresed racing. - */ -void RaceVote::voteReversed(bool reversed, uint8_t track_number) -{ - m_tracks_vote[track_number].voteReversed(reversed); -} // voteReversed - -//----------------------------------------------------------------------------- -/** Sets a vote for number of laps. - */ -void RaceVote::voteLaps(uint8_t laps, uint8_t track_number) -{ - m_tracks_vote[track_number].voteLaps(laps); -} // voteLaps - -//----------------------------------------------------------------------------- -bool RaceVote::hasVotedMajor() const -{ - return m_has_voted_major; -} // hasVotedMajor -//----------------------------------------------------------------------------- -bool RaceVote::hasVotedRacesCount() const -{ - return m_has_voted_races_count; -} // hasVotedRacesCount - -//----------------------------------------------------------------------------- -bool RaceVote::hasVotedMinor() const -{ - return m_has_voted_minor; -} // hasVotedMinor - -//----------------------------------------------------------------------------- -bool RaceVote::hasVotedTrack(uint8_t track_number) const -{ - return m_tracks_vote[track_number].has_voted_track; -} // hasVotedTrack - -//----------------------------------------------------------------------------- -bool RaceVote::hasVotedReversed(uint8_t track_number) const -{ - return m_tracks_vote[track_number].has_voted_reversed; -} // hasVotedReversed - -//----------------------------------------------------------------------------- -bool RaceVote::hasVotedLaps(uint8_t track_number) const -{ - return m_tracks_vote[track_number].has_voted_laps; -} // hasVotedLaps - -//----------------------------------------------------------------------------- -uint8_t RaceVote::getMajorVote() const -{ - return m_major_mode; -} // getMajorVote - -//----------------------------------------------------------------------------- -uint8_t RaceVote::getRacesCountVote() const -{ - return m_races_count; -} // getRacesCountVote - -//----------------------------------------------------------------------------- -uint8_t RaceVote::getMinorVote() const -{ - return m_minor_mode; -} // getMinorVote - -//----------------------------------------------------------------------------- -const std::string &RaceVote::getTrackVote(uint8_t track_number) const -{ - return m_tracks_vote[track_number].track_info.track; -} // getTrackVote - -//----------------------------------------------------------------------------- -bool RaceVote::getReversedVote(uint8_t track_number) const -{ - return m_tracks_vote[track_number].track_info.reversed; -} // getReversedVote - -//----------------------------------------------------------------------------- -uint8_t RaceVote::getLapsVote(uint8_t track_number) const -{ - return m_tracks_vote[track_number].track_info.laps; -} // getLapsVote - -//----------------------------------------------------------------------------- -//--------------------------------- RaceConfig ------------------------------- -//----------------------------------------------------------------------------- - -RaceConfig::RaceConfig() -{ - m_max_players = NetworkConfig::get()->getMaxPlayers(); -} // RaceConfig - -//----------------------------------------------------------------------------- -void RaceConfig::setPlayerMajorVote(uint8_t player_id, uint32_t major) -{ - Log::info("RaceConfig", "Player %d voted for major %d", player_id, major); - m_votes[player_id].voteMajor(major); -} // setPlayerMajorVote - -//----------------------------------------------------------------------------- -void RaceConfig::setPlayerRaceCountVote(uint8_t player_id, uint8_t count) -{ - Log::info("RaceConfig", "Player %d voted for %d races in GP", - player_id, count); - m_votes[player_id].voteRaceCount(count); -} // setPlayerRaceCountVote - -//----------------------------------------------------------------------------- -void RaceConfig::setPlayerMinorVote(uint8_t player_id, uint32_t minor) -{ - Log::info("RaceConfig", "Player %d voted for minor %d", player_id, minor); - m_votes[player_id].voteMinor(minor); -} // setPlayerMinorVote - -//----------------------------------------------------------------------------- -void RaceConfig::setPlayerTrackVote(uint8_t player_id, - const std::string &track, uint8_t track_nb) -{ - Log::info("RaceConfig", "Player %d voted for track %s", - player_id, track.c_str()); - m_votes[player_id].voteTrack(track, track_nb); -} // setPlayerTrackVote - -//----------------------------------------------------------------------------- -void RaceConfig::setPlayerReversedVote(uint8_t player_id, bool reversed, - uint8_t track_nb) -{ - if (reversed) - Log::info("RaceConfig", "Player %d voted map %d to be reversed", - player_id, track_nb); - else - Log::info("RaceConfig", "Player %d voted map %d NOT to be reversed", - player_id, track_nb); - m_votes[player_id].voteReversed(reversed, track_nb); -} // setPlayerReversedVote - -//----------------------------------------------------------------------------- -void RaceConfig::setPlayerLapsVote(uint8_t player_id, uint8_t lap_count, - uint8_t track_nb) -{ - Log::info("RaceConfig", "Player %d voted map %d to have %d laps", - player_id, track_nb, lap_count); - m_votes[player_id].voteLaps(lap_count, track_nb); -} // setPlayerLapsVote - -//----------------------------------------------------------------------------- -/** Computes the selected race mode. - */ -void RaceConfig::computeRaceMode() -{ - // calculate the race type and number of tracks (in GP mode). - std::map major_histogram; - std::map races_count_histogram; - std::map minor_histogram; - for (unsigned int i = 0; i < m_max_players; i++) - { - // increase the count of votes - if (m_votes[i].hasVotedMajor()) - { - try - { - major_histogram.at(m_votes[i].getMajorVote()) ++; - } - catch (const std::out_of_range&) // doesn't exist in the map - { - major_histogram[m_votes[i].getMajorVote()] = 1; - } - } - else if (m_votes[i].hasVotedRacesCount()) - { - try - { - races_count_histogram.at(m_votes[i].getRacesCountVote()) ++; - } - catch (const std::out_of_range&) // doesn't exist in the map - { - races_count_histogram[m_votes[i].getRacesCountVote()] = 1; - } - } - else if (m_votes[i].hasVotedMinor()) - { - try - { - minor_histogram.at(m_votes[i].getMinorVote()) ++; - } - catch (const std::out_of_range&) // doesn't exist in the map - { - minor_histogram[m_votes[i].getMinorVote()] = 1; - } - } - } - // now we know : - m_major_mode = getHighestInHistogram(&major_histogram, - (int)RaceManager::MAJOR_MODE_SINGLE); - m_minor_mode = getHighestInHistogram(&minor_histogram, - (int)RaceManager::MINOR_MODE_NORMAL_RACE); - - if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX) - { - m_races_count = getHighestInHistogram(&races_count_histogram, 1); - m_tracks.resize(m_races_count); - } - else - { - m_tracks.resize(1); - m_races_count = 1; - } - - Log::info("RaceConfig", "Major mode will be %d with %d races. Minor is %d", - m_major_mode, m_races_count, m_minor_mode); -} // computeRaceMode - -// ---------------------------------------------------------------------------- -void RaceConfig::computeNextTrack() -{ - for (unsigned int j = 0; j < m_races_count; j++) - { - // first create histograms of the votes - std::map tracks_histogram; - std::map reversed_histogram; - std::map laps_histogram; - for (unsigned int i = 0; i < m_max_players; i++) - { - // increase the count of votes - if (m_votes[i].hasVotedTrack()) - { - try // maps - { - tracks_histogram.at(m_votes[i].getTrackVote()) ++; - } - catch (const std::out_of_range&) // doesn't exist in the map - { - tracks_histogram[m_votes[i].getTrackVote()] = 1; - } - } - if (m_votes[i].hasVotedReversed()) - { - try // reversed - { - reversed_histogram.at(m_votes[i].getReversedVote()) ++; - } - catch (const std::out_of_range&) // doesn't exist in the map - { - reversed_histogram[m_votes[i].getReversedVote()] = 1; - } - } - if (m_votes[i].hasVotedLaps()) - { - try // laps - { - laps_histogram.at(m_votes[i].getLapsVote()) ++; - } - catch (const std::out_of_range&) // doesn't exist in the mapt - { - laps_histogram[m_votes[i].getLapsVote()] = 1; - } - } - } - // now find the highest votes - m_tracks[j].track = getHighestInHistogram(&tracks_histogram, - UserConfigParams::m_last_track); - m_tracks[j].reversed = getHighestInHistogram(&reversed_histogram, false); - m_tracks[j].laps = getHighestInHistogram(&laps_histogram, - UserConfigParams::m_num_laps); - if (m_tracks[j].reversed) - Log::info("RaceConfig", - "Race %d will be on %s with %d laps and reversed", - j, m_tracks[j].track.c_str(), m_tracks[j].laps); - else - Log::info("RaceConfig", "Race %d will be on %s with %d laps", - j, m_tracks[j].track.c_str(), m_tracks[j].laps); - } -} // computeNextTrack - -//----------------------------------------------------------------------------- -/** Computes the selected setting (based on the users' vote) and sets them - * in the race manager. Then it loads the world. - */ -void RaceConfig::loadWorld() -{ - computeRaceMode(); - computeNextTrack(); - race_manager->startSingleRace(m_tracks[0].track, m_tracks[0].laps, - m_tracks[0].reversed); -} // loadWorld - -//----------------------------------------------------------------------------- -const TrackInfo* RaceConfig::getNextTrackInfo() const -{ - return &m_tracks[0]; -} // getNextTrackInfo - -//----------------------------------------------------------------------------- -int RaceConfig::getNumTrackVotes() const -{ - int count = 0; - - for (auto entry : m_votes) - { - if (entry.second.hasVotedTrack()) - count++; - } - - return count; -} // getNumTrackVotes \ No newline at end of file diff --git a/src/network/race_config.hpp b/src/network/race_config.hpp deleted file mode 100644 index e88979862..000000000 --- a/src/network/race_config.hpp +++ /dev/null @@ -1,136 +0,0 @@ -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2014-2015 SuperTuxKart-Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 3 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#ifndef RACE_CONFIG_HPP -#define RACE_CONFIG_HPP - -#include -#include -#include -#include "utils/types.hpp" - -/** Stores the name of a track, number of laps, and reverse driving. - */ -class TrackInfo -{ -public: - std::string track; - bool reversed; - uint8_t laps; - TrackInfo() { laps = 0; reversed = false; } -}; // TrackInfo - -// ============================================================================ -/** Stores a vote about the name of a track, number of laps, and reverse - * driving. - */ -class TrackVote -{ -public: - TrackVote(); - - void voteTrack(const std::string &track); - void voteReversed(bool reversed); - void voteLaps(uint8_t laps); - - TrackInfo track_info; - - bool has_voted_track; - bool has_voted_reversed; - bool has_voted_laps; -}; // class TrackVote - -// ============================================================================ -class RaceVote -{ - public: - RaceVote(); - - void voteMajor(uint32_t major); - void voteRaceCount(uint8_t count); - void voteMinor(uint32_t minor); - void voteTrack(const std::string &track, uint8_t track_number = 0); - void voteReversed(bool reversed, uint8_t track_number = 0); - void voteLaps(uint8_t laps, uint8_t track_number = 0); - - bool hasVotedMajor() const; - bool hasVotedRacesCount() const; - bool hasVotedMinor() const; - bool hasVotedTrack(uint8_t track_number = 0) const; - bool hasVotedReversed(uint8_t track_number = 0) const; - bool hasVotedLaps(uint8_t track_number = 0) const; - - uint8_t getMajorVote() const; - uint8_t getRacesCountVote() const; - uint8_t getMinorVote() const; - const std::string &getTrackVote(uint8_t track_number = 0) const; - bool getReversedVote(uint8_t track_number = 0) const; - uint8_t getLapsVote(uint8_t track_number = 0) const; - - private: - uint32_t m_major_mode; - uint32_t m_minor_mode; - uint8_t m_races_count; //!< Stores the number of races that will be in a GP - bool m_has_voted_major; - bool m_has_voted_minor; - bool m_has_voted_races_count; - std::vector m_tracks_vote; -}; // RaceVote - -// ============================================================================ -class RaceConfig -{ -private: - void computeRaceMode(); - void computeNextTrack(); -public: - RaceConfig(); - - void setPlayerMajorVote(uint8_t player_id, uint32_t major); - void setPlayerRaceCountVote(uint8_t player_id, uint8_t count); - void setPlayerMinorVote(uint8_t player_id, uint32_t minor); - void setPlayerTrackVote(uint8_t player_id, const std::string &track, - uint8_t track_nb = 0); - void setPlayerReversedVote(uint8_t player_id, bool reversed, - uint8_t track_nb = 0); - void setPlayerLapsVote(uint8_t player_id, uint8_t lap_count, - uint8_t track_nb = 0); - - void loadWorld(); - - const TrackInfo* getNextTrackInfo() const; - bool getReverse() const; - bool getLapCount() const; - int getNumTrackVotes() const; - - const RaceVote& getRaceVote(int global_player_id) { return m_votes[global_player_id]; } - int getMaxPlayers() const { return m_max_players; } - -protected: - std::vector m_tracks; - int m_minor_mode; - int m_major_mode; - unsigned int m_races_count; - - /** Key: globalPlayerID */ - std::map m_votes; - - uint8_t m_max_players; -}; // class RaceConfig - - -#endif // RACE_CONFIG_HPP diff --git a/src/network/remote_kart_info.hpp b/src/network/remote_kart_info.hpp index a78db09e6..88ec51740 100644 --- a/src/network/remote_kart_info.hpp +++ b/src/network/remote_kart_info.hpp @@ -34,9 +34,9 @@ enum SoccerTeam }; /** Game difficulty per player. */ -enum PerPlayerDifficulty +enum PerPlayerDifficulty : uint8_t { - PLAYER_DIFFICULTY_NORMAL, + PLAYER_DIFFICULTY_NORMAL = 0, PLAYER_DIFFICULTY_HANDICAP, PLAYER_DIFFICULTY_COUNT }; @@ -51,22 +51,31 @@ class RemoteKartInfo SoccerTeam m_soccer_team; bool m_network_player; PerPlayerDifficulty m_difficulty; - + float m_default_kart_color; public: RemoteKartInfo(int player_id, const std::string& kart_name, const irr::core::stringw& user_name, int host_id, bool network) : m_kart_name(kart_name), m_user_name(user_name), - m_local_player_id(player_id), m_host_id(host_id), - m_soccer_team(SOCCER_TEAM_NONE), m_network_player(network), - m_difficulty(PLAYER_DIFFICULTY_NORMAL) + m_local_player_id(player_id), m_global_player_id(-1), + m_host_id(host_id), m_soccer_team(SOCCER_TEAM_NONE), + m_network_player(network), + m_difficulty(PLAYER_DIFFICULTY_NORMAL), + m_default_kart_color(0.0f) {} RemoteKartInfo(const std::string& kart_name) : m_kart_name(kart_name), - m_user_name(""), m_local_player_id(-1), m_host_id(-1), - m_difficulty(PLAYER_DIFFICULTY_NORMAL) + m_user_name(""), m_local_player_id(-1), + m_global_player_id(-1), m_host_id(-1), + m_soccer_team(SOCCER_TEAM_NONE), m_network_player(false), + m_difficulty(PLAYER_DIFFICULTY_NORMAL), + m_default_kart_color(0.0f) {} - RemoteKartInfo() : m_kart_name(""), m_user_name(""), m_local_player_id(-1), - m_host_id(-1), m_difficulty(PLAYER_DIFFICULTY_NORMAL) + RemoteKartInfo() : m_kart_name(""), m_user_name(""), + m_local_player_id(-1), m_global_player_id(-1), + m_host_id(-1), m_soccer_team(SOCCER_TEAM_NONE), + m_network_player(false), + m_difficulty(PLAYER_DIFFICULTY_NORMAL), + m_default_kart_color(0.0f) {} void setKartName(const std::string& n) { m_kart_name = n; } void setPlayerName(const irr::core::stringw& u) { m_user_name = u; } @@ -75,6 +84,7 @@ public: void setGlobalPlayerId(int id) { m_global_player_id = id; } void setSoccerTeam(SoccerTeam team) { m_soccer_team = team; } void setNetworkPlayer(bool value) { m_network_player = value; } + void setDefaultKartColor(float value) { m_default_kart_color = value; } void setPerPlayerDifficulty(PerPlayerDifficulty value) { m_difficulty = value; } int getHostId() const { return m_host_id; } @@ -85,6 +95,7 @@ public: const irr::core::stringw& getPlayerName() const { return m_user_name; } SoccerTeam getSoccerTeam() const { return m_soccer_team; } PerPlayerDifficulty getDifficulty() const { return m_difficulty; } + float getDefaultKartColor() const { return m_default_kart_color; } bool operator<(const RemoteKartInfo& other) const { diff --git a/src/network/server.cpp b/src/network/server.cpp index cb205c025..903226c7e 100644 --- a/src/network/server.cpp +++ b/src/network/server.cpp @@ -16,6 +16,10 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "network/server.hpp" +#include "config/player_manager.hpp" +#include "online/online_player_profile.hpp" +#include "online/online_profile.hpp" +#include "online/profile_manager.hpp" #include "network/network_config.hpp" #include "io/xml_node.hpp" #include "utils/constants.hpp" @@ -34,6 +38,7 @@ Server::Server(const XMLNode& xml) m_server_id = 0; m_current_players = 0; m_max_players = 0; + m_distance = 0.0f; unsigned server_data = 0; xml.get("game_mode", &server_data); m_minor_mode = NetworkConfig::get()->getLocalGameMode(server_data).first; @@ -46,7 +51,7 @@ Server::Server(const XMLNode& xml) m_lower_case_name = StringUtils::toLowerCase(m_lower_case_name); xml.get("id", &m_server_id); - xml.get("hostid", &m_host_id); + xml.get("host_id", &m_server_owner); xml.get("max_players", &m_max_players); xml.get("current_players", &m_current_players); uint32_t ip; @@ -56,6 +61,36 @@ Server::Server(const XMLNode& xml) xml.get("port", &port); m_address.setPort(port); xml.get("private_port", &m_private_port); + xml.get("password", &m_password_protected); + xml.get("distance", &m_distance); + m_server_owner_name = "-"; + + // Display server owner name if he's your friend or localhost + Online::OnlineProfile* opp = PlayerManager::getCurrentPlayer()->getProfile(); + // Check localhost owner + if (opp && opp->getID() == m_server_owner) + { + m_server_owner_name = + StringUtils::wideToUtf8(opp->getUserName()); + } + else if (opp && opp->hasFetchedFriends()) + { + // Check friend(s) + for (uint32_t user_id : opp->getFriends()) + { + if (user_id == m_server_owner) + { + Online::OnlineProfile* friend_profile = + Online::ProfileManager::get()->getProfileByID(user_id); + if (friend_profile) + { + m_server_owner_name = + StringUtils::wideToUtf8(friend_profile->getUserName()); + } + } + } + } + } // Server(const XML&) // ---------------------------------------------------------------------------- @@ -69,16 +104,17 @@ Server::Server(const XMLNode& xml) * \param difficulty The difficulty of server. * \param server_mode The game modes of server (including minor and major). * \param address IP and port of the server. + * \param password_protected True if can only be joined with a password. */ Server::Server(unsigned server_id, const core::stringw &name, int max_players, int current_players, unsigned difficulty, unsigned server_mode, - const TransportAddress &address) + const TransportAddress &address, bool password_protected) { m_name = name; m_lower_case_name = StringUtils::toLowerCase(StringUtils::wideToUtf8(name)); m_satisfaction_score = 0; m_server_id = server_id; - m_host_id = 0; + m_server_owner = 0; m_current_players = current_players; m_max_players = max_players; m_address.copy(address); @@ -87,6 +123,8 @@ Server::Server(unsigned server_id, const core::stringw &name, int max_players, m_difficulty = (RaceManager::Difficulty)difficulty; m_minor_mode = NetworkConfig::get()->getLocalGameMode(server_mode).first; m_major_mode = NetworkConfig::get()->getLocalGameMode(server_mode).second; + m_password_protected = password_protected; + m_distance = 0.0f; } // server(server_id, ...) // ---------------------------------------------------------------------------- diff --git a/src/network/server.hpp b/src/network/server.hpp index 058a31864..a4023708d 100644 --- a/src/network/server.hpp +++ b/src/network/server.hpp @@ -49,7 +49,7 @@ protected: std::string m_lower_case_name; uint32_t m_server_id; - uint32_t m_host_id; + uint32_t m_server_owner; /** The maximum number of players that the server supports */ int m_max_players; @@ -74,13 +74,22 @@ protected: RaceManager::Difficulty m_difficulty; + bool m_password_protected; + + /* WAN server only, show the owner name of server, can only be seen + * for localhost or if you are friend with the server owner. */ + std::string m_server_owner_name; + + /* WAN server only, distance based on IP latitude and longitude. */ + float m_distance; public: /** Initialises the object from an XML node. */ Server(const XMLNode &xml); Server(unsigned server_id, const irr::core::stringw &name, int max_players, int current_players, unsigned difficulty, - unsigned server_mode, const TransportAddress &address); + unsigned server_mode, const TransportAddress &address, + bool password_protected); bool filterByWords(const irr::core::stringw words) const; // ------------------------------------------------------------------------ /** Returns ip address and port of this server. */ @@ -95,9 +104,8 @@ public: /** Returns the ID of this server. */ const uint32_t getServerId() const { return m_server_id; } // ------------------------------------------------------------------------ - /** Returns the unique host id of this server (wan game only), which is - * the user id in STK addon server of the server owner. */ - const uint32_t getHostId() const { return m_host_id; } + /** Returns the user id in STK addon server of the server owner (WAN). */ + const uint32_t getServerOwner() const { return m_server_owner; } // ------------------------------------------------------------------------ uint16_t getPrivatePort() const { return m_private_port; } // ------------------------------------------------------------------------ @@ -114,7 +122,13 @@ public: { return m_major_mode; } // ------------------------------------------------------------------------ RaceManager::Difficulty getDifficulty() const { return m_difficulty; } - + // ------------------------------------------------------------------------ + bool isPasswordProtected() const { return m_password_protected; } + // ------------------------------------------------------------------------ + const std::string& getServerOwnerName() const + { return m_server_owner_name; } + // ------------------------------------------------------------------------ + float getDistance() const { return m_distance; } }; // Server #endif // HEADER_SERVER_HPP diff --git a/src/network/servers_manager.cpp b/src/network/servers_manager.cpp index 01bb43ae3..e07e98c23 100644 --- a/src/network/servers_manager.cpp +++ b/src/network/servers_manager.cpp @@ -150,7 +150,7 @@ Online::XMLRequest* ServersManager::getLANRefreshRequest() const double start_time = StkTime::getRealTime(); const double DURATION = 1.0; const auto& servers = ServersManager::get()->getServers(); - int cur_server_id = servers.size(); + int cur_server_id = (int)servers.size(); assert(cur_server_id == 0); std::vector > servers_now; while (StkTime::getRealTime() - start_time < DURATION) @@ -160,6 +160,12 @@ Online::XMLRequest* ServersManager::getLANRefreshRequest() const if (len > 0) { BareNetworkString s(buffer, len); + uint8_t version = s.getUInt8(); + if (version != NetworkConfig::m_server_version) + { + Log::verbose("ServersManager", "Skipping a server"); + continue; + } irr::core::stringw name; // bytes_read is the number of bytes read s.decodeStringW(&name); @@ -169,9 +175,10 @@ Online::XMLRequest* ServersManager::getLANRefreshRequest() const uint8_t difficulty = s.getUInt8(); uint8_t mode = s.getUInt8(); sender.setPort(port); + uint8_t password = s.getUInt8(); servers_now.emplace_back(std::make_shared (cur_server_id++, name, max_players, players, - difficulty, mode, sender)); + difficulty, mode, sender, password == 1)); } // if received_data } // while still waiting m_success = true; @@ -229,6 +236,14 @@ void ServersManager::setWanServers(bool success, const XMLNode* input) const XMLNode *servers_xml = input->getNode("servers"); for (unsigned int i = 0; i < servers_xml->getNumNodes(); i++) { + unsigned version = 0; + servers_xml->getNode(i)->get("version", &version); + assert(version != 0); + if (version != NetworkConfig::m_server_version) + { + Log::verbose("ServersManager", "Skipping a server"); + continue; + } m_servers.emplace_back( std::make_shared(*servers_xml->getNode(i))); } diff --git a/src/network/servers_manager.hpp b/src/network/servers_manager.hpp index 713484693..823ce74b3 100644 --- a/src/network/servers_manager.hpp +++ b/src/network/servers_manager.hpp @@ -20,9 +20,6 @@ #define HEADER_SERVERS_MANAGER_HPP #include -#include -#include -#include #include #include #include @@ -70,17 +67,9 @@ public: // ------------------------------------------------------------------------ void cleanUpServers() { m_servers.clear(); } // ------------------------------------------------------------------------ - void sortServers(std::function a, - const std::shared_ptr b)> sorting_function) - { - assert(m_list_updated); - std::sort(m_servers.begin(), m_servers.end(), sorting_function); - } - // ------------------------------------------------------------------------ bool refresh(); // ------------------------------------------------------------------------ - const std::vector >& getServers() const - { return m_servers; } + std::vector >& getServers() { return m_servers; } // ------------------------------------------------------------------------ bool listUpdated() const { return m_list_updated; } diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index 1df516a9e..a18259d8e 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -21,11 +21,11 @@ #include "config/user_config.hpp" #include "io/file_manager.hpp" #include "network/event.hpp" +#include "network/game_setup.hpp" #include "network/network_config.hpp" #include "network/network_console.hpp" #include "network/network_string.hpp" #include "network/protocols/connect_to_peer.hpp" -#include "network/protocols/connect_to_server.hpp" #include "network/protocols/server_lobby.hpp" #include "network/protocol_manager.hpp" #include "network/stk_peer.hpp" @@ -59,8 +59,10 @@ #include #include +#include #include #include +#include STKHost *STKHost::m_stk_host = NULL; bool STKHost::m_enable_console = false; @@ -69,7 +71,11 @@ void STKHost::create(std::shared_ptr server, SeparateProcess* p) { assert(m_stk_host == NULL); if (NetworkConfig::get()->isServer()) + { + std::shared_ptr sl = LobbyProtocol::create(); m_stk_host = new STKHost(NetworkConfig::get()->getServerName()); + sl->requestStart(); + } else { m_stk_host = new STKHost(server); @@ -256,7 +262,6 @@ void STKHost::create(std::shared_ptr server, SeparateProcess* p) */ STKHost::STKHost(std::shared_ptr server) { - m_next_unique_host_id = -1; // Will be overwritten with the correct value once a connection with the // server is made. m_host_id = 0; @@ -276,7 +281,6 @@ STKHost::STKHost(std::shared_ptr server) } setPrivatePort(); - std::make_shared(server)->requestStart(); } // STKHost // ---------------------------------------------------------------------------- @@ -286,16 +290,14 @@ STKHost::STKHost(std::shared_ptr server) STKHost::STKHost(const irr::core::stringw &server_name) { init(); - // The host id will be increased whenever a new peer is added, so the - // first client will have host id 1 (host id 0 is the server). - m_next_unique_host_id = 0; m_host_id = 0; // indicates a server host. ENetAddress addr; addr.host = STKHost::HOST_ANY; addr.port = NetworkConfig::get()->getServerPort(); - m_network= new Network(NetworkConfig::get()->getMaxPlayers(), + // Reserver 1 peer to handle full server message + m_network = new Network(NetworkConfig::get()->getMaxPlayers() + 1, /*channel_limit*/2, /*max_in_bandwidth*/0, /*max_out_bandwidth*/ 0, &addr, @@ -305,11 +307,7 @@ STKHost::STKHost(const irr::core::stringw &server_name) Log::fatal("STKHost", "An error occurred while trying to create an " "ENet server host."); } - setPrivatePort(); - ProtocolManager::lock() - ->requestStart(LobbyProtocol::create()); - } // STKHost(server_name) // ---------------------------------------------------------------------------- @@ -319,11 +317,9 @@ STKHost::STKHost(const irr::core::stringw &server_name) void STKHost::init() { m_shutdown = false; + m_authorised = false; m_network = NULL; - m_game_setup = NULL; - - m_exit_flag.clear(); - m_exit_flag.test_and_set(); + m_exit_timeout.store(std::numeric_limits::max()); // Start with initialising ENet // ============================ @@ -354,18 +350,8 @@ STKHost::~STKHost() requestShutdown(); if (m_network_console.joinable()) m_network_console.join(); - // delete the game setup - if (m_game_setup) - delete m_game_setup; - m_game_setup = NULL; - - // Delete all connected peers - while (!m_peers.empty()) - { - delete m_peers.back(); - m_peers.pop_back(); - } + disconnectAllPeers(true/*timeout_waiting*/); Network::closeLog(); stopListening(); @@ -391,7 +377,6 @@ STKHost::~STKHost() void STKHost::shutdown() { ProtocolManager::lock()->abort(); - deleteAllPeers(); destroy(); } // shutdown @@ -400,16 +385,29 @@ void STKHost::shutdown() */ void STKHost::setPublicAddress() { - std::vector untried_server = UserConfigParams::m_stun_servers; - // Generate random list of stun servers + std::vector > untried_server; + for (auto& p : UserConfigParams::m_stun_list) + untried_server.push_back(p); + + assert(untried_server.size() > 2); + // Randomly use stun servers of the low ping from top-half of the list + std::sort(untried_server.begin(), untried_server.end(), + [] (const std::pair& a, + const std::pair& b)->bool + { + return a.second > b.second; + }); std::random_device rd; std::mt19937 g(rd()); - std::shuffle(untried_server.begin(), untried_server.end(), g); + std::shuffle(untried_server.begin() + (untried_server.size() / 2), + untried_server.end(), g); + while (!untried_server.empty()) { // Pick last element in untried servers - const char* server_name = untried_server.back().c_str(); - Log::debug("STKHost", "Using STUN server %s", server_name); + std::string server_name = untried_server.back().first.c_str(); + UserConfigParams::m_stun_list[server_name] = (uint32_t)-1; + Log::debug("STKHost", "Using STUN server %s", server_name.c_str()); struct addrinfo hints, *res; @@ -418,11 +416,11 @@ void STKHost::setPublicAddress() hints.ai_socktype = SOCK_STREAM; // Resolve the stun server name so we can send it a STUN request - int status = getaddrinfo(server_name, NULL, &hints, &res); + int status = getaddrinfo(server_name.c_str(), NULL, &hints, &res); if (status != 0) { Log::error("STKHost", "Error in getaddrinfo for stun server" - " %s: %s", server_name, gai_strerror(status)); + " %s: %s", server_name.c_str(), gai_strerror(status)); untried_server.pop_back(); continue; } @@ -453,6 +451,7 @@ void STKHost::setPublicAddress() } m_network->sendRawPacket(s, m_stun_address); + double ping = StkTime::getRealTime(); freeaddrinfo(res); // Recieve now @@ -460,6 +459,7 @@ void STKHost::setPublicAddress() const int LEN = 2048; char buffer[LEN]; int len = m_network->receiveRawPacket(buffer, LEN, &sender, 2000); + ping = StkTime::getRealTime() - ping; if (sender.getIP() != m_stun_address.getIP()) { @@ -597,6 +597,9 @@ void STKHost::setPublicAddress() Log::warn("STKHost", "Only non xor-mapped address returned."); m_public_address.copy(non_xor_addr); } + // Succeed, save ping + UserConfigParams::m_stun_list[server_name] = + (uint32_t)(ping * 1000.0); untried_server.clear(); } } @@ -618,44 +621,22 @@ void STKHost::setPrivatePort() } // setPrivatePort //----------------------------------------------------------------------------- -/** A previous GameSetup is deletea and a new one is created. - * \return Newly create GameSetup object. - */ -GameSetup* STKHost::setupNewGame() -{ - if (m_game_setup) - delete m_game_setup; - m_game_setup = new GameSetup(); - return m_game_setup; -} // setupNewGame - -//----------------------------------------------------------------------------- -/** Called when you leave a server. +/** Disconnect all connected peers. */ -void STKHost::deleteAllPeers() +void STKHost::disconnectAllPeers(bool timeout_waiting) { - // remove all peers - for (unsigned int i = 0; i < m_peers.size(); i++) + std::lock_guard lock(m_peers_mutex); + if (!m_peers.empty() && timeout_waiting) { - delete m_peers[i]; - m_peers[i] = NULL; + for (auto peer : m_peers) + peer.second->disconnect(); + // Wait for at most 2 seconds for disconnect event to be generated + m_exit_timeout.store(StkTime::getRealTime() + 2.0); } m_peers.clear(); -} // deleteAllPeers +} // disconnectAllPeers -// ---------------------------------------------------------------------------- -/** Called when STK exits. It stops the listening thread and the - * ProtocolManager. - */ -void STKHost::abort() -{ - // Finish protocol manager first, to avoid that it access data - // in STKHost. - ProtocolManager::lock()->abort(); - stopListening(); -} // abort - -// -------------------------------------------------------------------- +//----------------------------------------------------------------------------- /** Sets an error message for the gui. */ void STKHost::setErrorMessage(const irr::core::stringw &message) @@ -668,13 +649,14 @@ void STKHost::setErrorMessage(const irr::core::stringw &message) m_error_message = message; } // setErrorMessage -// -------------------------------------------------------------------- +//----------------------------------------------------------------------------- /** \brief Try to establish a connection to a given transport address. * \param peer : The transport address which you want to connect to. * \return True if we're successfully connected. False elseway. */ bool STKHost::connect(const TransportAddress& address) { + assert(NetworkConfig::get()->isClient()); if (peerExists(address)) return isConnectedTo(address); @@ -696,8 +678,7 @@ bool STKHost::connect(const TransportAddress& address) */ void STKHost::startListening() { - m_exit_flag.clear(); - m_exit_flag.test_and_set(); + m_exit_timeout.store(std::numeric_limits::max()); m_listening_thread = std::thread(std::bind(&STKHost::mainLoop, this)); } // startListening @@ -707,28 +688,12 @@ void STKHost::startListening() */ void STKHost::stopListening() { - m_exit_flag.clear(); + if (m_exit_timeout.load() == std::numeric_limits::max()) + m_exit_timeout.store(0.0); if (m_listening_thread.joinable()) m_listening_thread.join(); } // stopListening -// ---------------------------------------------------------------------------- -/** Returns true if this client instance is allowed to control the server. - * A client can authorise itself by providing the server's password. It is - * then allowed to control the server (e.g. start kart selection). - * The information if this client was authorised by the server is actually - * stored in the peer (which is the server peer on a client). - */ -bool STKHost::isAuthorisedToControl() const -{ - assert(NetworkConfig::get()->isClient()); - // If we are not properly connected (i.e. only enet connection, but not - // stk logic), no peer is authorised. - if(m_peers.size()==0) - return false; - return m_peers[0]->isAuthorised(); -} // isAuthorisedToControl - // ---------------------------------------------------------------------------- /** \brief Thread function checking if data is received. * This function tries to get data from network low-level functions as @@ -739,12 +704,14 @@ bool STKHost::isAuthorisedToControl() const void STKHost::mainLoop() { VS::setThreadName("STKHost"); + Log::info("STKHost", "Listening has been started."); ENetEvent event; ENetHost* host = m_network->getENetHost(); + const bool is_server = NetworkConfig::get()->isServer(); // A separate network connection (socket) to handle LAN requests. Network* direct_socket = NULL; - if ((NetworkConfig::get()->isLAN() && NetworkConfig::get()->isServer()) || + if ((NetworkConfig::get()->isLAN() && is_server) || NetworkConfig::get()->isPublicServer()) { TransportAddress address(0, @@ -760,50 +727,158 @@ void STKHost::mainLoop() } } - while (m_exit_flag.test_and_set()) + while (m_exit_timeout.load() > StkTime::getRealTime()) { auto sl = LobbyProtocol::get(); if (direct_socket && sl && sl->waitingForPlayers()) { - handleDirectSocketRequest(direct_socket); + handleDirectSocketRequest(direct_socket, sl); } // if discovery host - while (enet_host_service(host, &event, 20) != 0) + if (is_server) + { + std::unique_lock peer_lock(m_peers_mutex); + // Remove any peer which has no token for 7 seconds + // The token is set when the first connection request has happened + for (auto it = m_peers.begin(); it != m_peers.end();) + { + if (!it->second->isClientServerTokenSet() && + (float)StkTime::getRealTime() > + it->second->getConnectedTime() + 7.0f) + { + Log::info("STKHost", "%s has no token for more than 7" + " seconds, disconnect it by force.", + it->second->getAddress().toString().c_str()); + enet_host_flush(host); + enet_peer_reset(it->first); + it = m_peers.erase(it); + } + else + { + it++; + } + } + peer_lock.unlock(); + } + + std::list > copied_list; + std::unique_lock lock(m_enet_cmd_mutex); + std::swap(copied_list, m_enet_cmd); + lock.unlock(); + for (auto& p : copied_list) + { + switch (std::get<3>(p)) + { + case ECT_SEND_PACKET: + enet_peer_send(std::get<0>(p), (uint8_t)std::get<2>(p), + std::get<1>(p)); + break; + case ECT_DISCONNECT: + enet_peer_disconnect(std::get<0>(p), std::get<2>(p)); + break; + case ECT_RESET: + // Flush enet before reset (so previous command is send) + enet_host_flush(host); + enet_peer_reset(std::get<0>(p)); + // Remove the stk peer of it + std::lock_guard lock(m_peers_mutex); + m_peers.erase(std::get<0>(p)); + break; + } + } + + while (enet_host_service(host, &event, 0) != 0) { if (event.type == ENET_EVENT_TYPE_NONE) continue; - auto pm = ProtocolManager::lock(); - if (!pm || pm->isExiting()) + Event* stk_event = NULL; + if (event.type == ENET_EVENT_TYPE_CONNECT) { - // Don't create more event if no protocol manager or it will - // be exiting + auto stk_peer = std::make_shared + (event.peer, this, m_next_unique_host_id++); + std::unique_lock lock(m_peers_mutex); + m_peers[event.peer] = stk_peer; + lock.unlock(); + stk_event = new Event(&event, stk_peer); + TransportAddress addr(event.peer->address); + Log::info("STKHost", "%s has just connected. There are " + "now %u peers.", addr.toString().c_str(), getPeerCount()); + } // ENET_EVENT_TYPE_CONNECT + else if (event.type == ENET_EVENT_TYPE_DISCONNECT) + { + Log::flushBuffers(); + + // If used a timeout waiting disconnect, exit now + if (m_exit_timeout.load() != + std::numeric_limits::max()) + { + m_exit_timeout.store(0.0); + break; + } + // Use the previous stk peer so protocol can see the network + // profile and handle it for disconnection + if (m_peers.find(event.peer) != m_peers.end()) + { + stk_event = new Event(&event, m_peers.at(event.peer)); + std::lock_guard lock(m_peers_mutex); + m_peers.erase(event.peer); + } + TransportAddress addr(event.peer->address); + Log::info("STKHost", "%s has just disconnected. There are " + "now %u peers.", addr.toString().c_str(), getPeerCount()); + } // ENET_EVENT_TYPE_DISCONNECT + + if (!stk_event && m_peers.find(event.peer) != m_peers.end()) + { + auto& peer = m_peers.at(event.peer); + unsigned token = 0; + // Token is after the protocol type (1 byte) in stk network + // string (network order) + token += event.packet->data[1]; + token <<= 8; + token += event.packet->data[2]; + token <<= 8; + token += event.packet->data[3]; + token <<= 8; + token += event.packet->data[4]; + + if (is_server && ((!peer->isClientServerTokenSet() && + !isConnectionRequestPacket(event.packet->data, + (int)event.packet->dataLength)) || + (token != peer->getClientServerToken()))) + { + // For server discard all events from wrong or unset token + // peers if that is not a connection request + if (token != peer->getClientServerToken()) + { + Log::error("STKHost", "Received event with invalid token!"); + Log::error("STKHost", "HostID %d Token %d message token %d", + peer->getHostId(), peer->getClientServerToken(), token); + NetworkString wrong_event(event.packet->data, + (int)event.packet->dataLength); + Log::error("STKHost", wrong_event.getLogMessage().c_str()); + peer->unsetClientServerToken(); + } + enet_packet_destroy(event.packet); + continue; + } + stk_event = new Event(&event, peer); + } + else if (!stk_event) + { + enet_packet_destroy(event.packet); continue; } - // Create an STKEvent with the event data. This will also - // create the peer if it doesn't exist already - Event* stk_event = new Event(&event); - STKPeer* peer = stk_event->getPeer(); - if (stk_event->getType() == EVENT_TYPE_CONNECTED) - { - Log::info("STKHost", "A client has just connected. There are " - "now %lu peers.", m_peers.size()); - Log::debug("STKHost", "Addresses are : %lx, %lx", - stk_event->getPeer(), peer); - } // EVENT_TYPE_CONNECTED - else if (stk_event->getType() == EVENT_TYPE_DISCONNECTED) - { - Log::info("STKHost", "A client has just disconnected."); - Log::flushBuffers(); - } // EVENT_TYPE_CONNECTED - else if (stk_event->getType() == EVENT_TYPE_MESSAGE) + if (stk_event->getType() == EVENT_TYPE_MESSAGE) { Network::logPacket(stk_event->data(), true); - TransportAddress stk_addr(peer->getAddress()); #ifdef DEBUG_MESSAGE_CONTENT Log::verbose("NetworkManager", "Message, Sender : %s time %f message:", - stk_addr.toString(/*show port*/false).c_str(), + stk_event->getPeer()->getAddress() + .toString(/*show port*/false).c_str(), StkTime::getRealTime()); Log::verbose("NetworkManager", "%s", stk_event->data().getLogMessage().c_str()); @@ -811,12 +886,16 @@ void STKHost::mainLoop() } // if message event // notify for the event now. - pm->propagateEvent(stk_event); - + auto pm = ProtocolManager::lock(); + if (pm && !pm->isExiting()) + pm->propagateEvent(stk_event); + else + delete stk_event; } // while enet_host_service - } // while m_exit_flag.test_and_set() + StkTime::sleep(10); + } // while m_exit_timeout.load() > StkTime::getRealTime() delete direct_socket; - Log::info("STKHost", "Listening has been stopped"); + Log::info("STKHost", "Listening has been stopped."); } // mainLoop // ---------------------------------------------------------------------------- @@ -828,7 +907,8 @@ void STKHost::mainLoop() * message is received, will answer with a message containing server details * (and sender IP address and port). */ -void STKHost::handleDirectSocketRequest(Network* direct_socket) +void STKHost::handleDirectSocketRequest(Network* direct_socket, + std::shared_ptr sl) { const int LEN=2048; char buffer[LEN]; @@ -853,18 +933,18 @@ void STKHost::handleDirectSocketRequest(Network* direct_socket) name = name.substr(0, 255); // Send the answer, consisting of server name, max players, - // current players, and the client's ip address and port - // number (which solves the problem which network interface - // might be the right one if there is more than one). + // current players BareNetworkString s((int)name.size()+1+11); + s.addUInt8(NetworkConfig::m_server_version); s.encodeString(name); s.addUInt8(NetworkConfig::get()->getMaxPlayers()); - s.addUInt8(0); // FIXME: current number of connected players + s.addUInt8((uint8_t)sl->getGameSetup()->getPlayerCount()); s.addUInt16(m_private_port); s.addUInt8((uint8_t)race_manager->getDifficulty()); s.addUInt8((uint8_t) NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(), race_manager->getMajorMode())); + s.addUInt8(!NetworkConfig::get()->getPassword().empty()); direct_socket->sendRawPacket(s, sender); } // if message is server-requested else if (command == connection_cmd) @@ -906,66 +986,29 @@ void STKHost::handleDirectSocketRequest(Network* direct_socket) */ bool STKHost::peerExists(const TransportAddress& peer) { - ENetHost *host = m_network->getENetHost(); - for (unsigned int i = 0; i < host->peerCount ; i++) + std::lock_guard lock(m_peers_mutex); + for (auto p : m_peers) { - if (host->peers[i].address.host == ntohl(peer.getIP()) && - host->peers[i].address.port == peer.getPort() ) - { + auto stk_peer = p.second; + if (stk_peer->getAddress() == peer || + ((stk_peer->getAddress().isPublicAddressLocalhost() || + peer.isPublicAddressLocalhost()) && + stk_peer->getAddress().getPort() == peer.getPort())) return true; - } } return false; } // peerExists -// ---------------------------------------------------------------------------- -std::vector STKHost::getMyPlayerProfiles() -{ - return m_game_setup->getAllPlayersOnHost(m_host_id); -} // getMyPlayerProfiles - -// ---------------------------------------------------------------------------- -/** Returns the STK peer belonging to the given enet_peer. If no STKPeer - * exists, create a new STKPeer. - * \param enet_peer The EnetPeer. - */ -STKPeer* STKHost::getPeer(ENetPeer *enet_peer) -{ - for(unsigned int i=0; iisSamePeer(enet_peer)) - return m_peers[i]; - } - - // Make sure that a client only adds one other peer (=the server). - if(NetworkConfig::get()->isClient() && m_peers.size()>0) - { - Log::error("STKHost", - "Client is adding more than one server, ignored for now."); - } - - - //FIXME Should we check #clients here? It might be easier to only - // handle this at connect time, not in all getPeer calls. - STKPeer *peer = new STKPeer(enet_peer); - Log::debug("getPeer", - "Creating a new peer, address are STKPeer:%p, Peer:%p", - peer, enet_peer); - - m_peers.push_back(peer); - m_next_unique_host_id ++; - return peer; -} // getPeer - // ---------------------------------------------------------------------------- /** \brief Return the only server peer for client. * \return STKPeer the STKPeer of server. */ -STKPeer* STKHost::getServerPeerForClient() const +std::shared_ptr STKHost::getServerPeerForClient() const { - assert(m_peers.size() == 1); assert(NetworkConfig::get()->isClient()); - return m_peers[0]; + if (m_peers.size() != 1) + return nullptr; + return m_peers.begin()->second; } // getServerPeerForClient // ---------------------------------------------------------------------------- @@ -986,43 +1029,20 @@ bool STKHost::isConnectedTo(const TransportAddress& peer) return false; } // isConnectedTo - -// ---------------------------------------------------------------------------- -void STKHost::removePeer(const STKPeer* peer) +//----------------------------------------------------------------------------- +/** Sends data to all peers + * \param data Data to sent. + * \param reliable If the data should be sent reliable or now. + */ +void STKHost::sendPacketToAllPeers(NetworkString *data, bool reliable) { - if (!peer || !peer->exists()) // peer does not exist (already removed) - return; - - TransportAddress addr(peer->getAddress()); - Log::debug("STKHost", "Disconnected host: %s", addr.toString().c_str()); - - // remove the peer: - bool removed = false; - for (unsigned int i = 0; i < m_peers.size(); i++) + std::lock_guard lock(m_peers_mutex); + for (auto p : m_peers) { - if (m_peers[i]->isSamePeer(peer) && !removed) // remove only one - { - delete m_peers[i]; - m_peers.erase(m_peers.begin() + i, m_peers.begin() + i + 1); - Log::verbose("NetworkManager", - "The peer has been removed from the Network Manager."); - removed = true; - } - else if (m_peers[i]->isSamePeer(peer)) - { - Log::fatal("NetworkManager", - "Multiple peers match the disconnected one."); - } - } // for i < m_peers.size() - - if (!removed) - Log::warn("NetworkManager", "The peer that has been disconnected was " - "not registered by the Network Manager."); - - Log::info("NetworkManager", - "Somebody is now disconnected. There are now %lu peers.", - m_peers.size()); -} // removePeer + if (p.second->isClientServerTokenSet()) + p.second->sendPacket(data, reliable); + } +} // sendPacketExcept //----------------------------------------------------------------------------- /** Sends data to all peers except the specified one. @@ -1033,13 +1053,79 @@ void STKHost::removePeer(const STKPeer* peer) void STKHost::sendPacketExcept(STKPeer* peer, NetworkString *data, bool reliable) { - for (unsigned int i = 0; i < m_peers.size(); i++) + std::lock_guard lock(m_peers_mutex); + for (auto p : m_peers) { - STKPeer* p = m_peers[i]; - if (!p->isSamePeer(peer)) + STKPeer* stk_peer = p.second.get(); + if (!stk_peer->isSamePeer(peer) && p.second->isClientServerTokenSet()) { - p->sendPacket(data, reliable); + stk_peer->sendPacket(data, reliable); } } } // sendPacketExcept +//----------------------------------------------------------------------------- +/** Sends a message from a client to the server. */ +void STKHost::sendToServer(NetworkString *data, bool reliable) +{ + std::lock_guard lock(m_peers_mutex); + if (m_peers.empty()) + return; + assert(NetworkConfig::get()->isClient()); + m_peers.begin()->second->sendPacket(data, reliable); +} // sendToServer + +//----------------------------------------------------------------------------- +std::vector > + STKHost::getAllPlayerProfiles() const +{ + std::vector > p; + std::unique_lock lock(m_peers_mutex); + for (auto peer : m_peers) + { + auto peer_profile = peer.second->getPlayerProfiles(); + p.insert(p.end(), peer_profile.begin(), peer_profile.end()); + } + lock.unlock(); + return p; +} // getAllPlayerProfiles + +//----------------------------------------------------------------------------- +std::shared_ptr STKHost::findPeerByHostId(uint32_t id) const +{ + std::lock_guard lock(m_peers_mutex); + auto ret = std::find_if(m_peers.begin(), m_peers.end(), + [id](const std::pair >& p) + { + return p.second->getHostId() == id; + }); + return ret != m_peers.end() ? ret->second : nullptr; +} // findPeerByHostId + +//----------------------------------------------------------------------------- +void STKHost::replaceNetwork(ENetEvent& event, Network* network) +{ + assert(NetworkConfig::get()->isClient()); + assert(!m_listening_thread.joinable()); + assert(network->getENetHost()->peerCount == 1); + delete m_network; + m_network = network; + auto stk_peer = std::make_shared(event.peer, this, + m_next_unique_host_id++); + m_peers[event.peer] = stk_peer; + setPrivatePort(); + startListening(); + auto pm = ProtocolManager::lock(); + if (pm && !pm->isExiting()) + pm->propagateEvent(new Event(&event, stk_peer)); +} // replaceNetwork + +//----------------------------------------------------------------------------- +bool STKHost::isConnectionRequestPacket(unsigned char* data, int length) +{ + if (length < 6) + return false; + // Connection request is not synchronous + return (uint8_t)data[0] == PROTOCOL_LOBBY_ROOM && + (uint8_t)data[5] == LobbyProtocol::LE_CONNECTION_REQUESTED; +} // isConnectionRequestPacket diff --git a/src/network/stk_host.hpp b/src/network/stk_host.hpp index 7ebda5ff9..d437dfaac 100644 --- a/src/network/stk_host.hpp +++ b/src/network/stk_host.hpp @@ -23,11 +23,8 @@ #define STK_HOST_HPP #include "network/network.hpp" -#include "network/network_config.hpp" #include "network/network_string.hpp" -#include "network/stk_peer.hpp" #include "network/transport_address.hpp" -#include "utils/synchronised.hpp" #include "irrString.h" @@ -38,13 +35,26 @@ #include #include +#include +#include #include +#include #include +#include class GameSetup; +class NetworkPlayerProfile; class Server; +class ServerLobby; class SeparateProcess; +enum ENetCommandType : unsigned int +{ + ECT_SEND_PACKET = 0, + ECT_DISCONNECT = 1, + ECT_RESET = 2 +}; + class STKHost { public: @@ -58,9 +68,6 @@ public: PORT_ANY = 0 //!< Any port. }; - - friend class STKPeer; // allow direct enet modifications in implementations - private: /** Singleton pointer to the instance. */ static STKHost* m_stk_host; @@ -74,20 +81,29 @@ private: /** Network console thread */ std::thread m_network_console; + /** Make sure the removing or adding a peer is thread-safe. */ + mutable std::mutex m_peers_mutex; + + /** Let (atm enet_peer_send and enet_peer_disconnect) run in the listening + * thread. */ + std::list > m_enet_cmd; + + /** Protect \ref m_enet_cmd from multiple threads usage. */ + std::mutex m_enet_cmd_mutex; + /** The list of peers connected to this instance. */ - std::vector m_peers; + std::map > m_peers; /** Next unique host id. It is increased whenever a new peer is added (see * getPeer()), but not decreased whena host (=peer) disconnects. This * results in a unique host id for each host, even when a host should * disconnect and then reconnect. */ - int m_next_unique_host_id; + uint32_t m_next_unique_host_id = 0; /** Host id of this host. */ - uint8_t m_host_id; - - /** Stores data about the online game to play. */ - GameSetup* m_game_setup; + uint32_t m_host_id = 0; /** Id of thread listening to enet events. */ std::thread m_listening_thread; @@ -96,8 +112,11 @@ private: * triggers a shutdown of the STKHost (and the Protocolmanager). */ std::atomic_bool m_shutdown; - /** Atomic flag used to stop this thread. */ - std::atomic_flag m_exit_flag = ATOMIC_FLAG_INIT; + /** True if this local host is authorised to control a server. */ + std::atomic_bool m_authorised; + + /** Use as a timeout to waiting a disconnect event when exiting. */ + std::atomic m_exit_timeout; /** An error message, which is set by a protocol to be displayed * in the GUI. */ @@ -112,23 +131,26 @@ private: /** The private port enet socket is bound. */ uint16_t m_private_port; - /** An error message, which is set by a protocol to be displayed - * in the GUI. */ - - STKHost(std::shared_ptr server); - STKHost(const irr::core::stringw &server_name); - virtual ~STKHost(); + // ------------------------------------------------------------------------ + STKHost(std::shared_ptr server); + // ------------------------------------------------------------------------ + STKHost(const irr::core::stringw &server_name); + // ------------------------------------------------------------------------ + ~STKHost(); + // ------------------------------------------------------------------------ void init(); - void handleDirectSocketRequest(Network* direct_socket); + // ------------------------------------------------------------------------ + void handleDirectSocketRequest(Network* direct_socket, + std::shared_ptr sl); // ------------------------------------------------------------------------ void mainLoop(); + // ------------------------------------------------------------------------ + bool isConnectionRequestPacket(unsigned char* data, int length); public: - /** If a network console should be started. Note that the console can cause - * a crash in release mode on windows (see #1529). */ + /** If a network console should be started. */ static bool m_enable_console; - /** Creates the STKHost. It takes all confifguration parameters from * NetworkConfig. This STKHost can either be a client or a server. */ @@ -156,21 +178,17 @@ public: const TransportAddress& getPublicAddress() const { return m_public_address; } // ------------------------------------------------------------------------ - const TransportAddress& getStunAddress() const - { return m_stun_address; } + const TransportAddress& getStunAddress() const { return m_stun_address; } // ------------------------------------------------------------------------ - uint16_t getPrivatePort() const - { return m_private_port; } + uint16_t getPrivatePort() const { return m_private_port; } // ------------------------------------------------------------------------ void setPrivatePort(); // ------------------------------------------------------------------------ void setPublicAddress(); // ------------------------------------------------------------------------ - virtual GameSetup* setupNewGame(); - void abort(); - void deleteAllPeers(); + void disconnectAllPeers(bool timeout_waiting = false); + // ------------------------------------------------------------------------ bool connect(const TransportAddress& peer); - //------------------------------------------------------------------------- /** Requests that the network infrastructure is to be shut down. This * function is called from a thread, but the actual shutdown needs to be @@ -185,24 +203,48 @@ public: } // requestExit //------------------------------------------------------------------------- void shutdown(); - - void sendPacketExcept(STKPeer* peer, - NetworkString *data, + //------------------------------------------------------------------------- + void sendPacketToAllPeers(NetworkString *data, bool reliable = true); + // ------------------------------------------------------------------------ + /** Returns true if this client instance is allowed to control the server. + * It will auto transfer ownership if previous server owner disconnected. + */ + bool isAuthorisedToControl() const { return m_authorised.load(); } + // ------------------------------------------------------------------------ + /** Sets if this local host is authorised to control the server. */ + void setAuthorisedToControl(bool authorised) + { m_authorised.store(authorised); } + // ------------------------------------------------------------------------ + std::vector > + getAllPlayerProfiles() const; + // ------------------------------------------------------------------------ + std::shared_ptr findPeerByHostId(uint32_t id) const; + // ------------------------------------------------------------------------ + void sendPacketExcept(STKPeer* peer, NetworkString *data, bool reliable = true); - void setupClient(int peer_count, int channel_limit, - uint32_t max_incoming_bandwidth, - uint32_t max_outgoing_bandwidth); - void startListening(); - void stopListening(); - bool peerExists(const TransportAddress& peer_address); - void removePeer(const STKPeer* peer); - bool isConnectedTo(const TransportAddress& peer_address); - STKPeer *getPeer(ENetPeer *enet_peer); - STKPeer *getServerPeerForClient() const; - std::vector getMyPlayerProfiles(); - void setErrorMessage(const irr::core::stringw &message); - bool isAuthorisedToControl() const; - + // ------------------------------------------------------------------------ + void setupClient(int peer_count, int channel_limit, + uint32_t max_incoming_bandwidth, + uint32_t max_outgoing_bandwidth); + // ------------------------------------------------------------------------ + void startListening(); + // ------------------------------------------------------------------------ + void stopListening(); + // ------------------------------------------------------------------------ + bool peerExists(const TransportAddress& peer_address); + // ------------------------------------------------------------------------ + bool isConnectedTo(const TransportAddress& peer_address); + // ------------------------------------------------------------------------ + std::shared_ptr getServerPeerForClient() const; + // ------------------------------------------------------------------------ + void setErrorMessage(const irr::core::stringw &message); + // ------------------------------------------------------------------------ + void addEnetCommand(ENetPeer* peer, ENetPacket* packet, uint32_t i, + ENetCommandType ect) + { + std::lock_guard lock(m_enet_cmd_mutex); + m_enet_cmd.emplace_back(peer, packet, i, ect); + } // ------------------------------------------------------------------------ /** Returns the last error (or "" if no error has happened). */ const irr::core::stringw& getErrorMessage() const @@ -212,9 +254,6 @@ public: * requested. */ bool requestedShutdown() const { return m_shutdown.load(); } // ------------------------------------------------------------------------ - /** Returns the current game setup. */ - GameSetup* getGameSetup() { return m_game_setup; } - // ------------------------------------------------------------------------ int receiveRawPacket(char *buffer, int buffer_len, TransportAddress* sender, int max_tries = -1) { @@ -228,8 +267,17 @@ public: m_network->sendRawPacket(buffer, dst); } // sendRawPacket // ------------------------------------------------------------------------ - /** Returns a const reference to the list of peers. */ - const std::vector &getPeers() { return m_peers; } + /** Returns a copied list of peers. */ + std::vector > getPeers() const + { + std::lock_guard lock(m_peers_mutex); + std::vector > peers; + for (auto p : m_peers) + { + peers.push_back(p.second); + } + return peers; + } // ------------------------------------------------------------------------ /** Returns the next (unique) host id. */ unsigned int getNextHostId() const @@ -239,24 +287,25 @@ public: } // ------------------------------------------------------------------------ /** Returns the number of currently connected peers. */ - unsigned int getPeerCount() { return (int)m_peers.size(); } + unsigned int getPeerCount() const + { + std::lock_guard lock(m_peers_mutex); + return (unsigned)m_peers.size(); + } // ------------------------------------------------------------------------ - /** Sets the global host id of this host. */ - void setMyHostId(uint8_t my_host_id) { m_host_id = my_host_id; } + /** Sets the global host id of this host (client use). */ + void setMyHostId(uint32_t my_host_id) { m_host_id = my_host_id; } // ------------------------------------------------------------------------ /** Returns the host id of this host. */ - uint8_t getMyHostId() const { return m_host_id; } + uint32_t getMyHostId() const { return m_host_id; } // ------------------------------------------------------------------------ - /** Sends a message from a client to the server. */ - void sendToServer(NetworkString *data, bool reliable = true) - { - assert(NetworkConfig::get()->isClient()); - m_peers[0]->sendPacket(data, reliable); - } // sendToServer + void sendToServer(NetworkString *data, bool reliable = true); // ------------------------------------------------------------------------ /** True if this is a client and server in graphics mode made by server * creation screen. */ bool isClientServer() const { return m_separate_process != NULL; } + // ------------------------------------------------------------------------ + void replaceNetwork(ENetEvent& event, Network* network); }; // class STKHost diff --git a/src/network/stk_peer.cpp b/src/network/stk_peer.cpp index 6aa5d8844..400a0caec 100644 --- a/src/network/stk_peer.cpp +++ b/src/network/stk_peer.cpp @@ -17,6 +17,7 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "network/stk_peer.hpp" +#include "config/user_config.hpp" #include "network/game_setup.hpp" #include "network/network_string.hpp" #include "network/network_player_profile.hpp" @@ -29,32 +30,50 @@ /** Constructor for an empty peer. */ -STKPeer::STKPeer(ENetPeer *enet_peer) +STKPeer::STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id) + : m_peer_address(enet_peer->address), m_host(host) { m_enet_peer = enet_peer; - m_is_authorised = false; - m_client_server_token = 0; - m_host_id = 0; - m_token_set = false; + m_host_id = host_id; + m_connected_time = (float)StkTime::getRealTime(); + m_token_set.store(false); + m_client_server_token.store(0); } // STKPeer //----------------------------------------------------------------------------- -/** Destructor. - */ -STKPeer::~STKPeer() -{ - m_enet_peer = NULL; - m_client_server_token = 0; -} // ~STKPeer - -//----------------------------------------------------------------------------- -/** Disconnect from the server. - */ void STKPeer::disconnect() { - enet_peer_disconnect(m_enet_peer, 0); + TransportAddress a(m_enet_peer->address); + if (m_enet_peer->state != ENET_PEER_STATE_CONNECTED || + a != m_peer_address) + return; + m_host->addEnetCommand(m_enet_peer, NULL, PDI_NORMAL, ECT_DISCONNECT); } // disconnect +//----------------------------------------------------------------------------- +/** Kick this peer (used by server). + */ +void STKPeer::kick() +{ + TransportAddress a(m_enet_peer->address); + if (m_enet_peer->state != ENET_PEER_STATE_CONNECTED || + a != m_peer_address) + return; + m_host->addEnetCommand(m_enet_peer, NULL, PDI_KICK, ECT_DISCONNECT); +} // kick + +//----------------------------------------------------------------------------- +/** Forcefully disconnects a peer (used by server). + */ +void STKPeer::reset() +{ + TransportAddress a(m_enet_peer->address); + if (m_enet_peer->state != ENET_PEER_STATE_CONNECTED || + a != m_peer_address) + return; + m_host->addEnetCommand(m_enet_peer, NULL, 0, ECT_RESET); +} // reset + //----------------------------------------------------------------------------- /** Sends a packet to this host. * \param data The data to send. @@ -62,50 +81,32 @@ void STKPeer::disconnect() */ void STKPeer::sendPacket(NetworkString *data, bool reliable) { - data->setToken(m_client_server_token); TransportAddress a(m_enet_peer->address); + // Enet will reuse a disconnected peer so we check here to avoid sending + // to wrong peer + if (m_enet_peer->state != ENET_PEER_STATE_CONNECTED || + a != m_peer_address) + return; + data->setToken(m_client_server_token); Log::verbose("STKPeer", "sending packet of size %d to %s at %f", data->size(), a.toString().c_str(),StkTime::getRealTime()); - + ENetPacket* packet = enet_packet_create(data->getData(), data->getTotalSize(), (reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED)); - enet_peer_send(m_enet_peer, 0, packet); + m_host->addEnetCommand(m_enet_peer, packet, 0, ECT_SEND_PACKET); } // sendPacket -//----------------------------------------------------------------------------- -/** Returns the IP address (in host format) of this client. - */ -uint32_t STKPeer::getAddress() const -{ - return ntohl(m_enet_peer->address.host); -} // getAddress - -//----------------------------------------------------------------------------- -/** Returns the port of this peer. - */ -uint16_t STKPeer::getPort() const -{ - return m_enet_peer->address.port; -} - //----------------------------------------------------------------------------- /** Returns if the peer is connected or not. */ bool STKPeer::isConnected() const { - Log::info("STKPeer", "The peer state is %i", m_enet_peer->state); + Log::debug("STKPeer", "The peer state is %i", m_enet_peer->state); return (m_enet_peer->state == ENET_PEER_STATE_CONNECTED); } // isConnected -//----------------------------------------------------------------------------- - -bool STKPeer::exists() const -{ - return (m_enet_peer != NULL); // assert that the peer exists -} - //----------------------------------------------------------------------------- /** Returns if this STKPeer is the same as the given peer. */ @@ -123,12 +124,12 @@ bool STKPeer::isSamePeer(const ENetPeer* peer) const } // isSamePeer //----------------------------------------------------------------------------- -/** Returns the list of all player profiles connected to this peer. Note that - * this function is somewhat expensive (it loops over all network profiles - * to find the ones with the same host id as this peer. +/** Returns the ping to this peer from host, it waits for 15 seconds for a + * stable ping returned by enet measured in ms. */ -std::vector STKPeer::getAllPlayerProfiles() +uint32_t STKPeer::getPing() const { - return STKHost::get()->getGameSetup()->getAllPlayersOnHost(getHostId()); -} // getAllPlayerProfiles - + if ((float)StkTime::getRealTime() - m_connected_time < 15.0f) + return 0; + return m_enet_peer->lastRoundTripTime; +} // getPing diff --git a/src/network/stk_peer.hpp b/src/network/stk_peer.hpp index 589c1bebf..c381c0f60 100644 --- a/src/network/stk_peer.hpp +++ b/src/network/stk_peer.hpp @@ -23,17 +23,28 @@ #ifndef STK_PEER_HPP #define STK_PEER_HPP +#include "network/transport_address.hpp" #include "utils/no_copy.hpp" #include "utils/types.hpp" #include +#include +#include #include class NetworkPlayerProfile; class NetworkString; +class STKHost; class TransportAddress; +enum PeerDisconnectInfo : unsigned int +{ + PDI_TIMEOUT = 0, //!< Timeout disconnected (default in enet). + PDI_NORMAL = 1, //!< Normal disconnction with acknowledgement + PDI_KICK = 2, //!< Kick disconnection +}; // PeerDisconnectInfo + /*! \class STKPeer * \brief Represents a peer. * This class is used to interface the ENetPeer structure. @@ -45,61 +56,78 @@ protected: ENetPeer* m_enet_peer; /** The token of this client. */ - uint32_t m_client_server_token; + std::atomic m_client_server_token; /** True if the token for this peer has been set. */ - bool m_token_set; + std::atomic_bool m_token_set; /** Host id of this peer. */ int m_host_id; - /** True if this peer is authorised to control a server. */ - bool m_is_authorised; -public: - STKPeer(ENetPeer *enet_peer); - virtual ~STKPeer(); + TransportAddress m_peer_address; - virtual void sendPacket(NetworkString *data, - bool reliable = true); + STKHost* m_host; + + std::vector > m_players; + + float m_connected_time; + +public: + STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id); + ~STKPeer() {} + // ------------------------------------------------------------------------ + void sendPacket(NetworkString *data, bool reliable = true); + // ------------------------------------------------------------------------ void disconnect(); + // ------------------------------------------------------------------------ + void kick(); + // ------------------------------------------------------------------------ + void reset(); + // ------------------------------------------------------------------------ bool isConnected() const; - bool exists() const; - uint32_t getAddress() const; - uint16_t getPort() const; + const TransportAddress& getAddress() const { return m_peer_address; } bool isSamePeer(const STKPeer* peer) const; bool isSamePeer(const ENetPeer* peer) const; - std::vector getAllPlayerProfiles(); + // ------------------------------------------------------------------------ + std::vector >& getPlayerProfiles() + { return m_players; } + // ------------------------------------------------------------------------ + bool hasPlayerProfiles() const { return !m_players.empty(); } + // ------------------------------------------------------------------------ + void cleanPlayerProfiles() { m_players.clear(); } + // ------------------------------------------------------------------------ + void addPlayer(std::shared_ptr p) + { m_players.push_back(p); } // ------------------------------------------------------------------------ /** Sets the token for this client. */ - void setClientServerToken(const uint32_t& token) + void setClientServerToken(const uint32_t token) { - m_client_server_token = token; - m_token_set = true; + m_client_server_token.store(token); + m_token_set.store(true); } // setClientServerToken // ------------------------------------------------------------------------ - void unsetClientServerToken() { m_token_set = false; } + /** Unsets the token for this client. (used in server to invalidate peer) + */ + void unsetClientServerToken() + { + m_client_server_token.store(0); + m_token_set.store(false); + } // ------------------------------------------------------------------------ /** Returns the token of this client. */ - uint32_t getClientServerToken() const { return m_client_server_token; } + uint32_t getClientServerToken() const + { return m_client_server_token.load(); } // ------------------------------------------------------------------------ /** Returns if the token for this client is known. */ - bool isClientServerTokenSet() const { return m_token_set; } - // ------------------------------------------------------------------------ - /** Sets the host if of this peer. */ - void setHostId(int host_id) { m_host_id = host_id; } + bool isClientServerTokenSet() const { return m_token_set.load(); } // ------------------------------------------------------------------------ /** Returns the host id of this peer. */ - int getHostId() const { return m_host_id; } + uint32_t getHostId() const { return m_host_id; } // ------------------------------------------------------------------------ - /** Sets if this peer is authorised to control the server. */ - void setAuthorised(bool authorised) { m_is_authorised = authorised; } + float getConnectedTime() const { return m_connected_time; } // ------------------------------------------------------------------------ - /** Returns if this peer is authorised to control the server. The server - * uses this to check if a peer is allowed certain commands; and a client - * uses this function (in which case this peer is actually the server - * peer) to see if this client is allowed certain command (i.e. to - * display additional GUI elements). */ - bool isAuthorised() const { return m_is_authorised; } + uint32_t getPing() const; + }; // STKPeer #endif // STK_PEER_HPP diff --git a/src/online/online_player_profile.hpp b/src/online/online_player_profile.hpp index 318a68dcd..9f2935b56 100644 --- a/src/online/online_player_profile.hpp +++ b/src/online/online_player_profile.hpp @@ -96,10 +96,6 @@ namespace Online return m_online_state; } // getOnlineState - // ---------------------------------------------------------------- - /** Returns a pointer to the profile associated with the current user. */ - OnlineProfile* getProfile() const { return m_profile; } - // ---------------------------------------------------------------- /** Returns the session token of the signed in user. */ const std::string& getToken() const { return m_token; } @@ -113,6 +109,9 @@ namespace Online OnlinePlayerProfile(const core::stringw &name, bool is_guest = false); virtual ~OnlinePlayerProfile() {} // ---------------------------------------------------------------- + /** Returns a pointer to the profile associated with the current user. */ + OnlineProfile* getProfile() const { return m_profile; } + // ---------------------------------------------------------------- }; // class OnlinePlayerProfile } // namespace Online #endif // HEADER_CURRENT_ONLINE_USER_HPP diff --git a/src/online/online_profile.cpp b/src/online/online_profile.cpp index e3c0f99fb..0beea1de9 100644 --- a/src/online/online_profile.cpp +++ b/src/online/online_profile.cpp @@ -65,7 +65,7 @@ OnlineProfile::OnlineProfile(const uint32_t & userid, m_id = userid; m_is_current_user = is_current_user; m_username = username; - m_has_fetched_friends = false; + m_has_fetched_friends.store(false); m_has_fetched_achievements = false; m_relation_info = NULL; m_is_friend = false; @@ -85,7 +85,7 @@ OnlineProfile::OnlineProfile(const XMLNode * xml, ConstructorType type) m_relation_info = NULL; m_is_friend = false; m_cache_bit = true; - m_has_fetched_friends = false; + m_has_fetched_friends.store(false); m_has_fetched_achievements = false; if (type == C_RELATION_INFO) { @@ -184,7 +184,7 @@ void OnlineProfile::storeAchievements(const XMLNode * input) void OnlineProfile::fetchFriends() { assert(PlayerManager::isCurrentLoggedIn()); - if (m_has_fetched_friends) + if (m_has_fetched_friends.load()) return; m_state = State(m_state | S_FETCHING_FRIENDS); @@ -238,8 +238,8 @@ void OnlineProfile::storeFriends(const XMLNode * input) ProfileManager::get()->addToCache(profile); } } // for i in nodes - m_has_fetched_friends = true; m_state = State(m_state & ~S_FETCHING_FRIENDS); + m_has_fetched_friends.store(true); } // storeFriends // ---------------------------------------------------------------------------- @@ -248,7 +248,7 @@ void OnlineProfile::storeFriends(const XMLNode * input) */ void OnlineProfile::removeFriend(const uint32_t id) { - assert(m_has_fetched_friends); + assert(m_has_fetched_friends.load()); IDList::iterator iter; for (iter = m_friends.begin(); iter != m_friends.end();) { @@ -270,7 +270,7 @@ void OnlineProfile::removeFriend(const uint32_t id) */ void OnlineProfile::addFriend(const uint32_t id) { - assert(m_has_fetched_friends); + assert(m_has_fetched_friends.load()); // find if friend id is is already in the user list for (unsigned int i = 0; i < m_friends.size(); i++) @@ -296,7 +296,7 @@ void OnlineProfile::deleteRelationalInfo() */ const OnlineProfile::IDList& OnlineProfile::getFriends() { - assert(m_has_fetched_friends && + assert(m_has_fetched_friends.load() && (m_state & S_FETCHING_FRIENDS) == 0); return m_friends; } // getFriends @@ -322,7 +322,8 @@ void OnlineProfile::merge(OnlineProfile *profile) assert(profile != NULL); // profile has fetched friends, use that instead - if (!m_has_fetched_friends && profile->m_has_fetched_friends) + if (!m_has_fetched_friends.load() && + profile->m_has_fetched_friends.load()) m_friends = profile->m_friends; // profile has fetched achievements, use that instead diff --git a/src/online/online_profile.hpp b/src/online/online_profile.hpp index acded73bb..2985bf52d 100644 --- a/src/online/online_profile.hpp +++ b/src/online/online_profile.hpp @@ -24,6 +24,7 @@ #include "utils/types.hpp" #include "utils/ptr_vector.hpp" +#include #include #include @@ -91,7 +92,7 @@ private: /** Whether or not the user of this profile, is a friend of the current user */ bool m_is_friend; - bool m_has_fetched_friends; + std::atomic_bool m_has_fetched_friends; /** List of user id's that are friends with the user of this profile. * In case this profile is of the current user, this list also contains @@ -123,15 +124,15 @@ public: // ------------------------------------------------------------------------ /** Returns true if the achievements for this profile have been fetched. */ - bool hasFetchedAchievements() const { return m_has_fetched_achievements; } + bool hasFetchedAchievements() const { return m_has_fetched_achievements; } // ------------------------------------------------------------------------ /** Unsets the flag that all friends of this profile are in cache. Used * when a profile is pushed out of cache. */ - void unsetHasFetchedFriends() { m_has_fetched_friends = false; } + void unsetHasFetchedFriends() { m_has_fetched_friends.store(false); } // ------------------------------------------------------------------------ /** Returns true if the friend list for this profile has been fetched. */ - bool hasFetchedFriends() const { return m_has_fetched_friends; } + bool hasFetchedFriends() const { return m_has_fetched_friends.load(); } // ------------------------------------------------------------------------ /** True if the profile has fetched friends. */ diff --git a/src/states_screens/addons_screen.cpp b/src/states_screens/addons_screen.cpp index d2acfdf44..4ba06209a 100644 --- a/src/states_screens/addons_screen.cpp +++ b/src/states_screens/addons_screen.cpp @@ -35,8 +35,6 @@ #include -DEFINE_SCREEN_SINGLETON( AddonsScreen ); - using namespace Online; // ---------------------------------------------------------------------------- diff --git a/src/states_screens/arenas_screen.cpp b/src/states_screens/arenas_screen.cpp index ff60893de..3c9072148 100644 --- a/src/states_screens/arenas_screen.cpp +++ b/src/states_screens/arenas_screen.cpp @@ -37,8 +37,6 @@ using namespace GUIEngine; using namespace irr::core; using namespace irr::video; -DEFINE_SCREEN_SINGLETON( ArenasScreen ); - static const char ALL_ARENA_GROUPS_ID[] = "all"; diff --git a/src/states_screens/create_server_screen.cpp b/src/states_screens/create_server_screen.cpp index 7587371bc..48ca02931 100644 --- a/src/states_screens/create_server_screen.cpp +++ b/src/states_screens/create_server_screen.cpp @@ -15,23 +15,16 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -#define DEBUG_MENU_ITEM 0 - #include "states_screens/create_server_screen.hpp" #include "audio/sfx_manager.hpp" -#include "challenges/unlock_manager.hpp" #include "config/player_manager.hpp" #include "config/user_config.hpp" -#include "modes/demo_world.hpp" -#include "network/protocols/lobby_protocol.hpp" #include "network/network_config.hpp" #include "network/server.hpp" #include "network/stk_host.hpp" #include "states_screens/state_manager.hpp" -#include "states_screens/dialogs/message_dialog.hpp" #include "states_screens/networking_lobby.hpp" -#include "states_screens/dialogs/server_info_dialog.hpp" #include "utils/separate_process.hpp" #include "utils/translation.hpp" @@ -43,8 +36,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( CreateServerScreen ); - // ---------------------------------------------------------------------------- CreateServerScreen::CreateServerScreen() : Screen("online/create_server.stkgui") @@ -62,7 +53,7 @@ void CreateServerScreen::loadedFromFile() assert(m_max_players_widget != NULL); int max = UserConfigParams::m_server_max_players.getDefaultValue(); m_max_players_widget->setMax(max); - + if (UserConfigParams::m_server_max_players > max) UserConfigParams::m_server_max_players = max; @@ -71,8 +62,15 @@ void CreateServerScreen::loadedFromFile() m_info_widget = getWidget("info"); assert(m_info_widget != NULL); + m_more_options_text = getWidget("more-options"); + assert(m_more_options_text != NULL); + m_more_options_spinner = getWidget("more-options-spinner"); + assert(m_more_options_spinner != NULL); + m_options_widget = getWidget("options"); assert(m_options_widget != NULL); + m_game_mode_widget = getWidget("gamemode"); + assert(m_game_mode_widget != NULL); m_create_widget = getWidget("create"); assert(m_create_widget != NULL); m_cancel_widget = getWidget("cancel"); @@ -83,7 +81,6 @@ void CreateServerScreen::loadedFromFile() void CreateServerScreen::init() { Screen::init(); - DemoWorld::resetIdleTime(); m_info_widget->setText("", false); LabelWidget *title = getWidget("title"); @@ -109,6 +106,7 @@ void CreateServerScreen::init() RibbonWidget* gamemode = getWidget("gamemode"); assert(gamemode != NULL); gamemode->setSelection(0, PLAYER_ID_GAME_MASTER); + updateMoreOption(0); } // init // ---------------------------------------------------------------------------- @@ -131,8 +129,62 @@ void CreateServerScreen::eventCallback(Widget* widget, const std::string& name, createServer(); } // is create_widget } + else if (name == m_game_mode_widget->m_properties[PROP_ID]) + { + const int selection = + m_game_mode_widget->getSelection(PLAYER_ID_GAME_MASTER); + updateMoreOption(selection); + } + } // eventCallback +// ---------------------------------------------------------------------------- +void CreateServerScreen::updateMoreOption(int game_mode) +{ + switch (game_mode) + { + case 0: + case 1: + { + m_more_options_text->setVisible(true); + //I18N: In the create server screen + m_more_options_text->setText(_("No. of grand prix track(s)"), + false); + m_more_options_spinner->setVisible(true); + m_more_options_spinner->clearLabels(); + m_more_options_spinner->addLabel(_("Disabled")); + for (int i = 1; i <= 20; i++) + { + m_more_options_spinner->addLabel(StringUtils::toWString(i)); + } + m_more_options_spinner->setValue(0); + break; + } + case 3: + { + m_more_options_text->setVisible(true); + m_more_options_spinner->setVisible(true); + m_more_options_spinner->clearLabels(); + //I18N: In the create server screen + m_more_options_text->setText(_("Soccer game type"), false); + m_more_options_spinner->setVisible(true); + m_more_options_spinner->clearLabels(); + //I18N: In the create server screen for soccer server + m_more_options_spinner->addLabel(_("Time limit")); + //I18N: In the create server screen for soccer server + m_more_options_spinner->addLabel(_("Goals limit")); + m_more_options_spinner->setValue(0); + break; + } + default: + { + m_more_options_text->setVisible(false); + m_more_options_spinner->setVisible(false); + break; + } + } +} // updateMoreOption + // ---------------------------------------------------------------------------- /** Called once per framce to check if the server creation request has * finished. If so, if pushes the server creation sceen. @@ -164,18 +216,31 @@ void CreateServerScreen::createServer() if (name.size() < 4 || name.size() > 30) { + //I18N: In the create server screen m_info_widget->setText( _("Name has to be between 4 and 30 characters long!"), false); SFXManager::get()->quickSound("anvil"); return; } - assert(max_players > 1 && - max_players <= UserConfigParams::m_server_max_players.getDefaultValue()); + assert(max_players > 1 && max_players <= + UserConfigParams::m_server_max_players.getDefaultValue()); UserConfigParams::m_server_max_players = max_players; - core::stringw password_w = getWidget("password")->getText(); - std::string password = StringUtils::xmlEncode(password_w); - NetworkConfig::get()->setPassword(StringUtils::wideToUtf8(password_w)); + std::string password = StringUtils::wideToUtf8(getWidget + ("password")->getText()); + if ((!password.empty() != 0 && + password.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + "QRSTUVWXYZ01234567890_") != std::string::npos) || + password.size() > 255) + { + //I18N: In the create server screen + m_info_widget->setText( + _("Incorrect characters in password!"), false); + SFXManager::get()->quickSound("anvil"); + return; + } + + NetworkConfig::get()->setPassword(password); if (!password.empty()) password = std::string(" --server-password=") + password; @@ -186,7 +251,7 @@ void CreateServerScreen::createServer() max_players, /*current_player*/0, (RaceManager::Difficulty) difficulty_widget->getSelection(PLAYER_ID_GAME_MASTER), NetworkConfig::get()->getServerGameMode(race_manager->getMinorMode(), - race_manager->getMajorMode()), server_address); + race_manager->getMajorMode()), server_address, !password.empty()); #undef USE_GRAPHICS_SERVER #ifdef USE_GRAPHICS_SERVER @@ -250,7 +315,7 @@ void CreateServerScreen::createServer() new SeparateProcess(SeparateProcess::getCurrentExecutableLocation(), server_cfg.str() + password); STKHost::create(server, sp); - + NetworkingLobby::getInstance()->setJoinedServer(server); #endif } // createServer diff --git a/src/states_screens/create_server_screen.hpp b/src/states_screens/create_server_screen.hpp index 5429d6ec2..2793ef98a 100644 --- a/src/states_screens/create_server_screen.hpp +++ b/src/states_screens/create_server_screen.hpp @@ -39,14 +39,18 @@ private: GUIEngine::TextBoxWidget * m_name_widget; GUIEngine::SpinnerWidget * m_max_players_widget; + GUIEngine::SpinnerWidget* m_more_options_spinner; + GUIEngine::LabelWidget * m_more_options_text; GUIEngine::LabelWidget * m_info_widget; + GUIEngine::RibbonWidget * m_game_mode_widget; GUIEngine::RibbonWidget * m_options_widget; GUIEngine::IconButtonWidget * m_create_widget; GUIEngine::IconButtonWidget * m_cancel_widget; void createServer(); + void updateMoreOption(int game_mode); public: diff --git a/src/states_screens/credits.cpp b/src/states_screens/credits.cpp index 1519c74b3..5c8375a7b 100644 --- a/src/states_screens/credits.cpp +++ b/src/states_screens/credits.cpp @@ -33,8 +33,6 @@ using irr::core::stringc; #include "utils/string_utils.hpp" #include "utils/translation.hpp" -DEFINE_SCREEN_SINGLETON( CreditsScreen ); - using namespace GUIEngine; const float TIME_SECTION_FADE = 0.8f; const float ENTRIES_FADE_TIME = 0.3f; diff --git a/src/states_screens/dialogs/kart_color_slider_dialog.cpp b/src/states_screens/dialogs/kart_color_slider_dialog.cpp index efb17e2de..228ba7582 100644 --- a/src/states_screens/dialogs/kart_color_slider_dialog.cpp +++ b/src/states_screens/dialogs/kart_color_slider_dialog.cpp @@ -27,6 +27,7 @@ #include "guiengine/engine.hpp" #include "guiengine/widgets/model_view_widget.hpp" #include "guiengine/widgets/spinner_widget.hpp" +#include "states_screens/state_manager.hpp" using namespace GUIEngine; @@ -36,10 +37,11 @@ KartColorSliderDialog::KartColorSliderDialog(PlayerProfile* pp) { loadFromFile("kart_color_slider.stkgui"); m_player_profile = pp; - getWidget("color-slider")->setValue( - int(pp->getDefaultKartColor() * 100.0f)); - m_model_view->getModelViewRenderInfo()->setHue( - float(getWidget("color-slider")->getValue()) / 100.0f); + + SpinnerWidget* color_slider = getWidget("color-slider"); + color_slider->setValue(int(pp->getDefaultKartColor() * 100.0f)); + m_model_view->getModelViewRenderInfo()->setHue(float(color_slider->getValue()) / 100.0f); + color_slider->setFocusForPlayer(PLAYER_ID_GAME_MASTER); } // KartColorSliderDialog // ---------------------------------------------------------------------------- diff --git a/src/states_screens/dialogs/network_user_dialog.cpp b/src/states_screens/dialogs/network_user_dialog.cpp new file mode 100644 index 000000000..35af4cb13 --- /dev/null +++ b/src/states_screens/dialogs/network_user_dialog.cpp @@ -0,0 +1,125 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "states_screens/dialogs/network_user_dialog.hpp" + +#include "config/player_manager.hpp" +#include "guiengine/dialog_queue.hpp" +#include "guiengine/engine.hpp" +#include "guiengine/widgets/icon_button_widget.hpp" +#include "guiengine/widgets/label_widget.hpp" +#include "guiengine/widgets/ribbon_widget.hpp" +#include "online/online_profile.hpp" +#include "network/protocols/lobby_protocol.hpp" +#include "network/stk_host.hpp" +#include "states_screens/state_manager.hpp" +#include "utils/translation.hpp" + +#include + +using namespace GUIEngine; +using namespace irr; +using namespace irr::gui; +using namespace Online; + +// ---------------------------------------------------------------------------- +void NetworkUserDialog::beforeAddingWidgets() +{ + m_name_widget = getWidget("name"); + assert(m_name_widget != NULL); + m_name_widget->setText(m_name, false); + + m_friend_widget = getWidget("friend"); + assert(m_friend_widget != NULL); + m_friend_widget->setVisible(m_online_id != 0); + + // Hide friend request button if already friend + Online::OnlineProfile* opp = + PlayerManager::getCurrentPlayer()->getProfile(); + if (m_online_id != 0 && opp && opp->hasFetchedFriends()) + { + for (uint32_t user_id : opp->getFriends()) + { + if (user_id == m_online_id) + { + m_friend_widget->setVisible(false); + } + } + } + + m_kick_widget = getWidget("decline"); + assert(m_kick_widget != NULL); + + //I18N: In the network user dialog + m_kick_widget->setText(_("Kick")); + m_kick_widget->setVisible(STKHost::get()->isAuthorisedToControl()); + + m_cancel_widget = getWidget("cancel"); + assert(m_cancel_widget != NULL); + + m_options_widget = getWidget("options"); + assert(m_options_widget != NULL); + m_options_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + m_options_widget->select("cancel", PLAYER_ID_GAME_MASTER); + + getWidget("accept")->setVisible(false); + getWidget("remove")->setVisible(false); + getWidget("enter")->setVisible(false); + getWidget("info")->setVisible(false); +} // beforeAddingWidgets + +// ----------------------------------------------------------------------------- +GUIEngine::EventPropagation + NetworkUserDialog::processEvent(const std::string& source) +{ + if (source == m_options_widget->m_properties[PROP_ID]) + { + const std::string& selection = m_options_widget + ->getSelectionIDString(PLAYER_ID_GAME_MASTER); + if (selection == m_cancel_widget->m_properties[PROP_ID]) + { + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + else if(selection == m_friend_widget->m_properties[PROP_ID]) + { + XMLRequest *request = new XMLRequest(); + PlayerManager::setUserDetails(request, "friend-request"); + request->addParameter("friendid", m_online_id); + request->queue(); + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + else if(selection == m_kick_widget->m_properties[PROP_ID]) + { + NetworkString kick(PROTOCOL_LOBBY_ROOM); + kick.addUInt8(LobbyProtocol::LE_KICK_HOST).addUInt32(m_host_id); + STKHost::get()->sendToServer(&kick, true/*reliable*/); + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + } + return GUIEngine::EVENT_LET; +} // processEvent + +// ----------------------------------------------------------------------------- +bool NetworkUserDialog::onEscapePressed() +{ + if (m_cancel_widget->isActivated()) + m_self_destroy = true; + return false; +} // onEscapePressed diff --git a/src/states_screens/dialogs/network_user_dialog.hpp b/src/states_screens/dialogs/network_user_dialog.hpp new file mode 100644 index 000000000..2b1ee1345 --- /dev/null +++ b/src/states_screens/dialogs/network_user_dialog.hpp @@ -0,0 +1,89 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef HEADER_NETWORK_USER_DIALOG_HPP +#define HEADER_NETWORK_USER_DIALOG_HPP + +#include "guiengine/modaldialog.hpp" +#include "utils/types.hpp" + +#include + +namespace GUIEngine +{ + class IconButtonWidget; + class LabelWidget; + class RibbonWidget; +} + +/** + * \brief Dialog that handle user in network lobby + * \ingroup states_screens + */ +class NetworkUserDialog : public GUIEngine::ModalDialog +{ +private: + const uint32_t m_host_id; + + const uint32_t m_online_id; + + const core::stringw m_name; + + bool m_self_destroy; + + GUIEngine::RibbonWidget * m_options_widget; + + GUIEngine::LabelWidget * m_name_widget; + + GUIEngine::IconButtonWidget * m_friend_widget; + + GUIEngine::IconButtonWidget * m_kick_widget; + + GUIEngine::IconButtonWidget * m_cancel_widget; + +public: + NetworkUserDialog(uint32_t host_id, uint32_t online_id, + const core::stringw& name) + : ModalDialog(0.8f,0.8f), m_host_id(host_id), m_online_id(online_id), + m_name(name), m_self_destroy(false) + { + loadFromFile("online/user_info_dialog.stkgui"); + } + // ------------------------------------------------------------------------ + ~NetworkUserDialog() {} + // ------------------------------------------------------------------------ + virtual void beforeAddingWidgets(); + // ------------------------------------------------------------------------ + void onEnterPressedInternal() { m_self_destroy = true; } + // ------------------------------------------------------------------------ + GUIEngine::EventPropagation processEvent(const std::string& source); + // ------------------------------------------------------------------------ + virtual bool onEscapePressed(); + // ------------------------------------------------------------------------ + virtual void onUpdate(float dt) + { + // It's unsafe to delete from inside the event handler so we do it here + if (m_self_destroy) + { + ModalDialog::dismiss(); + return; + } + } +}; + +#endif diff --git a/src/states_screens/dialogs/server_info_dialog.cpp b/src/states_screens/dialogs/server_info_dialog.cpp index 7597c3fbe..fb13a23d8 100644 --- a/src/states_screens/dialogs/server_info_dialog.cpp +++ b/src/states_screens/dialogs/server_info_dialog.cpp @@ -18,8 +18,13 @@ #include "states_screens/dialogs/server_info_dialog.hpp" #include "guiengine/engine.hpp" +#include "guiengine/widgets/icon_button_widget.hpp" +#include "guiengine/widgets/label_widget.hpp" +#include "guiengine/widgets/ribbon_widget.hpp" +#include "guiengine/widgets/text_box_widget.hpp" #include "network/server.hpp" #include "network/stk_host.hpp" +#include "network/network_config.hpp" #include "states_screens/networking_lobby.hpp" #include "states_screens/state_manager.hpp" #include "utils/string_utils.hpp" @@ -39,10 +44,10 @@ using namespace Online; * server (i.e. while it is being created). */ ServerInfoDialog::ServerInfoDialog(std::shared_ptr server) - : ModalDialog(0.8f,0.8f), m_server(server) + : ModalDialog(0.8f,0.8f), m_server(server), m_password(NULL) { - Log::info("ServerInfoDialog", "Server id is %d, Host id is %d", - server->getServerId(), server->getHostId()); + Log::info("ServerInfoDialog", "Server id is %d, owner is %d", + server->getServerId(), server->getServerOwner()); m_self_destroy = false; loadFromFile("online/server_info_dialog.stkgui"); @@ -67,10 +72,21 @@ ServerInfoDialog::ServerInfoDialog(std::shared_ptr server) assert(m_cancel_widget != NULL); m_options_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + if (m_server->isPasswordProtected()) + { + m_password = getWidget("password"); + m_password->setPasswordBox(true, L'*'); + assert(m_password != NULL); + } + else + { + getWidget("label_password")->setVisible(false); + getWidget("password")->setVisible(false); + } + } // ServerInfoDialog // ----------------------------------------------------------------------------- - ServerInfoDialog::~ServerInfoDialog() { } // ~ServerInfoDialog @@ -78,6 +94,16 @@ ServerInfoDialog::~ServerInfoDialog() // ----------------------------------------------------------------------------- void ServerInfoDialog::requestJoin() { + if (m_server->isPasswordProtected()) + { + assert(m_password != NULL); + NetworkConfig::get()->setPassword( + StringUtils::wideToUtf8(m_password->getText())); + } + else + { + NetworkConfig::get()->setPassword(""); + } STKHost::create(m_server); NetworkingLobby::getInstance()->setJoinedServer(m_server); ModalDialog::dismiss(); diff --git a/src/states_screens/dialogs/server_info_dialog.hpp b/src/states_screens/dialogs/server_info_dialog.hpp index 5c89ababa..38b08b985 100644 --- a/src/states_screens/dialogs/server_info_dialog.hpp +++ b/src/states_screens/dialogs/server_info_dialog.hpp @@ -20,11 +20,16 @@ #define HEADER_SERVER_INFO_DIALOG_HPP #include "guiengine/modaldialog.hpp" -#include "guiengine/widgets/icon_button_widget.hpp" -#include "guiengine/widgets/ribbon_widget.hpp" -#include "guiengine/widgets/label_widget.hpp" #include "utils/types.hpp" +namespace GUIEngine +{ + class LabelWidget; + class RibbonWidget; + class IconButtonWidget; + class TextBoxWidget; +} + #include #include @@ -49,6 +54,8 @@ private: /** The cancel button. */ GUIEngine::IconButtonWidget *m_cancel_widget; + /** Specify server password if needed. */ + GUIEngine::TextBoxWidget* m_password; public: ServerInfoDialog(std::shared_ptr server); diff --git a/src/states_screens/dialogs/splitscreen_player_dialog.cpp b/src/states_screens/dialogs/splitscreen_player_dialog.cpp new file mode 100644 index 000000000..9bab07929 --- /dev/null +++ b/src/states_screens/dialogs/splitscreen_player_dialog.cpp @@ -0,0 +1,160 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "states_screens/dialogs/splitscreen_player_dialog.hpp" + +#include "config/player_manager.hpp" +#include "config/user_config.hpp" +#include "input/device_manager.hpp" +#include "input/input_manager.hpp" +#include "guiengine/dialog_queue.hpp" +#include "guiengine/engine.hpp" +#include "guiengine/message_queue.hpp" +#include "guiengine/widgets/check_box_widget.hpp" +#include "guiengine/widgets/icon_button_widget.hpp" +#include "guiengine/widgets/label_widget.hpp" +#include "guiengine/widgets/ribbon_widget.hpp" +#include "guiengine/widgets/spinner_widget.hpp" +#include "network/network_config.hpp" +#include "states_screens/networking_lobby.hpp" +#include "utils/translation.hpp" + +#include + +using namespace GUIEngine; +using namespace irr; + +// ---------------------------------------------------------------------------- +void SplitscreenPlayerDialog::beforeAddingWidgets() +{ + m_profiles = getWidget("name-spinner"); + for (unsigned i = 0; i < PlayerManager::get()->getNumNonGuestPlayers(); + i++) + { + PlayerProfile* p = PlayerManager::get()->getPlayer(i); + if (!NetworkConfig::get()->playerExists(p)) + { + m_profiles->addLabel(p->getName()); + m_available_players.push_back(p); + } + } + + m_message = getWidget("message-label"); + assert(m_message != NULL); + m_handicap = getWidget("handicap"); + assert(m_handicap != NULL); + m_options_widget = getWidget("options"); + assert(m_options_widget != NULL); + m_add = getWidget("add"); + assert(m_add != NULL); + m_connect = getWidget("connect"); + assert(m_connect != NULL); + m_cancel = getWidget("cancel"); + assert(m_cancel != NULL); + m_reset = getWidget("reset"); + assert(m_reset != NULL); + + if (NetworkConfig::get()->getNetworkPlayers().size() == MAX_PLAYER_COUNT) + { + m_available_players.clear(); + } + + m_options_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + if (m_available_players.empty()) + { + getWidget("name-text")->setVisible(false); + getWidget("handicap-row")->setVisible(false); + m_add->setVisible(false); + m_profiles->setVisible(false); + m_options_widget->select("connect", PLAYER_ID_GAME_MASTER); + } + else + { + getWidget("name-text")->setVisible(true); + getWidget("handicap-row")->setVisible(true); + m_add->setVisible(true); + m_profiles->setVisible(true); + m_handicap->setState(false); + m_handicap->setActive(UserConfigParams::m_per_player_difficulty); + m_options_widget->select("add", PLAYER_ID_GAME_MASTER); + } + + input_manager->getDeviceManager()->setAssignMode(NO_ASSIGN); + input_manager->getDeviceManager()->mapFireToSelect(false); + +} // beforeAddingWidgets + +// ----------------------------------------------------------------------------- +GUIEngine::EventPropagation + SplitscreenPlayerDialog::processEvent(const std::string& source) +{ + if (source == m_options_widget->m_properties[PROP_ID]) + { + const std::string& selection = m_options_widget + ->getSelectionIDString(PLAYER_ID_GAME_MASTER); + if (selection == m_add->m_properties[PROP_ID]) + { + const unsigned pid = m_profiles->getValue(); + assert(pid < PlayerManager::get()->getNumPlayers()); + PlayerProfile* p = m_available_players[pid]; + const bool handicap = m_handicap->getState(); + if (NetworkConfig::get()->addNetworkPlayer(m_device, p, handicap)) + { + NetworkingLobby::getInstance() + ->addSplitscreenPlayer(p->getName()); + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + else + { + //I18N: in splitscreen player dialog for network game + m_message->setErrorColor(); + m_message->setText(_("Input device already exists."), + false); + } + return GUIEngine::EVENT_BLOCK; + } + else if(selection == m_connect->m_properties[PROP_ID]) + { + if (!NetworkConfig::get()->getNetworkPlayers().empty()) + { + NetworkConfig::get()->doneAddingNetworkPlayers(); + NetworkingLobby::getInstance()->finishAddingPlayers(); + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + //I18N: in splitscreen player dialog for network game + m_message->setErrorColor(); + m_message->setText( + _("No player available for connecting to server."), false); + return GUIEngine::EVENT_BLOCK; + } + else if(selection == m_cancel->m_properties[PROP_ID]) + { + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + else if(selection == m_reset->m_properties[PROP_ID]) + { + NetworkConfig::get()->cleanNetworkPlayers(); + NetworkingLobby::getInstance()->cleanAddedPlayers(); + m_self_destroy = true; + return GUIEngine::EVENT_BLOCK; + } + } + return GUIEngine::EVENT_LET; +} // processEvent diff --git a/src/states_screens/dialogs/splitscreen_player_dialog.hpp b/src/states_screens/dialogs/splitscreen_player_dialog.hpp new file mode 100644 index 000000000..3a84c707b --- /dev/null +++ b/src/states_screens/dialogs/splitscreen_player_dialog.hpp @@ -0,0 +1,101 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef HEADER_SPLITSCREEN_PLAYER_DIALOG_HPP +#define HEADER_SPLITSCREEN_PLAYER_DIALOG_HPP + +#include "guiengine/modaldialog.hpp" +#include "utils/types.hpp" + +#include +#include + +class InputDevice; +class PlayerProfile; + +namespace GUIEngine +{ + class CheckBoxWidget; + class IconButtonWidget; + class LabelWidget; + class RibbonWidget; + class SpinnerWidget; +} + +/** + * \brief Dialog that handle user in network lobby + * \ingroup states_screens + */ +class SplitscreenPlayerDialog : public GUIEngine::ModalDialog +{ +private: + InputDevice* m_device; + + bool m_self_destroy; + + std::vector m_available_players; + + GUIEngine::LabelWidget* m_message; + + GUIEngine::SpinnerWidget* m_profiles; + + GUIEngine::CheckBoxWidget* m_handicap; + + GUIEngine::RibbonWidget* m_options_widget; + + GUIEngine::IconButtonWidget* m_add; + + GUIEngine::IconButtonWidget* m_connect; + + GUIEngine::IconButtonWidget* m_cancel; + + GUIEngine::IconButtonWidget* m_reset; + +public: + SplitscreenPlayerDialog(InputDevice* device) + : ModalDialog(0.8f,0.8f), m_device(device), m_self_destroy(false) + { + loadFromFile("online/splitscreen_player_dialog.stkgui"); + } + // ------------------------------------------------------------------------ + ~SplitscreenPlayerDialog() {} + // ------------------------------------------------------------------------ + virtual void beforeAddingWidgets(); + // ------------------------------------------------------------------------ + void onEnterPressedInternal() { m_self_destroy = true; } + // ------------------------------------------------------------------------ + GUIEngine::EventPropagation processEvent(const std::string& source); + // ------------------------------------------------------------------------ + virtual bool onEscapePressed() + { + m_self_destroy = true; + return false; + } + // ------------------------------------------------------------------------ + virtual void onUpdate(float dt) + { + // It's unsafe to delete from inside the event handler so we do it here + if (m_self_destroy) + { + ModalDialog::dismiss(); + return; + } + } +}; + +#endif diff --git a/src/states_screens/easter_egg_screen.cpp b/src/states_screens/easter_egg_screen.cpp index 8d67a3791..e478a840f 100644 --- a/src/states_screens/easter_egg_screen.cpp +++ b/src/states_screens/easter_egg_screen.cpp @@ -39,8 +39,6 @@ using namespace irr::video; static const char ALL_TRACK_GROUPS_ID[] = "all"; -DEFINE_SCREEN_SINGLETON( EasterEggScreen ); - // ----------------------------------------------------------------------------- EasterEggScreen::EasterEggScreen() : Screen("easter_egg.stkgui") diff --git a/src/states_screens/edit_gp_screen.cpp b/src/states_screens/edit_gp_screen.cpp index 026f30a22..196ff2144 100644 --- a/src/states_screens/edit_gp_screen.cpp +++ b/src/states_screens/edit_gp_screen.cpp @@ -32,8 +32,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( EditGPScreen ); - // ----------------------------------------------------------------------------- EditGPScreen::EditGPScreen() : Screen("edit_gp.stkgui"), m_gp(NULL), m_list(NULL), m_icon_bank(NULL), diff --git a/src/states_screens/edit_track_screen.cpp b/src/states_screens/edit_track_screen.cpp index 9147a008c..c20014e6a 100644 --- a/src/states_screens/edit_track_screen.cpp +++ b/src/states_screens/edit_track_screen.cpp @@ -33,8 +33,6 @@ using namespace irr::core; const char* EditTrackScreen::ALL_TRACKS_GROUP_ID = "all"; -DEFINE_SCREEN_SINGLETON( EditTrackScreen ); - // ----------------------------------------------------------------------------- EditTrackScreen::EditTrackScreen() : Screen("edit_track.stkgui"), m_track_group(ALL_TRACKS_GROUP_ID), diff --git a/src/states_screens/feature_unlocked.cpp b/src/states_screens/feature_unlocked.cpp index 42c17733d..c57b7ea01 100644 --- a/src/states_screens/feature_unlocked.cpp +++ b/src/states_screens/feature_unlocked.cpp @@ -62,8 +62,6 @@ const float ANIM_TO = 7.0f; const int GIFT_EXIT_FROM = (int)ANIM_TO; const int GIFT_EXIT_TO = GIFT_EXIT_FROM + 7; -DEFINE_SCREEN_SINGLETON( FeatureUnlockedCutScene ); - // ============================================================================ #if 0 diff --git a/src/states_screens/ghost_replay_selection.cpp b/src/states_screens/ghost_replay_selection.cpp index 113612a73..9cb720ea0 100644 --- a/src/states_screens/ghost_replay_selection.cpp +++ b/src/states_screens/ghost_replay_selection.cpp @@ -27,8 +27,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( GhostReplaySelection ); - // ---------------------------------------------------------------------------- /** Constructor, which loads the stkgui file. */ diff --git a/src/states_screens/gp_info_screen.cpp b/src/states_screens/gp_info_screen.cpp index 5db857f98..1fbb3775a 100644 --- a/src/states_screens/gp_info_screen.cpp +++ b/src/states_screens/gp_info_screen.cpp @@ -48,8 +48,6 @@ using irr::gui::IGUIStaticText; using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( GPInfoScreen ); - /** Constructor, initialised some variables which might be used before * loadedFromFile is called. */ @@ -234,9 +232,9 @@ void GPInfoScreen::init() { const int local_players = race_manager->getNumLocalPlayers(); int min_ai = 0; - int num_ai = UserConfigParams::m_num_karts_per_gamemode[RaceManager::MAJOR_MODE_GRAND_PRIX] - - local_players; - + int num_ai = int(UserConfigParams::m_num_karts_per_gamemode + [RaceManager::MAJOR_MODE_GRAND_PRIX]) - local_players; + // A ftl reace needs at least three karts to make any sense if (race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER) { diff --git a/src/states_screens/grand_prix_editor_screen.cpp b/src/states_screens/grand_prix_editor_screen.cpp index c859f22b4..0043a4629 100644 --- a/src/states_screens/grand_prix_editor_screen.cpp +++ b/src/states_screens/grand_prix_editor_screen.cpp @@ -36,9 +36,6 @@ using namespace GUIEngine; using namespace irr::core; -DEFINE_SCREEN_SINGLETON( GrandPrixEditorScreen ); - - // ----------------------------------------------------------------------------- GrandPrixEditorScreen::GrandPrixEditorScreen() : Screen("grand_prix_editor.stkgui"), m_selection(NULL), diff --git a/src/states_screens/grand_prix_lose.cpp b/src/states_screens/grand_prix_lose.cpp index f8fabff02..db9c297ee 100644 --- a/src/states_screens/grand_prix_lose.cpp +++ b/src/states_screens/grand_prix_lose.cpp @@ -69,8 +69,6 @@ const float KART_Z = 0.0f; const int MAX_KART_COUNT = 4; -DEFINE_SCREEN_SINGLETON( GrandPrixLose ); - // ------------------------------------------------------------------------------------- void GrandPrixLose::onCutsceneEnd() diff --git a/src/states_screens/grand_prix_win.cpp b/src/states_screens/grand_prix_win.cpp index 67a1155d4..6f289ab31 100644 --- a/src/states_screens/grand_prix_win.cpp +++ b/src/states_screens/grand_prix_win.cpp @@ -106,8 +106,6 @@ const float PODIUMS_FINAL_Y[3] = { const float PODIUMS_AND_KARTS_SPEED_Y[3] = { 0.9f, 1.35f, 0.45f }; -DEFINE_SCREEN_SINGLETON( GrandPrixWin ); - // ------------------------------------------------------------------------------------- GrandPrixWin::GrandPrixWin() : GrandPrixCutscene("grand_prix_win.stkgui") diff --git a/src/states_screens/help_screen_1.cpp b/src/states_screens/help_screen_1.cpp index 08accdf54..ec1be7b02 100644 --- a/src/states_screens/help_screen_1.cpp +++ b/src/states_screens/help_screen_1.cpp @@ -36,8 +36,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( HelpScreen1 ); - // ----------------------------------------------------------------------------- HelpScreen1::HelpScreen1() : Screen("help1.stkgui") diff --git a/src/states_screens/help_screen_2.cpp b/src/states_screens/help_screen_2.cpp index a140ca298..a1493a061 100644 --- a/src/states_screens/help_screen_2.cpp +++ b/src/states_screens/help_screen_2.cpp @@ -27,8 +27,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( HelpScreen2 ); - // ----------------------------------------------------------------------------- HelpScreen2::HelpScreen2() : Screen("help2.stkgui") diff --git a/src/states_screens/help_screen_3.cpp b/src/states_screens/help_screen_3.cpp index 722f7e092..74b3ff02b 100644 --- a/src/states_screens/help_screen_3.cpp +++ b/src/states_screens/help_screen_3.cpp @@ -28,8 +28,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( HelpScreen3 ); - // ----------------------------------------------------------------------------- HelpScreen3::HelpScreen3() : Screen("help3.stkgui") diff --git a/src/states_screens/help_screen_4.cpp b/src/states_screens/help_screen_4.cpp index 7b1aa20ab..7289c2cbf 100644 --- a/src/states_screens/help_screen_4.cpp +++ b/src/states_screens/help_screen_4.cpp @@ -28,8 +28,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( HelpScreen4 ); - // ----------------------------------------------------------------------------- HelpScreen4::HelpScreen4() : Screen("help4.stkgui") diff --git a/src/states_screens/help_screen_5.cpp b/src/states_screens/help_screen_5.cpp index 83aa9d8e4..842a9deda 100644 --- a/src/states_screens/help_screen_5.cpp +++ b/src/states_screens/help_screen_5.cpp @@ -27,8 +27,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( HelpScreen5 ); - // ----------------------------------------------------------------------------- HelpScreen5::HelpScreen5() : Screen("help5.stkgui") diff --git a/src/states_screens/kart_selection.cpp b/src/states_screens/kart_selection.cpp index 22cfcc38e..550f7cd10 100644 --- a/src/states_screens/kart_selection.cpp +++ b/src/states_screens/kart_selection.cpp @@ -37,6 +37,7 @@ #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/overworld.hpp" +#include "network/network_config.hpp" #include "states_screens/race_setup_screen.hpp" #include "utils/log.hpp" #include "utils/translation.hpp" @@ -55,7 +56,6 @@ static const char ID_DONT_USE[] = "x"; // a kart called 'locked' static const char ID_LOCKED[] = "locked/"; -//DEFINE_SCREEN_SINGLETON( KartSelectionScreen ); KartSelectionScreen* KartSelectionScreen::m_instance_ptr = NULL; int g_root_id; @@ -320,8 +320,6 @@ void KartSelectionScreen::init() Widget* placeholder = getWidget("playerskarts"); assert(placeholder != NULL); - // FIXME : The reserved id value is -1 when we switch from KSS to NKSS and vice-versa - m_dispatcher->setRootID(placeholder->m_reserved_id); g_root_id = placeholder->m_reserved_id; @@ -340,8 +338,6 @@ void KartSelectionScreen::init() tabs->setActive(true); m_kart_widgets.clearAndDeleteAll(); - StateManager::get()->resetActivePlayers(); - input_manager->getDeviceManager()->setAssignMode(DETECT_NEW); DynamicRibbonWidget* w = getWidget("karts"); assert( w != NULL ); @@ -378,26 +374,34 @@ void KartSelectionScreen::init() } else */ // For now this is what will happen - if (!m_multiplayer) - { - joinPlayer(input_manager->getDeviceManager()->getLatestUsedDevice()); - w->updateItemDisplay(); - - // Player 0 select default kart - if (!w->setSelection(UserConfigParams::m_default_kart, 0, true)) - { - // if kart from config not found, select the first instead - w->setSelection(0, 0, true); - } - } else - // Add multiplayer message - addMultiplayerMessage(); - + input_manager->getDeviceManager()->setAssignMode(DETECT_NEW); // This flag will cause that a 'fire' event will be mapped to 'select' (if // 'fire' is not assigned to a GUI event). This is done to support the old // way of player joining by pressing 'fire' instead of 'select'. input_manager->getDeviceManager()->mapFireToSelect(true); + if (!NetworkConfig::get()->isNetworking()) + { + StateManager::get()->resetActivePlayers(); + if (!m_multiplayer) + { + joinPlayer(input_manager->getDeviceManager()->getLatestUsedDevice(), + NULL/*player profile*/); + w->updateItemDisplay(); + + // Player 0 select default kart + if (!w->setSelection(UserConfigParams::m_default_kart, 0, true)) + { + // if kart from config not found, select the first instead + w->setSelection(0, 0, true); + } + } + else + { + // Add multiplayer message + addMultiplayerMessage(); + } + } } // init // ---------------------------------------------------------------------------- @@ -421,8 +425,7 @@ void KartSelectionScreen::tearDown() Screen::tearDown(); m_kart_widgets.clearAndDeleteAll(); - if (m_must_delete_on_back) - GUIEngine::removeScreen(getName().c_str()); + GUIEngine::removeScreen(getName().c_str()); } // tearDown // ---------------------------------------------------------------------------- @@ -435,7 +438,7 @@ void KartSelectionScreen::unloaded() // ---------------------------------------------------------------------------- // Return true if event was handled successfully -bool KartSelectionScreen::joinPlayer(InputDevice* device) +bool KartSelectionScreen::joinPlayer(InputDevice* device, PlayerProfile* p) { bool first_player = m_kart_widgets.size() == 0; @@ -468,14 +471,18 @@ bool KartSelectionScreen::joinPlayer(InputDevice* device) } // ---- Create new active player - PlayerProfile* profile_to_use = PlayerManager::getCurrentPlayer(); + PlayerProfile* profile_to_use = p == NULL ? + PlayerManager::getCurrentPlayer() : p; // Make sure enough guest character exists. At this stage this player has // not been added, so the number of guests requested for the first player // is 0 --> forcing at least one real player. - PlayerManager::get()->createGuestPlayers( - StateManager::get()->activePlayerCount()); - if (!first_player) + if (p == NULL) + { + PlayerManager::get()->createGuestPlayers( + StateManager::get()->activePlayerCount()); + } + if (!first_player && p == NULL) { // Give each player a different start profile const int num_active_players = StateManager::get()->activePlayerCount(); @@ -514,6 +521,12 @@ bool KartSelectionScreen::joinPlayer(InputDevice* device) m_kart_widgets.push_back(newPlayerWidget); newPlayerWidget->add(); + // From network kart selection, the player name is already defined + if (p != NULL) + { + newPlayerWidget->getPlayerNameSpinner()->setActive(false); + newPlayerWidget->getPlayerNameSpinner()->setCustomText(p->getName()); + } // ---- Divide screen space among all karts const int amount = m_kart_widgets.size(); @@ -522,7 +535,8 @@ bool KartSelectionScreen::joinPlayer(InputDevice* device) // in this special case, leave room for a message on the right if (m_multiplayer && first_player) { - addMultiplayerMessage(); + if (p == NULL) + addMultiplayerMessage(); const int splitWidth = fullarea->m_w / 2; m_kart_widgets[0].move( fullarea->m_x, fullarea->m_y, splitWidth, fullarea->m_h ); @@ -1481,7 +1495,7 @@ void KartSelectionScreen::setKartsFromCurrentGroup() { const KartProperties* prop = karts.get(i); if (PlayerManager::getCurrentPlayer()->isLocked(prop->getIdent()) && - !m_multiplayer) + !m_multiplayer && !NetworkConfig::get()->isNetworking()) { w->addItem(_("Locked : solve active challenges to gain access to more!"), ID_LOCKED + prop->getIdent(), diff --git a/src/states_screens/kart_selection.hpp b/src/states_screens/kart_selection.hpp index 7d21621e1..cd1c94e3d 100644 --- a/src/states_screens/kart_selection.hpp +++ b/src/states_screens/kart_selection.hpp @@ -38,6 +38,7 @@ namespace Online class FocusDispatcher; class InputDevice; +class PlayerProfile; class KartHoverListener; extern int g_root_id; @@ -85,7 +86,7 @@ protected: KartSelectionScreen(const char* filename); /** Called when all players selected their kart */ - void allPlayersDone(); + virtual void allPlayersDone(); /** Called when number/order of karts changed, so that all will keep * an up-to-date ID */ @@ -105,7 +106,7 @@ protected: /** Fill the ribbon with the karts from the currently selected group */ void setKartsFromCurrentGroup(); - virtual void playerConfirm(const int playerID); + void playerConfirm(const int playerID); void updateKartStats(uint8_t widget_id, const std::string& selection); @@ -143,14 +144,14 @@ public: /** \brief Called when a player hits 'fire'/'select' on his device to * join the game */ - bool joinPlayer(InputDevice* device); + bool joinPlayer(InputDevice* device, PlayerProfile* p); /** * \brief Called when a player hits 'rescue'/'cancel' on his device * to leave the game * \return true if event was handled succesfully */ - bool playerQuit(StateManager::ActivePlayer* player); + virtual bool playerQuit(StateManager::ActivePlayer* player); /** \brief implement callback from parent class GUIEngine::Screen */ virtual void init() OVERRIDE; diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index 0600f1f7b..102bc0bbf 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -68,8 +68,6 @@ using namespace GUIEngine; using namespace Online; -DEFINE_SCREEN_SINGLETON( MainMenuScreen ); - bool MainMenuScreen::m_enable_online = false; // ---------------------------------------------------------------------------- @@ -123,6 +121,7 @@ void MainMenuScreen::init() assert(m_user_id); // reset in case we're coming back from a race + NetworkConfig::get()->cleanNetworkPlayers(); StateManager::get()->resetActivePlayers(); input_manager->getDeviceManager()->setAssignMode(NO_ASSIGN); input_manager->getDeviceManager()->setSinglePlayer( NULL ); @@ -149,8 +148,6 @@ void MainMenuScreen::init() w->setBadge(LOADING_BADGE); } - IconButtonWidget* online = getWidget("online"); - LabelWidget* w = getWidget("info_addons"); const core::stringw &news_text = NewsManager::get()->getNextNewsMessage(); w->setText(news_text, true); diff --git a/src/states_screens/network_kart_selection.cpp b/src/states_screens/network_kart_selection.cpp index 40078650d..b8ba39855 100644 --- a/src/states_screens/network_kart_selection.cpp +++ b/src/states_screens/network_kart_selection.cpp @@ -17,204 +17,81 @@ #include "states_screens/network_kart_selection.hpp" -#include "audio/sfx_manager.hpp" -#include "challenges/unlock_manager.hpp" -#include "config/player_manager.hpp" #include "config/user_config.hpp" -#include "graphics/irr_driver.hpp" -#include "guiengine/widgets/player_kart_widget.hpp" #include "input/device_manager.hpp" -#include "items/item_manager.hpp" -#include "karts/kart_properties.hpp" -#include "karts/kart_properties_manager.hpp" -#include "network/network_player_profile.hpp" -#include "network/protocol_manager.hpp" -#include "network/protocols/client_lobby.hpp" +#include "network/network_config.hpp" +#include "network/protocols/lobby_protocol.hpp" #include "network/stk_host.hpp" -#include "states_screens/race_setup_screen.hpp" #include "states_screens/state_manager.hpp" #include "states_screens/tracks_screen.hpp" -#include "states_screens/waiting_for_others.hpp" - -static const char ID_LOCKED[] = "locked/"; using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( NetworkKartSelectionScreen ); - -NetworkKartSelectionScreen::NetworkKartSelectionScreen() - : KartSelectionScreen("karts_online.stkgui") -{ -} // NetworkKartSelectionScreen - -// ---------------------------------------------------------------------------- -NetworkKartSelectionScreen::~NetworkKartSelectionScreen() -{ -} // ~NetworkKartSelectionScreen - // ---------------------------------------------------------------------------- void NetworkKartSelectionScreen::init() { - m_multiplayer = false; + assert(!NetworkConfig::get()->isAddingNetworkPlayers()); + m_multiplayer = NetworkConfig::get()->getNetworkPlayers().size() != 1; KartSelectionScreen::init(); - RibbonWidget* tabs = getWidget("kartgroups"); - assert( tabs != NULL ); - // Select standard kart group - tabs->select( "standard", PLAYER_ID_GAME_MASTER); - tabs->setActive(false); - tabs->setVisible(false); - // change the back button image (because it makes the game quit) IconButtonWidget* back_button = getWidget("back"); back_button->setImage("gui/main_quit.png"); - // add a widget for each player except self (already exists): - GameSetup* setup = STKHost::get()->getGameSetup(); - if (!setup) + DynamicRibbonWidget* w = getWidget("karts"); + assert(w != NULL); + for (auto& p : NetworkConfig::get()->getNetworkPlayers()) { - Log::error("NetworkKartSelectionScreen", - "No network game setup registered."); - return; + joinPlayer(std::get<0>(p), std::get<1>(p)); + w->updateItemDisplay(); + if (!w->setSelection(UserConfigParams::m_default_kart, 0, true)) + { + // if kart from config not found, select the first instead + w->setSelection(0, 0, true); + } } - std::vector players = setup->getPlayers(); - - Log::info("NKSS", "There are %d players", players.size()); - // ---- Get available area for karts - // make a copy of the area, ands move it to be outside the screen - Widget* kartsAreaWidget = getWidget("playerskarts"); - // start at the rightmost of the screen - const int shift = irr_driver->getFrameSize().Width; - core::recti kartsArea(kartsAreaWidget->m_x + shift, - kartsAreaWidget->m_y, - kartsAreaWidget->m_x + shift + kartsAreaWidget->m_w, - kartsAreaWidget->m_y + kartsAreaWidget->m_h); - GameSetup *game_setup = STKHost::get()->getGameSetup(); - - // FIXME: atm only adds the local master, split screen supports - // needs to be added - int player_id = game_setup->getLocalMasterID(); - m_id_mapping.clear(); - m_id_mapping.insert(m_id_mapping.begin(), player_id); - - const int amount = m_kart_widgets.size(); - Widget* fullarea = getWidget("playerskarts"); - - const int splitWidth = fullarea->m_w / amount; - - for (int n=0; nm_x + splitWidth*n, - fullarea->m_y, splitWidth, fullarea->m_h); - } - // In case of auto-connect, select default kart and go to track selection. - if (NetworkConfig::get()->isAutoConnect()) - { - DynamicRibbonWidget* w = getWidget("karts"); - assert(w != NULL); - w->setSelection(UserConfigParams::m_default_kart, /*player id*/0, /*focus*/true); - playerConfirm(0); - RaceSetupScreen::getInstance()->push(); - } - } // init // ---------------------------------------------------------------------------- -void NetworkKartSelectionScreen::playerConfirm(const int playerID) +void NetworkKartSelectionScreen::allPlayersDone() { - DynamicRibbonWidget* w = getWidget("karts"); - assert(w != NULL); - const std::string selection = w->getSelectionIDString(playerID); - if (StringUtils::startsWith(selection, ID_LOCKED)) + input_manager->setMasterPlayerOnly(true); + + RibbonWidget* tabs = getWidget("kartgroups"); + assert(tabs != NULL); + + std::string selected_kart_group = + tabs->getSelectionIDString(PLAYER_ID_GAME_MASTER); + + UserConfigParams::m_last_used_kart_group = selected_kart_group; + + const PtrVector& players = + StateManager::get()->getActivePlayers(); + for (unsigned int n = 0; n < players.size(); n++) { - unlock_manager->playLockSound(); - return; + StateManager::get()->getActivePlayer(n)->getProfile() + ->incrementUseFrequency(); } - if (playerID == PLAYER_ID_GAME_MASTER) + const uint8_t kart_count = (uint8_t)m_kart_widgets.size(); + NetworkString kart(PROTOCOL_LOBBY_ROOM); + kart.addUInt8(LobbyProtocol::LE_KART_SELECTION).addUInt8(kart_count); + for (unsigned n = 0; n < kart_count; n++) { - UserConfigParams::m_default_kart = selection; + // If server recieve an invalid name, it will auto correct to a random + // kart + kart.encodeString(m_kart_widgets[n].m_kartInternalName); } + STKHost::get()->sendToServer(&kart, true); - if (m_kart_widgets[playerID].getKartInternalName().size() == 0) - { - SFXManager::get()->quickSound( "anvil" ); - return; - } - if(playerID == PLAYER_ID_GAME_MASTER) // self - { - auto clrp = LobbyProtocol::get(); - assert(clrp); - // FIXME SPLITSCREEN: we need to supply the global player id of the - // player selecting the kart here. For now ... just vote the same kart - // for each local player. - std::vector players = - STKHost::get()->getMyPlayerProfiles(); - for(unsigned int i=0; irequestKartSelection(players[i]->getGlobalPlayerId(), - selection ); - } - input_manager->getDeviceManager()->setAssignMode(ASSIGN); - } -} // playerConfirm - -// ---------------------------------------------------------------------------- -void NetworkKartSelectionScreen::playerSelected(uint8_t player_id, - const std::string &kart_name) -{ - int widget_id = -1; - for (unsigned int i = 0; i < m_id_mapping.size(); i++) - { - Log::info("NKSS", "Checking race id %d : mapped of %d is %d", - player_id, i, m_id_mapping[i]); - if (m_id_mapping[i] == player_id) - widget_id = i; - } - - // This selection was for a remote kart, which is not shown - // Just ignore it. - if(widget_id==-1) - return; - - // In case of auto-connect the screen is already replaced, so - // m_kart_widget is empty. - if (! STKHost::get()->isAuthorisedToControl()) - { - KartSelectionScreen::updateKartWidgetModel(widget_id, kart_name, - irr::core::stringw(kart_name.c_str()), - /*Todo get color*/0.0f); - KartSelectionScreen::updateKartStats(widget_id, kart_name); - m_kart_widgets[widget_id].setKartInternalName(kart_name); - m_kart_widgets[widget_id].markAsReady(); // mark player ready - } - - // If this is the authorised client, send the currently set race config - // to the server. - if(STKHost::get()->isAuthorisedToControl()) - { - auto clrp = LobbyProtocol::get(); - assert(clrp); - // FIXME: for now we submit a vote from the authorised user - // for the various modes based on the settings in the race manager. - // This needs more/better gui elements (and some should be set when - // defining the server). - std::vector players = - STKHost::get()->getMyPlayerProfiles(); - for(unsigned int i=0; igetGlobalPlayerId(); - clrp->voteMajor(id, race_manager->getMajorMode()); - clrp->voteMinor(id, race_manager->getMinorMode()); - clrp->voteReversed(id, race_manager->getReverseTrack()); - clrp->voteRaceCount(id, 1); - clrp->voteLaps(id, 1); - } - //WaitingForOthersScreen::getInstance()->push(); - //return; - } + // ---- Switch to assign mode + input_manager->getDeviceManager()->setAssignMode(ASSIGN); + // Remove kart screen + StateManager::get()->popMenu(); + TracksScreen::getInstance()->setNetworkTracks(); TracksScreen::getInstance()->push(); -} // playerSelected + +} // allPlayersDone // ---------------------------------------------------------------------------- bool NetworkKartSelectionScreen::onEscapePressed() diff --git a/src/states_screens/network_kart_selection.hpp b/src/states_screens/network_kart_selection.hpp index 0c714e2ca..02beb3633 100644 --- a/src/states_screens/network_kart_selection.hpp +++ b/src/states_screens/network_kart_selection.hpp @@ -27,31 +27,41 @@ class NetworkKartSelectionScreen : public KartSelectionScreen, public GUIEngine::ScreenSingleton { friend class GUIEngine::ScreenSingleton; + protected: - //!< map the id of the kart widgets to race ids - std::vector m_id_mapping; - - NetworkKartSelectionScreen(); - virtual ~NetworkKartSelectionScreen(); - - virtual void playerConfirm(const int playerID) OVERRIDE; + // ------------------------------------------------------------------------ + NetworkKartSelectionScreen() : KartSelectionScreen("karts.stkgui") {} + // ------------------------------------------------------------------------ + ~NetworkKartSelectionScreen() {} + // ------------------------------------------------------------------------ + virtual void allPlayersDone() OVERRIDE; private: std::set m_available_karts; + + // ------------------------------------------------------------------------ virtual bool isIgnored(const std::string& ident) const OVERRIDE - { - return m_available_karts.find(ident) == m_available_karts.end(); - } + { return m_available_karts.find(ident) == m_available_karts.end(); } public: + // ------------------------------------------------------------------------ void setAvailableKartsFromServer(const std::set& k) - { - m_available_karts = k; - } + { m_available_karts = k; } + // ------------------------------------------------------------------------ virtual void init() OVERRIDE; + // ------------------------------------------------------------------------ virtual bool onEscapePressed() OVERRIDE; - virtual void playerSelected(uint8_t player_id, - const std::string &kart_name); + // ------------------------------------------------------------------------ + virtual bool playerQuit(StateManager::ActivePlayer* player) OVERRIDE + { return true; } + // ------------------------------------------------------------------------ + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual void tearDown() OVERRIDE + { + m_must_delete_on_back = true; + KartSelectionScreen::tearDown(); + } + }; #endif // NETWORK_KART_SELECTION_HPP diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index c4abb3559..341f575e0 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -15,38 +15,38 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -#define DEBUG_MENU_ITEM 0 - #include "states_screens/networking_lobby.hpp" #include -#include -#include "challenges/unlock_manager.hpp" +#include "config/user_config.hpp" #include "config/player_manager.hpp" -#include "guiengine/widgets/bubble_widget.hpp" +#include "graphics/irr_driver.hpp" +#include "guiengine/CGUISpriteBank.hpp" +#include "guiengine/scalable_font.hpp" +#include "guiengine/widgets/CGUIEditBox.hpp" +#include "guiengine/widgets/button_widget.hpp" #include "guiengine/widgets/icon_button_widget.hpp" #include "guiengine/widgets/label_widget.hpp" #include "guiengine/widgets/list_widget.hpp" #include "guiengine/widgets/ribbon_widget.hpp" -#include "guiengine/widgets/text_box_widget.hpp" #include "input/device_manager.hpp" #include "input/input_manager.hpp" #include "io/file_manager.hpp" -#include "network/network_player_profile.hpp" +#include "network/network_config.hpp" #include "network/protocols/client_lobby.hpp" +#include "network/protocols/connect_to_server.hpp" #include "network/protocols/server_lobby.hpp" #include "network/server.hpp" #include "network/stk_host.hpp" +#include "network/stk_peer.hpp" #include "states_screens/state_manager.hpp" -#include "states_screens/dialogs/message_dialog.hpp" +#include "states_screens/dialogs/network_user_dialog.hpp" #include "utils/translation.hpp" using namespace Online; using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( NetworkingLobby ); - /** This is the lobby screen that is shown on all clients, but not on the * server. It shows currently connected clients, and allows the 'master' * client (i.e. the stk instance that created the server) to control the @@ -68,6 +68,9 @@ NetworkingLobby::NetworkingLobby() : Screen("online/networking_lobby.stkgui") void NetworkingLobby::loadedFromFile() { + m_header = getWidget("lobby-text"); + assert(m_header != NULL); + m_back_widget = getWidget("back"); assert(m_back_widget != NULL); @@ -80,12 +83,27 @@ void NetworkingLobby::loadedFromFile() m_chat_box = getWidget("chat"); assert(m_chat_box != NULL); + m_send_button = getWidget("send"); + assert(m_send_button != NULL); + m_player_list = getWidget("players"); assert(m_player_list!= NULL); m_exit_widget = getWidget("exit"); assert(m_exit_widget != NULL); + m_icon_bank = new irr::gui::STKModifiedSpriteBank(GUIEngine::getGUIEnv()); + video::ITexture* icon_1 = irr_driver->getTexture + (file_manager->getAsset(FileManager::GUI, "crown.png")); + video::ITexture* icon_2 = irr_driver->getTexture + (file_manager->getAsset(FileManager::GUI, "difficulty_medium.png")); + video::ITexture* icon_3 = irr_driver->getTexture + (file_manager->getAsset(FileManager::GUI, "main_help.png")); + m_icon_bank->addTextureAsSprite(icon_1); + m_icon_bank->addTextureAsSprite(icon_2); + m_icon_bank->addTextureAsSprite(icon_3); + const int screen_width = irr_driver->getFrameSize().Width; + m_icon_bank->setScale(screen_width > 1280 ? 0.4f : 0.25f); } // loadedFromFile // --------------------------------------------------------------------------- @@ -101,14 +119,37 @@ void NetworkingLobby::beforeAddingWidget() void NetworkingLobby::init() { Screen::init(); - setInitialFocus(); - m_start_button->setVisible(false); - // For now create the active player and bind it to the right - // input device. - InputDevice *device = input_manager->getDeviceManager()->getLatestUsedDevice(); - PlayerProfile* profile = PlayerManager::getCurrentPlayer(); - StateManager::get()->createActivePlayer(profile, device); + //I18N: In the networking lobby + m_header->setText(_("Lobby"), false); + m_server_info_height = GUIEngine::getFont()->getDimension(L"X").Height; + m_start_button->setVisible(false); + m_state = LS_CONNECTING; + getWidget("chat")->setVisible(false); + getWidget("chat")->setActive(false); + getWidget("send")->setVisible(false); + getWidget("send")->setActive(false); + + // Connect to server now if we have saved players and not disconnected + if (!LobbyProtocol::get() && + !NetworkConfig::get()->getNetworkPlayers().empty()) + std::make_shared(m_joined_server)->requestStart(); + + if (NetworkConfig::get()->getNetworkPlayers().empty()) + { + m_state = LS_ADD_PLAYERS; + } + else if (NetworkConfig::get()->isClient() && + UserConfigParams::m_lobby_chat) + { + m_chat_box->clearListeners(); + m_chat_box->addListener(this); + getWidget("chat")->setVisible(true); + getWidget("chat")->setActive(true); + getWidget("send")->setVisible(true); + getWidget("send")->setActive(true); + } + } // init // ---------------------------------------------------------------------------- @@ -150,13 +191,49 @@ void NetworkingLobby::setJoinedServer(std::shared_ptr server) } // setJoinedServer // ---------------------------------------------------------------------------- -void NetworkingLobby::addMoreServerInfo(const core::stringw& info) +void NetworkingLobby::addMoreServerInfo(core::stringw info) { + assert(m_text_bubble->getDimension().Width > 10); + while ((int)GUIEngine::getFont()->getDimension(info.c_str()).Width > + m_text_bubble->getDimension().Width - 10) + { + int size = (m_text_bubble->getDimension().Width - 10) + / m_server_info_height; + assert(size > 0); + core::stringw new_info = info.subString(0, size); + m_server_info.push_back(new_info); + info = info.subString(new_info.size(), 80); + } + if (info.size() > 0) + { + m_server_info.push_back(info); + } + while ((int)m_server_info.size() * m_server_info_height > + m_text_bubble->getDimension().Height) + { + m_server_info.erase(m_server_info.begin()); + } } // addMoreServerInfo // ---------------------------------------------------------------------------- void NetworkingLobby::onUpdate(float delta) { + if (m_state == LS_ADD_PLAYERS && NetworkConfig::get()->isClient()) + { + m_text_bubble->setText(_("Everyone:\nPress the 'Select' button to " + "join the game"), true); + m_start_button->setVisible(false); + m_exit_widget->setVisible(false); + if (!GUIEngine::ModalDialog::isADialogActive()) + { + input_manager->getDeviceManager()->setAssignMode(DETECT_NEW); + input_manager->getDeviceManager()->mapFireToSelect(true); + } + return; + } + + m_start_button->setVisible(false); + m_exit_widget->setVisible(true); auto lp = LobbyProtocol::get(); if (!lp) { @@ -176,6 +253,8 @@ void NetworkingLobby::onUpdate(float delta) } else { + if (m_server_peer.expired() && STKHost::existHost()) + m_server_peer = STKHost::get()->getServerPeerForClient(); core::stringw total_msg; for (auto& string : m_server_info) { @@ -184,14 +263,45 @@ void NetworkingLobby::onUpdate(float delta) } m_text_bubble->setText(total_msg, true); } - if (NetworkConfig::get()->isClient() && - STKHost::get()->isAuthorisedToControl()) + if (NetworkConfig::get()->isClient()) { - m_start_button->setVisible(true); + if (STKHost::get()->isAuthorisedToControl()) + { + m_start_button->setVisible(true); + } + if (auto p = m_server_peer.lock()) + { + //I18N: In the networking lobby, display ping when connected + const uint32_t ping = p->getPing(); + if (ping != 0) + m_header->setText(_("Lobby (ping: %dms)", ping), false); + } } - } // onUpdate +// ---------------------------------------------------------------------------- +void NetworkingLobby::sendChat(irr::core::stringw text) +{ + text = text.trim().removeChars(L"\n\r"); + if (text.size() > 0) + { + NetworkString chat(PROTOCOL_LOBBY_ROOM); + chat.addUInt8(LobbyProtocol::LE_CHAT); + + core::stringw name; + PlayerProfile* player = PlayerManager::getCurrentPlayer(); + if (PlayerManager::getCurrentOnlineState() == + PlayerProfile::OS_SIGNED_IN) + name = PlayerManager::getCurrentOnlineUserName(); + else + name = player->getName(); + // Max 80 words + chat.encodeString((name + L": " + text).subString(0, 80)); + + STKHost::get()->sendToServer(&chat, true); + } +} // sendChat + // ---------------------------------------------------------------------------- void NetworkingLobby::eventCallback(Widget* widget, const std::string& name, const int playerID) @@ -201,6 +311,23 @@ void NetworkingLobby::eventCallback(Widget* widget, const std::string& name, StateManager::get()->escapePressed(); return; } + else if (name == m_player_list->m_properties[GUIEngine::PROP_ID]) + { + auto host_online_ids = StringUtils::splitToUInt + (m_player_list->getSelectionInternalName(), '_'); + if (host_online_ids.size() != 2 || + STKHost::get()->getMyHostId() == host_online_ids[0]) + { + return; + } + new NetworkUserDialog(host_online_ids[0], host_online_ids[1], + m_player_list->getSelectionLabel()); + } // click on a user + else if (name == m_send_button->m_properties[PROP_ID]) + { + sendChat(m_chat_box->getText()); + m_chat_box->setText(""); + } // send chat message RibbonWidget* ribbon = dynamic_cast(widget); if (ribbon == NULL) return; @@ -213,64 +340,98 @@ void NetworkingLobby::eventCallback(Widget* widget, const std::string& name, } else if (selection == m_start_button->m_properties[PROP_ID]) { - if (NetworkConfig::get()->isServer()) - { - auto slrp = LobbyProtocol::get(); - slrp->startSelection(); - } - else - { - // Send a message to the server to start - NetworkString start(PROTOCOL_LOBBY_ROOM); - start.setSynchronous(true); - start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN); - STKHost::get()->sendToServer(&start, true); - } + // Send a message to the server to start + NetworkString start(PROTOCOL_LOBBY_ROOM); + start.setSynchronous(true); + start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN); + STKHost::get()->sendToServer(&start, true); } } // eventCallback // ---------------------------------------------------------------------------- +void NetworkingLobby::unloaded() +{ + delete m_icon_bank; + m_icon_bank = NULL; +} // unloaded +// ---------------------------------------------------------------------------- void NetworkingLobby::tearDown() { + m_joined_server.reset(); + // Server has a dummy network lobby too + if (!NetworkConfig::get()->isClient()) + return; + input_manager->getDeviceManager()->mapFireToSelect(false); } // tearDown // ---------------------------------------------------------------------------- - bool NetworkingLobby::onEscapePressed() { + m_joined_server.reset(); + input_manager->getDeviceManager()->mapFireToSelect(false); STKHost::get()->shutdown(); return true; // close the screen } // onEscapePressed // ---------------------------------------------------------------------------- -void NetworkingLobby::onDisabledItemClicked(const std::string& item) -{ - -} // onDisabledItemClicked - -// ---------------------------------------------------------------------------- -void NetworkingLobby::setInitialFocus() -{ -} // setInitialFocus - -// ---------------------------------------------------------------------------- -void NetworkingLobby::onDialogClose() -{ - setInitialFocus(); -} // onDialogClose() - -// ---------------------------------------------------------------------------- -void NetworkingLobby::addPlayer(NetworkPlayerProfile *profile) +void NetworkingLobby::updatePlayers(const std::vector >& p) { // In GUI-less server this function will be called without proper // initialisation - if(m_player_list) - m_player_list->addItem(StringUtils::toString(profile->getGlobalPlayerId()), - profile->getName()); -} // addPlayer + if (!m_player_list) + return; + m_player_list->clear(); + + if (p.empty()) + return; + + irr::gui::STKModifiedSpriteBank* icon_bank = m_icon_bank; + for (auto& q : p) + { + if (icon_bank) + { + m_player_list->setIcons(icon_bank); + icon_bank = NULL; + } + const std::string internal_name = + StringUtils::toString(std::get<0>(q)) + "_" + + StringUtils::toString(std::get<1>(q)); + m_player_list->addItem(internal_name, std::get<2>(q), std::get<3>(q)); + } +} // updatePlayers // ---------------------------------------------------------------------------- -void NetworkingLobby::removePlayer(NetworkPlayerProfile *profile) +void NetworkingLobby::addSplitscreenPlayer(irr::core::stringw name) { -} // removePlayer + if (!m_player_list) + return; + m_player_list->setIcons(m_icon_bank); + m_player_list->addItem(StringUtils::wideToUtf8(name), name, 1); +} // addSplitscreenPlayer + +// ---------------------------------------------------------------------------- +void NetworkingLobby::finishAddingPlayers() +{ + m_state = LS_CONNECTING; + std::make_shared(m_joined_server)->requestStart(); + m_start_button->setVisible(false); + if (UserConfigParams::m_lobby_chat) + { + m_chat_box->clearListeners(); + m_chat_box->addListener(this); + getWidget("chat")->setVisible(true); + getWidget("chat")->setActive(true); + getWidget("send")->setVisible(true); + getWidget("send")->setActive(true); + } +} // finishAddingPlayers + +// ---------------------------------------------------------------------------- +void NetworkingLobby::cleanAddedPlayers() +{ + if (!m_player_list) + return; + m_player_list->clear(); +} // cleanAddedPlayers diff --git a/src/states_screens/networking_lobby.hpp b/src/states_screens/networking_lobby.hpp index 644a01301..0628679ff 100644 --- a/src/states_screens/networking_lobby.hpp +++ b/src/states_screens/networking_lobby.hpp @@ -19,45 +19,76 @@ #define HEADER_NETWORKING_LOBBY_HPP #include "guiengine/screen.hpp" +#include "guiengine/widgets/text_box_widget.hpp" #include +#include class Server; +class STKPeer; namespace GUIEngine { - class Widget; + class ButtonWidget; class LabelWidget; class ListWidget; class IconButtonWidget; class TextBoxWidget; } -class NetworkPlayerProfile; +namespace irr +{ + namespace gui + { + class STKModifiedSpriteBank; + } +} /** - * \brief Handles the main menu + * \brief Handles the networking lobby * \ingroup states_screens */ -class NetworkingLobby : public GUIEngine::Screen, - public GUIEngine::ScreenSingleton +class NetworkingLobby : public GUIEngine::Screen, + public GUIEngine::ScreenSingleton, + public GUIEngine::ITextBoxWidgetListener { private: + enum LobbyState + { + LS_ADD_PLAYERS, + LS_CONNECTING + } m_state; + friend class GUIEngine::ScreenSingleton; NetworkingLobby(); std::shared_ptr m_joined_server; + std::weak_ptr m_server_peer; std::vector m_server_info; + int m_server_info_height; - GUIEngine::IconButtonWidget * m_back_widget; - GUIEngine::LabelWidget * m_text_bubble; - GUIEngine::IconButtonWidget * m_exit_widget; - GUIEngine::IconButtonWidget *m_start_button; - GUIEngine::ListWidget *m_player_list; + GUIEngine::IconButtonWidget* m_back_widget; + GUIEngine::LabelWidget* m_header; + GUIEngine::LabelWidget* m_text_bubble; + GUIEngine::IconButtonWidget* m_exit_widget; + GUIEngine::IconButtonWidget* m_start_button; + GUIEngine::ListWidget* m_player_list; GUIEngine::TextBoxWidget* m_chat_box; + GUIEngine::ButtonWidget* m_send_button; - /** \brief Sets which widget has to be focused. Depends on the user state. */ - void setInitialFocus(); + irr::gui::STKModifiedSpriteBank* m_icon_bank; + + /** \brief implement optional callback from parent class GUIEngine::Screen */ + virtual void unloaded(); + + virtual void onTextUpdated() {} + virtual bool onEnterPressed(const irr::core::stringw& text) OVERRIDE + { + sendChat(text); + return true; + } + + void sendChat(irr::core::stringw text); public: @@ -82,17 +113,15 @@ public: /** \brief implement callback from parent class GUIEngine::Screen */ virtual bool onEscapePressed() OVERRIDE; - /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void onDisabledItemClicked(const std::string& item) OVERRIDE; - - /** \brief Implements the callback when a dialog gets closed. */ - virtual void onDialogClose() OVERRIDE; - - /** Used to insert each client chat message (reserved). */ - void addMoreServerInfo(const core::stringw& info); + void finishAddingPlayers(); + void addMoreServerInfo(core::stringw info); void setJoinedServer(std::shared_ptr server); - void addPlayer(NetworkPlayerProfile *profile); - void removePlayer(NetworkPlayerProfile *profile); + void updatePlayers(const std::vector >& p); + void addSplitscreenPlayer(irr::core::stringw name); + void cleanAddedPlayers(); + }; // class NetworkingLobby #endif diff --git a/src/states_screens/offline_kart_selection.cpp b/src/states_screens/offline_kart_selection.cpp index fe443de3c..74fd9e429 100644 --- a/src/states_screens/offline_kart_selection.cpp +++ b/src/states_screens/offline_kart_selection.cpp @@ -17,8 +17,6 @@ #include "states_screens/offline_kart_selection.hpp" -DEFINE_SCREEN_SINGLETON( OfflineKartSelectionScreen ); - OfflineKartSelectionScreen::OfflineKartSelectionScreen() : KartSelectionScreen("karts.stkgui") { } diff --git a/src/states_screens/online_lan.cpp b/src/states_screens/online_lan.cpp index 20e054772..64dcfc862 100644 --- a/src/states_screens/online_lan.cpp +++ b/src/states_screens/online_lan.cpp @@ -45,8 +45,6 @@ using namespace irr::core; using namespace irr::gui; using namespace Online; -DEFINE_SCREEN_SINGLETON( OnlineLanScreen ); - // ----------------------------------------------------------------------------- OnlineLanScreen::OnlineLanScreen() : GUIEngine::Screen("online/lan.stkgui") diff --git a/src/states_screens/online_profile_achievements.cpp b/src/states_screens/online_profile_achievements.cpp index 9bfdfeeb7..3a5bb9d2a 100644 --- a/src/states_screens/online_profile_achievements.cpp +++ b/src/states_screens/online_profile_achievements.cpp @@ -40,9 +40,6 @@ using namespace irr::core; using namespace irr::gui; using namespace Online; -DEFINE_SCREEN_SINGLETON( OnlineProfileAchievements ); -DEFINE_SCREEN_SINGLETON( TabOnlineProfileAchievements ); - // ----------------------------------------------------------------------------- /** Constructor. */ diff --git a/src/states_screens/online_profile_friends.cpp b/src/states_screens/online_profile_friends.cpp index 593b974a3..2db9b595b 100644 --- a/src/states_screens/online_profile_friends.cpp +++ b/src/states_screens/online_profile_friends.cpp @@ -33,8 +33,6 @@ using namespace irr::core; using namespace irr::gui; using namespace Online; -DEFINE_SCREEN_SINGLETON( OnlineProfileFriends ); - int OnlineProfileFriends::m_sort_column = 0; bool OnlineProfileFriends::m_sort_increasing = true; diff --git a/src/states_screens/online_profile_servers.cpp b/src/states_screens/online_profile_servers.cpp index ba055ee4c..d4d9194be 100644 --- a/src/states_screens/online_profile_servers.cpp +++ b/src/states_screens/online_profile_servers.cpp @@ -41,8 +41,6 @@ using namespace irr::core; using namespace irr::gui; using namespace Online; -DEFINE_SCREEN_SINGLETON( OnlineProfileServers ); - // ----------------------------------------------------------------------------- OnlineProfileServers::OnlineProfileServers() : GUIEngine::Screen("online/profile_servers.stkgui") @@ -110,6 +108,7 @@ void OnlineProfileServers::eventCallback(Widget* widget, const std::string& name // ---------------------------------------------------------------------------- void OnlineProfileServers::doQuickPlay() { + NetworkConfig::get()->setPassword(""); STKHost::create(); NetworkingLobby::getInstance()->setJoinedServer(nullptr); NetworkingLobby::getInstance()->push(); diff --git a/src/states_screens/online_profile_settings.cpp b/src/states_screens/online_profile_settings.cpp index 98c83129c..141123d4a 100644 --- a/src/states_screens/online_profile_settings.cpp +++ b/src/states_screens/online_profile_settings.cpp @@ -35,8 +35,6 @@ using namespace irr::core; using namespace irr::gui; using namespace Online; -DEFINE_SCREEN_SINGLETON( OnlineProfileSettings ); - // ----------------------------------------------------------------------------- OnlineProfileSettings::OnlineProfileSettings() : OnlineProfileBase("online/profile_settings.stkgui") diff --git a/src/states_screens/online_screen.cpp b/src/states_screens/online_screen.cpp index 629399389..9f54ad133 100644 --- a/src/states_screens/online_screen.cpp +++ b/src/states_screens/online_screen.cpp @@ -20,16 +20,11 @@ #include "config/player_manager.hpp" #include "config/user_config.hpp" -#include "graphics/irr_driver.hpp" -#include "guiengine/scalable_font.hpp" +#include "guiengine/widgets/check_box_widget.hpp" #include "guiengine/widgets/label_widget.hpp" #include "guiengine/widgets/list_widget.hpp" #include "guiengine/widgets/ribbon_widget.hpp" #include "input/device_manager.hpp" -#include "input/input_manager.hpp" -#include "input/keyboard_device.hpp" -#include "io/file_manager.hpp" -#include "main_loop.hpp" #include "network/network_config.hpp" #include "online/request_manager.hpp" #include "states_screens/online_lan.hpp" @@ -39,18 +34,14 @@ #include "states_screens/state_manager.hpp" #include "states_screens/user_screen.hpp" #include "states_screens/dialogs/message_dialog.hpp" -#include "tracks/track_manager.hpp" #include "utils/string_utils.hpp" - #include using namespace GUIEngine; using namespace Online; -DEFINE_SCREEN_SINGLETON( OnlineScreen ); - // ---------------------------------------------------------------------------- OnlineScreen::OnlineScreen() : Screen("online/online.stkgui") @@ -64,6 +55,9 @@ OnlineScreen::OnlineScreen() : Screen("online/online.stkgui") void OnlineScreen::loadedFromFile() { + m_enable_splitscreen = getWidget("enable-splitscreen"); + assert(m_enable_splitscreen); + m_enable_splitscreen->setState(false); } // loadedFromFile // ---------------------------------------------------------------------------- @@ -102,6 +96,15 @@ void OnlineScreen::init() RibbonWidget* r = getWidget("menu_toprow"); r->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + // Pre-add a default single player profile in network + if (!m_enable_splitscreen->getState() && + NetworkConfig::get()->getNetworkPlayers().empty()) + { + NetworkConfig::get()->addNetworkPlayer( + input_manager->getDeviceManager()->getLatestUsedDevice(), + PlayerManager::getCurrentPlayer(), false/*handicap*/); + NetworkConfig::get()->doneAddingNetworkPlayers(); + } } // init // ---------------------------------------------------------------------------- @@ -145,7 +148,27 @@ void OnlineScreen::eventCallback(Widget* widget, const std::string& name, } else if (name == "back") { - StateManager::get()->popMenu(); + StateManager::get()->escapePressed(); + return; + } + else if (name == "enable-splitscreen") + { + CheckBoxWidget* splitscreen = dynamic_cast(widget); + assert(splitscreen); + if (!splitscreen->getState()) + { + // Default single player + NetworkConfig::get()->cleanNetworkPlayers(); + NetworkConfig::get()->addNetworkPlayer( + input_manager->getDeviceManager()->getLatestUsedDevice(), + PlayerManager::getCurrentPlayer(), false/*handicap*/); + NetworkConfig::get()->doneAddingNetworkPlayers(); + } + else + { + // Let lobby add the players + NetworkConfig::get()->cleanNetworkPlayers(); + } return; } @@ -188,13 +211,14 @@ void OnlineScreen::eventCallback(Widget* widget, const std::string& name, } // eventCallback // ---------------------------------------------------------------------------- - -void OnlineScreen::tearDown() +/** Also called when pressing the back button. It resets the flags to indicate + * a networked game. + */ +bool OnlineScreen::onEscapePressed() { -} // tearDown + NetworkConfig::get()->cleanNetworkPlayers(); + NetworkConfig::get()->unsetNetworking(); + return true; +} // onEscapePressed -// ---------------------------------------------------------------------------- -void OnlineScreen::onDisabledItemClicked(const std::string& item) -{ -} // onDisabledItemClicked diff --git a/src/states_screens/online_screen.hpp b/src/states_screens/online_screen.hpp index 69f2b371b..a579715ef 100644 --- a/src/states_screens/online_screen.hpp +++ b/src/states_screens/online_screen.hpp @@ -20,7 +20,7 @@ #include "guiengine/screen.hpp" -namespace GUIEngine { class Widget; class ListWidget; +namespace GUIEngine { class CheckBoxWidget; class ListWidget; class ButtonWidget; class IconButtonWidget; } /** @@ -42,6 +42,8 @@ private: /** Keep the widget to avoid looking it up every frame. */ GUIEngine::IconButtonWidget* m_online; + GUIEngine::CheckBoxWidget* m_enable_splitscreen; + OnlineScreen(); public: @@ -62,10 +64,8 @@ public: virtual void init() OVERRIDE; /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void tearDown() OVERRIDE; + virtual bool onEscapePressed() OVERRIDE; - /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void onDisabledItemClicked(const std::string& item) OVERRIDE; }; #endif diff --git a/src/states_screens/online_user_search.cpp b/src/states_screens/online_user_search.cpp index 6fc6fbeae..09c7d6065 100644 --- a/src/states_screens/online_user_search.cpp +++ b/src/states_screens/online_user_search.cpp @@ -32,8 +32,6 @@ using namespace Online; #include #include -DEFINE_SCREEN_SINGLETON( OnlineUserSearch ); - // ---------------------------------------------------------------------------- OnlineUserSearch::OnlineUserSearch() : Screen("online/user_search.stkgui") diff --git a/src/states_screens/options_screen_audio.cpp b/src/states_screens/options_screen_audio.cpp index 799bab896..b21664768 100644 --- a/src/states_screens/options_screen_audio.cpp +++ b/src/states_screens/options_screen_audio.cpp @@ -39,8 +39,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( OptionsScreenAudio ); - // ----------------------------------------------------------------------------- OptionsScreenAudio::OptionsScreenAudio() : Screen("options_audio.stkgui") diff --git a/src/states_screens/options_screen_device.cpp b/src/states_screens/options_screen_device.cpp index 040507e4a..efd508bdc 100644 --- a/src/states_screens/options_screen_device.cpp +++ b/src/states_screens/options_screen_device.cpp @@ -45,8 +45,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( OptionsScreenDevice ); - // ---------------------------------------------------------------------------- OptionsScreenDevice::OptionsScreenDevice() : Screen("options_device.stkgui") diff --git a/src/states_screens/options_screen_input.cpp b/src/states_screens/options_screen_input.cpp index b86b00e79..57fed4ced 100644 --- a/src/states_screens/options_screen_input.cpp +++ b/src/states_screens/options_screen_input.cpp @@ -46,8 +46,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( OptionsScreenInput ); - // ----------------------------------------------------------------------------- OptionsScreenInput::OptionsScreenInput() : Screen("options_input.stkgui") diff --git a/src/states_screens/options_screen_ui.cpp b/src/states_screens/options_screen_ui.cpp index eb2479396..07902fee0 100644 --- a/src/states_screens/options_screen_ui.cpp +++ b/src/states_screens/options_screen_ui.cpp @@ -54,8 +54,6 @@ using namespace GUIEngine; using namespace Online; -DEFINE_SCREEN_SINGLETON( OptionsScreenUI ); - // ----------------------------------------------------------------------------- OptionsScreenUI::OptionsScreenUI() : Screen("options_ui.stkgui") @@ -149,6 +147,9 @@ void OptionsScreenUI::init() assert( stats_label ); stats->setState(UserConfigParams::m_hw_report_enable); + getWidget("enable-lobby-chat") + ->setState(UserConfigParams::m_lobby_chat); + if(news->getState()) { stats_label->setVisible(true); @@ -289,17 +290,24 @@ void OptionsScreenUI::eventCallback(Widget* widget, const std::string& name, con // If internet gets enabled, re-initialise the addon manager (which // happens in a separate thread) so that news.xml etc can be // downloaded if necessary. - CheckBoxWidget *stats = getWidget("enable-hw-report"); - LabelWidget *stats_label = getWidget("label-hw-report"); + CheckBoxWidget* stats = getWidget("enable-hw-report"); + LabelWidget* stats_label = getWidget("label-hw-report"); + CheckBoxWidget* chat = getWidget("enable-lobby-chat"); + LabelWidget* chat_label = getWidget("label-lobby-chat"); if(internet->getState()) { NewsManager::get()->init(false); stats->setVisible(true); stats_label->setVisible(true); stats->setState(UserConfigParams::m_hw_report_enable); + chat->setVisible(true); + stats->setState(UserConfigParams::m_lobby_chat); + chat_label->setVisible(true); } else { + chat->setVisible(false); + chat_label->setVisible(false); stats->setVisible(false); stats_label->setVisible(false); PlayerProfile* profile = PlayerManager::getCurrentPlayer(); @@ -314,6 +322,11 @@ void OptionsScreenUI::eventCallback(Widget* widget, const std::string& name, con if(stats->getState()) HardwareStats::reportHardwareStats(); } + else if (name=="enable-lobby-chat") + { + CheckBoxWidget* chat = getWidget("enable-lobby-chat"); + UserConfigParams::m_lobby_chat = chat->getState(); + } else if (name=="show-login") { CheckBoxWidget* show_login = getWidget("show-login"); diff --git a/src/states_screens/options_screen_video.cpp b/src/states_screens/options_screen_video.cpp index 4ac495a6a..17b3d6865 100644 --- a/src/states_screens/options_screen_video.cpp +++ b/src/states_screens/options_screen_video.cpp @@ -43,8 +43,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( OptionsScreenVideo ); - // ---------------------------------------------------------------------------- void OptionsScreenVideo::initPresets() { diff --git a/src/states_screens/race_result_gui.cpp b/src/states_screens/race_result_gui.cpp index 20830e17a..8036f8396 100644 --- a/src/states_screens/race_result_gui.cpp +++ b/src/states_screens/race_result_gui.cpp @@ -44,7 +44,8 @@ #include "modes/overworld.hpp" #include "modes/soccer_world.hpp" #include "modes/world_with_rank.hpp" -#include "network/protocol_manager.hpp" +#include "network/network_config.hpp" +#include "network/stk_host.hpp" #include "network/protocols/client_lobby.hpp" #include "race/highscores.hpp" #include "scriptengine/property_animator.hpp" @@ -52,16 +53,13 @@ #include "states_screens/main_menu_screen.hpp" #include "states_screens/networking_lobby.hpp" #include "states_screens/network_kart_selection.hpp" -#include "states_screens/online_profile_servers.hpp" +#include "states_screens/online_screen.hpp" #include "states_screens/race_setup_screen.hpp" -#include "states_screens/server_selection.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/string_utils.hpp" #include -DEFINE_SCREEN_SINGLETON(RaceResultGUI); - /** Constructor, initialises internal data structures. */ RaceResultGUI::RaceResultGUI() : Screen("race_result.stkgui", @@ -172,10 +170,10 @@ void RaceResultGUI::enableAllButtons() { Log::info("This work was networked", "This is a network world."); top->setVisible(false); - middle->setText(_("Continue.")); + middle->setText(_("Continue")); middle->setVisible(true); middle->setFocusForPlayer(PLAYER_ID_GAME_MASTER); - bottom->setText(_("Quit the server.")); + bottom->setText(_("Quit the server")); bottom->setVisible(true); if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) { @@ -355,9 +353,14 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, } if (name == "bottom") // Quit server (return to main menu) { + if (STKHost::existHost()) + { + STKHost::get()->shutdown(); + } race_manager->exitRace(); race_manager->setAIKartOverride(""); StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); + NetworkConfig::get()->unsetNetworking(); } return; } @@ -427,8 +430,7 @@ void RaceResultGUI::backToLobby() race_manager->exitRace(); race_manager->setAIKartOverride(""); Screen* newStack[] = { MainMenuScreen::getInstance(), - OnlineProfileServers::getInstance(), - ServerSelection::getInstance(), + OnlineScreen::getInstance(), NetworkingLobby::getInstance(), NULL }; StateManager::get()->resetAndSetStack(newStack); diff --git a/src/states_screens/race_setup_screen.cpp b/src/states_screens/race_setup_screen.cpp index fe51c8dbc..ab35fa1ec 100644 --- a/src/states_screens/race_setup_screen.cpp +++ b/src/states_screens/race_setup_screen.cpp @@ -43,7 +43,6 @@ const int CONFIG_CODE_SOCCER = 5; const int CONFIG_CODE_GHOST = 6; using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( RaceSetupScreen ); // ----------------------------------------------------------------------------- diff --git a/src/states_screens/register_screen.cpp b/src/states_screens/register_screen.cpp index 28cf27bc3..fa253d2eb 100644 --- a/src/states_screens/register_screen.cpp +++ b/src/states_screens/register_screen.cpp @@ -38,8 +38,6 @@ using namespace Online; using namespace irr; using namespace core; -DEFINE_SCREEN_SINGLETON( RegisterScreen ); - // ----------------------------------------------------------------------------- RegisterScreen::RegisterScreen() : Screen("online/register.stkgui") @@ -96,7 +94,8 @@ void RegisterScreen::init() username = getenv("LOGNAME"); } - getWidget("local_username")->setText(username); + TextBoxWidget* local_username = getWidget("local_username"); + local_username->setText(username); m_password_widget->setPasswordBox(true, L'*'); getWidget("password_confirm")->setPasswordBox(true, L'*'); @@ -106,6 +105,8 @@ void RegisterScreen::init() onDialogClose(); makeEntryFieldsVisible(); + + local_username->setFocusForPlayer(PLAYER_ID_GAME_MASTER); } // init // ----------------------------------------------------------------------------- diff --git a/src/states_screens/server_selection.cpp b/src/states_screens/server_selection.cpp index 3576168a3..81fe90113 100644 --- a/src/states_screens/server_selection.cpp +++ b/src/states_screens/server_selection.cpp @@ -18,6 +18,9 @@ #include "states_screens/server_selection.hpp" #include "audio/sfx_manager.hpp" +#include "guiengine/widgets/check_box_widget.hpp" +#include "guiengine/widgets/icon_button_widget.hpp" +#include "guiengine/widgets/label_widget.hpp" #include "guiengine/modaldialog.hpp" #include "network/network_config.hpp" #include "network/server.hpp" @@ -28,13 +31,11 @@ #include "utils/translation.hpp" #include "utils/string_utils.hpp" -#include -#include +#include +#include using namespace Online; -DEFINE_SCREEN_SINGLETON( ServerSelection ); - // ---------------------------------------------------------------------------- /** Constructor, which loads the stkgui file. */ @@ -55,6 +56,7 @@ ServerSelection::~ServerSelection() */ void ServerSelection::tearDown() { + m_servers.clear(); ServersManager::get()->cleanUpServers(); m_server_list_widget->clear(); } // tearDown @@ -85,6 +87,9 @@ void ServerSelection::loadedFromFile() m_server_list_widget = getWidget("server_list"); assert(m_server_list_widget != NULL); m_server_list_widget->setColumnListener(this); + m_private_server = getWidget("private_server"); + assert(m_private_server != NULL); + m_private_server->setState(false); } // loadedFromFile // ---------------------------------------------------------------------------- @@ -93,10 +98,18 @@ void ServerSelection::loadedFromFile() void ServerSelection::beforeAddingWidget() { m_server_list_widget->clearColumns(); - m_server_list_widget->addColumn( _("Name"), 2 ); + m_server_list_widget->addColumn( _("Name"), 3); m_server_list_widget->addColumn( _("Players"), 1); m_server_list_widget->addColumn(_("Difficulty"), 1); - m_server_list_widget->addColumn(_("Game mode"), 1); + m_server_list_widget->addColumn(_("Game mode"), 2); + if (NetworkConfig::get()->isWAN()) + { + // I18N: In server selection screen, owner of server, only displayed + // if it's localhost or friends' + m_server_list_widget->addColumn(_("Owner"), 1); + // I18N: In server selection screen, distance to server + m_server_list_widget->addColumn(_("Distance (km)"), 1); + } } // beforeAddingWidget // ---------------------------------------------------------------------------- @@ -118,7 +131,7 @@ void ServerSelection::init() void ServerSelection::loadList(unsigned sort_case) { m_server_list_widget->clear(); - ServersManager::get()->sortServers([sort_case, this] + std::sort(m_servers.begin(), m_servers.end(), [sort_case, this] (const std::shared_ptr a, const std::shared_ptr b)->bool { @@ -138,27 +151,44 @@ void ServerSelection::loadList(unsigned sort_case) case 3: return c->getRaceMinorMode() > d->getRaceMinorMode(); break; + case 4: + return c->getServerOwnerName() > d->getServerOwnerName(); + break; + case 5: + return c->getDistance() > d->getDistance(); + break; } // switch assert(false); return false; }); - for (auto server : ServersManager::get()->getServers()) + for (auto server : m_servers) { core::stringw num_players; num_players.append(StringUtils::toWString(server->getCurrentPlayers())); num_players.append("/"); num_players.append(StringUtils::toWString(server->getMaxPlayers())); std::vector row; - row.push_back(GUIEngine::ListWidget::ListCell(server->getName(),-1,3)); - row.push_back(GUIEngine::ListWidget::ListCell(num_players,-1,1,true)); + row.push_back(GUIEngine::ListWidget::ListCell(server->getName(), -1, 3)); + row.push_back(GUIEngine::ListWidget::ListCell(num_players, -1, 1, true)); core::stringw difficulty = race_manager->getDifficultyName(server->getDifficulty()); row.push_back(GUIEngine::ListWidget::ListCell(difficulty, -1, 1, true)); core::stringw mode = RaceManager::getNameOf(server->getRaceMinorMode()); - row.push_back(GUIEngine::ListWidget::ListCell(mode, -1, 1, true)); + row.push_back(GUIEngine::ListWidget::ListCell(mode, -1, 2, true)); + if (NetworkConfig::get()->isWAN()) + { + row.push_back(GUIEngine::ListWidget::ListCell(StringUtils:: + utf8ToWide(server->getServerOwnerName()), -1, 1, true)); + // I18N: In server selection screen, unknown distance to server + core::stringw distance = _("Unknown"); + if (!(server->getDistance() < 0.0f)) + distance = StringUtils::toWString(server->getDistance()); + row.push_back(GUIEngine::ListWidget::ListCell(distance, -1, 1, + true)); + } m_server_list_widget->addItem("server", row); } } // loadList @@ -182,24 +212,25 @@ void ServerSelection::eventCallback(GUIEngine::Widget* widget, { StateManager::get()->escapePressed(); } - else if (name == "reload") { refresh(); } - + else if (name == "private_server") + { + copyFromServersManager(); + } else if (name == m_server_list_widget->m_properties[GUIEngine::PROP_ID]) { int selected_index = m_server_list_widget->getSelectionID(); // This can happen e.g. when the list is empty and the user // clicks somewhere. if (selected_index < 0 || m_refreshing_server || - selected_index >= (int)ServersManager::get()->getServers().size()) + selected_index >= (int)m_servers.size()) { return; } - new ServerInfoDialog( - ServersManager::get()->getServers()[selected_index]); + new ServerInfoDialog(m_servers[selected_index]); } // click on server } // eventCallback @@ -209,14 +240,13 @@ void ServerSelection::eventCallback(GUIEngine::Widget* widget, * if so, update the list of servers. */ void ServerSelection::onUpdate(float dt) - { // In case of auto-connect command line parameter, select the first server asap if (NetworkConfig::get()->isAutoConnect() && m_refreshing_server == false && - !ServersManager::get()->getServers().empty()) + !m_servers.empty()) { - ServerInfoDialog *sid = new ServerInfoDialog(ServersManager::get()->getServers()[0]); + ServerInfoDialog *sid = new ServerInfoDialog(m_servers[0]); sid->requestJoin(); } @@ -228,8 +258,9 @@ void ServerSelection::onUpdate(float dt) if (!ServersManager::get()->getServers().empty()) { int selection = m_server_list_widget->getSelectionID(); - std::string selection_str = m_server_list_widget->getSelectionInternalName(); - loadList(0); + std::string selection_str = m_server_list_widget + ->getSelectionInternalName(); + copyFromServersManager(); // restore previous selection if (selection != -1 && selection_str != "loading") m_server_list_widget->setSelectionID(selection); @@ -250,3 +281,17 @@ void ServerSelection::onUpdate(float dt) } } // onUpdate + +// ---------------------------------------------------------------------------- +void ServerSelection::copyFromServersManager() +{ + m_servers = ServersManager::get()->getServers(); + if (m_servers.empty()) + return; + m_servers.erase(std::remove_if(m_servers.begin(), m_servers.end(), + [this](const std::shared_ptr a)->bool + { + return a->isPasswordProtected() != m_private_server->getState(); + }), m_servers.end()); + loadList(0); +} // copyFromServersManager diff --git a/src/states_screens/server_selection.hpp b/src/states_screens/server_selection.hpp index 8c2c1f1f8..b2c0d2131 100644 --- a/src/states_screens/server_selection.hpp +++ b/src/states_screens/server_selection.hpp @@ -19,11 +19,20 @@ #define HEADER_SERVER_SELECTION_HPP #include "guiengine/screen.hpp" -#include "guiengine/widgets.hpp" +#include "guiengine/widgets/list_widget.hpp" + +#include namespace Online { class XMLRequest; } -namespace GUIEngine { class Widget; } +namespace GUIEngine +{ + class CheckBoxWidget; + class IconButtonWidget; + class LabelWidget; + class ListWidget; +} +class Server; /** * \brief ServerSelection @@ -39,6 +48,9 @@ private: ServerSelection(); ~ServerSelection(); + std::vector > m_servers; + + GUIEngine::CheckBoxWidget* m_private_server; GUIEngine::IconButtonWidget* m_reload_widget; GUIEngine::LabelWidget* m_update_status; GUIEngine::ListWidget* m_server_list_widget; @@ -51,6 +63,8 @@ private: /** Load the servers into the main list.*/ void loadList(unsigned sort_case); + void copyFromServersManager(); + void refresh(); public: diff --git a/src/states_screens/soccer_setup_screen.cpp b/src/states_screens/soccer_setup_screen.cpp index 8a1899c5b..7c0d5f295 100644 --- a/src/states_screens/soccer_setup_screen.cpp +++ b/src/states_screens/soccer_setup_screen.cpp @@ -37,7 +37,6 @@ #include "states_screens/state_manager.hpp" using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( SoccerSetupScreen ); #define KART_CONTINUOUS_ROTATION_SPEED 35.f #define KART_CONFIRMATION_ROTATION_SPEED 4.f diff --git a/src/states_screens/track_info_screen.cpp b/src/states_screens/track_info_screen.cpp index adfa9959c..e4ddaa449 100644 --- a/src/states_screens/track_info_screen.cpp +++ b/src/states_screens/track_info_screen.cpp @@ -52,8 +52,6 @@ using namespace irr::video; using namespace irr::core; using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( TrackInfoScreen ); - // ---------------------------------------------------------------------------- /** Constructor, which loads the corresponding track_info.stkgui file. */ TrackInfoScreen::TrackInfoScreen() @@ -167,12 +165,13 @@ void TrackInfoScreen::init() if (has_AI) { m_ai_kart_spinner->setActive(true); - - int num_ai = UserConfigParams::m_num_karts_per_gamemode[race_manager->getMinorMode()] - local_players; - + + int num_ai = int(UserConfigParams::m_num_karts_per_gamemode + [race_manager->getMinorMode()]) - local_players; + // Avoid negative numbers (which can happen if e.g. the number of karts // in a previous race was lower than the number of players now. - + if (num_ai < 0) num_ai = 0; m_ai_kart_spinner->setValue(num_ai); @@ -373,7 +372,8 @@ void TrackInfoScreen::onEnterPressedInternal() num_ai = m_ai_kart_spinner->getValue(); - if (UserConfigParams::m_num_karts_per_gamemode[race_manager->getMinorMode()] != (local_players + num_ai)) + if (UserConfigParams::m_num_karts_per_gamemode + [race_manager->getMinorMode()] != unsigned(local_players + num_ai)) { race_manager->setNumKarts(local_players + num_ai); UserConfigParams::m_num_karts_per_gamemode[race_manager->getMinorMode()] = local_players + num_ai; diff --git a/src/states_screens/tracks_and_gp_screen.cpp b/src/states_screens/tracks_and_gp_screen.cpp index 558cbea6d..1f379c005 100644 --- a/src/states_screens/tracks_and_gp_screen.cpp +++ b/src/states_screens/tracks_and_gp_screen.cpp @@ -30,7 +30,6 @@ #include "states_screens/state_manager.hpp" #include "states_screens/track_info_screen.hpp" #include "states_screens/gp_info_screen.hpp" -#include "states_screens/waiting_for_others.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/translation.hpp" @@ -43,8 +42,6 @@ using namespace irr::video; static const char ALL_TRACK_GROUPS_ID[] = "all"; -DEFINE_SCREEN_SINGLETON( TracksAndGPScreen ); - // ----------------------------------------------------------------------------- void TracksAndGPScreen::eventCallback(Widget* widget, const std::string& name, diff --git a/src/states_screens/tracks_screen.cpp b/src/states_screens/tracks_screen.cpp index 259cec128..e66a67320 100644 --- a/src/states_screens/tracks_screen.cpp +++ b/src/states_screens/tracks_screen.cpp @@ -21,16 +21,19 @@ #include "config/player_manager.hpp" #include "config/user_config.hpp" #include "graphics/stk_tex_manager.hpp" +#include "guiengine/scalable_font.hpp" #include "guiengine/widget.hpp" +#include "guiengine/widgets/check_box_widget.hpp" #include "guiengine/widgets/dynamic_ribbon_widget.hpp" #include "guiengine/widgets/icon_button_widget.hpp" +#include "guiengine/widgets/label_widget.hpp" +#include "guiengine/widgets/spinner_widget.hpp" #include "io/file_manager.hpp" -#include "network/network_player_profile.hpp" #include "network/protocols/client_lobby.hpp" +#include "network/network_config.hpp" #include "network/stk_host.hpp" #include "states_screens/state_manager.hpp" #include "states_screens/track_info_screen.hpp" -#include "states_screens/waiting_for_others.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/translation.hpp" @@ -43,8 +46,6 @@ using namespace irr::video; static const char ALL_TRACK_GROUPS_ID[] = "all"; -DEFINE_SCREEN_SINGLETON( TracksScreen ); - // ----------------------------------------------------------------------------- void TracksScreen::eventCallback(Widget* widget, const std::string& name, @@ -87,21 +88,19 @@ void TracksScreen::eventCallback(Widget* widget, const std::string& name, if (track) { - if(STKHost::existHost()) + if (STKHost::existHost()) { - auto clrp = LobbyProtocol::get(); - // server never shows the track screen. - assert(clrp); - // FIXME SPLITSCREEN: we need to supply the global player id of the - // player selecting the track here. For now ... just vote the same - // track for each local player. - std::vector players = - STKHost::get()->getMyPlayerProfiles(); - for(unsigned int i=0; ivoteTrack(players[i]->getGlobalPlayerId(),selection); - } - WaitingForOthersScreen::getInstance()->push(); + assert(m_laps); + assert(m_reversed); + // Remember reverse globally for each stk instance + m_reverse_checked = m_reversed->getState(); + UserConfigParams::m_num_laps = m_laps->getValue(); + NetworkString vote(PROTOCOL_LOBBY_ROOM); + vote.addUInt8(LobbyProtocol::LE_VOTE); + vote.encodeString(track->getIdent()) + .addUInt8(m_laps->getValue()) + .addUInt8(m_reversed->getState()); + STKHost::get()->sendToServer(&vote, true); } else { @@ -124,10 +123,79 @@ void TracksScreen::eventCallback(Widget* widget, const std::string& name, } // eventCallback // ----------------------------------------------------------------------------- +bool TracksScreen::onEscapePressed() +{ + if (m_network_tracks) + { + // Remove this screen + StateManager::get()->popMenu(); + STKHost::get()->shutdown(); + } + return true; // remove the screen +} // onEscapePressed +// ----------------------------------------------------------------------------- +void TracksScreen::tearDown() +{ + m_network_tracks = false; + m_vote_timeout = -1.0f; +} // tearDown + +// ----------------------------------------------------------------------------- +void TracksScreen::loadedFromFile() +{ + m_reversed = NULL; + m_laps = NULL; + m_votes = NULL; +} // loadedFromFile + +// ----------------------------------------------------------------------------- void TracksScreen::beforeAddingWidget() { Screen::init(); + + Widget* rect_box = getWidget("rect-box"); + + if (m_bottom_box_height == -1) + m_bottom_box_height = rect_box->m_h; + + if (m_network_tracks) + { + rect_box->setVisible(true); + rect_box->m_properties[GUIEngine::PROP_HEIGHT] = StringUtils::toString(m_bottom_box_height); + getWidget("lap-text")->setVisible(true); + //I18N: In track screen + getWidget("lap-text")->setText(_("Number of laps"), false); + m_laps = getWidget("lap-spinner"); + assert(m_laps != NULL); + m_laps->setVisible(true); + getWidget("reverse-text")->setVisible(true); + //I18N: In track screen + getWidget("reverse-text")->setText(_("Drive in reverse"), false); + m_reversed = getWidget("reverse"); + assert(m_reversed != NULL); + m_reversed->m_properties[GUIEngine::PROP_ALIGN] = "center"; + getWidget("all-track")->m_properties[GUIEngine::PROP_WIDTH] = "60%"; + getWidget("vote")->setVisible(true); + calculateLayout(); + } + else + { + rect_box->setVisible(false); + rect_box->m_properties[GUIEngine::PROP_HEIGHT] = "0"; + m_laps = NULL; + m_reversed = NULL; + getWidget("lap-text")->setVisible(false); + getWidget("lap-spinner")->setVisible(false); + getWidget("reverse-text")->setVisible(false); + getWidget("reverse")->setVisible(false); + getWidget("all-track")->m_properties[GUIEngine::PROP_WIDTH] = "98%"; + getWidget("vote")->setVisible(false); + calculateLayout(); + } + m_votes = getWidget("vote-text"); + m_votes->setVisible(false); + RibbonWidget* tabs = getWidget("trackgroups"); tabs->clearAllChildren(); @@ -146,12 +214,24 @@ void TracksScreen::beforeAddingWidget() DynamicRibbonWidget* tracks_widget = getWidget("tracks"); tracks_widget->setItemCountHint( (int)track_manager->getNumberOfTracks()+1 ); + } // beforeAddingWidget // ----------------------------------------------------------------------------- - void TracksScreen::init() { + // change the back button image (because it makes the game quit) + if (m_network_tracks) + { + IconButtonWidget* back_button = getWidget("back"); + back_button->setImage("gui/main_quit.png"); + } + else + { + IconButtonWidget* back_button = getWidget("back"); + back_button->setImage("gui/back.png"); + } + DynamicRibbonWidget* tracks_widget = getWidget("tracks"); assert(tracks_widget != NULL); @@ -170,13 +250,22 @@ void TracksScreen::init() tracks_widget->setSelection(0, PLAYER_ID_GAME_MASTER, true); } STKTexManager::getInstance()->unsetTextureErrorMessage(); - if (NetworkConfig::get()->isAutoConnect()) + if (m_network_tracks) { - DynamicRibbonWidget* tw = getWidget("tracks"); - tw->setSelection(UserConfigParams::m_last_track, 0, - /*focus*/true); - eventCallback(tw, "tracks", - /*player id*/0); + if (UserConfigParams::m_num_laps == 0 || + UserConfigParams::m_num_laps > 20) + UserConfigParams::m_num_laps = 1; + m_laps->setValue(UserConfigParams::m_num_laps); + m_reversed->setState(m_reverse_checked); + } + + if (NetworkConfig::get()->isAutoConnect() && m_network_tracks) + { + assert(!m_random_track_list.empty()); + NetworkString vote(PROTOCOL_LOBBY_ROOM); + vote.addUInt8(LobbyProtocol::LE_VOTE); + vote.encodeString(m_random_track_list[0]).addUInt8(1).addUInt8(0); + STKHost::get()->sendToServer(&vote, true); } } // init @@ -256,7 +345,6 @@ void TracksScreen::buildTrackList() } // buildTrackList // ----------------------------------------------------------------------------- - void TracksScreen::setFocusOnTrack(const std::string& trackName) { DynamicRibbonWidget* tracks_widget = this->getWidget("tracks"); @@ -267,3 +355,33 @@ void TracksScreen::setFocusOnTrack(const std::string& trackName) } // setFocusOnTrack // ----------------------------------------------------------------------------- +void TracksScreen::onUpdate(float dt) +{ + assert(m_votes); + if (m_vote_timeout == -1.0f) + { + m_votes->setText(L"", false); + return; + } + + m_votes->setVisible(true); + int remaining_time = int(m_vote_timeout - float(StkTime::getRealTime())); + if (remaining_time < 0) + remaining_time = 0; + //I18N: In tracks screen, about voting of tracks in network + core::stringw message = _("Remaining time: %d\n", remaining_time); + unsigned height = GUIEngine::getFont()->getDimension(L"X").Height; + const unsigned total_height = m_votes->getDimension().Height; + m_vote_messages.lock(); + for (auto& p : m_vote_messages.getData()) + { + height += GUIEngine::getFont()->getDimension(L"X").Height * 2; + if (height > total_height) + break; + message += p.second; + message += L"\n"; + } + m_vote_messages.unlock(); + m_votes->setText(message, true); + +} // onUpdate diff --git a/src/states_screens/tracks_screen.hpp b/src/states_screens/tracks_screen.hpp index 5ca99313e..d5af6b086 100644 --- a/src/states_screens/tracks_screen.hpp +++ b/src/states_screens/tracks_screen.hpp @@ -19,9 +19,17 @@ #define HEADER_TRACKS_SCREEN_HPP #include "guiengine/screen.hpp" +#include "utils/synchronised.hpp" #include +#include +#include -namespace GUIEngine { class Widget; } +namespace GUIEngine +{ + class CheckBoxWidget; + class LabelWidget; + class SpinnerWidget; +} /** * \brief screen where the user can select a track @@ -33,7 +41,23 @@ class TracksScreen : public GUIEngine::Screen, friend class GUIEngine::ScreenSingleton; private: - TracksScreen() : Screen("tracks.stkgui") {} + TracksScreen() : Screen("tracks.stkgui") + { + m_network_tracks = false; + m_reverse_checked = false; + } + + GUIEngine::CheckBoxWidget* m_reversed; + GUIEngine::SpinnerWidget* m_laps; + GUIEngine::LabelWidget* m_votes; + + bool m_network_tracks, m_reverse_checked; + + int m_bottom_box_height = -1; + + float m_vote_timeout = -1.0f; + + Synchronised > m_vote_messages; /** adds the tracks from the current track group into the tracks ribbon */ void buildTrackList(); @@ -43,7 +67,7 @@ private: public: /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void loadedFromFile() OVERRIDE {}; + virtual void loadedFromFile() OVERRIDE; /** \brief implement callback from parent class GUIEngine::Screen */ virtual void eventCallback(GUIEngine::Widget* widget, @@ -56,8 +80,42 @@ public: /** \brief implement callback from parent class GUIEngine::Screen */ virtual void beforeAddingWidget() OVERRIDE; + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual void tearDown() OVERRIDE; + + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual bool onEscapePressed() OVERRIDE; + + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual void onUpdate(float dt) OVERRIDE; + void setFocusOnTrack(const std::string& trackName); + void setNetworkTracks() { m_network_tracks = true; } + + void resetVote() + { + m_vote_messages.lock(); + m_vote_messages.getData().clear(); + m_vote_messages.unlock(); + m_vote_timeout = -1.0f; + } + + void setVoteTimeout(float timeout) + { + if (m_vote_timeout != -1.0f) + return; + m_vote_timeout = timeout; + } + + void addVoteMessage(const std::string& user, + const irr::core::stringw& message) + { + m_vote_messages.lock(); + m_vote_messages.getData()[user] = message; + m_vote_messages.unlock(); + } + }; #endif diff --git a/src/states_screens/user_screen.cpp b/src/states_screens/user_screen.cpp index 46d60f78e..4332ce8c0 100644 --- a/src/states_screens/user_screen.cpp +++ b/src/states_screens/user_screen.cpp @@ -40,9 +40,6 @@ using namespace GUIEngine; -DEFINE_SCREEN_SINGLETON( UserScreen ); -DEFINE_SCREEN_SINGLETON( TabbedUserScreen ); - // ---------------------------------------------------------------------------- BaseUserScreen::BaseUserScreen(const std::string &name) : Screen(name.c_str()) @@ -197,8 +194,8 @@ EventPropagation BaseUserScreen::filterActions(PlayerAction action, || (m_password_tb != NULL && m_password_tb->isFocusedForPlayer(PLAYER_ID_GAME_MASTER))) { login(); + return EVENT_BLOCK; } - return EVENT_BLOCK; } return EVENT_LET; diff --git a/src/states_screens/waiting_for_others.cpp b/src/states_screens/waiting_for_others.cpp deleted file mode 100644 index ab9e72574..000000000 --- a/src/states_screens/waiting_for_others.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2009-2015 Marianne Gagnon -// -// 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 "states_screens/waiting_for_others.hpp" - -#include "config/user_config.hpp" -#include "guiengine/widget.hpp" -#include "guiengine/widgets/label_widget.hpp" -#include "input/device_manager.hpp" -#include "input/input_manager.hpp" -#include "input/keyboard_device.hpp" -#include "karts/kart_properties_manager.hpp" -#include "network/game_setup.hpp" -#include "network/network_player_profile.hpp" -#include "network/stk_host.hpp" -#include "network/stk_peer.hpp" -#include "race/race_manager.hpp" -#include "states_screens/state_manager.hpp" - -using namespace GUIEngine; - -DEFINE_SCREEN_SINGLETON( WaitingForOthersScreen ); - -// ----------------------------------------------------------------------------- - -WaitingForOthersScreen::WaitingForOthersScreen() : Screen("online/waiting_for_others.stkgui") -{ -} // WaitingForOthersScreen - -// ----------------------------------------------------------------------------- - -void WaitingForOthersScreen::loadedFromFile() -{ -} // loadedFromFile - -// ----------------------------------------------------------------------------- - -void WaitingForOthersScreen::eventCallback(Widget* widget, const std::string& name, const int playerID) -{ -} // eventCallback - -// ----------------------------------------------------------------------------- - -void WaitingForOthersScreen::init() -{ - Screen::init(); -} //init - -// ----------------------------------------------------------------------------- - -void WaitingForOthersScreen::onUpdate(float dt) -{ - const GameSetup *setup = STKHost::get()->getGameSetup(); - const std::vector &all_profiles = setup->getPlayers(); - - RaceConfig* config = STKHost::get()->getGameSetup()->getRaceConfig(); - core::stringw w; - for (unsigned int i = 0; i < all_profiles.size(); i++) - { - const NetworkPlayerProfile* profile = all_profiles[i]; - if (profile == NULL) - continue; - core::stringw name = profile->getName(); - - - int playerId = profile->getGlobalPlayerId(); - const std::string &kart = profile->getKartName(); - if (!kart.empty()) - name += StringUtils::insertValues(L" (%s)", core::stringw(kart.c_str())); - - w += name + L" : "; - const RaceVote& vote = config->getRaceVote(playerId); - if (vote.hasVotedTrack()) - { - w += vote.getTrackVote().c_str(); - } - else - { - w += L"..."; - } - - w += "\n"; - } - - GUIEngine::LabelWidget* lbl = getWidget("lblDetails"); - lbl->setText(w.c_str(), true); -} - -// ----------------------------------------------------------------------------- - diff --git a/src/states_screens/waiting_for_others.hpp b/src/states_screens/waiting_for_others.hpp deleted file mode 100644 index b89b6a7a9..000000000 --- a/src/states_screens/waiting_for_others.hpp +++ /dev/null @@ -1,49 +0,0 @@ -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2009-2015 Marianne Gagnon -// -// 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 HEADER_WAITING_FOR_OTHERS_HPP -#define HEADER_WAITING_FOR_OTHERS_HPP - -#include "guiengine/screen.hpp" - -namespace GUIEngine { class Widget; } - -/** - * \brief Help screen, part 1 - * \ingroup states_screens - */ -class WaitingForOthersScreen : public GUIEngine::Screen, public GUIEngine::ScreenSingleton -{ - friend class GUIEngine::ScreenSingleton; - WaitingForOthersScreen(); - -public: - - /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void loadedFromFile() OVERRIDE; - - /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void eventCallback(GUIEngine::Widget* widget, const std::string& name, - const int playerID) OVERRIDE; - - /** \brief implement callback from parent class GUIEngine::Screen */ - virtual void init() OVERRIDE; - - virtual void onUpdate(float dt) OVERRIDE; -}; - -#endif diff --git a/src/utils/separate_process.cpp b/src/utils/separate_process.cpp index b31864667..5d19bebe9 100644 --- a/src/utils/separate_process.cpp +++ b/src/utils/separate_process.cpp @@ -253,6 +253,9 @@ bool SeparateProcess::createChildProcess(const std::string& exe, } } + std::string parent_pid = "--parent-process="; + int current_pid = getpid(); + parent_pid += StringUtils::toString(current_pid); child = fork(); if (child == 0) { @@ -289,6 +292,7 @@ bool SeparateProcess::createChildProcess(const std::string& exe, argv.push_back(const_cast(exe_file.c_str())); for (unsigned i = 0; i < rest_argv.size(); i++) argv.push_back(const_cast(rest_argv[i].c_str())); + argv.push_back(const_cast(parent_pid.c_str())); argv.push_back(NULL); execvp(exe.c_str(), argv.data()); Log::error("SeparateProcess", "Error in execl: errnp %d", errno);