The Battle for Wesnoth  1.19.10+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();
301  grid* row_grid = &mod_list_->add_row(widget_data{{ "mod_name", {{ "label", mod->name }}}});
302 
303  row_grid->find_widget<toggle_panel>("panel").set_tooltip(mod->description);
304 
305  toggle_button& mog_toggle = row_grid->find_widget<toggle_button>("mod_active_state");
306 
307  if(std::find(activemods.begin(), activemods.end(), mod->id) != activemods.end()) {
308  create_engine_.active_mods().push_back(mod->id);
309  mog_toggle.set_value_bool(true);
310  }
311 
312  connect_signal_notify_modified(mog_toggle, std::bind(&mp_create_game::on_mod_toggle, this, mod->id, &mog_toggle));
313  }
314 
315  // No mods, hide the header
316  if(mod_list_->get_item_count() <= 0) {
317  find_widget<styled_widget>("mods_header").set_visible(widget::visibility::invisible);
318  }
319 
320  //
321  // Set up eras menu_button
322  //
323  eras_menu_button_ = &find_widget<menu_button>("eras");
324 
325  std::vector<config> era_names;
327  era_names.emplace_back("label", era->name, "tooltip", era->description);
328  }
329 
330  if(era_names.empty()) {
331  gui2::show_transient_message("", _("No eras found."));
332  throw config::error(_("No eras found"));
333  }
334 
335  eras_menu_button_->set_values(era_names);
336 
338  std::bind(&mp_create_game::on_era_select, this));
339 
340  const int era_selection = create_engine_.find_extra_by_id(ng::create_engine::ERA, prefs::get().mp_era());
341  if(era_selection >= 0) {
342  eras_menu_button_->set_selected(era_selection);
343  }
344 
345  on_era_select();
346 
347  //
348  // Set up random faction mode menu_button
349  //
350  const int initial_index = static_cast<int>(random_faction_mode::get_enum(prefs::get().random_faction_mode()).value_or(random_faction_mode::type::independent));
351 
352  menu_button& rfm_menu_button = find_widget<menu_button>("random_faction_mode");
353  rfm_menu_button.set_selected(initial_index);
354 
355  connect_signal_notify_modified(rfm_menu_button,
357 
359 
360  //
361  // Set up the setting status labels
362  //
363  bind_status_label<slider>(this, turns_->id());
364  bind_status_label<slider>(this, gold_->id());
365  bind_status_label<slider>(this, support_->id());
366  bind_status_label<slider>(this, experience_->id());
367  bind_status_label<slider>(this, init_turn_limit_->id());
368  bind_status_label<slider>(this, turn_bonus_->id());
369  bind_status_label<slider>(this, reservoir_->id());
370  bind_status_label<slider>(this, action_bonus_->id());
371 
372  //
373  // Timer reset button
374  //
376  find_widget<button>("reset_timer_defaults"),
377  std::bind(&mp_create_game::reset_timer_settings, this));
378 
379  //
380  // Disable certain settings if we're playing a local game.
381  //
382  if(local_mode_) {
383  find_widget<text_box>("game_name").set_active(false);
384  find_widget<text_box>("game_password").set_active(false);
385 
386  observers_->widget_set_enabled(false, false);
387  strict_sync_->widget_set_enabled(false, false);
388  private_replay_->widget_set_enabled(false, false);
389  }
390 
391  //
392  // Set up tab control
393  //
394  listbox& tab_bar = find_widget<listbox>("tab_bar");
395 
397  std::bind(&mp_create_game::on_tab_select, this));
398 
399  // Allow the settings stack to find widgets in all pages, regardless of which is selected.
400  // This ensures settings (especially game settings) widgets are appropriately updated when
401  // a new game is selected, regardless of which settings tab is active at the time.
402  find_widget<stacked_widget>("pager").set_find_in_all_layers(true);
403 
404  // We call on_tab_select farther down.
405 
406  //
407  // Main games list
408  //
409  listbox& list = find_widget<listbox>("games_list");
410 
412  std::bind(&mp_create_game::on_game_select, this));
413 
414  add_to_keyboard_chain(&list);
415 
416  // This handles the initial game selection as well
417  display_games_of_type(level_types_[get_initial_type_index()].first, prefs::get().mp_level());
418 
419  // Initial tab selection must be done after game selection so the field widgets are set to their correct active state.
420  on_tab_select();
421 
422  //
423  // Set up the Lua plugin context
424  //
425  plugins_context_.reset(new plugins_context("Multiplayer Create"));
426 
427  plugins_context_->set_callback("create", [this](const config&) { set_retval(retval::OK); }, false);
428  plugins_context_->set_callback("quit", [this](const config&) { set_retval(retval::CANCEL); }, false);
429  plugins_context_->set_callback("load", [this](const config&) { load_game_callback(); }, false);
430 
431 #define UPDATE_ATTRIBUTE(field, convert) \
432  do { if(cfg.has_attribute(#field)) { field##_->set_widget_value(cfg[#field].convert()); } } while(false) \
433 
434  plugins_context_->set_callback("update_settings", [this](const config& cfg) {
435  UPDATE_ATTRIBUTE(turns, to_int);
436  UPDATE_ATTRIBUTE(gold, to_int);
437  UPDATE_ATTRIBUTE(support, to_int);
438  UPDATE_ATTRIBUTE(experience, to_int);
439  UPDATE_ATTRIBUTE(start_time, to_bool);
440  UPDATE_ATTRIBUTE(fog, to_bool);
441  UPDATE_ATTRIBUTE(shroud, to_bool);
442  UPDATE_ATTRIBUTE(time_limit, to_bool);
443  UPDATE_ATTRIBUTE(init_turn_limit, to_int);
444  UPDATE_ATTRIBUTE(turn_bonus, to_int);
445  UPDATE_ATTRIBUTE(reservoir, to_int);
446  UPDATE_ATTRIBUTE(action_bonus, to_int);
447  UPDATE_ATTRIBUTE(observers, to_bool);
448  UPDATE_ATTRIBUTE(strict_sync, to_bool);
449  UPDATE_ATTRIBUTE(private_replay, to_bool);
450  UPDATE_ATTRIBUTE(shuffle_sides, to_bool);
451  }, true);
452 
453 #undef UPDATE_ATTRIBUTE
454 
455  plugins_context_->set_callback("set_name", [this](const config& cfg) {
456  create_engine_.get_state().mp_settings().name = cfg["name"];
457  }, true);
458 
459  plugins_context_->set_callback("set_password", [this](const config& cfg) {
460  create_engine_.get_state().mp_settings().password = cfg["password"];
461  }, true);
462 
463  plugins_context_->set_callback("select_level", [this](const config& cfg) {
464  selected_game_index_ = convert_to_game_filtered_index(cfg["index"].to_int());
466  }, true);
467 
468  plugins_context_->set_callback("select_type", [this](const config& cfg) {
469  create_engine_.set_current_level_type(level_type::get_enum(cfg["type"].str()).value_or(level_type::type::scenario)); }, true);
470 
471  plugins_context_->set_callback("select_era", [this](const config& cfg) {
472  create_engine_.set_current_era_index(cfg["index"].to_int()); }, true);
473 
474  plugins_context_->set_callback("select_mod", [this](const config& cfg) {
475  on_mod_toggle(cfg["id"].str(), nullptr);
476  }, true);
477 
478  plugins_context_->set_accessor("get_selected", [this](const config&) {
479  const ng::level& current_level = create_engine_.current_level();
480  return config {
481  "id", current_level.id(),
482  "name", current_level.name(),
483  "icon", current_level.icon(),
484  "description", current_level.description(),
485  "allow_era_choice", current_level.allow_era_choice(),
487  };
488  });
489 
490  plugins_context_->set_accessor("find_level", [this](const config& cfg) {
491  const std::string id = cfg["id"].str();
492  auto result = create_engine_.find_level_by_id(id);
493  return config {
494  "index", result.second,
495  "type", level_type::get_string(result.first),
496  };
497  });
498 
499  plugins_context_->set_accessor_int("find_era", [this](const config& cfg) {
501  });
502 
503  plugins_context_->set_accessor_int("find_mod", [this](const config& cfg) {
505  });
506 }
507 
509 {
510  DBG_MP << "sync_with_depcheck: start";
511 
513  DBG_MP << "sync_with_depcheck: correcting era";
514  const int new_era_index = create_engine_.dependency_manager().get_era_index();
515 
516  create_engine_.set_current_era_index(new_era_index, true);
517  eras_menu_button_->set_value(new_era_index);
518  }
519 
521  DBG_MP << "sync_with_depcheck: correcting scenario";
522 
523  // Match scenario and scenario type
525  const bool different_type = new_level_index.first != create_engine_.current_level_type();
526 
527  if(new_level_index.second != -1) {
528  create_engine_.set_current_level_type(new_level_index.first);
529  create_engine_.set_current_level(new_level_index.second);
530  selected_game_index_ = new_level_index.second;
531 
532  auto& game_types_list = find_widget<menu_button>("game_types");
533  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; })));
534 
535  if(different_type) {
536  display_games_of_type(new_level_index.first, create_engine_.current_level().id());
537  } else {
538  // This function (or rather on_game_select) might be triggered by a listbox callback, in
539  // which case we cannot use display_games_of_type since it destroys the list (and its
540  // elements) which might result in segfaults. Instead, we assume that a listbox-triggered
541  // sync_with_depcheck call never changes the game type and goes to this branch instead.
542  find_widget<listbox>("games_list").select_row(new_level_index.second);
543 
544  // Override the last selection so on_game_select selects the new level
546 
547  on_game_select();
548  }
549  }
550  }
551 
553  DBG_MP << "sync_with_depcheck: correcting modifications";
555  }
556 
558  DBG_MP << "sync_with_depcheck: end";
559 }
560 
561 template<typename T>
562 void mp_create_game::on_filter_change(const std::string& id, bool do_select)
563 {
564  create_engine_.apply_level_filter(find_widget<T>(id).get_value());
565 
566  listbox& game_list = find_widget<listbox>("games_list");
567 
568  boost::dynamic_bitset<> filtered(game_list.get_item_count());
570  filtered[i] = true;
571  }
572 
573  game_list.set_row_shown(filtered);
574 
575  if(do_select) {
576  on_game_select();
577  }
578 }
579 
581 {
582  const int selected_game = find_widget<listbox>("games_list").get_selected_row();
583 
584  if(selected_game == selected_game_index_) {
585  return;
586  }
587 
588  // Convert the absolute-index get_selected_row to a relative index for the create_engine to handle
590 
592 
594 
595  update_details();
596 
597  // General settings
598  const bool can_select_era = create_engine_.current_level().allow_era_choice();
599 
600  if(!can_select_era) {
601  eras_menu_button_->set_label(_("No eras available for this game."));
602  } else {
604  }
605 
606  eras_menu_button_->set_active(can_select_era);
607 
608  // Custom options
609  options_manager_->update_game_options();
610 
611  // Game settings
613 }
614 
616 {
617  const int i = find_widget<listbox>("tab_bar").get_selected_row();
618  find_widget<stacked_widget>("pager").select_layer(i);
619 }
620 
621 void mp_create_game::on_mod_toggle(const std::string& id, toggle_button* sender)
622 {
623  if(sender && (sender->get_value_bool() == create_engine_.dependency_manager().is_modification_active(id))) {
624  ERR_MP << "ignoring on_mod_toggle that is already set";
625  return;
626  }
627 
629 
631 
632  options_manager_->update_mod_options();
633 }
634 
636 {
638 
640 
642 
643  options_manager_->update_era_options();
644 }
645 
647 {
648  selected_rfm_index_ = find_widget<menu_button>("random_faction_mode").get_value();
649 }
650 
651 void mp_create_game::show_description(const std::string& new_description)
652 {
653  styled_widget& description = find_widget<styled_widget>("description");
654 
655  description.set_label(!new_description.empty() ? new_description : _("No description available."));
656  description.set_use_markup(true);
657 }
658 
660 {
661  const int index = find_widget<menu_button>("game_types").get_value();
662 
664 }
665 
667 {
669 
670  listbox& list = find_widget<listbox>("games_list");
671 
672  list.clear();
673 
676  widget_item item;
677 
678  if(type == level_type::type::campaign || type == level_type::type::sp_campaign) {
679  item["label"] = game->icon();
680  data.emplace("game_icon", item);
681  }
682 
683  item["label"] = game->name();
684  data.emplace("game_name", item);
685 
686  grid& rg = list.add_row(data);
687 
688  auto& icon = rg.find_widget<image>("game_icon");
689  if(icon.get_label().empty()) {
690  icon.set_visible(gui2::widget::visibility::invisible);
691  }
692  }
693 
694  if(!level.empty() && !list.get_rows_shown().empty()) {
695  // Recalculate which rows should be visible
696  on_filter_change<slider>("num_players", false);
697  on_filter_change<text_box>("game_filter", false);
698 
699  int level_index = create_engine_.find_level_by_id(level).second;
700  if(level_index >= 0 && std::size_t(level_index) < list.get_item_count()) {
701  list.select_row(level_index);
702  }
703  }
704 
705  const bool is_random_map = type == level_type::type::random_map;
706 
707  find_widget<button>("random_map_regenerate").set_active(is_random_map);
708  find_widget<button>("random_map_settings").set_active(is_random_map);
709 
710  // Override the last selection so on_game_select selects the new level
712 
713  on_game_select();
714 }
715 
717 {
719 
721 }
722 
724 {
726 
727  update_details();
728 }
729 
730 int mp_create_game::convert_to_game_filtered_index(const unsigned int initial_index)
731 {
732  const std::vector<std::size_t>& filtered_indices = create_engine_.get_filtered_level_indices(create_engine_.current_level_type());
733  return std::distance(filtered_indices.begin(), std::find(filtered_indices.begin(), filtered_indices.end(), initial_index));
734 }
735 
737 {
738  styled_widget& players = find_widget<styled_widget>("map_num_players");
739  styled_widget& map_size = find_widget<styled_widget>("map_size");
740 
741  if(create_engine_.current_level_type() == level_type::type::random_map) {
742  // If the current random map doesn't have data, generate it
744  create_engine_.current_level().data()["map_data"].empty() &&
745  create_engine_.current_level().data()["map_file"].empty()) {
747  }
748 
749  find_widget<button>("random_map_settings").set_active(create_engine_.generator_has_settings());
750  }
751 
753 
754  // Reset the mp_parameters with the defaults
756 
757  // Set the title, with newlines replaced. Newlines are sometimes found in SP Campaign names
758  std::string title = create_engine_.current_level().name();
759  boost::replace_all(title, "\n", " " + font::unicode_em_dash + " ");
760  find_widget<styled_widget>("game_title").set_label(title);
761 
762 
764  case level_type::type::scenario:
765  case level_type::type::user_map:
766  case level_type::type::user_scenario:
767  case level_type::type::random_map: {
768  ng::scenario* current_scenario = dynamic_cast<ng::scenario*>(&create_engine_.current_level());
769 
770  assert(current_scenario);
771 
773 
774  find_widget<stacked_widget>("minimap_stack").select_layer(0);
775 
776  if(current_scenario->data()["map_data"].empty()) {
777  saved_game::expand_map_file(current_scenario->data());
778  current_scenario->set_metadata();
779  }
780 
781  find_widget<minimap>("minimap").set_map_data(current_scenario->data()["map_data"]);
782 
783  players.set_label(std::to_string(current_scenario->num_players()));
784  map_size.set_label(current_scenario->map_size());
785 
786  break;
787  }
788  case level_type::type::campaign:
789  case level_type::type::sp_campaign: {
790  ng::campaign* current_campaign = dynamic_cast<ng::campaign*>(&create_engine_.current_level());
791 
792  assert(current_campaign);
793 
794  create_engine_.get_state().classification().campaign = current_campaign->data()["id"].str();
795 
796  const std::string img = formatter() << current_campaign->data()["image"] << "~SCALE_INTO(265,265)";
797 
798  find_widget<stacked_widget>("minimap_stack").select_layer(1);
799  find_widget<image>("campaign_image").set_image(img);
800 
801  const int p_min = current_campaign->min_players();
802  const int p_max = current_campaign->max_players();
803 
804  if(p_max > p_min) {
805  players.set_label(VGETTEXT("number of players^$min to $max", {{"min", std::to_string(p_min)}, {"max", std::to_string(p_max)}}));
806  } else {
807  players.set_label(std::to_string(p_min));
808  }
809 
811 
812  break;
813  }
814  }
815 
816  // This needs to be at the end, since errors found expanding the map data are put into the description
818 }
819 
821 {
823  if(level["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
824  use_map_settings_->widget_set_enabled(false, false);
826  } else {
828  }
829 
831 
832  const bool use_map_settings = create_engine_.get_state().mp_settings().use_map_settings;
833 
834  fog_ ->widget_set_enabled(!use_map_settings, false);
835  shroud_ ->widget_set_enabled(!use_map_settings, false);
836  start_time_ ->widget_set_enabled(!use_map_settings, false);
837 
838  turns_ ->widget_set_enabled(!use_map_settings, false);
839  gold_ ->widget_set_enabled(!use_map_settings, false);
840  support_ ->widget_set_enabled(!use_map_settings, false);
841  experience_ ->widget_set_enabled(!use_map_settings, false);
842 
843  const bool time_limit = time_limit_->get_widget_value();
844 
845  init_turn_limit_->widget_set_enabled(time_limit, false);
846  turn_bonus_ ->widget_set_enabled(time_limit, false);
847  reservoir_ ->widget_set_enabled(time_limit, false);
848  action_bonus_ ->widget_set_enabled(time_limit, false);
849 
850  find_widget<button>("reset_timer_defaults").set_active(time_limit);
851 
855 
860 }
861 
863 {
865 
866  if(!load.load_multiplayer_game()) {
867  return;
868  }
869 
870  if(load.data().cancel_orders) {
872  }
873 
875 }
876 
877 std::vector<std::string> mp_create_game::get_active_mods()
878 {
879  int i = 0;
880  std::set<std::string> res;
882  if(mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").get_value_bool()) {
883  res.insert(mod->id);
884  }
885  ++i;
886  }
887  return std::vector<std::string>(res.begin(), res.end());
888 }
889 
890 void mp_create_game::set_active_mods(const std::vector<std::string>& val)
891 {
892  std::set<std::string> val2(val.begin(), val.end());
893  int i = 0;
894  std::set<std::string> res;
896  mod_list_->get_row_grid(i)->find_widget<toggle_button>("mod_active_state").set_value_bool(val2.find(mod->id) != val2.end());
897  ++i;
898  }
899 }
900 
902 {
903  // This allows the defaults to be returned by the pref getters below
908 
909  init_turn_limit_->set_widget_value(prefs::get().countdown_init_time().count());
910  turn_bonus_->set_widget_value(prefs::get().countdown_turn_bonus().count());
911  reservoir_->set_widget_value(prefs::get().countdown_reservoir_time().count());
912  action_bonus_->set_widget_value(prefs::get().countdown_action_bonus().count());
913 }
914 
916 {
918  gui2::show_transient_error_message(_("The selected game has no sides!"));
919  return false;
920  }
921 
923  std::stringstream msg;
924  // 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
925  msg << _("The selected game cannot be created.");
926  msg << "\n\n";
929  return false;
930  }
931 
932  if(!create_engine_.is_campaign()) {
933  return true;
934  }
935 
936  return create_engine_.select_campaign_difficulty() != "CANCEL";
937 }
938 
940 {
941  plugins_context_.reset();
942 
943  // Show all tabs so that find_widget works correctly
944  find_widget<stacked_widget>("pager").select_layer(-1);
945 
946  if(get_retval() == LOAD_GAME) {
948 
949  // 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.
951  return;
952  }
953 
954  if(get_retval() == retval::OK) {
956  prefs::get().set_mp_level_type(static_cast<int>(create_engine_.current_level_type()));
957  prefs::get().set_mp_level(create_engine_.current_level().id());
958  prefs::get().set_mp_era(create_engine_.current_era().id);
959 
961 
962  if(create_engine_.current_level_type() == level_type::type::campaign ||
963  create_engine_.current_level_type() == level_type::type::sp_campaign) {
965  } else if(create_engine_.current_level_type() == level_type::type::scenario) {
967  } else {
968  // This means define= doesn't work for randomly generated scenarios
970  }
971 
973 
975 
976  std::vector<const config*> entry_points;
977  std::vector<std::string> entry_point_titles;
978 
979  const auto& tagname = create_engine_.get_state().classification().get_tagname();
980 
981  if(tagname == "scenario") {
982  const std::string first_scenario = create_engine_.current_level().data()["first_scenario"];
983  for(const config& scenario : game_config_manager::get()->game_config().child_range(tagname)) {
984  const bool is_first = scenario["id"] == first_scenario;
985  if(scenario["allow_new_game"].to_bool(false) || is_first || game_config::debug ) {
986  const std::string& title = !scenario["new_game_title"].empty()
987  ? scenario["new_game_title"]
988  : scenario["name"];
989 
990  entry_points.insert(is_first ? entry_points.begin() : entry_points.end(), &scenario);
991  entry_point_titles.insert(is_first ? entry_point_titles.begin() : entry_point_titles.end(), title);
992  }
993  }
994  }
995 
997  if(entry_points.size() > 1) {
998  gui2::dialogs::simple_item_selector dlg(_("Choose Starting Scenario"), _("Select at which point to begin this campaign."), entry_point_titles);
999 
1000  dlg.set_single_button(true);
1001  dlg.show();
1002 
1003  const config& scenario = *entry_points[dlg.selected_index()];
1004 
1005  params.hash = scenario.hash();
1007  }
1008 
1010 
1011  if(!create_engine_.current_level().data()["force_lock_settings"].to_bool(!create_engine_.get_state().classification().is_normal_mp_game())) {
1012  // Max slider value (in this case, 100) means 'unlimited turns', so pass the value -1
1013  const int num_turns = turns_->get_widget_value();
1014  params.num_turns = num_turns < ::settings::turns_max ? num_turns : - 1;
1015  params.village_gold = gold_->get_widget_value();
1019  params.fog_game = fog_->get_widget_value();
1020  params.shroud_game = shroud_->get_widget_value();
1021 
1022  // write to scenario
1024 
1025  if(params.random_start_time) {
1026  if(!tod_manager::is_start_ToD(scenario["random_start_time"])) {
1027  scenario["random_start_time"] = true;
1028  }
1029  } else {
1030  scenario["random_start_time"] = false;
1031  }
1032 
1033  scenario["experience_modifier"] = params.xp_modifier;
1034  scenario["turns"] = params.num_turns;
1035 
1036  for(config& side : scenario.child_range("side")) {
1037  if(!params.use_map_settings) {
1038  side["fog"] = params.fog_game;
1039  side["shroud"] = params.shroud_game;
1040  side["village_gold"] = params.village_gold;
1041  side["village_support"] = params.village_support;
1042  } else {
1043  if(side["fog"].empty()) {
1044  side["fog"] = params.fog_game;
1045  }
1046 
1047  if(side["shroud"].empty()) {
1048  side["shroud"] = params.shroud_game;
1049  }
1050 
1051  if(side["village_gold"].empty()) {
1052  side["village_gold"] = params.village_gold;
1053  }
1054 
1055  if(side["village_support"].empty()) {
1056  side["village_support"] = params.village_support;
1057  }
1058  }
1059  }
1060  }
1061 
1063  params.mp_countdown_init_time = std::chrono::seconds{init_turn_limit_->get_widget_value()};
1064  params.mp_countdown_turn_bonus = std::chrono::seconds{turn_bonus_->get_widget_value()};
1065  params.mp_countdown_reservoir_time = std::chrono::seconds{reservoir_->get_widget_value()};
1066  params.mp_countdown_action_bonus = std::chrono::seconds{action_bonus_->get_widget_value()};
1067 
1072 
1073  random_faction_mode::type type = random_faction_mode::get_enum(selected_rfm_index_).value_or(random_faction_mode::type::independent);
1074  params.mode = type;
1075 
1076  // Since we don't have a field handling this option, we need to save the value manually
1077  prefs::get().set_random_faction_mode(random_faction_mode::get_string(type));
1078 
1079  // Save custom option settings
1080  params.options = options_manager_->get_options_config();
1081  prefs::get().set_options(options_manager_->get_options_config());
1082 
1083  // Set game name
1084  const std::string name = find_widget<text_box>("game_name").get_value();
1085  if(!name.empty() && (name != settings::game_name_default())) {
1086  params.name = name;
1087  }
1088 
1089  // Set game password
1090  const std::string password = find_widget<text_box>("game_password").get_value();
1091  if(!password.empty()) {
1092  params.password = password;
1093  }
1094  }
1095 }
1096 
1097 } // 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: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