The Battle for Wesnoth  1.15.0-dev
undo.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Undoing, redoing.
18  */
19 
20 #include "actions/undo.hpp"
21 
22 #include "game_board.hpp" // for game_board
23 #include "log.hpp" // for LOG_STREAM, logger, etc
24 #include "map/map.hpp" // for gamemap
25 #include "map/location.hpp" // for map_location, operator<<, etc
26 #include "mouse_handler_base.hpp" // for command_disabler
27 #include "preferences/general.hpp"
28 #include "recall_list_manager.hpp" // for recall_list_manager
29 #include "replay.hpp" // for recorder, replay
30 #include "replay_helper.hpp" // for replay_helper
31 #include "resources.hpp" // for screen, teams, units, etc
32 #include "synced_context.hpp" // for set_scontext_synced
33 #include "team.hpp" // for team
34 #include "units/unit.hpp" // for unit
36 #include "units/id.hpp"
37 #include "units/map.hpp" // for unit_map, etc
38 #include "units/ptr.hpp" // for unit_const_ptr, unit_ptr
39 #include "units/types.hpp" // for unit_type, unit_type_data, etc
40 #include "whiteboard/manager.hpp" // for manager
41 
42 #include "actions/create.hpp" // for find_recall_location, etc
43 #include "actions/move.hpp" // for get_village
44 #include "actions/vision.hpp" // for clearer_info, etc
51 
52 #include <algorithm> // for reverse
53 #include <cassert> // for assert
54 #include <ostream> // for operator<<, basic_ostream, etc
55 #include <set> // for set
56 
57 static lg::log_domain log_engine("engine");
58 #define ERR_NG LOG_STREAM(err, log_engine)
59 #define LOG_NG LOG_STREAM(info, log_engine)
60 
61 
62 namespace actions {
63 
64 
65 
66 /**
67  * Creates an undo_action based on a config.
68  * @return a pointer that must be deleted, or nullptr if the @a cfg could not be parsed.
69  */
71 {
72  const std::string str = cfg["type"];
73  undo_action_base * res = nullptr;
74  // The general division of labor in this function is that the various
75  // constructors will parse the "unit" child config, while this function
76  // parses everything else.
77 
78  if ( str == "move" ) {
79  res = new undo::move_action(cfg, cfg.child_or_empty("unit"),
80  cfg["starting_moves"],
81  map_location::parse_direction(cfg["starting_direction"]));
82  }
83 
84  else if ( str == "recruit" ) {
85  // Validate the unit type.
86  const config & child = cfg.child("unit");
87  const unit_type * u_type = unit_types.find(child["type"]);
88 
89  if ( !u_type ) {
90  // Bad data.
91  ERR_NG << "Invalid recruit found in [undo] or [redo]; unit type '"
92  << child["type"] << "' was not found.\n";
93  return nullptr;
94  }
95  res = new undo::recruit_action(cfg, *u_type, map_location(cfg.child_or_empty("leader"), nullptr));
96  }
97 
98  else if ( str == "recall" )
99  res = new undo::recall_action(cfg, map_location(cfg.child_or_empty("leader"), nullptr));
100 
101  else if ( str == "dismiss" )
102  res = new undo::dismiss_action(cfg, cfg.child("unit"));
103 
104  else if ( str == "auto_shroud" )
105  res = new undo::auto_shroud_action(cfg["active"].to_bool());
106 
107  else if ( str == "update_shroud" )
108  res = new undo::update_shroud_action();
109  else if ( str == "dummy" )
110  res = new undo_dummy_action(cfg);
111  else
112  {
113  // Unrecognized type.
114  ERR_NG << "Unrecognized undo action type: " << str << "." << std::endl;
115  return nullptr;
116  }
117  return res;
118 }
119 
120 
121 /**
122  * Constructor.
123  * The config is allowed to be invalid.
124  */
126  undos_(), redos_(), side_(1), committed_actions_(false)
127 {
128  if ( cfg )
129  read(cfg);
130 }
131 
132 /**
133  * Destructor.
134  */
136 {
137  // Default destructor, but defined out-of-line to localize the templating.
138  // (Might make compiles faster.)
139 }
140 
141 
142 /**
143  * Adds an auto-shroud toggle to the undo stack.
144  */
145 void undo_list::add_auto_shroud(bool turned_on)
146 {
147  /// @todo: Consecutive shroud actions can be collapsed into one.
148 
149  // Do not call add(), as this should not clear the redo stack.
150  add(new undo::auto_shroud_action(turned_on));
151 }
152 
154 {
155  /// @todo: Consecutive shroud actions can be collapsed into one.
156 
157  // Do not call add(), as this should not clear the redo stack.
158  add(new undo_dummy_action());
159 }
160 
161 /**
162  * Adds a dismissal to the undo stack.
163  */
165 {
166  add(new undo::dismiss_action(u));
167 }
168 
169 /**
170  * Adds a move to the undo stack.
171  */
173  const std::vector<map_location>::const_iterator & begin,
174  const std::vector<map_location>::const_iterator & end,
175  int start_moves, int timebonus, int village_owner,
176  const map_location::DIRECTION dir)
177 {
178  add(new undo::move_action(u, begin, end, start_moves, timebonus, village_owner, dir));
179 }
180 
181 /**
182  * Adds a recall to the undo stack.
183  */
185  const map_location& from, int orig_village_owner, bool time_bonus)
186 {
187  add(new undo::recall_action(u, loc, from, orig_village_owner, time_bonus));
188 }
189 
190 /**
191  * Adds a recruit to the undo stack.
192  */
194  const map_location& from, int orig_village_owner, bool time_bonus)
195 {
196  add(new undo::recruit_action(u, loc, from, orig_village_owner, time_bonus));
197 }
198 
199 /**
200  * Adds a shroud update to the undo stack.
201  * This is called from within commit_vision(), so there should be no need
202  * for this to be publicly visible.
203  */
205 {
206  /// @todo: Consecutive shroud actions can be collapsed into one.
207 
209 }
210 
211 
212 /**
213  * Clears the stack of undoable (and redoable) actions.
214  * (Also handles updating fog/shroud if needed.)
215  * Call this if an action alters the game state, but add that action to the
216  * stack before calling this (if the action is a kind that can be undone).
217  * This may fire events and change the game state.
218  */
220 {
221  // The fact that this function was called indicates that something was done.
222  // (Some actions, such as attacks, are never put on the stack.)
223  committed_actions_ = true;
224 
225  // We can save some overhead by not calling apply_shroud_changes() for an
226  // empty stack.
227  if ( !undos_.empty() ) {
229  undos_.clear();
230  }
231  // No special handling for redos, so just clear that stack.
232  redos_.clear();
233 }
234 
235 
236 /**
237  * Updates fog/shroud based on the undo stack, then updates stack as needed.
238  * Call this when "updating shroud now".
239  * This may fire events and change the game state.
240  * @param[in] is_replay Set to true when this is called during a replay.
241  */
243 {
244  // Update fog/shroud.
245  bool cleared_something = apply_shroud_changes();
246 
247  if (cleared_something) {
248  // The actions that led to information being revealed can no longer
249  // be undone.
250  undos_.clear();
251  //undos_.erase(undos_.begin(), undos_.begin() + erase_to);
252  committed_actions_ = true;
253  }
254 }
255 
256 
257 /**
258  * Performs some initializations and error checks when starting a new side-turn.
259  * @param[in] side The side whose turn is about to start.
260  */
262 {
263  // Error checks.
264  if ( !undos_.empty() ) {
265  ERR_NG << "Undo stack not empty in new_side_turn()." << std::endl;
266  // At worst, someone missed some sighted events, so try to recover.
267  undos_.clear();
268  redos_.clear();
269  }
270  else if ( !redos_.empty() ) {
271  ERR_NG << "Redo stack not empty in new_side_turn()." << std::endl;
272  // Sloppy tracking somewhere, but not critically so.
273  redos_.clear();
274  }
275 
276  // Reset the side.
277  side_ = side;
278  committed_actions_ = false;
279 }
280 
281 
282 /**
283  * Read the undo_list from the provided config.
284  * Currently, this is only used when the undo_list is empty, but in theory
285  * it could be used to append the config to the current data.
286  */
287 void undo_list::read(const config & cfg)
288 {
289  // Merge header data.
290  side_ = cfg["side"].to_int(side_);
291  committed_actions_ = committed_actions_ || cfg["committed"].to_bool();
292 
293  // Build the undo stack.
294  for (const config & child : cfg.child_range("undo")) {
295  try {
296  undo_action_base * action = create_action(child);
297  if ( action ) {
298  undos_.emplace_back(action);
299  }
300  } catch (const bad_lexical_cast &) {
301  ERR_NG << "Error when parsing undo list from config: bad lexical cast." << std::endl;
302  ERR_NG << "config was: " << child.debug() << std::endl;
303  ERR_NG << "Skipping this undo action..." << std::endl;
304  } catch (const config::error& e) {
305  ERR_NG << "Error when parsing undo list from config: " << e.what() << std::endl;
306  ERR_NG << "config was: " << child.debug() << std::endl;
307  ERR_NG << "Skipping this undo action..." << std::endl;
308  }
309  }
310 
311  // Build the redo stack.
312  for (const config & child : cfg.child_range("redo")) {
313  try {
314  redos_.emplace_back(new config(child));
315  } catch (const bad_lexical_cast &) {
316  ERR_NG << "Error when parsing redo list from config: bad lexical cast." << std::endl;
317  ERR_NG << "config was: " << child.debug() << std::endl;
318  ERR_NG << "Skipping this redo action..." << std::endl;
319  } catch (const config::error& e) {
320  ERR_NG << "Error when parsing redo list from config: " << e.what() << std::endl;
321  ERR_NG << "config was: " << child.debug() << std::endl;
322  ERR_NG << "Skipping this redo action..." << std::endl;
323  }
324  }
325 }
326 
327 
328 /**
329  * Write the undo_list into the provided config.
330  */
331 void undo_list::write(config & cfg) const
332 {
333  cfg["side"] = side_;
334  cfg["committed"] = committed_actions_;
335 
336  for ( const auto& action_ptr : undos_)
337  action_ptr->write(cfg.add_child("undo"));
338 
339  for ( const auto& cfg_ptr : redos_)
340  cfg.add_child("redo") = *cfg_ptr;
341 }
342 
343 
344 /**
345  * Undoes the top action on the undo stack.
346  */
348 {
349  if ( undos_.empty() )
350  return;
351 
352  const events::command_disabler disable_commands;
353 
354  // Get the action to undo. (This will be placed on the redo stack, but
355  // only if the undo is successful.)
356  auto action = std::move(undos_.back());
357  undos_.pop_back();
358  if (undo_action* undoable_action = dynamic_cast<undo_action*>(action.get()))
359  {
360  int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
361  if ( !undoable_action->undo(side_) ) {
362  return;
363  }
364  if(last_unit_id - undoable_action->unit_id_diff < 0) {
365  ERR_NG << "Next unit id is below 0 after undoing" << std::endl;
366  }
367  resources::gameboard->unit_id_manager().set_save_id(last_unit_id - undoable_action->unit_id_diff);
368 
369  // Bookkeeping.
370  redos_.emplace_back(new config());
372 
373  resources::whiteboard->on_gamestate_change();
374  }
375  else
376  {
377  //ignore this action, and undo the previous one.
378  config replay_data;
379  resources::recorder->undo_cut(replay_data);
380  undo();
381  resources::recorder->redo(replay_data);
382  undos_.emplace_back(std::move(action));
383  }
384  if(std::all_of(undos_.begin(), undos_.begin(), [](const action_ptr_t& action){ return dynamic_cast<undo_action*>(action.get()) == nullptr; }))
385  {
386  //clear the undo stack if it only contains dsu related actions, this in particular makes sure loops like `while(can_undo()) { undo(); }`always stop.
387  undos_.clear();
388  }
389 }
390 
391 
392 
393 /**
394  * Redoes the top action on the redo stack.
395  */
397 {
398  if ( redos_.empty() )
399  return;
400 
401  const events::command_disabler disable_commands;
402 
403  // Get the action to redo. (This will be placed on the undo stack, but
404  // only if the redo is successful.)
405  auto action = std::move(redos_.back());
406  redos_.pop_back();
407 
408  const config& command_wml = action->child("command");
409  std::string commandname = command_wml.all_children_range().front().key;
410  const config& data = command_wml.all_children_range().front().cfg;
411 
412  resources::recorder->redo(const_cast<const config&>(*action));
413 
414 
415  // synced_context::run readds the undo command with the normal undo_lis::add function which clears the
416  // redo stack which makes redoign of more than one move impossible. to work around that we save redo stack here and set it later.
417  redos_list temp;
418  temp.swap(redos_);
419  synced_context::run(commandname, data, /*use_undo*/ true, /*show*/ true);
420  temp.swap(redos_);
421 }
422 
423 
424 
425 
426 
427 /**
428  * Applies the pending fog/shroud changes from the undo stack.
429  * Does nothing if the the current side does not use fog or shroud.
430  * @returns true if shroud or fog was cleared.
431  */
433 {
435  // No need to do clearing if fog/shroud has been kept up-to-date.
436  if ( tm.auto_shroud_updates() || !tm.fog_or_shroud() ) {
437  return false;
438  }
439  shroud_clearer clearer;
440  bool cleared_shroud = false;
441  const std::size_t list_size = undos_.size();
442 
443 
444  // Loop through the list of undo_actions.
445  for( std::size_t i = 0; i != list_size; ++i ) {
446  if (const shroud_clearing_action* action = dynamic_cast<const shroud_clearing_action*>(undos_[i].get())) {
447  LOG_NG << "Turning an undo...\n";
448 
449  // Clear the hexes this unit can see from each hex occupied during
450  // the action.
451  std::vector<map_location>::const_iterator step;
452  for (step = action->route.begin(); step != action->route.end(); ++step) {
453  // Clear the shroud, collecting new sighted events.
454  // (This can be made gradual by changing "true" to "false".)
455  if ( clearer.clear_unit(*step, tm, action->view_info, true) ) {
456  cleared_shroud = true;
457  }
458  }
459  }
460  }
461 
462 
463  if (!cleared_shroud) {
464  return false;
465  }
466  // If we clear fog or shroud outside a synced context we get OOS
467  // Note that it can happen that we call this function from ouside a synced context
468  // when we reload a game and want to prevent undoing. But in this case this is
469  // preceded by a manual update_shroud call so that cleared_shroud is false.
470  assert(synced_context::is_synced());
471 
472  // The entire stack needs to be cleared in order to preserve replays.
473  // (The events that fired might depend on current unit positions.)
474  // (Also the events that did not fire might depend on unit positions (they whould have fired if the unit would have standed on different positions, for example this can happen if they have a [have_unit] in [filter_condition]))
475 
476  // Update the display before pumping events.
477  clearer.invalidate_after_clear();
478 
479  // Fire sighted events
480  if ( std::get<0>(clearer.fire_events() )) {
481  // Fix up the display in case WML changed stuff.
483  }
484 
485  return true;
486 }
487 
488 }//namespace actions
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:219
boost::intrusive_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:30
base class for classes that clear srhoud (move/recruit/recall)
bool committed_actions_
Tracks if actions have been cleared from the stack since the turn began.
Definition: undo.hpp:115
static DIRECTION parse_direction(const std::string &str)
Definition: location.cpp:64
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:423
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:874
std::size_t get_save_id() const
Used for saving id to savegame.
Definition: id.cpp:41
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1265
bool apply_shroud_changes() const
Applies the pending fog/shroud changes from the undo stack.
Definition: undo.cpp:432
Various functions implementing vision (through fog of war and shroud).
static bool run(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
Sets the context to &#39;synced&#39;, initialises random context, and calls the given function.
action_list undos_
Definition: undo.hpp:109
std::vector< std::unique_ptr< config > > redos_list
Definition: undo.hpp:39
child_itors child_range(config_key_type key)
Definition: config.cpp:366
void undo()
Undoes the top action on the undo stack.
Definition: undo.cpp:347
void add(undo_action_base *action)
Adds an action to the undo stack.
Definition: undo.hpp:103
std::unique_ptr< undo_action_base > action_ptr_t
Definition: undo.hpp:37
int side_
Tracks the current side.
Definition: undo.hpp:113
unit_type_data unit_types
Definition: types.cpp:1442
void new_side_turn(int side)
Performs some initializations and error checks when starting a new side-turn.
Definition: undo.cpp:261
Replay control code.
-file sdl_utils.hpp
A single unit type that the player may recruit.
Definition: types.hpp:42
Records information to be able to undo an action.
Definition: undo_action.hpp:38
bool clear_unit(const map_location &view_loc, team &view_team, std::size_t viewer_id, int sight_range, bool slowed, const movetype::terrain_costs &costs, const map_location &real_loc, const std::set< map_location > *known_units=nullptr, std::size_t *enemy_count=nullptr, std::size_t *friend_count=nullptr, move_unit_spectator *spectator=nullptr, bool instant=true)
Clears shroud (and fog) around the provided location for view_team based on sight_range, costs, and slowed.
Definition: vision.cpp:319
void read(const config &cfg)
Read the undo_list from the provided config.
Definition: undo.cpp:287
undo_list(const undo_list &)=delete
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
team & get_team(int i)
Definition: game_board.hpp:104
game_events::pump_result_t fire_events()
Fires the sighted events that were earlier recorded by fog/shroud clearing.
Definition: vision.cpp:535
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:414
#define ERR_NG
Definition: undo.cpp:58
game_board * gameboard
Definition: resources.cpp:20
bool fog_or_shroud() const
Definition: team.hpp:318
static lg::log_domain log_engine("engine")
std::shared_ptr< action > action_ptr
Definition: typedefs.hpp:61
replay * recorder
Definition: resources.cpp:28
const char * what() const noexcept
Definition: exceptions.hpp:37
void commit_vision()
Updates fog/shroud based on the undo stack, then updates stack as needed.
Definition: undo.cpp:242
void set_save_id(std::size_t)
Definition: id.cpp:46
Encapsulates the map of the game.
Definition: location.hpp:42
bool auto_shroud_updates() const
Definition: team.hpp:336
Various functions related to moving units.
void add_recruit(const unit_const_ptr u, const map_location &loc, const map_location &from, int orig_village_owner, bool time_bonus)
Adds a recruit to the undo stack.
Definition: undo.cpp:193
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
Various functions related to the creation of units (recruits, recalls, and placed units)...
#define LOG_NG
Definition: undo.cpp:59
void add_dismissal(const unit_const_ptr u)
Adds a dismissal to the undo stack.
Definition: undo.cpp:164
static undo_action_base * create_action(const config &cfg)
Creates an undo_action based on a config.
Definition: undo.cpp:70
std::size_t i
Definition: function.cpp:933
entry for player actions that do not need any special code to be performed when undoing such as right...
Definition: undo_action.hpp:89
void redo()
Redoes the top action on the redo stack.
Definition: undo.cpp:396
redos_list redos_
Definition: undo.hpp:110
void add_recall(const unit_const_ptr u, const map_location &loc, const map_location &from, int orig_village_owner, bool time_bonus)
Adds a recall to the undo stack.
Definition: undo.cpp:184
void add_move(const unit_const_ptr u, const std::vector< map_location >::const_iterator &begin, const std::vector< map_location >::const_iterator &end, int start_moves, int timebonus=0, int village_owner=-1, const map_location::DIRECTION dir=map_location::NDIRECTIONS)
Adds a move to the undo stack.
Definition: undo.cpp:172
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:44
config & add_child(config_key_type key)
Definition: config.cpp:479
bool clear_shroud(int side, bool reset_fog, bool fire_events)
Function that will clear shroud (and fog) based on current unit positions.
Definition: vision.cpp:738
void add_update_shroud()
Adds a shroud update to the undo stack.
Definition: undo.cpp:204
n_unit::id_manager & unit_id_manager()
Definition: game_board.hpp:86
Various functions that implement the undoing (and redoing) of in-game commands.
void undo_cut(config &dst)
Definition: replay.cpp:494
Standard logging facilities (interface).
void add_dummy()
Adds an auto-shroud toggle to the undo stack.
Definition: undo.cpp:153
Class to encapsulate fog/shroud clearing and the resultant sighted events.
Definition: vision.hpp:57
#define e
void add_auto_shroud(bool turned_on)
Adds an auto-shroud toggle to the undo stack.
Definition: undo.cpp:145
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:456
void invalidate_after_clear()
The invalidations that should occur after invoking clear_unit().
Definition: vision.cpp:570
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
Thrown when a lexical_cast fails.
static bool is_synced()
void write(config &cfg) const
Write the undo_list into the provided config.
Definition: undo.cpp:331
actions that are undoable (this does not include update_shroud and auto_shroud)
Definition: undo_action.hpp:60
~undo_list()
Destructor.
Definition: undo.cpp:135