Transfer of the validation logic into the action hierarchy.

This commit is contained in:
Étienne Simon 2012-08-17 20:04:51 +00:00
parent 72fd497294
commit 18e0ef7822
15 changed files with 420 additions and 449 deletions

View file

@ -42,6 +42,7 @@ struct dummy_action: action{
fake_unit_ptr get_fake_unit(){ return fake_unit_ptr(); }
void set_valid(bool){}
bool is_valid() const { return true; }
bool validate(){ return action_ptr(); }
};
BOOST_AUTO_TEST_SUITE( whiteboard_side_actions_container )

View file

@ -114,7 +114,8 @@ action::~action()
{
}
size_t action::get_unit_id() const {
size_t action::get_unit_id() const
{
unit *ret = get_unit();
return ret ? ret->underlying_id() : 0;
}

View file

@ -90,6 +90,13 @@ public:
virtual void set_valid(bool valid) = 0;
virtual bool is_valid() const = 0;
/**
* Recalculate the validity of the action.
*
* @return whether the action should be kept.
* */
virtual bool validate() = 0;
/** Constructs and returns a config object representing this object. */
virtual config to_config() const;
/** Constructs an object of a subclass of wb::action using a config. Current behavior is to return a null pointer for unrecognized config. */

View file

@ -20,6 +20,7 @@
#include "attack.hpp"
#include "visitor.hpp"
#include "utility.hpp"
#include "arrow.hpp"
#include "play_controller.hpp"
@ -214,6 +215,42 @@ void attack::draw_hex(const map_location& hex)
}
}
bool attack::validate()
{
DBG_WB <<"validating attack from " << get_dest_hex()
<< " to " << target_hex_ << "\n";
//invalidate target hex to make sure attack indicators are updated
resources::screen->invalidate(get_dest_hex());
resources::screen->invalidate(target_hex_);
if(
// Verify that the unit that planned this attack exists
get_unit()
// Verify that the target hex is still valid
&& target_hex_.valid()
// Verify that the target hex isn't empty
&& resources::units->find(target_hex_) != resources::units->end()
// Verify that the attacking unit has attacks left
&& get_unit()->attacks_left() > 0
// Verify that the attacker and target are enemies
&& (*resources::teams)[get_unit()->side() - 1].is_enemy(resources::units->find(target_hex_)->side())
//@todo: (maybe) verify that the target hex contains the same unit that before,
// comparing for example the unit ID
) {
//All checks pass, so call validate on the superclass
return move::validate();
} else {
set_valid(false);
if(viewer_team() == team_index()) { //< Don't mess with any other team's queue -- only our own
LOG_WB << "Worthless invalid attack detected, adding to actions_to_erase_.\n";
return false;
}
}
return true;
}
config attack::to_config() const
{
config final_cfg = move::to_config();

View file

@ -28,8 +28,6 @@ namespace wb
class attack: public move
{
public:
friend class validate_visitor;
attack(size_t team_index, bool hidden, unit& mover, const map_location& target_hex, int weapon_choice, const pathfind::marked_route& route,
arrow_ptr arrow, fake_unit_ptr fake_unit);
attack(config const&, bool hidden); // For deserialization
@ -51,6 +49,8 @@ public:
map_location const& get_target_hex() const {return target_hex_; }
bool validate();
virtual config to_config() const;
protected:

View file

@ -421,6 +421,90 @@ void move::set_valid(bool valid)
}
}
// This helper function determines whether there are any invalid actions planned for (*itor)->get_unit()
// that occur earlier in viewer_actions() than itor.
static bool no_previous_invalids(side_actions::iterator const& itor)
{
if(itor == viewer_actions()->begin()) {
return true;
}
side_actions::iterator prev_action_of_unit = viewer_actions()->find_last_action_of(*((*itor)->get_unit()),itor-1);
if(prev_action_of_unit == viewer_actions()->end()) {
return true;
}
return (*prev_action_of_unit)->is_valid();
}
move::VALIDITY move::evaluate_validity()
{
if(!(get_source_hex().valid() && get_dest_hex().valid())) {
return WORTHLESS;
}
//Check that the unit still exists in the source hex
unit_map::iterator unit_it;
unit_it = resources::units->find(get_source_hex());
if(unit_it == resources::units->end()) {
return WORTHLESS;
}
//check if the unit in the source hex has the same unit id as before,
//i.e. that it's the same unit
if(unit_id_ != unit_it->id() || unit_underlying_id_ != unit_it->underlying_id()) {
return WORTHLESS;
}
//If the path has at least two hexes (it can have less with the attack subclass), ensure destination hex is free
if(get_route().steps.size() >= 2 && get_visible_unit(get_dest_hex(),resources::teams->at(viewer_team())) != NULL) {
return WORTHLESS;
}
//check that the path is good
if(get_source_hex() != get_dest_hex()) { //skip zero-hex move used by attack subclass
// Mark the plain route to see if the move can still be done in one turn,
// which is always the case for planned moves
pathfind::marked_route checked_route = pathfind::mark_route(get_route().route);
if(checked_route.marks[checked_route.steps.back()].turns != 1) {
return OBSTRUCTED;
}
}
return VALID;
}
bool move::validate()
{
DBG_WB <<"validating move from " << get_source_hex()
<< " to " << get_dest_hex() << "\n";
//invalidate start and end hexes so number display is updated properly
resources::screen->invalidate(get_source_hex());
resources::screen->invalidate(get_dest_hex());
switch(evaluate_validity()) { //< private helper fcn
case VALID:
// Now call the superclass to apply the result of this move to the unit map,
// so that further pathfinding takes it into account.
set_valid(true);
break;
case OBSTRUCTED:
set_valid(false);
break;
case WORTHLESS: {
set_valid(false);
// Erase only if no previous invalid actions are planned for this unit -- otherwise, just mark it invalid.
// Otherwise, we wouldn't be able to keep invalid actions that depend on previous invalid actions.
side_actions::iterator itor = viewer_actions()->get_position_of(shared_from_this());
if(viewer_team() == team_index() && no_previous_invalids(itor)) { //< Don't mess with any other team's queue -- only our own
LOG_WB << "Worthless invalid move detected, adding to actions_to_erase_.\n";
return false;
}
break;
}
}
return true;
}
config move::to_config() const
{
config final_cfg = action::to_config();

View file

@ -33,8 +33,6 @@ namespace wb {
class move : public action
{
public:
friend class validate_visitor;
move(size_t team_index, bool hidden, unit& mover, const pathfind::marked_route& route,
arrow_ptr arrow, fake_unit_ptr fake_unit);
move(config const&, bool hidden); // For deserialization
@ -76,6 +74,7 @@ public:
virtual void set_valid(bool valid);
virtual bool is_valid() const { return valid_; }
bool validate();
virtual config to_config() const;
@ -115,6 +114,9 @@ private:
void hide_fake_unit();
void show_fake_unit();
enum VALIDITY {VALID, OBSTRUCTED, WORTHLESS};
VALIDITY evaluate_validity();
void init();
void update_arrow_style();
boost::scoped_ptr<temporary_unit_mover> mover_;

View file

@ -176,6 +176,46 @@ void recall::draw_hex(map_location const& hex)
}
}
bool recall::validate()
{
DBG_WB << "validating recall on hex " << recall_hex_ << "\n";
//invalidate recall hex so number display is updated properly
resources::screen->invalidate(recall_hex_);
//Check that destination hex is still free
if(resources::units->find(recall_hex_) != resources::units->end()) {
LOG_WB << "Recall set as invalid because target hex is occupied.\n";
set_valid(false);
}
//Check that unit to recall is still in side's recall list
if(is_valid()) {
const std::vector<unit>& recalls = (*resources::teams)[team_index()].recall_list();
if( find_if_matches_id(recalls, temp_unit_->id()) == recalls.end() ) {
set_valid(false);
LOG_WB << " Validate visitor: Planned recall invalid since unit is not in recall list anymore.\n";
}
}
//Check that there is still enough gold to recall this unit
if(is_valid() && (*resources::teams)[team_index()].recall_cost() > (*resources::teams)[team_index()].gold()) {
LOG_WB << "Recall set as invalid, team doesn't have enough gold.\n";
set_valid(false);
}
//Check that there is a leader available to recall this unit
if(is_valid() && !find_recruiter(team_index(),get_recall_hex())) {
LOG_WB << "Recall set as invalid, no leader can recall this unit.\n";
set_valid(false);
}
if(!is_valid()) {
if(viewer_team() == team_index()) { //< Don't mess with any other team's queue -- only our own
LOG_WB << "Invalid recall detected, adding to actions_to_erase_.\n";
return false;
}
}
return true;
}
///@todo Find a better way to serialize unit_ because underlying_id isn't cutting it
config recall::to_config() const
{

View file

@ -32,8 +32,6 @@ public:
recall(config const&, bool hidden); // For deserialization
virtual ~recall();
friend class validate_visitor;
virtual std::ostream& print(std::ostream& s) const;
virtual void accept(visitor& v);
@ -66,6 +64,7 @@ public:
*/
virtual void set_valid(bool valid) { valid_ = valid; }
virtual bool is_valid() const { return valid_; }
bool validate();
virtual config to_config() const;

View file

@ -174,6 +174,46 @@ std::auto_ptr<unit> recruit::create_corresponding_unit()
return result; //ownership gets transferred to returned auto_ptr copy
}
bool recruit::validate()
{
DBG_WB << "validating recruit on hex " << recruit_hex_ << "\n";
//invalidate recruit hex so number display is updated properly
resources::screen->invalidate(recruit_hex_);
//Check that destination hex is still free
if(resources::units->find(recruit_hex_) != resources::units->end()) {
LOG_WB << "Recruit set as invalid because target hex is occupied.\n";
set_valid(false);
}
//Check that unit to recruit is still in side's recruit list
if(is_valid()) {
const std::set<std::string>& recruits = (*resources::teams)[team_index()].recruits();
if(recruits.find(unit_name_) == recruits.end()) {
set_valid(false);
LOG_WB << " Validate visitor: Planned recruit invalid since unit is not in recruit list anymore.\n";
}
}
//Check that there is still enough gold to recruit this unit
if(is_valid() && temp_unit_->cost() > (*resources::teams)[team_index()].gold()) {
LOG_WB << "Recruit set as invalid, team doesn't have enough gold.\n";
set_valid(false);
}
//Check that there is a leader available to recruit this unit
if(is_valid() && !find_recruiter(team_index(),get_recruit_hex())) {
LOG_WB << "Recruit set as invalid, no leader can recruit this unit.\n";
set_valid(false);
}
if(!is_valid()) {
if(viewer_team() == team_index()) { //< Don't mess with any other team's queue -- only our own
LOG_WB << "Invalid recruit detected, adding to actions_to_erase_.\n";
return false;
}
}
return true;
}
config recruit::to_config() const
{
config final_cfg = action::to_config();

View file

@ -36,8 +36,6 @@ public:
recruit(config const&, bool hidden); // For deserialization
virtual ~recruit();
friend class validate_visitor;
virtual std::ostream& print(std::ostream& s) const;
virtual void accept(visitor& v);
@ -70,6 +68,7 @@ public:
*/
virtual void set_valid(bool valid) { valid_ = valid; }
virtual bool is_valid() const { return valid_; }
bool validate();
virtual config to_config() const;

View file

@ -40,137 +40,173 @@
namespace wb
{
std::ostream& operator<<(std::ostream &s, suppose_dead_ptr sup_d)
{
assert(sup_d);
return sup_d->print(s);
}
std::ostream& operator<<(std::ostream &s, suppose_dead_ptr sup_d)
{
assert(sup_d);
return sup_d->print(s);
}
std::ostream& operator<<(std::ostream &s, suppose_dead_const_ptr sup_d)
{
assert(sup_d);
return sup_d->print(s);
}
std::ostream& operator<<(std::ostream &s, suppose_dead_const_ptr sup_d)
{
assert(sup_d);
return sup_d->print(s);
}
std::ostream& suppose_dead::print(std::ostream &s) const
{
s << "Suppose-dead for unit " << get_unit()->name() << " [" << get_unit()->id() << "] "
<< "at (" << loc_ << ")";
return s;
}
std::ostream& suppose_dead::print(std::ostream &s) const
{
s << "Suppose-dead for unit " << get_unit()->name() << " [" << get_unit()->id() << "] "
<< "at (" << loc_ << ")";
return s;
}
suppose_dead::suppose_dead(size_t team_index, bool hidden, unit& curr_unit, map_location const& loc)
: action(team_index,hidden)
, unit_underlying_id_(curr_unit.underlying_id())
, unit_id_(curr_unit.id())
, loc_(loc)
, valid_(true)
{
this->init();
}
suppose_dead::suppose_dead(size_t team_index, bool hidden, unit& curr_unit, map_location const& loc)
: action(team_index,hidden)
, unit_underlying_id_(curr_unit.underlying_id())
, unit_id_(curr_unit.id())
, loc_(loc)
, valid_(true)
{
this->init();
}
suppose_dead::suppose_dead(config const& cfg, bool hidden)
: action(cfg,hidden)
, unit_underlying_id_(0)
, unit_id_()
, loc_(cfg.child("loc_")["x"],cfg.child("loc_")["y"])
, valid_(true)
{
// Construct and validate unit_
unit_map::iterator unit_itor = resources::units->find(cfg["unit_"]);
if(unit_itor == resources::units->end())
throw action::ctor_err("suppose_dead: Invalid underlying_id");
suppose_dead::suppose_dead(config const& cfg, bool hidden)
: action(cfg,hidden)
, unit_underlying_id_(0)
, unit_id_()
, loc_(cfg.child("loc_")["x"],cfg.child("loc_")["y"])
, valid_(true)
{
// Construct and validate unit_
unit_map::iterator unit_itor = resources::units->find(cfg["unit_"]);
if(unit_itor == resources::units->end())
throw action::ctor_err("suppose_dead: Invalid underlying_id");
unit_underlying_id_ = unit_itor->underlying_id();
unit_id_ = unit_itor->id();
unit_underlying_id_ = unit_itor->underlying_id();
unit_id_ = unit_itor->id();
this->init();
}
this->init();
}
void suppose_dead::init()
{
void suppose_dead::init()
{
resources::screen->invalidate(loc_);
}
suppose_dead::~suppose_dead()
{
//invalidate hex so that skull indicator is properly cleared
if(resources::screen)
resources::screen->invalidate(loc_);
}
unit* suppose_dead::get_unit() const
{
unit_map::iterator itor = resources::units->find(unit_underlying_id_);
if (itor.valid())
return &*itor;
else
return NULL;
}
void suppose_dead::accept(visitor& v)
{
v.visit(shared_from_this());
}
void suppose_dead::execute(bool& success, bool& complete)
{success = false; complete = true;}
void suppose_dead::apply_temp_modifier(unit_map& unit_map)
{
// Remove the unit
unit const* removed_unit = unit_map.extract(loc_);
DBG_WB << "Suppose dead: Temporarily removing unit " << removed_unit->name() << " [" << removed_unit->id()
<< "] from (" << loc_ << ")\n";
// Just check to make sure we removed the unit we expected to remove
assert(get_unit() == removed_unit);
}
void suppose_dead::remove_temp_modifier(unit_map& unit_map)
{
// Just check to make sure the hex is empty
unit_map::iterator unit_it = resources::units->find(loc_);
assert(unit_it == resources::units->end());
// Restore the unit
unit_map.insert(get_unit());
}
void suppose_dead::draw_hex(const map_location& hex)
{
if(hex == loc_) //add symbol to hex
{
//@todo: Possibly use a different layer
const display::tdrawing_layer layer = display::LAYER_ARROWS;
int xpos = resources::screen->get_location_x(loc_);
int ypos = resources::screen->get_location_y(loc_);
resources::screen->drawing_buffer_add(layer, loc_, xpos, ypos,
image::get_image("whiteboard/suppose_dead.png", image::SCALED_TO_HEX));
}
}
void suppose_dead::set_valid(bool valid)
{
valid_ = valid;
}
bool suppose_dead::validate()
{
DBG_WB << "validating suppose_dead on hex " << loc_ << "\n";
//invalidate suppose-dead hex so number display is updated properly
resources::screen->invalidate(loc_);
if(!get_source_hex().valid()) {
set_valid(false);
}
suppose_dead::~suppose_dead()
{
//invalidate hex so that skull indicator is properly cleared
if(resources::screen)
resources::screen->invalidate(loc_);
}
unit_map::const_iterator unit_it;
//Check that the unit still exists in the source hex
if(is_valid()) {
unit_it = resources::units->find(get_source_hex());
unit* suppose_dead::get_unit() const
{
unit_map::iterator itor = resources::units->find(unit_underlying_id_);
if (itor.valid())
return &*itor;
else
return NULL;
}
void suppose_dead::accept(visitor& v)
{
v.visit(shared_from_this());
}
void suppose_dead::execute(bool& success, bool& complete)
{success = false; complete = true;}
void suppose_dead::apply_temp_modifier(unit_map& unit_map)
{
// Remove the unit
unit const* removed_unit = unit_map.extract(loc_);
DBG_WB << "Suppose dead: Temporarily removing unit " << removed_unit->name() << " [" << removed_unit->id()
<< "] from (" << loc_ << ")\n";
// Just check to make sure we removed the unit we expected to remove
assert(get_unit() == removed_unit);
}
void suppose_dead::remove_temp_modifier(unit_map& unit_map)
{
// Just check to make sure the hex is empty
unit_map::iterator unit_it = resources::units->find(loc_);
assert(unit_it == resources::units->end());
// Restore the unit
unit_map.insert(get_unit());
}
void suppose_dead::draw_hex(const map_location& hex)
{
if(hex == loc_) //add symbol to hex
{
//@todo: Possibly use a different layer
const display::tdrawing_layer layer = display::LAYER_ARROWS;
int xpos = resources::screen->get_location_x(loc_);
int ypos = resources::screen->get_location_y(loc_);
resources::screen->drawing_buffer_add(layer, loc_, xpos, ypos,
image::get_image("whiteboard/suppose_dead.png", image::SCALED_TO_HEX));
if(unit_it == resources::units->end()) {
set_valid(false);
}
}
void suppose_dead::set_valid(bool valid)
{
valid_ = valid;
//check if the unit in the source hex has the same unit id as before,
//i.e. that it's the same unit
if(is_valid() && unit_id_ != unit_it->id()) {
set_valid(false);
}
config suppose_dead::to_config() const
{
config final_cfg = action::to_config();
final_cfg["type"]="suppose_dead";
final_cfg["unit_"]=static_cast<int>(unit_underlying_id_);
final_cfg["unit_id_"]=unit_id_;
config loc_cfg;
loc_cfg["x"]=loc_.x;
loc_cfg["y"]=loc_.y;
final_cfg.add_child("loc_",loc_cfg);
return final_cfg;
if(!is_valid()) {
if(viewer_team() == team_index()) { //< Don't mess with any other team's queue -- only our own
LOG_WB << "Invalid suppose_dead detected, adding to actions_to_erase_.\n";
return false;
}
}
return true;
}
config suppose_dead::to_config() const
{
config final_cfg = action::to_config();
final_cfg["type"]="suppose_dead";
final_cfg["unit_"]=static_cast<int>(unit_underlying_id_);
final_cfg["unit_id_"]=unit_id_;
config loc_cfg;
loc_cfg["x"]=loc_.x;
loc_cfg["y"]=loc_.y;
final_cfg.add_child("loc_",loc_cfg);
return final_cfg;
}
} // end namespace wb

View file

@ -24,70 +24,65 @@
namespace wb {
/**
* A planned action that temporarily removes a unit from the map
* for planning purposes
*/
class suppose_dead
: public action
{
friend class validate_visitor;
/**
* A planned action that temporarily removes a unit from the map
* for planning purposes
*/
class suppose_dead: public action
{
public:
suppose_dead(size_t team_index, bool hidden, unit& curr_unit, map_location const& loc);
suppose_dead(config const&, bool hidden); // For deserialization
virtual ~suppose_dead();
public:
suppose_dead(size_t team_index, bool hidden, unit& curr_unit, map_location const& loc);
suppose_dead(config const&, bool hidden); // For deserialization
virtual ~suppose_dead();
/** Return the unit targeted by this action. Null if unit doesn't exist. */
virtual unit* get_unit() const;
/** @return null pointer */
virtual fake_unit_ptr get_fake_unit() { return fake_unit_ptr(); }
/** Return the location at which this action was planned. */
virtual map_location get_source_hex() const { return loc_; }
/** Return the unit targeted by this action. Null if unit doesn't exist. */
virtual unit* get_unit() const;
/** @return null pointer */
virtual fake_unit_ptr get_fake_unit() { return fake_unit_ptr(); }
/** Return the location at which this action was planned. */
virtual map_location get_source_hex() const { return loc_; }
virtual std::ostream& print(std::ostream& s) const;
// Inherits from action
// {
virtual std::ostream& print(std::ostream& s) const;
virtual void accept(visitor &v);
virtual void accept(visitor &v);
virtual void execute(bool& success, bool& complete);
virtual void execute(bool& success, bool& complete);
/** Applies temporarily the result of this action to the specified unit map. */
virtual void apply_temp_modifier(unit_map& unit_map);
/** Removes the result of this action from the specified unit map. */
virtual void remove_temp_modifier(unit_map& unit_map);
/** Applies temporarily the result of this action to the specified unit map. */
virtual void apply_temp_modifier(unit_map& unit_map);
/** Removes the result of this action from the specified unit map. */
virtual void remove_temp_modifier(unit_map& unit_map);
/** Gets called by display when drawing a hex, to allow actions to draw to the screen. */
virtual void draw_hex(const map_location& hex);
/** Gets called by display when drawing a hex, to allow actions to draw to the screen. */
virtual void draw_hex(const map_location& hex);
virtual map_location get_numbering_hex() const { return loc_; }
virtual map_location get_numbering_hex() const { return loc_; }
virtual void set_valid(bool valid);
virtual bool is_valid() const { return valid_; }
bool validate();
virtual void set_valid(bool valid);
virtual bool is_valid() const { return valid_; }
virtual config to_config() const;
virtual config to_config() const;
// } End Inherits from action
protected:
protected:
boost::shared_ptr<suppose_dead> shared_from_this() {
return boost::static_pointer_cast<suppose_dead>(action::shared_from_this());
}
boost::shared_ptr<suppose_dead> shared_from_this() {
return boost::static_pointer_cast<suppose_dead>(action::shared_from_this());
}
size_t unit_underlying_id_;
std::string unit_id_;
map_location loc_;
size_t unit_underlying_id_;
std::string unit_id_;
map_location loc_;
bool valid_;
bool valid_;
private:
void init();
};
private:
void init();
};
/** Dumps a suppose_dead on a stream, for debug purposes. */
std::ostream &operator<<(std::ostream &s, suppose_dead_ptr sup_d);
std::ostream &operator<<(std::ostream &s, suppose_dead_const_ptr sup_d);
/** Dumps a suppose_dead on a stream, for debug purposes. */
std::ostream &operator<<(std::ostream &s, suppose_dead_ptr sup_d);
std::ostream &operator<<(std::ostream &s, suppose_dead_const_ptr sup_d);
} // end namespace wb
#endif /* WB_SUPPOSE_DEAD_HPP_ */

View file

@ -57,284 +57,26 @@ bool validate_visitor::validate_actions()
//FIXME: by reverse iterating this can be done in a more efficiant way
// by using the iterator returned by remove_action it could even be done in visit_all above
if (!actions_to_erase_.empty())
{
if (!actions_to_erase_.empty()) {
int side_actions_size_before = viewer_actions_.size();
LOG_WB << "Erasing " << actions_to_erase_.size() << " invalid actions.\n";
BOOST_FOREACH(action_ptr action, actions_to_erase_)
{
BOOST_FOREACH(action_ptr action, actions_to_erase_) {
viewer_actions_.remove_action(viewer_actions_.get_position_of(action), false);
}
assert(side_actions_size_before - viewer_actions_.size() == actions_to_erase_.size());
actions_to_erase_.clear();
return false;
}
else
{
} else {
return true;
}
}
/* private */
validate_visitor::VALIDITY validate_visitor::evaluate_move_validity(move_ptr m_ptr)
{
move& m = *m_ptr;
if (!(m.get_source_hex().valid() && m.get_dest_hex().valid()))
return WORTHLESS;
//Check that the unit still exists in the source hex
unit_map::iterator unit_it;
unit_it = resources::units->find(m.get_source_hex());
if (unit_it == resources::units->end())
return WORTHLESS;
//check if the unit in the source hex has the same unit id as before,
//i.e. that it's the same unit
if (m.unit_id_ != unit_it->id() || m.unit_underlying_id_ != unit_it->underlying_id())
return WORTHLESS;
//If the path has at least two hexes (it can have less with the attack subclass), ensure destination hex is free
if (m.get_route().steps.size() >= 2 && get_visible_unit(m.get_dest_hex(),resources::teams->at(viewer_team())) != NULL) {
return WORTHLESS;
}
//check that the path is good
if (m.get_source_hex() != m.get_dest_hex()) //skip zero-hex move used by attack subclass
{
// Mark the plain route to see if the move can still be done in one turn,
// which is always the case for planned moves
pathfind::marked_route checked_route = pathfind::mark_route(m.get_route().route);
if (checked_route.marks[checked_route.steps.back()].turns != 1)
return OBSTRUCTED;
}
return VALID;
}
// This helper function determines whether there are any invalid actions planned for (*itor)->get_unit()
// that occur earlier in viewer_actions_ than itor.
/* private */
bool validate_visitor::no_previous_invalids(side_actions::iterator const& itor)
{
if(itor == viewer_actions_.begin())
return true;
side_actions::iterator prev_action_of_unit = viewer_actions_.find_last_action_of(*((*itor)->get_unit()),itor-1);
if(prev_action_of_unit == viewer_actions_.end())
return true;
return (*prev_action_of_unit)->is_valid();
}
void validate_visitor::visit(move_ptr move)
{
DBG_WB <<"visiting move from " << move->get_source_hex()
<< " to " << move->get_dest_hex() << "\n";
//invalidate start and end hexes so number display is updated properly
resources::screen->invalidate(move->get_source_hex());
resources::screen->invalidate(move->get_dest_hex());
switch(evaluate_move_validity(move)) //< private helper fcn
{
case VALID:
// Now call the superclass to apply the result of this move to the unit map,
// so that further pathfinding takes it into account.
move->set_valid(true);
break;
case OBSTRUCTED:
move->set_valid(false);
break;
case WORTHLESS:
move->set_valid(false);
// Erase only if no previous invalid actions are planned for this unit -- otherwise, just mark it invalid.
// Otherwise, we wouldn't be able to keep invalid actions that depend on previous invalid actions.
if(viewer_team() == move->team_index() //< Don't mess with any other team's queue -- only our own
&& no_previous_invalids(arg_itor_)) //< private helper fcn
{
LOG_WB << "Worthless invalid move detected, adding to actions_to_erase_.\n";
actions_to_erase_.insert(move);
}
break;
}
}
void validate_visitor::visit(attack_ptr attack)
{
DBG_WB <<"visiting attack from " << attack->get_dest_hex()
<< " to " << attack->target_hex_ << "\n";
//invalidate target hex to make sure attack indicators are updated
resources::screen->invalidate(attack->get_dest_hex());
resources::screen->invalidate(attack->target_hex_);
if (
// Verify that the unit that planned this attack exists
attack->get_unit()
// Verify that the target hex is still valid
&& attack->target_hex_.valid()
// Verify that the target hex isn't empty
&& resources::units->find(attack->target_hex_) != resources::units->end()
// Verify that the attacking unit has attacks left
&& attack->get_unit()->attacks_left() > 0
// Verify that the attacker and target are enemies
&& (*resources::teams)[attack->get_unit()->side() - 1].is_enemy(resources::units->find(attack->target_hex_)->side())
//@todo: (maybe) verify that the target hex contains the same unit that before,
// comparing for example the unit ID
)
{
//All checks pass, so call the visitor on the superclass
visit(boost::static_pointer_cast<move>(attack));
}
else
{
attack->set_valid(false);
if (viewer_team() == attack->team_index()) //< Don't mess with any other team's queue -- only our own
{
LOG_WB << "Worthless invalid attack detected, adding to actions_to_erase_.\n";
actions_to_erase_.insert(attack);
}
}
}
void validate_visitor::visit(recruit_ptr recruit)
{
DBG_WB << "visiting recruit on hex " << recruit->recruit_hex_ << "\n";
//invalidate recruit hex so number display is updated properly
resources::screen->invalidate(recruit->recruit_hex_);
size_t team_index = recruit->team_index();
//Check that destination hex is still free
if(resources::units->find(recruit->recruit_hex_) != resources::units->end())
{
LOG_WB << "Recruit set as invalid because target hex is occupied.\n";
recruit->set_valid(false);
}
//Check that unit to recruit is still in side's recruit list
if (recruit->is_valid())
{
const std::set<std::string>& recruits = (*resources::teams)[team_index].recruits();
if (recruits.find(recruit->unit_name_) == recruits.end())
{
recruit->set_valid(false);
LOG_WB << " Validate visitor: Planned recruit invalid since unit is not in recruit list anymore.\n";
}
}
//Check that there is still enough gold to recruit this unit
if (recruit->is_valid() && recruit->temp_unit_->cost() > (*resources::teams)[team_index].gold())
{
LOG_WB << "Recruit set as invalid, team doesn't have enough gold.\n";
recruit->set_valid(false);
}
//Check that there is a leader available to recruit this unit
if(recruit->is_valid() && !find_recruiter(recruit->team_index(),recruit->get_recruit_hex()))
{
LOG_WB << "Recruit set as invalid, no leader can recruit this unit.\n";
recruit->set_valid(false);
}
if(!recruit->is_valid())
{
if(viewer_team() == recruit->team_index()) //< Don't mess with any other team's queue -- only our own
{
LOG_WB << "Invalid recruit detected, adding to actions_to_erase_.\n";
actions_to_erase_.insert(recruit);
}
}
}
void validate_visitor::visit(recall_ptr recall)
{
DBG_WB << "visiting recall on hex " << recall->recall_hex_ << "\n";
//invalidate recall hex so number display is updated properly
resources::screen->invalidate(recall->recall_hex_);
size_t team_index = recall->team_index();
//Check that destination hex is still free
if(resources::units->find(recall->recall_hex_) != resources::units->end())
{
LOG_WB << "Recall set as invalid because target hex is occupied.\n";
recall->set_valid(false);
}
//Check that unit to recall is still in side's recall list
if (recall->is_valid())
{
const std::vector<unit>& recalls = (*resources::teams)[team_index].recall_list();
if ( find_if_matches_id(recalls, recall->temp_unit_->id()) == recalls.end() )
{
recall->set_valid(false);
LOG_WB << " Validate visitor: Planned recall invalid since unit is not in recall list anymore.\n";
}
}
//Check that there is still enough gold to recall this unit
if (recall->is_valid()
&& (*resources::teams)[team_index].recall_cost() > (*resources::teams)[team_index].gold())
{
LOG_WB << "Recall set as invalid, team doesn't have enough gold.\n";
recall->set_valid(false);
}
//Check that there is a leader available to recall this unit
if(recall->is_valid() && !find_recruiter(recall->team_index(),recall->get_recall_hex()))
{
LOG_WB << "Recall set as invalid, no leader can recall this unit.\n";
recall->set_valid(false);
}
if(!recall->is_valid())
{
if(viewer_team() == recall->team_index()) //< Don't mess with any other team's queue -- only our own
{
LOG_WB << "Invalid recall detected, adding to actions_to_erase_.\n";
actions_to_erase_.insert(recall);
}
}
}
void validate_visitor::visit(suppose_dead_ptr sup_d)
{
DBG_WB << "visiting suppose_dead on hex " << sup_d->loc_ << "\n";
//invalidate suppose-dead hex so number display is updated properly
resources::screen->invalidate(sup_d->loc_);
if(!sup_d->get_source_hex().valid())
sup_d->set_valid(false);
unit_map::const_iterator unit_it;
//Check that the unit still exists in the source hex
if(sup_d->valid_)
{
unit_it = resources::units->find(sup_d->get_source_hex());
if(unit_it == resources::units->end())
{
sup_d->set_valid(false);
}
}
//check if the unit in the source hex has the same unit id as before,
//i.e. that it's the same unit
if(sup_d->valid_ && sup_d->unit_id_ != unit_it->id())
{
sup_d->set_valid(false);
}
if(!sup_d->valid_)
{
if(viewer_team() == sup_d->team_index()) //< Don't mess with any other team's queue -- only our own
{
LOG_WB << "Invalid suppose_dead detected, adding to actions_to_erase_.\n";
actions_to_erase_.insert(sup_d);
}
}
}
void validate_visitor::helper::validate(side_actions::iterator const& itor)
{
parent_.arg_itor_ = itor;
(*itor)->accept(parent_);
if((*itor)->validate()) {
parent_.actions_to_erase_.insert(*itor);
}
}
}//end namespace wb

View file

@ -36,7 +36,6 @@ namespace wb
* * Some invalid actions are deleted.
*/
class validate_visitor
: private visitor
{
public:
explicit validate_visitor(unit_map& unit_map);
@ -47,17 +46,6 @@ public:
bool validate_actions();
private:
virtual void visit(move_ptr move);
virtual void visit(attack_ptr attack);
virtual void visit(recruit_ptr recruit);
virtual void visit(recall_ptr recall);
virtual void visit(suppose_dead_ptr sup_d);
enum VALIDITY {VALID, OBSTRUCTED, WORTHLESS};
VALIDITY evaluate_move_validity(move_ptr);
bool no_previous_invalids(side_actions::iterator const&);
struct helper: public mapbuilder
{
helper(unit_map& umap, validate_visitor& parent)