The Battle for Wesnoth  1.17.0-dev
lobby_info.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 
17 
18 #include "addon/manager.hpp" // for installed_addons
19 #include "gettext.hpp"
20 #include "log.hpp"
21 #include "map/exception.hpp"
22 #include "map/map.hpp"
23 #include "mp_ui_alerts.hpp"
24 #include "preferences/game.hpp"
25 #include "wesnothd_connection.hpp"
26 
27 #include <iterator>
28 
29 static lg::log_domain log_engine("engine");
30 #define WRN_NG LOG_STREAM(warn, log_engine)
31 
32 static lg::log_domain log_lobby("lobby");
33 #define DBG_LB LOG_STREAM(debug, log_lobby)
34 #define WRN_LB LOG_STREAM(warn, log_lobby)
35 #define ERR_LB LOG_STREAM(err, log_lobby)
36 #define SCOPE_LB log_scope2(log_lobby, __func__)
37 
38 namespace mp
39 {
41  : installed_addons_()
42  , gamelist_()
43  , gamelist_initialized_(false)
44  , games_by_id_()
45  , games_()
46  , users_()
47  , game_filters_()
48  , game_filter_invert_()
49  , games_visibility_()
50 {
52 }
53 
55 {
56  // This function does not refer to an addon database, it calls filesystem functions.
57  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
58  // even if the user changes the contents of the addon directory in the meantime.
60 }
61 
62 void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
63 {
64  switch(mode) {
65  case NOTIFY_WHISPER:
67  case NOTIFY_OWN_NICK:
68  mp_ui_alerts::private_message(true, sender, message);
69  break;
71  mp_ui_alerts::friend_message(true, sender, message);
72  break;
74  mp_ui_alerts::server_message(true, sender, message);
75  break;
76  case NOTIFY_LOBBY_QUIT:
78  break;
79  case NOTIFY_LOBBY_JOIN:
81  break;
82  case NOTIFY_MESSAGE:
83  mp_ui_alerts::public_message(true, sender, message);
84  break;
86  mp_ui_alerts::game_created(sender, message);
87  break;
88  default:
89  break;
90  }
91 }
92 
93 namespace
94 {
95 std::string dump_games_map(const lobby_info::game_info_map& games)
96 {
97  std::stringstream ss;
98  for(const auto& v : games) {
99  const game_info& game = v.second;
100  ss << "G" << game.id << "(" << game.name << ") " << game.display_status_string() << " ";
101  }
102 
103  ss << "\n";
104  return ss.str();
105 }
106 
107 std::string dump_games_config(const config& gamelist)
108 {
109  std::stringstream ss;
110  for(const auto& c : gamelist.child_range("game")) {
111  ss << "g" << c["id"] << "(" << c["name"] << ") " << c[config::diff_track_attribute] << " ";
112  }
113 
114  ss << "\n";
115  return ss.str();
116 }
117 
118 } // end anonymous namespace
119 
121 {
122  SCOPE_LB;
123 
124  gamelist_ = data;
125  gamelist_initialized_ = true;
126 
127  games_by_id_.clear();
128 
129  for(const auto& c : gamelist_.child("gamelist").child_range("game")) {
131  games_by_id_.emplace(game.id, std::move(game));
132  }
133 
134  DBG_LB << dump_games_map(games_by_id_);
135  DBG_LB << dump_games_config(gamelist_.child("gamelist"));
136 
138 }
139 
141 {
142  if(!process_gamelist_diff_impl(data)) {
143  // the gamelist is now corrupted, stop further processing and wait for a fresh list.
144  gamelist_initialized_ = false;
145  return false;
146  } else {
147  return true;
148  }
149 }
150 
152 {
153  SCOPE_LB;
154  if(!gamelist_initialized_) {
155  return false;
156  }
157 
158  DBG_LB << "prediff " << dump_games_config(gamelist_.child("gamelist"));
159 
160  try {
161  gamelist_.apply_diff(data, true);
162  } catch(const config::error& e) {
163  ERR_LB << "Error while applying the gamelist diff: '" << e.message << "' Getting a new gamelist.\n";
164  return false;
165  }
166 
167  DBG_LB << "postdiff " << dump_games_config(gamelist_.child("gamelist"));
168  DBG_LB << dump_games_map(games_by_id_);
169 
170  for(config& c : gamelist_.child("gamelist").child_range("game")) {
171  DBG_LB << "data process: " << c["id"] << " (" << c[config::diff_track_attribute] << ")\n";
172 
173  const int game_id = c["id"];
174  if(game_id == 0) {
175  ERR_LB << "game with id 0 in gamelist config" << std::endl;
176  return false;
177  }
178 
179  auto current_i = games_by_id_.find(game_id);
180 
181  const std::string& diff_result = c[config::diff_track_attribute];
182 
183  if(diff_result == "new" || diff_result == "modified") {
184  // note: at this point (1.14.3) the server never sends a 'modified' and instead
185  // just sends a 'delete' followed by a 'new', it still works becasue the delete doesn't
186  // delete the element and just marks it as game_info::DELETED so that game_info::DELETED
187  // is replaced by game_info::UPDATED below. See also
188  // https://github.com/wesnoth/wesnoth/blob/1.14/src/server/server.cpp#L149
189  if(current_i == games_by_id_.end()) {
190  games_by_id_.emplace(game_id, game_info(c, installed_addons_));
191  continue;
192  }
193 
194  // Had a game with that id, so update it and mark it as such
195  current_i->second = game_info(c, installed_addons_);
196  current_i->second.display_status = game_info::disp_status::UPDATED;
197  } else if(diff_result == "deleted") {
198  if(current_i == games_by_id_.end()) {
199  WRN_LB << "Would have to delete a game that I don't have: " << game_id << std::endl;
200  continue;
201  }
202 
203  if(current_i->second.display_status == game_info::disp_status::NEW) {
204  // This means the game never made it through to the user interface,
205  // so just deleting it is fine.
206  games_by_id_.erase(current_i);
207  } else {
208  current_i->second.display_status = game_info::disp_status::DELETED;
209  }
210  }
211  }
212 
213  DBG_LB << dump_games_map(games_by_id_);
214 
215  try {
217  } catch(const config::error& e) {
218  ERR_LB << "Error while applying the gamelist diff (2): '" << e.message << "' Getting a new gamelist.\n";
219  return false;
220  }
221 
222  DBG_LB << "postclean " << dump_games_config(gamelist_.child("gamelist"));
223 
225  return true;
226 }
227 
229 {
230  SCOPE_LB;
231 
232  users_.clear();
233  for(const auto& c : gamelist_.child_range("user")) {
234  user_info& ui = users_.emplace_back(c);
235 
236  if(ui.game_id == 0) {
237  continue;
238  }
239 
241  if(!g) {
242  WRN_NG << "User " << ui.name << " has unknown game_id: " << ui.game_id << std::endl;
243  continue;
244  }
245 
246  switch(ui.relation) {
248  g->has_friends = true;
249  break;
251  g->has_ignored = true;
252  break;
253  default:
254  break;
255  }
256  }
257 
258  std::stable_sort(users_.begin(), users_.end());
259 }
260 
262 {
263  DBG_LB << "lobby_info::sync_games_display_status";
264  DBG_LB << "games_by_id_ size: " << games_by_id_.size();
265 
266  auto i = games_by_id_.begin();
267 
268  while(i != games_by_id_.end()) {
269  if(i->second.display_status == game_info::disp_status::DELETED) {
270  i = games_by_id_.erase(i);
271  } else {
272  i->second.display_status = game_info::disp_status::CLEAN;
273  ++i;
274  }
275  }
276 
277  DBG_LB << " -> " << games_by_id_.size() << std::endl;
278 
280 }
281 
283 {
284  auto i = games_by_id_.find(id);
285  return i == games_by_id_.end() ? nullptr : &i->second;
286 }
287 
289 {
290  auto i = games_by_id_.find(id);
291  return i == games_by_id_.end() ? nullptr : &i->second;
292 }
293 
294 room_info* chat_info::get_room(const std::string& name)
295 {
296  try {
297  return &rooms_.at(name);
298  } catch(const std::out_of_range&) {
299  return nullptr;
300  }
301 }
302 
303 const room_info* chat_info::get_room(const std::string& name) const
304 {
305  try {
306  return &rooms_.at(name);
307  } catch(const std::out_of_range&) {
308  return nullptr;
309  }
310 }
311 
312 bool chat_info::has_room(const std::string& name) const
313 {
314  return get_room(name) != nullptr;
315 }
316 
317 user_info* lobby_info::get_user(const std::string& name)
318 {
319  for(auto& user : users_) {
320  if(user.name == name) {
321  return &user;
322  }
323  }
324 
325  return nullptr;
326 }
327 
328 void chat_info::open_room(const std::string& name)
329 {
330  // TODO: use try_emplace with C++20
331  rooms_.emplace(name, room_info(name));
332 }
333 
334 void chat_info::close_room(const std::string& name)
335 {
336  DBG_LB << "lobby info: closing room " << name << std::endl;
337  rooms_.erase(name);
338 }
339 
341 {
342  games_.reserve(games_by_id_.size());
343  games_.clear();
344 
345  for(auto& v : games_by_id_) {
346  games_.push_back(&v.second);
347  }
348 
349  // Reset the visibility mask. Its size should then match games_'s and all its bits be true.
350  games_visibility_.resize(games_.size());
351  games_visibility_.reset();
352  games_visibility_.flip();
353 }
354 
356 {
357  for(const auto& filter_func : game_filters_) {
358  if(!game_filter_invert_(filter_func(game))) {
359  return false;
360  }
361  }
362 
363  return true;
364 }
365 
367 {
368  // Since games_visibility_ is a visibility mask over games_,
369  // they need to be the same size or we'll end up with issues.
370  assert(games_visibility_.size() == games_.size());
371 
372  for(unsigned i = 0; i < games_.size(); ++i) {
374  }
375 }
376 
378 {
379  for(auto& user : users_) {
380  user.update_state(game_id);
381  }
382 }
383 
384 } // end namespace mp
#define SCOPE_LB
Definition: lobby_info.cpp:36
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
const config & gamelist() const
Returns the raw game list config data.
Definition: lobby_info.hpp:62
user_info * get_user(const std::string &name)
Returns info on the user with the given name, or nullptr if they don&#39;t eixst.
Definition: lobby_info.cpp:317
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
static lg::log_domain log_engine("engine")
This class represents the info a client has about a game on the server.
Definition: lobby_data.hpp:141
#define DBG_LB
Definition: lobby_info.cpp:33
child_itors child_range(config_key_type key)
Definition: config.cpp:344
std::map< int, game_info > game_info_map
Definition: lobby_info.hpp:36
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition: config.cpp:1188
std::function< bool(bool)> game_filter_invert_
Definition: lobby_info.hpp:148
std::vector< game_filter_func > game_filters_
Definition: lobby_info.hpp:146
std::string name
Definition: lobby_data.hpp:150
void process_gamelist(const config &data)
Process a full game list.
Definition: lobby_info.cpp:120
void apply_game_filter()
Generates a new list of games that match the current filter functions and inversion setting...
Definition: lobby_info.cpp:366
std::vector< std::string > installed_addons_
Definition: lobby_info.hpp:134
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
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:52
void friend_message(bool is_lobby, const std::string &sender, const std::string &message)
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
#define WRN_LB
Definition: lobby_info.cpp:34
void private_message(bool is_lobby, const std::string &sender, const std::string &message)
boost::dynamic_bitset games_visibility_
Definition: lobby_info.hpp:150
void sync_games_display_status()
Definition: lobby_info.cpp:261
static lg::log_domain log_lobby("lobby")
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:178
game_info_map games_by_id_
Definition: lobby_info.hpp:140
bool has_room(const std::string &name) const
Returns whether a room with the given name has been opened.
Definition: lobby_info.cpp:312
room_info * get_room(const std::string &name)
Returns info on room with the given name, or nullptr if it doesn&#39;t exist.
Definition: lobby_info.cpp:294
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1125
bool gamelist_initialized_
Definition: lobby_info.hpp:138
std::string name
Definition: lobby_data.hpp:128
const char * display_status_string() const
Definition: lobby_data.cpp:545
std::size_t i
Definition: function.cpp:967
#define WRN_NG
Definition: lobby_info.cpp:30
const std::vector< game_info * > & games() const
Definition: lobby_info.hpp:104
void public_message(bool is_lobby, const std::string &sender, const std::string &message)
double g
Definition: astarsearch.cpp:65
This class represents the information a client has about a room.
Definition: lobby_data.hpp:68
void server_message(bool is_lobby, const std::string &sender, const std::string &message)
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
Definition: lobby_info.cpp:140
void refresh_installed_addons_cache()
Definition: lobby_info.cpp:54
void process_userlist()
Definition: lobby_info.cpp:228
void player_leaves(bool is_lobby)
This class represents the information a client has about another player.
Definition: lobby_data.hpp:105
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition: config.hpp:745
void game_created(const std::string &scenario, const std::string &name)
void player_joins(bool is_lobby)
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:30
notify_mode
Definition: lobby_info.hpp:187
void close_room(const std::string &name)
Close the chat room with the given name.
Definition: lobby_info.cpp:334
#define e
#define ERR_LB
Definition: lobby_info.cpp:35
user_relation relation
Definition: lobby_data.hpp:131
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
std::vector< user_info > users_
Definition: lobby_info.hpp:144
std::vector< game_info * > games_
Definition: lobby_info.hpp:142
mock_char c
void open_room(const std::string &name)
Open a new chat room with the given name.
Definition: lobby_info.cpp:328
bool process_gamelist_diff_impl(const config &data)
Definition: lobby_info.cpp:151