The Battle for Wesnoth  1.19.10+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"
22 
23 
24 static lg::log_domain log_engine("engine");
25 #define WRN_NG LOG_STREAM(warn, log_engine)
26 
27 static lg::log_domain log_lobby("lobby");
28 #define DBG_LB LOG_STREAM(debug, log_lobby)
29 #define WRN_LB LOG_STREAM(warn, log_lobby)
30 #define ERR_LB LOG_STREAM(err, log_lobby)
31 #define SCOPE_LB log_scope2(log_lobby, __func__)
32 
33 namespace mp
34 {
36  : installed_addons_()
37  , gamelist_()
38  , gamelist_initialized_(false)
39  , games_by_id_()
40  , games_()
41  , users_()
42  , game_filters_()
43  , game_filter_invert_()
44  , games_visibility_()
45 {
47 }
48 
50 {
51  // This function does not refer to an addon database, it calls filesystem functions.
52  // For the sanity of the mp lobby, this list should be fixed for the entire lobby session,
53  // even if the user changes the contents of the addon directory in the meantime.
55 }
56 
57 void do_notify(notify_mode mode, const std::string& sender, const std::string& message)
58 {
59  switch(mode) {
64  break;
67  break;
70  break;
73  break;
76  break;
79  break;
82  break;
83  default:
84  break;
85  }
86 }
87 
88 namespace
89 {
90 std::string dump_games_map(const lobby_info::game_info_map& games)
91 {
92  std::stringstream ss;
93  for(const auto& v : games) {
94  const game_info& game = v.second;
95  ss << "G" << game.id << "(" << game.name << ") " << game.display_status_string() << " ";
96  }
97 
98  ss << "\n";
99  return ss.str();
100 }
101 
102 std::string dump_games_config(const config& gamelist)
103 {
104  std::stringstream ss;
105  for(const auto& c : gamelist.child_range("game")) {
106  ss << "g" << c["id"] << "(" << c["name"] << ") " << c[config::diff_track_attribute] << " ";
107  }
108 
109  ss << "\n";
110  return ss.str();
111 }
112 
113 } // end anonymous namespace
114 
116 {
117  SCOPE_LB;
118 
119  gamelist_ = data;
120  gamelist_initialized_ = true;
121 
122  games_by_id_.clear();
123 
124  int queued_id = 0;
125  for(const config& game : game_config_manager::get()->game_config().mandatory_child("game_presets").child_range("game")) {
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 queue-defined game
139  queued_id--;
140  qgame["id"] = queued_id;
141  // all are set as auto_hosted so they show up in that tab of the MP lobby
142  qgame["auto_hosted"] = true;
143 
144  qgame["name"] = scenario["name"];
145  qgame["mp_scenario"] = game["scenario"];
146  qgame["mp_era"] = game["era"];
147  qgame["mp_use_map_settings"] = game["use_map_settings"];
148  qgame["mp_fog"] = game["fog"];
149  qgame["mp_shroud"] = game["shroud"];
150  qgame["mp_village_gold"] = game["village_gold"];
151  qgame["experience_modifier"] = game["experience_modifier"];
152 
153  qgame["mp_countdown"] = game["countdown"];
154  if(qgame["countdown"].to_bool()) {
155  qgame["mp_countdown_reservoir_time"] = game["countdown_reservoir_time"];
156  qgame["mp_countdown_init_time"] = game["countdown_init_time"];
157  qgame["mp_countdown_action_bonus"] = game["countdown_action_bonus"];
158  qgame["mp_countdown_turn_bonus"] = game["countdown_turn_bonus"];
159  }
160 
161  qgame["observer"] = game["observer"];
162  qgame["human_sides"] = human_sides;
163 
164  if(scenario.has_attribute("map_data")) {
165  qgame["map_data"] = scenario["map_data"];
166  } else {
167  qgame["map_data"] = filesystem::read_map(scenario["map_file"]);
168  }
169  qgame["hash"] = game_config_manager::get()->game_config().mandatory_child("multiplayer_hashes")[game["scenario"].str()];
170 
171  config& qchild = qgame.add_child("slot_data");
172  qchild["vacant"] = human_sides;
173  qchild["max"] = human_sides;
174 
175  game_info g(qgame, installed_addons_);
176  games_by_id_.emplace(g.id, std::move(g));
177  }
178 
179  for(const auto& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
181  games_by_id_.emplace(game.id, std::move(game));
182  }
183 
184  DBG_LB << dump_games_map(games_by_id_);
185  DBG_LB << dump_games_config(gamelist_.mandatory_child("gamelist"));
186 
188 }
189 
191 {
193  // the gamelist is now corrupted, stop further processing and wait for a fresh list.
194  gamelist_initialized_ = false;
195  return false;
196  } else {
197  return true;
198  }
199 }
200 
202 {
203  SCOPE_LB;
204  if(!gamelist_initialized_) {
205  return false;
206  }
207 
208  DBG_LB << "prediff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
209 
210  try {
211  gamelist_.apply_diff(data, true);
212  } catch(const config::error& e) {
213  ERR_LB << "Error while applying the gamelist diff: '" << e.message << "' Getting a new gamelist.";
214  return false;
215  }
216 
217  DBG_LB << "postdiff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
218  DBG_LB << dump_games_map(games_by_id_);
219 
220  for(config& c : gamelist_.mandatory_child("gamelist").child_range("game")) {
221  DBG_LB << "data process: " << c["id"] << " (" << c[config::diff_track_attribute] << ")";
222 
223  const int game_id = c["id"].to_int();
224  if(game_id == 0) {
225  ERR_LB << "game with id 0 in gamelist config";
226  return false;
227  }
228 
229  auto current_i = games_by_id_.find(game_id);
230 
231  const std::string& diff_result = c[config::diff_track_attribute];
232 
233  if(diff_result == "new" || diff_result == "modified") {
234  // note: at this point (1.14.3) the server never sends a 'modified' and instead
235  // just sends a 'delete' followed by a 'new', it still works becasue the delete doesn't
236  // delete the element and just marks it as game_info::DELETED so that game_info::DELETED
237  // is replaced by game_info::UPDATED below. See also
238  // https://github.com/wesnoth/wesnoth/blob/1.14/src/server/server.cpp#L149
239  if(current_i == games_by_id_.end()) {
240  games_by_id_.emplace(game_id, game_info(c, installed_addons_));
241  continue;
242  }
243 
244  // Had a game with that id, so update it and mark it as such
245  current_i->second = game_info(c, installed_addons_);
246  current_i->second.display_status = game_info::disp_status::UPDATED;
247  } else if(diff_result == "deleted") {
248  if(current_i == games_by_id_.end()) {
249  WRN_LB << "Would have to delete a game that I don't have: " << game_id;
250  continue;
251  }
252 
253  if(current_i->second.display_status == game_info::disp_status::NEW) {
254  // This means the game never made it through to the user interface,
255  // so just deleting it is fine.
256  games_by_id_.erase(current_i);
257  } else {
258  current_i->second.display_status = game_info::disp_status::DELETED;
259  }
260  }
261  }
262 
263  DBG_LB << dump_games_map(games_by_id_);
264 
265  try {
267  } catch(const config::error& e) {
268  ERR_LB << "Error while applying the gamelist diff (2): '" << e.message << "' Getting a new gamelist.";
269  return false;
270  }
271 
272  DBG_LB << "postclean " << dump_games_config(gamelist_.mandatory_child("gamelist"));
273 
275  return true;
276 }
277 
279 {
280  SCOPE_LB;
281 
282  users_.clear();
283  for(const auto& c : gamelist_.child_range("user")) {
284  user_info& ui = users_.emplace_back(c);
285 
286  if(ui.game_id == 0) {
287  continue;
288  }
289 
291  if(!g) {
292  WRN_NG << "User " << ui.name << " has unknown game_id: " << ui.game_id;
293  continue;
294  }
295 
296  switch(ui.get_relation()) {
298  g->has_friends = true;
299  break;
301  g->has_ignored = true;
302  break;
303  default:
304  break;
305  }
306  }
307 
308  std::stable_sort(users_.begin(), users_.end());
309 }
310 
311 std::function<void()> lobby_info::begin_state_sync()
312 {
313  // First, update the list of game pointers to reflect any changes made to games_by_id_.
314  // This guarantees anything that calls games() before post cleanup has valid pointers,
315  // since there will likely have been changes to games_by_id_ caused by network traffic.
317 
318  return [this]() {
319  DBG_LB << "lobby_info, second state sync stage";
320  DBG_LB << "games_by_id_ size: " << games_by_id_.size();
321 
322  auto i = games_by_id_.begin();
323 
324  while(i != games_by_id_.end()) {
325  if(i->second.display_status == game_info::disp_status::DELETED) {
326  i = games_by_id_.erase(i);
327  } else {
328  i->second.display_status = game_info::disp_status::CLEAN;
329  ++i;
330  }
331  }
332 
333  DBG_LB << " -> " << games_by_id_.size();
334 
336 
337  // Now that both containers are again in sync, update the visibility mask. We want to do
338  // this last since the filer functions are expensive.
340  };
341 }
342 
344 {
345  auto i = games_by_id_.find(id);
346  return i == games_by_id_.end() ? nullptr : &i->second;
347 }
348 
350 {
351  auto i = games_by_id_.find(id);
352  return i == games_by_id_.end() ? nullptr : &i->second;
353 }
354 
355 user_info* lobby_info::get_user(const std::string& name)
356 {
357  for(auto& user : users_) {
358  if(user.name == name) {
359  return &user;
360  }
361  }
362 
363  return nullptr;
364 }
365 
367 {
368  games_.reserve(games_by_id_.size());
369  games_.clear();
370 
371  for(auto& v : games_by_id_) {
372  games_.push_back(&v.second);
373  }
374 
375  // Reset the visibility mask. Its size should then match games_'s and all its bits be true.
376  games_visibility_.resize(games_.size());
377  games_visibility_.reset();
378  games_visibility_.flip();
379 }
380 
382 {
383  for(const auto& filter_func : game_filters_) {
384  if(!game_filter_invert_(filter_func(game))) {
385  return false;
386  }
387  }
388 
389  return true;
390 }
391 
393 {
394  // Since games_visibility_ is a visibility mask over games_,
395  // they need to be the same size or we'll end up with issues.
396  assert(games_visibility_.size() == games_.size());
397 
398  for(unsigned i = 0; i < games_.size(); ++i) {
400  }
401 }
402 
403 } // 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:278
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:381
bool process_gamelist_diff_impl(const config &data)
Definition: lobby_info.cpp:201
void process_gamelist(const config &data)
Process a full game list.
Definition: lobby_info.cpp:115
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
Definition: lobby_info.cpp:190
void refresh_installed_addons_cache()
Definition: lobby_info.cpp:49
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:311
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
Definition: lobby_info.cpp:343
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:366
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:355
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:392
std::vector< game_filter_func > game_filters_
Definition: lobby_info.hpp:147
std::vector< std::string > installed_addons_
Definition: lobby_info.hpp:135
std::size_t i
Definition: function.cpp:1030
#define WRN_NG
Definition: lobby_info.cpp:25
#define ERR_LB
Definition: lobby_info.cpp:30
static lg::log_domain log_engine("engine")
static lg::log_domain log_lobby("lobby")
#define DBG_LB
Definition: lobby_info.cpp:28
#define WRN_LB
Definition: lobby_info.cpp:29
#define SCOPE_LB
Definition: lobby_info.cpp:31
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:57
notify_mode
Definition: lobby_info.hpp:154
std::string_view data
Definition: picture.cpp:178
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