The Battle for Wesnoth  1.19.8+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" << markup::tag("big", markup::span_color(font::TITLE_COLOR, _("Settings"))) << "\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  SCOPE_LB;
558 
559  gamelistbox_ = find_widget<listbox>("game_list", false, true);
560 
562  std::bind(&mp_lobby::update_selected_game, this));
563 
564  player_list_.init(*this);
565 
566  set_enter_disabled(true);
567 
568  // Exit hook to add a confirmation when quitting the Lobby.
569  set_exit_hook(window::exit_hook::always, [this] { return get_retval() == retval::CANCEL ? quit() : true; });
570 
571  chatbox_ = find_widget<chatbox>("chat", false, true);
572 
574 
577 
578  find_widget<button>("create").set_retval(CREATE);
579 
581  find_widget<button>("show_preferences"),
583 
585  find_widget<button>("join_global"),
586  std::bind(&mp_lobby::enter_selected_game, this, DO_JOIN));
587 
588  find_widget<button>("join_global").set_active(false);
589 
591  find_widget<button>("observe_global"),
592  std::bind(&mp_lobby::enter_selected_game, this, DO_OBSERVE));
593 
595  find_widget<button>("server_info"),
596  std::bind(&mp_lobby::show_server_info, this));
597 
598  find_widget<button>("observe_global").set_active(false);
599 
600  menu_button& replay_options = find_widget<menu_button>("replay_options");
601 
602  if(prefs::get().skip_mp_replay()) {
603  replay_options.set_selected(1);
604  }
605 
606  if(prefs::get().blindfold_replay()) {
607  replay_options.set_selected(2);
608  }
609 
610  connect_signal_notify_modified(replay_options,
611  std::bind(&mp_lobby::skip_replay_changed_callback, this));
612 
613  filter_text_ = find_widget<text_box>("filter_text", false, true);
614 
616  *filter_text_,
617  std::bind(&mp_lobby::game_filter_keypress_callback, this, std::placeholders::_5));
618 
619  chatbox_->room_window_open(N_("lobby"), true, false);
621 
623 
624  // Force first update to be directly.
625  update_gamelist();
627 
628  // TODO: currently getting a crash in the chatbox if we use
629  // -- vultraz, 2017-11-10
630  //mp_lobby::network_handler();
631 
634 
635  //
636  // Profile box
637  //
638  if(panel* profile_panel = find_widget<panel>("profile", false, false)) {
639  auto your_info = std::find_if(lobby_info_.users().begin(), lobby_info_.users().end(),
640  [](const auto& u) { return u.get_relation() == mp::user_info::user_relation::ME; });
641 
642  if(your_info != lobby_info_.users().end()) {
643  profile_panel->find_widget<label>("username").set_label(your_info->name);
644 
645  auto& profile_button = profile_panel->find_widget<button>("view_profile");
646  connect_signal_mouse_left_click(profile_button, std::bind(&mp_lobby::open_profile_url, this));
647 
648  auto& history_button = profile_panel->find_widget<button>("view_match_history");
649  connect_signal_mouse_left_click(history_button, std::bind(&mp_lobby::open_match_history, this));
650  }
651  }
652 
653  listbox& tab_bar = find_widget<listbox>("games_list_tab_bar");
655 
656  // Set up Lua plugin context
657  plugins_context_.reset(new plugins_context("Multiplayer Lobby"));
658 
659  plugins_context_->set_callback("join", [&, this](const config&) {
661  }, true);
662 
663  plugins_context_->set_callback("observe", [&, this](const config&) {
665  }, true);
666 
667  plugins_context_->set_callback("create", [this](const config&) { set_retval(CREATE); }, true);
668  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
669 
670  plugins_context_->set_callback("chat", [this](const config& cfg) { chatbox_->send_chat_message(cfg["message"], false); }, true);
671  plugins_context_->set_callback("select_game", [this](const config& cfg) {
672  selected_game_id_ = cfg.has_attribute("id") ? cfg["id"].to_int() : lobby_info_.games()[cfg["index"].to_int()]->id;
673  }, true);
674 
675  plugins_context_->set_accessor("game_list", [this](const config&) { return lobby_info_.gamelist(); });
676 }
677 
679 {
680  filter_auto_hosted_ = find_widget<listbox>("games_list_tab_bar").get_selected_row() == 1;
682 }
683 
685 {
687  if(info && info->forum_id != 0) {
689  }
690 }
691 
693 {
696  plugins_context_.reset();
697 }
698 
700 {
702  if(info) {
704  }
705 }
706 
708 {
709  try {
710  config data;
713  }
714  } catch (const wesnothd_error& e) {
715  LOG_LB << "caught wesnothd_error in network_handler: " << e.message;
716  throw;
717  }
718 
719  if(std::chrono::steady_clock::now() - last_lobby_update_ < game_config::lobby_refresh) {
720  return;
721  }
722 
724  //don't process a corrupted gamelist further to prevent crashes later.
725  return;
726  }
727 
731  } else {
732  update_gamelist();
733  gamelist_diff_update_ = true;
734  }
735  }
736 
740  }
741 }
742 
744 {
745  if(auto error = data.optional_child("error")) {
746  throw wesnothd_error(error["message"]);
747  } else if(data.has_child("gamelist")) {
749  } else if(auto gamelist_diff = data.optional_child("gamelist_diff")) {
750  process_gamelist_diff(*gamelist_diff);
751  } else if(auto info = data.optional_child("message")) {
752  if(info["type"] == "server_info") {
753  server_information_ = info["message"].str();
754  return;
755  } else if(info["type"] == "announcements") {
756  announcements_ = info["message"].str();
757  return;
758  }
759  }
760 
762 }
763 
765 {
767 
769  DBG_LB << "Received gamelist";
770  gamelist_dirty_ = true;
771  gamelist_diff_update_ = false;
772 }
773 
775 {
777 
779  DBG_LB << "Received gamelist diff";
780  gamelist_dirty_ = true;
781  } else {
782  ERR_LB << "process_gamelist_diff failed!";
783  refresh_lobby();
784  }
785  const int joined = data.child_count("insert_child");
786  const int left = data.child_count("remove_child");
787  if(joined > 0 || left > 0) {
788  if(left > joined) {
790  } else {
792  }
793  }
794 }
795 
797 {
798  switch(mode) {
799  case DO_JOIN:
800  if(!game.can_join()) {
801  ERR_LB << "Attempted to join a game with no vacant slots";
802  return;
803  }
804 
805  break;
806  case DO_OBSERVE:
807  if(!game.can_observe()) {
808  ERR_LB << "Attempted to observe a game with observers disabled";
809  return;
810  }
811 
812  break;
813  case DO_EITHER:
814  if(game.can_join()) {
815  mode = DO_JOIN;
816  } else if(game.can_observe()) {
817  mode = DO_OBSERVE;
818  } else {
819  DBG_LB << "Cannot join or observe a game.";
820  return;
821  }
822 
823  break;
824  }
825 
826  // prompt moderators for whether they want to join a game with observers disabled
827  if(!game.observers && mp::logged_in_as_moderator()) {
828  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) {
829  return;
830  }
831  }
832 
833  const bool try_join = mode == DO_JOIN;
834  const bool try_obsv = mode == DO_OBSERVE;
835 
836  // Prompt user to download this game's required addons if its requirements have not been met
837  if(game.addons_outcome != mp::game_info::addon_req::SATISFIED) {
838  if(game.required_addons.empty()) {
839  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."));
840  return;
841  }
842 
843  if(!handle_addon_requirements_gui(game.required_addons, game.addons_outcome)) {
844  return;
845  }
846 
847  // Addons have been downloaded, so the game_config and installed addons list need to be reloaded.
848  // The lobby is closed and reopened.
850  return;
851  }
852 
853  config response;
854 
855  config& join_data = response.add_child("join");
856  join_data["id"] = std::to_string(game.id);
857  join_data["observe"] = try_obsv;
858 
859  if(mp::logged_in_as_moderator() && game.password_required) {
860  if(gui2::show_message(_("Join"), _("This game is password protected. Join using moderator rights anyway?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
861  return;
862  }
863  } else if(!join_data.empty() && game.password_required) {
864  std::string password;
865 
866  if(!gui2::dialogs::mp_join_game_password_prompt::execute(password)) {
867  return;
868  }
869 
870  join_data["password"] = password;
871  }
872 
873  mp::send_to_server(response);
874  joined_game_id_ = game.id;
875 
876  // We're all good. Close lobby and proceed to game!
877  set_retval(try_join ? JOIN : OBSERVE);
878 }
879 
881 {
882  try {
884  } catch(const std::out_of_range&) {
885  // Game index was invalid!
886  ERR_LB << "Attempted to join/observe a game with index out of range: " << index << ". "
887  << "Games vector size is " << lobby_info_.games().size();
888  }
889 }
890 
891 void mp_lobby::enter_game_by_id(const int game_id, JOIN_MODE mode)
892 {
893  mp::game_info* game_ptr = lobby_info_.get_game_by_id(game_id);
894 
895  if(!game_ptr) {
896  ERR_LB << "Attempted to join/observe a game with an invalid id: " << game_id;
897  return;
898  }
899 
900  enter_game(*game_ptr, mode);
901 }
902 
904 {
906 }
907 
909 {
910  mp::send_to_server(config("refresh_lobby"));
911 }
912 
914 {
915  help::show_help();
916 }
917 
919 {
920  gui2::dialogs::preferences_dialog::display();
921 
922  refresh_lobby();
923 }
924 
926 {
928 }
929 
931 {
933 
935  for(const auto& s : utils::split(filter_text_->get_value(), ' ')) {
936  if(!info.match_string_filter(s)) {
937  return false;
938  }
939  }
940 
941  return true;
942  });
943 
944  lobby_info_.add_game_filter([this](const mp::game_info& info) {
945  return filter_friends_->get_widget_value() ? info.has_friends == true : true;
946  });
947 
948  // Unlike the friends filter, this is an inclusion filter (do we want to also show
949  // games with blocked players) rather than an exclusion filter (do we want to show
950  // only games with friends).
951  lobby_info_.add_game_filter([this](const mp::game_info& info) {
952  return filter_ignored_->get_widget_value() == false ? info.has_ignored == false : true;
953  });
954 
955  lobby_info_.add_game_filter([this](const mp::game_info& info) {
956  return filter_slots_->get_widget_value() ? info.vacant_slots > 0 : true;
957  });
958 
959  lobby_info_.add_game_filter([this](const mp::game_info& info) {
960  return info.auto_hosted == filter_auto_hosted_;
961  });
962 
963  lobby_info_.set_game_filter_invert(
964  [this](bool val) { return filter_invert_->get_widget_value() ? !val : val; });
965 }
966 
967 void mp_lobby::game_filter_keypress_callback(const SDL_Keycode key)
968 {
969  if(key == SDLK_RETURN || key == SDLK_KP_ENTER) {
971  }
972 }
973 
975 {
978 
980  dlg.show();
981 
982  if(dlg.result_open_whisper()) {
985  }
986 
987  selected_game_id_ = info->game_id;
988 
989  // do not update here as it can cause issues with removing the widget
990  // from within it's event handler. Should get updated as soon as possible
991  // update_gamelist();
992  delay_playerlist_update_ = false;
993  player_list_dirty_ = true;
994  refresh_lobby();
995 }
996 
998 {
999  // TODO: this prefence should probably be controlled with an enum
1000  const int value = find_widget<menu_button>("replay_options").get_value();
1001  prefs::get().set_skip_mp_replay(value == 1);
1002  prefs::get().set_blindfold_replay(value == 2);
1003 }
1004 
1005 } // 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:158
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
bool empty() const
Definition: config.cpp:845
config & add_child(config_key_type key)
Definition: config.cpp:436
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:381
virtual void send_chat_message(const std::string &message, bool allies_only) override
Inherited form chat_handler.
Definition: chatbox.cpp:253
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:387
void switch_to_window(lobby_chat_window *t)
Switch to the window given by a valid pointer (e.g.
Definition: chatbox.cpp:130
void process_network_data(const ::config &data)
Definition: chatbox.cpp:630
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:115
@ 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:903
void process_gamelist(const config &data)
Definition: lobby.cpp:764
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:692
mp::lobby_info & lobby_info_
Definition: lobby.hpp:140
text_box * filter_text_
Definition: lobby.hpp:150
lobby_player_list_helper player_list_
Definition: lobby.hpp:154
void skip_replay_changed_callback()
Definition: lobby.cpp:997
static std::string announcements_
Definition: lobby.hpp:180
wesnothd_connection & network_connection_
Definition: lobby.hpp:164
void update_gamelist_filter()
Definition: lobby.cpp:511
listbox * gamelistbox_
Definition: lobby.hpp:138
void process_network_data(const config &data)
Definition: lobby.cpp:743
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:967
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:891
void update_gamelist_diff()
Definition: lobby.cpp:257
void show_preferences_button_callback()
Definition: lobby.cpp:918
static std::string server_information_
Definition: lobby.hpp:179
void enter_game(const mp::game_info &game, JOIN_MODE mode)
Exits the lobby and enters the given game.
Definition: lobby.cpp:796
void user_dialog_callback(const mp::user_info *info)
Definition: lobby.cpp:974
void tab_switch_callback()
Definition: lobby.cpp:678
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:774
std::size_t lobby_update_timer_
Timer for updating the lobby.
Definition: lobby.hpp:167
virtual void pre_show() override
Actions to be taken before showing the window.
Definition: lobby.cpp:555
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:169
std::chrono::steady_clock::time_point last_lobby_update_
Definition: lobby.hpp:160
void network_handler()
Network polling callback.
Definition: lobby.cpp:707
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:880
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_view id, const bool must_be_active) override
See widget::find.
Definition: grid.cpp:645
The listbox class.
Definition: listbox.hpp:41
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:171
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:92
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:267
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:280
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:112
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:153
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:305
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:159
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
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:
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:747
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:321
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:695
void keyboard_capture(widget *widget)
Definition: window.cpp:1193
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition: window.hpp:448
int get_retval()
Definition: window.hpp:402
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:1029
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:319
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:153
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:87
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified formatting tag.
Definition: markup.hpp:50
std::string span_size(std::string_view size, Args &&... data)
Applies Pango markup to the input specifying its display size.
Definition: markup.hpp:123
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 size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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