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