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