mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-15 17:25:32 +00:00
Refactor calculate_healing().
This should be easier for humans to read and computers to execute.
This commit is contained in:
parent
ff3dd7f60f
commit
86fbdad8fb
@ -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";
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user