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