The Battle for Wesnoth  1.19.15+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"
24 
25 
26 static lg::log_domain log_engine("engine");
27 #define WRN_NG LOG_STREAM(warn, log_engine)
28 
29 static lg::log_domain log_lobby("lobby");
30 #define DBG_LB LOG_STREAM(debug, log_lobby)
31 #define WRN_LB LOG_STREAM(warn, log_lobby)
32 #define ERR_LB LOG_STREAM(err, log_lobby)
33 #define SCOPE_LB log_scope2(log_lobby, __func__)
34 
35 namespace mp
36 {
38  : installed_addons_()
39  , gamelist_()
40  , gamelist_initialized_(false)
41  , games_by_id_()
42  , games_()
43  , users_()
44  , game_filters_()
45  , game_filter_invert_()
46  , games_visibility_()
47 {
49 }
50 
52 {
53  // This function does not refer to an addon database, it calls filesystem functions.
54  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
55  // even if the user changes the contents of the addon directory in the meantime.
57 }
58 
59 void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
60 {
61  switch(mode) {
66  break;
69  break;
72  break;
75  break;
78  break;
81  break;
84  break;
85  default:
86  break;
87  }
88 }
89 
90 namespace
91 {
92 std::string dump_games_map(const lobby_info::game_info_map& games)
93 {
94  std::stringstream ss;
95  for(const auto& v : games) {
96  const game_info& game = v.second;
97  ss << "G" << game.id << "(" << game.name << ") " << game.display_status_string() << " ";
98  }
99 
100  ss << "\n";
101  return ss.str();
102 }
103 
104 std::string dump_games_config(const config& gamelist)
105 {
106  std::stringstream ss;
107  for(const auto& c : gamelist.child_range("game")) {
108  ss << "g" << c["id"] << "(" << c["name"] << ") " << c[config::diff_track_attribute] << " ";
109  }
110 
111  ss << "\n";
112  return ss.str();
113 }
114 
115 } // end anonymous namespace
116 
118 {
119  SCOPE_LB;
120 
121  gamelist_ = data;
122  gamelist_initialized_ = true;
123 
124  games_by_id_.clear();
125 
126  for(const config& game : prefs::get().get_game_presets()) {
128 
129  optional_const_config scenario = game_config.find_child("multiplayer", "id", game["scenario"].str());
130  if(!scenario) {
131  ERR_LB << "Scenario " << game["scenario"].str() << " not found in game config " << game["id"];
132  continue;
133  }
134  optional_const_config era = game_config.find_child("era", "id", game["era"].str());
135  if(!era) {
136  ERR_LB << "Era " << game["era"].str() << " not found in game config " << game["id"];
137  continue;
138  }
139 
140  config qgame;
141  int human_sides = 0;
142  for(const auto& side : scenario->child_range("side")) {
143  if(side["controller"].str() == "human") {
144  human_sides++;
145  }
146  }
147  if(human_sides == 0) {
148  ERR_LB << "No human sides for scenario " << game["scenario"];
149  continue;
150  }
151  // negative id means a game preset
152  qgame["id"] = game["id"].to_int();
153  // all are set as game_preset so they show up in that tab of the MP lobby
154  qgame["game_preset"] = true;
155 
156  qgame["name"] = scenario["name"];
157  qgame["mp_scenario"] = game["scenario"];
158  qgame["mp_era"] = game["era"];
159  qgame["mp_use_map_settings"] = game["use_map_settings"];
160  qgame["mp_fog"] = game["fog"];
161  qgame["mp_shroud"] = game["shroud"];
162  qgame["mp_village_gold"] = game["village_gold"];
163  qgame["experience_modifier"] = game["experience_modifier"];
164  qgame["random_faction_mode"] = game["random_faction_mode"];
165 
166  qgame["mp_countdown"] = game["countdown"];
167  if(qgame["mp_countdown"].to_bool()) {
168  qgame["mp_countdown_reservoir_time"] = game["countdown_reservoir_time"];
169  qgame["mp_countdown_init_time"] = game["countdown_init_time"];
170  qgame["mp_countdown_action_bonus"] = game["countdown_action_bonus"];
171  qgame["mp_countdown_turn_bonus"] = game["countdown_turn_bonus"];
172  }
173 
174  qgame["observer"] = game["observer"];
175  qgame["human_sides"] = human_sides;
176 
177  for(const std::string& mod : utils::split(game["modifications"].str())) {
178  auto cfg = game_config.find_child("modification", "id", mod);
179 
180  if(!cfg) {
181  ERR_LB << "Modification " << mod << " not found in game config " << game["id"];
182  continue;
183  }
184 
185  qgame.add_child("modification", config{ "name", cfg["name"], "id", mod });
186  }
187 
188  if(game.has_child("options")) {
189  qgame.add_child("options", game.mandatory_child("options"));
190  }
191 
192  if(scenario->has_attribute("map_data")) {
193  qgame["map_data"] = scenario["map_data"];
194  } else {
195  qgame["map_data"] = filesystem::read_map(scenario["map_file"]);
196  }
197  qgame["hash"] = game_config.mandatory_child("multiplayer_hashes")[game["scenario"].str()];
198 
199  config& qchild = qgame.add_child("slot_data");
200  qchild["vacant"] = human_sides;
201  qchild["max"] = human_sides;
202 
203  game_info g(qgame, installed_addons_);
204  games_by_id_.emplace(g.id, std::move(g));
205  }
206 
207  for(const auto& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
209  games_by_id_.emplace(game.id, std::move(game));
210  }
211 
212  DBG_LB << dump_games_map(games_by_id_);
213  DBG_LB << dump_games_config(gamelist_.mandatory_child("gamelist"));
214 
216 }
217 
219 {
221  // the gamelist is now corrupted, stop further processing and wait for a fresh list.
222  gamelist_initialized_ = false;
223  return false;
224  } else {
225  return true;
226  }
227 }
228 
230 {
231  SCOPE_LB;
232  if(!gamelist_initialized_) {
233  return false;
234  }
235 
236  DBG_LB << "prediff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
237 
238  try {
239  gamelist_.apply_diff(data, true);
240  } catch(const config::error& e) {
241  ERR_LB << "Error while applying the gamelist diff: '" << e.message << "' Getting a new gamelist.";
242  return false;
243  }
244 
245  DBG_LB << "postdiff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
246  DBG_LB << dump_games_map(games_by_id_);
247 
248  for(config& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
249  DBG_LB << "data process: " << c["id"] << " (" << c[config::diff_track_attribute] << ")";
250 
251  const int game_id = c["id"].to_int();
252  if(game_id == 0) {
253  ERR_LB << "game with id 0 in gamelist config";
254  return false;
255  }
256 
257  auto current_i = games_by_id_.find(game_id);
258 
259  const std::string& diff_result = c[config::diff_track_attribute];
260 
261  if(diff_result == "new" || diff_result == "modified") {
262  // note: at this point (1.14.3) the server never sends a 'modified' and instead
263  // just sends a 'delete' followed by a 'new', it still works becasue the delete doesn't
264  // delete the element and just marks it as game_info::DELETED so that game_info::DELETED
265  // is replaced by game_info::UPDATED below. See also
266  // https://github.com/wesnoth/wesnoth/blob/1.14/src/server/server.cpp#L149
267  if(current_i == games_by_id_.end()) {
268  games_by_id_.emplace(game_id, game_info(c, installed_addons_));
269  continue;
270  }
271 
272  // Had a game with that id, so update it and mark it as such
273  current_i->second = game_info(c, installed_addons_);
274  current_i->second.display_status = game_info::disp_status::UPDATED;
275  } else if(diff_result == "deleted") {
276  if(current_i == games_by_id_.end()) {
277  WRN_LB << "Would have to delete a game that I don't have: " << game_id;
278  continue;
279  }
280 
281  if(current_i->second.display_status == game_info::disp_status::NEW) {
282  // This means the game never made it through to the user interface,
283  // so just deleting it is fine.
284  games_by_id_.erase(current_i);
285  } else {
286  current_i->second.display_status = game_info::disp_status::DELETED;
287  }
288  }
289  }
290 
291  DBG_LB << dump_games_map(games_by_id_);
292 
293  try {
295  } catch(const config::error& e) {
296  ERR_LB << "Error while applying the gamelist diff (2): '" << e.message << "' Getting a new gamelist.";
297  return false;
298  }
299 
300  DBG_LB << "postclean " << dump_games_config(gamelist_.mandatory_child("gamelist"));
301 
303  return true;
304 }
305 
307 {
308  SCOPE_LB;
309 
310  users_.clear();
311  for(const auto& c : gamelist_.child_range("user")) {
312  user_info& ui = users_.emplace_back(c);
313 
314  if(ui.game_id == 0) {
315  continue;
316  }
317 
319  if(!g) {
320  WRN_NG << "User " << ui.name << " has unknown game_id: " << ui.game_id;
321  continue;
322  }
323 
324  switch(ui.get_relation()) {
326  g->has_friends = true;
327  break;
329  g->has_ignored = true;
330  break;
331  default:
332  break;
333  }
334  }
335 
336  std::stable_sort(users_.begin(), users_.end());
337 }
338 
339 std::function<void()> lobby_info::begin_state_sync()
340 {
341  // First, update the list of game pointers to reflect any changes made to games_by_id_.
342  // This guarantees anything that calls games() before post cleanup has valid pointers,
343  // since there will likely have been changes to games_by_id_ caused by network traffic.
345 
346  return [this]() {
347  DBG_LB << "lobby_info, second state sync stage";
348  DBG_LB << "games_by_id_ size: " << games_by_id_.size();
349 
350  auto i = games_by_id_.begin();
351 
352  while(i != games_by_id_.end()) {
353  if(i->second.display_status == game_info::disp_status::DELETED) {
354  i = games_by_id_.erase(i);
355  } else {
356  i->second.display_status = game_info::disp_status::CLEAN;
357  ++i;
358  }
359  }
360 
361  DBG_LB << " -> " << games_by_id_.size();
362 
364 
365  // Now that both containers are again in sync, update the visibility mask. We want to do
366  // this last since the filer functions are expensive.
368  };
369 }
370 
372 {
373  auto i = games_by_id_.find(id);
374  return i == games_by_id_.end() ? nullptr : &i->second;
375 }
376 
378 {
379  auto i = games_by_id_.find(id);
380  return i == games_by_id_.end() ? nullptr : &i->second;
381 }
382 
383 user_info* lobby_info::get_user(const std::string& name)
384 {
385  for(auto& user : users_) {
386  if(user.name == name) {
387  return &user;
388  }
389  }
390 
391  return nullptr;
392 }
393 
395 {
396  games_.reserve(games_by_id_.size());
397  games_.clear();
398 
399  for(auto& v : games_by_id_) {
400  games_.push_back(&v.second);
401  }
402 
403  // Reset the visibility mask. Its size should then match games_'s and all its bits be true.
404  games_visibility_.resize(games_.size());
405  games_visibility_.reset();
406  games_visibility_.flip();
407 }
408 
410 {
411  for(const auto& filter_func : game_filters_) {
412  if(!game_filter_invert_(filter_func(game))) {
413  return false;
414  }
415  }
416 
417  return true;
418 }
419 
421 {
422  // Since games_visibility_ is a visibility mask over games_,
423  // they need to be the same size or we'll end up with issues.
424  assert(games_visibility_.size() == games_.size());
425 
426  for(unsigned i = 0; i < games_.size(); ++i) {
428  }
429 }
430 
431 } // 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:1016
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition: config.cpp:1077
config & add_child(config_key_type key)
Definition: config.cpp:436
static game_config_manager * get()
const game_config_view & game_config() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
std::function< bool(bool)> game_filter_invert_
Definition: lobby_info.hpp:149
void process_userlist()
Definition: lobby_info.cpp:306
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:409
bool process_gamelist_diff_impl(const config &data)
Definition: lobby_info.cpp:229
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
void refresh_installed_addons_cache()
Definition: lobby_info.cpp:51
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
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:394
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:383
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:420
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()
const config * cfg
std::size_t i
Definition: function.cpp:1032
#define WRN_NG
Definition: lobby_info.cpp:27
#define ERR_LB
Definition: lobby_info.cpp:32
static lg::log_domain log_engine("engine")
static lg::log_domain log_lobby("lobby")
#define DBG_LB
Definition: lobby_info.cpp:30
#define WRN_LB
Definition: lobby_info.cpp:31
#define SCOPE_LB
Definition: lobby_info.cpp:33
Standard logging facilities (interface).
std::string read_map(const std::string &name)
Game configuration data as global variables.
Definition: build_info.cpp:61
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:59
notify_mode
Definition: lobby_info.hpp:154
std::vector< std::string > split(const config_attribute_value &val)
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