The Battle for Wesnoth  1.19.0-dev
playcampaign.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2024
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 
24 #include "carryover.hpp"
25 #include "carryover_show_gold.hpp"
26 #include "formula/string_utils.hpp"
27 #include "game_config.hpp"
28 #include "game_errors.hpp"
34 #include "gettext.hpp"
35 #include "gui/dialogs/message.hpp"
36 #include "gui/dialogs/outro.hpp"
38 #include "gui/widgets/retval.hpp"
39 #include "log.hpp"
40 #include "map/exception.hpp"
41 #include "map/map.hpp"
42 #include "persist_manager.hpp"
43 #include "playmp_controller.hpp"
44 #include "preferences/game.hpp"
45 #include "saved_game.hpp"
46 #include "savegame.hpp"
47 #include "sound.hpp"
48 #include "video.hpp"
49 #include "wesnothd_connection.hpp"
50 #include "wml_exception.hpp"
51 
52 #define LOG_G LOG_STREAM(info, lg::general)
53 
54 static lg::log_domain log_engine("engine");
55 #define LOG_NG LOG_STREAM(info, log_engine)
56 #define ERR_NG LOG_STREAM(err, log_engine)
57 
58 static lg::log_domain log_enginerefac("enginerefac");
59 #define LOG_RG LOG_STREAM(info, log_enginerefac)
60 
62 {
63  const config& starting_point = is_replay_
66 
67  playsingle_controller playcontroller(starting_point, state_, false);
68 
69  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks());
70  if(is_replay_) {
71  playcontroller.enable_replay(is_unit_test_);
72  }
73 
74  level_result::type res = playcontroller.play_scenario(starting_point);
75  if(res == level_result::type::quit) {
76  return level_result::type::quit;
77  }
78 
79  if(!is_unit_test_) {
80  is_replay_ = false;
81  }
82 
83  if(is_replay_) {
84  return res;
85  }
86 
87  end_level = playcontroller.get_end_level_data();
88  state_.set_snapshot(playcontroller.to_config());
89  return res;
90 }
91 
93 {
96 
97  // Check if the player started as mp client and changed to host
98  if(res == level_result::type::quit) {
99  return level_result::type::quit;
100  }
101 
102  end_level = playcontroller.get_end_level_data();
103 
104  playcontroller.update_savegame_snapshot();
105 
106  if(mp_info_) {
107  mp_info_->connected_players = playcontroller.all_players();
108  mp_info_->skip_replay = false;
110  }
111 
112  return res;
113 }
114 
116 {
117  if(is_replay_) {
119  } else {
121  }
122 
124 
125  while(state_.valid()) {
126  level_result::type res = level_result::type::victory;
127  end_level_data end_level;
128 
129  try {
131  // In case this an mp scenario reloaded by sp this was not already done yet.
133 
135 
137 
138  // expand_mp_options must be called after expand_carryover because expand_carryover will to set previous
139  // variables if there are already variables in the [scenario]
141 
142 #if !defined(ALWAYS_USE_MP_CONTROLLER)
144  res = playsingle_scenario(end_level);
145  if(is_replay_) {
146  return res;
147  }
148  } else
149 #endif
150  {
151  res = playmp_scenario(end_level);
152  }
153  } catch(const leavegame_wesnothd_error&) {
154  LOG_NG << "The game was remotely ended";
155  return level_result::type::quit;
156  } catch(const game::load_game_failed& e) {
157  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
158  return level_result::type::quit;
159  } catch(const quit_game_exception&) {
160  LOG_NG << "The game was aborted";
161  return level_result::type::quit;
162  } catch(const game::game_error& e) {
163  gui2::show_error_message(_("Error while playing the game: ") + e.message);
164  return level_result::type::quit;
165  } catch(const incorrect_map_format_error& e) {
166  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
167  return level_result::type::quit;
168  } catch(const mapgen_exception& e) {
169  gui2::show_error_message(_("Map generator error: ") + e.message);
170  } catch(const config::error& e) {
171  gui2::show_error_message(_("Error while reading the WML: ") + e.message);
172  return level_result::type::quit;
173  } catch(const wml_exception& e) {
174  e.show();
175  return level_result::type::quit;
176  }
177 
178  if(is_unit_test_) {
179  return res;
180  }
181 
182  if(res == level_result::type::quit) {
183  return res;
184  }
185 
186  // proceed_to_next_level <=> 'any human side received victory'
187  // If 'any human side received victory' we do the Save-management options
188  // Otherwise we are done now
189  if(!end_level.proceed_to_next_level) {
190  return res;
191  }
192 
195  }
196 
197  if(preferences::save_replays() && end_level.replay_save) {
199  save.save_game_automatic(true);
200  }
201 
203 
204  // If there is no next scenario we're done now.
205  if(state_.get_scenario_id().empty()) {
206  // Don't show The End for multiplayer scenarios.
207  if(res == level_result::type::victory && !state_.classification().is_normal_mp_game()) {
210 
212  gui2::dialogs::outro::display(state_.classification());
213  }
214  }
215 
216  return res;
217  } else if(res == level_result::type::observer_end && mp_info_ && !mp_info_->is_host) {
218  const int dlg_res = gui2::show_message(_("Game Over"),
219  _("This scenario has ended. Do you want to continue the campaign?"),
221 
222  if(dlg_res == gui2::retval::CANCEL) {
223  return res;
224  }
225  }
226 
227  if(mp_info_ && !mp_info_->is_host) {
228  // Opens join game dialog to get a new gamestate.
229  if(!mp::goto_mp_wait(res == level_result::type::observer_end)) {
230  return level_result::type::quit;
231  }
232 
233  // The host should send the complete savegame now that also contains the carryover sides start.
234  } else {
235  // clear previous game content information
236  // otherwise it keeps getting appended for each scenario resulting in incorrect data being sent to the server to be stored
237  state_.mp_settings().addons.clear();
238  // Retrieve next scenario data.
240 
241  if(state_.valid()) {
242  // note that although starting_pos is const it might be changed by gamestate.some_non_const_operation()
243  const config& starting_pos = state_.get_starting_point();
244 
245  const bool is_mp = state_.classification().is_normal_mp_game();
246  state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
247 
248  if(state_.mp_settings().saved_game == saved_game_mode::type::midgame) {
249  state_.mp_settings().saved_game = saved_game_mode::type::scenaro_start;
250  }
251 
252  state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
253 
254  ng::connect_engine connect_engine(state_, false, mp_info_);
255 
256  if(!connect_engine.can_start_game() || (game_config::debug && state_.classification().is_multiplayer())) {
257  // Opens staging dialog to allow users to make an adjustments for scenario.
258  if(!mp::goto_mp_staging(connect_engine)) {
259  return level_result::type::quit;
260  }
261  } else {
262  // Start the next scenario immediately.
263  connect_engine.start_game();
264  }
265  }
266  }
267 
268  if(state_.valid()) {
269  // Update the label
271 
272  // If this isn't the last scenario, then save the game
273  if(end_level.prescenario_save) {
274  // For multiplayer, we want the save to contain the starting position.
275  // For campaigns however, this is the start-of-scenario save and the
276  // starting position needs to be empty, to force a reload of the scenario config.
278  save.save_game_automatic();
279  }
280  }
281  }
282 
283  if(!state_.get_scenario_id().empty()) {
284  utils::string_map symbols;
285  symbols["scenario"] = state_.get_scenario_id();
286 
287  std::string message = _("Unknown scenario: '$scenario|'");
288  message = utils::interpolate_variables_into_string(message, &symbols);
289 
290  gui2::show_error_message(message);
291  return level_result::type::quit;
292  }
293 
297  }
298  }
299 
300  return level_result::type::victory;
301 }
const bool is_unit_test_
level_result::type play_game()
level_result::type playsingle_scenario(end_level_data &end_level)
mp_game_metadata * mp_info_
saved_game & state_
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:159
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.
void update_savegame_snapshot() const
const end_level_data & get_end_level_data() const
std::set< std::string > all_players() const
level_result::type play_scenario(const config &level)
void enable_replay(bool is_unit_test=false)
game_classification & classification()
Definition: saved_game.hpp:56
void expand_scenario()
copies the content of a [scenario] with the correct id attribute from the game config into this objec...
Definition: saved_game.cpp:284
void expand_mp_options()
adds values of [option]s into [carryover_sides_start][variables] so that they are applied in the next...
Definition: saved_game.cpp:427
std::string get_scenario_id() const
Definition: saved_game.cpp:679
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:702
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
config & get_starting_point()
Definition: saved_game.cpp:612
const config & get_replay_starting_point()
Definition: saved_game.cpp:617
void expand_mp_events()
adds [event]s from [era] and [modification] into this scenario does NOT expand [option]s because vari...
Definition: saved_game.cpp:386
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:505
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
Definition: saved_game.cpp:566
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...
Definition: saved_game.cpp:636
bool valid() const
Definition: saved_game.cpp:583
replay_recorder_base & get_replay()
Definition: saved_game.hpp:140
config & set_snapshot(config snapshot)
Definition: saved_game.cpp:588
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:268
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:366
Class for start-of-scenario saves.
Definition: savegame.hpp:307
static std::string _(const char *str)
Definition: gettext.hpp:93
Standard logging facilities (interface).
const bool & debug
Definition: game_config.cpp:91
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:204
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:151
@ 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 add_completed_campaign(const std::string &campaign_id, const std::string &difficulty_level)
Definition: game.cpp:288
bool delete_saves()
Definition: game.cpp:766
compression::format save_compression_format()
Definition: game.cpp:843
bool save_replays()
Definition: game.cpp:756
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:68
void empty_playlist()
Definition: sound.cpp:612
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")
#define LOG_NG
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.
Definition: game_errors.hpp:47
Error used when game loading fails.
Definition: game_errors.hpp:31
bool skip_replay_blindfolded
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