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