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