diff --git a/src/game_initialization/multiplayer.cpp b/src/game_initialization/multiplayer.cpp index 7254911edb83..a19994aa8ffe 100644 --- a/src/game_initialization/multiplayer.cpp +++ b/src/game_initialization/multiplayer.cpp @@ -11,17 +11,18 @@ See the COPYING file for more details. */ + #include "game_initialization/multiplayer.hpp" -#include "addon/manager.hpp" // for get_installed_addons +#include "addon/manager.hpp" // for installed_addons #include "config_assign.hpp" -#include "font/sdl_ttf.hpp" #include "formula/string_utils.hpp" +#include "game_config_manager.hpp" +#include "game_initialization/lobby_reload_request_exception.hpp" +#include "game_initialization/mp_game_utils.hpp" +#include "game_initialization/playcampaign.hpp" #include "game_preferences.hpp" -#include "generators/map_create.hpp" -#include "generators/map_generator.hpp" #include "gettext.hpp" -#include "gui/dialogs/loading_screen.hpp" #include "gui/dialogs/lobby/lobby.hpp" #include "gui/dialogs/message.hpp" #include "gui/dialogs/multiplayer/mp_connect.hpp" @@ -30,50 +31,38 @@ #include "gui/dialogs/multiplayer/mp_login.hpp" #include "gui/dialogs/multiplayer/mp_staging.hpp" #include "gui/dialogs/network_transmission.hpp" -#include "gui/dialogs/preferences_dialog.hpp" #include "gui/widgets/settings.hpp" -#include "gui/widgets/window.hpp" #include "hash.hpp" -#include "game_initialization/lobby_reload_request_exception.hpp" #include "log.hpp" -#include "generators/map_create.hpp" -#include "game_initialization/mp_game_utils.hpp" -#include "game_initialization/configure_engine.hpp" #include "multiplayer_error_codes.hpp" -#include "game_initialization/playcampaign.hpp" #include "settings.hpp" -#include "scripting/plugins/context.hpp" -#include "sdl/rect.hpp" #include "sound.hpp" #include "statistics.hpp" -#include "units/id.hpp" #include "video.hpp" #include "wesnothd_connection.hpp" -#include "game_config_manager.hpp" #include "utils/functional.hpp" -static lg::log_domain log_network("network"); -#define LOG_NW LOG_STREAM(info, log_network) - static lg::log_domain log_mp("mp/main"); #define DBG_MP LOG_STREAM(debug, log_mp) +/** Opens a new server connection and prompts the client for login credentials, if necessary. */ static std::unique_ptr open_connection(CVideo& video, const std::string& original_host) { DBG_MP << "opening connection" << std::endl; - std::string h = original_host; + std::string h = original_host; if(h.empty()) { gui2::dialogs::mp_connect dlg; - dlg.show(video); - if(dlg.get_retval() == gui2::window::OK) { - h = preferences::network_host(); - } else { - return 0; + + if(dlg.get_retval() != gui2::window::OK) { + return nullptr; } + + h = preferences::network_host(); } + std::unique_ptr sock; const int colon_index = h.find_first_of(":"); @@ -88,8 +77,7 @@ static std::unique_ptr open_connection(CVideo& video, const port = lexical_cast_default(h.substr(colon_index + 1), 15000); } - // shown_hosts is used to prevent the client being locked in a redirect - // loop. + // shown_hosts is used to prevent the client being locked in a redirect loop. typedef std::pair hostpair; std::set shown_hosts; shown_hosts.insert(hostpair(host, port)); @@ -97,8 +85,7 @@ static std::unique_ptr open_connection(CVideo& video, const config data; sock = gui2::dialogs::network_transmission::wesnothd_connect_dialog(video, "connect to server", host, port); do { - - if (!sock) { + if(!sock) { return sock; } @@ -106,9 +93,9 @@ static std::unique_ptr open_connection(CVideo& video, const gui2::dialogs::network_transmission::wesnothd_receive_dialog(video, "waiting", data, *sock); //mp::check_response(data_res, data); - if (data.has_child("reject") || data.has_attribute("version")) { + if(data.has_child("reject") || data.has_attribute("version")) { std::string version; - if (const config &reject = data.child("reject")) { + if(const config& reject = data.child("reject")) { version = reject["accepted_versions"].str(); } else { // Backwards-compatibility "version" attribute @@ -123,7 +110,7 @@ static std::unique_ptr open_connection(CVideo& video, const } // Check for "redirect" messages - if (const config &redirect = data.child("redirect")) + if(const config& redirect = data.child("redirect")) { host = redirect["host"].str(); port =redirect["port"].to_int(15000); @@ -145,390 +132,452 @@ static std::unique_ptr open_connection(CVideo& video, const sock->send_data(res); } - //if we got a direction to login - if(data.child("mustlogin")) { + // Continue if we did not get a direction to login + if(!data.child("mustlogin")) { + continue; + } - for(;;) { - std::string password_reminder = ""; + // Enter login loop + for(;;) { + std::string password_reminder = ""; - std::string login = preferences::login(); + std::string login = preferences::login(); - config response ; - config &sp = response.add_child("login") ; - sp["username"] = login ; + config response ; + config& sp = response.add_child("login") ; + sp["username"] = login ; - // Login and enable selective pings -- saves server bandwidth - // If ping_timeout has a non-zero value, do not enable - // selective pings as this will cause clients to falsely - // believe the server has died and disconnect. - if (preferences::get_ping_timeout()) { - // Pings required so disable selective pings - sp["selective_ping"] = false; - } else { - // Client is bandwidth friendly so allow - // server to optimize ping frequency as needed. - sp["selective_ping"] = true; - } - sock->send_data(response); - gui2::dialogs::network_transmission::wesnothd_receive_dialog(video, "login response", data, *sock); - config *warning = &data.child("warning"); - - if(*warning) { - std::string warning_msg; - - utils::string_map i18n_symbols; - i18n_symbols["nick"] = login; - - if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) { - warning_msg = vgettext("The nickname ‘$nick’ is inactive. " - "You cannot claim ownership of this nickname until you " - "activate your account via email or ask an " - "administrator to do it for you.", i18n_symbols); - } else { - warning_msg = (*warning)["message"].str(); - } + // Login and enable selective pings -- saves server bandwidth + // If ping_timeout has a non-zero value, do not enable + // selective pings as this will cause clients to falsely + // believe the server has died and disconnect. + if(preferences::get_ping_timeout()) { + // Pings required so disable selective pings + sp["selective_ping"] = false; + } else { + // Client is bandwidth friendly so allow + // server to optimize ping frequency as needed. + sp["selective_ping"] = true; + } - warning_msg += "\n\n"; - warning_msg += _("Do you want to continue?"); + sock->send_data(response); + gui2::dialogs::network_transmission::wesnothd_receive_dialog(video, "login response", data, *sock); + config* warning = &data.child("warning"); - if(gui2::show_message(video, _("Warning"), warning_msg, gui2::dialogs::message::yes_no_buttons) != gui2::window::OK) { - return 0; - } - } + if(*warning) { + std::string warning_msg; - config *error = &data.child("error"); + if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) { + warning_msg = vgettext("The nickname ‘$nick’ is inactive. " + "You cannot claim ownership of this nickname until you " + "activate your account via email or ask an " + "administrator to do it for you.", {{"nick", login}}); + } else { + warning_msg = (*warning)["message"].str(); + } - // ... and get us out of here if the server did not complain - if (!*error) break; + warning_msg += "\n\n"; + warning_msg += _("Do you want to continue?"); - do { - std::string password = preferences::password(); + if(gui2::show_message(video, _("Warning"), warning_msg, gui2::dialogs::message::yes_no_buttons) != gui2::window::OK) { + return 0; + } + } - bool fall_through = (*error)["force_confirmation"].to_bool() ? - (gui2::show_message(video, _("Confirm"), (*error)["message"], gui2::dialogs::message::ok_cancel_buttons) == gui2::window::CANCEL) : - false; + config* error = &data.child("error"); - const bool is_pw_request = !((*error)["password_request"].empty()) && !(password.empty()); + // ... and get us out of here if the server did not complain + if(!*error) break; - // If the server asks for a password, provide one if we can - // or request a password reminder. - // Otherwise or if the user pressed 'cancel' in the confirmation dialog - // above go directly to the username/password dialog - if((is_pw_request || !password_reminder.empty()) && !fall_through) { - if(is_pw_request) { - if ((*error)["phpbb_encryption"].to_bool()) - { + do { + std::string password = preferences::password(); - // Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form. - // I will do closer investigations on this, for now let's just hope these are all of them. + bool fall_through = (*error)["force_confirmation"].to_bool() ? + (gui2::show_message(video, _("Confirm"), (*error)["message"], gui2::dialogs::message::ok_cancel_buttons) == gui2::window::CANCEL) : + false; - // Note: we must obviously replace '&' first, I wasted some time before I figured that out... :) - for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos ) - password.replace(pos, 1, "&"); - for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos ) - password.replace(pos, 1, """); - for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos ) - password.replace(pos, 1, "<"); - for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos ) - password.replace(pos, 1, ">"); + const bool is_pw_request = !((*error)["password_request"].empty()) && !(password.empty()); - const std::string salt = (*error)["salt"]; + // If the server asks for a password, provide one if we can + // or request a password reminder. + // Otherwise or if the user pressed 'cancel' in the confirmation dialog + // above go directly to the username/password dialog + if((is_pw_request || !password_reminder.empty()) && !fall_through) { + if(is_pw_request) { + if((*error)["phpbb_encryption"].to_bool()) { + // Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form. + // I will do closer investigations on this, for now let's just hope these are all of them. - if (salt.length() < 12) { - throw wesnothd_error(_("Bad data received from server")); - } + // Note: we must obviously replace '&' first, I wasted some time before I figured that out... :) + for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos) + password.replace(pos, 1, "&"); + for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos) + password.replace(pos, 1, """); + for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos) + password.replace(pos, 1, "<"); + for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos) + password.replace(pos, 1, ">"); - sp["password"] = util::create_hash(util::create_hash(password, util::get_salt(salt), - util::get_iteration_count(salt)), salt.substr(12, 8)); + const std::string salt = (*error)["salt"]; - } else { - sp["password"] = password; + if(salt.length() < 12) { + throw wesnothd_error(_("Bad data received from server")); } - } - - sp["password_reminder"] = password_reminder; - // Once again send our request... - sock->send_data(response); - gui2::dialogs::network_transmission::wesnothd_receive_dialog(video, "login response", data, *sock); + sp["password"] = util::create_hash(util::create_hash(password, util::get_salt(salt), + util::get_iteration_count(salt)), salt.substr(12, 8)); + } else { + sp["password"] = password; + } + } - error = &data.child("error"); + sp["password_reminder"] = password_reminder; - // ... and get us out of here if the server is happy now - if (!*error) break; + // Once again send our request... + sock->send_data(response); + gui2::dialogs::network_transmission::wesnothd_receive_dialog(video, "login response", data, *sock); + error = &data.child("error"); - } + // ... and get us out of here if the server is happy now + if(!*error) break; + } - password_reminder = ""; - - // Providing a password either was not attempted because we did not - // have any or failed: - // Now show a dialog that displays the error and allows to - // enter a new user name and/or password - - std::string error_message; - utils::string_map i18n_symbols; - i18n_symbols["nick"] = login; - - if((*error)["error_code"] == MP_MUST_LOGIN) { - error_message = _("You must login first."); - } else if((*error)["error_code"] == MP_NAME_TAKEN_ERROR) { - error_message = vgettext("The nickname ‘$nick’ is already taken.", i18n_symbols); - } else if((*error)["error_code"] == MP_INVALID_CHARS_IN_NAME_ERROR) { - error_message = vgettext("The nickname ‘$nick’ contains invalid " - "characters. Only alpha-numeric characters, underscores and " - "hyphens are allowed.", i18n_symbols); - } else if((*error)["error_code"] == MP_NAME_TOO_LONG_ERROR) { - error_message = vgettext("The nickname ‘$nick’ is too long. Nicks must " - "be 20 characters or less.", i18n_symbols); - } else if((*error)["error_code"] == MP_NAME_RESERVED_ERROR) { - error_message = vgettext("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols); - } else if((*error)["error_code"] == MP_NAME_UNREGISTERED_ERROR) { - error_message = vgettext("The nickname ‘$nick’ is not registered on this server.", i18n_symbols) - + _(" This server disallows unregistered nicknames."); - } else if((*error)["error_code"] == MP_PASSWORD_REQUEST) { - error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols); - } else if((*error)["error_code"] == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) { - error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols) - + "\n\n" + _("WARNING: There is already a client using this nickname, " - "logging in will cause that client to be kicked!"); - } else if((*error)["error_code"] == MP_NO_SEED_ERROR) { - error_message = _("Error in the login procedure (the server had no " - "seed for your connection)."); - } else if((*error)["error_code"] == MP_INCORRECT_PASSWORD_ERROR) { - error_message = _("The password you provided was incorrect."); - } else if((*error)["error_code"] == MP_TOO_MANY_ATTEMPTS_ERROR) { - error_message = _("You have made too many login attempts."); - } else { - error_message = (*error)["message"].str(); - } + password_reminder = ""; + + // Providing a password either was not attempted because we did not + // have any or failed: + // Now show a dialog that displays the error and allows to + // enter a new user name and/or password + + std::string error_message; + utils::string_map i18n_symbols; + i18n_symbols["nick"] = login; + + if((*error)["error_code"] == MP_MUST_LOGIN) { + error_message = _("You must login first."); + } else if((*error)["error_code"] == MP_NAME_TAKEN_ERROR) { + error_message = vgettext("The nickname ‘$nick’ is already taken.", i18n_symbols); + } else if((*error)["error_code"] == MP_INVALID_CHARS_IN_NAME_ERROR) { + error_message = vgettext("The nickname ‘$nick’ contains invalid " + "characters. Only alpha-numeric characters, underscores and " + "hyphens are allowed.", i18n_symbols); + } else if((*error)["error_code"] == MP_NAME_TOO_LONG_ERROR) { + error_message = vgettext("The nickname ‘$nick’ is too long. Nicks must " + "be 20 characters or less.", i18n_symbols); + } else if((*error)["error_code"] == MP_NAME_RESERVED_ERROR) { + error_message = vgettext("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols); + } else if((*error)["error_code"] == MP_NAME_UNREGISTERED_ERROR) { + error_message = vgettext("The nickname ‘$nick’ is not registered on this server.", i18n_symbols) + + _(" This server disallows unregistered nicknames."); + } else if((*error)["error_code"] == MP_PASSWORD_REQUEST) { + error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols); + } else if((*error)["error_code"] == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) { + error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols) + + "\n\n" + _("WARNING: There is already a client using this nickname, " + "logging in will cause that client to be kicked!"); + } else if((*error)["error_code"] == MP_NO_SEED_ERROR) { + error_message = _("Error in the login procedure (the server had no " + "seed for your connection)."); + } else if((*error)["error_code"] == MP_INCORRECT_PASSWORD_ERROR) { + error_message = _("The password you provided was incorrect."); + } else if((*error)["error_code"] == MP_TOO_MANY_ATTEMPTS_ERROR) { + error_message = _("You have made too many login attempts."); + } else { + error_message = (*error)["message"].str(); + } - gui2::dialogs::mp_login dlg(error_message, !((*error)["password_request"].empty())); - dlg.show(video); - - switch(dlg.get_retval()) { - //Log in with password - case gui2::window::OK: - break; - //Request a password reminder - case 1: - password_reminder = "yes"; - break; - // Cancel - default: - return 0; - } + gui2::dialogs::mp_login dlg(error_message, !((*error)["password_request"].empty())); + dlg.show(video); + + switch(dlg.get_retval()) { + //Log in with password + case gui2::window::OK: + break; + //Request a password reminder + case 1: + password_reminder = "yes"; + break; + // Cancel + default: + return 0; + } - // If we have got a new username we have to start all over again - } while(login == preferences::login()); + // If we have got a new username we have to start all over again + } while(login == preferences::login()); - // Somewhat hacky... - // If we broke out of the do-while loop above error - // is still going to be nullptr - if(!*error) break; - } // end login loop - } + // Somewhat hacky... + // If we broke out of the do-while loop above error + // is still going to be nullptr + if(!*error) break; + } // end login loop } while(!(data.child("join_lobby") || data.child("join_game"))); - if (h != preferences::server_list().front().address) + if(h != preferences::server_list().front().address) { preferences::set_network_host(h); + } - if (data.child("join_lobby")) { + if(data.child("join_lobby")) { return sock; - } else { - return 0; } + return nullptr; } -// The multiplayer logic consists in 4 screens: -// -// lobby <-> create <-> connect <-> (game) -// <------------> wait <-> (game) -// -// To each of this screen corresponds a dialog, each dialog being defined in -// the multiplayer_(screen) file. -// -// The functions enter_(screen)_mode are simple functions that take care of -// creating the dialogs, then, according to the dialog result, of calling other -// of those screen functions. -static void enter_wait_mode(CVideo& video, const config& game_config, - saved_game& state, wesnothd_connection* connection, - mp::lobby_info& li, int game_id, bool observe) +/** Helper struct to manage the MP workflow arguments. */ +struct mp_workflow_helper +{ + mp_workflow_helper(CVideo& video, const config& gc, saved_game& state, wesnothd_connection* connection, mp::lobby_info* li) + : video(video) + , game_config(gc) + , state(state) + , connection(connection) + , lobby_info(li) + {} + + CVideo& video; + + const config& game_config; + + saved_game& state; + + wesnothd_connection* connection; + + mp::lobby_info* lobby_info; +}; + +using mp_workflow_helper_ptr = std::shared_ptr; + +/** + * The main components of the MP workflow. It consists of four screens: + * + * Host POV: LOBBY <---> CREATE GAME ---> STAGING ------------------> GAME BEGINS + * Player POV: LOBBY <---------------------------------> JOIN GAME ---> GAME BEGINS + * + * NOTE: since these functions are static, they appear here in the opposite order they'd be accessed. + */ +static void enter_wait_mode(mp_workflow_helper_ptr helper, int game_id, bool observe) { DBG_MP << "entering wait mode" << std::endl; + // The connection should never be null here, since one should never reach this screen in local game mode. + assert(helper->connection); + statistics::fresh_stats(); - std::unique_ptr campaign_info; - campaign_info.reset(new mp_campaign_info(*connection)); + + std::unique_ptr campaign_info(new mp_campaign_info(*helper->connection)); campaign_info->is_host = false; - if (li.get_game_by_id(game_id)) { - campaign_info->current_turn = li.get_game_by_id(game_id)->current_turn; + + if(helper->lobby_info->get_game_by_id(game_id)) { + campaign_info->current_turn = helper->lobby_info->get_game_by_id(game_id)->current_turn; } + if(preferences::skip_mp_replay() || preferences::blindfold_replay()) { campaign_info->skip_replay = true; campaign_info->skip_replay_blindfolded = preferences::blindfold_replay(); } + gui2::dialogs::mp_join_game dlg(helper->state, *helper->lobby_info, *helper->connection, true, observe); - gui2::dialogs::mp_join_game dlg(state, li, *connection, true, observe); - - if(!dlg.fetch_game_config(video)) { + if(!dlg.fetch_game_config(helper->video)) { return; } - dlg.show(video); + dlg.show(helper->video); if(dlg.get_retval() == gui2::window::OK) { - campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types()); + campaign_controller controller(helper->video, helper->state, helper->game_config, game_config_manager::get()->terrain_types()); controller.set_mp_info(campaign_info.get()); controller.play_game(); } - if(connection) { - connection->send_data(config("leave_game")); - } - - return; - + helper->connection->send_data(config("leave_game")); } -static void enter_create_mode(CVideo& video, const config& game_config, saved_game& state, wesnothd_connection* connection, - mp::lobby_info& li, bool local_players_only = false); - -static bool enter_connect_mode(CVideo& video, const config& game_config, - saved_game& state, wesnothd_connection* connection, mp::lobby_info& li, - bool local_players_only = false) +static void enter_staging_mode(mp_workflow_helper_ptr helper) { DBG_MP << "entering connect mode" << std::endl; statistics::fresh_stats(); std::unique_ptr campaign_info; - if(!local_players_only) { - assert(connection); - campaign_info.reset(new mp_campaign_info(*connection)); + + // If we have a connection, set the appropriate info. No connection means we're in local game mode. + if(helper->connection) { + campaign_info.reset(new mp_campaign_info(*helper->connection)); campaign_info->connected_players.insert(preferences::login()); campaign_info->is_host = true; } { - ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, campaign_info.get())); + ng::connect_engine_ptr connect_engine(new ng::connect_engine(helper->state, true, campaign_info.get())); - gui2::dialogs::mp_staging dlg(*connect_engine, li, connection); - dlg.show(video); + gui2::dialogs::mp_staging dlg(*connect_engine, *helper->lobby_info, helper->connection); + dlg.show(helper->video); if(dlg.get_retval() == gui2::window::OK) { - campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types()); + campaign_controller controller(helper->video, helper->state, helper->game_config, game_config_manager::get()->terrain_types()); controller.set_mp_info(campaign_info.get()); controller.play_game(); } - if(connection) { - connection->send_data(config("leave_game")); + if(helper->connection) { + helper->connection->send_data(config("leave_game")); } } // end connect_engine_ptr scope - - return true; } -static void enter_create_mode(CVideo& video, const config& game_config, - saved_game& state, wesnothd_connection* connection, mp::lobby_info& li, bool local_players_only) +static void enter_create_mode(mp_workflow_helper_ptr helper) { DBG_MP << "entering create mode" << std::endl; - ng::create_engine create_eng(video, state); - - gui2::dialogs::mp_create_game dlg(game_config, create_eng); + ng::create_engine create_eng(helper->video, helper->state); - dlg.show(video); + gui2::dialogs::mp_create_game dlg(helper->game_config, create_eng); + dlg.show(helper->video); if(dlg.get_retval() != gui2::window::CANCEL) { - enter_connect_mode(video, game_config, state, connection, li, local_players_only); - } else if(connection) { - connection->send_data(config("refresh_lobby")); + enter_staging_mode(helper); + } else if(helper->connection) { + helper->connection->send_data(config("refresh_lobby")); } } -static void do_preferences_dialog(CVideo& video, const config& game_config) +static void enter_lobby_mode(mp_workflow_helper_ptr helper, const std::vector& installed_addons) { - DBG_MP << "displaying preferences dialog" << std::endl; - gui2::dialogs::preferences_dialog::display(video, game_config); - - /** - * The screen size might have changed force an update of the size. - * - * @todo This might no longer be needed when gui2 is done. - */ - const SDL_Rect rect = screen_area(); - - gui2::settings::gamemap_width += rect.w - gui2::settings::screen_width; - gui2::settings::gamemap_height += rect.h - gui2::settings::screen_height; - gui2::settings::screen_width = rect.w; - gui2::settings::screen_height = rect.h; -} - -static void enter_lobby_mode(CVideo& video, const config& game_config, - saved_game& state, wesnothd_connection* connection, const std::vector & installed_addons) -{ - assert(connection); DBG_MP << "entering lobby mode" << std::endl; - while (true) { - const config &cfg = game_config.child("lobby_music"); - if (cfg) { - for (const config &i : cfg.child_range("music")) { + // Connection should never be null in the lobby. + assert(helper->connection); + + // We use a loop here to allow returning to the lobby if you, say, cancel game creation. + while(true) { + if(const config& cfg = helper->game_config.child("lobby_music")) { + for(const config& i : cfg.child_range("music")) { sound::play_music_config(i); } + sound::commit_music_changes(); } else { sound::empty_playlist(); sound::stop_music(); } - mp::lobby_info li(game_config, installed_addons); - gui2::dialogs::mp_lobby dlg(game_config, li, *connection); - dlg.set_preferences_callback(std::bind(do_preferences_dialog, std::ref(video), std::ref(game_config))); - dlg.show(video); + mp::lobby_info li(helper->game_config, installed_addons); + helper->lobby_info = &li; + + gui2::dialogs::mp_lobby dlg(helper->game_config, li, *helper->connection); + dlg.show(helper->video); - //ugly kludge for launching other dialogs like the old lobby switch(dlg.get_retval()) { case gui2::dialogs::mp_lobby::CREATE: try { - enter_create_mode(video, game_config, state, connection, li, false); + enter_create_mode(helper); } catch(config::error& error) { - if (!error.message.empty()) - gui2::show_error_message(video, error.message); - //update lobby content - connection->send_data(config("refresh_lobby")); + if(!error.message.empty()) { + gui2::show_error_message(helper->video, error.message); + } + + // Update lobby content + helper->connection->send_data(config("refresh_lobby")); } break; case gui2::dialogs::mp_lobby::JOIN: case gui2::dialogs::mp_lobby::OBSERVE: try { - enter_wait_mode(video, game_config, state, connection, li, - dlg.get_joined_game_id(), - dlg.get_retval() == gui2::dialogs::mp_lobby::OBSERVE); + enter_wait_mode(helper, + dlg.get_joined_game_id(), + dlg.get_retval() == gui2::dialogs::mp_lobby::OBSERVE + ); } catch(config::error& error) { if(!error.message.empty()) { - gui2::show_error_message(video, error.message); + gui2::show_error_message(helper->video, error.message); } - //update lobby content - connection->send_data(config("refresh_lobby")); + + // Update lobby content + helper->connection->send_data(config("refresh_lobby")); } break; default: - // Needed to handle the Quit signal or the dialog never closes for some reason + // Needed to handle the Quit signal and exit the loop return; } } } +/** Pubic entry points for the MP workflow */ namespace mp { +void start_client(CVideo& video, const config& game_config, saved_game& state, const std::string& host) +{ + const config* game_config_ptr = &game_config; + + // This function does not refer to an addon database, it calls filesystem functions. + // For the sanity of the mp lobby, this list should be fixed for the entire lobby session, + // even if the user changes the contents of the addon directory in the meantime. + std::vector installed_addons = ::installed_addons(); + + DBG_MP << "starting client" << std::endl; + + preferences::admin_authentication_reset r; + + std::unique_ptr connection = open_connection(video, host); + if(!connection) { + return; + } + + mp_workflow_helper_ptr workflow_helper; + bool re_enter; + + do { + workflow_helper.reset(new mp_workflow_helper(video, *game_config_ptr, state, connection.get(), nullptr)); + re_enter = false; + + try { + enter_lobby_mode(workflow_helper, installed_addons); + } catch(lobby_reload_request_exception&) { + re_enter = true; + + game_config_manager* gcm = game_config_manager::get(); + gcm->reload_changed_game_config(); + gcm->load_game_config_for_game(state.classification()); // NOTE: Using reload_changed_game_config only doesn't seem to work here + + game_config_ptr = &gcm->game_config(); + + installed_addons = ::installed_addons(); // Refersh the installed add-on list for this session. + + connection->send_data(config("refresh_lobby")); + } + } while(re_enter); +} + +bool goto_mp_connect(CVideo& video, ng::connect_engine& engine, const config& game_config, wesnothd_connection* connection) +{ + lobby_info li(game_config, {}); + + gui2::dialogs::mp_staging dlg(engine, li, connection); + return dlg.show(video); +} + +bool goto_mp_wait(CVideo& video, saved_game& state, const config& game_config, wesnothd_connection* connection, bool observe) +{ + lobby_info li(game_config, std::vector()); + + gui2::dialogs::mp_join_game dlg(state, li, *connection, false, observe); + + if(!dlg.fetch_game_config(video)) { + return false; + } + + if(dlg.started()) { + return true; + } + + return dlg.show(video); +} + void start_local_game(CVideo& video, const config& game_config, saved_game& state) { DBG_MP << "starting local game" << std::endl; @@ -536,8 +585,10 @@ void start_local_game(CVideo& video, const config& game_config, saved_game& stat preferences::set_message_private(false); // TODO: should lobby_info take a nullptr in this case, or should we pass the installed_addons data here too? - lobby_info li(game_config, std::vector()); - enter_create_mode(video, game_config, state, nullptr, li, true); + lobby_info li(game_config, {}); + mp_workflow_helper_ptr workflow_helper = std::make_shared(video, game_config, state, nullptr, &li); + + enter_create_mode(workflow_helper); } void start_local_game_commandline(CVideo& video, const config& game_config, saved_game& state, const commandline_options& cmdline_opts) @@ -577,7 +628,8 @@ void start_local_game_commandline(CVideo& video, const config& game_config, save // In particular, we do not want to use the preferences values. state.classification().campaign_type = game_classification::CAMPAIGN_TYPE::MULTIPLAYER; - //[era] define. + + // [era] define. if(cmdline_opts.multiplayer_era) { parameters.mp_era = *cmdline_opts.multiplayer_era; } @@ -589,7 +641,7 @@ void start_local_game_commandline(CVideo& video, const config& game_config, save return; } - //[multiplayer] define. + // [multiplayer] define. if(cmdline_opts.multiplayer_scenario) { parameters.name = *cmdline_opts.multiplayer_scenario; } @@ -600,6 +652,7 @@ void start_local_game_commandline(CVideo& video, const config& game_config, save std::cerr << "Could not find [multiplayer] '" << parameters.name << "'\n"; return; } + game_config_manager::get()->load_game_config_for_game(state.classification()); state.set_carryover_sides_start( config_of("next_scenario", parameters.name) @@ -639,65 +692,4 @@ void start_local_game_commandline(CVideo& video, const config& game_config, save } } -void start_client(CVideo& video, const config& game_config, saved_game& state, const std::string& host) -{ - const config * game_config_ptr = &game_config; - std::vector installed_addons = ::installed_addons(); - // This function does not refer to an addon database, it calls filesystem functions. - // For the sanity of the mp lobby, this list should be fixed for the entire lobby session, - // even if the user changes the contents of the addon directory in the meantime. - - DBG_MP << "starting client" << std::endl; - - preferences::admin_authentication_reset r; - - std::unique_ptr connection = open_connection(video, host); - if(connection) { - bool re_enter; - do { - re_enter = false; - try { - enter_lobby_mode(video, *game_config_ptr, state, connection.get(), installed_addons); - } catch (lobby_reload_request_exception &) { - re_enter = true; - game_config_manager * gcm = game_config_manager::get(); - gcm->reload_changed_game_config(); - gcm->load_game_config_for_game(state.classification()); // NOTE: Using reload_changed_game_config only doesn't seem to work here - game_config_ptr = &gcm->game_config(); - - installed_addons = ::installed_addons(); // Refersh the installed add-on list for this session. - - connection->send_data(config("refresh_lobby")); - } - } while (re_enter); - } -} - -// TODO: see if the game_name parameter is needed -bool goto_mp_connect(CVideo& video, ng::connect_engine& engine, - const config& game_config, wesnothd_connection* connection, const std::string& /*game_name*/) -{ - lobby_info li(game_config, std::vector()); - - gui2::dialogs::mp_staging dlg(engine, li, connection); - return dlg.show(video); -} - -bool goto_mp_wait(CVideo& video, saved_game& state, const config& game_config, wesnothd_connection* connection, bool observe) -{ - lobby_info li(game_config, std::vector()); - - gui2::dialogs::mp_join_game dlg(state, li, *connection, false, observe); - - if(!dlg.fetch_game_config(video)) { - return false; - } - if(dlg.started()) { - return true; - } - - return dlg.show(video); -} - } // end namespace mp - diff --git a/src/game_initialization/multiplayer.hpp b/src/game_initialization/multiplayer.hpp index 318011ffcc71..e870b3a7283f 100644 --- a/src/game_initialization/multiplayer.hpp +++ b/src/game_initialization/multiplayer.hpp @@ -61,7 +61,7 @@ void start_client(CVideo& video, const config& game_config, * changes made. */ bool goto_mp_connect(CVideo& video, ng::connect_engine& engine, - const config& game_config, wesnothd_connection* connection, const std::string& game_name); + const config& game_config, wesnothd_connection* connection); /** * Opens mp::wait screen and sets game state according to the diff --git a/src/game_initialization/playcampaign.cpp b/src/game_initialization/playcampaign.cpp index b5232daee8b9..c3cd3782d359 100644 --- a/src/game_initialization/playcampaign.cpp +++ b/src/game_initialization/playcampaign.cpp @@ -372,7 +372,7 @@ LEVEL_RESULT campaign_controller::play_game() if (!connect_engine->can_start_game() || (game_config::debug && game_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER)) { // Opens staging dialog to allow users to make an adjustments for scenario. - if(!mp::goto_mp_connect(video_, *connect_engine, game_config_, mp_info_ ? &mp_info_->connection : nullptr, state_.mp_settings().name)) { + if(!mp::goto_mp_connect(video_, *connect_engine, game_config_, mp_info_ ? &mp_info_->connection : nullptr)) { return LEVEL_RESULT::QUIT; } } else { diff --git a/src/gui/dialogs/lobby/lobby.cpp b/src/gui/dialogs/lobby/lobby.cpp index bed2f2f9db8d..465db6f8b95b 100644 --- a/src/gui/dialogs/lobby/lobby.cpp +++ b/src/gui/dialogs/lobby/lobby.cpp @@ -19,6 +19,7 @@ #include "gui/dialogs/lobby/player_info.hpp" #include "gui/dialogs/message.hpp" #include "gui/dialogs/multiplayer/mp_join_game_password_prompt.hpp" +#include "gui/dialogs/preferences_dialog.hpp" #include "gui/dialogs/helper.hpp" #include "gui/core/log.hpp" @@ -130,7 +131,6 @@ mp_lobby::mp_lobby(const config& game_config, mp::lobby_info& info, wesnothd_con , gamelistbox_(nullptr) , window_(nullptr) , lobby_info_(info) - , preferences_callback_() , filter_friends_(nullptr) , filter_ignored_(nullptr) , filter_slots_(nullptr) @@ -998,18 +998,28 @@ void mp_lobby::refresh_button_callback(window& /*window*/) void mp_lobby::show_preferences_button_callback(window& window) { - if(preferences_callback_) { - preferences_callback_(); + gui2::dialogs::preferences_dialog::display(window.video(), game_config_); - /** - * The screen size might have changed force an update of the size. - * - * @todo This might no longer be needed when gui2 is done. - */ - window.invalidate_layout(); + /** + * The screen size might have changed force an update of the size. + * + * @todo This might no longer be needed when gui2 is done. + */ + const SDL_Rect rect = screen_area(); - wesnothd_connection_.send_data(config("refresh_lobby")); - } + gui2::settings::gamemap_width += rect.w - gui2::settings::screen_width; + gui2::settings::gamemap_height += rect.h - gui2::settings::screen_height; + gui2::settings::screen_width = rect.w; + gui2::settings::screen_height = rect.h; + + /** + * The screen size might have changed force an update of the size. + * + * @todo This might no longer be needed when gui2 is done. + */ + window.invalidate_layout(); + + wesnothd_connection_.send_data(config("refresh_lobby")); } void mp_lobby::game_filter_reload() diff --git a/src/gui/dialogs/lobby/lobby.hpp b/src/gui/dialogs/lobby/lobby.hpp index 51a90aa7d327..f7dd7010c85a 100644 --- a/src/gui/dialogs/lobby/lobby.hpp +++ b/src/gui/dialogs/lobby/lobby.hpp @@ -77,14 +77,6 @@ class mp_lobby : public modal_dialog, public quit_confirmation, private plugin_e ~mp_lobby(); - /** - * Set the callback used to show the preferences. - */ - void set_preferences_callback(std::function f) - { - preferences_callback_ = f; - } - int get_joined_game_id() const { return joined_game_id_; } void update_gamelist(); @@ -186,8 +178,6 @@ class mp_lobby : public modal_dialog, public quit_confirmation, private plugin_e chatbox* chatbox_; - std::function preferences_callback_; - toggle_button* filter_friends_; toggle_button* filter_ignored_;