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