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