The Battle for Wesnoth  1.17.0-dev
playcampaign.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2021
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 "wesnothd_connection.hpp"
48 #include "wml_exception.hpp"
49 
50 #define LOG_G LOG_STREAM(info, lg::general)
51 
52 static lg::log_domain log_engine("engine");
53 #define LOG_NG LOG_STREAM(info, log_engine)
54 #define ERR_NG LOG_STREAM(err, log_engine)
55 
56 static lg::log_domain log_enginerefac("enginerefac");
57 #define LOG_RG LOG_STREAM(info, log_enginerefac)
58 
60  playsingle_controller& playcontroller, const end_level_data& end_level, const LEVEL_RESULT res)
61 {
62  // We need to write the carryover amount to the team that's why we need non const
63  std::vector<team>& teams = playcontroller.get_teams();
64 
65  // maybe this can be the case for scenario that only contain a story and end during the prestart event ?
66  if(teams.size() < 1) {
67  return;
68  }
69 
70  std::ostringstream report;
71  std::string title;
72 
73  bool obs = playcontroller.is_observer();
74 
75  if(obs) {
76  title = _("Scenario Report");
77  } else if(res == LEVEL_RESULT::VICTORY) {
78  title = _("Victory");
79  report << "<b>" << _("You have emerged victorious!") << "</b>";
80  } else {
81  title = _("Defeat");
82  report << _("You have been defeated!");
83  }
84 
85  const std::string& next_scenario = playcontroller.gamestate().get_game_data()->next_scenario();
86  const bool has_next_scenario = !next_scenario.empty() && next_scenario != "null";
87 
88  int persistent_teams = 0;
89  for(const team& t : teams) {
90  if(t.persistent()) {
91  ++persistent_teams;
92  }
93  }
94 
95  if(persistent_teams > 0 && ((has_next_scenario && end_level.proceed_to_next_level) || state_.classification().is_test())) {
96  const gamemap& map = playcontroller.get_map();
97  const tod_manager& tod = playcontroller.get_tod_manager();
98 
99  const int turns_left = std::max<int>(0, tod.number_of_turns() - tod.turn());
100  for(team& t : teams) {
101  if(!t.persistent() || t.lost()) {
102  continue;
103  }
104 
105  const int finishing_bonus_per_turn = map.villages().size() * t.village_gold() + t.base_income();
106  const int finishing_bonus = t.carryover_bonus() * finishing_bonus_per_turn * turns_left;
107 
108  t.set_carryover_gold(div100rounded((t.gold() + finishing_bonus) * t.carryover_percentage()));
109 
110  if(!t.is_local_human()) {
111  continue;
112  }
113 
114  if(persistent_teams > 1) {
115  report << "\n\n<b>" << t.side_name() << "</b>";
116  }
117 
118  report << "<small>\n" << _("Remaining gold: ") << utils::half_signed_value(t.gold()) << "</small>";
119 
120  if(t.carryover_bonus() != 0) {
121  if(turns_left > -1) {
122  report << "\n\n<b>" << _("Turns finished early: ") << turns_left << "</b>\n"
123  << "<small>" << _("Early finish bonus: ") << finishing_bonus_per_turn << _(" per turn") << "</small>\n"
124  << "<small>" << _("Total bonus: ") << finishing_bonus << "</small>\n";
125  }
126 
127  report << "<small>" << _("Total gold: ") << utils::half_signed_value(t.gold() + finishing_bonus) << "</small>";
128  }
129 
130  if(t.gold() > 0) {
131  report << "\n<small>" << _("Carryover percentage: ") << t.carryover_percentage() << "</small>";
132  }
133 
134  if(t.carryover_add()) {
135  report << "\n\n<big><b>" << _("Bonus gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
136  } else {
137  report << "\n\n<big><b>" << _("Retained gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
138  }
139 
140  std::string goldmsg;
141  utils::string_map symbols;
142 
143  symbols["gold"] = lexical_cast_default<std::string>(t.carryover_gold());
144 
145  // Note that both strings are the same in English, but some languages will
146  // want to translate them differently.
147  if(t.carryover_add()) {
148  if(t.carryover_gold() > 0) {
149  goldmsg = VNGETTEXT(
150  "You will start the next scenario with $gold on top of the defined minimum starting gold.",
151  "You will start the next scenario with $gold on top of the defined minimum starting gold.",
152  t.carryover_gold(), symbols
153  );
154 
155  } else {
156  goldmsg = VNGETTEXT(
157  "You will start the next scenario with the defined minimum starting gold.",
158  "You will start the next scenario with the defined minimum starting gold.",
159  t.carryover_gold(), symbols
160  );
161  }
162  } else {
163  goldmsg = VNGETTEXT(
164  "You will start the next scenario with $gold or its defined minimum starting gold, "
165  "whichever is higher.",
166  "You will start the next scenario with $gold or its defined minimum starting gold, "
167  "whichever is higher.",
168  t.carryover_gold(), symbols
169  );
170  }
171 
172  // xgettext:no-c-format
173  report << "\n" << goldmsg;
174  }
175  }
176 
177  if(end_level.transient.carryover_report) {
178  gui2::show_transient_message(title, report.str(), "", true);
179  }
180 }
181 
183 {
184  const config& starting_point = is_replay_
187 
188  playsingle_controller playcontroller(starting_point, state_, false);
189 
190  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks()) << "\n";
191  if(is_replay_) {
192  playcontroller.enable_replay(is_unit_test_);
193  }
194 
195  LEVEL_RESULT res = playcontroller.play_scenario(starting_point);
196  if(res == LEVEL_RESULT::QUIT) {
197  return LEVEL_RESULT::QUIT;
198  }
199 
200  if(!is_unit_test_) {
201  is_replay_ = false;
202  }
203 
204  if(is_replay_) {
205  return res;
206  }
207 
208  end_level = playcontroller.get_end_level_data();
209  show_carryover_message(playcontroller, end_level, res);
210 
211  if(!CVideo::get_singleton().faked()) {
212  playcontroller.maybe_linger();
213  }
214 
215  state_.set_snapshot(playcontroller.to_config());
216  return res;
217 }
218 
220 {
222  LEVEL_RESULT res = playcontroller.play_scenario(state_.get_starting_point());
223 
224  // Check if the player started as mp client and changed to host
225  if(res == LEVEL_RESULT::QUIT) {
226  return LEVEL_RESULT::QUIT;
227  }
228 
229  end_level = playcontroller.get_end_level_data();
230 
231  if(res != LEVEL_RESULT::OBSERVER_END) {
232  // We need to call this before linger because it prints the defeated/victory message.
233  //(we want to see that message before entering the linger mode)
234  show_carryover_message(playcontroller, end_level, res);
235  }
236 
237  playcontroller.maybe_linger();
238  playcontroller.update_savegame_snapshot();
239 
240  if(mp_info_) {
241  mp_info_->connected_players = playcontroller.all_players();
242  mp_info_->skip_replay = false;
244  }
245 
246  return res;
247 }
248 
250 {
251  if(is_replay_) {
253  } else {
255  }
256 
258 
259  while(state_.valid()) {
260  LEVEL_RESULT res = LEVEL_RESULT::VICTORY;
261  end_level_data end_level;
262 
263  try {
265  // In case this an mp scenario reloaded by sp this was not already done yet.
267 
269 
271 
272  // expand_mp_options must be called after expand_carryover because expand_carryover will to set previous
273  // variables if there are already variables in the [scenario]
275 
276 #if !defined(ALWAYS_USE_MP_CONTROLLER)
278  res = playsingle_scenario(end_level);
279  if(is_replay_) {
280  return res;
281  }
282  } else
283 #endif
284  {
285  res = playmp_scenario(end_level);
286  }
287  } catch(const leavegame_wesnothd_error&) {
288  LOG_NG << "The game was remotely ended\n";
289  return LEVEL_RESULT::QUIT;
290  } catch(const game::load_game_failed& e) {
291  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
292  return LEVEL_RESULT::QUIT;
293  } catch(const quit_game_exception&) {
294  LOG_NG << "The game was aborted\n";
295  return LEVEL_RESULT::QUIT;
296  } catch(const game::game_error& e) {
297  gui2::show_error_message(_("Error while playing the game: ") + e.message);
298  return LEVEL_RESULT::QUIT;
299  } catch(const incorrect_map_format_error& e) {
300  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
301  return LEVEL_RESULT::QUIT;
302  } catch(const mapgen_exception& e) {
303  gui2::show_error_message(_("Map generator error: ") + e.message);
304  } catch(const config::error& e) {
305  gui2::show_error_message(_("Error while reading the WML: ") + e.message);
306  return LEVEL_RESULT::QUIT;
307  } catch(const wml_exception& e) {
308  e.show();
309  return LEVEL_RESULT::QUIT;
310  }
311 
312  if(is_unit_test_) {
313  return res;
314  }
315 
316  if(res == LEVEL_RESULT::QUIT) {
317  return res;
318  }
319 
320  // proceed_to_next_level <=> 'any human side received victory'
321  // If 'any human side received victory' we do the Save-management options
322  // Otherwise we are done now
323  if(!end_level.proceed_to_next_level) {
324  return res;
325  }
326 
329  }
330 
331  if(preferences::save_replays() && end_level.replay_save) {
333  save.save_game_automatic(true);
334  }
335 
337 
338  // If there is no next scenario we're done now.
339  if(state_.get_scenario_id().empty()) {
340  // Don't show The End for multiplayer scenarios.
341  if(res == LEVEL_RESULT::VICTORY && !state_.classification().is_normal_mp_game()) {
344 
346  gui2::dialogs::outro::display(state_.classification());
347  }
348  }
349 
350  return res;
351  } else if(res == LEVEL_RESULT::OBSERVER_END && mp_info_ && !mp_info_->is_host) {
352  const int dlg_res = gui2::show_message(_("Game Over"),
353  _("This scenario has ended. Do you want to continue the campaign?"),
355 
356  if(dlg_res == gui2::retval::CANCEL) {
357  return res;
358  }
359  }
360 
361  if(mp_info_ && !mp_info_->is_host) {
362  // Opens join game dialog to get a new gamestate.
363  if(!mp::goto_mp_wait(res == LEVEL_RESULT::OBSERVER_END)) {
364  return LEVEL_RESULT::QUIT;
365  }
366 
367  // The host should send the complete savegame now that also contains the carryover sides start.
368  } else {
369  // clear previous game content information
370  // otherwise it keeps getting appended for each scenario resulting in incorrect data being sent to the server to be stored
371  state_.mp_settings().addons.clear();
372  // Retrieve next scenario data.
374 
375  if(state_.valid()) {
376  // note that although starting_pos is const it might be changed by gamestate.some_non_const_operation()
377  const config& starting_pos = state_.get_starting_point();
378 
379  const bool is_mp = state_.classification().is_normal_mp_game();
380  state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
381 
382  if(state_.mp_settings().saved_game == mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
383  state_.mp_settings().saved_game = mp_game_settings::SAVED_GAME_MODE::SCENARIO_START;
384  }
385 
386  state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
387 
388  ng::connect_engine connect_engine(state_, false, mp_info_);
389 
390  if(!connect_engine.can_start_game() || (game_config::debug && state_.classification().is_multiplayer())) {
391  // Opens staging dialog to allow users to make an adjustments for scenario.
392  if(!mp::goto_mp_staging(connect_engine)) {
393  return LEVEL_RESULT::QUIT;
394  }
395  } else {
396  // Start the next scenario immediately.
397  connect_engine.start_game();
398  }
399  }
400  }
401 
402  if(state_.valid()) {
403  // Update the label
405 
406  // If this isn't the last scenario, then save the game
407  if(end_level.prescenario_save) {
408  // For multiplayer, we want the save to contain the starting position.
409  // For campaigns however, this is the start-of-scenario save and the
410  // starting position needs to be empty, to force a reload of the scenario config.
412  save.save_game_automatic();
413  }
414  }
415  }
416 
417  if(!state_.get_scenario_id().empty()) {
418  utils::string_map symbols;
419  symbols["scenario"] = state_.get_scenario_id();
420 
421  std::string message = _("Unknown scenario: '$scenario|'");
422  message = utils::interpolate_variables_into_string(message, &symbols);
423 
424  gui2::show_error_message(message);
425  return LEVEL_RESULT::QUIT;
426  }
427 
431  }
432  }
433 
434  return LEVEL_RESULT::VICTORY;
435 }
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:153
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)
Function which will interpolate variables, starting with &#39;$&#39; in the string &#39;str&#39; with the equivalent ...
LEVEL_RESULT play_game()
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:268
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...
Error used when game loading fails.
Definition: game_errors.hpp:31
replay_recorder_base & get_replay()
Definition: saved_game.hpp:135
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, const bool restore_background)
Shows a transient message to the user.
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:476
bool valid() const
Definition: saved_game.cpp:553
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)
static CVideo & get_singleton()
Definition: video.hpp:49
#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:558
saved_game & state_
static std::string _(const char *str)
Definition: gettext.hpp:93
LEVEL_RESULT playmp_scenario(end_level_data &end_level)
std::string get_scenario_id() const
Definition: saved_game.cpp:648
void show() const
Shows the error in a dialog.
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:72
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
int div100rounded(int num)
Guarantees portable results for division by 100; round half up, to the nearest integer.
Definition: math.hpp:39
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:412
config to_config() const
Builds the snapshot config from members and their respective configs.
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:671
Encapsulates the map of the game.
Definition: map.hpp:171
std::vector< team > & get_teams()
LEVEL_RESULT playsingle_scenario(end_level_data &end_level)
config & get_starting_point()
Definition: saved_game.cpp:582
Shows a yes and no button.
Definition: message.hpp:80
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
LEVEL_RESULT play_scenario(const config &level)
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
void show_carryover_message(playsingle_controller &playcontroller, const end_level_data &end_level, LEVEL_RESULT res)
const config & get_replay_starting_point()
Definition: saved_game.cpp:587
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:302
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
bool delete_saves()
Definition: game.cpp:780
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:371
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:537
static void save(LexState *ls, int c)
Definition: llex.cpp:57
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:857
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:606
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:206
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:61
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:157
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:770