The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
playcampaign.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2005 - 2025
3 by Philippe Plantier <ayin@anathas.org>
4 Copyright (C) 2003 - 2005 by David White <dave@whitevine.net>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
13
14 See the COPYING file for more details.
15*/
16
17/**
18 * @file
19 * Controls setup, play, (auto)save and replay of campaigns.
20 */
21
23
25#include "game_config.hpp"
26#include "game_errors.hpp"
30#include "gettext.hpp"
31#include "gui/gui.hpp"
33#include "gui/dialogs/outro.hpp"
35#include "log.hpp"
36#include "map/exception.hpp"
37#include "playmp_controller.hpp"
39#include "saved_game.hpp"
40#include "savegame.hpp"
41#include "sound.hpp"
43#include "wml_exception.hpp"
44
45#define LOG_G LOG_STREAM(info, lg::general)
46
47static lg::log_domain log_engine("engine");
48#define LOG_NG LOG_STREAM(info, log_engine)
49#define ERR_NG LOG_STREAM(err, log_engine)
50
51static lg::log_domain log_enginerefac("enginerefac");
52#define LOG_RG LOG_STREAM(info, log_enginerefac)
53
55{
56 const config& starting_point = is_replay_
59
60 playsingle_controller playcontroller(starting_point, state_);
61
62 LOG_NG << "created objects... " << playcontroller.timer();
63 if(is_replay_) {
64 playcontroller.enable_replay(is_unit_test_);
65 }
66
67 level_result::type res = playcontroller.play_scenario(starting_point);
68 if(res == level_result::type::quit) {
69 return level_result::type::quit;
70 }
71
72 if(!is_unit_test_) {
73 is_replay_ = false;
74 }
75
76 if(is_replay_) {
77 return res;
78 }
79
80 end_level = playcontroller.get_end_level_data();
81 state_.set_snapshot(playcontroller.to_config());
82 return res;
83}
84
86{
88 level_result::type res = playcontroller.play_scenario(state_.get_starting_point());
89
90 // Check if the player started as mp client and changed to host
91 if(res == level_result::type::quit) {
92 return level_result::type::quit;
93 }
94
95 end_level = playcontroller.get_end_level_data();
96
97 playcontroller.update_savegame_snapshot();
98
99 if(mp_info_) {
100 mp_info_->connected_players = playcontroller.all_players();
101 mp_info_->skip_replay = false;
103 }
104
105 return res;
106}
107
109{
110 if(is_replay_) {
112 } else {
114 }
115
117
118 while(state_.valid()) {
119 level_result::type res = level_result::type::victory;
120 end_level_data end_level;
121
122 try {
124 // In case this an mp scenario reloaded by sp this was not already done yet.
126
128
130
131 // expand_mp_options must be called after expand_carryover because expand_carryover will to set previous
132 // variables if there are already variables in the [scenario]
134
135#if !defined(ALWAYS_USE_MP_CONTROLLER)
137 res = playsingle_scenario(end_level);
138 if(is_replay_) {
139 return res;
140 }
141 } else
142#endif
143 {
144 res = playmp_scenario(end_level);
145 }
146 } catch(const leavegame_wesnothd_error&) {
147 LOG_NG << "The game was remotely ended";
148 return level_result::type::quit;
149 } catch(const game::load_game_failed& e) {
150 gui2::show_error_message(_("The game could not be loaded: ") + e.message);
151 return level_result::type::quit;
152 } catch(const quit_game_exception&) {
153 LOG_NG << "The game was aborted";
154 return level_result::type::quit;
155 } catch(const game::game_error& e) {
156 gui2::show_error_message(_("Error while playing the game: ") + e.message);
157 return level_result::type::quit;
158 } catch(const incorrect_map_format_error& e) {
159 gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
160 return level_result::type::quit;
161 } catch(const mapgen_exception& e) {
162 gui2::show_error_message(_("Map generator error: ") + e.message);
163 } catch(const config::error& e) {
164 gui2::show_error_message(_("Error while reading the WML: ") + e.message);
165 return level_result::type::quit;
166 } catch(const wml_exception& e) {
167 e.show();
168 return level_result::type::quit;
169 }
170
171 if(is_unit_test_) {
172 return res;
173 }
174
175 if(res == level_result::type::quit) {
176 return res;
177 }
178
179 // proceed_to_next_level <=> 'any human side received victory'
180 // If 'any human side received victory' we do the Save-management options
181 // Otherwise we are done now
182 if(!end_level.proceed_to_next_level) {
183 return res;
184 }
185
186 if(prefs::get().delete_saves()) {
188 }
189
190 if(prefs::get().save_replays() && end_level.replay_save) {
191 savegame::replay_savegame save(state_, prefs::get().save_compression_format());
192 save.save_game_automatic(true);
193 }
194
196
197 // If there is no next scenario we're done now.
198 if(state_.get_scenario_id().empty()) {
199 // Don't show The End for multiplayer scenarios.
200 if(res == level_result::type::victory && !state_.classification().is_normal_mp_game()) {
203
205 gui2::dialogs::outro::display(state_.classification());
206 }
207 }
208 return res;
209 } else if(res == level_result::type::observer_end && mp_info_ && !mp_info_->is_host) {
210 const int dlg_res = gui2::show_message(_("Game Over"),
211 _("This scenario has ended. Do you want to continue the campaign?"),
213
214 if(dlg_res == gui2::retval::CANCEL) {
215 return res;
216 }
217 }
218
219 if(mp_info_ && !mp_info_->is_host) {
220 // Opens join game dialog to get a new gamestate.
221 if(!mp::goto_mp_wait(res == level_result::type::observer_end)) {
222 return level_result::type::quit;
223 }
224
225 // The host should send the complete savegame now that also contains the carryover sides start.
226 } else {
227 // clear previous game content information
228 // otherwise it keeps getting appended for each scenario resulting in incorrect data being sent to the server to be stored
229 state_.mp_settings().addons.clear();
230 // Retrieve next scenario data.
232
233 if(state_.valid()) {
234 // note that although starting_pos is const it might be changed by gamestate.some_non_const_operation()
235 const config& starting_pos = state_.get_starting_point();
236
237 const bool is_mp = state_.classification().is_normal_mp_game();
238 state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
239
240 if(state_.mp_settings().saved_game == saved_game_mode::type::midgame) {
241 state_.mp_settings().saved_game = saved_game_mode::type::scenaro_start;
242 }
243
244 state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
245
246 ng::connect_engine connect_engine(state_, false, mp_info_);
247
248 if(!connect_engine.can_start_game()) {
249 // Opens staging dialog to allow users to make an adjustments for scenario.
250 if(!mp::goto_mp_staging(connect_engine)) {
251 return level_result::type::quit;
252 }
253 } else {
254 // Start the next scenario immediately.
255 connect_engine.start_game();
256 }
257 }
258 }
259
260 if(state_.valid()) {
261 // Update the label
263
264 // If this isn't the last scenario, then save the game
265 if(end_level.prescenario_save) {
266 // For multiplayer, we want the save to contain the starting position.
267 // For campaigns however, this is the start-of-scenario save and the
268 // starting position needs to be empty, to force a reload of the scenario config.
269 savegame::scenariostart_savegame save(state_, prefs::get().save_compression_format());
270 save.save_game_automatic();
271 }
272 }
273 }
274
275 if(!state_.get_scenario_id().empty()) {
276 utils::string_map symbols;
277 symbols["scenario"] = state_.get_scenario_id();
278
279 std::string message = _("Unknown scenario: ‘$scenario|’");
280 message = utils::interpolate_variables_into_string(message, &symbols);
281
283 return level_result::type::quit;
284 }
285
287 if(prefs::get().delete_saves()) {
289 }
290 }
291
292 return level_result::type::victory;
293}
294
296{
297 // If the scenario changed the current gui2 theme,
298 // change it back to the value stored in preferences
299 gui2::switch_theme(prefs::get().gui2_theme());
300}
#define LOG_NG
level_result::type play_game()
level_result::type playsingle_scenario(end_level_data &end_level)
mp_game_metadata * mp_info_
level_result::type playmp_scenario(end_level_data &end_level)
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
std::string difficulty
The difficulty level the game is being played on.
bool end_credits
whether to show the standard credits at the end
std::string label
Name of the game (e.g.
std::string campaign
The id of the campaign being played.
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
bool can_start_game() const
config to_config() const
Builds the snapshot config from members and their respective configs.
const end_level_data & get_end_level_data() const
void update_savegame_snapshot() const
std::set< std::string > all_players() const
const auto & timer() const
level_result::type play_scenario(const config &level)
void enable_replay(bool is_unit_test=false)
static prefs & get()
void add_completed_campaign(const std::string &campaign_id, const std::string &difficulty_level)
replay_recorder_base & get_replay()
void expand_scenario()
copies the content of a [scenario] with the correct id attribute from the game config into this objec...
void expand_mp_options()
adds values of [option]s into [carryover_sides_start][variables] so that they are applied in the next...
std::string get_scenario_id() const
void update_label()
sets classification().label to the correct value.
game_classification & classification()
config & get_starting_point()
const config & get_replay_starting_point()
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
void expand_mp_events()
adds [event]s from [era] and [modification] into this scenario does NOT expand [option]s because vari...
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
void convert_to_start_save()
converts a normal savegame form the end of a scenaio to a start-of-scenario savefile for the next sce...
bool valid() const
config & set_snapshot(config snapshot)
Class for replay saves (either manually or automatically).
Definition savegame.hpp:244
bool save_game_automatic(bool ask_for_overwrite=false, const std::string &filename="")
Saves a game without user interaction, unless the file exists and it should be asked to overwrite it.
Definition savegame.cpp:274
Class for start-of-scenario saves.
Definition savegame.hpp:283
static std::string _(const char *str)
Definition gettext.hpp:100
Standard logging facilities (interface).
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition message.cpp:201
void switch_theme(const std::string &theme_id)
Set and activate the given gui2 theme.
Definition gui.cpp:149
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition message.cpp:148
@ CANCEL
Dialog was closed with the CANCEL button.
Definition retval.hpp:38
bool goto_mp_staging(ng::connect_engine &engine)
Opens the MP Staging screen and sets the game state according to the changes made.
bool goto_mp_wait(bool observe)
Opens the MP Join Game screen and sets the game state according to the changes made.
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition savegame.cpp:64
void empty_playlist()
Definition sound.cpp:605
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
std::map< std::string, t_string > string_map
static lg::log_domain log_engine("engine")
static lg::log_domain log_enginerefac("enginerefac")
Additional information on the game outcome which can be provided by WML.
bool prescenario_save
Should a prescenario be created the next game?
bool replay_save
Should a replay save be made?
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
Error used for any general game error, e.g.
Error used when game loading fails.
std::set< std::string > connected_players
players and observers
std::map< std::string, addon_version_info > addons
the key is the addon_id
saved_game_mode::type saved_game
Helper class, don't construct this directly.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e