The Battle for Wesnoth  1.17.12+dev
lobby.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2022
3  by Tomasz Sniatowski <kailoran@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "gui/auxiliary/field.hpp"
22 #include "gui/dialogs/message.hpp"
26 
27 #include "gui/core/log.hpp"
28 #include "gui/core/timer.hpp"
29 #include "gui/widgets/button.hpp"
30 #include "gui/widgets/image.hpp"
31 #include "gui/widgets/label.hpp"
32 #include "gui/widgets/listbox.hpp"
34 #include "gui/widgets/minimap.hpp"
35 #include "gui/widgets/chatbox.hpp"
36 #include "gui/widgets/settings.hpp"
37 #include "gui/widgets/text_box.hpp"
41 
42 #include "addon/client.hpp"
43 #include "addon/manager_ui.hpp"
44 #include "chat_log.hpp"
45 #include "desktop/open.hpp"
46 #include "font/text_formatting.hpp"
47 #include "formatter.hpp"
48 #include "formula/string_utils.hpp"
49 #include "preferences/game.hpp"
50 #include "gettext.hpp"
51 #include "help/help.hpp"
52 #include "preferences/lobby.hpp"
53 #include "wesnothd_connection.hpp"
54 
55 #include <functional>
56 
57 static lg::log_domain log_lobby("lobby");
58 #define DBG_LB LOG_STREAM(debug, log_lobby)
59 #define LOG_LB LOG_STREAM(info, log_lobby)
60 #define ERR_LB LOG_STREAM(err, log_lobby)
61 #define SCOPE_LB log_scope2(log_lobby, __func__)
62 
63 namespace gui2::dialogs
64 {
65 REGISTER_DIALOG(mp_lobby)
66 
67 bool mp_lobby::logout_prompt()
68 {
69  return show_prompt(_("Do you really want to log out?"));
70 }
71 
73  : modal_dialog(window_id())
74  , quit_confirmation(&mp_lobby::logout_prompt)
75  , gamelistbox_(nullptr)
76  , lobby_info_(info)
77  , chatbox_(nullptr)
78  , filter_friends_(register_bool("filter_with_friends",
79  true,
82  std::bind(&mp_lobby::update_gamelist_filter, this)))
83  , filter_ignored_(register_bool("filter_with_ignored",
84  true,
87  std::bind(&mp_lobby::update_gamelist_filter, this)))
88  , filter_slots_(register_bool("filter_vacant_slots",
89  true,
92  std::bind(&mp_lobby::update_gamelist_filter, this)))
93  , filter_invert_(register_bool("filter_invert",
94  true,
97  std::bind(&mp_lobby::update_gamelist_filter, this)))
98  , filter_auto_hosted_(false)
99  , filter_text_(nullptr)
100  , selected_game_id_()
101  , player_list_(std::bind(&mp_lobby::user_dialog_callback, this, std::placeholders::_1))
102  , player_list_dirty_(true)
103  , gamelist_dirty_(true)
104  , last_lobby_update_(0)
105  , gamelist_diff_update_(true)
106  , network_connection_(connection)
107  , lobby_update_timer_(0)
108  , gamelist_id_at_row_()
109  , delay_playerlist_update_(false)
110  , delay_gamelist_update_(false)
111  , joined_game_id_(joined_game)
112 {
114  set_allow_plugin_skip(false);
116 
117  /*** Local hotkeys. ***/
119  std::bind(&mp_lobby::show_help_callback, this));
120 
123 }
124 
126 {
128  {
129  l.delay_gamelist_update_ = true;
130  }
132  {
133  l.delay_gamelist_update_ = false;
134  }
136 };
137 
139 {
140  if(lobby_update_timer_) {
142  }
143 }
144 
145 namespace
146 {
147 void modify_grid_with_data(grid* grid, const widget_data& map)
148 {
149  for(const auto& v : map) {
150  const std::string& key = v.first;
151  const widget_item& strmap = v.second;
152 
153  widget* w = grid->find(key, false);
154  if(!w) {
155  continue;
156  }
157 
158  styled_widget* c = dynamic_cast<styled_widget*>(w);
159  if(!c) {
160  continue;
161  }
162 
163  for(const auto & vv : strmap) {
164  if(vv.first == "label") {
165  c->set_label(vv.second);
166  } else if(vv.first == "tooltip") {
167  c->set_tooltip(vv.second);
168  }
169  }
170  }
171 }
172 
173 bool handle_addon_requirements_gui(const std::vector<mp::game_info::required_addon>& reqs, mp::game_info::addon_req addon_outcome)
174 {
175  if(addon_outcome == mp::game_info::addon_req::CANNOT_SATISFY) {
176  std::string e_title = _("Incompatible User-made Content");
177  std::string err_msg = _("This game cannot be joined because the host has out-of-date add-ons that are incompatible with your version. You might wish to suggest that the host's add-ons be updated.");
178 
179  err_msg +="\n\n";
180  err_msg += _("Details:");
181  err_msg += "\n";
182 
183  for(const mp::game_info::required_addon & a : reqs) {
185  err_msg += font::unicode_bullet + " " + a.message + "\n";
186  }
187  }
188  gui2::show_message(e_title, err_msg, message::auto_close, true);
189 
190  return false;
191  } else if(addon_outcome == mp::game_info::addon_req::NEED_DOWNLOAD) {
192  std::string e_title = _("Missing User-made Content");
193  std::string err_msg = _("This game requires one or more user-made addons to be installed or updated in order to join.\nDo you want to try to install them?");
194 
195  err_msg +="\n\n";
196  err_msg += _("Details:");
197  err_msg += "\n";
198 
199  std::vector<std::string> needs_download;
200  for(const mp::game_info::required_addon & a : reqs) {
202  err_msg += font::unicode_bullet + " " + a.message + "\n";
203 
204  needs_download.push_back(a.addon_id);
205  }
206  }
207 
208  assert(needs_download.size() > 0);
209 
210  if(gui2::show_message(e_title, err_msg, message::yes_no_buttons, true) == gui2::retval::OK) {
211  // Begin download session
212  try {
213  return ad_hoc_addon_fetch_session(needs_download);
214  } catch (const addons_client::user_exit&) {
215  } catch (const addons_client::user_disconnect&) {
216  }
217  }
218  }
219 
220  return false;
221 }
222 
223 } // end anonymous namespace
224 
226 {
227  if(delay_gamelist_update_) return;
228 
229  SCOPE_LB;
230  gamelistbox_->clear();
231  gamelist_id_at_row_.clear();
232 
233  const auto finish_state_sync = lobby_info_.begin_state_sync();
234 
235  int select_row = -1;
236  for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
237  const mp::game_info& game = *lobby_info_.games()[i];
238 
239  if(game.id == selected_game_id_) {
240  select_row = i;
241  }
242 
243  gamelist_id_at_row_.push_back(game.id);
244  LOG_LB << "Adding game to listbox (1)" << game.id;
246 
247  adjust_game_row_contents(game, grid);
248  }
249 
250  if(select_row >= 0 && select_row != gamelistbox_->get_selected_row()) {
251  gamelistbox_->select_row(select_row);
252  }
253 
255  gamelist_dirty_ = false;
256  last_lobby_update_ = SDL_GetTicks();
257  finish_state_sync();
259 }
260 
262 {
263  if(delay_gamelist_update_) return;
264 
265  SCOPE_LB;
266  int select_row = -1;
267  unsigned list_i = 0;
268  int list_rows_deleted = 0;
269 
270  const auto finish_state_sync = lobby_info_.begin_state_sync();
271 
272  std::vector<int> next_gamelist_id_at_row;
273  for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
274  const mp::game_info& game = *lobby_info_.games()[i];
275 
277  // call void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
278  // sender will be the game_info.scenario (std::string) and message will be game_info.name (std::string)
279  if (lobby_info_.is_game_visible(game)) {
281  }
282 
283  LOG_LB << "Adding game to listbox " << game.id;
284 
285  if(list_i != gamelistbox_->get_item_count()) {
286  gamelistbox_->add_row(make_game_row_data(game), list_i);
287  DBG_LB << "Added a game listbox row not at the end" << list_i
288  << " " << gamelistbox_->get_item_count();
289  list_rows_deleted--;
290  } else {
292  }
293 
295  adjust_game_row_contents(game, grid);
296 
297  list_i++;
298  next_gamelist_id_at_row.push_back(game.id);
299  } else {
300  if(list_i >= gamelistbox_->get_item_count()) {
301  ERR_LB << "Ran out of listbox items -- triggering a full refresh";
302  refresh_lobby();
303  return;
304  }
305 
306  if(list_i + list_rows_deleted >= gamelist_id_at_row_.size()) {
307  ERR_LB << "gamelist_id_at_row_ overflow! " << list_i << " + "
308  << list_rows_deleted
309  << " >= " << gamelist_id_at_row_.size()
310  << " -- triggering a full refresh";
311  refresh_lobby();
312  return;
313  }
314 
315  int listbox_game_id = gamelist_id_at_row_[list_i + list_rows_deleted];
316  if(game.id != listbox_game_id) {
317  ERR_LB << "Listbox game id does not match expected id "
318  << listbox_game_id << " " << game.id << " (row " << list_i << ")";
319  refresh_lobby();
320  return;
321  }
322 
324  LOG_LB << "Modifying game in listbox " << game.id << " (row " << list_i << ")";
325  grid* grid = gamelistbox_->get_row_grid(list_i);
326  modify_grid_with_data(grid, make_game_row_data(game));
327  adjust_game_row_contents(game, grid, false);
328  ++list_i;
329  next_gamelist_id_at_row.push_back(game.id);
331  LOG_LB << "Deleting game from listbox " << game.id << " (row "
332  << list_i << ")";
333  gamelistbox_->remove_row(list_i);
334  ++list_rows_deleted;
335  } else {
336  // clean
337  LOG_LB << "Clean game in listbox " << game.id << " (row " << list_i << ")";
338  next_gamelist_id_at_row.push_back(game.id);
339  ++list_i;
340  }
341  }
342  }
343 
344  for(unsigned i = 0; i < next_gamelist_id_at_row.size(); ++i) {
345  if(next_gamelist_id_at_row[i] == selected_game_id_) {
346  select_row = i;
347  }
348  }
349 
350  next_gamelist_id_at_row.swap(gamelist_id_at_row_);
351  if(select_row >= static_cast<int>(gamelistbox_->get_item_count())) {
352  ERR_LB << "Would select a row beyond the listbox" << select_row << " "
354  select_row = gamelistbox_->get_item_count() - 1;
355  }
356 
357  if(select_row >= 0 && select_row != gamelistbox_->get_selected_row()) {
358  gamelistbox_->select_row(select_row);
359  }
360 
362  gamelist_dirty_ = false;
363  last_lobby_update_ = SDL_GetTicks();
364  finish_state_sync();
366 }
367 
369 {
370  const std::string games_string = VGETTEXT("Games: showing $num_shown out of $num_total", {
371  {"num_shown", std::to_string(lobby_info_.games_visibility().count())},
372  {"num_total", std::to_string(lobby_info_.games().size())}
373  });
374 
375  find_widget<label>(gamelistbox_, "map", false).set_label(games_string);
376 
377  gamelistbox_->set_row_shown(lobby_info_.games_visibility());
378 }
379 
381 {
384 
385  item["use_markup"] = "true";
386 
387  color_t color_string;
388  if(game.vacant_slots > 0) {
389  color_string = (game.reloaded || game.started) ? font::YELLOW_COLOR : font::GOOD_COLOR;
390  }
391 
392  const std::string scenario_text = VGETTEXT("$game_name (Era: $era_name)", {
393  {"game_name", game.scenario},
394  {"era_name", game.era}
395  });
396 
397  item["label"] = game.vacant_slots > 0 ? font::span_color(color_string, game.name) : game.name;
398  data.emplace("name", item);
399 
400  item["label"] = font::span_color(font::GRAY_COLOR, game.type_marker + "<i>" + scenario_text + "</i>");
401  data.emplace("scenario", item);
402 
403  item["label"] = font::span_color(color_string, game.status);
404  data.emplace("status", item);
405 
406  return data;
407 }
408 
410 {
411  find_widget<styled_widget>(grid, "name", false).set_use_markup(true);
412  find_widget<styled_widget>(grid, "status", false).set_use_markup(true);
413 
414  toggle_panel& row_panel = find_widget<toggle_panel>(grid, "panel", false);
415 
416  //
417  // Game info
418  //
419  std::ostringstream ss;
420 
421  const auto mark_missing = [&ss]() {
422  ss << ' ' << font::span_color(font::BAD_COLOR) << "(" << _("era_or_mod^not installed") << ")</span>";
423  };
424 
425  ss << "<big>" << font::span_color(font::TITLE_COLOR, _("Era")) << "</big>\n" << game.era;
426 
427  if(!game.have_era) {
428  // NOTE: not using colorize() here deliberately to avoid awkward string concatenation.
429  mark_missing();
430  }
431 
432  ss << "\n\n<big>" << font::span_color(font::TITLE_COLOR, _("Modifications")) << "</big>\n";
433 
434  auto mods = game.mod_info;
435 
436  if(mods.empty()) {
437  ss << _("active_modifications^None") << "\n";
438  } else {
439  for(const auto& mod : mods) {
440  ss << mod.first;
441 
442  if(!mod.second) {
443  mark_missing();
444  }
445 
446  ss << '\n';
447  }
448  }
449 
450  // TODO: move to some general area of the code.
451  const auto yes_or_no = [](bool val) { return val ? _("yes") : _("no"); };
452 
453  ss << "\n<big>" << font::span_color(font::TITLE_COLOR, _("Settings")) << "</big>\n";
454  ss << _("Experience modifier:") << " " << game.xp << "\n";
455  ss << _("Gold per village:") << " " << game.gold << "\n";
456  ss << _("Map size:") << " " << game.map_size_info << "\n";
457  ss << _("Reloaded:") << " " << yes_or_no(game.reloaded) << "\n";
458  ss << _("Shared vision:") << " " << game.vision << "\n";
459  ss << _("Shuffle sides:") << " " << yes_or_no(game.shuffle_sides) << "\n";
460  ss << _("Time limit:") << " " << game.time_limit << "\n";
461  ss << _("Use map settings:") << " " << yes_or_no(game.use_map_settings);
462 
463  image& info_icon = find_widget<image>(grid, "game_info", false);
464 
465  if(!game.have_era || !game.have_all_mods || !game.required_addons.empty()) {
466  info_icon.set_label("icons/icon-info-error.png");
467 
468  ss << "\n\n<span color='#f00' size='x-large'>! </span>";
469  ss << _("One or more add-ons need to be installed\nin order to join this game.");
470  } else {
471  info_icon.set_label("icons/icon-info.png");
472  }
473 
474  info_icon.set_tooltip(ss.str());
475 
476  //
477  // Password icon
478  //
479  image& password_icon = find_widget<image>(grid, "needs_password", false);
480 
481  if(game.password_required) {
483  } else {
485  }
486 
487  //
488  // Observer icon
489  //
490  image& observer_icon = find_widget<image>(grid, "observer_icon", false);
491 
492  if(game.observers) {
493  observer_icon.set_label("misc/eye.png");
494  observer_icon.set_tooltip( _("Observers allowed"));
495  } else {
496  observer_icon.set_label("misc/no_observer.png");
497  observer_icon.set_tooltip( _("Observers not allowed"));
498  }
499 
500  //
501  // Minimap
502  //
503  minimap& map = find_widget<minimap>(grid, "minimap", false);
504 
505  map.set_map_data(game.map_data);
506 
507  if(!add_callbacks) {
508  return;
509  }
510 
512  std::bind(&mp_lobby::enter_game_by_id, this, game.id, DO_EITHER));
513 }
514 
516 {
517  DBG_LB << "mp_lobby::update_gamelist_filter";
519  DBG_LB << "Games in lobby_info: " << lobby_info_.games().size()
520  << ", games in listbox: " << gamelistbox_->get_item_count();
521  assert(lobby_info_.games().size() == gamelistbox_->get_item_count());
522 
524 }
525 
527 {
528  if(delay_playerlist_update_) return;
529 
530  SCOPE_LB;
531  DBG_LB << "Playerlist update: " << lobby_info_.users().size();
532 
534 
535  player_list_dirty_ = false;
536  last_lobby_update_ = SDL_GetTicks();
537 }
538 
540 {
541  const int idx = gamelistbox_->get_selected_row();
542  bool can_join = false, can_observe = false;
543 
544  if(idx >= 0) {
545  const mp::game_info& game = *lobby_info_.games()[idx];
546  can_observe = game.can_observe();
547  can_join = game.can_join();
548  selected_game_id_ = game.id;
549  } else {
550  selected_game_id_ = 0;
551  }
552 
553  find_widget<button>(get_window(), "observe_global", false).set_active(can_observe);
554  find_widget<button>(get_window(), "join_global", false).set_active(can_join);
555 
556  player_list_dirty_ = true;
557 }
558 
560 {
561  return window.get_retval() == retval::CANCEL ? quit() : true;
562 }
563 
565 {
566  SCOPE_LB;
567 
568  gamelistbox_ = find_widget<listbox>(&window, "game_list", false, true);
569 
571  std::bind(&mp_lobby::update_selected_game, this));
572 
573  player_list_.init(window);
574 
575  window.set_enter_disabled(true);
576 
577  // Exit hook to add a confirmation when quitting the Lobby.
578  window.set_exit_hook(window::exit_hook::on_all, std::bind(&mp_lobby::exit_hook, this, std::placeholders::_1));
579 
580  chatbox_ = find_widget<chatbox>(&window, "chat", false, true);
581 
582  window.keyboard_capture(chatbox_);
583 
586 
587  find_widget<button>(&window, "create", false).set_retval(CREATE);
588 
590  find_widget<button>(&window, "show_preferences", false),
592 
594  find_widget<button>(&window, "join_global", false),
595  std::bind(&mp_lobby::enter_selected_game, this, DO_JOIN));
596 
597  find_widget<button>(&window, "join_global", false).set_active(false);
598 
600  find_widget<button>(&window, "observe_global", false),
601  std::bind(&mp_lobby::enter_selected_game, this, DO_OBSERVE));
602 
604  find_widget<button>(&window, "server_info", false),
605  std::bind(&mp_lobby::show_server_info, this));
606 
607  find_widget<button>(&window, "observe_global", false).set_active(false);
608 
609  menu_button& replay_options = find_widget<menu_button>(&window, "replay_options", false);
610 
612  replay_options.set_selected(1);
613  }
614 
616  replay_options.set_selected(2);
617  }
618 
619  connect_signal_notify_modified(replay_options,
620  std::bind(&mp_lobby::skip_replay_changed_callback, this));
621 
622  filter_text_ = find_widget<text_box>(&window, "filter_text", false, true);
623 
625  *filter_text_,
626  std::bind(&mp_lobby::game_filter_keypress_callback, this, std::placeholders::_5));
627 
628  chatbox_->room_window_open(N_("lobby"), true, false);
630 
632 
633  // Force first update to be directly.
634  update_gamelist();
636 
637  // TODO: currently getting a crash in the chatbox if we use this.
638  // -- vultraz, 2017-11-10
639  //mp_lobby::network_handler();
640 
643 
644  //
645  // Profile box
646  //
647  if(auto* profile_panel = find_widget<panel>(&window, "profile", false, false)) {
648  auto your_info = std::find_if(lobby_info_.users().begin(), lobby_info_.users().end(),
649  [](const auto& u) { return u.get_relation() == mp::user_info::user_relation::ME; });
650 
651  if(your_info != lobby_info_.users().end()) {
652  find_widget<label>(profile_panel, "username", false).set_label(your_info->name);
653 
654  auto& profile_button = find_widget<button>(profile_panel, "view_profile", false);
655  connect_signal_mouse_left_click(profile_button, std::bind(&mp_lobby::open_profile_url, this));
656 
657  // TODO: implement
658  find_widget<button>(profile_panel, "view_match_history", false).set_active(false);
659  }
660  }
661 
662  listbox& tab_bar = find_widget<listbox>(&window, "games_list_tab_bar", false);
664 
665  // Set up Lua plugin context
666  plugins_context_.reset(new plugins_context("Multiplayer Lobby"));
667 
668  plugins_context_->set_callback("join", [&, this](const config&) {
670  }, true);
671 
672  plugins_context_->set_callback("observe", [&, this](const config&) {
674  }, true);
675 
676  plugins_context_->set_callback("create", [&window](const config&) { window.set_retval(CREATE); }, true);
677  plugins_context_->set_callback("quit", [&window](const config&) { window.set_retval(retval::CANCEL); }, false);
678 
679  plugins_context_->set_callback("chat", [this](const config& cfg) { chatbox_->send_chat_message(cfg["message"], false); }, true);
680  plugins_context_->set_callback("select_game", [this](const config& cfg) {
681  selected_game_id_ = cfg.has_attribute("id") ? cfg["id"].to_int() : lobby_info_.games()[cfg["index"].to_int()]->id;
682  }, true);
683 
684  plugins_context_->set_accessor("game_list", [this](const config&) { return lobby_info_.gamelist(); });
685 }
686 
688 {
691 }
692 
694 {
696  if(info && info->forum_id != 0) {
698  }
699 }
700 
701 void mp_lobby::post_show(window& /*window*/)
702 {
705  plugins_context_.reset();
706 }
707 
709 {
710  try {
711  config data;
712  if (network_connection_.receive_data(data)) {
713  process_network_data(data);
714  }
715  } catch (const wesnothd_error& e) {
716  LOG_LB << "caught wesnothd_error in network_handler: " << e.message;
717  throw;
718  }
719 
720  if ((SDL_GetTicks() - last_lobby_update_ < game_config::lobby_refresh)) {
721  return;
722  }
723 
725  //don't process a corrupted gamelist further to prevent crashes later.
726  return;
727  }
728 
732  } else {
733  update_gamelist();
734  gamelist_diff_update_ = true;
735  }
736  }
737 
741  }
742 }
743 
745 {
746  if(const config& error = data.child("error")) {
747  throw wesnothd_error(error["message"]);
748  } else if(data.child("gamelist")) {
749  process_gamelist(data);
750  } else if(const config& gamelist_diff = data.child("gamelist_diff")) {
751  process_gamelist_diff(gamelist_diff);
752  } else if(const config& info = data.child("message")) {
753  if(info["type"] == "server_info") {
754  server_information_ = info["message"].str();
755  return;
756  } else if(info["type"] == "announcements") {
757  announcements_ = info["message"].str();
758  return;
759  }
760  }
761 
763 }
764 
766 {
768 
770  DBG_LB << "Received gamelist";
771  gamelist_dirty_ = true;
772  gamelist_diff_update_ = false;
773 }
774 
776 {
778 
780  DBG_LB << "Received gamelist diff";
781  gamelist_dirty_ = true;
782  } else {
783  ERR_LB << "process_gamelist_diff failed!";
784  refresh_lobby();
785  }
786  const int joined = data.child_count("insert_child");
787  const int left = data.child_count("remove_child");
788  if(joined > 0 || left > 0) {
789  if(left > joined) {
791  } else {
793  }
794  }
795 }
796 
798 {
799  switch(mode) {
800  case DO_JOIN:
801  if(!game.can_join()) {
802  ERR_LB << "Attempted to join a game with no vacant slots";
803  return;
804  }
805 
806  break;
807  case DO_OBSERVE:
808  if(!game.can_observe()) {
809  ERR_LB << "Attempted to observe a game with observers disabled";
810  return;
811  }
812 
813  break;
814  case DO_EITHER:
815  if(game.can_join()) {
816  mode = DO_JOIN;
817  } else if(game.can_observe()) {
818  mode = DO_OBSERVE;
819  } else {
820  DBG_LB << "Cannot join or observe a game.";
821  return;
822  }
823 
824  break;
825  }
826 
827  // prompt moderators for whether they want to join a game with observers disabled
828  if(!game.observers && mp::logged_in_as_moderator()) {
829  if(gui2::show_message(_("Observe"), _("This game doesn't allow observers. Observe using moderator rights anyway?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
830  return;
831  }
832  }
833 
834  const bool try_join = mode == DO_JOIN;
835  const bool try_obsv = mode == DO_OBSERVE;
836 
837  window& window = *get_window();
838 
839  // Prompt user to download this game's required addons if its requirements have not been met
841  if(game.required_addons.empty()) {
842  gui2::show_error_message(_("Something is wrong with the addon version check database supporting the multiplayer lobby. Please report this at https://bugs.wesnoth.org."));
843  return;
844  }
845 
846  if(!handle_addon_requirements_gui(game.required_addons, game.addons_outcome)) {
847  return;
848  }
849 
850  // Addons have been downloaded, so the game_config and installed addons list need to be reloaded.
851  // The lobby is closed and reopened.
852  window.set_retval(RELOAD_CONFIG);
853  return;
854  }
855 
856  config response;
857 
858  config& join_data = response.add_child("join");
859  join_data["id"] = std::to_string(game.id);
860  join_data["observe"] = try_obsv;
861 
863  if(gui2::show_message(_("Join"), _("This game is password protected. Join using moderator rights anyway?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
864  return;
865  }
866  } else if(!join_data.empty() && game.password_required) {
867  std::string password;
868 
869  if(!gui2::dialogs::mp_join_game_password_prompt::execute(password)) {
870  return;
871  }
872 
873  join_data["password"] = password;
874  }
875 
876  mp::send_to_server(response);
877  joined_game_id_ = game.id;
878 
879  // We're all good. Close lobby and proceed to game!
880  window.set_retval(try_join ? JOIN : OBSERVE);
881 }
882 
884 {
885  try {
886  enter_game(*lobby_info_.games().at(index), mode);
887  } catch(const std::out_of_range&) {
888  // Game index was invalid!
889  ERR_LB << "Attempted to join/observe a game with index out of range: " << index << ". "
890  << "Games vector size is " << lobby_info_.games().size();
891  }
892 }
893 
894 void mp_lobby::enter_game_by_id(const int game_id, JOIN_MODE mode)
895 {
896  mp::game_info* game_ptr = lobby_info_.get_game_by_id(game_id);
897 
898  if(!game_ptr) {
899  ERR_LB << "Attempted to join/observe a game with an invalid id: " << game_id;
900  return;
901  }
902 
903  enter_game(*game_ptr, mode);
904 }
905 
907 {
909 }
910 
912 {
913  mp::send_to_server(config("refresh_lobby"));
914 }
915 
917 {
918  help::show_help();
919 }
920 
922 {
923  gui2::dialogs::preferences_dialog::display();
924 
925  refresh_lobby();
926 }
927 
929 {
931 }
932 
934 {
936 
938  for(const auto& s : utils::split(filter_text_->get_value(), ' ')) {
939  if(!info.match_string_filter(s)) {
940  return false;
941  }
942  }
943 
944  return true;
945  });
946 
947  lobby_info_.add_game_filter([this](const mp::game_info& info) {
948  return filter_friends_->get_widget_value() ? info.has_friends == true : true;
949  });
950 
951  // Unlike the friends filter, this is an inclusion filter (do we want to also show
952  // games with blocked players) rather than an exclusion filter (do we want to show
953  // only games with friends).
954  lobby_info_.add_game_filter([this](const mp::game_info& info) {
955  return filter_ignored_->get_widget_value() == false ? info.has_ignored == false : true;
956  });
957 
958  lobby_info_.add_game_filter([this](const mp::game_info& info) {
959  return filter_slots_->get_widget_value() ? info.vacant_slots > 0 : true;
960  });
961 
962  lobby_info_.add_game_filter([this](const mp::game_info& info) {
963  return info.auto_hosted == filter_auto_hosted_;
964  });
965 
967  [this](bool val) { return filter_invert_->get_widget_value() ? !val : val; });
968 }
969 
970 void mp_lobby::game_filter_keypress_callback(const SDL_Keycode key)
971 {
972  if(key == SDLK_RETURN || key == SDLK_KP_ENTER) {
974  }
975 }
976 
978 {
981 
982  lobby_player_info dlg(*chatbox_, *info, lobby_info_);
983  dlg.show();
984 
985  if(dlg.result_open_whisper()) {
988  }
989 
990  selected_game_id_ = info->game_id;
991 
992  // do not update here as it can cause issues with removing the widget
993  // from within it's event handler. Should get updated as soon as possible
994  // update_gamelist();
995  delay_playerlist_update_ = false;
996  player_list_dirty_ = true;
997  refresh_lobby();
998 }
999 
1001 {
1002  // TODO: this prefence should probably be controlled with an enum
1003  const int value = find_widget<menu_button>(get_window(), "replay_options", false).get_value();
1004  preferences::set_skip_mp_replay(value == 1);
1006 }
1007 
1008 } // namespace dialogs
Define the common log macros for the gui toolkit.
void game_filter_keypress_callback(const SDL_Keycode key)
Definition: lobby.cpp:970
window(const builder_window::window_resolution &definition)
< Needs to be initialized in show.
Definition: window.cpp:263
void switch_to_window(lobby_chat_window *t)
Switch to the window given by a valid pointer (e.g.
Definition: chatbox.cpp:123
void active_window_changed()
Definition: chatbox.cpp:108
An error occurred during when trying to communicate with the wesnothd server.
const mp::user_info * get_selected_info() const
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
Class for a toggle button.
static std::string announcements_
Definition: lobby.hpp:182
show_mode mode() const
Returns the dialog mode for this window.
Definition: window.hpp:657
std::string scenario
Definition: lobby_data.hpp:75
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:151
void set_selected(unsigned selected, bool fire_event=true)
std::string map_size_info
Definition: lobby_data.hpp:79
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:402
void set_map_data(const std::string &map_data)
Definition: minimap.cpp:77
void skip_replay_changed_callback()
Definition: lobby.cpp:1000
void show_help(const std::string &show_topic, int xloc, int yloc)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:144
const config & gamelist() const
Returns the raw game list config data.
Definition: lobby_info.hpp:58
field_bool * filter_invert_
Definition: lobby.hpp:149
std::string status
Definition: lobby_data.hpp:89
bool shuffle_sides
Definition: lobby_data.hpp:99
bool fi_vacant_slots()
Definition: lobby.cpp:46
#define ERR_LB
Definition: lobby.cpp:60
std::vector< int > gamelist_id_at_row_
Definition: lobby.hpp:171
void enter_game_by_index(const int index, JOIN_MODE mode)
Entry wrapper for enter_game, where game is located by index.
Definition: lobby.cpp:883
A menu_button is a styled_widget to choose an element from a list of elements.
Definition: menu_button.hpp:61
std::vector< std::pair< std::string, bool > > mod_info
List of modification names and whether they&#39;re installed or not.
Definition: lobby_data.hpp:83
const color_t GRAY_COLOR
bool is_game_visible(const game_info &)
Returns whether the game would be visible after the game filters are applied.
Definition: lobby_info.cpp:331
void clear_game_filters()
Clears all game filter functions.
Definition: lobby_info.hpp:72
std::size_t lobby_update_timer_
Timer for updating the lobby.
Definition: lobby.hpp:169
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:179
text_box * filter_text_
Definition: lobby.hpp:152
const color_t GOOD_COLOR
void register_hotkey(const hotkey::HOTKEY_COMMAND id, const hotkey_function &function)
Registers a hotkey.
Definition: dispatcher.cpp:149
bool has_attribute(config_key_type key) const
Definition: config.cpp:211
logger & info()
Definition: log.cpp:182
#define a
This class represents the info a client has about a game on the server.
Definition: lobby_data.hpp:65
const color_t TITLE_COLOR
addon_req addons_outcome
Definition: lobby_data.hpp:129
void set_fi_vacant_slots(bool value)
Definition: lobby.cpp:51
unsigned child_count(config_key_type key) const
Definition: config.cpp:372
#define DBG_LB
Definition: lobby.cpp:58
virtual void pre_show(window &window) override
Actions to be taken before showing the window.
Definition: lobby.cpp:564
std::string get_value() const
Base class for all widgets.
Definition: widget.hpp:53
static std::string server_information_
Definition: lobby.hpp:181
This class represents the collective information the client has about the players and games on the se...
Definition: lobby_info.hpp:31
std::string_view data
Definition: picture.cpp:206
void set_game_filter_invert(std::function< bool(bool)> value)
Sets whether the result of each game filter should be inverted.
Definition: lobby_info.hpp:78
void add_game_filter(game_filter_func func)
Adds a new filter function to be considered when apply_game_filter is called.
Definition: lobby_info.hpp:66
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:61
STL namespace.
void adjust_game_row_contents(const mp::game_info &game, grid *grid, bool add_callbacks=true)
Definition: lobby.cpp:409
#define LOG_LB
Definition: lobby.cpp:59
void set_fi_blocked_in_game(bool value)
Definition: lobby.cpp:71
void set_fi_friends_in_game(bool value)
Definition: lobby.cpp:61
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:270
bool receive_data(config &result)
Receives the next pending data pack from the server, if available.
Implements some helper classes to ease adding fields to a dialog and hide the synchronization needed...
void process_network_data(const config &data)
Definition: lobby.cpp:744
void update_selected_game()
Definition: lobby.cpp:539
static std::string _(const char *str)
Definition: gettext.hpp:93
void enter_selected_game(JOIN_MODE mode)
Enter game by index, where index is the selected game listbox row.
Definition: lobby.cpp:906
bool show(const unsigned auto_close_time=0)
Shows the window.
field_bool * filter_ignored_
Definition: lobby.hpp:147
void update_visible_games()
Definition: lobby.cpp:368
std::string name
Definition: lobby_data.hpp:74
void process_gamelist(const config &data)
Process a full game list.
Definition: lobby_info.cpp:120
std::string time_limit
Definition: lobby_data.hpp:90
void apply_game_filter()
Generates a new list of games that match the current filter functions and inversion setting...
Definition: lobby_info.cpp:342
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:245
static lg::log_domain log_lobby("lobby")
bool fi_blocked_in_game()
Definition: lobby.cpp:66
bool logged_in_as_moderator()
Gets whether the currently logged-in user is a moderator.
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
Definition: lobby_info.cpp:293
virtual void set_label(const t_string &label)
The listbox class.
Definition: listbox.hpp:45
std::string get_profile_link(int user_id)
Gets the forum profile link for the given user.
std::string span_color(const color_t &color)
Returns a Pango formatting string using the provided color_t object.
Base container class.
Definition: grid.hpp:31
bool password_required
Definition: lobby_data.hpp:103
Desktop environment interaction functions.
listbox * gamelistbox_
Definition: lobby.hpp:140
void do_notify(notify_mode mode, const std::string &sender, const std::string &message)
Definition: lobby_info.cpp:62
void set_active_window_changed_callback(const std::function< void(void)> &f)
Definition: chatbox.hpp:82
std::map< std::string, t_string > widget_item
Definition: widget.hpp:32
void enter_game_by_id(const int game_id, JOIN_MODE mode)
Entry wrapper for enter_game, where game is located by game id.
Definition: lobby.cpp:894
void set_tooltip(const t_string &tooltip)
bool ad_hoc_addon_fetch_session(const std::vector< std::string > &addon_ids)
Conducts an ad-hoc add-ons server connection to download an add-on with a particular id and all it&#39;s ...
Definition: manager_ui.cpp:259
This file contains the settings handling of the widget library.
void set_show_even_without_video(const bool show_even_without_video)
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:120
std::unique_ptr< plugins_context > plugins_context_
void set_visible(const visibility visible)
Definition: widget.cpp:456
unsigned lobby_network_timer
Definition: game_config.cpp:87
Implements a quit confirmation dialog.
void show_preferences_button_callback()
Definition: lobby.cpp:921
lobby_chat_window * whisper_window_open(const std::string &name, bool open_new)
Check if a whisper window for user "name" is open, if open_new is true then it will be created if not...
Definition: chatbox.cpp:385
std::string vision
Definition: lobby_data.hpp:88
bool blindfold_replay()
Definition: game.cpp:592
A class that represents a TCP/IP connection to the wesnothd server.
void set_fi_invert(bool value)
Definition: lobby.cpp:41
virtual void send_chat_message(const std::string &message, bool allies_only) override
Inherited form chat_handler.
Definition: chatbox.cpp:247
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
void update_gamelist_filter()
Definition: lobby.cpp:515
void load_log(std::map< std::string, chatroom_log > &log, bool show_lobby)
Definition: chatbox.cpp:93
disp_status display_status
Definition: lobby_data.hpp:118
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
Definition: grid.cpp:645
std::string map_data
Definition: lobby_data.hpp:73
Modify, read and display user preferences.
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:126
Shows a yes and no button.
Definition: message.hpp:81
virtual void set_use_markup(bool use_markup)
void process_gamelist_diff(const config &data)
Definition: lobby.cpp:775
void update_gamelist_diff()
Definition: lobby.cpp:261
std::string gold
Definition: lobby_data.hpp:85
const color_t YELLOW_COLOR
void set_blindfold_replay(bool value)
Definition: game.cpp:597
std::function< void()> begin_state_sync()
Updates the game pointer list and returns a second stage cleanup function to be called after any acti...
Definition: lobby_info.cpp:261
lobby_player_list_helper player_list_
Definition: lobby.hpp:156
void tab_switch_callback()
Definition: lobby.cpp:687
window * get_window()
Returns a pointer to the dialog&#39;s window.
static bool quit()
Shows the quit confirmation if needed.
int get_retval()
Definition: window.hpp:365
void user_dialog_callback(const mp::user_info *info)
Definition: lobby.cpp:977
void process_network_data(const ::config &data)
Definition: chatbox.cpp:628
const boost::dynamic_bitset & games_visibility() const
Definition: lobby_info.hpp:103
bool use_map_settings
Definition: lobby_data.hpp:100
std::string name
Definition: lobby_data.hpp:54
bool open_object([[maybe_unused]] const std::string &path_or_url)
Definition: open.cpp:47
void network_handler()
Network polling callback.
Definition: lobby.cpp:708
void enter_game(const mp::game_info &game, JOIN_MODE mode)
Exits the lobby and enters the given game.
Definition: lobby.cpp:797
std::size_t i
Definition: function.cpp:968
widget_data make_game_row_data(const mp::game_info &game)
Definition: lobby.cpp:380
static void display(const std::string &info, const std::string &announcements)
The display function.
const std::vector< game_info * > & games() const
Definition: lobby_info.hpp:98
static map_location::DIRECTION s
double g
Definition: astarsearch.cpp:65
bool gamelist_initialized() const
Definition: lobby_info.hpp:118
std::string password(const std::string &server, const std::string &login)
std::string type_marker
Definition: lobby_data.hpp:76
Contains the gui2 timer routines.
T get_widget_value()
Gets the value of the field.
Definition: field.hpp:379
std::size_t add_timer(const uint32_t interval, const std::function< void(std::size_t id)> &callback, const bool repeat)
Adds a new timer.
Definition: timer.cpp:127
bool can_observe() const
Definition: lobby_data.cpp:480
int w
void set_skip_mp_replay(bool value)
Definition: game.cpp:587
unsigned last_lobby_update_
Definition: lobby.hpp:162
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:358
#define N_(String)
Definition: gettext.hpp:101
mp_lobby(mp::lobby_info &info, wesnothd_connection &connection, int &joined_game)
Definition: lobby.cpp:72
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
Base class for all visible items.
const std::string unicode_bullet
Definition: constants.cpp:47
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
bool skip_mp_replay()
Definition: game.cpp:582
The basic minimap class.
Definition: minimap.hpp:45
void process_gamelist(const config &data)
Definition: lobby.cpp:765
bool fi_invert()
Definition: lobby.cpp:36
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
Definition: lobby_info.cpp:140
virtual void set_active(const bool active) override
See styled_widget::set_active.
config & add_child(config_key_type key)
Definition: config.cpp:514
std::string era
Definition: lobby_data.hpp:80
bool grid()
Definition: general.cpp:565
bool fi_friends_in_game()
Definition: lobby.cpp:56
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:232
This class represents the information a client has about another player.
Definition: lobby_data.hpp:32
The user sets the widget visible, that means:
void set_allow_plugin_skip(const bool allow_plugin_skip)
double t
Definition: astarsearch.cpp:65
The user sets the widget hidden, that means:
std::vector< std::string > split(const config_attribute_value &val)
field_bool * filter_slots_
Definition: lobby.hpp:148
std::size_t vacant_slots
Definition: lobby_data.hpp:91
std::map< std::string, chatroom_log > default_chat_log
Definition: chat_log.cpp:18
unsigned lobby_refresh
Definition: game_config.cpp:88
Abstract base class for all modal dialogs.
Functions to load and save images from/to disk.
void connect_signal_pre_key_press(dispatcher &dispatcher, const signal_keyboard &signal)
Connects the signal for &#39;snooping&#39; on the keypress.
Definition: dispatcher.cpp:174
void connect_signal_mouse_left_double_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button double click.
Definition: dispatcher.cpp:200
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:81
std::string message
Definition: exceptions.hpp:30
wesnothd_connection & network_connection_
Definition: lobby.hpp:166
std::vector< required_addon > required_addons
Definition: lobby_data.hpp:128
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:204
field_bool * filter_friends_
Definition: lobby.hpp:146
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:35
#define e
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
mp::lobby_info & lobby_info_
Definition: lobby.hpp:142
Dialog was closed with the OK button.
Definition: retval.hpp:35
const std::vector< user_info > & users() const
Definition: lobby_info.hpp:108
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
bool can_join() const
Definition: lobby_data.cpp:475
mock_char c
Enables auto close.
Definition: message.hpp:71
void update(const std::vector< mp::user_info > &user_info, int focused_game)
Updates the tree contents based on the given user data.
virtual void post_show(window &window) override
Actions to be taken after the window has been shown.
Definition: lobby.cpp:701
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:66
lobby_chat_window * room_window_open(const std::string &room, const bool open_new, const bool allow_close=true)
Check if a room window for "room" is open, if open_new is true then it will be created if not found...
Definition: chatbox.cpp:379
bool empty() const
Definition: config.cpp:941
Networked add-ons (campaignd) client interface.
bool exit_hook(window &window)
Definition: lobby.cpp:559
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:205
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
const color_t BAD_COLOR
std::string xp
Definition: lobby_data.hpp:87
bool match_string_filter(const std::string &filter) const
Definition: lobby_data.cpp:502
void set_always_save_fields(const bool always_save_fields)
bool remove_timer(const std::size_t id)
Removes a timer.
Definition: timer.cpp:168
#define SCOPE_LB
Definition: lobby.cpp:61