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