The Battle for Wesnoth  1.19.7+dev
mp_create_game.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 
45 #include <boost/algorithm/string.hpp>
46 
47 static lg::log_domain log_mp_create("mp/create");
48 
49 #define DBG_MP LOG_STREAM(debug, log_mp_create)
50 #define WRN_MP LOG_STREAM(warn, log_mp_create)
51 #define ERR_MP LOG_STREAM(err, log_mp_create)
52 
53 namespace gui2::dialogs
54 {
55 
56 // Special retval value for loading a game
57 static const int LOAD_GAME = 100;
58 
60 
61 mp_create_game::mp_create_game(saved_game& state, bool local_mode)
62  : modal_dialog(window_id())
63  , create_engine_(state)
64  , config_engine_()
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  };
129 
130  utils::erase_if(level_types_, [this](level_type_info& type_info) {
131  return create_engine_.get_levels_by_type_unfiltered(type_info.first).empty();
132  });
133 
134  set_show_even_without_video(true);
135 
136  create_engine_.init_active_mods();
137 
138  create_engine_.get_state().clear();
139  create_engine_.get_state().classification().type = campaign_type::type::multiplayer;
140 
141  // Need to set this in the constructor, pre_show() is too late
142  set_allow_plugin_skip(false);
143 }
144 
146 {
147  find_widget<text_box>("game_name").set_value(local_mode_ ? "" : ng::configure_engine::game_name_default());
148 
150  find_widget<button>("random_map_regenerate"),
151  std::bind(&mp_create_game::regenerate_random_map, this));
152 
154  find_widget<button>("random_map_settings"),
155  std::bind(&mp_create_game::show_generator_settings, this));
156 
158  find_widget<button>("load_game"),
159  std::bind(&mp_create_game::load_game_callback, this));
160 
161  // Custom dialog close hook
163 
164  //
165  // Set up the options manager. Needs to be done before selecting an initial tab
166  //
168 
169  //
170  // Set up filtering
171  //
172  connect_signal_notify_modified(find_widget<slider>("num_players"),
173  std::bind(&mp_create_game::on_filter_change<slider>, this, "num_players", true));
174 
175  text_box& filter = find_widget<text_box>("game_filter");
176 
177  filter.on_modified([this](const auto&) { on_filter_change<text_box>("game_filter", true); });
178 
179  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
181 
182  //
183  // Set up game types menu_button
184  //
185  std::vector<config> game_types;
186  for(level_type_info& type_info : level_types_) {
187  game_types.emplace_back("label", type_info.second);
188  }
189 
190  if(game_types.empty()) {
191  gui2::show_transient_message("", _("No games found."));
192  throw game::error(_("No games found."));
193  }
194 
195  menu_button& game_menu_button = find_widget<menu_button>("game_types");
196 
197  // Helper to make sure the initially selected level type is valid
198  auto get_initial_type_index = [this]()->int {
199  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
200  return info.first == *level_type::get_enum(prefs::get().mp_level_type());
201  });
202 
203  if(index != level_types_.end()) {
204  return std::distance(level_types_.begin(), index);
205  }
206 
207  return 0;
208  };
209 
210  game_menu_button.set_values(game_types, get_initial_type_index());
211 
212  connect_signal_notify_modified(game_menu_button,
213  std::bind(&mp_create_game::update_games_list, this));
214 
215  //
216  // Set up mods list
217  //
218  mod_list_ = &find_widget<listbox>("mod_list");
219 
220  const auto& activemods = prefs::get().modifications();
223  widget_item item;
224 
225  item["label"] = mod->name;
226  data.emplace("mod_name", item);
227 
228  grid* row_grid = &mod_list_->add_row(data);
229 
230  row_grid->find_widget<toggle_panel>("panel").set_tooltip(mod->description);
231 
232  toggle_button& mog_toggle = row_grid->find_widget<toggle_button>("mod_active_state");
233 
234  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
235  create_engine_.active_mods().push_back(mod->id);
236  mog_toggle.set_value_bool(true);
237  }
238 
239  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
240  }
241 
242  // No mods, hide the header
243  if(mod_list_->get_item_count() <= 0) {
244  find_widget<styled_widget>("mods_header").set_visible(widget::visibility::invisible);
245  }
246 
247  //
248  // Set up eras menu_button
249  //
250  eras_menu_button_ = &find_widget<menu_button>("eras");
251 
252  std::vector<config> era_names;
254  era_names.emplace_back("label", era->name, "tooltip", era->description);
255  }
256 
257  if(era_names.empty()) {
258  gui2::show_transient_message("", _("No eras found."));
259  throw config::error(_("No eras found"));
260  }
261 
262  eras_menu_button_->set_values(era_names);
263 
265  std::bind(&mp_create_game::on_era_select, this));
266 
267  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().mp_era());
268  if(era_selection >= 0) {
269  eras_menu_button_->set_selected(era_selection);
270  }
271 
272  on_era_select();
273 
274  //
275  // Set up random faction mode menu_button
276  //
277  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
278 
279  menu_button& rfm_menu_button = find_widget<menu_button>("random_faction_mode");
280  rfm_menu_button.set_selected(initial_index);
281 
282  connect_signal_notify_modified(rfm_menu_button,
284 
286 
287  //
288  // Set up the setting status labels
289  //
290  bind_status_label<slider>(this, turns_->id());
291  bind_status_label<slider>(this, gold_->id());
292  bind_status_label<slider>(this, support_->id());
293  bind_status_label<slider>(this, experience_->id());
294  bind_status_label<slider>(this, init_turn_limit_->id());
295  bind_status_label<slider>(this, turn_bonus_->id());
296  bind_status_label<slider>(this, reservoir_->id());
297  bind_status_label<slider>(this, action_bonus_->id());
298 
299  //
300  // Timer reset button
301  //
303  find_widget<button>("reset_timer_defaults"),
304  std::bind(&mp_create_game::reset_timer_settings, this));
305 
306  //
307  // Disable certain settings if we're playing a local game.
308  //
309  if(local_mode_) {
310  find_widget<text_box>("game_name").set_active(false);
311  find_widget<text_box>("game_password").set_active(false);
312 
313  observers_->widget_set_enabled(false, false);
314  strict_sync_->widget_set_enabled(false, false);
315  private_replay_->widget_set_enabled(false, false);
316  }
317 
318  //
319  // Set up tab control
320  //
321  listbox& tab_bar = find_widget<listbox>("tab_bar");
322 
324  std::bind(&mp_create_game::on_tab_select, this));
325 
326  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
327  // This ensures settings (especially game settings) widgets are appropriately updated when
328  // a new game is selected, regardless of which settings tab is active at the time.
329  find_widget<stacked_widget>("pager").set_find_in_all_layers(true);
330 
331  // We call on_tab_select farther down.
332 
333  //
334  // Main games list
335  //
336  listbox& list = find_widget<listbox>("games_list");
337 
339  std::bind(&mp_create_game::on_game_select, this));
340 
341  add_to_keyboard_chain(&list);
342 
343  // This handles the initial game selection as well
344  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().mp_level());
345 
346  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
347  on_tab_select();
348 
349  //
350  // Set up the Lua plugin context
351  //
352  plugins_context_.reset(new plugins_context("Multiplayer Create"));
353 
354  plugins_context_->set_callback("create", [this](const config&) { set_retval(retval::OK); }, false);
355  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
356  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
357 
358 #define UPDATE_ATTRIBUTE(field, convert) \
359  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
360 
361  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
362  UPDATE_ATTRIBUTE(turns, to_int);
363  UPDATE_ATTRIBUTE(gold, to_int);
364  UPDATE_ATTRIBUTE(support, to_int);
365  UPDATE_ATTRIBUTE(experience, to_int);
366  UPDATE_ATTRIBUTE(start_time, to_bool);
367  UPDATE_ATTRIBUTE(fog, to_bool);
368  UPDATE_ATTRIBUTE(shroud, to_bool);
369  UPDATE_ATTRIBUTE(time_limit, to_bool);
370  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
371  UPDATE_ATTRIBUTE(turn_bonus, to_int);
372  UPDATE_ATTRIBUTE(reservoir, to_int);
373  UPDATE_ATTRIBUTE(action_bonus, to_int);
374  UPDATE_ATTRIBUTE(observers, to_bool);
375  UPDATE_ATTRIBUTE(strict_sync, to_bool);
376  UPDATE_ATTRIBUTE(private_replay, to_bool);
377  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
378  }, true);
379 
380 #undef UPDATE_ATTRIBUTE
381 
382  plugins_context_->set_callback("set_name", [this](const config& cfg) {
383  config_engine_->set_game_name(cfg["name"]); }, true);
384 
385  plugins_context_->set_callback("set_password", [this](const config& cfg) {
386  config_engine_->set_game_password(cfg["password"]); }, true);
387 
388  plugins_context_->set_callback("select_level", [this](const config& cfg) {
389  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
391  }, true);
392 
393  plugins_context_->set_callback("select_type", [this](const config& cfg) {
394  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
395 
396  plugins_context_->set_callback("select_era", [this](const config& cfg) {
397  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
398 
399  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
400  on_mod_toggle(cfg["id"].str(), nullptr);
401  }, true);
402 
403  plugins_context_->set_accessor("get_selected", [this](const config&) {
404  const ng::level& current_level = create_engine_.current_level();
405  return config {
406  "id", current_level.id(),
407  "name", current_level.name(),
408  "icon", current_level.icon(),
409  "description", current_level.description(),
410  "allow_era_choice", current_level.allow_era_choice(),
412  };
413  });
414 
415  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
416  const std::string id = cfg["id"].str();
417  return config {
418  "index", create_engine_.find_level_by_id(id).second,
420  };
421  });
422 
423  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
425  });
426 
427  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
429  });
430 }
431 
433 {
434  DBG_MP << "sync_with_depcheck: start";
435 
437  DBG_MP << "sync_with_depcheck: correcting era";
438  const int new_era_index = create_engine_.dependency_manager().get_era_index();
439 
440  create_engine_.set_current_era_index(new_era_index, true);
441  eras_menu_button_->set_value(new_era_index);
442  }
443 
445  DBG_MP << "sync_with_depcheck: correcting scenario";
446 
447  // Match scenario and scenario type
449  const bool different_type = new_level_index.first != create_engine_.current_level_type();
450 
451  if(new_level_index.second != -1) {
452  create_engine_.set_current_level_type(new_level_index.first);
453  create_engine_.set_current_level(new_level_index.second);
454  selected_game_index_ = new_level_index.second;
455 
456  auto& game_types_list = find_widget<menu_button>("game_types");
457  game_types_list.set_value(std::distance(level_types_.begin(), std::find_if(level_types_.begin(), level_types_.begin(), [&](const level_type_info& info){ return info.first == new_level_index.first; })));
458 
459  if(different_type) {
460  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
461  } else {
462  // This function (or rather on_game_select) might be triggered by a listbox callback, in
463  // which case we cannot use display_games_of_type since it destroys the list (and its
464  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
465  // sync_with_depcheck call never changes the game type and goes to this branch instead.
466  find_widget<listbox>("games_list").select_row(new_level_index.second);
467 
468  // Override the last selection so on_game_select selects the new level
470 
471  on_game_select();
472  }
473  }
474  }
475 
477  DBG_MP << "sync_with_depcheck: correcting modifications";
479  }
480 
482  DBG_MP << "sync_with_depcheck: end";
483 }
484 
485 template<typename T>
486 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
487 {
488  create_engine_.apply_level_filter(find_widget<T>(id).get_value());
489 
490  listbox& game_list = find_widget<listbox>("games_list");
491 
492  boost::dynamic_bitset<> filtered(game_list.get_item_count());
494  filtered[i] = true;
495  }
496 
497  game_list.set_row_shown(filtered);
498 
499  if(do_select) {
500  on_game_select();
501  }
502 }
503 
505 {
506  const int selected_game = find_widget<listbox>("games_list").get_selected_row();
507 
508  if(selected_game == selected_game_index_) {
509  return;
510  }
511 
512  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
514 
516 
518 
519  update_details();
520 
521  // General settings
522  const bool can_select_era = create_engine_.current_level().allow_era_choice();
523 
524  if(!can_select_era) {
525  eras_menu_button_->set_label(_("No eras available for this game."));
526  } else {
528  }
529 
530  eras_menu_button_->set_active(can_select_era);
531 
532  // Custom options
533  options_manager_->update_game_options();
534 
535  // Game settings
537 }
538 
540 {
541  const int i = find_widget<listbox>("tab_bar").get_selected_row();
542  find_widget<stacked_widget>("pager").select_layer(i);
543 }
544 
545 void mp_create_game::on_mod_toggle(const std::string& id, toggle_button* sender)
546 {
547  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
548  ERR_MP << "ignoring on_mod_toggle that is already set";
549  return;
550  }
551 
553 
555 
556  options_manager_->update_mod_options();
557 }
558 
560 {
562 
564 
566 
567  options_manager_->update_era_options();
568 }
569 
571 {
572  selected_rfm_index_ = find_widget<menu_button>("random_faction_mode").get_value();
573 }
574 
575 void mp_create_game::show_description(const std::string& new_description)
576 {
577  styled_widget& description = find_widget<styled_widget>("description");
578 
579  description.set_label(!new_description.empty() ? new_description : _("No description available."));
580  description.set_use_markup(true);
581 }
582 
584 {
585  const int index = find_widget<menu_button>("game_types").get_value();
586 
588 }
589 
591 {
593 
594  listbox& list = find_widget<listbox>("games_list");
595 
596  list.clear();
597 
600  widget_item item;
601 
602  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
603  item["label"] = game->icon();
604  data.emplace("game_icon", item);
605  }
606 
607  item["label"] = game->name();
608  data.emplace("game_name", item);
609 
610  grid& rg = list.add_row(data);
611 
612  auto& icon = rg.find_widget<image>("game_icon");
613  if(icon.get_label().empty()) {
614  icon.set_visible(gui2::widget::visibility::invisible);
615  }
616  }
617 
618  if(!level.empty() && !list.get_rows_shown().empty()) {
619  // Recalculate which rows should be visible
620  on_filter_change<slider>("num_players", false);
621  on_filter_change<text_box>("game_filter", false);
622 
623  int level_index = create_engine_.find_level_by_id(level).second;
624  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
625  list.select_row(level_index);
626  }
627  }
628 
629  const bool is_random_map = type == level_type::type::random_map;
630 
631  find_widget<button>("random_map_regenerate").set_active(is_random_map);
632  find_widget<button>("random_map_settings").set_active(is_random_map);
633 
634  // Override the last selection so on_game_select selects the new level
636 
637  on_game_select();
638 }
639 
641 {
643 
645 }
646 
648 {
650 
651  update_details();
652 }
653 
654 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
655 {
656  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
657  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
658 }
659 
661 {
662  styled_widget& players = find_widget<styled_widget>("map_num_players");
663  styled_widget& map_size = find_widget<styled_widget>("map_size");
664 
665  if(create_engine_.current_level_type() == level_type::type::random_map) {
666  // If the current random map doesn't have data, generate it
668  create_engine_.current_level().data()["map_data"].empty() &&
669  create_engine_.current_level().data()["map_file"].empty()) {
671  }
672 
673  find_widget<button>("random_map_settings").set_active(create_engine_.generator_has_settings());
674  }
675 
677 
678  // Reset the config_engine with new values
680  config_engine_->update_initial_cfg(create_engine_.current_level().data());
681  config_engine_->set_default_values();
682 
683  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
684  std::string title = create_engine_.current_level().name();
685  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
686  find_widget<styled_widget>("game_title").set_label(title);
687 
688 
690  case level_type::type::scenario:
691  case level_type::type::user_map:
692  case level_type::type::user_scenario:
693  case level_type::type::random_map: {
694  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
695 
696  assert(current_scenario);
697 
699 
700  find_widget<stacked_widget>("minimap_stack").select_layer(0);
701 
702  if(current_scenario->data()["map_data"].empty()) {
703  saved_game::expand_map_file(current_scenario->data());
704  current_scenario->set_metadata();
705  }
706 
707  find_widget<minimap>("minimap").set_map_data(current_scenario->data()["map_data"]);
708 
709  players.set_label(std::to_string(current_scenario->num_players()));
710  map_size.set_label(current_scenario->map_size());
711 
712  break;
713  }
714  case level_type::type::campaign:
715  case level_type::type::sp_campaign: {
716  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
717 
718  assert(current_campaign);
719 
720  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
721 
722  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
723 
724  find_widget<stacked_widget>("minimap_stack").select_layer(1);
725  find_widget<image>("campaign_image").set_image(img);
726 
727  const int p_min = current_campaign->min_players();
728  const int p_max = current_campaign->max_players();
729 
730  if(p_max > p_min) {
731  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
732  } else {
733  players.set_label(std::to_string(p_min));
734  }
735 
737 
738  break;
739  }
740  }
741 
742  // This needs to be at the end, since errors found expanding the map data are put into the description
744 }
745 
747 {
748  if(config_engine_->force_lock_settings()) {
749  use_map_settings_->widget_set_enabled(false, false);
751  } else {
753  }
754 
755  const bool use_map_settings = use_map_settings_->get_widget_value();
756 
757  config_engine_->set_use_map_settings(use_map_settings);
758 
759  fog_ ->widget_set_enabled(!use_map_settings, false);
760  shroud_ ->widget_set_enabled(!use_map_settings, false);
761  start_time_ ->widget_set_enabled(!use_map_settings, false);
762 
763  turns_ ->widget_set_enabled(!use_map_settings, false);
764  gold_ ->widget_set_enabled(!use_map_settings, false);
765  support_ ->widget_set_enabled(!use_map_settings, false);
766  experience_ ->widget_set_enabled(!use_map_settings, false);
767 
768  const bool time_limit = time_limit_->get_widget_value();
769 
770  init_turn_limit_->widget_set_enabled(time_limit, false);
771  turn_bonus_ ->widget_set_enabled(time_limit, false);
772  reservoir_ ->widget_set_enabled(time_limit, false);
773  action_bonus_ ->widget_set_enabled(time_limit, false);
774 
775  find_widget<button>("reset_timer_defaults").set_active(time_limit);
776 
777  if(use_map_settings) {
778  fog_ ->set_widget_value(config_engine_->fog_game_default());
779  shroud_ ->set_widget_value(config_engine_->shroud_game_default());
780  start_time_->set_widget_value(config_engine_->random_start_time_default());
781 
782  turns_ ->set_widget_value(config_engine_->num_turns_default());
783  gold_ ->set_widget_value(config_engine_->village_gold_default());
784  support_ ->set_widget_value(config_engine_->village_support_default());
785  experience_->set_widget_value(config_engine_->xp_modifier_default());
786  }
787 }
788 
790 {
792 
793  if(!load.load_multiplayer_game()) {
794  return;
795  }
796 
797  if(load.data().cancel_orders) {
799  }
800 
802 }
803 
804 std::vector<std::string> mp_create_game::get_active_mods()
805 {
806  int i = 0;
807  std::set<std::string> res;
809  if(mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").get_value_bool()) {
810  res.insert(mod->id);
811  }
812  ++i;
813  }
814  return std::vector<std::string>(res.begin(), res.end());
815 }
816 
817 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
818 {
819  std::set<std::string> val2(val.begin(), val.end());
820  int i = 0;
821  std::set<std::string> res;
823  mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").set_value_bool(val2.find(mod->id) != val2.end());
824  ++i;
825  }
826 }
827 
829 {
830  // This allows the defaults to be returned by the pref getters below
835 
836  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time().count());
837  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus().count());
838  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time().count());
839  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus().count());
840 }
841 
843 {
845  gui2::show_transient_error_message(_("The selected game has no sides!"));
846  return false;
847  }
848 
850  std::stringstream msg;
851  // 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
852  msg << _("The selected game cannot be created.");
853  msg << "\n\n";
856  return false;
857  }
858 
859  if(!create_engine_.is_campaign()) {
860  return true;
861  }
862 
863  return create_engine_.select_campaign_difficulty() != "CANCEL";
864 }
865 
867 {
868  plugins_context_.reset();
869 
870  // Show all tabs so that find_widget works correctly
871  find_widget<stacked_widget>("pager").select_layer(-1);
872 
873  if(get_retval() == LOAD_GAME) {
875 
876  // 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.
878  return;
879  }
880 
881  if(get_retval() == retval::OK) {
883  prefs::get().set_mp_level_type(static_cast<int>(create_engine_.current_level_type()));
884  prefs::get().set_mp_level(create_engine_.current_level().id());
885  prefs::get().set_mp_era(create_engine_.current_era().id);
886 
888 
889  if(create_engine_.current_level_type() == level_type::type::campaign ||
890  create_engine_.current_level_type() == level_type::type::sp_campaign) {
892  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
894  } else {
895  // This means define= doesn't work for randomly generated scenarios
897  }
898 
900 
902 
903  std::vector<const config*> entry_points;
904  std::vector<std::string> entry_point_titles;
905 
906  const auto& tagname = create_engine_.get_state().classification().get_tagname();
907 
908  if(tagname == "scenario") {
909  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
910  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
911  const bool is_first = scenario["id"] == first_scenario;
912  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
913  const std::string& title = !scenario["new_game_title"].empty()
914  ? scenario["new_game_title"]
915  : scenario["name"];
916 
917  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
918  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
919  }
920  }
921  }
922 
923  if(entry_points.size() > 1) {
924  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
925 
926  dlg.set_single_button(true);
927  dlg.show();
928 
929  const config& scenario = *entry_points[dlg.selected_index()];
930 
931  create_engine_.get_state().mp_settings().hash = scenario.hash();
933  }
934 
935  config_engine_->set_use_map_settings(use_map_settings_->get_widget_value());
936 
937  if(!config_engine_->force_lock_settings()) {
938  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
939  const int num_turns = turns_->get_widget_value();
940  config_engine_->set_num_turns(num_turns < ::settings::turns_max ? num_turns : - 1);
941  config_engine_->set_village_gold(gold_->get_widget_value());
942  config_engine_->set_village_support(support_->get_widget_value());
943  config_engine_->set_xp_modifier(experience_->get_widget_value());
944  config_engine_->set_random_start_time(start_time_->get_widget_value());
945  config_engine_->set_fog_game(fog_->get_widget_value());
946  config_engine_->set_shroud_game(shroud_->get_widget_value());
947 
948  config_engine_->write_parameters();
949  }
950 
951  config_engine_->set_mp_countdown(time_limit_->get_widget_value());
952  config_engine_->set_mp_countdown_init_time(std::chrono::seconds{init_turn_limit_->get_widget_value()});
953  config_engine_->set_mp_countdown_turn_bonus(std::chrono::seconds{turn_bonus_->get_widget_value()});
954  config_engine_->set_mp_countdown_reservoir_time(std::chrono::seconds{reservoir_->get_widget_value()});
955  config_engine_->set_mp_countdown_action_bonus(std::chrono::seconds{action_bonus_->get_widget_value()});
956 
957  config_engine_->set_allow_observers(observers_->get_widget_value());
958  config_engine_->set_private_replay(private_replay_->get_widget_value());
959  config_engine_->set_oos_debug(strict_sync_->get_widget_value());
960  config_engine_->set_shuffle_sides(shuffle_sides_->get_widget_value());
961 
962  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
963  config_engine_->set_random_faction_mode(type);
964 
965  // Since we don't have a field handling this option, we need to save the value manually
966  prefs::get().set_random_faction_mode(random_faction_mode::get_string(type));
967 
968  // Save custom option settings
969  config_engine_->set_options(options_manager_->get_options_config());
970 
971  // Set game name
972  const std::string name = find_widget<text_box>("game_name").get_value();
973  if(!name.empty() && (name != ng::configure_engine::game_name_default())) {
974  config_engine_->set_game_name(name);
975  }
976 
977  // Set game password
978  const std::string password = find_widget<text_box>("game_password").get_value();
979  if(!password.empty()) {
980  config_engine_->set_game_password(password);
981  }
982  }
983 }
984 
985 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
bool empty() const
Definition: config.cpp:849
std::string hash() const
Definition: config.cpp:1283
std::ostringstream wrapper.
Definition: formatter.hpp:40
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::unique_ptr< ng::configure_engine > config_engine_
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)
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.
const std::string & id() const
Definition: field.hpp:182
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:171
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:267
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:280
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:262
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:153
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:159
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:1193
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1199
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
static std::string game_name_default()
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
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:427
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_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 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
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
void set_scenario(config scenario)
Definition: saved_game.cpp:618
static void expand_map_file(config &scenario)
reads scenario["map_file"]
Definition: saved_game.cpp:498
void cancel_orders()
Definition: saved_game.cpp:738
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:209
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:1029
static std::string _(const char *str)
Definition: gettext.hpp:93
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:94
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:203
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:37
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:319
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
const int turns_max
maximum number of turns
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:178
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
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