The Battle for Wesnoth  1.19.13+dev
lobby_info.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 
17 
18 #include "addon/manager.hpp" // for installed_addons
19 #include "game_config_manager.hpp"
20 #include "log.hpp"
21 #include "mp_ui_alerts.hpp"
23 
24 
25 static lg::log_domain log_engine("engine");
26 #define WRN_NG LOG_STREAM(warn, log_engine)
27 
28 static lg::log_domain log_lobby("lobby");
29 #define DBG_LB LOG_STREAM(debug, log_lobby)
30 #define WRN_LB LOG_STREAM(warn, log_lobby)
31 #define ERR_LB LOG_STREAM(err, log_lobby)
32 #define SCOPE_LB log_scope2(log_lobby, __func__)
33 
34 namespace mp
35 {
37  : installed_addons_()
38  , gamelist_()
39  , gamelist_initialized_(false)
40  , games_by_id_()
41  , games_()
42  , users_()
43  , game_filters_()
44  , game_filter_invert_()
45  , games_visibility_()
46 {
48 }
49 
51 {
52  // This function does not refer to an addon database, it calls filesystem functions.
53  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
54  // even if the user changes the contents of the addon directory in the meantime.
56 }
57 
58 void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
59 {
60  switch(mode) {
65  break;
68  break;
71  break;
74  break;
77  break;
80  break;
83  break;
84  default:
85  break;
86  }
87 }
88 
89 namespace
90 {
91 std::string dump_games_map(const lobby_info::game_info_map& games)
92 {
93  std::stringstream ss;
94  for(const auto& v : games) {
95  const game_info& game = v.second;
96  ss << "G" << game.id << "(" << game.name << ") " << game.display_status_string() << " ";
97  }
98 
99  ss << "\n";
100  return ss.str();
101 }
102 
103 std::string dump_games_config(const config& gamelist)
104 {
105  std::stringstream ss;
106  for(const auto& c : gamelist.child_range("game")) {
107  ss << "g" << c["id"] << "(" << c["name"] << ") " << c[config::diff_track_attribute] << " ";
108  }
109 
110  ss << "\n";
111  return ss.str();
112 }
113 
114 } // end anonymous namespace
115 
117 {
118  SCOPE_LB;
119 
120  gamelist_ = data;
121  gamelist_initialized_ = true;
122 
123  games_by_id_.clear();
124 
125  for(const config& game : prefs::get().get_game_presets()) {
126  config qgame;
127  const config& scenario = game_config_manager::get()->game_config().find_mandatory_child("multiplayer", "id", game["scenario"].str());
128  int human_sides = 0;
129  for(const auto& side : scenario.child_range("side")) {
130  if(side["controller"].str() == "human") {
131  human_sides++;
132  }
133  }
134  if(human_sides == 0) {
135  ERR_LB << "No human sides for scenario " << game["scenario"];
136  continue;
137  }
138  // negative id means a game preset
139  qgame["id"] = game["id"].to_int();
140  // all are set as game_preset so they show up in that tab of the MP lobby
141  qgame["game_preset"] = true;
142 
143  qgame["name"] = scenario["name"];
144  qgame["mp_scenario"] = game["scenario"];
145  qgame["mp_era"] = game["era"];
146  qgame["mp_use_map_settings"] = game["use_map_settings"];
147  qgame["mp_fog"] = game["fog"];
148  qgame["mp_shroud"] = game["shroud"];
149  qgame["mp_village_gold"] = game["village_gold"];
150  qgame["experience_modifier"] = game["experience_modifier"];
151 
152  qgame["mp_countdown"] = game["countdown"];
153  if(qgame["mp_countdown"].to_bool()) {
154  qgame["mp_countdown_reservoir_time"] = game["countdown_reservoir_time"];
155  qgame["mp_countdown_init_time"] = game["countdown_init_time"];
156  qgame["mp_countdown_action_bonus"] = game["countdown_action_bonus"];
157  qgame["mp_countdown_turn_bonus"] = game["countdown_turn_bonus"];
158  }
159 
160  qgame["observer"] = game["observer"];
161  qgame["human_sides"] = human_sides;
162 
163  if(scenario.has_attribute("map_data")) {
164  qgame["map_data"] = scenario["map_data"];
165  } else {
166  qgame["map_data"] = filesystem::read_map(scenario["map_file"]);
167  }
168  qgame["hash"] = game_config_manager::get()->game_config().mandatory_child("multiplayer_hashes")[game["scenario"].str()];
169 
170  config& qchild = qgame.add_child("slot_data");
171  qchild["vacant"] = human_sides;
172  qchild["max"] = human_sides;
173 
174  game_info g(qgame, installed_addons_);
175  games_by_id_.emplace(g.id, std::move(g));
176  }
177 
178  for(const auto& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
180  games_by_id_.emplace(game.id, std::move(game));
181  }
182 
183  DBG_LB << dump_games_map(games_by_id_);
184  DBG_LB << dump_games_config(gamelist_.mandatory_child("gamelist"));
185 
187 }
188 
190 {
192  // the gamelist is now corrupted, stop further processing and wait for a fresh list.
193  gamelist_initialized_ = false;
194  return false;
195  } else {
196  return true;
197  }
198 }
199 
201 {
202  SCOPE_LB;
203  if(!gamelist_initialized_) {
204  return false;
205  }
206 
207  DBG_LB << "prediff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
208 
209  try {
210  gamelist_.apply_diff(data, true);
211  } catch(const config::error& e) {
212  ERR_LB << "Error while applying the gamelist diff: '" << e.message << "' Getting a new gamelist.";
213  return false;
214  }
215 
216  DBG_LB << "postdiff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
217  DBG_LB << dump_games_map(games_by_id_);
218 
219  for(config& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
220  DBG_LB << "data process: " << c["id"] << " (" << c[config::diff_track_attribute] << ")";
221 
222  const int game_id = c["id"].to_int();
223  if(game_id == 0) {
224  ERR_LB << "game with id 0 in gamelist config";
225  return false;
226  }
227 
228  auto current_i = games_by_id_.find(game_id);
229 
230  const std::string& diff_result = c[config::diff_track_attribute];
231 
232  if(diff_result == "new" || diff_result == "modified") {
233  // note: at this point (1.14.3) the server never sends a 'modified' and instead
234  // just sends a 'delete' followed by a 'new', it still works becasue the delete doesn't
235  // delete the element and just marks it as game_info::DELETED so that game_info::DELETED
236  // is replaced by game_info::UPDATED below. See also
237  // https://github.com/wesnoth/wesnoth/blob/1.14/src/server/server.cpp#L149
238  if(current_i == games_by_id_.end()) {
239  games_by_id_.emplace(game_id, game_info(c, installed_addons_));
240  continue;
241  }
242 
243  // Had a game with that id, so update it and mark it as such
244  current_i->second = game_info(c, installed_addons_);
245  current_i->second.display_status = game_info::disp_status::UPDATED;
246  } else if(diff_result == "deleted") {
247  if(current_i == games_by_id_.end()) {
248  WRN_LB << "Would have to delete a game that I don't have: " << game_id;
249  continue;
250  }
251 
252  if(current_i->second.display_status == game_info::disp_status::NEW) {
253  // This means the game never made it through to the user interface,
254  // so just deleting it is fine.
255  games_by_id_.erase(current_i);
256  } else {
257  current_i->second.display_status = game_info::disp_status::DELETED;
258  }
259  }
260  }
261 
262  DBG_LB << dump_games_map(games_by_id_);
263 
264  try {
266  } catch(const config::error& e) {
267  ERR_LB << "Error while applying the gamelist diff (2): '" << e.message << "' Getting a new gamelist.";
268  return false;
269  }
270 
271  DBG_LB << "postclean " << dump_games_config(gamelist_.mandatory_child("gamelist"));
272 
274  return true;
275 }
276 
278 {
279  SCOPE_LB;
280 
281  users_.clear();
282  for(const auto& c : gamelist_.child_range("user")) {
283  user_info& ui = users_.emplace_back(c);
284 
285  if(ui.game_id == 0) {
286  continue;
287  }
288 
290  if(!g) {
291  WRN_NG << "User " << ui.name << " has unknown game_id: " << ui.game_id;
292  continue;
293  }
294 
295  switch(ui.get_relation()) {
297  g->has_friends = true;
298  break;
300  g->has_ignored = true;
301  break;
302  default:
303  break;
304  }
305  }
306 
307  std::stable_sort(users_.begin(), users_.end());
308 }
309 
310 std::function<void()> lobby_info::begin_state_sync()
311 {
312  // First, update the list of game pointers to reflect any changes made to games_by_id_.
313  // This guarantees anything that calls games() before post cleanup has valid pointers,
314  // since there will likely have been changes to games_by_id_ caused by network traffic.
316 
317  return [this]() {
318  DBG_LB << "lobby_info, second state sync stage";
319  DBG_LB << "games_by_id_ size: " << games_by_id_.size();
320 
321  auto i = games_by_id_.begin();
322 
323  while(i != games_by_id_.end()) {
324  if(i->second.display_status == game_info::disp_status::DELETED) {
325  i = games_by_id_.erase(i);
326  } else {
327  i->second.display_status = game_info::disp_status::CLEAN;
328  ++i;
329  }
330  }
331 
332  DBG_LB << " -> " << games_by_id_.size();
333 
335 
336  // Now that both containers are again in sync, update the visibility mask. We want to do
337  // this last since the filer functions are expensive.
339  };
340 }
341 
343 {
344  auto i = games_by_id_.find(id);
345  return i == games_by_id_.end() ? nullptr : &i->second;
346 }
347 
349 {
350  auto i = games_by_id_.find(id);
351  return i == games_by_id_.end() ? nullptr : &i->second;
352 }
353 
354 user_info* lobby_info::get_user(const std::string& name)
355 {
356  for(auto& user : users_) {
357  if(user.name == name) {
358  return &user;
359  }
360  }
361 
362  return nullptr;
363 }
364 
366 {
367  games_.reserve(games_by_id_.size());
368  games_.clear();
369 
370  for(auto& v : games_by_id_) {
371  games_.push_back(&v.second);
372  }
373 
374  // Reset the visibility mask. Its size should then match games_'s and all its bits be true.
375  games_visibility_.resize(games_.size());
376  games_visibility_.reset();
377  games_visibility_.flip();
378 }
379 
381 {
382  for(const auto& filter_func : game_filters_) {
383  if(!game_filter_invert_(filter_func(game))) {
384  return false;
385  }
386  }
387 
388  return true;
389 }
390 
392 {
393  // Since games_visibility_ is a visibility mask over games_,
394  // they need to be the same size or we'll end up with issues.
395  assert(games_visibility_.size() == games_.size());
396 
397  for(unsigned i = 0; i < games_.size(); ++i) {
399  }
400 }
401 
402 } // end namespace mp
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:186
double g
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition: config.hpp:817
child_itors child_range(config_key_type key)
Definition: config.cpp:268
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1022
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition: config.cpp:1083
config & add_child(config_key_type key)
Definition: config.cpp:436
static game_config_manager * get()
const game_config_view & game_config() const
const config & mandatory_child(config_key_type key) const
const config & find_mandatory_child(config_key_type key, const std::string &name, const std::string &value) const
std::function< bool(bool)> game_filter_invert_
Definition: lobby_info.hpp:149
void process_userlist()
Definition: lobby_info.cpp:277
bool gamelist_initialized_
Definition: lobby_info.hpp:139
bool is_game_visible(const game_info &)
Returns whether the game would be visible after the game filters are applied.
Definition: lobby_info.cpp:380
bool process_gamelist_diff_impl(const config &data)
Definition: lobby_info.cpp:200
void process_gamelist(const config &data)
Process a full game list.
Definition: lobby_info.cpp:116
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
Definition: lobby_info.cpp:189
void refresh_installed_addons_cache()
Definition: lobby_info.cpp:50
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:310
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
Definition: lobby_info.cpp:342
game_info_map games_by_id_
Definition: lobby_info.hpp:141
void make_games_vector()
Generates a new vector of game pointers from the ID/game map.
Definition: lobby_info.cpp:365
std::vector< user_info > users_
Definition: lobby_info.hpp:145
user_info * get_user(const std::string &name)
Returns info on the user with the given name, or nullptr if they don't eixst.
Definition: lobby_info.cpp:354
std::vector< game_info * > games_
Definition: lobby_info.hpp:143
boost::dynamic_bitset games_visibility_
Definition: lobby_info.hpp:151
std::map< int, game_info > game_info_map
Definition: lobby_info.hpp:35
void apply_game_filter()
Generates a new list of games that match the current filter functions and inversion setting.
Definition: lobby_info.cpp:391
std::vector< game_filter_func > game_filters_
Definition: lobby_info.hpp:147
std::vector< std::string > installed_addons_
Definition: lobby_info.hpp:135
static prefs & get()
std::size_t i
Definition: function.cpp:1032
#define WRN_NG
Definition: lobby_info.cpp:26
#define ERR_LB
Definition: lobby_info.cpp:31
static lg::log_domain log_engine("engine")
static lg::log_domain log_lobby("lobby")
#define DBG_LB
Definition: lobby_info.cpp:29
#define WRN_LB
Definition: lobby_info.cpp:30
#define SCOPE_LB
Definition: lobby_info.cpp:32
Standard logging facilities (interface).
std::string read_map(const std::string &name)
void public_message(bool is_lobby, const std::string &sender, const std::string &message)
void game_created(const std::string &scenario, const std::string &name)
void private_message(bool is_lobby, const std::string &sender, const std::string &message)
void player_joins(bool is_lobby)
void friend_message(bool is_lobby, const std::string &sender, const std::string &message)
void server_message(bool is_lobby, const std::string &sender, const std::string &message)
void player_leaves(bool is_lobby)
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:49
void do_notify(notify_mode mode, const std::string &sender, const std::string &message)
Definition: lobby_info.cpp:58
notify_mode
Definition: lobby_info.hpp:154
std::string_view data
Definition: picture.cpp:188
This class represents the info a client has about a game on the server.
Definition: lobby_data.hpp:62
This class represents the information a client has about another player.
Definition: lobby_data.hpp:29
user_relation get_relation() const
Definition: lobby_data.cpp:72
std::string name
Definition: lobby_data.hpp:50
mock_char c
#define e