1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3    Part of the Battle for Wesnoth Project https://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 {
heal_unit__anonaf70f81d0111::heal_unit44 		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 	 */
poison_status(const std::string & status)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 	 */
poison_progress(int side,const unit & patient,std::vector<unit * > & healers)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();
86 		unit_map &units = resources::gameboard->units();
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 	 */
update_healing(int & healing,int & harming,int value)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 	 */
heal_amount(int side,const unit & patient,std::vector<unit * > & healers)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 	 */
do_heal(unit & patient,int amount,bool cure_poison)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);
241 		game_display::get_singleton()->invalidate_unit();
242 	}
243 
244 
245 	/**
246 	 * Animates the healings in the provided list.
247 	 * (The list will be empty when this returns.)
248 	 */
animate_heals(std::list<heal_unit> & unit_list)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 		{
255 			std::list<heal_unit>::iterator nearest;
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.
calculate_healing(int side,bool update_display)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() )
317 				healing += game_config::rest_heal_amount;
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 =
350 			resources::gameboard->teams()[display::get_singleton()->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