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