1 /*
2    Copyright (C) 2013 - 2018 by Andrius Silinskas <silinskas.andrius@gmail.com>
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 #include "game_initialization/create_engine.hpp"
16 
17 #include "filesystem.hpp"
18 #include "game_config_manager.hpp"
19 #include "preferences/credentials.hpp"
20 #include "preferences/game.hpp"
21 #include "generators/map_create.hpp"
22 #include "gui/dialogs/campaign_difficulty.hpp"
23 #include "log.hpp"
24 #include "map/exception.hpp"
25 #include "map/map.hpp"
26 #include "minimap.hpp"
27 #include "saved_game.hpp"
28 #include "wml_exception.hpp"
29 
30 #include "serialization/preprocessor.hpp"
31 #include "serialization/parser.hpp"
32 #include "serialization/string_utils.hpp"
33 
34 #include <sstream>
35 #include <cctype>
36 
37 static lg::log_domain log_config("config");
38 #define ERR_CF LOG_STREAM(err, log_config)
39 
40 static lg::log_domain log_mp_create_engine("mp/create/engine");
41 #define WRN_MP LOG_STREAM(warn, log_mp_create_engine)
42 #define DBG_MP LOG_STREAM(debug, log_mp_create_engine)
43 
44 namespace ng {
45 
level(const config & data)46 level::level(const config& data)
47 	: data_(data)
48 {
49 }
50 
scenario(const config & data)51 scenario::scenario(const config& data)
52 	: level(data)
53 	, map_()
54 	, map_hash_()
55 	, num_players_(0)
56 {
57 	set_metadata();
58 }
59 
can_launch_game() const60 bool scenario::can_launch_game() const
61 {
62 	return map_.get() != nullptr;
63 }
64 
set_metadata()65 void scenario::set_metadata()
66 {
67 	const std::string& map_data = data_["map_data"];
68 
69 	try {
70 		map_.reset(new gamemap(game_config_manager::get()->terrain_types(),
71 			map_data));
72 	} catch(const incorrect_map_format_error& e) {
73 		data_["description"] = _("Map could not be loaded: ") + e.message;
74 
75 		ERR_CF << "map could not be loaded: " << e.message << '\n';
76 	} catch(const wml_exception& e) {
77 		data_["description"] = _("Map could not be loaded.");
78 
79 		ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
80 	}
81 
82 	set_sides();
83 }
84 
map_size() const85 std::string scenario::map_size() const
86 {
87 	std::stringstream map_size;
88 
89 	if(map_.get() != nullptr) {
90 		map_size << map_->w();
91 		map_size << font::unicode_multiplication_sign;
92 		map_size << map_->h();
93 	} else {
94 		map_size << _("not available.");
95 	}
96 
97 	return map_size.str();
98 }
99 
set_sides()100 void scenario::set_sides()
101 {
102 	if(map_.get() != nullptr) {
103 		// If there are fewer sides in the configuration than there are
104 		// starting positions, then generate the additional sides
105 		const int map_positions = map_->num_valid_starting_positions();
106 
107 		if(!data_.has_child("side")) {
108 			for(int pos = 0; pos < map_positions; ++pos) {
109 				config& side = data_.add_child("side");
110 				side["side"] = pos + 1;
111 				side["team_name"] = "Team " + std::to_string(pos + 1);
112 				side["canrecruit"] = true;
113 				side["controller"] = "human";
114 			}
115 		}
116 
117 		num_players_ = 0;
118 		for(const config& scenario : data_.child_range("side")) {
119 			if(scenario["allow_player"].to_bool(true)) {
120 				++num_players_;
121 			}
122 		}
123 	}
124 }
125 
user_map(const config & data,const std::string & name,gamemap * map)126 user_map::user_map(const config& data, const std::string& name, gamemap* map)
127 	: scenario(data)
128 	, name_(name)
129 {
130 	if(map != nullptr) {
131 		map_.reset(new gamemap(*map));
132 	}
133 
134 	set_metadata();
135 }
136 
set_metadata()137 void user_map::set_metadata()
138 {
139 	set_sides();
140 }
141 
description() const142 std::string user_map::description() const
143 {
144 	if(!data_["description"].empty()) {
145 		return data_["description"];
146 	}
147 
148 	// map error message
149 	return _("Custom map.");
150 }
151 
random_map(const config & data)152 random_map::random_map(const config& data)
153 	: scenario(data)
154 	, generator_data_()
155 	, generate_whole_scenario_(data_.has_attribute("scenario_generation"))
156 	, generator_name_(generate_whole_scenario_ ? data_["scenario_generation"] : data_["map_generation"])
157 {
158 	if(!data.has_child("generator")) {
159 		data_.clear();
160 		generator_data_.clear();
161 		data_["description"] = "Error: Random map found with missing generator information. Scenario should have a [generator] child.";
162 		data_["error_message"] = "missing [generator] tag";
163 	} else {
164 		generator_data_ = data.child("generator");
165 	}
166 
167 	if(!data.has_attribute("scenario_generation") && !data.has_attribute("map_generation")) {
168 		data_.clear();
169 		generator_data_.clear();
170 		data_["description"] = "Error: Random map found with missing generator information. Scenario should have a [generator] child.";
171 		data_["error_message"] = "couldn't find 'scenario_generation' or 'map_generation' attribute";
172 	}
173 }
174 
create_map_generator() const175 map_generator* random_map::create_map_generator() const
176 {
177 	return ::create_map_generator(generator_name(), generator_data());
178 }
179 
campaign(const config & data)180 campaign::campaign(const config& data)
181 	: level(data)
182 	, id_(data["id"])
183 	, allow_era_choice_(level::allow_era_choice())
184 	, image_label_()
185 	, min_players_(2)
186 	, max_players_(2)
187 {
188 	if(data.has_attribute("start_year")) {
189 		dates_.first = irdya_date::read_date(data["start_year"]);
190 		if(data.has_attribute("end_year")) {
191 			dates_.second = irdya_date::read_date(data["end_year"]);
192 		} else {
193 			dates_.second = dates_.first;
194 		}
195 	} else if(data.has_attribute("year")) {
196 		dates_.first = dates_.second = irdya_date::read_date(data["year"]);
197 	}
198 	set_metadata();
199 }
200 
can_launch_game() const201 bool campaign::can_launch_game() const
202 {
203 	return !data_.empty();
204 }
205 
set_metadata()206 void campaign::set_metadata()
207 {
208 	image_label_ = data_["image"].str();
209 
210 	int min = data_["min_players"].to_int(2);
211 	int max = data_["max_players"].to_int(2);
212 
213 	min_players_ = max_players_ =  min;
214 
215 	if(max > min) {
216 		max_players_ = max;
217 	}
218 }
219 
mark_if_completed()220 void campaign::mark_if_completed()
221 {
222 	data_["completed"] = preferences::is_campaign_completed(data_["id"]);
223 
224 	for(auto& cfg : data_.child_range("difficulty")) {
225 		cfg["completed_at"] = preferences::is_campaign_completed(data_["id"], cfg["define"]);
226 	}
227 }
228 
create_engine(saved_game & state)229 create_engine::create_engine(saved_game& state)
230 	: current_level_type_()
231 	, current_level_index_(0)
232 	, current_era_index_(0)
233 	, level_name_filter_()
234 	, player_count_filter_(1)
235 	, type_map_()
236 	, user_map_names_()
237 	, user_scenario_names_()
238 	, eras_()
239 	, mods_()
240 	, state_(state)
241 	, dependency_manager_(nullptr)
242 	, generator_(nullptr)
243 	, selected_campaign_difficulty_()
244 	, game_config_(game_config_manager::get()->game_config())
245 {
246 	// Set up the type map. Do this first!
247 	type_map_.emplace(level::TYPE::SCENARIO, type_list());
248 	type_map_.emplace(level::TYPE::USER_MAP, type_list());
249 	type_map_.emplace(level::TYPE::USER_SCENARIO, type_list());
250 	type_map_.emplace(level::TYPE::CAMPAIGN, type_list());
251 	type_map_.emplace(level::TYPE::SP_CAMPAIGN, type_list());
252 	type_map_.emplace(level::TYPE::RANDOM_MAP, type_list());
253 
254 	DBG_MP << "restoring game config\n";
255 
256 	// Restore game config for multiplayer.
257 	game_classification::CAMPAIGN_TYPE type = state_.classification().campaign_type;
258 
259 	state_.clear();
260 	state_.classification().campaign_type = type;
261 
262 	game_config_manager::get()->load_game_config_for_create(type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER);
263 
264 	// Initialize dependency_manager_ after refreshing game config.
265 	dependency_manager_.reset(new depcheck::manager(
266 		game_config_, type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER));
267 
268 	// TODO: the editor dir is already configurable, is the preferences value
269 	filesystem::get_files_in_dir(filesystem::get_user_data_dir() + "/editor/maps", &user_map_names_,
270 		nullptr, filesystem::FILE_NAME_ONLY);
271 
272 	filesystem::get_files_in_dir(filesystem::get_user_data_dir() + "/editor/scenarios", &user_scenario_names_,
273 		nullptr, filesystem::FILE_NAME_ONLY);
274 
275 	DBG_MP << "initializing all levels, eras and mods\n";
276 
277 	init_all_levels();
278 	init_extras(ERA);
279 	init_extras(MOD);
280 
281 	state_.mp_settings().saved_game = false;
282 
283 	for(const std::string& str : preferences::modifications(state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER)) {
284 		if(game_config_.find_child("modification", "id", str)) {
285 			state_.mp_settings().active_mods.push_back(str);
286 		}
287 	}
288 
289 	dependency_manager_->try_modifications(state_.mp_settings().active_mods, true);
290 
291 	reset_level_filters();
292 }
293 
init_generated_level_data()294 void create_engine::init_generated_level_data()
295 {
296 	DBG_MP << "initializing generated level data\n";
297 
298 	//DBG_MP << "current data:\n";
299 	//DBG_MP << current_level().data().debug();
300 
301 	random_map * cur_lev = dynamic_cast<random_map *> (&current_level());
302 
303 	if(!cur_lev) {
304 		WRN_MP << "Tried to initialized generated level data on a level that wasn't a random map\n";
305 		return;
306 	}
307 
308 	try {
309 		if(!cur_lev->generate_whole_scenario())
310 		{
311 			DBG_MP << "** replacing map ** \n";
312 
313 			config data = cur_lev->data();
314 
315 			data["map_data"] = generator_->create_map();
316 
317 			cur_lev->set_data(data);
318 
319 		} else { //scenario generation
320 
321 			DBG_MP << "** replacing scenario ** \n";
322 
323 			config data = generator_->create_scenario();
324 
325 			// Set the scenario to have placing of sides
326 			// based on the terrain they prefer
327 			if(!data.has_attribute("modify_placing")) {
328 				data["modify_placing"] = true;
329 			}
330 
331 			const std::string& description = cur_lev->data()["description"];
332 			data["description"] = description;
333 			saved_game::post_scenario_generation(cur_lev->data(), data);
334 
335 			cur_lev->set_data(data);
336 		}
337 	} catch (const mapgen_exception & e) {
338 		config data = cur_lev->data();
339 
340 		data["error_message"] = e.what();
341 
342 		cur_lev->set_data(data);
343 	}
344 
345 	//DBG_MP << "final data:\n";
346 	//DBG_MP << current_level().data().debug();
347 }
348 
current_level_has_side_data()349 bool create_engine::current_level_has_side_data()
350 {
351 	//
352 	// We exclude campaigns from this check since they require preprocessing in order to check
353 	// their side data. Since this function is used by the MP Create screen to verify side data
354 	// before proceeding to Staging, this should cover most cases of false positives. It does,
355 	// however, leave open the possibility of scenarios that require preprocessing before their
356 	// side data is accessible, but that's an unlikely occurrence.
357 	//
358 	if(is_campaign()) {
359 		return true;
360 	}
361 
362 	return current_level().data().has_child("side");
363 }
364 
prepare_for_new_level()365 void create_engine::prepare_for_new_level()
366 {
367 	DBG_MP << "preparing mp_game_settings for new level\n";
368 	state_.expand_scenario();
369 	state_.expand_random_scenario();
370 }
371 
prepare_for_era_and_mods()372 void create_engine::prepare_for_era_and_mods()
373 {
374 	state_.classification().era_define = game_config_.find_child("era", "id", get_parameters().mp_era)["define"].str();
375 	for(const std::string& mod_id : get_parameters().active_mods) {
376 		state_.classification().mod_defines.push_back(game_config_.find_child("modification", "id", mod_id)["define"].str());
377 	}
378 }
379 
prepare_for_scenario()380 void create_engine::prepare_for_scenario()
381 {
382 	DBG_MP << "preparing data for scenario by reloading game config\n";
383 
384 	state_.classification().scenario_define = current_level().data()["define"].str();
385 
386 	state_.set_carryover_sides_start(
387 		config {"next_scenario", current_level().data()["id"]}
388 	);
389 }
390 
prepare_for_campaign(const std::string & difficulty)391 void create_engine::prepare_for_campaign(const std::string& difficulty)
392 {
393 	DBG_MP << "preparing data for campaign by reloading game config\n";
394 
395 	if(!difficulty.empty()) {
396 		state_.classification().difficulty = difficulty;
397 	} else if(!selected_campaign_difficulty_.empty()) {
398 		state_.classification().difficulty = selected_campaign_difficulty_;
399 	}
400 
401 	config& current_level_data = current_level().data();
402 
403 	state_.classification().campaign = current_level_data["id"].str();
404 	state_.classification().campaign_name = current_level_data["name"].str();
405 	state_.classification().abbrev = current_level_data["abbrev"].str();
406 
407 	state_.classification().end_text = current_level_data["end_text"].str();
408 	state_.classification().end_text_duration = current_level_data["end_text_duration"];
409 
410 	state_.classification().campaign_define = current_level_data["define"].str();
411 	state_.classification().campaign_xtra_defines =
412 		utils::split(current_level_data["extra_defines"]);
413 
414 	state_.set_carryover_sides_start(
415 		config {"next_scenario", current_level_data["first_scenario"]}
416 	);
417 }
418 
select_campaign_difficulty(int set_value)419 std::string create_engine::select_campaign_difficulty(int set_value)
420 {
421 	// Verify the existence of difficulties
422 	std::vector<std::string> difficulties;
423 
424 	for(const config& d : current_level().data().child_range("difficulty")) {
425 		difficulties.push_back(d["define"]);
426 	}
427 
428 	if(difficulties.empty()) {
429 		difficulties = utils::split(current_level().data()["difficulties"]);
430 	}
431 
432 	// No difficulties found. Exit
433 	if(difficulties.empty()) {
434 		return "";
435 	}
436 
437 	// One difficulty found. Use it
438 	if(difficulties.size() == 1) {
439 		return difficulties[0];
440 	}
441 
442 	// A specific difficulty value was passed
443 	// Use a minimalistic interface to get the specified define
444 	if(set_value != -1) {
445 		if(set_value > static_cast<int>(difficulties.size())) {
446 			std::cerr << "incorrect difficulty number: [" <<
447 				set_value << "]. maximum is [" << difficulties.size() << "].\n";
448 			return "FAIL";
449 		} else if(set_value < 1) {
450 			std::cerr << "incorrect difficulty number: [" <<
451 				set_value << "]. minimum is [1].\n";
452 			return "FAIL";
453 		} else {
454 			return difficulties[set_value - 1];
455 		}
456 	}
457 
458 	// If not, let the user pick one from the prompt
459 	// We don't pass the difficulties vector here because additional data is required
460 	// to constrict the dialog
461 	gui2::dialogs::campaign_difficulty dlg(current_level().data());
462 	dlg.show();
463 
464 	selected_campaign_difficulty_ = dlg.selected_difficulty();
465 
466 	return selected_campaign_difficulty_;
467 }
468 
prepare_for_saved_game()469 void create_engine::prepare_for_saved_game()
470 {
471 	DBG_MP << "preparing mp_game_settings for saved game\n";
472 
473 	game_config_manager::get()->load_game_config_for_game(state_.classification());
474 
475 	// The save might be a start-of-scenario save so make sure we have the scenario data loaded.
476 	state_.expand_scenario();
477 	state_.mp_settings().saved_game = true;
478 }
479 
prepare_for_other()480 void create_engine::prepare_for_other()
481 {
482 	DBG_MP << "prepare_for_other\n";
483 	state_.set_scenario(current_level().data());
484 	state_.mp_settings().hash = current_level().data().hash();
485 	state_.check_require_scenario();
486 }
487 
apply_level_filter(const std::string & name)488 void create_engine::apply_level_filter(const std::string& name)
489 {
490 	level_name_filter_ = name;
491 	apply_level_filters();
492 }
493 
apply_level_filter(int players)494 void create_engine::apply_level_filter(int players)
495 {
496 	player_count_filter_ = players;
497 	apply_level_filters();
498 }
499 
reset_level_filters()500 void create_engine::reset_level_filters()
501 {
502 	for(auto& type : type_map_) {
503 		type.second.reset_filter();
504 	}
505 
506 	level_name_filter_ = "";
507 }
508 
current_level() const509 level& create_engine::current_level() const
510 {
511 	return *type_map_.at(current_level_type_.v).games[current_level_index_];
512 }
513 
current_era() const514 const create_engine::extras_metadata& create_engine::current_era() const
515 {
516 	return *get_const_extras_by_type(ERA)[current_era_index_];
517 }
518 
set_current_level(const size_t index)519 void create_engine::set_current_level(const size_t index)
520 {
521 	try {
522 		current_level_index_ = type_map_.at(current_level_type_.v).games_filtered.at(index);
523 	} catch (const std::out_of_range&) {
524 		current_level_index_ = 0u;
525 	}
526 
527 	if(current_level_type_ == level::TYPE::RANDOM_MAP) {
528 		random_map* current_random_map = dynamic_cast<random_map*>(&current_level());
529 
530 		// If dynamic cast has failed then we somehow have gotten all the pointers mixed together.
531 		assert(current_random_map);
532 
533 		generator_.reset(current_random_map->create_map_generator());
534 	} else {
535 		generator_.reset(nullptr);
536 	}
537 
538 	if(state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER) {
539 		dependency_manager_->try_scenario(current_level().id());
540 	}
541 }
542 
set_current_era_index(const size_t index,bool force)543 void create_engine::set_current_era_index(const size_t index, bool force)
544 {
545 	current_era_index_ = index;
546 
547 	dependency_manager_->try_era_by_index(index, force);
548 }
549 
toggle_mod(int index,bool force)550 bool create_engine::toggle_mod(int index, bool force)
551 {
552 	force |= state_.classification().campaign_type != game_classification::CAMPAIGN_TYPE::MULTIPLAYER;
553 
554 	bool is_active = dependency_manager_->is_modification_active(index);
555 	dependency_manager_->try_modification_by_index(index, !is_active, force);
556 
557 	state_.mp_settings().active_mods = dependency_manager_->get_modifications();
558 
559 	return !is_active;
560 }
561 
generator_assigned() const562 bool create_engine::generator_assigned() const
563 {
564 	return generator_ != nullptr;
565 }
566 
generator_has_settings() const567 bool create_engine::generator_has_settings() const
568 {
569 	return generator_->allow_user_config();
570 }
571 
generator_user_config()572 void create_engine::generator_user_config()
573 {
574 	generator_->user_config();
575 }
576 
find_level_by_id(const std::string & id) const577 std::pair<level::TYPE, int> create_engine::find_level_by_id(const std::string& id) const
578 {
579 	for(const auto& type : type_map_) {
580 		int i = 0;
581 
582 		for(const auto game : type.second.games) {
583 			if(game->id() == id) {
584 				return {type.first, i};
585 			}
586 
587 			i++;
588 		}
589 	}
590 
591 	return {level::TYPE::SP_CAMPAIGN, -1};
592 }
593 
find_extra_by_id(const MP_EXTRA extra_type,const std::string & id) const594 int create_engine::find_extra_by_id(const MP_EXTRA extra_type, const std::string& id) const
595 {
596 	int i = 0;
597 	for(extras_metadata_ptr extra : get_const_extras_by_type(extra_type)) {
598 		if(extra->id == id) {
599 			return i;
600 		}
601 		i++;
602 	}
603 
604 	return -1;
605 }
606 
init_active_mods()607 void create_engine::init_active_mods()
608 {
609 	state_.mp_settings().active_mods = dependency_manager_->get_modifications();
610 }
611 
active_mods()612 std::vector<std::string>& create_engine::active_mods()
613 {
614 	return state_.mp_settings().active_mods;
615 }
616 
active_mods_data()617 std::vector<create_engine::extras_metadata_ptr> create_engine::active_mods_data()
618 {
619 	const std::vector<extras_metadata_ptr>& mods = get_const_extras_by_type(MP_EXTRA::MOD);
620 
621 	std::vector<extras_metadata_ptr> data_vec;
622 	std::copy_if(mods.begin(), mods.end(), std::back_inserter(data_vec), [this](extras_metadata_ptr mod) {
623 		return dependency_manager_->is_modification_active(mod->id);
624 	});
625 
626 	return data_vec;
627 }
628 
curent_era_cfg() const629 const config& create_engine::curent_era_cfg() const
630 {
631 	int era_index = current_level().allow_era_choice() ? current_era_index_ : 0;
632 	return *eras_[era_index]->cfg;
633 }
634 
get_parameters()635 const mp_game_settings& create_engine::get_parameters()
636 {
637 	DBG_MP << "getting parameter values" << std::endl;
638 
639 	int era_index = current_level().allow_era_choice() ? current_era_index_ : 0;
640 	state_.mp_settings().mp_era = eras_[era_index]->id;
641 	state_.mp_settings().mp_era_name = eras_[era_index]->name;
642 
643 	return state_.mp_settings();
644 }
645 
init_all_levels()646 void create_engine::init_all_levels()
647 {
648 	if(const config& generic_multiplayer = game_config_.child("generic_multiplayer")) {
649 		config gen_mp_data = generic_multiplayer;
650 
651 		// User maps.
652 		int dep_index_offset = 0;
653 		for(size_t i = 0; i < user_map_names_.size(); i++)
654 		{
655 			config user_map_data = gen_mp_data;
656 			user_map_data["map_data"] = filesystem::read_map(user_map_names_[i]);
657 
658 			// Check if a file is actually a map.
659 			// Note that invalid maps should be displayed in order to
660 			// show error messages in the GUI.
661 			bool add_map = true;
662 			std::unique_ptr<gamemap> map;
663 			try {
664 				map.reset(new gamemap(game_config_manager::get()->terrain_types(), user_map_data["map_data"]));
665 			} catch (const incorrect_map_format_error& e) {
666 				user_map_data["description"] = _("Map could not be loaded: ") + e.message;
667 
668 				ERR_CF << "map could not be loaded: " << e.message << '\n';
669 			} catch (const wml_exception&) {
670 				add_map = false;
671 				dep_index_offset++;
672 			}
673 
674 			if(add_map) {
675 				type_map_[level::TYPE::USER_MAP].games.emplace_back(new user_map(user_map_data, user_map_names_[i], map.get()));
676 
677 				// Since user maps are treated as scenarios, some dependency info is required
678 				config depinfo;
679 				depinfo["id"] = user_map_names_[i];
680 				depinfo["name"] = user_map_names_[i];
681 				dependency_manager_->insert_element(depcheck::SCENARIO, depinfo, i - dep_index_offset);
682 			}
683 		}
684 
685 		// User made scenarios.
686 		dep_index_offset = 0;
687 		for(size_t i = 0; i < user_scenario_names_.size(); i++)
688 		{
689 			config data;
690 			try {
691 				read(data, *preprocess_file(filesystem::get_user_data_dir() + "/editor/scenarios/" + user_scenario_names_[i]));
692 			} catch(const config::error & e) {
693 				ERR_CF << "Caught a config error while parsing user made (editor) scenarios:\n" << e.message << std::endl;
694 				ERR_CF << "Skipping file: " << (filesystem::get_user_data_dir() + "/editor/scenarios/" + user_scenario_names_[i]) << std::endl;
695 				continue;
696 			}
697 
698 			scenario_ptr new_scenario(new scenario(data));
699 			if(new_scenario->id().empty()) continue;
700 
701 			type_map_[level::TYPE::USER_SCENARIO].games.push_back(std::move(new_scenario));
702 
703 			// Since user scenarios are treated as scenarios, some dependency info is required
704 			config depinfo;
705 			depinfo["id"] = data["id"];
706 			depinfo["name"] = data["name"];
707 			dependency_manager_->insert_element(depcheck::SCENARIO, depinfo, i - dep_index_offset++);
708 		}
709 	}
710 
711 	// Stand-alone scenarios.
712 	for(const config& data : game_config_.child_range("multiplayer"))
713 	{
714 		if(!data["allow_new_game"].to_bool(true))
715 			continue;
716 
717 		if(!data["campaign_id"].empty())
718 			continue;
719 
720 		if(data.has_attribute("map_generation") || data.has_attribute("scenario_generation")) {
721 			type_map_[level::TYPE::RANDOM_MAP].games.emplace_back(new random_map(data));
722 		} else {
723 			type_map_[level::TYPE::SCENARIO].games.emplace_back(new scenario(data));
724 		}
725 	}
726 
727 	// Campaigns.
728 	for(const config& data : game_config_.child_range("campaign"))
729 	{
730 		if(data["id"].empty()) {
731 			if(data["name"].empty()) {
732 				ERR_CF << "Found a [campaign] with neither a name nor an id attribute, ignoring it" << std::endl;
733 			} else {
734 				ERR_CF << "Ignoring a [campaign] with no id attribute, but name '" << data["name"] << "'" << std::endl;
735 			}
736 			continue;
737 		}
738 
739 		const std::string& type = data["type"];
740 		bool mp = state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER;
741 
742 		if(type == "mp" || (type == "hybrid" && mp)) {
743 			type_map_[level::TYPE::CAMPAIGN].games.emplace_back(new campaign(data));
744 		}
745 
746 		if(type == "sp" || type.empty() || (type == "hybrid" && !mp)) {
747 			campaign_ptr new_sp_campaign(new campaign(data));
748 			new_sp_campaign->mark_if_completed();
749 
750 			type_map_[level::TYPE::SP_CAMPAIGN].games.push_back(std::move(new_sp_campaign));
751 		}
752 	}
753 
754 	auto& sp_campaigns = type_map_[level::TYPE::SP_CAMPAIGN].games;
755 
756 	// Sort sp campaigns by rank.
757 	std::stable_sort(sp_campaigns.begin(), sp_campaigns.end(),
758         [](const create_engine::level_ptr& a, const create_engine::level_ptr& b) {
759 			return a->data()["rank"].to_int(1000) < b->data()["rank"].to_int(1000);
760 		}
761     );
762 }
763 
init_extras(const MP_EXTRA extra_type)764 void create_engine::init_extras(const MP_EXTRA extra_type)
765 {
766 	std::vector<extras_metadata_ptr>& extras = get_extras_by_type(extra_type);
767 	const std::string extra_name = (extra_type == ERA) ? "era" : "modification";
768 
769 	ng::depcheck::component_availability default_availabilty = (extra_type == ERA)
770 		? ng::depcheck::component_availability::MP
771 		: ng::depcheck::component_availability::HYBRID;
772 
773 	std::set<std::string> found_ids;
774 	for(const config& extra : game_config_.child_range(extra_name))
775 	{
776 		ng::depcheck::component_availability type = extra["type"].to_enum(default_availabilty);
777 		bool mp = state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER;
778 
779 		if((type != ng::depcheck::component_availability::MP || mp) && (type != ng::depcheck::component_availability::SP || !mp) )
780 		{
781 			if(found_ids.insert(extra["id"]).second) {
782 				extras_metadata_ptr new_extras_metadata(new extras_metadata());
783 				new_extras_metadata->id = extra["id"].str();
784 				new_extras_metadata->name = extra["name"].str();
785 				new_extras_metadata->description = extra["description"].str();
786 				new_extras_metadata->cfg = &extra;
787 
788 				extras.push_back(std::move(new_extras_metadata));
789 			}
790 			else {
791 				//TODO: use a more visible error message.
792 				ERR_CF << "found " << extra_name << " with id=" << extra["id"] << " twice\n";
793 			}
794 		}
795 	}
796 }
797 
apply_level_filters()798 void create_engine::apply_level_filters()
799 {
800 	for(auto& type : type_map_) {
801 		type.second.apply_filter(player_count_filter_, level_name_filter_);
802 	}
803 }
804 
get_levels_by_type_unfiltered(level::TYPE type) const805 std::vector<create_engine::level_ptr> create_engine::get_levels_by_type_unfiltered(level::TYPE type) const
806 {
807 	std::vector<level_ptr> levels;
808 	for(const level_ptr lvl : type_map_.at(type.v).games) {
809 		levels.push_back(lvl);
810 	}
811 
812 	return levels;
813 }
814 
get_levels_by_type(level::TYPE type) const815 std::vector<create_engine::level_ptr> create_engine::get_levels_by_type(level::TYPE type) const
816 {
817     auto& g_list = type_map_.at(type.v);
818 
819 	std::vector<level_ptr> levels;
820 	for(size_t level : g_list.games_filtered) {
821 		levels.push_back(g_list.games[level]);
822 	}
823 
824 	return levels;
825 }
826 
get_filtered_level_indices(level::TYPE type) const827 std::vector<size_t> create_engine::get_filtered_level_indices(level::TYPE type) const
828 {
829 	return type_map_.at(type.v).games_filtered;
830 }
831 
832 const std::vector<create_engine::extras_metadata_ptr>&
get_const_extras_by_type(const MP_EXTRA extra_type) const833 	create_engine::get_const_extras_by_type(const MP_EXTRA extra_type) const
834 {
835 	return (extra_type == ERA) ? eras_ : mods_;
836 }
837 
838 std::vector<create_engine::extras_metadata_ptr>&
get_extras_by_type(const MP_EXTRA extra_type)839 	create_engine::get_extras_by_type(const MP_EXTRA extra_type)
840 {
841 	return (extra_type == ERA) ? eras_ : mods_;
842 }
843 
get_state()844 saved_game& create_engine::get_state()
845 {
846 	return state_;
847 }
848 
849 } // end namespace ng
850