The Battle for Wesnoth  1.19.7+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  * Constructor.
59  * The config is allowed to be invalid.
60  */
62  undos_(), redos_(), side_(1), committed_actions_(false)
63 {
64 }
65 
66 /**
67  * Destructor.
68  */
70 {
71  // Default destructor, but defined out-of-line to localize the templating.
72  // (Might make compiles faster.)
73 }
74 
75 
76 /**
77  * Adds an auto-shroud toggle to the undo stack.
78  */
79 void undo_list::add_auto_shroud(bool turned_on)
80 {
81  add(std::make_unique<undo::auto_shroud_action>(turned_on));
82 }
83 
84 
85 /**
86  * Adds a dismissal to the undo stack.
87  */
89 {
90  add(std::make_unique<undo::dismiss_action>(u));
91 }
92 
93 /**
94  * Adds a move to the undo stack.
95  */
97  const std::vector<map_location>::const_iterator & begin,
98  const std::vector<map_location>::const_iterator & end,
99  int start_moves,
100  const map_location::direction dir)
101 {
102  add(std::make_unique<undo::move_action>(u, begin, end, start_moves, dir));
103 }
104 
105 /**
106  * Adds a recall to the undo stack.
107  */
109  const map_location& from)
110 {
111  add(std::make_unique<undo::recall_action>(u, loc, from));
112 }
113 
114 /**
115  * Adds a recruit to the undo stack.
116  */
118  const map_location& from)
119 {
120  add(std::make_unique<undo::recruit_action>(u, loc, from));
121 }
122 
123 
124 /**
125  * Clears the stack of undoable (and redoable) actions.
126  * (Also handles updating fog/shroud if needed.)
127  * Call this if an action alters the game state, but add that action to the
128  * stack before calling this (if the action is a kind that can be undone).
129  * This may fire events and change the game state.
130  */
132 {
133  // The fact that this function was called indicates that something was done.
134  // (Some actions, such as attacks, are never put on the stack.)
135  committed_actions_ = true;
136 
137  // We can save some overhead by not calling apply_shroud_changes() for an
138  // empty stack.
139  if ( !undos_.empty() ) {
141  undos_.clear();
142  }
143  // No special handling for redos, so just clear that stack.
144  redos_.clear();
145 }
146 
147 
148 /**
149  * Updates fog/shroud based on the undo stack, then updates stack as needed.
150  * Call this when "updating shroud now".
151  * This may fire events and change the game state.
152  */
154 {
155  // Update fog/shroud.
156  bool cleared_something = apply_shroud_changes();
157 
158  if (cleared_something) {
159  // The actions that led to information being revealed can no longer
160  // be undone.
161  undos_.clear();
162  //undos_.erase(undos_.begin(), undos_.begin() + erase_to);
163  committed_actions_ = true;
164  }
165  return cleared_something;
166 }
167 
168 
169 /**
170  * Performs some initializations and error checks when starting a new side-turn.
171  * @param[in] side The side whose turn is about to start.
172  */
174 {
175  // Error checks.
176  if ( !undos_.empty() ) {
177  ERR_NG << "Undo stack not empty in new_side_turn().";
178  // At worst, someone missed some sighted events, so try to recover.
179  undos_.clear();
180  redos_.clear();
181  }
182  else if ( !redos_.empty() ) {
183  ERR_NG << "Redo stack not empty in new_side_turn().";
184  // Sloppy tracking somewhere, but not critically so.
185  redos_.clear();
186  }
187 
188  // Reset the side.
189  side_ = side;
190  committed_actions_ = false;
191 }
192 
193 
194 /**
195  * Read the undo_list from the provided config.
196  * Currently, this is only used when the undo_list is empty, but in theory
197  * it could be used to append the config to the current data.
198  */
199 void undo_list::read(const config& cfg, int current_side)
200 {
201  side_ = current_side;
202  committed_actions_ = committed_actions_ || cfg["committed"].to_bool();
203 
204  //If we have the side parameter this means that this was the old format pre 1.19.7, we ignore this since it's incompatible.
205  if(cfg.has_attribute("side")) {
206  return;
207  }
208 
209  // Build the undo stack.
210  try {
211  for(const config& child : cfg.child_range("undo")) {
212  undos_.push_back(std::make_unique<undo_action_container>());
213  undos_.back()->read(child);
214  }
215  } catch(const bad_lexical_cast&) {
216  //It ddoenst make sense to "skip" actions in the undo stakc since that would just result in errors later.
217  ERR_NG << "Error when parsing undo list from config: bad lexical cast.";
218  ERR_NG << "config was: " << cfg.debug();
219  ERR_NG << "discardind undo stack...";
220  undos_.clear();
221  } catch(const config::error& e) {
222  ERR_NG << "Error when parsing undo list from config: " << e.what();
223  ERR_NG << "config was: " << cfg.debug();
224  ERR_NG << "discardind undo stack...";
225  undos_.clear();
226  }
227 
228 
229  // Build the redo stack.
230  for (const config & child : cfg.child_range("redo")) {
231  redos_.emplace_back(new config(child));
232  }
233 }
234 
235 
236 /**
237  * Write the undo_list into the provided config.
238  */
239 void undo_list::write(config & cfg) const
240 {
241  cfg["committed"] = committed_actions_;
242 
243  for ( const auto& action_ptr : undos_)
244  action_ptr->write(cfg.add_child("undo"));
245 
246  for ( const auto& cfg_ptr : redos_)
247  cfg.add_child("redo") = *cfg_ptr;
248 }
249 
250 
252 {
253  current_ = std::make_unique<undo_action_container>();
254  redos_.clear();
255 }
256 
257 
258 void undo_list::finish_action(bool can_undo)
259 {
260  if(current_) {
261  current_->set_unit_id_diff(synced_context::get_unit_id_diff());
262  undos_.emplace_back(std::move(current_));
263  if(!can_undo) {
264  clear();
265  }
266  }
267 }
268 
270 {
271  // This in particular makes sure no commands that do nothing stay on the undo stack but also on the recorder
272  // in particular so that menu items that did nothing because the user aborted in a custom menu dont persist on the replay.
273  if(!undos_.empty() && undos_.back()->empty()) {
274  undo();
275  }
276 }
277 /**
278  * Undoes the top action on the undo stack.
279  */
281 {
282  if ( undos_.empty() )
283  return;
284 
285  const events::command_disabler disable_commands;
286 
287  // Get the action to undo. (This will be placed on the redo stack, but
288  // only if the undo is successful.)
289  auto action = std::move(undos_.back());
290  if(!action->undo(side_)) {
291  return;
292  }
293 
294  // Bookkeeping.
295  undos_.pop_back();
296  redos_.emplace_back(new config());
298 
299  resources::whiteboard->on_gamestate_change();
300 
301  // Screen updates.
303  gui.invalidate_unit();
304  gui.invalidate_game_status();
305  gui.redraw_minimap();
306 }
307 
308 
309 
310 /**
311  * Redoes the top action on the redo stack.
312  */
314 {
315  if (redos_.empty()) {
316  return;
317  }
318  // Get the action to redo.
319  auto action = std::move(redos_.back());
320  redos_.pop_back();
321 
322  auto [commandname, data] = action->mandatory_child("command").all_children_view().front();
323 
324  // Note that this might add more than one [command]
325  resources::recorder->redo(*action);
326 
327  auto spectator = action_spectator([](const std::string& msg)
328  {
329  ERR_NG << "Out of sync when redoing: " << msg;
330  gui2::show_transient_message(_("Redo Error"),
331  _("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);
332 
333  });
334  // synced_context::run readds the undo command with the normal
335  // undo_list::add function which clears the redo stack which would
336  // make redoing of more than one move impossible. To work around
337  // that we save redo stack here and set it later.
338  redos_list temp;
339  temp.swap(redos_);
340  synced_context::run(commandname, data, spectator);
341  temp.swap(redos_);
342 
343  // Screen updates.
345  gui.invalidate_unit();
346  gui.invalidate_game_status();
347  gui.redraw_minimap();
348 }
349 
350 
351 
352 
353 
354 /**
355  * Applies the pending fog/shroud changes from the undo stack.
356  * Does nothing if the the current side does not use fog or shroud.
357  * @returns true if shroud or fog was cleared.
358  */
360 {
363  // No need to do clearing if fog/shroud has been kept up-to-date.
364  if ( tm.auto_shroud_updates() || !tm.fog_or_shroud() ) {
365  return false;
366  }
367  shroud_clearer clearer;
368  bool cleared_shroud = false;
369  const std::size_t list_size = undos_.size();
370 
371 
372  // Loop through the list of undo_actions.
373  for( std::size_t i = 0; i != list_size; ++i ) {
374  // Loop through the staps of the action.
375  for(auto& step_ptr : undos_[i]->steps()) {
376  if(const shroud_clearing_action* action = dynamic_cast<const shroud_clearing_action*>(step_ptr.get())) {
377  LOG_NG << "Turning an undo...";
378 
379  // Clear the hexes this unit can see from each hex occupied during
380  // the action.
381  std::vector<map_location>::const_iterator step;
382  for(step = action->route.begin(); step != action->route.end(); ++step) {
383  // Clear the shroud, collecting new sighted events.
384  // (This can be made gradual by changing "true" to "false".)
385  if(clearer.clear_unit(*step, tm, action->view_info, true)) {
386  cleared_shroud = true;
387  }
388  }
389  }
390  }
391  }
392 
393 
394  if (!cleared_shroud) {
395  return false;
396  }
397  // If we clear fog or shroud outside a synced context we get OOS
398  // Note that it can happen that we call this function from ouside a synced context
399  // when we reload a game and want to prevent undoing. But in this case this is
400  // preceded by a manual update_shroud call so that cleared_shroud is false.
401  assert(synced_context::is_synced());
402 
403  // The entire stack needs to be cleared in order to preserve replays.
404  // (The events that fired might depend on current unit positions.)
405  // (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]))
406 
407  // Update the display before pumping events.
408  clearer.invalidate_after_clear();
409 
410  // Fire sighted events
411  if ( std::get<0>(clearer.fire_events() )) {
412  // Fix up the display in case WML changed stuff.
414  disp.invalidate_unit();
415  }
416 
417  return true;
418 }
419 
420 }//namespace actions
map_location loc
Definition: move.cpp:172
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
void new_side_turn(int side)
Performs some initializations and error checks when starting a new side-turn.
Definition: undo.cpp:173
bool can_undo() const
True if there are actions that can be undone.
Definition: undo.hpp:95
void add_dismissal(const unit_const_ptr &u)
Adds a dismissal to the undo stack.
Definition: undo.cpp:88
void add_recall(const unit_const_ptr &u, const map_location &loc, const map_location &from)
Adds a recall to the undo stack.
Definition: undo.cpp:108
bool committed_actions_
Tracks if actions have been cleared from the stack since the turn began.
Definition: undo.hpp:134
action_ptr_t current_
Definition: undo.hpp:127
void add(std::unique_ptr< undo_action > &&action)
Adds an undo step to the current action.
Definition: undo.hpp:117
void write(config &cfg) const
Write the undo_list into the provided config.
Definition: undo.cpp:239
action_list undos_
Definition: undo.hpp:128
~undo_list()
Destructor.
Definition: undo.cpp:69
bool commit_vision()
Updates fog/shroud based on the undo stack, then updates stack as needed.
Definition: undo.cpp:153
void read(const config &cfg, int current_side)
Read the undo_list from the provided config.
Definition: undo.cpp:199
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, const map_location::direction dir=map_location::direction::indeterminate)
Adds a move to the undo stack.
Definition: undo.cpp:96
void redo()
Redoes the top action on the redo stack.
Definition: undo.cpp:313
undo_list()
Constructor.
Definition: undo.cpp:61
void cleanup_action()
called after a user action, removes empty actions
Definition: undo.cpp:269
bool apply_shroud_changes() const
Applies the pending fog/shroud changes from the undo stack.
Definition: undo.cpp:359
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:131
int side_
Tracks the current side.
Definition: undo.hpp:132
void init_action()
called before a user action, starts collecting undo steps for the new action.
Definition: undo.cpp:251
void add_auto_shroud(bool turned_on)
Adds an auto-shroud toggle to the undo stack.
Definition: undo.cpp:79
void add_recruit(const unit_const_ptr &u, const map_location &loc, const map_location &from)
Adds a recruit to the undo stack.
Definition: undo.cpp:117
redos_list redos_
Definition: undo.hpp:129
void finish_action(bool can_undo)
called after a user action, pushes the collected undo steps on the undo stack.
Definition: undo.cpp:258
void undo()
Undoes the top action on the undo stack.
Definition: undo.cpp:280
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
child_itors child_range(config_key_type key)
Definition: config.cpp:272
std::string debug() const
Definition: config.cpp:1240
config & add_child(config_key_type key)
Definition: config.cpp:440
team & get_team(int i)
Definition: game_board.hpp:92
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
void undo_cut(config &dst)
Definition: replay.cpp:487
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:402
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 int get_unit_id_diff()
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
std::size_t i
Definition: function.cpp:1029
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
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)
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 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