The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
lobby_data.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2018 by Tomasz Sniatowski <kailoran@gmail.com>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "config.hpp"
19 #include "preferences/game.hpp"
21 #include "filesystem.hpp"
22 #include "formatter.hpp"
23 #include "formula/string_utils.hpp"
24 #include "gettext.hpp"
25 #include "lexical_cast.hpp"
26 #include "log.hpp"
27 #include "map/map.hpp"
28 #include "map/exception.hpp"
29 #include "terrain/type_data.hpp"
30 #include "wml_exception.hpp"
31 #include "version.hpp"
32 
33 #include <iterator>
34 
35 #include <boost/algorithm/string.hpp>
36 
37 static lg::log_domain log_config("config");
38 #define ERR_CF LOG_STREAM(err, log_config)
39 static lg::log_domain log_engine("engine");
40 #define WRN_NG LOG_STREAM(warn, log_engine)
41 
42 static lg::log_domain log_lobby("lobby");
43 #define DBG_LB LOG_STREAM(info, log_lobby)
44 #define LOG_LB LOG_STREAM(info, log_lobby)
45 #define ERR_LB LOG_STREAM(err, log_lobby)
46 
47 namespace mp {
48 
50  const std::string& user,
51  const std::string& message)
52  : timestamp(timestamp), user(user), message(message)
53 {
54 }
55 
57 {
58 }
59 
61  const std::string& user,
62  const std::string& message)
63 {
64  history_.emplace_back(timestamp, user, message);
65 }
66 
67 
68 void chat_session::add_message(const std::string& user, const std::string& message)
69 {
70  add_message(time(nullptr), user, message);
71 }
72 
74 {
75  history_.clear();
76 }
77 
78 room_info::room_info(const std::string& name) : name_(name), members_(), log_()
79 {
80 }
81 
82 bool room_info::is_member(const std::string& user) const
83 {
84  return members_.find(user) != members_.end();
85 }
86 
88 {
89  members_.insert(user);
90 }
91 
93 {
94  members_.erase(user);
95 }
96 
98 {
99  members_.clear();
100  for(const auto & m : data.child_range("member"))
101  {
102  members_.insert(m["name"]);
103  }
104 }
105 
107  : name(c["name"])
108  , game_id(c["game_id"])
109  , relation(ME)
110  , state(game_id == 0 ? LOBBY : GAME)
111  , registered(c["registered"].to_bool())
112  , observing(c["status"] == "observing")
113 {
114  update_relation();
115 }
116 
117 void user_info::update_state(int selected_game_id,
118  const room_info* current_room /*= nullptr*/)
119 {
120  if(game_id != 0) {
121  if(game_id == selected_game_id) {
122  state = SEL_GAME;
123  } else {
124  state = GAME;
125  }
126  } else {
127  if(current_room != nullptr && current_room->is_member(name)) {
128  state = SEL_ROOM;
129  } else {
130  state = LOBBY;
131  }
132  }
133  update_relation();
134 }
135 
137 {
138  if(name == preferences::login()) {
139  relation = ME;
140  } else if(preferences::is_ignored(name)) {
141  relation = IGNORED;
142  } else if(preferences::is_friend(name)) {
143  relation = FRIEND;
144  } else {
145  relation = NEUTRAL;
146  }
147 }
148 
149 namespace
150 {
151 const std::string& spaced_em_dash()
152 {
153  static const std::string res = " " + font::unicode_em_dash + " ";
154  return res;
155 }
156 
157 // Returns an abbreviated form of the provided string - ie, 'Ageless Era' should become 'AE'
158 std::string make_short_name(const std::string& long_name)
159 {
160  if(long_name.empty()) {
161  return "";
162  }
163 
164  size_t pos = 0;
165 
166  std::stringstream ss;
167  ss << long_name[pos];
168 
169  while(pos < long_name.size()) {
170  pos = long_name.find(' ', pos + 1);
171 
172  if(pos <= long_name.size() - 2) {
173  ss << long_name[pos + 1];
174  }
175  }
176 
177  return ss.str();
178 }
179 
180 std::string make_game_type_marker(std::string text, bool color_for_missing)
181 {
182  if(color_for_missing) {
183  return formatter() << "<b><span color='#f00'>[" << text << "]</span></b> ";
184  } else {
185  return formatter() << "<b>[" << text << "]</b> ";
186  }
187 }
188 
189 } // end anon namespace
190 
191 game_info::game_info(const config& game, const config& game_config, const std::vector<std::string>& installed_addons)
192  : id(game["id"])
193  , map_data(game["map_data"])
194  , name(game["name"])
195  , scenario()
196  , remote_scenario(false)
197  , map_info()
198  , map_size_info()
199  , era()
200  , era_short()
201  , gold(game["mp_village_gold"])
202  , support(game["mp_village_support"])
203  , xp(game["experience_modifier"].str() + "%")
204  , vision()
205  , status()
206  , time_limit()
207  , vacant_slots()
208  , current_turn(0)
209  , reloaded(game["savegame"].to_bool())
210  , started(false)
211  , fog(game["mp_fog"].to_bool())
212  , shroud(game["mp_shroud"].to_bool())
213  , observers(game["observer"].to_bool(true))
214  , shuffle_sides(game["shuffle_sides"].to_bool(true))
215  , use_map_settings(game["mp_use_map_settings"].to_bool())
216  , registered_users_only(game["registered_users_only"].to_bool())
217  , verified(true)
218  , password_required(game["password"].to_bool())
219  , have_era(true)
220  , have_all_mods(true)
221  , has_friends(false)
222  , has_ignored(false)
223  , display_status(NEW)
224  , required_addons()
225  , addons_outcome(SATISFIED)
226 {
227  // Parse the list of addons required to join this game.
228  for(const config& addon : game.child_range("addon")) {
229  if(addon.has_attribute("id")) {
230  if(std::find(installed_addons.begin(), installed_addons.end(), addon["id"].str()) == installed_addons.end()) {
231  required_addon r;
232  r.addon_id = addon["id"].str();
234 
235  // Use addon name if provided, else fall back on the addon id.
236  if(addon.has_attribute("name")) {
237  r.message = vgettext("Missing addon: $name", {{"name", addon["name"].str()}});
238  } else {
239  r.message = vgettext("Missing addon: $id", {{"id", addon["id"].str()}});
240  }
241 
242  required_addons.push_back(std::move(r));
243 
244  if(addons_outcome == SATISFIED) {
246  }
247  }
248  }
249  }
250 
251  if(!game["mp_era"].empty()) {
252  const config& era_cfg = game_config.find_child("era", "id", game["mp_era"]);
253  if(era_cfg) {
254  era = era_cfg["name"].str();
255  era_short = era_cfg["short_name"].str();
256  if(era_short.empty()) {
257  era_short = make_short_name(era);
258  }
259 
260  ADDON_REQ result = check_addon_version_compatibility(era_cfg, game);
261  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
262  } else {
263  have_era = !game["require_era"].to_bool(true);
264  era = vgettext("$era_name (missing)", {{"era_name", game["mp_era_name"].str()}});
265  era_short = make_short_name(era);
266  verified = false;
267 
269  }
270  } else {
271  era = _("Unknown era");
272  era_short = "??";
273  verified = false;
274  }
275 
276  std::stringstream info_stream;
277  info_stream << era;
278 
279  for(const config& cfg : game.child_range("modification")) {
280  mod_info += (mod_info.empty() ? "" : ", ") + cfg["name"].str();
281 
282  if(cfg["require_modification"].to_bool(false)) {
283  if(const config& mod = game_config.find_child("modification", "id", cfg["id"])) {
284  ADDON_REQ result = check_addon_version_compatibility(mod, game);
285  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
286  } else {
287  have_all_mods = false;
288  mod_info += " " + _("(missing)");
289 
291  }
292  }
293  }
294 
295  info_stream << mod_info;
296 
297  if(map_data.empty()) {
298  map_data = filesystem::read_map(game["mp_scenario"]);
299  }
300 
301  if(map_data.empty()) {
302  info_stream << " — ??×??";
303  } else {
304  try {
305  gamemap map(std::make_shared<terrain_type_data>(game_config), map_data);
306  std::ostringstream msi;
307  msi << map.w() << font::unicode_multiplication_sign << map.h();
308  map_size_info = msi.str();
309  info_stream << spaced_em_dash() << map_size_info;
310  } catch(incorrect_map_format_error& e) {
311  ERR_CF << "illegal map: " << e.message << std::endl;
312  verified = false;
313  } catch(wml_exception& e) {
314  ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
315  verified = false;
316  }
317  }
318 
319  info_stream << " ";
320 
321  //
322  // Check scenarios and campaigns
323  //
324  if(!game["mp_scenario"].empty() && game["mp_campaign"].empty()) {
325  // Check if it's a multiplayer scenario
326  const config* level_cfg = &game_config.find_child("multiplayer", "id", game["mp_scenario"]);
327 
328  // Check if it's a user map
329  if(!*level_cfg) {
330  level_cfg = &game_config.find_child("generic_multiplayer", "id", game["mp_scenario"]);
331  }
332 
333  if(*level_cfg) {
334  scenario = formatter() << make_game_type_marker(_("S"), false) << (*level_cfg)["name"].str();
335  info_stream << scenario;
336 
337  // Reloaded games do not match the original scenario hash, so it makes no sense
338  // to test them, since they always would appear as remote scenarios
339  if(!reloaded) {
340  if(const config& hashes = game_config.child("multiplayer_hashes")) {
341  std::string hash = game["hash"];
342  bool hash_found = false;
343  for(const auto & i : hashes.attribute_range()) {
344  if(i.first == game["mp_scenario"] && i.second == hash) {
345  hash_found = true;
346  break;
347  }
348  }
349 
350  if(!hash_found) {
351  remote_scenario = true;
352  info_stream << spaced_em_dash();
353  info_stream << _("Remote scenario");
354  verified = false;
355  }
356  }
357  }
358 
359  if((*level_cfg)["require_scenario"].to_bool(false)) {
360  ADDON_REQ result = check_addon_version_compatibility((*level_cfg), game);
361  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
362  }
363  } else {
364  scenario = formatter() << make_game_type_marker(_("S"), true) << game["mp_scenario_name"].str();
365  info_stream << scenario;
366  verified = false;
367  }
368  } else if(!game["mp_campaign"].empty()) {
369  if(const config& campaign_cfg = game_config.find_child("campaign", "id", game["mp_campaign"])) {
370  std::stringstream campaign_text;
371  campaign_text
372  << make_game_type_marker(_("C"), false)
373  << campaign_cfg["name"] << spaced_em_dash()
374  << game["mp_scenario_name"];
375 
376  // Difficulty
377  config difficulties = gui2::dialogs::generate_difficulty_config(campaign_cfg);
378  for(const config& difficulty : difficulties.child_range("difficulty")) {
379  if(difficulty["define"] == game["difficulty_define"]) {
380  campaign_text << spaced_em_dash() << difficulty["description"];
381 
382  break;
383  }
384  }
385 
386  scenario = campaign_text.str();
387  info_stream << campaign_text.rdbuf();
388 
389  // TODO: should we have this?
390  //if(game["require_scenario"].to_bool(false)) {
391  ADDON_REQ result = check_addon_version_compatibility(campaign_cfg, game);
392  addons_outcome = std::max(addons_outcome, result); // Elevate to most severe error level encountered so far
393  //}
394  } else {
395  scenario = formatter() << make_game_type_marker(_("C"), true) << game["mp_campaign_name"].str();
396  info_stream << scenario;
397  verified = false;
398  }
399  } else {
400  scenario = _("Unknown scenario");
401  info_stream << scenario;
402  verified = false;
403  }
404 
405  // Remove any newlines that might have been in game titles
406  boost::replace_all(scenario, "\n", " " + font::unicode_em_dash + " ");
407 
408  if(reloaded) {
409  info_stream << spaced_em_dash();
410  info_stream << _("Reloaded game");
411  verified = false;
412  }
413 
414  // These should always be present in the data the server sends, but may or may not be empty.
415  // I'm just using child_or_empty here to preempt any cases where they might not be included.
416  const config& s = game.child_or_empty("slot_data");
417  const config& t = game.child_or_empty("turn_data");
418 
419  if(!s.empty()) {
420  started = false;
421 
422  vacant_slots = s["vacant"].to_unsigned();
423 
424  if(vacant_slots > 0) {
425  status = formatter() << _n("Vacant Slot:", "Vacant Slots:", vacant_slots) << " " << vacant_slots << "/" << s["max"];
426  } else {
427  // TODO: status message for no vacant sides!
428  }
429  }
430 
431  if(!t.empty()) {
432  started = true;
433 
434  current_turn = t["current"].to_unsigned();
435  const int max_turns = t["max"].to_int();
436 
437  if(max_turns > -1) {
438  status = formatter() << _("Turn") << " " << t["current"] << "/" << max_turns;
439  } else {
440  status = formatter() << _("Turn") << " " << t["current"];
441  }
442  }
443 
444  if(fog) {
445  vision = _("Fog");
446  if(shroud) {
447  vision += "/";
448  vision += _("Shroud");
449  }
450  } else if(shroud) {
451  vision = _("Shroud");
452  } else {
453  vision = _("none");
454  }
455 
456  if(game["mp_countdown"].to_bool()) {
458  << game["mp_countdown_init_time"].str() << "+"
459  << game["mp_countdown_turn_bonus"].str() << "/"
460  << game["mp_countdown_action_bonus"].str();
461  } else {
462  time_limit = _("none");
463  }
464 
465  map_info = info_stream.str();
466 }
467 
469 {
470  if(!local_item.has_attribute("addon_id") || !local_item.has_attribute("addon_version")) {
471  return SATISFIED;
472  }
473 
474  if(const config& game_req = game.find_child("addon", "id", local_item["addon_id"])) {
475  required_addon r {local_item["addon_id"].str(), SATISFIED, ""};
476 
477  // Local version
478  const version_info local_ver(local_item["addon_version"].str());
479  version_info local_min_ver(local_item.has_attribute("addon_min_version") ? local_item["addon_min_version"] : local_item["addon_version"]);
480 
481  // If the UMC didn't specify last compatible version, assume no backwards compatibility.
482  // Also apply some sanity checking regarding min version; if the min ver doesn't make sense, ignore it.
483  local_min_ver = std::min(local_min_ver, local_ver);
484 
485  // Remote version
486  const version_info remote_ver(game_req["version"].str());
487  version_info remote_min_ver(game_req.has_attribute("min_version") ? game_req["min_version"] : game_req["version"]);
488 
489  remote_min_ver = std::min(remote_min_ver, remote_ver);
490 
491  // Check if the host is too out of date to play.
492  if(local_min_ver > remote_ver) {
493  r.outcome = CANNOT_SATISFY;
494 
495  r.message = vgettext("The host's version of <i>$addon</i> is incompatible. They have version <b>$host_ver</b> while you have version <b>$local_ver</b>.", {
496  {"addon", local_item["addon_title"].str()},
497  {"host_ver", remote_ver.str()},
498  {"local_ver", local_ver.str()}
499  });
500 
501  required_addons.push_back(r);
502  return r.outcome;
503  }
504 
505  // Check if our version is too out of date to play.
506  if(remote_min_ver > local_ver) {
507  r.outcome = NEED_DOWNLOAD;
508 
509  r.message = vgettext("Your version of <i>$addon</i> is incompatible. You have version <b>$local_ver</b> while the host has version <b>$host_ver</b>.", {
510  {"addon", local_item["addon_title"].str()},
511  {"host_ver", remote_ver.str()},
512  {"local_ver", local_ver.str()}
513  });
514 
515  required_addons.push_back(r);
516  return r.outcome;
517  }
518  }
519 
520  return SATISFIED;
521 }
522 
524 {
525  return !started && vacant_slots > 0;
526 }
527 
529 {
531 }
532 
534 {
535  switch(display_status) {
536  case game_info::CLEAN:
537  return "clean";
538  case game_info::NEW:
539  return "new";
540  case game_info::DELETED:
541  return "deleted";
542  case game_info::UPDATED:
543  return "updated";
544  default:
545  ERR_CF << "BAD display_status " << display_status << " in game " << id << "\n";
546  return "?";
547  }
548 }
549 
551 {
552  const std::string& s1 = map_info;
553  const std::string& s2 = name;
554  return std::search(s1.begin(), s1.end(), filter.begin(), filter.end(),
555  chars_equal_insensitive) != s1.end()
556  || std::search(s2.begin(), s2.end(), filter.begin(), filter.end(),
557  chars_equal_insensitive) != s2.end();
558 }
559 
560 }
std::string scenario
Definition: lobby_data.hpp:147
std::string map_size_info
Definition: lobby_data.hpp:150
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:419
ADDON_REQ addons_outcome
Definition: lobby_data.hpp:197
std::vector< char_t > string
std::string status
Definition: lobby_data.hpp:159
void add_message(const time_t &timestamp, const std::string &user, const std::string &message)
Definition: lobby_data.cpp:60
std::string mod_info
Definition: lobby_data.hpp:153
std::string era()
Definition: game.cpp:704
#define ERR_CF
Definition: lobby_data.cpp:38
bool match_string_filter(const std::string &filter) const
Definition: lobby_data.cpp:550
bool is_authenticated()
Definition: game.cpp:185
unsigned int current_turn
Definition: lobby_data.hpp:163
std::deque< chat_message > history_
Definition: lobby_data.hpp:60
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:782
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
New lexcical_cast header.
user_state state
Definition: lobby_data.hpp:129
bool can_join() const
Definition: lobby_data.cpp:523
child_itors child_range(config_key_type key)
Definition: config.cpp:362
bool shuffle_sides()
Definition: game.cpp:479
std::set< std::string > members_
Definition: lobby_data.hpp:95
std::string map_info
Definition: lobby_data.hpp:149
bool chars_equal_insensitive(char a, char b)
Definition: general.hpp:21
size_t vacant_slots
Definition: lobby_data.hpp:161
bool remote_scenario
Definition: lobby_data.hpp:148
bool empty() const
Definition: config.cpp:830
Definitions for the interface to Wesnoth Markup Language (WML).
std::string name
Definition: lobby_data.hpp:146
std::string str() const
Definition: formatter.hpp:64
std::string time_limit
Definition: lobby_data.hpp:160
static lg::log_domain log_lobby("lobby")
bool fog()
Definition: game.cpp:544
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:47
user_info(const config &c)
Definition: lobby_data.cpp:106
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
bool is_member(const std::string &user) const
Definition: lobby_data.cpp:82
bool is_friend(const std::string &nick)
Definition: game.cpp:293
std::ostringstream wrapper.
Definition: formatter.hpp:38
int w() const
Effective map width.
Definition: map.hpp:90
std::string vision
Definition: lobby_data.hpp:158
chat_message(const time_t &timestamp, const std::string &user, const std::string &message)
Create a chat message.
Definition: lobby_data.cpp:49
const std::string unicode_multiplication_sign
Definition: constants.cpp:41
Encapsulates the map of the game.
Definition: map.hpp:34
static UNUSEDNOWARN std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:93
static lg::log_domain log_config("config")
std::string map_data
Definition: lobby_data.hpp:145
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:148
bool registered_users_only()
Definition: game.cpp:469
std::string era_short
Definition: lobby_data.hpp:152
bool can_observe() const
Definition: lobby_data.cpp:528
bool is_ignored(const std::string &nick)
Definition: game.cpp:306
std::string read_map(const std::string &name)
std::string str() const
Serializes the version number into string form.
Definition: version.cpp:93
Helper class, don't construct this directly.
std::string login()
std::string name
Definition: lobby_data.hpp:126
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
std::string dev_message
The message for developers telling which problem was triggered, this shouldn't be translated...
bool shroud()
Definition: game.cpp:554
static lg::log_domain log_engine("engine")
int h() const
Effective map height.
Definition: map.hpp:93
config generate_difficulty_config(const config &source)
Helper function to convert old difficulty markup.
const char * display_status_string() const
Definition: lobby_data.cpp:533
Game configuration data as global variables.
Definition: build_info.cpp:53
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:452
static map_location::DIRECTION s
ADDON_REQ check_addon_version_compatibility(const config &local_item, const config &game)
Definition: lobby_data.cpp:468
bool use_map_settings()
Definition: game.cpp:497
size_t i
Definition: function.cpp:933
This class represents the information a client has about a room.
Definition: lobby_data.hpp:66
Declarations for File-IO.
Represents version numbers.
Definition: version.hpp:43
std::string era
Definition: lobby_data.hpp:151
std::string vgettext(const char *msgid, const utils::string_map &symbols)
void update_state(int selected_game_id, const room_info *current_room=nullptr)
Definition: lobby_data.cpp:117
room_info(const std::string &name)
Definition: lobby_data.cpp:78
double t
Definition: astarsearch.cpp:64
bool find(E event, F functor)
Tests whether an event handler is available.
static bool timestamp
Definition: log.cpp:46
const std::string unicode_em_dash
Definition: constants.cpp:39
void remove_member(const std::string &user)
Definition: lobby_data.cpp:92
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:31
game_display_status display_status
Definition: lobby_data.hpp:186
game_info(const config &c, const config &game_config, const std::vector< std::string > &installed_addons)
Definition: lobby_data.cpp:191
static const char * name(const std::vector< SDL_Joystick * > &joysticks, const size_t index)
Definition: joystick.cpp:48
std::vector< required_addon > required_addons
Definition: lobby_data.hpp:196
void update_relation()
Definition: lobby_data.cpp:136
#define e
user_relation relation
Definition: lobby_data.hpp:128
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
mock_char c
Interfaces for manipulating version numbers of engine, add-ons, etc.
void process_room_members(const config &data)
Definition: lobby_data.cpp:97
void add_member(const std::string &user)
Definition: lobby_data.cpp:87