refactor undo_action class

First we split the undo_ation into subclasses (undo_action_base, undo_action
and shroud_clearing_action) so that undo actions that don't clear shroud don't
contain the route and view_info data.
Also the non undoable actions now don't need to implement undo() and redo()

Second we move the undo action classes to different files.
This commit is contained in:
gfgtdf 2015-05-19 04:53:17 +02:00
parent cd756b74db
commit 138f7c92c4
18 changed files with 909 additions and 777 deletions

View file

@ -701,7 +701,14 @@ set(wesnoth-main_SRC
actions/create.cpp actions/create.cpp
actions/heal.cpp actions/heal.cpp
actions/move.cpp actions/move.cpp
actions/shroud_clearing_action.cpp
actions/undo.cpp actions/undo.cpp
actions/undo_action.cpp
actions/undo_dismiss_action.cpp
actions/undo_move_action.cpp
actions/undo_recall_action.cpp
actions/undo_recruit_action.cpp
actions/undo_update_shroud_action.cpp
actions/vision.cpp actions/vision.cpp
addon/client.cpp addon/client.cpp
addon/info.cpp addon/info.cpp

View file

@ -189,7 +189,14 @@ wesnoth_sources = Split("""
actions/create.cpp actions/create.cpp
actions/heal.cpp actions/heal.cpp
actions/move.cpp actions/move.cpp
actions/shroud_clearing_action.cpp
actions/undo.cpp actions/undo.cpp
actions/undo_action.cpp
actions/undo_dismiss_action.cpp
actions/undo_move_action.cpp
actions/undo_recall_action.cpp
actions/undo_recruit_action.cpp
actions/undo_update_shroud_action.cpp
actions/vision.cpp actions/vision.cpp
addon/client.cpp addon/client.cpp
addon/info.cpp addon/info.cpp

View file

@ -0,0 +1 @@
#include "shroud_clearing_action.hpp"

View file

@ -0,0 +1,53 @@
#pragma once
#include "vision.hpp"
#include "../map_location.hpp"
#include "../unit_ptr.hpp"
#include <boost/noncopyable.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/optional.hpp>
namespace actions
{
/// base class for classes that clear srhoud (move/recruit/recall)
struct shroud_clearing_action
{
shroud_clearing_action(const config& cfg)
: route()
, view_info(cfg.child_or_empty("unit"))
{
read_locations(cfg, route);
}
shroud_clearing_action(const unit_const_ptr u, const map_location& loc)
: route(1, loc)
, view_info(*u)
{
}
typedef std::vector<map_location> t_route;
shroud_clearing_action(const unit_const_ptr u, const t_route::const_iterator& begin, const t_route::const_iterator& end)
: route(begin, end)
, view_info(*u)
{
}
/// The hexes occupied by the affected unit during this action.
/// For recruits and recalls this only contains one hex.
t_route route;
/// A record of the affected unit's ability to see.
clearer_info view_info;
void write(config & cfg) const
{
write_locations(route, cfg);
view_info.write(cfg.add_child("unit"));
}
virtual ~shroud_clearing_action() {}
};
}

View file

@ -47,6 +47,12 @@
#include "create.hpp" // for find_recall_location, etc #include "create.hpp" // for find_recall_location, etc
#include "move.hpp" // for get_village #include "move.hpp" // for get_village
#include "vision.hpp" // for clearer_info, etc #include "vision.hpp" // for clearer_info, etc
#include "shroud_clearing_action.hpp"
#include "undo_dismiss_action.hpp"
#include "undo_move_action.hpp"
#include "undo_recall_action.hpp"
#include "undo_recruit_action.hpp"
#include "undo_update_shroud_action.hpp"
#include <algorithm> // for reverse #include <algorithm> // for reverse
#include <boost/foreach.hpp> // for auto_any_base, etc #include <boost/foreach.hpp> // for auto_any_base, etc
@ -68,214 +74,21 @@ static lg::log_domain log_engine("engine");
namespace actions { namespace actions {
/**
* Virtual destructor
*/
undo_list::undo_action::~undo_action()
{
delete view_info;
}
struct undo_list::dismiss_action : undo_list::undo_action {
unit_ptr dismissed_unit;
explicit dismiss_action(const unit_const_ptr dismissed) : undo_action(),
dismissed_unit(new unit(*dismissed))
{
this->unit_id_diff = synced_context::get_unit_id_diff();
}
explicit dismiss_action(const config & unit_cfg) : undo_action(),
dismissed_unit(new unit(unit_cfg))
{}
virtual ~dismiss_action();
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side, undo_list & undos);
/// Redoes this action.
virtual bool redo(int side);
};
undo_list::dismiss_action::~dismiss_action()
{}
struct undo_list::move_action : undo_list::undo_action {
int starting_moves;
int original_village_owner;
int countdown_time_bonus;
map_location::DIRECTION starting_dir;
map_location goto_hex;
move_action(const unit_const_ptr moved,
const std::vector<map_location>::const_iterator & begin,
const std::vector<map_location>::const_iterator & end,
int sm, int timebonus, int orig, const map_location::DIRECTION dir) :
undo_action(moved, begin, end),
starting_moves(sm),
original_village_owner(orig),
countdown_time_bonus(timebonus),
starting_dir(dir == map_location::NDIRECTIONS ? moved->facing() : dir),
goto_hex(moved->get_goto())
{
this->unit_id_diff = synced_context::get_unit_id_diff();
}
move_action(const config & unit_cfg, const config & route_cfg,
int sm, int timebonus, int orig, const map_location::DIRECTION dir) :
undo_action(unit_cfg),
starting_moves(sm),
original_village_owner(orig),
countdown_time_bonus(timebonus),
starting_dir(dir),
goto_hex(unit_cfg["goto_x"].to_int(-999) - 1,
unit_cfg["goto_y"].to_int(-999) - 1)
{
read_locations(route_cfg, route);
}
virtual ~move_action();
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side, undo_list & undos);
/// Redoes this action.
virtual bool redo(int side);
};
undo_list::move_action::~move_action()
{}
struct undo_list::recall_action : undo_list::undo_action {
std::string id;
map_location recall_from;
recall_action(const unit_const_ptr recalled, const map_location& loc,
const map_location& from) :
undo_action(recalled, loc),
id(recalled->id()),
recall_from(from)
{
this->unit_id_diff = synced_context::get_unit_id_diff();
}
recall_action(const config & unit_cfg, const map_location & loc,
const map_location & from) :
undo_action(unit_cfg, loc),
id(unit_cfg["id"]),
recall_from(from)
{}
virtual ~recall_action();
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side, undo_list & undos);
/// Redoes this action.
virtual bool redo(int side);
};
undo_list::recall_action::~recall_action()
{}
struct undo_list::recruit_action : undo_list::undo_action {
const unit_type & u_type;
map_location recruit_from;
recruit_action(const unit_const_ptr recruited, const map_location& loc,
const map_location& from) :
undo_action(recruited, loc),
u_type(recruited->type()),
recruit_from(from)
{
this->unit_id_diff = synced_context::get_unit_id_diff();
}
recruit_action(const config & unit_cfg, const unit_type & type,
const map_location& loc, const map_location& from) :
undo_action(unit_cfg, loc),
u_type(type),
recruit_from(from)
{}
virtual ~recruit_action();
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side, undo_list & undos);
/// Redoes this action.
virtual bool redo(int side);
};
undo_list::recruit_action::~recruit_action()
{}
struct undo_list::auto_shroud_action : undo_list::undo_action {
bool active;
explicit auto_shroud_action(bool turned_on) :
undo_action(),
active(turned_on)
{}
explicit auto_shroud_action(bool turned_on, int unit_id_diff) :
undo_action(),
active(turned_on)
{
this->unit_id_diff = unit_id_diff;
}
virtual ~auto_shroud_action();
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side, undo_list & undos);
/// Redoes this action.
virtual bool redo(int side);
};
undo_list::auto_shroud_action::~auto_shroud_action()
{}
struct undo_list::update_shroud_action : undo_list::undo_action {
// No additional data.
update_shroud_action() : undo_action() {}
update_shroud_action(int unit_id_diff) : undo_action()
{
this->unit_id_diff = unit_id_diff;
}
virtual ~update_shroud_action();
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side, undo_list & undos);
/// Redoes this action.
virtual bool redo(int side);
};
undo_list::update_shroud_action::~update_shroud_action()
{}
/** /**
* Creates an undo_action based on a config. * Creates an undo_action based on a config.
* @return a pointer that must be deleted, or NULL if the @a cfg could not be parsed. * @return a pointer that must be deleted, or NULL if the @a cfg could not be parsed.
*/ */
undo_list::undo_action * undo_action_base * undo_list::create_action(const config & cfg)
undo_list::undo_action::create(const config & cfg)
{ {
const std::string str = cfg["type"]; const std::string str = cfg["type"];
undo_list::undo_action * res = NULL; undo_action_base * res = NULL;
// The general division of labor in this function is that the various // The general division of labor in this function is that the various
// constructors will parse the "unit" child config, while this function // constructors will parse the "unit" child config, while this function
// parses everything else. // parses everything else.
if ( str == "move" ) { if ( str == "move" ) {
res = new move_action(cfg.child("unit"), cfg, res = new undo::move_action(cfg, cfg.child_or_empty("unit"),
cfg["starting_moves"], cfg["starting_moves"],
cfg["time_bonus"], cfg["time_bonus"],
cfg["village_owner"], cfg["village_owner"],
@ -293,122 +106,30 @@ undo_list::undo_action::create(const config & cfg)
<< child["type"] << "' was not found.\n"; << child["type"] << "' was not found.\n";
return NULL; return NULL;
} }
res = new recruit_action(child, *u_type, res = new undo::recruit_action(cfg, *u_type, map_location(cfg.child_or_empty("leader"), NULL));
map_location(cfg, NULL),
map_location(cfg.child_or_empty("leader"), NULL));
} }
else if ( str == "recall" ) else if ( str == "recall" )
res = new recall_action(cfg.child("unit"), res = new undo::recall_action(cfg, map_location(cfg.child_or_empty("leader"), NULL));
map_location(cfg, NULL),
map_location(cfg.child_or_empty("leader"), NULL));
else if ( str == "dismiss" ) else if ( str == "dismiss" )
res = new dismiss_action(cfg.child("unit")); res = new undo::dismiss_action(cfg, cfg.child("unit"));
else if ( str == "auto_shroud" ) else if ( str == "auto_shroud" )
res = new auto_shroud_action(cfg["active"].to_bool()); res = new undo::auto_shroud_action(cfg["active"].to_bool());
else if ( str == "update_shroud" ) else if ( str == "update_shroud" )
res = new update_shroud_action; res = new undo::update_shroud_action();
else else
{ {
// Unrecognized type. // Unrecognized type.
ERR_NG << "Unrecognized undo action type: " << str << "." << std::endl; ERR_NG << "Unrecognized undo action type: " << str << "." << std::endl;
return NULL; return NULL;
} }
res->replay_data = cfg.child_or_empty("replay_data");
res->unit_id_diff = cfg["unit_id_diff"];
return res; return res;
} }
/**
* Writes this into the provided config.
*/
void undo_list::dismiss_action::write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
cfg["type"] = "dismiss";
dismissed_unit->write(cfg.add_child("unit"));
}
/**
* Writes this into the provided config.
*/
void undo_list::recall_action::write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
cfg["type"] = "recall";
route.front().write(cfg);
recall_from.write(cfg.add_child("leader"));
config & child = cfg.add_child("unit");
view_info->write(child);
child["id"] = id;
}
/**
* Writes this into the provided config.
*/
void undo_list::recruit_action::write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
cfg["type"] = "recruit";
route.front().write(cfg);
recruit_from.write(cfg.add_child("leader"));
config & child = cfg.add_child("unit");
view_info->write(child);
child["type"] = u_type.base_id();
}
/**
* Writes this into the provided config.
*/
void undo_list::move_action::write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
cfg["type"] = "move";
cfg["starting_direction"] = map_location::write_direction(starting_dir);
cfg["starting_moves"] = starting_moves;
cfg["time_bonus"] = countdown_time_bonus;
cfg["village_owner"] = original_village_owner;
write_locations(route, cfg);
config & child = cfg.add_child("unit");
view_info->write(child);
child["goto_x"] = goto_hex.x + 1;
child["goto_y"] = goto_hex.y + 1;
}
/**
* Writes this into the provided config.
*/
void undo_list::auto_shroud_action::write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
cfg["type"] = "auto_shroud";
cfg["active"] = active;
}
/**
* Writes this into the provided config.
*/
void undo_list::update_shroud_action::write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
cfg["type"] = "update_shroud";
}
/** /**
* Constructor. * Constructor.
* The config is allowed to be invalid. * The config is allowed to be invalid.
@ -433,14 +154,12 @@ undo_list::~undo_list()
/** /**
* Adds an auto-shroud toggle to the undo stack. * Adds an auto-shroud toggle to the undo stack.
*/ */
void undo_list::add_auto_shroud(bool turned_on, boost::optional<int> unit_id_diff) void undo_list::add_auto_shroud(bool turned_on)
{ {
if(!unit_id_diff)
unit_id_diff = synced_context::get_unit_id_diff();
/// @todo: Consecutive shroud actions can be collapsed into one. /// @todo: Consecutive shroud actions can be collapsed into one.
// Do not call add(), as this should not clear the redo stack. // Do not call add(), as this should not clear the redo stack.
undos_.push_back(new auto_shroud_action(turned_on, unit_id_diff.get())); undos_.push_back(new undo::auto_shroud_action(turned_on));
} }
/** /**
@ -448,7 +167,7 @@ void undo_list::add_auto_shroud(bool turned_on, boost::optional<int> unit_id_dif
*/ */
void undo_list::add_dismissal(const unit_const_ptr u) void undo_list::add_dismissal(const unit_const_ptr u)
{ {
add(new dismiss_action(u)); add(new undo::dismiss_action(u));
} }
/** /**
@ -460,7 +179,7 @@ void undo_list::add_move(const unit_const_ptr u,
int start_moves, int timebonus, int village_owner, int start_moves, int timebonus, int village_owner,
const map_location::DIRECTION dir) const map_location::DIRECTION dir)
{ {
add(new move_action(u, begin, end, start_moves, timebonus, village_owner, dir)); add(new undo::move_action(u, begin, end, start_moves, timebonus, village_owner, dir));
} }
/** /**
@ -469,7 +188,7 @@ void undo_list::add_move(const unit_const_ptr u,
void undo_list::add_recall(const unit_const_ptr u, const map_location& loc, void undo_list::add_recall(const unit_const_ptr u, const map_location& loc,
const map_location& from) const map_location& from)
{ {
add(new recall_action(u, loc, from)); add(new undo::recall_action(u, loc, from));
} }
/** /**
@ -478,7 +197,7 @@ void undo_list::add_recall(const unit_const_ptr u, const map_location& loc,
void undo_list::add_recruit(const unit_const_ptr u, const map_location& loc, void undo_list::add_recruit(const unit_const_ptr u, const map_location& loc,
const map_location& from) const map_location& from)
{ {
add(new recruit_action(u, loc, from)); add(new undo::recruit_action(u, loc, from));
} }
/** /**
@ -486,14 +205,12 @@ void undo_list::add_recruit(const unit_const_ptr u, const map_location& loc,
* This is called from within commit_vision(), so there should be no need * This is called from within commit_vision(), so there should be no need
* for this to be publicly visible. * for this to be publicly visible.
*/ */
void undo_list::add_update_shroud(boost::optional<int> unit_id_diff) void undo_list::add_update_shroud()
{ {
if(!unit_id_diff)
unit_id_diff = synced_context::get_unit_id_diff();
/// @todo: Consecutive shroud actions can be collapsed into one. /// @todo: Consecutive shroud actions can be collapsed into one.
// Do not call add(), as this should not clear the redo stack. // Do not call add(), as this should not clear the redo stack.
undos_.push_back(new update_shroud_action(unit_id_diff.get())); undos_.push_back(new undo::update_shroud_action());
} }
@ -583,7 +300,7 @@ void undo_list::read(const config & cfg)
// Build the undo stack. // Build the undo stack.
BOOST_FOREACH( const config & child, cfg.child_range("undo") ) { BOOST_FOREACH( const config & child, cfg.child_range("undo") ) {
try { try {
undo_action * action = undo_action::create(child); undo_action_base * action = create_action(child);
if ( action ) { if ( action ) {
undos_.push_back(action); undos_.push_back(action);
} }
@ -601,9 +318,9 @@ void undo_list::read(const config & cfg)
// Build the redo stack. // Build the redo stack.
BOOST_FOREACH( const config & child, cfg.child_range("redo") ) { BOOST_FOREACH( const config & child, cfg.child_range("redo") ) {
try { try {
undo_action * action = undo_action::create(child); undo_action_base * action = create_action(child);
if ( action ) { if ( undo_action* undoable_action = dynamic_cast<undo_action*>(action)) {
redos_.push_back(action); redos_.push_back(undoable_action);
} }
} catch (bad_lexical_cast &) { } catch (bad_lexical_cast &) {
ERR_NG << "Error when parsing redo list from config: bad lexical cast." << std::endl; ERR_NG << "Error when parsing redo list from config: bad lexical cast." << std::endl;
@ -629,7 +346,7 @@ void undo_list::write(config & cfg) const
for ( action_list::const_iterator it = undos_.begin(); it != undos_.end(); ++it ) for ( action_list::const_iterator it = undos_.begin(); it != undos_.end(); ++it )
it->write(cfg.add_child("undo")); it->write(cfg.add_child("undo"));
for ( action_list::const_iterator it = redos_.begin(); it != redos_.end(); ++it ) for ( redos_list::const_iterator it = redos_.begin(); it != redos_.end(); ++it )
it->write(cfg.add_child("redo")); it->write(cfg.add_child("redo"));
} }
@ -649,18 +366,21 @@ void undo_list::undo()
// Get the action to undo. (This will be placed on the redo stack, but // Get the action to undo. (This will be placed on the redo stack, but
// only if the undo is successful.) // only if the undo is successful.)
action_list::auto_type action = undos_.pop_back(); action_list::auto_type action = undos_.pop_back();
if (undo_action* undoable_action = dynamic_cast<undo_action*>(action.ptr()))
{
int last_unit_id = n_unit::id_manager::instance().get_save_id(); int last_unit_id = n_unit::id_manager::instance().get_save_id();
if ( !action->undo(side_, *this) ) { if ( !undoable_action->undo(side_) ) {
return; return;
} }
if(last_unit_id - action->unit_id_diff < 0) { if(last_unit_id - undoable_action->unit_id_diff < 0) {
ERR_NG << "Next unit id is below 0 after undoing" << std::endl; ERR_NG << "Next unit id is below 0 after undoing" << std::endl;
} }
n_unit::id_manager::instance().set_save_id(last_unit_id - action->unit_id_diff); n_unit::id_manager::instance().set_save_id(last_unit_id - undoable_action->unit_id_diff);
// Bookkeeping. // Bookkeeping.
resources::recorder->undo_cut(action->get_replay_data()); resources::recorder->undo_cut(undoable_action->replay_data);
redos_.push_back(action.release()); //we can do a static cast here because we alreeady checked with the dynamic cast above.
redos_.push_back(static_cast<undo_action*>(action.release()));
resources::whiteboard->on_gamestate_change(); resources::whiteboard->on_gamestate_change();
// Screen updates. // Screen updates.
@ -668,174 +388,18 @@ void undo_list::undo()
gui.invalidate_game_status(); gui.invalidate_game_status();
gui.redraw_minimap(); gui.redraw_minimap();
gui.draw(); gui.draw();
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool undo_list::dismiss_action::undo(int side, undo_list & /*undos*/)
{
team &current_team = (*resources::teams)[side-1];
current_team.recall_list().add(dismissed_unit);
return true;
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool undo_list::recall_action::undo(int side, undo_list & /*undos*/)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
const map_location & recall_loc = route.front();
unit_map::iterator un_it = units.find(recall_loc);
if ( un_it == units.end() ) {
return false;
} }
else
unit_ptr un = un_it.get_shared_ptr();
if (!un) {
return false;
}
statistics::un_recall_unit(*un);
int cost = statistics::un_recall_unit_cost(*un);
if (cost < 0) {
current_team.spend_gold(-current_team.recall_cost());
}
else {
current_team.spend_gold(-cost);
}
current_team.recall_list().add(un);
// invalidate before erasing allow us
// to also do the overlapped hexes
gui.invalidate(recall_loc);
units.erase(recall_loc);
return true;
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool undo_list::recruit_action::undo(int side, undo_list & /*undos*/)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
const map_location & recruit_loc = route.front();
unit_map::iterator un_it = units.find(recruit_loc);
if ( un_it == units.end() ) {
return false;
}
const unit &un = *un_it;
statistics::un_recruit_unit(un);
current_team.spend_gold(-un.type().cost());
//MP_COUNTDOWN take away recruit bonus
current_team.set_action_bonus_count(current_team.action_bonus_count() - 1);
// invalidate before erasing allow us
// to also do the overlapped hexes
gui.invalidate(recruit_loc);
units.erase(recruit_loc);
return true;
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool undo_list::move_action::undo(int side, undo_list & /*undos*/)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
// Copy some of our stored data.
const int saved_moves = starting_moves;
std::vector<map_location> rev_route = route;
std::reverse(rev_route.begin(), rev_route.end());
// Check units.
unit_map::iterator u = units.find(rev_route.front());
const unit_map::iterator u_end = units.find(rev_route.back());
if ( u == units.end() || u_end != units.end() ) {
//this can actually happen if the scenario designer has abused the [allow_undo] command
ERR_NG << "Illegal 'undo' found. Possible abuse of [allow_undo]?" << std::endl;
return false;
}
if ( resources::gameboard->map().is_village(rev_route.front()) ) {
get_village(rev_route.front(), original_village_owner + 1);
//MP_COUNTDOWN take away capture bonus
if ( countdown_time_bonus )
{ {
current_team.set_action_bonus_count(current_team.action_bonus_count() - 1); //ignore this action, and undo the previous one.
config replay_data;
resources::recorder->undo_cut(replay_data);
undo();
resources::recorder->redo(replay_data);
undos_.push_back(action.release());
} }
}
// Record the unit's current state so it can be redone.
starting_moves = u->movement_left();
goto_hex = u->get_goto();
// Move the unit.
unit_display::move_unit(rev_route, u.get_shared_ptr(), true, starting_dir);
units.move(u->get_location(), rev_route.back());
unit::clear_status_caches();
// Restore the unit's old state.
u = units.find(rev_route.back());
u->set_goto(map_location());
u->set_movement(saved_moves, true);
u->anim_comp().set_standing();
gui.invalidate_unit_after_move(rev_route.front(), rev_route.back());
return true;
} }
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool undo_list::auto_shroud_action::undo(int /*side*/, undo_list & undos)
{
// This does not count as an undoable action, so undo the next
// action instead.
resources::recorder->undo();
undos.undo();
// Now keep the auto-shroud toggle at the top of the undo stack.
resources::recorder->add_synced_command("auto_shroud", replay_helper::get_auto_shroud(active));
undos.add_auto_shroud(active, this->unit_id_diff);
// Shroud actions never get moved to the redo stack, so claim an error.
return false;
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool undo_list::update_shroud_action::undo(int /*side*/, undo_list & undos)
{
// This does not count as an undoable action, so undo the next
// action instead.
resources::recorder->undo();
undos.undo();
// Now keep the shroud update at the top of the undo stack.
resources::recorder->add_synced_command("update_shroud", replay_helper::get_update_shroud());
undos.add_update_shroud(this->unit_id_diff);
// Shroud actions never get moved to the redo stack, so claim an error.
return false;
}
/** /**
@ -852,7 +416,7 @@ void undo_list::redo()
// Get the action to redo. (This will be placed on the undo stack, but // Get the action to redo. (This will be placed on the undo stack, but
// only if the redo is successful.) // only if the redo is successful.)
action_list::auto_type action = redos_.pop_back(); redos_list::auto_type action = redos_.pop_back();
int last_unit_id = n_unit::id_manager::instance().get_save_id(); int last_unit_id = n_unit::id_manager::instance().get_save_id();
if ( !action->redo(side_) ) { if ( !action->redo(side_) ) {
return; return;
@ -873,182 +437,8 @@ void undo_list::redo()
gui.draw(); gui.draw();
} }
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool undo_list::dismiss_action::redo(int side)
{
team &current_team = (*resources::teams)[side-1];
resources::recorder->redo(replay_data);
replay_data.clear();
current_team.recall_list().erase_if_matches_id(dismissed_unit->id());
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool undo_list::recall_action::redo(int side)
{
game_display & gui = *resources::screen;
team &current_team = (*resources::teams)[side-1];
map_location loc = route.front();
map_location from = recall_from;
unit_ptr un = current_team.recall_list().find_if_matches_id(id);
if ( !un ) {
ERR_NG << "Trying to redo a recall of '" << id
<< "', but that unit is not in the recall list.";
return false;
}
const std::string &msg = find_recall_location(side, loc, from, *un);
if ( msg.empty() ) {
resources::recorder->redo(replay_data);
replay_data.clear();
set_scontext_synced sync;
recall_unit(id, current_team, loc, from, true, false);
// Quick error check. (Abuse of [allow_undo]?)
if ( loc != route.front() ) {
ERR_NG << "When redoing a recall at " << route.front()
<< ", the location was moved to " << loc << ".\n";
// Not really fatal, I suppose. Just update the action so
// undoing this works.
route.front() = loc;
}
sync.do_final_checkup();
} else {
gui::dialog(gui, "", msg, gui::OK_ONLY).show();
return false;
}
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool undo_list::recruit_action::redo(int side)
{
game_display & gui = *resources::screen;
team &current_team = (*resources::teams)[side-1];
map_location loc = route.front();
map_location from = recruit_from;
const std::string & name = u_type.base_id();
//search for the unit to be recruited in recruits
if ( !util::contains(get_recruits(side, loc), name) ) {
ERR_NG << "Trying to redo a recruit for side " << side
<< ", which does not recruit type \"" << name << "\"\n";
assert(false);
return false;
}
current_team.last_recruit(name);
const std::string &msg = find_recruit_location(side, loc, from, name);
if ( msg.empty() ) {
//MP_COUNTDOWN: restore recruitment bonus
current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
resources::recorder->redo(replay_data);
replay_data.clear();
set_scontext_synced sync;
recruit_unit(u_type, side, loc, from, true, false);
// Quick error check. (Abuse of [allow_undo]?)
if ( loc != route.front() ) {
ERR_NG << "When redoing a recruit at " << route.front()
<< ", the location was moved to " << loc << ".\n";
// Not really fatal, I suppose. Just update the action so
// undoing this works.
route.front() = loc;
}
sync.do_final_checkup();
} else {
gui::dialog(gui, "", msg, gui::OK_ONLY).show();
return false;
}
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool undo_list::move_action::redo(int side)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
// Check units.
unit_map::iterator u = units.find(route.front());
if ( u == units.end() ) {
ERR_NG << "Illegal movement 'redo'." << std::endl;
assert(false);
return false;
}
// Adjust starting moves.
const int saved_moves = starting_moves;
starting_moves = u->movement_left();
// Move the unit.
unit_display::move_unit(route, u.get_shared_ptr());
units.move(u->get_location(), route.back());
u = units.find(route.back());
unit::clear_status_caches();
// Set the unit's state.
u->set_goto(goto_hex);
u->set_movement(saved_moves, true);
u->anim_comp().set_standing();
if ( resources::gameboard->map().is_village(route.back()) ) {
get_village(route.back(), u->side());
//MP_COUNTDOWN restore capture bonus
if ( countdown_time_bonus )
{
current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
}
}
gui.invalidate_unit_after_move(route.front(), route.back());
resources::recorder->redo(replay_data);
replay_data.clear();
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool undo_list::auto_shroud_action::redo(int /*side*/)
{
// This should never happen.
ERR_NG << "Attempt to redo an auto shroud toggle." << std::endl;
assert(false);
return false;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool undo_list::update_shroud_action::redo(int /*side*/)
{
// This should never happen.
ERR_NG << "Attempt to redo a shroud update." << std::endl;
assert(false);
return false;
}
/** /**
@ -1074,24 +464,22 @@ size_t undo_list::apply_shroud_changes() const
// Loop through the list of undo_actions. // Loop through the list of undo_actions.
for( size_t i = 0; i != list_size; ++i ) { for( size_t i = 0; i != list_size; ++i ) {
const undo_action & action = undos_[i]; if (const shroud_clearing_action* action = dynamic_cast<const shroud_clearing_action*>(&undos_[i])) {
// Only actions with vision data are relevant.
if ( !action.view_info )
continue;
LOG_NG << "Turning an undo...\n"; LOG_NG << "Turning an undo...\n";
// Clear the hexes this unit can see from each hex occupied during // Clear the hexes this unit can see from each hex occupied during
// the action. // the action.
std::vector<map_location>::const_iterator step; std::vector<map_location>::const_iterator step;
for (step = action.route.begin(); step != action.route.end(); ++step) { for (step = action->route.begin(); step != action->route.end(); ++step) {
// Clear the shroud, collecting new sighted events. // Clear the shroud, collecting new sighted events.
// (This can be made gradual by changing "true" to "false".) // (This can be made gradual by changing "true" to "false".)
if ( clearer.clear_unit(*step, tm, *action.view_info, true) ) { if ( clearer.clear_unit(*step, tm, action->view_info, true) ) {
cleared_shroud = true; cleared_shroud = true;
erase_to = i + 1; erase_to = i + 1;
} }
} }
} }
}
// Optimization: if nothing was cleared, then there is nothing to redraw. // Optimization: if nothing was cleared, then there is nothing to redraw.
if ( cleared_shroud ) { if ( cleared_shroud ) {

View file

@ -23,6 +23,7 @@
#include "vision.hpp" #include "vision.hpp"
#include "../map_location.hpp" #include "../map_location.hpp"
#include "../unit_ptr.hpp" #include "../unit_ptr.hpp"
#include "undo_action.hpp"
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <boost/ptr_container/ptr_vector.hpp> #include <boost/ptr_container/ptr_vector.hpp>
@ -35,106 +36,21 @@ namespace actions {
/// Class to store the actions that a player can undo and redo. /// Class to store the actions that a player can undo and redo.
class undo_list : boost::noncopyable { class undo_list : boost::noncopyable {
/// Records information to be able to undo an action.
/// Each type of action gets its own derived type.
struct undo_action : boost::noncopyable {
/// Constructor for move actions.
undo_action(const unit_const_ptr u,
const std::vector<map_location>::const_iterator & begin,
const std::vector<map_location>::const_iterator & end) :
replay_data(),
unit_id_diff(),
route(begin, end),
view_info(new clearer_info(*u))
{
}
/// Constructor for recruit and recall actions.
/// These types of actions are guaranteed to have a non-empty route.
undo_action(const unit_const_ptr u, const map_location& loc) :
replay_data(),
unit_id_diff(),
route(1, loc),
view_info(new clearer_info(*u))
{}
/// Constructor from a config storing the view info.
/// Does not set @a route.
explicit undo_action(const config & cfg) :
replay_data(),
unit_id_diff(),
route(),
view_info(new clearer_info(cfg))
{}
/// Constructor from a config storing the view info and a location.
/// Guarantees a non-empty route.
explicit undo_action(const config & cfg, const map_location & loc) :
replay_data(),
unit_id_diff(),
route(1, loc),
view_info(new clearer_info(cfg))
{}
/// Default constructor.
/// This is the only way to get NULL view_info.
undo_action() :
replay_data(),
unit_id_diff(),
route(),
view_info(NULL)
{}
// Virtual destructor to support derived classes.
virtual ~undo_action();
typedef boost::ptr_vector<undo_action_base> action_list;
/// Creates an undo_action based on a config. typedef boost::ptr_vector<undo_action> redos_list;
/// Throws bad_lexical_cast or config::error if it cannot parse the config properly.
static undo_action * create(const config & cfg);
/// Writes this into the provided config.
virtual void write(config & cfg) const = 0;
/// Undoes this action.
/// @return true on success; false on an error.
virtual bool undo(int side, undo_list & undos) = 0;
/// Redoes this action.
/// @return true on success; false on an error.
virtual bool redo(int side) = 0;
config& get_replay_data() { return replay_data; }
// Data:
/// the replay data to do this action, this is only !empty() when this action is on the redo stack
/// we need this because we dont recalculate the redos like they would be in real game,
/// but even undoable commands can have "dependent" (= user_input) commands, which we save here.
config replay_data;
int unit_id_diff;
/// The hexes occupied by the affected unit during this action.
std::vector<map_location> route;
/// A record of the affected unit's ability to see.
/// For derived classes that use this, it must be never NULL.
clearer_info * const view_info;
// This pointer is the reason for deriving from noncopyable (an
// alternative would be to implement deep copies, but we have no
// need for copying, so noncopyable is simpler).
};
// The structs derived from undo_action.
struct dismiss_action;
struct move_action;
struct recall_action;
struct recruit_action;
struct auto_shroud_action;
struct update_shroud_action;
// The update_shroud_action needs to be able to call add_update_shroud().
friend struct update_shroud_action;
typedef boost::ptr_vector<undo_action> action_list;
public: public:
explicit undo_list(const config & cfg); explicit undo_list(const config & cfg);
~undo_list(); ~undo_list();
/// Creates an undo_action based on a config.
/// Throws bad_lexical_cast or config::error if it cannot parse the config properly.
static undo_action_base * create_action(const config & cfg);
// Functions related to managing the undo stack: // Functions related to managing the undo stack:
/// Adds an auto-shroud toggle to the undo stack. /// Adds an auto-shroud toggle to the undo stack.
void add_auto_shroud(bool turned_on, boost::optional<int> unit_id_diff = boost::optional<int>()); void add_auto_shroud(bool turned_on);
/// Adds a dismissal to the undo stack. /// Adds a dismissal to the undo stack.
void add_dismissal(const unit_const_ptr u); void add_dismissal(const unit_const_ptr u);
/// Adds a move to the undo stack. /// Adds a move to the undo stack.
@ -151,7 +67,7 @@ public:
const map_location& from); const map_location& from);
private: private:
/// Adds a shroud update to the undo stack. /// Adds a shroud update to the undo stack.
void add_update_shroud(boost::optional<int> unit_id_diff = boost::optional<int>()); void add_update_shroud();
public: public:
/// Clears the stack of undoable (and redoable) actions. /// Clears the stack of undoable (and redoable) actions.
void clear(); void clear();
@ -180,14 +96,14 @@ public:
private: // functions private: // functions
/// Adds an action to the undo stack. /// Adds an action to the undo stack.
void add(undo_action * action) void add(undo_action_base * action)
{ undos_.push_back(action); redos_.clear(); } { undos_.push_back(action); redos_.clear(); }
/// Applies the pending fog/shroud changes from the undo stack. /// Applies the pending fog/shroud changes from the undo stack.
size_t apply_shroud_changes() const; size_t apply_shroud_changes() const;
private: // data private: // data
action_list undos_; action_list undos_;
action_list redos_; redos_list redos_;
/// Tracks the current side. /// Tracks the current side.
int side_; int side_;

View file

@ -0,0 +1 @@
#include "undo_action.hpp"

View file

@ -0,0 +1,77 @@
#pragma once
#include "vision.hpp"
#include "../map_location.hpp"
#include "../unit_ptr.hpp"
#include "../synced_context.hpp"
#include <boost/noncopyable.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/optional.hpp>
namespace actions {
class undo_list;
}
namespace actions {
/// Records information to be able to undo an action.
/// Each type of action gets its own derived type.
/// Base class for all entries in the undo stack, also contains non undoable actions like update_shroud or auto_shroud.
struct undo_action_base : boost::noncopyable
{
/// Default constructor.
/// This is the only way to get NULL view_info.
undo_action_base()
{ }
// Virtual destructor to support derived classes.
virtual ~undo_action_base() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const
{
cfg["type"] = this->get_type();
}
virtual const char* get_type() const = 0;
};
/// actions that are undoable (this does not include update_shroud and auto_shroud)
struct undo_action : undo_action_base
{
/// Default constructor.
/// It is assumed that undo actions are contructed after the action is performed
/// so that the unit id diff does not change after this contructor.
undo_action()
: undo_action_base()
, replay_data()
, unit_id_diff(synced_context::get_unit_id_diff())
{ }
undo_action(const config& cfg)
: undo_action_base()
, replay_data(cfg.child_or_empty("replay_data"))
, unit_id_diff(cfg["unit_id_diff"])
{ }
// Virtual destructor to support derived classes.
virtual ~undo_action() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const
{
cfg.add_child("replay_data", replay_data);
cfg["unit_id_diff"] = unit_id_diff;
undo_action_base::write(cfg);
}
/// Undoes this action.
/// @return true on success; false on an error.
virtual bool undo(int side) = 0;
/// Redoes this action.
/// @return true on success; false on an error.
virtual bool redo(int side) = 0;
/// the replay data to do this action, this is only !empty() when this action is on the redo stack
/// we need this because we don't recalculate the redos like they would be in real game,
/// but even undoable commands can have "dependent" (= user_input) commands, which we save here.
config replay_data;
int unit_id_diff;
};
}

View file

@ -0,0 +1,47 @@
#include "undo_dismiss_action.hpp"
#include "../resources.hpp"
#include "../team.hpp"
#include "../replay.hpp"
namespace actions
{
namespace undo
{
/**
* Writes this into the provided config.
*/
void dismiss_action::write(config & cfg) const
{
undo_action::write(cfg);
dismissed_unit->write(cfg.add_child("unit"));
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool dismiss_action::undo(int side)
{
team &current_team = (*resources::teams)[side-1];
current_team.recall_list().add(dismissed_unit);
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool dismiss_action::redo(int side)
{
team &current_team = (*resources::teams)[side-1];
resources::recorder->redo(replay_data);
replay_data.clear();
current_team.recall_list().erase_if_matches_id(dismissed_unit->id());
return true;
}
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "undo_action.hpp"
#include "../unit_ptr.hpp"
#include "../unit.hpp"
namespace actions
{
namespace undo
{
struct dismiss_action : undo_action
{
unit_ptr dismissed_unit;
explicit dismiss_action(const unit_const_ptr dismissed)
: undo_action()
, dismissed_unit(new unit(*dismissed))
{
}
explicit dismiss_action(const config & cfg, const config & unit_cfg)
: undo_action(cfg)
, dismissed_unit(new unit(unit_cfg))
{
}
virtual const char* get_type() const { return "dismiss"; }
virtual ~dismiss_action() {};
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side);
/// Redoes this action.
virtual bool redo(int side);
};
}
}

View file

@ -0,0 +1,143 @@
#include "undo_move_action.hpp"
#include "move.hpp"
#include "../construct_dialog.hpp"
#include "../resources.hpp"
#include "../team.hpp"
#include "../replay.hpp"
#include "../unit_map.hpp"
#include "../unit_animation_component.hpp"
#include "../log.hpp"
#include "../game_display.hpp"
#include "../unit_display.hpp"
#include "../game_board.hpp"
#include "../map.hpp"
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
namespace actions
{
namespace undo
{
/**
* Writes this into the provided config.
*/
void move_action::write(config & cfg) const
{
undo_action::write(cfg);
shroud_clearing_action::write(cfg);
cfg["starting_direction"] = map_location::write_direction(starting_dir);
cfg["starting_moves"] = starting_moves;
cfg["time_bonus"] = countdown_time_bonus;
cfg["village_owner"] = original_village_owner;
config & child = cfg.child("unit");
child["goto_x"] = goto_hex.x + 1;
child["goto_y"] = goto_hex.y + 1;
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool move_action::undo(int side)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
// Copy some of our stored data.
const int saved_moves = starting_moves;
std::vector<map_location> rev_route = route;
std::reverse(rev_route.begin(), rev_route.end());
// Check units.
unit_map::iterator u = units.find(rev_route.front());
const unit_map::iterator u_end = units.find(rev_route.back());
if ( u == units.end() || u_end != units.end() ) {
//this can actually happen if the scenario designer has abused the [allow_undo] command
ERR_NG << "Illegal 'undo' found. Possible abuse of [allow_undo]?" << std::endl;
return false;
}
if ( resources::gameboard->map().is_village(rev_route.front()) ) {
get_village(rev_route.front(), original_village_owner + 1);
//MP_COUNTDOWN take away capture bonus
if ( countdown_time_bonus )
{
current_team.set_action_bonus_count(current_team.action_bonus_count() - 1);
}
}
// Record the unit's current state so it can be redone.
starting_moves = u->movement_left();
goto_hex = u->get_goto();
// Move the unit.
unit_display::move_unit(rev_route, u.get_shared_ptr(), true, starting_dir);
units.move(u->get_location(), rev_route.back());
unit::clear_status_caches();
// Restore the unit's old state.
u = units.find(rev_route.back());
u->set_goto(map_location());
u->set_movement(saved_moves, true);
u->anim_comp().set_standing();
gui.invalidate_unit_after_move(rev_route.front(), rev_route.back());
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool move_action::redo(int side)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
// Check units.
unit_map::iterator u = units.find(route.front());
if ( u == units.end() ) {
ERR_NG << "Illegal movement 'redo'." << std::endl;
assert(false);
return false;
}
// Adjust starting moves.
const int saved_moves = starting_moves;
starting_moves = u->movement_left();
// Move the unit.
unit_display::move_unit(route, u.get_shared_ptr());
units.move(u->get_location(), route.back());
u = units.find(route.back());
unit::clear_status_caches();
// Set the unit's state.
u->set_goto(goto_hex);
u->set_movement(saved_moves, true);
u->anim_comp().set_standing();
if ( resources::gameboard->map().is_village(route.back()) ) {
get_village(route.back(), u->side());
//MP_COUNTDOWN restore capture bonus
if ( countdown_time_bonus )
{
current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
}
}
gui.invalidate_unit_after_move(route.front(), route.back());
resources::recorder->redo(replay_data);
replay_data.clear();
return true;
}
}
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "undo_action.hpp"
#include "shroud_clearing_action.hpp"
#include "../unit_ptr.hpp"
#include "../unit.hpp"
namespace actions
{
namespace undo
{
struct move_action : undo_action, shroud_clearing_action
{
int starting_moves;
int original_village_owner;
int countdown_time_bonus;
map_location::DIRECTION starting_dir;
map_location goto_hex;
move_action(const unit_const_ptr moved,
const std::vector<map_location>::const_iterator & begin,
const std::vector<map_location>::const_iterator & end,
int sm, int timebonus, int orig, const map_location::DIRECTION dir)
: undo_action()
, shroud_clearing_action(moved, begin, end)
, starting_moves(sm)
, original_village_owner(orig)
, countdown_time_bonus(timebonus)
, starting_dir(dir == map_location::NDIRECTIONS ? moved->facing() : dir)
, goto_hex(moved->get_goto())
{
}
move_action(const config & cfg, const config & unit_cfg,
int sm, int timebonus, int orig, const map_location::DIRECTION dir)
: undo_action(cfg)
, shroud_clearing_action(cfg)
, starting_moves(sm)
, original_village_owner(orig)
, countdown_time_bonus(timebonus)
, starting_dir(dir)
, goto_hex(unit_cfg["goto_x"].to_int(-999) - 1,
unit_cfg["goto_y"].to_int(-999) - 1)
{
}
virtual const char* get_type() const { return "move"; }
virtual ~move_action() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side);
/// Redoes this action.
virtual bool redo(int side);
};
}
}

View file

@ -0,0 +1,115 @@
#include "undo_recall_action.hpp"
#include "create.hpp"
#include "../construct_dialog.hpp"
#include "../resources.hpp"
#include "../team.hpp"
#include "../replay.hpp"
#include "../unit_map.hpp"
#include "../statistics.hpp"
#include "../log.hpp"
#include "../game_display.hpp"
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
namespace actions
{
namespace undo
{
/**
* Writes this into the provided config.
*/
void recall_action::write(config & cfg) const
{
undo_action::write(cfg);
shroud_clearing_action::write(cfg);
recall_from.write(cfg.add_child("leader"));
cfg["id"] = id;
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool recall_action::undo(int side)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
const map_location & recall_loc = route.front();
unit_map::iterator un_it = units.find(recall_loc);
if ( un_it == units.end() ) {
return false;
}
unit_ptr un = un_it.get_shared_ptr();
if (!un) {
return false;
}
statistics::un_recall_unit(*un);
int cost = statistics::un_recall_unit_cost(*un);
if (cost < 0) {
current_team.spend_gold(-current_team.recall_cost());
}
else {
current_team.spend_gold(-cost);
}
current_team.recall_list().add(un);
// invalidate before erasing allow us
// to also do the overlapped hexes
gui.invalidate(recall_loc);
units.erase(recall_loc);
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool recall_action::redo(int side)
{
game_display & gui = *resources::screen;
team &current_team = (*resources::teams)[side-1];
map_location loc = route.front();
map_location from = recall_from;
unit_ptr un = current_team.recall_list().find_if_matches_id(id);
if ( !un ) {
ERR_NG << "Trying to redo a recall of '" << id
<< "', but that unit is not in the recall list.";
return false;
}
const std::string &msg = find_recall_location(side, loc, from, *un);
if ( msg.empty() ) {
resources::recorder->redo(replay_data);
replay_data.clear();
set_scontext_synced sync;
recall_unit(id, current_team, loc, from, true, false);
// Quick error check. (Abuse of [allow_undo]?)
if ( loc != route.front() ) {
ERR_NG << "When redoing a recall at " << route.front()
<< ", the location was moved to " << loc << ".\n";
// Not really fatal, I suppose. Just update the action so
// undoing this works.
route.front() = loc;
}
sync.do_final_checkup();
} else {
gui::dialog(gui, "", msg, gui::OK_ONLY).show();
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,47 @@
#pragma once
#include "undo_action.hpp"
#include "shroud_clearing_action.hpp"
#include "../unit_ptr.hpp"
#include "../unit.hpp"
namespace actions
{
namespace undo
{
struct recall_action : undo_action, shroud_clearing_action
{
std::string id;
map_location recall_from;
recall_action(const unit_const_ptr recalled, const map_location& loc,
const map_location& from)
: undo_action()
, shroud_clearing_action(recalled, loc)
, id(recalled->id())
, recall_from(from)
{
}
recall_action(const config & cfg, const map_location & from)
: undo_action(cfg)
, shroud_clearing_action(cfg)
, id(cfg["id"])
, recall_from(from)
{}
virtual const char* get_type() const { return "recall"; }
virtual ~recall_action() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side);
/// Redoes this action.
virtual bool redo(int side);
};
}
}

View file

@ -0,0 +1,113 @@
#include "undo_recruit_action.hpp"
#include "create.hpp"
#include "../construct_dialog.hpp"
#include "../resources.hpp"
#include "../team.hpp"
#include "../replay.hpp"
#include "../unit_map.hpp"
#include "../statistics.hpp"
#include "../log.hpp"
#include "../game_display.hpp"
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
namespace actions
{
namespace undo
{
/**
* Writes this into the provided config.
*/
void recruit_action::write(config & cfg) const
{
undo_action::write(cfg);
shroud_clearing_action::write(cfg);
recruit_from.write(cfg.add_child("leader"));
config & child = cfg.child("unit");
child["type"] = u_type.base_id();
}
/**
* Undoes this action.
* @return true on success; false on an error.
*/
bool recruit_action::undo(int side)
{
game_display & gui = *resources::screen;
unit_map & units = *resources::units;
team &current_team = (*resources::teams)[side-1];
const map_location & recruit_loc = route.front();
unit_map::iterator un_it = units.find(recruit_loc);
if ( un_it == units.end() ) {
return false;
}
const unit &un = *un_it;
statistics::un_recruit_unit(un);
current_team.spend_gold(-un.type().cost());
//MP_COUNTDOWN take away recruit bonus
current_team.set_action_bonus_count(current_team.action_bonus_count() - 1);
// invalidate before erasing allow us
// to also do the overlapped hexes
gui.invalidate(recruit_loc);
units.erase(recruit_loc);
return true;
}
/**
* Redoes this action.
* @return true on success; false on an error.
*/
bool recruit_action::redo(int side)
{
game_display & gui = *resources::screen;
team &current_team = (*resources::teams)[side-1];
map_location loc = route.front();
map_location from = recruit_from;
const std::string & name = u_type.base_id();
//search for the unit to be recruited in recruits
if ( !util::contains(get_recruits(side, loc), name) ) {
ERR_NG << "Trying to redo a recruit for side " << side
<< ", which does not recruit type \"" << name << "\"\n";
assert(false);
return false;
}
current_team.last_recruit(name);
const std::string &msg = find_recruit_location(side, loc, from, name);
if ( msg.empty() ) {
//MP_COUNTDOWN: restore recruitment bonus
current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
resources::recorder->redo(replay_data);
replay_data.clear();
set_scontext_synced sync;
recruit_unit(u_type, side, loc, from, true, false);
// Quick error check. (Abuse of [allow_undo]?)
if ( loc != route.front() ) {
ERR_NG << "When redoing a recruit at " << route.front()
<< ", the location was moved to " << loc << ".\n";
// Not really fatal, I suppose. Just update the action so
// undoing this works.
route.front() = loc;
}
sync.do_final_checkup();
} else {
gui::dialog(gui, "", msg, gui::OK_ONLY).show();
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,46 @@
#pragma once
#include "undo_action.hpp"
#include "shroud_clearing_action.hpp"
#include "../unit_ptr.hpp"
#include "../unit.hpp"
namespace actions
{
namespace undo
{
struct recruit_action : undo_action, shroud_clearing_action
{
const unit_type & u_type;
map_location recruit_from;
recruit_action(const unit_const_ptr recruited, const map_location& loc,
const map_location& from)
: undo_action()
, shroud_clearing_action(recruited, loc)
, u_type(recruited->type())
, recruit_from(from)
{
}
recruit_action(const config & cfg, const unit_type & type, const map_location& from)
: undo_action(cfg)
, shroud_clearing_action(cfg)
, u_type(type)
, recruit_from(from)
{}
virtual const char* get_type() const { return "recruit"; }
virtual ~recruit_action() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const;
/// Undoes this action.
virtual bool undo(int side);
/// Redoes this action.
virtual bool redo(int side);
};
}
}

View file

@ -0,0 +1,31 @@
#include "undo_update_shroud_action.hpp"
#include "../resources.hpp"
#include "../team.hpp"
#include "../replay.hpp"
namespace actions
{
namespace undo
{
/**
* Writes this into the provided config.
*/
void auto_shroud_action::write(config & cfg) const
{
undo_action_base::write(cfg);
cfg["active"] = active;
}
/**
* Writes this into the provided config.
*/
void update_shroud_action::write(config & cfg) const
{
undo_action_base::write(cfg);
}
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "undo_action.hpp"
#include "../unit_ptr.hpp"
#include "../unit.hpp"
namespace actions
{
namespace undo
{
struct auto_shroud_action : undo_action_base {
bool active;
explicit auto_shroud_action(bool turned_on)
: undo_action_base()
, active(turned_on)
{}
virtual const char* get_type() const { return "auto_shroud"; }
virtual ~auto_shroud_action() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const;
};
struct update_shroud_action : undo_action_base {
// No additional data.
update_shroud_action()
: undo_action_base()
{}
virtual const char* get_type() const { return "update_shroud"; }
virtual ~update_shroud_action() {}
/// Writes this into the provided config.
virtual void write(config & cfg) const;
};
}
}