The Battle for Wesnoth  1.19.11+dev
mp_create_game.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
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 #include "tod_manager.hpp"
45 
46 #include <boost/algorithm/string.hpp>
47 
48 static lg::log_domain log_mp_create("mp/create");
49 
50 #define DBG_MP LOG_STREAM(debug, log_mp_create)
51 #define WRN_MP LOG_STREAM(warn, log_mp_create)
52 #define ERR_MP LOG_STREAM(err, log_mp_create)
53 
54 namespace gui2::dialogs
55 {
56 
57 // Special retval value for loading a game
58 static const int LOAD_GAME = 100;
59 
61 
62 mp_create_game::mp_create_game(saved_game& state, bool local_mode)
63  : modal_dialog(window_id())
64  , create_engine_(state)
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 
145 // NOLINTNEXTLINE(performance-unnecessary-value-param)
147 {
148  // from constructor
149  ng::create_engine create(state);
150  create.init_active_mods();
151  create.get_state().clear();
152  create.get_state().classification().type = campaign_type::type::multiplayer;
153 
154  // from pre_show
155  create.set_current_level_type(level_type::type::scenario);
156  const auto& levels = create.get_levels_by_type(level_type::type::scenario);
157  for(std::size_t i = 0; i < levels.size(); i++) {
158  if(levels[i]->id() == presets["scenario"].str()) {
159  create.set_current_level(i);
160  }
161  }
162 
163  create.set_current_era_id(presets["era"]);
164 
165  // from post_show
166  create.prepare_for_era_and_mods();
167  create.prepare_for_scenario();
168  create.get_parameters();
169  create.prepare_for_new_level();
170 
171  mp_game_settings& params = create.get_state().mp_settings();
172  params.mp_scenario = presets["scenario"].str();
173  params.use_map_settings = true;
174  params.num_turns = presets["turn_count"].to_int(-1);
175  params.village_gold = presets["village_gold"].to_int();
176  params.village_support = presets["village_support"].to_int();
177  params.xp_modifier = presets["experience_modifier"].to_int();
178  params.random_start_time = presets["random_start_time"].to_bool();
179  params.fog_game = presets["fog"].to_bool();
180  params.shroud_game = presets["shroud"].to_bool();
181 
182  // write to scenario
183  // queue games are supposed to all use the same settings, not be modified by the user
184  // can be removed later if we jump straight from the lobby into a game instead of going to the staging screen to wait for other players to join
185  config& scenario = create.get_state().get_starting_point();
186 
187  if(params.random_start_time) {
188  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
189  scenario["random_start_time"] = true;
190  }
191  } else {
192  scenario["random_start_time"] = false;
193  }
194 
195  scenario["experience_modifier"] = params.xp_modifier;
196  scenario["turns"] = params.num_turns;
197 
198  for(config& side : scenario.child_range("side")) {
199  side["controller_lock"] = true;
200  side["team_lock"] = true;
201  side["gold_lock"] = true;
202  side["income_lock"] = true;
203 
204  side["fog"] = params.fog_game;
205  side["shroud"] = params.shroud_game;
206  side["village_gold"] = params.village_gold;
207  side["village_support"] = params.village_support;
208  }
209 
210  params.mp_countdown = presets["countdown"].to_bool();
211  params.mp_countdown_init_time = std::chrono::seconds{presets["countdown_init_time"].to_int()};
212  params.mp_countdown_turn_bonus = std::chrono::seconds{presets["countdown_turn_bonus"].to_int()};
213  params.mp_countdown_reservoir_time = std::chrono::seconds{presets["countdown_reservoir_time"].to_int()};
214  params.mp_countdown_action_bonus = std::chrono::seconds{presets["countdown_action_bonus"].to_int()};
215 
216  params.allow_observers = true;
217  params.private_replay = false;
218  create.get_state().classification().oos_debug = false;
219  params.shuffle_sides = presets["shuffle_sides"].to_bool();
220 
221  params.mode = random_faction_mode::type::no_mirror;
223 }
224 
226 {
227  find_widget<text_box>("game_name").set_value(local_mode_ ? "" : settings::game_name_default());
228 
230  find_widget<button>("random_map_regenerate"),
231  std::bind(&mp_create_game::regenerate_random_map, this));
232 
234  find_widget<button>("random_map_settings"),
235  std::bind(&mp_create_game::show_generator_settings, this));
236 
238  find_widget<button>("load_game"),
239  std::bind(&mp_create_game::load_game_callback, this));
240 
241  // Custom dialog close hook
243 
244  //
245  // Set up the options manager. Needs to be done before selecting an initial tab
246  //
248 
249  //
250  // Set up filtering
251  //
252  connect_signal_notify_modified(find_widget<slider>("num_players"),
253  std::bind(&mp_create_game::on_filter_change<slider>, this, "num_players", true));
254 
255  text_box& filter = find_widget<text_box>("game_filter");
256 
257  filter.on_modified([this](const auto&) { on_filter_change<text_box>("game_filter", true); });
258 
259  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
261 
262  //
263  // Set up game types menu_button
264  //
265  std::vector<config> game_types;
266  for(level_type_info& type_info : level_types_) {
267  game_types.emplace_back("label", type_info.second);
268  }
269 
270  if(game_types.empty()) {
271  gui2::show_transient_message("", _("No games found."));
272  throw game::error(_("No games found."));
273  }
274 
275  menu_button& game_menu_button = find_widget<menu_button>("game_types");
276 
277  // Helper to make sure the initially selected level type is valid
278  auto get_initial_type_index = [this]()->int {
279  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
280  return info.first == *level_type::get_enum(prefs::get().mp_level_type());
281  });
282 
283  if(index != level_types_.end()) {
284  return std::distance(level_types_.begin(), index);
285  }
286 
287  return 0;
288  };
289 
290  game_menu_button.set_values(game_types, get_initial_type_index());
291 
292  connect_signal_notify_modified(game_menu_button,
293  std::bind(&mp_create_game::update_games_list, this));
294 
295  //
296  // Set up mods list
297  //
298  mod_list_ = &find_widget<listbox>("mod_list");
299 
300  const auto& activemods = prefs::get().modifications();
302  grid* row_grid = &mod_list_->add_row(widget_data{{ "mod_name", {{ "label", mod->name }}}});
303 
304  row_grid->find_widget<toggle_panel>("panel").set_tooltip(mod->description);
305 
306  toggle_button& mog_toggle = row_grid->find_widget<toggle_button>("mod_active_state");
307 
308  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
309  create_engine_.active_mods().push_back(mod->id);
310  mog_toggle.set_value_bool(true);
311  }
312 
313  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
314  }
315 
316  // No mods, hide the header
317  if(mod_list_->get_item_count() <= 0) {
318  find_widget<styled_widget>("mods_header").set_visible(widget::visibility::invisible);
319  }
320 
321  //
322  // Set up eras menu_button
323  //
324  eras_menu_button_ = &find_widget<menu_button>("eras");
325 
326  std::vector<config> era_names;
328  era_names.emplace_back("label", era->name, "tooltip", era->description);
329  }
330 
331  if(era_names.empty()) {
332  gui2::show_transient_message("", _("No eras found."));
333  throw config::error(_("No eras found"));
334  }
335 
336  eras_menu_button_->set_values(era_names);
337 
339  std::bind(&mp_create_game::on_era_select, this));
340 
341  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().mp_era());
342  if(era_selection >= 0) {
343  eras_menu_button_->set_selected(era_selection);
344  }
345 
346  on_era_select();
347 
348  //
349  // Set up random faction mode menu_button
350  //
351  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
352 
353  menu_button& rfm_menu_button = find_widget<menu_button>("random_faction_mode");
354  rfm_menu_button.set_selected(initial_index);
355 
356  connect_signal_notify_modified(rfm_menu_button,
358 
360 
361  //
362  // Set up the setting status labels
363  //
364  bind_status_label<slider>(this, turns_->id());
365  bind_status_label<slider>(this, gold_->id());
366  bind_status_label<slider>(this, support_->id());
367  bind_status_label<slider>(this, experience_->id());
368  bind_status_label<slider>(this, init_turn_limit_->id());
369  bind_status_label<slider>(this, turn_bonus_->id());
370  bind_status_label<slider>(this, reservoir_->id());
371  bind_status_label<slider>(this, action_bonus_->id());
372 
373  //
374  // Timer reset button
375  //
377  find_widget<button>("reset_timer_defaults"),
378  std::bind(&mp_create_game::reset_timer_settings, this));
379 
380  //
381  // Disable certain settings if we're playing a local game.
382  //
383  if(local_mode_) {
384  find_widget<text_box>("game_name").set_active(false);
385  find_widget<text_box>("game_password").set_active(false);
386 
387  observers_->widget_set_enabled(false, false);
388  strict_sync_->widget_set_enabled(false, false);
389  private_replay_->widget_set_enabled(false, false);
390  }
391 
392  //
393  // Set up tab control
394  //
395  listbox& tab_bar = find_widget<listbox>("tab_bar");
396 
398  std::bind(&mp_create_game::on_tab_select, this));
399 
400  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
401  // This ensures settings (especially game settings) widgets are appropriately updated when
402  // a new game is selected, regardless of which settings tab is active at the time.
403  find_widget<stacked_widget>("pager").set_find_in_all_layers(true);
404 
405  // We call on_tab_select farther down.
406 
407  //
408  // Main games list
409  //
410  listbox& list = find_widget<listbox>("games_list");
411 
413  std::bind(&mp_create_game::on_game_select, this));
414 
415  add_to_keyboard_chain(&list);
416 
417  // This handles the initial game selection as well
418  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().mp_level());
419 
420  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
421  on_tab_select();
422 
423  //
424  // Set up the Lua plugin context
425  //
426  plugins_context_.reset(new plugins_context("Multiplayer Create"));
427 
428  plugins_context_->set_callback("create", [this](const config&) { set_retval(retval::OK); }, false);
429  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
430  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
431 
432 #define UPDATE_ATTRIBUTE(field, convert) \
433  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
434 
435  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
436  UPDATE_ATTRIBUTE(turns, to_int);
437  UPDATE_ATTRIBUTE(gold, to_int);
438  UPDATE_ATTRIBUTE(support, to_int);
439  UPDATE_ATTRIBUTE(experience, to_int);
440  UPDATE_ATTRIBUTE(start_time, to_bool);
441  UPDATE_ATTRIBUTE(fog, to_bool);
442  UPDATE_ATTRIBUTE(shroud, to_bool);
443  UPDATE_ATTRIBUTE(time_limit, to_bool);
444  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
445  UPDATE_ATTRIBUTE(turn_bonus, to_int);
446  UPDATE_ATTRIBUTE(reservoir, to_int);
447  UPDATE_ATTRIBUTE(action_bonus, to_int);
448  UPDATE_ATTRIBUTE(observers, to_bool);
449  UPDATE_ATTRIBUTE(strict_sync, to_bool);
450  UPDATE_ATTRIBUTE(private_replay, to_bool);
451  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
452  }, true);
453 
454 #undef UPDATE_ATTRIBUTE
455 
456  plugins_context_->set_callback("set_name", [this](const config& cfg) {
457  create_engine_.get_state().mp_settings().name = cfg["name"];
458  }, true);
459 
460  plugins_context_->set_callback("set_password", [this](const config& cfg) {
461  create_engine_.get_state().mp_settings().password = cfg["password"];
462  }, true);
463 
464  plugins_context_->set_callback("select_level", [this](const config& cfg) {
465  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
467  }, true);
468 
469  plugins_context_->set_callback("select_type", [this](const config& cfg) {
470  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
471 
472  plugins_context_->set_callback("select_era", [this](const config& cfg) {
473  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
474 
475  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
476  on_mod_toggle(cfg["id"].str(), nullptr);
477  }, true);
478 
479  plugins_context_->set_accessor("get_selected", [this](const config&) {
480  const ng::level& current_level = create_engine_.current_level();
481  return config {
482  "id", current_level.id(),
483  "name", current_level.name(),
484  "icon", current_level.icon(),
485  "description", current_level.description(),
486  "allow_era_choice", current_level.allow_era_choice(),
488  };
489  });
490 
491  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
492  const std::string id = cfg["id"].str();
493  auto result = create_engine_.find_level_by_id(id);
494  return config {
495  "index", result.second,
496  "type", level_type::get_string(result.first),
497  };
498  });
499 
500  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
502  });
503 
504  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
506  });
507 }
508 
510 {
511  DBG_MP << "sync_with_depcheck: start";
512 
514  DBG_MP << "sync_with_depcheck: correcting era";
515  const int new_era_index = create_engine_.dependency_manager().get_era_index();
516 
517  create_engine_.set_current_era_index(new_era_index, true);
518  eras_menu_button_->set_value(new_era_index);
519  }
520 
522  DBG_MP << "sync_with_depcheck: correcting scenario";
523 
524  // Match scenario and scenario type
526  const bool different_type = new_level_index.first != create_engine_.current_level_type();
527 
528  if(new_level_index.second != -1) {
529  create_engine_.set_current_level_type(new_level_index.first);
530  create_engine_.set_current_level(new_level_index.second);
531  selected_game_index_ = new_level_index.second;
532 
533  auto& game_types_list = find_widget<menu_button>("game_types");
534  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; })));
535 
536  if(different_type) {
537  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
538  } else {
539  // This function (or rather on_game_select) might be triggered by a listbox callback, in
540  // which case we cannot use display_games_of_type since it destroys the list (and its
541  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
542  // sync_with_depcheck call never changes the game type and goes to this branch instead.
543  find_widget<listbox>("games_list").select_row(new_level_index.second);
544 
545  // Override the last selection so on_game_select selects the new level
547 
548  on_game_select();
549  }
550  }
551  }
552 
554  DBG_MP << "sync_with_depcheck: correcting modifications";
556  }
557 
559  DBG_MP << "sync_with_depcheck: end";
560 }
561 
562 template<typename T>
563 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
564 {
565  create_engine_.apply_level_filter(find_widget<T>(id).get_value());
566 
567  listbox& game_list = find_widget<listbox>("games_list");
568 
569  boost::dynamic_bitset<> filtered(game_list.get_item_count());
571  filtered[i] = true;
572  }
573 
574  game_list.set_row_shown(filtered);
575 
576  if(do_select) {
577  on_game_select();
578  }
579 }
580 
582 {
583  const int selected_game = find_widget<listbox>("games_list").get_selected_row();
584 
585  if(selected_game == selected_game_index_) {
586  return;
587  }
588 
589  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
591 
593 
595 
596  update_details();
597 
598  // General settings
599  const bool can_select_era = create_engine_.current_level().allow_era_choice();
600 
601  if(!can_select_era) {
602  eras_menu_button_->set_label(_("No eras available for this game."));
603  } else {
605  }
606 
607  eras_menu_button_->set_active(can_select_era);
608 
609  // Custom options
610  options_manager_->update_game_options();
611 
612  // Game settings
614 }
615 
617 {
618  const int i = find_widget<listbox>("tab_bar").get_selected_row();
619  find_widget<stacked_widget>("pager").select_layer(i);
620 }
621 
622 void mp_create_game::on_mod_toggle(const std::string& id, toggle_button* sender)
623 {
624  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
625  ERR_MP << "ignoring on_mod_toggle that is already set";
626  return;
627  }
628 
630 
632 
633  options_manager_->update_mod_options();
634 }
635 
637 {
639 
641 
643 
644  options_manager_->update_era_options();
645 }
646 
648 {
649  selected_rfm_index_ = find_widget<menu_button>("random_faction_mode").get_value();
650 }
651 
652 void mp_create_game::show_description(const std::string& new_description)
653 {
654  styled_widget& description = find_widget<styled_widget>("description");
655 
656  description.set_label(!new_description.empty() ? new_description : _("No description available."));
657  description.set_use_markup(true);
658 }
659 
661 {
662  const int index = find_widget<menu_button>("game_types").get_value();
663 
665 }
666 
668 {
670 
671  listbox& list = find_widget<listbox>("games_list");
672 
673  list.clear();
674 
677  widget_item item;
678 
679  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
680  item["label"] = game->icon();
681  data.emplace("game_icon", item);
682  }
683 
684  item["label"] = game->name();
685  data.emplace("game_name", item);
686 
687  grid& rg = list.add_row(data);
688 
689  auto& icon = rg.find_widget<image>("game_icon");
690  if(icon.get_label().empty()) {
691  icon.set_visible(gui2::widget::visibility::invisible);
692  }
693  }
694 
695  if(!level.empty() && !list.get_rows_shown().empty()) {
696  // Recalculate which rows should be visible
697  on_filter_change<slider>("num_players", false);
698  on_filter_change<text_box>("game_filter", false);
699 
700  int level_index = create_engine_.find_level_by_id(level).second;
701  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
702  list.select_row(level_index);
703  }
704  }
705 
706  const bool is_random_map = type == level_type::type::random_map;
707 
708  find_widget<button>("random_map_regenerate").set_active(is_random_map);
709  find_widget<button>("random_map_settings").set_active(is_random_map);
710 
711  // Override the last selection so on_game_select selects the new level
713 
714  on_game_select();
715 }
716 
718 {
720 
722 }
723 
725 {
727 
728  update_details();
729 }
730 
731 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
732 {
733  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
734  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
735 }
736 
738 {
739  styled_widget& players = find_widget<styled_widget>("map_num_players");
740  styled_widget& map_size = find_widget<styled_widget>("map_size");
741 
742  if(create_engine_.current_level_type() == level_type::type::random_map) {
743  // If the current random map doesn't have data, generate it
745  create_engine_.current_level().data()["map_data"].empty() &&
746  create_engine_.current_level().data()["map_file"].empty()) {
748  }
749 
750  find_widget<button>("random_map_settings").set_active(create_engine_.generator_has_settings());
751  }
752 
754 
755  // Reset the mp_parameters with the defaults
757 
758  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
759  std::string title = create_engine_.current_level().name();
760  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
761  find_widget<styled_widget>("game_title").set_label(title);
762 
763 
765  case level_type::type::scenario:
766  case level_type::type::user_map:
767  case level_type::type::user_scenario:
768  case level_type::type::random_map: {
769  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
770 
771  assert(current_scenario);
772 
774 
775  find_widget<stacked_widget>("minimap_stack").select_layer(0);
776 
777  if(current_scenario->data()["map_data"].empty()) {
778  saved_game::expand_map_file(current_scenario->data());
779  current_scenario->set_metadata();
780  }
781 
782  find_widget<minimap>("minimap").set_map_data(current_scenario->data()["map_data"]);
783 
784  players.set_label(std::to_string(current_scenario->num_players()));
785  map_size.set_label(current_scenario->map_size());
786 
787  break;
788  }
789  case level_type::type::campaign:
790  case level_type::type::sp_campaign: {
791  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
792 
793  assert(current_campaign);
794 
795  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
796 
797  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
798 
799  find_widget<stacked_widget>("minimap_stack").select_layer(1);
800  find_widget<image>("campaign_image").set_image(img);
801 
802  const int p_min = current_campaign->min_players();
803  const int p_max = current_campaign->max_players();
804 
805  if(p_max > p_min) {
806  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
807  } else {
808  players.set_label(std::to_string(p_min));
809  }
810 
812 
813  break;
814  }
815  }
816 
817  // This needs to be at the end, since errors found expanding the map data are put into the description
819 }
820 
822 {
824  if(level["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
825  use_map_settings_->widget_set_enabled(false, false);
827  } else {
829  }
830 
832 
833  const bool use_map_settings = create_engine_.get_state().mp_settings().use_map_settings;
834 
835  fog_ ->widget_set_enabled(!use_map_settings, false);
836  shroud_ ->widget_set_enabled(!use_map_settings, false);
837  start_time_ ->widget_set_enabled(!use_map_settings, false);
838 
839  turns_ ->widget_set_enabled(!use_map_settings, false);
840  gold_ ->widget_set_enabled(!use_map_settings, false);
841  support_ ->widget_set_enabled(!use_map_settings, false);
842  experience_ ->widget_set_enabled(!use_map_settings, false);
843 
844  const bool time_limit = time_limit_->get_widget_value();
845 
846  init_turn_limit_->widget_set_enabled(time_limit, false);
847  turn_bonus_ ->widget_set_enabled(time_limit, false);
848  reservoir_ ->widget_set_enabled(time_limit, false);
849  action_bonus_ ->widget_set_enabled(time_limit, false);
850 
851  find_widget<button>("reset_timer_defaults").set_active(time_limit);
852 
856 
861 }
862 
864 {
866 
867  if(!load.load_multiplayer_game()) {
868  return;
869  }
870 
871  if(load.data().cancel_orders) {
873  }
874 
876 }
877 
878 std::vector<std::string> mp_create_game::get_active_mods()
879 {
880  int i = 0;
881  std::set<std::string> res;
883  if(mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").get_value_bool()) {
884  res.insert(mod->id);
885  }
886  ++i;
887  }
888  return std::vector<std::string>(res.begin(), res.end());
889 }
890 
891 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
892 {
893  std::set<std::string> val2(val.begin(), val.end());
894  int i = 0;
895  std::set<std::string> res;
897  mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").set_value_bool(val2.find(mod->id) != val2.end());
898  ++i;
899  }
900 }
901 
903 {
904  // This allows the defaults to be returned by the pref getters below
909 
910  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time().count());
911  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus().count());
912  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time().count());
913  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus().count());
914 }
915 
917 {
919  gui2::show_transient_error_message(_("The selected game has no sides!"));
920  return false;
921  }
922 
924  std::stringstream msg;
925  // 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
926  msg << _("The selected game cannot be created.");
927  msg << "\n\n";
930  return false;
931  }
932 
933  if(!create_engine_.is_campaign()) {
934  return true;
935  }
936 
937  return create_engine_.select_campaign_difficulty() != "CANCEL";
938 }
939 
941 {
942  plugins_context_.reset();
943 
944  // Show all tabs so that find_widget works correctly
945  find_widget<stacked_widget>("pager").select_layer(-1);
946 
947  if(get_retval() == LOAD_GAME) {
949 
950  // 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.
952  return;
953  }
954 
955  if(get_retval() == retval::OK) {
957  prefs::get().set_mp_level_type(static_cast<int>(create_engine_.current_level_type()));
958  prefs::get().set_mp_level(create_engine_.current_level().id());
959  prefs::get().set_mp_era(create_engine_.current_era().id);
960 
962 
963  if(create_engine_.current_level_type() == level_type::type::campaign ||
964  create_engine_.current_level_type() == level_type::type::sp_campaign) {
966  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
968  } else {
969  // This means define= doesn't work for randomly generated scenarios
971  }
972 
974 
976 
977  std::vector<const config*> entry_points;
978  std::vector<std::string> entry_point_titles;
979 
980  const auto& tagname = create_engine_.get_state().classification().get_tagname();
981 
982  if(tagname == "scenario") {
983  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
984  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
985  const bool is_first = scenario["id"] == first_scenario;
986  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
987  const std::string& title = !scenario["new_game_title"].empty()
988  ? scenario["new_game_title"]
989  : scenario["name"];
990 
991  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
992  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
993  }
994  }
995  }
996 
998  if(entry_points.size() > 1) {
999  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
1000 
1001  dlg.set_single_button(true);
1002  dlg.show();
1003 
1004  const config& scenario = *entry_points[dlg.selected_index()];
1005 
1006  params.hash = scenario.hash();
1008  }
1009 
1011 
1012  if(!create_engine_.current_level().data()["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
1013  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
1014  const int num_turns = turns_->get_widget_value();
1015  params.num_turns = num_turns < ::settings::turns_max ? num_turns : - 1;
1016  params.village_gold = gold_->get_widget_value();
1020  params.fog_game = fog_->get_widget_value();
1021  params.shroud_game = shroud_->get_widget_value();
1022 
1023  // write to scenario
1025 
1026  if(params.random_start_time) {
1027  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
1028  scenario["random_start_time"] = true;
1029  }
1030  } else {
1031  scenario["random_start_time"] = false;
1032  }
1033 
1034  scenario["experience_modifier"] = params.xp_modifier;
1035  scenario["turns"] = params.num_turns;
1036 
1037  for(config& side : scenario.child_range("side")) {
1038  if(!params.use_map_settings) {
1039  side["fog"] = params.fog_game;
1040  side["shroud"] = params.shroud_game;
1041  side["village_gold"] = params.village_gold;
1042  side["village_support"] = params.village_support;
1043  } else {
1044  if(side["fog"].empty()) {
1045  side["fog"] = params.fog_game;
1046  }
1047 
1048  if(side["shroud"].empty()) {
1049  side["shroud"] = params.shroud_game;
1050  }
1051 
1052  if(side["village_gold"].empty()) {
1053  side["village_gold"] = params.village_gold;
1054  }
1055 
1056  if(side["village_support"].empty()) {
1057  side["village_support"] = params.village_support;
1058  }
1059  }
1060  }
1061  }
1062 
1064  params.mp_countdown_init_time = std::chrono::seconds{init_turn_limit_->get_widget_value()};
1065  params.mp_countdown_turn_bonus = std::chrono::seconds{turn_bonus_->get_widget_value()};
1066  params.mp_countdown_reservoir_time = std::chrono::seconds{reservoir_->get_widget_value()};
1067  params.mp_countdown_action_bonus = std::chrono::seconds{action_bonus_->get_widget_value()};
1068 
1073 
1074  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
1075  params.mode = type;
1076 
1077  // Since we don't have a field handling this option, we need to save the value manually
1078  prefs::get().set_random_faction_mode(random_faction_mode::get_string(type));
1079 
1080  // Save custom option settings
1081  params.options = options_manager_->get_options_config();
1082  prefs::get().set_options(options_manager_->get_options_config());
1083 
1084  // Set game name
1085  const std::string name = find_widget<text_box>("game_name").get_value();
1086  if(!name.empty() && (name != settings::game_name_default())) {
1087  params.name = name;
1088  }
1089 
1090  // Set game password
1091  const std::string password = find_widget<text_box>("game_password").get_value();
1092  if(!password.empty()) {
1093  params.password = password;
1094  }
1095  }
1096 }
1097 
1098 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
bool empty() const
Definition: config.cpp:845
std::string hash() const
Definition: config.cpp:1279
std::ostringstream wrapper.
Definition: formatter.hpp:40
campaign_type::type type
std::string get_tagname() const
std::string campaign
The id of the campaign being played.
static game_config_manager * get()
Abstract base class for all modal dialogs.
bool show(const unsigned auto_close_time=0)
Shows the window.
std::pair< level_type::type, std::string > level_type_info
void display_games_of_type(level_type::type type, const std::string &level)
virtual void post_show() override
Actions to be taken after the window has been shown.
void set_active_mods(const std::vector< std::string > &val)
void show_description(const std::string &new_description)
virtual void pre_show() override
Actions to be taken before showing the window.
void on_mod_toggle(const std::string &id, toggle_button *sender)
std::unique_ptr< mp_options_helper > options_manager_
std::vector< level_type_info > level_types_
ng::create_engine create_engine_
void on_filter_change(const std::string &id, bool do_select)
static void quick_mp_setup(saved_game &state, const config presets)
presets needs to be a copy! Otherwise you'll get segfaults when clicking the Join button since it res...
std::vector< std::string > get_active_mods()
bool dialog_exit_hook()
Dialog exit hook to bring up the difficulty dialog when starting a campaign.
int convert_to_game_filtered_index(const unsigned int initial_index)
field_bool * use_map_settings_
All fields are also in the normal field vector, but they need to be manually controlled as well so ad...
std::unique_ptr< plugins_context > plugins_context_
void set_single_button(bool value)
Sets whether the Cancel button should be hidden or not.
int selected_index() const
Returns the selected item index after displaying.
const std::string & id() const
Definition: field.hpp:182
void widget_set_enabled(const bool enable, const bool sync)
Enables a widget.
Definition: field.hpp:158
void set_widget_value(CT value)
Sets the value of the field.
Definition: field.hpp:343
T get_widget_value()
Gets the value of the field.
Definition: field.hpp:378
Base container class.
Definition: grid.hpp:32
The listbox class.
Definition: listbox.hpp:41
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:171
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:92
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:267
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:280
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:262
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:153
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:159
void set_selected(unsigned selected, bool fire_event=true)
virtual void set_value(unsigned value, bool fire_event=false) override
Inherited from selectable_item.
Definition: menu_button.hpp:58
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual unsigned get_value() const override
Inherited from selectable_item.
Definition: menu_button.hpp:55
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition: menu_button.cpp:74
void set_value_bool(bool value, bool fire_event=false)
void set_tooltip(const t_string &tooltip)
virtual void set_label(const t_string &text)
virtual void set_use_markup(bool use_markup)
A widget that allows the user to input text in single line.
Definition: text_box.hpp:125
@ invisible
The user set the widget invisible, that means:
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:747
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:395
void keyboard_capture(widget *widget)
Definition: window.cpp:1201
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1207
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
Definition: window.hpp:448
int get_retval()
Definition: window.hpp:402
int max_players() const
int min_players() const
void set_current_era_id(const std::string &id, bool force=false)
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
std::vector< level_ptr > get_levels_by_type(level_type::type type) const
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:441
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_options(const config &values)
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
void clear()
Definition: saved_game.cpp:833
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
void set_scenario(config scenario)
Definition: saved_game.cpp:615
static void expand_map_file(config &scenario)
reads scenario["map_file"]
Definition: saved_game.cpp:499
config & get_starting_point()
Definition: saved_game.cpp:631
void cancel_orders()
Definition: saved_game.cpp:735
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
static bool is_start_ToD(const std::string &)
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:1030
static std::string _(const char *str)
Definition: gettext.hpp:97
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:95
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:189
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
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:318
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition: markup.cpp:31
bool shroud_game_default(ng::create_engine &create)
int village_support_default(ng::create_engine &create)
int village_gold_default(ng::create_engine &create)
int num_turns_default(ng::create_engine &create)
std::string game_name_default()
const int turns_max
maximum number of turns
bool fog_game_default(ng::create_engine &create)
bool random_start_time_default(ng::create_engine &create)
int xp_modifier_default(ng::create_engine &create)
void set_default_values(ng::create_engine &create)
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
constexpr auto filter
Definition: ranges.hpp:38
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:188
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
random_faction_mode::type mode
std::chrono::seconds mp_countdown_reservoir_time
std::chrono::seconds mp_countdown_action_bonus
std::chrono::seconds mp_countdown_turn_bonus
std::chrono::seconds mp_countdown_init_time
std::string mp_scenario
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