The Battle for Wesnoth  1.15.0-dev
connect_engine.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2018 by Andrius Silinskas <silinskas.andrius@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
15 
16 #include "ai/configuration.hpp"
17 #include "formula/string_utils.hpp"
21 #include "preferences/game.hpp"
22 #include "gettext.hpp"
23 #include "log.hpp"
24 #include "map/map.hpp"
25 #include "mt_rng.hpp"
26 #include "tod_manager.hpp"
27 #include "team.hpp"
28 #include "wesnothd_connection.hpp"
29 
30 #include <cstdlib>
31 #include <ctime>
32 
33 static lg::log_domain log_config("config");
34 #define LOG_CF LOG_STREAM(info, log_config)
35 #define ERR_CF LOG_STREAM(err, log_config)
36 
37 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
38 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
39 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
40 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
41 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
42 
43 static lg::log_domain log_network("network");
44 #define LOG_NW LOG_STREAM(info, log_network)
45 
46 static const std::string controller_names[] {
47  "human",
48  "human",
49  "ai",
50  "null",
51  "reserved"
52 };
53 
54 namespace ng {
55 
56 connect_engine::connect_engine(saved_game& state, const bool first_scenario, mp_campaign_info* campaign_info)
57  : level_()
58  , state_(state)
59  , params_(state.mp_settings())
60  , default_controller_(campaign_info ? CNTR_NETWORK : CNTR_LOCAL)
61  , campaign_info_(campaign_info)
62  , first_scenario_(first_scenario)
63  , force_lock_settings_()
64  , side_engines_()
65  , era_factions_()
66  , team_data_()
67 {
68  // Initial level config from the mp_game_settings.
70  if(level_.empty()) {
71  return;
72  }
73 
74  const bool is_mp = state_.classification().is_normal_mp_game();
75  force_lock_settings_ = (state.mp_settings().saved_game != mp_game_settings::SAVED_GAME_MODE::MIDGAME) && scenario()["force_lock_settings"].to_bool(!is_mp);
76 
77  // Original level sides.
79 
80  // AI algorithms.
83 
84  // Set the team name lists and modify the original level sides if necessary.
85  std::vector<std::string> original_team_names;
86  std::string team_prefix(_("Team") + " ");
87 
88  int side_count = 1;
89  for(config& side : sides) {
90  const std::string side_str = std::to_string(side_count);
91 
92  config::attribute_value& team_name = side["team_name"];
93  config::attribute_value& user_team_name = side["user_team_name"];
94 
95  // Revert to default values if appropriate.
96  if(team_name.empty()) {
97  team_name = side_str;
98  }
99 
100  if(params_.use_map_settings && user_team_name.empty()) {
101  user_team_name = team_name;
102  }
103 
104  bool add_team = true;
106  // Only add a team if it is not found.
107  if(std::any_of(team_data_.begin(), team_data_.end(), [&team_name](const team_data_pod& data){
108  return data.team_name == team_name.str();
109  })) {
110  add_team = false;
111  }
112  } else {
113  // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
114  auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
115 
116  // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
117  // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
118  if(name_itor == original_team_names.end()) {
119  original_team_names.push_back(team_name);
120 
121  team_name = "Team " + std::to_string(original_team_names.size());
122  } else {
123  team_name = "Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
124  }
125 
126  user_team_name = team_prefix + side_str;
127  }
128 
129  // Write the serialized translatable team name back to the config. Without this,
130  // the string can appear all messed up after leaving and rejoining a game (see
131  // issue #2040. This affected the mp_join_game dialog). I don't know why that issue
132  // didn't appear the first time you join a game, but whatever.
133  //
134  // The difference between that dialog and mp_staging is that the latter has access
135  // to connect_engine object, meaning it has access to serialized versions of the
136  // user_team_name string stored in the team_data_ vector. mp_join_game handled the
137  // raw side config instead. Again, I don't know why issues only cropped up on a
138  // subsequent join and not the first, but it doesn't really matter.
139  //
140  // This ensures both dialogs have access to the serialized form of the utn string.
141  // As for why this needs to be done in the first place, apparently the simple_wml
142  // format the server (wesnothd) uses doesn't preserve translatable strings (see
143  // issue #342).
144  //
145  // --vultraz, 2018-02-06
146  user_team_name = user_team_name.t_str().to_serialized();
147 
148  if(add_team) {
149  team_data_pod data;
150  data.team_name = params_.use_map_settings ? team_name : "Team " + side_str;
151  data.user_team_name = user_team_name.str();
152  data.is_player_team = side["allow_player"].to_bool(true);
153 
154  team_data_.push_back(data);
155  }
156 
157  ++side_count;
158  }
159 
160  // Selected era's factions.
161  for(const config& era : level_.child("era").child_range("multiplayer_side")) {
162  era_factions_.push_back(&era);
163  }
164 
165  // Sort alphabetically, but with the random faction options always first.
166  // Since some eras have multiple random options we can't just assume there is
167  // only one random faction on top of the list.
168  std::sort(era_factions_.begin(), era_factions_.end(), [](const config* c1, const config* c2) {
169  const config& lhs = *c1;
170  const config& rhs = *c2;
171 
172  // Random factions always first.
173  if(lhs["random_faction"].to_bool() && !rhs["random_faction"].to_bool()) {
174  return true;
175  }
176 
177  if(!lhs["random_faction"].to_bool() && rhs["random_faction"].to_bool()) {
178  return false;
179  }
180 
181  return translation::compare(lhs["name"].str(), rhs["name"].str()) < 0;
182  });
183 
185 
186  // Create side engines.
187  int index = 0;
188  for(const config& s : sides) {
189  side_engines_.emplace_back(new side_engine(s, *this, index));
190 
191  index++;
192  }
193 
194  if(first_scenario_) {
195  // Add host to the connected users list.
197  } else {
198  // Add host but don't assign a side to him.
200 
201  // Load reserved players information into the sides.
203  }
204 
205  // Only updates the sides in the level.
206  update_level();
207 
208  // If we are connected, send data to the connected host.
209  send_level_data();
210 }
211 
212 
214  if(config& s = scenario()) {
215  return &s;
216  }
217 
218  return nullptr;
219 }
220 
221 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
222 {
223  config user_data;
224  user_data["name"] = name;
225  import_user(user_data, observer, side_taken);
226 }
227 
228 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
229 {
230  const std::string& username = data["name"];
231  assert(!username.empty());
232  if(campaign_info_) {
233  connected_users_rw().insert(username);
234  }
235 
237 
238  if(observer) {
239  return;
240  }
241 
242  bool side_assigned = false;
243  if(side_taken >= 0) {
244  side_engines_[side_taken]->place_user(data, true);
245  side_assigned = true;
246  }
247 
248  // Check if user has a side(s) reserved for him.
249  for(side_engine_ptr side : side_engines_) {
250  if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
251  side->place_user(data);
252 
253  side_assigned = true;
254  }
255  }
256 
257  // If no sides were assigned for a user,
258  // take a first available side.
259  if(side_taken < 0 && !side_assigned) {
260  for(side_engine_ptr side : side_engines_) {
261  if(side->available_for_user(username) ||
262  side->controller() == CNTR_LOCAL) {
263  side->place_user(data);
264 
265  side_assigned = true;
266  break;
267  }
268  }
269  }
270 
271  // Check if user has taken any sides, which should get control
272  // over any other sides.
273  for(side_engine_ptr user_side : side_engines_) {
274  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
275  for(side_engine_ptr side : side_engines_){
276  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
277  side->place_user(data);
278  }
279  }
280  }
281  }
282 }
283 
285 {
286  for(side_engine_ptr side : side_engines_) {
287  if(side->available_for_user()) {
288  return true;
289  }
290  }
291 
292  return false;
293 }
294 
296 {
297  DBG_MP << "updating level" << std::endl;
298 
299  scenario().clear_children("side");
300 
301  for(side_engine_ptr side : side_engines_) {
302  scenario().add_child("side", side->new_config());
303  }
304 }
305 
306 void connect_engine::update_and_send_diff(bool /*update_time_of_day*/)
307 {
308  config old_level = level_;
309  update_level();
310 
311  config diff = level_.get_diff(old_level);
312  if(!diff.empty()) {
313  config scenario_diff;
314  scenario_diff.add_child("scenario_diff", std::move(diff));
315  send_to_server(scenario_diff);
316  }
317 }
318 
320 {
321  if(side_engines_.empty()) {
322  return true;
323  }
324 
325  // First check if all sides are ready to start the game.
326  for(side_engine_ptr side : side_engines_) {
327  if(!side->ready_for_start()) {
328  const int side_num = side->index() + 1;
329  DBG_MP << "not all sides are ready, side " <<
330  side_num << " not ready\n";
331 
332  return false;
333  }
334  }
335 
336  DBG_MP << "all sides are ready" << std::endl;
337 
338  /*
339  * If at least one human player is slotted with a player/ai we're allowed
340  * to start. Before used a more advanced test but it seems people are
341  * creative in what is used in multiplayer [1] so use a simpler test now.
342  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
343  */
344  for(side_engine_ptr side : side_engines_) {
345  if(side->controller() != CNTR_EMPTY && side->allow_player()) {
346  return true;
347  }
348  }
349 
350  return false;
351 }
352 
354 {
355  if(campaign_info_) {
357  }
358 }
359 
361 {
362  if(campaign_info_) {
364  }
365  else {
366  return false;
367  }
368 }
369 
370 std::vector<std::string> side_engine::get_children_to_swap()
371 {
372  std::vector<std::string> children;
373 
374  children.push_back("village");
375  children.push_back("unit");
376  children.push_back("ai");
377 
378  return children;
379 }
380 
381 std::multimap<std::string, config> side_engine::get_side_children()
382 {
383  std::multimap<std::string, config> children;
384 
385  for(const std::string& children_to_swap : get_children_to_swap()) {
386  for(const config& child : cfg_.child_range(children_to_swap)) {
387  children.emplace(children_to_swap, child);
388  }
389  }
390 
391  return children;
392 }
393 
394 void side_engine::set_side_children(std::multimap<std::string, config> children)
395 {
396  for(const std::string& children_to_remove : get_children_to_swap()) {
397  cfg_.clear_children(children_to_remove);
398  }
399 
400  for(std::pair<std::string, config> child_map : children) {
401  cfg_.add_child(child_map.first, child_map.second);
402  }
403 }
404 
406 {
407  DBG_MP << "starting a new game" << std::endl;
408 
409  // Resolves the "random faction", "random gender" and "random message"
410  // Must be done before shuffle sides, or some cases will cause errors
411  randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
412  for(side_engine_ptr side : side_engines_) {
413  std::vector<std::string> avoid_faction_ids;
414 
415  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
416  if(params_.random_faction_mode != mp_game_settings::RANDOM_FACTION_MODE::DEFAULT) {
417  for(side_engine_ptr side2 : side_engines_) {
418  if(!side2->flg().is_random_faction()) {
419  switch(params_.random_faction_mode.v) {
420  case mp_game_settings::RANDOM_FACTION_MODE::NO_MIRROR:
421  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
422  break;
423  case mp_game_settings::RANDOM_FACTION_MODE::NO_ALLY_MIRROR:
424  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"
425  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
426  }
427  break;
428  default:
429  break; // assert(false);
430  }
431  }
432  }
433  }
434  side->resolve_random(rng, avoid_faction_ids);
435  }
436 
437  // Shuffle sides (check settings and if it is a re-loaded game).
438  // Must be done after resolve_random() or shuffle sides, or they won't work.
439  if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.child("snapshot") && level_.child("snapshot").child("side"))) {
440 
441  // Only playable sides should be shuffled.
442  std::vector<int> playable_sides;
443  for(side_engine_ptr side : side_engines_) {
444  if(side->allow_player() && side->allow_shuffle()) {
445  playable_sides.push_back(side->index());
446  }
447  }
448 
449  // Fisher-Yates shuffle.
450  for(int i = playable_sides.size(); i > 1; i--) {
451  int j_side = playable_sides[rng.get_next_random() % i];
452  int i_side = playable_sides[i - 1];
453 
454  if(i_side == j_side) continue; //nothing to swap
455 
456  // First we swap everything about a side with another
457  side_engine_ptr tmp_side = side_engines_[j_side];
458  side_engines_[j_side] = side_engines_[i_side];
459  side_engines_[i_side] = tmp_side;
460 
461  // Some 'child' variables such as village ownership and
462  // initial side units need to be swapped over as well
463  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
464  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
465  side_engines_[i_side]->set_side_children(tmp_side_children);
466 
467  // Then we revert the swap for fields that are unique to
468  // player control and the team they selected
469  int tmp_index = side_engines_[j_side]->index();
470  side_engines_[j_side]->set_index(side_engines_[i_side]->index());
471  side_engines_[i_side]->set_index(tmp_index);
472 
473  int tmp_team = side_engines_[j_side]->team();
474  side_engines_[j_side]->set_team(side_engines_[i_side]->team());
475  side_engines_[i_side]->set_team(tmp_team);
476  }
477  }
478 
479  // Make other clients not show the results of resolve_random().
480  config lock("stop_updates");
481  send_to_server(lock);
482 
483  update_and_send_diff(true);
484 
486 
487  // Build the gamestate object after updating the level.
489 
490  send_to_server(config("start_game"));
491 }
492 
494 {
495  DBG_MP << "starting a new game in commandline mode" << std::endl;
496 
497  typedef std::tuple<unsigned int, std::string> mp_option;
498 
499  randomness::mt_rng rng;
500 
501  unsigned num = 0;
502  for(side_engine_ptr side : side_engines_) {
503  num++;
504 
505  // Set the faction, if commandline option is given.
506  if(cmdline_opts.multiplayer_side) {
507  for(const mp_option& option : *cmdline_opts.multiplayer_side) {
508 
509  if(std::get<0>(option) == num) {
510  if(std::find_if(era_factions_.begin(), era_factions_.end(), [&option](const config* faction) { return (*faction)["id"] == std::get<1>(option); }) != era_factions_.end()) {
511  DBG_MP << "\tsetting side " << std::get<0>(option) << "\tfaction: " << std::get<1>(option) << std::endl;
512 
513  side->set_faction_commandline(std::get<1>(option));
514  }
515  else {
516  ERR_MP << "failed to set side " << std::get<0>(option) << " to faction " << std::get<1>(option) << std::endl;
517  }
518  }
519  }
520  }
521 
522  // Set the controller, if commandline option is given.
523  if(cmdline_opts.multiplayer_controller) {
524  for(const mp_option& option : *cmdline_opts.multiplayer_controller) {
525 
526  if(std::get<0>(option) == num) {
527  DBG_MP << "\tsetting side " << std::get<0>(option) <<
528  "\tfaction: " << std::get<1>(option) << std::endl;
529 
530  side->set_controller_commandline(std::get<1>(option));
531  }
532  }
533  }
534 
535  // Set AI algorithm to default for all sides,
536  // then override if commandline option was given.
537  std::string ai_algorithm = game_config.child("ais")["default_ai_algorithm"].str();
538  side->set_ai_algorithm(ai_algorithm);
539  if(cmdline_opts.multiplayer_algorithm) {
540  for(const mp_option& option : *cmdline_opts.multiplayer_algorithm) {
541 
542  if(std::get<0>(option) == num) {
543  DBG_MP << "\tsetting side " << std::get<0>(option) <<
544  "\tfaction: " << std::get<1>(option) << std::endl;
545 
546  side->set_ai_algorithm(std::get<1>(option));
547  }
548  }
549  }
550 
551  // Finally, resolve "random faction",
552  // "random gender" and "random message", if any remains unresolved.
553  side->resolve_random(rng);
554  } // end top-level loop
555 
556  update_and_send_diff(true);
557 
558  // Update sides with commandline parameters.
559  if(cmdline_opts.multiplayer_turns) {
560  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns <<
561  std::endl;
562  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
563  }
564 
565  for(config &side : scenario().child_range("side")) {
566  if(cmdline_opts.multiplayer_ai_config) {
567  for(const mp_option& option : *cmdline_opts.multiplayer_ai_config) {
568 
569  if(std::get<0>(option) == side["side"].to_unsigned()) {
570  DBG_MP << "\tsetting side " << side["side"] <<
571  "\tai_config: " << std::get<1>(option) << std::endl;
572 
573  side["ai_config"] = std::get<1>(option);
574  }
575  }
576  }
577 
578  // Having hard-coded values here is undesirable,
579  // but that's how it is done in the MP lobby
580  // part of the code also.
581  // Should be replaced by settings/constants in both places
582  if(cmdline_opts.multiplayer_ignore_map_settings) {
583  side["gold"] = 100;
584  side["income"] = 1;
585  }
586 
587  typedef std::tuple<unsigned int, std::string, std::string> mp_parameter;
588 
589  if(cmdline_opts.multiplayer_parm) {
590  for(const mp_parameter& parameter : *cmdline_opts.multiplayer_parm) {
591 
592  if(std::get<0>(parameter) == side["side"].to_unsigned()) {
593  DBG_MP << "\tsetting side " << side["side"] << " " <<
594  std::get<1>(parameter) << ": " << std::get<2>(parameter) << std::endl;
595 
596  side[std::get<1>(parameter)] = std::get<2>(parameter);
597  }
598  }
599  }
600  }
601 
603 
604  // Build the gamestate object after updating the level
606  send_to_server(config("start_game"));
607 }
608 
610 {
611  DBG_MP << "leaving the game" << std::endl;
612 
613  send_to_server(config("leave_game"));
614 }
615 
616 std::pair<bool, bool> connect_engine::process_network_data(const config& data)
617 {
618  std::pair<bool, bool> result(std::make_pair(false, true));
619 
620  if(data.child("leave_game")) {
621  result.first = true;
622  return result;
623  }
624 
625  // A side has been dropped.
626  if(const config& side_drop = data.child("side_drop")) {
627  unsigned side_index = side_drop["side_num"].to_int() - 1;
628 
629  if(side_index < side_engines_.size()) {
630  side_engine_ptr side_to_drop = side_engines_[side_index];
631 
632  // Remove user, whose side was dropped.
633  connected_users_rw().erase(side_to_drop->player_id());
635 
636  side_to_drop->reset();
637 
639 
640  return result;
641  }
642  }
643 
644  // A player is connecting to the game.
645  if(!data["side"].empty()) {
646  unsigned side_taken = data["side"].to_int() - 1;
647 
648  // Checks if the connecting user has a valid and unique name.
649  const std::string name = data["name"];
650  if(name.empty()) {
651  config response;
652  response["failed"] = true;
653  send_to_server(response);
654 
655  ERR_CF << "ERROR: No username provided with the side." << std::endl;
656 
657  return result;
658  }
659 
660  if(connected_users().find(name) != connected_users().end()) {
661  // TODO: Seems like a needless limitation
662  // to only allow one side per player.
663  if(find_user_side_index_by_id(name) != -1) {
664  config response;
665  response["failed"] = true;
666  response["message"] = "The nickname '" + name +
667  "' is already in use.";
668  send_to_server(response);
669 
670  return result;
671  } else {
672  connected_users_rw().erase(name);
674  config observer_quit;
675  observer_quit.add_child("observer_quit")["name"] = name;
676  send_to_server(observer_quit);
677  }
678  }
679 
680  // Assigns this user to a side.
681  if(side_taken < side_engines_.size()) {
682  if(!side_engines_[side_taken]->available_for_user(name)) {
683  // This side is already taken.
684  // Try to reassing the player to a different position.
685  side_taken = 0;
687  if(s->available_for_user()) {
688  break;
689  }
690 
691  side_taken++;
692  }
693 
694  if(side_taken >= side_engines_.size()) {
695  config response;
696  response["failed"] = true;
697  send_to_server(response);
698 
699  config res;
700  config& kick = res.add_child("kick");
701  kick["username"] = data["name"];
702  send_to_server(res);
703 
705 
706  ERR_CF << "ERROR: Couldn't assign a side to '" <<
707  name << "'\n";
708 
709  return result;
710  }
711  }
712 
713  LOG_CF << "client has taken a valid position\n";
714 
715  import_user(data, false, side_taken);
717 
718  // Wait for them to choose faction if allowed.
719  side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
720  LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes() << std::endl;
721  result.second = false;
722 
723  LOG_NW << "sent player data\n";
724  } else {
725  ERR_CF << "tried to take illegal side: " << side_taken << std::endl;
726 
727  config response;
728  response["failed"] = true;
729  send_to_server(response);
730  }
731  }
732 
733  if(const config& change_faction = data.child("change_faction")) {
734  int side_taken = find_user_side_index_by_id(change_faction["name"]);
735  if(side_taken != -1 || !first_scenario_) {
736  import_user(change_faction, false, side_taken);
738  }
739  }
740 
741  if(const config& observer = data.child("observer")) {
742  import_user(observer, true);
744  }
745 
746  if(const config& observer = data.child("observer_quit")) {
747  const std::string& observer_name = observer["name"];
748 
749  if(connected_users().find(observer_name) != connected_users().end()) {
750  connected_users_rw().erase(observer_name);
752 
753  // If the observer was assigned a side, we need to send an update to other
754  // players so they no longer see the observer assigned to that side.
755  if(find_user_side_index_by_id(observer_name) != -1) {
757  }
758  }
759  }
760 
761  return result;
762 }
763 
764 int connect_engine::find_user_side_index_by_id(const std::string& id) const
765 {
766  std::size_t i = 0;
767  for(side_engine_ptr side : side_engines_) {
768  if(side->player_id() == id) {
769  break;
770  }
771 
772  i++;
773  }
774 
775  if(i >= side_engines_.size()) {
776  return -1;
777  }
778 
779  return i;
780 }
781 
783 {
784  // Send initial information.
785  if(first_scenario_) {
787  "create_game", config {
788  "name", params_.name,
789  "password", params_.password,
790  },
791  });
793  } else {
794  send_to_server(config {"update_game", config()});
795  config next_level;
796  next_level.add_child("store_next_scenario", level_);
797  send_to_server(next_level);
798  }
799 }
800 
802 {
803  // Add information about reserved sides to the level config.
804  // N.B. This information is needed only for a host player.
805  std::map<std::string, std::string> side_users = utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
806  for(side_engine_ptr side : side_engines_) {
807  const std::string& save_id = side->save_id();
808  const std::string& player_id = side->player_id();
809  if(!save_id.empty() && !player_id.empty()) {
810  side_users[save_id] = player_id;
811  }
812  }
813 
814  level_.child("multiplayer")["side_users"] = utils::join_map(side_users);
815 }
816 
818 {
819  std::map<std::string, std::string> side_users = utils::map_split(level_.child("multiplayer")["side_users"]);
820  std::set<std::string> names;
821  for(side_engine_ptr side : side_engines_) {
822  const std::string& save_id = side->previous_save_id();
823  if(side_users.find(save_id) != side_users.end()) {
824  side->set_reserved_for(side_users[save_id]);
825 
826  if(side->controller() != CNTR_COMPUTER) {
827  side->set_controller(CNTR_RESERVED);
828  names.insert(side_users[save_id]);
829  }
830 
831  side->update_controller_options();
832  }
833  }
834 
835  //Do this in an extra loop to make sure we import each user only once.
836  for(const std::string& name : names)
837  {
838  if(connected_users().find(name) != connected_users().end() || !campaign_info_) {
839  import_user(name, false);
840  }
841  }
842 }
843 
845 {
846  for(side_engine_ptr side : side_engines_) {
847  side->update_controller_options();
848  }
849 }
850 
851 const std::set<std::string>& connect_engine::connected_users() const
852 {
853  if(campaign_info_) {
855  }
856 
857  static std::set<std::string> empty;
858  return empty;
859 }
860 
861 std::set<std::string>& connect_engine::connected_users_rw()
862 {
863  assert(campaign_info_);
865 }
866 
867 side_engine::side_engine(const config& cfg, connect_engine& parent_engine, const int index)
868  : cfg_(cfg)
869  , parent_(parent_engine)
870  , controller_(CNTR_NETWORK)
871  , current_controller_index_(0)
872  , controller_options_()
873  , allow_player_(cfg["allow_player"].to_bool(true))
874  , controller_lock_(cfg["controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
875  , index_(index)
876  , team_(0)
877  , color_(std::min(index, gamemap::MAX_PLAYERS - 1))
878  , gold_(cfg["gold"].to_int(100))
879  , income_(cfg["income"])
880  , reserved_for_(cfg["current_player"])
881  , player_id_()
882  , ai_algorithm_()
883  , chose_random_(cfg["chose_random"].to_bool(false))
884  , disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false))
885  , flg_(parent_.era_factions_, cfg_, parent_.force_lock_settings_, parent_.params_.use_map_settings, parent_.params_.saved_game == mp_game_settings::SAVED_GAME_MODE::MIDGAME)
886  , allow_changes_(parent_.params_.saved_game != mp_game_settings::SAVED_GAME_MODE::MIDGAME && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1))
887  , waiting_to_choose_faction_(allow_changes_)
888  , color_options_(game_config::default_colors)
889  , color_id_(color_options_[color_])
890 {
891  // Save default attributes that could be overwirtten by the faction, so that correct faction lists would be
892  // initialized by flg_manager when the new side config is sent over network.
893  cfg_.add_child("default_faction", config {
894  "type", cfg_["type"],
895  "gender", cfg_["gender"],
896  "faction", cfg_["faction"],
897  "recruit", cfg_["recruit"],
898  });
899 
900  if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
901  ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1 << std::endl;
902  }
903 
904  cfg_["side"] = index_ + 1;
905 
906  // Check if this side should give its control to some other side.
907  const std::size_t side_cntr_index = cfg_["controller"].to_int(-1) - 1;
908  if(side_cntr_index < parent_.side_engines().size()) {
909  // Remove this attribute to avoid locking side
910  // to non-existing controller type.
911  cfg_.remove_attribute("controller");
912 
913  cfg_["previous_save_id"] = parent_.side_engines()[side_cntr_index]->previous_save_id();
914  ERR_MP << "controller=<number> is deperecated\n";
915  }
916 
917  if(cfg_["controller"] != "human" && cfg_["controller"] != "ai" && cfg_["controller"] != "null") {
918  //an invalid controller type was specified. Remove it to prevent asertion failures later.
919  cfg_.remove_attribute("controller");
920  }
921 
923 
924  // Tweak the controllers.
925  if(parent_.state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::SCENARIO && cfg_["controller"].blank()) {
926  cfg_["controller"] = "ai";
927  }
928 
929  if(cfg_["controller"] == "null") {
931  } else if(cfg_["controller"] == "ai") {
933  } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
934  // Reserve a side for "current_player", unless the side
935  // is played by an AI.
937  } else if(allow_player_) {
939  } else {
940  // AI is the default.
942  }
943 
944  // Initialize team and color.
945  unsigned team_name_index = 0;
947  if(data.team_name == cfg["team_name"]) {
948  break;
949  }
950 
951  ++team_name_index;
952  }
953 
954  if(team_name_index >= parent_.team_data_.size()) {
955  assert(!parent_.team_data_.empty());
956  team_ = 0;
957  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;
958  } else {
959  team_ = team_name_index;
960  }
961 
962  // Check the value of the config's color= key.
963  const std::string given_color = team::get_side_color_id_from_config(cfg_);
964 
965  if(!given_color.empty()) {
966  // If it's valid, save the color...
967  color_id_ = given_color;
968 
969  // ... and find the appropriate index for it.
970  const auto iter = std::find(color_options_.begin(), color_options_.end(), color_id_);
971 
972  if(iter != color_options_.end()) {
973  color_ = std::distance(color_options_.begin(), iter);
974  } else {
975  color_options_.push_back(color_id_);
976  color_ = color_options_.size() - 1;
977  }
978  }
979 
980  // Initialize ai algorithm.
981  if(const config& ai = cfg.child("ai")) {
982  ai_algorithm_ = ai["ai_algorithm"].str();
983  }
984 }
985 
986 std::string side_engine::user_description() const
987 {
988  switch(controller_) {
989  case CNTR_LOCAL:
990  return N_("Anonymous player");
991  case CNTR_COMPUTER:
992  if(allow_player_) {
994  } else {
995  return N_("Computer Player");
996  }
997  default:
998  return "";
999  }
1000 }
1001 
1003 {
1004  config res = cfg_;
1005 
1006  // In case of 'shuffle sides' the side index in cfg_ might be wrong which will confuse the team constructor later.
1007  res["side"] = index_ + 1;
1008 
1009  // If the user is allowed to change type, faction, leader etc, then import their new values in the config.
1010  if(parent_.params_.saved_game != mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
1011  // Merge the faction data to res.
1012  config faction = flg_.current_faction();
1013  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " faction=" << faction["id"] << " recruit=" << faction["recruit"] << "\n";
1014  res["faction_name"] = faction["name"];
1015  res["faction"] = faction["id"];
1016  faction.remove_attributes("id", "name", "image", "gender", "type", "description");
1017  res.append(faction);
1018  }
1019 
1020  res["controller"] = controller_names[controller_];
1021 
1022  // The hosts receives the serversided controller tweaks after the start event, but
1023  // for mp sync it's very important that the controller types are correct
1024  // during the start/prestart event (otherwise random unit creation during prestart fails).
1025  res["is_local"] = player_id_ == preferences::login() || controller_ == CNTR_COMPUTER || controller_ == CNTR_LOCAL;
1026 
1027  // This function (new_config) is only meant to be called by the host's machine, which is why this check
1028  // works. It essentially certifies that whatever side has the player_id that matches the host's login
1029  // will be flagged. The reason we cannot check mp_campaign_info::is_host is because that flag is *always*
1030  // true on the host's machine, meaning this flag would be set to true for every side.
1031  res["is_host"] = player_id_ == preferences::login();
1032 
1033  std::string desc = user_description();
1034  if(!desc.empty()) {
1035  res["user_description"] = t_string(desc, "wesnoth");
1036 
1037  desc = VGETTEXT("$playername $side", {
1038  {"playername", _(desc.c_str())},
1039  {"side", res["side"].str()}
1040  });
1041  } else if(!player_id_.empty()) {
1042  desc = player_id_;
1043  }
1044 
1045  if(res["name"].str().empty() && !desc.empty()) {
1046  //TODO: maybe we should add this in to the leaders config instead of the side config?
1047  res["name"] = desc;
1048  }
1049 
1051  // If this is a saved game and "use_saved" (the default) was chosen for the
1052  // AI algorithm, we do nothing. Otherwise we add the chosen AI and if this
1053  // is a saved game, we also remove the old stages from the AI config.
1054  if(ai_algorithm_ != "use_saved") {
1055  if(parent_.params_.saved_game == mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
1056  for (config &ai_config : res.child_range("ai")) {
1057  ai_config.clear_children("stage");
1058  }
1059  }
1060  res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
1061  }
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 != mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
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 
1170 
1171  if(parent_.params_.use_map_settings && parent_.params_.saved_game != mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
1172  if(cfg_.has_attribute("name")){
1173  res["name"] = cfg_["name"];
1174  }
1175  if(cfg_.has_attribute("user_description") && controller_ == CNTR_COMPUTER){
1176  res["user_description"] = cfg_["user_description"];
1177  }
1178  }
1179 
1180  return res;
1181 }
1182 
1184 {
1185  if(!allow_player_) {
1186  // Sides without players are always ready.
1187  return true;
1188  }
1189 
1190  if((controller_ == CNTR_COMPUTER) ||
1191  (controller_ == CNTR_EMPTY) ||
1192  (controller_ == CNTR_LOCAL)) {
1193 
1194  return true;
1195  }
1196 
1197  if(available_for_user()) {
1198  // If controller_ == CNTR_NETWORK and player_id_.empty().
1199  return false;
1200  }
1201 
1202  if(controller_ == CNTR_NETWORK) {
1204  // The host is ready. A network player, who got a chance
1205  // to choose faction if allowed, is also ready.
1206  return true;
1207  }
1208  }
1209 
1210  return false;
1211 }
1212 
1213 bool side_engine::available_for_user(const std::string& name) const
1214 {
1215  if(controller_ == CNTR_NETWORK && player_id_.empty()) {
1216  // Side is free and waiting for user.
1217  return true;
1218  }
1219 
1220  if(controller_ == CNTR_RESERVED && name.empty()) {
1221  // Side is still available to someone.
1222  return true;
1223  }
1224 
1225  if(controller_ == CNTR_RESERVED && reserved_for_ == name) {
1226  // Side is available only for the player with specific name.
1227  return true;
1228  }
1229 
1230  return false;
1231 }
1232 
1233 void side_engine::resolve_random(randomness::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
1234 {
1235  if(parent_.params_.saved_game == mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
1236  return;
1237  }
1238 
1240 
1241  flg_.resolve_random(rng, avoid_faction_ids);
1242 
1243  LOG_MP << "side " << (index_ + 1) << ": faction=" <<
1244  (flg_.current_faction())["name"] << ", leader=" <<
1245  flg_.current_leader() << ", gender=" << flg_.current_gender() << "\n";
1246 }
1247 
1249 {
1250  player_id_.clear();
1253 
1254  if(parent_.params_.saved_game != mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
1256  }
1257 }
1258 
1259 void side_engine::place_user(const std::string& name)
1260 {
1261  config data;
1262  data["name"] = name;
1263 
1264  place_user(data);
1265 }
1266 
1267 void side_engine::place_user(const config& data, bool contains_selection)
1268 {
1269  player_id_ = data["name"].str();
1271 
1272  if(data["change_faction"].to_bool() && contains_selection) {
1273  // Network user's data carry information about chosen
1274  // faction, leader and genders.
1275  flg_.set_current_faction(data["faction"].str());
1276  flg_.set_current_leader(data["leader"].str());
1277  flg_.set_current_gender(data["gender"].str());
1278  }
1279 
1281 }
1282 
1284 {
1285  controller_options_.clear();
1286 
1287  // Default options.
1288  if(parent_.campaign_info_) {
1289  add_controller_option(CNTR_NETWORK, _("Network Player"), "human");
1290  }
1291 
1292  add_controller_option(CNTR_LOCAL, _("Local Player"), "human");
1293  add_controller_option(CNTR_COMPUTER, _("Computer Player"), "ai");
1294  add_controller_option(CNTR_EMPTY, _("Empty"), "null");
1295 
1296  if(!reserved_for_.empty()) {
1297  add_controller_option(CNTR_RESERVED, _("Reserved"), "human");
1298  }
1299 
1300  // Connected users.
1301  for(const std::string& user : parent_.connected_users()) {
1303  }
1304 
1306 }
1307 
1309 {
1310  int i = 0;
1311  for(const controller_option& option : controller_options_) {
1312  if(option.first == controller_) {
1314 
1315  if(player_id_.empty() || player_id_ == option.second) {
1316  // Stop searching if no user is assigned to a side
1317  // or the selected user is found.
1318  break;
1319  }
1320  }
1321 
1322  i++;
1323  }
1324 
1325  assert(current_controller_index_ < controller_options_.size());
1326 }
1327 
1328 bool side_engine::controller_changed(const int selection)
1329 {
1330  const ng::controller selected_cntr = controller_options_[selection].first;
1331 
1332  // Check if user was selected. If so assign a side to him/her.
1333  // If not, make sure that no user is assigned to this side.
1334  if(selected_cntr == parent_.default_controller_ && selection != 0) {
1335  player_id_ = controller_options_[selection].second;
1337  } else {
1338  player_id_.clear();
1339  }
1340 
1341  set_controller(selected_cntr);
1342 
1343  return true;
1344 }
1345 
1347 {
1349 
1351 }
1352 
1353 void side_engine::set_faction_commandline(const std::string& faction_name)
1354 {
1355  flg_.set_current_faction(faction_name);
1356 }
1357 
1359 {
1361 
1362  if(controller_name == "ai") {
1364  }
1365 
1366  if(controller_name == "null") {
1368  }
1369 
1370  player_id_.clear();
1371 }
1372 
1374  const std::string& name, const std::string& controller_value)
1375 {
1376  if(controller_lock_ && !cfg_["controller"].empty() &&
1377  cfg_["controller"] != controller_value) {
1378 
1379  return;
1380  }
1381 
1382  controller_options_.emplace_back(controller, name);
1383 }
1384 
1385 } // end namespace ng
bool ready_for_start() const
void send_data(const configr_of &request)
bool empty() const
Tests for an attribute that either was never set or was set to "".
#define LOG_MP
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:420
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
void save_reserved_sides_information()
#define ERR_MP
void set_waiting_to_choose_status(bool status)
bool is_random_faction()
boost::optional< std::string > multiplayer_turns
Non-empty if –turns was given on the command line. Dependent on –multiplayer.
void clear_children(T... keys)
Definition: config.hpp:509
std::string era()
Definition: game.cpp:698
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:287
const std::string & current_gender() const
Definition: flg_manager.hpp:70
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:979
void import_user(const std::string &name, const bool observer, int side_taken=-1)
std::vector< side_engine_ptr > side_engines_
Variant for storing WML attributes.
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
void add_controller_option(ng::controller controller, const std::string &name, const std::string &controller_value)
std::set< std::string > connected_players
players and observers
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
Definition: gettext.cpp:458
static lg::log_domain log_config("config")
config initial_level_config(saved_game &state)
child_itors child_range(config_key_type key)
Definition: config.cpp:362
void place_user(const std::string &name)
void remove_attributes(T... keys)
Definition: config.hpp:487
#define LOG_NW
void set_faction_commandline(const std::string &faction_name)
#define WRN_MP
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:62
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line. Do not use map settings.
void set_controller_commandline(const std::string &controller_name)
std::string ai_algorithm_
std::string player_id_
static void add_mod_ai_from_config(config::const_child_itors configs)
void set_current_leader(const unsigned index)
STL namespace.
#define ERR_CF
std::vector< team_data_pod > team_data_
void clear()
Definition: config.cpp:863
bool receive_data(config &result)
void set_current_gender(const unsigned index)
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
void level_to_gamestate(const config &level, saved_game &state)
const bool allow_changes_
-file sdl_utils.hpp
config new_config() const
void remove_attribute(config_key_type key)
Definition: config.cpp:235
std::vector< controller_option > controller_options_
boost::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_side
Non-empty if –side was given on the command line. Vector of pairs (side number, faction id)...
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid_faction_ids=std::vector< std::string >())
static void add_color_info(const config &v, bool build_defaults)
void update_and_send_diff(bool update_time_of_day=false)
bool available_for_user(const std::string &name="") const
config get_diff(const config &c) const
A function to get the differences between this object, and &#39;c&#39;, as another config object...
Definition: config.cpp:947
void send_to_server(const config &cfg) const
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
std::string user_description() const
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
A small explanation about what&#39;s going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:58
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
int find_user_side_index_by_id(const std::string &id) const
std::vector< side_engine_ptr > & side_engines()
boost::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_ai_config
Non-empty if –ai-config was given on the command line. Vector of pairs (side number, value). Dependent on –multiplayer.
const std::string & current_leader() const
Definition: flg_manager.hpp:68
config & add_child_at(config_key_type key, const config &val, unsigned index)
Definition: config.cpp:508
void set_controller(ng::controller controller)
const config * default_leader_cfg() const
Definition: flg_manager.hpp:73
Encapsulates the map of the game.
Definition: map.hpp:36
#define LOG_CF
std::vector< std::string > color_options_
void send_level_data() const
const bool first_scenario_
ng::controller controller_
std::vector< const config * > era_factions_
boost::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_algorithm
Non-empty if –algorithm was given on the command line. Vector of pairs (side number, value). Dependent on –multiplayer.
mp_campaign_info * campaign_info_
const ng::controller default_controller_
#define DBG_MP
bool receive_from_server(config &dst) const
std::string login()
std::vector< std::string > get_children_to_swap()
void set_current_faction(const unsigned index)
std::size_t i
Definition: function.cpp:933
const config & current_faction() const
Definition: flg_manager.hpp:66
static void add_era_ai_from_config(const config &game_config)
void update_controller_options()
Game configuration data as global variables.
Definition: build_info.cpp:49
static map_location::DIRECTION s
std::vector< std::string > names
Definition: build_info.cpp:56
std::string reserved_for_
static lg::log_domain log_network("network")
bool use_map_settings()
Definition: game.cpp:491
connect_engine(saved_game &state, const bool first_scenario, mp_campaign_info *campaign_info)
boost::optional< std::vector< std::tuple< unsigned int, std::string, std::string > > > multiplayer_parm
Non-empty if –parm was given on the command line. Vector of pairs (side number, parm name...
std::string to_serialized() const
Definition: tstring.hpp:150
#define N_(String)
Definition: gettext.hpp:99
void update_current_controller_index()
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
static const std::string controller_names[]
static int sort(lua_State *L)
Definition: ltablib.cpp:411
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
unsigned current_controller_index_
connect_engine & parent_
std::string observer
std::shared_ptr< side_engine > side_engine_ptr
config & add_child(config_key_type key)
Definition: config.cpp:476
std::pair< ng::controller, std::string > controller_option
const std::set< std::string > & connected_users() const
std::set< std::string > & connected_users_rw()
wesnothd_connection & connection
bool find(E event, F functor)
Tests whether an event handler is available.
game_classification & classification()
Definition: saved_game.hpp:55
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:237
Managing the AIs configuration - headers.
const bool controller_lock_
Standard logging facilities (interface).
bool controller_changed(const int selection)
void set_side_children(std::multimap< std::string, config > children)
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
const mp_game_settings & params_
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:453
std::string color_id_
std::vector< std::string > default_colors
ng::controller controller() const
bool can_start_game() const
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
void start_game_commandline(const commandline_options &cmdline_opts, const config &game_config)
const bool allow_player_
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
friend class side_engine
static const config & get_ai_config_for(const std::string &id)
Return the config for a specified ai.
boost::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_controller
Non-empty if –controller was given on the command line. Vector of pairs (side number, controller). Dependent on –multiplayer.
side_engine(const config &cfg, connect_engine &parent_engine, const int index)
std::multimap< std::string, config > get_side_children()
bool empty() const
Definition: config.cpp:884
bool sides_available() const
static std::string controller_name(const team &t)
Definition: game_stats.cpp:72
std::pair< bool, bool > process_network_data(const config &data)
std::string str(const std::string &fallback="") const
void update_side_controller_options()