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