50 #define DBG_MP LOG_STREAM(debug, log_mp)
51 #define LOG_MP LOG_STREAM(info, log_mp)
52 #define WRN_MP LOG_STREAM(warn, log_mp)
53 #define ERR_MP LOG_STREAM(err, log_mp)
60 class mp_manager* manager =
nullptr;
71 mp_manager(
const utils::optional<std::string> host);
78 if(network_worker.joinable()) {
80 network_worker.join();
90 void run_lobby_loop();
94 bool post_scenario_wait(
bool observe);
98 struct session_metadata
100 session_metadata() =
default;
102 session_metadata(
const config& cfg)
103 : is_moderator(cfg[
"is_moderator"].to_bool(
false))
104 , profile_url_prefix(cfg[
"profile_url_prefix"].str())
110 info.id = queue[
"id"].to_int();
111 info.scenario_id = queue[
"scenario_id"].str();
112 info.display_name = queue[
"display_name"].str();
113 info.players_required = queue[
"players_required"].to_int();
115 queues.emplace_back(
info);
121 bool is_moderator =
false;
124 std::string profile_url_prefix;
127 std::vector<queue_info> queues;
131 std::unique_ptr<wesnothd_connection> open_connection(
const std::string& host);
134 bool enter_lobby_mode();
140 void enter_create_mode(utils::optional<std::string> preset_scenario = utils::nullopt, utils::optional<config> server_preset = utils::nullopt,
int queue_id = 0);
146 void enter_wait_mode(
int game_id,
bool observe);
149 std::thread network_worker;
152 std::atomic_bool stop;
155 std::unique_ptr<wesnothd_connection> connection;
158 session_metadata session_info;
165 std::list<mp::network_registrar::handler> process_handlers;
168 const session_metadata& get_session_info()
const
172 session_metadata& get_session_info()
177 auto add_network_handler(
const decltype(process_handlers)::value_type& func)
179 return [
this, iter = process_handlers.insert(process_handlers.end(), func)]() { process_handlers.erase(iter); };
183 mp_manager::mp_manager(
const utils::optional<std::string> host)
186 , connection(
nullptr)
192 state.classification().type = campaign_type::type::multiplayer;
196 connection = open_connection(*host);
201 if(connection ==
nullptr) {
210 connection->wait_and_receive_data(
data);
212 if(
const auto error =
data.optional_child(
"error")) {
216 else if(
data.has_child(
"gamelist")) {
217 this->lobby_info.process_gamelist(
data);
221 else if(
const auto gamelist_diff =
data.optional_child(
"gamelist_diff")) {
222 this->lobby_info.process_gamelist_diff(*gamelist_diff);
227 for(
const auto& handler : process_handlers) {
241 std::unique_ptr<wesnothd_connection> mp_manager::open_connection(
const std::string& host)
243 DBG_MP <<
"opening connection";
250 std::set<std::pair<std::string, std::string>> shown_hosts;
251 auto addr = shown_hosts.end();
255 }
catch(
const std::runtime_error&) {
256 throw wesnothd_error(
_(
"Invalid address specified for multiplayer server"));
263 auto conn = std::make_unique<wesnothd_connection>(addr->first, addr->second);
266 conn->wait_for_handshake();
275 conn->wait_and_receive_data(
data);
277 if(
const auto reject =
data.optional_child(
"reject"); reject ||
data.has_attribute(
"version")) {
281 version = (*reject)[
"accepted_versions"].str();
284 version =
data[
"version"].str();
288 i18n_symbols[
"required_version"] = version;
291 const std::string errorstring =
VGETTEXT(
"The server accepts versions ‘$required_version’, but you are using version ‘$your_version’", i18n_symbols);
296 if(
const auto redirect =
data.optional_child(
"redirect")) {
297 auto redirect_host = (*redirect)[
"host"].str();
298 auto redirect_port = (*redirect)[
"port"].str(
"15000");
301 std::tie(std::ignore, recorded_host) = shown_hosts.emplace(redirect_host, redirect_port);
311 conn = std::make_unique<wesnothd_connection>(redirect_host, redirect_port);
314 conn->wait_for_handshake();
320 if(
data.has_child(
"version")) {
325 conn->send_data(res);
328 if(
const auto error =
data.optional_child(
"error")) {
333 if(!
data.has_child(
"mustlogin")) {
343 sp[
"username"] = login;
345 conn->send_data(response);
346 conn->wait_and_receive_data(
data);
350 if(
const auto warning =
data.optional_child(
"warning")) {
351 std::string warning_msg;
354 warning_msg =
VGETTEXT(
"The nickname ‘$nick’ is inactive. "
355 "You cannot claim ownership of this nickname until you "
356 "activate your account via email or ask an "
357 "administrator to do it for you.", {{
"nick", login}});
359 warning_msg = (*warning)[
"message"].str();
362 warning_msg +=
"\n\n";
363 warning_msg +=
_(
"Do you want to continue?");
372 auto error =
data.optional_child(
"error");
380 const bool fall_through = (*error)[
"force_confirmation"].to_bool()
391 if(!(*error)[
"password_request"].empty() && !password.empty() && !fall_through && (conn->using_tls() ||
game_config::allow_insecure)) {
397 sp[
"password"] = password;
400 conn->send_data(response);
401 conn->wait_and_receive_data(
data);
405 error =
data.optional_child(
"error");
416 std::string error_message;
418 i18n_symbols[
"nick"] = login;
420 const auto extra_data = error->optional_child(
"data");
422 using namespace std::chrono_literals;
426 const std::string ec = (*error)[
"error_code"];
429 error_message =
_(
"The remote server requested a password while using an insecure connection.");
431 error_message =
_(
"You must login first.");
433 error_message =
VGETTEXT(
"The nickname ‘$nick’ is already taken.", i18n_symbols);
435 error_message =
VGETTEXT(
"The nickname ‘$nick’ contains invalid "
436 "characters. Only alpha-numeric characters (one at minimum), underscores and "
437 "hyphens are allowed.", i18n_symbols);
439 error_message =
VGETTEXT(
"The nickname ‘$nick’ is too long. Nicks must be 20 characters or less.", i18n_symbols);
441 error_message =
VGETTEXT(
"The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
443 error_message =
VGETTEXT(
"The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
444 +
_(
" This server disallows unregistered nicknames.");
447 error_message =
VGETTEXT(
"Your IP address is banned on this server for $duration|.", i18n_symbols);
449 error_message =
_(
"Your IP address is banned on this server.");
453 error_message =
VGETTEXT(
"The nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
455 error_message =
VGETTEXT(
"The nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
459 error_message =
VGETTEXT(
"Your IP address is banned on this server’s forums for $duration|.", i18n_symbols);
461 error_message =
_(
"Your IP address is banned on this server’s forums.");
465 error_message =
VGETTEXT(
"The email address for the nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
467 error_message =
VGETTEXT(
"The email address for the nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
470 error_message =
VGETTEXT(
"The nickname ‘$nick’ is registered on this server.", i18n_symbols);
472 error_message =
VGETTEXT(
"The nickname ‘$nick’ is registered on this server.", i18n_symbols)
473 +
"\n\n" +
_(
"WARNING: There is already a client using this nickname, "
474 "logging in will cause that client to be kicked!");
476 error_message =
_(
"The password you provided was incorrect.");
478 error_message =
_(
"You have made too many login attempts.");
480 error_message =
_(
"Password hashing failed.");
482 error_message = (*error)[
"message"].str();
507 if(
const auto join_lobby =
data.optional_child(
"join_lobby")) {
509 session_info = { join_lobby.value() };
519 void mp_manager::run_lobby_loop()
528 while(!enter_lobby_mode()) {
533 lobby_info.refresh_installed_addons_cache();
535 connection->send_data(
config(
"refresh_lobby"));
539 bool mp_manager::enter_lobby_mode()
541 DBG_MP <<
"entering lobby mode";
549 for(
const config&
i : cfg->child_range(
"music")) {
560 int dlg_joined_game_id = 0;
561 std::string preset_scenario =
"";
576 enter_create_mode(utils::make_optional(preset_scenario), utils::make_optional(server_preset), queue_id);
599 connection->send_data(
config(
"refresh_lobby"));
606 void mp_manager::enter_create_mode(utils::optional<std::string> preset_scenario, utils::optional<config> server_preset,
int queue_id)
608 DBG_MP <<
"entering create mode";
612 if(preset_scenario && server_preset) {
614 enter_staging_mode(queue_type::type::server_preset, queue_id);
615 }
else if(preset_scenario && !server_preset) {
617 if(
game[
"scenario"].str() == preset_scenario.value()) {
619 enter_staging_mode(queue_type::type::client_preset);
623 }
else if(gui2::dialogs::mp_create_game::execute(state, connection ==
nullptr)) {
624 enter_staging_mode(queue_type::type::normal);
625 }
else if(connection) {
626 connection->send_data(
config(
"refresh_lobby"));
632 DBG_MP <<
"entering connect mode";
634 std::unique_ptr<mp_game_metadata> metadata;
638 metadata = std::make_unique<mp_game_metadata>(*connection);
639 metadata->connected_players.insert(
prefs::get().login());
640 metadata->is_host =
true;
642 metadata->queue_id = queue_id;
648 dlg_ok = gui2::dialogs::mp_staging::execute(connect_engine, connection.get());
658 connection->send_data(
config(
"leave_game"));
662 void mp_manager::enter_wait_mode(
int game_id,
bool observe)
664 DBG_MP <<
"entering wait mode";
672 if(
const mp::game_info* gi = lobby_info.get_game_by_id(game_id)) {
686 connection->send_data(
config(
"leave_game"));
699 connection->send_data(
config(
"leave_game"));
704 return gui2::dialogs::mp_staging::execute(engine, connection.get());
707 bool mp_manager::post_scenario_wait(
bool observe)
712 connection->send_data(
config(
"leave_game"));
729 DBG_MP <<
"starting client";
730 mp_manager(host).run_lobby_loop();
735 DBG_MP <<
"starting local game";
739 mp_manager(utils::nullopt).enter_create_mode();
744 DBG_MP <<
"starting local MP game from commandline";
753 DBG_MP <<
"entering create mode";
763 parameters.
name =
"multiplayer_The_Freelands";
773 DBG_MP <<
"ignoring map settings";
801 if(
auto cfg_multiplayer =
game_config.find_child(
"multiplayer",
"id", parameters.
name)) {
804 PLAIN_LOG <<
"Could not find [multiplayer] '" << parameters.
name <<
"'";
824 DBG_MP <<
"entering connect mode";
839 for(
unsigned int i = 0;
i < repeat;
i++){
848 return manager && manager->post_scenario_staging(engine);
853 return manager && manager->post_scenario_wait(observe);
858 return manager && manager->get_session_info().is_moderator;
864 const std::string& prefix = manager->get_session_info().profile_url_prefix;
866 if(!prefix.empty()) {
867 return prefix + std::to_string(user_id);
876 static std::vector<queue_info> queues;
878 return manager->get_session_info().queues;
885 if(manager && manager->connection) {
886 manager->connection->send_data(
data);
906 return manager ? &manager->
lobby_info :
nullptr;
utils::optional< std::string > multiplayer_scenario
Non-empty if –scenario was given on the command line.
utils::optional< unsigned int > multiplayer_repeat
Repeats specified by –multiplayer-repeat option.
utils::optional< std::string > multiplayer_label
Non-empty if –label was given on the command line.
utils::optional< std::string > multiplayer_era
Non-empty if –era was given on the command line.
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line.
A config object defines a single node in a WML file, with access to child nodes.
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
bool has_attribute(config_key_type key) const
child_itors child_range(config_key_type key)
config & add_child(config_key_type key)
std::string scenario_define
If there is a define the scenario uses to customize data.
std::string era_define
If there is a define the era uses to customize data.
static game_config_manager * get()
void load_game_config_for_game(const game_classification &classification, const std::string &scenario_id)
void load_game_config_for_create(bool is_mp, bool is_test=false)
void reload_changed_game_config()
const game_config_view & game_config() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
static void display(const std::function< void()> &f)
@ yes_no_buttons
Shows a yes and no button.
@ ok_cancel_buttons
Shows an ok and cancel button.
bool show(const unsigned auto_close_time=0)
Shows the window.
static void quick_mp_setup(saved_game &state, const config presets)
presets needs to be a copy! Otherwise you'll get segfaults when clicking the Join button since it res...
const std::string queue_game_scenario_id() const
const config queue_game_server_preset() const
@ RELOAD_CONFIG
player clicked the Create button
@ CREATE_PRESET
player clicked Join button on an [mp_queue] game, but there was no existing game to join
This class represents the collective information the client has about the players and games on the se...
network_registrar(const handler &func)
std::function< void()> remove_handler
std::function< void(const config &)> handler
void start_game_commandline(const commandline_options &cmdline_opts, const game_config_view &game_config)
void set_message_private(bool value)
std::string password(const std::string &server, const std::string &login)
void add_log_data(const std::string &key, const std::string &var)
game_classification & classification()
void expand_mp_options()
adds values of [option]s into [carryover_sides_start][variables] so that they are applied in the next...
void set_carryover_sides_start(config carryover_sides_start)
std::string get_scenario_id() const
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
config & get_starting_point()
void expand_mp_events()
adds [event]s from [era] and [modification] into this scenario does NOT expand [option]s because vari...
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
std::string str() const
Serializes the version number into string form.
static std::string _(const char *str)
std::string label
What to show in the filter's drop-down list.
Standard logging facilities (interface).
General settings and defaults for scenarios.
static lg::log_domain log_mp("mp/main")
Define the errors the server may send during the login procedure.
#define MP_INCORRECT_PASSWORD_ERROR
#define MP_NAME_AUTH_BAN_USER_ERROR
#define MP_NAME_RESERVED_ERROR
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME
#define MP_NAME_AUTH_BAN_EMAIL_ERROR
#define MP_TOO_MANY_ATTEMPTS_ERROR
#define MP_HASHING_PASSWORD_FAILED
#define MP_SERVER_IP_BAN_ERROR
#define MP_NAME_TOO_LONG_ERROR
#define MP_NAME_AUTH_BAN_IP_ERROR
#define MP_PASSWORD_REQUEST
#define MP_NAME_INACTIVE_WARNING
#define MP_NAME_UNREGISTERED_ERROR
#define MP_INVALID_CHARS_IN_NAME_ERROR
#define MP_NAME_TAKEN_ERROR
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
void call_in_main_thread(const std::function< void(void)> &f)
Game configuration data as global variables.
const version_info wesnoth_version(VERSION)
std::string dist_channel_id()
Return the distribution channel identifier, or "Default" if missing.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
@ OK
Dialog was closed with the OK button.
@ CANCEL
Dialog was closed with the CANCEL button.
Main entry points of multiplayer mode.
lobby_info * get_lobby_info()
Returns the lobby_info object for the given session.
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
void start_local_game()
Starts a multiplayer game in single-user mode.
std::vector< queue_info > & get_server_queues()
Gets the list of server-side queues received on login.
bool goto_mp_staging(ng::connect_engine &engine)
Opens the MP Staging screen and sets the game state according to the changes made.
void start_local_game_commandline(const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode using command line settings.
std::string get_profile_link(int user_id)
Gets the forum profile link for the given user.
void start_client(const std::string &host)
Pubic entry points for the MP workflow.
bool logged_in_as_moderator()
Gets whether the currently logged-in user is a moderator.
bool goto_mp_wait(bool observe)
Opens the MP Join Game screen and sets the game state according to the changes made.
int get_village_support(const std::string &value)
Gets the village unit level support.
int get_turns(const std::string &value)
Gets the number of turns.
int get_xp_modifier(const std::string &value)
Gets the xp modifier.
int get_village_gold(const std::string &value, const game_classification *classification)
Gets the village gold.
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
void commit_music_changes()
static std::string format_timespan(const std::chrono::duration< Rep, Period > &span, bool detailed=false)
Formats a timespan into human-readable text for player authentication functions.
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
std::map< std::string, t_string > string_map
std::pair< std::string, std::string > parse_network_address(const std::string &address, const std::string &default_port)
Parse a host:port style network address, supporting [] notation for ipv6 addresses.
This class represents the info a client has about a game on the server.
The base template for associating string values with enum values.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.
static map_location::direction s