The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
playcampaign.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003-2005 by David White <[email protected]>
3  Copyright (C) 2005 - 2016 by Philippe Plantier <[email protected]>
4  Part of the Battle for Wesnoth Project http://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 
21 #include "global.hpp"
22 
23 #include "playcampaign.hpp"
24 
25 #include "carryover.hpp"
26 #include "game_config.hpp"
27 #include "game_errors.hpp"
28 #include "game_preferences.hpp"
31 #include "gui/dialogs/message.hpp"
33 #include "gui/widgets/window.hpp"
34 #include "persist_manager.hpp"
35 #include "playmp_controller.hpp"
36 #include "log.hpp"
37 #include "map/exception.hpp"
38 #include "mp_game_utils.hpp"
39 #include "multiplayer.hpp"
40 #include "connect_engine.hpp"
41 #include "dialogs.hpp"
42 #include "gettext.hpp"
43 #include "resources.hpp"
44 #include "savegame.hpp"
45 #include "saved_game.hpp"
46 #include "sound.hpp"
47 #include "terrain/type_data.hpp"
48 #include "wml_exception.hpp"
49 #include "formula/string_utils.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  std::ostringstream &report, team& t,
62  int finishing_bonus_per_turn, int turns_left, int finishing_bonus)
63 {
64  report << "<small>" << _("Remaining gold: ") << utils::half_signed_value(t.gold()) << "</small>";
65 
66  if(t.carryover_bonus() != 0) {
67  if (turns_left > -1) {
68  report << "\n\n<b>" << _("Turns finished early: ") << turns_left << "</b>\n"
69  << "<small>" << _("Early finish bonus: ") << finishing_bonus_per_turn << _(" per turn") << "</small>\n"
70  << "<small>" << _("Total bonus: ") << finishing_bonus << "</small>\n";
71  }
72  report << "<small>" << _("Total gold: ") << utils::half_signed_value(t.gold() + finishing_bonus) << "</small>";
73  }
74  if (t.gold() > 0) {
75  report << "\n<small>" << _("Carryover percentage: ") << t.carryover_percentage() << "</small>";
76  }
77  if(t.carryover_add()) {
78  report << "\n\n<big><b>" << _("Bonus gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
79  } else {
80  report << "\n\n<big><b>" << _("Retained gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
81  }
82 
83  std::string goldmsg;
84  utils::string_map symbols;
85 
86  symbols["gold"] = lexical_cast_default<std::string>(t.carryover_gold());
87 
88  // Note that both strings are the same in English, but some languages will
89  // want to translate them differently.
90  if(t.carryover_add()) {
91  if(t.carryover_gold() > 0) {
92  goldmsg = vngettext(
93  "You will start the next scenario with $gold "
94  "on top of the defined minimum starting gold.",
95  "You will start the next scenario with $gold "
96  "on top of the defined minimum starting gold.",
97  t.carryover_gold(), symbols);
98 
99  } else {
100  goldmsg = vngettext(
101  "You will start the next scenario with "
102  "the defined minimum starting gold.",
103  "You will start the next scenario with "
104  "the defined minimum starting gold.",
105  t.carryover_gold(), symbols);
106  }
107  } else {
108  goldmsg = vngettext(
109  "You will start the next scenario with $gold "
110  "or its defined minimum starting gold, "
111  "whichever is higher.",
112  "You will start the next scenario with $gold "
113  "or its defined minimum starting gold, "
114  "whichever is higher.",
115  t.carryover_gold(), symbols);
116  }
117 
118  // xgettext:no-c-format
119  report << "\n" << goldmsg;
120 }
121 
122 void campaign_controller::show_carryover_message(playsingle_controller& playcontroller, const end_level_data& end_level, const LEVEL_RESULT res)
123 {
124  assert(resources::teams);
125 
126  bool has_next_scenario = !resources::gamedata->next_scenario().empty() &&
127  resources::gamedata->next_scenario() != "null";
128  //maybe this can be the case for scenario that only contain a story and end during the prestart event ?
129  if(resources::teams->size() < 1){
130  return;
131  }
132 
133  std::ostringstream report;
134  std::string title;
135 
136  bool obs = playcontroller.is_observer();
137 
138  if (obs) {
139  title = _("Scenario Report");
140  } else if (res == LEVEL_RESULT::VICTORY) {
141  title = _("Victory");
142  report << "<b>" << _("You have emerged victorious!") << "</b>\n\n";
143  } else {
144  title = _("Defeat");
145  report << _("You have been defeated!") << "\n";
146  }
147 
148  //We need to write the carryover amount to the team thats why we need non const
149  std::vector<team>& teams = *resources::teams;
150  int persistent_teams = 0;
151  for (const team &t : teams) {
152  if (t.persistent()){
153  ++persistent_teams;
154  }
155  }
156 
157  if (persistent_teams > 0 && ((has_next_scenario && end_level.proceed_to_next_level)||
158  state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::TEST))
159  {
160  gamemap map = playcontroller.get_map_const();
161  tod_manager tod = playcontroller.get_tod_manager_const();
162  int turns_left = std::max<int>(0, tod.number_of_turns() - tod.turn());
163  for (team &t : teams)
164  {
165  if (!t.persistent() || t.lost())
166  {
167  continue;
168  }
169  int finishing_bonus_per_turn = map.villages().size() * t.village_gold() + t.base_income();
170  int finishing_bonus = t.carryover_bonus() * finishing_bonus_per_turn * turns_left;
171  t.set_carryover_gold(div100rounded((t.gold() + finishing_bonus) * t.carryover_percentage()));
172  if(!t.is_local_human())
173  {
174  continue;
175  }
176  if (persistent_teams > 1) {
177  report << "\n<b>" << t.side_name() << "</b>\n";
178  }
179 
180  report_victory(report, t, finishing_bonus_per_turn, turns_left, finishing_bonus);
181  }
182  }
183 
184  if (end_level.transient.carryover_report) {
185  gui2::show_transient_message(video_, title, report.str(), "", true);
186  }
187 }
188 
190 {
192  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks()) << "\n";
193  if(is_replay_) {
194  playcontroller.enable_replay(is_unit_test_);
195  }
196  LEVEL_RESULT res = playcontroller.play_scenario(is_replay_ ? state_.get_replay_starting_pos() : state_.get_starting_pos());
197 
198  if (res == LEVEL_RESULT::QUIT)
199  {
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(!video_.faked())
214  {
215  playcontroller.maybe_linger();
216  }
217  state_.set_snapshot(playcontroller.to_config());
218  return res;
219 }
220 
221 
223 {
224 
227  LEVEL_RESULT res = playcontroller.play_scenario(state_.get_starting_pos());
228 
229  //Check if the player started as mp client and changed to host
230 
231  if (res == LEVEL_RESULT::QUIT)
232  {
233  return LEVEL_RESULT::QUIT;
234  }
235 
236  end_level = playcontroller.get_end_level_data_const();
237 
238  if(res != LEVEL_RESULT::OBSERVER_END)
239  {
240  //We need to call this before linger because it prints the defeated/victory message.
241  //(we want to see that message before entering the linger mode)
242  show_carryover_message(playcontroller, end_level, res);
243  }
244  playcontroller.maybe_linger();
245  playcontroller.update_savegame_snapshot();
246  if(mp_info_) {
247  mp_info_->connected_players = playcontroller.all_players();
248  }
249  return res;
250 }
251 
253 {
254  if(is_replay_) {
256  }
257  else {
259  }
260 
262 
263  game_classification::CAMPAIGN_TYPE game_type = state_.classification().campaign_type;
264 
265  while(state_.valid())
266  {
267  LEVEL_RESULT res = LEVEL_RESULT::VICTORY;
268  end_level_data end_level;
269  try {
270 
272  //In case this an mp scenario reloaded by sp this was not already done yet.
274 
276 
278  //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]
280 
281 #if !defined(ALWAYS_USE_MP_CONTROLLER)
282  if (game_type != game_classification::CAMPAIGN_TYPE::MULTIPLAYER || is_replay_) {
283  res = playsingle_scenario(end_level);
284  if(is_replay_) {
285  return res;
286  }
287  } else
288 #endif
289  {
290  res = playmp_scenario(end_level);
291  }
292  } catch(game::load_game_failed& e) {
293  gui2::show_error_message(video_, _("The game could not be loaded: ") + e.message);
294  return LEVEL_RESULT::QUIT;
295  } catch(quit_game_exception&) {
296  LOG_NG << "The game was aborted\n";
297  return LEVEL_RESULT::QUIT;
298  } catch(game::game_error& e) {
299  gui2::show_error_message(video_, _("Error while playing the game: ") + e.message);
300  return LEVEL_RESULT::QUIT;
301  } catch(incorrect_map_format_error& e) {
302  gui2::show_error_message(video_, std::string(_("The game map could not be loaded: ")) + e.message);
303  return LEVEL_RESULT::QUIT;
304  } catch (mapgen_exception& e) {
305  gui2::show_error_message(video_, std::string(_("Map generator error: ") + e.message));
306  } catch(config::error& e) {
307  gui2::show_error_message(video_, _("Error while reading the WML: ") + e.message);
308  return LEVEL_RESULT::QUIT;
309  } catch(twml_exception& e) {
310  e.show(video_);
311  return LEVEL_RESULT::QUIT;
312  }
313 
314  if (is_unit_test_) {
315  return res;
316  }
317  if(res == LEVEL_RESULT::QUIT) {
318  return res;
319  }
320  // proceed_to_next_level <=> 'any human side recieved victory'
321  // If 'any human side recieved 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  if (preferences::save_replays() && end_level.replay_save) {
332  save.save_game_automatic(video_, true);
333  }
334 
336 
337  //If there is no next scenario we're done now.
338  if(state_.get_scenario_id().empty())
339  {
340  return res;
341  }
342  else if(res == LEVEL_RESULT::OBSERVER_END && mp_info_ && !mp_info_->is_host)
343  {
344  const int dlg_res = gui2::show_message(video_, _("Game Over"),
345  _("This scenario has ended. Do you want to continue the campaign?"),
347 
348  if(dlg_res == gui2::twindow::CANCEL) {
349  return res;
350  }
351  }
352 
353  if (mp_info_ && !mp_info_->is_host) {
354  // Opens mp::connect dialog to get a new gamestate.
356  game_config_, &mp_info_->wesnothd_connection, res == LEVEL_RESULT::OBSERVER_END);
357  if (wait_res == mp::ui::QUIT) {
358  return LEVEL_RESULT::QUIT;
359  }
360 
361  //The host should send the complete savegame now that also contains the carryvoer sides start.
362  } else {
363  // Retrieve next scenario data.
365 
366  if (state_.valid()) {
367  //note that although starting_pos is const it might be changed by gamestate.some_non_const_operation() .
368  const config& starting_pos = state_.get_starting_pos();
369 
370  const bool is_mp = state_.classification().is_normal_mp_game();
371  state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
372  state_.mp_settings().saved_game = false;
373  state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
374 
375  ng::connect_engine_ptr connect_engine(new ng::connect_engine(state_, false, mp_info_));
376 
377  if (!connect_engine->can_start_game() || (game_config::debug && game_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER)) {
378  // Opens mp::connect dialog to allow users to make an adjustments for scenario.
380  *connect_engine, game_config_, mp_info_ ? &mp_info_->wesnothd_connection : nullptr, state_.mp_settings().name);
381  if (connect_res == mp::ui::QUIT) {
382  return LEVEL_RESULT::QUIT;
383  }
384  } else {
385  // Start the next scenario immediately.
386  connect_engine->start_game();
387  }
388  }
389  }
390 
391  if(state_.valid()) {
392  // Update the label
394 
395  // If this isn't the last scenario, then save the game
396  if(end_level.prescenario_save) {
397 
398  // For multiplayer, we want the save
399  // to contain the starting position.
400  // For campaigns however, this is the
401  // start-of-scenario save and the
402  // starting position needs to be empty,
403  // to force a reload of the scenario config.
404 
406 
408  }
409 
410  }
411  }
412 
413  if (!state_.get_scenario_id().empty()) {
414  std::string message = _("Unknown scenario: '$scenario|'");
415  utils::string_map symbols;
416  symbols["scenario"] = state_.get_scenario_id();
417  message = utils::interpolate_variables_into_string(message, &symbols);
419  return LEVEL_RESULT::QUIT;
420  }
421 
425  }
426  }
427  return LEVEL_RESULT::VICTORY;
428 }
429 
void empty_playlist()
Definition: sound.cpp:480
void show_error_message(CVideo &video, const std::string &message, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:198
Class for start-of-scenario saves.
Definition: savegame.hpp:243
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 ...
LEVEL_RESULT play_game()
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:204
const config & game_config_
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't contain any problems that...
Error used when game loading fails.
Definition: game_errors.hpp:30
mp::ui::result goto_mp_connect(CVideo &video, ng::connect_engine &engine, const config &game_config, twesnothd_connection *wesnothd_connection, const std::string &game_name)
Opens mp::connect screen and sets game state according to the changes made.
int div100rounded(int num)
Guarantees portable results for division by 100; round towards 0.
Definition: util.hpp:52
std::set< std::string > connected_players
players and observers
replay_recorder_base & get_replay()
Definition: saved_game.hpp:116
static int report(lua_State *L, int status)
Definition: lua.cpp:134
double carryover_bonus() const
Definition: team.hpp:356
This file contains the window object, this object is a top level container which has the event manage...
void show(CVideo &video)
Shows the error in a dialog.
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:387
static lg::log_domain log_engine("engine")
bool prescenario_save
Should a prescenario be created the next game?
void show_transient_message(CVideo &video, 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.
bool replay_save
Should a replay save be made?
config & set_snapshot(config snapshot)
Definition: saved_game.cpp:451
saved_game & state_
LEVEL_RESULT playmp_scenario(end_level_data &end_level)
GLdouble GLdouble t
Definition: glew.h:1366
int carryover_gold() const
Definition: team.hpp:358
game_data * gamedata
Definition: resources.cpp:22
const gamemap & get_map_const() const
static std::vector< team > *& teams
Definition: team.cpp:50
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:50
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
std::map< std::string, t_string > string_map
std::vector< team > * teams
Definition: resources.cpp:29
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:356
int carryover_percentage() const
Definition: team.hpp:352
Dialog is closed with the cancel button.
Definition: window.hpp:126
void show_message(CVideo &video, const std::string &title, const std::string &message, const std::string &button_caption, const bool auto_close, const bool message_use_markup)
Shows a message to the user.
Definition: message.cpp:143
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:558
Encapsulates the map of the game.
Definition: map.hpp:37
LEVEL_RESULT playsingle_scenario(end_level_data &end_level)
twesnothd_connection & wesnothd_connection
bool is_observer() const
bool carryover_report
Should a summary of the scenario outcome be displayed?
config & get_starting_pos()
Definition: saved_game.cpp:472
Error used for any general game error, e.g.
Definition: game_errors.hpp:46
LEVEL_RESULT play_scenario(const config &level)
mp::ui::result goto_mp_wait(CVideo &video, saved_game &state, const config &game_config, twesnothd_connection *wesnothd_connection, bool observe)
Opens mp::wait screen and sets game state according to the changes made.
GLuint res
Definition: glew.h:9258
transient_end_level transient
void show_carryover_message(playsingle_controller &playcontroller, const end_level_data &end_level, LEVEL_RESULT res)
const bool is_unit_test_
const tdata_cache & tdata_
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario.
Definition: savegame.cpp:71
const std::string & next_scenario() const
Definition: game_data.hpp:89
int turn() const
std::string get_scenario_id()
Definition: saved_game.cpp:533
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
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:321
Additional information on the game outcome which can be provided by WML.
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
Definition: saved_game.cpp:428
const tod_manager & get_tod_manager_const() const
static void save(LexState *ls, int c)
Definition: llex.cpp:51
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:206
-file mapgen.hpp
compression::format save_compression_format()
GLsizeiptr size
Definition: glew.h:1649
int gold() const
Definition: team.hpp:194
#define LOG_NG
game_classification & classification()
Definition: saved_game.hpp:54
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:497
std::string message
Definition: exceptions.hpp:29
GLsizei GLenum GLuint GLuint GLsizei char * message
Definition: glew.h:2499
bool carryover_add() const
Definition: team.hpp:354
std::string vngettext(const char *sing, const char *plur, int n, const utils::string_map &symbols)
#define e
bool valid()
Definition: saved_game.cpp:446
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
boost::scoped_ptr< connect_engine > connect_engine_ptr
Helper class, don't construct this directly.
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:58
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:163
GLsizei const GLcharARB ** string
Definition: glew.h:4503
bool save_game_automatic(CVideo &video, 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:350
int turns_left
Definition: pathfind.cpp:163
Shows a yes and no button.
Definition: message.hpp:75
int number_of_turns() const
const config & get_replay_starting_pos()
Definition: saved_game.cpp:478