The Battle for Wesnoth  1.17.10+dev
playcampaign.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2022
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 "formula/string_utils.hpp"
26 #include "game_config.hpp"
27 #include "game_errors.hpp"
33 #include "gettext.hpp"
34 #include "gui/dialogs/message.hpp"
35 #include "gui/dialogs/outro.hpp"
37 #include "gui/widgets/retval.hpp"
38 #include "log.hpp"
39 #include "map/exception.hpp"
40 #include "map/map.hpp"
41 #include "persist_manager.hpp"
42 #include "playmp_controller.hpp"
43 #include "preferences/game.hpp"
44 #include "saved_game.hpp"
45 #include "savegame.hpp"
46 #include "sound.hpp"
47 #include "video.hpp"
48 #include "wesnothd_connection.hpp"
49 #include "wml_exception.hpp"
50 
51 #define LOG_G LOG_STREAM(info, lg::general)
52 
53 static lg::log_domain log_engine("engine");
54 #define LOG_NG LOG_STREAM(info, log_engine)
55 #define ERR_NG LOG_STREAM(err, log_engine)
56 
57 static lg::log_domain log_enginerefac("enginerefac");
58 #define LOG_RG LOG_STREAM(info, log_enginerefac)
59 
61  playsingle_controller& playcontroller, const end_level_data& end_level, const level_result::type res)
62 {
63  // We need to write the carryover amount to the team that's why we need non const
64  std::vector<team>& teams = playcontroller.get_teams();
65 
66  // maybe this can be the case for scenario that only contain a story and end during the prestart event ?
67  if(teams.size() < 1) {
68  return;
69  }
70 
71  std::ostringstream report;
72  std::string title;
73 
74  bool obs = playcontroller.is_observer();
75 
76  if(obs) {
77  title = _("Scenario Report");
78  } else if(res == level_result::type::victory) {
79  title = _("Victory");
80  report << "<b>" << _("You have emerged victorious!") << "</b>";
81  } else {
82  title = _("Defeat");
83  report << _("You have been defeated!");
84  }
85 
86  const std::string& next_scenario = playcontroller.gamestate().get_game_data()->next_scenario();
87  const bool has_next_scenario = !next_scenario.empty() && next_scenario != "null";
88 
89  int persistent_teams = 0;
90  for(const team& t : teams) {
91  if(t.persistent()) {
92  ++persistent_teams;
93  }
94  }
95 
96  if(persistent_teams > 0 && ((has_next_scenario && end_level.proceed_to_next_level) || state_.classification().is_test())) {
97  const gamemap& map = playcontroller.get_map();
98  const tod_manager& tod = playcontroller.get_tod_manager();
99 
100  const int turns_left = std::max<int>(0, tod.number_of_turns() - tod.turn());
101  for(team& t : teams) {
102  if(!t.persistent() || t.lost()) {
103  continue;
104  }
105 
106  const int finishing_bonus_per_turn = map.villages().size() * t.village_gold() + t.base_income();
107  const int finishing_bonus = t.carryover_bonus() * finishing_bonus_per_turn * turns_left;
108 
109  t.set_carryover_gold(div100rounded((t.gold() + finishing_bonus) * t.carryover_percentage()));
110 
111  if(!t.is_local_human()) {
112  continue;
113  }
114 
115  if(persistent_teams > 1) {
116  report << "\n\n<b>" << t.side_name() << "</b>";
117  }
118 
119  report << "<small>\n" << _("Remaining gold: ") << utils::half_signed_value(t.gold()) << "</small>";
120 
121  if(t.carryover_bonus() != 0) {
122  if(turns_left > -1) {
123  report << "\n\n<b>" << _("Turns finished early: ") << turns_left << "</b>\n"
124  << "<small>" << _("Early finish bonus: ") << finishing_bonus_per_turn << _(" per turn") << "</small>\n"
125  << "<small>" << _("Total bonus: ") << finishing_bonus << "</small>\n";
126  }
127 
128  report << "<small>" << _("Total gold: ") << utils::half_signed_value(t.gold() + finishing_bonus) << "</small>";
129  }
130 
131  if(t.gold() > 0) {
132  report << "\n<small>" << _("Carryover percentage: ") << t.carryover_percentage() << "</small>";
133  }
134 
135  if(t.carryover_add()) {
136  report << "\n\n<big><b>" << _("Bonus gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
137  } else {
138  report << "\n\n<big><b>" << _("Retained gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
139  }
140 
141  std::string goldmsg;
142  utils::string_map symbols;
143 
144  symbols["gold"] = lexical_cast_default<std::string>(t.carryover_gold());
145 
146  // Note that both strings are the same in English, but some languages will
147  // want to translate them differently.
148  if(t.carryover_add()) {
149  if(t.carryover_gold() > 0) {
150  goldmsg = VNGETTEXT(
151  "You will start the next scenario with $gold on top of the defined minimum starting gold.",
152  "You will start the next scenario with $gold on top of the defined minimum starting gold.",
153  t.carryover_gold(), symbols
154  );
155 
156  } else {
157  goldmsg = VNGETTEXT(
158  "You will start the next scenario with the defined minimum starting gold.",
159  "You will start the next scenario with the defined minimum starting gold.",
160  t.carryover_gold(), symbols
161  );
162  }
163  } else {
164  goldmsg = VNGETTEXT(
165  "You will start the next scenario with $gold or its defined minimum starting gold, "
166  "whichever is higher.",
167  "You will start the next scenario with $gold or its defined minimum starting gold, "
168  "whichever is higher.",
169  t.carryover_gold(), symbols
170  );
171  }
172 
173  // xgettext:no-c-format
174  report << "\n" << goldmsg;
175  }
176  }
177 
178  if(end_level.transient.carryover_report) {
179  gui2::show_transient_message(title, report.str(), "", true);
180  }
181 }
182 
184 {
185  const config& starting_point = is_replay_
188 
189  playsingle_controller playcontroller(starting_point, state_, false);
190 
191  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks());
192  if(is_replay_) {
193  playcontroller.enable_replay(is_unit_test_);
194  }
195 
196  level_result::type res = playcontroller.play_scenario(starting_point);
197  if(res == level_result::type::quit) {
198  return level_result::type::quit;
199  }
200 
201  if(!is_unit_test_) {
202  is_replay_ = false;
203  }
204 
205  if(is_replay_) {
206  return res;
207  }
208 
209  end_level = playcontroller.get_end_level_data();
210  show_carryover_message(playcontroller, end_level, res);
211 
212  if(!video::headless()) {
213  playcontroller.maybe_linger();
214  }
215 
216  state_.set_snapshot(playcontroller.to_config());
217  return res;
218 }
219 
221 {
223  level_result::type res = playcontroller.play_scenario(state_.get_starting_point());
224 
225  // Check if the player started as mp client and changed to host
226  if(res == level_result::type::quit) {
227  return level_result::type::quit;
228  }
229 
230  end_level = playcontroller.get_end_level_data();
231 
232  if(res != level_result::type::observer_end) {
233  // We need to call this before linger because it prints the defeated/victory message.
234  //(we want to see that message before entering the linger mode)
235  show_carryover_message(playcontroller, end_level, res);
236  }
237 
238  playcontroller.maybe_linger();
239  playcontroller.update_savegame_snapshot();
240 
241  if(mp_info_) {
242  mp_info_->connected_players = playcontroller.all_players();
243  mp_info_->skip_replay = false;
245  }
246 
247  return res;
248 }
249 
251 {
252  if(is_replay_) {
254  } else {
256  }
257 
259 
260  while(state_.valid()) {
261  level_result::type res = level_result::type::victory;
262  end_level_data end_level;
263 
264  try {
266  // In case this an mp scenario reloaded by sp this was not already done yet.
268 
270 
272 
273  // expand_mp_options must be called after expand_carryover because expand_carryover will to set previous
274  // variables if there are already variables in the [scenario]
276 
277 #if !defined(ALWAYS_USE_MP_CONTROLLER)
279  res = playsingle_scenario(end_level);
280  if(is_replay_) {
281  return res;
282  }
283  } else
284 #endif
285  {
286  res = playmp_scenario(end_level);
287  }
288  } catch(const leavegame_wesnothd_error&) {
289  LOG_NG << "The game was remotely ended";
290  return level_result::type::quit;
291  } catch(const game::load_game_failed& e) {
292  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
293  return level_result::type::quit;
294  } catch(const quit_game_exception&) {
295  LOG_NG << "The game was aborted";
296  return level_result::type::quit;
297  } catch(const game::game_error& e) {
298  gui2::show_error_message(_("Error while playing the game: ") + e.message);
299  return level_result::type::quit;
300  } catch(const incorrect_map_format_error& e) {
301  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
302  return level_result::type::quit;
303  } catch(const mapgen_exception& e) {
304  gui2::show_error_message(_("Map generator error: ") + e.message);
305  } catch(const config::error& e) {
306  gui2::show_error_message(_("Error while reading the WML: ") + e.message);
307  return level_result::type::quit;
308  } catch(const wml_exception& e) {
309  e.show();
310  return level_result::type::quit;
311  }
312 
313  if(is_unit_test_) {
314  return res;
315  }
316 
317  if(res == level_result::type::quit) {
318  return res;
319  }
320 
321  // proceed_to_next_level <=> 'any human side received victory'
322  // If 'any human side received victory' we do the Save-management options
323  // Otherwise we are done now
324  if(!end_level.proceed_to_next_level) {
325  return res;
326  }
327 
330  }
331 
332  if(preferences::save_replays() && end_level.replay_save) {
334  save.save_game_automatic(true);
335  }
336 
338 
339  // If there is no next scenario we're done now.
340  if(state_.get_scenario_id().empty()) {
341  // Don't show The End for multiplayer scenarios.
342  if(res == level_result::type::victory && !state_.classification().is_normal_mp_game()) {
345 
347  gui2::dialogs::outro::display(state_.classification());
348  }
349  }
350 
351  return res;
352  } else if(res == level_result::type::observer_end && mp_info_ && !mp_info_->is_host) {
353  const int dlg_res = gui2::show_message(_("Game Over"),
354  _("This scenario has ended. Do you want to continue the campaign?"),
356 
357  if(dlg_res == gui2::retval::CANCEL) {
358  return res;
359  }
360  }
361 
362  if(mp_info_ && !mp_info_->is_host) {
363  // Opens join game dialog to get a new gamestate.
364  if(!mp::goto_mp_wait(res == level_result::type::observer_end)) {
365  return level_result::type::quit;
366  }
367 
368  // The host should send the complete savegame now that also contains the carryover sides start.
369  } else {
370  // clear previous game content information
371  // otherwise it keeps getting appended for each scenario resulting in incorrect data being sent to the server to be stored
372  state_.mp_settings().addons.clear();
373  // Retrieve next scenario data.
375 
376  if(state_.valid()) {
377  // note that although starting_pos is const it might be changed by gamestate.some_non_const_operation()
378  const config& starting_pos = state_.get_starting_point();
379 
380  const bool is_mp = state_.classification().is_normal_mp_game();
381  state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
382 
383  if(state_.mp_settings().saved_game == saved_game_mode::type::midgame) {
384  state_.mp_settings().saved_game = saved_game_mode::type::scenaro_start;
385  }
386 
387  state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
388 
389  ng::connect_engine connect_engine(state_, false, mp_info_);
390 
391  if(!connect_engine.can_start_game() || (game_config::debug && state_.classification().is_multiplayer())) {
392  // Opens staging dialog to allow users to make an adjustments for scenario.
393  if(!mp::goto_mp_staging(connect_engine)) {
394  return level_result::type::quit;
395  }
396  } else {
397  // Start the next scenario immediately.
398  connect_engine.start_game();
399  }
400  }
401  }
402 
403  if(state_.valid()) {
404  // Update the label
406 
407  // If this isn't the last scenario, then save the game
408  if(end_level.prescenario_save) {
409  // For multiplayer, we want the save to contain the starting position.
410  // For campaigns however, this is the start-of-scenario save and the
411  // starting position needs to be empty, to force a reload of the scenario config.
413  save.save_game_automatic();
414  }
415  }
416  }
417 
418  if(!state_.get_scenario_id().empty()) {
419  utils::string_map symbols;
420  symbols["scenario"] = state_.get_scenario_id();
421 
422  std::string message = _("Unknown scenario: '$scenario|'");
423  message = utils::interpolate_variables_into_string(message, &symbols);
424 
425  gui2::show_error_message(message);
426  return level_result::type::quit;
427  }
428 
432  }
433  }
434 
435  return level_result::type::victory;
436 }
void empty_playlist()
Definition: sound.cpp:612
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
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
bool end_credits
whether to show the standard credits at the end
Class for start-of-scenario saves.
Definition: savegame.hpp:305
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
std::map< std::string, t_string > string_map
std::string label
Name of the game (e.g.
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:269
static lg::log_domain log_enginerefac("enginerefac")
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
level_result::type play_scenario(const config &level)
level_result::type play_game()
Error used when game loading fails.
Definition: game_errors.hpp:31
replay_recorder_base & get_replay()
Definition: saved_game.hpp:135
level_result::type playsingle_scenario(end_level_data &end_level)
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:477
bool valid() const
Definition: saved_game.cpp:554
mp_game_metadata * mp_info_
static lg::log_domain log_engine("engine")
bool goto_mp_wait(bool observe)
Opens the MP Join Game screen and sets the game state according to the changes made.
bool prescenario_save
Should a prescenario be created the next game?
void enable_replay(bool is_unit_test=false)
#define VNGETTEXT(msgid, msgid_plural, count,...)
const gamemap & get_map() const
bool replay_save
Should a replay save be made?
config & set_snapshot(config snapshot)
Definition: saved_game.cpp:559
saved_game & state_
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string get_scenario_id() const
Definition: saved_game.cpp:649
void show() const
Shows the error in a dialog.
constexpr int div100rounded(int num)
Guarantees portable results for division by 100; round half up, to the nearest integer.
Definition: math.hpp:39
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:75
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
level_result::type playmp_scenario(end_level_data &end_level)
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:413
config to_config() const
Builds the snapshot config from members and their respective configs.
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:672
Encapsulates the map of the game.
Definition: map.hpp:171
std::vector< team > & get_teams()
bool headless()
The game is running headless.
Definition: video.cpp:143
config & get_starting_point()
Definition: saved_game.cpp:583
saved_game_mode::type saved_game
Shows a yes and no button.
Definition: message.hpp:81
void show_carryover_message(playsingle_controller &playcontroller, const end_level_data &end_level, level_result::type res)
bool carryover_report
Should a summary of the scenario outcome be displayed?
std::string campaign
The id of the campaign being played.
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
std::set< std::string > connected_players
players and observers
bool is_observer() const
int number_of_turns() const
Helper class, don&#39;t construct this directly.
transient_end_level transient
const config & get_replay_starting_point()
Definition: saved_game.cpp:588
const bool is_unit_test_
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:69
std::map< std::string, addon_version_info > addons
the key is the addon_id
void add_completed_campaign(const std::string &campaign_id, const std::string &difficulty_level)
Definition: game.cpp:289
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
bool delete_saves()
Definition: game.cpp:767
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:372
Additional information on the game outcome which can be provided by WML.
const bool & debug
const end_level_data & get_end_level_data() const
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
Definition: saved_game.cpp:538
const std::string & next_scenario() const
Definition: game_data.hpp:96
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:266
std::string difficulty
The difficulty level the game is being played on.
compression::format save_compression_format()
Definition: game.cpp:844
virtual const game_data * get_game_data() const override
Inherited from filter_context.
Definition: game_state.hpp:105
#define LOG_NG
game_state & gamestate()
double t
Definition: astarsearch.cpp:65
const tod_manager & get_tod_manager() const
game_classification & classification()
Definition: saved_game.hpp:55
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:237
Standard logging facilities (interface).
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:607
std::string message
Definition: exceptions.hpp:30
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:204
bool goto_mp_staging(ng::connect_engine &engine)
Opens the MP Staging screen and sets the game state according to the changes made.
int turn() const
#define e
bool can_start_game() const
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
bool skip_replay_blindfolded
int turns_left
Definition: pathfind.cpp:156
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:367
bool save_replays()
Definition: game.cpp:757