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