The Battle for Wesnoth  1.19.5+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/gui.hpp"
32 #include "gui/dialogs/message.hpp"
33 #include "gui/dialogs/outro.hpp"
34 #include "gui/widgets/retval.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"
42 #include "wesnothd_connection.hpp"
43 #include "wml_exception.hpp"
44 
45 #define LOG_G LOG_STREAM(info, lg::general)
46 
47 static 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 
51 static 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 {
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() || (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.
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 
282  gui2::show_error_message(message);
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 }
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:172
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
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)
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:361
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:94
void switch_theme(const std::string &current_theme)
Set and activate the given gui2 theme.
Definition: gui.cpp:135
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
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:63
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