The Battle for Wesnoth  1.15.7+dev
lobby_data.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2018 by Tomasz Sniatowski <kailoran@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "config.hpp"
19 #include "preferences/game.hpp"
21 #include "filesystem.hpp"
22 #include "font/pango/escape.hpp"
23 #include "formatter.hpp"
24 #include "formula/string_utils.hpp"
25 #include "game_config_manager.hpp"
26 #include "gettext.hpp"
27 #include "lexical_cast.hpp"
28 #include "log.hpp"
29 #include "map/map.hpp"
30 #include "map/exception.hpp"
31 #include "terrain/type_data.hpp"
32 #include "wml_exception.hpp"
33 #include "game_version.hpp"
34 #include "mp_game_settings.hpp"
35 #include "game_config_view.hpp"
36 
37 #include <iterator>
38 
39 #include <boost/algorithm/string.hpp>
40 
41 static lg::log_domain log_config("config");
42 #define ERR_CF LOG_STREAM(err, log_config)
43 static lg::log_domain log_engine("engine");
44 #define WRN_NG LOG_STREAM(warn, log_engine)
45 
46 static lg::log_domain log_lobby("lobby");
47 #define DBG_LB LOG_STREAM(info, log_lobby)
48 #define LOG_LB LOG_STREAM(info, log_lobby)
49 #define ERR_LB LOG_STREAM(err, log_lobby)
50 
51 namespace mp {
52 
54  const std::string& user,
55  const std::string& message)
56  : timestamp(timestamp), user(user), message(message)
57 {
58 }
59 
61 {
62 }
63 
64 void chat_session::add_message(const std::time_t& timestamp,
65  const std::string& user,
66  const std::string& message)
67 {
68  history_.emplace_back(timestamp, user, message);
69 }
70 
71 
72 void chat_session::add_message(const std::string& user, const std::string& message)
73 {
74  add_message(std::time(nullptr), user, message);
75 }
76 
78 {
79  history_.clear();
80 }
81 
82 room_info::room_info(const std::string& name) : name_(name), members_(), log_()
83 {
84 }
85 
86 bool room_info::is_member(const std::string& user) const
87 {
88  return members_.find(user) != members_.end();
89 }
90 
92 {
93  members_.insert(user);
94 }
95 
97 {
98  members_.erase(user);
99 }
100 
102 {
103  members_.clear();
104  for(const auto & m : data.child_range("member"))
105  {
106  members_.insert(m["name"]);
107  }
108 }
109 
111  : name(c["name"])
112  , game_id(c["game_id"])
113  , relation(ME)
114  , state(game_id == 0 ? LOBBY : GAME)
115  , registered(c["registered"].to_bool())
116  , observing(c["status"] == "observing")
117 {
118  update_relation();
119 }
120 
121 void user_info::update_state(int selected_game_id,
122  const room_info* current_room /*= nullptr*/)
123 {
124  if(game_id != 0) {
125  if(game_id == selected_game_id) {
126  state = SEL_GAME;
127  } else {
128  state = GAME;
129  }
130  } else {
131  if(current_room != nullptr && current_room->is_member(name)) {
132  state = SEL_ROOM;
133  } else {
134  state = LOBBY;
135  }
136  }
137  update_relation();
138 }
139 
141 {
142  if(name == preferences::login()) {
143  relation = ME;
144  } else if(preferences::is_ignored(name)) {
145  relation = IGNORED;
146  } else if(preferences::is_friend(name)) {
147  relation = FRIEND;
148  } else {
149  relation = NEUTRAL;
150  }
151 }
152 
153 bool user_info::operator<(const user_info& b) const
154 {
155  return relation < b.relation || (relation == b.relation && translation::icompare(name, b.name) < 0);
156 }
157 
158 namespace
159 {
160 const std::string& spaced_em_dash()
161 {
162  static const std::string res = " " + font::unicode_em_dash + " ";
163  return res;
164 }
165 
166 std::string make_game_type_marker(const std::string& text, bool color_for_missing)
167 {
168  if(color_for_missing) {
169  return formatter() << "<b><span color='#f00'>[" << text << "]</span></b> ";
170  } else {
171  return formatter() << "<b>[" << text << "]</b> ";
172  }
173 }
174 
175 } // end anon namespace
176 
177 game_info::game_info(const config& game, const std::vector<std::string>& installed_addons)
178  : id(game["id"])
179  , map_data(game["map_data"])
180  , name(font::escape_text(game["name"]))
181  , scenario()
182  , type_marker()
183  , remote_scenario(false)
184  , map_info()
185  , map_size_info()
186  , era()
187  , gold(game["mp_village_gold"])
188  , support(game["mp_village_support"])
189  , xp(game["experience_modifier"].str() + "%")
190  , vision()
191  , status()
192  , time_limit()
193  , vacant_slots()
194  , current_turn(0)
195  , reloaded(game["savegame"].to_enum<mp_game_settings::SAVED_GAME_MODE>(mp_game_settings::SAVED_GAME_MODE::NONE) != mp_game_settings::SAVED_GAME_MODE::NONE)
196  , started(false)
197  , fog(game["mp_fog"].to_bool())
198  , shroud(game["mp_shroud"].to_bool())
199  , observers(game["observer"].to_bool(true))
200  , shuffle_sides(game["shuffle_sides"].to_bool(true))
201  , use_map_settings(game["mp_use_map_settings"].to_bool())
202  , private_replay(game["private_replay"].to_bool())
203  , verified(true)
204  , password_required(game["password"].to_bool())
205  , have_era(true)
206  , have_all_mods(true)
207  , has_friends(false)
208  , has_ignored(false)
209  , display_status(NEW)
210  , required_addons()
211  , addons_outcome(SATISFIED)
212 {
214 
215  // Parse the list of addons required to join this game.
216  for(const config& addon : game.child_range("addon")) {
217  if(addon.has_attribute("id") && addon["require"].to_bool(false)) {
218  if(std::find(installed_addons.begin(), installed_addons.end(), addon["id"].str()) == installed_addons.end()) {
219  required_addon r;
220  r.addon_id = addon["id"].str();
222 
223  // Use addon name if provided, else fall back on the addon id.
224  if(addon.has_attribute("name")) {
225  r.message = VGETTEXT("Missing addon: $name", {{"name", addon["name"].str()}});
226  } else {
227  r.message = VGETTEXT("Missing addon: $id", {{"id", addon["id"].str()}});
228  }
229 
230  required_addons.push_back(std::move(r));
231 
232  if(addons_outcome == SATISFIED) {
234  }
235  }
236  }
237  }
238 
239  if(!game["mp_era"].empty()) {
240  const config& era_cfg = game_config.find_child("era", "id", game["mp_era"]);
241  const bool require = game["require_era"].to_bool(true);
242  if(era_cfg) {
243  era = era_cfg["name"].str();
244 
245  if(require) {
246  ADDON_REQ result = check_addon_version_compatibility(era_cfg, game);
247  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
248  }
249  } else {
250  have_era = !require;
251  era = game["mp_era_name"].str();
252  verified = false;
253 
254  if(!have_era) {
256  }
257  }
258  } else {
259  era = _("Unknown era");
260  verified = false;
261  }
262 
263  std::stringstream info_stream;
264  info_stream << era;
265 
266  for(const config& cfg : game.child_range("modification")) {
267  mod_info.emplace_back(cfg["name"].str(), true);
268  info_stream << ' ' << mod_info.back().first;
269 
270  if(cfg["require_modification"].to_bool(false)) {
271  if(const config& mod = game_config.find_child("modification", "id", cfg["id"])) {
272  ADDON_REQ result = check_addon_version_compatibility(mod, game);
273  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
274  } else {
275  have_all_mods = false;
276  mod_info.back().second = false;
277 
279  }
280  }
281  }
282 
283  std::sort(mod_info.begin(), mod_info.end(), [](const auto& lhs, const auto& rhs) {
284  return translation::icompare(lhs.first, rhs.first) < 0;
285  });
286 
287  info_stream << ' ';
288 
289  if(map_data.empty()) {
290  map_data = filesystem::read_map(game["mp_scenario"]);
291  }
292 
293  if(map_data.empty()) {
294  info_stream << " — ??×??";
295  } else {
296  try {
297  gamemap map(std::make_shared<terrain_type_data>(game_config), map_data);
298  std::ostringstream msi;
299  msi << map.w() << font::unicode_multiplication_sign << map.h();
300  map_size_info = msi.str();
301  info_stream << spaced_em_dash() << map_size_info;
302  } catch(const incorrect_map_format_error&) {
303  verified = false;
304  } catch(const wml_exception& e) {
305  ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
306  verified = false;
307  }
308  }
309 
310  info_stream << " ";
311 
312  //
313  // Check scenarios and campaigns
314  //
315  if(!game["mp_scenario"].empty() && game["mp_campaign"].empty()) {
316  // Check if it's a multiplayer scenario
317  const config* level_cfg = &game_config.find_child("multiplayer", "id", game["mp_scenario"]);
318  const bool require = game["require_scenario"].to_bool(false);
319 
320  // Check if it's a user map
321  if(!*level_cfg) {
322  level_cfg = &game_config.find_child("generic_multiplayer", "id", game["mp_scenario"]);
323  }
324 
325  if(*level_cfg) {
326  type_marker = make_game_type_marker(_("scenario_abbreviation^S"), false);
327  scenario = (*level_cfg)["name"].str();
328  info_stream << scenario;
329 
330  // Reloaded games do not match the original scenario hash, so it makes no sense
331  // to test them, since they always would appear as remote scenarios
332  if(!reloaded) {
333  if(const config& hashes = game_config.child("multiplayer_hashes")) {
334  std::string hash = game["hash"];
335  bool hash_found = false;
336  for(const auto & i : hashes.attribute_range()) {
337  if(i.first == game["mp_scenario"] && i.second == hash) {
338  hash_found = true;
339  break;
340  }
341  }
342 
343  if(!hash_found) {
344  remote_scenario = true;
345  info_stream << spaced_em_dash();
346  info_stream << _("Remote scenario");
347  verified = false;
348  }
349  }
350  }
351 
352  if(require) {
353  ADDON_REQ result = check_addon_version_compatibility((*level_cfg), game);
354  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
355  }
356  } else {
357  if(require) {
358  addons_outcome = std::max(addons_outcome, NEED_DOWNLOAD); // Elevate to most severe error level encountered so far
359  }
360  type_marker = make_game_type_marker(_("scenario_abbreviation^S"), true);
361  scenario = game["mp_scenario_name"].str();
362  info_stream << scenario;
363  verified = false;
364  }
365  } else if(!game["mp_campaign"].empty()) {
366  if(const config& campaign_cfg = game_config.find_child("campaign", "id", game["mp_campaign"])) {
367  type_marker = make_game_type_marker(_("campaign_abbreviation^C"), false);
368 
369  std::stringstream campaign_text;
370  campaign_text
371  << campaign_cfg["name"] << spaced_em_dash()
372  << game["mp_scenario_name"];
373 
374  // Difficulty
375  config difficulties = gui2::dialogs::generate_difficulty_config(campaign_cfg);
376  for(const config& difficulty : difficulties.child_range("difficulty")) {
377  if(difficulty["define"] == game["difficulty_define"]) {
378  campaign_text << spaced_em_dash() << difficulty["description"];
379 
380  break;
381  }
382  }
383 
384  scenario = campaign_text.str();
385  info_stream << campaign_text.rdbuf();
386 
387  // TODO: should we have this?
388  //if(game["require_scenario"].to_bool(false)) {
389  ADDON_REQ result = check_addon_version_compatibility(campaign_cfg, game);
390  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
391  //}
392  } else {
393  type_marker = make_game_type_marker(_("campaign_abbreviation^C"), true);
394  scenario = game["mp_campaign_name"].str();
395  info_stream << scenario;
396  verified = false;
397  }
398  } else {
399  scenario = _("Unknown scenario");
400  info_stream << scenario;
401  verified = false;
402  }
403 
404  // Remove any newlines that might have been in game names (the player-set ones)
405  // No idea how this could happen, but I've seen it (vultraz, 2020-10-26)
406  boost::erase_all(name, "\n");
407 
408  // Remove any newlines that might have been in game titles (scenario/campaign name, etc.)
409  boost::replace_all(scenario, "\n", " " + font::unicode_em_dash + " ");
410 
411  if(reloaded) {
412  info_stream << spaced_em_dash();
413  info_stream << _("Reloaded game");
414  verified = false;
415  }
416 
417  // These should always be present in the data the server sends, but may or may not be empty.
418  // I'm just using child_or_empty here to preempt any cases where they might not be included.
419  const config& s = game.child_or_empty("slot_data");
420  const config& t = game.child_or_empty("turn_data");
421 
422  if(!s.empty()) {
423  started = false;
424 
425  vacant_slots = s["vacant"].to_unsigned();
426 
427  if(vacant_slots > 0) {
428  status = formatter() << _n("Vacant Slot:", "Vacant Slots:", vacant_slots) << " " << vacant_slots << "/" << s["max"];
429  } else {
430  status = _("mp_game_available_slots^Full");
431  }
432  }
433 
434  if(!t.empty()) {
435  started = true;
436 
437  current_turn = t["current"].to_unsigned();
438  const int max_turns = t["max"].to_int();
439 
440  if(max_turns > -1) {
441  status = formatter() << _("Turn") << " " << t["current"] << "/" << max_turns;
442  } else {
443  status = formatter() << _("Turn") << " " << t["current"];
444  }
445  }
446 
447  if(fog) {
448  vision = _("Fog");
449  if(shroud) {
450  vision += "/";
451  vision += _("Shroud");
452  }
453  } else if(shroud) {
454  vision = _("Shroud");
455  } else {
456  vision = _("vision^none");
457  }
458 
459  if(game["mp_countdown"].to_bool()) {
461  << game["mp_countdown_init_time"].str() << "+"
462  << game["mp_countdown_turn_bonus"].str() << "/"
463  << game["mp_countdown_action_bonus"].str();
464  } else {
465  time_limit = _("time limit^none");
466  }
467 
468  map_info = info_stream.str();
469 }
470 
472 {
473  if(!local_item.has_attribute("addon_id") || !local_item.has_attribute("addon_version")) {
474  return SATISFIED;
475  }
476 
477  if(const config& game_req = game.find_child("addon", "id", local_item["addon_id"])) {
478  if(!game_req["require"].to_bool(false)) {
479  return SATISFIED;
480  }
481 
482  required_addon r {local_item["addon_id"].str(), SATISFIED, ""};
483 
484  // Local version
485  const version_info local_ver(local_item["addon_version"].str());
486  version_info local_min_ver(local_item.has_attribute("addon_min_version") ? local_item["addon_min_version"] : local_item["addon_version"]);
487 
488  // If the UMC didn't specify last compatible version, assume no backwards compatibility.
489  // Also apply some sanity checking regarding min version; if the min ver doesn't make sense, ignore it.
490  local_min_ver = std::min(local_min_ver, local_ver);
491 
492  // Remote version
493  const version_info remote_ver(game_req["version"].str());
494  version_info remote_min_ver(game_req.has_attribute("min_version") ? game_req["min_version"] : game_req["version"]);
495 
496  remote_min_ver = std::min(remote_min_ver, remote_ver);
497 
498  // Check if the host is too out of date to play.
499  if(local_min_ver > remote_ver) {
500  DBG_LB << "r.outcome = CANNOT_SATISFY for item='" << local_item["id"]
501  << "' addon='" << local_item["addon_id"]
502  << "' addon_min_version='" << local_item["addon_min_version"]
503  << "' addon_min_version_parsed='" << local_min_ver.str()
504  << "' addon_version='" << local_item["addon_version"]
505  << "' remote_ver='" << remote_ver.str()
506  << "'\n";
507  r.outcome = CANNOT_SATISFY;
508 
509  r.message = VGETTEXT("The host's version of <i>$addon</i> is incompatible. They have version <b>$host_ver</b> while you have version <b>$local_ver</b>.", {
510  {"addon", local_item["addon_title"].str()},
511  {"host_ver", remote_ver.str()},
512  {"local_ver", local_ver.str()}
513  });
514 
515  required_addons.push_back(r);
516  return r.outcome;
517  }
518 
519  // Check if our version is too out of date to play.
520  if(remote_min_ver > local_ver) {
521  r.outcome = NEED_DOWNLOAD;
522 
523  r.message = VGETTEXT("Your version of <i>$addon</i> is incompatible. You have version <b>$local_ver</b> while the host has version <b>$host_ver</b>.", {
524  {"addon", local_item["addon_title"].str()},
525  {"host_ver", remote_ver.str()},
526  {"local_ver", local_ver.str()}
527  });
528 
529  required_addons.push_back(r);
530  return r.outcome;
531  }
532  }
533 
534  return SATISFIED;
535 }
536 
538 {
539  return !started && vacant_slots > 0;
540 }
541 
543 {
545 }
546 
548 {
549  switch(display_status) {
550  case game_info::CLEAN:
551  return "clean";
552  case game_info::NEW:
553  return "new";
554  case game_info::DELETED:
555  return "deleted";
556  case game_info::UPDATED:
557  return "updated";
558  default:
559  ERR_CF << "BAD display_status " << display_status << " in game " << id << "\n";
560  return "?";
561  }
562 }
563 
565 {
566  const std::string& s1 = name;
567  const std::string& s2 = map_info;
568  return std::search(s1.begin(), s1.end(), filter.begin(), filter.end(),
569  chars_equal_insensitive) != s1.end()
570  || std::search(s2.begin(), s2.end(), filter.begin(), filter.end(),
571  chars_equal_insensitive) != s2.end();
572 }
573 
574 }
std::string scenario
Definition: lobby_data.hpp:148
std::string map_size_info
Definition: lobby_data.hpp:152
void add_message(const std::time_t &timestamp, const std::string &user, const std::string &message)
Definition: lobby_data.cpp:64
int h() const
Effective map height, in hexes.
Definition: map.hpp:128
ADDON_REQ addons_outcome
Definition: lobby_data.hpp:200
std::string status
Definition: lobby_data.hpp:162
std::string era()
Definition: game.cpp:712
#define ERR_CF
Definition: lobby_data.cpp:42
Interfaces for manipulating version numbers of engine, add-ons, etc.
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:156
bool is_authenticated()
Definition: game.cpp:180
Note: Specific to sdl_ttf.
unsigned int current_turn
Definition: lobby_data.hpp:166
std::deque< chat_message > history_
Definition: lobby_data.hpp:61
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:836
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
New lexcical_cast header.
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
user_state state
Definition: lobby_data.hpp:130
child_itors child_range(config_key_type key)
Definition: config.cpp:362
bool shuffle_sides()
Definition: game.cpp:487
std::set< std::string > members_
Definition: lobby_data.hpp:96
std::string map_info
Definition: lobby_data.hpp:151
bool chars_equal_insensitive(char a, char b)
Definition: general.hpp:21
bool remote_scenario
Definition: lobby_data.hpp:150
std::string str
Definition: statement.cpp:110
Definitions for the interface to Wesnoth Markup Language (WML).
std::string name
Definition: lobby_data.hpp:147
std::string time_limit
Definition: lobby_data.hpp:163
#define b
static lg::log_domain log_lobby("lobby")
bool fog()
Definition: game.cpp:552
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:51
static game_config_manager * get()
user_info(const config &c)
Definition: lobby_data.cpp:110
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:100
chat_message(const std::time_t &timestamp, const std::string &user, const std::string &message)
Create a chat message.
Definition: lobby_data.cpp:53
bool is_friend(const std::string &nick)
Definition: game.cpp:302
std::ostringstream wrapper.
Definition: formatter.hpp:38
#define DBG_LB
Definition: lobby_data.cpp:47
const game_config_view & game_config() const
std::string vision
Definition: lobby_data.hpp:161
const std::string unicode_multiplication_sign
Definition: constants.cpp:42
Encapsulates the map of the game.
Definition: map.hpp:36
static UNUSEDNOWARN std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:104
static lg::log_domain log_config("config")
std::string map_data
Definition: lobby_data.hpp:146
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:148
bool is_member(const std::string &user) const
Definition: lobby_data.cpp:86
bool is_ignored(const std::string &nick)
Definition: game.cpp:315
std::string read_map(const std::string &name)
Helper class, don&#39;t construct this directly.
std::string login()
std::string name
Definition: lobby_data.hpp:127
std::string dev_message
The message for developers telling which problem was triggered, this shouldn&#39;t be translated...
bool shroud()
Definition: game.cpp:562
int w() const
Effective map width, in hexes.
Definition: map.hpp:125
std::string escape_text(const std::string &text)
Escapes the pango markup characters in a text.
Definition: escape.hpp:32
const char * display_status_string() const
Definition: lobby_data.cpp:547
std::size_t i
Definition: function.cpp:933
static lg::log_domain log_engine("engine")
Default, unset return value.
Definition: retval.hpp:31
config generate_difficulty_config(const config &source)
Helper function to convert old difficulty markup.
Game configuration data as global variables.
Definition: build_info.cpp:55
static map_location::DIRECTION s
ADDON_REQ check_addon_version_compatibility(const config &local_item, const config &game)
Definition: lobby_data.cpp:471
std::string name
Definition: sdl_ttf.cpp:70
std::string type_marker
Definition: lobby_data.hpp:149
bool use_map_settings()
Definition: game.cpp:505
This class represents the information a client has about a room.
Definition: lobby_data.hpp:67
Declarations for File-IO.
bool can_observe() const
Definition: lobby_data.cpp:542
static int sort(lua_State *L)
Definition: ltablib.cpp:411
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
Represents version numbers.
std::string era
Definition: lobby_data.hpp:153
const config & find_child(config_key_type key, const std::string &name, const std::string &value) const
This class represents the information a client has about another player.
Definition: lobby_data.hpp:104
void update_state(int selected_game_id, const room_info *current_room=nullptr)
Definition: lobby_data.cpp:121
room_info(const std::string &name)
Definition: lobby_data.cpp:82
double t
Definition: astarsearch.cpp:64
static bool timestamp
Definition: log.cpp:46
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
Definition: gettext.cpp:475
std::size_t vacant_slots
Definition: lobby_data.hpp:164
const std::string unicode_em_dash
Definition: constants.cpp:40
void remove_member(const std::string &user)
Definition: lobby_data.cpp:96
Standard logging facilities (interface).
std::string str() const
Serializes the version number into string form.
game_display_status display_status
Definition: lobby_data.hpp:189
std::vector< required_addon > required_addons
Definition: lobby_data.hpp:199
void update_relation()
Definition: lobby_data.cpp:140
#define e
game_info(const config &c, const std::vector< std::string > &installed_addons)
Definition: lobby_data.cpp:177
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:453
user_relation relation
Definition: lobby_data.hpp:129
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
bool can_join() const
Definition: lobby_data.cpp:537
mock_char c
bool operator<(const user_info &b) const
Definition: lobby_data.cpp:153
bool empty() const
Definition: config.cpp:884
const config & child(config_key_type key) const
void process_room_members(const config &data)
Definition: lobby_data.cpp:101
bool match_string_filter(const std::string &filter) const
Definition: lobby_data.cpp:564
const std::string & name() const
Definition: lobby_data.hpp:72
void add_member(const std::string &user)
Definition: lobby_data.cpp:91