The Battle for Wesnoth  1.15.2+dev
playcampaign.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003-2005 by David White <dave@whitevine.net>
3  Copyright (C) 2005 - 2018 by Philippe Plantier <ayin@anathas.org>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Controls setup, play, (auto)save and replay of campaigns.
19  */
20 
22 
23 #include "carryover.hpp"
24 #include "game_config.hpp"
25 #include "game_errors.hpp"
26 #include "preferences/game.hpp"
29 #include "gui/dialogs/message.hpp"
31 #include "gui/widgets/retval.hpp"
32 #include "persist_manager.hpp"
33 #include "playmp_controller.hpp"
34 #include "log.hpp"
35 #include "map/map.hpp"
36 #include "map/exception.hpp"
40 #include "gettext.hpp"
41 #include "resources.hpp"
42 #include "savegame.hpp"
43 #include "saved_game.hpp"
44 #include "sound.hpp"
45 #include "terrain/type_data.hpp"
46 #include "wml_exception.hpp"
47 #include "formula/string_utils.hpp"
48 #include "wesnothd_connection.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  std::ostringstream &report, team& t,
61  int finishing_bonus_per_turn, int turns_left, int finishing_bonus)
62 {
63  report << "<small>\n" << _("Remaining gold: ") << utils::half_signed_value(t.gold()) << "</small>";
64 
65  if(t.carryover_bonus() != 0) {
66  if (turns_left > -1) {
67  report << "\n\n<b>" << _("Turns finished early: ") << turns_left << "</b>\n"
68  << "<small>" << _("Early finish bonus: ") << finishing_bonus_per_turn << _(" per turn") << "</small>\n"
69  << "<small>" << _("Total bonus: ") << finishing_bonus << "</small>\n";
70  }
71  report << "<small>" << _("Total gold: ") << utils::half_signed_value(t.gold() + finishing_bonus) << "</small>";
72  }
73  if (t.gold() > 0) {
74  report << "\n<small>" << _("Carryover percentage: ") << t.carryover_percentage() << "</small>";
75  }
76  if(t.carryover_add()) {
77  report << "\n\n<big><b>" << _("Bonus gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
78  } else {
79  report << "\n\n<big><b>" << _("Retained gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
80  }
81 
82  std::string goldmsg;
83  utils::string_map symbols;
84 
85  symbols["gold"] = lexical_cast_default<std::string>(t.carryover_gold());
86 
87  // Note that both strings are the same in English, but some languages will
88  // want to translate them differently.
89  if(t.carryover_add()) {
90  if(t.carryover_gold() > 0) {
91  goldmsg = VNGETTEXT(
92  "You will start the next scenario with $gold "
93  "on top of the defined minimum starting gold.",
94  "You will start the next scenario with $gold "
95  "on top of the defined minimum starting gold.",
96  t.carryover_gold(), symbols);
97 
98  } else {
99  goldmsg = VNGETTEXT(
100  "You will start the next scenario with "
101  "the defined minimum starting gold.",
102  "You will start the next scenario with "
103  "the defined minimum starting gold.",
104  t.carryover_gold(), symbols);
105  }
106  } else {
107  goldmsg = VNGETTEXT(
108  "You will start the next scenario with $gold "
109  "or its defined minimum starting gold, "
110  "whichever is higher.",
111  "You will start the next scenario with $gold "
112  "or its defined minimum starting gold, "
113  "whichever is higher.",
114  t.carryover_gold(), symbols);
115  }
116 
117  // xgettext:no-c-format
118  report << "\n" << goldmsg;
119 }
120 
121 void campaign_controller::show_carryover_message(playsingle_controller& playcontroller, const end_level_data& end_level, const LEVEL_RESULT res)
122 {
123  assert(resources::gameboard);
124 
125  bool has_next_scenario = !resources::gamedata->next_scenario().empty() &&
126  resources::gamedata->next_scenario() != "null";
127  //maybe this can be the case for scenario that only contain a story and end during the prestart event ?
128  if(resources::gameboard->teams().size() < 1){
129  return;
130  }
131 
132  std::ostringstream report;
133  std::string title;
134 
135  bool obs = playcontroller.is_observer();
136 
137  if (obs) {
138  title = _("Scenario Report");
139  } else if (res == LEVEL_RESULT::VICTORY) {
140  title = _("Victory");
141  report << "<b>" << _("You have emerged victorious!") << "</b>";
142  } else {
143  title = _("Defeat");
144  report << _("You have been defeated!");
145  }
146 
147  //We need to write the carryover amount to the team that's why we need non const
148  std::vector<team>& teams = resources::gameboard->teams();
149  int persistent_teams = 0;
150  for (const team &t : teams) {
151  if (t.persistent()){
152  ++persistent_teams;
153  }
154  }
155 
156  if (persistent_teams > 0 && ((has_next_scenario && end_level.proceed_to_next_level)||
157  state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::TEST))
158  {
159  const gamemap& map = playcontroller.get_map_const();
160  const tod_manager& tod = playcontroller.get_tod_manager_const();
161  int turns_left = std::max<int>(0, tod.number_of_turns() - tod.turn());
162  for (team &t : teams)
163  {
164  if (!t.persistent() || t.lost())
165  {
166  continue;
167  }
168  int finishing_bonus_per_turn = map.villages().size() * t.village_gold() + t.base_income();
169  int finishing_bonus = t.carryover_bonus() * finishing_bonus_per_turn * turns_left;
170  t.set_carryover_gold(div100rounded((t.gold() + finishing_bonus) * t.carryover_percentage()));
171  if(!t.is_local_human())
172  {
173  continue;
174  }
175  if (persistent_teams > 1) {
176  report << "\n\n<b>" << t.side_name() << "</b>";
177  }
178 
179  report_victory(report, t, finishing_bonus_per_turn, turns_left, finishing_bonus);
180  }
181  }
182 
183  if (end_level.transient.carryover_report) {
184  gui2::show_transient_message(title, report.str(), "", true);
185  }
186 }
187 
189 {
191  state_, tdata_, false);
192 
193  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks()) << "\n";
194  if(is_replay_) {
195  playcontroller.enable_replay(is_unit_test_);
196  }
197  LEVEL_RESULT res = playcontroller.play_scenario(is_replay_ ? state_.get_replay_starting_point() : state_.get_starting_point());
198 
199  if (res == LEVEL_RESULT::QUIT) {
200  return LEVEL_RESULT::QUIT;
201  }
202  if(!is_unit_test_)
203  {
204  is_replay_ = false;
205  }
206  if(is_replay_)
207  {
208  return res;
209  }
210  end_level = playcontroller.get_end_level_data_const();
211 
212  show_carryover_message(playcontroller, end_level, res);
213  if(!CVideo::get_singleton().faked())
214  {
215  playcontroller.maybe_linger();
216  }
217  state_.set_snapshot(playcontroller.to_config());
218  return res;
219 }
220 
221 
223 {
224 
226  LEVEL_RESULT res = playcontroller.play_scenario(state_.get_starting_point());
227 
228  //Check if the player started as mp client and changed to host
229 
230  if (res == LEVEL_RESULT::QUIT)
231  {
232  return LEVEL_RESULT::QUIT;
233  }
234 
235  end_level = playcontroller.get_end_level_data_const();
236 
237  if(res != LEVEL_RESULT::OBSERVER_END)
238  {
239  //We need to call this before linger because it prints the defeated/victory message.
240  //(we want to see that message before entering the linger mode)
241  show_carryover_message(playcontroller, end_level, res);
242  }
243  playcontroller.maybe_linger();
244  playcontroller.update_savegame_snapshot();
245  if(mp_info_) {
246  mp_info_->connected_players = playcontroller.all_players();
247  mp_info_->skip_replay = false;
249  }
250  return res;
251 }
252 
254 {
255  if(is_replay_) {
257  }
258  else {
260  }
261 
263 
264  game_classification::CAMPAIGN_TYPE game_type = state_.classification().campaign_type;
265 
266  while(state_.valid())
267  {
268  LEVEL_RESULT res = LEVEL_RESULT::VICTORY;
269  end_level_data end_level;
270  try {
271 
273  //In case this an mp scenario reloaded by sp this was not already done yet.
275 
277 
279  //expand_mp_options must be called after expand_carryover because expand_carryover will to set previous variables if there are already variables in the [scenario]
281 
282 #if !defined(ALWAYS_USE_MP_CONTROLLER)
283  if (game_type != game_classification::CAMPAIGN_TYPE::MULTIPLAYER || is_replay_) {
284  res = playsingle_scenario(end_level);
285  if(is_replay_) {
286  return res;
287  }
288  } else
289 #endif
290  {
291  res = playmp_scenario(end_level);
292  }
293  } catch(const leavegame_wesnothd_error&) {
294  LOG_NG << "The game was remotely ended\n";
295  return LEVEL_RESULT::QUIT;
296  } catch(const game::load_game_failed& e) {
297  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
298  return LEVEL_RESULT::QUIT;
299  } catch(const quit_game_exception&) {
300  LOG_NG << "The game was aborted\n";
301  return LEVEL_RESULT::QUIT;
302  } catch(const game::game_error& e) {
303  gui2::show_error_message(_("Error while playing the game: ") + e.message);
304  return LEVEL_RESULT::QUIT;
305  } catch(const incorrect_map_format_error& e) {
306  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
307  return LEVEL_RESULT::QUIT;
308  } catch(const mapgen_exception& e) {
309  gui2::show_error_message(_("Map generator error: ") + e.message);
310  } catch(const config::error& e) {
311  gui2::show_error_message(_("Error while reading the WML: ") + e.message);
312  return LEVEL_RESULT::QUIT;
313  } catch(const wml_exception& e) {
314  e.show();
315  return LEVEL_RESULT::QUIT;
316  }
317 
318  if (is_unit_test_) {
319  return res;
320  }
321  if(res == LEVEL_RESULT::QUIT) {
322  return res;
323  }
324  // proceed_to_next_level <=> 'any human side received victory'
325  // If 'any human side received victory' we do the Save-management options
326  // Otherwise we are done now
327  if(!end_level.proceed_to_next_level) {
328  return res;
329  }
330 
333  }
334  if (preferences::save_replays() && end_level.replay_save) {
336  save.save_game_automatic(true);
337  }
338 
340 
341  //If there is no next scenario we're done now.
342  if(state_.get_scenario_id().empty())
343  {
344  return res;
345  }
346  else if(res == LEVEL_RESULT::OBSERVER_END && mp_info_ && !mp_info_->is_host)
347  {
348  const int dlg_res = gui2::show_message(_("Game Over"),
349  _("This scenario has ended. Do you want to continue the campaign?"),
351 
352  if(dlg_res == gui2::retval::CANCEL) {
353  return res;
354  }
355  }
356 
357  if (mp_info_ && !mp_info_->is_host) {
358  // Opens join game dialog to get a new gamestate.
359  if(!mp::goto_mp_wait(state_, &mp_info_->connection, res == LEVEL_RESULT::OBSERVER_END)) {
360  return LEVEL_RESULT::QUIT;
361  }
362 
363  //The host should send the complete savegame now that also contains the carryvoer sides start.
364  } else {
365  // Retrieve next scenario data.
367 
368  if (state_.valid()) {
369  //note that although starting_pos is const it might be changed by gamestate.some_non_const_operation() .
370  const config& starting_pos = state_.get_starting_point();
371 
372  const bool is_mp = state_.classification().is_normal_mp_game();
373  state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
374  if(state_.mp_settings().saved_game == mp_game_settings::SAVED_GAME_MODE::MIDGAME) {
375  state_.mp_settings().saved_game = mp_game_settings::SAVED_GAME_MODE::SCENARIO_START;
376  }
377 
378  state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
379 
380  ng::connect_engine_ptr connect_engine(new ng::connect_engine(state_, false, mp_info_));
381 
382  if (!connect_engine->can_start_game() || (game_config::debug && game_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER)) {
383  // Opens staging dialog to allow users to make an adjustments for scenario.
384  if(!mp::goto_mp_connect(*connect_engine, mp_info_ ? &mp_info_->connection : nullptr)) {
385  return LEVEL_RESULT::QUIT;
386  }
387  } else {
388  // Start the next scenario immediately.
389  connect_engine->start_game();
390  }
391  }
392  }
393 
394  if(state_.valid()) {
395  // Update the label
397 
398  // If this isn't the last scenario, then save the game
399  if(end_level.prescenario_save) {
400 
401  // For multiplayer, we want the save
402  // to contain the starting position.
403  // For campaigns however, this is the
404  // start-of-scenario save and the
405  // starting position needs to be empty,
406  // to force a reload of the scenario config.
407 
409 
410  save.save_game_automatic();
411  }
412 
413  }
414  }
415 
416  if (!state_.get_scenario_id().empty()) {
417  std::string message = _("Unknown scenario: '$scenario|'");
418  utils::string_map symbols;
419  symbols["scenario"] = state_.get_scenario_id();
420  message = utils::interpolate_variables_into_string(message, &symbols);
421  gui2::show_error_message(message);
422  return LEVEL_RESULT::QUIT;
423  }
424 
428  }
429  }
430  return LEVEL_RESULT::VICTORY;
431 }
void empty_playlist()
Definition: sound.cpp:580
Exception used to escape form the ai or ui code to playsingle_controller::play_side.
Dialog was closed with the CANCEL button.
Definition: retval.hpp:37
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:152
Class for start-of-scenario saves.
Definition: savegame.hpp:314
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()
bool skip_replay_blindfolded
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:92
std::map< std::string, t_string > string_map
mp_campaign_info * mp_info_
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:262
static void report_victory(std::ostringstream &report, team &t, int finishing_bonus_per_turn, int turns_left, int finishing_bonus)
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:30
std::set< std::string > connected_players
players and observers
replay_recorder_base & get_replay()
Definition: saved_game.hpp:121
bool carryover_add() const
Definition: team.hpp:357
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:435
bool valid() const
Definition: saved_game.cpp:515
static lg::log_domain log_engine("engine")
bool prescenario_save
Should a prescenario be created the next game?
static CVideo & get_singleton()
Definition: video.hpp:43
#define VNGETTEXT(msgid, msgid_plural, count,...)
bool replay_save
Should a replay save be made?
config & set_snapshot(config snapshot)
Definition: saved_game.cpp:520
saved_game & state_
LEVEL_RESULT playmp_scenario(end_level_data &end_level)
double carryover_bonus() const
Definition: team.hpp:359
game_data * gamedata
Definition: resources.cpp:22
const tod_manager & get_tod_manager_const() const
int gold() const
Definition: team.hpp:189
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:44
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:37
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
const ter_data_cache & tdata_
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:404
int carryover_percentage() const
Definition: team.hpp:355
game_board * gameboard
Definition: resources.cpp:20
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:631
Encapsulates the map of the game.
Definition: map.hpp:36
LEVEL_RESULT playsingle_scenario(end_level_data &end_level)
config & get_starting_point()
Definition: saved_game.cpp:544
Shows a yes and no button.
Definition: message.hpp:79
bool carryover_report
Should a summary of the scenario outcome be displayed?
Error used for any general game error, e.g.
Definition: game_errors.hpp:46
LEVEL_RESULT play_scenario(const config &level)
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:549
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:68
std::string get_scenario_id()
Definition: saved_game.cpp:610
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
bool delete_saves()
Definition: game.cpp:808
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:364
Additional information on the game outcome which can be provided by WML.
const bool & debug
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
Definition: saved_game.cpp:499
static void save(LexState *ls, int c)
Definition: llex.cpp:57
const std::string & next_scenario() const
Definition: game_data.hpp:93
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:275
-file mapgen.hpp
compression::format save_compression_format()
Definition: game.cpp:885
#define LOG_NG
double t
Definition: astarsearch.cpp:64
wesnothd_connection & connection
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:192
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:568
const gamemap & get_map_const() const
std::string message
Definition: exceptions.hpp:31
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:205
int carryover_gold() const
Definition: team.hpp:361
int turn() const
#define e
const std::unique_ptr< connect_engine > connect_engine_ptr
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
int turns_left
Definition: pathfind.cpp:161
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:355
bool goto_mp_wait(saved_game &state, wesnothd_connection *connection, bool observe)
Opens mp::wait screen and sets game state according to the changes made.
bool goto_mp_connect(ng::connect_engine &engine, wesnothd_connection *connection)
Opens mp::connect screen and sets game state according to the changes made.
bool save_replays()
Definition: game.cpp:798