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