The Battle for Wesnoth  1.13.10+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 <dave@whitevine.net>
3  Copyright (C) 2005 - 2017 by Philippe Plantier <ayin@anathas.org>
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 
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/window.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 
49 #define LOG_G LOG_STREAM(info, lg::general)
50 
51 static lg::log_domain log_engine("engine");
52 #define LOG_NG LOG_STREAM(info, log_engine)
53 #define ERR_NG LOG_STREAM(err, log_engine)
54 
55 static lg::log_domain log_enginerefac("enginerefac");
56 #define LOG_RG LOG_STREAM(info, log_enginerefac)
57 
59  std::ostringstream &report, team& t,
60  int finishing_bonus_per_turn, int turns_left, int finishing_bonus)
61 {
62  report << "<small>\n" << _("Remaining gold: ") << utils::half_signed_value(t.gold()) << "</small>";
63 
64  if(t.carryover_bonus() != 0) {
65  if (turns_left > -1) {
66  report << "\n\n<b>" << _("Turns finished early: ") << turns_left << "</b>\n"
67  << "<small>" << _("Early finish bonus: ") << finishing_bonus_per_turn << _(" per turn") << "</small>\n"
68  << "<small>" << _("Total bonus: ") << finishing_bonus << "</small>\n";
69  }
70  report << "<small>" << _("Total gold: ") << utils::half_signed_value(t.gold() + finishing_bonus) << "</small>";
71  }
72  if (t.gold() > 0) {
73  report << "\n<small>" << _("Carryover percentage: ") << t.carryover_percentage() << "</small>";
74  }
75  if(t.carryover_add()) {
76  report << "\n\n<big><b>" << _("Bonus gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
77  } else {
78  report << "\n\n<big><b>" << _("Retained gold: ") << utils::half_signed_value(t.carryover_gold()) << "</b></big>";
79  }
80 
81  std::string goldmsg;
82  utils::string_map symbols;
83 
84  symbols["gold"] = lexical_cast_default<std::string>(t.carryover_gold());
85 
86  // Note that both strings are the same in English, but some languages will
87  // want to translate them differently.
88  if(t.carryover_add()) {
89  if(t.carryover_gold() > 0) {
90  goldmsg = VNGETTEXT(
91  "You will start the next scenario with $gold "
92  "on top of the defined minimum starting gold.",
93  "You will start the next scenario with $gold "
94  "on top of the defined minimum starting gold.",
95  t.carryover_gold(), symbols);
96 
97  } else {
98  goldmsg = VNGETTEXT(
99  "You will start the next scenario with "
100  "the defined minimum starting gold.",
101  "You will start the next scenario with "
102  "the defined minimum starting gold.",
103  t.carryover_gold(), symbols);
104  }
105  } else {
106  goldmsg = VNGETTEXT(
107  "You will start the next scenario with $gold "
108  "or its defined minimum starting gold, "
109  "whichever is higher.",
110  "You will start the next scenario with $gold "
111  "or its defined minimum starting gold, "
112  "whichever is higher.",
113  t.carryover_gold(), symbols);
114  }
115 
116  // xgettext:no-c-format
117  report << "\n" << goldmsg;
118 }
119 
120 void campaign_controller::show_carryover_message(playsingle_controller& playcontroller, const end_level_data& end_level, const LEVEL_RESULT res)
121 {
122  assert(resources::gameboard);
123 
124  bool has_next_scenario = !resources::gamedata->next_scenario().empty() &&
125  resources::gamedata->next_scenario() != "null";
126  //maybe this can be the case for scenario that only contain a story and end during the prestart event ?
127  if(resources::gameboard->teams().size() < 1){
128  return;
129  }
130 
131  std::ostringstream report;
132  std::string title;
133 
134  bool obs = playcontroller.is_observer();
135 
136  if (obs) {
137  title = _("Scenario Report");
138  } else if (res == LEVEL_RESULT::VICTORY) {
139  title = _("Victory");
140  report << "<b>" << _("You have emerged victorious!") << "</b>";
141  } else {
142  title = _("Defeat");
143  report << _("You have been defeated!");
144  }
145 
146  //We need to write the carryover amount to the team thats why we need non const
147  std::vector<team>& teams = resources::gameboard->teams();
148  int persistent_teams = 0;
149  for (const team &t : teams) {
150  if (t.persistent()){
151  ++persistent_teams;
152  }
153  }
154 
155  if (persistent_teams > 0 && ((has_next_scenario && end_level.proceed_to_next_level)||
156  state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::TEST))
157  {
158  const gamemap& map = playcontroller.get_map_const();
159  const tod_manager& tod = playcontroller.get_tod_manager_const();
160  int turns_left = std::max<int>(0, tod.number_of_turns() - tod.turn());
161  for (team &t : teams)
162  {
163  if (!t.persistent() || t.lost())
164  {
165  continue;
166  }
167  int finishing_bonus_per_turn = map.villages().size() * t.village_gold() + t.base_income();
168  int finishing_bonus = t.carryover_bonus() * finishing_bonus_per_turn * turns_left;
169  t.set_carryover_gold(div100rounded((t.gold() + finishing_bonus) * t.carryover_percentage()));
170  if(!t.is_local_human())
171  {
172  continue;
173  }
174  if (persistent_teams > 1) {
175  report << "\n\n<b>" << t.side_name() << "</b>";
176  }
177 
178  report_victory(report, t, finishing_bonus_per_turn, turns_left, finishing_bonus);
179  }
180  }
181 
182  if (end_level.transient.carryover_report) {
183  gui2::show_transient_message(video_, title, report.str(), "", true);
184  }
185 }
186 
188 {
190  LOG_NG << "created objects... " << (SDL_GetTicks() - playcontroller.get_ticks()) << "\n";
191  if(is_replay_) {
192  playcontroller.enable_replay(is_unit_test_);
193  }
194  LEVEL_RESULT res = playcontroller.play_scenario(is_replay_ ? state_.get_replay_starting_pos() : state_.get_starting_pos());
195 
196  if (res == LEVEL_RESULT::QUIT)
197  {
198  return LEVEL_RESULT::QUIT;
199  }
200  if(!is_unit_test_)
201  {
202  is_replay_ = false;
203  }
204  if(is_replay_)
205  {
206  return res;
207  }
208  end_level = playcontroller.get_end_level_data_const();
209 
210  show_carryover_message(playcontroller, end_level, res);
211  if(!video_.faked())
212  {
213  playcontroller.maybe_linger();
214  }
215  state_.set_snapshot(playcontroller.to_config());
216  return res;
217 }
218 
219 
221 {
222 
225  LEVEL_RESULT res = playcontroller.play_scenario(state_.get_starting_pos());
226 
227  //Check if the player started as mp client and changed to host
228 
229  if (res == LEVEL_RESULT::QUIT)
230  {
231  return LEVEL_RESULT::QUIT;
232  }
233 
234  end_level = playcontroller.get_end_level_data_const();
235 
236  if(res != LEVEL_RESULT::OBSERVER_END)
237  {
238  //We need to call this before linger because it prints the defeated/victory message.
239  //(we want to see that message before entering the linger mode)
240  show_carryover_message(playcontroller, end_level, res);
241  }
242  playcontroller.maybe_linger();
243  playcontroller.update_savegame_snapshot();
244  if(mp_info_) {
245  mp_info_->connected_players = playcontroller.all_players();
246  }
247  return res;
248 }
249 
251 {
252  if(is_replay_) {
254  }
255  else {
257  }
258 
260 
261  game_classification::CAMPAIGN_TYPE game_type = state_.classification().campaign_type;
262 
263  while(state_.valid())
264  {
265  LEVEL_RESULT res = LEVEL_RESULT::VICTORY;
266  end_level_data end_level;
267  try {
268 
270  //In case this an mp scenario reloaded by sp this was not already done yet.
272 
274 
276  //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]
278 
279 #if !defined(ALWAYS_USE_MP_CONTROLLER)
280  if (game_type != game_classification::CAMPAIGN_TYPE::MULTIPLAYER || is_replay_) {
281  res = playsingle_scenario(end_level);
282  if(is_replay_) {
283  return res;
284  }
285  } else
286 #endif
287  {
288  res = playmp_scenario(end_level);
289  }
290  } catch(game::load_game_failed& e) {
291  gui2::show_error_message(video_, _("The game could not be loaded: ") + e.message);
292  return LEVEL_RESULT::QUIT;
293  } catch(quit_game_exception&) {
294  LOG_NG << "The game was aborted\n";
295  return LEVEL_RESULT::QUIT;
296  } catch(game::game_error& e) {
297  gui2::show_error_message(video_, _("Error while playing the game: ") + e.message);
298  return LEVEL_RESULT::QUIT;
299  } catch(incorrect_map_format_error& e) {
300  gui2::show_error_message(video_, _("The game map could not be loaded: ") + e.message);
301  return LEVEL_RESULT::QUIT;
302  } catch (mapgen_exception& e) {
303  gui2::show_error_message(video_, _("Map generator error: ") + e.message);
304  } catch(config::error& e) {
305  gui2::show_error_message(video_, _("Error while reading the WML: ") + e.message);
306  return LEVEL_RESULT::QUIT;
307  } catch(wml_exception& e) {
308  e.show(video_);
309  return LEVEL_RESULT::QUIT;
310  }
311 
312  if (is_unit_test_) {
313  return res;
314  }
315  if(res == LEVEL_RESULT::QUIT) {
316  return res;
317  }
318  // proceed_to_next_level <=> 'any human side recieved victory'
319  // If 'any human side recieved victory' we do the Save-management options
320  // Otherwise we are done now
321  if(!end_level.proceed_to_next_level) {
322  return res;
323  }
324 
327  }
328  if (preferences::save_replays() && end_level.replay_save) {
330  save.save_game_automatic(video_, true);
331  }
332 
334 
335  //If there is no next scenario we're done now.
336  if(state_.get_scenario_id().empty())
337  {
338  return res;
339  }
340  else if(res == LEVEL_RESULT::OBSERVER_END && mp_info_ && !mp_info_->is_host)
341  {
342  const int dlg_res = gui2::show_message(video_, _("Game Over"),
343  _("This scenario has ended. Do you want to continue the campaign?"),
345 
346  if(dlg_res == gui2::window::CANCEL) {
347  return res;
348  }
349  }
350 
351  if (mp_info_ && !mp_info_->is_host) {
352  // Opens join game dialog to get a new gamestate.
353  if(!mp::goto_mp_wait(video_, state_, game_config_, &mp_info_->connection, res == LEVEL_RESULT::OBSERVER_END)) {
354  return LEVEL_RESULT::QUIT;
355  }
356 
357  //The host should send the complete savegame now that also contains the carryvoer sides start.
358  } else {
359  // Retrieve next scenario data.
361 
362  if (state_.valid()) {
363  //note that although starting_pos is const it might be changed by gamestate.some_non_const_operation() .
364  const config& starting_pos = state_.get_starting_pos();
365 
366  const bool is_mp = state_.classification().is_normal_mp_game();
367  state_.mp_settings().num_turns = starting_pos["turns"].to_int(-1);
368  state_.mp_settings().saved_game = false;
369  state_.mp_settings().use_map_settings = starting_pos["force_lock_settings"].to_bool(!is_mp);
370 
371  ng::connect_engine_ptr connect_engine(new ng::connect_engine(state_, false, mp_info_));
372 
373  if (!connect_engine->can_start_game() || (game_config::debug && game_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER)) {
374  // Opens staging dialog to allow users to make an adjustments for scenario.
375  if(!mp::goto_mp_connect(video_, *connect_engine, game_config_, mp_info_ ? &mp_info_->connection : nullptr)) {
376  return LEVEL_RESULT::QUIT;
377  }
378  } else {
379  // Start the next scenario immediately.
380  connect_engine->start_game();
381  }
382  }
383  }
384 
385  if(state_.valid()) {
386  // Update the label
388 
389  // If this isn't the last scenario, then save the game
390  if(end_level.prescenario_save) {
391 
392  // For multiplayer, we want the save
393  // to contain the starting position.
394  // For campaigns however, this is the
395  // start-of-scenario save and the
396  // starting position needs to be empty,
397  // to force a reload of the scenario config.
398 
400 
402  }
403 
404  }
405  }
406 
407  if (!state_.get_scenario_id().empty()) {
408  std::string message = _("Unknown scenario: '$scenario|'");
409  utils::string_map symbols;
410  symbols["scenario"] = state_.get_scenario_id();
411  message = utils::interpolate_variables_into_string(message, &symbols);
413  return LEVEL_RESULT::QUIT;
414  }
415 
419  }
420  }
421  return LEVEL_RESULT::VICTORY;
422 }
423 
void empty_playlist()
Definition: sound.cpp:564
void show_error_message(CVideo &video, const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:207
std::vector< char_t > string
Class for start-of-scenario saves.
Definition: savegame.hpp:294
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()
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:217
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
std::set< std::string > connected_players
players and observers
replay_recorder_base & get_replay()
Definition: saved_game.hpp:117
double carryover_bonus() const
Definition: team.hpp:353
This file contains the window object, this object is a top level container which has the event manage...
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
Definition: saved_game.cpp:401
bool goto_mp_connect(CVideo &video, ng::connect_engine &engine, const config &game_config, wesnothd_connection *connection)
Opens mp::connect screen and sets game state according to the changes made.
static lg::log_domain log_engine("engine")
bool prescenario_save
Should a prescenario be created the next game?
#define VNGETTEXT(msgid, msgid_plural, count,...)
virtual const std::vector< team > & teams() const
Definition: game_board.hpp:92
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:464
saved_game & state_
bool goto_mp_wait(CVideo &video, saved_game &state, const config &game_config, wesnothd_connection *connection, bool observe)
Opens mp::wait screen and sets game state according to the changes made.
LEVEL_RESULT playmp_scenario(end_level_data &end_level)
int carryover_gold() const
Definition: team.hpp:355
game_data * gamedata
Definition: resources.cpp:22
void show(CVideo &video)
Shows the error in a dialog.
const gamemap & get_map_const() const
This class stores all the data for a single 'side' (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 towards 0.
Definition: math.hpp:49
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
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:360
int carryover_percentage() const
Definition: team.hpp:349
game_board * gameboard
Definition: resources.cpp:20
void update_label()
sets classification().label to the correct value.
Definition: saved_game.cpp:575
Encapsulates the map of the game.
Definition: map.hpp:34
LEVEL_RESULT playsingle_scenario(end_level_data &end_level)
Shows a yes and no button.
Definition: message.hpp:79
bool is_observer() const
bool carryover_report
Should a summary of the scenario outcome be displayed?
config & get_starting_pos()
Definition: saved_game.cpp:488
Error used for any general game error, e.g.
Definition: game_errors.hpp:46
LEVEL_RESULT play_scenario(const config &level)
Helper class, don't construct this directly.
size_t size(const utf8::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
transient_end_level transient
void show_message(CVideo &video, 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
void show_carryover_message(playsingle_controller &playcontroller, const end_level_data &end_level, LEVEL_RESULT res)
const bool is_unit_test_
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario.
Definition: savegame.cpp:67
const std::string & next_scenario() const
Definition: game_data.hpp:87
int turn() const
std::string get_scenario_id()
Definition: saved_game.cpp:554
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
bool delete_saves()
Definition: game.cpp:785
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:316
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:443
const tod_manager & get_tod_manager_const() const
static void save(LexState *ls, int c)
Definition: llex.cpp:57
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:257
bool valid() const
Definition: saved_game.cpp:459
-file mapgen.hpp
compression::format save_compression_format()
Definition: game.cpp:873
int gold() const
Definition: team.hpp:188
#define LOG_NG
double t
Definition: astarsearch.cpp:64
wesnothd_connection & connection
game_classification & classification()
Definition: saved_game.hpp:55
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:512
std::string message
Definition: exceptions.hpp:31
bool carryover_add() const
Definition: team.hpp:351
#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:93
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
const std::vector< map_location > & villages() const
Return a list of the locations of villages on the map.
Definition: map.hpp:157
Dialog is closed with the cancel button.
Definition: window.hpp:112
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:338
int turns_left
Definition: pathfind.cpp:161
int number_of_turns() const
const config & get_replay_starting_pos()
Definition: saved_game.cpp:493
bool save_replays()
Definition: game.cpp:775