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