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