The Battle for Wesnoth  1.19.9+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.use_map_settings = true;
173  params.num_turns = presets["turn_count"].to_int(-1);
174  params.village_gold = presets["village_gold"].to_int();
175  params.village_support = presets["village_support"].to_int();
176  params.xp_modifier = presets["experience_modifier"].to_int();
177  params.random_start_time = presets["random_start_time"].to_bool();
178  params.fog_game = presets["fog"].to_bool();
179  params.shroud_game = presets["shroud"].to_bool();
180 
181  // write to scenario
182  // queue games are supposed to all use the same settings, not be modified by the user
183  // 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
184  config& scenario = create.get_state().get_starting_point();
185 
186  if(params.random_start_time) {
187  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
188  scenario["random_start_time"] = true;
189  }
190  } else {
191  scenario["random_start_time"] = false;
192  }
193 
194  scenario["experience_modifier"] = params.xp_modifier;
195  scenario["turns"] = params.num_turns;
196 
197  for(config& side : scenario.child_range("side")) {
198  side["controller_lock"] = true;
199  side["team_lock"] = true;
200  side["gold_lock"] = true;
201  side["income_lock"] = true;
202 
203  side["fog"] = params.fog_game;
204  side["shroud"] = params.shroud_game;
205  side["village_gold"] = params.village_gold;
206  side["village_support"] = params.village_support;
207  }
208 
209  params.mp_countdown = presets["countdown"].to_bool();
210  params.mp_countdown_init_time = std::chrono::seconds{presets["countdown_init_time"].to_int()};
211  params.mp_countdown_turn_bonus = std::chrono::seconds{presets["countdown_turn_bonus"].to_int()};
212  params.mp_countdown_reservoir_time = std::chrono::seconds{presets["countdown_reservoir_time"].to_int()};
213  params.mp_countdown_action_bonus = std::chrono::seconds{presets["countdown_action_bonus"].to_int()};
214 
215  params.allow_observers = true;
216  params.private_replay = false;
217  create.get_state().classification().oos_debug = false;
218  params.shuffle_sides = presets["shuffle_sides"].to_bool();
219 
220  params.mode = random_faction_mode::type::no_mirror;
222 }
223 
225 {
226  find_widget<text_box>("game_name").set_value(local_mode_ ? "" : settings::game_name_default());
227 
229  find_widget<button>("random_map_regenerate"),
230  std::bind(&mp_create_game::regenerate_random_map, this));
231 
233  find_widget<button>("random_map_settings"),
234  std::bind(&mp_create_game::show_generator_settings, this));
235 
237  find_widget<button>("load_game"),
238  std::bind(&mp_create_game::load_game_callback, this));
239 
240  // Custom dialog close hook
242 
243  //
244  // Set up the options manager. Needs to be done before selecting an initial tab
245  //
247 
248  //
249  // Set up filtering
250  //
251  connect_signal_notify_modified(find_widget<slider>("num_players"),
252  std::bind(&mp_create_game::on_filter_change<slider>, this, "num_players", true));
253 
254  text_box& filter = find_widget<text_box>("game_filter");
255 
256  filter.on_modified([this](const auto&) { on_filter_change<text_box>("game_filter", true); });
257 
258  // Note this cannot be in the keyboard chain or it will capture focus from other text boxes
260 
261  //
262  // Set up game types menu_button
263  //
264  std::vector<config> game_types;
265  for(level_type_info& type_info : level_types_) {
266  game_types.emplace_back("label", type_info.second);
267  }
268 
269  if(game_types.empty()) {
270  gui2::show_transient_message("", _("No games found."));
271  throw game::error(_("No games found."));
272  }
273 
274  menu_button& game_menu_button = find_widget<menu_button>("game_types");
275 
276  // Helper to make sure the initially selected level type is valid
277  auto get_initial_type_index = [this]()->int {
278  const auto index = std::find_if(level_types_.begin(), level_types_.end(), [](level_type_info& info) {
279  return info.first == *level_type::get_enum(prefs::get().mp_level_type());
280  });
281 
282  if(index != level_types_.end()) {
283  return std::distance(level_types_.begin(), index);
284  }
285 
286  return 0;
287  };
288 
289  game_menu_button.set_values(game_types, get_initial_type_index());
290 
291  connect_signal_notify_modified(game_menu_button,
292  std::bind(&mp_create_game::update_games_list, this));
293 
294  //
295  // Set up mods list
296  //
297  mod_list_ = &find_widget<listbox>("mod_list");
298 
299  const auto& activemods = prefs::get().modifications();
302  widget_item item;
303 
304  item["label"] = mod->name;
305  data.emplace("mod_name", item);
306 
307  grid* row_grid = &mod_list_->add_row(data);
308 
309  row_grid->find_widget<toggle_panel>("panel").set_tooltip(mod->description);
310 
311  toggle_button& mog_toggle = row_grid->find_widget<toggle_button>("mod_active_state");
312 
313  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
314  create_engine_.active_mods().push_back(mod->id);
315  mog_toggle.set_value_bool(true);
316  }
317 
318  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
319  }
320 
321  // No mods, hide the header
322  if(mod_list_->get_item_count() <= 0) {
323  find_widget<styled_widget>("mods_header").set_visible(widget::visibility::invisible);
324  }
325 
326  //
327  // Set up eras menu_button
328  //
329  eras_menu_button_ = &find_widget<menu_button>("eras");
330 
331  std::vector<config> era_names;
333  era_names.emplace_back("label", era->name, "tooltip", era->description);
334  }
335 
336  if(era_names.empty()) {
337  gui2::show_transient_message("", _("No eras found."));
338  throw config::error(_("No eras found"));
339  }
340 
341  eras_menu_button_->set_values(era_names);
342 
344  std::bind(&mp_create_game::on_era_select, this));
345 
346  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().mp_era());
347  if(era_selection >= 0) {
348  eras_menu_button_->set_selected(era_selection);
349  }
350 
351  on_era_select();
352 
353  //
354  // Set up random faction mode menu_button
355  //
356  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
357 
358  menu_button& rfm_menu_button = find_widget<menu_button>("random_faction_mode");
359  rfm_menu_button.set_selected(initial_index);
360 
361  connect_signal_notify_modified(rfm_menu_button,
363 
365 
366  //
367  // Set up the setting status labels
368  //
369  bind_status_label<slider>(this, turns_->id());
370  bind_status_label<slider>(this, gold_->id());
371  bind_status_label<slider>(this, support_->id());
372  bind_status_label<slider>(this, experience_->id());
373  bind_status_label<slider>(this, init_turn_limit_->id());
374  bind_status_label<slider>(this, turn_bonus_->id());
375  bind_status_label<slider>(this, reservoir_->id());
376  bind_status_label<slider>(this, action_bonus_->id());
377 
378  //
379  // Timer reset button
380  //
382  find_widget<button>("reset_timer_defaults"),
383  std::bind(&mp_create_game::reset_timer_settings, this));
384 
385  //
386  // Disable certain settings if we're playing a local game.
387  //
388  if(local_mode_) {
389  find_widget<text_box>("game_name").set_active(false);
390  find_widget<text_box>("game_password").set_active(false);
391 
392  observers_->widget_set_enabled(false, false);
393  strict_sync_->widget_set_enabled(false, false);
394  private_replay_->widget_set_enabled(false, false);
395  }
396 
397  //
398  // Set up tab control
399  //
400  listbox& tab_bar = find_widget<listbox>("tab_bar");
401 
403  std::bind(&mp_create_game::on_tab_select, this));
404 
405  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
406  // This ensures settings (especially game settings) widgets are appropriately updated when
407  // a new game is selected, regardless of which settings tab is active at the time.
408  find_widget<stacked_widget>("pager").set_find_in_all_layers(true);
409 
410  // We call on_tab_select farther down.
411 
412  //
413  // Main games list
414  //
415  listbox& list = find_widget<listbox>("games_list");
416 
418  std::bind(&mp_create_game::on_game_select, this));
419 
420  add_to_keyboard_chain(&list);
421 
422  // This handles the initial game selection as well
423  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().mp_level());
424 
425  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
426  on_tab_select();
427 
428  //
429  // Set up the Lua plugin context
430  //
431  plugins_context_.reset(new plugins_context("Multiplayer Create"));
432 
433  plugins_context_->set_callback("create", [this](const config&) { set_retval(retval::OK); }, false);
434  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
435  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
436 
437 #define UPDATE_ATTRIBUTE(field, convert) \
438  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
439 
440  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
441  UPDATE_ATTRIBUTE(turns, to_int);
442  UPDATE_ATTRIBUTE(gold, to_int);
443  UPDATE_ATTRIBUTE(support, to_int);
444  UPDATE_ATTRIBUTE(experience, to_int);
445  UPDATE_ATTRIBUTE(start_time, to_bool);
446  UPDATE_ATTRIBUTE(fog, to_bool);
447  UPDATE_ATTRIBUTE(shroud, to_bool);
448  UPDATE_ATTRIBUTE(time_limit, to_bool);
449  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
450  UPDATE_ATTRIBUTE(turn_bonus, to_int);
451  UPDATE_ATTRIBUTE(reservoir, to_int);
452  UPDATE_ATTRIBUTE(action_bonus, to_int);
453  UPDATE_ATTRIBUTE(observers, to_bool);
454  UPDATE_ATTRIBUTE(strict_sync, to_bool);
455  UPDATE_ATTRIBUTE(private_replay, to_bool);
456  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
457  }, true);
458 
459 #undef UPDATE_ATTRIBUTE
460 
461  plugins_context_->set_callback("set_name", [this](const config& cfg) {
462  create_engine_.get_state().mp_settings().name = cfg["name"];
463  }, true);
464 
465  plugins_context_->set_callback("set_password", [this](const config& cfg) {
466  create_engine_.get_state().mp_settings().password = cfg["password"];
467  }, true);
468 
469  plugins_context_->set_callback("select_level", [this](const config& cfg) {
470  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
472  }, true);
473 
474  plugins_context_->set_callback("select_type", [this](const config& cfg) {
475  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
476 
477  plugins_context_->set_callback("select_era", [this](const config& cfg) {
478  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
479 
480  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
481  on_mod_toggle(cfg["id"].str(), nullptr);
482  }, true);
483 
484  plugins_context_->set_accessor("get_selected", [this](const config&) {
485  const ng::level& current_level = create_engine_.current_level();
486  return config {
487  "id", current_level.id(),
488  "name", current_level.name(),
489  "icon", current_level.icon(),
490  "description", current_level.description(),
491  "allow_era_choice", current_level.allow_era_choice(),
493  };
494  });
495 
496  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
497  const std::string id = cfg["id"].str();
498  auto result = create_engine_.find_level_by_id(id);
499  return config {
500  "index", result.second,
501  "type", level_type::get_string(result.first),
502  };
503  });
504 
505  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
507  });
508 
509  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
511  });
512 }
513 
515 {
516  DBG_MP << "sync_with_depcheck: start";
517 
519  DBG_MP << "sync_with_depcheck: correcting era";
520  const int new_era_index = create_engine_.dependency_manager().get_era_index();
521 
522  create_engine_.set_current_era_index(new_era_index, true);
523  eras_menu_button_->set_value(new_era_index);
524  }
525 
527  DBG_MP << "sync_with_depcheck: correcting scenario";
528 
529  // Match scenario and scenario type
531  const bool different_type = new_level_index.first != create_engine_.current_level_type();
532 
533  if(new_level_index.second != -1) {
534  create_engine_.set_current_level_type(new_level_index.first);
535  create_engine_.set_current_level(new_level_index.second);
536  selected_game_index_ = new_level_index.second;
537 
538  auto& game_types_list = find_widget<menu_button>("game_types");
539  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; })));
540 
541  if(different_type) {
542  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
543  } else {
544  // This function (or rather on_game_select) might be triggered by a listbox callback, in
545  // which case we cannot use display_games_of_type since it destroys the list (and its
546  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
547  // sync_with_depcheck call never changes the game type and goes to this branch instead.
548  find_widget<listbox>("games_list").select_row(new_level_index.second);
549 
550  // Override the last selection so on_game_select selects the new level
552 
553  on_game_select();
554  }
555  }
556  }
557 
559  DBG_MP << "sync_with_depcheck: correcting modifications";
561  }
562 
564  DBG_MP << "sync_with_depcheck: end";
565 }
566 
567 template<typename T>
568 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
569 {
570  create_engine_.apply_level_filter(find_widget<T>(id).get_value());
571 
572  listbox& game_list = find_widget<listbox>("games_list");
573 
574  boost::dynamic_bitset<> filtered(game_list.get_item_count());
576  filtered[i] = true;
577  }
578 
579  game_list.set_row_shown(filtered);
580 
581  if(do_select) {
582  on_game_select();
583  }
584 }
585 
587 {
588  const int selected_game = find_widget<listbox>("games_list").get_selected_row();
589 
590  if(selected_game == selected_game_index_) {
591  return;
592  }
593 
594  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
596 
598 
600 
601  update_details();
602 
603  // General settings
604  const bool can_select_era = create_engine_.current_level().allow_era_choice();
605 
606  if(!can_select_era) {
607  eras_menu_button_->set_label(_("No eras available for this game."));
608  } else {
610  }
611 
612  eras_menu_button_->set_active(can_select_era);
613 
614  // Custom options
615  options_manager_->update_game_options();
616 
617  // Game settings
619 }
620 
622 {
623  const int i = find_widget<listbox>("tab_bar").get_selected_row();
624  find_widget<stacked_widget>("pager").select_layer(i);
625 }
626 
627 void mp_create_game::on_mod_toggle(const std::string& id, toggle_button* sender)
628 {
629  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
630  ERR_MP << "ignoring on_mod_toggle that is already set";
631  return;
632  }
633 
635 
637 
638  options_manager_->update_mod_options();
639 }
640 
642 {
644 
646 
648 
649  options_manager_->update_era_options();
650 }
651 
653 {
654  selected_rfm_index_ = find_widget<menu_button>("random_faction_mode").get_value();
655 }
656 
657 void mp_create_game::show_description(const std::string& new_description)
658 {
659  styled_widget& description = find_widget<styled_widget>("description");
660 
661  description.set_label(!new_description.empty() ? new_description : _("No description available."));
662  description.set_use_markup(true);
663 }
664 
666 {
667  const int index = find_widget<menu_button>("game_types").get_value();
668 
670 }
671 
673 {
675 
676  listbox& list = find_widget<listbox>("games_list");
677 
678  list.clear();
679 
682  widget_item item;
683 
684  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
685  item["label"] = game->icon();
686  data.emplace("game_icon", item);
687  }
688 
689  item["label"] = game->name();
690  data.emplace("game_name", item);
691 
692  grid& rg = list.add_row(data);
693 
694  auto& icon = rg.find_widget<image>("game_icon");
695  if(icon.get_label().empty()) {
696  icon.set_visible(gui2::widget::visibility::invisible);
697  }
698  }
699 
700  if(!level.empty() && !list.get_rows_shown().empty()) {
701  // Recalculate which rows should be visible
702  on_filter_change<slider>("num_players", false);
703  on_filter_change<text_box>("game_filter", false);
704 
705  int level_index = create_engine_.find_level_by_id(level).second;
706  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
707  list.select_row(level_index);
708  }
709  }
710 
711  const bool is_random_map = type == level_type::type::random_map;
712 
713  find_widget<button>("random_map_regenerate").set_active(is_random_map);
714  find_widget<button>("random_map_settings").set_active(is_random_map);
715 
716  // Override the last selection so on_game_select selects the new level
718 
719  on_game_select();
720 }
721 
723 {
725 
727 }
728 
730 {
732 
733  update_details();
734 }
735 
736 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
737 {
738  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
739  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
740 }
741 
743 {
744  styled_widget& players = find_widget<styled_widget>("map_num_players");
745  styled_widget& map_size = find_widget<styled_widget>("map_size");
746 
747  if(create_engine_.current_level_type() == level_type::type::random_map) {
748  // If the current random map doesn't have data, generate it
750  create_engine_.current_level().data()["map_data"].empty() &&
751  create_engine_.current_level().data()["map_file"].empty()) {
753  }
754 
755  find_widget<button>("random_map_settings").set_active(create_engine_.generator_has_settings());
756  }
757 
759 
760  // Reset the mp_parameters with the defaults
762 
763  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
764  std::string title = create_engine_.current_level().name();
765  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
766  find_widget<styled_widget>("game_title").set_label(title);
767 
768 
770  case level_type::type::scenario:
771  case level_type::type::user_map:
772  case level_type::type::user_scenario:
773  case level_type::type::random_map: {
774  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
775 
776  assert(current_scenario);
777 
779 
780  find_widget<stacked_widget>("minimap_stack").select_layer(0);
781 
782  if(current_scenario->data()["map_data"].empty()) {
783  saved_game::expand_map_file(current_scenario->data());
784  current_scenario->set_metadata();
785  }
786 
787  find_widget<minimap>("minimap").set_map_data(current_scenario->data()["map_data"]);
788 
789  players.set_label(std::to_string(current_scenario->num_players()));
790  map_size.set_label(current_scenario->map_size());
791 
792  break;
793  }
794  case level_type::type::campaign:
795  case level_type::type::sp_campaign: {
796  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
797 
798  assert(current_campaign);
799 
800  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
801 
802  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
803 
804  find_widget<stacked_widget>("minimap_stack").select_layer(1);
805  find_widget<image>("campaign_image").set_image(img);
806 
807  const int p_min = current_campaign->min_players();
808  const int p_max = current_campaign->max_players();
809 
810  if(p_max > p_min) {
811  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
812  } else {
813  players.set_label(std::to_string(p_min));
814  }
815 
817 
818  break;
819  }
820  }
821 
822  // This needs to be at the end, since errors found expanding the map data are put into the description
824 }
825 
827 {
829  if(level["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
830  use_map_settings_->widget_set_enabled(false, false);
832  } else {
834  }
835 
837 
838  const bool use_map_settings = create_engine_.get_state().mp_settings().use_map_settings;
839 
840  fog_ ->widget_set_enabled(!use_map_settings, false);
841  shroud_ ->widget_set_enabled(!use_map_settings, false);
842  start_time_ ->widget_set_enabled(!use_map_settings, false);
843 
844  turns_ ->widget_set_enabled(!use_map_settings, false);
845  gold_ ->widget_set_enabled(!use_map_settings, false);
846  support_ ->widget_set_enabled(!use_map_settings, false);
847  experience_ ->widget_set_enabled(!use_map_settings, false);
848 
849  const bool time_limit = time_limit_->get_widget_value();
850 
851  init_turn_limit_->widget_set_enabled(time_limit, false);
852  turn_bonus_ ->widget_set_enabled(time_limit, false);
853  reservoir_ ->widget_set_enabled(time_limit, false);
854  action_bonus_ ->widget_set_enabled(time_limit, false);
855 
856  find_widget<button>("reset_timer_defaults").set_active(time_limit);
857 
861 
866 }
867 
869 {
871 
872  if(!load.load_multiplayer_game()) {
873  return;
874  }
875 
876  if(load.data().cancel_orders) {
878  }
879 
881 }
882 
883 std::vector<std::string> mp_create_game::get_active_mods()
884 {
885  int i = 0;
886  std::set<std::string> res;
888  if(mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").get_value_bool()) {
889  res.insert(mod->id);
890  }
891  ++i;
892  }
893  return std::vector<std::string>(res.begin(), res.end());
894 }
895 
896 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
897 {
898  std::set<std::string> val2(val.begin(), val.end());
899  int i = 0;
900  std::set<std::string> res;
902  mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").set_value_bool(val2.find(mod->id) != val2.end());
903  ++i;
904  }
905 }
906 
908 {
909  // This allows the defaults to be returned by the pref getters below
914 
915  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time().count());
916  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus().count());
917  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time().count());
918  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus().count());
919 }
920 
922 {
924  gui2::show_transient_error_message(_("The selected game has no sides!"));
925  return false;
926  }
927 
929  std::stringstream msg;
930  // 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
931  msg << _("The selected game cannot be created.");
932  msg << "\n\n";
935  return false;
936  }
937 
938  if(!create_engine_.is_campaign()) {
939  return true;
940  }
941 
942  return create_engine_.select_campaign_difficulty() != "CANCEL";
943 }
944 
946 {
947  plugins_context_.reset();
948 
949  // Show all tabs so that find_widget works correctly
950  find_widget<stacked_widget>("pager").select_layer(-1);
951 
952  if(get_retval() == LOAD_GAME) {
954 
955  // 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.
957  return;
958  }
959 
960  if(get_retval() == retval::OK) {
962  prefs::get().set_mp_level_type(static_cast<int>(create_engine_.current_level_type()));
963  prefs::get().set_mp_level(create_engine_.current_level().id());
964  prefs::get().set_mp_era(create_engine_.current_era().id);
965 
967 
968  if(create_engine_.current_level_type() == level_type::type::campaign ||
969  create_engine_.current_level_type() == level_type::type::sp_campaign) {
971  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
973  } else {
974  // This means define= doesn't work for randomly generated scenarios
976  }
977 
979 
981 
982  std::vector<const config*> entry_points;
983  std::vector<std::string> entry_point_titles;
984 
985  const auto& tagname = create_engine_.get_state().classification().get_tagname();
986 
987  if(tagname == "scenario") {
988  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
989  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
990  const bool is_first = scenario["id"] == first_scenario;
991  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
992  const std::string& title = !scenario["new_game_title"].empty()
993  ? scenario["new_game_title"]
994  : scenario["name"];
995 
996  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
997  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
998  }
999  }
1000  }
1001 
1003  if(entry_points.size() > 1) {
1004  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
1005 
1006  dlg.set_single_button(true);
1007  dlg.show();
1008 
1009  const config& scenario = *entry_points[dlg.selected_index()];
1010 
1011  params.hash = scenario.hash();
1013  }
1014 
1016 
1017  if(!create_engine_.current_level().data()["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
1018  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
1019  const int num_turns = turns_->get_widget_value();
1020  params.num_turns = num_turns < ::settings::turns_max ? num_turns : - 1;
1021  params.village_gold = gold_->get_widget_value();
1025  params.fog_game = fog_->get_widget_value();
1026  params.shroud_game = shroud_->get_widget_value();
1027 
1028  // write to scenario
1030 
1031  if(params.random_start_time) {
1032  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
1033  scenario["random_start_time"] = true;
1034  }
1035  } else {
1036  scenario["random_start_time"] = false;
1037  }
1038 
1039  scenario["experience_modifier"] = params.xp_modifier;
1040  scenario["turns"] = params.num_turns;
1041 
1042  for(config& side : scenario.child_range("side")) {
1043  if(!params.use_map_settings) {
1044  side["fog"] = params.fog_game;
1045  side["shroud"] = params.shroud_game;
1046  side["village_gold"] = params.village_gold;
1047  side["village_support"] = params.village_support;
1048  } else {
1049  if(side["fog"].empty()) {
1050  side["fog"] = params.fog_game;
1051  }
1052 
1053  if(side["shroud"].empty()) {
1054  side["shroud"] = params.shroud_game;
1055  }
1056 
1057  if(side["village_gold"].empty()) {
1058  side["village_gold"] = params.village_gold;
1059  }
1060 
1061  if(side["village_support"].empty()) {
1062  side["village_support"] = params.village_support;
1063  }
1064  }
1065  }
1066  }
1067 
1069  params.mp_countdown_init_time = std::chrono::seconds{init_turn_limit_->get_widget_value()};
1070  params.mp_countdown_turn_bonus = std::chrono::seconds{turn_bonus_->get_widget_value()};
1071  params.mp_countdown_reservoir_time = std::chrono::seconds{reservoir_->get_widget_value()};
1072  params.mp_countdown_action_bonus = std::chrono::seconds{action_bonus_->get_widget_value()};
1073 
1078 
1079  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
1080  params.mode = type;
1081 
1082  // Since we don't have a field handling this option, we need to save the value manually
1083  prefs::get().set_random_faction_mode(random_faction_mode::get_string(type));
1084 
1085  // Save custom option settings
1086  params.options = options_manager_->get_options_config();
1087  prefs::get().set_options(options_manager_->get_options_config());
1088 
1089  // Set game name
1090  const std::string name = find_widget<text_box>("game_name").get_value();
1091  if(!name.empty() && (name != settings::game_name_default())) {
1092  params.name = name;
1093  }
1094 
1095  // Set game password
1096  const std::string password = find_widget<text_box>("game_password").get_value();
1097  if(!password.empty()) {
1098  params.password = password;
1099  }
1100  }
1101 }
1102 
1103 } // 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:1193
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1199
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:836
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
void set_scenario(config scenario)
Definition: saved_game.cpp:618
static void expand_map_file(config &scenario)
reads scenario["map_file"]
Definition: saved_game.cpp:498
config & get_starting_point()
Definition: saved_game.cpp:634
void cancel_orders()
Definition: saved_game.cpp:738
The class for loading a savefile.
Definition: savegame.hpp:101
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:209
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:1022
static std::string _(const char *str)
Definition: gettext.hpp:105
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:203
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:37
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
void show_transient_error_message(const std::string &message, const std::string &image, const bool message_use_markup)
Shows a transient error message to the user.
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
Functions to load and save images from/to disk.
logger & info()
Definition: log.cpp:319
std::string img(const std::string &src, const std::string &align, 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:178
string_enums::enum_base< random_faction_mode_defines > random_faction_mode
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
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
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