The Battle for Wesnoth  1.19.5+dev
heal.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Healing (at start of side turn).
19  */
20 
21 #include "actions/heal.hpp"
22 
23 #include "gettext.hpp"
24 #include "log.hpp"
25 #include "map/map.hpp"
26 #include "play_controller.hpp"
27 #include "resources.hpp"
28 #include "team.hpp"
29 #include "units/unit.hpp"
30 #include "units/abilities.hpp"
31 #include "units/udisplay.hpp"
32 #include "units/map.hpp"
33 #include "utils/general.hpp"
34 
35 #include <list>
36 #include <vector>
37 
38 static lg::log_domain log_engine("engine");
39 #define DBG_NG LOG_STREAM(debug, log_engine)
40 
41 
42 namespace {
43 
44  // Contains the data needed to display healing.
45  struct heal_unit {
46  heal_unit(unit &patient, const std::vector<unit *> &treaters, int healing,
47  bool poison) :
48  healed(patient),
49  healers(treaters),
50  amount(healing),
51  cure_poison(poison)
52  {}
53 
54  unit & healed;
55  std::vector<unit *> healers;
56  int amount;
57  bool cure_poison;
58  };
59 
60  // Keep these ordered from weakest cure to strongest cure.
61  enum POISON_STATUS { POISON_NORMAL, POISON_SLOW , POISON_CURE };
62 
63 
64  /**
65  * Converts a string into its corresponding POISON_STATUS.
66  */
67  POISON_STATUS poison_status(const std::string & status)
68  {
69  if ( status == "cured" )
70  return POISON_CURE;
71 
72  if ( status == "slowed" )
73  return POISON_SLOW;
74 
75  // No other states recognized.
76  return POISON_NORMAL;
77  }
78 
79 
80  /**
81  * Determines if @a patient is affected by anything that impacts poison.
82  * If cured by a unit, that unit is added to @a healers.
83  */
84  POISON_STATUS poison_progress(int side, const unit & patient,
85  std::vector<unit *> & healers)
86  {
87  const std::vector<team> &teams = resources::gameboard->teams();
89 
90  POISON_STATUS curing = POISON_NORMAL;
91 
92 
93  if ( patient.side() == side )
94  {
95  // Village healing?
96  if ( resources::gameboard->map().gives_healing(patient.get_location()) )
97  return POISON_CURE;
98 
99  // Regeneration?
100  for (const unit_ability & regen : patient.get_abilities("regenerate"))
101  {
102  curing = std::max(curing, poison_status((*regen.ability_cfg)["poison"]));
103  if ( curing == POISON_CURE )
104  // This is as good as it gets.
105  return POISON_CURE;
106  }
107  }
108 
109  // Look through the healers to find a curer.
110  unit_map::iterator curer = units.end();
111  // Assumed: curing is not POISON_CURE at the start of any iteration.
112  for (const unit_ability & heal : patient.get_abilities("heals"))
113  {
114  POISON_STATUS this_cure = poison_status((*heal.ability_cfg)["poison"]);
115  if ( this_cure <= curing )
116  // We already recorded this level of curing.
117  continue;
118 
119  // NOTE: At this point, this_cure will be *_SLOW or *_CURE.
120 
121  unit_map::iterator cure_it = units.find(heal.teacher_loc);
122  assert(cure_it != units.end());
123  const int cure_side = cure_it->side();
124 
125  // Healers on the current side can cure poison (for any side).
126  // Allies of the current side can slow poison (for the current side).
127  // Enemies of the current side can do nothing.
128  if ( teams[cure_side-1].is_enemy(side) )
129  continue;
130 
131  // Allied healers can only slow poison, not cure it.
132  if ( cure_side != side )
133  this_cure = POISON_SLOW;
134  // This is where the loop assumption comes into play,
135  // as we do not bother comparing POISON_SLOW to curing.
136 
137  if ( this_cure == POISON_CURE ) {
138  // Return what we found.
139  healers.push_back(&*cure_it);
140  return POISON_CURE;
141  }
142 
143  // Record this potential treatment.
144  curer = cure_it;
145  curing = this_cure;
146  }
147 
148  // Return the best curing we found.
149  if ( curer != units.end() )
150  healers.push_back(&*curer);
151  return curing;
152  }
153 
154 
155  /**
156  * Updates the current healing and harming amounts based on a new value.
157  * This is a helper function for heal_amount().
158  * @returns true if an amount was updated.
159  */
160  inline bool update_healing(int & healing, int & harming, int value)
161  {
162  // If the new value magnifies the healing, update and return true.
163  if ( value > healing ) {
164  healing = value;
165  return true;
166  }
167 
168  // If the new value magnifies the harming, update and return true.
169  if ( value < harming ) {
170  harming = value;
171  return true;
172  }
173 
174  // Keeping the same values as before.
175  return false;
176  }
177  /**
178  * Calculate how much @patient heals this turn.
179  * If healed by units, those units are added to @a healers.
180  */
181  int heal_amount(int side, const unit & patient, std::vector<unit *> & healers)
182  {
183  unit_map &units = resources::gameboard->units();
184 
185  int healing = 0;
186  int harming = 0;
187 
188 
189  if ( patient.side() == side )
190  {
191  // Village healing?
192  update_healing(healing, harming,
193  resources::gameboard->map().gives_healing(patient.get_location()));
194 
195  // Regeneration?
196  unit_ability_list regen_list = patient.get_abilities("regenerate");
197  unit_abilities::effect regen_effect(regen_list, 0);
198  update_healing(healing, harming, regen_effect.get_composite_value());
199  }
200 
201  // Check healing from other units.
202  unit_ability_list heal_list = patient.get_abilities("heals");
203  // Remove all healers not on this side (since they do not heal now).
204  utils::erase_if(heal_list, [&](const unit_ability& i) {
205  unit_map::iterator healer = units.find(i.teacher_loc);
206  assert(healer != units.end());
207 
208  return healer->side() != side;
209  });
210 
211  // Now we can get the aggregate healing amount.
212  unit_abilities::effect heal_effect(heal_list, 0);
213  if ( update_healing(healing, harming, heal_effect.get_composite_value()) )
214  {
215  // Collect the healers involved.
216  for (const unit_abilities::individual_effect & heal : heal_effect)
217  healers.push_back(&*units.find(heal.loc));
218 
219  if ( !healers.empty() ) {
220  DBG_NG << "Unit has " << healers.size() << " healers.";
221  }
222  }
223 
224  return healing + harming;
225  }
226 
227 
228  /**
229  * Handles the actual healing.
230  */
231  void do_heal(unit &patient, int amount, bool cure_poison)
232  {
233  if ( cure_poison )
234  patient.set_state(unit::STATE_POISONED, false);
235  if ( amount > 0)
236  patient.heal(amount);
237  else if ( amount < 0 )
238  patient.take_hit(-amount);
240  }
241 
242 
243  /**
244  * Animates the healings in the provided list.
245  * (The list will be empty when this returns.)
246  */
247  void animate_heals(std::list<heal_unit> &unit_list)
248  {
249  // Use a nearest-first algorithm.
250  map_location last_loc(0,-1);
251  while ( !unit_list.empty() )
252  {
254  int min_dist = std::numeric_limits<int>::max();
255 
256  // Next unit to be healed is the entry in list nearest to last_loc.
257  for ( std::list<heal_unit>::iterator check_it = unit_list.begin();
258  check_it != unit_list.end(); ++check_it )
259  {
260  int distance = distance_between(last_loc, check_it->healed.get_location());
261  if ( distance < min_dist ) {
262  min_dist = distance;
263  nearest = check_it;
264  // Allow an early exit if we cannot get closer.
265  if ( distance == 1 )
266  break;
267  }
268  }
269 
270  std::string cure_text = "";
271  if ( nearest->cure_poison )
272  cure_text = nearest->healed.gender() == unit_race::FEMALE ?
273  _("female^cured") : _("cured");
274 
275  // The heal (animated, then actual):
276  unit_display::unit_healing(nearest->healed, nearest->healers,
277  nearest->amount, cure_text);
278  do_heal(nearest->healed, nearest->amount, nearest->cure_poison);
279 
280  // Update the loop variables.
281  last_loc = nearest->healed.get_location();
282  unit_list.erase(nearest);
283  }
284  }
285 
286 }//anonymous namespace
287 
288 
289 // Simple algorithm: no maximum number of patients per healer.
290 void calculate_healing(int side, bool update_display)
291 {
292  DBG_NG << "beginning of healing calculations";
293 
294  std::list<heal_unit> unit_list;
295 
296  // We look for all allied units, then we see if our healer is near them.
297  for (unit &patient : resources::gameboard->units()) {
298 
299  if ( patient.get_state("unhealable") || patient.incapacitated() ) {
300  continue;
301  }
302 
303  DBG_NG << "found healable unit at (" << patient.get_location() << ")";
304 
305  POISON_STATUS curing = POISON_NORMAL;
306  int healing = 0;
307  std::vector<unit *> healers;
308 
309 
310  // Rest healing.
311  if ( patient.side() == side ) {
312  if ( patient.resting() || patient.is_healthy() )
314  }
315 
316  // Main healing.
317  if ( !patient.get_state(unit::STATE_POISONED) ) {
318  healing += heal_amount(side, patient, healers);
319  }
320  else {
321  curing = poison_progress(side, patient, healers);
322  // Poison can be cured at any time, but damage is only
323  // taken on the patient's turn.
324  if ( curing == POISON_NORMAL && patient.side() == side )
325  healing -= game_config::poison_amount;
326  }
327 
328  // Cap the healing.
329  int max_heal = std::max(0, patient.max_hitpoints() - patient.hitpoints());
330  int min_heal = std::min(0, 1 - patient.hitpoints());
331  if ( healing < min_heal )
332  healing = min_heal;
333  else if ( healing > max_heal )
334  healing = max_heal;
335 
336  // Is there nothing to do?
337  if ( curing != POISON_CURE && healing == 0 )
338  continue;
339 
340  if (!healers.empty()) {
341  DBG_NG << "Just before healing animations, unit has " << healers.size() << " potential healers.";
342  }
343 
344  if (!resources::controller->is_skipping_replay() && update_display)
345  {
346  unit_list.emplace_front(patient, healers, healing, curing == POISON_CURE);
347  }
348  else
349  {
350  // Do the healing now since it will not be animated.
351  do_heal(patient, healing, curing == POISON_CURE);
352  }
353  }
354 
355  animate_heals(unit_list);
356 
357  DBG_NG << "end of healing calculations";
358 }
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
virtual const unit_map & units() const override
Definition: game_board.hpp:107
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
int get_composite_value() const
Definition: abilities.hpp:49
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
@ FEMALE
Definition: race.hpp:28
This class represents a single unit of a specific type.
Definition: unit.hpp:133
std::size_t i
Definition: function.cpp:1028
static std::string _(const char *str)
Definition: gettext.hpp:93
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:216
void heal(int amount)
Heal the unit.
Definition: unit.cpp:1353
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
Definition: unit.cpp:1445
bool take_hit(int damage)
Damage the unit.
Definition: unit.hpp:816
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:861
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1404
void calculate_healing(int side, bool update_display)
Calculates healing for all units for the given side.
Definition: heal.cpp:290
static lg::log_domain log_engine("engine")
#define DBG_NG
Definition: heal.cpp:39
Various functions that implement healing of units (when a side turn starts).
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:550
Standard logging facilities (interface).
int rest_heal_amount
Definition: game_config.cpp:48
game_board * gameboard
Definition: resources.cpp:20
play_controller * controller
Definition: resources.cpp:21
void unit_healing(unit &healed, const std::vector< unit * > &healers, int healing, const std::string &extra_text)
This will use a poisoning anim if healing<0.
Definition: udisplay.cpp:847
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:100
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
Encapsulates the map of the game.
Definition: location.hpp:45
Data typedef for unit_ability_list.
Definition: unit.hpp:38
Display units performing various actions: moving, attacking, and dying.