The Battle for Wesnoth  1.19.28+dev
lobby.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2025
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.hpp"
41 #include "addon/manager_ui.hpp"
42 #include "chat_log.hpp"
43 #include "desktop/open.hpp"
44 #include "serialization/markup.hpp"
45 #include "formatter.hpp"
46 #include "formula/string_utils.hpp"
48 #include "gettext.hpp"
49 #include "help/help.hpp"
50 #include "wesnothd_connection.hpp"
51 
52 #include <functional>
53 
54 static lg::log_domain log_lobby("lobby");
55 #define DBG_LB LOG_STREAM(debug, log_lobby)
56 #define LOG_LB LOG_STREAM(info, log_lobby)
57 #define ERR_LB LOG_STREAM(err, log_lobby)
58 #define SCOPE_LB log_scope2(log_lobby, __func__)
59 
60 namespace gui2::dialogs
61 {
62 REGISTER_DIALOG(mp_lobby)
63 
64 bool mp_lobby::logout_prompt()
65 {
66  return show_prompt(_("Do you really want to log out?"));
67 }
68 
70  : modal_dialog(window_id())
71  , quit_confirmation(&mp_lobby::logout_prompt)
72  , gamelistbox_(nullptr)
73  , lobby_info_(info)
74  , chatbox_(nullptr)
75  , filter_friends_(register_bool("filter_with_friends",
76  true,
77  []() {return prefs::get().fi_friends_in_game();},
78  [](bool v) {prefs::get().set_fi_friends_in_game(v);},
79  std::bind(&mp_lobby::update_gamelist_filter, this)))
80  , filter_ignored_(register_bool("filter_with_ignored",
81  true,
82  []() {return prefs::get().fi_blocked_in_game();},
83  [](bool v) {prefs::get().set_fi_blocked_in_game(v);},
84  std::bind(&mp_lobby::update_gamelist_filter, this)))
85  , filter_slots_(register_bool("filter_vacant_slots",
86  true,
87  []() {return prefs::get().fi_vacant_slots();},
88  [](bool v) {prefs::get().set_fi_vacant_slots(v);},
89  std::bind(&mp_lobby::update_gamelist_filter, this)))
90  , filter_invert_(register_bool("filter_invert",
91  true,
92  []() {return prefs::get().fi_invert();},
93  [](bool v) {prefs::get().set_fi_invert(v);},
94  std::bind(&mp_lobby::update_gamelist_filter, this)))
95  , filter_auto_hosted_(false)
96  , filter_game_presets_(false)
97  , filter_text_(nullptr)
98  , selected_game_id_()
99  , player_list_(std::bind(&mp_lobby::user_dialog_callback, this, std::placeholders::_1),
100  std::bind(&mp_lobby::select_game_by_player, this, std::placeholders::_1))
101  , player_list_dirty_(true)
102  , gamelist_dirty_(true)
103  , last_lobby_update_()
104  , gamelist_diff_update_(true)
105  , network_connection_(connection)
106  , lobby_update_timer_(0)
107  , gamelist_id_at_row_()
108  , delay_playerlist_update_(false)
109  , delay_gamelist_update_(false)
110  , joined_game_id_(joined_game)
111  , queue_game_server_preset_()
112 {
113  set_show_even_without_video(true);
114  set_allow_plugin_skip(false);
115  set_always_save_fields(true);
116 
117  /*** Local hotkeys. ***/
119  [](auto&&...) { help::show_help(); return true; });
120 
122  [this](auto&&...) { show_preferences_button_callback(); return true; });
123 }
124 
126 {
128  {
129  l.delay_gamelist_update_ = true;
130  }
132  {
133  l.delay_gamelist_update_ = false;
134  }
136 };
137 
139 {
140  if(lobby_update_timer_) {
142  }
143 }
144 
145 namespace
146 {
147 void modify_grid_with_data(grid* grid, const widget_data& map)
148 {
149  for(const auto& v : map) {
150  const std::string& key = v.first;
151  const widget_item& strmap = v.second;
152 
153  widget* w = grid->find(key, false);
154  if(!w) {
155  continue;
156  }
157 
158  styled_widget* c = dynamic_cast<styled_widget*>(w);
159  if(!c) {
160  continue;
161  }
162 
163  for(const auto & vv : strmap) {
164  if(vv.first == "label") {
165  c->set_label(vv.second);
166  } else if(vv.first == "tooltip") {
167  c->set_tooltip(vv.second);
168  }
169  }
170  }
171 }
172 
173 bool handle_addon_requirements_gui(const std::vector<mp::game_info::required_addon>& reqs, mp::game_info::addon_req addon_outcome)
174 {
175  if(addon_outcome == mp::game_info::addon_req::CANNOT_SATISFY) {
176  std::string e_title = _("Incompatible User-made Content");
177  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.");
178 
179  err_msg +="\n\n";
180  err_msg += _("Details:");
181  err_msg += "\n";
182 
183  for(const mp::game_info::required_addon & a : reqs) {
184  if (a.outcome == mp::game_info::addon_req::CANNOT_SATISFY) {
185  err_msg += font::unicode_bullet + " " + a.message + "\n";
186  }
187  }
188  gui2::show_message(e_title, err_msg, message::auto_close, true);
189 
190  return false;
191  } else if(addon_outcome == mp::game_info::addon_req::NEED_DOWNLOAD) {
192  std::string e_title = _("Missing User-made Content");
193  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?");
194 
195  err_msg +="\n\n";
196  err_msg += _("Details:");
197  err_msg += "\n";
198 
199  std::vector<std::string> needs_download;
200  for(const mp::game_info::required_addon & a : reqs) {
201  if(a.outcome == mp::game_info::addon_req::NEED_DOWNLOAD) {
202  err_msg += font::unicode_bullet + " " + a.message + "\n";
203 
204  needs_download.push_back(a.addon_id);
205  }
206  }
207 
208  assert(needs_download.size() > 0);
209 
210  if(gui2::show_message(e_title, err_msg, message::yes_no_buttons, true) == gui2::retval::OK) {
211  // Begin download session
212  try {
213  return ad_hoc_addon_fetch_session(needs_download);
214  } catch (const addons_client::user_exit&) {
215  } catch (const addons_client::user_disconnect&) {
216  }
217  }
218  }
219 
220  return false;
221 }
222 
223 } // end anonymous namespace
224 
226 {
227  if(delay_gamelist_update_) return;
228 
229  SCOPE_LB;
230  gamelistbox_->clear();
231  gamelist_id_at_row_.clear();
232 
233  const auto finish_state_sync = lobby_info_.begin_state_sync();
234 
235  int select_row = -1;
236  for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
237  const mp::game_info& game = *lobby_info_.games()[i];
238 
239  if(game.id == selected_game_id_) {
240  select_row = i;
241  }
242 
243  gamelist_id_at_row_.push_back(game.id);
244  LOG_LB << "Adding game to listbox (1)" << game.id;
246 
248  }
249 
250  if(select_row >= 0 && select_row != gamelistbox_->get_selected_row()) {
251  gamelistbox_->select_row(select_row);
252  }
253 
255  gamelist_dirty_ = false;
256  last_lobby_update_ = std::chrono::steady_clock::now();
257  finish_state_sync();
259 }
260 
262 {
263  if(delay_gamelist_update_) return;
264 
265  SCOPE_LB;
266  int select_row = -1;
267  unsigned list_i = 0;
268  int list_rows_deleted = 0;
269 
270  const auto finish_state_sync = lobby_info_.begin_state_sync();
271 
272  std::vector<int> next_gamelist_id_at_row;
273  for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
274  const mp::game_info& game = *lobby_info_.games()[i];
275 
276  if(game.display_status == mp::game_info::disp_status::NEW) {
277  // call void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
278  // sender will be the game_info.scenario (std::string) and message will be game_info.name (std::string)
281  }
282 
283  LOG_LB << "Adding game to listbox " << game.id;
284 
285  if(list_i != gamelistbox_->get_item_count()) {
287  DBG_LB << "Added a game listbox row not at the end" << list_i
288  << " " << gamelistbox_->get_item_count();
289  list_rows_deleted--;
290  } else {
292  }
293 
296 
297  list_i++;
298  next_gamelist_id_at_row.push_back(game.id);
299  } else {
300  if(list_i >= gamelistbox_->get_item_count()) {
301  ERR_LB << "Ran out of listbox items -- triggering a full refresh";
302  refresh_lobby();
303  return;
304  }
305 
306  if(list_i + list_rows_deleted >= gamelist_id_at_row_.size()) {
307  ERR_LB << "gamelist_id_at_row_ overflow! " << list_i << " + "
308  << list_rows_deleted
309  << " >= " << gamelist_id_at_row_.size()
310  << " -- triggering a full refresh";
311  refresh_lobby();
312  return;
313  }
314 
315  int listbox_game_id = gamelist_id_at_row_[list_i + list_rows_deleted];
316  if(game.id != listbox_game_id) {
317  ERR_LB << "Listbox game id does not match expected id "
318  << listbox_game_id << " " << game.id << " (row " << list_i << ")";
319  refresh_lobby();
320  return;
321  }
322 
323  if(game.display_status == mp::game_info::disp_status::UPDATED) {
324  LOG_LB << "Modifying game in listbox " << game.id << " (row " << list_i << ")";
325  grid* grid = gamelistbox_->get_row_grid(list_i);
326  modify_grid_with_data(grid, make_game_row_data(game));
328  ++list_i;
329  next_gamelist_id_at_row.push_back(game.id);
330  } else if(game.display_status == mp::game_info::disp_status::DELETED) {
331  LOG_LB << "Deleting game from listbox " << game.id << " (row "
332  << list_i << ")";
333  gamelistbox_->remove_row(list_i);
334  ++list_rows_deleted;
335  } else {
336  // clean
337  LOG_LB << "Clean game in listbox " << game.id << " (row " << list_i << ")";
338  next_gamelist_id_at_row.push_back(game.id);
339  ++list_i;
340  }
341  }
342  }
343 
344  for(unsigned i = 0; i < next_gamelist_id_at_row.size(); ++i) {
345  if(next_gamelist_id_at_row[i] == selected_game_id_) {
346  select_row = i;
347  }
348  }
349 
350  next_gamelist_id_at_row.swap(gamelist_id_at_row_);
351  if(select_row >= static_cast<int>(gamelistbox_->get_item_count())) {
352  ERR_LB << "Would select a row beyond the listbox" << select_row << " "
354  select_row = gamelistbox_->get_item_count() - 1;
355  }
356 
357  if(select_row >= 0 && select_row != gamelistbox_->get_selected_row()) {
358  gamelistbox_->select_row(select_row);
359  }
360 
362  gamelist_dirty_ = false;
363  last_lobby_update_ = std::chrono::steady_clock::now();
364  finish_state_sync();
366 }
367 
369 {
370  const std::string games_string = VGETTEXT("Games: showing $num_shown out of $num_total", {
371  {"num_shown", std::to_string(lobby_info_.games_visibility().count())},
372  {"num_total", std::to_string(lobby_info_.games().size())}
373  });
374 
375  gamelistbox_->find_widget<label>("map").set_label(games_string);
376 
378 }
379 
381 {
383  widget_item item;
384 
385  item["use_markup"] = "true";
386 
387  color_t color_string;
388  if(game.vacant_slots > 0) {
389  color_string = (game.reloaded || game.started) ? font::YELLOW_COLOR : font::GOOD_COLOR;
390  }
391 
392  const std::string scenario_text = VGETTEXT("$game_name (Era: $era_name)", {
393  {"game_name", game.scenario},
394  {"era_name", game.era}
395  });
396 
397  item["label"] = game.vacant_slots > 0 ? markup::span_color(color_string, game.name) : game.name;
398  data.emplace("name", item);
399 
400  item["label"] = markup::span_color(font::GRAY_COLOR, game.type_marker, markup::italic(scenario_text));
401  data.emplace("scenario", item);
402 
403  item["label"] = markup::span_color(color_string, game.status);
404  data.emplace("status", item);
405 
406  return data;
407 }
408 
410 {
412  grid->find_widget<styled_widget>("status").set_use_markup(true);
413 
414  toggle_panel& row_panel = grid->find_widget<toggle_panel>("panel");
415 
416  //
417  // Game info
418  //
419  std::ostringstream ss;
420 
421  const auto mark_missing = [&ss]() {
422  ss << ' ' << markup::span_color(font::BAD_COLOR, "(", _("era_or_mod^not installed"), ")");
423  };
424 
425  ss << markup::tag("big", markup::span_color(font::TITLE_COLOR, _("Era"))) << "\n" << game.era;
426 
427  if(!game.have_era) {
428  // NOTE: not using colorize() here deliberately to avoid awkward string concatenation.
429  mark_missing();
430  }
431 
432  ss << "\n\n" << markup::tag("big", markup::span_color(font::TITLE_COLOR, _("Modifications"))) << "\n";
433 
434  auto mods = game.mod_info;
435 
436  if(mods.empty()) {
437  ss << _("active_modifications^None") << "\n";
438  } else {
439  for(const auto& mod : mods) {
440  ss << mod.first;
441 
442  if(!mod.second) {
443  mark_missing();
444  }
445 
446  ss << '\n';
447  }
448  }
449 
450  // TODO: move to some general area of the code.
451  const auto yes_or_no = [](bool val) { return val ? _("yes") : _("no"); };
452 
453  ss << "\n" << markup::tag("big", markup::span_color(font::TITLE_COLOR, _("Settings"))) << "\n";
454  ss << _("Experience modifier:") << " " << game.xp << "\n";
455  ss << _("Gold per village:") << " " << game.gold << "\n";
456  ss << _("Map size:") << " " << game.map_size_info << "\n";
457  ss << _("Reloaded:") << " " << yes_or_no(game.reloaded) << "\n";
458  ss << _("Shared vision:") << " " << game.vision << "\n";
459  ss << _("Shuffle sides:") << " " << yes_or_no(game.shuffle_sides) << "\n";
460  ss << _("Time limit:") << " " << game.time_limit << "\n";
461  ss << _("Use map settings:") << " " << yes_or_no(game.use_map_settings);
462 
463  image& info_icon = grid->find_widget<image>("game_info");
464 
465  if(!game.have_era || !game.have_all_mods || !game.required_addons.empty()) {
466  info_icon.set_label("icons/icon-info-error.png");
467 
468  ss << "\n\n" << markup::span_color("#f00", markup::span_size("x-large", "! "));
469  ss << _("One or more add-ons need to be installed\nin order to join this game.");
470  } else {
471  info_icon.set_label("icons/icon-info.png");
472  }
473 
474  info_icon.set_tooltip(ss.str());
475 
476  //
477  // Password icon
478  //
479  image& password_icon = grid->find_widget<image>("needs_password");
480 
481  if(game.password_required) {
483  } else {
485  }
486 
487  //
488  // Observer icon
489  //
490  image& observer_icon = grid->find_widget<image>("observer_icon");
491 
492  if(game.observers) {
493  observer_icon.set_label("misc/eye.png");
494  observer_icon.set_tooltip( _("Observers allowed"));
495  } else {
496  observer_icon.set_label("misc/no_observer.png");
497  observer_icon.set_tooltip( _("Observers not allowed"));
498  }
499 
500  //
501  // Minimap
502  //
503  minimap& map = grid->find_widget<minimap>("minimap");
504 
505  map.set_map_data(game.map_data);
506 
507  if(!add_callbacks) {
508  return;
509  }
510 
511  //
512  // Presets buttons
513  //
514  if(game.game_preset) {
515  button& delete_preset = grid->find_widget<button>("delete_preset");
516 
518  } else {
520  }
521 
522  //
523  // Enter game by double clicking row
524  //
526  std::bind(&mp_lobby::enter_game_by_id, this, game.id, DO_EITHER));
527 }
528 
529 void mp_lobby::delete_preset(const int game_id)
530 {
531  prefs::get().remove_game_preset(game_id);
532  refresh_lobby();
533 }
534 
536 {
537  DBG_LB << "mp_lobby::update_gamelist_filter";
539  DBG_LB << "Games in lobby_info: " << lobby_info_.games().size()
540  << ", games in listbox: " << gamelistbox_->get_item_count();
541  assert(lobby_info_.games().size() == gamelistbox_->get_item_count());
542 
544 }
545 
547 {
548  if(delay_playerlist_update_) return;
549 
550  SCOPE_LB;
551  DBG_LB << "Playerlist update: " << lobby_info_.users().size();
552 
554 
555  player_list_dirty_ = false;
556  last_lobby_update_ = std::chrono::steady_clock::now();
557 }
558 
560 {
561  const int idx = gamelistbox_->get_selected_row();
562  bool can_join = false, can_observe = false;
563 
564  if(idx >= 0) {
565  const mp::game_info& game = *lobby_info_.games()[idx];
566  can_observe = game.can_observe();
567  can_join = game.can_join();
568  selected_game_id_ = game.id;
569  } else {
570  selected_game_id_ = 0;
571  }
572 
573  find_widget<button>("observe_global").set_active(can_observe);
574  find_widget<button>("join_global").set_active(can_join);
575 
576  player_list_dirty_ = true;
577 }
578 
580 {
581  SCOPE_LB;
582 
583  gamelistbox_ = find_widget<listbox>("game_list", false, true);
584 
586  std::bind(&mp_lobby::update_selected_game, this));
587 
588  player_list_.init(*this);
589 
590  set_enter_disabled(true);
591 
592  // Exit hook to add a confirmation when quitting the Lobby.
593  set_exit_hook(window::exit_hook::always, [this] { return get_retval() == retval::CANCEL ? quit() : true; });
594 
595  chatbox_ = find_widget<chatbox>("chat", false, true);
596 
598 
601 
602  find_widget<button>("create").set_retval(CREATE);
603 
605  find_widget<button>("show_preferences"),
607 
609  find_widget<button>("join_global"),
610  std::bind(&mp_lobby::enter_selected_game, this, DO_JOIN));
611 
612  find_widget<button>("join_global").set_active(false);
613 
615  find_widget<button>("observe_global"),
616  std::bind(&mp_lobby::enter_selected_game, this, DO_OBSERVE));
617 
619  find_widget<button>("server_info"),
620  std::bind(&mp_lobby::show_server_info, this));
621 
622  find_widget<button>("observe_global").set_active(false);
623 
624  menu_button& replay_options = find_widget<menu_button>("replay_options");
625 
626  if(prefs::get().skip_mp_replay()) {
627  replay_options.set_selected(1);
628  }
629 
630  if(prefs::get().blindfold_replay()) {
631  replay_options.set_selected(2);
632  }
633 
634  connect_signal_notify_modified(replay_options,
635  std::bind(&mp_lobby::skip_replay_changed_callback, this));
636 
637  filter_text_ = find_widget<text_box>("filter_text", false, true);
638 
640  *filter_text_,
641  std::bind(&mp_lobby::game_filter_keypress_callback, this, std::placeholders::_5));
642 
643  chatbox_->room_window_open(N_("lobby"), true, false);
645 
647 
648  // Force first update to be directly.
649  update_gamelist();
651 
652  // TODO: currently getting a crash in the chatbox if we use
653  // -- vultraz, 2017-11-10
654  //mp_lobby::network_handler();
655 
658 
659  //
660  // Profile box
661  //
662  if(panel* profile_panel = find_widget<panel>("profile", false, false)) {
663  auto your_info = std::find_if(lobby_info_.users().begin(), lobby_info_.users().end(),
664  [](const auto& u) { return u.get_relation() == mp::user_info::user_relation::ME; });
665 
666  if(your_info != lobby_info_.users().end()) {
667  profile_panel->find_widget<label>("username").set_label(your_info->name);
668 
669  auto& profile_button = profile_panel->find_widget<button>("view_profile");
670  connect_signal_mouse_left_click(profile_button, std::bind(&mp_lobby::open_profile_url, this));
671 
672  auto& history_button = profile_panel->find_widget<button>("view_match_history");
673  connect_signal_mouse_left_click(history_button, std::bind(&mp_lobby::open_match_history, this));
674  }
675  }
676 
677  listbox& tab_bar = find_widget<listbox>("games_list_tab_bar");
679 
680  // server-side queue join button
681  button* queue_join_button = find_widget<button>("join_queue", false, true);
682  connect_signal_mouse_left_click(*queue_join_button, std::bind(&mp_lobby::join_queue, this));
683 
684  // server-side queue leave button
685  button* queue_leave_button = find_widget<button>("leave_queue", false, true);
686  connect_signal_mouse_left_click(*queue_leave_button, std::bind(&mp_lobby::leave_queue, this));
687 
688  // server-side queues list
690 
691  // Set up Lua plugin context
692  plugins_context_.reset(new plugins_context("Multiplayer Lobby"));
693 
694  plugins_context_->set_callback("join", [&, this](const config&) {
696  }, true);
697 
698  plugins_context_->set_callback("observe", [&, this](const config&) {
700  }, true);
701 
702  plugins_context_->set_callback("create", [this](const config&) { set_retval(CREATE); }, true);
703  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
704 
705  plugins_context_->set_callback("chat", [this](const config& cfg) { chatbox_->send_chat_message(cfg["message"], false); }, true);
706  plugins_context_->set_callback("select_game", [this](const config& cfg) {
707  selected_game_id_ = cfg.has_attribute("id") ? cfg["id"].to_int() : lobby_info_.games()[cfg["index"].to_int()]->id;
708  }, true);
709 
710  plugins_context_->set_accessor("game_list", [this](const config&) { return lobby_info_.gamelist(); });
711 }
712 
714 {
715  filter_auto_hosted_ = find_widget<listbox>("games_list_tab_bar").get_selected_row() == 1;
716  filter_game_presets_ = find_widget<listbox>("games_list_tab_bar").get_selected_row() == 2;
718 }
719 
721 {
723  if(info && info->forum_id != 0) {
725  }
726 }
727 
729 {
730  listbox* queues_listbox = find_widget<listbox>("queue_list", false, true);
731  const std::vector<mp::queue_info>& queues = mp::get_server_queues();
732  if(queues.size() > static_cast<std::size_t>(queues_listbox->get_selected_row())) {
733  const mp::queue_info& queue = queues[queues_listbox->get_selected_row()];
734 
735  std::vector<std::string> installed = installed_addons();
736  std::vector<mp::game_info::required_addon> missing;
737  for(const std::string& required : queue.required_addons) {
738  if(!utils::contains(installed, required)) {
740  req.addon_id = required;
741  req.message = required;
743  missing.emplace_back(req);
744  }
745  }
746 
747  if(!missing.empty()) {
748  if(handle_addon_requirements_gui(missing, mp::game_info::addon_req::NEED_DOWNLOAD)) {
750  } else {
751  return;
752  }
753  }
754 
755  mp::send_to_server(config{"join_server_queue", config{"queue_id", queue.id}});
756  } else {
757  ERR_LB << "Attempted to join queue but couldn't find queue info";
758  }
759 }
760 
762 {
763  listbox* queues_listbox = find_widget<listbox>("queue_list", false, true);
764  const std::vector<mp::queue_info>& queues = mp::get_server_queues();
765  if(queues.size() > static_cast<std::size_t>(queues_listbox->get_selected_row())) {
766  const mp::queue_info& queue = queues[queues_listbox->get_selected_row()];
767  mp::send_to_server(config{"leave_server_queue", config{"queue_id", queue.id}});
768  } else {
769  ERR_LB << "Attempted to join queue but couldn't find queue info";
770  }
771 }
772 
774 {
777  plugins_context_.reset();
778 }
779 
781 {
783  if(info) {
785  }
786 }
787 
789 {
790  try {
791  config data;
794  }
795  } catch (const wesnothd_error& e) {
796  LOG_LB << "caught wesnothd_error in network_handler: " << e.message;
797  throw;
798  }
799 
800  if(std::chrono::steady_clock::now() - last_lobby_update_ < game_config::lobby_refresh) {
801  return;
802  }
803 
805  //don't process a corrupted gamelist further to prevent crashes later.
806  return;
807  }
808 
812  } else {
813  update_gamelist();
814  gamelist_diff_update_ = true;
815  }
816  }
817 
821  }
822 }
823 
825 {
826  listbox* queues_listbox = find_widget<listbox>("queue_list", false, true);
827  queues_listbox->clear();
828 
829  std::vector<mp::queue_info>& queues = mp::get_server_queues();
830  std::sort(queues.begin(), queues.end(), [](const mp::queue_info& q1, const mp::queue_info& q2) { return q1.display_name < q2.display_name; });
831 
832  for(const mp::queue_info& info : queues) {
834  widget_item item;
835 
836  item["label"] = info.current_players.count(prefs::get().login()) > 0 ? "x" : "o";
837  data.emplace("is_in_queue", item);
838 
839  item["label"] = info.display_name;
840  data.emplace("queue_name", item);
841 
842  item["label"] = std::to_string(info.current_players.size())+"/"+std::to_string(info.players_required);
843  data.emplace("queue_player_count", item);
844 
845  queues_listbox->add_row(data);
846  }
847 }
848 
850 {
851  if(auto error = data.optional_child("error")) {
852  throw wesnothd_error(error["message"]);
853  } else if(data.has_child("gamelist")) {
855  } else if(auto gamelist_diff = data.optional_child("gamelist_diff")) {
856  process_gamelist_diff(*gamelist_diff);
857  } else if(auto info = data.optional_child("message")) {
858  if(info["type"] == "server_info") {
859  server_information_ = info["message"].str();
860  return;
861  } else if(info["type"] == "announcements") {
862  announcements_ = info["message"].str();
863  return;
864  }
865  } else if(auto create = data.optional_child("create_game")) {
866  queue_game_server_preset_ = create.value().mandatory_child("game");
867  queue_id_ = create["queue_id"].to_int();
869  return;
870  } else if(auto join_game = data.optional_child("join_game")) {
871  enter_game_by_id(join_game["id"].to_int(), JOIN_MODE::DO_JOIN);
872  return;
873  } else if(auto queue_update = data.optional_child("queue_update")) {
874  std::vector<mp::queue_info>& queues = mp::get_server_queues();
875  if(queue_update["action"].str() == "add") {
876  mp::queue_info& new_info = queues.emplace_back();
877  new_info.id = queue_update["queue_id"].to_int();
878  new_info.players_required = queue_update["players_required"].to_int();
879  new_info.display_name = queue_update["display_name"].str();
880  } else {
881  for(mp::queue_info& info : queues) {
882  if(info.id == queue_update["queue_id"].to_int()) {
883  if(queue_update["action"].str() == "remove") {
884  utils::erase_if(queues, [&](const mp::queue_info& i) { return i.id == queue_update["queue_id"].to_int(); });
885  } else if(queue_update["action"].str() == "update") {
886  if(queue_update->has_attribute("display_name")) {
887  info.display_name = queue_update["display_name"].str();
888  }
889  if(queue_update->has_attribute("players_required")) {
890  info.players_required = queue_update["players_required"].to_int();
891  }
892  if(queue_update->has_attribute("current_players")){
893  info.current_players = utils::split_set(queue_update["current_players"].str());
894  }
895  if(queue_update->has_attribute("addons")){
896  info.required_addons = utils::split_set(queue_update["addons"].str());
897  }
898  } else {
899  continue;
900  }
901  }
902  }
903  }
904 
906 
907  return;
908  }
909 
911 }
912 
914 {
916 
918  DBG_LB << "Received gamelist";
919  gamelist_dirty_ = true;
920  gamelist_diff_update_ = false;
921 }
922 
924 {
926 
928  DBG_LB << "Received gamelist diff";
929  gamelist_dirty_ = true;
930  } else {
931  ERR_LB << "process_gamelist_diff failed!";
932  refresh_lobby();
933  }
934  const int joined = data.child_count("insert_child");
935  const int left = data.child_count("remove_child");
936  if(joined > 0 || left > 0) {
937  if(left > joined) {
939  } else {
941  }
942  }
943 }
944 
946 {
947  if(game.id < 0) {
949 
950  if(preset) {
951  queue_game_server_preset_ = *preset;
952  queue_id_ = preset["id"].to_int();
954  return;
955  }
956  }
957 
958  switch(mode) {
959  case DO_JOIN:
960  if(!game.can_join()) {
961  ERR_LB << "Attempted to join a game with no vacant slots";
962  return;
963  }
964 
965  break;
966  case DO_OBSERVE:
967  if(!game.can_observe()) {
968  ERR_LB << "Attempted to observe a game with observers disabled";
969  return;
970  }
971 
972  break;
973  case DO_EITHER:
974  if(game.can_join()) {
975  mode = DO_JOIN;
976  } else if(game.can_observe()) {
977  mode = DO_OBSERVE;
978  } else {
979  DBG_LB << "Cannot join or observe a game.";
980  return;
981  }
982 
983  break;
984  }
985 
986  // prompt moderators for whether they want to join a game with observers disabled
987  if(!game.observers && mp::logged_in_as_moderator()) {
988  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) {
989  return;
990  }
991  }
992 
993  const bool try_join = mode == DO_JOIN;
994  const bool try_obsv = mode == DO_OBSERVE;
995 
996  // Prompt user to download this game's required addons if its requirements have not been met
997  if(game.addons_outcome != mp::game_info::addon_req::SATISFIED) {
998  if(game.required_addons.empty()) {
999  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."));
1000  return;
1001  }
1002 
1003  if(!handle_addon_requirements_gui(game.required_addons, game.addons_outcome)) {
1004  return;
1005  }
1006 
1007  // Addons have been downloaded, so the game_config and installed addons list need to be reloaded.
1008  // The lobby is closed and reopened.
1010  return;
1011  }
1012 
1013  config join_data;
1014  join_data["id"] = std::to_string(game.id);
1015  join_data["observe"] = try_obsv;
1016 
1017  if(mp::logged_in_as_moderator() && game.password_required) {
1018  if(gui2::show_message(_("Join"), _("This game is password protected. Join using moderator rights anyway?"), gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
1019  return;
1020  }
1021  } else if(!join_data.empty() && game.password_required) {
1022  std::string password;
1023 
1024  if(!gui2::dialogs::mp_join_game_password_prompt::execute(password)) {
1025  return;
1026  }
1027 
1028  join_data["password"] = password;
1029  }
1030 
1031  join_data["mp_scenario"] = game.scenario_id;
1032  mp::send_to_server(config{"join", std::move(join_data)});
1033 
1034  joined_game_id_ = game.id;
1035 
1036  // We're all good. Close lobby and proceed to game!
1037  set_retval(try_join ? JOIN : OBSERVE);
1038 }
1039 
1040 void mp_lobby::enter_game_by_id(const int game_id, JOIN_MODE mode)
1041 {
1042  // 0 or positive is a normal game
1043  // negative is a preset
1044  mp::game_info* game_ptr = game_id >= 0
1045  ? lobby_info_.get_game_by_id(game_id)
1047 
1048  if(!game_ptr) {
1049  ERR_LB << "Attempted to join/observe a game with an invalid id: " << game_id;
1050  return;
1051  }
1052 
1053  enter_game(*game_ptr, mode);
1054 }
1055 
1057 {
1059 }
1060 
1062 {
1063  mp::send_to_server(config("refresh_lobby"));
1064 }
1065 
1067 {
1068  if(info->game_id == 0 || info->game_id == selected_game_id_) {
1069  return;
1070  }
1071 
1072  for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
1073  if(lobby_info_.games()[i]->id == info->game_id) {
1076  break;
1077  }
1078  }
1079 }
1080 
1082 {
1083  gui2::dialogs::preferences_dialog::display();
1084 
1085  refresh_lobby();
1086 }
1087 
1089 {
1091 }
1092 
1094 {
1096 
1098  for(const auto& s : utils::split(filter_text_->get_value(), ' ')) {
1099  if(!info.match_string_filter(s)) {
1100  return false;
1101  }
1102  }
1103 
1104  return true;
1105  });
1106 
1107  lobby_info_.add_game_filter([this](const mp::game_info& info) {
1108  return filter_friends_->get_widget_value() ? info.has_friends == true : true;
1109  });
1110 
1111  // Unlike the friends filter, this is an inclusion filter (do we want to also show
1112  // games with blocked players) rather than an exclusion filter (do we want to show
1113  // only games with friends).
1114  lobby_info_.add_game_filter([this](const mp::game_info& info) {
1115  return filter_ignored_->get_widget_value() == false ? info.has_ignored == false : true;
1116  });
1117 
1118  lobby_info_.add_game_filter([this](const mp::game_info& info) {
1119  return filter_slots_->get_widget_value() ? info.vacant_slots > 0 : true;
1120  });
1121 
1122  lobby_info_.add_game_filter([this](const mp::game_info& info) {
1123  return info.auto_hosted == filter_auto_hosted_;
1124  });
1125 
1126  lobby_info_.add_game_filter([this](const mp::game_info& info) {
1127  return info.game_preset == filter_game_presets_;
1128  });
1129 
1130  lobby_info_.set_game_filter_invert(
1131  [this](bool val) { return filter_invert_->get_widget_value() ? !val : val; });
1132 }
1133 
1134 void mp_lobby::game_filter_keypress_callback(const SDL_Keycode key)
1135 {
1136  if(key == SDLK_RETURN || key == SDLK_KP_ENTER) {
1138  }
1139 }
1140 
1142 {
1143  delay_playerlist_update_ = true;
1145 
1147  dlg.show();
1148 
1149  if(dlg.result_open_whisper()) {
1152  }
1153 
1154  // Only follow a player to their game if they are actually in one.
1155  // Otherwise leave the current selection alone instead of losing it on refresh.
1156  // GitHub #1197.
1157  if(info->game_id != 0) {
1158  selected_game_id_ = info->game_id;
1159  }
1160 
1161  // do not update here as it can cause issues with removing the widget
1162  // from within it's event handler. Should get updated as soon as possible
1163  // update_gamelist();
1164  delay_playerlist_update_ = false;
1165  player_list_dirty_ = true;
1166  refresh_lobby();
1167 }
1168 
1170 {
1171  // TODO: this prefence should probably be controlled with an enum
1172  const int value = find_widget<menu_button>("replay_options").get_value();
1173  prefs::get().set_skip_mp_replay(value == 1);
1174  prefs::get().set_blindfold_replay(value == 2);
1175 }
1176 
1177 } // namespace dialogs
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:186
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:157
bool has_attribute(std::string_view key) const
Definition: config.cpp:157
bool empty() const
Definition: config.cpp:823
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
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:379
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:385
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:625
void set_active_window_changed_callback(const std::function< void(void)> &f)
Definition: chatbox.hpp:81
void load_log(std::map< std::string, chatroom_log > &log, bool show_lobby)
Definition: chatbox.cpp:90
void active_window_changed()
Definition: chatbox.cpp: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:1056
void process_gamelist(const config &data)
Definition: lobby.cpp:913
void update_visible_games()
Definition: lobby.cpp:368
void update_selected_game()
Definition: lobby.cpp:559
virtual void post_show() override
Actions to be taken after the window has been shown.
Definition: lobby.cpp:773
mp::lobby_info & lobby_info_
Definition: lobby.hpp:149
text_box * filter_text_
Definition: lobby.hpp:160
lobby_player_list_helper player_list_
Definition: lobby.hpp:164
void skip_replay_changed_callback()
Definition: lobby.cpp:1169
static std::string announcements_
Definition: lobby.hpp:193
wesnothd_connection & network_connection_
Definition: lobby.hpp:174
void update_gamelist_filter()
Definition: lobby.cpp:535
listbox * gamelistbox_
Definition: lobby.hpp:147
void process_network_data(const config &data)
Definition: lobby.cpp:849
widget_data make_game_row_data(const mp::game_info &game)
Definition: lobby.cpp:380
void select_game_by_player(const mp::user_info *info)
Definition: lobby.cpp:1066
void game_filter_keypress_callback(const SDL_Keycode key)
Definition: lobby.cpp:1134
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:1040
void update_gamelist_diff()
Definition: lobby.cpp:261
void show_preferences_button_callback()
Definition: lobby.cpp:1081
static std::string server_information_
Definition: lobby.hpp:192
void enter_game(const mp::game_info &game, JOIN_MODE mode)
Exits the lobby and enters the given game.
Definition: lobby.cpp:945
void user_dialog_callback(const mp::user_info *info)
Definition: lobby.cpp:1141
void tab_switch_callback()
Definition: lobby.cpp:713
mp_lobby(mp::lobby_info &info, wesnothd_connection &connection, int &joined_game)
Definition: lobby.cpp:69
void process_gamelist_diff(const config &data)
Definition: lobby.cpp:923
std::size_t lobby_update_timer_
Timer for updating the lobby.
Definition: lobby.hpp:177
config queue_game_server_preset_
Definition: lobby.hpp:187
virtual void pre_show() override
Actions to be taken before showing the window.
Definition: lobby.cpp:579
void adjust_game_row_contents(const mp::game_info &game, grid *grid, bool add_callbacks=true)
Definition: lobby.cpp:409
void delete_preset(const int game_id)
Definition: lobby.cpp:529
std::vector< int > gamelist_id_at_row_
Definition: lobby.hpp:179
std::chrono::steady_clock::time_point last_lobby_update_
Definition: lobby.hpp:170
void network_handler()
Network polling callback.
Definition: lobby.cpp:788
@ RELOAD_CONFIG
player clicked the Create button
Definition: lobby.hpp:65
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, Func &&function)
Registers a hotkey.
Definition: dispatcher.hpp:444
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:163
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:256
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:267
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:108
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:147
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:290
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:153
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:
@ invisible
The user set the widget invisible, 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:313
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:387
show_mode mode() const
Returns the dialog mode for this window.
Definition: window.hpp:669
void keyboard_capture(widget *widget)
Definition: window.cpp:1197
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition: window.hpp:435
int get_retval()
Definition: window.hpp:394
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:409
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:117
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
Definition: lobby_info.cpp:218
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:339
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
Definition: lobby_info.cpp:371
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:420
const boost::dynamic_bitset & games_visibility() const
Definition: lobby_info.hpp:102
const std::vector< user_info > & users() const
Definition: lobby_info.hpp:107
void remove_game_preset(int id)
static prefs & get()
optional_const_config get_game_preset(int id)
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.
const config * cfg
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:1031
#define N_(String)
Definition: gettext.hpp:108
static std::string _(const char *str)
Definition: gettext.hpp:100
#define LOG_LB
Definition: lobby.cpp:56
#define ERR_LB
Definition: lobby.cpp:57
static lg::log_domain log_lobby("lobby")
#define DBG_LB
Definition: lobby.cpp:55
#define SCOPE_LB
Definition: lobby.cpp:58
bool ad_hoc_addon_fetch_session(const std::vector< std::string > &addon_ids)
Conducts an ad-hoc add-ons server connection to download an add-on with a particular id and all it's ...
Definition: manager_ui.cpp:271
bool open_object([[maybe_unused]] const std::string &path_or_url)
Definition: open.cpp:50
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:158
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:189
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
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:184
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.
Definition: help.cpp:84
@ HOTKEY_PREFERENCES
Functions to load and save images from/to disk.
logger & info()
Definition: log.cpp:351
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:176
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
std::string span_size(std::string_view size, Args &&... data)
Applies Pango markup to the input specifying its display size.
Definition: markup.hpp:146
void do_notify(notify_mode mode, const std::string &sender, const std::string &message)
Definition: lobby_info.cpp:59
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
std::vector< queue_info > & get_server_queues()
Gets the list of server-side queues received on login.
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:81
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:88
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
Definition: general.hpp:152
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:108
std::vector< std::string > split(const config_attribute_value &val)
std::string to_string(const Range &range, const Func &op)
Desktop environment interaction functions.
int w
Definition: pathfind.cpp:188
std::string_view data
Definition: picture.cpp:188
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition: sound.cpp:139
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
This class represents the info a client has about a game on the server.
Definition: lobby_data.hpp:62
std::string display_name
Definition: multiplayer.hpp:33
std::set< std::string > required_addons
Definition: multiplayer.hpp:36
This class represents the information a client has about another player.
Definition: lobby_data.hpp:29
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