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