The Battle for Wesnoth  1.19.14+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  optional_const_config scenario = game_config_manager::get()->game_config().find_child("multiplayer", "id", game["scenario"].str());
127  if(!scenario) {
128  ERR_LB << "Scenario " << game["scenario"].str() << " not found in game config";
129  continue;
130  }
131 
132  config qgame;
133  int human_sides = 0;
134  for(const auto& side : scenario->child_range("side")) {
135  if(side["controller"].str() == "human") {
136  human_sides++;
137  }
138  }
139  if(human_sides == 0) {
140  ERR_LB << "No human sides for scenario " << game["scenario"];
141  continue;
142  }
143  // negative id means a game preset
144  qgame["id"] = game["id"].to_int();
145  // all are set as game_preset so they show up in that tab of the MP lobby
146  qgame["game_preset"] = true;
147 
148  qgame["name"] = scenario["name"];
149  qgame["mp_scenario"] = game["scenario"];
150  qgame["mp_era"] = game["era"];
151  qgame["mp_use_map_settings"] = game["use_map_settings"];
152  qgame["mp_fog"] = game["fog"];
153  qgame["mp_shroud"] = game["shroud"];
154  qgame["mp_village_gold"] = game["village_gold"];
155  qgame["experience_modifier"] = game["experience_modifier"];
156 
157  qgame["mp_countdown"] = game["countdown"];
158  if(qgame["mp_countdown"].to_bool()) {
159  qgame["mp_countdown_reservoir_time"] = game["countdown_reservoir_time"];
160  qgame["mp_countdown_init_time"] = game["countdown_init_time"];
161  qgame["mp_countdown_action_bonus"] = game["countdown_action_bonus"];
162  qgame["mp_countdown_turn_bonus"] = game["countdown_turn_bonus"];
163  }
164 
165  qgame["observer"] = game["observer"];
166  qgame["human_sides"] = human_sides;
167 
168  if(scenario->has_attribute("map_data")) {
169  qgame["map_data"] = scenario["map_data"];
170  } else {
171  qgame["map_data"] = filesystem::read_map(scenario["map_file"]);
172  }
173  qgame["hash"] = game_config_manager::get()->game_config().mandatory_child("multiplayer_hashes")[game["scenario"].str()];
174 
175  config& qchild = qgame.add_child("slot_data");
176  qchild["vacant"] = human_sides;
177  qchild["max"] = human_sides;
178 
179  game_info g(qgame, installed_addons_);
180  games_by_id_.emplace(g.id, std::move(g));
181  }
182 
183  for(const auto& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
185  games_by_id_.emplace(game.id, std::move(game));
186  }
187 
188  DBG_LB << dump_games_map(games_by_id_);
189  DBG_LB << dump_games_config(gamelist_.mandatory_child("gamelist"));
190 
192 }
193 
195 {
197  // the gamelist is now corrupted, stop further processing and wait for a fresh list.
198  gamelist_initialized_ = false;
199  return false;
200  } else {
201  return true;
202  }
203 }
204 
206 {
207  SCOPE_LB;
208  if(!gamelist_initialized_) {
209  return false;
210  }
211 
212  DBG_LB << "prediff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
213 
214  try {
215  gamelist_.apply_diff(data, true);
216  } catch(const config::error& e) {
217  ERR_LB << "Error while applying the gamelist diff: '" << e.message << "' Getting a new gamelist.";
218  return false;
219  }
220 
221  DBG_LB << "postdiff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
222  DBG_LB << dump_games_map(games_by_id_);
223 
224  for(config& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
225  DBG_LB << "data process: " << c["id"] << " (" << c[config::diff_track_attribute] << ")";
226 
227  const int game_id = c["id"].to_int();
228  if(game_id == 0) {
229  ERR_LB << "game with id 0 in gamelist config";
230  return false;
231  }
232 
233  auto current_i = games_by_id_.find(game_id);
234 
235  const std::string& diff_result = c[config::diff_track_attribute];
236 
237  if(diff_result == "new" || diff_result == "modified") {
238  // note: at this point (1.14.3) the server never sends a 'modified' and instead
239  // just sends a 'delete' followed by a 'new', it still works becasue the delete doesn't
240  // delete the element and just marks it as game_info::DELETED so that game_info::DELETED
241  // is replaced by game_info::UPDATED below. See also
242  // https://github.com/wesnoth/wesnoth/blob/1.14/src/server/server.cpp#L149
243  if(current_i == games_by_id_.end()) {
244  games_by_id_.emplace(game_id, game_info(c, installed_addons_));
245  continue;
246  }
247 
248  // Had a game with that id, so update it and mark it as such
249  current_i->second = game_info(c, installed_addons_);
250  current_i->second.display_status = game_info::disp_status::UPDATED;
251  } else if(diff_result == "deleted") {
252  if(current_i == games_by_id_.end()) {
253  WRN_LB << "Would have to delete a game that I don't have: " << game_id;
254  continue;
255  }
256 
257  if(current_i->second.display_status == game_info::disp_status::NEW) {
258  // This means the game never made it through to the user interface,
259  // so just deleting it is fine.
260  games_by_id_.erase(current_i);
261  } else {
262  current_i->second.display_status = game_info::disp_status::DELETED;
263  }
264  }
265  }
266 
267  DBG_LB << dump_games_map(games_by_id_);
268 
269  try {
271  } catch(const config::error& e) {
272  ERR_LB << "Error while applying the gamelist diff (2): '" << e.message << "' Getting a new gamelist.";
273  return false;
274  }
275 
276  DBG_LB << "postclean " << dump_games_config(gamelist_.mandatory_child("gamelist"));
277 
279  return true;
280 }
281 
283 {
284  SCOPE_LB;
285 
286  users_.clear();
287  for(const auto& c : gamelist_.child_range("user")) {
288  user_info& ui = users_.emplace_back(c);
289 
290  if(ui.game_id == 0) {
291  continue;
292  }
293 
295  if(!g) {
296  WRN_NG << "User " << ui.name << " has unknown game_id: " << ui.game_id;
297  continue;
298  }
299 
300  switch(ui.get_relation()) {
302  g->has_friends = true;
303  break;
305  g->has_ignored = true;
306  break;
307  default:
308  break;
309  }
310  }
311 
312  std::stable_sort(users_.begin(), users_.end());
313 }
314 
315 std::function<void()> lobby_info::begin_state_sync()
316 {
317  // First, update the list of game pointers to reflect any changes made to games_by_id_.
318  // This guarantees anything that calls games() before post cleanup has valid pointers,
319  // since there will likely have been changes to games_by_id_ caused by network traffic.
321 
322  return [this]() {
323  DBG_LB << "lobby_info, second state sync stage";
324  DBG_LB << "games_by_id_ size: " << games_by_id_.size();
325 
326  auto i = games_by_id_.begin();
327 
328  while(i != games_by_id_.end()) {
329  if(i->second.display_status == game_info::disp_status::DELETED) {
330  i = games_by_id_.erase(i);
331  } else {
332  i->second.display_status = game_info::disp_status::CLEAN;
333  ++i;
334  }
335  }
336 
337  DBG_LB << " -> " << games_by_id_.size();
338 
340 
341  // Now that both containers are again in sync, update the visibility mask. We want to do
342  // this last since the filer functions are expensive.
344  };
345 }
346 
348 {
349  auto i = games_by_id_.find(id);
350  return i == games_by_id_.end() ? nullptr : &i->second;
351 }
352 
354 {
355  auto i = games_by_id_.find(id);
356  return i == games_by_id_.end() ? nullptr : &i->second;
357 }
358 
359 user_info* lobby_info::get_user(const std::string& name)
360 {
361  for(auto& user : users_) {
362  if(user.name == name) {
363  return &user;
364  }
365  }
366 
367  return nullptr;
368 }
369 
371 {
372  games_.reserve(games_by_id_.size());
373  games_.clear();
374 
375  for(auto& v : games_by_id_) {
376  games_.push_back(&v.second);
377  }
378 
379  // Reset the visibility mask. Its size should then match games_'s and all its bits be true.
380  games_visibility_.resize(games_.size());
381  games_visibility_.reset();
382  games_visibility_.flip();
383 }
384 
386 {
387  for(const auto& filter_func : game_filters_) {
388  if(!game_filter_invert_(filter_func(game))) {
389  return false;
390  }
391  }
392 
393  return true;
394 }
395 
397 {
398  // Since games_visibility_ is a visibility mask over games_,
399  // they need to be the same size or we'll end up with issues.
400  assert(games_visibility_.size() == games_.size());
401 
402  for(unsigned i = 0; i < games_.size(); ++i) {
404  }
405 }
406 
407 } // 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
const config & mandatory_child(config_key_type key) const
optional_const_config find_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:282
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:385
bool process_gamelist_diff_impl(const config &data)
Definition: lobby_info.cpp:205
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:194
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:315
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
Definition: lobby_info.cpp:347
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:370
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:359
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:396
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