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