The Battle for Wesnoth  1.19.13+dev
connect_engine.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2025
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"
24 #include "gettext.hpp"
25 #include "log.hpp"
26 #include "map/map.hpp"
27 #include "mt_rng.hpp"
28 #include "side_controller.hpp"
29 #include "team.hpp"
30 
31 #include <array>
32 #include <cstdlib>
33 
34 static lg::log_domain log_config("config");
35 #define LOG_CF LOG_STREAM(info, log_config)
36 #define ERR_CF LOG_STREAM(err, log_config)
37 
38 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
39 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
40 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
41 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
42 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
43 
44 static lg::log_domain log_network("network");
45 #define LOG_NW LOG_STREAM(info, log_network)
46 
47 namespace
48 {
49 const std::array controller_names {
50  side_controller::human,
51  side_controller::human,
52  side_controller::ai,
53  side_controller::none,
54  side_controller::reserved
55 };
56 
57 const std::set<std::string> children_to_swap {
58  "village",
59  "unit",
60  "ai"
61 };
62 
63 } // end anon namespace
64 
65 namespace ng {
66 
67 connect_engine::connect_engine(saved_game& state, const bool first_scenario, mp_game_metadata* metadata)
68  : level_(mp::initial_level_config(state))
69  , state_(state)
70  , params_(state.mp_settings())
71  , default_controller_(metadata ? CNTR_NETWORK : CNTR_LOCAL)
72  , mp_metadata_(metadata)
73  , first_scenario_(first_scenario)
74  , force_lock_settings_()
75  , side_engines_()
76  , era_factions_()
77  , team_data_()
78  , era_info_(level_.mandatory_child("era"))
79 {
80  const config& era_config = level_.mandatory_child("era");
81 
82  const bool is_mp = state_.classification().is_normal_mp_game();
83  force_lock_settings_ = (state.mp_settings().saved_game != saved_game_mode::type::midgame) && scenario()["force_lock_settings"].to_bool(!is_mp);
84 
85  // Original level sides.
86  config::child_itors sides = scenario().child_range("side");
87 
88  // AI algorithms.
91 
92  // Set the team name lists and modify the original level sides if necessary.
93  std::vector<std::string> original_team_names;
94  std::string team_prefix(_("Team") + " ");
95 
96  int side_count = 1;
97  for(config& side : sides) {
98  const std::string side_str = std::to_string(side_count);
99 
100  config::attribute_value& team_name = side["team_name"];
101  config::attribute_value& user_team_name = side["user_team_name"];
102 
103  // Revert to default values if appropriate.
104  if(team_name.empty()) {
105  team_name = side_str;
106  }
107 
108  if(params_.use_map_settings && user_team_name.empty()) {
109  user_team_name = team_name;
110  }
111 
112  bool add_team = true;
114  // Only add a team if it is not found.
115  if(std::any_of(team_data_.begin(), team_data_.end(), [&team_name](const team_data_pod& data){
116  return data.team_name == team_name.str();
117  })) {
118  add_team = false;
119  }
120  } else {
121  // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
122  auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
123 
124  // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
125  // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
126  if(name_itor == original_team_names.end()) {
127  original_team_names.push_back(team_name);
128 
129  team_name = "Team " + std::to_string(original_team_names.size());
130  } else {
131  team_name = "Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
132  }
133 
134  user_team_name = team_prefix + side_str;
135  }
136 
137  // Write the serialized translatable team name back to the config. Without this,
138  // the string can appear all messed up after leaving and rejoining a game (see
139  // issue #2040. This affected the mp_join_game dialog). I don't know why that issue
140  // didn't appear the first time you join a game, but whatever.
141  //
142  // The difference between that dialog and mp_staging is that the latter has access
143  // to connect_engine object, meaning it has access to serialized versions of the
144  // user_team_name string stored in the team_data_ vector. mp_join_game handled the
145  // raw side config instead. Again, I don't know why issues only cropped up on a
146  // subsequent join and not the first, but it doesn't really matter.
147  //
148  // This ensures both dialogs have access to the serialized form of the utn string.
149  // As for why this needs to be done in the first place, apparently the simple_wml
150  // format the server (wesnothd) uses doesn't preserve translatable strings (see
151  // issue #342).
152  //
153  // --vultraz, 2018-02-06
154  user_team_name = user_team_name.t_str().to_serialized();
155 
156  if(add_team) {
158  data.team_name = params_.use_map_settings ? team_name : "Team " + side_str;
159  data.user_team_name = user_team_name.str();
160  data.is_player_team = side["allow_player"].to_bool(true);
161 
162  team_data_.push_back(data);
163  }
164 
165  ++side_count;
166  }
167 
168  // Selected era's factions.
169  for(const config& ms : era_config.child_range("multiplayer_side")) {
170  era_factions_.push_back(&ms);
171  }
172 
174 
175  // Create side engines.
176  int index = 0;
177  for(const config& s : sides) {
178  side_engines_.emplace_back(new side_engine(s, *this, index++));
179  }
180 
181  if(first_scenario_) {
182  // Add host to the connected users list.
183  import_user(prefs::get().login(), false);
184  } else {
185  // Add host but don't assign a side to him.
186  import_user(prefs::get().login(), true);
187 
188  // Load reserved players information into the sides.
190  }
191 
192  // Only updates the sides in the level.
193  update_level();
194 
195  // If we are connected, send data to the connected host.
196  send_level_data();
197 }
198 
199 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
200 {
202  user_data["name"] = name;
203  import_user(user_data, observer, side_taken);
204 }
205 
206 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
207 {
208  const std::string& username = data["name"];
209  assert(!username.empty());
210  if(mp_metadata_) {
211  connected_users_rw().insert(username);
212  }
213 
215 
216  if(observer) {
217  return;
218  }
219 
220  bool side_assigned = false;
221  if(side_taken >= 0) {
222  side_engines_[side_taken]->place_user(data, true);
223  side_assigned = true;
224  }
225 
226  // Check if user has a side(s) reserved for him.
227  for(const side_engine_ptr& side : side_engines_) {
228  if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
229  side->place_user(data);
230 
231  side_assigned = true;
232  }
233  }
234 
235  // If no sides were assigned for a user,
236  // take a first available side.
237  if(side_taken < 0 && !side_assigned) {
238  for(const side_engine_ptr& side : side_engines_) {
239  if(side->available_for_user(username) ||
240  side->controller() == CNTR_LOCAL) {
241  side->place_user(data);
242 
243  side_assigned = true;
244  break;
245  }
246  }
247  }
248 
249  // Check if user has taken any sides, which should get control
250  // over any other sides.
251  for(const side_engine_ptr& user_side : side_engines_) {
252  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
253  for(const side_engine_ptr& side : side_engines_){
254  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
255  side->place_user(data);
256  }
257  }
258  }
259  }
260 }
261 
263 {
264  for(const side_engine_ptr& side : side_engines_) {
265  if(side->available_for_user()) {
266  return true;
267  }
268  }
269 
270  return false;
271 }
272 
274 {
275  DBG_MP << "updating level";
276 
277  scenario().clear_children("side");
278 
279  for(const side_engine_ptr& side : side_engines_) {
280  scenario().add_child("side", side->new_config());
281  }
282 }
283 
285 {
286  config old_level = level_;
287  update_level();
288 
289  if(config diff = level_.get_diff(old_level); !diff.empty()) {
290  mp::send_to_server(config{"scenario_diff", std::move(diff)});
291  }
292 }
293 
295 {
296  if(side_engines_.empty()) {
297  return true;
298  }
299 
300  // First check if all sides are ready to start the game.
301  for(const side_engine_ptr& side : side_engines_) {
302  if(!side->ready_for_start()) {
303  const int side_num = side->index() + 1;
304  DBG_MP << "not all sides are ready, side " <<
305  side_num << " not ready";
306 
307  return false;
308  }
309  }
310 
311  DBG_MP << "all sides are ready";
312 
313  /*
314  * If at least one human player is slotted with a player/ai we're allowed
315  * to start. Before used a more advanced test but it seems people are
316  * creative in what is used in multiplayer [1] so use a simpler test now.
317  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
318  */
319  for(const side_engine_ptr& side : side_engines_) {
320  if(side->controller() != CNTR_EMPTY && side->allow_player()) {
321  return true;
322  }
323  }
324 
325  return false;
326 }
327 
328 std::multimap<std::string, config> side_engine::get_side_children()
329 {
330  std::multimap<std::string, config> children;
331 
332  for(const std::string& to_swap : children_to_swap) {
333  for(const config& child : cfg_.child_range(to_swap)) {
334  children.emplace(to_swap, child);
335  }
336  }
337 
338  return children;
339 }
340 
341 void side_engine::set_side_children(const std::multimap<std::string, config>& children)
342 {
343  for(const std::string& children_to_remove : children_to_swap) {
344  cfg_.clear_children(children_to_remove);
345  }
346 
347  for(std::pair<std::string, config> child_map : children) {
348  cfg_.add_child(child_map.first, child_map.second);
349  }
350 }
351 
353 {
354  DBG_MP << "starting a new game";
355 
356  // Resolves the "random faction", "random gender" and "random message"
357  // Must be done before shuffle sides, or some cases will cause errors
358  randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
359  for(const side_engine_ptr& side : side_engines_) {
360  std::vector<std::string> avoid_faction_ids;
361 
362  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
363  if(params_.mode != random_faction_mode::type::independent) {
364  for(const side_engine_ptr& side2 : side_engines_) {
365  if(!side2->flg().is_random_faction()) {
366  switch(params_.mode) {
367  case random_faction_mode::type::no_mirror:
368  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
369  break;
370  case random_faction_mode::type::no_ally_mirror:
371  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"
372  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
373  }
374  break;
375  default:
376  break; // assert(false);
377  }
378  }
379  }
380  }
381  side->resolve_random(rng, avoid_faction_ids);
382  }
383 
384  // Shuffle sides (check settings and if it is a re-loaded game).
385  // Must be done after resolve_random() or shuffle sides, or they won't work.
386  if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.has_child("snapshot") && level_.mandatory_child("snapshot").has_child("side"))) {
387 
388  // Only playable sides should be shuffled.
389  std::vector<int> playable_sides;
390  for(const side_engine_ptr& side : side_engines_) {
391  if(side->allow_player() && side->allow_shuffle()) {
392  playable_sides.push_back(side->index());
393  }
394  }
395 
396  // Fisher-Yates shuffle.
397  for(int i = playable_sides.size(); i > 1; i--) {
398  const int j_side = playable_sides[rng.get_next_random() % i];
399  const int i_side = playable_sides[i - 1];
400 
401  if(i_side == j_side) continue; //nothing to swap
402 
403  // First we swap everything about a side with another
404  std::swap(side_engines_[j_side], side_engines_[i_side]);
405 
406  // Some 'child' variables such as village ownership and initial side units need to be swapped over as well
407  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
408  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
409  side_engines_[i_side]->set_side_children(tmp_side_children);
410 
411  // Then we revert the swap for fields that are unique to player control and the team they selected
412  std::swap(side_engines_[j_side]->index_, side_engines_[i_side]->index_);
413  std::swap(side_engines_[j_side]->team_, side_engines_[i_side]->team_);
414  }
415  }
416 
417  // Make other clients not show the results of resolve_random().
418  mp::send_to_server(config{"stop_updates"});
419 
421 
423 
424  // Build the gamestate object after updating the level.
426 
427  mp::send_to_server(config{"start_game"});
428 }
429 
431 {
432  DBG_MP << "starting a new game in commandline mode";
433 
434  randomness::mt_rng rng;
435 
436  unsigned num = 0;
437  for(const side_engine_ptr& side : side_engines_) {
438  num++;
439 
440  // Set the faction, if commandline option is given.
441  if(cmdline_opts.multiplayer_side) {
442  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_side) {
443  if(side_num == num) {
444  if(std::find_if(era_factions_.begin(), era_factions_.end(),
445  [fid = faction_id](const config* faction) { return (*faction)["id"] == fid; })
446  != era_factions_.end()
447  ) {
448  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
449  side->set_faction_commandline(faction_id);
450  } else {
451  ERR_MP << "failed to set side " << side_num << " to faction " << faction_id;
452  }
453  }
454  }
455  }
456 
457  // Set the controller, if commandline option is given.
458  if(cmdline_opts.multiplayer_controller) {
459  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_controller) {
460  if(side_num == num) {
461  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
462  side->set_controller_commandline(faction_id);
463  }
464  }
465  }
466 
467  // Set AI algorithm to default for all sides,
468  // then override if commandline option was given.
469  std::string ai_algorithm = game_config.mandatory_child("ais")["default_ai_algorithm"].str();
470  side->set_ai_algorithm(ai_algorithm);
471 
472  if(cmdline_opts.multiplayer_algorithm) {
473  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_algorithm) {
474  if(side_num == num) {
475  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
476  side->set_ai_algorithm(faction_id);
477  }
478  }
479  }
480 
481  // Finally, resolve "random faction",
482  // "random gender" and "random message", if any remains unresolved.
483  side->resolve_random(rng);
484  } // end top-level loop
485 
487 
488  // Update sides with commandline parameters.
489  if(cmdline_opts.multiplayer_turns) {
490  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns;
491  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
492  }
493 
494  for(config& side : scenario().child_range("side")) {
495  if(cmdline_opts.multiplayer_ai_config) {
496  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_ai_config) {
497  if(side_num == side["side"].to_unsigned()) {
498  DBG_MP << "\tsetting side " << side["side"] << "\tai_config: " << faction_id;
499  side["ai_config"] = faction_id;
500  }
501  }
502  }
503 
504  // Having hard-coded values here is undesirable,
505  // but that's how it is done in the MP lobby
506  // part of the code also.
507  // Should be replaced by settings/constants in both places
508  if(cmdline_opts.multiplayer_ignore_map_settings) {
509  side["gold"] = 100;
510  side["income"] = 1;
511  }
512 
513  if(cmdline_opts.multiplayer_parm) {
514  for(const auto& [side_num, pname, pvalue] : *cmdline_opts.multiplayer_parm) {
515  if(side_num == side["side"].to_unsigned()) {
516  DBG_MP << "\tsetting side " << side["side"] << " " << pname << ": " << pvalue;
517  side[pname] = pvalue;
518  }
519  }
520  }
521  }
522 
524 
525  // Build the gamestate object after updating the level
527  mp::send_to_server(config{"start_game"});
528 }
529 
531 {
532  DBG_MP << "leaving the game";
533  mp::send_to_server(config{"leave_game"});
534 }
535 
537 {
538  if(data.has_child("leave_game")) {
539  return true;
540  }
541 
542  // A side has been dropped.
543  if(auto side_drop = data.optional_child("side_drop")) {
544  unsigned side_index = side_drop["side_num"].to_int() - 1;
545 
546  if(side_index < side_engines_.size()) {
547  const side_engine_ptr& side_to_drop = side_engines_[side_index];
548 
549  // Remove user, whose side was dropped.
550  connected_users_rw().erase(side_to_drop->player_id());
552 
553  side_to_drop->reset();
554 
556  return false;
557  }
558  }
559 
560  // A player is connecting to the game.
561  if(!data["side"].empty()) {
562  unsigned side_taken = data["side"].to_int() - 1;
563 
564  // Checks if the connecting user has a valid and unique name.
565  const std::string name = data["name"];
566  if(name.empty()) {
567  ERR_CF << "ERROR: No username provided with the side.";
568  return false;
569  }
570 
571  if(connected_users().find(name) != connected_users().end()) {
572  // TODO: Seems like a needless limitation
573  // to only allow one side per player.
574  if(find_user_side_index_by_id(name) != -1) {
575  return false;
576  } else {
577  connected_users_rw().erase(name);
579  }
580  }
581 
582  // Assigns this user to a side.
583  if(side_taken < side_engines_.size()) {
584  if(!side_engines_[side_taken]->available_for_user(name)) {
585  // This side is already taken.
586  // Try to reassing the player to a different position.
587  side_taken = 0;
588  for(const side_engine_ptr& s : side_engines_) {
589  if(s->available_for_user()) {
590  break;
591  }
592 
593  side_taken++;
594  }
595 
596  if(side_taken >= side_engines_.size()) {
597  mp::send_to_server(config{"kick", config{"username", data["name"]}});
598 
600 
601  ERR_CF << "ERROR: Couldn't assign a side to '" << name << "'";
602  return false;
603  }
604  }
605 
606  LOG_CF << "client has taken a valid position";
607 
608  import_user(data, false, side_taken);
610 
611  // Wait for them to choose faction if allowed.
612  side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
613  LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes();
614  LOG_NW << "sent player data";
615  } else {
616  ERR_CF << "tried to take illegal side: " << side_taken;
617  }
618  }
619 
620  if(auto change_faction = data.optional_child("change_faction")) {
621  int side_taken = find_user_side_index_by_id(change_faction["name"]);
622  if(side_taken != -1 || !first_scenario_) {
623  import_user(*change_faction, false, side_taken);
625  }
626  }
627 
628  if(auto observer = data.optional_child("observer")) {
629  import_user(*observer, true);
631  }
632 
633  if(auto observer = data.optional_child("observer_quit")) {
634  const std::string& observer_name = observer["name"];
635 
636  if(connected_users().find(observer_name) != connected_users().end()) {
637  connected_users_rw().erase(observer_name);
639 
640  // If the observer was assigned a side, we need to send an update to other
641  // players so they no longer see the observer assigned to that side.
642  if(find_user_side_index_by_id(observer_name) != -1) {
644  }
645  }
646  }
647 
648  return false;
649 }
650 
651 int connect_engine::find_user_side_index_by_id(const std::string& id) const
652 {
653  std::size_t i = 0;
654  for(const side_engine_ptr& side : side_engines_) {
655  if(side->player_id() == id) {
656  break;
657  }
658 
659  i++;
660  }
661 
662  if(i >= side_engines_.size()) {
663  return -1;
664  }
665 
666  return i;
667 }
668 
670 {
671  // Send initial information.
672  if(first_scenario_) {
674  "create_game", config {
675  "name", params_.name,
676  "password", params_.password,
677  "ignored", prefs::get().get_ignored_delim(),
678  // all queue games count as auto hosted, but not all auto hosted games are queue games
679  "auto_hosted", mp_metadata_ ? mp_metadata_->queue_type != queue_type::normal : false,
680  "queue_type", mp_metadata_ ? mp_metadata_->queue_type : queue_type::normal,
681  "queue_id", mp_metadata_ ? mp_metadata_->queue_id : 0,
682  },
683  });
685  } else {
686  mp::send_to_server(config{"store_next_scenario", level_});
687  }
688 }
689 
691 {
692  // Add information about reserved sides to the level config.
693  // N.B. This information is needed only for a host player.
694  std::map<std::string, std::string> side_users = utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
695  for(const side_engine_ptr& side : side_engines_) {
696  const std::string& save_id = side->save_id();
697  const std::string& player_id = side->player_id();
698  if(!save_id.empty() && !player_id.empty()) {
699  side_users[save_id] = player_id;
700  }
701  }
702 
703  level_.mandatory_child("multiplayer")["side_users"] = utils::join_map(side_users);
704 }
705 
707 {
708  std::map<std::string, std::string> side_users = utils::map_split(level_.mandatory_child("multiplayer")["side_users"]);
709  std::set<std::string> names;
710  for(const side_engine_ptr& side : side_engines_) {
711  const std::string& save_id = side->previous_save_id();
712  if(side_users.find(save_id) != side_users.end()) {
713  side->set_reserved_for(side_users[save_id]);
714 
715  if(side->controller() != CNTR_COMPUTER) {
716  side->set_controller(CNTR_RESERVED);
717  names.insert(side_users[save_id]);
718  }
719 
720  side->update_controller_options();
721  }
722  }
723 
724  //Do this in an extra loop to make sure we import each user only once.
725  for(const std::string& name : names)
726  {
727  if(connected_users().find(name) != connected_users().end() || !mp_metadata_) {
728  import_user(name, false);
729  }
730  }
731 }
732 
734 {
735  for(const side_engine_ptr& side : side_engines_) {
736  side->update_controller_options();
737  }
738 }
739 
740 const std::set<std::string>& connect_engine::connected_users() const
741 {
742  if(mp_metadata_) {
744  }
745 
746  static std::set<std::string> empty;
747  return empty;
748 }
749 
750 std::set<std::string>& connect_engine::connected_users_rw()
751 {
752  assert(mp_metadata_);
754 }
755 
756 side_engine::side_engine(const config& cfg, connect_engine& parent_engine, const int index)
757  : cfg_(cfg)
758  , parent_(parent_engine)
759  , controller_(CNTR_NETWORK)
760  , current_controller_index_(0)
761  , controller_options_()
762  , allow_player_(cfg["allow_player"].to_bool(true))
763  , controller_lock_(cfg["controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
764  , index_(index)
765  , team_(0)
766  , color_(std::min(index, gamemap::MAX_PLAYERS - 1))
767  , gold_(cfg["gold"].to_int(100))
768  , income_(cfg["income"].to_int())
769  , reserved_for_(cfg["current_player"])
770  , player_id_()
771  , ai_algorithm_()
772  , chose_random_(cfg["chose_random"].to_bool(false))
773  , disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false))
774  , flg_(parent_.era_info_, parent_.era_factions_, cfg_, parent_.force_lock_settings_, parent_.params_.use_map_settings, parent_.params_.saved_game == saved_game_mode::type::midgame)
775  , allow_changes_(parent_.params_.saved_game != saved_game_mode::type::midgame && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1))
776  , waiting_to_choose_faction_(allow_changes_)
777  , color_options_(game_config::default_colors)
778  //TODO: what should we do if color_ is out of range?
779  , color_id_(color_options_.at(color_))
780 {
781  // Save default attributes that could be overwritten by the faction, so that correct faction lists would be
782  // initialized by flg_manager when the new side config is sent over network.
783  cfg_.clear_children("default_faction");
784  cfg_.add_child("default_faction", config {
785  "faction", cfg_["faction"],
786  "recruit", cfg_["recruit"],
787  });
788  if(auto p_cfg = cfg_.optional_child("leader")) {
789  cfg_.mandatory_child("default_faction").add_child("leader", config {
790  "type", (p_cfg)["type"],
791  "gender", (p_cfg)["gender"],
792  });
793  }
794 
795  if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
796  ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1;
797  }
798 
799  cfg_["side"] = index_ + 1;
800 
801  if(cfg_["controller"] != side_controller::human && cfg_["controller"] != side_controller::ai && cfg_["controller"] != side_controller::none) {
802  //an invalid controller type was specified. Remove it to prevent asertion failures later.
803  cfg_.remove_attribute("controller");
804  }
805 
807 
808  // Tweak the controllers.
809  if(parent_.state_.classification().is_scenario() && cfg_["controller"].blank()) {
810  cfg_["controller"] = side_controller::ai;
811  }
812 
813  if(cfg_["controller"] == side_controller::none) {
815  } else if(cfg_["controller"] == side_controller::ai) {
817  } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
818  // Reserve a side for "current_player", unless the side
819  // is played by an AI.
821  } else if(allow_player_) {
823  } else {
824  // AI is the default.
826  }
827 
828  // Initialize team and color.
829  unsigned team_name_index = 0;
831  if(data.team_name == cfg["team_name"]) {
832  break;
833  }
834 
835  ++team_name_index;
836  }
837 
838  if(team_name_index >= parent_.team_data_.size()) {
839  assert(!parent_.team_data_.empty());
840  team_ = 0;
841  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!";
842  } else {
843  team_ = team_name_index;
844  }
845 
846  // Check the value of the config's color= key.
847  const std::string given_color = team::get_side_color_id_from_config(cfg_);
848 
849  if(!given_color.empty()) {
850  // If it's valid, save the color...
851  color_id_ = given_color;
852 
853  // ... and find the appropriate index for it.
854  const auto iter = std::find(color_options_.begin(), color_options_.end(), color_id_);
855 
856  if(iter != color_options_.end()) {
857  color_ = std::distance(color_options_.begin(), iter);
858  } else {
859  color_options_.push_back(color_id_);
860  color_ = color_options_.size() - 1;
861  }
862  }
863 
864  // Initialize ai algorithm.
865  if(auto ai = cfg.optional_child("ai")) {
866  ai_algorithm_ = ai["ai_algorithm"].str();
867  }
868 }
869 
870 std::string side_engine::user_description() const
871 {
872  switch(controller_) {
873  case CNTR_LOCAL:
874  return N_("Anonymous player");
875  case CNTR_COMPUTER:
876  if(allow_player_) {
878  } else {
879  return N_("Computer Player");
880  }
881  default:
882  return "";
883  }
884 }
885 
887 {
888  config res = cfg_;
889 
890  // In case of 'shuffle sides' the side index in cfg_ might be wrong which will confuse the team constructor later.
891  res["side"] = index_ + 1;
892 
893  // If the user is allowed to change type, faction, leader etc, then import their new values in the config.
894  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
895  // Merge the faction data to res.
896  config faction = flg_.current_faction();
897  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " faction=" << faction["id"] << " recruit=" << faction["recruit"];
898  res["faction_name"] = faction["name"];
899  res["faction"] = faction["id"];
900  faction.remove_attributes("id", "name", "image", "gender", "type", "description");
901  res.append(faction);
902  }
903 
904  res["controller"] = controller_names[controller_];
905 
906  // The hosts receives the serversided controller tweaks after the start event, but
907  // for mp sync it's very important that the controller types are correct
908  // during the start/prestart event (otherwise random unit creation during prestart fails).
909  res["is_local"] = player_id_ == prefs::get().login() || controller_ == CNTR_COMPUTER || controller_ == CNTR_LOCAL;
910 
911  // This function (new_config) is only meant to be called by the host's machine, which is why this check
912  // works. It essentially certifies that whatever side has the player_id that matches the host's login
913  // will be flagged. The reason we cannot check mp_game_metadata::is_host is because that flag is *always*
914  // true on the host's machine, meaning this flag would be set to true for every side.
915  res["is_host"] = player_id_ == prefs::get().login();
916 
917  std::string desc = user_description();
918  if(!desc.empty()) {
919  res["user_description"] = t_string(desc, "wesnoth");
920 
921  desc = VGETTEXT("$playername $side", {
922  {"playername", _(desc.c_str())},
923  {"side", res["side"].str()}
924  });
925  } else if(!player_id_.empty()) {
926  desc = player_id_;
927  }
928 
929  if(res["name"].str().empty() && !desc.empty()) {
930  //TODO: maybe we should add this in to the leaders config instead of the side config?
931  res["name"] = desc;
932  }
933 
935  // If this is a saved game and "use_saved" (the default) was chosen for the
936  // AI algorithm, we do nothing. Otherwise we add the chosen AI and if this
937  // is a saved game, we also remove the old stages from the AI config.
938  if(ai_algorithm_ != "use_saved") {
939  if(parent_.params_.saved_game == saved_game_mode::type::midgame) {
940  for (config &ai_config : res.child_range("ai")) {
941  ai_config.clear_children("stage");
942  }
943  }
944  res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
945  }
946  }
947 
948  // A side's "current_player" is the player which has currently taken that side or the one for which it is reserved.
949  // The "player_id" is the id of the client who controls that side. It's always the host for Local and AI players and
950  // always empty for free/reserved sides or null controlled sides. You can use !res["player_id"].empty() to check
951  // whether a side is already taken.
952  assert(!prefs::get().login().empty());
953  if(controller_ == CNTR_LOCAL) {
954  res["player_id"] = prefs::get().login();
955  res["current_player"] = prefs::get().login();
956  } else if(controller_ == CNTR_RESERVED) {
957  res.remove_attribute("player_id");
958  res["current_player"] = reserved_for_;
959  } else if(controller_ == CNTR_COMPUTER) {
960  // TODO: what is the content of player_id_ here ?
961  res["current_player"] = desc;
962  res["player_id"] = prefs::get().login();
963  } else if(!player_id_.empty()) {
964  res["player_id"] = player_id_;
965  res["current_player"] = player_id_;
966  }
967 
968  res["allow_changes"] = allow_changes_;
969  res["chose_random"] = chose_random_;
970 
971  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
972 
973  if(!flg_.leader_lock()) {
974  if(controller_ != CNTR_EMPTY) {
975  auto& leader = res.child_or_add("leader");
976  leader["type"] = flg_.current_leader();
977  leader["gender"] = flg_.current_gender();
978  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " type=" << leader["type"]
979  << " gender=" << leader["gender"];
980  } else if(!controller_lock_) {
981  //if controller_lock_ == false and controller_ == CNTR_EMPTY, this means the user disalbles this side, so remove it's leader.
982  res.remove_children("leader");
983  }
984  }
985 
986  const std::string& new_team_name = parent_.team_data_[team_].team_name;
987 
988  if(res["user_team_name"].empty() || !parent_.params_.use_map_settings || res["team_name"] != new_team_name) {
989  res["team_name"] = new_team_name;
990  res["user_team_name"] = parent_.team_data_[team_].user_team_name;
991  }
992 
993  res["allow_player"] = allow_player_;
994  res["color"] = color_id_;
995  res["gold"] = gold_;
996  res["income"] = income_;
997  }
998 
999 
1000  if(parent_.params_.use_map_settings && parent_.params_.saved_game != saved_game_mode::type::midgame) {
1001  if(cfg_.has_attribute("name")){
1002  res["name"] = cfg_["name"];
1003  }
1004  if(cfg_.has_attribute("user_description") && controller_ == CNTR_COMPUTER){
1005  res["user_description"] = cfg_["user_description"];
1006  }
1007  }
1008 
1009  return res;
1010 }
1011 
1013 {
1014  if(!allow_player_) {
1015  // Sides without players are always ready.
1016  return true;
1017  }
1018 
1019  if((controller_ == CNTR_COMPUTER) ||
1020  (controller_ == CNTR_EMPTY) ||
1021  (controller_ == CNTR_LOCAL)) {
1022 
1023  return true;
1024  }
1025 
1026  if(available_for_user()) {
1027  // If controller_ == CNTR_NETWORK and player_id_.empty().
1028  return false;
1029  }
1030 
1031  if(controller_ == CNTR_NETWORK) {
1033  // The host is ready. A network player, who got a chance
1034  // to choose faction if allowed, is also ready.
1035  return true;
1036  }
1037  }
1038 
1039  return false;
1040 }
1041 
1042 bool side_engine::available_for_user(const std::string& name) const
1043 {
1044  if(controller_ == CNTR_NETWORK && player_id_.empty()) {
1045  // Side is free and waiting for user.
1046  return true;
1047  }
1048 
1049  if(controller_ == CNTR_RESERVED && name.empty()) {
1050  // Side is still available to someone.
1051  return true;
1052  }
1053 
1054  if(controller_ == CNTR_RESERVED && reserved_for_ == name) {
1055  // Side is available only for the player with specific name.
1056  return true;
1057  }
1058 
1059  return false;
1060 }
1061 
1062 void side_engine::resolve_random(randomness::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
1063 {
1064  if(parent_.params_.saved_game == saved_game_mode::type::midgame) {
1065  return;
1066  }
1067 
1069 
1070  flg_.resolve_random(rng, avoid_faction_ids);
1071 
1072  LOG_MP << "side " << (index_ + 1) << ": faction=" <<
1073  (flg_.current_faction())["name"] << ", leader=" <<
1074  flg_.current_leader() << ", gender=" << flg_.current_gender();
1075 }
1076 
1078 {
1079  player_id_.clear();
1082 
1083  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
1085  }
1086 }
1087 
1088 void side_engine::place_user(const std::string& name)
1089 {
1090  config data;
1091  data["name"] = name;
1092 
1093  place_user(data);
1094 }
1095 
1096 void side_engine::place_user(const config& data, bool contains_selection)
1097 {
1098  player_id_ = data["name"].str();
1100 
1101  if(data["change_faction"].to_bool() && contains_selection) {
1102  // Network user's data carry information about chosen
1103  // faction, leader and genders.
1104  flg_.set_current_faction(data["faction"].str());
1105  flg_.set_current_leader(data["leader"].str());
1106  flg_.set_current_gender(data["gender"].str());
1107  }
1108 
1110 }
1111 
1113 {
1114  controller_options_.clear();
1115 
1116  // Default options.
1117  if(parent_.mp_metadata_) {
1118  add_controller_option(CNTR_NETWORK, _("Network Player"), side_controller::human);
1119  }
1120 
1121  add_controller_option(CNTR_LOCAL, _("Local Player"), side_controller::human);
1122  add_controller_option(CNTR_COMPUTER, _("Computer Player"), side_controller::ai);
1123  add_controller_option(CNTR_EMPTY, _("Nobody"), side_controller::none);
1124 
1125  if(!reserved_for_.empty()) {
1126  add_controller_option(CNTR_RESERVED, _("Reserved"), side_controller::human);
1127  }
1128 
1129  // Connected users.
1130  for(const std::string& user : parent_.connected_users()) {
1131  add_controller_option(parent_.default_controller_, user, side_controller::human);
1132  }
1133 
1135 }
1136 
1138 {
1139  int i = 0;
1140  for(const controller_option& option : controller_options_) {
1141  if(option.first == controller_) {
1143 
1144  if(player_id_.empty() || player_id_ == option.second) {
1145  // Stop searching if no user is assigned to a side
1146  // or the selected user is found.
1147  break;
1148  }
1149  }
1150 
1151  i++;
1152  }
1153 
1155 }
1156 
1157 bool side_engine::controller_changed(const int selection)
1158 {
1159  const ng::controller selected_cntr = controller_options_[selection].first;
1160 
1161  // Check if user was selected. If so assign a side to him/her.
1162  // If not, make sure that no user is assigned to this side.
1163  if(selected_cntr == parent_.default_controller_ && selection != 0) {
1164  player_id_ = controller_options_[selection].second;
1166  } else {
1167  player_id_.clear();
1168  }
1169 
1170  set_controller(selected_cntr);
1171 
1172  return true;
1173 }
1174 
1176 {
1178 
1180 }
1181 
1182 void side_engine::set_faction_commandline(const std::string& faction_name)
1183 {
1184  flg_.set_current_faction(faction_name);
1185 }
1186 
1188 {
1190 
1191  if(controller_name == side_controller::ai) {
1193  }
1194 
1195  if(controller_name == side_controller::none) {
1197  }
1198 
1199  player_id_.clear();
1200 }
1201 
1203  const std::string& name, const std::string& controller_value)
1204 {
1205  if(controller_lock_ && !cfg_["controller"].empty() && cfg_["controller"] != controller_value) {
1206  return;
1207  }
1208 
1209  controller_options_.emplace_back(controller, name);
1210 }
1211 
1212 } // end namespace ng
std::vector< std::string > names
Definition: build_info.cpp:67
static const config & get_ai_config_for(const std::string &id)
Return the config for a specified ai.
static void add_era_ai_from_config(const config &game_config)
static void add_mod_ai_from_config(const config::const_child_itors &configs)
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_side
Non-empty if –side was given on the command line.
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_ai_config
Non-empty if –ai-config was given on the command line.
utils::optional< std::string > multiplayer_turns
Non-empty if –turns was given on the command line.
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line.
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_controller
Non-empty if –controller was given on the command line.
utils::optional< std::vector< std::pair< unsigned int, std::string > > > multiplayer_algorithm
Non-empty if –algorithm was given on the command line.
utils::optional< std::vector< std::tuple< unsigned int, std::string, std::string > > > multiplayer_parm
Non-empty if –parm was given on the command line.
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:188
void remove_attributes(T... keys)
Definition: config.hpp:537
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:390
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:281
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:465
void clear_children(T... keys)
Definition: config.hpp:602
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
void remove_children(config_key_type key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:650
child_itors child_range(config_key_type key)
Definition: config.cpp:268
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:401
void remove_attribute(config_key_type key)
Definition: config.cpp:162
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object.
Definition: config.cpp:906
bool empty() const
Definition: config.cpp:845
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & add_child(config_key_type key)
Definition: config.cpp:436
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
Encapsulates the map of the game.
Definition: map.hpp:172
std::vector< const config * > era_factions_
std::vector< team_data_pod > team_data_
const ng::controller default_controller_
std::set< std::string > & connected_users_rw()
void import_user(const std::string &name, const bool observer, int side_taken=-1)
bool sides_available() const
std::vector< side_engine_ptr > side_engines_
const bool first_scenario_
void save_reserved_sides_information()
const std::set< std::string > & connected_users() const
const mp_game_settings & params_
friend class side_engine
void send_level_data() const
mp_game_metadata * mp_metadata_
void update_side_controller_options()
connect_engine(saved_game &state, const bool first_scenario, mp_game_metadata *metadata)
void start_game_commandline(const commandline_options &cmdline_opts, const game_config_view &game_config)
int find_user_side_index_by_id(const std::string &id) const
bool process_network_data(const config &data)
bool can_start_game() const
const std::string & current_gender() const
Definition: flg_manager.hpp:84
void set_current_faction(const unsigned index)
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
void set_current_leader(const unsigned index)
bool leader_lock() const
Definition: flg_manager.hpp:93
void set_current_gender(const unsigned index)
bool is_random_faction()
const config & current_faction() const
Definition: flg_manager.hpp:80
const std::string & current_leader() const
Definition: flg_manager.hpp:82
const config & cfg() const
unsigned current_controller_index_
void update_controller_options()
std::string player_id_
const bool controller_lock_
const bool allow_changes_
void set_controller_commandline(const std::string &controller_name)
void set_faction_commandline(const std::string &faction_name)
void place_user(const std::string &name)
void set_side_children(const std::multimap< std::string, config > &children)
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_faction_ids=std::vector< std::string >())
void update_current_controller_index()
side_engine(const config &cfg, connect_engine &parent_engine, const int index)
std::vector< controller_option > controller_options_
connect_engine & parent_
std::string ai_algorithm_
std::string reserved_for_
std::vector< std::string > color_options_
void set_controller(ng::controller controller)
void set_waiting_to_choose_status(bool status)
bool controller_changed(const int selection)
std::string color_id_
std::string user_description() const
ng::controller controller_
std::multimap< std::string, config > get_side_children()
bool ready_for_start() const
config new_config() const
ng::controller controller() const
bool available_for_user(const std::string &name="") const
const bool allow_player_
static prefs & get()
const std::string get_ignored_delim()
std::string login()
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
game_classification & classification()
Definition: saved_game.hpp:56
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
std::string to_serialized() const
Definition: tstring.hpp:165
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:995
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1339
Managing the AIs configuration - headers.
#define WRN_MP
#define ERR_MP
#define DBG_MP
#define LOG_NW
static lg::log_domain log_network("network")
#define LOG_CF
#define ERR_CF
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
#define LOG_MP
static lg::log_domain log_config("config")
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
#define N_(String)
Definition: gettext.hpp:105
static std::string _(const char *str)
Definition: gettext.hpp:97
std::vector< const mp::user_info * > user_data
The associated user data for each node, index-to-index.
Standard logging facilities (interface).
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:59
std::string observer
Game configuration data as global variables.
Definition: build_info.cpp:61
std::vector< std::string > default_colors
static void add_color_info(const game_config_view &v, bool build_defaults)
static std::string controller_name(const team &t)
Definition: game_stats.cpp:61
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:49
config initial_level_config(saved_game &state)
void level_to_gamestate(const config &level, saved_game &state)
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
@ CNTR_RESERVED
@ CNTR_NETWORK
@ CNTR_COMPUTER
@ CNTR_EMPTY
@ CNTR_LOCAL
std::pair< ng::controller, std::string > controller_option
std::shared_ptr< side_engine > side_engine_ptr
static std::string at(const std::string &file, int line)
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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.
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
std::string_view data
Definition: picture.cpp:188
std::string queue_type
std::set< std::string > connected_players
players and observers
random_faction_mode::type mode
saved_game_mode::type saved_game
The base template for associating string values with enum values.
Definition: enum_base.hpp:33
static map_location::direction s