The Battle for Wesnoth  1.19.1+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().use_map_settings();},
70  [](bool v) {prefs::get().set_use_map_settings(v);},
71  std::bind(&mp_create_game::update_map_settings, this)))
72  , fog_(register_bool("fog", true,
73  []() {return prefs::get().fog();},
74  [](bool v) {prefs::get().set_fog(v);}))
75  , shroud_(register_bool("shroud", true,
76  []() {return prefs::get().shroud();},
77  [](bool v) {prefs::get().set_shroud(v);}))
78  , start_time_(register_bool("random_start_time", true,
79  []() {return prefs::get().random_start_time();},
80  [](bool v) {prefs::get().set_random_start_time(v);}))
81  , time_limit_(register_bool("time_limit", true,
82  []() {return prefs::get().countdown();},
83  [](bool v) {prefs::get().set_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().turns();},
95  [](int v) {prefs::get().set_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();},
107  [](int v) {prefs::get().set_countdown_init_time(v);}))
108  , turn_bonus_(register_integer("turn_bonus", true,
109  []() {return prefs::get().countdown_turn_bonus();},
110  [](int v) {prefs::get().set_countdown_turn_bonus(v);}))
111  , reservoir_(register_integer("reservoir", true,
112  []() {return prefs::get().countdown_reservoir_time();},
113  [](int v) {prefs::get().set_countdown_reservoir_time(v);}))
114  , action_bonus_(register_integer("action_bonus", true,
115  []() {return prefs::get().countdown_action_bonus();},
116  [](int v) {prefs::get().set_countdown_action_bonus(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  level_types_.erase(std::remove_if(level_types_.begin(), level_types_.end(),
131  [this](level_type_info& type_info) {
132  return create_engine_.get_levels_by_type_unfiltered(type_info.first).empty();
133  }), level_types_.end());
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 
147 {
148  find_widget<text_box>(&win, "game_name", false).set_value(local_mode_ ? "" : ng::configure_engine::game_name_default());
149 
151  find_widget<button>(&win, "random_map_regenerate", false),
152  std::bind(&mp_create_game::regenerate_random_map, this));
153 
155  find_widget<button>(&win, "random_map_settings", false),
156  std::bind(&mp_create_game::show_generator_settings, this));
157 
159  find_widget<button>(&win, "load_game", false),
160  std::bind(&mp_create_game::load_game_callback, this));
161 
162  // Custom dialog close hook
163  win.set_exit_hook(window::exit_hook::on_ok, [this](window& w) { return dialog_exit_hook(w); });
164 
165  //
166  // Set up the options manager. Needs to be done before selecting an initial tab
167  //
169 
170  //
171  // Set up filtering
172  //
173  connect_signal_notify_modified(find_widget<slider>(&win, "num_players", false),
174  std::bind(&mp_create_game::on_filter_change<slider>, this, "num_players", true));
175 
176  text_box& filter = find_widget<text_box>(&win, "game_filter", false);
177 
179  std::bind(&mp_create_game::on_filter_change<text_box>, this, "game_filter", true));
180 
181  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
182  win.keyboard_capture(&filter);
183 
184  //
185  // Set up game types menu_button
186  //
187  std::vector<config> game_types;
188  for(level_type_info& type_info : level_types_) {
189  game_types.emplace_back("label", type_info.second);
190  }
191 
192  if(game_types.empty()) {
193  gui2::show_transient_message("", _("No games found."));
194  throw game::error(_("No games found."));
195  }
196 
197  menu_button& game_menu_button = find_widget<menu_button>(&win, "game_types", false);
198 
199  // Helper to make sure the initially selected level type is valid
200  auto get_initial_type_index = [this]()->int {
201  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
202  return info.first == *level_type::get_enum(prefs::get().level_type());
203  });
204 
205  if(index != level_types_.end()) {
206  return std::distance(level_types_.begin(), index);
207  }
208 
209  return 0;
210  };
211 
212  game_menu_button.set_values(game_types, get_initial_type_index());
213 
214  connect_signal_notify_modified(game_menu_button,
215  std::bind(&mp_create_game::update_games_list, this));
216 
217  //
218  // Set up mods list
219  //
220  mod_list_ = &find_widget<listbox>(&win, "mod_list", false);
221 
222  const auto& activemods = prefs::get().modifications();
226 
227  item["label"] = mod->name;
228  data.emplace("mod_name", item);
229 
230  grid* row_grid = &mod_list_->add_row(data);
231 
232  find_widget<toggle_panel>(row_grid, "panel", false).set_tooltip(mod->description);
233 
234  toggle_button& mog_toggle = find_widget<toggle_button>(row_grid, "mod_active_state", false);
235 
236  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
237  create_engine_.active_mods().push_back(mod->id);
238  mog_toggle.set_value_bool(true);
239  }
240 
241  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
242  }
243 
244  // No mods, hide the header
245  if(mod_list_->get_item_count() <= 0) {
246  find_widget<styled_widget>(&win, "mods_header", false).set_visible(widget::visibility::invisible);
247  }
248 
249  //
250  // Set up eras menu_button
251  //
252  eras_menu_button_ = &find_widget<menu_button>(&win, "eras", false);
253 
254  std::vector<config> era_names;
256  era_names.emplace_back("label", era->name, "tooltip", era->description);
257  }
258 
259  if(era_names.empty()) {
260  gui2::show_transient_message("", _("No eras found."));
261  throw config::error(_("No eras found"));
262  }
263 
264  eras_menu_button_->set_values(era_names);
265 
267  std::bind(&mp_create_game::on_era_select, this));
268 
269  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().era());
270  if(era_selection >= 0) {
271  eras_menu_button_->set_selected(era_selection);
272  }
273 
274  on_era_select();
275 
276  //
277  // Set up random faction mode menu_button
278  //
279  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
280 
281  menu_button& rfm_menu_button = find_widget<menu_button>(&win, "random_faction_mode", false);
282  rfm_menu_button.set_selected(initial_index);
283 
284  connect_signal_notify_modified(rfm_menu_button,
286 
288 
289  //
290  // Set up the setting status labels
291  //
292  bind_status_label<slider>(&win, turns_->id());
293  bind_status_label<slider>(&win, gold_->id());
294  bind_status_label<slider>(&win, support_->id());
295  bind_status_label<slider>(&win, experience_->id());
296 
297  bind_status_label<slider>(&win, init_turn_limit_->id());
298  bind_status_label<slider>(&win, turn_bonus_->id());
299  bind_status_label<slider>(&win, reservoir_->id());
300  bind_status_label<slider>(&win, action_bonus_->id());
301 
302  //
303  // Timer reset button
304  //
306  find_widget<button>(&win, "reset_timer_defaults", false),
307  std::bind(&mp_create_game::reset_timer_settings, this));
308 
309  //
310  // Disable certain settings if we're playing a local game.
311  //
312  if(local_mode_) {
313  find_widget<text_box>(&win, "game_name", false).set_active(false);
314  find_widget<text_box>(&win, "game_password", false).set_active(false);
315 
316  observers_->widget_set_enabled(false, false);
317  strict_sync_->widget_set_enabled(false, false);
318  private_replay_->widget_set_enabled(false, false);
319  }
320 
321  //
322  // Set up tab control
323  //
324  listbox& tab_bar = find_widget<listbox>(&win, "tab_bar", false);
325 
327  std::bind(&mp_create_game::on_tab_select, this));
328 
329  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
330  // This ensures settings (especially game settings) widgets are appropriately updated when
331  // a new game is selected, regardless of which settings tab is active at the time.
332  find_widget<stacked_widget>(&win, "pager", false).set_find_in_all_layers(true);
333 
334  // We call on_tab_select farther down.
335 
336  //
337  // Main games list
338  //
339  listbox& list = find_widget<listbox>(&win, "games_list", false);
340 
342  std::bind(&mp_create_game::on_game_select, this));
343 
344  win.add_to_keyboard_chain(&list);
345 
346  // This handles the initial game selection as well
347  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().level());
348 
349  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
350  on_tab_select();
351 
352  //
353  // Set up the Lua plugin context
354  //
355  plugins_context_.reset(new plugins_context("Multiplayer Create"));
356 
357  plugins_context_->set_callback("create", [&win](const config&) { win.set_retval(retval::OK); }, false);
358  plugins_context_->set_callback("quit", [&win](const config&) { win.set_retval(retval::CANCEL); }, false);
359  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
360 
361 #define UPDATE_ATTRIBUTE(field, convert) \
362  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
363 
364  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
365  UPDATE_ATTRIBUTE(turns, to_int);
366  UPDATE_ATTRIBUTE(gold, to_int);
367  UPDATE_ATTRIBUTE(support, to_int);
368  UPDATE_ATTRIBUTE(experience, to_int);
369  UPDATE_ATTRIBUTE(start_time, to_bool);
370  UPDATE_ATTRIBUTE(fog, to_bool);
371  UPDATE_ATTRIBUTE(shroud, to_bool);
372  UPDATE_ATTRIBUTE(time_limit, to_bool);
373  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
374  UPDATE_ATTRIBUTE(turn_bonus, to_int);
375  UPDATE_ATTRIBUTE(reservoir, to_int);
376  UPDATE_ATTRIBUTE(action_bonus, to_int);
377  UPDATE_ATTRIBUTE(observers, to_bool);
378  UPDATE_ATTRIBUTE(strict_sync, to_bool);
379  UPDATE_ATTRIBUTE(private_replay, to_bool);
380  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
381  }, true);
382 
383 #undef UPDATE_ATTRIBUTE
384 
385  plugins_context_->set_callback("set_name", [this](const config& cfg) {
386  config_engine_->set_game_name(cfg["name"]); }, true);
387 
388  plugins_context_->set_callback("set_password", [this](const config& cfg) {
389  config_engine_->set_game_password(cfg["password"]); }, true);
390 
391  plugins_context_->set_callback("select_level", [this](const config& cfg) {
392  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
394  }, true);
395 
396  plugins_context_->set_callback("select_type", [this](const config& cfg) {
397  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
398 
399  plugins_context_->set_callback("select_era", [this](const config& cfg) {
400  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
401 
402  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
403  on_mod_toggle(cfg["id"].str(), nullptr);
404  }, true);
405 
406  plugins_context_->set_accessor("get_selected", [this](const config&) {
407  const ng::level& current_level = create_engine_.current_level();
408  return config {
409  "id", current_level.id(),
410  "name", current_level.name(),
411  "icon", current_level.icon(),
412  "description", current_level.description(),
413  "allow_era_choice", current_level.allow_era_choice(),
415  };
416  });
417 
418  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
419  const std::string id = cfg["id"].str();
420  return config {
421  "index", create_engine_.find_level_by_id(id).second,
423  };
424  });
425 
426  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
428  });
429 
430  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
432  });
433 }
434 
436 {
437  DBG_MP << "sync_with_depcheck: start";
438 
440  DBG_MP << "sync_with_depcheck: correcting era";
441  const int new_era_index = create_engine_.dependency_manager().get_era_index();
442 
443  create_engine_.set_current_era_index(new_era_index, true);
444  eras_menu_button_->set_value(new_era_index);
445  }
446 
448  DBG_MP << "sync_with_depcheck: correcting scenario";
449 
450  // Match scenario and scenario type
452  const bool different_type = new_level_index.first != create_engine_.current_level_type();
453 
454  if(new_level_index.second != -1) {
455  create_engine_.set_current_level_type(new_level_index.first);
456  create_engine_.set_current_level(new_level_index.second);
457  selected_game_index_ = new_level_index.second;
458 
459  auto& game_types_list = find_widget<menu_button>(get_window(), "game_types", false);
460  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; })));
461 
462  if(different_type) {
463  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
464  } else {
465  // This function (or rather on_game_select) might be triggered by a listbox callback, in
466  // which case we cannot use display_games_of_type since it destroys the list (and its
467  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
468  // sync_with_depcheck call never changes the game type and goes to this branch instead.
469  find_widget<listbox>(get_window(), "games_list", false).select_row(new_level_index.second);
470 
471  // Override the last selection so on_game_select selects the new level
473 
474  on_game_select();
475  }
476  }
477  }
478 
480  DBG_MP << "sync_with_depcheck: correcting modifications";
482  }
483 
485  DBG_MP << "sync_with_depcheck: end";
486 }
487 
488 template<typename T>
489 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
490 {
491  create_engine_.apply_level_filter(find_widget<T>(get_window(), id, false).get_value());
492 
493  listbox& game_list = find_widget<listbox>(get_window(), "games_list", false);
494 
495  boost::dynamic_bitset<> filtered(game_list.get_item_count());
497  filtered[i] = true;
498  }
499 
500  game_list.set_row_shown(filtered);
501 
502  if(do_select) {
503  on_game_select();
504  }
505 }
506 
508 {
509  const int selected_game = find_widget<listbox>(get_window(), "games_list", false).get_selected_row();
510 
511  if(selected_game == selected_game_index_) {
512  return;
513  }
514 
515  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
517 
519 
521 
522  update_details();
523 
524  // General settings
525  const bool can_select_era = create_engine_.current_level().allow_era_choice();
526 
527  if(!can_select_era) {
528  eras_menu_button_->set_label(_("No eras available for this game."));
529  } else {
531  }
532 
533  eras_menu_button_->set_active(can_select_era);
534 
535  // Custom options
536  options_manager_->update_game_options();
537 
538  // Game settings
540 }
541 
543 {
544  const int i = find_widget<listbox>(get_window(), "tab_bar", false).get_selected_row();
545  find_widget<stacked_widget>(get_window(), "pager", false).select_layer(i);
546 }
547 
548 void mp_create_game::on_mod_toggle(const std::string id, toggle_button* sender)
549 {
550  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
551  ERR_MP << "ignoring on_mod_toggle that is already set";
552  return;
553  }
554 
556 
558 
559  options_manager_->update_mod_options();
560 }
561 
563 {
565 
567 
569 
570  options_manager_->update_era_options();
571 }
572 
574 {
575  selected_rfm_index_ = find_widget<menu_button>(get_window(), "random_faction_mode", false).get_value();
576 }
577 
578 void mp_create_game::show_description(const std::string& new_description)
579 {
580  styled_widget& description = find_widget<styled_widget>(get_window(), "description", false);
581 
582  description.set_label(!new_description.empty() ? new_description : _("No description available."));
583  description.set_use_markup(true);
584 }
585 
587 {
588  const int index = find_widget<menu_button>(get_window(), "game_types", false).get_value();
589 
591 }
592 
594 {
596 
597  listbox& list = find_widget<listbox>(get_window(), "games_list", false);
598 
599  list.clear();
600 
604 
605  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
606  item["label"] = game->icon();
607  data.emplace("game_icon", item);
608  }
609 
610  item["label"] = game->name();
611  data.emplace("game_name", item);
612 
613  grid& rg = list.add_row(data);
614 
615  auto& icon = find_widget<image>(&rg, "game_icon", false);
616  if(icon.get_label().empty()) {
617  icon.set_visible(gui2::widget::visibility::invisible);
618  }
619  }
620 
621  if(!level.empty() && !list.get_rows_shown().empty()) {
622  // Recalculate which rows should be visible
623  on_filter_change<slider>("num_players", false);
624  on_filter_change<text_box>("game_filter", false);
625 
626  int level_index = create_engine_.find_level_by_id(level).second;
627  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
628  list.select_row(level_index);
629  }
630  }
631 
632  const bool is_random_map = type == level_type::type::random_map;
633 
634  find_widget<button>(get_window(), "random_map_regenerate", false).set_active(is_random_map);
635  find_widget<button>(get_window(), "random_map_settings", false).set_active(is_random_map);
636 
637  // Override the last selection so on_game_select selects the new level
639 
640  on_game_select();
641 }
642 
644 {
646 
648 }
649 
651 {
653 
654  update_details();
655 }
656 
657 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
658 {
659  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
660  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
661 }
662 
664 {
665  styled_widget& players = find_widget<styled_widget>(get_window(), "map_num_players", false);
666  styled_widget& map_size = find_widget<styled_widget>(get_window(), "map_size", false);
667 
668  if(create_engine_.current_level_type() == level_type::type::random_map) {
669  // If the current random map doesn't have data, generate it
671  create_engine_.current_level().data()["map_data"].empty() &&
672  create_engine_.current_level().data()["map_file"].empty()) {
674  }
675 
676  find_widget<button>(get_window(), "random_map_settings", false).set_active(create_engine_.generator_has_settings());
677  }
678 
680 
681  // Reset the config_engine with new values
683  config_engine_->update_initial_cfg(create_engine_.current_level().data());
684  config_engine_->set_default_values();
685 
686  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
687  std::string title = create_engine_.current_level().name();
688  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
689  find_widget<styled_widget>(get_window(), "game_title", false).set_label(title);
690 
691 
693  case level_type::type::scenario:
694  case level_type::type::user_map:
695  case level_type::type::user_scenario:
696  case level_type::type::random_map: {
697  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
698 
699  assert(current_scenario);
700 
702 
703  find_widget<stacked_widget>(get_window(), "minimap_stack", false).select_layer(0);
704 
705  if(current_scenario->data()["map_data"].empty()) {
706  saved_game::expand_map_file(current_scenario->data());
707  current_scenario->set_metadata();
708  }
709 
710  find_widget<minimap>(get_window(), "minimap", false).set_map_data(current_scenario->data()["map_data"]);
711 
712  players.set_label(std::to_string(current_scenario->num_players()));
713  map_size.set_label(current_scenario->map_size());
714 
715  break;
716  }
717  case level_type::type::campaign:
718  case level_type::type::sp_campaign: {
719  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
720 
721  assert(current_campaign);
722 
723  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
724 
725  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
726 
727  find_widget<stacked_widget>(get_window(), "minimap_stack", false).select_layer(1);
728  find_widget<image>(get_window(), "campaign_image", false).set_image(img);
729 
730  const int p_min = current_campaign->min_players();
731  const int p_max = current_campaign->max_players();
732 
733  if(p_max > p_min) {
734  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
735  } else {
736  players.set_label(std::to_string(p_min));
737  }
738 
740 
741  break;
742  }
743  }
744 
745  // This needs to be at the end, since errors found expanding the map data are put into the description
747 }
748 
750 {
751  if(config_engine_->force_lock_settings()) {
752  use_map_settings_->widget_set_enabled(false, false);
754  } else {
756  }
757 
758  const bool use_map_settings = use_map_settings_->get_widget_value();
759 
760  config_engine_->set_use_map_settings(use_map_settings);
761 
762  fog_ ->widget_set_enabled(!use_map_settings, false);
763  shroud_ ->widget_set_enabled(!use_map_settings, false);
764  start_time_ ->widget_set_enabled(!use_map_settings, false);
765 
766  turns_ ->widget_set_enabled(!use_map_settings, false);
767  gold_ ->widget_set_enabled(!use_map_settings, false);
768  support_ ->widget_set_enabled(!use_map_settings, false);
769  experience_ ->widget_set_enabled(!use_map_settings, false);
770 
771  const bool time_limit = time_limit_->get_widget_value();
772 
773  init_turn_limit_->widget_set_enabled(time_limit, false);
774  turn_bonus_ ->widget_set_enabled(time_limit, false);
775  reservoir_ ->widget_set_enabled(time_limit, false);
776  action_bonus_ ->widget_set_enabled(time_limit, false);
777 
778  find_widget<button>(get_window(), "reset_timer_defaults", false).set_active(time_limit);
779 
780  if(use_map_settings) {
781  fog_ ->set_widget_value(config_engine_->fog_game_default());
782  shroud_ ->set_widget_value(config_engine_->shroud_game_default());
783  start_time_->set_widget_value(config_engine_->random_start_time_default());
784 
785  turns_ ->set_widget_value(config_engine_->num_turns_default());
786  gold_ ->set_widget_value(config_engine_->village_gold_default());
787  support_ ->set_widget_value(config_engine_->village_support_default());
788  experience_->set_widget_value(config_engine_->xp_modifier_default());
789  }
790 }
791 
793 {
795 
796  if(!load.load_multiplayer_game()) {
797  return;
798  }
799 
800  if(load.data().cancel_orders) {
802  }
803 
805 }
806 
807 std::vector<std::string> mp_create_game::get_active_mods()
808 {
809  int i = 0;
810  std::set<std::string> res;
812  if(find_widget<toggle_button>(mod_list_->get_row_grid(i), "mod_active_state", false).get_value_bool()) {
813  res.insert(mod->id);
814  }
815  ++i;
816  }
817  return std::vector<std::string>(res.begin(), res.end());
818 }
819 
820 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
821 {
822  std::set<std::string> val2(val.begin(), val.end());
823  int i = 0;
824  std::set<std::string> res;
826  find_widget<toggle_button>(mod_list_->get_row_grid(i), "mod_active_state", false).set_value_bool(val2.find(mod->id) != val2.end());
827  ++i;
828  }
829 }
830 
832 {
833  // This allows the defaults to be returned by the pref getters below
838 
839  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time());
840  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus());
841  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time());
842  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus());
843 }
844 
846 {
848  gui2::show_transient_error_message(_("The selected game has no sides!"));
849  return false;
850  }
851 
853  std::stringstream msg;
854  // 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
855  msg << _("The selected game cannot be created.");
856  msg << "\n\n";
859  return false;
860  }
861 
862  if(!create_engine_.is_campaign()) {
863  return true;
864  }
865 
866  return create_engine_.select_campaign_difficulty() != "CANCEL";
867 }
868 
870 {
871  plugins_context_.reset();
872 
873  // Show all tabs so that find_widget works correctly
874  find_widget<stacked_widget>(&window, "pager", false).select_layer(-1);
875 
876  if(get_retval() == LOAD_GAME) {
878 
879  // 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.
881  return;
882  }
883 
884  if(get_retval() == retval::OK) {
889 
891 
892  if(create_engine_.current_level_type() == level_type::type::campaign ||
893  create_engine_.current_level_type() == level_type::type::sp_campaign) {
895  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
897  } else {
898  // This means define= doesn't work for randomly generated scenarios
900  }
901 
903 
905 
906  std::vector<const config*> entry_points;
907  std::vector<std::string> entry_point_titles;
908 
909  const auto& tagname = create_engine_.get_state().classification().get_tagname();
910 
911  if(tagname == "scenario") {
912  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
913  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
914  const bool is_first = scenario["id"] == first_scenario;
915  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
916  const std::string& title = !scenario["new_game_title"].empty()
917  ? scenario["new_game_title"]
918  : scenario["name"];
919 
920  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
921  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
922  }
923  }
924  }
925 
926  if(entry_points.size() > 1) {
927  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
928 
929  dlg.set_single_button(true);
930  dlg.show();
931 
932  const config& scenario = *entry_points[dlg.selected_index()];
933 
934  create_engine_.get_state().mp_settings().hash = scenario.hash();
936  }
937 
938  config_engine_->set_use_map_settings(use_map_settings_->get_widget_value());
939 
940  if(!config_engine_->force_lock_settings()) {
941  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
942  const int num_turns = turns_->get_widget_value();
943  config_engine_->set_num_turns(num_turns < ::settings::turns_max ? num_turns : - 1);
944  config_engine_->set_village_gold(gold_->get_widget_value());
945  config_engine_->set_village_support(support_->get_widget_value());
946  config_engine_->set_xp_modifier(experience_->get_widget_value());
947  config_engine_->set_random_start_time(start_time_->get_widget_value());
948  config_engine_->set_fog_game(fog_->get_widget_value());
949  config_engine_->set_shroud_game(shroud_->get_widget_value());
950 
951  config_engine_->write_parameters();
952  }
953 
954  config_engine_->set_mp_countdown(time_limit_->get_widget_value());
955  config_engine_->set_mp_countdown_init_time(init_turn_limit_->get_widget_value());
956  config_engine_->set_mp_countdown_turn_bonus(turn_bonus_->get_widget_value());
957  config_engine_->set_mp_countdown_reservoir_time(reservoir_->get_widget_value());
958  config_engine_->set_mp_countdown_action_bonus(action_bonus_->get_widget_value());
959 
960  config_engine_->set_allow_observers(observers_->get_widget_value());
961  config_engine_->set_private_replay(private_replay_->get_widget_value());
962  config_engine_->set_oos_debug(strict_sync_->get_widget_value());
963  config_engine_->set_shuffle_sides(shuffle_sides_->get_widget_value());
964 
965  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
966  config_engine_->set_random_faction_mode(type);
967 
968  // Since we don't have a field handling this option, we need to save the value manually
970 
971  // Save custom option settings
972  config_engine_->set_options(options_manager_->get_options_config());
973 
974  // Set game name
975  const std::string name = find_widget<text_box>(&window, "game_name", false).get_value();
976  if(!name.empty() && (name != ng::configure_engine::game_name_default())) {
977  config_engine_->set_game_name(name);
978  }
979 
980  // Set game password
981  const std::string password = find_widget<text_box>(&window, "game_password", false).get_value();
982  if(!password.empty()) {
983  config_engine_->set_game_password(password);
984  }
985  }
986 }
987 
988 } // 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_
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
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)
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
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:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:397
void keyboard_capture(widget *widget)
Definition: window.cpp:1221
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1227
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
Definition: window.hpp:451
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
bool allow_observers()
int turns()
int village_support()
int countdown_init_time()
void set_countdown_reservoir_time(int value)
void set_turns(int value)
void set_village_gold(int value)
void clear_countdown_init_time()
static prefs & get()
void set_countdown_action_bonus(int value)
void set_level_type(int value)
void set_xp_modifier(int value)
int countdown_action_bonus()
bool fog()
void set_countdown(bool value)
void set_countdown_turn_bonus(int value)
void set_village_support(int value)
void set_use_map_settings(bool value)
int village_gold()
void clear_countdown_turn_bonus()
void set_random_faction_mode(const std::string &value)
void set_random_start_time(bool value)
bool random_start_time()
void set_allow_observers(bool value)
void set_shroud(bool value)
void set_countdown_init_time(int value)
void clear_countdown_reservoir_time()
const std::vector< std::string > & modifications(bool mp=true)
void set_era(const std::string &value)
bool countdown()
bool shroud()
void set_modifications(const std::vector< std::string > &value, bool mp=true)
void set_shuffle_sides(bool value)
int countdown_reservoir_time()
int countdown_turn_bonus()
bool shuffle_sides()
void set_level(const std::string &value)
void set_fog(bool value)
int xp_modifier()
bool use_map_settings()
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: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:61
const bool & debug
Definition: game_config.cpp:91
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: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:410
logger & info()
Definition: log.cpp:316
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