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