The Battle for Wesnoth  1.19.13+dev
mp_create_game.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  by Mark de Wever <koraq@xs4all.nl>
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 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "formatter.hpp"
21 #include "formula/string_utils.hpp"
22 #include "game_config.hpp"
23 #include "game_config_manager.hpp"
24 #include "gettext.hpp"
25 #include "gui/auxiliary/field.hpp"
28 #include "gui/widgets/button.hpp"
29 #include "gui/widgets/image.hpp"
30 #include "gui/widgets/listbox.hpp"
32 #include "gui/widgets/minimap.hpp"
33 #include "gui/widgets/slider.hpp"
36 #include "gui/widgets/text_box.hpp"
39 #include "log.hpp"
40 #include "map_settings.hpp"
42 #include "save_index.hpp"
43 #include "savegame.hpp"
44 #include "tod_manager.hpp"
45 
46 #include <boost/algorithm/string.hpp>
47 
48 static lg::log_domain log_mp_create("mp/create");
49 
50 #define DBG_MP LOG_STREAM(debug, log_mp_create)
51 #define WRN_MP LOG_STREAM(warn, log_mp_create)
52 #define ERR_MP LOG_STREAM(err, log_mp_create)
53 
54 namespace gui2::dialogs
55 {
56 
57 // Special retval value for loading a game
58 static const int LOAD_GAME = 100;
59 
61 
62 mp_create_game::mp_create_game(saved_game& state, bool local_mode)
63  : modal_dialog(window_id())
64  , create_engine_(state)
65  , options_manager_()
66  , selected_game_index_(-1)
67  , selected_rfm_index_(-1)
68  , use_map_settings_(register_bool( "use_map_settings", true,
69  []() {return prefs::get().mp_use_map_settings();},
70  [](bool v) {prefs::get().set_mp_use_map_settings(v);},
71  std::bind(&mp_create_game::update_map_settings, this)))
72  , fog_(register_bool("fog", true,
73  []() {return prefs::get().mp_fog();},
74  [](bool v) {prefs::get().set_mp_fog(v);}))
75  , shroud_(register_bool("shroud", true,
76  []() {return prefs::get().mp_shroud();},
77  [](bool v) {prefs::get().set_mp_shroud(v);}))
78  , start_time_(register_bool("random_start_time", true,
79  []() {return prefs::get().mp_random_start_time();},
80  [](bool v) {prefs::get().set_mp_random_start_time(v);}))
81  , time_limit_(register_bool("time_limit", true,
82  []() {return prefs::get().mp_countdown();},
83  [](bool v) {prefs::get().set_mp_countdown(v);},
84  std::bind(&mp_create_game::update_map_settings, this)))
85  , shuffle_sides_(register_bool("shuffle_sides", true,
86  []() {return prefs::get().shuffle_sides();},
87  [](bool v) {prefs::get().set_shuffle_sides(v);}))
88  , observers_(register_bool("observers", true,
89  []() {return prefs::get().allow_observers();},
90  [](bool v) {prefs::get().set_allow_observers(v);}))
91  , strict_sync_(register_bool("strict_sync", true))
92  , private_replay_(register_bool("private_replay", true))
93  , turns_(register_integer("turn_count", true,
94  []() {return prefs::get().mp_turns();},
95  [](int v) {prefs::get().set_mp_turns(v);}))
96  , gold_(register_integer("village_gold", true,
97  []() {return prefs::get().village_gold();},
98  [](int v) {prefs::get().set_village_gold(v);}))
99  , support_(register_integer("village_support", true,
100  []() {return prefs::get().village_support();},
101  [](int v) {prefs::get().set_village_support(v);}))
102  , experience_(register_integer("experience_modifier", true,
103  []() {return prefs::get().xp_modifier();},
104  [](int v) {prefs::get().set_xp_modifier(v);}))
105  , init_turn_limit_(register_integer("init_turn_limit", true,
106  []() {return prefs::get().countdown_init_time().count();},
107  [](int v) {prefs::get().set_countdown_init_time(std::chrono::seconds{v});}))
108  , turn_bonus_(register_integer("turn_bonus", true,
109  []() {return prefs::get().countdown_turn_bonus().count();},
110  [](int v) {prefs::get().set_countdown_turn_bonus(std::chrono::seconds{v});}))
111  , reservoir_(register_integer("reservoir", true,
112  []() {return prefs::get().countdown_reservoir_time().count();},
113  [](int v) {prefs::get().set_countdown_reservoir_time(std::chrono::seconds{v});}))
114  , action_bonus_(register_integer("action_bonus", true,
115  []() {return prefs::get().countdown_action_bonus().count();},
116  [](int v) {prefs::get().set_countdown_action_bonus(std::chrono::seconds{v});}))
117  , mod_list_()
118  , eras_menu_button_()
119  , local_mode_(local_mode)
120 {
121  level_types_ = {
122  {level_type::type::scenario, _("Scenarios")},
123  {level_type::type::campaign, _("Multiplayer Campaigns")},
124  {level_type::type::sp_campaign, _("Singleplayer Campaigns")},
125  {level_type::type::user_map, _("Custom Maps")},
126  {level_type::type::user_scenario, _("Custom Scenarios")},
127  {level_type::type::random_map, _("Random Maps")},
128  {level_type::type::preset, _("Presets")},
129  };
130 
131  utils::erase_if(level_types_, [this](level_type_info& type_info) {
132  return create_engine_.get_levels_by_type_unfiltered(type_info.first).empty();
133  });
134 
135  set_show_even_without_video(true);
136 
137  create_engine_.init_active_mods();
138 
139  create_engine_.get_state().clear();
140  create_engine_.get_state().classification().type = campaign_type::type::multiplayer;
141 
142  // Need to set this in the constructor, pre_show() is too late
143  set_allow_plugin_skip(false);
144 }
145 
146 // NOLINTNEXTLINE(performance-unnecessary-value-param)
148 {
149  // from constructor
150  ng::create_engine create(state);
151  create.init_active_mods();
152  create.get_state().clear();
153  create.get_state().classification().type = campaign_type::type::multiplayer;
154 
155  // from pre_show
156  create.set_current_level_type(level_type::type::scenario);
157  const auto& levels = create.get_levels_by_type(level_type::type::scenario);
158  for(std::size_t i = 0; i < levels.size(); i++) {
159  if(levels[i]->id() == presets["scenario"].str()) {
160  create.set_current_level(i);
161  }
162  }
163 
164  create.set_current_era_id(presets["era"]);
165 
166  // from post_show
167  create.prepare_for_era_and_mods();
168  create.prepare_for_scenario();
169  create.get_parameters();
170  create.prepare_for_new_level();
171 
172  mp_game_settings& params = create.get_state().mp_settings();
173  params.mp_scenario = presets["scenario"].str();
174  params.use_map_settings = true;
175  params.num_turns = presets["turn_count"].to_int(-1);
176  params.village_gold = presets["village_gold"].to_int();
177  params.village_support = presets["village_support"].to_int();
178  params.xp_modifier = presets["experience_modifier"].to_int();
179  params.random_start_time = presets["random_start_time"].to_bool();
180  params.fog_game = presets["fog"].to_bool();
181  params.shroud_game = presets["shroud"].to_bool();
182 
183  // write to scenario
184  // queue games are supposed to all use the same settings, not be modified by the user
185  // can be removed later if we jump straight from the lobby into a game instead of going to the staging screen to wait for other players to join
186  config& scenario = create.get_state().get_starting_point();
187 
188  if(params.random_start_time) {
189  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
190  scenario["random_start_time"] = true;
191  }
192  } else {
193  scenario["random_start_time"] = false;
194  }
195 
196  scenario["experience_modifier"] = params.xp_modifier;
197  scenario["turns"] = params.num_turns;
198 
199  for(config& side : scenario.child_range("side")) {
200  side["controller_lock"] = true;
201  side["team_lock"] = true;
202  side["gold_lock"] = true;
203  side["income_lock"] = true;
204 
205  side["fog"] = params.fog_game;
206  side["shroud"] = params.shroud_game;
207  side["village_gold"] = params.village_gold;
208  side["village_support"] = params.village_support;
209  }
210 
211  params.mp_countdown = presets["countdown"].to_bool();
212  params.mp_countdown_init_time = std::chrono::seconds{presets["countdown_init_time"].to_int()};
213  params.mp_countdown_turn_bonus = std::chrono::seconds{presets["countdown_turn_bonus"].to_int()};
214  params.mp_countdown_reservoir_time = std::chrono::seconds{presets["countdown_reservoir_time"].to_int()};
215  params.mp_countdown_action_bonus = std::chrono::seconds{presets["countdown_action_bonus"].to_int()};
216 
217  params.allow_observers = true;
218  params.private_replay = false;
219  create.get_state().classification().oos_debug = false;
220  params.shuffle_sides = presets["shuffle_sides"].to_bool();
221 
222  params.mode = random_faction_mode::type::no_mirror;
224 }
225 
227 {
228  find_widget<text_box>("game_name").set_value(local_mode_ ? "" : settings::game_name_default());
229 
231  find_widget<button>("random_map_regenerate"),
232  std::bind(&mp_create_game::regenerate_random_map, this));
233 
235  find_widget<button>("random_map_settings"),
236  std::bind(&mp_create_game::show_generator_settings, this));
237 
239  find_widget<button>("load_game"),
240  std::bind(&mp_create_game::load_game_callback, this));
241 
243  find_widget<button>("save_preset"),
244  std::bind(&mp_create_game::save_preset, this));
245 
246  // Custom dialog close hook
248 
249  //
250  // Set up the options manager. Needs to be done before selecting an initial tab
251  //
253 
254  //
255  // Set up filtering
256  //
257  connect_signal_notify_modified(find_widget<slider>("num_players"),
258  std::bind(&mp_create_game::on_filter_change<slider>, this, "num_players", true));
259 
260  text_box& filter = find_widget<text_box>("game_filter");
261 
262  filter.on_modified([this](const auto&) { on_filter_change<text_box>("game_filter", true); });
263 
264  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
266 
267  //
268  // Set up game types menu_button
269  //
270  std::vector<config> game_types;
271  for(level_type_info& type_info : level_types_) {
272  game_types.emplace_back("label", type_info.second);
273  }
274 
275  if(game_types.empty()) {
276  gui2::show_transient_message("", _("No games found."));
277  throw game::error(_("No games found."));
278  }
279 
280  menu_button& game_menu_button = find_widget<menu_button>("game_types");
281 
282  // Helper to make sure the initially selected level type is valid
283  auto get_initial_type_index = [this]()->int {
284  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
285  return info.first == *level_type::get_enum(prefs::get().mp_level_type());
286  });
287 
288  if(index != level_types_.end()) {
289  return std::distance(level_types_.begin(), index);
290  }
291 
292  return 0;
293  };
294 
295  int initial_type = get_initial_type_index();
296  game_menu_button.set_values(game_types, initial_type);
297  find_widget<button>("save_preset").set_active(level_type::get_enum(initial_type) == level_type::type::scenario);
298 
299  connect_signal_notify_modified(game_menu_button,
300  std::bind(&mp_create_game::update_games_list, this));
301 
302  //
303  // Set up mods list
304  //
305  mod_list_ = &find_widget<listbox>("mod_list");
306 
307  const auto& activemods = prefs::get().modifications();
309  grid* row_grid = &mod_list_->add_row(widget_data{{ "mod_name", {{ "label", mod->name }}}});
310 
311  row_grid->find_widget<toggle_panel>("panel").set_tooltip(mod->description);
312 
313  toggle_button& mog_toggle = row_grid->find_widget<toggle_button>("mod_active_state");
314 
315  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
316  create_engine_.active_mods().push_back(mod->id);
317  mog_toggle.set_value_bool(true);
318  }
319 
320  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
321  }
322 
323  // No mods, hide the header
324  if(mod_list_->get_item_count() <= 0) {
325  find_widget<styled_widget>("mods_header").set_visible(widget::visibility::invisible);
326  }
327 
328  //
329  // Set up eras menu_button
330  //
331  eras_menu_button_ = &find_widget<menu_button>("eras");
332 
333  std::vector<config> era_names;
335  era_names.emplace_back("label", era->name, "tooltip", era->description);
336  }
337 
338  if(era_names.empty()) {
339  gui2::show_transient_message("", _("No eras found."));
340  throw config::error(_("No eras found"));
341  }
342 
343  eras_menu_button_->set_values(era_names);
344 
346  std::bind(&mp_create_game::on_era_select, this));
347 
348  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().mp_era());
349  if(era_selection >= 0) {
350  eras_menu_button_->set_selected(era_selection);
351  }
352 
353  on_era_select();
354 
355  //
356  // Set up random faction mode menu_button
357  //
358  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
359 
360  menu_button& rfm_menu_button = find_widget<menu_button>("random_faction_mode");
361  rfm_menu_button.set_selected(initial_index);
362 
363  connect_signal_notify_modified(rfm_menu_button,
365 
367 
368  //
369  // Set up the setting status labels
370  //
372  bind_default_status_label(static_cast<slider&>(*gold_->get_widget()));
379 
380  //
381  // Timer reset button
382  //
384  find_widget<button>("reset_timer_defaults"),
385  std::bind(&mp_create_game::reset_timer_settings, this));
386 
387  //
388  // Disable certain settings if we're playing a local game.
389  //
390  if(local_mode_) {
391  find_widget<text_box>("game_name").set_active(false);
392  find_widget<text_box>("game_password").set_active(false);
393 
394  observers_->widget_set_enabled(false, false);
395  strict_sync_->widget_set_enabled(false, false);
396  private_replay_->widget_set_enabled(false, false);
397  }
398 
399  //
400  // Set up tab control
401  //
402  listbox& tab_bar = find_widget<listbox>("tab_bar");
403 
405  std::bind(&mp_create_game::on_tab_select, this));
406 
407  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
408  // This ensures settings (especially game settings) widgets are appropriately updated when
409  // a new game is selected, regardless of which settings tab is active at the time.
410  find_widget<stacked_widget>("pager").set_find_in_all_layers(true);
411 
412  // We call on_tab_select farther down.
413 
414  //
415  // Main games list
416  //
417  listbox& list = find_widget<listbox>("games_list");
418 
420  std::bind(&mp_create_game::on_game_select, this));
421 
422  add_to_keyboard_chain(&list);
423 
424  // This handles the initial game selection as well
425  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().mp_level());
426 
427  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
428  on_tab_select();
429 
430  //
431  // Set up the Lua plugin context
432  //
433  plugins_context_.reset(new plugins_context("Multiplayer Create"));
434 
435  plugins_context_->set_callback("create", [this](const config&) { set_retval(retval::OK); }, false);
436  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
437  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
438 
439 #define UPDATE_ATTRIBUTE(field, convert) \
440  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
441 
442  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
443  UPDATE_ATTRIBUTE(turns, to_int);
444  UPDATE_ATTRIBUTE(gold, to_int);
445  UPDATE_ATTRIBUTE(support, to_int);
446  UPDATE_ATTRIBUTE(experience, to_int);
447  UPDATE_ATTRIBUTE(start_time, to_bool);
448  UPDATE_ATTRIBUTE(fog, to_bool);
449  UPDATE_ATTRIBUTE(shroud, to_bool);
450  UPDATE_ATTRIBUTE(time_limit, to_bool);
451  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
452  UPDATE_ATTRIBUTE(turn_bonus, to_int);
453  UPDATE_ATTRIBUTE(reservoir, to_int);
454  UPDATE_ATTRIBUTE(action_bonus, to_int);
455  UPDATE_ATTRIBUTE(observers, to_bool);
456  UPDATE_ATTRIBUTE(strict_sync, to_bool);
457  UPDATE_ATTRIBUTE(private_replay, to_bool);
458  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
459  }, true);
460 
461 #undef UPDATE_ATTRIBUTE
462 
463  plugins_context_->set_callback("set_name", [this](const config& cfg) {
465  }, true);
466 
467  plugins_context_->set_callback("set_password", [this](const config& cfg) {
468  create_engine_.get_state().mp_settings().password = cfg["password"];
469  }, true);
470 
471  plugins_context_->set_callback("select_level", [this](const config& cfg) {
474  }, true);
475 
476  plugins_context_->set_callback("select_type", [this](const config& cfg) {
477  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
478 
479  plugins_context_->set_callback("select_era", [this](const config& cfg) {
480  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
481 
482  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
483  on_mod_toggle(cfg["id"].str(), nullptr);
484  }, true);
485 
486  plugins_context_->set_accessor("get_selected", [this](const config&) {
487  const ng::level& current_level = create_engine_.current_level();
488  return config {
489  "id", current_level.id(),
490  "name", current_level.name(),
491  "icon", current_level.icon(),
492  "description", current_level.description(),
493  "allow_era_choice", current_level.allow_era_choice(),
495  };
496  });
497 
498  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
499  const std::string id = cfg["id"].str();
500  auto result = create_engine_.find_level_by_id(id);
501  return config {
502  "index", result.second,
503  "type", level_type::get_string(result.first),
504  };
505  });
506 
507  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
509  });
510 
511  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
513  });
514 }
515 
517 {
518  DBG_MP << "sync_with_depcheck: start";
519 
521  DBG_MP << "sync_with_depcheck: correcting era";
522  const int new_era_index = create_engine_.dependency_manager().get_era_index();
523 
524  create_engine_.set_current_era_index(new_era_index, true);
525  eras_menu_button_->set_value(new_era_index);
526  }
527 
529  DBG_MP << "sync_with_depcheck: correcting scenario";
530 
531  // Match scenario and scenario type
532  const auto [new_level_type, new_level_index] = create_engine_.find_level_by_id(create_engine_.dependency_manager().get_scenario());
533  const bool different_type = new_level_type != create_engine_.current_level_type();
534 
535  if(new_level_index != -1) {
536  create_engine_.set_current_level_type(new_level_type);
537  create_engine_.set_current_level(new_level_index);
538  selected_game_index_ = new_level_index;
539 
540 #ifdef __cpp_lib_ranges
541  auto iter = std::ranges::find(level_types_, new_level_type, &level_type_info::first);
542 #else
543  auto iter = std::find_if(level_types_.begin(), level_types_.end(),
544  [type = new_level_type](const level_type_info& info) { return info.first == type; });
545 #endif
546 
547  auto& game_types_list = find_widget<menu_button>("game_types");
548  game_types_list.set_value(std::distance(level_types_.begin(), iter));
549 
550  if(different_type) {
552  } else {
553  // This function (or rather on_game_select) might be triggered by a listbox callback, in
554  // which case we cannot use display_games_of_type since it destroys the list (and its
555  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
556  // sync_with_depcheck call never changes the game type and goes to this branch instead.
557  find_widget<listbox>("games_list").select_row(new_level_index);
558 
559  // Override the last selection so on_game_select selects the new level
561 
562  on_game_select();
563  }
564  }
565  }
566 
568  DBG_MP << "sync_with_depcheck: correcting modifications";
570  }
571 
573  DBG_MP << "sync_with_depcheck: end";
574 }
575 
576 template<typename T>
577 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
578 {
579  create_engine_.apply_level_filter(find_widget<T>(id).get_value());
580 
581  listbox& game_list = find_widget<listbox>("games_list");
582 
583  boost::dynamic_bitset<> filtered(game_list.get_item_count());
585  filtered[i] = true;
586  }
587 
588  game_list.set_row_shown(filtered);
589 
590  if(do_select) {
591  on_game_select();
592  }
593 }
594 
596 {
597  const int selected_game = find_widget<listbox>("games_list").get_selected_row();
598 
599  if(selected_game == selected_game_index_) {
600  return;
601  }
602 
603  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
605 
607 
609 
610  update_details();
611 
612  // General settings
613  const bool can_select_era = create_engine_.current_level().allow_era_choice();
614 
615  if(!can_select_era) {
616  eras_menu_button_->set_label(_("No eras available for this game."));
617  } else {
619  }
620 
621  eras_menu_button_->set_active(can_select_era);
622 
623  // Custom options
624  options_manager_->update_game_options();
625 
626  // Game settings
628 }
629 
631 {
632  const int i = find_widget<listbox>("tab_bar").get_selected_row();
633  find_widget<stacked_widget>("pager").select_layer(i);
634 }
635 
636 void mp_create_game::on_mod_toggle(const std::string& id, toggle_button* sender)
637 {
638  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
639  ERR_MP << "ignoring on_mod_toggle that is already set";
640  return;
641  }
642 
644 
646 
647  options_manager_->update_mod_options();
648 }
649 
651 {
653 
655 
657 
658  options_manager_->update_era_options();
659 }
660 
662 {
663  selected_rfm_index_ = find_widget<menu_button>("random_faction_mode").get_value();
664 }
665 
666 void mp_create_game::show_description(const std::string& new_description)
667 {
668  styled_widget& description = find_widget<styled_widget>("description");
669 
670  description.set_label(!new_description.empty() ? new_description : _("No description available."));
671  description.set_use_markup(true);
672 }
673 
675 {
676  const int index = find_widget<menu_button>("game_types").get_value();
677 
679  find_widget<button>("save_preset").set_active(level_types_[index].first == level_type::type::scenario);
680 }
681 
683 {
685 
686  listbox& list = find_widget<listbox>("games_list");
687 
688  list.clear();
689 
692  widget_item item;
693 
694  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
695  item["label"] = game->icon();
696  data.emplace("game_icon", item);
697  }
698 
699  item["label"] = game->name();
700  data.emplace("game_name", item);
701 
702  grid& rg = list.add_row(data);
703 
704  auto& icon = rg.find_widget<image>("game_icon");
705  if(icon.get_label().empty()) {
706  icon.set_visible(gui2::widget::visibility::invisible);
707  }
708  }
709 
710  if(!level.empty() && !list.get_rows_shown().empty()) {
711  // Recalculate which rows should be visible
712  on_filter_change<slider>("num_players", false);
713  on_filter_change<text_box>("game_filter", false);
714 
715  int level_index = create_engine_.find_level_by_id(level).second;
716  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
717  list.select_row(level_index);
718  }
719  }
720 
721  const bool is_random_map = type == level_type::type::random_map;
722 
723  find_widget<button>("random_map_regenerate").set_active(is_random_map);
724  find_widget<button>("random_map_settings").set_active(is_random_map);
725 
726  // Override the last selection so on_game_select selects the new level
728 
729  on_game_select();
730 }
731 
733 {
735 
737 }
738 
740 {
742 
743  update_details();
744 }
745 
746 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
747 {
748  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
749  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
750 }
751 
753 {
754  styled_widget& players = find_widget<styled_widget>("map_num_players");
755  styled_widget& map_size = find_widget<styled_widget>("map_size");
756 
757  if(create_engine_.current_level_type() == level_type::type::random_map) {
758  // If the current random map doesn't have data, generate it
760  create_engine_.current_level().data()["map_data"].empty() &&
761  create_engine_.current_level().data()["map_file"].empty()) {
763  }
764 
765  find_widget<button>("random_map_settings").set_active(create_engine_.generator_has_settings());
766  }
767 
769 
770  // Reset the mp_parameters with the defaults
772 
773  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
774  std::string title = create_engine_.current_level().name();
775  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
776  find_widget<styled_widget>("game_title").set_label(title);
777 
778 
780  case level_type::type::preset:
781  case level_type::type::scenario:
782  case level_type::type::user_map:
783  case level_type::type::user_scenario:
784  case level_type::type::random_map: {
785  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
786 
787  assert(current_scenario);
788 
790 
791  find_widget<stacked_widget>("minimap_stack").select_layer(0);
792 
793  if(current_scenario->data()["map_data"].empty()) {
794  saved_game::expand_map_file(current_scenario->data());
795  current_scenario->set_metadata();
796  }
797 
798  find_widget<minimap>("minimap").set_map_data(current_scenario->data()["map_data"]);
799 
800  players.set_label(std::to_string(current_scenario->num_players()));
801  map_size.set_label(current_scenario->map_size());
802 
803  break;
804  }
805  case level_type::type::campaign:
806  case level_type::type::sp_campaign: {
807  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
808 
809  assert(current_campaign);
810 
811  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
812 
813  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
814 
815  find_widget<stacked_widget>("minimap_stack").select_layer(1);
816  find_widget<image>("campaign_image").set_image(img);
817 
818  const int p_min = current_campaign->min_players();
819  const int p_max = current_campaign->max_players();
820 
821  if(p_max > p_min) {
822  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
823  } else {
824  players.set_label(std::to_string(p_min));
825  }
826 
828 
829  break;
830  }
831  }
832 
833  // This needs to be at the end, since errors found expanding the map data are put into the description
835 }
836 
838 {
840  if(level["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
841  use_map_settings_->widget_set_enabled(false, false);
843  } else {
845  }
846 
848 
849  const bool use_map_settings = create_engine_.get_state().mp_settings().use_map_settings;
850 
851  fog_ ->widget_set_enabled(!use_map_settings, false);
852  shroud_ ->widget_set_enabled(!use_map_settings, false);
853  start_time_ ->widget_set_enabled(!use_map_settings, false);
854 
855  turns_ ->widget_set_enabled(!use_map_settings, false);
856  gold_ ->widget_set_enabled(!use_map_settings, false);
857  support_ ->widget_set_enabled(!use_map_settings, false);
858  experience_ ->widget_set_enabled(!use_map_settings, false);
859 
860  const bool time_limit = time_limit_->get_widget_value();
861 
862  init_turn_limit_->widget_set_enabled(time_limit, false);
863  turn_bonus_ ->widget_set_enabled(time_limit, false);
864  reservoir_ ->widget_set_enabled(time_limit, false);
865  action_bonus_ ->widget_set_enabled(time_limit, false);
866 
867  find_widget<button>("reset_timer_defaults").set_active(time_limit);
868 
872 
877 }
878 
880 {
882 
883  if(!load.load_multiplayer_game()) {
884  return;
885  }
886 
887  if(load.data().cancel_orders) {
889  }
890 
892 }
893 
895 {
896  config preset;
897  preset["scenario"] = create_engine_.current_level().id();
898  preset["era"] = create_engine_.current_era().id;
899  preset["fog"] = fog_->get_widget_value();
900  preset["shroud"] = shroud_->get_widget_value();
901  preset["village_gold"] = gold_->get_widget_value();
902  preset["village_support"] = support_->get_widget_value();
903  preset["experience_modifier"] = experience_->get_widget_value();
904  preset["countdown"] = time_limit_->get_widget_value();
905  preset["countdown_turn_limit"] = init_turn_limit_->get_widget_value();
906  preset["countdown_action_bonus"] = action_bonus_->get_widget_value();
907  preset["countdown_turn_bonus"] = turn_bonus_->get_widget_value();
908  preset["countdown_reservoir"] = reservoir_->get_widget_value();
909  preset["random_start_time"] = start_time_->get_widget_value();
910  preset["shuffle_sides"] = shuffle_sides_->get_widget_value();
911  preset["turns"] = turns_->get_widget_value();
912  preset["observer"] = observers_->get_widget_value();
913  preset["use_map_settings"] = use_map_settings_->get_widget_value();
914 
915  prefs::get().add_game_preset(std::move(preset));
916 }
917 
918 std::vector<std::string> mp_create_game::get_active_mods()
919 {
920  int i = 0;
921  std::set<std::string> res;
923  if(mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").get_value_bool()) {
924  res.insert(mod->id);
925  }
926  ++i;
927  }
928  return std::vector<std::string>(res.begin(), res.end());
929 }
930 
931 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
932 {
933  std::set<std::string> val2(val.begin(), val.end());
934  int i = 0;
935  std::set<std::string> res;
937  mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").set_value_bool(val2.find(mod->id) != val2.end());
938  ++i;
939  }
940 }
941 
943 {
944  // This allows the defaults to be returned by the pref getters below
949 
950  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time().count());
951  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus().count());
952  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time().count());
953  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus().count());
954 }
955 
957 {
959  gui2::show_transient_error_message(_("The selected game has no sides!"));
960  return false;
961  }
962 
964  std::stringstream msg;
965  // TRANSLATORS: This sentence will be followed by some details of the error, most likely the "Map could not be loaded" message from create_engine.cpp
966  msg << _("The selected game cannot be created.");
967  msg << "\n\n";
970  return false;
971  }
972 
973  if(!create_engine_.is_campaign()) {
974  return true;
975  }
976 
977  return create_engine_.select_campaign_difficulty() != "CANCEL";
978 }
979 
981 {
982  plugins_context_.reset();
983 
984  // Show all tabs so that find_widget works correctly
985  find_widget<stacked_widget>("pager").select_layer(-1);
986 
987  if(get_retval() == LOAD_GAME) {
989 
990  // We don't need the LOAD_GAME retval past this point. For convenience, reset it to OK so we can use the execute wrapper, then exit.
992  return;
993  }
994 
995  if(get_retval() == retval::OK) {
997  prefs::get().set_mp_level_type(static_cast<int>(create_engine_.current_level_type()));
998  prefs::get().set_mp_level(create_engine_.current_level().id());
999  prefs::get().set_mp_era(create_engine_.current_era().id);
1000 
1002 
1003  if(create_engine_.current_level_type() == level_type::type::campaign ||
1004  create_engine_.current_level_type() == level_type::type::sp_campaign) {
1006  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
1008  } else {
1009  // This means define= doesn't work for randomly generated scenarios
1011  }
1012 
1014 
1016 
1017  std::vector<const config*> entry_points;
1018  std::vector<std::string> entry_point_titles;
1019 
1020  const auto& tagname = create_engine_.get_state().classification().get_tagname();
1021 
1022  if(tagname == "scenario") {
1023  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
1024  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
1025  const bool is_first = scenario["id"] == first_scenario;
1026  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
1027  const std::string& title = !scenario["new_game_title"].empty()
1028  ? scenario["new_game_title"]
1029  : scenario["name"];
1030 
1031  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
1032  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
1033  }
1034  }
1035  }
1036 
1038  if(entry_points.size() > 1) {
1039  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
1040 
1041  dlg.set_single_button(true);
1042  dlg.show();
1043 
1044  const config& scenario = *entry_points[dlg.selected_index()];
1045 
1046  params.hash = scenario.hash();
1048  }
1049 
1051 
1052  if(!create_engine_.current_level().data()["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
1053  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
1054  const int num_turns = turns_->get_widget_value();
1055  params.num_turns = num_turns < ::settings::turns_max ? num_turns : - 1;
1056  params.village_gold = gold_->get_widget_value();
1060  params.fog_game = fog_->get_widget_value();
1061  params.shroud_game = shroud_->get_widget_value();
1062 
1063  // write to scenario
1065 
1066  if(params.random_start_time) {
1067  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
1068  scenario["random_start_time"] = true;
1069  }
1070  } else {
1071  scenario["random_start_time"] = false;
1072  }
1073 
1074  scenario["experience_modifier"] = params.xp_modifier;
1075  scenario["turns"] = params.num_turns;
1076 
1077  for(config& side : scenario.child_range("side")) {
1078  if(!params.use_map_settings) {
1079  side["fog"] = params.fog_game;
1080  side["shroud"] = params.shroud_game;
1081  side["village_gold"] = params.village_gold;
1082  side["village_support"] = params.village_support;
1083  } else {
1084  if(side["fog"].empty()) {
1085  side["fog"] = params.fog_game;
1086  }
1087 
1088  if(side["shroud"].empty()) {
1089  side["shroud"] = params.shroud_game;
1090  }
1091 
1092  if(side["village_gold"].empty()) {
1093  side["village_gold"] = params.village_gold;
1094  }
1095 
1096  if(side["village_support"].empty()) {
1097  side["village_support"] = params.village_support;
1098  }
1099  }
1100  }
1101  }
1102 
1104  params.mp_countdown_init_time = std::chrono::seconds{init_turn_limit_->get_widget_value()};
1105  params.mp_countdown_turn_bonus = std::chrono::seconds{turn_bonus_->get_widget_value()};
1106  params.mp_countdown_reservoir_time = std::chrono::seconds{reservoir_->get_widget_value()};
1107  params.mp_countdown_action_bonus = std::chrono::seconds{action_bonus_->get_widget_value()};
1108 
1113 
1114  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
1115  params.mode = type;
1116 
1117  // Since we don't have a field handling this option, we need to save the value manually
1118  prefs::get().set_random_faction_mode(random_faction_mode::get_string(type));
1119 
1120  // Save custom option settings
1121  params.options = options_manager_->get_options_config();
1122  prefs::get().set_options(options_manager_->get_options_config());
1123 
1124  // Set game name
1125  const std::string name = find_widget<text_box>("game_name").get_value();
1126  if(!name.empty() && (name != settings::game_name_default())) {
1127  params.name = name;
1128  }
1129 
1130  // Set game password
1131  const std::string password = find_widget<text_box>("game_password").get_value();
1132  if(!password.empty()) {
1133  params.password = password;
1134  }
1135  }
1136 }
1137 
1138 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
bool empty() const
Definition: config.cpp:845
std::string hash() const
Definition: config.cpp:1279
std::ostringstream wrapper.
Definition: formatter.hpp:40
campaign_type::type type
std::string get_tagname() const
std::string campaign
The id of the campaign being played.
static game_config_manager * get()
Abstract base class for all modal dialogs.
bool show(const unsigned auto_close_time=0)
Shows the window.
std::pair< level_type::type, std::string > level_type_info
void display_games_of_type(level_type::type type, const std::string &level)
virtual void post_show() override
Actions to be taken after the window has been shown.
void set_active_mods(const std::vector< std::string > &val)
void show_description(const std::string &new_description)
virtual void pre_show() override
Actions to be taken before showing the window.
void on_mod_toggle(const std::string &id, toggle_button *sender)
std::unique_ptr< mp_options_helper > options_manager_
std::vector< level_type_info > level_types_
ng::create_engine create_engine_
void on_filter_change(const std::string &id, bool do_select)
static void quick_mp_setup(saved_game &state, const config presets)
presets needs to be a copy! Otherwise you'll get segfaults when clicking the Join button since it res...
std::vector< std::string > get_active_mods()
bool dialog_exit_hook()
Dialog exit hook to bring up the difficulty dialog when starting a campaign.
int convert_to_game_filtered_index(const unsigned int initial_index)
field_bool * use_map_settings_
All fields are also in the normal field vector, but they need to be manually controlled as well so ad...
std::unique_ptr< plugins_context > plugins_context_
void set_single_button(bool value)
Sets whether the Cancel button should be hidden or not.
int selected_index() const
Returns the selected item index after displaying.
styled_widget * get_widget()
Definition: field.hpp:192
void widget_set_enabled(const bool enable, const bool sync)
Enables a widget.
Definition: field.hpp:158
void set_widget_value(CT value)
Sets the value of the field.
Definition: field.hpp:343
T get_widget_value()
Gets the value of the field.
Definition: field.hpp:378
Base container class.
Definition: grid.hpp:32
The listbox class.
Definition: listbox.hpp:41
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:163
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:92
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:256
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:267
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:251
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:147
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:153
void set_selected(unsigned selected, bool fire_event=true)
virtual void set_value(unsigned value, bool fire_event=false) override
Inherited from selectable_item.
Definition: menu_button.hpp:58
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual unsigned get_value() const override
Inherited from selectable_item.
Definition: menu_button.hpp:55
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition: menu_button.cpp:74
void set_value_bool(bool value, bool fire_event=false)
void set_tooltip(const t_string &tooltip)
virtual void set_label(const t_string &text)
virtual void set_use_markup(bool use_markup)
A widget that allows the user to input text in single line.
Definition: text_box.hpp:125
@ invisible
The user set the widget invisible, that means:
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:747
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:395
void keyboard_capture(widget *widget)
Definition: window.cpp:1201
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1215
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition: window.hpp:448
int get_retval()
Definition: window.hpp:402
int max_players() const
int min_players() const
void set_current_era_id(const std::string &id, bool force=false)
int find_extra_by_id(const MP_EXTRA extra_type, const std::string &id) const
std::vector< std::size_t > get_filtered_level_indices(level_type::type type) const
bool toggle_mod(const std::string &id, bool force=false)
void set_current_era_index(const std::size_t index, bool force=false)
std::string select_campaign_difficulty(int set_value=-1)
select_campaign_difficulty
std::vector< level_ptr > get_levels_by_type(level_type::type type) const
bool generator_has_settings() const
std::vector< std::string > & active_mods()
bool current_level_has_side_data()
Returns true if the current level has one or more [side] tags.
bool generator_assigned() const
level_type::type current_level_type() const
std::vector< extras_metadata_ptr > & get_extras_by_type(const MP_EXTRA extra_type)
const std::vector< extras_metadata_ptr > & get_const_extras_by_type(const MP_EXTRA extra_type) const
void apply_level_filter(const std::string &name)
const depcheck::manager & dependency_manager() const
const extras_metadata & current_era() const
void set_current_level_type(const level_type::type type)
std::pair< level_type::type, int > find_level_by_id(const std::string &id) const
saved_game & get_state()
bool is_campaign() const
Wrapper to simplify the is-type-campaign-or-sp-campaign check.
void prepare_for_campaign(const std::string &difficulty="")
std::vector< level_ptr > get_levels_by_type_unfiltered(level_type::type type) const
const mp_game_settings & get_parameters()
void set_current_level(const std::size_t index)
void prepare_for_era_and_mods()
std::size_t current_era_index() const
void init_generated_level_data()
level & current_level() const
const std::string & get_scenario() const
Returns the selected scenario.
Definition: depcheck.hpp:116
int get_era_index() const
Returns the selected era.
Definition: depcheck.cpp:398
bool is_modification_active(int index) const
Tells whether a certain mod is activated.
Definition: depcheck.cpp:441
const std::vector< std::string > & get_modifications() const
Returns the enabled modifications.
Definition: depcheck.hpp:123
Base class for all level type classes.
const config & data() const
virtual bool can_launch_game() const =0
virtual std::string id() const
virtual std::string name() const
virtual void set_metadata()=0
virtual bool allow_era_choice() const
virtual std::string icon() const
virtual std::string description() const
int num_players() const
void set_metadata()
std::string map_size() const
int village_support()
std::chrono::seconds countdown_turn_bonus()
void set_countdown_reservoir_time(const std::chrono::seconds &value)
void set_village_gold(int value)
void clear_countdown_init_time()
static prefs & get()
void set_xp_modifier(int value)
std::chrono::seconds countdown_init_time()
void set_countdown_turn_bonus(const std::chrono::seconds &value)
void set_options(const config &values)
void set_village_support(int value)
std::chrono::seconds countdown_action_bonus()
int village_gold()
void clear_countdown_turn_bonus()
void clear_countdown_reservoir_time()
const std::vector< std::string > & modifications(bool mp=true)
std::chrono::seconds countdown_reservoir_time()
void add_game_preset(config &&preset_data)
void set_modifications(const std::vector< std::string > &value, bool mp=true)
void set_countdown_init_time(const std::chrono::seconds &value)
int xp_modifier()
void set_countdown_action_bonus(const std::chrono::seconds &value)
void clear_countdown_action_bonus()
game_classification & classification()
Definition: saved_game.hpp:56
void clear()
Definition: saved_game.cpp:833
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
void set_scenario(config scenario)
Definition: saved_game.cpp:615
static void expand_map_file(config &scenario)
reads scenario["map_file"]
Definition: saved_game.cpp:499
config & get_starting_point()
Definition: saved_game.cpp:631
void cancel_orders()
Definition: saved_game.cpp:735
The class for loading a savefile.
Definition: savegame.hpp:101
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:210
static bool is_start_ToD(const std::string &)
const config * cfg
Implements some helper classes to ease adding fields to a dialog and hide the synchronization needed.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
static std::string _(const char *str)
Definition: gettext.hpp:97
Standard logging facilities (interface).
General settings and defaults for scenarios.
#define ERR_MP
#define DBG_MP
static lg::log_domain log_mp_create("mp/create")
#define UPDATE_ATTRIBUTE(field, convert)
const std::string unicode_em_dash
Definition: constants.cpp:44
Game configuration data as global variables.
Definition: build_info.cpp:61
const bool & debug
Definition: game_config.cpp:95
REGISTER_DIALOG(editor_edit_unit)
static const int LOAD_GAME
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:189
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:37
void bind_default_status_label(W &source)
Binds a status label using the default value getter and default target ID.
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
void show_transient_error_message(const std::string &message, const std::string &image, const bool message_use_markup)
Shows a transient error message to the user.
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
Functions to load and save images from/to disk.
logger & info()
Definition: log.cpp:351
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition: markup.cpp:31
bool shroud_game_default(ng::create_engine &create)
int village_support_default(ng::create_engine &create)
int village_gold_default(ng::create_engine &create)
int num_turns_default(ng::create_engine &create)
std::string game_name_default()
const int turns_max
maximum number of turns
bool fog_game_default(ng::create_engine &create)
bool random_start_time_default(ng::create_engine &create)
int xp_modifier_default(ng::create_engine &create)
void set_default_values(ng::create_engine &create)
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
constexpr auto filter
Definition: ranges.hpp:38
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
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
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:188
string_enums::enum_base< random_faction_mode_defines > random_faction_mode
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
random_faction_mode::type mode
std::chrono::seconds mp_countdown_reservoir_time
std::chrono::seconds mp_countdown_action_bonus
std::chrono::seconds mp_countdown_turn_bonus
std::chrono::seconds mp_countdown_init_time
std::string mp_scenario
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57