The Battle for Wesnoth  1.17.23+dev
multiplayer.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2023
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
17 
18 #include "build_info.hpp"
19 #include "commandline_options.hpp"
20 #include "connect_engine.hpp"
21 #include "events.hpp"
22 #include "formula/string_utils.hpp"
23 #include "game_config_manager.hpp"
26 #include "gettext.hpp"
28 #include "gui/dialogs/message.hpp"
34 #include "log.hpp"
35 #include "map_settings.hpp"
38 #include "preferences/game.hpp"
39 #include "replay.hpp"
40 #include "resources.hpp"
41 #include "saved_game.hpp"
42 #include "sound.hpp"
44 #include "wesnothd_connection.hpp"
45 
46 #include <fstream>
47 #include <functional>
48 #include <future>
49 #include <optional>
50 #include <thread>
51 
52 static lg::log_domain log_mp("mp/main");
53 #define DBG_MP LOG_STREAM(debug, log_mp)
54 #define LOG_MP LOG_STREAM(info, log_mp)
55 #define WRN_MP LOG_STREAM(warn, log_mp)
56 #define ERR_MP LOG_STREAM(err, log_mp)
57 
58 namespace mp
59 {
60 namespace
61 {
62 /** Pointer to the current mp_manager instance. */
63 class mp_manager* manager = nullptr;
64 
65 /** The main controller of the MP workflow. */
66 class mp_manager
67 {
68 public:
69  // Declare this as a friend to allow direct access to enter_create_mode
70  friend void mp::start_local_game();
71  friend void mp::send_to_server(const config&);
73 
74  mp_manager(const std::optional<std::string> host);
75 
76  ~mp_manager()
77  {
78  assert(manager);
79  manager = nullptr;
80 
81  if(network_worker.joinable()) {
82  stop = true;
83  network_worker.join();
84  }
85  }
86 
87  /**
88  * Enters the mp loop. It consists of four screens:
89  *
90  * Host POV: LOBBY <---> CREATE GAME ---> STAGING -----> GAME BEGINS
91  * Player POV: LOBBY <--------------------> JOIN GAME ---> GAME BEGINS
92  */
93  void run_lobby_loop();
94 
95  bool post_scenario_staging(ng::connect_engine& engine);
96 
97  bool post_scenario_wait(bool observe);
98 
99 private:
100  /** Represents the contents of the [join_lobby] response. */
101  struct session_metadata
102  {
103  session_metadata() = default;
104 
105  session_metadata(const config& cfg)
106  : is_moderator(cfg["is_moderator"].to_bool(false))
107  , profile_url_prefix(cfg["profile_url_prefix"].str())
108  {
109  }
110 
111  /** Whether you are logged in as a server moderator. */
112  bool is_moderator = false;
113 
114  /** The external URL prefix for player profiles (empty if the server doesn't have an attached database). */
115  std::string profile_url_prefix;
116  };
117 
118  /** Opens a new server connection and prompts the client for login credentials, if necessary. */
119  std::unique_ptr<wesnothd_connection> open_connection(std::string host);
120 
121  /** Opens the MP lobby. */
122  bool enter_lobby_mode();
123 
124  /** Opens the MP Create screen for hosts to configure a new game. */
125  void enter_create_mode();
126 
127  /** Opens the MP Staging screen for hosts to wait for players. */
128  void enter_staging_mode();
129 
130  /** Opens the MP Join Game screen for non-host players and observers. */
131  void enter_wait_mode(int game_id, bool observe);
132 
133  /** Worker thread to handle receiving and processing network data. */
134  std::thread network_worker;
135 
136  /** Flag to signal the worker thread terminate. */
137  std::atomic_bool stop;
138 
139  /** The connection to the server. */
140  std::unique_ptr<wesnothd_connection> connection;
141 
142  /** The current session's info sent by the server on login. */
143  session_metadata session_info;
144 
145  /** This single instance is reused for all games played during the current connection to the server. */
146  saved_game state;
147 
148  mp::lobby_info lobby_info;
149 
150  std::list<mp::network_registrar::handler> process_handlers;
151 
152 public:
153  const session_metadata& get_session_info() const
154  {
155  return session_info;
156  }
157 
158  auto add_network_handler(decltype(process_handlers)::value_type func)
159  {
160  return [this, iter = process_handlers.insert(process_handlers.end(), func)]() { process_handlers.erase(iter); };
161  }
162 };
163 
164 mp_manager::mp_manager(const std::optional<std::string> host)
165  : network_worker()
166  , stop(false)
167  , connection(nullptr)
168  , session_info()
169  , state()
170  , lobby_info()
171  , process_handlers()
172 {
173  state.classification().type = campaign_type::type::multiplayer;
174 
175  if(host) {
177  connection = open_connection(*host);
178 
179  // If for whatever reason our connection is null at this point (dismissing the password prompt, for
180  // instance), treat it as a normal condition and exit. Any actual error conditions throw exceptions
181  // which can be handled higher up the stack.
182  if(connection == nullptr) {
183  return;
184  }
185 
187 
188  config data;
189 
190  while(!stop) {
191  connection->wait_and_receive_data(data);
192 
193  if(const auto error = data.optional_child("error")) {
194  throw wesnothd_error((*error)["message"]);
195  }
196 
197  else if(data.has_child("gamelist")) {
198  this->lobby_info.process_gamelist(data);
199  break;
200  }
201 
202  else if(const auto gamelist_diff = data.optional_child("gamelist_diff")) {
203  this->lobby_info.process_gamelist_diff(*gamelist_diff);
204  }
205 
206  else {
207  // No special actions to take. Pass the data on to the network handlers.
208  for(const auto& handler : process_handlers) {
209  handler(data);
210  }
211  }
212  }
213  });
214  }
215 
216  // Avoid setting this until the connection has been fully established. open_connection may throw,
217  // in which case we don't want to point to an object instance that has not properly connected.
218  assert(!manager);
219  manager = this;
220 }
221 
222 std::unique_ptr<wesnothd_connection> mp_manager::open_connection(std::string host)
223 {
224  DBG_MP << "opening connection";
225 
226  if(host.empty()) {
227  return nullptr;
228  }
229 
230  // shown_hosts is used to prevent the client being locked in a redirect loop.
231  std::set<std::pair<std::string, std::string>> shown_hosts;
232  auto addr = shown_hosts.end();
233 
234  try {
235  std::tie(addr, std::ignore) = shown_hosts.insert(parse_network_address(host, "15000"));
236  } catch(const std::runtime_error&) {
237  throw wesnothd_error(_("Invalid address specified for multiplayer server"));
238  }
239 
240  // Start stage
242 
243  // Initializes the connection to the server.
244  auto conn = std::make_unique<wesnothd_connection>(addr->first, addr->second);
245 
246  // First, spin until we get a handshake from the server.
247  conn->wait_for_handshake();
248 
250 
251  config data;
252 
253  // Then, log in and wait for the lobby/game join prompt.
254  while(true) {
255  data.clear();
256  conn->wait_and_receive_data(data);
257 
258  if(const auto reject = data.optional_child("reject"); reject || data.has_attribute("version")) {
259  std::string version;
260 
261  if(reject) {
262  version = (*reject)["accepted_versions"].str();
263  } else {
264  // Backwards-compatibility "version" attribute
265  version = data["version"].str();
266  }
267 
268  utils::string_map i18n_symbols;
269  i18n_symbols["required_version"] = version;
270  i18n_symbols["your_version"] = game_config::wesnoth_version.str();
271 
272  const std::string errorstring = VGETTEXT("The server accepts versions '$required_version', but you are using version '$your_version'", i18n_symbols);
273  throw wesnothd_error(errorstring);
274  }
275 
276  // Check for "redirect" messages
277  if(const auto redirect = data.optional_child("redirect")) {
278  auto redirect_host = (*redirect)["host"].str();
279  auto redirect_port = (*redirect)["port"].str("15000");
280 
281  bool recorded_host;
282  std::tie(std::ignore, recorded_host) = shown_hosts.emplace(redirect_host, redirect_port);
283 
284  if(!recorded_host) {
285  throw wesnothd_error(_("Server-side redirect loop"));
286  }
287 
289 
290  // Open a new connection with the new host and port.
291  conn.reset();
292  conn = std::make_unique<wesnothd_connection>(redirect_host, redirect_port);
293 
294  // Wait for new handshake.
295  conn->wait_for_handshake();
296 
298  continue;
299  }
300 
301  if(data.has_child("version")) {
302  config res;
303  config& cfg = res.add_child("version");
304  cfg["version"] = game_config::wesnoth_version.str();
305  cfg["client_source"] = game_config::dist_channel_id();
306  conn->send_data(res);
307  }
308 
309  if(const auto error = data.optional_child("error")) {
310  throw wesnothd_rejected_client_error((*error)["message"].str());
311  }
312 
313  // Continue if we did not get a direction to login
314  if(!data.has_child("mustlogin")) {
315  continue;
316  }
317 
318  // Enter login loop
319  while(true) {
320  std::string login = preferences::login();
321 
322  config response;
323  config& sp = response.add_child("login");
324  sp["username"] = login;
325 
326  conn->send_data(response);
327  conn->wait_and_receive_data(data);
328 
330 
331  if(const auto warning = data.optional_child("warning")) {
332  std::string warning_msg;
333 
334  if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) {
335  warning_msg = VGETTEXT("The nickname ‘$nick’ is inactive. "
336  "You cannot claim ownership of this nickname until you "
337  "activate your account via email or ask an "
338  "administrator to do it for you.", {{"nick", login}});
339  } else {
340  warning_msg = (*warning)["message"].str();
341  }
342 
343  warning_msg += "\n\n";
344  warning_msg += _("Do you want to continue?");
345 
347  return nullptr;
348  } else {
349  continue;
350  }
351  }
352 
353  auto error = data.optional_child("error");
354 
355  // ... and get us out of here if the server did not complain
356  if(!error) break;
357 
358  do {
359  std::string password = preferences::password(host, login);
360 
361  const bool fall_through = (*error)["force_confirmation"].to_bool()
363  : false;
364 
365  // If:
366  // * the server asked for a password
367  // * the password isn't empty
368  // * the user didn't press Cancel
369  // * the connection is secure or the client was started with the option to use insecure connections
370  // send the password to the server
371  // otherwise go directly to the username/password dialog
372  if(!(*error)["password_request"].empty() && !password.empty() && !fall_through && (conn->using_tls() || game_config::allow_insecure)) {
373  // the possible cases here are that either:
374  // 1) TLS encryption is enabled, thus sending the plaintext password is still secure
375  // 2) TLS encryption is not enabled, in which case the server should not be requesting a password in the first place
376  // 3) This is being used for local testing/development, so using an insecure connection is enabled manually
377 
378  sp["password"] = password;
379 
380  // Once again send our request...
381  conn->send_data(response);
382  conn->wait_and_receive_data(data);
383 
385 
386  error = data.optional_child("error");
387 
388  // ... and get us out of here if the server is happy now
389  if(!error) break;
390  }
391 
392  // Providing a password either was not attempted because we did not
393  // have any or failed:
394  // Now show a dialog that displays the error and allows to
395  // enter a new user name and/or password
396 
397  std::string error_message;
398  utils::string_map i18n_symbols;
399  i18n_symbols["nick"] = login;
400 
401  const auto extra_data = error->optional_child("data");
402  if(extra_data) {
403  i18n_symbols["duration"] = utils::format_timespan((*extra_data)["duration"]);
404  }
405 
406  const std::string ec = (*error)["error_code"];
407 
408  if(!(*error)["password_request"].empty() && !conn->using_tls() && !game_config::allow_insecure) {
409  error_message = _("The remote server requested a password while using an insecure connection.");
410  } else if(ec == MP_MUST_LOGIN) {
411  error_message = _("You must login first.");
412  } else if(ec == MP_NAME_TAKEN_ERROR) {
413  error_message = VGETTEXT("The nickname ‘$nick’ is already taken.", i18n_symbols);
414  } else if(ec == MP_INVALID_CHARS_IN_NAME_ERROR) {
415  error_message = VGETTEXT("The nickname ‘$nick’ contains invalid "
416  "characters. Only alpha-numeric characters (one at minimum), underscores and "
417  "hyphens are allowed.", i18n_symbols);
418  } else if(ec == MP_NAME_TOO_LONG_ERROR) {
419  error_message = VGETTEXT("The nickname ‘$nick’ is too long. Nicks must be 20 characters or less.", i18n_symbols);
420  } else if(ec == MP_NAME_RESERVED_ERROR) {
421  error_message = VGETTEXT("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
422  } else if(ec == MP_NAME_UNREGISTERED_ERROR) {
423  error_message = VGETTEXT("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
424  + _(" This server disallows unregistered nicknames.");
425  } else if(ec == MP_NAME_AUTH_BAN_USER_ERROR) {
426  if(extra_data) {
427  error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
428  } else {
429  error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
430  }
431  } else if(ec == MP_NAME_AUTH_BAN_IP_ERROR) {
432  if(extra_data) {
433  error_message = VGETTEXT("Your IP address is banned on this server’s forums for $duration|.", i18n_symbols);
434  } else {
435  error_message = _("Your IP address is banned on this server’s forums.");
436  }
437  } else if(ec == MP_NAME_AUTH_BAN_EMAIL_ERROR) {
438  if(extra_data) {
439  error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
440  } else {
441  error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
442  }
443  } else if(ec == MP_PASSWORD_REQUEST) {
444  error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols);
445  } else if(ec == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) {
446  error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols)
447  + "\n\n" + _("WARNING: There is already a client using this nickname, "
448  "logging in will cause that client to be kicked!");
449  } else if(ec == MP_INCORRECT_PASSWORD_ERROR) {
450  error_message = _("The password you provided was incorrect.");
451  } else if(ec == MP_TOO_MANY_ATTEMPTS_ERROR) {
452  error_message = _("You have made too many login attempts.");
453  } else if(ec == MP_HASHING_PASSWORD_FAILED) {
454  error_message = _("Password hashing failed.");
455  } else {
456  error_message = (*error)["message"].str();
457  }
458 
459  gui2::dialogs::mp_login dlg(host, error_message, !((*error)["password_request"].empty()));
460 
461  // Need to show the dialog from the main thread or it won't appear.
462  events::call_in_main_thread([&dlg]() { dlg.show(); });
463 
464  switch(dlg.get_retval()) {
465  // Log in with password
466  case gui2::retval::OK:
467  break;
468  // Cancel
469  default:
470  return nullptr;
471  }
472 
473  // If we have got a new username we have to start all over again
474  } while(login == preferences::login());
475 
476  // Somewhat hacky...
477  // If we broke out of the do-while loop above error is still going to be nullopt
478  if(!error) break;
479  } // end login loop
480 
481  if(const auto join_lobby = data.optional_child("join_lobby")) {
482  // Note any session data sent with the response. This should be the only place session_info is set.
483  session_info = { join_lobby.value() };
484 
485  // All done!
486  break;
487  }
488  }
489 
490  return conn;
491 }
492 
493 void mp_manager::run_lobby_loop()
494 {
495  // This should only work if we have a connection. If we're in a local mode,
496  // enter_create_mode should be accessed directly.
497  if(!connection) {
498  return;
499  }
500 
501  // A return of false means a config reload was requested, so do that and then loop.
502  while(!enter_lobby_mode()) {
505  gcm->load_game_config_for_create(true); // NOTE: Using reload_changed_game_config only doesn't seem to work here
506 
507  lobby_info.refresh_installed_addons_cache();
508 
509  connection->send_data(config("refresh_lobby"));
510  }
511 }
512 
513 bool mp_manager::enter_lobby_mode()
514 {
515  DBG_MP << "entering lobby mode";
516 
517  // Connection should never be null in the lobby.
518  assert(connection);
519 
520  // We use a loop here to allow returning to the lobby if you, say, cancel game creation.
521  while(true) {
522  if(auto cfg = game_config_manager::get()->game_config().optional_child("lobby_music")) {
523  for(const config& i : cfg->child_range("music")) {
525  }
526 
528  } else {
531  }
532 
533  int dlg_retval = 0;
534  int dlg_joined_game_id = 0;
535  {
536  gui2::dialogs::mp_lobby dlg(lobby_info, *connection, dlg_joined_game_id);
537  dlg.show();
538  dlg_retval = dlg.get_retval();
539  }
540 
541  try {
542  switch(dlg_retval) {
544  enter_create_mode();
545  break;
547  [[fallthrough]];
549  enter_wait_mode(dlg_joined_game_id, dlg_retval == gui2::dialogs::mp_lobby::OBSERVE);
550  break;
552  // Let this function's caller reload the config and re-call.
553  return false;
554  default:
555  // Needed to handle the Quit signal and exit the loop
556  return true;
557  }
558  } catch(const config::error& error) {
559  if(!error.message.empty()) {
561  }
562 
563  // Update lobby content
564  connection->send_data(config("refresh_lobby"));
565  }
566  }
567 
568  return true;
569 }
570 
571 void mp_manager::enter_create_mode()
572 {
573  DBG_MP << "entering create mode";
574 
575  if(gui2::dialogs::mp_create_game::execute(state, connection == nullptr)) {
576  enter_staging_mode();
577  } else if(connection) {
578  connection->send_data(config("refresh_lobby"));
579  }
580 }
581 
582 void mp_manager::enter_staging_mode()
583 {
584  DBG_MP << "entering connect mode";
585 
586  std::unique_ptr<mp_game_metadata> metadata;
587 
588  // If we have a connection, set the appropriate info. No connection means we're in local game mode.
589  if(connection) {
590  metadata = std::make_unique<mp_game_metadata>(*connection);
591  metadata->connected_players.insert(preferences::login());
592  metadata->is_host = true;
593  }
594 
595  bool dlg_ok = false;
596  {
597  ng::connect_engine connect_engine(state, true, metadata.get());
598  dlg_ok = gui2::dialogs::mp_staging::execute(connect_engine, connection.get());
599  } // end connect_engine
600 
601  if(dlg_ok) {
603  controller.set_mp_info(metadata.get());
604  controller.play_game();
605  }
606 
607  if(connection) {
608  connection->send_data(config("leave_game"));
609  }
610 }
611 
612 void mp_manager::enter_wait_mode(int game_id, bool observe)
613 {
614  DBG_MP << "entering wait mode";
615 
616  // The connection should never be null here, since one should never reach this screen in local game mode.
617  assert(connection);
618 
619  mp_game_metadata metadata(*connection);
620  metadata.is_host = false;
621 
622  if(const mp::game_info* gi = lobby_info.get_game_by_id(game_id)) {
623  metadata.current_turn = gi->current_turn;
624  }
625 
627  metadata.skip_replay = true;
629  }
630 
631  bool dlg_ok = false;
632  {
633  gui2::dialogs::mp_join_game dlg(state, *connection, true, observe);
634 
635  if(!dlg.fetch_game_config()) {
636  connection->send_data(config("leave_game"));
637  return;
638  }
639 
640  dlg_ok = dlg.show();
641  }
642 
643  if(dlg_ok) {
645  controller.set_mp_info(&metadata);
646  controller.play_game();
647  }
648 
649  connection->send_data(config("leave_game"));
650 }
651 
652 bool mp_manager::post_scenario_staging(ng::connect_engine& engine)
653 {
654  return gui2::dialogs::mp_staging::execute(engine, connection.get());
655 }
656 
657 bool mp_manager::post_scenario_wait(bool observe)
658 {
659  gui2::dialogs::mp_join_game dlg(state, *connection, false, observe);
660 
661  if(!dlg.fetch_game_config()) {
662  connection->send_data(config("leave_game"));
663  return false;
664  }
665 
666  if(dlg.started()) {
667  return true;
668  }
669 
670  return dlg.show();
671 }
672 
673 } // end anon namespace
674 
675 /** Pubic entry points for the MP workflow */
676 
677 void start_client(const std::string& host)
678 {
679  DBG_MP << "starting client";
680  mp_manager(host).run_lobby_loop();
681 }
682 
684 {
685  DBG_MP << "starting local game";
686 
688 
689  mp_manager(std::nullopt).enter_create_mode();
690 }
691 
693 {
694  DBG_MP << "starting local MP game from commandline";
695 
697 
698  // The setup is done equivalently to lobby MP games using as much of existing
699  // code as possible. This means that some things are set up that are not
700  // needed in commandline mode, but they are required by the functions called.
702 
703  DBG_MP << "entering create mode";
704 
705  // Set the default parameters
706  saved_game state;
707  state.classification().type = campaign_type::type::multiplayer;
708 
709  mp_game_settings& parameters = state.mp_settings();
710 
711  // Hardcoded default values
712  state.classification().era_id = "era_default";
713  parameters.name = "multiplayer_The_Freelands";
714 
715  // Default values for which at getter function exists
716  parameters.num_turns = settings::get_turns("");
717  parameters.village_gold = settings::get_village_gold("");
719  parameters.xp_modifier = settings::get_xp_modifier("");
720 
721  // Do not use map settings if --ignore-map-settings commandline option is set
722  if(cmdline_opts.multiplayer_ignore_map_settings) {
723  DBG_MP << "ignoring map settings";
724  parameters.use_map_settings = false;
725  } else {
726  parameters.use_map_settings = true;
727  }
728 
729  // None of the other parameters need to be set, as their creation values above are good enough for CL mode.
730  // In particular, we do not want to use the preferences values.
731 
732  state.classification().type = campaign_type::type::multiplayer;
733 
734  // [era] define.
735  if(cmdline_opts.multiplayer_era) {
736  state.classification().era_id = *cmdline_opts.multiplayer_era;
737  }
738 
739  if(auto cfg_era = game_config.find_child("era", "id", state.classification().era_id)) {
740  state.classification().era_define = cfg_era["define"].str();
741  } else {
742  PLAIN_LOG << "Could not find era '" << state.classification().era_id << "'";
743  return;
744  }
745 
746  // [multiplayer] define.
747  if(cmdline_opts.multiplayer_scenario) {
748  parameters.name = *cmdline_opts.multiplayer_scenario;
749  }
750 
751  if(auto cfg_multiplayer = game_config.find_child("multiplayer", "id", parameters.name)) {
752  state.classification().scenario_define = cfg_multiplayer["define"].str();
753  } else {
754  PLAIN_LOG << "Could not find [multiplayer] '" << parameters.name << "'";
755  return;
756  }
757 
759  config {"next_scenario", parameters.name}
760  );
761 
763 
764  state.expand_random_scenario();
765  state.expand_mp_events();
766  state.expand_mp_options();
767 
768  // Should number of turns be determined from scenario data?
769  if(parameters.use_map_settings && state.get_starting_point()["turns"]) {
770  DBG_MP << "setting turns from scenario data: " << state.get_starting_point()["turns"];
771  parameters.num_turns = state.get_starting_point()["turns"];
772  }
773 
774  DBG_MP << "entering connect mode";
775 
776  {
777  ng::connect_engine connect_engine(state, true, nullptr);
778 
779  // Update the parameters to reflect game start conditions
780  connect_engine.start_game_commandline(cmdline_opts, game_config);
781  }
782 
783  if(resources::recorder && cmdline_opts.multiplayer_label) {
784  std::string label = *cmdline_opts.multiplayer_label;
785  resources::recorder->add_log_data("ai_log","ai_label",label);
786  }
787 
788  unsigned int repeat = (cmdline_opts.multiplayer_repeat) ? *cmdline_opts.multiplayer_repeat : 1;
789  for(unsigned int i = 0; i < repeat; i++){
790  saved_game state_copy(state);
791  campaign_controller controller(state_copy);
792  controller.play_game();
793  }
794 }
795 
797 {
798  return manager && manager->post_scenario_staging(engine);
799 }
800 
801 bool goto_mp_wait(bool observe)
802 {
803  return manager && manager->post_scenario_wait(observe);
804 }
805 
807 {
808  return manager && manager->get_session_info().is_moderator;
809 }
810 
811 std::string get_profile_link(int user_id)
812 {
813  if(manager) {
814  const std::string& prefix = manager->get_session_info().profile_url_prefix;
815 
816  if(!prefix.empty()) {
817  return prefix + std::to_string(user_id);
818  }
819  }
820 
821  return "";
822 }
823 
825 {
826  if(manager && manager->connection) {
827  manager->connection->send_data(data);
828  }
829 }
830 
832 {
833  if(manager /*&& manager->connection*/) {
834  remove_handler = manager->add_network_handler(func);
835  }
836 }
837 
839 {
840  if(remove_handler) {
841  remove_handler();
842  }
843 }
844 
846 {
847  return manager ? &manager->lobby_info : nullptr;
848 }
849 
850 } // end namespace mp
std::optional< unsigned int > multiplayer_repeat
Repeats specified by –multiplayer-repeat option.
std::optional< std::string > multiplayer_label
Non-empty if –label was given on the command line.
std::optional< std::string > multiplayer_scenario
Non-empty if –scenario was given on the command line.
std::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.
Definition: config.hpp:161
config & add_child(config_key_type key)
Definition: config.cpp:445
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.
campaign_type::type type
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)
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(std::function< void()> f)
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
@ ok_cancel_buttons
Shows an ok and cancel button.
Definition: message.hpp:77
bool show(const unsigned auto_close_time=0)
Shows the window.
int get_retval() const
Returns the cached window exit code.
This shows the dialog to log in to the MP server.
Definition: mp_login.hpp:41
This class represents the collective information the client has about the players and games on the se...
Definition: lobby_info.hpp:32
std::function< void()> remove_handler
Definition: multiplayer.hpp:87
std::function< void(const config &)> handler
Definition: multiplayer.hpp:81
network_registrar(handler func)
void start_game_commandline(const commandline_options &cmdline_opts, const game_config_view &game_config)
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:314
game_classification & classification()
Definition: saved_game.hpp:56
void expand_mp_options()
adds values of [option]s into [carryover_sides_start][variables] so that they are applied in the next...
Definition: saved_game.cpp:426
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:165
std::string get_scenario_id() const
Definition: saved_game.cpp:678
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
config & get_starting_point()
Definition: saved_game.cpp:611
void expand_mp_events()
adds [event]s from [era] and [modification] into this scenario does NOT expand [option]s because vari...
Definition: saved_game.cpp:385
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:504
std::string str() const
Serializes the version number into string form.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:968
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:217
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:262
General settings and defaults for scenarios.
static lg::log_domain log_mp("mp/main")
#define DBG_MP
Definition: multiplayer.cpp:53
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_MUST_LOGIN
#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_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
void call_in_main_thread(const std::function< void(void)> &f)
Definition: events.cpp:804
Game configuration data as global variables.
Definition: build_info.cpp:63
const version_info wesnoth_version(VERSION)
bool allow_insecure
Definition: game_config.cpp:74
std::string dist_channel_id()
Return the distribution channel identifier, or "Default" if missing.
Definition: build_info.cpp:397
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:204
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.
Definition: message.cpp:151
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:52
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.
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.
void set_message_private(bool value)
Definition: game.cpp:838
bool blindfold_replay()
Definition: game.cpp:591
std::string password(const std::string &server, const std::string &login)
bool skip_mp_replay()
Definition: game.cpp:581
std::string login()
replay * recorder
Definition: resources.cpp:29
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 empty_playlist()
Definition: sound.cpp:612
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition: sound.cpp:713
void stop_music()
Definition: sound.cpp:557
void commit_music_changes()
Definition: sound.cpp:837
std::string format_timespan(std::time_t time, bool detailed)
Formats a timespan into human-readable text for player authentication functions.
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.
std::string_view data
Definition: picture.cpp:199
Replay control code.
std::string message
Definition: exceptions.hpp:30
This class represents the info a client has about a game on the server.
Definition: lobby_data.hpp:66
unsigned current_turn
bool skip_replay_blindfolded
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.