The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
heal.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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
38static lg::log_domain log_engine("engine");
39#define DBG_NG LOG_STREAM(debug, log_engine)
40
41
42namespace {
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 active_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 active_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 {
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 active_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 active_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 active_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 {
253 std::list<heal_unit>::iterator nearest;
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.
290void 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 )
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}
static bool is_enemy(std::size_t side, std::size_t other_side)
virtual const std::vector< team > & teams() const override
virtual const unit_map & units() const override
static game_display * get_singleton()
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
int get_composite_value() const
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:39
static std::string _(const char *str)
Definition gettext.hpp:100
active_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.
void heal(int amount)
Heal the unit.
Definition unit.cpp:1413
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
Definition unit.cpp:1505
bool take_hit(int damage)
Damage the unit.
Definition unit.hpp:731
int side() const
The side this unit belongs to.
Definition unit.hpp:249
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition unit.hpp:776
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
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:584
Standard logging facilities (interface).
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:108
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
Data typedef for active_ability_list.
Encapsulates the map of the game.
Definition location.hpp:46
Display units performing various actions: moving, attacking, and dying.
#define i