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