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 "formula/string_utils.hpp"
25 #include "game_config.hpp"
26 #include "game_errors.hpp"
30 #include "gettext.hpp"
31 #include "gui/dialogs/message.hpp"
32 #include "gui/dialogs/outro.hpp"
33 #include "gui/widgets/retval.hpp"
34 #include "log.hpp"
35 #include "map/exception.hpp"
36 #include "playmp_controller.hpp"
37 #include "preferences/game.hpp"
38 #include "saved_game.hpp"
39 #include "savegame.hpp"
40 #include "sound.hpp"
41 #include "wesnothd_connection.hpp"
42 #include "wml_exception.hpp"
43 
44 #define LOG_G LOG_STREAM(info, lg::general)
45 
46 static lg::log_domain log_engine("engine");
47 #define LOG_NG LOG_STREAM(info, log_engine)
48 #define ERR_NG LOG_STREAM(err, log_engine)
49 
50 static lg::log_domain log_enginerefac("enginerefac");
51 #define LOG_RG LOG_STREAM(info, log_enginerefac)
52 
54 {
55  const config& starting_point = is_replay_
58 
59  playsingle_controller playcontroller(starting_point, state_, false);
60 
61  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks());
62  if(is_replay_) {
63  playcontroller.enable_replay(is_unit_test_);
64  }
65 
66  level_result::type res = playcontroller.play_scenario(starting_point);
67  if(res == level_result::type::quit) {
68  return level_result::type::quit;
69  }
70 
71  if(!is_unit_test_) {
72  is_replay_ = false;
73  }
74 
75  if(is_replay_) {
76  return res;
77  }
78 
79  end_level = playcontroller.get_end_level_data();
80  state_.set_snapshot(playcontroller.to_config());
81  return res;
82 }
83 
85 {
88 
89  // Check if the player started as mp client and changed to host
90  if(res == level_result::type::quit) {
91  return level_result::type::quit;
92  }
93 
94  end_level = playcontroller.get_end_level_data();
95 
96  playcontroller.update_savegame_snapshot();
97 
98  if(mp_info_) {
99  mp_info_->connected_players = playcontroller.all_players();
100  mp_info_->skip_replay = false;
102  }
103 
104  return res;
105 }
106 
108 {
109  if(is_replay_) {
111  } else {
113  }
114 
116 
117  while(state_.valid()) {
118  level_result::type res = level_result::type::victory;
119  end_level_data end_level;
120 
121  try {
123  // In case this an mp scenario reloaded by sp this was not already done yet.
125 
127 
129 
130  // expand_mp_options must be called after expand_carryover because expand_carryover will to set previous
131  // variables if there are already variables in the [scenario]
133 
134 #if !defined(ALWAYS_USE_MP_CONTROLLER)
136  res = playsingle_scenario(end_level);
137  if(is_replay_) {
138  return res;
139  }
140  } else
141 #endif
142  {
143  res = playmp_scenario(end_level);
144  }
145  } catch(const leavegame_wesnothd_error&) {
146  LOG_NG << "The game was remotely ended";
147  return level_result::type::quit;
148  } catch(const game::load_game_failed& e) {
149  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
150  return level_result::type::quit;
151  } catch(const quit_game_exception&) {
152  LOG_NG << "The game was aborted";
153  return level_result::type::quit;
154  } catch(const game::game_error& e) {
155  gui2::show_error_message(_("Error while playing the game: ") + e.message);
156  return level_result::type::quit;
157  } catch(const incorrect_map_format_error& e) {
158  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
159  return level_result::type::quit;
160  } catch(const mapgen_exception& e) {
161  gui2::show_error_message(_("Map generator error: ") + e.message);
162  } catch(const config::error& e) {
163  gui2::show_error_message(_("Error while reading the WML: ") + e.message);
164  return level_result::type::quit;
165  } catch(const wml_exception& e) {
166  e.show();
167  return level_result::type::quit;
168  }
169 
170  if(is_unit_test_) {
171  return res;
172  }
173 
174  if(res == level_result::type::quit) {
175  return res;
176  }
177 
178  // proceed_to_next_level <=> 'any human side received victory'
179  // If 'any human side received victory' we do the Save-management options
180  // Otherwise we are done now
181  if(!end_level.proceed_to_next_level) {
182  return res;
183  }
184 
187  }
188 
189  if(preferences::save_replays() && end_level.replay_save) {
191  save.save_game_automatic(true);
192  }
193 
195 
196  // If there is no next scenario we're done now.
197  if(state_.get_scenario_id().empty()) {
198  // Don't show The End for multiplayer scenarios.
199  if(res == level_result::type::victory && !state_.classification().is_normal_mp_game()) {
202 
204  gui2::dialogs::outro::display(state_.classification());
205  }
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() || (game_config::debug && state_.classification().is_multiplayer())) {
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.
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 
282  gui2::show_error_message(message);
283  return level_result::type::quit;
284  }
285 
289  }
290  }
291 
292  return level_result::type::victory;
293 }
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:283
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:426
std::string get_scenario_id() const
Definition: saved_game.cpp:678
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:701
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
config & get_starting_point()
Definition: saved_game.cpp:611
const config & get_replay_starting_point()
Definition: saved_game.cpp:616
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:385
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:504
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
Definition: saved_game.cpp:565
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:635
bool valid() const
Definition: saved_game.cpp:582
replay_recorder_base & get_replay()
Definition: saved_game.hpp:140
config & set_snapshot(config snapshot)
Definition: saved_game.cpp:587
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:360
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:203
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:150
@ 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:285
bool delete_saves()
Definition: game.cpp:763
compression::format save_compression_format()
Definition: game.cpp:840
bool save_replays()
Definition: game.cpp:753
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:62
void empty_playlist()
Definition: sound.cpp:610
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