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