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