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