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