The Battle for Wesnoth  1.17.17+dev
connect_engine.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2023
3  by Andrius Silinskas <silinskas.andrius@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 "ai/configuration.hpp"
19 #include "formula/string_utils.hpp"
24 #include "preferences/game.hpp"
25 #include "gettext.hpp"
26 #include "log.hpp"
27 #include "map/map.hpp"
28 #include "mt_rng.hpp"
29 #include "side_controller.hpp"
30 #include "tod_manager.hpp"
31 #include "team.hpp"
32 #include "wesnothd_connection.hpp"
33 
34 #include <array>
35 #include <cstdlib>
36 #include <ctime>
37 
38 static lg::log_domain log_config("config");
39 #define LOG_CF LOG_STREAM(info, log_config)
40 #define ERR_CF LOG_STREAM(err, log_config)
41 
42 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
43 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
44 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
45 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
46 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
47 
48 static lg::log_domain log_network("network");
49 #define LOG_NW LOG_STREAM(info, log_network)
50 
51 namespace
52 {
53 const std::array controller_names {
54  side_controller::human,
55  side_controller::human,
56  side_controller::ai,
57  side_controller::none,
58  side_controller::reserved
59 };
60 
61 const std::set<std::string> children_to_swap {
62  "village",
63  "unit",
64  "ai"
65 };
66 } // end anon namespace
67 
68 namespace ng {
69 
70 connect_engine::connect_engine(saved_game& state, const bool first_scenario, mp_game_metadata* metadata)
71  : level_()
72  , state_(state)
73  , params_(state.mp_settings())
74  , default_controller_(metadata ? CNTR_NETWORK : CNTR_LOCAL)
75  , mp_metadata_(metadata)
76  , first_scenario_(first_scenario)
77  , force_lock_settings_()
78  , side_engines_()
79  , era_factions_()
80  , team_data_()
81 {
82  // Initial level config from the mp_game_settings.
84  if(level_.empty()) {
85  return;
86  }
87 
88  const bool is_mp = state_.classification().is_normal_mp_game();
89  force_lock_settings_ = (state.mp_settings().saved_game != saved_game_mode::type::midgame) && scenario()["force_lock_settings"].to_bool(!is_mp);
90 
91  // Original level sides.
93 
94  // AI algorithms.
95  if(auto era = level_.optional_child("era")) {
97  }
99 
100  // Set the team name lists and modify the original level sides if necessary.
101  std::vector<std::string> original_team_names;
102  std::string team_prefix(_("Team") + " ");
103 
104  int side_count = 1;
105  for(config& side : sides) {
106  const std::string side_str = std::to_string(side_count);
107 
108  config::attribute_value& team_name = side["team_name"];
109  config::attribute_value& user_team_name = side["user_team_name"];
110 
111  // Revert to default values if appropriate.
112  if(team_name.empty()) {
113  team_name = side_str;
114  }
115 
116  if(params_.use_map_settings && user_team_name.empty()) {
117  user_team_name = team_name;
118  }
119 
120  bool add_team = true;
122  // Only add a team if it is not found.
123  if(std::any_of(team_data_.begin(), team_data_.end(), [&team_name](const team_data_pod& data){
124  return data.team_name == team_name.str();
125  })) {
126  add_team = false;
127  }
128  } else {
129  // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
130  auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
131 
132  // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
133  // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
134  if(name_itor == original_team_names.end()) {
135  original_team_names.push_back(team_name);
136 
137  team_name = "Team " + std::to_string(original_team_names.size());
138  } else {
139  team_name = "Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
140  }
141 
142  user_team_name = team_prefix + side_str;
143  }
144 
145  // Write the serialized translatable team name back to the config. Without this,
146  // the string can appear all messed up after leaving and rejoining a game (see
147  // issue #2040. This affected the mp_join_game dialog). I don't know why that issue
148  // didn't appear the first time you join a game, but whatever.
149  //
150  // The difference between that dialog and mp_staging is that the latter has access
151  // to connect_engine object, meaning it has access to serialized versions of the
152  // user_team_name string stored in the team_data_ vector. mp_join_game handled the
153  // raw side config instead. Again, I don't know why issues only cropped up on a
154  // subsequent join and not the first, but it doesn't really matter.
155  //
156  // This ensures both dialogs have access to the serialized form of the utn string.
157  // As for why this needs to be done in the first place, apparently the simple_wml
158  // format the server (wesnothd) uses doesn't preserve translatable strings (see
159  // issue #342).
160  //
161  // --vultraz, 2018-02-06
162  user_team_name = user_team_name.t_str().to_serialized();
163 
164  if(add_team) {
166  data.team_name = params_.use_map_settings ? team_name : "Team " + side_str;
167  data.user_team_name = user_team_name.str();
168  data.is_player_team = side["allow_player"].to_bool(true);
169 
170  team_data_.push_back(data);
171  }
172 
173  ++side_count;
174  }
175 
176  // Selected era's factions.
177  for(const config& era : level_.mandatory_child("era").child_range("multiplayer_side")) {
178  era_factions_.push_back(&era);
179  }
180 
181  // Sort alphabetically, but with the random faction options always first.
182  // Since some eras have multiple random options we can't just assume there is
183  // only one random faction on top of the list.
184  std::sort(era_factions_.begin(), era_factions_.end(), [](const config* c1, const config* c2) {
185  const config& lhs = *c1;
186  const config& rhs = *c2;
187 
188  // Random factions always first.
189  if(lhs["random_faction"].to_bool() && !rhs["random_faction"].to_bool()) {
190  return true;
191  }
192 
193  if(!lhs["random_faction"].to_bool() && rhs["random_faction"].to_bool()) {
194  return false;
195  }
196 
197  return translation::compare(lhs["name"].str(), rhs["name"].str()) < 0;
198  });
199 
201 
202  // Create side engines.
203  int index = 0;
204  for(const config& s : sides) {
205  side_engines_.emplace_back(new side_engine(s, *this, index++));
206  }
207 
208  if(first_scenario_) {
209  // Add host to the connected users list.
210  import_user(preferences::login(), false);
211  } else {
212  // Add host but don't assign a side to him.
213  import_user(preferences::login(), true);
214 
215  // Load reserved players information into the sides.
216  load_previous_sides_users();
217  }
218 
219  // Only updates the sides in the level.
220  update_level();
221 
222  // If we are connected, send data to the connected host.
223  send_level_data();
224 }
225 
226 
227 config* connect_engine::current_config() {
228  return &scenario();
229 }
230 
231 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
232 {
234  user_data["name"] = name;
235  import_user(user_data, observer, side_taken);
236 }
237 
238 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
239 {
240  const std::string& username = data["name"];
241  assert(!username.empty());
242  if(mp_metadata_) {
243  connected_users_rw().insert(username);
244  }
245 
246  update_side_controller_options();
247 
248  if(observer) {
249  return;
250  }
251 
252  bool side_assigned = false;
253  if(side_taken >= 0) {
254  side_engines_[side_taken]->place_user(data, true);
255  side_assigned = true;
256  }
257 
258  // Check if user has a side(s) reserved for him.
259  for(side_engine_ptr side : side_engines_) {
260  if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
261  side->place_user(data);
262 
263  side_assigned = true;
264  }
265  }
266 
267  // If no sides were assigned for a user,
268  // take a first available side.
269  if(side_taken < 0 && !side_assigned) {
270  for(side_engine_ptr side : side_engines_) {
271  if(side->available_for_user(username) ||
272  side->controller() == CNTR_LOCAL) {
273  side->place_user(data);
274 
275  side_assigned = true;
276  break;
277  }
278  }
279  }
280 
281  // Check if user has taken any sides, which should get control
282  // over any other sides.
283  for(side_engine_ptr user_side : side_engines_) {
284  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
285  for(side_engine_ptr side : side_engines_){
286  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
287  side->place_user(data);
288  }
289  }
290  }
291  }
292 }
293 
294 bool connect_engine::sides_available() const
295 {
296  for(side_engine_ptr side : side_engines_) {
297  if(side->available_for_user()) {
298  return true;
299  }
300  }
301 
302  return false;
303 }
304 
305 void connect_engine::update_level()
306 {
307  DBG_MP << "updating level";
308 
309  scenario().clear_children("side");
310 
311  for(side_engine_ptr side : side_engines_) {
312  scenario().add_child("side", side->new_config());
313  }
314 }
315 
316 void connect_engine::update_and_send_diff()
317 {
318  config old_level = level_;
319  update_level();
320 
321  config diff = level_.get_diff(old_level);
322  if(!diff.empty()) {
323  config scenario_diff;
324  scenario_diff.add_child("scenario_diff", std::move(diff));
325  mp::send_to_server(scenario_diff);
326  }
327 }
328 
329 bool connect_engine::can_start_game() const
330 {
331  if(side_engines_.empty()) {
332  return true;
333  }
334 
335  // First check if all sides are ready to start the game.
336  for(side_engine_ptr side : side_engines_) {
337  if(!side->ready_for_start()) {
338  const int side_num = side->index() + 1;
339  DBG_MP << "not all sides are ready, side " <<
340  side_num << " not ready";
341 
342  return false;
343  }
344  }
345 
346  DBG_MP << "all sides are ready";
347 
348  /*
349  * If at least one human player is slotted with a player/ai we're allowed
350  * to start. Before used a more advanced test but it seems people are
351  * creative in what is used in multiplayer [1] so use a simpler test now.
352  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
353  */
354  for(side_engine_ptr side : side_engines_) {
355  if(side->controller() != CNTR_EMPTY && side->allow_player()) {
356  return true;
357  }
358  }
359 
360  return false;
361 }
362 
363 std::multimap<std::string, config> side_engine::get_side_children()
364 {
365  std::multimap<std::string, config> children;
366 
367  for(const std::string& to_swap : children_to_swap) {
368  for(const config& child : cfg_.child_range(to_swap)) {
369  children.emplace(to_swap, child);
370  }
371  }
372 
373  return children;
374 }
375 
376 void side_engine::set_side_children(std::multimap<std::string, config> children)
377 {
378  for(const std::string& children_to_remove : children_to_swap) {
379  cfg_.clear_children(children_to_remove);
380  }
381 
382  for(std::pair<std::string, config> child_map : children) {
383  cfg_.add_child(child_map.first, child_map.second);
384  }
385 }
386 
387 void connect_engine::start_game()
388 {
389  DBG_MP << "starting a new game";
390 
391  // Resolves the "random faction", "random gender" and "random message"
392  // Must be done before shuffle sides, or some cases will cause errors
393  randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
394  for(side_engine_ptr side : side_engines_) {
395  std::vector<std::string> avoid_faction_ids;
396 
397  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
398  if(params_.mode != random_faction_mode::type::independent) {
399  for(side_engine_ptr side2 : side_engines_) {
400  if(!side2->flg().is_random_faction()) {
401  switch(params_.mode) {
402  case random_faction_mode::type::no_mirror:
403  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
404  break;
405  case random_faction_mode::type::no_ally_mirror:
406  if(side2->team() == side->team()) {// TODO: When the connect engines are fixed to allow multiple teams, this should be changed to "if side1 and side2 are allied, i.e. their list of teams has nonempty intersection"
407  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
408  }
409  break;
410  default:
411  break; // assert(false);
412  }
413  }
414  }
415  }
416  side->resolve_random(rng, avoid_faction_ids);
417  }
418 
419  // Shuffle sides (check settings and if it is a re-loaded game).
420  // Must be done after resolve_random() or shuffle sides, or they won't work.
421  if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.has_child("snapshot") && level_.mandatory_child("snapshot").has_child("side"))) {
422 
423  // Only playable sides should be shuffled.
424  std::vector<int> playable_sides;
425  for(side_engine_ptr side : side_engines_) {
426  if(side->allow_player() && side->allow_shuffle()) {
427  playable_sides.push_back(side->index());
428  }
429  }
430 
431  // Fisher-Yates shuffle.
432  for(int i = playable_sides.size(); i > 1; i--) {
433  const int j_side = playable_sides[rng.get_next_random() % i];
434  const int i_side = playable_sides[i - 1];
435 
436  if(i_side == j_side) continue; //nothing to swap
437 
438  // First we swap everything about a side with another
439  std::swap(side_engines_[j_side], side_engines_[i_side]);
440 
441  // Some 'child' variables such as village ownership and initial side units need to be swapped over as well
442  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
443  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
444  side_engines_[i_side]->set_side_children(tmp_side_children);
445 
446  // Then we revert the swap for fields that are unique to player control and the team they selected
447  std::swap(side_engines_[j_side]->index_, side_engines_[i_side]->index_);
448  std::swap(side_engines_[j_side]->team_, side_engines_[i_side]->team_);
449  }
450  }
451 
452  // Make other clients not show the results of resolve_random().
453  config lock("stop_updates");
454  mp::send_to_server(lock);
455 
456  update_and_send_diff();
457 
458  save_reserved_sides_information();
459 
460  // Build the gamestate object after updating the level.
461  mp::level_to_gamestate(level_, state_);
462 
463  mp::send_to_server(config("start_game"));
464 }
465 
466 void connect_engine::start_game_commandline(const commandline_options& cmdline_opts, const game_config_view& game_config)
467 {
468  DBG_MP << "starting a new game in commandline mode";
469 
470  randomness::mt_rng rng;
471 
472  unsigned num = 0;
473  for(side_engine_ptr side : side_engines_) {
474  num++;
475 
476  // Set the faction, if commandline option is given.
477  if(cmdline_opts.multiplayer_side) {
478  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_side) {
479  if(side_num == num) {
480  if(std::find_if(era_factions_.begin(), era_factions_.end(),
481  [fid = faction_id](const config* faction) { return (*faction)["id"] == fid; })
482  != era_factions_.end()
483  ) {
484  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
485  side->set_faction_commandline(faction_id);
486  } else {
487  ERR_MP << "failed to set side " << side_num << " to faction " << faction_id;
488  }
489  }
490  }
491  }
492 
493  // Set the controller, if commandline option is given.
494  if(cmdline_opts.multiplayer_controller) {
495  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_controller) {
496  if(side_num == num) {
497  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
498  side->set_controller_commandline(faction_id);
499  }
500  }
501  }
502 
503  // Set AI algorithm to default for all sides,
504  // then override if commandline option was given.
505  std::string ai_algorithm = game_config.mandatory_child("ais")["default_ai_algorithm"].str();
506  side->set_ai_algorithm(ai_algorithm);
507 
508  if(cmdline_opts.multiplayer_algorithm) {
509  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_algorithm) {
510  if(side_num == num) {
511  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
512  side->set_ai_algorithm(faction_id);
513  }
514  }
515  }
516 
517  // Finally, resolve "random faction",
518  // "random gender" and "random message", if any remains unresolved.
519  side->resolve_random(rng);
520  } // end top-level loop
521 
522  update_and_send_diff();
523 
524  // Update sides with commandline parameters.
525  if(cmdline_opts.multiplayer_turns) {
526  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns;
527  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
528  }
529 
530  for(config& side : scenario().child_range("side")) {
531  if(cmdline_opts.multiplayer_ai_config) {
532  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_ai_config) {
533  if(side_num == side["side"].to_unsigned()) {
534  DBG_MP << "\tsetting side " << side["side"] << "\tai_config: " << faction_id;
535  side["ai_config"] = faction_id;
536  }
537  }
538  }
539 
540  // Having hard-coded values here is undesirable,
541  // but that's how it is done in the MP lobby
542  // part of the code also.
543  // Should be replaced by settings/constants in both places
544  if(cmdline_opts.multiplayer_ignore_map_settings) {
545  side["gold"] = 100;
546  side["income"] = 1;
547  }
548 
549  if(cmdline_opts.multiplayer_parm) {
550  for(const auto& [side_num, pname, pvalue] : *cmdline_opts.multiplayer_parm) {
551  if(side_num == side["side"].to_unsigned()) {
552  DBG_MP << "\tsetting side " << side["side"] << " " << pname << ": " << pvalue;
553  side[pname] = pvalue;
554  }
555  }
556  }
557  }
558 
559  save_reserved_sides_information();
560 
561  // Build the gamestate object after updating the level
562  mp::level_to_gamestate(level_, state_);
563  mp::send_to_server(config("start_game"));
564 }
565 
566 void connect_engine::leave_game()
567 {
568  DBG_MP << "leaving the game";
569 
570  mp::send_to_server(config("leave_game"));
571 }
572 
573 std::pair<bool, bool> connect_engine::process_network_data(const config& data)
574 {
575  std::pair<bool, bool> result(false, true);
576 
577  if(data.has_child("leave_game")) {
578  result.first = true;
579  return result;
580  }
581 
582  // A side has been dropped.
583  if(auto side_drop = data.optional_child("side_drop")) {
584  unsigned side_index = side_drop["side_num"].to_int() - 1;
585 
586  if(side_index < side_engines_.size()) {
587  side_engine_ptr side_to_drop = side_engines_[side_index];
588 
589  // Remove user, whose side was dropped.
590  connected_users_rw().erase(side_to_drop->player_id());
591  update_side_controller_options();
592 
593  side_to_drop->reset();
594 
595  update_and_send_diff();
596 
597  return result;
598  }
599  }
600 
601  // A player is connecting to the game.
602  if(!data["side"].empty()) {
603  unsigned side_taken = data["side"].to_int() - 1;
604 
605  // Checks if the connecting user has a valid and unique name.
606  const std::string name = data["name"];
607  if(name.empty()) {
608  config response;
609  response["failed"] = true;
610  mp::send_to_server(response);
611 
612  ERR_CF << "ERROR: No username provided with the side.";
613 
614  return result;
615  }
616 
617  if(connected_users().find(name) != connected_users().end()) {
618  // TODO: Seems like a needless limitation
619  // to only allow one side per player.
620  if(find_user_side_index_by_id(name) != -1) {
621  config response;
622  response["failed"] = true;
623  response["message"] = "The nickname '" + name +
624  "' is already in use.";
625  mp::send_to_server(response);
626 
627  return result;
628  } else {
629  connected_users_rw().erase(name);
630  update_side_controller_options();
631  config observer_quit;
632  observer_quit.add_child("observer_quit")["name"] = name;
633  mp::send_to_server(observer_quit);
634  }
635  }
636 
637  // Assigns this user to a side.
638  if(side_taken < side_engines_.size()) {
639  if(!side_engines_[side_taken]->available_for_user(name)) {
640  // This side is already taken.
641  // Try to reassing the player to a different position.
642  side_taken = 0;
643  for(side_engine_ptr s : side_engines_) {
644  if(s->available_for_user()) {
645  break;
646  }
647 
648  side_taken++;
649  }
650 
651  if(side_taken >= side_engines_.size()) {
652  config response;
653  response["failed"] = true;
654  mp::send_to_server(response);
655 
656  config res;
657  config& kick = res.add_child("kick");
658  kick["username"] = data["name"];
659  mp::send_to_server(res);
660 
661  update_and_send_diff();
662 
663  ERR_CF << "ERROR: Couldn't assign a side to '" <<
664  name << "'";
665 
666  return result;
667  }
668  }
669 
670  LOG_CF << "client has taken a valid position";
671 
672  import_user(data, false, side_taken);
673  update_and_send_diff();
674 
675  // Wait for them to choose faction if allowed.
676  side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
677  LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes();
678  result.second = false;
679 
680  LOG_NW << "sent player data";
681  } else {
682  ERR_CF << "tried to take illegal side: " << side_taken;
683 
684  config response;
685  response["failed"] = true;
686  mp::send_to_server(response);
687  }
688  }
689 
690  if(auto change_faction = data.optional_child("change_faction")) {
691  int side_taken = find_user_side_index_by_id(change_faction["name"]);
692  if(side_taken != -1 || !first_scenario_) {
693  import_user(*change_faction, false, side_taken);
694  update_and_send_diff();
695  }
696  }
697 
698  if(auto observer = data.optional_child("observer")) {
699  import_user(*observer, true);
700  update_and_send_diff();
701  }
702 
703  if(auto observer = data.optional_child("observer_quit")) {
704  const std::string& observer_name = observer["name"];
705 
706  if(connected_users().find(observer_name) != connected_users().end()) {
707  connected_users_rw().erase(observer_name);
708  update_side_controller_options();
709 
710  // If the observer was assigned a side, we need to send an update to other
711  // players so they no longer see the observer assigned to that side.
712  if(find_user_side_index_by_id(observer_name) != -1) {
713  update_and_send_diff();
714  }
715  }
716  }
717 
718  return result;
719 }
720 
721 int connect_engine::find_user_side_index_by_id(const std::string& id) const
722 {
723  std::size_t i = 0;
724  for(side_engine_ptr side : side_engines_) {
725  if(side->player_id() == id) {
726  break;
727  }
728 
729  i++;
730  }
731 
732  if(i >= side_engines_.size()) {
733  return -1;
734  }
735 
736  return i;
737 }
738 
739 void connect_engine::send_level_data() const
740 {
741  // Send initial information.
742  if(first_scenario_) {
744  "create_game", config {
745  "name", params_.name,
746  "password", params_.password,
747  "ignored", preferences::get_ignored_delim(),
748  "auto_hosted", false,
749  },
750  });
751  mp::send_to_server(level_);
752  } else {
753  config next_level;
754  next_level.add_child("store_next_scenario", level_);
755  mp::send_to_server(next_level);
756  }
757 }
758 
759 void connect_engine::save_reserved_sides_information()
760 {
761  // Add information about reserved sides to the level config.
762  // N.B. This information is needed only for a host player.
763  std::map<std::string, std::string> side_users = utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
764  for(side_engine_ptr side : side_engines_) {
765  const std::string& save_id = side->save_id();
766  const std::string& player_id = side->player_id();
767  if(!save_id.empty() && !player_id.empty()) {
768  side_users[save_id] = player_id;
769  }
770  }
771 
772  level_.mandatory_child("multiplayer")["side_users"] = utils::join_map(side_users);
773 }
774 
775 void connect_engine::load_previous_sides_users()
776 {
777  std::map<std::string, std::string> side_users = utils::map_split(level_.mandatory_child("multiplayer")["side_users"]);
778  std::set<std::string> names;
779  for(side_engine_ptr side : side_engines_) {
780  const std::string& save_id = side->previous_save_id();
781  if(side_users.find(save_id) != side_users.end()) {
782  side->set_reserved_for(side_users[save_id]);
783 
784  if(side->controller() != CNTR_COMPUTER) {
785  side->set_controller(CNTR_RESERVED);
786  names.insert(side_users[save_id]);
787  }
788 
789  side->update_controller_options();
790  }
791  }
792 
793  //Do this in an extra loop to make sure we import each user only once.
794  for(const std::string& name : names)
795  {
796  if(connected_users().find(name) != connected_users().end() || !mp_metadata_) {
797  import_user(name, false);
798  }
799  }
800 }
801 
802 void connect_engine::update_side_controller_options()
803 {
804  for(side_engine_ptr side : side_engines_) {
805  side->update_controller_options();
806  }
807 }
808 
809 const std::set<std::string>& connect_engine::connected_users() const
810 {
811  if(mp_metadata_) {
812  return mp_metadata_->connected_players;
813  }
814 
815  static std::set<std::string> empty;
816  return empty;
817 }
818 
819 std::set<std::string>& connect_engine::connected_users_rw()
820 {
821  assert(mp_metadata_);
822  return mp_metadata_->connected_players;
823 }
824 
825 side_engine::side_engine(const config& cfg, connect_engine& parent_engine, const int index)
826  : cfg_(cfg)
827  , parent_(parent_engine)
828  , controller_(CNTR_NETWORK)
829  , current_controller_index_(0)
830  , controller_options_()
831  , allow_player_(cfg["allow_player"].to_bool(true))
832  , controller_lock_(cfg["controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
833  , index_(index)
834  , team_(0)
835  , color_(std::min(index, gamemap::MAX_PLAYERS - 1))
836  , gold_(cfg["gold"].to_int(100))
837  , income_(cfg["income"])
838  , reserved_for_(cfg["current_player"])
839  , player_id_()
840  , ai_algorithm_()
841  , chose_random_(cfg["chose_random"].to_bool(false))
842  , disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false))
843  , flg_(parent_.era_factions_, cfg_, parent_.force_lock_settings_, parent_.params_.use_map_settings, parent_.params_.saved_game == saved_game_mode::type::midgame)
844  , allow_changes_(parent_.params_.saved_game != saved_game_mode::type::midgame && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1))
845  , waiting_to_choose_faction_(allow_changes_)
846  , color_options_(game_config::default_colors)
847  //TODO: what should we do if color_ is out of range?
848  , color_id_(color_options_.at(color_))
849 {
850 
851  // Save default attributes that could be overwritten by the faction, so that correct faction lists would be
852  // initialized by flg_manager when the new side config is sent over network.
853  cfg_.add_child("default_faction", config {
854  "type", cfg_["type"],
855  "gender", cfg_["gender"],
856  "faction", cfg_["faction"],
857  "recruit", cfg_["recruit"],
858  });
859 
860  if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
861  ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1;
862  }
863 
864  cfg_["side"] = index_ + 1;
865 
866  // Check if this side should give its control to some other side.
867  const std::size_t side_cntr_index = cfg_["controller"].to_int(-1) - 1;
868  if(side_cntr_index < parent_.side_engines().size()) {
869  // Remove this attribute to avoid locking side
870  // to non-existing controller type.
871  cfg_.remove_attribute("controller");
872 
873  cfg_["previous_save_id"] = parent_.side_engines()[side_cntr_index]->previous_save_id();
874  ERR_MP << "controller=<number> is deperecated";
875  }
876 
877  if(cfg_["controller"] != side_controller::human && cfg_["controller"] != side_controller::ai && cfg_["controller"] != side_controller::none) {
878  //an invalid controller type was specified. Remove it to prevent asertion failures later.
879  cfg_.remove_attribute("controller");
880  }
881 
883 
884  // Tweak the controllers.
885  if(parent_.state_.classification().is_scenario() && cfg_["controller"].blank()) {
886  cfg_["controller"] = side_controller::ai;
887  }
888 
889  if(cfg_["controller"] == side_controller::none) {
891  } else if(cfg_["controller"] == side_controller::ai) {
893  } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
894  // Reserve a side for "current_player", unless the side
895  // is played by an AI.
897  } else if(allow_player_) {
899  } else {
900  // AI is the default.
902  }
903 
904  // Initialize team and color.
905  unsigned team_name_index = 0;
907  if(data.team_name == cfg["team_name"]) {
908  break;
909  }
910 
911  ++team_name_index;
912  }
913 
914  if(team_name_index >= parent_.team_data_.size()) {
915  assert(!parent_.team_data_.empty());
916  team_ = 0;
917  WRN_MP << "In side_engine constructor: Could not find my team_name " << cfg["team_name"] << " among the mp connect engine's list of team names. I am being assigned to the first team. This may indicate a bug!";
918  } else {
919  team_ = team_name_index;
920  }
921 
922  // Check the value of the config's color= key.
923  const std::string given_color = team::get_side_color_id_from_config(cfg_);
924 
925  if(!given_color.empty()) {
926  // If it's valid, save the color...
927  color_id_ = given_color;
928 
929  // ... and find the appropriate index for it.
930  const auto iter = std::find(color_options_.begin(), color_options_.end(), color_id_);
931 
932  if(iter != color_options_.end()) {
933  color_ = std::distance(color_options_.begin(), iter);
934  } else {
935  color_options_.push_back(color_id_);
936  color_ = color_options_.size() - 1;
937  }
938  }
939 
940  // Initialize ai algorithm.
941  if(auto ai = cfg.optional_child("ai")) {
942  ai_algorithm_ = ai["ai_algorithm"].str();
943  }
944 }
945 
946 std::string side_engine::user_description() const
947 {
948  switch(controller_) {
949  case CNTR_LOCAL:
950  return N_("Anonymous player");
951  case CNTR_COMPUTER:
952  if(allow_player_) {
954  } else {
955  return N_("Computer Player");
956  }
957  default:
958  return "";
959  }
960 }
961 
963 {
964  config res = cfg_;
965 
966  // In case of 'shuffle sides' the side index in cfg_ might be wrong which will confuse the team constructor later.
967  res["side"] = index_ + 1;
968 
969  // If the user is allowed to change type, faction, leader etc, then import their new values in the config.
970  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
971  // Merge the faction data to res.
972  config faction = flg_.current_faction();
973  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " faction=" << faction["id"] << " recruit=" << faction["recruit"];
974  res["faction_name"] = faction["name"];
975  res["faction"] = faction["id"];
976  faction.remove_attributes("id", "name", "image", "gender", "type", "description");
977  res.append(faction);
978  }
979 
980  res["controller"] = controller_names[controller_];
981 
982  // The hosts receives the serversided controller tweaks after the start event, but
983  // for mp sync it's very important that the controller types are correct
984  // during the start/prestart event (otherwise random unit creation during prestart fails).
986 
987  // This function (new_config) is only meant to be called by the host's machine, which is why this check
988  // works. It essentially certifies that whatever side has the player_id that matches the host's login
989  // will be flagged. The reason we cannot check mp_game_metadata::is_host is because that flag is *always*
990  // true on the host's machine, meaning this flag would be set to true for every side.
991  res["is_host"] = player_id_ == preferences::login();
992 
993  std::string desc = user_description();
994  if(!desc.empty()) {
995  res["user_description"] = t_string(desc, "wesnoth");
996 
997  desc = VGETTEXT("$playername $side", {
998  {"playername", _(desc.c_str())},
999  {"side", res["side"].str()}
1000  });
1001  } else if(!player_id_.empty()) {
1002  desc = player_id_;
1003  }
1004 
1005  if(res["name"].str().empty() && !desc.empty()) {
1006  //TODO: maybe we should add this in to the leaders config instead of the side config?
1007  res["name"] = desc;
1008  }
1009 
1011  // If this is a saved game and "use_saved" (the default) was chosen for the
1012  // AI algorithm, we do nothing. Otherwise we add the chosen AI and if this
1013  // is a saved game, we also remove the old stages from the AI config.
1014  if(ai_algorithm_ != "use_saved") {
1015  if(parent_.params_.saved_game == saved_game_mode::type::midgame) {
1016  for (config &ai_config : res.child_range("ai")) {
1017  ai_config.clear_children("stage");
1018  }
1019  }
1020  res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
1021  }
1022  }
1023 
1024  // A side's "current_player" is the player which has currently taken that side or the one for which it is reserved.
1025  // The "player_id" is the id of the client who controls that side. It's always the host for Local and AI players and
1026  // always empty for free/reserved sides or null controlled sides. You can use !res["player_id"].empty() to check
1027  // whether a side is already taken.
1028  assert(!preferences::login().empty());
1029  if(controller_ == CNTR_LOCAL) {
1030  res["player_id"] = preferences::login();
1031  res["current_player"] = preferences::login();
1032  } else if(controller_ == CNTR_RESERVED) {
1033  res.remove_attribute("player_id");
1034  res["current_player"] = reserved_for_;
1035  } else if(controller_ == CNTR_COMPUTER) {
1036  // TODO: what is the content of player_id_ here ?
1037  res["current_player"] = desc;
1038  res["player_id"] = preferences::login();
1039  } else if(!player_id_.empty()) {
1040  res["player_id"] = player_id_;
1041  res["current_player"] = player_id_;
1042  }
1043 
1044  res["allow_changes"] = allow_changes_;
1045  res["chose_random"] = chose_random_;
1046 
1047  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
1048  // Find a config where a default leader is and set a new type and gender values for it.
1049  config* leader = &res;
1050 
1051  if(flg_.default_leader_cfg() != nullptr) {
1052  for(config& side_unit : res.child_range("unit")) {
1053  if(*flg_.default_leader_cfg() != side_unit) {
1054  continue;
1055  }
1056 
1057  leader = &side_unit;
1058 
1059  if(flg_.current_leader() != (*leader)["type"]) {
1060  // If a new leader type was selected from carryover, make sure that we reset the leader.
1061  std::string leader_id = (*leader)["id"];
1062  leader->clear();
1063 
1064  if(!leader_id.empty()) {
1065  (*leader)["id"] = leader_id;
1066  }
1067  }
1068 
1069  break;
1070  }
1071  }
1072 
1073  // NOTE: the presence of a type= key overrides no_leader
1074  if(controller_ != CNTR_EMPTY) {
1075  (*leader)["type"] = flg_.current_leader();
1076  (*leader)["gender"] = flg_.current_gender();
1077  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " type=" << (*leader)["type"] << " gender=" << (*leader)["gender"];
1078  } else {
1079  // TODO: FIX THIS SHIT! We shouldn't have a special string to denote no-leader-ness...
1080  (*leader)["type"] = "null";
1081  (*leader)["gender"] = "null";
1082  }
1083 
1084  res["team_name"] = parent_.team_data_[team_].team_name;
1085 
1086  // TODO: Fix this mess!
1087  //
1088  // There is a fundamental disconnect, here. One the one hand we have the idea of
1089  // 'teams' (which don't actually exist). A 'team' has a name (internal key:
1090  // team_name) and a translatable display name (internal key: user_team_name). But
1091  // what we actually have are sides. Sides relate to each other by 'team' (internal
1092  // key: team_name) and each side has it's own name for the team (internal key:
1093  // user_team_name).
1094  //
1095  // The confusion is that the keys from the side have names which one might expect
1096  // always refer to the 'team' concept. THEY DO NOT! They are simply named in such
1097  // a way to confuse the unwary.
1098  //
1099  // There is no simple, clean way to clear up the confusion. So, I'm applying the
1100  // Principle of Least Surprise. The user can see the user_team_name, and it should
1101  // not change. So if the side already has a user_team_name, use it.
1102  //
1103  // In the rare and unlikely (like, probably never happens) case that the side does
1104  // not have a user_team_name, but an (nebulous and non-deterministic term here)
1105  // EARLIER side has the same team_name and that side gives a user_team_name, we'll
1106  // use it.
1107  //
1108  // The effect of this mess, and my lame fix for it, is probably only visible when
1109  // randomizing the sides on a team for multi-player games. But the effect when it's
1110  // not fixed is an obvious mistake on the player's screen when playing a campaign
1111  // in single-player mode.
1112  //
1113  // At some level, all this is probably wrong, but it is the least breakage from the
1114  // mess I found; so deal with it, or fix it.
1115  //
1116  // If, by now, you get the impression this is a kludged-together mess which cries
1117  // out for an honest design and a thoughtful implementation, you're correct! But
1118  // I'm tired, and I'm cranky from wasting a over day on this, and so I'm exercising
1119  // my prerogative as a grey-beard and leaving this for someone else to clean up.
1120  if(res["user_team_name"].empty() || !parent_.params_.use_map_settings) {
1121  res["user_team_name"] = parent_.team_data_[team_].user_team_name;
1122  }
1123 
1124  res["allow_player"] = allow_player_;
1125  res["color"] = color_id_;
1126  res["gold"] = gold_;
1127  res["income"] = income_;
1128  }
1129 
1130 
1131  if(parent_.params_.use_map_settings && parent_.params_.saved_game != saved_game_mode::type::midgame) {
1132  if(cfg_.has_attribute("name")){
1133  res["name"] = cfg_["name"];
1134  }
1135  if(cfg_.has_attribute("user_description") && controller_ == CNTR_COMPUTER){
1136  res["user_description"] = cfg_["user_description"];
1137  }
1138  }
1139 
1140  return res;
1141 }
1142 
1144 {
1145  if(!allow_player_) {
1146  // Sides without players are always ready.
1147  return true;
1148  }
1149 
1150  if((controller_ == CNTR_COMPUTER) ||
1151  (controller_ == CNTR_EMPTY) ||
1152  (controller_ == CNTR_LOCAL)) {
1153 
1154  return true;
1155  }
1156 
1157  if(available_for_user()) {
1158  // If controller_ == CNTR_NETWORK and player_id_.empty().
1159  return false;
1160  }
1161 
1162  if(controller_ == CNTR_NETWORK) {
1164  // The host is ready. A network player, who got a chance
1165  // to choose faction if allowed, is also ready.
1166  return true;
1167  }
1168  }
1169 
1170  return false;
1171 }
1172 
1173 bool side_engine::available_for_user(const std::string& name) const
1174 {
1175  if(controller_ == CNTR_NETWORK && player_id_.empty()) {
1176  // Side is free and waiting for user.
1177  return true;
1178  }
1179 
1180  if(controller_ == CNTR_RESERVED && name.empty()) {
1181  // Side is still available to someone.
1182  return true;
1183  }
1184 
1185  if(controller_ == CNTR_RESERVED && reserved_for_ == name) {
1186  // Side is available only for the player with specific name.
1187  return true;
1188  }
1189 
1190  return false;
1191 }
1192 
1193 void side_engine::resolve_random(randomness::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
1194 {
1195  if(parent_.params_.saved_game == saved_game_mode::type::midgame) {
1196  return;
1197  }
1198 
1200 
1201  flg_.resolve_random(rng, avoid_faction_ids);
1202 
1203  LOG_MP << "side " << (index_ + 1) << ": faction=" <<
1204  (flg_.current_faction())["name"] << ", leader=" <<
1205  flg_.current_leader() << ", gender=" << flg_.current_gender();
1206 }
1207 
1209 {
1210  player_id_.clear();
1213 
1214  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
1216  }
1217 }
1218 
1219 void side_engine::place_user(const std::string& name)
1220 {
1221  config data;
1222  data["name"] = name;
1223 
1224  place_user(data);
1225 }
1226 
1227 void side_engine::place_user(const config& data, bool contains_selection)
1228 {
1229  player_id_ = data["name"].str();
1231 
1232  if(data["change_faction"].to_bool() && contains_selection) {
1233  // Network user's data carry information about chosen
1234  // faction, leader and genders.
1235  flg_.set_current_faction(data["faction"].str());
1236  flg_.set_current_leader(data["leader"].str());
1237  flg_.set_current_gender(data["gender"].str());
1238  }
1239 
1241 }
1242 
1244 {
1245  controller_options_.clear();
1246 
1247  // Default options.
1248  if(parent_.mp_metadata_) {
1249  add_controller_option(CNTR_NETWORK, _("Network Player"), side_controller::human);
1250  }
1251 
1252  add_controller_option(CNTR_LOCAL, _("Local Player"), side_controller::human);
1253  add_controller_option(CNTR_COMPUTER, _("Computer Player"), side_controller::ai);
1254  add_controller_option(CNTR_EMPTY, _("Nobody"), side_controller::none);
1255 
1256  if(!reserved_for_.empty()) {
1257  add_controller_option(CNTR_RESERVED, _("Reserved"), side_controller::human);
1258  }
1259 
1260  // Connected users.
1261  for(const std::string& user : parent_.connected_users()) {
1262  add_controller_option(parent_.default_controller_, user, side_controller::human);
1263  }
1264 
1266 }
1267 
1269 {
1270  int i = 0;
1271  for(const controller_option& option : controller_options_) {
1272  if(option.first == controller_) {
1274 
1275  if(player_id_.empty() || player_id_ == option.second) {
1276  // Stop searching if no user is assigned to a side
1277  // or the selected user is found.
1278  break;
1279  }
1280  }
1281 
1282  i++;
1283  }
1284 
1286 }
1287 
1288 bool side_engine::controller_changed(const int selection)
1289 {
1290  const ng::controller selected_cntr = controller_options_[selection].first;
1291 
1292  // Check if user was selected. If so assign a side to him/her.
1293  // If not, make sure that no user is assigned to this side.
1294  if(selected_cntr == parent_.default_controller_ && selection != 0) {
1295  player_id_ = controller_options_[selection].second;
1297  } else {
1298  player_id_.clear();
1299  }
1300 
1301  set_controller(selected_cntr);
1302 
1303  return true;
1304 }
1305 
1307 {
1309 
1311 }
1312 
1313 void side_engine::set_faction_commandline(const std::string& faction_name)
1314 {
1315  flg_.set_current_faction(faction_name);
1316 }
1317 
1319 {
1321 
1322  if(controller_name == side_controller::ai) {
1324  }
1325 
1326  if(controller_name == side_controller::none) {
1328  }
1329 
1330  player_id_.clear();
1331 }
1332 
1334  const std::string& name, const std::string& controller_value)
1335 {
1336  if(controller_lock_ && !cfg_["controller"].empty() && cfg_["controller"] != controller_value) {
1337  return;
1338  }
1339 
1340  controller_options_.emplace_back(controller, name);
1341 }
1342 
1343 } // end namespace ng
std::vector< std::string > names
Definition: build_info.cpp:69
static void add_mod_ai_from_config(config::const_child_itors configs)
static const config & get_ai_config_for(const std::string &id)
Return the config for a specified ai.
static void add_era_ai_from_config(const config &game_config)
std::optional< std::string > multiplayer_turns
Non-empty if –turns was given on the command line.
std::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_controller
Non-empty if –controller was given on the command line.
std::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_algorithm
Non-empty if –algorithm was given on the command line.
std::optional< std::vector< std::tuple< unsigned int, std::string, std::string > > > multiplayer_parm
Non-empty if –parm was given on the command line.
std::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_side
Non-empty if –side was given on the command line.
std::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_ai_config
Non-empty if –ai-config was given on the command line.
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line.
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:208
void remove_attributes(T... keys)
Definition: config.hpp:575
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:371
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:284
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:471
void clear_children(T... keys)
Definition: config.hpp:644
bool has_attribute(config_key_type key) const
Definition: config.cpp:159
child_itors child_range(config_key_type key)
Definition: config.cpp:277
void remove_attribute(config_key_type key)
Definition: config.cpp:164
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object.
Definition: config.cpp:917
bool empty() const
Definition: config.cpp:856
void clear()
Definition: config.cpp:835
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:389
config & add_child(config_key_type key)
Definition: config.cpp:445
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
Encapsulates the map of the game.
Definition: map.hpp:172
std::vector< const config * > era_factions_
std::vector< team_data_pod > team_data_
std::vector< side_engine_ptr > & side_engines()
const ng::controller default_controller_
const std::set< std::string > & connected_users() const
const mp_game_settings & params_
mp_game_metadata * mp_metadata_
connect_engine(saved_game &state, const bool first_scenario, mp_game_metadata *metadata)
const std::string & current_gender() const
Definition: flg_manager.hpp:71
void set_current_faction(const unsigned index)
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
const config * default_leader_cfg() const
Definition: flg_manager.hpp:74
void set_current_leader(const unsigned index)
void set_current_gender(const unsigned index)
bool is_random_faction()
const config & current_faction() const
Definition: flg_manager.hpp:67
const std::string & current_leader() const
Definition: flg_manager.hpp:69
const config & cfg() const
unsigned current_controller_index_
void update_controller_options()
std::string player_id_
const bool controller_lock_
const bool allow_changes_
void set_controller_commandline(const std::string &controller_name)
void set_faction_commandline(const std::string &faction_name)
void place_user(const std::string &name)
void add_controller_option(ng::controller controller, const std::string &name, const std::string &controller_value)
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid_faction_ids=std::vector< std::string >())
void update_current_controller_index()
std::vector< controller_option > controller_options_
connect_engine & parent_
std::string ai_algorithm_
std::string reserved_for_
std::vector< std::string > color_options_
void set_controller(ng::controller controller)
void set_waiting_to_choose_status(bool status)
bool controller_changed(const int selection)
std::string color_id_
std::string user_description() const
ng::controller controller_
bool ready_for_start() const
config new_config() const
ng::controller controller() const
bool available_for_user(const std::string &name="") const
const bool allow_player_
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
game_classification & classification()
Definition: saved_game.hpp:56
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
std::string to_serialized() const
Definition: tstring.hpp:155
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:1008
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1351
Managing the AIs configuration - headers.
#define WRN_MP
#define ERR_MP
#define DBG_MP
#define LOG_NW
static lg::log_domain log_network("network")
#define LOG_CF
#define ERR_CF
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
#define LOG_MP
static lg::log_domain log_config("config")
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:968
#define N_(String)
Definition: gettext.hpp:101
static std::string _(const char *str)
Definition: gettext.hpp:93
std::vector< const mp::user_info * > user_data
The associated user data for each node, index-to-index.
Standard logging facilities (interface).
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:61
std::string observer
Game configuration data as global variables.
Definition: build_info.cpp:63
std::vector< std::string > default_colors
static void add_color_info(const game_config_view &v, bool build_defaults)
static std::string controller_name(const team &t)
Definition: game_stats.cpp:69
config initial_level_config(saved_game &state)
void level_to_gamestate(const config &level, saved_game &state)
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
@ CNTR_RESERVED
@ CNTR_NETWORK
@ CNTR_COMPUTER
@ CNTR_EMPTY
@ CNTR_LOCAL
std::pair< ng::controller, std::string > controller_option
std::shared_ptr< side_engine > side_engine_ptr
const std::string get_ignored_delim()
Definition: game.cpp:198
std::string era()
Definition: game.cpp:681
bool use_map_settings()
Definition: game.cpp:481
std::string login()
static std::string at(const std::string &file, int line)
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
Definition: gettext.cpp:500
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
std::string_view data
Definition: picture.cpp:199
saved_game_mode::type saved_game
The base template for associating string values with enum values.
Definition: enum_base.hpp:33
static map_location::DIRECTION s