The Battle for Wesnoth  1.19.15+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 
33 static lg::log_domain log_config("config");
34 #define LOG_CF LOG_STREAM(info, log_config)
35 #define ERR_CF LOG_STREAM(err, log_config)
36 
37 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
38 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
39 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
40 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
41 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
42 
43 static lg::log_domain log_network("network");
44 #define LOG_NW LOG_STREAM(info, log_network)
45 
46 namespace
47 {
48 const std::array controller_names {
49  side_controller::human,
50  side_controller::human,
51  side_controller::ai,
52  side_controller::none,
53  side_controller::reserved
54 };
55 
56 const std::set<std::string> children_to_swap {
57  "village",
58  "unit",
59  "ai"
60 };
61 
62 } // end anon namespace
63 
64 namespace ng {
65 
66 connect_engine::connect_engine(saved_game& state, const bool first_scenario, mp_game_metadata* metadata)
67  : level_(mp::initial_level_config(state))
68  , state_(state)
69  , params_(state.mp_settings())
70  , default_controller_(metadata ? CNTR_NETWORK : CNTR_LOCAL)
71  , mp_metadata_(metadata)
72  , first_scenario_(first_scenario)
73  , force_lock_settings_()
74  , side_engines_()
75  , era_factions_()
76  , team_data_()
77  , era_info_(level_.mandatory_child("era"))
78 {
79  const config& era_config = level_.mandatory_child("era");
80 
81  const bool is_mp = state_.classification().is_normal_mp_game();
82  force_lock_settings_ = (state.mp_settings().saved_game != saved_game_mode::type::midgame) && scenario()["force_lock_settings"].to_bool(!is_mp);
83 
84  // Original level sides.
85  config::child_itors sides = scenario().child_range("side");
86 
87  // AI algorithms.
90 
91  // Set the team name lists and modify the original level sides if necessary.
92  std::vector<std::string> original_team_names;
93  std::string team_prefix(_("Team") + " ");
94 
95  int side_count = 1;
96  for(config& side : sides) {
97  const std::string side_str = std::to_string(side_count);
98 
99  config::attribute_value& team_name = side["team_name"];
100  config::attribute_value& user_team_name = side["user_team_name"];
101 
102  // Revert to default values if appropriate.
103  if(team_name.empty()) {
104  team_name = side_str;
105  }
106 
107  if(params_.use_map_settings && user_team_name.empty()) {
108  user_team_name = team_name;
109  }
110 
111  bool add_team = true;
113  // Only add a team if it is not found.
114  if(std::any_of(team_data_.begin(), team_data_.end(), [&team_name](const team_data_pod& data){
115  return data.team_name == team_name.str();
116  })) {
117  add_team = false;
118  }
119  } else {
120  // Always add a new team for every side, but leave the specified team assigned to a side if there is one.
121  auto name_itor = std::find(original_team_names.begin(), original_team_names.end(), team_name.str());
122 
123  // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed
124  // so that the attribute is not interpretted as an int when reading from config, which causes bugs later.
125  if(name_itor == original_team_names.end()) {
126  original_team_names.push_back(team_name);
127 
128  team_name = "Team " + std::to_string(original_team_names.size());
129  } else {
130  team_name = "Team " + std::to_string(std::distance(original_team_names.begin(), name_itor) + 1);
131  }
132 
133  user_team_name = team_prefix + side_str;
134  }
135 
136  // Write the serialized translatable team name back to the config. Without this,
137  // the string can appear all messed up after leaving and rejoining a game (see
138  // issue #2040. This affected the mp_join_game dialog). I don't know why that issue
139  // didn't appear the first time you join a game, but whatever.
140  //
141  // The difference between that dialog and mp_staging is that the latter has access
142  // to connect_engine object, meaning it has access to serialized versions of the
143  // user_team_name string stored in the team_data_ vector. mp_join_game handled the
144  // raw side config instead. Again, I don't know why issues only cropped up on a
145  // subsequent join and not the first, but it doesn't really matter.
146  //
147  // This ensures both dialogs have access to the serialized form of the utn string.
148  // As for why this needs to be done in the first place, apparently the simple_wml
149  // format the server (wesnothd) uses doesn't preserve translatable strings (see
150  // issue #342).
151  //
152  // --vultraz, 2018-02-06
153  user_team_name = user_team_name.t_str().to_serialized();
154 
155  if(add_team) {
157  data.team_name = params_.use_map_settings ? team_name : "Team " + side_str;
158  data.user_team_name = user_team_name.str();
159  data.is_player_team = side["allow_player"].to_bool(true);
160 
161  team_data_.push_back(data);
162  }
163 
164  ++side_count;
165  }
166 
167  // Selected era's factions.
168  for(const config& ms : era_config.child_range("multiplayer_side")) {
169  era_factions_.push_back(&ms);
170  }
171 
173 
174  // Create side engines.
175  int index = 0;
176  for(const config& s : sides) {
177  side_engines_.emplace_back(new side_engine(s, *this, index++));
178  }
179 
180  if(first_scenario_) {
181  // Add host to the connected users list.
182  import_user(prefs::get().login(), false);
183  } else {
184  // Add host but don't assign a side to him.
185  import_user(prefs::get().login(), true);
186 
187  // Load reserved players information into the sides.
189  }
190 
191  // Only updates the sides in the level.
192  update_level();
193 
194  // If we are connected, send data to the connected host.
195  send_level_data();
196 }
197 
198 void connect_engine::import_user(const std::string& name, const bool observer, int side_taken)
199 {
201  user_data["name"] = name;
202  import_user(user_data, observer, side_taken);
203 }
204 
205 void connect_engine::import_user(const config& data, const bool observer, int side_taken)
206 {
207  const std::string& username = data["name"];
208  assert(!username.empty());
209  if(mp_metadata_) {
210  connected_users_rw().insert(username);
211  }
212 
214 
215  if(observer) {
216  return;
217  }
218 
219  bool side_assigned = false;
220  if(side_taken >= 0) {
221  side_engines_[side_taken]->place_user(data, true);
222  side_assigned = true;
223  }
224 
225  // Check if user has a side(s) reserved for him.
226  for(const side_engine_ptr& side : side_engines_) {
227  if(side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
228  side->place_user(data);
229 
230  side_assigned = true;
231  }
232  }
233 
234  // If no sides were assigned for a user,
235  // take a first available side.
236  if(side_taken < 0 && !side_assigned) {
237  for(const side_engine_ptr& side : side_engines_) {
238  if(side->available_for_user(username) ||
239  side->controller() == CNTR_LOCAL) {
240  side->place_user(data);
241 
242  side_assigned = true;
243  break;
244  }
245  }
246  }
247 
248  // Check if user has taken any sides, which should get control
249  // over any other sides.
250  for(const side_engine_ptr& user_side : side_engines_) {
251  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
252  for(const side_engine_ptr& side : side_engines_){
253  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
254  side->place_user(data);
255  }
256  }
257  }
258  }
259 }
260 
262 {
263  for(const side_engine_ptr& side : side_engines_) {
264  if(side->available_for_user()) {
265  return true;
266  }
267  }
268 
269  return false;
270 }
271 
273 {
274  DBG_MP << "updating level";
275 
276  scenario().clear_children("side");
277 
278  for(const side_engine_ptr& side : side_engines_) {
279  scenario().add_child("side", side->new_config());
280  }
281 }
282 
284 {
285  config old_level = level_;
286  update_level();
287 
288  if(config diff = level_.get_diff(old_level); !diff.empty()) {
289  mp::send_to_server(config{"scenario_diff", std::move(diff)});
290  }
291 }
292 
294 {
295  if(side_engines_.empty()) {
296  return true;
297  }
298 
299  // First check if all sides are ready to start the game.
300  for(const side_engine_ptr& side : side_engines_) {
301  if(!side->ready_for_start()) {
302  const int side_num = side->index() + 1;
303  DBG_MP << "not all sides are ready, side " <<
304  side_num << " not ready";
305 
306  return false;
307  }
308  }
309 
310  DBG_MP << "all sides are ready";
311 
312  /*
313  * If at least one human player is slotted with a player/ai we're allowed
314  * to start. Before used a more advanced test but it seems people are
315  * creative in what is used in multiplayer [1] so use a simpler test now.
316  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
317  */
318  for(const side_engine_ptr& side : side_engines_) {
319  if(side->controller() != CNTR_EMPTY && side->allow_player()) {
320  return true;
321  }
322  }
323 
324  return false;
325 }
326 
327 std::multimap<std::string, config> side_engine::get_side_children()
328 {
329  std::multimap<std::string, config> children;
330 
331  for(const std::string& to_swap : children_to_swap) {
332  for(const config& child : cfg_.child_range(to_swap)) {
333  children.emplace(to_swap, child);
334  }
335  }
336 
337  return children;
338 }
339 
340 void side_engine::set_side_children(const std::multimap<std::string, config>& children)
341 {
342  for(const std::string& children_to_remove : children_to_swap) {
343  cfg_.clear_children(children_to_remove);
344  }
345 
346  for(std::pair<std::string, config> child_map : children) {
347  cfg_.add_child(child_map.first, child_map.second);
348  }
349 }
350 
352 {
353  DBG_MP << "starting a new game";
354 
355  // Resolves the "random faction", "random gender" and "random message"
356  // Must be done before shuffle sides, or some cases will cause errors
357  randomness::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
358  for(const side_engine_ptr& side : side_engines_) {
359  std::vector<std::string> avoid_faction_ids;
360 
361  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
362  if(params_.mode != random_faction_mode::type::independent) {
363  for(const side_engine_ptr& side2 : side_engines_) {
364  if(!side2->flg().is_random_faction()) {
365  switch(params_.mode) {
366  case random_faction_mode::type::no_mirror:
367  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
368  break;
369  case random_faction_mode::type::no_ally_mirror:
370  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"
371  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
372  }
373  break;
374  default:
375  break; // assert(false);
376  }
377  }
378  }
379  }
380  side->resolve_random(rng, avoid_faction_ids);
381  }
382 
383  // Shuffle sides (check settings and if it is a re-loaded game).
384  // Must be done after resolve_random() or shuffle sides, or they won't work.
385  if(state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.has_child("snapshot") && level_.mandatory_child("snapshot").has_child("side"))) {
386 
387  // Only playable sides should be shuffled.
388  std::vector<int> playable_sides;
389  for(const side_engine_ptr& side : side_engines_) {
390  if(side->allow_player() && side->allow_shuffle()) {
391  playable_sides.push_back(side->index());
392  }
393  }
394 
395  // Fisher-Yates shuffle.
396  for(int i = playable_sides.size(); i > 1; i--) {
397  const int j_side = playable_sides[rng.get_next_random() % i];
398  const int i_side = playable_sides[i - 1];
399 
400  if(i_side == j_side) continue; //nothing to swap
401 
402  // First we swap everything about a side with another
403  std::swap(side_engines_[j_side], side_engines_[i_side]);
404 
405  // Some 'child' variables such as village ownership and initial side units need to be swapped over as well
406  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
407  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
408  side_engines_[i_side]->set_side_children(tmp_side_children);
409 
410  // Then we revert the swap for fields that are unique to player control and the team they selected
411  std::swap(side_engines_[j_side]->index_, side_engines_[i_side]->index_);
412  std::swap(side_engines_[j_side]->team_, side_engines_[i_side]->team_);
413  }
414  }
415 
416  // Make other clients not show the results of resolve_random().
417  mp::send_to_server(config{"stop_updates"});
418 
420 
422 
423  // Build the gamestate object after updating the level.
425 
426  mp::send_to_server(config{"start_game"});
427 }
428 
430 {
431  DBG_MP << "starting a new game in commandline mode";
432 
433  randomness::mt_rng rng;
434 
435  unsigned num = 0;
436  for(const side_engine_ptr& side : side_engines_) {
437  num++;
438 
439  // Set the faction, if commandline option is given.
440  if(cmdline_opts.multiplayer_side) {
441  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_side) {
442  if(side_num == num) {
443  if(std::find_if(era_factions_.begin(), era_factions_.end(),
444  [fid = faction_id](const config* faction) { return (*faction)["id"] == fid; })
445  != era_factions_.end()
446  ) {
447  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
448  side->set_faction_commandline(faction_id);
449  } else {
450  ERR_MP << "failed to set side " << side_num << " to faction " << faction_id;
451  }
452  }
453  }
454  }
455 
456  // Set the controller, if commandline option is given.
457  if(cmdline_opts.multiplayer_controller) {
458  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_controller) {
459  if(side_num == num) {
460  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
461  side->set_controller_commandline(faction_id);
462  }
463  }
464  }
465 
466  // Set AI algorithm to default for all sides,
467  // then override if commandline option was given.
468  std::string ai_algorithm = game_config.mandatory_child("ais")["default_ai_algorithm"].str();
469  side->set_ai_algorithm(ai_algorithm);
470 
471  if(cmdline_opts.multiplayer_algorithm) {
472  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_algorithm) {
473  if(side_num == num) {
474  DBG_MP << "\tsetting side " << side_num << "\tfaction: " << faction_id;
475  side->set_ai_algorithm(faction_id);
476  }
477  }
478  }
479 
480  // Finally, resolve "random faction",
481  // "random gender" and "random message", if any remains unresolved.
482  side->resolve_random(rng);
483  } // end top-level loop
484 
486 
487  // Update sides with commandline parameters.
488  if(cmdline_opts.multiplayer_turns) {
489  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns;
490  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
491  }
492 
493  for(config& side : scenario().child_range("side")) {
494  if(cmdline_opts.multiplayer_ai_config) {
495  for(const auto& [side_num, faction_id] : *cmdline_opts.multiplayer_ai_config) {
496  if(side_num == side["side"].to_unsigned()) {
497  DBG_MP << "\tsetting side " << side["side"] << "\tai_config: " << faction_id;
498  side["ai_config"] = faction_id;
499  }
500  }
501  }
502 
503  // Having hard-coded values here is undesirable,
504  // but that's how it is done in the MP lobby
505  // part of the code also.
506  // Should be replaced by settings/constants in both places
507  if(cmdline_opts.multiplayer_ignore_map_settings) {
508  side["gold"] = 100;
509  side["income"] = 1;
510  }
511 
512  if(cmdline_opts.multiplayer_parm) {
513  for(const auto& [side_num, pname, pvalue] : *cmdline_opts.multiplayer_parm) {
514  if(side_num == side["side"].to_unsigned()) {
515  DBG_MP << "\tsetting side " << side["side"] << " " << pname << ": " << pvalue;
516  side[pname] = pvalue;
517  }
518  }
519  }
520  }
521 
523 
524  // Build the gamestate object after updating the level
526  mp::send_to_server(config{"start_game"});
527 }
528 
530 {
531  DBG_MP << "leaving the game";
532  mp::send_to_server(config{"leave_game"});
533 }
534 
536 {
537  if(data.has_child("leave_game")) {
538  return true;
539  }
540 
541  // A side has been dropped.
542  if(auto side_drop = data.optional_child("side_drop")) {
543  unsigned side_index = side_drop["side_num"].to_int() - 1;
544 
545  if(side_index < side_engines_.size()) {
546  const side_engine_ptr& side_to_drop = side_engines_[side_index];
547 
548  // Remove user, whose side was dropped.
549  connected_users_rw().erase(side_to_drop->player_id());
551 
552  side_to_drop->reset();
553 
555  return false;
556  }
557  }
558 
559  // A player is connecting to the game.
560  if(!data["side"].empty()) {
561  unsigned side_taken = data["side"].to_int() - 1;
562 
563  // Checks if the connecting user has a valid and unique name.
564  const std::string name = data["name"];
565  if(name.empty()) {
566  ERR_CF << "ERROR: No username provided with the side.";
567  return false;
568  }
569 
570  if(connected_users().find(name) != connected_users().end()) {
571  // TODO: Seems like a needless limitation
572  // to only allow one side per player.
573  if(find_user_side_index_by_id(name) != -1) {
574  return false;
575  } else {
576  connected_users_rw().erase(name);
578  }
579  }
580 
581  // Assigns this user to a side.
582  if(side_taken < side_engines_.size()) {
583  if(!side_engines_[side_taken]->available_for_user(name)) {
584  // This side is already taken.
585  // Try to reassing the player to a different position.
586  side_taken = 0;
587  for(const side_engine_ptr& s : side_engines_) {
588  if(s->available_for_user()) {
589  break;
590  }
591 
592  side_taken++;
593  }
594 
595  if(side_taken >= side_engines_.size()) {
596  mp::send_to_server(config{"kick", config{"username", data["name"]}});
597 
599 
600  ERR_CF << "ERROR: Couldn't assign a side to '" << name << "'";
601  return false;
602  }
603  }
604 
605  LOG_CF << "client has taken a valid position";
606 
607  import_user(data, false, side_taken);
609 
610  // Wait for them to choose faction if allowed.
611  side_engines_[side_taken]->set_waiting_to_choose_status(side_engines_[side_taken]->allow_changes());
612  LOG_MP << "waiting to choose status = " << side_engines_[side_taken]->allow_changes();
613  LOG_NW << "sent player data";
614  } else {
615  ERR_CF << "tried to take illegal side: " << side_taken;
616  }
617  }
618 
619  if(auto change_faction = data.optional_child("change_faction")) {
620  int side_taken = find_user_side_index_by_id(change_faction["name"]);
621  if(side_taken != -1 || !first_scenario_) {
622  import_user(*change_faction, false, side_taken);
624  }
625  }
626 
627  if(auto observer = data.optional_child("observer")) {
628  import_user(*observer, true);
630  }
631 
632  if(auto observer = data.optional_child("observer_quit")) {
633  const std::string& observer_name = observer["name"];
634 
635  if(connected_users().find(observer_name) != connected_users().end()) {
636  connected_users_rw().erase(observer_name);
638 
639  // If the observer was assigned a side, we need to send an update to other
640  // players so they no longer see the observer assigned to that side.
641  if(find_user_side_index_by_id(observer_name) != -1) {
643  }
644  }
645  }
646 
647  return false;
648 }
649 
650 int connect_engine::find_user_side_index_by_id(const std::string& id) const
651 {
652  std::size_t i = 0;
653  for(const side_engine_ptr& side : side_engines_) {
654  if(side->player_id() == id) {
655  break;
656  }
657 
658  i++;
659  }
660 
661  if(i >= side_engines_.size()) {
662  return -1;
663  }
664 
665  return i;
666 }
667 
669 {
670  // Send initial information.
671  if(first_scenario_) {
673  "create_game", config {
674  "name", params_.name,
675  "password", params_.password,
676  "ignored", prefs::get().get_ignored_delim(),
677  // all queue games count as auto hosted, but not all auto hosted games are queue games
678  "auto_hosted", mp_metadata_ ? mp_metadata_->queue_type != queue_type::normal : false,
679  "queue_type", mp_metadata_ ? mp_metadata_->queue_type : queue_type::normal,
680  "queue_id", mp_metadata_ ? mp_metadata_->queue_id : 0,
681  },
682  });
684  } else {
685  mp::send_to_server(config{"store_next_scenario", level_});
686  }
687 }
688 
690 {
691  // Add information about reserved sides to the level config.
692  // N.B. This information is needed only for a host player.
693  std::map<std::string, std::string> side_users = utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
694  for(const side_engine_ptr& side : side_engines_) {
695  const std::string& save_id = side->save_id();
696  const std::string& player_id = side->player_id();
697  if(!save_id.empty() && !player_id.empty()) {
698  side_users[save_id] = player_id;
699  }
700  }
701 
702  level_.mandatory_child("multiplayer")["side_users"] = utils::join_map(side_users);
703 }
704 
706 {
707  std::map<std::string, std::string> side_users = utils::map_split(level_.mandatory_child("multiplayer")["side_users"]);
708  std::set<std::string> names;
709  for(const side_engine_ptr& side : side_engines_) {
710  const std::string& save_id = side->previous_save_id();
711  if(side_users.find(save_id) != side_users.end()) {
712  side->set_reserved_for(side_users[save_id]);
713 
714  if(side->controller() != CNTR_COMPUTER) {
715  side->set_controller(CNTR_RESERVED);
716  names.insert(side_users[save_id]);
717  }
718 
719  side->update_controller_options();
720  }
721  }
722 
723  //Do this in an extra loop to make sure we import each user only once.
724  for(const std::string& name : names)
725  {
726  if(connected_users().find(name) != connected_users().end() || !mp_metadata_) {
727  import_user(name, false);
728  }
729  }
730 }
731 
733 {
734  for(const side_engine_ptr& side : side_engines_) {
735  side->update_controller_options();
736  }
737 }
738 
739 const std::set<std::string>& connect_engine::connected_users() const
740 {
741  if(mp_metadata_) {
743  }
744 
745  static std::set<std::string> empty;
746  return empty;
747 }
748 
749 std::set<std::string>& connect_engine::connected_users_rw()
750 {
751  assert(mp_metadata_);
753 }
754 
755 side_engine::side_engine(const config& cfg, connect_engine& parent_engine, const int index)
756  : cfg_(cfg)
757  , parent_(parent_engine)
758  , controller_(CNTR_NETWORK)
759  , current_controller_index_(0)
760  , controller_options_()
761  , allow_player_(cfg["allow_player"].to_bool(true))
762  , controller_lock_(cfg["controller_lock"].to_bool(parent_.force_lock_settings_) && parent_.params_.use_map_settings)
763  , index_(index)
764  , team_(0)
765  , color_(std::min(index, gamemap::MAX_PLAYERS - 1))
766  , gold_(cfg["gold"].to_int(100))
767  , income_(cfg["income"].to_int())
768  , reserved_for_(cfg["current_player"])
769  , player_id_()
770  , ai_algorithm_()
771  , chose_random_(cfg["chose_random"].to_bool(false))
772  , disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false))
773  , 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)
774  , 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))
775  , waiting_to_choose_faction_(allow_changes_)
776  , color_options_(game_config::default_colors)
777  //TODO: what should we do if color_ is out of range?
778  , color_id_(color_options_.at(color_))
779 {
780  // Save default attributes that could be overwritten by the faction, so that correct faction lists would be
781  // initialized by flg_manager when the new side config is sent over network.
782  cfg_.clear_children("default_faction");
783  cfg_.add_child("default_faction", config {
784  "faction", cfg_["faction"],
785  "recruit", cfg_["recruit"],
786  });
787  if(auto p_cfg = cfg_.optional_child("leader")) {
788  cfg_.mandatory_child("default_faction").add_child("leader", config {
789  "type", (p_cfg)["type"],
790  "gender", (p_cfg)["gender"],
791  });
792  }
793 
794  if(cfg_["side"].to_int(index_ + 1) != index_ + 1) {
795  ERR_CF << "found invalid side=" << cfg_["side"].to_int(index_ + 1) << " in definition of side number " << index_ + 1;
796  }
797 
798  cfg_["side"] = index_ + 1;
799 
800  if(cfg_["controller"] != side_controller::human && cfg_["controller"] != side_controller::ai && cfg_["controller"] != side_controller::none) {
801  //an invalid controller type was specified. Remove it to prevent asertion failures later.
802  cfg_.remove_attribute("controller");
803  }
804 
806 
807  // Tweak the controllers.
808  if(parent_.state_.classification().is_scenario() && cfg_["controller"].blank()) {
809  cfg_["controller"] = side_controller::ai;
810  }
811 
812  if(cfg_["controller"] == side_controller::none) {
814  } else if(cfg_["controller"] == side_controller::ai) {
816  } else if(parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
817  // Reserve a side for "current_player", unless the side
818  // is played by an AI.
820  } else if(allow_player_) {
822  } else {
823  // AI is the default.
825  }
826 
827  // Initialize team and color.
828  unsigned team_name_index = 0;
830  if(cfg["team_name"] == data.team_name) {
831  break;
832  }
833 
834  ++team_name_index;
835  }
836 
837  if(team_name_index >= parent_.team_data_.size()) {
838  assert(!parent_.team_data_.empty());
839  team_ = 0;
840  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!";
841  } else {
842  team_ = team_name_index;
843  }
844 
845  // Check the value of the config's color= key.
846  const std::string given_color = team::get_side_color_id_from_config(cfg_);
847 
848  if(!given_color.empty()) {
849  // If it's valid, save the color...
850  color_id_ = given_color;
851 
852  // ... and find the appropriate index for it.
853  const auto iter = std::find(color_options_.begin(), color_options_.end(), color_id_);
854 
855  if(iter != color_options_.end()) {
856  color_ = std::distance(color_options_.begin(), iter);
857  } else {
858  color_options_.push_back(color_id_);
859  color_ = color_options_.size() - 1;
860  }
861  }
862 
863  // Initialize ai algorithm.
864  if(auto ai = cfg.optional_child("ai")) {
865  ai_algorithm_ = ai["ai_algorithm"].str();
866  }
867 }
868 
869 std::string side_engine::user_description() const
870 {
871  switch(controller_) {
872  case CNTR_LOCAL:
873  return N_("Anonymous player");
874  case CNTR_COMPUTER:
875  if(allow_player_) {
877  } else {
878  return N_("Computer Player");
879  }
880  default:
881  return "";
882  }
883 }
884 
886 {
887  config res = cfg_;
888 
889  // In case of 'shuffle sides' the side index in cfg_ might be wrong which will confuse the team constructor later.
890  res["side"] = index_ + 1;
891 
892  // If the user is allowed to change type, faction, leader etc, then import their new values in the config.
893  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
894  // Merge the faction data to res.
895  config faction = flg_.current_faction();
896  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " faction=" << faction["id"] << " recruit=" << faction["recruit"];
897  res["faction_name"] = faction["name"];
898  res["faction"] = faction["id"];
899  faction.remove_attributes("id", "name", "image", "gender", "type", "description");
900  res.append(faction);
901  }
902 
903  res["controller"] = controller_names[controller_];
904 
905  // The hosts receives the serversided controller tweaks after the start event, but
906  // for mp sync it's very important that the controller types are correct
907  // during the start/prestart event (otherwise random unit creation during prestart fails).
908  res["is_local"] = player_id_ == prefs::get().login() || controller_ == CNTR_COMPUTER || controller_ == CNTR_LOCAL;
909 
910  // This function (new_config) is only meant to be called by the host's machine, which is why this check
911  // works. It essentially certifies that whatever side has the player_id that matches the host's login
912  // will be flagged. The reason we cannot check mp_game_metadata::is_host is because that flag is *always*
913  // true on the host's machine, meaning this flag would be set to true for every side.
914  res["is_host"] = player_id_ == prefs::get().login();
915 
916  std::string desc = user_description();
917  if(!desc.empty()) {
918  res["user_description"] = t_string(desc, "wesnoth");
919 
920  desc = VGETTEXT("$playername $side", {
921  {"playername", _(desc.c_str())},
922  {"side", res["side"].str()}
923  });
924  } else if(!player_id_.empty()) {
925  desc = player_id_;
926  }
927 
928  if(res["name"].str().empty() && !desc.empty()) {
929  //TODO: maybe we should add this in to the leaders config instead of the side config?
930  res["name"] = desc;
931  }
932 
934  // If this is a saved game and "use_saved" (the default) was chosen for the
935  // AI algorithm, we do nothing. Otherwise we add the chosen AI and if this
936  // is a saved game, we also remove the old stages from the AI config.
937  if(ai_algorithm_ != "use_saved") {
938  if(parent_.params_.saved_game == saved_game_mode::type::midgame) {
939  for (config &ai_config : res.child_range("ai")) {
940  ai_config.clear_children("stage");
941  }
942  }
943  res.add_child_at("ai", config {"ai_algorithm", ai_algorithm_}, 0);
944  }
945  }
946 
947  // A side's "current_player" is the player which has currently taken that side or the one for which it is reserved.
948  // The "player_id" is the id of the client who controls that side. It's always the host for Local and AI players and
949  // always empty for free/reserved sides or null controlled sides. You can use !res["player_id"].empty() to check
950  // whether a side is already taken.
951  assert(!prefs::get().login().empty());
952  if(controller_ == CNTR_LOCAL) {
953  res["player_id"] = prefs::get().login();
954  res["current_player"] = prefs::get().login();
955  } else if(controller_ == CNTR_RESERVED) {
956  res.remove_attribute("player_id");
957  res["current_player"] = reserved_for_;
958  } else if(controller_ == CNTR_COMPUTER) {
959  // TODO: what is the content of player_id_ here ?
960  res["current_player"] = desc;
961  res["player_id"] = prefs::get().login();
962  } else if(!player_id_.empty()) {
963  res["player_id"] = player_id_;
964  res["current_player"] = player_id_;
965  }
966 
967  res["allow_changes"] = allow_changes_;
968  res["chose_random"] = chose_random_;
969 
970  if(parent_.params_.saved_game != saved_game_mode::type::midgame) {
971 
972  if(controller_ != CNTR_EMPTY) {
973  if(!flg_.leader_lock()) {
974  auto& leader = res.child_or_add("leader");
975  leader["type"] = flg_.current_leader();
976  leader["gender"] = flg_.current_gender();
977  LOG_MP << "side_engine::new_config: side=" << index_ + 1 << " type=" << leader["type"]
978  << " gender=" << leader["gender"];
979  }
980  } else if(!controller_lock_) {
981  // if controller_lock_ == false and controller_ == CNTR_EMPTY, this means the user disalbles this side,
982  // so remove it's leader.
983  res.remove_children("leader");
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:900
bool empty() const
Definition: config.cpp:839
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:55
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
std::string to_serialized() const
Definition: tstring.hpp:165
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:994
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1333
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:81
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:141
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