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 #include "game_initialization/mp_game_utils.hpp"
15 
16 #include "config.hpp"
17 #include "formula/string_utils.hpp"
18 #include "game_config.hpp"
19 #include "game_config_manager.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 #include "saved_game.hpp"
23 #include "wesnothd_connection_error.hpp"
24 
25 static lg::log_domain log_engine("engine");
26 #define LOG_NG LOG_STREAM(info, log_engine)
27 #define ERR_NG LOG_STREAM(err, log_engine)
28 
29 static lg::log_domain log_config("config");
30 #define LOG_CF LOG_STREAM(info, log_config)
31 #define WRN_CF LOG_STREAM(warn, log_config)
32 #define ERR_CF LOG_STREAM(err, log_config)
33 
34 static lg::log_domain log_network("network");
35 #define LOG_NW LOG_STREAM(info, log_network)
36 #define ERR_NW LOG_STREAM(err, log_network)
37 
38 namespace mp
39 {
40 // This is for the wesnothd server, it expects a more detailed summary in [multiplayer]
add_multiplayer_classification(config & multiplayer,saved_game & state)41 static void add_multiplayer_classification(config& multiplayer, saved_game& state)
42 {
43 	multiplayer["mp_scenario"] = state.get_scenario_id();
44 	multiplayer["mp_scenario_name"] = state.get_starting_point()["name"];
45 	multiplayer["difficulty_define"] = state.classification().difficulty;
46 	multiplayer["mp_campaign"] = state.classification().campaign;
47 	multiplayer["mp_campaign_name"] = state.classification().campaign_name;
48 }
49 
initial_level_config(saved_game & state)50 config initial_level_config(saved_game& state)
51 {
52 	const mp_game_settings& params = state.mp_settings();
53 	state.set_defaults();
54 
55 	// Also impliers state.expand_scenario()
56 	// We need to call this before expand_mp_events/options otherwise they might be overwritten.
57 	state.expand_random_scenario();
58 	state.expand_mp_events();
59 	state.expand_mp_options();
60 
61 	if(!state.valid()) {
62 		throw config::error("Failed to load the scenario");
63 	}
64 
65 	config& scenario = state.get_starting_point();
66 	if(!state.mp_settings().saved_game) {
67 		state.set_random_seed();
68 	}
69 
70 	if(scenario["objectives"].empty()) {
71 		// Generic victory objectives.
72 		std::ostringstream ss;
73 		ss << "<big>";
74 		ss << t_string(N_("Victory:"), "wesnoth") << "</big>\n";
75 		ss << "<span color='#00ff00'>" << font::unicode_bullet << " ";
76 		ss << t_string(N_("Defeat enemy leader(s)"), "wesnoth") << "</span>";
77 
78 		scenario["objectives"] = ss.str();
79 	}
80 
81 	config level = state.to_config();
82 	add_multiplayer_classification(level.child_or_add("multiplayer"), state);
83 
84 	// [multiplayer] mp_era= should be persistent over saves.
85 	std::string era = params.mp_era;
86 
87 	/**
88 	 * [era] and [modification]s are toplevel tags here.
89 	 * They are not part of the saved_game and are only used during mp_staging/mp_join_game.
90 	 *
91 	 * @todo: see if all the comments ai algorithms are still up-to-date and relevant.
92 	 *
93 	 * -- vultraz, 2017-11-24
94 	 */
95 
96 	const config& game_config = game_config_manager::get()->game_config();
97 	const config& era_cfg = game_config.find_child("era", "id", era);
98 
99 	if(const config& temp_cfg = game_config.find_child("scenario", "id", level.child("multiplayer")["mp_scenario"])) {
100 		level.child("multiplayer")["mp_scenario_addon_id"] = temp_cfg["addon_id"].str();
101 		level.child("multiplayer")["mp_scenario_addon_version"] = temp_cfg["addon_version"].str();
102 	} else if(const config& temp_cfg = game_config.find_child("multiplayer", "id", level.child("multiplayer")["mp_scenario"])) {
103 		level.child("multiplayer")["mp_scenario_addon_id"] = temp_cfg["addon_id"].str();
104 		level.child("multiplayer")["mp_scenario_addon_version"] = temp_cfg["addon_version"].str();
105 	} else {
106 		level.child("multiplayer")["mp_scenario_addon_id"] = "";
107 		level.child("multiplayer")["mp_scenario_addon_version"] = "";
108 	}
109 
110 	if(!era_cfg) {
111 		if(!params.saved_game) {
112 			throw config::error(VGETTEXT("Cannot find era $era", {{"era", era}}));
113 		}
114 
115 		// FIXME: @todo We should tell user about missing era but still load game...
116 		WRN_CF << "Missing era in MP load game " << era << std::endl;
117 
118 		// Otherwise we get an error when when we try to add ai algorithms in mp_staging.
119 		level.add_child("era");
120 	} else {
121 		level.add_child("era", era_cfg);
122 
123 		level.child("multiplayer")["mp_era_addon_id"] = era_cfg["addon_id"].str();
124 		level.child("multiplayer")["mp_era_addon_version"] = era_cfg["addon_version"].str();
125 
126 		// Initialize the list of sides available for the current era.
127 		// We also need this so not to get a segfault in mp_staging for ai configuration.
128 		const config& custom_side = game_config.find_child("multiplayer_side", "id", "Custom");
129 		level.child("era").add_child_at("multiplayer_side", custom_side, 0);
130 	}
131 
132 	// Add modifications, needed for ai algorithms which are applied in mp_staging.
133 	const std::vector<std::string>& mods = params.active_mods;
134 	std::vector<std::string> mod_versions;
135 	std::vector<std::string> mod_addon_ids;
136 
137 	for(unsigned i = 0; i < mods.size(); ++i) {
138 		if(const config& mod_cfg = game_config.find_child("modification", "id", mods[i])) {
139 			mod_versions.push_back(mod_cfg["addon_version"].str());
140 			mod_addon_ids.push_back(mod_cfg["addon_id"].str());
141 			level.add_child("modification", mod_cfg);
142 		}
143 	}
144 
145 	level.child("multiplayer")["active_mod_versions"] = utils::join(mod_versions);
146 	level.child("multiplayer")["active_mod_addon_ids"] = utils::join(mod_addon_ids);
147 
148 	// This will force connecting clients to be using the same version number as us.
149 	level["version"] = game_config::version;
150 	return level;
151 }
152 
level_to_gamestate(const config & level,saved_game & state)153 void level_to_gamestate(const config& level, saved_game& state)
154 {
155 	game_classification::CAMPAIGN_TYPE type = state.classification().campaign_type;
156 	state = saved_game(level);
157 	state.classification().campaign_type = type;
158 }
159 
check_response(bool res,const config & data)160 void check_response(bool res, const config& data)
161 {
162 	if(!res) {
163 		throw wesnothd_error(_("Connection timed out"));
164 	}
165 
166 	if(const config& err = data.child("error")) {
167 		throw wesnothd_error(err["message"]);
168 	}
169 }
170 
171 } // end namespace mp
172