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