The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
multiplayer.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2016 by David White <[email protected]>
3  Part of the Battle for Wesnoth Project http://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 #include "multiplayer.hpp"
15 
16 #include "addon/manager.hpp" // for get_installed_addons
17 #include "config_assign.hpp"
18 #include "dialogs.hpp"
19 #include "formula/string_utils.hpp"
20 #include "game_preferences.hpp"
23 #include "gettext.hpp"
25 #include "gui/dialogs/message.hpp"
30 #include "gui/widgets/settings.hpp"
31 #include "gui/widgets/window.hpp"
32 #include "hash.hpp"
34 #include "log.hpp"
36 #include "mp_game_utils.hpp"
38 #include "configure_engine.hpp"
39 #include "multiplayer_connect.hpp"
40 #include "multiplayer_create.hpp"
42 #include "multiplayer_wait.hpp"
43 #include "multiplayer_lobby.hpp"
44 #include "playcampaign.hpp"
45 #include "settings.hpp"
47 #include "sdl/rect.hpp"
48 #include "sound.hpp"
49 #include "statistics.hpp"
50 #include "units/id.hpp"
51 #include "video.hpp"
52 #include "wesnothd_connection.hpp"
53 #include "game_config_manager.hpp"
54 
55 #include "utils/functional.hpp"
56 
57 static lg::log_domain log_network("network");
58 #define LOG_NW LOG_STREAM(info, log_network)
59 
60 static lg::log_domain log_mp("mp/main");
61 #define DBG_MP LOG_STREAM(debug, log_mp)
62 
63 namespace {
64 
65 mp::chat gamechat;
66 config gamelist;
67 
68 }
69 
70 namespace mp {
71 
73 {
74  DBG_MP << "running lobby loop" << std::endl;
75  video.modeChanged();
76  bool first = true;
78  while (ui.get_result() == mp::ui::CONTINUE) {
79  if (video.modeChanged() || first) {
80  SDL_Rect lobby_pos = sdl::create_rect(0
81  , 0
82  , video.getx()
83  , video.gety());
84  ui.set_location(lobby_pos);
85  first = false;
86  }
87  // process network data first so user actions can override the result
88  // or uptodate data can prevent invalid actions
89  // i.e. press cancel while you receive [start_game] or press start game while someone leaves
90  ui.process_network();
91 
92  if (plugins_context * pc = ui.get_plugins_context()) {
93  pc->play_slice();
94  //DBG_MP << "* playing a plugins slice\n";
95  }
96 
97  events::pump();
100 
101  video.flip();
102  CVideo::delay(20);
103  }
105 }
106 
107 }
108 
109 
110 static std::unique_ptr<twesnothd_connection> open_connection(CVideo& video, const std::string& original_host)
111 {
112  DBG_MP << "opening connection" << std::endl;
113  std::string h = original_host;
114 
115  if(h.empty()) {
116  gui2::tmp_connect dlg;
117 
118  dlg.show(video);
119  if(dlg.get_retval() == gui2::twindow::OK) {
121  } else {
122  return 0;
123  }
124  }
125  std::unique_ptr<twesnothd_connection> sock;
126 
127  const int pos = h.find_first_of(":");
128  std::string host;
129  unsigned int port;
130 
131  if(pos == -1) {
132  host = h;
133  port = 15000;
134  } else {
135  host = h.substr(0, pos);
136  port = lexical_cast_default<unsigned int>(h.substr(pos + 1), 15000);
137  }
138 
139  // shown_hosts is used to prevent the client being locked in a redirect
140  // loop.
141  typedef std::pair<std::string, int> hostpair;
142  std::set<hostpair> shown_hosts;
143  shown_hosts.insert(hostpair(host, port));
144 
145  config data;
146  sock = dialogs::network_connect_dialog(video, _("Connecting to Server..."), host, port);
147 
148  do {
149 
150  if (!sock) {
151  return std::move(sock);
152  }
153 
154  data.clear();
155  dialogs::network_receive_dialog(video, "", data, *sock);
156  //mp::check_response(data_res, data);
157 
158  if (data.has_child("reject") || data.has_attribute("version")) {
160  if (const config &reject = data.child("reject")) {
161  version = reject["accepted_versions"].str();
162  } else {
163  // Backwards-compatibility "version" attribute
164  version = data["version"].str();
165  }
166 
167  utils::string_map i18n_symbols;
168  i18n_symbols["version1"] = version;
169  i18n_symbols["version2"] = game_config::version;
170  const std::string errorstring = vgettext("The server accepts versions '$version1' while you are using version '$version2'", i18n_symbols);
171  throw wesnothd_error(errorstring);
172  }
173 
174  // Check for "redirect" messages
175  if (const config &redirect = data.child("redirect"))
176  {
177  host = redirect["host"].str();
178  port =redirect["port"].to_int(15000);
179 
180  if(shown_hosts.find(hostpair(host,port)) != shown_hosts.end()) {
181  throw wesnothd_error(_("Server-side redirect loop"));
182  }
183  shown_hosts.insert(hostpair(host, port));
184  sock.release();
185  sock = dialogs::network_connect_dialog(video, _("Connecting to Server..."), host, port);
186  continue;
187  }
188 
189  if(data.child("version")) {
190  config cfg;
191  config res;
192  cfg["version"] = game_config::version;
193  res.add_child("version", cfg);
194  sock->send_data(res);
195  }
196 
197  //if we got a direction to login
198  if(data.child("mustlogin")) {
199 
200  for(;;) {
201  std::string password_reminder = "";
202 
204 
205  config response ;
206  config &sp = response.add_child("login") ;
207  sp["username"] = login ;
208 
209  // Login and enable selective pings -- saves server bandwidth
210  // If ping_timeout has a non-zero value, do not enable
211  // selective pings as this will cause clients to falsely
212  // believe the server has died and disconnect.
214  // Pings required so disable selective pings
215  sp["selective_ping"] = false;
216  } else {
217  // Client is bandwidth friendly so allow
218  // server to optimize ping frequency as needed.
219  sp["selective_ping"] = true;
220  }
221  sock->send_data(response);
222  dialogs::network_receive_dialog(video, "login response", data, *sock);
223  config *warning = &data.child("warning");
224 
225  if(*warning) {
226  std::string warning_msg;
227 
228  utils::string_map i18n_symbols;
229  i18n_symbols["nick"] = login;
230 
231  if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) {
232  warning_msg = vgettext("The nickname ‘$nick’ is inactive. "
233  "You cannot claim ownership of this nickname until you "
234  "activate your account via email or ask an "
235  "administrator to do it for you.", i18n_symbols);
236  } else {
237  warning_msg = (*warning)["message"].str();
238  }
239 
240  warning_msg += "\n\n";
241  warning_msg += _("Do you want to continue?");
242 
243  if(gui2::show_message(video, _("Warning"), warning_msg, gui2::tmessage::yes_no_buttons) != gui2::twindow::OK) {
244  return 0;
245  }
246  }
247 
248  config *error = &data.child("error");
249 
250  // ... and get us out of here if the server did not complain
251  if (!*error) break;
252 
253  do {
255 
256  bool fall_through = (*error)["force_confirmation"].to_bool() ?
257  (gui2::show_message(video, _("Confirm"), (*error)["message"], gui2::tmessage::ok_cancel_buttons) == gui2::twindow::CANCEL) :
258  false;
259 
260  const bool is_pw_request = !((*error)["password_request"].empty()) && !(password.empty());
261 
262  // If the server asks for a password, provide one if we can
263  // or request a password reminder.
264  // Otherwise or if the user pressed 'cancel' in the confirmation dialog
265  // above go directly to the username/password dialog
266  if((is_pw_request || !password_reminder.empty()) && !fall_through) {
267  if(is_pw_request) {
268  if ((*error)["phpbb_encryption"].to_bool())
269  {
270 
271  // Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form.
272  // I will do closer investigations on this, for now let's just hope these are all of them.
273 
274  // Note: we must obviously replace '&' first, I wasted some time before I figured that out... :)
275  for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos )
276  password.replace(pos, 1, "&amp;");
277  for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos )
278  password.replace(pos, 1, "&quot;");
279  for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos )
280  password.replace(pos, 1, "&lt;");
281  for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos )
282  password.replace(pos, 1, "&gt;");
283 
284  const std::string salt = (*error)["salt"];
285 
286  if (salt.length() < 12) {
287  throw wesnothd_error(_("Bad data received from server"));
288  }
289 
290  sp["password"] = util::create_hash(util::create_hash(password, util::get_salt(salt),
291  util::get_iteration_count(salt)), salt.substr(12, 8));
292 
293  } else {
294  sp["password"] = password;
295  }
296  }
297 
298  sp["password_reminder"] = password_reminder;
299 
300  // Once again send our request...
301  sock->send_data(response);
302  dialogs::network_receive_dialog(video, "", data, *sock);
303 
304 
305  error = &data.child("error");
306 
307  // ... and get us out of here if the server is happy now
308  if (!*error) break;
309 
310 
311  }
312 
313  password_reminder = "";
314 
315  // Providing a password either was not attempted because we did not
316  // have any or failed:
317  // Now show a dialog that displays the error and allows to
318  // enter a new user name and/or password
319 
320  std::string error_message;
321  utils::string_map i18n_symbols;
322  i18n_symbols["nick"] = login;
323 
324  if((*error)["error_code"] == MP_MUST_LOGIN) {
325  error_message = _("You must login first.");
326  } else if((*error)["error_code"] == MP_NAME_TAKEN_ERROR) {
327  error_message = vgettext("The nickname ‘$nick’ is already taken.", i18n_symbols);
328  } else if((*error)["error_code"] == MP_INVALID_CHARS_IN_NAME_ERROR) {
329  error_message = vgettext("The nickname ‘$nick’ contains invalid "
330  "characters. Only alpha-numeric characters, underscores and "
331  "hyphens are allowed.", i18n_symbols);
332  } else if((*error)["error_code"] == MP_NAME_TOO_LONG_ERROR) {
333  error_message = vgettext("The nickname ‘$nick’ is too long. Nicks must "
334  "be 20 characters or less.", i18n_symbols);
335  } else if((*error)["error_code"] == MP_NAME_RESERVED_ERROR) {
336  error_message = vgettext("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
337  } else if((*error)["error_code"] == MP_NAME_UNREGISTERED_ERROR) {
338  error_message = vgettext("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
339  + _(" This server disallows unregistered nicknames.");
340  } else if((*error)["error_code"] == MP_PASSWORD_REQUEST) {
341  error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols);
342  } else if((*error)["error_code"] == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) {
343  error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols)
344  + "\n\n" + _("WARNING: There is already a client using this nickname, "
345  "logging in will cause that client to be kicked!");
346  } else if((*error)["error_code"] == MP_NO_SEED_ERROR) {
347  error_message = _("Error in the login procedure (the server had no "
348  "seed for your connection).");
349  } else if((*error)["error_code"] == MP_INCORRECT_PASSWORD_ERROR) {
350  error_message = _("The password you provided was incorrect.");
351  } else if((*error)["error_code"] == MP_TOO_MANY_ATTEMPTS_ERROR) {
352  error_message = _("You have made too many login attempts.");
353  } else {
354  error_message = (*error)["message"].str();
355  }
356 
357  gui2::tmp_login dlg(error_message, !((*error)["password_request"].empty()));
358  dlg.show(video);
359 
360  switch(dlg.get_retval()) {
361  //Log in with password
362  case gui2::twindow::OK:
363  break;
364  //Request a password reminder
365  case 1:
366  password_reminder = "yes";
367  break;
368  // Cancel
369  default:
370  return 0;
371  }
372 
373  // If we have got a new username we have to start all over again
374  } while(login == preferences::login());
375 
376  // Somewhat hacky...
377  // If we broke out of the do-while loop above error
378  // is still going to be nullptr
379  if(!*error) break;
380  } // end login loop
381  }
382  } while(!(data.child("join_lobby") || data.child("join_game")));
383 
384  if (h != preferences::server_list().front().address)
386 
387  if (data.child("join_lobby")) {
388  return sock;
389  } else {
390  return 0;
391  }
392 
393 }
394 
395 
396 // The multiplayer logic consists in 4 screens:
397 //
398 // lobby <-> create <-> connect <-> (game)
399 // <------------> wait <-> (game)
400 //
401 // To each of this screen corresponds a dialog, each dialog being defined in
402 // the multiplayer_(screen) file.
403 //
404 // The functions enter_(screen)_mode are simple functions that take care of
405 // creating the dialogs, then, according to the dialog result, of calling other
406 // of those screen functions.
407 
408 static void enter_wait_mode(CVideo& video, const config& game_config, saved_game& state, twesnothd_connection* wesnothd_connection,
409  bool observe, int current_turn = 0)
410 {
411  DBG_MP << "entering wait mode" << std::endl;
412 
414 
415  gamelist.clear();
417  mp_campaign_info campaign_info(*wesnothd_connection);
418  campaign_info.is_host = false;
420  campaign_info.skip_replay_until_turn = current_turn;
422  }
423 
424  {
425  mp::wait ui(video, wesnothd_connection, game_config, state, gamechat, gamelist);
426 
427  ui.join_game(observe);
428 
429  run_lobby_loop(video, ui);
430  res = ui.get_result();
431  campaign_info.connected_players.insert(ui.user_list().begin(), ui.user_list().end());
432 
433  if (res == mp::ui::PLAY) {
434  ui.start_game();
435  } else {
436  DBG_MP << "skipped wait mode, result = " << res << std::endl;
437  }
438  }
439 
440  switch (res) {
441  case mp::ui::PLAY: {
442  campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
443  controller.set_mp_info(&campaign_info);
444  controller.play_game();
445  break;
446  }
447  case mp::ui::QUIT:
448  default:
449  break;
450  }
451  wesnothd_connection->send_data(config("leave_game"));
452 }
453 
454 static void enter_create_mode(CVideo& video, const config& game_config, saved_game& state, twesnothd_connection* wesnothd_connection,
455  bool local_players_only = false);
456 
457 static bool enter_connect_mode(CVideo& video, const config& game_config,
458  saved_game& state, twesnothd_connection* wesnothd_connection,
459  bool local_players_only = false)
460 {
461  DBG_MP << "entering connect mode" << std::endl;
462 
464 
465  gamelist.clear();
467  std::unique_ptr<mp_campaign_info> campaign_info;
468  if(!local_players_only) {
469  assert(wesnothd_connection);
470  campaign_info.reset(new mp_campaign_info(*wesnothd_connection));
471  campaign_info->connected_players.insert(preferences::login());
472  campaign_info->is_host = true;
473  }
474 
475  {
476  ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, campaign_info.get()));
477  mp::connect ui(video, wesnothd_connection, state.mp_settings().name, game_config, gamechat, gamelist,
478  *connect_engine);
479  run_lobby_loop(video, ui);
480 
481  res = ui.get_result();
482 
483  // start_game() updates the parameters to reflect game start,
484  // so it must be called before get_level()
485  if (res == mp::ui::PLAY) {
486  ui.start_game();
487  }
488  } // end connect_engine_ptr scope
489 
490  switch (res) {
491  case mp::ui::PLAY: {
492  campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
493  controller.set_mp_info(campaign_info.get());
494  controller.play_game();
495  if (wesnothd_connection) {
496  wesnothd_connection->send_data(config("leave_game"));
497  }
498  break;
499  }
500  case mp::ui::CREATE:
501  enter_create_mode(video, game_config, state, wesnothd_connection, local_players_only);
502  if (wesnothd_connection) {
503  wesnothd_connection->send_data(config("leave_game"));
504  }
505  break;
506  case mp::ui::QUIT:
507  default:
508  if (wesnothd_connection) {
509  wesnothd_connection->send_data(config("refresh_lobby"));
510  wesnothd_connection->send_data(config("leave_game"));
511  }
512  return false;
513  }
514 
515  return true;
516 }
517 
518 static bool enter_configure_mode(CVideo& video, const config& game_config, saved_game& state, twesnothd_connection* wesnothd_connection,
519  bool local_players_only = false);
520 
521 static void enter_create_mode(CVideo& video, const config& game_config,
522  saved_game& state, twesnothd_connection* wesnothd_connection, bool local_players_only)
523 {
524  DBG_MP << "entering create mode" << std::endl;
525 
526  bool configure_canceled;
527  bool connect_canceled;
528 
529  do {
530  configure_canceled = false;
531  connect_canceled = false;
532 
533  if (gui2::new_widgets) {
534 
535  gui2::tmp_create_game dlg(game_config);
536 
537  dlg.show(video);
538 
539  if (wesnothd_connection) {
540  wesnothd_connection->send_data(config("refresh_lobby"));
541  }
542  } else {
543 
545 
546  {
547  mp::create ui(video, wesnothd_connection, game_config, state, gamechat, gamelist);
548  run_lobby_loop(video, ui);
549  res = ui.get_result();
550  ui.get_parameters();
551  }
552 
553  switch (res) {
554  case mp::ui::CREATE:
555  configure_canceled = !enter_configure_mode(video, game_config, state, wesnothd_connection, local_players_only);
556  break;
557  case mp::ui::LOAD_GAME:
558  connect_canceled = !enter_connect_mode(video, game_config, state, wesnothd_connection, local_players_only);
559  break;
560  case mp::ui::QUIT:
561  default:
562  //update lobby content
563  if (wesnothd_connection) {
564  wesnothd_connection->send_data(config("refresh_lobby"));
565  }
566  break;
567  }
568  }
569  } while(configure_canceled || connect_canceled);
570 }
571 
572 static bool enter_configure_mode(CVideo& video, const config& game_config,
573  saved_game& state, twesnothd_connection* wesnothd_connection, bool local_players_only)
574 {
575  DBG_MP << "entering configure mode" << std::endl;
576 
577  bool connect_canceled;
578 
579  do {
580  connect_canceled = false;
581 
583 
584  {
585  if (state.get_starting_pos().child("side")) {
586  mp::configure ui(video, wesnothd_connection, game_config, gamechat, gamelist, state,
587  local_players_only);
588  run_lobby_loop(video, ui);
589  res = ui.get_result();
590  ui.get_parameters();
591  } else {
592  gui2::show_error_message(video, _("No sides found. At least one side or starting position must be defined."));
593  res = mp::ui::QUIT;
594  }
595  }
596 
597  switch (res) {
598  case mp::ui::CREATE:
599  connect_canceled = !enter_connect_mode(video, game_config, state, wesnothd_connection, local_players_only);
600  break;
601  case mp::ui::QUIT:
602  default:
603  //update lobby content
604  if (wesnothd_connection) {
605  wesnothd_connection->send_data(config("refresh_lobby"));
606  }
607  return false;
608  }
609  } while(connect_canceled);
610 
611  return true;
612 }
613 
614 static void do_preferences_dialog(CVideo& video, const config& game_config)
615 {
616  DBG_MP << "displaying preferences dialog" << std::endl;
617  preferences::show_preferences_dialog(video, game_config);
618 
619  /**
620  * The screen size might have changed force an update of the size.
621  *
622  * @todo This might no longer be needed when gui2 is done.
623  */
624  const SDL_Rect rect = screen_area();
625 
626  video.set_resolution(rect.w, rect.h);
627 
632 }
633 
634 static void enter_lobby_mode(CVideo& video, const config& game_config,
635  saved_game& state, twesnothd_connection* wesnothd_connection, const std::vector<std::string> & installed_addons)
636 {
637  assert(wesnothd_connection);
638  DBG_MP << "entering lobby mode" << std::endl;
639 
641  int current_turn = 0;
642  while (true) {
643  const config &cfg = game_config.child("lobby_music");
644  if (cfg) {
645  for (const config &i : cfg.child_range("music")) {
647  }
649  } else {
652  }
653  lobby_info li(game_config, *wesnothd_connection);
654 
655  // Force a black background
656  const Uint32 color = SDL_MapRGBA(video.getSurface()->format
657  , 0
658  , 0
659  , 0
660  , SDL_ALPHA_OPAQUE);
661 
662  sdl::fill_rect(video.getSurface(), nullptr, color);
663 
664  if(preferences::new_lobby()) {
665  gui2::tlobby_main dlg(game_config, li, video, *wesnothd_connection);
667  std::bind(do_preferences_dialog,
668  std::ref(video), std::ref(game_config)));
669  dlg.show(video);
670  //ugly kludge for launching other dialogs like the old lobby
671  switch (dlg.get_legacy_result()) {
673  res = mp::ui::CREATE;
674  break;
676  res = mp::ui::JOIN;
677  break;
679  res = mp::ui::OBSERVE;
680  break;
681  default:
682  res = mp::ui::QUIT;
683  }
684  } else {
685  mp::lobby ui(video, wesnothd_connection, game_config, gamechat, gamelist, installed_addons);
686  run_lobby_loop(video, ui);
687  res = ui.get_result();
688  current_turn = ui.current_turn;
689  }
690 
691  switch (res) {
692  case mp::ui::JOIN:
693  case mp::ui::OBSERVE:
694  try {
695  enter_wait_mode(video, game_config, state, wesnothd_connection, res == mp::ui::OBSERVE, current_turn);
696  } catch(config::error& error) {
697  if(!error.message.empty()) {
698  gui2::show_error_message(video, error.message);
699  }
700  //update lobby content
701  wesnothd_connection->send_data(config("refresh_lobby"));
702  }
703  break;
704  case mp::ui::CREATE:
705  try {
706  enter_create_mode(video, game_config, state, wesnothd_connection, false);
707  } catch(config::error& error) {
708  if (!error.message.empty())
709  gui2::show_error_message(video, error.message);
710  //update lobby content
711  wesnothd_connection->send_data(config("refresh_lobby"));
712  }
713  break;
714  case mp::ui::QUIT:
715  return;
716  case mp::ui::PREFERENCES:
717  {
718  do_preferences_dialog(video, game_config);
719  //update lobby content
720  wesnothd_connection->send_data(config("refresh_lobby"));
721  }
722  break;
723  default:
724  return;
725  }
726  }
727 }
728 
729 namespace mp {
730 
732  saved_game& state)
733 {
734  DBG_MP << "starting local game" << std::endl;
735  gamechat.clear_history();
736  gamelist.clear();
738  enter_create_mode(video, game_config, state, nullptr, true);
739 }
740 
742  saved_game& state, const commandline_options& cmdline_opts)
743 {
744  DBG_MP << "starting local MP game from commandline" << std::endl;
745 
746  // The setup is done equivalently to lobby MP games using as much of existing
747  // code as possible. This means that some things are set up that are not
748  // needed in commandline mode, but they are required by the functions called.
749  gamechat.clear_history();
750  gamelist.clear();
752 
753  DBG_MP << "entering create mode" << std::endl;
754 
755  // Set the default parameters
756  state = saved_game(); // This creates these parameters with default values defined in mp_game_settings.cpp
758  // Hardcoded default values
759  parameters.mp_era = "era_default";
760  parameters.name = "multiplayer_The_Freelands";
761 
762  // Default values for which at getter function exists
763  parameters.num_turns = settings::get_turns("");
764  parameters.village_gold = settings::get_village_gold("");
766  parameters.xp_modifier = settings::get_xp_modifier("");
767 
768  // Do not use map settings if --ignore-map-settings commandline option is set
769  if(cmdline_opts.multiplayer_ignore_map_settings) {
770  DBG_MP << "ignoring map settings" << std::endl;
771  parameters.use_map_settings = false;
772  } else {
773  parameters.use_map_settings = true;
774  }
775 
776  // None of the other parameters need to be set, as their creation values above are good enough for CL mode.
777  // In particular, we do not want to use the preferences values.
778 
779  state.classification().campaign_type = game_classification::CAMPAIGN_TYPE::MULTIPLAYER;
780  //[era] define.
781  if (cmdline_opts.multiplayer_era) {
782  parameters.mp_era = *cmdline_opts.multiplayer_era;
783  }
784  if (const config& cfg_era = game_config.find_child("era", "id", parameters.mp_era)) {
785  state.classification().era_define = cfg_era["define"].str();
786  }
787  else {
788  std::cerr << "Could not find era '" << parameters.mp_era << "'\n";
789  return;
790  }
791  //[multiplayer] define.
792  if (cmdline_opts.multiplayer_scenario) {
793  parameters.name = *cmdline_opts.multiplayer_scenario;
794  }
795  if (const config& cfg_multiplayer = game_config.find_child("multiplayer", "id", parameters.name)) {
796  state.classification().scenario_define = cfg_multiplayer["define"].str();
797  }
798  else {
799  std::cerr << "Could not find [multiplayer] '" << parameters.name << "'\n";
800  return;
801  }
804  config_of("next_scenario", parameters.name)
805  );
806  state.expand_random_scenario();
807  state.expand_mp_events();
808  state.expand_mp_options();
809 
810  // Should number of turns be determined from scenario data?
811  if (parameters.use_map_settings && state.get_starting_pos()["turns"]) {
812  DBG_MP << "setting turns from scenario data: " << state.get_starting_pos()["turns"] << std::endl;
813  parameters.num_turns = state.get_starting_pos()["turns"];
814  }
815 
816  DBG_MP << "entering connect mode" << std::endl;
817 
819 
820  {
821  ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, nullptr));
822  mp::connect ui(video, 0, parameters.name, game_config, gamechat, gamelist,
823  *connect_engine);
824 
825  // Update the parameters to reflect game start conditions
826  ui.start_game_commandline(cmdline_opts);
827  }
828 
829  std::string label = "";
830  if (cmdline_opts.multiplayer_label) label = *cmdline_opts.multiplayer_label;
831 
832  //resources::recorder->add_log_data("ai_log","ai_label",label);
833 
834  unsigned int repeat = (cmdline_opts.multiplayer_repeat) ? *cmdline_opts.multiplayer_repeat : 1;
835  for(unsigned int i = 0; i < repeat; i++){
836  saved_game state_copy(state);
837  campaign_controller controller(video, state_copy, game_config, game_config_manager::get()->terrain_types());
838  controller.play_game();
839  break;
840  }
841 }
842 
843 void start_client(CVideo& video, const config& game_config,
844  saved_game& state, const std::string& host)
845 {
846  const config * game_config_ptr = &game_config;
847  std::vector<std::string> installed_addons = ::installed_addons();
848  // This function does not refer to an addon database, it calls filesystem functions.
849  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
850  // even if the user changes the contents of the addon directory in the meantime.
851 
852  DBG_MP << "starting client" << std::endl;
853 
855 
856  gamechat.clear_history();
857  gamelist.clear();
858  std::unique_ptr<twesnothd_connection> wesnothd_connection = open_connection(video, host);
859  if(wesnothd_connection) {
860  bool re_enter;
861  do {
862  re_enter = false;
863  try {
864  enter_lobby_mode(video, *game_config_ptr, state, wesnothd_connection.get(), installed_addons);
865  } catch (lobby_reload_request_exception &) {
866  re_enter = true;
869  gcm->load_game_config_for_game(state.classification()); // NOTE: Using reload_changed_game_config only doesn't seem to work here
870  game_config_ptr = &gcm->game_config();
871 
872  installed_addons = ::installed_addons(); // Refersh the installed add-on list for this session.
873 
874  gamelist.clear(); //needed to make sure we update which games we have content for
875  wesnothd_connection->send_data(config("refresh_lobby"));
876  }
877  } while (re_enter);
878  }
879 }
880 
882  const config& game_config, twesnothd_connection* wesnothd_connection, const std::string& game_name)
883 {
885 
886  {
887  mp::connect ui(video, wesnothd_connection, game_name, game_config, gamechat, gamelist,
888  engine);
889  run_lobby_loop(video, ui);
890 
891  res = ui.get_result();
892  if (res == mp::ui::PLAY) {
893  ui.start_game();
894  }
895  }
896 
897  return res;
898 }
899 
900 mp::ui::result goto_mp_wait(CVideo& video, saved_game& state, const config& game_config, twesnothd_connection* wesnothd_connection, bool observe)
901 {
903 
904  {
905  mp::wait ui(video, wesnothd_connection, game_config, state, gamechat, gamelist, false);
906 
907  ui.join_game(observe);
908  run_lobby_loop(video, ui);
909 
910  res = ui.get_result();
911  if (res == mp::ui::PLAY) {
912  ui.start_game();
913  }
914  }
915 
916  return res;
917 }
918 
919 } // end namespace mp
920 
void empty_playlist()
Definition: sound.cpp:480
An error occured during when trying to coommunicate with the wesnothd server.
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:40
child_itors child_range(const std::string &key)
Definition: config.cpp:613
void show_error_message(CVideo &video, const std::string &message, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:198
std::unique_ptr< twesnothd_connection > network_connect_dialog(CVideo &video, const std::string &msg, const std::string &hostname, int port)
Definition: dialogs.cpp:1195
LEVEL_RESULT play_game()
bool skip_replay_blindfolded
static bool enter_configure_mode(CVideo &video, const config &game_config, saved_game &state, twesnothd_connection *wesnothd_connection, bool local_players_only=false)
void stop_music()
Definition: sound.cpp:411
static void enter_create_mode(CVideo &video, const config &game_config, saved_game &state, twesnothd_connection *wesnothd_connection, bool local_players_only=false)
void fill_rect(surface &dst, SDL_Rect *dst_rect, const Uint32 color)
Fill a rectangle on a given surface.
Definition: rect.hpp:143
int get_retval() const
Definition: dialog.hpp:161
unsigned int get_ping_timeout()
void set_resolution(const std::pair< int, int > &res)
Set the resolution.
Definition: video.cpp:696
void set_mp_info(mp_campaign_info *mp_info)
void start_client(CVideo &video, const config &game_config, saved_game &state, const std::string &host)
Starts a multiplayer game in client mode.
int pos
Definition: formula.cpp:800
const mp_game_settings & get_parameters()
bool show(CVideo &video, const unsigned auto_close_time=0)
Shows the window.
Definition: dialog.cpp:34
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:29
#define MP_TOO_MANY_ATTEMPTS_ERROR
void set_preferences_callback(std::function< void()> f)
Set the callback used to show the preferences.
Definition: lobby.cpp:388
static void do_preferences_dialog(CVideo &video, const config &game_config)
mp::ui::result goto_mp_connect(CVideo &video, ng::connect_engine &engine, const config &game_config, twesnothd_connection *wesnothd_connection, const std::string &game_name)
Opens mp::connect screen and sets game state according to the changes made.
std::set< std::string > connected_players
players and observers
Definition: video.hpp:58
void flip()
Definition: video.cpp:496
static std::unique_ptr< twesnothd_connection > open_connection(CVideo &video, const std::string &original_host)
result get_result()
Returns the result of the current widget.
This file contains the window object, this object is a top level container which has the event manage...
std::string network_host()
boost::optional< std::string > multiplayer_scenario
Non-empty if –scenario was given on the command line. Dependent on –multiplayer.
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:387
This class represents the collective information the client has about the players and games on the se...
Definition: info.hpp:25
#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.
Definition: settings.cpp:42
static void enter_wait_mode(CVideo &video, const config &game_config, saved_game &state, twesnothd_connection *wesnothd_connection, bool observe, int current_turn=0)
void fresh_stats()
Definition: statistics.cpp:617
#define MP_NAME_TOO_LONG_ERROR
#define h
void set_network_host(const std::string &host)
void clear()
Definition: config.cpp:1055
unsigned gamemap_width
The size of the map area, if not available equal to the screen size.
Definition: settings.cpp:49
Define the errors the server may send during the login procedure.
GLint GLenum GLsizei GLint GLsizei const GLvoid * data
Definition: glew.h:1347
boost::optional< std::string > multiplayer_era
Non-empty if –era was given on the command line. Dependent on –multiplayer.
-file util.hpp
std::string get_salt(const std::string &hash)
Definition: hash.cpp:37
surface & getSurface()
Definition: dummy_video.cpp:22
boost::optional< std::string > multiplayer_label
Non-empty if –label was given on the command line. Dependent on –multiplayer.
SDL_Rect screen_area()
Definition: video.cpp:135
void send_data(const configr_of &request)
#define MP_INCORRECT_PASSWORD_ERROR
int get_village_support(const std::string &value)
Gets the village unit level support.
Definition: settings.cpp:47
This module controls the multiplayer lobby.
bool has_child(const std::string &key) const
Determine whether a config has a child or not.
Definition: config.cpp:651
static game_config_manager * get()
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
#define MP_MUST_LOGIN
#define MP_NAME_UNREGISTERED_ERROR
Dialog is closed with ok button.
Definition: window.hpp:125
std::map< std::string, t_string > string_map
A class that represents a TCP/IP connection to the wesnothd server.
This file contains the settings handling of the widget library.
plugins_context * get_plugins_context()
int gety() const
Definition: video.cpp:481
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:356
std::string create_hash(const std::string &password, const std::string &salt, int iteration_count)
Definition: hash.cpp:74
Dialog is closed with the cancel button.
Definition: window.hpp:126
void show_preferences_dialog(CVideo &video, const config &game_cfg, const DIALOG_OPEN_TO initial_view)
unsigned gamemap_height
Definition: settings.cpp:50
void show_message(CVideo &video, const std::string &title, const std::string &message, const std::string &button_caption, const bool auto_close, const bool message_use_markup)
Shows a message to the user.
Definition: message.cpp:143
void play_music_config(const config &music_node)
Definition: sound.cpp:552
const std::vector< game_config::server_info > & server_list()
Shows an ok and cancel button.
Definition: message.hpp:71
config & add_child(const std::string &key)
Definition: config.cpp:743
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:150
Used to reset is_authenticated flag after disconnecting.
config & get_starting_pos()
Definition: saved_game.cpp:472
void start_game_commandline(const commandline_options &cmdline_opts)
static lg::log_domain log_network("network")
void set_message_private(bool value)
void raise_draw_event()
Definition: events.cpp:565
GLuint color
Definition: glew.h:5801
void pump()
Definition: events.cpp:336
std::string era_define
If there is a define the era uses to customize data.
std::string login()
mp::ui::result goto_mp_wait(CVideo &video, saved_game &state, const config &game_config, twesnothd_connection *wesnothd_connection, bool observe)
Opens mp::wait screen and sets game state according to the changes made.
bool network_receive_dialog(CVideo &video, const std::string &msg, config &cfg, twesnothd_connection &wesnothd_connection)
Definition: dialogs.cpp:1179
GLuint res
Definition: glew.h:9258
void raise_process_event()
Definition: events.cpp:539
#define DBG_MP
Definition: multiplayer.cpp:61
void cache_mode(CACHE mode)
Definition: font.cpp:1120
static bool enter_connect_mode(CVideo &video, const config &game_config, saved_game &state, twesnothd_connection *wesnothd_connection, bool local_players_only=false)
int getx() const
Definition: video.cpp:472
Game configuration data as global variables.
Definition: build_info.cpp:38
const config & game_config() const
std::string scenario_define
If there is a define the scenario uses to customize data.
const std::string & parameters
Definition: filter.cpp:155
GLfloat GLfloat GLfloat GLfloat h
Definition: glew.h:5910
size_t i
Definition: function.cpp:1057
int get_xp_modifier(const std::string &value)
Gets the xp modifier.
Definition: settings.cpp:52
unsigned screen_width
The screen resolution should be available for all widgets since their drawing method will depend on i...
Definition: settings.cpp:44
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:321
GLdouble GLdouble GLdouble r
Definition: glew.h:1374
#define MP_PASSWORD_REQUEST
static lg::log_domain log_mp("mp/main")
#define MP_NAME_INACTIVE_WARNING
-file mapgen.hpp
std::string vgettext(const char *msgid, const utils::string_map &symbols)
const std::vector< std::string > & user_list() const
#define MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an empty SDL_Rect.
Definition: rect.cpp:28
#define MP_NAME_TAKEN_ERROR
void load_game_config_for_game(const game_classification &classification)
Contains the SDL_Rect helper code.
void start_local_game_commandline(CVideo &video, const config &game_config, saved_game &state, const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode.
GLenum GLint ref
Definition: glew.h:1813
bool has_attribute(const std::string &key) const
Definition: config.cpp:514
unsigned screen_height
Definition: settings.cpp:45
boost::optional< unsigned int > multiplayer_repeat
Repeats specified by –multiplayer-repeat option. Repeats a multiplayer game after it is finished...
game_classification & classification()
Definition: saved_game.hpp:54
void join_game(bool observe)
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:113
config & child(const std::string &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:658
static void enter_lobby_mode(CVideo &video, const config &game_config, saved_game &state, twesnothd_connection *wesnothd_connection, const std::vector< std::string > &installed_addons)
void process_network()
Asks the multiplayer_ui to pump some data from the network, and then to process it.
config & find_child(const std::string &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:1010
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:29
GLint * first
Definition: glew.h:1496
void set_location(const SDL_Rect &rect)
Hides children, moves them (using layout_children), then shows them.
static void delay(unsigned int milliseconds)
Definition: video.cpp:490
this class memorizes a chat session.
#define MP_NAME_RESERVED_ERROR
void commit_music_changes()
Definition: sound.cpp:621
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
boost::scoped_ptr< connect_engine > connect_engine_ptr
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:58
std::string password()
int get_turns(const std::string &value)
Gets the number of turns.
Definition: settings.cpp:29
const std::string version
Definition: game_config.cpp:48
GLsizei const GLcharARB ** string
Definition: glew.h:4503
Shows a yes and no button.
Definition: message.hpp:75
int get_iteration_count(const std::string &hash)
Definition: hash.cpp:33
a base class for the different multiplayer base dialogs: game list, create game, wait game...
#define MP_INVALID_CHARS_IN_NAME_ERROR
bool modeChanged()
Definition: video.cpp:465
legacy_result get_legacy_result() const
Definition: lobby.hpp:123
void run_lobby_loop(CVideo &video, mp::ui &ui)
Definition: multiplayer.cpp:72
void start_local_game(CVideo &video, const config &game_config, saved_game &state)
Starts a multiplayer game in single-user mode.