The Battle for Wesnoth  1.15.0-dev
multiplayer.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "addon/manager.hpp" // for installed_addons
18 #include "events.hpp"
19 #include "formula/string_utils.hpp"
20 #include "game_config_manager.hpp"
24 #include "preferences/game.hpp"
25 #include "gettext.hpp"
27 #include "gui/dialogs/message.hpp"
33 #include "hash.hpp"
34 #include "log.hpp"
36 #include "map_settings.hpp"
37 #include "sound.hpp"
38 #include "statistics.hpp"
39 #include "wesnothd_connection.hpp"
40 #include "resources.hpp"
41 #include "replay.hpp"
42 
43 #include "utils/functional.hpp"
44 
45 static lg::log_domain log_mp("mp/main");
46 #define DBG_MP LOG_STREAM(debug, log_mp)
47 #define ERR_MP LOG_STREAM(err, log_mp)
48 
49 namespace
50 {
51 /** Opens a new server connection and prompts the client for login credentials, if necessary. */
52 std::pair<wesnothd_connection_ptr, config> open_connection(std::string host)
53 {
54  DBG_MP << "opening connection" << std::endl;
55 
56  wesnothd_connection_ptr sock(nullptr);
57  if(host.empty()) {
58  return std::make_pair(std::move(sock), config());
59  }
60 
61  const int colon_index = host.find_first_of(":");
62  unsigned int port;
63 
64  if(colon_index == -1) {
65  port = 15000;
66  } else {
67  port = lexical_cast_default<unsigned int>(host.substr(colon_index + 1), 15000);
68  host = host.substr(0, colon_index);
69  }
70 
71  // shown_hosts is used to prevent the client being locked in a redirect loop.
72  using hostpair = std::pair<std::string, int>;
73 
74  std::set<hostpair> shown_hosts;
75  shown_hosts.emplace(host, port);
76 
77  // Initializes the connection to the server.
78  sock = std::make_unique<wesnothd_connection>(host, std::to_string(port));
79  if(!sock) {
80  return std::make_pair(std::move(sock), config());
81  }
82 
83  // Start stage
85 
86  // First, spin until we get a handshake from the server.
87  while(!sock->handshake_finished()) {
88  sock->poll();
89  SDL_Delay(1);
90  }
91 
93 
94  config data;
95  config initial_lobby_config;
96 
97  bool received_join_lobby = false;
98  bool received_gamelist = false;
99 
100  // Then, log in and wait for the lobby/game join prompt.
101  do {
102  if(!sock) {
103  return std::make_pair(std::move(sock), config());
104  }
105 
106  data.clear();
107  sock->wait_and_receive_data(data);
108 
109  if(data.has_child("reject") || data.has_attribute("version")) {
110  std::string version;
111 
112  if(const config& reject = data.child("reject")) {
113  version = reject["accepted_versions"].str();
114  } else {
115  // Backwards-compatibility "version" attribute
116  version = data["version"].str();
117  }
118 
119  utils::string_map i18n_symbols;
120  i18n_symbols["required_version"] = version;
121  i18n_symbols["your_version"] = game_config::wesnoth_version.str();
122 
123  const std::string errorstring = VGETTEXT("The server accepts versions '$required_version', but you are using version '$your_version'", i18n_symbols);
124  throw wesnothd_error(errorstring);
125  }
126 
127  // Check for "redirect" messages
128  if(const config& redirect = data.child("redirect")) {
129  host = redirect["host"].str();
130  port = redirect["port"].to_int(15000);
131 
132  if(shown_hosts.find(hostpair(host, port)) != shown_hosts.end()) {
133  throw wesnothd_error(_("Server-side redirect loop"));
134  }
135 
136  shown_hosts.emplace(host, port);
137 
139 
140  // Open a new connection with the new host and port.
141  sock.reset();
142  sock = std::make_unique<wesnothd_connection>(host, std::to_string(port));
143 
144  // Wait for new handshake.
145  while(!sock->handshake_finished()) {
146  sock->poll();
147  SDL_Delay(1);
148  }
149 
151  continue;
152  }
153 
154  if(data.has_child("version")) {
155  config cfg;
156  config res;
157  cfg["version"] = game_config::wesnoth_version.str();
158  res.add_child("version", std::move(cfg));
159  sock->send_data(res);
160  }
161 
162  // Check for gamelist. This *must* be done before the mustlogin check
163  // or else this loop will run ad-infinitum.
164  if(data.has_child("gamelist")) {
165  received_gamelist = true;
166 
167  // data should only contain the game and user lists at this point, so just swap it.
168  std::swap(initial_lobby_config, data);
169  }
170 
171  if(data.has_child("error")) {
172  std::string error_message;
173  config* error = &data.child("error");
174  error_message = (*error)["message"].str();
175  throw wesnothd_rejected_client_error(error_message);
176  }
177 
178  // The only message we should get here is the admin authentication message.
179  // It's sent after [join_lobby] and before the initial gamelist.
180  if(const config& message = data.child("message")) {
181  preferences::parse_admin_authentication(message["sender"], message["message"]);
182  }
183 
184  // Continue if we did not get a direction to login
185  if(!data.has_child("mustlogin")) {
186  continue;
187  }
188 
189  // Enter login loop
190  for(;;) {
191  std::string login = preferences::login();
192 
193  config response ;
194  config& sp = response.add_child("login") ;
195  sp["username"] = login ;
196 
197  sock->send_data(response);
198  sock->wait_and_receive_data(data);
199 
201 
202  config* warning = &data.child("warning");
203 
204  if(*warning) {
205  std::string warning_msg;
206 
207  if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) {
208  warning_msg = VGETTEXT("The nickname ‘$nick’ is inactive. "
209  "You cannot claim ownership of this nickname until you "
210  "activate your account via email or ask an "
211  "administrator to do it for you.", {{"nick", login}});
212  } else {
213  warning_msg = (*warning)["message"].str();
214  }
215 
216  warning_msg += "\n\n";
217  warning_msg += _("Do you want to continue?");
218 
220  return std::make_pair(wesnothd_connection_ptr(), config());
221  } else {
222  continue;
223  }
224  }
225 
226  config* error = &data.child("error");
227 
228  // ... and get us out of here if the server did not complain
229  if(!*error) break;
230 
231  do {
232  std::string password = preferences::password(host, login);
233 
234  bool fall_through = (*error)["force_confirmation"].to_bool() ?
236  false;
237 
238  const bool is_pw_request = !((*error)["password_request"].empty()) && !(password.empty());
239 
240  // If the server asks for a password, provide one if we can
241  // or request a password reminder.
242  // Otherwise or if the user pressed 'cancel' in the confirmation dialog
243  // above go directly to the username/password dialog
244  if(is_pw_request && !fall_through) {
245  if((*error)["phpbb_encryption"].to_bool()) {
246  // Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form.
247  // I will do closer investigations on this, for now let's just hope these are all of them.
248 
249  // Note: we must obviously replace '&' first, I wasted some time before I figured that out... :)
250  for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos)
251  password.replace(pos, 1, "&amp;");
252  for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos)
253  password.replace(pos, 1, "&quot;");
254  for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos)
255  password.replace(pos, 1, "&lt;");
256  for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos)
257  password.replace(pos, 1, "&gt;");
258 
259  const std::string salt = (*error)["salt"];
260 
261  if(salt.length() < 12) {
262  throw wesnothd_error(_("Bad data received from server"));
263  }
264 
265  if(utils::md5::is_valid_prefix(salt)) {
266  sp["password"] = utils::md5(utils::md5(password, utils::md5::get_salt(salt),
267  utils::md5::get_iteration_count(salt)).base64_digest(), salt.substr(12, 8)).base64_digest();
268  } else if(utils::bcrypt::is_valid_prefix(salt)) {
269  try {
270  auto bcrypt_salt = utils::bcrypt::from_salted_salt(salt);
271  auto hash = utils::bcrypt::hash_pw(password, bcrypt_salt);
272  std::string outer_salt = salt.substr(bcrypt_salt.iteration_count_delim_pos + 23);
273  if(outer_salt.size() != 32)
274  throw utils::hash_error("salt wrong size");
275  sp["password"] = utils::md5(hash.base64_digest(), outer_salt).base64_digest();
276  } catch(const utils::hash_error& err) {
277  ERR_MP << "bcrypt hash failed: " << err.what() << std::endl;
278  throw wesnothd_error(_("Bad data received from server"));
279  }
280  } else {
281  throw wesnothd_error(_("Bad data received from server"));
282  }
283  } else {
284  sp["password"] = password;
285  }
286 
287  // Once again send our request...
288  sock->send_data(response);
289  sock->wait_and_receive_data(data);
290 
292 
293  error = &data.child("error");
294 
295  // ... and get us out of here if the server is happy now
296  if(!*error) break;
297  }
298 
299  // Providing a password either was not attempted because we did not
300  // have any or failed:
301  // Now show a dialog that displays the error and allows to
302  // enter a new user name and/or password
303 
304  std::string error_message;
305  utils::string_map i18n_symbols;
306  i18n_symbols["nick"] = login;
307 
308  if((*error)["error_code"] == MP_MUST_LOGIN) {
309  error_message = _("You must login first.");
310  } else if((*error)["error_code"] == MP_NAME_TAKEN_ERROR) {
311  error_message = VGETTEXT("The nickname ‘$nick’ is already taken.", i18n_symbols);
312  } else if((*error)["error_code"] == MP_INVALID_CHARS_IN_NAME_ERROR) {
313  error_message = VGETTEXT("The nickname ‘$nick’ contains invalid "
314  "characters. Only alpha-numeric characters (one at minimum), underscores and "
315  "hyphens are allowed.", i18n_symbols);
316  } else if((*error)["error_code"] == MP_NAME_TOO_LONG_ERROR) {
317  error_message = VGETTEXT("The nickname ‘$nick’ is too long. Nicks must "
318  "be 20 characters or less.", i18n_symbols);
319  } else if((*error)["error_code"] == MP_NAME_RESERVED_ERROR) {
320  error_message = VGETTEXT("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
321  } else if((*error)["error_code"] == MP_NAME_UNREGISTERED_ERROR) {
322  error_message = VGETTEXT("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
323  + _(" This server disallows unregistered nicknames.");
324  } else if((*error)["error_code"] == MP_NAME_AUTH_BAN_USER_ERROR) {
325  error_message = VGETTEXT("The nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
326  } else if((*error)["error_code"] == MP_NAME_AUTH_BAN_IP_ERROR) {
327  error_message = _("Your IP address is banned on this server’s forums.");
328  } else if((*error)["error_code"] == MP_NAME_AUTH_BAN_EMAIL_ERROR) {
329  error_message = VGETTEXT("The email address for the nickname ‘$nick’ is banned on this server’s forums.", i18n_symbols);
330  } else if((*error)["error_code"] == MP_PASSWORD_REQUEST) {
331  error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols);
332  } else if((*error)["error_code"] == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) {
333  error_message = VGETTEXT("The nickname ‘$nick’ is registered on this server.", i18n_symbols)
334  + "\n\n" + _("WARNING: There is already a client using this nickname, "
335  "logging in will cause that client to be kicked!");
336  } else if((*error)["error_code"] == MP_NO_SEED_ERROR) {
337  error_message = _("Error in the login procedure (the server had no "
338  "seed for your connection).");
339  } else if((*error)["error_code"] == MP_INCORRECT_PASSWORD_ERROR) {
340  error_message = _("The password you provided was incorrect.");
341  } else if((*error)["error_code"] == MP_TOO_MANY_ATTEMPTS_ERROR) {
342  error_message = _("You have made too many login attempts.");
343  } else {
344  error_message = (*error)["message"].str();
345  }
346 
347  gui2::dialogs::mp_login dlg(host, error_message, !((*error)["password_request"].empty()));
348 
349  // Need to show the dialog from the main thread or it won't appear.
350  events::call_in_main_thread([&dlg]() { dlg.show(); });
351 
352  switch(dlg.get_retval()) {
353  //Log in with password
354  case gui2::retval::OK:
355  break;
356  // Cancel
357  default:
358  return std::make_pair(wesnothd_connection_ptr(), config());
359  }
360 
361  // If we have got a new username we have to start all over again
362  } while(login == preferences::login());
363 
364  // Somewhat hacky...
365  // If we broke out of the do-while loop above error
366  // is still going to be nullptr
367  if(!*error) break;
368  } // end login loop
369 
370  if(data.has_child("join_lobby")) {
371  received_join_lobby = true;
372 
374  }
375  } while(!received_join_lobby || !received_gamelist);
376 
377  return std::make_pair(std::move(sock), std::move(initial_lobby_config));
378 }
379 
380 /** Helper struct to manage the MP workflow arguments. */
381 struct mp_workflow_helper
382 {
383  mp_workflow_helper(const config& gc, saved_game& state, wesnothd_connection* connection, mp::lobby_info* li)
384  : game_config(gc)
385  , state(state)
386  , connection(connection)
387  , lobby_info(li)
388  {}
389 
390  const config& game_config;
391 
392  saved_game& state;
393 
394  wesnothd_connection* connection;
395 
396  mp::lobby_info* lobby_info;
397 };
398 
399 using mp_workflow_helper_ptr = std::shared_ptr<mp_workflow_helper>;
400 
401 /**
402  * The main components of the MP workflow. It consists of four screens:
403  *
404  * Host POV: LOBBY <---> CREATE GAME ---> STAGING ------------------> GAME BEGINS
405  * Player POV: LOBBY <---------------------------------> JOIN GAME ---> GAME BEGINS
406  *
407  * NOTE: since these functions are static, they appear here in the opposite order they'd be accessed.
408  */
409 void enter_wait_mode(mp_workflow_helper_ptr helper, int game_id, bool observe)
410 {
411  DBG_MP << "entering wait mode" << std::endl;
412 
413  // The connection should never be null here, since one should never reach this screen in local game mode.
414  assert(helper->connection);
415 
417 
418  auto campaign_info = std::make_unique<mp_campaign_info>(*helper->connection);
419  campaign_info->is_host = false;
420 
421  if(helper->lobby_info->get_game_by_id(game_id)) {
422  campaign_info->current_turn = helper->lobby_info->get_game_by_id(game_id)->current_turn;
423  }
424 
426  campaign_info->skip_replay = true;
427  campaign_info->skip_replay_blindfolded = preferences::blindfold_replay();
428  }
429 
430  bool dlg_ok = false;
431  {
432  gui2::dialogs::mp_join_game dlg(helper->state, *helper->lobby_info, *helper->connection, true, observe);
433 
434  if(!dlg.fetch_game_config()) {
435  helper->connection->send_data(config("leave_game"));
436  return;
437  }
438 
439  dlg.show();
440  dlg_ok = dlg.get_retval() == gui2::retval::OK;
441  }
442 
443  if(dlg_ok) {
445  controller.set_mp_info(campaign_info.get());
446  controller.play_game();
447  }
448 
449  helper->connection->send_data(config("leave_game"));
450 }
451 
452 void enter_staging_mode(mp_workflow_helper_ptr helper)
453 {
454  DBG_MP << "entering connect mode" << std::endl;
455 
456  std::unique_ptr<mp_campaign_info> campaign_info;
457 
458  // If we have a connection, set the appropriate info. No connection means we're in local game mode.
459  if(helper->connection) {
460  campaign_info.reset(new mp_campaign_info(*helper->connection));
461  campaign_info->connected_players.insert(preferences::login());
462  campaign_info->is_host = true;
463  }
464 
465  bool dlg_ok = false;
466  {
467  ng::connect_engine_ptr connect_engine(new ng::connect_engine(helper->state, true, campaign_info.get()));
468 
469  gui2::dialogs::mp_staging dlg(*connect_engine, *helper->lobby_info, helper->connection);
470  dlg.show();
471  dlg_ok = dlg.get_retval() == gui2::retval::OK;
472  } // end connect_engine_ptr, dlg scope
473 
474  if(dlg_ok) {
476  controller.set_mp_info(campaign_info.get());
477  controller.play_game();
478  }
479 
480  if(helper->connection) {
481  helper->connection->send_data(config("leave_game"));
482  }
483 }
484 
485 void enter_create_mode(mp_workflow_helper_ptr helper)
486 {
487  DBG_MP << "entering create mode" << std::endl;
488 
489  bool dlg_ok = false;
490  {
491  bool local_mode = helper->connection == nullptr;
492  mp::user_info* host_info = helper->lobby_info->get_user(preferences::login());
493 
494  gui2::dialogs::mp_create_game dlg(helper->game_config, helper->state, local_mode, host_info);
495  dlg.show();
496 
497  // The Create Game dialog also has a LOAD_GAME retval besides OK.
498  // Do a did-not-cancel check here to catch that
499  dlg_ok = dlg.get_retval() != gui2::retval::CANCEL;
500  }
501 
502  if(dlg_ok) {
503  enter_staging_mode(helper);
504  } else if(helper->connection) {
505  helper->connection->send_data(config("refresh_lobby"));
506  }
507 }
508 
509 bool enter_lobby_mode(mp_workflow_helper_ptr helper, const std::vector<std::string>& installed_addons, const config& initial_lobby_config)
510 {
511  DBG_MP << "entering lobby mode" << std::endl;
512 
513  // Connection should never be null in the lobby.
514  assert(helper->connection);
515 
516  // We use a loop here to allow returning to the lobby if you, say, cancel game creation.
517  while(true) {
518  if(const config& cfg = helper->game_config.child("lobby_music")) {
519  for(const config& i : cfg.child_range("music")) {
521  }
522 
524  } else {
527  }
528 
529  mp::lobby_info li(installed_addons);
530  helper->lobby_info = &li;
531 
532  if(!initial_lobby_config.empty()) {
533  li.process_gamelist(initial_lobby_config);
534  }
535 
536  int dlg_retval = 0;
537  int dlg_joined_game_id = 0;
538  {
539 
540  gui2::dialogs::mp_lobby dlg(helper->game_config, li, *helper->connection);
541  dlg.show();
542  dlg_retval = dlg.get_retval();
543  dlg_joined_game_id = dlg.get_joined_game_id();
544  }
545 
546  switch(dlg_retval) {
548  try {
549  enter_create_mode(helper);
550  } catch(const config::error& error) {
551  if(!error.message.empty()) {
553  }
554 
555  // Update lobby content
556  helper->connection->send_data(config("refresh_lobby"));
557  }
558 
559  break;
562  try {
563  enter_wait_mode(helper,
564  dlg_joined_game_id,
566  );
567  } catch(const config::error& error) {
568  if(!error.message.empty()) {
570  }
571 
572  // Update lobby content
573  helper->connection->send_data(config("refresh_lobby"));
574  }
575 
576  break;
578  // Let this function's caller reload the config and re-call.
579  return false;
580  default:
581  // Needed to handle the Quit signal and exit the loop
582  return true;
583  }
584  }
585 
586  return true;
587 }
588 
589 } // end anon namespace
590 
591 /** Pubic entry points for the MP workflow */
592 namespace mp
593 {
594 void start_client(const config& game_config, saved_game& state, const std::string& host)
595 {
596  const config* game_config_ptr = &game_config;
597 
598  // This function does not refer to an addon database, it calls filesystem functions.
599  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
600  // even if the user changes the contents of the addon directory in the meantime.
601  std::vector<std::string> installed_addons = ::installed_addons();
602 
603  DBG_MP << "starting client" << std::endl;
604 
606 
607  wesnothd_connection_ptr connection;
608  config lobby_config;
609 
611  std::tie(connection, lobby_config) = open_connection(host);
612  });
613 
614  if(!connection) {
615  return;
616  }
617 
618  mp_workflow_helper_ptr workflow_helper;
619  bool re_enter = false;
620 
621  do {
622  workflow_helper.reset(new mp_workflow_helper(*game_config_ptr, state, connection.get(), nullptr));
623 
624  // A return of false means a config reload was requested, so do that and then loop.
625  re_enter = !enter_lobby_mode(workflow_helper, installed_addons, lobby_config);
626 
627  if(re_enter) {
630  gcm->load_game_config_for_game(state.classification()); // NOTE: Using reload_changed_game_config only doesn't seem to work here
631 
632  game_config_ptr = &gcm->game_config();
633 
634  installed_addons = ::installed_addons(); // Refresh the installed add-on list for this session.
635 
636  connection->send_data(config("refresh_lobby"));
637  }
638  } while(re_enter);
639 }
640 
642 {
643  lobby_info li({});
644 
645  return gui2::dialogs::mp_staging::execute(engine, li, connection);
646 }
647 
648 bool goto_mp_wait(saved_game& state, wesnothd_connection* connection, bool observe)
649 {
650  lobby_info li({});
651 
652  gui2::dialogs::mp_join_game dlg(state, li, *connection, false, observe);
653 
654  if(!dlg.fetch_game_config()) {
655  connection->send_data(config("leave_game"));
656  return false;
657  }
658 
659  if(dlg.started()) {
660  return true;
661  }
662 
663  return dlg.show();
664 }
665 
667 {
668  DBG_MP << "starting local game" << std::endl;
669 
671 
672  // TODO: should lobby_info take a nullptr in this case, or should we pass the installed_addons data here too?
673  lobby_info li({});
674  mp_workflow_helper_ptr workflow_helper = std::make_shared<mp_workflow_helper>(game_config, state, nullptr, &li);
675 
676  enter_create_mode(workflow_helper);
677 }
678 
680 {
681  DBG_MP << "starting local MP game from commandline" << std::endl;
682 
683  // The setup is done equivalently to lobby MP games using as much of existing
684  // code as possible. This means that some things are set up that are not
685  // needed in commandline mode, but they are required by the functions called.
687 
688  DBG_MP << "entering create mode" << std::endl;
689 
690  // Set the default parameters
691  state.clear(); // This creates these parameters with default values defined in mp_game_settings.cpp
692  mp_game_settings& parameters = state.mp_settings();
693 
694  // Hardcoded default values
695  parameters.mp_era = "era_default";
696  parameters.name = "multiplayer_The_Freelands";
697 
698  // Default values for which at getter function exists
699  parameters.num_turns = settings::get_turns("");
700  parameters.village_gold = settings::get_village_gold("");
702  parameters.xp_modifier = settings::get_xp_modifier("");
703 
704  // Do not use map settings if --ignore-map-settings commandline option is set
705  if(cmdline_opts.multiplayer_ignore_map_settings) {
706  DBG_MP << "ignoring map settings" << std::endl;
707  parameters.use_map_settings = false;
708  } else {
709  parameters.use_map_settings = true;
710  }
711 
712  // None of the other parameters need to be set, as their creation values above are good enough for CL mode.
713  // In particular, we do not want to use the preferences values.
714 
715  state.classification().campaign_type = game_classification::CAMPAIGN_TYPE::MULTIPLAYER;
716 
717  // [era] define.
718  if(cmdline_opts.multiplayer_era) {
719  parameters.mp_era = *cmdline_opts.multiplayer_era;
720  }
721 
722  if(const config& cfg_era = game_config.find_child("era", "id", parameters.mp_era)) {
723  state.classification().era_define = cfg_era["define"].str();
724  } else {
725  std::cerr << "Could not find era '" << parameters.mp_era << "'\n";
726  return;
727  }
728 
729  // [multiplayer] define.
730  if(cmdline_opts.multiplayer_scenario) {
731  parameters.name = *cmdline_opts.multiplayer_scenario;
732  }
733 
734  if(const config& cfg_multiplayer = game_config.find_child("multiplayer", "id", parameters.name)) {
735  state.classification().scenario_define = cfg_multiplayer["define"].str();
736  } else {
737  std::cerr << "Could not find [multiplayer] '" << parameters.name << "'\n";
738  return;
739  }
740 
743  config {"next_scenario", parameters.name}
744  );
745 
746  state.expand_random_scenario();
747  state.expand_mp_events();
748  state.expand_mp_options();
749 
750  // Should number of turns be determined from scenario data?
751  if(parameters.use_map_settings && state.get_starting_point()["turns"]) {
752  DBG_MP << "setting turns from scenario data: " << state.get_starting_point()["turns"] << std::endl;
753  parameters.num_turns = state.get_starting_point()["turns"];
754  }
755 
756  DBG_MP << "entering connect mode" << std::endl;
757 
759 
760  {
761  ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, nullptr));
762 
763  // Update the parameters to reflect game start conditions
764  connect_engine->start_game_commandline(cmdline_opts);
765  }
766 
767  if(resources::recorder && cmdline_opts.multiplayer_label) {
768  std::string label = *cmdline_opts.multiplayer_label;
769  resources::recorder->add_log_data("ai_log","ai_label",label);
770  }
771 
772  unsigned int repeat = (cmdline_opts.multiplayer_repeat) ? *cmdline_opts.multiplayer_repeat : 1;
773  for(unsigned int i = 0; i < repeat; i++){
774  saved_game state_copy(state);
775  campaign_controller controller(state_copy, game_config_manager::get()->terrain_types());
776  controller.play_game();
777  }
778 }
779 
780 } // end namespace mp
void empty_playlist()
Definition: sound.cpp:574
void send_data(const configr_of &request)
An error occurred during when trying to coommunicate with the wesnothd server.
Dialog was closed with the CANCEL button.
Definition: retval.hpp:37
int get_joined_game_id() const
Definition: lobby.hpp:69
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:149
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:423
void start_local_game_commandline(const config &game_config, saved_game &state, const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode.
Error used when the client is rejected by the MP server.
LEVEL_RESULT play_game()
void stop_music()
Definition: sound.cpp:519
std::map< std::string, t_string > string_map
#define MP_NAME_AUTH_BAN_USER_ERROR
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:74
const ter_data_cache & terrain_types() const
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:825
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:310
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
#define MP_TOO_MANY_ATTEMPTS_ERROR
bool has_attribute(config_key_type key) const
Definition: config.cpp:217
std::unique_ptr< wesnothd_connection > wesnothd_connection_ptr
std::set< std::string > connected_players
players and observers
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:416
Shows an ok and cancel button.
Definition: message.hpp:75
boost::optional< std::string > multiplayer_scenario
Non-empty if –scenario was given on the command line. Dependent on –multiplayer.
static void progress(loading_stage stage=loading_stage::none)
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:423
void parse_admin_authentication(const std::string &sender, const std::string &message)
Definition: game.cpp:186
This class represents the collective information the client has about the players and games on the se...
Definition: lobby_info.hpp:30
#define MP_NO_SEED_ERROR
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line. Do not use map settings.
int get_village_gold(const std::string &value, const game_classification *classification)
Gets the village gold.
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:166
void fresh_stats()
Definition: statistics.cpp:640
static int get_iteration_count(const std::string &hash)
Definition: hash.cpp:66
void clear()
Definition: saved_game.cpp:735
#define MP_NAME_TOO_LONG_ERROR
Replay control code.
void clear()
Definition: config.cpp:852
Define the errors the server may send during the login procedure.
boost::optional< std::string > multiplayer_era
Non-empty if –era was given on the command line. Dependent on –multiplayer.
bool show(const unsigned auto_close_time=0)
Shows the window.
void process_gamelist(const config &data)
Process a full game list.
Definition: lobby_info.cpp:108
boost::optional< std::string > multiplayer_label
Non-empty if –label was given on the command line. Dependent on –multiplayer.
void call_in_main_thread(const std::function< void(void)> &f)
Definition: events.cpp:723
#define MP_INCORRECT_PASSWORD_ERROR
int get_village_support(const std::string &value)
Gets the village unit level support.
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:49
static game_config_manager * get()
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
#define MP_MUST_LOGIN
#define MP_NAME_UNREGISTERED_ERROR
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:392
static bcrypt from_salted_salt(const std::string &input)
Definition: hash.cpp:136
bool blindfold_replay()
Definition: game.cpp:602
A class that represents a TCP/IP connection to the wesnothd server.
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:146
config & get_starting_point()
Definition: saved_game.cpp:532
Shows a yes and no button.
Definition: message.hpp:79
replay * recorder
Definition: resources.cpp:28
Used to reset is_authenticated flag after disconnecting.
Definition: game.hpp:51
const char * what() const noexcept
Definition: exceptions.hpp:37
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1375
void set_message_private(bool value)
Definition: game.cpp:853
General settings and defaults for scenarios.
std::string era_define
If there is a define the era uses to customize data.
void start_client(const config &game_config, saved_game &state, const std::string &host)
Starts a multiplayer game in client mode.
std::string login()
#define DBG_MP
Definition: multiplayer.cpp:46
void start_local_game(const config &game_config, saved_game &state)
Starts a multiplayer game in single-user mode.
std::size_t i
Definition: function.cpp:933
logger & err()
Definition: log.cpp:78
Game configuration data as global variables.
Definition: build_info.cpp:46
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)
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:349
#define MP_PASSWORD_REQUEST
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
bool skip_mp_replay()
Definition: game.cpp:592
#define ERR_MP
Definition: multiplayer.cpp:47
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:479
-file mapgen.hpp
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME
#define MP_NAME_TAKEN_ERROR
This class represents the information a client has about another player.
Definition: lobby_data.hpp:104
static void display(std::function< void()> f)
void load_game_config_for_game(const game_classification &classification)
boost::optional< unsigned int > multiplayer_repeat
Repeats specified by –multiplayer-repeat option. Repeats a multiplayer game after it is finished...
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition: sound.cpp:666
game_classification & classification()
Definition: saved_game.hpp:55
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:129
Standard logging facilities (interface).
std::string str() const
Serializes the version number into string form.
std::string message
Definition: exceptions.hpp:31
static std::string get_salt(const std::string &hash)
Definition: hash.cpp:70
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:202
static bcrypt hash_pw(const std::string &password, bcrypt &salt)
Definition: hash.cpp:157
#define MP_NAME_RESERVED_ERROR
const std::unique_ptr< connect_engine > connect_engine_ptr
void commit_music_changes()
Definition: sound.cpp:784
Dialog was closed with the OK button.
Definition: retval.hpp:34
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
#define MP_NAME_AUTH_BAN_EMAIL_ERROR
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
bool enter_create_mode(saved_game &state, jump_to_campaign_info jump_to_campaign)
int get_turns(const std::string &value)
Gets the number of turns.
bool empty() const
Definition: config.cpp:873
const config & game_config() const
bool goto_mp_wait(saved_game &state, wesnothd_connection *connection, bool observe)
Opens mp::wait screen and sets game state according to the changes made.
bool goto_mp_connect(ng::connect_engine &engine, wesnothd_connection *connection)
Opens mp::connect screen and sets game state according to the changes made.
#define MP_NAME_AUTH_BAN_IP_ERROR
#define MP_INVALID_CHARS_IN_NAME_ERROR