The Battle for Wesnoth  1.19.5+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 
178  std::bind(&mp_create_game::on_filter_change<text_box>, this, "game_filter", true));
179 
180  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
181  keyboard_capture(&filter);
182 
183  //
184  // Set up game types menu_button
185  //
186  std::vector<config> game_types;
187  for(level_type_info& type_info : level_types_) {
188  game_types.emplace_back("label", type_info.second);
189  }
190 
191  if(game_types.empty()) {
192  gui2::show_transient_message("", _("No games found."));
193  throw game::error(_("No games found."));
194  }
195 
196  menu_button& game_menu_button = find_widget<menu_button>("game_types");
197 
198  // Helper to make sure the initially selected level type is valid
199  auto get_initial_type_index = [this]()->int {
200  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
201  return info.first == *level_type::get_enum(prefs::get().mp_level_type());
202  });
203 
204  if(index != level_types_.end()) {
205  return std::distance(level_types_.begin(), index);
206  }
207 
208  return 0;
209  };
210 
211  game_menu_button.set_values(game_types, get_initial_type_index());
212 
213  connect_signal_notify_modified(game_menu_button,
214  std::bind(&mp_create_game::update_games_list, this));
215 
216  //
217  // Set up mods list
218  //
219  mod_list_ = &find_widget<listbox>("mod_list");
220 
221  const auto& activemods = prefs::get().modifications();
224  widget_item item;
225 
226  item["label"] = mod->name;
227  data.emplace("mod_name", item);
228 
229  grid* row_grid = &mod_list_->add_row(data);
230 
231  row_grid->find_widget<toggle_panel>("panel").set_tooltip(mod->description);
232 
233  toggle_button& mog_toggle = row_grid->find_widget<toggle_button>("mod_active_state");
234 
235  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
236  create_engine_.active_mods().push_back(mod->id);
237  mog_toggle.set_value_bool(true);
238  }
239 
240  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
241  }
242 
243  // No mods, hide the header
244  if(mod_list_->get_item_count() <= 0) {
245  find_widget<styled_widget>("mods_header").set_visible(widget::visibility::invisible);
246  }
247 
248  //
249  // Set up eras menu_button
250  //
251  eras_menu_button_ = &find_widget<menu_button>("eras");
252 
253  std::vector<config> era_names;
255  era_names.emplace_back("label", era->name, "tooltip", era->description);
256  }
257 
258  if(era_names.empty()) {
259  gui2::show_transient_message("", _("No eras found."));
260  throw config::error(_("No eras found"));
261  }
262 
263  eras_menu_button_->set_values(era_names);
264 
266  std::bind(&mp_create_game::on_era_select, this));
267 
268  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().mp_era());
269  if(era_selection >= 0) {
270  eras_menu_button_->set_selected(era_selection);
271  }
272 
273  on_era_select();
274 
275  //
276  // Set up random faction mode menu_button
277  //
278  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
279 
280  menu_button& rfm_menu_button = find_widget<menu_button>("random_faction_mode");
281  rfm_menu_button.set_selected(initial_index);
282 
283  connect_signal_notify_modified(rfm_menu_button,
285 
287 
288  //
289  // Set up the setting status labels
290  //
291  bind_status_label<slider>(this, turns_->id());
292  bind_status_label<slider>(this, gold_->id());
293  bind_status_label<slider>(this, support_->id());
294  bind_status_label<slider>(this, experience_->id());
295  bind_status_label<slider>(this, init_turn_limit_->id());
296  bind_status_label<slider>(this, turn_bonus_->id());
297  bind_status_label<slider>(this, reservoir_->id());
298  bind_status_label<slider>(this, action_bonus_->id());
299 
300  //
301  // Timer reset button
302  //
304  find_widget<button>("reset_timer_defaults"),
305  std::bind(&mp_create_game::reset_timer_settings, this));
306 
307  //
308  // Disable certain settings if we're playing a local game.
309  //
310  if(local_mode_) {
311  find_widget<text_box>("game_name").set_active(false);
312  find_widget<text_box>("game_password").set_active(false);
313 
314  observers_->widget_set_enabled(false, false);
315  strict_sync_->widget_set_enabled(false, false);
316  private_replay_->widget_set_enabled(false, false);
317  }
318 
319  //
320  // Set up tab control
321  //
322  listbox& tab_bar = find_widget<listbox>("tab_bar");
323 
325  std::bind(&mp_create_game::on_tab_select, this));
326 
327  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
328  // This ensures settings (especially game settings) widgets are appropriately updated when
329  // a new game is selected, regardless of which settings tab is active at the time.
330  find_widget<stacked_widget>("pager").set_find_in_all_layers(true);
331 
332  // We call on_tab_select farther down.
333 
334  //
335  // Main games list
336  //
337  listbox& list = find_widget<listbox>("games_list");
338 
340  std::bind(&mp_create_game::on_game_select, this));
341 
342  add_to_keyboard_chain(&list);
343 
344  // This handles the initial game selection as well
345  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().mp_level());
346 
347  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
348  on_tab_select();
349 
350  //
351  // Set up the Lua plugin context
352  //
353  plugins_context_.reset(new plugins_context("Multiplayer Create"));
354 
355  plugins_context_->set_callback("create", [this](const config&) { set_retval(retval::OK); }, false);
356  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
357  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
358 
359 #define UPDATE_ATTRIBUTE(field, convert) \
360  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
361 
362  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
363  UPDATE_ATTRIBUTE(turns, to_int);
364  UPDATE_ATTRIBUTE(gold, to_int);
365  UPDATE_ATTRIBUTE(support, to_int);
366  UPDATE_ATTRIBUTE(experience, to_int);
367  UPDATE_ATTRIBUTE(start_time, to_bool);
368  UPDATE_ATTRIBUTE(fog, to_bool);
369  UPDATE_ATTRIBUTE(shroud, to_bool);
370  UPDATE_ATTRIBUTE(time_limit, to_bool);
371  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
372  UPDATE_ATTRIBUTE(turn_bonus, to_int);
373  UPDATE_ATTRIBUTE(reservoir, to_int);
374  UPDATE_ATTRIBUTE(action_bonus, to_int);
375  UPDATE_ATTRIBUTE(observers, to_bool);
376  UPDATE_ATTRIBUTE(strict_sync, to_bool);
377  UPDATE_ATTRIBUTE(private_replay, to_bool);
378  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
379  }, true);
380 
381 #undef UPDATE_ATTRIBUTE
382 
383  plugins_context_->set_callback("set_name", [this](const config& cfg) {
384  config_engine_->set_game_name(cfg["name"]); }, true);
385 
386  plugins_context_->set_callback("set_password", [this](const config& cfg) {
387  config_engine_->set_game_password(cfg["password"]); }, true);
388 
389  plugins_context_->set_callback("select_level", [this](const config& cfg) {
390  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
392  }, true);
393 
394  plugins_context_->set_callback("select_type", [this](const config& cfg) {
395  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
396 
397  plugins_context_->set_callback("select_era", [this](const config& cfg) {
398  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
399 
400  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
401  on_mod_toggle(cfg["id"].str(), nullptr);
402  }, true);
403 
404  plugins_context_->set_accessor("get_selected", [this](const config&) {
405  const ng::level& current_level = create_engine_.current_level();
406  return config {
407  "id", current_level.id(),
408  "name", current_level.name(),
409  "icon", current_level.icon(),
410  "description", current_level.description(),
411  "allow_era_choice", current_level.allow_era_choice(),
413  };
414  });
415 
416  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
417  const std::string id = cfg["id"].str();
418  return config {
419  "index", create_engine_.find_level_by_id(id).second,
421  };
422  });
423 
424  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
426  });
427 
428  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
430  });
431 }
432 
434 {
435  DBG_MP << "sync_with_depcheck: start";
436 
438  DBG_MP << "sync_with_depcheck: correcting era";
439  const int new_era_index = create_engine_.dependency_manager().get_era_index();
440 
441  create_engine_.set_current_era_index(new_era_index, true);
442  eras_menu_button_->set_value(new_era_index);
443  }
444 
446  DBG_MP << "sync_with_depcheck: correcting scenario";
447 
448  // Match scenario and scenario type
450  const bool different_type = new_level_index.first != create_engine_.current_level_type();
451 
452  if(new_level_index.second != -1) {
453  create_engine_.set_current_level_type(new_level_index.first);
454  create_engine_.set_current_level(new_level_index.second);
455  selected_game_index_ = new_level_index.second;
456 
457  auto& game_types_list = find_widget<menu_button>("game_types");
458  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; })));
459 
460  if(different_type) {
461  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
462  } else {
463  // This function (or rather on_game_select) might be triggered by a listbox callback, in
464  // which case we cannot use display_games_of_type since it destroys the list (and its
465  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
466  // sync_with_depcheck call never changes the game type and goes to this branch instead.
467  find_widget<listbox>("games_list").select_row(new_level_index.second);
468 
469  // Override the last selection so on_game_select selects the new level
471 
472  on_game_select();
473  }
474  }
475  }
476 
478  DBG_MP << "sync_with_depcheck: correcting modifications";
480  }
481 
483  DBG_MP << "sync_with_depcheck: end";
484 }
485 
486 template<typename T>
487 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
488 {
489  create_engine_.apply_level_filter(find_widget<T>(id).get_value());
490 
491  listbox& game_list = find_widget<listbox>("games_list");
492 
493  boost::dynamic_bitset<> filtered(game_list.get_item_count());
495  filtered[i] = true;
496  }
497 
498  game_list.set_row_shown(filtered);
499 
500  if(do_select) {
501  on_game_select();
502  }
503 }
504 
506 {
507  const int selected_game = find_widget<listbox>("games_list").get_selected_row();
508 
509  if(selected_game == selected_game_index_) {
510  return;
511  }
512 
513  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
515 
517 
519 
520  update_details();
521 
522  // General settings
523  const bool can_select_era = create_engine_.current_level().allow_era_choice();
524 
525  if(!can_select_era) {
526  eras_menu_button_->set_label(_("No eras available for this game."));
527  } else {
529  }
530 
531  eras_menu_button_->set_active(can_select_era);
532 
533  // Custom options
534  options_manager_->update_game_options();
535 
536  // Game settings
538 }
539 
541 {
542  const int i = find_widget<listbox>("tab_bar").get_selected_row();
543  find_widget<stacked_widget>("pager").select_layer(i);
544 }
545 
546 void mp_create_game::on_mod_toggle(const std::string id, toggle_button* sender)
547 {
548  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
549  ERR_MP << "ignoring on_mod_toggle that is already set";
550  return;
551  }
552 
554 
556 
557  options_manager_->update_mod_options();
558 }
559 
561 {
563 
565 
567 
568  options_manager_->update_era_options();
569 }
570 
572 {
573  selected_rfm_index_ = find_widget<menu_button>("random_faction_mode").get_value();
574 }
575 
576 void mp_create_game::show_description(const std::string& new_description)
577 {
578  styled_widget& description = find_widget<styled_widget>("description");
579 
580  description.set_label(!new_description.empty() ? new_description : _("No description available."));
581  description.set_use_markup(true);
582 }
583 
585 {
586  const int index = find_widget<menu_button>("game_types").get_value();
587 
589 }
590 
592 {
594 
595  listbox& list = find_widget<listbox>("games_list");
596 
597  list.clear();
598 
601  widget_item item;
602 
603  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
604  item["label"] = game->icon();
605  data.emplace("game_icon", item);
606  }
607 
608  item["label"] = game->name();
609  data.emplace("game_name", item);
610 
611  grid& rg = list.add_row(data);
612 
613  auto& icon = rg.find_widget<image>("game_icon");
614  if(icon.get_label().empty()) {
615  icon.set_visible(gui2::widget::visibility::invisible);
616  }
617  }
618 
619  if(!level.empty() && !list.get_rows_shown().empty()) {
620  // Recalculate which rows should be visible
621  on_filter_change<slider>("num_players", false);
622  on_filter_change<text_box>("game_filter", false);
623 
624  int level_index = create_engine_.find_level_by_id(level).second;
625  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
626  list.select_row(level_index);
627  }
628  }
629 
630  const bool is_random_map = type == level_type::type::random_map;
631 
632  find_widget<button>("random_map_regenerate").set_active(is_random_map);
633  find_widget<button>("random_map_settings").set_active(is_random_map);
634 
635  // Override the last selection so on_game_select selects the new level
637 
638  on_game_select();
639 }
640 
642 {
644 
646 }
647 
649 {
651 
652  update_details();
653 }
654 
655 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
656 {
657  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
658  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
659 }
660 
662 {
663  styled_widget& players = find_widget<styled_widget>("map_num_players");
664  styled_widget& map_size = find_widget<styled_widget>("map_size");
665 
666  if(create_engine_.current_level_type() == level_type::type::random_map) {
667  // If the current random map doesn't have data, generate it
669  create_engine_.current_level().data()["map_data"].empty() &&
670  create_engine_.current_level().data()["map_file"].empty()) {
672  }
673 
674  find_widget<button>("random_map_settings").set_active(create_engine_.generator_has_settings());
675  }
676 
678 
679  // Reset the config_engine with new values
681  config_engine_->update_initial_cfg(create_engine_.current_level().data());
682  config_engine_->set_default_values();
683 
684  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
685  std::string title = create_engine_.current_level().name();
686  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
687  find_widget<styled_widget>("game_title").set_label(title);
688 
689 
691  case level_type::type::scenario:
692  case level_type::type::user_map:
693  case level_type::type::user_scenario:
694  case level_type::type::random_map: {
695  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
696 
697  assert(current_scenario);
698 
700 
701  find_widget<stacked_widget>("minimap_stack").select_layer(0);
702 
703  if(current_scenario->data()["map_data"].empty()) {
704  saved_game::expand_map_file(current_scenario->data());
705  current_scenario->set_metadata();
706  }
707 
708  find_widget<minimap>("minimap").set_map_data(current_scenario->data()["map_data"]);
709 
710  players.set_label(std::to_string(current_scenario->num_players()));
711  map_size.set_label(current_scenario->map_size());
712 
713  break;
714  }
715  case level_type::type::campaign:
716  case level_type::type::sp_campaign: {
717  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
718 
719  assert(current_campaign);
720 
721  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
722 
723  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
724 
725  find_widget<stacked_widget>("minimap_stack").select_layer(1);
726  find_widget<image>("campaign_image").set_image(img);
727 
728  const int p_min = current_campaign->min_players();
729  const int p_max = current_campaign->max_players();
730 
731  if(p_max > p_min) {
732  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
733  } else {
734  players.set_label(std::to_string(p_min));
735  }
736 
738 
739  break;
740  }
741  }
742 
743  // This needs to be at the end, since errors found expanding the map data are put into the description
745 }
746 
748 {
749  if(config_engine_->force_lock_settings()) {
750  use_map_settings_->widget_set_enabled(false, false);
752  } else {
754  }
755 
756  const bool use_map_settings = use_map_settings_->get_widget_value();
757 
758  config_engine_->set_use_map_settings(use_map_settings);
759 
760  fog_ ->widget_set_enabled(!use_map_settings, false);
761  shroud_ ->widget_set_enabled(!use_map_settings, false);
762  start_time_ ->widget_set_enabled(!use_map_settings, false);
763 
764  turns_ ->widget_set_enabled(!use_map_settings, false);
765  gold_ ->widget_set_enabled(!use_map_settings, false);
766  support_ ->widget_set_enabled(!use_map_settings, false);
767  experience_ ->widget_set_enabled(!use_map_settings, false);
768 
769  const bool time_limit = time_limit_->get_widget_value();
770 
771  init_turn_limit_->widget_set_enabled(time_limit, false);
772  turn_bonus_ ->widget_set_enabled(time_limit, false);
773  reservoir_ ->widget_set_enabled(time_limit, false);
774  action_bonus_ ->widget_set_enabled(time_limit, false);
775 
776  find_widget<button>("reset_timer_defaults").set_active(time_limit);
777 
778  if(use_map_settings) {
779  fog_ ->set_widget_value(config_engine_->fog_game_default());
780  shroud_ ->set_widget_value(config_engine_->shroud_game_default());
781  start_time_->set_widget_value(config_engine_->random_start_time_default());
782 
783  turns_ ->set_widget_value(config_engine_->num_turns_default());
784  gold_ ->set_widget_value(config_engine_->village_gold_default());
785  support_ ->set_widget_value(config_engine_->village_support_default());
786  experience_->set_widget_value(config_engine_->xp_modifier_default());
787  }
788 }
789 
791 {
793 
794  if(!load.load_multiplayer_game()) {
795  return;
796  }
797 
798  if(load.data().cancel_orders) {
800  }
801 
803 }
804 
805 std::vector<std::string> mp_create_game::get_active_mods()
806 {
807  int i = 0;
808  std::set<std::string> res;
810  if(mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").get_value_bool()) {
811  res.insert(mod->id);
812  }
813  ++i;
814  }
815  return std::vector<std::string>(res.begin(), res.end());
816 }
817 
818 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
819 {
820  std::set<std::string> val2(val.begin(), val.end());
821  int i = 0;
822  std::set<std::string> res;
824  mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").set_value_bool(val2.find(mod->id) != val2.end());
825  ++i;
826  }
827 }
828 
830 {
831  // This allows the defaults to be returned by the pref getters below
836 
837  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time().count());
838  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus().count());
839  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time().count());
840  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus().count());
841 }
842 
844 {
846  gui2::show_transient_error_message(_("The selected game has no sides!"));
847  return false;
848  }
849 
851  std::stringstream msg;
852  // 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
853  msg << _("The selected game cannot be created.");
854  msg << "\n\n";
857  return false;
858  }
859 
860  if(!create_engine_.is_campaign()) {
861  return true;
862  }
863 
864  return create_engine_.select_campaign_difficulty() != "CANCEL";
865 }
866 
868 {
869  plugins_context_.reset();
870 
871  // Show all tabs so that find_widget works correctly
872  find_widget<stacked_widget>("pager").select_layer(-1);
873 
874  if(get_retval() == LOAD_GAME) {
876 
877  // 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.
879  return;
880  }
881 
882  if(get_retval() == retval::OK) {
884  prefs::get().set_mp_level_type(static_cast<int>(create_engine_.current_level_type()));
885  prefs::get().set_mp_level(create_engine_.current_level().id());
886  prefs::get().set_mp_era(create_engine_.current_era().id);
887 
889 
890  if(create_engine_.current_level_type() == level_type::type::campaign ||
891  create_engine_.current_level_type() == level_type::type::sp_campaign) {
893  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
895  } else {
896  // This means define= doesn't work for randomly generated scenarios
898  }
899 
901 
903 
904  std::vector<const config*> entry_points;
905  std::vector<std::string> entry_point_titles;
906 
907  const auto& tagname = create_engine_.get_state().classification().get_tagname();
908 
909  if(tagname == "scenario") {
910  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
911  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
912  const bool is_first = scenario["id"] == first_scenario;
913  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
914  const std::string& title = !scenario["new_game_title"].empty()
915  ? scenario["new_game_title"]
916  : scenario["name"];
917 
918  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
919  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
920  }
921  }
922  }
923 
924  if(entry_points.size() > 1) {
925  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
926 
927  dlg.set_single_button(true);
928  dlg.show();
929 
930  const config& scenario = *entry_points[dlg.selected_index()];
931 
932  create_engine_.get_state().mp_settings().hash = scenario.hash();
934  }
935 
936  config_engine_->set_use_map_settings(use_map_settings_->get_widget_value());
937 
938  if(!config_engine_->force_lock_settings()) {
939  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
940  const int num_turns = turns_->get_widget_value();
941  config_engine_->set_num_turns(num_turns < ::settings::turns_max ? num_turns : - 1);
942  config_engine_->set_village_gold(gold_->get_widget_value());
943  config_engine_->set_village_support(support_->get_widget_value());
944  config_engine_->set_xp_modifier(experience_->get_widget_value());
945  config_engine_->set_random_start_time(start_time_->get_widget_value());
946  config_engine_->set_fog_game(fog_->get_widget_value());
947  config_engine_->set_shroud_game(shroud_->get_widget_value());
948 
949  config_engine_->write_parameters();
950  }
951 
952  config_engine_->set_mp_countdown(time_limit_->get_widget_value());
953  config_engine_->set_mp_countdown_init_time(std::chrono::seconds{init_turn_limit_->get_widget_value()});
954  config_engine_->set_mp_countdown_turn_bonus(std::chrono::seconds{turn_bonus_->get_widget_value()});
955  config_engine_->set_mp_countdown_reservoir_time(std::chrono::seconds{reservoir_->get_widget_value()});
956  config_engine_->set_mp_countdown_action_bonus(std::chrono::seconds{action_bonus_->get_widget_value()});
957 
958  config_engine_->set_allow_observers(observers_->get_widget_value());
959  config_engine_->set_private_replay(private_replay_->get_widget_value());
960  config_engine_->set_oos_debug(strict_sync_->get_widget_value());
961  config_engine_->set_shuffle_sides(shuffle_sides_->get_widget_value());
962 
963  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
964  config_engine_->set_random_faction_mode(type);
965 
966  // Since we don't have a field handling this option, we need to save the value manually
967  prefs::get().set_random_faction_mode(random_faction_mode::get_string(type));
968 
969  // Save custom option settings
970  config_engine_->set_options(options_manager_->get_options_config());
971 
972  // Set game name
973  const std::string name = find_widget<text_box>("game_name").get_value();
974  if(!name.empty() && (name != ng::configure_engine::game_name_default())) {
975  config_engine_->set_game_name(name);
976  }
977 
978  // Set game password
979  const std::string password = find_widget<text_box>("game_password").get_value();
980  if(!password.empty()) {
981  config_engine_->set_game_password(password);
982  }
983  }
984 }
985 
986 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
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.
int get_retval() const
Returns the cached window exit code.
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)
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.
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: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:43
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:135
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:58
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:229
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:242
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:213
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:117
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:123
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
NOT_DANGLING T * find_widget(const std::string &id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:742
@ 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:395
void keyboard_capture(widget *widget)
Definition: window.cpp:1207
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1213
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
Definition: window.hpp:449
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
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:596
static void expand_map_file(config &scenario)
reads scenario["map_file"]
Definition: saved_game.cpp:476
void cancel_orders()
Definition: saved_game.cpp:716
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:1028
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: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, const bool floating)
Definition: markup.cpp:29
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
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:100
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