Enable recruit/recall context menu options when right-clicking on a leader.

In this case, the right-click is interpreted as "this unit recruits",
rather than "recruit here".
This commit is contained in:
J. Tyne 2012-08-29 11:49:28 +00:00
parent e946b83cd7
commit ad6430f28f
5 changed files with 187 additions and 63 deletions

View file

@ -9,6 +9,9 @@ Version 1.11.0+svn:
* Updated translations:
* User interface:
* Healing animations are now played when poison is cured.
* The recruit and recall commands are restored when right-clicking on a
leader, but with new semantics -- only that leader's recruits/recalls will
be presented as options.
* Miscellaneous and bug fixes:
* Fix invalid memory access crash resulting from deleting all saved games
in the Load Game dialog

View file

@ -13,6 +13,9 @@ Version 1.11.0+svn:
* User interface:
* Healing animations are now played when poison is cured.
* The recruit and recall commands are restored when right-clicking on a
leader, but with new semantics -- only that leader's recruits/recalls will
be presented as options.
* Miscellaneous and bug fixes:
* Fix invalid memory access crash resulting from deleting all saved games

View file

@ -250,6 +250,33 @@ void unit_creator::post_create(const map_location &loc, const unit &new_unit, bo
}
/**
* Checks to see if a leader at @a leader_loc could recruit somewhere.
* This takes into account terrain, shroud (for side @a side), and the presence
* of visible units.
* The behavior for an invalid @a side is subject to change for future needs.
*/
bool can_recruit_from(const map_location& leader_loc, int side)
{
const gamemap& map = *resources::game_map;
if( !map.is_keep(leader_loc) )
return false;
if ( side < 1 || resources::teams == NULL ||
resources::teams->size() < static_cast<size_t>(side) ) {
// Invalid side specified.
// Currently this cannot happen, but it could conceivably be used in
// the future to request that shroud and visibility be ignored. Until
// that comes to pass, just return.
return false;
}
return pathfind::find_vacant_tile(leader_loc, pathfind::VACANT_CASTLE, NULL,
&(*resources::teams)[side-1])
!= map_location::null_location;
}
/**
* Checks to see if a leader at @a leader_loc could recruit on @a recruit_loc.
* This takes into account terrain, shroud (for side @a side), and whether or
@ -294,59 +321,139 @@ bool can_recruit_on(const map_location& leader_loc, const map_location& recruit_
const std::set<std::string> get_recruits_for_location(int side, const map_location &recruit_loc)
{
const team & current_team = (*resources::teams)[side -1];
LOG_NG << "getting recruit list for side " << side << " at location " << recruit_loc << "\n";
const std::set<std::string>& recruit_list = (*resources::teams)[side -1].recruits();
std::set<std::string> local_result;
std::set<std::string> global_result;
unit_map::const_iterator u = resources::units->begin(),
u_end = resources::units->end();
bool leader_in_place = false;
bool recruit_loc_is_castle = resources::game_map->is_castle(recruit_loc);
bool allow_local = resources::game_map->is_castle(recruit_loc);
for(; u != u_end; ++u) {
//Only consider leaders on this side.
if (!(u->can_recruit() && u->side() == side))
continue;
// Check if the leader is on a connected keep.
if ( can_recruit_on(*u, recruit_loc) ) {
leader_in_place= true;
local_result.insert(u->recruits().begin(), u->recruits().end());
} else
global_result.insert(u->recruits().begin(), u->recruits().end());
// Check for a leader at recruit_loc (means we are recruiting from there,
// rather than to there).
unit_map::const_iterator find_it = resources::units->find(recruit_loc);
if ( find_it != u_end ) {
if ( find_it->can_recruit() && find_it->side() == side &&
resources::game_map->is_keep(recruit_loc) )
{
// We have been requested to get the recruit list for this
// particular leader.
leader_in_place = true;
local_result.insert(find_it->recruits().begin(),
find_it->recruits().end());
}
else if ( find_it->is_visible_to_team(current_team, false) )
{
// This hex is visibly occupied, so we cannot recruit here.
allow_local = false;
}
}
bool global = !(recruit_loc_is_castle && leader_in_place);
if ( !leader_in_place ) {
// Check all leaders for their ability to recruit here.
for( ; u != u_end; ++u ) {
// Only consider leaders on this side.
if ( !(u->can_recruit() && u->side() == side) )
continue;
if (global)
global_result.insert(recruit_list.begin(),recruit_list.end());
else if (leader_in_place)
local_result.insert(recruit_list.begin(),recruit_list.end());
// Check if the leader is on a connected keep.
if ( allow_local && can_recruit_on(*u, recruit_loc) ) {
leader_in_place= true;
local_result.insert(u->recruits().begin(), u->recruits().end());
}
else if ( !leader_in_place )
global_result.insert(u->recruits().begin(), u->recruits().end());
}
}
return global ? global_result : local_result;
// Determine which result set to use.
std::set<std::string> & result = leader_in_place ? local_result : global_result;
// Add the team-wide recruit list.
const std::set<std::string>& recruit_list = current_team.recruits();
result.insert(recruit_list.begin(), recruit_list.end());
return result;
}
const std::vector<const unit*> get_recalls_for_location(int side, const map_location &recall_loc) {
/**
* Adds to @a result those units that @a leader (assumed a leader) can recall.
* If @a already_added is supplied, it contains the underlying IDs of units
* that can be skipped (because they are already in @a result), and the
* underlying ID of units added to @a result will be added to @a already_added.
*/
static void add_leader_filtered_recalls(const unit & leader,
std::vector<const unit*> & result,
std::set<size_t> * already_added = NULL)
{
const team& leader_team = (*resources::teams)[leader.side()-1];
const std::vector<unit>& recall_list = leader_team.recall_list();
const std::string& save_id = leader_team.save_id();
BOOST_FOREACH(const unit& recall_unit, recall_list)
{
// Do not add a unit twice.
size_t underlying_id = recall_unit.underlying_id();
if ( !already_added || already_added->count(underlying_id) == 0 )
{
// Only units that match the leader's recall filter are valid.
scoped_recall_unit this_unit("this_unit", save_id, &recall_unit - &recall_list[0]);
if ( recall_unit.matches_filter(vconfig(leader.recall_filter()), map_location::null_location) )
{
result.push_back(&recall_unit);
if ( already_added != NULL )
already_added->insert(underlying_id);
}
}
}
}
const std::vector<const unit*> get_recalls_for_location(int side, const map_location &recall_loc)
{
LOG_NG << "getting recall list for side " << side << " at location " << recall_loc << "\n";
const team& t = (*resources::teams)[side-1];
const std::vector<unit>& recall_list = t.recall_list();
std::vector<const unit*> result;
/*
* We have two use cases:
* 1. A castle tile is highlighted, we only present the units recallable there.
* 2. A non castle tile is highlighted, we present all units in the recall list.
* We have three use cases:
* 1. An empty castle tile is highlighted; we return only the units recallable there.
* 2. A leader on a keep is highlighted; we return only the units recallable by that leader.
* 3. Otherwise, we return all units in the recall list.
*/
bool leader_in_place = false;
bool recall_loc_is_castle = resources::game_map->is_castle(recall_loc);
bool allow_local = resources::game_map->is_castle(recall_loc);
if (recall_loc_is_castle) {
// Check for a leader at recall_loc (means we are recalling from there,
// rather than to there).
unit_map::const_iterator find_it = resources::units->find(recall_loc);
if ( find_it != resources::units->end() ) {
if ( find_it->can_recruit() && find_it->side() == side &&
resources::game_map->is_keep(recall_loc) )
{
// We have been requested to get the recalls for this
// particular leader.
add_leader_filtered_recalls(*find_it, result);
return result;
}
else if ( find_it->is_visible_to_team((*resources::teams)[side-1], false) )
{
// This hex is visibly occupied, so we cannot recall here.
allow_local = false;
}
}
if ( allow_local )
{
unit_map::const_iterator u = resources::units->begin(),
u_end = resources::units->end();
std::set<size_t> valid_local_recalls;
@ -357,28 +464,18 @@ const std::vector<const unit*> get_recalls_for_location(int side, const map_loca
continue;
// Check if the leader is on a connected keep.
if ( can_recruit_on(*u, recall_loc) )
leader_in_place= true;
else continue;
if ( !can_recruit_on(*u, recall_loc) )
continue;
leader_in_place= true;
BOOST_FOREACH(const unit& recall_unit, recall_list)
{
//Only units which match the leaders recall filter are valid.
scoped_recall_unit this_unit("this_unit", t.save_id(), &recall_unit - &recall_list[0]);
if (!(recall_unit.matches_filter(vconfig(u->recall_filter()), map_location::null_location)))
continue;
//Do not add a unit twice.
if (valid_local_recalls.find(recall_unit.underlying_id())
== valid_local_recalls.end()) {
valid_local_recalls.insert(recall_unit.underlying_id());
result.push_back(&recall_unit);
}
}
add_leader_filtered_recalls(*u, result, &valid_local_recalls);
}
}
if (!(recall_loc_is_castle && leader_in_place)) {
if ( !leader_in_place )
{
// Return the full recall list.
const std::vector<unit>& recall_list = (*resources::teams)[side-1].recall_list();
BOOST_FOREACH(const unit &recall, recall_list)
{
result.push_back(&recall);

View file

@ -68,6 +68,13 @@ private:
};
/// Checks to see if a leader at @a leader_loc could recruit somewhere.
bool can_recruit_from(const map_location& leader_loc, int side);
/// Checks to see if @a leader (assumed a leader) can recruit somewhere.
/// This takes into account terrain, shroud, and the presence of visible units.
inline bool can_recruit_from(const unit& leader)
{ return can_recruit_from(leader.get_location(), leader.side()); }
/// Checks to see if a leader at @a leader_loc could recruit on @a recruit_loc.
bool can_recruit_on(const map_location& leader_loc, const map_location& recruit_loc, int side);
/// Checks to see if @a leader (assumed a leader) can recruit on @a recruit_loc.
@ -107,21 +114,21 @@ std::string find_recruit_location(const int side, map_location &recruit_location
std::string find_recall_location(const int side, map_location& recall_location, map_location& recall_from, const unit &unit_recall);
/**
* Get's the recruitable units from a side's leaders' personal recruit lists who can recruit on a specific hex field.
* Gets the recruitable units from a side's leaders' personal recruit lists who can recruit on or from a specific hex field.
* @param side of the leaders to search for their personal recruit lists.
* @param recruit_location the hex field being part of the castle the player wants to recruit on.
* @return a set of units that can be recruited by leaders on a keep connected by castle tiles with @a recruit_location.
* @param recruit_location the hex field being part of the castle the player wants to recruit on or from.
* @return a set of units that can be recruited either by the leader on @a recruit_location or by leaders on keeps connected by castle tiles to @a recruit_location.
*/
const std::set<std::string> get_recruits_for_location(int side, const map_location &recruit_location);
/**
* Get's the recruitable units from a side's leaders' personal recruit lists who can recruit on a specific hex field.
* If no leader is able to recruit on the given location the full recall list of the side is returned.
* @param side of the leaders to search for their personal recruit lists.
* @param recruit_location the hex field being part of the castle the player wants to recruit on.
* @return a set of units that can be recruited by @a side on @a recall_loc or the full recall list of @a side.
* Gets the recallable units for a side, restricted by that side's leaders' personal abilities to recall on or from a specific hex field.
* If no leader is able to recall on or from the given location, the full recall list of the side is returned.
* @param side of the leaders to search for their personal recall filters.
* @param recall_loc the hex field being part of the castle the player wants to recruit on or from.
* @return a set of units that can be recalled by @a side on (or from) @a recall_loc or the full recall list of @a side.
*/
const std::vector<const unit*> get_recalls_for_location(int side, const map_location &recruit_location);
const std::vector<const unit*> get_recalls_for_location(int side, const map_location &recall_loc);
/**
* Place a unit into the game.

View file

@ -1244,16 +1244,30 @@ bool play_controller::in_context_menu(hotkey::HOTKEY_COMMAND command) const
case hotkey::HOTKEY_RECRUIT:
case hotkey::HOTKEY_REPEAT_RECRUIT:
case hotkey::HOTKEY_RECALL: {
wb::future_map future; //< lasts until method returns.
// last_hex_ is set by mouse_events::mouse_motion
// Enable recruit/recall on castle/keep tiles
for(unit_map::const_iterator leader = units_.begin();
leader != units_.end();++leader) {
if (leader->can_recruit() &&
leader->side() == resources::screen->viewing_side() &&
can_recruit_on(*leader, mouse_handler_.get_last_hex()))
return true;
}
const map_location & last_hex = mouse_handler_.get_last_hex();
const int viewing_side = resources::screen->viewing_side();
// A quick check to save us having to create the future map and
// possibly loop through all units.
if ( !resources::game_map->is_keep(last_hex) &&
!resources::game_map->is_castle(last_hex) )
return false;
wb::future_map future; //< lasts until method returns.
unit_map::const_iterator leader = units_.find(last_hex);
if ( leader != units_.end() )
return leader->can_recruit() && leader->side() == viewing_side &&
can_recruit_from(*leader);
else
// Look for a leader who can recruit on last_hex.
for ( leader = units_.begin(); leader != units_.end(); ++leader) {
if ( leader->can_recruit() && leader->side() == viewing_side &&
can_recruit_on(*leader, last_hex) )
return true;
}
// No leader found who can recruit at last_hex.
return false;
}
default: