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