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