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