The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
undo.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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
49static 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
54namespace 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 */
79void 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 */
199void 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 */
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
258void undo_list::finish_action(bool can_undo)
259{
260 if(current_) {
261 current_->combine_moves();
262 current_->set_unit_id_diff(synced_context::get_unit_id_diff());
263 undos_.emplace_back(std::move(current_));
264 if(!can_undo) {
265 clear();
266 }
267 }
268}
269
271{
272 // This in particular makes sure no commands that do nothing stay on the undo stack but also on the recorder
273 // in particular so that menu items that did nothing because the user aborted in a custom menu dont persist on the replay.
274 if(!undos_.empty() && undos_.back()->empty()) {
275 undo();
276 }
277}
278/**
279 * Undoes the top action on the undo stack.
280 */
282{
283 if ( undos_.empty() )
284 return;
285
286 const events::command_disabler disable_commands;
287
288 // Get the action to undo. (This will be placed on the redo stack, but
289 // only if the undo is successful.)
290 auto action = std::move(undos_.back());
291 if(!action->undo(side_)) {
292 return;
293 }
294
295 // Bookkeeping.
296 undos_.pop_back();
297 redos_.emplace_back(new config());
299
300 resources::whiteboard->on_gamestate_change();
301
302 // Screen updates.
304 gui.invalidate_unit();
305 gui.invalidate_game_status();
306 gui.redraw_minimap();
307}
308
309
310
311/**
312 * Redoes the top action on the redo stack.
313 */
315{
316 if (redos_.empty()) {
317 return;
318 }
319 // Get the action to redo.
320 auto action = std::move(redos_.back());
321 redos_.pop_back();
322
323 auto [commandname, data] = action->mandatory_child("command").all_children_view().front();
324
325 // Note that this might add more than one [command]
326 resources::recorder->redo(*action);
327
328 auto spectator = action_spectator([](const std::string& msg)
329 {
330 ERR_NG << "Out of sync when redoing: " << msg;
331 gui2::show_transient_message(_("Redo Error"),
332 _("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);
333
334 });
335 // synced_context::run readds the undo command with the normal
336 // undo_list::add function which clears the redo stack which would
337 // make redoing of more than one move impossible. To work around
338 // that we save redo stack here and set it later.
339 redos_list temp;
340 temp.swap(redos_);
341 synced_context::run(commandname, data, spectator);
342 if(!resources::recorder->at_end()) {
343 spectator.error(_("Unhandled choices while redoing"));
345 }
346 temp.swap(redos_);
347
348 // Screen updates.
350 gui.invalidate_unit();
351 gui.invalidate_game_status();
352 gui.redraw_minimap();
353}
354
355
356
357
358
359/**
360 * Applies the pending fog/shroud changes from the undo stack.
361 * Does nothing if the the current side does not use fog or shroud.
362 * @returns true if shroud or fog was cleared.
363 */
365{
368 // No need to do clearing if fog/shroud has been kept up-to-date.
369 if ( tm.auto_shroud_updates() || !tm.fog_or_shroud() ) {
370 return false;
371 }
372 shroud_clearer clearer;
373 bool cleared_shroud = false;
374 const std::size_t list_size = undos_.size();
375
376
377 // Loop through the list of undo_actions.
378 for( std::size_t i = 0; i != list_size; ++i ) {
379 // Loop through the staps of the action.
380 for(auto& step_ptr : undos_[i]->steps()) {
381 if(const shroud_clearing_action* action = dynamic_cast<const shroud_clearing_action*>(step_ptr.get())) {
382 LOG_NG << "Turning an undo...";
383
384 // Clear the hexes this unit can see from each hex occupied during
385 // the action.
386 std::vector<map_location>::const_iterator step;
387 for(step = action->route.begin(); step != action->route.end(); ++step) {
388 // Clear the shroud, collecting new sighted events.
389 // (This can be made gradual by changing "true" to "false".)
390 if(clearer.clear_unit(*step, tm, action->view_info, true)) {
391 cleared_shroud = true;
392 }
393 }
394 }
395 }
396 }
397
398
399 if (!cleared_shroud) {
400 return false;
401 }
402 // If we clear fog or shroud outside a synced context we get OOS
403 // Note that it can happen that we call this function from ouside a synced context
404 // when we reload a game and want to prevent undoing. But in this case this is
405 // preceded by a manual update_shroud call so that cleared_shroud is false.
407
408 // The entire stack needs to be cleared in order to preserve replays.
409 // (The events that fired might depend on current unit positions.)
410 // (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]))
411
412 // Update the display before pumping events.
413 clearer.invalidate_after_clear();
414
415 // Fire sighted events
416 if ( std::get<0>(clearer.fire_events() )) {
417 // Fix up the display in case WML changed stuff.
419 disp.invalidate_unit();
420 }
421
422 return true;
423}
424
425}//namespace actions
map_location loc
Definition move.cpp:172
#define ERR_NG
#define LOG_NG
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:314
undo_list()
Constructor.
Definition undo.cpp:61
void cleanup_action()
called after a user action, removes empty actions
Definition undo.cpp:270
bool apply_shroud_changes() const
Applies the pending fog/shroud changes from the undo stack.
Definition undo.cpp:364
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:281
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
child_itors child_range(std::string_view key)
Definition config.cpp:268
bool has_attribute(std::string_view key) const
Definition config.cpp:157
std::string debug() const
Definition config.cpp:1214
team & get_team(int i)
static game_display * get_singleton()
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
void undo_cut(config &dst)
Definition replay.cpp:488
void set_to_end()
Definition replay.cpp:637
void redo(const config &dst, bool set_to_end=false)
Definition replay.cpp:403
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:74
bool fog_or_shroud() const
Definition team.hpp:346
bool auto_shroud_updates() const
Definition team.hpp:366
const config * cfg
static std::string _(const char *str)
Definition gettext.hpp:100
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::string_view data
Definition picture.cpp:188
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:46
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
static lg::log_domain log_engine("engine")
Various functions that implement the undoing (and redoing) of in-game commands.
Various functions implementing vision (through fog of war and shroud).
#define i
#define e