The Battle for Wesnoth  1.19.0-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"
41 #include "preferences/game.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 
59 // Shorthand
60 namespace prefs = preferences;
61 
63 
64 mp_create_game::mp_create_game(saved_game& state, bool local_mode)
65  : modal_dialog(window_id())
66  , create_engine_(state)
67  , config_engine_()
68  , options_manager_()
69  , selected_game_index_(-1)
70  , selected_rfm_index_(-1)
71  , use_map_settings_(register_bool( "use_map_settings", true, prefs::use_map_settings, prefs::set_use_map_settings,
72  std::bind(&mp_create_game::update_map_settings, this)))
73  , fog_(register_bool("fog", true, prefs::fog, prefs::set_fog))
74  , shroud_(register_bool("shroud", true, prefs::shroud, prefs::set_shroud))
75  , start_time_(register_bool("random_start_time", true, prefs::random_start_time, prefs::set_random_start_time))
76  , time_limit_(register_bool("time_limit", true, prefs::countdown, prefs::set_countdown,
77  std::bind(&mp_create_game::update_map_settings, this)))
78  , shuffle_sides_(register_bool("shuffle_sides", true, prefs::shuffle_sides, prefs::set_shuffle_sides))
79  , observers_(register_bool("observers", true, prefs::allow_observers, prefs::set_allow_observers))
80  , strict_sync_(register_bool("strict_sync", true))
81  , private_replay_(register_bool("private_replay", true))
82  , turns_(register_integer("turn_count", true, prefs::turns, prefs::set_turns))
83  , gold_(register_integer("village_gold", true, prefs::village_gold, prefs::set_village_gold))
84  , support_(register_integer("village_support", true, prefs::village_support, prefs::set_village_support))
85  , experience_(register_integer("experience_modifier", true, prefs::xp_modifier, prefs::set_xp_modifier))
86  , init_turn_limit_(register_integer("init_turn_limit", true, prefs::countdown_init_time, prefs::set_countdown_init_time))
87  , turn_bonus_(register_integer("turn_bonus", true, prefs::countdown_turn_bonus, prefs::set_countdown_turn_bonus))
88  , reservoir_(register_integer("reservoir", true, prefs::countdown_reservoir_time, prefs::set_countdown_reservoir_time))
89  , action_bonus_(register_integer("action_bonus", true, prefs::countdown_action_bonus, prefs::set_countdown_action_bonus))
90  , mod_list_()
91  , eras_menu_button_()
92  , local_mode_(local_mode)
93 {
94  level_types_ = {
95  {level_type::type::scenario, _("Scenarios")},
96  {level_type::type::campaign, _("Multiplayer Campaigns")},
97  {level_type::type::sp_campaign, _("Singleplayer Campaigns")},
98  {level_type::type::user_map, _("Custom Maps")},
99  {level_type::type::user_scenario, _("Custom Scenarios")},
100  {level_type::type::random_map, _("Random Maps")},
101  };
102 
103  level_types_.erase(std::remove_if(level_types_.begin(), level_types_.end(),
104  [this](level_type_info& type_info) {
105  return create_engine_.get_levels_by_type_unfiltered(type_info.first).empty();
106  }), level_types_.end());
107 
108  set_show_even_without_video(true);
109 
110  create_engine_.init_active_mods();
111 
112  create_engine_.get_state().clear();
113  create_engine_.get_state().classification().type = campaign_type::type::multiplayer;
114 
115  // Need to set this in the constructor, pre_show() is too late
116  set_allow_plugin_skip(false);
117 }
118 
120 {
121  find_widget<text_box>(&win, "game_name", false).set_value(local_mode_ ? "" : ng::configure_engine::game_name_default());
122 
124  find_widget<button>(&win, "random_map_regenerate", false),
125  std::bind(&mp_create_game::regenerate_random_map, this));
126 
128  find_widget<button>(&win, "random_map_settings", false),
129  std::bind(&mp_create_game::show_generator_settings, this));
130 
132  find_widget<button>(&win, "load_game", false),
133  std::bind(&mp_create_game::load_game_callback, this));
134 
135  // Custom dialog close hook
136  win.set_exit_hook(window::exit_hook::on_ok, [this](window& w) { return dialog_exit_hook(w); });
137 
138  //
139  // Set up the options manager. Needs to be done before selecting an initial tab
140  //
142 
143  //
144  // Set up filtering
145  //
146  connect_signal_notify_modified(find_widget<slider>(&win, "num_players", false),
147  std::bind(&mp_create_game::on_filter_change<slider>, this, "num_players", true));
148 
149  text_box& filter = find_widget<text_box>(&win, "game_filter", false);
150 
152  std::bind(&mp_create_game::on_filter_change<text_box>, this, "game_filter", true));
153 
154  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
155  win.keyboard_capture(&filter);
156 
157  //
158  // Set up game types menu_button
159  //
160  std::vector<config> game_types;
161  for(level_type_info& type_info : level_types_) {
162  game_types.emplace_back("label", type_info.second);
163  }
164 
165  if(game_types.empty()) {
166  gui2::show_transient_message("", _("No games found."));
167  throw game::error(_("No games found."));
168  }
169 
170  menu_button& game_menu_button = find_widget<menu_button>(&win, "game_types", false);
171 
172  // Helper to make sure the initially selected level type is valid
173  auto get_initial_type_index = [this]()->int {
174  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
175  return info.first == *level_type::get_enum(preferences::level_type());
176  });
177 
178  if(index != level_types_.end()) {
179  return std::distance(level_types_.begin(), index);
180  }
181 
182  return 0;
183  };
184 
185  game_menu_button.set_values(game_types, get_initial_type_index());
186 
187  connect_signal_notify_modified(game_menu_button,
188  std::bind(&mp_create_game::update_games_list, this));
189 
190  //
191  // Set up mods list
192  //
193  mod_list_ = &find_widget<listbox>(&win, "mod_list", false);
194 
195  const auto& activemods = preferences::modifications();
199 
200  item["label"] = mod->name;
201  data.emplace("mod_name", item);
202 
203  grid* row_grid = &mod_list_->add_row(data);
204 
205  find_widget<toggle_panel>(row_grid, "panel", false).set_tooltip(mod->description);
206 
207  toggle_button& mog_toggle = find_widget<toggle_button>(row_grid, "mod_active_state", false);
208 
209  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
210  create_engine_.active_mods().push_back(mod->id);
211  mog_toggle.set_value_bool(true);
212  }
213 
214  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
215  }
216 
217  // No mods, hide the header
218  if(mod_list_->get_item_count() <= 0) {
219  find_widget<styled_widget>(&win, "mods_header", false).set_visible(widget::visibility::invisible);
220  }
221 
222  //
223  // Set up eras menu_button
224  //
225  eras_menu_button_ = &find_widget<menu_button>(&win, "eras", false);
226 
227  std::vector<config> era_names;
229  era_names.emplace_back("label", era->name, "tooltip", era->description);
230  }
231 
232  if(era_names.empty()) {
233  gui2::show_transient_message("", _("No eras found."));
234  throw config::error(_("No eras found"));
235  }
236 
237  eras_menu_button_->set_values(era_names);
238 
240  std::bind(&mp_create_game::on_era_select, this));
241 
243  if(era_selection >= 0) {
244  eras_menu_button_->set_selected(era_selection);
245  }
246 
247  on_era_select();
248 
249  //
250  // Set up random faction mode menu_button
251  //
252  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::random_faction_mode()).value_or(random_faction_mode::type::independent));
253 
254  menu_button& rfm_menu_button = find_widget<menu_button>(&win, "random_faction_mode", false);
255  rfm_menu_button.set_selected(initial_index);
256 
257  connect_signal_notify_modified(rfm_menu_button,
259 
261 
262  //
263  // Set up the setting status labels
264  //
265  bind_status_label<slider>(&win, turns_->id());
266  bind_status_label<slider>(&win, gold_->id());
267  bind_status_label<slider>(&win, support_->id());
268  bind_status_label<slider>(&win, experience_->id());
269 
270  bind_status_label<slider>(&win, init_turn_limit_->id());
271  bind_status_label<slider>(&win, turn_bonus_->id());
272  bind_status_label<slider>(&win, reservoir_->id());
273  bind_status_label<slider>(&win, action_bonus_->id());
274 
275  //
276  // Timer reset button
277  //
279  find_widget<button>(&win, "reset_timer_defaults", false),
280  std::bind(&mp_create_game::reset_timer_settings, this));
281 
282  //
283  // Disable certain settings if we're playing a local game.
284  //
285  if(local_mode_) {
286  find_widget<text_box>(&win, "game_name", false).set_active(false);
287  find_widget<text_box>(&win, "game_password", false).set_active(false);
288 
289  observers_->widget_set_enabled(false, false);
290  strict_sync_->widget_set_enabled(false, false);
291  private_replay_->widget_set_enabled(false, false);
292  }
293 
294  //
295  // Set up tab control
296  //
297  listbox& tab_bar = find_widget<listbox>(&win, "tab_bar", false);
298 
300  std::bind(&mp_create_game::on_tab_select, this));
301 
302  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
303  // This ensures settings (especially game settings) widgets are appropriately updated when
304  // a new game is selected, regardless of which settings tab is active at the time.
305  find_widget<stacked_widget>(&win, "pager", false).set_find_in_all_layers(true);
306 
307  // We call on_tab_select farther down.
308 
309  //
310  // Main games list
311  //
312  listbox& list = find_widget<listbox>(&win, "games_list", false);
313 
315  std::bind(&mp_create_game::on_game_select, this));
316 
317  win.add_to_keyboard_chain(&list);
318 
319  // This handles the initial game selection as well
320  display_games_of_type(level_types_[get_initial_type_index()].first, preferences::level());
321 
322  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
323  on_tab_select();
324 
325  //
326  // Set up the Lua plugin context
327  //
328  plugins_context_.reset(new plugins_context("Multiplayer Create"));
329 
330  plugins_context_->set_callback("create", [&win](const config&) { win.set_retval(retval::OK); }, false);
331  plugins_context_->set_callback("quit", [&win](const config&) { win.set_retval(retval::CANCEL); }, false);
332  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
333 
334 #define UPDATE_ATTRIBUTE(field, convert) \
335  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
336 
337  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
338  UPDATE_ATTRIBUTE(turns, to_int);
339  UPDATE_ATTRIBUTE(gold, to_int);
340  UPDATE_ATTRIBUTE(support, to_int);
341  UPDATE_ATTRIBUTE(experience, to_int);
342  UPDATE_ATTRIBUTE(start_time, to_bool);
343  UPDATE_ATTRIBUTE(fog, to_bool);
344  UPDATE_ATTRIBUTE(shroud, to_bool);
345  UPDATE_ATTRIBUTE(time_limit, to_bool);
346  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
347  UPDATE_ATTRIBUTE(turn_bonus, to_int);
348  UPDATE_ATTRIBUTE(reservoir, to_int);
349  UPDATE_ATTRIBUTE(action_bonus, to_int);
350  UPDATE_ATTRIBUTE(observers, to_bool);
351  UPDATE_ATTRIBUTE(strict_sync, to_bool);
352  UPDATE_ATTRIBUTE(private_replay, to_bool);
354  }, true);
355 
356 #undef UPDATE_ATTRIBUTE
357 
358  plugins_context_->set_callback("set_name", [this](const config& cfg) {
359  config_engine_->set_game_name(cfg["name"]); }, true);
360 
361  plugins_context_->set_callback("set_password", [this](const config& cfg) {
362  config_engine_->set_game_password(cfg["password"]); }, true);
363 
364  plugins_context_->set_callback("select_level", [this](const config& cfg) {
365  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
367  }, true);
368 
369  plugins_context_->set_callback("select_type", [this](const config& cfg) {
370  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
371 
372  plugins_context_->set_callback("select_era", [this](const config& cfg) {
373  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
374 
375  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
376  on_mod_toggle(cfg["id"].str(), nullptr);
377  }, true);
378 
379  plugins_context_->set_accessor("get_selected", [this](const config&) {
380  const ng::level& current_level = create_engine_.current_level();
381  return config {
382  "id", current_level.id(),
383  "name", current_level.name(),
384  "icon", current_level.icon(),
385  "description", current_level.description(),
386  "allow_era_choice", current_level.allow_era_choice(),
388  };
389  });
390 
391  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
392  const std::string id = cfg["id"].str();
393  return config {
394  "index", create_engine_.find_level_by_id(id).second,
396  };
397  });
398 
399  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
401  });
402 
403  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
405  });
406 }
407 
409 {
410  DBG_MP << "sync_with_depcheck: start";
411 
413  DBG_MP << "sync_with_depcheck: correcting era";
414  const int new_era_index = create_engine_.dependency_manager().get_era_index();
415 
416  create_engine_.set_current_era_index(new_era_index, true);
417  eras_menu_button_->set_value(new_era_index);
418  }
419 
421  DBG_MP << "sync_with_depcheck: correcting scenario";
422 
423  // Match scenario and scenario type
425  const bool different_type = new_level_index.first != create_engine_.current_level_type();
426 
427  if(new_level_index.second != -1) {
428  create_engine_.set_current_level_type(new_level_index.first);
429  create_engine_.set_current_level(new_level_index.second);
430  selected_game_index_ = new_level_index.second;
431 
432  auto& game_types_list = find_widget<menu_button>(get_window(), "game_types", false);
433  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; })));
434 
435  if(different_type) {
436  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
437  } else {
438  // This function (or rather on_game_select) might be triggered by a listbox callback, in
439  // which case we cannot use display_games_of_type since it destroys the list (and its
440  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
441  // sync_with_depcheck call never changes the game type and goes to this branch instead.
442  find_widget<listbox>(get_window(), "games_list", false).select_row(new_level_index.second);
443 
444  // Override the last selection so on_game_select selects the new level
446 
447  on_game_select();
448  }
449  }
450  }
451 
453  DBG_MP << "sync_with_depcheck: correcting modifications";
455  }
456 
458  DBG_MP << "sync_with_depcheck: end";
459 }
460 
461 template<typename T>
462 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
463 {
464  create_engine_.apply_level_filter(find_widget<T>(get_window(), id, false).get_value());
465 
466  listbox& game_list = find_widget<listbox>(get_window(), "games_list", false);
467 
468  boost::dynamic_bitset<> filtered(game_list.get_item_count());
470  filtered[i] = true;
471  }
472 
473  game_list.set_row_shown(filtered);
474 
475  if(do_select) {
476  on_game_select();
477  }
478 }
479 
481 {
482  const int selected_game = find_widget<listbox>(get_window(), "games_list", false).get_selected_row();
483 
484  if(selected_game == selected_game_index_) {
485  return;
486  }
487 
488  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
490 
492 
494 
495  update_details();
496 
497  // General settings
498  const bool can_select_era = create_engine_.current_level().allow_era_choice();
499 
500  if(!can_select_era) {
501  eras_menu_button_->set_label(_("No eras available for this game."));
502  } else {
504  }
505 
506  eras_menu_button_->set_active(can_select_era);
507 
508  // Custom options
509  options_manager_->update_game_options();
510 
511  // Game settings
513 }
514 
516 {
517  const int i = find_widget<listbox>(get_window(), "tab_bar", false).get_selected_row();
518  find_widget<stacked_widget>(get_window(), "pager", false).select_layer(i);
519 }
520 
521 void mp_create_game::on_mod_toggle(const std::string id, toggle_button* sender)
522 {
523  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
524  ERR_MP << "ignoring on_mod_toggle that is already set";
525  return;
526  }
527 
529 
531 
532  options_manager_->update_mod_options();
533 }
534 
536 {
538 
540 
542 
543  options_manager_->update_era_options();
544 }
545 
547 {
548  selected_rfm_index_ = find_widget<menu_button>(get_window(), "random_faction_mode", false).get_value();
549 }
550 
551 void mp_create_game::show_description(const std::string& new_description)
552 {
553  styled_widget& description = find_widget<styled_widget>(get_window(), "description", false);
554 
555  description.set_label(!new_description.empty() ? new_description : _("No description available."));
556  description.set_use_markup(true);
557 }
558 
560 {
561  const int index = find_widget<menu_button>(get_window(), "game_types", false).get_value();
562 
564 }
565 
567 {
569 
570  listbox& list = find_widget<listbox>(get_window(), "games_list", false);
571 
572  list.clear();
573 
577 
578  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
579  item["label"] = game->icon();
580  data.emplace("game_icon", item);
581  }
582 
583  item["label"] = game->name();
584  data.emplace("game_name", item);
585 
586  grid& rg = list.add_row(data);
587 
588  auto& icon = find_widget<image>(&rg, "game_icon", false);
589  if(icon.get_label().empty()) {
590  icon.set_visible(gui2::widget::visibility::invisible);
591  }
592  }
593 
594  if(!level.empty() && !list.get_rows_shown().empty()) {
595  // Recalculate which rows should be visible
596  on_filter_change<slider>("num_players", false);
597  on_filter_change<text_box>("game_filter", false);
598 
599  int level_index = create_engine_.find_level_by_id(level).second;
600  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
601  list.select_row(level_index);
602  }
603  }
604 
605  const bool is_random_map = type == level_type::type::random_map;
606 
607  find_widget<button>(get_window(), "random_map_regenerate", false).set_active(is_random_map);
608  find_widget<button>(get_window(), "random_map_settings", false).set_active(is_random_map);
609 
610  // Override the last selection so on_game_select selects the new level
612 
613  on_game_select();
614 }
615 
617 {
619 
621 }
622 
624 {
626 
627  update_details();
628 }
629 
630 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
631 {
632  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
633  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
634 }
635 
637 {
638  styled_widget& players = find_widget<styled_widget>(get_window(), "map_num_players", false);
639  styled_widget& map_size = find_widget<styled_widget>(get_window(), "map_size", false);
640 
641  if(create_engine_.current_level_type() == level_type::type::random_map) {
642  // If the current random map doesn't have data, generate it
644  create_engine_.current_level().data()["map_data"].empty() &&
645  create_engine_.current_level().data()["map_file"].empty()) {
647  }
648 
649  find_widget<button>(get_window(), "random_map_settings", false).set_active(create_engine_.generator_has_settings());
650  }
651 
653 
654  // Reset the config_engine with new values
656  config_engine_->update_initial_cfg(create_engine_.current_level().data());
657  config_engine_->set_default_values();
658 
659  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
660  std::string title = create_engine_.current_level().name();
661  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
662  find_widget<styled_widget>(get_window(), "game_title", false).set_label(title);
663 
664 
666  case level_type::type::scenario:
667  case level_type::type::user_map:
668  case level_type::type::user_scenario:
669  case level_type::type::random_map: {
670  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
671 
672  assert(current_scenario);
673 
675 
676  find_widget<stacked_widget>(get_window(), "minimap_stack", false).select_layer(0);
677 
678  if(current_scenario->data()["map_data"].empty()) {
679  saved_game::expand_map_file(current_scenario->data());
680  current_scenario->set_metadata();
681  }
682 
683  find_widget<minimap>(get_window(), "minimap", false).set_map_data(current_scenario->data()["map_data"]);
684 
685  players.set_label(std::to_string(current_scenario->num_players()));
686  map_size.set_label(current_scenario->map_size());
687 
688  break;
689  }
690  case level_type::type::campaign:
691  case level_type::type::sp_campaign: {
692  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
693 
694  assert(current_campaign);
695 
696  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
697 
698  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
699 
700  find_widget<stacked_widget>(get_window(), "minimap_stack", false).select_layer(1);
701  find_widget<image>(get_window(), "campaign_image", false).set_image(img);
702 
703  const int p_min = current_campaign->min_players();
704  const int p_max = current_campaign->max_players();
705 
706  if(p_max > p_min) {
707  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
708  } else {
709  players.set_label(std::to_string(p_min));
710  }
711 
713 
714  break;
715  }
716  }
717 
718  // This needs to be at the end, since errors found expanding the map data are put into the description
720 }
721 
723 {
724  if(config_engine_->force_lock_settings()) {
725  use_map_settings_->widget_set_enabled(false, false);
727  } else {
729  }
730 
732 
733  config_engine_->set_use_map_settings(use_map_settings);
734 
738 
743 
744  const bool time_limit = time_limit_->get_widget_value();
745 
746  init_turn_limit_->widget_set_enabled(time_limit, false);
747  turn_bonus_ ->widget_set_enabled(time_limit, false);
748  reservoir_ ->widget_set_enabled(time_limit, false);
749  action_bonus_ ->widget_set_enabled(time_limit, false);
750 
751  find_widget<button>(get_window(), "reset_timer_defaults", false).set_active(time_limit);
752 
753  if(use_map_settings) {
754  fog_ ->set_widget_value(config_engine_->fog_game_default());
755  shroud_ ->set_widget_value(config_engine_->shroud_game_default());
756  start_time_->set_widget_value(config_engine_->random_start_time_default());
757 
758  turns_ ->set_widget_value(config_engine_->num_turns_default());
759  gold_ ->set_widget_value(config_engine_->village_gold_default());
760  support_ ->set_widget_value(config_engine_->village_support_default());
761  experience_->set_widget_value(config_engine_->xp_modifier_default());
762  }
763 }
764 
766 {
768 
769  if(!load.load_multiplayer_game()) {
770  return;
771  }
772 
773  if(load.data().cancel_orders) {
775  }
776 
778 }
779 
780 std::vector<std::string> mp_create_game::get_active_mods()
781 {
782  int i = 0;
783  std::set<std::string> res;
785  if(find_widget<toggle_button>(mod_list_->get_row_grid(i), "mod_active_state", false).get_value_bool()) {
786  res.insert(mod->id);
787  }
788  ++i;
789  }
790  return std::vector<std::string>(res.begin(), res.end());
791 }
792 
793 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
794 {
795  std::set<std::string> val2(val.begin(), val.end());
796  int i = 0;
797  std::set<std::string> res;
799  find_widget<toggle_button>(mod_list_->get_row_grid(i), "mod_active_state", false).set_value_bool(val2.find(mod->id) != val2.end());
800  ++i;
801  }
802 }
803 
805 {
806  // This allows the defaults to be returned by the pref getters below
807  preferences::erase("mp_countdown_init_time");
808  preferences::erase("mp_countdown_reservoir_time");
809  preferences::erase("mp_countdown_turn_bonus");
810  preferences::erase("mp_countdown_action_bonus");
811 
816 }
817 
819 {
821  gui2::show_transient_error_message(_("The selected game has no sides!"));
822  return false;
823  }
824 
826  std::stringstream msg;
827  // 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
828  msg << _("The selected game cannot be created.");
829  msg << "\n\n";
832  return false;
833  }
834 
835  if(!create_engine_.is_campaign()) {
836  return true;
837  }
838 
839  return create_engine_.select_campaign_difficulty() != "CANCEL";
840 }
841 
843 {
844  plugins_context_.reset();
845 
846  // Show all tabs so that find_widget works correctly
847  find_widget<stacked_widget>(&window, "pager", false).select_layer(-1);
848 
849  if(get_retval() == LOAD_GAME) {
851 
852  // 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.
854  return;
855  }
856 
857  if(get_retval() == retval::OK) {
862 
864 
865  if(create_engine_.current_level_type() == level_type::type::campaign ||
866  create_engine_.current_level_type() == level_type::type::sp_campaign) {
868  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
870  } else {
871  // This means define= doesn't work for randomly generated scenarios
873  }
874 
876 
878 
879  std::vector<const config*> entry_points;
880  std::vector<std::string> entry_point_titles;
881 
882  const auto& tagname = create_engine_.get_state().classification().get_tagname();
883 
884  if(tagname == "scenario") {
885  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
886  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
887  const bool is_first = scenario["id"] == first_scenario;
888  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
889  const std::string& title = !scenario["new_game_title"].empty()
890  ? scenario["new_game_title"]
891  : scenario["name"];
892 
893  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
894  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
895  }
896  }
897  }
898 
899  if(entry_points.size() > 1) {
900  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
901 
902  dlg.set_single_button(true);
903  dlg.show();
904 
905  const config& scenario = *entry_points[dlg.selected_index()];
906 
907  create_engine_.get_state().mp_settings().hash = scenario.hash();
909  }
910 
911  config_engine_->set_use_map_settings(use_map_settings_->get_widget_value());
912 
913  if(!config_engine_->force_lock_settings()) {
914  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
915  const int num_turns = turns_->get_widget_value();
916  config_engine_->set_num_turns(num_turns < ::settings::turns_max ? num_turns : - 1);
917  config_engine_->set_village_gold(gold_->get_widget_value());
918  config_engine_->set_village_support(support_->get_widget_value());
919  config_engine_->set_xp_modifier(experience_->get_widget_value());
920  config_engine_->set_random_start_time(start_time_->get_widget_value());
921  config_engine_->set_fog_game(fog_->get_widget_value());
922  config_engine_->set_shroud_game(shroud_->get_widget_value());
923 
924  config_engine_->write_parameters();
925  }
926 
927  config_engine_->set_mp_countdown(time_limit_->get_widget_value());
928  config_engine_->set_mp_countdown_init_time(init_turn_limit_->get_widget_value());
929  config_engine_->set_mp_countdown_turn_bonus(turn_bonus_->get_widget_value());
930  config_engine_->set_mp_countdown_reservoir_time(reservoir_->get_widget_value());
931  config_engine_->set_mp_countdown_action_bonus(action_bonus_->get_widget_value());
932 
933  config_engine_->set_allow_observers(observers_->get_widget_value());
934  config_engine_->set_private_replay(private_replay_->get_widget_value());
935  config_engine_->set_oos_debug(strict_sync_->get_widget_value());
936  config_engine_->set_shuffle_sides(shuffle_sides_->get_widget_value());
937 
938  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
939  config_engine_->set_random_faction_mode(type);
940 
941  // Since we don't have a field handling this option, we need to save the value manually
943 
944  // Save custom option settings
945  config_engine_->set_options(options_manager_->get_options_config());
946 
947  // Set game name
948  const std::string name = find_widget<text_box>(&window, "game_name", false).get_value();
949  if(!name.empty() && (name != ng::configure_engine::game_name_default())) {
950  config_engine_->set_game_name(name);
951  }
952 
953  // Set game password
954  const std::string password = find_widget<text_box>(&window, "game_password", false).get_value();
955  if(!password.empty()) {
956  config_engine_->set_game_password(password);
957  }
958  }
959 }
960 
961 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
bool empty() const
Definition: config.cpp:852
std::string hash() const
Definition: config.cpp:1287
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.
int get_retval() const
Returns the cached window exit code.
window * get_window()
Returns a pointer to the dialog's window.
std::unique_ptr< ng::configure_engine > config_engine_
bool dialog_exit_hook(window &)
Dialog exit hook to bring up the difficulty dialog when starting a campaign.
std::pair< level_type::type, std::string > level_type_info
void on_mod_toggle(const std::string id, toggle_button *sender)
void display_games_of_type(level_type::type type, const std::string &level)
void set_active_mods(const std::vector< std::string > &val)
void show_description(const std::string &new_description)
virtual void pre_show(window &window) override
Actions to be taken before showing the window.
virtual void post_show(window &window) override
Actions to be taken after the window has been shown.
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()
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_
A simple one-column listbox with OK and Cancel buttons.
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:183
void widget_set_enabled(const bool enable, const bool sync)
Enables a widget.
Definition: field.hpp:159
void set_widget_value(CT value)
Sets the value of the field.
Definition: field.hpp:344
T get_widget_value()
Gets the value of the field.
Definition: field.hpp:379
Base container class.
Definition: grid.hpp:32
The listbox class.
Definition: listbox.hpp:43
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:136
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:59
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:230
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:243
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:214
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:118
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:124
A menu_button is a styled_widget to choose an element from a list of elements.
Definition: menu_button.hpp:59
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:84
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:81
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)
Base class for all visible items.
void set_tooltip(const t_string &tooltip)
virtual void set_label(const t_string &text)
virtual void set_use_markup(bool use_markup)
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
Class for a single line text area.
Definition: text_box.hpp:142
Class for a toggle button.
@ invisible
The user set the widget invisible, that means:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:63
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:399
void keyboard_capture(widget *widget)
Definition: window.cpp:1215
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1221
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
Definition: window.hpp:453
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
@ on_ok
Run hook only if result is OK.
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
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:595
static void expand_map_file(config &scenario)
reads scenario["map_file"]
Definition: saved_game.cpp:475
void cancel_orders()
Definition: saved_game.cpp:715
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:208
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:968
int w
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:60
const bool & debug
Definition: game_config.cpp:91
int village_support
Definition: game_config.cpp:38
REGISTER_DIALOG(tod_new_schedule)
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:36
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:34
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:31
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
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:412
logger & info()
Definition: log.cpp:314
Modify, read and display user preferences.
void set_village_support(int value)
Definition: game.cpp:663
void set_village_gold(int value)
Definition: game.cpp:653
bool fog()
Definition: game.cpp:522
void set_era(const std::string &value)
Definition: game.cpp:683
void set_shuffle_sides(bool value)
Definition: game.cpp:463
void set_allow_observers(bool value)
Definition: game.cpp:453
bool shroud()
Definition: game.cpp:532
std::string password(const std::string &server, const std::string &login)
int village_gold()
Definition: game.cpp:648
void set_shroud(bool value)
Definition: game.cpp:537
int countdown_init_time()
Definition: game.cpp:608
void set_random_faction_mode(const std::string &value)
Definition: game.cpp:473
void set_modifications(const std::vector< std::string > &value, bool mp)
Definition: game.cpp:717
void set_turns(int value)
Definition: game.cpp:547
std::string level()
Definition: game.cpp:688
std::string era()
Definition: game.cpp:678
bool countdown()
Definition: game.cpp:598
void set_xp_modifier(int value)
Definition: game.cpp:673
void set_use_map_settings(bool value)
Definition: game.cpp:483
bool shuffle_sides()
Definition: game.cpp:458
void set_level_type(int value)
Definition: game.cpp:703
bool use_map_settings()
Definition: game.cpp:478
bool random_start_time()
Definition: game.cpp:512
void set_countdown_reservoir_time(int value)
Definition: game.cpp:623
int countdown_turn_bonus()
Definition: game.cpp:628
int countdown_action_bonus()
Definition: game.cpp:638
int turns()
Definition: game.cpp:542
void set_countdown(bool value)
Definition: game.cpp:603
void set_countdown_init_time(int value)
Definition: game.cpp:613
void set_countdown_action_bonus(int value)
Definition: game.cpp:643
void set_level(const std::string &value)
Definition: game.cpp:693
void set_fog(bool value)
Definition: game.cpp:527
void set_random_start_time(bool value)
Definition: game.cpp:517
int countdown_reservoir_time()
Definition: game.cpp:618
void erase(const std::string &key)
Definition: general.cpp:205
void set_countdown_turn_bonus(int value)
Definition: game.cpp:633
bool allow_observers()
Definition: game.cpp:448
int xp_modifier()
Definition: game.cpp:668
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:708
const int turns_max
maximum number of turns
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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:194
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 std::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57