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