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