The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
20#include "log.hpp"
21#include "mp_ui_alerts.hpp"
24
25
26static lg::log_domain log_engine("engine");
27#define WRN_NG LOG_STREAM(warn, log_engine)
28
29static 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
35namespace 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
59void 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
90namespace
91{
92std::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
104std::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;
123
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
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;
233 return false;
234 }
235
236 DBG_LB << "prediff " << dump_games_config(gamelist_.mandatory_child("gamelist"));
237
238 try {
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
339std::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
383user_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
#define WRN_NG
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
child_itors child_range(std::string_view key)
Definition config.cpp:268
static const char * diff_track_attribute
The name of the attribute used for tracking diff changes.
Definition config.hpp:816
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition config.cpp:1000
bool has_attribute(std::string_view key) const
Definition config.cpp:157
void clear_diff_track(const config &diff)
Clear any tracking info from a previous apply_diff call with tracking.
Definition config.cpp:1061
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition config.cpp:362
void clear()
Definition config.cpp:802
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_
void process_userlist()
bool gamelist_initialized_
bool is_game_visible(const game_info &)
Returns whether the game would be visible after the game filters are applied.
bool process_gamelist_diff_impl(const config &data)
void process_gamelist(const config &data)
Process a full game list.
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
void refresh_installed_addons_cache()
std::function< void()> begin_state_sync()
Updates the game pointer list and returns a second stage cleanup function to be called after any acti...
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
game_info_map games_by_id_
void make_games_vector()
Generates a new vector of game pointers from the ID/game map.
std::vector< user_info > users_
user_info * get_user(const std::string &name)
Returns info on the user with the given name, or nullptr if they don't eixst.
std::vector< game_info * > games_
boost::dynamic_bitset games_visibility_
std::map< int, game_info > game_info_map
void apply_game_filter()
Generates a new list of games that match the current filter functions and inversion setting.
std::vector< game_filter_func > game_filters_
std::vector< std::string > installed_addons_
static prefs & get()
config::child_itors get_game_presets()
const config * cfg
#define ERR_LB
#define DBG_LB
static lg::log_domain log_engine("engine")
static lg::log_domain log_lobby("lobby")
#define WRN_LB
#define SCOPE_LB
Standard logging facilities (interface).
std::string read_map(const std::string &name)
Game configuration data as global variables.
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.
void do_notify(notify_mode mode, const std::string &sender, const std::string &message)
notify_mode
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.
This class represents the information a client has about another player.
user_relation get_relation() const
std::string name
#define i
#define g
#define e
#define c