The Battle for Wesnoth  1.17.4+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  std::promise<void> received_initial_gamelist;
190 
191  network_worker = std::thread([this, &received_initial_gamelist]() {
192  config data;
193 
194  while(!stop) {
195  connection->wait_and_receive_data(data);
196 
197  if(const auto error = data.optional_child("error")) {
198  throw wesnothd_error((*error)["message"]);
199  }
200 
201  else if(data.has_child("gamelist")) {
202  this->lobby_info.process_gamelist(data);
203 
204  try {
205  received_initial_gamelist.set_value();
206  // TODO: only here while we transition away from dialog-bound timer-based handling
207  return;
208  } catch(const std::future_error& e) {
209  if(e.code() == std::future_errc::promise_already_satisfied) {
210  // We only need this for the first gamelist
211  }
212  }
213  }
214 
215  else if(const auto gamelist_diff = data.optional_child("gamelist_diff")) {
216  this->lobby_info.process_gamelist_diff(*gamelist_diff);
217  }
218 
219  else {
220  // No special actions to take. Pass the data on to the network handlers.
221  for(const auto& handler : process_handlers) {
222  handler(data);
223  }
224  }
225  }
226  });
227 
228  // Wait at the loading screen until the initial gamelist has been processed
229  received_initial_gamelist.get_future().wait();
230  });
231  }
232 
233  // Avoid setting this until the connection has been fully established. open_connection may throw,
234  // in which case we don't want to point to an object instance that has not properly connected.
235  assert(!manager);
236  manager = this;
237 }
238 
239 std::unique_ptr<wesnothd_connection> mp_manager::open_connection(std::string host)
240 {
241  DBG_MP << "opening connection" << std::endl;
242 
243  if(host.empty()) {
244  return nullptr;
245  }
246 
247  // shown_hosts is used to prevent the client being locked in a redirect loop.
248  std::set<std::pair<std::string, std::string>> shown_hosts;
249  auto addr = shown_hosts.end();
250 
251  try {
252  std::tie(addr, std::ignore) = shown_hosts.insert(parse_network_address(host, "15000"));
253  } catch(const std::runtime_error&) {
254  throw wesnothd_error(_("Invalid address specified for multiplayer server"));
255  }
256 
257  // Start stage
259 
260  // Initializes the connection to the server.
261  auto conn = std::make_unique<wesnothd_connection>(addr->first, addr->second);
262 
263  // First, spin until we get a handshake from the server.
264  conn->wait_for_handshake();
265 
267 
268  config data;
269 
270  // Then, log in and wait for the lobby/game join prompt.
271  while(true) {
272  data.clear();
273  conn->wait_and_receive_data(data);
274 
275  if(const auto reject = data.optional_child("reject"); reject || data.has_attribute("version")) {
276  std::string version;
277 
278  if(reject) {
279  version = (*reject)["accepted_versions"].str();
280  } else {
281  // Backwards-compatibility "version" attribute
282  version = data["version"].str();
283  }
284 
285  utils::string_map i18n_symbols;
286  i18n_symbols["required_version"] = version;
287  i18n_symbols["your_version"] = game_config::wesnoth_version.str();
288 
289  const std::string errorstring = VGETTEXT("The server accepts versions '$required_version', but you are using version '$your_version'", i18n_symbols);
290  throw wesnothd_error(errorstring);
291  }
292 
293  // Check for "redirect" messages
294  if(const auto redirect = data.optional_child("redirect")) {
295  auto redirect_host = (*redirect)["host"].str();
296  auto redirect_port = (*redirect)["port"].str("15000");
297 
298  bool recorded_host;
299  std::tie(std::ignore, recorded_host) = shown_hosts.emplace(redirect_host, redirect_port);
300 
301  if(!recorded_host) {
302  throw wesnothd_error(_("Server-side redirect loop"));
303  }
304 
306 
307  // Open a new connection with the new host and port.
308  conn.reset();
309  conn = std::make_unique<wesnothd_connection>(redirect_host, redirect_port);
310 
311  // Wait for new handshake.
312  conn->wait_for_handshake();
313 
315  continue;
316  }
317 
318  if(data.has_child("version")) {
319  config res;
320  config& cfg = res.add_child("version");
321  cfg["version"] = game_config::wesnoth_version.str();
322  cfg["client_source"] = game_config::dist_channel_id();
323  conn->send_data(res);
324  }
325 
326  if(const auto error = data.optional_child("error")) {
327  throw wesnothd_rejected_client_error((*error)["message"].str());
328  }
329 
330  // Continue if we did not get a direction to login
331  if(!data.has_child("mustlogin")) {
332  continue;
333  }
334 
335  // Enter login loop
336  while(true) {
337  std::string login = preferences::login();
338 
339  config response;
340  config& sp = response.add_child("login");
341  sp["username"] = login;
342 
343  conn->send_data(response);
344  conn->wait_and_receive_data(data);
345 
347 
348  if(const auto warning = data.optional_child("warning")) {
349  std::string warning_msg;
350 
351  if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) {
352  warning_msg = VGETTEXT("The nickname ‘$nick’ is inactive. "
353  "You cannot claim ownership of this nickname until you "
354  "activate your account via email or ask an "
355  "administrator to do it for you.", {{"nick", login}});
356  } else {
357  warning_msg = (*warning)["message"].str();
358  }
359 
360  warning_msg += "\n\n";
361  warning_msg += _("Do you want to continue?");
362 
364  return nullptr;
365  } else {
366  continue;
367  }
368  }
369 
370  auto error = data.optional_child("error");
371 
372  // ... and get us out of here if the server did not complain
373  if(!error) break;
374 
375  do {
376  std::string password = preferences::password(host, login);
377 
378  const bool fall_through = (*error)["force_confirmation"].to_bool()
380  : false;
381 
382  // If:
383  // * the server asked for a password
384  // * the password isn't empty
385  // * the user didn't press Cancel
386  // * the connection is secure or the client was started with the option to use insecure connections
387  // send the password to the server
388  // otherwise go directly to the username/password dialog
389  if(!(*error)["password_request"].empty() && !password.empty() && !fall_through && (conn->using_tls() || game_config::allow_insecure)) {
390  // the possible cases here are that either:
391  // 1) TLS encryption is enabled, thus sending the plaintext password is still secure
392  // 2) TLS encryption is not enabled, in which case the server should not be requesting a password in the first place
393  // 3) This is being used for local testing/development, so using an insecure connection is enabled manually
394 
395  sp["password"] = password;
396 
397  // Once again send our request...
398  conn->send_data(response);
399  conn->wait_and_receive_data(data);
400 
402 
403  error = data.optional_child("error");
404 
405  // ... and get us out of here if the server is happy now
406  if(!error) break;
407  }
408 
409  // Providing a password either was not attempted because we did not
410  // have any or failed:
411  // Now show a dialog that displays the error and allows to
412  // enter a new user name and/or password
413 
414  std::string error_message;
415  utils::string_map i18n_symbols;
416  i18n_symbols["nick"] = login;
417 
418  const auto extra_data = error->optional_child("data");
419  if(extra_data) {
420  i18n_symbols["duration"] = utils::format_timespan((*extra_data)["duration"]);
421  }
422 
423  const std::string ec = (*error)["error_code"];
424 
425  if(!(*error)["password_request"].empty() && !conn->using_tls() && !game_config::allow_insecure) {
426  error_message = _("The remote server requested a password while using an insecure connection.");
427  } else if(ec == MP_MUST_LOGIN) {
428  error_message = _("You must login first.");
429  } else if(ec == MP_NAME_TAKEN_ERROR) {
430  error_message = VGETTEXT("The nickname ‘$nick’ is already taken.", i18n_symbols);
431  } else if(ec == MP_INVALID_CHARS_IN_NAME_ERROR) {
432  error_message = VGETTEXT("The nickname ‘$nick’ contains invalid "
433  "characters. Only alpha-numeric characters (one at minimum), underscores and "
434  "hyphens are allowed.", i18n_symbols);
435  } else if(ec == MP_NAME_TOO_LONG_ERROR) {
436  error_message = VGETTEXT("The nickname ‘$nick’ is too long. Nicks must be 20 characters or less.", i18n_symbols);
437  } else if(ec == MP_NAME_RESERVED_ERROR) {
438  error_message = VGETTEXT("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
439  } else if(ec == MP_NAME_UNREGISTERED_ERROR) {
440  error_message = VGETTEXT("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
441  + _(" This server disallows unregistered nicknames.");
442  } else if(ec == MP_NAME_AUTH_BAN_USER_ERROR) {
443  if(extra_data) {
444  error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
445  } else {
446  error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
447  }
448  } else if(ec == MP_NAME_AUTH_BAN_IP_ERROR) {
449  if(extra_data) {
450  error_message = VGETTEXT("Your IP address is banned on this server’s forums for $duration|.", i18n_symbols);
451  } else {
452  error_message = _("Your IP address is banned on this server’s forums.");
453  }
454  } else if(ec == MP_NAME_AUTH_BAN_EMAIL_ERROR) {
455  if(extra_data) {
456  error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums for $duration|.", i18n_symbols);
457  } else {
458  error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
459  }
460  } else if(ec == MP_PASSWORD_REQUEST) {
461  error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols);
462  } else if(ec == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) {
463  error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols)
464  + "\n\n" + _("WARNING: There is already a client using this nickname, "
465  "logging in will cause that client to be kicked!");
466  } else if(ec == MP_INCORRECT_PASSWORD_ERROR) {
467  error_message = _("The password you provided was incorrect.");
468  } else if(ec == MP_TOO_MANY_ATTEMPTS_ERROR) {
469  error_message = _("You have made too many login attempts.");
470  } else if(ec == MP_HASHING_PASSWORD_FAILED) {
471  error_message = _("Password hashing failed.");
472  } else {
473  error_message = (*error)["message"].str();
474  }
475 
476  gui2::dialogs::mp_login dlg(host, error_message, !((*error)["password_request"].empty()));
477 
478  // Need to show the dialog from the main thread or it won't appear.
479  events::call_in_main_thread([&dlg]() { dlg.show(); });
480 
481  switch(dlg.get_retval()) {
482  // Log in with password
483  case gui2::retval::OK:
484  break;
485  // Cancel
486  default:
487  return nullptr;
488  }
489 
490  // If we have got a new username we have to start all over again
491  } while(login == preferences::login());
492 
493  // Somewhat hacky...
494  // If we broke out of the do-while loop above error is still going to be nullopt
495  if(!error) break;
496  } // end login loop
497 
498  if(const auto join_lobby = data.optional_child("join_lobby")) {
499  // Note any session data sent with the response. This should be the only place session_info is set.
500  session_info = { join_lobby.value() };
501 
502  // All done!
503  break;
504  }
505  }
506 
507  return conn;
508 }
509 
510 void mp_manager::run_lobby_loop()
511 {
512  // This should only work if we have a connection. If we're in a local mode,
513  // enter_create_mode should be accessed directly.
514  if(!connection) {
515  return;
516  }
517 
518  // A return of false means a config reload was requested, so do that and then loop.
519  while(!enter_lobby_mode()) {
522  gcm->load_game_config_for_create(true); // NOTE: Using reload_changed_game_config only doesn't seem to work here
523 
524  lobby_info.refresh_installed_addons_cache();
525 
526  connection->send_data(config("refresh_lobby"));
527  }
528 }
529 
530 bool mp_manager::enter_lobby_mode()
531 {
532  DBG_MP << "entering lobby mode" << std::endl;
533 
534  // Connection should never be null in the lobby.
535  assert(connection);
536 
537  // We use a loop here to allow returning to the lobby if you, say, cancel game creation.
538  while(true) {
539  if(const config& cfg = game_config_manager::get()->game_config().child("lobby_music")) {
540  for(const config& i : cfg.child_range("music")) {
542  }
543 
545  } else {
548  }
549 
550  int dlg_retval = 0;
551  int dlg_joined_game_id = 0;
552  {
553  gui2::dialogs::mp_lobby dlg(lobby_info, *connection, dlg_joined_game_id);
554  dlg.show();
555  dlg_retval = dlg.get_retval();
556  }
557 
558  try {
559  switch(dlg_retval) {
561  enter_create_mode();
562  break;
564  [[fallthrough]];
566  enter_wait_mode(dlg_joined_game_id, dlg_retval == gui2::dialogs::mp_lobby::OBSERVE);
567  break;
569  // Let this function's caller reload the config and re-call.
570  return false;
571  default:
572  // Needed to handle the Quit signal and exit the loop
573  return true;
574  }
575  } catch(const config::error& error) {
576  if(!error.message.empty()) {
578  }
579 
580  // Update lobby content
581  connection->send_data(config("refresh_lobby"));
582  }
583  }
584 
585  return true;
586 }
587 
588 void mp_manager::enter_create_mode()
589 {
590  DBG_MP << "entering create mode" << std::endl;
591 
592  if(gui2::dialogs::mp_create_game::execute(state, connection == nullptr)) {
593  enter_staging_mode();
594  } else if(connection) {
595  connection->send_data(config("refresh_lobby"));
596  }
597 }
598 
599 void mp_manager::enter_staging_mode()
600 {
601  DBG_MP << "entering connect mode" << std::endl;
602 
603  std::unique_ptr<mp_game_metadata> metadata;
604 
605  // If we have a connection, set the appropriate info. No connection means we're in local game mode.
606  if(connection) {
607  metadata = std::make_unique<mp_game_metadata>(*connection);
608  metadata->connected_players.insert(preferences::login());
609  metadata->is_host = true;
610  }
611 
612  bool dlg_ok = false;
613  {
614  ng::connect_engine connect_engine(state, true, metadata.get());
615  dlg_ok = gui2::dialogs::mp_staging::execute(connect_engine, connection.get());
616  } // end connect_engine
617 
618  if(dlg_ok) {
620  controller.set_mp_info(metadata.get());
621  controller.play_game();
622  }
623 
624  if(connection) {
625  connection->send_data(config("leave_game"));
626  }
627 }
628 
629 void mp_manager::enter_wait_mode(int game_id, bool observe)
630 {
631  DBG_MP << "entering wait mode" << std::endl;
632 
633  // The connection should never be null here, since one should never reach this screen in local game mode.
634  assert(connection);
635 
637 
638  mp_game_metadata metadata(*connection);
639  metadata.is_host = false;
640 
641  if(const mp::game_info* gi = lobby_info.get_game_by_id(game_id)) {
642  metadata.current_turn = gi->current_turn;
643  }
644 
646  metadata.skip_replay = true;
648  }
649 
650  bool dlg_ok = false;
651  {
652  gui2::dialogs::mp_join_game dlg(state, *connection, true, observe);
653 
654  if(!dlg.fetch_game_config()) {
655  connection->send_data(config("leave_game"));
656  return;
657  }
658 
659  dlg_ok = dlg.show();
660  }
661 
662  if(dlg_ok) {
664  controller.set_mp_info(&metadata);
665  controller.play_game();
666  }
667 
668  connection->send_data(config("leave_game"));
669 }
670 
671 bool mp_manager::post_scenario_staging(ng::connect_engine& engine)
672 {
673  return gui2::dialogs::mp_staging::execute(engine, connection.get());
674 }
675 
676 bool mp_manager::post_scenario_wait(bool observe)
677 {
678  gui2::dialogs::mp_join_game dlg(state, *connection, false, observe);
679 
680  if(!dlg.fetch_game_config()) {
681  connection->send_data(config("leave_game"));
682  return false;
683  }
684 
685  if(dlg.started()) {
686  return true;
687  }
688 
689  return dlg.show();
690 }
691 
692 } // end anon namespace
693 
694 /** Pubic entry points for the MP workflow */
695 
696 void start_client(const std::string& host)
697 {
698  DBG_MP << "starting client" << std::endl;
699  mp_manager(host).run_lobby_loop();
700 }
701 
703 {
704  DBG_MP << "starting local game" << std::endl;
705 
707 
708  mp_manager(std::nullopt).enter_create_mode();
709 }
710 
712 {
713  DBG_MP << "starting local MP game from commandline" << std::endl;
714 
716 
717  // The setup is done equivalently to lobby MP games using as much of existing
718  // code as possible. This means that some things are set up that are not
719  // needed in commandline mode, but they are required by the functions called.
721 
722  DBG_MP << "entering create mode" << std::endl;
723 
724  // Set the default parameters
725  saved_game state;
726  state.classification().type = campaign_type::type::multiplayer;
727 
728  mp_game_settings& parameters = state.mp_settings();
729 
730  // Hardcoded default values
731  state.classification().era_id = "era_default";
732  parameters.name = "multiplayer_The_Freelands";
733 
734  // Default values for which at getter function exists
735  parameters.num_turns = settings::get_turns("");
736  parameters.village_gold = settings::get_village_gold("");
738  parameters.xp_modifier = settings::get_xp_modifier("");
739 
740  // Do not use map settings if --ignore-map-settings commandline option is set
741  if(cmdline_opts.multiplayer_ignore_map_settings) {
742  DBG_MP << "ignoring map settings" << std::endl;
743  parameters.use_map_settings = false;
744  } else {
745  parameters.use_map_settings = true;
746  }
747 
748  // None of the other parameters need to be set, as their creation values above are good enough for CL mode.
749  // In particular, we do not want to use the preferences values.
750 
751  state.classification().type = campaign_type::type::multiplayer;
752 
753  // [era] define.
754  if(cmdline_opts.multiplayer_era) {
755  state.classification().era_id = *cmdline_opts.multiplayer_era;
756  }
757 
758  if(const config& cfg_era = game_config.find_child("era", "id", state.classification().era_id)) {
759  state.classification().era_define = cfg_era["define"].str();
760  } else {
761  std::cerr << "Could not find era '" << state.classification().era_id << "'\n";
762  return;
763  }
764 
765  // [multiplayer] define.
766  if(cmdline_opts.multiplayer_scenario) {
767  parameters.name = *cmdline_opts.multiplayer_scenario;
768  }
769 
770  if(const config& cfg_multiplayer = game_config.find_child("multiplayer", "id", parameters.name)) {
771  state.classification().scenario_define = cfg_multiplayer["define"].str();
772  } else {
773  std::cerr << "Could not find [multiplayer] '" << parameters.name << "'\n";
774  return;
775  }
776 
778  config {"next_scenario", parameters.name}
779  );
780 
782 
783  state.expand_random_scenario();
784  state.expand_mp_events();
785  state.expand_mp_options();
786 
787  // Should number of turns be determined from scenario data?
788  if(parameters.use_map_settings && state.get_starting_point()["turns"]) {
789  DBG_MP << "setting turns from scenario data: " << state.get_starting_point()["turns"] << std::endl;
790  parameters.num_turns = state.get_starting_point()["turns"];
791  }
792 
793  DBG_MP << "entering connect mode" << std::endl;
794 
796 
797  {
798  ng::connect_engine connect_engine(state, true, nullptr);
799 
800  // Update the parameters to reflect game start conditions
801  connect_engine.start_game_commandline(cmdline_opts, game_config);
802  }
803 
804  if(resources::recorder && cmdline_opts.multiplayer_label) {
805  std::string label = *cmdline_opts.multiplayer_label;
806  resources::recorder->add_log_data("ai_log","ai_label",label);
807  }
808 
809  unsigned int repeat = (cmdline_opts.multiplayer_repeat) ? *cmdline_opts.multiplayer_repeat : 1;
810  for(unsigned int i = 0; i < repeat; i++){
811  saved_game state_copy(state);
812  campaign_controller controller(state_copy);
813  controller.play_game();
814  }
815 }
816 
818 {
819  return manager && manager->post_scenario_staging(engine);
820 }
821 
822 bool goto_mp_wait(bool observe)
823 {
824  return manager && manager->post_scenario_wait(observe);
825 }
826 
828 {
829  return manager && manager->get_session_info().is_moderator;
830 }
831 
832 std::string get_profile_link(int user_id)
833 {
834  if(manager) {
835  const std::string& prefix = manager->get_session_info().profile_url_prefix;
836 
837  if(!prefix.empty()) {
838  return prefix + std::to_string(user_id);
839  }
840  }
841 
842  return "";
843 }
844 
845 void send_to_server(const config& data)
846 {
847  if(manager && manager->connection) {
848  manager->connection->send_data(data);
849  }
850 }
851 
853 {
854  if(manager /*&& manager->connection*/) {
855  remove_handler = manager->add_network_handler(func);
856  }
857 }
858 
860 {
861  if(remove_handler) {
862  remove_handler();
863  }
864 }
865 
867 {
868  return manager ? &manager->lobby_info : nullptr;
869 }
870 
871 } // 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:153
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:76
static void progress(loading_stage stage=loading_stage::none)
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.
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:869
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:591
config & get_starting_point()
Definition: saved_game.cpp:583
Shows a yes and no button.
Definition: message.hpp:80
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:838
General settings and defaults for scenarios.
std::set< std::string > connected_players
players and observers
bool allow_insecure
Definition: game_config.cpp:97
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:581
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:705
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:206
bool goto_mp_staging(ng::connect_engine &engine)
Opens the MP Staging screen and sets the game state according to the changes made.
#define e
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:823
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.