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