The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
connect_engine.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2017 by Andrius Silinskas <silinskas.andrius@gmail.com>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
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 "wesnothd_connection.hpp"
28 
29 #include <cstdlib>
30 #include <ctime>
31 
32 static lg::log_domain log_config("config");
33 #define LOG_CF LOG_STREAM(info, log_config)
34 #define ERR_CF LOG_STREAM(err, log_config)
35 
36 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
37 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
38 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
39 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
40 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
41 
42 static lg::log_domain log_network("network");
43 #define LOG_NW LOG_STREAM(info, log_network)
44 
45 static const std::string controller_names[] {
46  "human",
47  "human",
48  "ai",
49  "null",
50  "reserved"
51 };
52 
54  "side",
55  "type",
56  "gender",
57  "recruit",
58  "player_id",
59  "previous_recruits",
60  "controller",
61  "current_player",
62  "team_name",
63  "user_team_name",
64  "color",
65  "gold",
66  "income",
67  "allow_changes",
68  "faction"
69 };
70 
71 namespace ng {
72 
73 connect_engine::connect_engine(saved_game& state, const bool first_scenario, mp_campaign_info* campaign_info)
74  : level_()
75  , state_(state)
76  , params_(state.mp_settings())
77  , default_controller_(campaign_info ? CNTR_NETWORK : CNTR_LOCAL)
78  , campaign_info_(campaign_info)
79  , first_scenario_(first_scenario)
80  , force_lock_settings_()
81  , side_engines_()
82  , era_factions_()
83  , team_names_()
84  , user_team_names_()
85  , player_teams_()
86 {
87  // Initial level config from the mp_game_settings.
89  if(level_.empty()) {
90  return;
91  }
92 
93  const bool is_mp = state_.classification().is_normal_mp_game();
94  force_lock_settings_ = (!state.mp_settings().saved_game) && scenario()["force_lock_settings"].to_bool(!is_mp);
95 
96  // Original level sides.
98 
99  // AI algorithms.
102 
103  // Set the team name lists and modify the original level sides if necessary.
104  std::vector<std::string> original_team_names;
105  std::string team_prefix(_("Team") + " ");
106 
107  int side_count = 1;
108  for(config& side : sides) {
109  const std::string side_str = std::to_string(side_count);
110 
111  config::attribute_value& team_name = side["team_name"];
112  config::attribute_value& user_team_name = side["user_team_name"];
113 
114  // Revert to default values if appropriate.
115  if(team_name.empty()) {
116  team_name = side_str;
117  }
118 
119  if(params_.use_map_settings && user_team_name.empty()) {
120  user_team_name = team_name;
121  }
122 
123  bool add_team = true;
125  // Only add a team if it is not found.
126  bool found = std::find(team_names_.begin(), team_names_.end(),
127  team_name.str()) != team_names_.end();
128 
129  if(found) {
130  add_team = false;
131  }
132  } else {
133  // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
134  auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
135 
136  // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
137  // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
138  if(name_itor == original_team_names.end()) {
139  original_team_names.push_back(team_name);
140 
141  team_name = "Team " + std::to_string(original_team_names.size());
142  } else {
143  team_name = "Team " + std::to_string(name_itor - original_team_names.begin() + 1);
144  }
145 
146  user_team_name = team_prefix + side_str;
147  }
148 
149  if(add_team) {
150  team_names_.push_back(params_.use_map_settings ? team_name : "Team " + side_str);
151  user_team_names_.push_back(user_team_name.t_str().to_serialized());
152 
153  if(side["allow_player"].to_bool(true) || game_config::debug) {
154  player_teams_.push_back(user_team_name.str());
155  }
156  }
157 
158  ++side_count;
159  }
160 
161  // Selected era's factions.
162  for(const config& era : level_.child("era").child_range("multiplayer_side")) {
163  era_factions_.push_back(&era);
164  }
165 
167 
168  // Create side engines.
169  int index = 0;
170  for(const config& s : sides) {
171  side_engines_.emplace_back(new side_engine(s, *this, index));
172 
173  index++;
174  }
175 
176  if(first_scenario_) {
177  // Add host to the connected users list.
179  } else {
180  // Add host but don't assign a side to him.
182 
183  // Load reserved players information into the sides.
185  }
186 
187  // Only updates the sides in the level.
188  update_level();
189 
190  // If we are connected, send data to the connected host.
191  send_level_data();
192 }
193 
194 
196  if(config& s = scenario()) {
197  return &s;
198  }
199 
200  return nullptr;
201 }
202 
203 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
204 {
205  config user_data;
206  user_data["name"] = name;
207  import_user(user_data, observer, side_taken);
208 }
209 
210 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
211 {
212  const std::string& username = data["name"];
213  assert(!username.empty());
214  if(campaign_info_) {
215  connected_users_rw().insert(username);
216  }
217 
219 
220  if(observer) {
221  return;
222  }
223 
224  bool side_assigned = false;
225  if(side_taken >= 0) {
226  side_engines_[side_taken]->place_user(data, true);
227  side_assigned = true;
228  }
229 
230  // Check if user has a side(s) reserved for him.
231  for(side_engine_ptr side : side_engines_) {
232  if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
233  side->place_user(data);
234 
235  side_assigned = true;
236  }
237  }
238 
239  // If no sides were assigned for a user,
240  // take a first available side.
241  if(side_taken < 0 && !side_assigned) {
242  for(side_engine_ptr side : side_engines_) {
243  if(side->available_for_user(username) ||
244  side->controller() == CNTR_LOCAL) {
245  side->place_user(data);
246 
247  side_assigned = true;
248  break;
249  }
250  }
251  }
252 
253  // Check if user has taken any sides, which should get control
254  // over any other sides.
255  for(side_engine_ptr user_side : side_engines_) {
256  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
257  for(side_engine_ptr side : side_engines_){
258  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
259  side->place_user(data);
260  }
261  }
262  }
263  }
264 }
265 
267 {
268  for(side_engine_ptr side : side_engines_) {
269  if(side->available_for_user()) {
270  return true;
271  }
272  }
273 
274  return false;
275 }
276 
278 {
279  DBG_MP << "updating level" << std::endl;
280 
281  scenario().clear_children("side");
282 
283  for(side_engine_ptr side : side_engines_) {
284  scenario().add_child("side", side->new_config());
285  }
286 }
287 
288 void connect_engine::update_and_send_diff(bool /*update_time_of_day*/)
289 {
290  config old_level = level_;
291  update_level();
292 
293  config diff = level_.get_diff(old_level);
294  if(!diff.empty()) {
295  config scenario_diff;
296  scenario_diff.add_child("scenario_diff", diff);
297  send_to_server(scenario_diff);
298  }
299 }
300 
302 {
303  if(side_engines_.empty()) {
304  return true;
305  }
306 
307  // First check if all sides are ready to start the game.
308  for(side_engine_ptr side : side_engines_) {
309  if(!side->ready_for_start()) {
310  const int side_num = side->index() + 1;
311  DBG_MP << "not all sides are ready, side " <<
312  side_num << " not ready\n";
313 
314  return false;
315  }
316  }
317 
318  DBG_MP << "all sides are ready" << std::endl;
319 
320  /*
321  * If at least one human player is slotted with a player/ai we're allowed
322  * to start. Before used a more advanced test but it seems people are
323  * creative in what is used in multiplayer [1] so use a simpler test now.
324  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
325  */
326  for(side_engine_ptr side : side_engines_) {
327  if(side->controller() != CNTR_EMPTY && side->allow_player()) {
328  return true;
329  }
330  }
331 
332  return false;
333 }
334 
336 {
337  if(campaign_info_) {
339  }
340 }
341 
343 {
344  if(campaign_info_) {
346  }
347  else {
348  return false;
349  }
350 }
351 
352 std::vector<std::string> side_engine::get_children_to_swap()
353 {
354  std::vector<std::string> children;
355 
356  children.push_back("village");
357  children.push_back("unit");
358  children.push_back("ai");
359 
360  return children;
361 }
362 
363 std::multimap<std::string, config> side_engine::get_side_children()
364 {
365  std::multimap<std::string, config> children;
366 
367  for(const std::string& children_to_swap : get_children_to_swap()) {
368  for(const config& child : cfg_.child_range(children_to_swap)) {
369  children.emplace(children_to_swap, child);
370  }
371  }
372 
373  return children;
374 }
375 
376 void side_engine::set_side_children(std::multimap<std::string, config> children)
377 {
378  for(const std::string& children_to_remove : get_children_to_swap()) {
379  cfg_.clear_children(children_to_remove);
380  }
381 
382  for(std::pair<std::string, config> child_map : children) {
383  cfg_.add_child(child_map.first, child_map.second);
384  }
385 }
386 
388 {
389  DBG_MP << "starting a new game" << std::endl;
390 
391  // Resolves the "random faction", "random gender" and "random message"
392  // Must be done before shuffle sides, or some cases will cause errors
393  randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
394  for(side_engine_ptr side : side_engines_) {
395  std::vector<std::string> avoid_faction_ids;
396 
397  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
398  if(params_.random_faction_mode != mp_game_settings::RANDOM_FACTION_MODE::DEFAULT) {
399  for(side_engine_ptr side2 : side_engines_) {
400  if(!side2->flg().is_random_faction()) {
401  switch(params_.random_faction_mode.v) {
402  case mp_game_settings::RANDOM_FACTION_MODE::NO_MIRROR:
403  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
404  break;
405  case mp_game_settings::RANDOM_FACTION_MODE::NO_ALLY_MIRROR:
406  if(side2->team() == side->team()) {// TODO: When the connect engines are fixed to allow multiple teams, this should be changed to "if side1 and side2 are allied, i.e. their list of teams has nonempty intersection"
407  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
408  }
409  break;
410  default:
411  break; // assert(false);
412  }
413  }
414  }
415  }
416  side->resolve_random(rng, avoid_faction_ids);
417  }
418 
419  // Shuffle sides (check settings and if it is a re-loaded game).
420  // Must be done after resolve_random() or shuffle sides, or they won't work.
421  if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.child("snapshot") && level_.child("snapshot").child("side"))) {
422 
423  // Only playable sides should be shuffled.
424  std::vector<int> playable_sides;
425  for(side_engine_ptr side : side_engines_) {
426  if(side->allow_player() && side->allow_shuffle()) {
427  playable_sides.push_back(side->index());
428  }
429  }
430 
431  // Fisher-Yates shuffle.
432  for(int i = playable_sides.size(); i > 1; i--) {
433  int j_side = playable_sides[rng.get_next_random() % i];
434  int i_side = playable_sides[i - 1];
435 
436  if(i_side == j_side) continue; //nothing to swap
437 
438  // First we swap everything about a side with another
439  side_engine_ptr tmp_side = side_engines_[j_side];
440  side_engines_[j_side] = side_engines_[i_side];
441  side_engines_[i_side] = tmp_side;
442 
443  // Some 'child' variables such as village ownership and
444  // initial side units need to be swapped over as well
445  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
446  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
447  side_engines_[i_side]->set_side_children(tmp_side_children);
448 
449  // Then we revert the swap for fields that are unique to
450  // player control and the team they selected
451  int tmp_index = side_engines_[j_side]->index();
452  side_engines_[j_side]->set_index(side_engines_[i_side]->index());
453  side_engines_[i_side]->set_index(tmp_index);
454 
455  int tmp_team = side_engines_[j_side]->team();
456  side_engines_[j_side]->set_team(side_engines_[i_side]->team());
457  side_engines_[i_side]->set_team(tmp_team);
458  }
459  }
460 
461  // Make other clients not show the results of resolve_random().
462  config lock("stop_updates");
463  send_to_server(lock);
464 
465  update_and_send_diff(true);
466 
468 
469  // Build the gamestate object after updating the level.
471 
472  send_to_server(config("start_game"));
473 }
474 
476 {
477  DBG_MP << "starting a new game in commandline mode" << std::endl;
478 
479  typedef std::tuple<unsigned int, std::string> mp_option;
480 
481  randomness::mt_rng rng;
482 
483  unsigned num = 0;
484  for(side_engine_ptr side : side_engines_) {
485  num++;
486 
487  // Set the faction, if commandline option is given.
488  if(cmdline_opts.multiplayer_side) {
489  for(const mp_option& option : *cmdline_opts.multiplayer_side) {
490 
491  if(std::get<0>(option) == num) {
492  if(std::find_if(era_factions_.begin(), era_factions_.end(), [&option](const config* faction) { return (*faction)["id"] == std::get<1>(option); }) != era_factions_.end()) {
493  DBG_MP << "\tsetting side " << std::get<0>(option) << "\tfaction: " << std::get<1>(option) << std::endl;
494 
495  side->set_faction_commandline(std::get<1>(option));
496  }
497  else {
498  ERR_MP << "failed to set side " << std::get<0>(option) << " to faction " << std::get<1>(option) << std::endl;
499  }
500  }
501  }
502  }
503 
504  // Set the controller, if commandline option is given.
505  if(cmdline_opts.multiplayer_controller) {
506  for(const mp_option& option : *cmdline_opts.multiplayer_controller) {
507 
508  if(std::get<0>(option) == num) {
509  DBG_MP << "\tsetting side " << std::get<0>(option) <<
510  "\tfaction: " << std::get<1>(option) << std::endl;
511 
512  side->set_controller_commandline(std::get<1>(option));
513  }
514  }
515  }
516 
517  // Set AI algorithm to RCA AI for all sides,
518  // then override if commandline option was given.
519  side->set_ai_algorithm("ai_default_rca");
520  if(cmdline_opts.multiplayer_algorithm) {
521  for(const mp_option& option : *cmdline_opts.multiplayer_algorithm) {
522 
523  if(std::get<0>(option) == num) {
524  DBG_MP << "\tsetting side " << std::get<0>(option) <<
525  "\tfaction: " << std::get<1>(option) << std::endl;
526 
527  side->set_ai_algorithm(std::get<1>(option));
528  }
529  }
530  }
531 
532  // Finally, resolve "random faction",
533  // "random gender" and "random message", if any remains unresolved.
534  side->resolve_random(rng);
535  } // end top-level loop
536 
537  update_and_send_diff(true);
538 
539  // Update sides with commandline parameters.
540  if(cmdline_opts.multiplayer_turns) {
541  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns <<
542  std::endl;
543  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
544  }
545 
546  for(config &side : scenario().child_range("side")) {
547  if(cmdline_opts.multiplayer_ai_config) {
548  for(const mp_option& option : *cmdline_opts.multiplayer_ai_config) {
549 
550  if(std::get<0>(option) == side["side"].to_unsigned()) {
551  DBG_MP << "\tsetting side " << side["side"] <<
552  "\tai_config: " << std::get<1>(option) << std::endl;
553 
554  side["ai_config"] = std::get<1>(option);
555  }
556  }
557  }
558 
559  // Having hard-coded values here is undesirable,
560  // but that's how it is done in the MP lobby
561  // part of the code also.
562  // Should be replaced by settings/constants in both places
563  if(cmdline_opts.multiplayer_ignore_map_settings) {
564  side["gold"] = 100;
565  side["income"] = 1;
566  }
567 
568  typedef std::tuple<unsigned int, std::string, std::string> mp_parameter;
569 
570  if(cmdline_opts.multiplayer_parm) {
571  for(const mp_parameter& parameter : *cmdline_opts.multiplayer_parm) {
572 
573  if(std::get<0>(parameter) == side["side"].to_unsigned()) {
574  DBG_MP << "\tsetting side " << side["side"] << " " <<
575  std::get<1>(parameter) << ": " << std::get<2>(parameter) << std::endl;
576 
577  side[std::get<1>(parameter)] = std::get<2>(parameter);
578  }
579  }
580  }
581  }
582 
584 
585  // Build the gamestate object after updating the level
587  send_to_server(config("start_game"));
588 }
589 
591 {
592  DBG_MP << "leaving the game" << std::endl;
593 
594  send_to_server(config("leave_game"));
595 }
596 
597 std::pair<bool, bool> connect_engine::process_network_data(const config& data)
598 {
599  std::pair<bool, bool> result(std::make_pair(false, true));
600 
601  if(data.child("leave_game")) {
602  result.first = true;
603  return result;
604  }
605 
606  // A side has been dropped.
607  if(const config& side_drop = data.child("side_drop")) {
608  unsigned side_index = side_drop["side_num"].to_int() - 1;
609 
610  if(side_index < side_engines_.size()) {
611  side_engine_ptr side_to_drop = side_engines_[side_index];
612 
613  // Remove user, whose side was dropped.
614  connected_users_rw().erase(side_to_drop->player_id());
616 
617  side_to_drop->reset();
618 
620 
621  return result;
622  }
623  }
624 
625  // A player is connecting to the game.
626  if(!data["side"].empty()) {
627  unsigned side_taken = data["side"].to_int() - 1;
628 
629  // Checks if the connecting user has a valid and unique name.
630  const std::string name = data["name"];
631  if(name.empty()) {
632  config response;
633  response["failed"] = true;
634  send_to_server(response);
635 
636  ERR_CF << "ERROR: No username provided with the side." << std::endl;
637 
638  return result;
639  }
640 
641  if(connected_users().find(name) != connected_users().end()) {
642  // TODO: Seems like a needless limitation
643  // to only allow one side per player.
644  if(find_user_side_index_by_id(name) != -1) {
645  config response;
646  response["failed"] = true;
647  response["message"] = "The nickname '" + name +
648  "' is already in use.";
649  send_to_server(response);
650 
651  return result;
652  } else {
653  connected_users_rw().erase(name);
655  config observer_quit;
656  observer_quit.add_child("observer_quit")["name"] = name;
657  send_to_server(observer_quit);
658  }
659  }
660 
661  // Assigns this user to a side.
662  if(side_taken < side_engines_.size()) {
663  if(!side_engines_[side_taken]->available_for_user(name)) {
664  // This side is already taken.
665  // Try to reassing the player to a different position.
666  side_taken = 0;
668  if(s->available_for_user()) {
669  break;
670  }
671 
672  side_taken++;
673  }
674 
675  if(side_taken >= side_engines_.size()) {
676  config response;
677  response["failed"] = true;
678  send_to_server(response);
679 
680  config res;
681  config& kick = res.add_child("kick");
682  kick["username"] = data["name"];
683  send_to_server(res);
684 
686 
687  ERR_CF << "ERROR: Couldn't assign a side to '" <<
688  name << "'\n";
689 
690  return result;
691  }
692  }
693 
694  LOG_CF << "client has taken a valid position\n";
695 
696  import_user(data, false, side_taken);
698 
699  // Wait for them to choose faction if allowed.
700  side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
701  LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes() << std::endl;
702  result.second = false;
703 
704  LOG_NW << "sent player data\n";
705  } else {
706  ERR_CF << "tried to take illegal side: " << side_taken << std::endl;
707 
708  config response;
709  response["failed"] = true;
710  send_to_server(response);
711  }
712  }
713 
714 
715  if(const config& change_faction = data.child("change_faction")) {
716  int side_taken = find_user_side_index_by_id(change_faction["name"]);
717  if(side_taken != -1 || !first_scenario_) {
718  import_user(change_faction, false, side_taken);
720  }
721  }
722 
723  if(const config& observer = data.child("observer")) {
724  import_user(observer, true);
726  }
727 
728  if(const config& observer = data.child("observer_quit")) {
729  const t_string& observer_name = observer["name"];
730  if(!observer_name.empty()) {
731  if((connected_users().find(observer_name) != connected_users().end()) &&
732  (find_user_side_index_by_id(observer_name) != -1)) {
733 
734  connected_users_rw().erase(observer_name);
737  }
738  }
739  }
740 
741  return result;
742 }
743 
745 {
746  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  },
771  });
773  } else {
774  send_to_server(config {"update_game", config()});
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() || !campaign_info_) {
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(campaign_info_) {
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(campaign_info_);
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)
866  , allow_changes_(!parent_.params_.saved_game && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1))
867  , waiting_to_choose_faction_(allow_changes_)
868  , custom_color_()
869  , color_id_()
870  , color_options_()
871 {
872  // Save default attributes that could be overwirtten by the faction, so that correct faction lists would be
873  // initialized by flg_manager when the new side config is sent over network.
874  cfg_.add_child("default_faction", config {
875  "type", cfg_["type"],
876  "gender", cfg_["gender"],
877  "faction", cfg_["faction"],
878  "recruit", cfg_["recruit"],
879  });
880 
881  if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
882  ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1 << std::endl;
883  }
884 
885  cfg_["side"] = index_ + 1;
886 
887  // Check if this side should give its control to some other side.
888  const size_t side_cntr_index = cfg_["controller"].to_int(-1) - 1;
889  if(side_cntr_index < parent_.side_engines().size()) {
890  // Remove this attribute to avoid locking side
891  // to non-existing controller type.
892  cfg_.remove_attribute("controller");
893 
894  cfg_["previous_save_id"] = parent_.side_engines()[side_cntr_index]->previous_save_id();
895  ERR_MP << "contoller=<number> is deperecated\n";
896  }
897 
898  if(!parent_.params_.saved_game && cfg_["save_id"].str().empty()) {
899  assert(cfg_["id"].empty()); // we already setted "save_id" to "id" if "id" existed.
900  cfg_["save_id"] = parent_.scenario()["id"].str() + "_" + std::to_string(index);
901  }
902 
903  if(cfg_["controller"] != "human" && cfg_["controller"] != "ai" && cfg_["controller"] != "null") {
904  //an invalid contoller type was specified. Remove it to prevent asertion failures later.
905  cfg_.remove_attribute("controller");
906  }
907 
909 
910  // Tweak the controllers.
911  if(parent_.state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::SCENARIO && cfg_["controller"].blank()) {
912  cfg_["controller"] = "ai";
913  }
914 
915  if(cfg_["controller"] == "null") {
917  } else if(cfg_["controller"] == "ai") {
919  } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
920  // Reserve a side for "current_player", unless the side
921  // is played by an AI.
923  } else if(allow_player_) {
925  } else {
926  // AI is the default.
928  }
929 
930  // Initialize team and color.
931  unsigned team_name_index = 0;
932  for(const std::string& name : parent_.team_names_) {
933  if(name == cfg["team_name"]) {
934  break;
935  }
936 
937  team_name_index++;
938  }
939  if(team_name_index >= parent_.team_names_.size()) {
940  assert(!parent_.team_names_.empty());
941  team_ = 0;
942  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;
943  } else {
944  team_ = team_name_index;
945  }
946 
949 
950  if(!cfg["color"].empty()) {
951  if(cfg["color"].to_int()) {
952  color_ = cfg["color"].to_int() - 1;
954  } else {
955  custom_color_ = cfg["color"].str();
956 
957  const auto iter = std::find(color_options_.begin(), color_options_.end(), custom_color_);
958 
959  if(iter != color_options_.end()) {
960  color_id_ = *iter;
961  color_ = iter - color_options_.begin();
962  } else {
963  color_options_.push_back(custom_color_);
964 
966  color_ = color_options_.size() - 1;
967  }
968  }
969  }
970 
971  // Initialize ai algorithm.
972  if(const config& ai = cfg.child("ai")) {
973  ai_algorithm_ = ai["ai_algorithm"].str();
974  }
975 }
976 
978 {
979  switch(controller_) {
980  case CNTR_LOCAL:
981  return N_("Anonymous player");
982  case CNTR_COMPUTER:
983  if(allow_player_) {
985  } else {
986  return N_("Computer Player");
987  }
988  default:
989  return "";
990  }
991 }
992 
994 {
995  config res = cfg_;
996 
997  // In case of 'shuffle sides' the side index in cfg_ might be wrong which will confuse the team constuctor later.
998  res["side"] = index_ + 1;
999 
1000  // If the user is allowed to change type, faction, leader etc, then import their new values in the config.
1001  if(!parent_.params_.saved_game) {
1002  // Merge the faction data to res.
1003  config faction = flg_.current_faction();
1004  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " faction=" << faction["id"] << " recruit=" << faction["recruit"] << "\n";
1005  res["faction_name"] = faction["name"];
1006  res["faction"] = faction["id"];
1007  faction.remove_attributes("id", "name", "image", "gender", "type");
1008  res.append(faction);
1009  }
1010 
1011  res["controller"] = controller_names[controller_];
1012 
1013  // The hosts receives the serversided controller tweaks after the start event, but
1014  // for mp sync it's very important that the controller types are correct
1015  // during the start/prestart event (otherwise random unit creation during prestart fails).
1016  res["is_local"] = player_id_ == preferences::login() || controller_ == CNTR_COMPUTER || controller_ == CNTR_LOCAL;
1017 
1018  // This function (new_config) is only meant to be called by the host's machine, which is why this check
1019  // works. It essentially certifies that whatever side has the player_id that matches the host's login
1020  // will be flagged. The reason we cannot check mp_campaign_info::is_host is because that flag is *always*
1021  // true on the host's machine, meaning this flag would be set to true for every side.
1022  res["is_host"] = player_id_ == preferences::login();
1023 
1024  std::string desc = user_description();
1025  if(!desc.empty()) {
1026  res["user_description"] = t_string(desc, "wesnoth");
1027 
1028  desc = vgettext("$playername $side", {
1029  {"playername", _(desc.c_str())},
1030  {"side", res["side"].str()}
1031  });
1032  } else if(!player_id_.empty()) {
1033  desc = player_id_;
1034  }
1035 
1036  if(res["name"].str().empty() && !desc.empty()) {
1037  //TODO: maybe we should add this in to the leaders config instead of the side config?
1038  res["name"] = desc;
1039  }
1040 
1041  assert(controller_ != CNTR_LAST);
1043  // Do not import default ai cfg otherwise - all is set by scenario config.
1044  res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
1045  }
1046 
1047  if(controller_ == CNTR_EMPTY) {
1048  res["no_leader"] = true;
1049  }
1050 
1051  // A side's "current_player" is the player which has currently taken that side or the one for which it is reserved.
1052  // The "player_id" is the id of the client who controls that side. It's always the host for Local and AI players and
1053  // always empty for free/reserved sides or null controlled sides. You can use !res["player_id"].empty() to check
1054  // whether a side is already taken.
1055  assert(!preferences::login().empty());
1056  if(controller_ == CNTR_LOCAL) {
1057  res["player_id"] = preferences::login();
1058  res["current_player"] = preferences::login();
1059  } else if(controller_ == CNTR_RESERVED) {
1060  res.remove_attribute("player_id");
1061  res["current_player"] = reserved_for_;
1062  } else if(controller_ == CNTR_COMPUTER) {
1063  // TODO: what is the content of player_id_ here ?
1064  res["current_player"] = desc;
1065  res["player_id"] = preferences::login();
1066  } else if(!player_id_.empty()) {
1067  res["player_id"] = player_id_;
1068  res["current_player"] = player_id_;
1069  }
1070 
1071  res["allow_changes"] = allow_changes_;
1072  res["chose_random"] = chose_random_;
1073 
1074  if(!parent_.params_.saved_game) {
1075  // Find a config where a default leader is and set a new type and gender values for it.
1076  config* leader = &res;
1077 
1078  if(flg_.default_leader_cfg() != nullptr) {
1079  for(config& side_unit : res.child_range("unit")) {
1080  if(*flg_.default_leader_cfg() != side_unit) {
1081  continue;
1082  }
1083 
1084  leader = &side_unit;
1085 
1086  if(flg_.current_leader() != (*leader)["type"]) {
1087  // If a new leader type was selected from carryover, make sure that we reset the leader.
1088  std::string leader_id = (*leader)["id"];
1089  leader->clear();
1090 
1091  if(!leader_id.empty()) {
1092  (*leader)["id"] = leader_id;
1093  }
1094  }
1095 
1096  break;
1097  }
1098  }
1099 
1100  // NOTE: the presence of a type= key overrides no_leader
1101  if(controller_ != CNTR_EMPTY) {
1102  (*leader)["type"] = flg_.current_leader();
1103  (*leader)["gender"] = flg_.current_gender();
1104  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " type=" << (*leader)["type"] << " gender=" << (*leader)["gender"] << "\n";
1105  } else {
1106  // TODO: FIX THIS SHIT! We shouldn't have a special string to denote no-leader-ness...
1107  (*leader)["type"] = "null";
1108  (*leader)["gender"] = "null";
1109  }
1110 
1111  res["team_name"] = parent_.team_names_[team_];
1112 
1113  // TODO: Fix this mess!
1114  //
1115  // There is a fundamental disconnect, here. One the one hand we have the idea of
1116  // 'teams' (which don't actually exist). A 'team' has a name (internal key:
1117  // team_name) and a translatable display name (internal key: user_team_name). But
1118  // what we actually have are sides. Sides relate to each other by 'team' (internal
1119  // key: team_name) and each side has it's own name for the team (internal key:
1120  // user_team_name).
1121  //
1122  // The confusion is that the keys from the side have names which one might expect
1123  // always refer to the 'team' concept. THEY DO NOT! They are simply named in such
1124  // a way to confuse the unwary.
1125  //
1126  // There is no simple, clean way to clear up the confusion. So, I'm applying the
1127  // Principle of Least Surprise. The user can see the user_team_name, and it should
1128  // not change. So if the side already has a user_team_name, use it.
1129  //
1130  // In the rare and unlikely (like, probably never happens) case that the side does
1131  // not have a user_team_name, but an (nebulous and non-deterministic term here)
1132  // EARLIER side has the same team_name and that side gives a user_team_name, we'll
1133  // use it.
1134  //
1135  // The effect of this mess, and my lame fix for it, is probably only visible when
1136  // randomizing the sides on a team for multi-player games. But the effect when it's
1137  // not fixed is an obvious mistake on the player's screen when playing a campaign
1138  // in single-player mode.
1139  //
1140  // At some level, all this is probably wrong, but it is the least breakage from the
1141  // mess I found; so deal with it, or fix it.
1142  //
1143  // If, by now, you get the impression this is a kludged-together mess which cries
1144  // out for an honest design and a thoughtful implementation, you're correct! But
1145  // I'm tired, and I'm cranky from wasting a over day on this, and so I'm exercising
1146  // my prerogative as a grey-beard and leaving this for someone else to clean up.
1147  if(res["user_team_name"].empty() || !parent_.params_.use_map_settings) {
1148  res["user_team_name"] = parent_.user_team_names_[team_];
1149  }
1150 
1151  res["allow_player"] = allow_player_;
1152  res["color"] = color_id_;
1153  res["gold"] = gold_;
1154  res["income"] = income_;
1155  }
1156 
1158  config trimmed = cfg_;
1159 
1160  for(const std::string& attribute : attributes_to_trim) {
1161  trimmed.remove_attribute(attribute);
1162  }
1163 
1164  if(controller_ != CNTR_COMPUTER) {
1165  // Only override names for computer controlled players.
1166  trimmed.remove_attribute("user_description");
1167  }
1168 
1169  res.merge_with(trimmed);
1170  }
1171 
1172  return res;
1173 }
1174 
1176 {
1177  if(!allow_player_) {
1178  // Sides without players are always ready.
1179  return true;
1180  }
1181 
1182  if((controller_ == CNTR_COMPUTER) ||
1183  (controller_ == CNTR_EMPTY) ||
1184  (controller_ == CNTR_LOCAL)) {
1185 
1186  return true;
1187  }
1188 
1189  if(available_for_user()) {
1190  // If controller_ == CNTR_NETWORK and player_id_.empty().
1191  return false;
1192  }
1193 
1194  if(controller_ == CNTR_NETWORK) {
1196  // The host is ready. A network player, who got a chance
1197  // to choose faction if allowed, is also ready.
1198  return true;
1199  }
1200  }
1201 
1202  return false;
1203 }
1204 
1206 {
1207  if(controller_ == CNTR_NETWORK && player_id_.empty()) {
1208  // Side is free and waiting for user.
1209  return true;
1210  }
1211 
1212  if(controller_ == CNTR_RESERVED && name.empty()) {
1213  // Side is still available to someone.
1214  return true;
1215  }
1216 
1217  if(controller_ == CNTR_RESERVED && reserved_for_ == name) {
1218  // Side is available only for the player with specific name.
1219  return true;
1220  }
1221 
1222  return false;
1223 }
1224 
1225 void side_engine::resolve_random(randomness::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
1226 {
1227  if(parent_.params_.saved_game) {
1228  return;
1229  }
1230 
1232 
1233  flg_.resolve_random(rng, avoid_faction_ids);
1234 
1235  LOG_MP << "side " << (index_ + 1) << ": faction=" <<
1236  (flg_.current_faction())["name"] << ", leader=" <<
1237  flg_.current_leader() << ", gender=" << flg_.current_gender() << "\n";
1238 }
1239 
1241 {
1242  player_id_.clear();
1245 
1246  if(!parent_.params_.saved_game) {
1248  }
1249 }
1250 
1252 {
1253  config data;
1254  data["name"] = name;
1255 
1256  place_user(data);
1257 }
1258 
1259 void side_engine::place_user(const config& data, bool contains_selection)
1260 {
1261  player_id_ = data["name"].str();
1263 
1264  if(data["change_faction"].to_bool() && contains_selection) {
1265  // Network user's data carry information about chosen
1266  // faction, leader and genders.
1267  flg_.set_current_faction(data["faction"].str());
1268  flg_.set_current_leader(data["leader"].str());
1269  flg_.set_current_gender(data["gender"].str());
1270  }
1271 
1273 }
1274 
1276 {
1277  controller_options_.clear();
1278 
1279  // Default options.
1280  if(parent_.campaign_info_) {
1281  add_controller_option(CNTR_NETWORK, _("Network Player"), "human");
1282  }
1283 
1284  add_controller_option(CNTR_LOCAL, _("Local Player"), "human");
1285  add_controller_option(CNTR_COMPUTER, _("Computer Player"), "ai");
1286  add_controller_option(CNTR_EMPTY, _("Empty"), "null");
1287 
1288  if(!reserved_for_.empty()) {
1289  add_controller_option(CNTR_RESERVED, _("Reserved"), "human");
1290  }
1291 
1292  // Connected users.
1293  add_controller_option(CNTR_LAST, _("--give--"), "human");
1294  for(const std::string& user : parent_.connected_users()) {
1296  }
1297 
1299 }
1300 
1302 {
1303  int i = 0;
1304  for(const controller_option& option : controller_options_) {
1305  if(option.first == controller_) {
1307 
1308  if(player_id_.empty() || player_id_ == option.second) {
1309  // Stop searching if no user is assigned to a side
1310  // or the selected user is found.
1311  break;
1312  }
1313  }
1314 
1315  i++;
1316  }
1317 
1318  assert(current_controller_index_ < controller_options_.size());
1319 }
1320 
1321 bool side_engine::controller_changed(const int selection)
1322 {
1323  const ng::controller selected_cntr = controller_options_[selection].first;
1324  if(selected_cntr == CNTR_LAST) {
1325  return false;
1326  }
1327 
1328  // Check if user was selected. If so assign a side to him/her.
1329  // If not, make sure that no user is assigned to this side.
1330  if(selected_cntr == parent_.default_controller_ && selection != 0) {
1331  player_id_ = controller_options_[selection].second;
1333  } else {
1334  player_id_.clear();
1335  }
1336 
1337  set_controller(selected_cntr);
1338 
1339  return true;
1340 }
1341 
1343 {
1345 
1347 }
1348 
1350 {
1351  flg_.set_current_faction(faction_name);
1352 }
1353 
1355 {
1357 
1358  if(controller_name == "ai") {
1360  }
1361 
1362  if(controller_name == "null") {
1364  }
1365 
1366  player_id_.clear();
1367 }
1368 
1370  const std::string& name, const std::string& controller_value)
1371 {
1372  if(controller_lock_ && !cfg_["controller"].empty() &&
1373  cfg_["controller"] != controller_value) {
1374 
1375  return;
1376  }
1377 
1378  controller_options_.emplace_back(controller, name);
1379 }
1380 
1381 } // end namespace ng
void send_data(const configr_of &request)
bool can_start_game() const
#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:352
std::vector< char_t > string
size_t index(const utf8::string &str, const size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
void save_reserved_sides_information()
#define ERR_MP
std::string str(const std::string &fallback="") const
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.
std::string era()
Definition: game.cpp:700
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:250
std::vector< std::string > player_teams_
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.
void add_controller_option(ng::controller controller, const std::string &name, const std::string &controller_value)
config new_config() const
std::set< std::string > connected_players
players and observers
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
static lg::log_domain log_config("config")
bool empty() const
Tests for an attribute that either was never set or was set to "".
config initial_level_config(saved_game &state)
child_itors child_range(config_key_type key)
Definition: config.cpp:295
void place_user(const std::string &name)
#define LOG_NW
void set_faction_commandline(const std::string &faction_name)
#define WRN_MP
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line. 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
void clear()
Definition: config.cpp:742
bool receive_data(config &result)
void set_current_gender(const unsigned index)
std::vector< std::string > user_team_names_
void level_to_gamestate(const config &level, saved_game &state)
const bool allow_changes_
bool empty() const
Definition: config.cpp:750
void remove_attribute(config_key_type key)
Definition: config.cpp:218
std::vector< controller_option > controller_options_
bool ready_for_start() const
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)
const std::set< std::string > & connected_users() const
bool sides_available() const
static const std::string attributes_to_trim[]
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1033
std::string custom_color_
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:44
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:58
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
std::vector< side_engine_ptr > & side_engines()
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.
void send_to_server(const config &cfg) const
const std::string & current_gender() const
Definition: flg_manager.hpp:70
void clear_children(T...keys)
Definition: config.hpp:507
config & add_child_at(config_key_type key, const config &val, unsigned index)
Definition: config.cpp:440
void set_controller(ng::controller controller)
Encapsulates the map of the game.
Definition: map.hpp:34
std::string user_description() const
#define LOG_CF
void start_game_commandline(const commandline_options &cmdline_opts)
std::vector< std::string > color_options_
const bool first_scenario_
ng::controller controller() const
bool available_for_user(const std::string &name="") const
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
std::string login()
size_t size(const utf8::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
std::vector< std::string > get_children_to_swap()
void set_current_faction(const unsigned index)
Definition: flg_manager.cpp:87
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object...
Definition: config.cpp:813
void send_level_data() const
static void add_era_ai_from_config(const config &game_config)
void update_controller_options()
const config * default_leader_cfg() const
Definition: flg_manager.hpp:73
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:385
static map_location::DIRECTION s
std::vector< std::string > names
Definition: build_info.cpp:60
std::string reserved_for_
static lg::log_domain log_network("network")
bool use_map_settings()
Definition: game.cpp:493
#define i
connect_engine(saved_game &state, const bool first_scenario, mp_campaign_info *campaign_info)
bool receive_from_server(config &dst) const
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...
#define N_(String)
Definition: gettext.hpp:97
void update_current_controller_index()
static const std::string controller_names[]
const config & current_faction() const
Definition: flg_manager.hpp:66
unsigned current_controller_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.
connect_engine & parent_
std::string observer
std::shared_ptr< side_engine > side_engine_ptr
config & add_child(config_key_type key)
Definition: config.cpp:408
std::string to_serialized() const
Definition: tstring.hpp:142
std::string vgettext(const char *msgid, const utils::string_map &symbols)
std::pair< ng::controller, std::string > controller_option
const std::string & current_leader() const
Definition: flg_manager.hpp:68
int find_user_side_index_by_id(const std::string &id) 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:235
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 const char * name(const std::vector< SDL_Joystick * > &joysticks, const size_t index)
Definition: joystick.cpp:48
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
const mp_game_settings & params_
void remove_attributes(T...keys)
Definition: config.hpp:485
std::string color_id_
std::vector< std::string > default_colors
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
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.
std::vector< std::string > team_names_
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: tstring.hpp:172
static std::string controller_name(const team &t)
Definition: game_stats.cpp:77
std::pair< bool, bool > process_network_data(const config &data)
void update_side_controller_options()