Refactor calculate_healing().

This should be easier for humans to read and computers to execute.
This commit is contained in:
J. Tyne 2012-08-26 23:38:25 +00:00
parent ff3dd7f60f
commit 86fbdad8fb

View File

@ -47,215 +47,277 @@ void reset_resting(unit_map& units, int side)
}
}
/* Contains all the data used to display healing */
struct unit_healing_struct {
unit *healed;
std::vector<unit *> healers;
int healing;
};
namespace {
// Contains the data needed to display healing.
struct heal_unit {
heal_unit(unit &patient, const std::vector<unit *> &treaters, int healing) :
healed(patient),
healers(treaters),
amount(healing)
{}
unit & healed;
std::vector<unit *> healers;
int amount;
};
// Keep these ordered from weakest cure to strongest cure.
enum POISON_STATUS { POISON_NORMAL, POISON_SLOW , POISON_CURE };
/**
* Converts a string into its corresponding POISON_STATUS.
*/
POISON_STATUS poison_status(const std::string & status)
{
if ( status == "cured" )
return POISON_CURE;
if ( status == "slowed" )
return POISON_SLOW;
// No other states recognized.
return POISON_NORMAL;
}
/**
* Determines if @a patient is affected by anything that impacts poison.
* If cured by a unit, that unit is returned through (added to) @a healers.
*/
POISON_STATUS poison_progress(int side, const unit & patient,
std::vector<unit *> & healers)
{
const std::vector<team> &teams = *resources::teams;
unit_map &units = *resources::units;
POISON_STATUS curing = POISON_NORMAL;
if ( patient.side() == side )
{
// Village healing?
if ( resources::game_map->gives_healing(patient.get_location()) )
return POISON_CURE;
// Regeneration?
BOOST_FOREACH (const unit_ability & regen,
patient.get_abilities("regenerate"))
{
curing = std::max(curing, poison_status((*regen.first)["poison"]));
if ( curing == POISON_CURE )
// This is as good as it gets.
return POISON_CURE;
}
}
// Look through the healers to find a curer.
unit_map::iterator curer = units.end();
// Assumed: curing is not POISON_CURE at the start of any iteration.
BOOST_FOREACH (const unit_ability & heal, patient.get_abilities("heals"))
{
POISON_STATUS this_cure = poison_status((*heal.first)["poison"]);
if ( this_cure <= curing )
// We already recorded this level of curing.
continue;
// NOTE: At this point, this_cure will be *_SLOW or *_CURE.
unit_map::iterator cure_it = units.find(heal.second);
assert(cure_it != units.end());
const int cure_side = cure_it->side();
// Healers on the current side can cure poison (for any side).
// Allies of the current side can slow poison (for the current side).
// Enemies of the current side can do nothing.
if ( teams[cure_side-1].is_enemy(side) )
continue;
// Allied healers can only slow poison, not cure it.
if ( cure_side != side )
this_cure = POISON_SLOW;
// This is where the loop assumption comes into play,
// as we do not bother comparing POISON_SLOW to curing.
if ( this_cure == POISON_CURE ) {
// Return what we found.
healers.push_back(&*cure_it);
return POISON_CURE;
}
// Record this potential treatment.
curer = cure_it;
curing = this_cure;
}
// Return the best curing we found.
if ( curer != units.end() )
healers.push_back(&*curer);
return curing;
}
/**
* Calculate how much @patient heals this turn.
* If cured by units, those units are returned through (added to) @a healers.
*/
int heal_amount(int side, const unit & patient, std::vector<unit *> & healers)
{
unit_map &units = *resources::units;
int healing = 0;
if ( patient.side() == side )
{
// Village healing?
healing = resources::game_map->gives_healing(patient.get_location());
// Regeneration?
unit_ability_list regen_list = patient.get_abilities("regenerate");
unit_abilities::effect regen_effect(regen_list, 0, false);
healing = std::max(healing, regen_effect.get_composite_value());
}
// Check healing from other units.
unit_ability_list heal_list = patient.get_abilities("heals");
// Remove all healers not on this side (since they do not heal now).
unit_ability_list::iterator heal_it = heal_list.begin();
while ( heal_it != heal_list.end() ) {
unit_map::iterator healer = units.find(heal_it->second);
assert(healer != units.end());
if ( healer->side() != side )
heal_it = heal_list.erase(heal_it);
else
++heal_it;
}
// Now we can get the aggregate healing amount.
unit_abilities::effect heal_effect(heal_list, 0, false);
int unit_heal = heal_effect.get_composite_value();
if ( unit_heal > healing )
{
healing = unit_heal;
// Collect the healers involved.
BOOST_FOREACH (const unit_abilities::individual_effect & heal, heal_effect)
healers.push_back(&*units.find(heal.loc));
if ( !healers.empty() ) {
DBG_NG << "Unit has " << healers.size() << " healers.\n";
}
}
return healing;
}
/**
* Animates the healings in the provided list.
* (The list will be empty when this returns.)
*/
void animate_heals(std::list<heal_unit> &unit_list)
{
// Use a nearest-first algorithm.
map_location last_loc(0,-1);
while ( !unit_list.empty() )
{
std::list<heal_unit>::iterator nearest;
int min_dist = INT_MAX;
// Next unit to be healed is the entry in list nearest to last_loc.
for ( std::list<heal_unit>::iterator check_it = unit_list.begin();
check_it != unit_list.end(); ++check_it )
{
int distance = distance_between(last_loc, check_it->healed.get_location());
if ( distance < min_dist ) {
min_dist = distance;
nearest = check_it;
// Allow an early exit if we cannot get closer.
if ( distance == 1 )
break;
}
}
last_loc = nearest->healed.get_location();
unit_display::unit_healing(nearest->healed, nearest->healers,
nearest->amount);
unit_list.erase(nearest);
}
}
}
// Simple algorithm: no maximum number of patients per healer.
void calculate_healing(int side, bool update_display)
{
DBG_NG << "beginning of healing calculations\n";
unit_map &units = *resources::units;
std::list<unit_healing_struct> l;
std::list<heal_unit> unit_list;
// We look for all allied units, then we see if our healer is near them.
BOOST_FOREACH(unit &u, units) {
BOOST_FOREACH(unit &patient, *resources::units) {
if (u.get_state("unhealable") || u.incapacitated())
if ( patient.get_state("unhealable") || patient.incapacitated() )
continue;
DBG_NG << "found healable unit at (" << u.get_location() << ")\n";
DBG_NG << "found healable unit at (" << patient.get_location() << ")\n";
unit_map::iterator curer = units.end();
POISON_STATUS curing = POISON_NORMAL;
int healing = 0;
std::vector<unit *> healers;
int healing = 0;
int rest_healing = 0;
std::string curing;
// Rest healing.
if ( patient.side() == side )
if ( patient.resting() || patient.is_healthy() )
healing += game_config::rest_heal_amount;
unit_ability_list heal = u.get_abilities("heals");
const bool is_poisoned = u.get_state(unit::STATE_POISONED);
if(is_poisoned) {
// Remove the enemies' healers to determine if poison is slowed or cured
for ( unit_ability_list::iterator h_it = heal.begin();
h_it != heal.end(); ) {
unit_map::iterator potential_healer = units.find(h_it->second);
assert(potential_healer != units.end());
if ((*resources::teams)[potential_healer->side() - 1].is_enemy(side)) {
h_it = heal.erase(h_it);
} else {
++h_it;
}
}
BOOST_FOREACH (const unit_ability & ability, heal) {
if((*ability.first)["poison"] == "cured") {
curer = units.find(ability.second);
// Full curing only occurs on the healer turn (may be changed)
if(curer->side() == side) {
curing = "cured";
} else if(curing != "cured") {
curing = "slowed";
}
} else if(curing != "cured" && (*ability.first)["poison"] == "slowed") {
curer = units.find(ability.second);
curing = "slowed";
}
}
// Main healing.
if ( !patient.get_state(unit::STATE_POISONED) ) {
healing += heal_amount(side, patient, healers);
}
else {
curing = poison_progress(side, patient, healers);
// Poison can be cured at any time, but damage is only
// taken on the patient's turn.
if ( curing == POISON_NORMAL && patient.side() == side )
healing -= game_config::poison_amount;
}
// For heal amounts, only consider healers on side which is starting now.
// Remove all healers not on this side.
for ( unit_ability_list::iterator h_it = heal.begin(); h_it != heal.end(); ) {
// Cap the healing.
int max_heal = std::max(0, patient.max_hitpoints() - patient.hitpoints());
int min_heal = std::min(0, 1 - patient.hitpoints());
if ( healing < min_heal )
healing = min_heal;
else if ( healing > max_heal )
healing = max_heal;
unit_map::iterator potential_healer = units.find(h_it->second);
assert(potential_healer != units.end());
if(potential_healer->side() != side) {
h_it = heal.erase(h_it);
} else {
++h_it;
}
}
unit_abilities::effect heal_effect(heal,0,false);
healing = heal_effect.get_composite_value();
BOOST_FOREACH (const unit_abilities::individual_effect &heal_loc, heal_effect ) {
healers.push_back(&*units.find(heal_loc.loc));
}
// Is there nothing to do?
if ( curing != POISON_CURE && healing == 0 )
continue;
if (!healers.empty()) {
DBG_NG << "Unit has " << healers.size() << " potential healers\n";
DBG_NG << "Just before healing animations, unit has " << healers.size() << " potential healers.\n";
}
if (u.side() == side) {
unit_ability_list regen = u.get_abilities("regenerate");
unit_abilities::effect regen_effect(regen,0,false);
if(regen_effect.get_composite_value() > healing) {
healing = regen_effect.get_composite_value();
healers.clear();
}
if ( !regen.empty() ) {
BOOST_FOREACH (const unit_ability & ability, regen) {
if((*ability.first)["poison"] == "cured") {
curer = units.end();
curing = "cured";
} else if(curing != "cured" && (*ability.first)["poison"] == "slowed") {
curer = units.end();
curing = "slowed";
}
}
}
if (int h = resources::game_map->gives_healing(u.get_location())) {
if (h > healing) {
healing = h;
healers.clear();
}
/** @todo FIXME */
curing = "cured";
curer = units.end();
}
if (u.resting() || u.is_healthy()) {
rest_healing = game_config::rest_heal_amount;
healing += rest_healing;
}
}
if(is_poisoned) {
if(curing == "cured") {
u.set_state(unit::STATE_POISONED, false);
healing = rest_healing;
healers.clear();
if (curer != units.end())
healers.push_back(&*curer);
} else if(curing == "slowed") {
healing = rest_healing;
healers.clear();
if (curer != units.end())
healers.push_back(&*curer);
} else {
healers.clear();
healing = rest_healing;
if (u.side() == side) {
healing -= game_config::poison_amount;
}
}
}
if (curing == "" && healing==0) {
continue;
}
int pos_max = u.max_hitpoints() - u.hitpoints();
int neg_max = -(u.hitpoints() - 1);
if(healing > 0 && pos_max <= 0) {
// Do not try to "heal" if HP >= max HP
continue;
}
if(healing > pos_max) {
healing = pos_max;
} else if(healing < neg_max) {
healing = neg_max;
}
if (!healers.empty()) {
DBG_NG << "Just before healing animations, unit has " << healers.size() << " potential healers\n";
}
const team & viewing_team =
(*resources::teams)[resources::screen->viewing_team()];
if (!recorder.is_skipping() && update_display &&
!(u.invisible(u.get_location()) &&
(*resources::teams)[resources::screen->viewing_team()].is_enemy(side)))
patient.is_visible_to_team(viewing_team, false) )
{
unit_healing_struct uhs = { &u, healers, healing };
l.push_front(uhs);
unit_list.push_front(heal_unit(patient, healers, healing));
}
if ( curing == POISON_CURE )
patient.set_state(unit::STATE_POISONED, false);
if (healing > 0)
u.heal(healing);
patient.heal(healing);
else if (healing < 0)
u.take_hit(-healing);
patient.take_hit(-healing);
resources::screen->invalidate_unit();
}
// Display healing with nearest first algorithm.
if (!l.empty()) {
// The first unit to be healed is chosen arbitrarily.
unit_healing_struct uhs = l.front();
l.pop_front();
unit_display::unit_healing(*uhs.healed, uhs.healers, uhs.healing);
/* next unit to be healed is nearest from uhs left in list l */
while (!l.empty()) {
std::list<unit_healing_struct>::iterator nearest;
int min_d = INT_MAX;
/* for each unit in l, remember nearest */
for (std::list<unit_healing_struct>::iterator i =
l.begin(), i_end = l.end(); i != i_end; ++i)
{
int d = distance_between(uhs.healed->get_location(), i->healed->get_location());
if (d < min_d) {
min_d = d;
nearest = i;
}
}
uhs = *nearest;
l.erase(nearest);
unit_display::unit_healing(*uhs.healed, uhs.healers, uhs.healing);
}
}
animate_heals(unit_list);
DBG_NG << "end of healing calculations\n";
}