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