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