The Battle for Wesnoth  1.13.11+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 - 2018 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_data_()
84 {
85  // Initial level config from the mp_game_settings.
87  if(level_.empty()) {
88  return;
89  }
90 
91  const bool is_mp = state_.classification().is_normal_mp_game();
92  force_lock_settings_ = (!state.mp_settings().saved_game) && scenario()["force_lock_settings"].to_bool(!is_mp);
93 
94  // Original level sides.
96 
97  // AI algorithms.
100 
101  // Set the team name lists and modify the original level sides if necessary.
102  std::vector<std::string> original_team_names;
103  std::string team_prefix(_("Team") + " ");
104 
105  int side_count = 1;
106  for(config& side : sides) {
107  const std::string side_str = std::to_string(side_count);
108 
109  config::attribute_value& team_name = side["team_name"];
110  config::attribute_value& user_team_name = side["user_team_name"];
111 
112  // Revert to default values if appropriate.
113  if(team_name.empty()) {
114  team_name = side_str;
115  }
116 
117  if(params_.use_map_settings && user_team_name.empty()) {
118  user_team_name = team_name;
119  }
120 
121  bool add_team = true;
123  // Only add a team if it is not found.
124  if(std::any_of(team_data_.begin(), team_data_.end(), [&team_name](const team_data_pod& data){
125  return data.team_name == team_name.str();
126  })) {
127  add_team = false;
128  }
129  } else {
130  // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
131  auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
132 
133  // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
134  // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
135  if(name_itor == original_team_names.end()) {
136  original_team_names.push_back(team_name);
137 
138  team_name = "Team " + std::to_string(original_team_names.size());
139  } else {
140  team_name = "Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
141  }
142 
143  user_team_name = team_prefix + side_str;
144  }
145 
146  // Write the serialized translatable team name back to the config. Without this,
147  // the string can appear all messed up after leaving and rejoining a game (see
148  // issue #2040. This affected the mp_join_game dialog). I don't know why that issue
149  // didn't appear the first time you join a game, but whatever.
150  //
151  // The difference between that dialog and mp_staging is that the latter has access
152  // to connect_engine object, meaning it has access to serialized versions of the
153  // user_team_name string stored in the team_data_ vector. mp_join_game handled the
154  // raw side config instead. Again, I don't know why issues only cropped up on a
155  // subsequent join and not the first, but it doesn't really matter.
156  //
157  // This ensures both dialogs have access to the serialized form of the utn string.
158  // As for why this needs to be done in the first place, apparently the simple_wml
159  // format the server (wesnothd) uses doesn't preserve translatable strings (see
160  // issue #342).
161  //
162  // --vultraz, 2018-02-06
163  user_team_name = user_team_name.t_str().to_serialized();
164 
165  if(add_team) {
166  team_data_pod data;
167  data.team_name = params_.use_map_settings ? team_name : "Team " + side_str;
168  data.user_team_name = user_team_name.str();
169  data.is_player_team = side["allow_player"].to_bool(true);
170 
171  team_data_.push_back(data);
172  }
173 
174  ++side_count;
175  }
176 
177  // Selected era's factions.
178  for(const config& era : level_.child("era").child_range("multiplayer_side")) {
179  era_factions_.push_back(&era);
180  }
181 
183 
184  // Create side engines.
185  int index = 0;
186  for(const config& s : sides) {
187  side_engines_.emplace_back(new side_engine(s, *this, index));
188 
189  index++;
190  }
191 
192  if(first_scenario_) {
193  // Add host to the connected users list.
195  } else {
196  // Add host but don't assign a side to him.
198 
199  // Load reserved players information into the sides.
201  }
202 
203  // Only updates the sides in the level.
204  update_level();
205 
206  // If we are connected, send data to the connected host.
207  send_level_data();
208 }
209 
210 
212  if(config& s = scenario()) {
213  return &s;
214  }
215 
216  return nullptr;
217 }
218 
219 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
220 {
221  config user_data;
222  user_data["name"] = name;
223  import_user(user_data, observer, side_taken);
224 }
225 
226 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
227 {
228  const std::string& username = data["name"];
229  assert(!username.empty());
230  if(campaign_info_) {
231  connected_users_rw().insert(username);
232  }
233 
235 
236  if(observer) {
237  return;
238  }
239 
240  bool side_assigned = false;
241  if(side_taken >= 0) {
242  side_engines_[side_taken]->place_user(data, true);
243  side_assigned = true;
244  }
245 
246  // Check if user has a side(s) reserved for him.
247  for(side_engine_ptr side : side_engines_) {
248  if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
249  side->place_user(data);
250 
251  side_assigned = true;
252  }
253  }
254 
255  // If no sides were assigned for a user,
256  // take a first available side.
257  if(side_taken < 0 && !side_assigned) {
258  for(side_engine_ptr side : side_engines_) {
259  if(side->available_for_user(username) ||
260  side->controller() == CNTR_LOCAL) {
261  side->place_user(data);
262 
263  side_assigned = true;
264  break;
265  }
266  }
267  }
268 
269  // Check if user has taken any sides, which should get control
270  // over any other sides.
271  for(side_engine_ptr user_side : side_engines_) {
272  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
273  for(side_engine_ptr side : side_engines_){
274  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
275  side->place_user(data);
276  }
277  }
278  }
279  }
280 }
281 
283 {
284  for(side_engine_ptr side : side_engines_) {
285  if(side->available_for_user()) {
286  return true;
287  }
288  }
289 
290  return false;
291 }
292 
294 {
295  DBG_MP << "updating level" << std::endl;
296 
297  scenario().clear_children("side");
298 
299  for(side_engine_ptr side : side_engines_) {
300  scenario().add_child("side", side->new_config());
301  }
302 }
303 
304 void connect_engine::update_and_send_diff(bool /*update_time_of_day*/)
305 {
306  config old_level = level_;
307  update_level();
308 
309  config diff = level_.get_diff(old_level);
310  if(!diff.empty()) {
311  config scenario_diff;
312  scenario_diff.add_child("scenario_diff", std::move(diff));
313  send_to_server(scenario_diff);
314  }
315 }
316 
318 {
319  if(side_engines_.empty()) {
320  return true;
321  }
322 
323  // First check if all sides are ready to start the game.
324  for(side_engine_ptr side : side_engines_) {
325  if(!side->ready_for_start()) {
326  const int side_num = side->index() + 1;
327  DBG_MP << "not all sides are ready, side " <<
328  side_num << " not ready\n";
329 
330  return false;
331  }
332  }
333 
334  DBG_MP << "all sides are ready" << std::endl;
335 
336  /*
337  * If at least one human player is slotted with a player/ai we're allowed
338  * to start. Before used a more advanced test but it seems people are
339  * creative in what is used in multiplayer [1] so use a simpler test now.
340  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
341  */
342  for(side_engine_ptr side : side_engines_) {
343  if(side->controller() != CNTR_EMPTY && side->allow_player()) {
344  return true;
345  }
346  }
347 
348  return false;
349 }
350 
352 {
353  if(campaign_info_) {
355  }
356 }
357 
359 {
360  if(campaign_info_) {
362  }
363  else {
364  return false;
365  }
366 }
367 
368 std::vector<std::string> side_engine::get_children_to_swap()
369 {
370  std::vector<std::string> children;
371 
372  children.push_back("village");
373  children.push_back("unit");
374  children.push_back("ai");
375 
376  return children;
377 }
378 
379 std::multimap<std::string, config> side_engine::get_side_children()
380 {
381  std::multimap<std::string, config> children;
382 
383  for(const std::string& children_to_swap : get_children_to_swap()) {
384  for(const config& child : cfg_.child_range(children_to_swap)) {
385  children.emplace(children_to_swap, child);
386  }
387  }
388 
389  return children;
390 }
391 
392 void side_engine::set_side_children(std::multimap<std::string, config> children)
393 {
394  for(const std::string& children_to_remove : get_children_to_swap()) {
395  cfg_.clear_children(children_to_remove);
396  }
397 
398  for(std::pair<std::string, config> child_map : children) {
399  cfg_.add_child(child_map.first, child_map.second);
400  }
401 }
402 
404 {
405  DBG_MP << "starting a new game" << std::endl;
406 
407  // Resolves the "random faction", "random gender" and "random message"
408  // Must be done before shuffle sides, or some cases will cause errors
409  randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
410  for(side_engine_ptr side : side_engines_) {
411  std::vector<std::string> avoid_faction_ids;
412 
413  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
414  if(params_.random_faction_mode != mp_game_settings::RANDOM_FACTION_MODE::DEFAULT) {
415  for(side_engine_ptr side2 : side_engines_) {
416  if(!side2->flg().is_random_faction()) {
417  switch(params_.random_faction_mode.v) {
418  case mp_game_settings::RANDOM_FACTION_MODE::NO_MIRROR:
419  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
420  break;
421  case mp_game_settings::RANDOM_FACTION_MODE::NO_ALLY_MIRROR:
422  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"
423  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
424  }
425  break;
426  default:
427  break; // assert(false);
428  }
429  }
430  }
431  }
432  side->resolve_random(rng, avoid_faction_ids);
433  }
434 
435  // Shuffle sides (check settings and if it is a re-loaded game).
436  // Must be done after resolve_random() or shuffle sides, or they won't work.
437  if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.child("snapshot") && level_.child("snapshot").child("side"))) {
438 
439  // Only playable sides should be shuffled.
440  std::vector<int> playable_sides;
441  for(side_engine_ptr side : side_engines_) {
442  if(side->allow_player() && side->allow_shuffle()) {
443  playable_sides.push_back(side->index());
444  }
445  }
446 
447  // Fisher-Yates shuffle.
448  for(int i = playable_sides.size(); i > 1; i--) {
449  int j_side = playable_sides[rng.get_next_random() % i];
450  int i_side = playable_sides[i - 1];
451 
452  if(i_side == j_side) continue; //nothing to swap
453 
454  // First we swap everything about a side with another
455  side_engine_ptr tmp_side = side_engines_[j_side];
456  side_engines_[j_side] = side_engines_[i_side];
457  side_engines_[i_side] = tmp_side;
458 
459  // Some 'child' variables such as village ownership and
460  // initial side units need to be swapped over as well
461  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
462  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
463  side_engines_[i_side]->set_side_children(tmp_side_children);
464 
465  // Then we revert the swap for fields that are unique to
466  // player control and the team they selected
467  int tmp_index = side_engines_[j_side]->index();
468  side_engines_[j_side]->set_index(side_engines_[i_side]->index());
469  side_engines_[i_side]->set_index(tmp_index);
470 
471  int tmp_team = side_engines_[j_side]->team();
472  side_engines_[j_side]->set_team(side_engines_[i_side]->team());
473  side_engines_[i_side]->set_team(tmp_team);
474  }
475  }
476 
477  // Make other clients not show the results of resolve_random().
478  config lock("stop_updates");
479  send_to_server(lock);
480 
481  update_and_send_diff(true);
482 
484 
485  // Build the gamestate object after updating the level.
487 
488  send_to_server(config("start_game"));
489 }
490 
492 {
493  DBG_MP << "starting a new game in commandline mode" << std::endl;
494 
495  typedef std::tuple<unsigned int, std::string> mp_option;
496 
497  randomness::mt_rng rng;
498 
499  unsigned num = 0;
500  for(side_engine_ptr side : side_engines_) {
501  num++;
502 
503  // Set the faction, if commandline option is given.
504  if(cmdline_opts.multiplayer_side) {
505  for(const mp_option& option : *cmdline_opts.multiplayer_side) {
506 
507  if(std::get<0>(option) == num) {
508  if(std::find_if(era_factions_.begin(), era_factions_.end(), [&option](const config* faction) { return (*faction)["id"] == std::get<1>(option); }) != era_factions_.end()) {
509  DBG_MP << "\tsetting side " << std::get<0>(option) << "\tfaction: " << std::get<1>(option) << std::endl;
510 
511  side->set_faction_commandline(std::get<1>(option));
512  }
513  else {
514  ERR_MP << "failed to set side " << std::get<0>(option) << " to faction " << std::get<1>(option) << std::endl;
515  }
516  }
517  }
518  }
519 
520  // Set the controller, if commandline option is given.
521  if(cmdline_opts.multiplayer_controller) {
522  for(const mp_option& option : *cmdline_opts.multiplayer_controller) {
523 
524  if(std::get<0>(option) == num) {
525  DBG_MP << "\tsetting side " << std::get<0>(option) <<
526  "\tfaction: " << std::get<1>(option) << std::endl;
527 
528  side->set_controller_commandline(std::get<1>(option));
529  }
530  }
531  }
532 
533  // Set AI algorithm to RCA AI for all sides,
534  // then override if commandline option was given.
535  side->set_ai_algorithm("ai_default_rca");
536  if(cmdline_opts.multiplayer_algorithm) {
537  for(const mp_option& option : *cmdline_opts.multiplayer_algorithm) {
538 
539  if(std::get<0>(option) == num) {
540  DBG_MP << "\tsetting side " << std::get<0>(option) <<
541  "\tfaction: " << std::get<1>(option) << std::endl;
542 
543  side->set_ai_algorithm(std::get<1>(option));
544  }
545  }
546  }
547 
548  // Finally, resolve "random faction",
549  // "random gender" and "random message", if any remains unresolved.
550  side->resolve_random(rng);
551  } // end top-level loop
552 
553  update_and_send_diff(true);
554 
555  // Update sides with commandline parameters.
556  if(cmdline_opts.multiplayer_turns) {
557  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns <<
558  std::endl;
559  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
560  }
561 
562  for(config &side : scenario().child_range("side")) {
563  if(cmdline_opts.multiplayer_ai_config) {
564  for(const mp_option& option : *cmdline_opts.multiplayer_ai_config) {
565 
566  if(std::get<0>(option) == side["side"].to_unsigned()) {
567  DBG_MP << "\tsetting side " << side["side"] <<
568  "\tai_config: " << std::get<1>(option) << std::endl;
569 
570  side["ai_config"] = std::get<1>(option);
571  }
572  }
573  }
574 
575  // Having hard-coded values here is undesirable,
576  // but that's how it is done in the MP lobby
577  // part of the code also.
578  // Should be replaced by settings/constants in both places
579  if(cmdline_opts.multiplayer_ignore_map_settings) {
580  side["gold"] = 100;
581  side["income"] = 1;
582  }
583 
584  typedef std::tuple<unsigned int, std::string, std::string> mp_parameter;
585 
586  if(cmdline_opts.multiplayer_parm) {
587  for(const mp_parameter& parameter : *cmdline_opts.multiplayer_parm) {
588 
589  if(std::get<0>(parameter) == side["side"].to_unsigned()) {
590  DBG_MP << "\tsetting side " << side["side"] << " " <<
591  std::get<1>(parameter) << ": " << std::get<2>(parameter) << std::endl;
592 
593  side[std::get<1>(parameter)] = std::get<2>(parameter);
594  }
595  }
596  }
597  }
598 
600 
601  // Build the gamestate object after updating the level
603  send_to_server(config("start_game"));
604 }
605 
607 {
608  DBG_MP << "leaving the game" << std::endl;
609 
610  send_to_server(config("leave_game"));
611 }
612 
613 std::pair<bool, bool> connect_engine::process_network_data(const config& data)
614 {
615  std::pair<bool, bool> result(std::make_pair(false, true));
616 
617  if(data.child("leave_game")) {
618  result.first = true;
619  return result;
620  }
621 
622  // A side has been dropped.
623  if(const config& side_drop = data.child("side_drop")) {
624  unsigned side_index = side_drop["side_num"].to_int() - 1;
625 
626  if(side_index < side_engines_.size()) {
627  side_engine_ptr side_to_drop = side_engines_[side_index];
628 
629  // Remove user, whose side was dropped.
630  connected_users_rw().erase(side_to_drop->player_id());
632 
633  side_to_drop->reset();
634 
636 
637  return result;
638  }
639  }
640 
641  // A player is connecting to the game.
642  if(!data["side"].empty()) {
643  unsigned side_taken = data["side"].to_int() - 1;
644 
645  // Checks if the connecting user has a valid and unique name.
646  const std::string name = data["name"];
647  if(name.empty()) {
648  config response;
649  response["failed"] = true;
650  send_to_server(response);
651 
652  ERR_CF << "ERROR: No username provided with the side." << std::endl;
653 
654  return result;
655  }
656 
657  if(connected_users().find(name) != connected_users().end()) {
658  // TODO: Seems like a needless limitation
659  // to only allow one side per player.
660  if(find_user_side_index_by_id(name) != -1) {
661  config response;
662  response["failed"] = true;
663  response["message"] = "The nickname '" + name +
664  "' is already in use.";
665  send_to_server(response);
666 
667  return result;
668  } else {
669  connected_users_rw().erase(name);
671  config observer_quit;
672  observer_quit.add_child("observer_quit")["name"] = name;
673  send_to_server(observer_quit);
674  }
675  }
676 
677  // Assigns this user to a side.
678  if(side_taken < side_engines_.size()) {
679  if(!side_engines_[side_taken]->available_for_user(name)) {
680  // This side is already taken.
681  // Try to reassing the player to a different position.
682  side_taken = 0;
684  if(s->available_for_user()) {
685  break;
686  }
687 
688  side_taken++;
689  }
690 
691  if(side_taken >= side_engines_.size()) {
692  config response;
693  response["failed"] = true;
694  send_to_server(response);
695 
696  config res;
697  config& kick = res.add_child("kick");
698  kick["username"] = data["name"];
699  send_to_server(res);
700 
702 
703  ERR_CF << "ERROR: Couldn't assign a side to '" <<
704  name << "'\n";
705 
706  return result;
707  }
708  }
709 
710  LOG_CF << "client has taken a valid position\n";
711 
712  import_user(data, false, side_taken);
714 
715  // Wait for them to choose faction if allowed.
716  side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
717  LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes() << std::endl;
718  result.second = false;
719 
720  LOG_NW << "sent player data\n";
721  } else {
722  ERR_CF << "tried to take illegal side: " << side_taken << std::endl;
723 
724  config response;
725  response["failed"] = true;
726  send_to_server(response);
727  }
728  }
729 
730  if(const config& change_faction = data.child("change_faction")) {
731  int side_taken = find_user_side_index_by_id(change_faction["name"]);
732  if(side_taken != -1 || !first_scenario_) {
733  import_user(change_faction, false, side_taken);
735  }
736  }
737 
738  if(const config& observer = data.child("observer")) {
739  import_user(observer, true);
741  }
742 
743  if(const config& observer = data.child("observer_quit")) {
744  const std::string& observer_name = observer["name"];
745 
746  if(connected_users().find(observer_name) != connected_users().end()) {
747  connected_users_rw().erase(observer_name);
749 
750  // If the observer was assigned a side, we need to send an update to other
751  // players so they no longer see the observer assigned to that side.
752  if(find_user_side_index_by_id(observer_name) != -1) {
754  }
755  }
756  }
757 
758  return result;
759 }
760 
762 {
763  size_t i = 0;
764  for(side_engine_ptr side : side_engines_) {
765  if(side->player_id() == id) {
766  break;
767  }
768 
769  i++;
770  }
771 
772  if(i >= side_engines_.size()) {
773  return -1;
774  }
775 
776  return i;
777 }
778 
780 {
781  // Send initial information.
782  if(first_scenario_) {
784  "create_game", config {
785  "name", params_.name,
786  "password", params_.password,
787  },
788  });
790  } else {
791  send_to_server(config {"update_game", config()});
792  config next_level;
793  next_level.add_child("store_next_scenario", level_);
794  send_to_server(next_level);
795  }
796 }
797 
799 {
800  // Add information about reserved sides to the level config.
801  // N.B. This information is needed only for a host player.
802  std::map<std::string, std::string> side_users = utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
803  for(side_engine_ptr side : side_engines_) {
804  const std::string& save_id = side->save_id();
805  const std::string& player_id = side->player_id();
806  if(!save_id.empty() && !player_id.empty()) {
807  side_users[save_id] = player_id;
808  }
809  }
810 
811  level_.child("multiplayer")["side_users"] = utils::join_map(side_users);
812 }
813 
815 {
816  std::map<std::string, std::string> side_users = utils::map_split(level_.child("multiplayer")["side_users"]);
817  std::set<std::string> names;
818  for(side_engine_ptr side : side_engines_) {
819  const std::string& save_id = side->previous_save_id();
820  if(side_users.find(save_id) != side_users.end()) {
821  side->set_reserved_for(side_users[save_id]);
822 
823  if(side->controller() != CNTR_COMPUTER) {
824  side->set_controller(CNTR_RESERVED);
825  names.insert(side_users[save_id]);
826  }
827 
828  side->update_controller_options();
829  }
830  }
831 
832  //Do this in an extra loop to make sure we import each user only once.
833  for(const std::string& name : names)
834  {
835  if(connected_users().find(name) != connected_users().end() || !campaign_info_) {
836  import_user(name, false);
837  }
838  }
839 }
840 
842 {
843  for(side_engine_ptr side : side_engines_) {
844  side->update_controller_options();
845  }
846 }
847 
848 const std::set<std::string>& connect_engine::connected_users() const
849 {
850  if(campaign_info_) {
852  }
853 
854  static std::set<std::string> empty;
855  return empty;
856 }
857 
858 std::set<std::string>& connect_engine::connected_users_rw()
859 {
860  assert(campaign_info_);
862 }
863 
864 side_engine::side_engine(const config& cfg, connect_engine& parent_engine, const int index)
865  : cfg_(cfg)
866  , parent_(parent_engine)
867  , controller_(CNTR_NETWORK)
868  , current_controller_index_(0)
869  , controller_options_()
870  , allow_player_(cfg["allow_player"].to_bool(true))
871  , controller_lock_(cfg["controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
872  , index_(index)
873  , team_(0)
874  , color_(std::min(index, gamemap::MAX_PLAYERS - 1))
875  , gold_(cfg["gold"].to_int(100))
876  , income_(cfg["income"])
877  , reserved_for_(cfg["current_player"])
878  , player_id_()
879  , ai_algorithm_()
880  , chose_random_(cfg["chose_random"].to_bool(false))
881  , disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false))
882  , flg_(parent_.era_factions_, cfg_, parent_.force_lock_settings_, parent_.params_.use_map_settings, parent_.params_.saved_game)
883  , allow_changes_(!parent_.params_.saved_game && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1))
884  , waiting_to_choose_faction_(allow_changes_)
885  , color_options_(game_config::default_colors)
886  , color_id_(color_options_[color_])
887 {
888  // Save default attributes that could be overwirtten by the faction, so that correct faction lists would be
889  // initialized by flg_manager when the new side config is sent over network.
890  cfg_.add_child("default_faction", config {
891  "type", cfg_["type"],
892  "gender", cfg_["gender"],
893  "faction", cfg_["faction"],
894  "recruit", cfg_["recruit"],
895  });
896 
897  if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
898  ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1 << std::endl;
899  }
900 
901  cfg_["side"] = index_ + 1;
902 
903  // Check if this side should give its control to some other side.
904  const size_t side_cntr_index = cfg_["controller"].to_int(-1) - 1;
905  if(side_cntr_index < parent_.side_engines().size()) {
906  // Remove this attribute to avoid locking side
907  // to non-existing controller type.
908  cfg_.remove_attribute("controller");
909 
910  cfg_["previous_save_id"] = parent_.side_engines()[side_cntr_index]->previous_save_id();
911  ERR_MP << "controller=<number> is deperecated\n";
912  }
913 
914  if(!parent_.params_.saved_game && cfg_["save_id"].str().empty()) {
915  assert(cfg_["id"].empty()); // we already set "save_id" to "id" if "id" existed.
916  cfg_["save_id"] = parent_.scenario()["id"].str() + "_" + std::to_string(index);
917  }
918 
919  if(cfg_["controller"] != "human" && cfg_["controller"] != "ai" && cfg_["controller"] != "null") {
920  //an invalid controller type was specified. Remove it to prevent asertion failures later.
921  cfg_.remove_attribute("controller");
922  }
923 
925 
926  // Tweak the controllers.
927  if(parent_.state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::SCENARIO && cfg_["controller"].blank()) {
928  cfg_["controller"] = "ai";
929  }
930 
931  if(cfg_["controller"] == "null") {
933  } else if(cfg_["controller"] == "ai") {
935  } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
936  // Reserve a side for "current_player", unless the side
937  // is played by an AI.
939  } else if(allow_player_) {
941  } else {
942  // AI is the default.
944  }
945 
946  // Initialize team and color.
947  unsigned team_name_index = 0;
949  if(data.team_name == cfg["team_name"]) {
950  break;
951  }
952 
953  ++team_name_index;
954  }
955 
956  if(team_name_index >= parent_.team_data_.size()) {
957  assert(!parent_.team_data_.empty());
958  team_ = 0;
959  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;
960  } else {
961  team_ = team_name_index;
962  }
963 
964  if(!cfg["color"].empty()) {
965  if(cfg["color"].to_int()) {
966  color_ = cfg["color"].to_int() - 1;
968  } else {
969  const std::string custom_color = cfg["color"].str();
970 
971  const auto iter = std::find(color_options_.begin(), color_options_.end(), custom_color);
972 
973  if(iter != color_options_.end()) {
974  color_id_ = *iter;
975  color_ = std::distance(color_options_.begin(), iter);
976  } else {
977  color_options_.push_back(custom_color);
978 
979  color_id_ = custom_color;
980  color_ = color_options_.size() - 1;
981  }
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 
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) {
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  // Do not import default ai cfg otherwise - all is set by scenario config.
1057  res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
1058  }
1059 
1060  if(controller_ == CNTR_EMPTY) {
1061  res["no_leader"] = true;
1062  }
1063 
1064  // A side's "current_player" is the player which has currently taken that side or the one for which it is reserved.
1065  // The "player_id" is the id of the client who controls that side. It's always the host for Local and AI players and
1066  // always empty for free/reserved sides or null controlled sides. You can use !res["player_id"].empty() to check
1067  // whether a side is already taken.
1068  assert(!preferences::login().empty());
1069  if(controller_ == CNTR_LOCAL) {
1070  res["player_id"] = preferences::login();
1071  res["current_player"] = preferences::login();
1072  } else if(controller_ == CNTR_RESERVED) {
1073  res.remove_attribute("player_id");
1074  res["current_player"] = reserved_for_;
1075  } else if(controller_ == CNTR_COMPUTER) {
1076  // TODO: what is the content of player_id_ here ?
1077  res["current_player"] = desc;
1078  res["player_id"] = preferences::login();
1079  } else if(!player_id_.empty()) {
1080  res["player_id"] = player_id_;
1081  res["current_player"] = player_id_;
1082  }
1083 
1084  res["allow_changes"] = allow_changes_;
1085  res["chose_random"] = chose_random_;
1086 
1087  if(!parent_.params_.saved_game) {
1088  // Find a config where a default leader is and set a new type and gender values for it.
1089  config* leader = &res;
1090 
1091  if(flg_.default_leader_cfg() != nullptr) {
1092  for(config& side_unit : res.child_range("unit")) {
1093  if(*flg_.default_leader_cfg() != side_unit) {
1094  continue;
1095  }
1096 
1097  leader = &side_unit;
1098 
1099  if(flg_.current_leader() != (*leader)["type"]) {
1100  // If a new leader type was selected from carryover, make sure that we reset the leader.
1101  std::string leader_id = (*leader)["id"];
1102  leader->clear();
1103 
1104  if(!leader_id.empty()) {
1105  (*leader)["id"] = leader_id;
1106  }
1107  }
1108 
1109  break;
1110  }
1111  }
1112 
1113  // NOTE: the presence of a type= key overrides no_leader
1114  if(controller_ != CNTR_EMPTY) {
1115  (*leader)["type"] = flg_.current_leader();
1116  (*leader)["gender"] = flg_.current_gender();
1117  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " type=" << (*leader)["type"] << " gender=" << (*leader)["gender"] << "\n";
1118  } else {
1119  // TODO: FIX THIS SHIT! We shouldn't have a special string to denote no-leader-ness...
1120  (*leader)["type"] = "null";
1121  (*leader)["gender"] = "null";
1122  }
1123 
1124  res["team_name"] = parent_.team_data_[team_].team_name;
1125 
1126  // TODO: Fix this mess!
1127  //
1128  // There is a fundamental disconnect, here. One the one hand we have the idea of
1129  // 'teams' (which don't actually exist). A 'team' has a name (internal key:
1130  // team_name) and a translatable display name (internal key: user_team_name). But
1131  // what we actually have are sides. Sides relate to each other by 'team' (internal
1132  // key: team_name) and each side has it's own name for the team (internal key:
1133  // user_team_name).
1134  //
1135  // The confusion is that the keys from the side have names which one might expect
1136  // always refer to the 'team' concept. THEY DO NOT! They are simply named in such
1137  // a way to confuse the unwary.
1138  //
1139  // There is no simple, clean way to clear up the confusion. So, I'm applying the
1140  // Principle of Least Surprise. The user can see the user_team_name, and it should
1141  // not change. So if the side already has a user_team_name, use it.
1142  //
1143  // In the rare and unlikely (like, probably never happens) case that the side does
1144  // not have a user_team_name, but an (nebulous and non-deterministic term here)
1145  // EARLIER side has the same team_name and that side gives a user_team_name, we'll
1146  // use it.
1147  //
1148  // The effect of this mess, and my lame fix for it, is probably only visible when
1149  // randomizing the sides on a team for multi-player games. But the effect when it's
1150  // not fixed is an obvious mistake on the player's screen when playing a campaign
1151  // in single-player mode.
1152  //
1153  // At some level, all this is probably wrong, but it is the least breakage from the
1154  // mess I found; so deal with it, or fix it.
1155  //
1156  // If, by now, you get the impression this is a kludged-together mess which cries
1157  // out for an honest design and a thoughtful implementation, you're correct! But
1158  // I'm tired, and I'm cranky from wasting a over day on this, and so I'm exercising
1159  // my prerogative as a grey-beard and leaving this for someone else to clean up.
1160  if(res["user_team_name"].empty() || !parent_.params_.use_map_settings) {
1161  res["user_team_name"] = parent_.team_data_[team_].user_team_name;
1162  }
1163 
1164  res["allow_player"] = allow_player_;
1165  res["color"] = color_id_;
1166  res["gold"] = gold_;
1167  res["income"] = income_;
1168  }
1169 
1171  config trimmed = cfg_;
1172 
1173  for(const std::string& attribute : attributes_to_trim) {
1174  trimmed.remove_attribute(attribute);
1175  }
1176 
1177  if(controller_ != CNTR_COMPUTER) {
1178  // Only override names for computer controlled players.
1179  trimmed.remove_attribute("user_description");
1180  }
1181 
1182  res.merge_with(trimmed);
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 
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) {
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) {
1261  }
1262 }
1263 
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, _("Empty"), "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 
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
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:419
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:704
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:287
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:362
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
std::vector< team_data_pod > team_data_
void clear()
Definition: config.cpp:809
bool receive_data(config &result)
void set_current_gender(const unsigned index)
void level_to_gamestate(const config &level, saved_game &state)
const bool allow_changes_
bool empty() const
Definition: config.cpp:830
void remove_attribute(config_key_type key)
Definition: config.cpp:235
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:1113
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:510
config & add_child_at(config_key_type key, const config &val, unsigned index)
Definition: config.cpp:507
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:89
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:893
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
Game configuration data as global variables.
Definition: build_info.cpp:53
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:452
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:497
size_t i
Definition: function.cpp:933
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:475
std::string to_serialized() const
Definition: tstring.hpp:150
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:238
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:488
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.
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()
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()