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