1 /*
2 Copyright © 2011-2012 Clint Bellanger
3 Copyright © 2012 Igor Paliychuk
4 Copyright © 2012 Stefan Beller
5 Copyright © 2013 Henrik Andersson
6 Copyright © 2012-2016 Justin Jacobs
7 
8 This file is part of FLARE.
9 
10 FLARE is free software: you can redistribute it and/or modify it under the terms
11 of the GNU General Public License as published by the Free Software Foundation,
12 either version 3 of the License, or (at your option) any later version.
13 
14 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with
19 FLARE.  If not, see http://www.gnu.org/licenses/
20 */
21 
22 /**
23  * class StatBlock
24  *
25  * Character stats and calculations
26  */
27 
28 #include "Avatar.h"
29 #include "CampaignManager.h"
30 #include "CombatText.h"
31 #include "Entity.h"
32 #include "EntityManager.h"
33 #include "EngineSettings.h"
34 #include "FileParser.h"
35 #include "Hazard.h"
36 #include "LootManager.h"
37 #include "MapCollision.h"
38 #include "MapRenderer.h"
39 #include "MenuPowers.h"
40 #include "MessageEngine.h"
41 #include "PowerManager.h"
42 #include "Settings.h"
43 #include "SharedGameResources.h"
44 #include "SharedResources.h"
45 #include "StatBlock.h"
46 #include "UtilsMath.h"
47 #include "UtilsParsing.h"
48 
49 #include <limits>
50 
51 #include <math.h>
52 #ifndef M_SQRT2
53 #define M_SQRT2 sqrt(2.0)
54 #endif
55 
56 const float StatBlock::DIRECTION_DELTA_X[8] =   {-1, -1, -1,  0,  1,  1,  1,  0};
57 const float StatBlock::DIRECTION_DELTA_Y[8] =   { 1,  0, -1, -1, -1,  0,  1,  1};
58 const float StatBlock::SPEED_MULTIPLIER[8] = { static_cast<float>(1.0/M_SQRT2), 1.0f, static_cast<float>(1.0/M_SQRT2), 1.0f, static_cast<float>(1.0/M_SQRT2), 1.0f, static_cast<float>(1.0/M_SQRT2), 1.0f};
59 
StatBlock()60 StatBlock::StatBlock()
61 	: statsLoaded(false)
62 	, alive(true)
63 	, corpse(false)
64 	, corpse_timer()
65 	, hero(false)
66 	, hero_ally(false)
67 	, enemy_ally(false)
68 	, npc(false)
69 	, humanoid(false)
70 	, lifeform(true)
71 	, permadeath(false)
72 	, transformed(false)
73 	, refresh_stats(false)
74 	, converted(false)
75 	, summoned(false)
76 	, summoned_power_index(0)
77 	, encountered(false)
78 	, target_corpse(NULL) // hero only
79 	, target_nearest(NULL) // hero only
80 	, target_nearest_corpse(NULL) // hero only
81 	, target_nearest_dist(0) // hero only
82 	, target_nearest_corpse_dist(0) // hero only
83 	, block_power(0)
84 	, movement_type(MapCollision::MOVE_NORMAL)
85 	, flying(false)
86 	, intangible(false)
87 	, facing(true)
88 	, name("")
89 	, level(0)
90 	, xp(0)
91 	, level_up(false)
92 	, check_title(false)
93 	, stat_points_per_level(1)
94 	, power_points_per_level(1)
95 	, starting(Stats::COUNT + eset->damage_types.count, 0)
96 	, base(Stats::COUNT + eset->damage_types.count, 0)
97 	, current(Stats::COUNT + eset->damage_types.count, 0)
98 	, per_level(Stats::COUNT + eset->damage_types.count, 0)
99 	, character_class("")
100 	, character_subclass("")
101 	, hp(0)
102 	, hp_f(0)
103 	, mp(0)
104 	, mp_f(0)
105 	, speed_default(0.1f)
106 	, dmg_min_add(eset->damage_types.list.size(), 0)
107 	, dmg_max_add(eset->damage_types.list.size(), 0)
108 	, absorb_min_add(0)
109 	, absorb_max_add(0)
110 	, speed(0.1f)
111 	, charge_speed(0.0f)
112 	, vulnerable(eset->elements.list.size(), 100)
113 	, vulnerable_base(eset->elements.list.size(), 100)
114 	, transform_duration(0)
115 	, transform_duration_total(0)
116 	, manual_untransform(false)
117 	, transform_with_equipment(false)
118 	, untransform_on_hit(false)
119 	, effects()
120 	, blocking(false) // hero only
121 	, pos()
122 	, knockback_speed()
123 	, knockback_srcpos()
124 	, knockback_destpos()
125 	, direction(0)
126 	, cooldown_hit()
127 	, cooldown_hit_enabled(false)
128 	, cur_state(ENTITY_STANCE)
129 	, state_timer()
130 	, hold_state(false)
131 	, prevent_interrupt(false)
132 	, waypoints()		// enemy only
133 	, waypoint_timer(settings->max_frames_per_sec)	// enemy only
134 	, wander(false)					// enemy only
135 	, wander_area()		// enemy only
136 	, chance_pursue(0)
137 	, chance_flee(0) // enemy only
138 	, powers_list()	// hero only
139 	, powers_list_items()	// hero only
140 	, powers_passive()
141 	, powers_ai() // enemy only
142 	, melee_range(1.0f) //both
143 	, threat_range(0)  // enemy
144 	, threat_range_far(0)  // enemy
145 	, flee_range(0)  // enemy
146 	, combat_style(COMBAT_DEFAULT)//enemy
147 	, hero_stealth(0)
148 	, turn_delay(0)
149 	, in_combat(false)  //enemy only
150 	, join_combat(false)
151 	, cooldown()
152 	, activated_power(NULL) // enemy only
153 	, half_dead_power(false) // enemy only
154 	, suppress_hp(false)
155 	, flee_timer(settings->max_frames_per_sec) // enemy only
156 	, flee_cooldown_timer(settings->max_frames_per_sec) // enemy only
157 	, perfect_accuracy(false)
158 	, teleportation(false)
159 	, teleport_destination()
160 	, currency(0)
161 	, death_penalty(false)
162 	, defeat_status(0)			// enemy only
163 	, convert_status(0)		// enemy only
164 	, quest_loot_requires_status(0)	// enemy only
165 	, quest_loot_requires_not_status(0)		// enemy only
166 	, quest_loot_id(0)			// enemy only
167 	, first_defeat_loot(0)		// enemy only
168 	, gfx_base("male")
169 	, gfx_head("head_short")
170 	, gfx_portrait("")
171 	, transform_type("")
172 	, animations("")
173 	, sfx_attack()
174 	, sfx_step("")
175 	, sfx_hit()
176 	, sfx_die()
177 	, sfx_critdie()
178 	, sfx_block()
179 	, sfx_levelup("")
180 	, sfx_lowhp("")
181 	, sfx_lowhp_loop(false)
182 	, max_spendable_stat_points(0)
183 	, max_points_per_stat(0)
184 	, prev_maxhp(0)
185 	, prev_maxmp(0)
186 	, prev_hp(0)
187 	, prev_mp(0)
188 	, summons()
189 	, summoner(NULL)
190 	, abort_npc_interact(false)
191 {
192 	primary.resize(eset->primary_stats.list.size(), 0);
193 	primary_starting.resize(eset->primary_stats.list.size(), 0);
194 	primary_additional.resize(eset->primary_stats.list.size(), 0);
195 	per_primary.resize(eset->primary_stats.list.size());
196 
197 	for (size_t i = 0; i < per_primary.size(); ++i) {
198 		per_primary[i].resize(Stats::COUNT + eset->damage_types.count, 0);
199 	}
200 
201 	cooldown.reset(Timer::END);
202 }
203 
~StatBlock()204 StatBlock::~StatBlock() {
205 	removeFromSummons();
206 	if (loot)
207 		loot->removeFromEnemiesDroppingLoot(this);
208 }
209 
loadCoreStat(FileParser * infile)210 bool StatBlock::loadCoreStat(FileParser *infile) {
211 	// @CLASS StatBlock: Core stats|Description of engine/stats.txt and enemies in enemies/
212 
213 	if (infile->key == "speed") {
214 		// @ATTR speed|float|Movement speed
215 		float fvalue = Parse::toFloat(infile->val, 0);
216 		speed = speed_default = fvalue / settings->max_frames_per_sec;
217 		return true;
218 	}
219 	else if (infile->key == "cooldown") {
220 		// @ATTR cooldown|int|Cooldown between attacks in 'ms' or 's'.
221 		cooldown.setDuration(Parse::toDuration(infile->val));
222 		return true;
223 	}
224 	else if (infile->key == "cooldown_hit") {
225 		// @ATTR cooldown_hit|duration|Duration of cooldown after being hit in 'ms' or 's'.
226 		cooldown_hit.setDuration(Parse::toDuration(infile->val));
227 		cooldown_hit_enabled = true;
228 		return true;
229 	}
230 	else if (infile->key == "stat") {
231 		// @ATTR stat|string, int : Stat name, Value|The starting value for this stat.
232 		std::string stat = Parse::popFirstString(infile->val);
233 		int value = Parse::popFirstInt(infile->val);
234 
235 		for (int i=0; i<Stats::COUNT; ++i) {
236 			if (Stats::KEY[i] == stat) {
237 				starting[i] = value;
238 				return true;
239 			}
240 		}
241 
242 		for (size_t i = 0; i < eset->damage_types.list.size(); ++i) {
243 			if (eset->damage_types.list[i].min == stat) {
244 				starting[Stats::COUNT + (i*2)] = value;
245 				return true;
246 			}
247 			else if (eset->damage_types.list[i].max == stat) {
248 				starting[Stats::COUNT + (i*2) + 1] = value;
249 				return true;
250 			}
251 		}
252 	}
253 	else if (infile->key == "stat_per_level") {
254 		// @ATTR stat_per_level|predefined_string, int : Stat name, Value|The value for this stat added per level.
255 		std::string stat = Parse::popFirstString(infile->val);
256 		int value = Parse::popFirstInt(infile->val);
257 
258 		for (int i=0; i<Stats::COUNT; i++) {
259 			if (Stats::KEY[i] == stat) {
260 				per_level[i] = value;
261 				return true;
262 			}
263 		}
264 
265 		for (size_t i = 0; i < eset->damage_types.list.size(); ++i) {
266 			if (eset->damage_types.list[i].min == stat) {
267 				per_level[Stats::COUNT + (i*2)] = value;
268 				return true;
269 			}
270 			else if (eset->damage_types.list[i].max == stat) {
271 				per_level[Stats::COUNT + (i*2) + 1] = value;
272 				return true;
273 			}
274 		}
275 	}
276 	else if (infile->key == "stat_per_primary") {
277 		// @ATTR stat_per_primary|predefined_string, predefined_string, int : Primary Stat, Stat name, Value|The value for this stat added for every point allocated to this primary stat.
278 		std::string prim_stat = Parse::popFirstString(infile->val);
279 		size_t prim_stat_index = eset->primary_stats.getIndexByID(prim_stat);
280 		if (prim_stat_index == eset->primary_stats.list.size()) {
281 			infile->error("StatBlock: '%s' is not a valid primary stat.", prim_stat.c_str());
282 			return true;
283 		}
284 
285 		std::string stat = Parse::popFirstString(infile->val);
286 		int value = Parse::popFirstInt(infile->val);
287 
288 		for (int i=0; i<Stats::COUNT; i++) {
289 			if (Stats::KEY[i] == stat) {
290 				per_primary[prim_stat_index][i] = value;
291 				return true;
292 			}
293 		}
294 
295 		for (size_t i = 0; i < eset->damage_types.list.size(); ++i) {
296 			if (eset->damage_types.list[i].min == stat) {
297 				per_primary[prim_stat_index][Stats::COUNT + (i*2)] = value;
298 				return true;
299 			}
300 			else if (eset->damage_types.list[i].max == stat) {
301 				per_primary[prim_stat_index][Stats::COUNT + (i*2) + 1] = value;
302 				return true;
303 			}
304 		}
305 	}
306 	else if (infile->key == "vulnerable") {
307 		// @ATTR vulnerable|predefined_string, int : Element, Value|Percentage weakness to this element.
308 		std::string element = Parse::popFirstString(infile->val);
309 		int value = Parse::popFirstInt(infile->val);
310 
311 		for (unsigned int i=0; i<eset->elements.list.size(); i++) {
312 			if (element == eset->elements.list[i].id) {
313 				vulnerable[i] = vulnerable_base[i] = value;
314 				return true;
315 			}
316 		}
317 	}
318 	else if (infile->key == "power_filter") {
319 		// @ATTR power_filter|list(power_id)|Only these powers are allowed to hit this entity.
320 		std::string power_id = Parse::popFirstString(infile->val);
321 		while (!power_id.empty()) {
322 			power_filter.push_back(Parse::toPowerID(power_id));
323 			power_id = Parse::popFirstString(infile->val);
324 		}
325 		return true;
326 	}
327 	else if (infile->key == "categories") {
328 		// @ATTR categories|list(string)|Categories that this entity belongs to.
329 		categories.clear();
330 		std::string cat;
331 		while ((cat = Parse::popFirstString(infile->val)) != "") {
332 			categories.push_back(cat);
333 		}
334 		return true;
335 	}
336 	else if (infile->key == "melee_range") {
337 		// @ATTR melee_range|float|Determines the distance from the caster that some powers will be placed. For AI entities, it also means the minimum distance from target required to use melee powers.
338 		melee_range = Parse::toFloat(infile->val);
339 		return true;
340 	}
341 
342 	return false;
343 }
344 
345 /**
346  * Set paths for sound effects
347  */
loadSfxStat(FileParser * infile)348 bool StatBlock::loadSfxStat(FileParser *infile) {
349 	// @CLASS StatBlock: Sound effects|Description of heroes in engine/avatar/ and enemies in enemies/
350 
351 	if (infile->new_section) {
352 		sfx_attack.clear();
353 		sfx_hit.clear();
354 		sfx_die.clear();
355 		sfx_critdie.clear();
356 		sfx_block.clear();
357 	}
358 
359 	if (infile->key == "sfx_attack") {
360 		// @ATTR sfx_attack|repeatable(predefined_string, filename) : Animation name, Sound file|Filename of sound effect for the specified attack animation.
361 		std::string anim_name = Parse::popFirstString(infile->val);
362 		std::string filename = Parse::popFirstString(infile->val);
363 
364 		size_t found_index = sfx_attack.size();
365 		for (size_t i = 0; i < sfx_attack.size(); ++i) {
366 			if (anim_name == sfx_attack[i].first) {
367 				found_index = i;
368 				break;
369 			}
370 		}
371 
372 		if (found_index == sfx_attack.size()) {
373 			sfx_attack.push_back(std::pair<std::string, std::vector<std::string> >());
374 			sfx_attack.back().first = anim_name;
375 			sfx_attack.back().second.push_back(filename);
376 		}
377 		else {
378 			if (std::find(sfx_attack[found_index].second.begin(), sfx_attack[found_index].second.end(), filename) == sfx_attack[found_index].second.end()) {
379 				sfx_attack[found_index].second.push_back(filename);
380 			}
381 		}
382 	}
383 	else if (infile->key == "sfx_hit") {
384 		// @ATTR sfx_hit|repeatable(filename)|Filename of sound effect for being hit.
385 		if (std::find(sfx_hit.begin(), sfx_hit.end(), infile->val) == sfx_hit.end()) {
386 			sfx_hit.push_back(infile->val);
387 		}
388 	}
389 	else if (infile->key == "sfx_die") {
390 		// @ATTR sfx_die|repeatable(filename)|Filename of sound effect for dying.
391 		if (std::find(sfx_die.begin(), sfx_die.end(), infile->val) == sfx_die.end()) {
392 			sfx_die.push_back(infile->val);
393 		}
394 	}
395 	else if (infile->key == "sfx_critdie") {
396 		// @ATTR sfx_critdie|repeatable(filename)|Filename of sound effect for dying to a critical hit.
397 		if (std::find(sfx_critdie.begin(), sfx_critdie.end(), infile->val) == sfx_critdie.end()) {
398 			sfx_critdie.push_back(infile->val);
399 		}
400 	}
401 	else if (infile->key == "sfx_block") {
402 		// @ATTR sfx_block|repeatable(filename)|Filename of sound effect for blocking an incoming hit.
403 		if (std::find(sfx_block.begin(), sfx_block.end(), infile->val) == sfx_block.end()) {
404 			sfx_block.push_back(infile->val);
405 		}
406 	}
407 	else if (infile->key == "sfx_levelup") {
408 		// @ATTR sfx_levelup|filename|Filename of sound effect for leveling up.
409 		sfx_levelup = infile->val;
410 	}
411 	else if (infile->key == "sfx_lowhp") {
412 		// @ATTR sfx_lowhp|filename, bool: Sound file, loop|Filename of sound effect for low health warning. Optionally, it can be looped.
413 		sfx_lowhp = Parse::popFirstString(infile->val);
414 		if (infile->val != "") sfx_lowhp_loop = Parse::toBool(infile->val);
415 	}
416 	else {
417 		return false;
418 	}
419 
420 	return true;
421 }
422 
isNPCStat(FileParser * infile)423 bool StatBlock::isNPCStat(FileParser *infile) {
424 	if (infile->section == "npc") return true;
425 	else if (infile->section == "dialog") return true;
426 
427 	if (infile->key == "gfx") {
428 		infile->error("StatBlock: Warning! 'gfx' is deprecated. Use 'animations' instead.");
429 		animations = infile->val;
430 		return true;
431 	}
432 	else if (infile->key == "direction") return true;
433 	else if (infile->key == "talker") return true;
434 	else if (infile->key == "portrait") return true;
435 	else if (infile->key == "vendor") return true;
436 	else if (infile->key == "vendor_requires_status") return true;
437 	else if (infile->key == "vendor_requires_not_status") return true;
438 	else if (infile->key == "constant_stock") return true;
439 	else if (infile->key == "status_stock") return true;
440 	else if (infile->key == "random_stock") return true;
441 	else if (infile->key == "random_stock_count") return true;
442 	else if (infile->key == "vox_intro") return true;
443 
444 	return false;
445 }
446 
447 /**
448  * load a statblock, typically for an enemy definition
449  */
load(const std::string & filename)450 void StatBlock::load(const std::string& filename) {
451 	// @CLASS StatBlock: Enemies|Description of enemies in enemies/
452 	FileParser infile;
453 	if (!infile.open(filename, FileParser::MOD_FILE, FileParser::ERROR_NORMAL))
454 		return;
455 
456 	bool clear_loot = true;
457 	bool flee_range_defined = false;
458 
459 	while (infile.next()) {
460 		if (infile.new_section) {
461 			// APPENDed file
462 			clear_loot = true;
463 		}
464 
465 		int num = Parse::toInt(infile.val);
466 		float fnum = Parse::toFloat(infile.val);
467 		bool valid = loadCoreStat(&infile) || loadSfxStat(&infile) || isNPCStat(&infile);
468 
469 		// @ATTR name|string|Name
470 		if (infile.key == "name") name = msg->get(infile.val);
471 		// @ATTR humanoid|bool|This creature gives human traits when transformed into, such as the ability to talk with NPCs.
472 		else if (infile.key == "humanoid") humanoid = Parse::toBool(infile.val);
473 		// @ATTR lifeform|bool|Determines whether or not this entity is referred to as a living thing, such as displaying "Dead" vs "Destroyed" when their HP is 0.
474 		else if (infile.key == "lifeform") lifeform = Parse::toBool(infile.val);
475 
476 		// @ATTR level|int|Level
477 		else if (infile.key == "level") level = num;
478 
479 		// enemy death rewards and events
480 		// @ATTR xp|int|XP awarded upon death.
481 		else if (infile.key == "xp") xp = num;
482 		else if (infile.key == "loot") {
483 			// @ATTR loot|repeatable(loot)|Possible loot that can be dropped on death.
484 
485 			// loot entries format:
486 			// loot=[id],[percent_chance]
487 			// optionally allow range:
488 			// loot=[id],[percent_chance],[count_min],[count_max]
489 
490 			if (clear_loot) {
491 				loot_table.clear();
492 				clear_loot = false;
493 			}
494 
495 			loot_table.push_back(EventComponent());
496 			loot->parseLoot(infile.val, &loot_table.back(), &loot_table);
497 		}
498 		else if (infile.key == "loot_count") {
499 			// @ATTR loot_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of loot this creature can drop. Overrides the global drop_max setting.
500 			loot_count.x = Parse::popFirstInt(infile.val);
501 			loot_count.y = Parse::popFirstInt(infile.val);
502 			if (loot_count.x != 0 || loot_count.y != 0) {
503 				loot_count.x = std::max(loot_count.x, 1);
504 				loot_count.y = std::max(loot_count.y, loot_count.x);
505 			}
506 		}
507 		// @ATTR defeat_status|string|Campaign status to set upon death.
508 		else if (infile.key == "defeat_status") defeat_status = camp->registerStatus(infile.val);
509 		// @ATTR convert_status|string|Campaign status to set upon being converted to a player ally.
510 		else if (infile.key == "convert_status") convert_status = camp->registerStatus(infile.val);
511 		// @ATTR first_defeat_loot|item_id|Drops this item upon first death.
512 		else if (infile.key == "first_defeat_loot") first_defeat_loot = Parse::toItemID(infile.val);
513 		// @ATTR quest_loot|string, string, item_id : Required status, Required not status, Item|Drops this item when campaign status is met.
514 		else if (infile.key == "quest_loot") {
515 			quest_loot_requires_status = camp->registerStatus(Parse::popFirstString(infile.val));
516 			quest_loot_requires_not_status = camp->registerStatus(Parse::popFirstString(infile.val));
517 			quest_loot_id = Parse::toItemID(Parse::popFirstString(infile.val));
518 		}
519 
520 		// behavior stats
521 		// @ATTR flying|bool|Creature can move over gaps/water.
522 		else if (infile.key == "flying") flying = Parse::toBool(infile.val);
523 		// @ATTR intangible|bool|Creature can move through walls.
524 		else if (infile.key == "intangible") intangible = Parse::toBool(infile.val);
525 		// @ATTR facing|bool|Creature can turn to face their target.
526 		else if (infile.key == "facing") facing = Parse::toBool(infile.val);
527 
528 		// @ATTR waypoint_pause|duration|Duration to wait at each waypoint in 'ms' or 's'.
529 		else if (infile.key == "waypoint_pause") waypoint_timer.setDuration(Parse::toDuration(infile.val));
530 
531 		// @ATTR turn_delay|duration|Duration it takes for this creature to turn and face their target in 'ms' or 's'.
532 		else if (infile.key == "turn_delay") turn_delay = Parse::toDuration(infile.val);
533 		// @ATTR chance_pursue|int|Percentage change that the creature will chase their target.
534 		else if (infile.key == "chance_pursue") chance_pursue = num;
535 		// @ATTR chance_flee|int|Percentage chance that the creature will run away from their target.
536 		else if (infile.key == "chance_flee") chance_flee = num;
537 
538 		else if (infile.key == "power") {
539 			// @ATTR power|["melee", "ranged", "beacon", "on_hit", "on_death", "on_half_dead", "on_join_combat", "on_debuff"], power_id, int : State, Power, Chance|A power that has a chance of being triggered in a certain state.
540 			AIPower ai_power;
541 
542 			std::string ai_type = Parse::popFirstString(infile.val);
543 
544 			ai_power.id = powers->verifyID(Parse::toPowerID(Parse::popFirstString(infile.val)), &infile, !PowerManager::ALLOW_ZERO_ID);
545 			if (ai_power.id == 0)
546 				continue; // verifyID() will print our error message
547 
548 			ai_power.chance = Parse::popFirstInt(infile.val);
549 
550 			if (ai_type == "melee") ai_power.type = AI_POWER_MELEE;
551 			else if (ai_type == "ranged") ai_power.type = AI_POWER_RANGED;
552 			else if (ai_type == "beacon") ai_power.type = AI_POWER_BEACON;
553 			else if (ai_type == "on_hit") ai_power.type = AI_POWER_HIT;
554 			else if (ai_type == "on_death") ai_power.type = AI_POWER_DEATH;
555 			else if (ai_type == "on_half_dead") ai_power.type = AI_POWER_HALF_DEAD;
556 			else if (ai_type == "on_join_combat") ai_power.type = AI_POWER_JOIN_COMBAT;
557 			else if (ai_type == "on_debuff") ai_power.type = AI_POWER_DEBUFF;
558 			else {
559 				infile.error("StatBlock: '%s' is not a valid enemy power type.", ai_type.c_str());
560 				continue;
561 			}
562 
563 			if (ai_power.type == AI_POWER_HALF_DEAD)
564 				half_dead_power = true;
565 
566 			powers_ai.push_back(ai_power);
567 		}
568 
569 		else if (infile.key == "passive_powers") {
570 			// @ATTR passive_powers|list(power_id)|A list of passive powers this creature has.
571 			powers_passive.clear();
572 			std::string p = Parse::popFirstString(infile.val);
573 			while (p != "") {
574 				powers_passive.push_back(Parse::toPowerID(p));
575 				p = Parse::popFirstString(infile.val);
576 
577 				// if a passive power has a post power, add it to the AI power list so we can track its cooldown
578 				PowerID post_power = powers->powers[powers_passive.back()].post_power;
579 				if (post_power > 0) {
580 					AIPower passive_post_power;
581 					passive_post_power.type = AI_POWER_PASSIVE_POST;
582 					passive_post_power.id = post_power;
583 					passive_post_power.chance = 0; // post_power chance is used instead
584 					powers_ai.push_back(passive_post_power);
585 				}
586 			}
587 		}
588 
589 		// @ATTR threat_range|float, float: Engage distance, Stop distance|The first value is the radius of the area this creature will be able to start chasing the hero. The second, optional, value is the radius at which this creature will stop pursuing their target and defaults to double the first value.
590 		else if (infile.key == "threat_range") {
591 			threat_range = Parse::toFloat(Parse::popFirstString(infile.val));
592 
593 			std::string tr_far = Parse::popFirstString(infile.val);
594 			if (!tr_far.empty())
595 				threat_range_far = Parse::toFloat(tr_far);
596 			else
597 				threat_range_far = threat_range * 2;
598 		}
599 		// @ATTR flee_range|float|The radius at which this creature will start moving to a safe distance. Defaults to half of the threat_range.
600 		else if (infile.key == "flee_range") {
601 			flee_range = fnum;
602 			flee_range_defined = true;
603 		}
604 		// @ATTR combat_style|["default", "aggressive", "passive"]|How the creature will enter combat. Default is within range of the hero; Aggressive is always in combat; Passive must be attacked to enter combat.
605 		else if (infile.key == "combat_style") {
606 			if (infile.val == "default") combat_style = COMBAT_DEFAULT;
607 			else if (infile.val == "aggressive") combat_style = COMBAT_AGGRESSIVE;
608 			else if (infile.val == "passive") combat_style = COMBAT_PASSIVE;
609 			else infile.error("StatBlock: Unknown combat style '%s'", infile.val.c_str());
610 		}
611 
612 		// @ATTR animations|filename|Filename of an animation definition.
613 		else if (infile.key == "animations") animations = infile.val;
614 
615 		// @ATTR suppress_hp|bool|Hides the enemy HP bar for this creature.
616 		else if (infile.key == "suppress_hp") suppress_hp = Parse::toBool(infile.val);
617 
618 		// @ATTR flee_duration|duration|The minimum amount of time that this creature will flee. They may flee longer than the specified time.
619 		else if (infile.key == "flee_duration") flee_timer.setDuration(Parse::toDuration(infile.val));
620 		// @ATTR flee_cooldown|duration|The amount of time this creature must wait before they can start fleeing again.
621 		else if (infile.key == "flee_cooldown") flee_cooldown_timer.setDuration(Parse::toDuration(infile.val));
622 
623 		// this is only used for EnemyGroupManager
624 		// we check for them here so that we don't get an error saying they are invalid
625 		else if (infile.key == "rarity") ; // but do nothing
626 
627 		else if (!valid) {
628 			infile.error("StatBlock: '%s' is not a valid key.", infile.key.c_str());
629 		}
630 	}
631 	infile.close();
632 
633 	hp = starting[Stats::HP_MAX];
634 	mp = starting[Stats::MP_MAX];
635 
636 	if (!flee_range_defined)
637 		flee_range = threat_range / 2;
638 
639 	applyEffects();
640 }
641 
642 /**
643  * Reduce temphp first, then hp
644  */
takeDamage(int dmg,bool crit,int source_type)645 void StatBlock::takeDamage(int dmg, bool crit, int source_type) {
646 	hp -= effects.damageShields(dmg);
647 	if (hp <= 0) {
648 		hp = 0;
649 
650 		effects.triggered_death = true;
651 		// TODO should effects.clearEffects() be here as well?
652 		// what about other things that happen in the "dead" entity states?
653 
654 		if (hero) {
655 			cur_state = StatBlock::ENTITY_DEAD;
656 		}
657 		else {
658 			// enemy died; do rewards
659 			if (!hero_ally || converted) {
660 				// some creatures create special loot if we're on a quest
661 				if (quest_loot_requires_status != 0) {
662 					// the loot manager will check quest_loot_id
663 					// if set (not zero), the loot manager will 100% generate that loot.
664 					if (!(camp->checkStatus(quest_loot_requires_status) && !camp->checkStatus(quest_loot_requires_not_status))) {
665 						quest_loot_id = 0;
666 					}
667 				}
668 
669 				// some creatures drop special loot the first time they are defeated
670 				// this must be done in conjunction with defeat status
671 				if (first_defeat_loot > 0) {
672 					if (!camp->checkStatus(defeat_status)) {
673 						quest_loot_id = first_defeat_loot;
674 					}
675 				}
676 
677 				// defeating some creatures (e.g. bosses) affects the story
678 				if (defeat_status != 0) {
679 					camp->setStatus(defeat_status);
680 				}
681 
682 				// reward XP; adjust for party exp if necessary
683 				float xp_multiplier = 1;
684 				if (source_type == Power::SOURCE_TYPE_ALLY)
685 					xp_multiplier = static_cast<float>(eset->misc.party_exp_percentage) / 100.0f;
686 
687 				camp->rewardXP(static_cast<int>((static_cast<float>(xp) * xp_multiplier)), !CampaignManager::XP_SHOW_MSG);
688 
689 				// drop loot
690 				loot->addEnemyLoot(this);
691 			}
692 
693 			if (crit)
694 				cur_state = StatBlock::ENTITY_CRITDEAD;
695 			else
696 				cur_state = StatBlock::ENTITY_DEAD;
697 
698 			mapr->collider.unblock(pos.x, pos.y);
699 		}
700 
701 	}
702 }
703 
704 /**
705  * Recalc level and stats
706  * Refill HP/MP
707  * Creatures might skip these formulas.
708  */
recalc()709 void StatBlock::recalc() {
710 
711 	if (hero) {
712 		if (!statsLoaded) loadHeroStats();
713 
714 		refresh_stats = true;
715 
716 		unsigned long xp_max = eset->xp.getLevelXP(eset->xp.getMaxLevel());
717 		xp = std::min(xp, xp_max);
718 
719 		level = eset->xp.getLevelFromXP(xp);
720 		if (level != 0)
721 			check_title = true;
722 	}
723 
724 	if (level < 1)
725 		level = 1;
726 
727 	applyEffects();
728 
729 	hp = get(Stats::HP_MAX);
730 	mp = get(Stats::MP_MAX);
731 }
732 
733 /**
734  * Base damage and absorb is 0
735  * Plus an optional bonus_per_[base stat]
736  */
calcBase()737 void StatBlock::calcBase() {
738 	// bonuses are skipped for the default level 1 of a stat
739 	const int lev0 = std::max(level - 1, 0);
740 
741 	if (per_primary.empty()) {
742 		for (size_t i = 0; i < Stats::COUNT + eset->damage_types.count; ++i) {
743 			base[i] = starting[i] + (lev0 * per_level[i]);
744 		}
745 	}
746 	else {
747 		for (size_t j = 0; j < per_primary.size(); ++j) {
748 			const int current_primary = std::max(get_primary(j) - 1, 0);
749 			const std::vector<int>& per_primary_vec = per_primary[j];
750 			for (size_t i = 0; i < Stats::COUNT + eset->damage_types.count; ++i) {
751 				if (j==0)
752 					base[i] = starting[i] + (lev0 * per_level[i]);
753 				base[i] += (current_primary * per_primary_vec[i]);
754 			}
755 		}
756 	}
757 
758 	// add damage from equipment and increase to minimum amounts
759 	for (size_t i = 0; i < eset->damage_types.list.size(); ++i) {
760 		base[Stats::COUNT + (i*2)] += dmg_min_add[i];
761 		base[Stats::COUNT + (i*2) + 1] += dmg_max_add[i];
762 		base[Stats::COUNT + (i*2)] = std::max(base[Stats::COUNT + (i*2)], 0);
763 		base[Stats::COUNT + (i*2) + 1] = std::max(base[Stats::COUNT + (i*2) + 1], base[Stats::COUNT + (i*2)]);
764 	}
765 
766 	// add absorb from equipment and increase to minimum amounts
767 	base[Stats::ABS_MIN] += absorb_min_add;
768 	base[Stats::ABS_MAX] += absorb_max_add;
769 	base[Stats::ABS_MIN] = std::max(base[Stats::ABS_MIN], 0);
770 	base[Stats::ABS_MAX] = std::max(base[Stats::ABS_MAX], base[Stats::ABS_MIN]);
771 }
772 
773 /**
774  * Recalc derived stats from base stats + effect bonuses
775  */
applyEffects()776 void StatBlock::applyEffects() {
777 	// preserve hp/mp states
778 	// max HP and MP can't drop below 1
779 	prev_maxhp = std::max(get(Stats::HP_MAX), 1);
780 	prev_maxmp = std::max(get(Stats::MP_MAX), 1);
781 	prev_hp = hp;
782 	prev_mp = mp;
783 
784 	// calculate primary stats
785 	// refresh the character menu if there has been a change
786 	for (size_t i = 0; i < primary.size(); ++i) {
787 		if (get_primary(i) != primary[i] + effects.bonus_primary[i])
788 			refresh_stats = true;
789 
790 		primary_additional[i] = effects.bonus_primary[i];
791 	}
792 
793 	calcBase();
794 
795 	for (size_t i = 0; i < Stats::COUNT + eset->damage_types.count; ++i) {
796 		current[i] = base[i] + effects.bonus[i];
797 	}
798 
799 	for (size_t i = 0; i < effects.bonus_resist.size(); ++i) {
800 		vulnerable[i] = vulnerable_base[i] - effects.bonus_resist[i];
801 	}
802 
803 	current[Stats::HP_MAX] += (current[Stats::HP_MAX] * current[Stats::HP_PERCENT]) / 100;
804 	current[Stats::MP_MAX] += (current[Stats::MP_MAX] * current[Stats::MP_PERCENT]) / 100;
805 
806 	// max HP and MP can't drop below 1
807 	current[Stats::HP_MAX] = std::max(get(Stats::HP_MAX), 1);
808 	current[Stats::MP_MAX] = std::max(get(Stats::MP_MAX), 1);
809 
810 	if (hp > get(Stats::HP_MAX)) hp = get(Stats::HP_MAX);
811 	if (mp > get(Stats::MP_MAX)) mp = get(Stats::MP_MAX);
812 
813 	speed = speed_default;
814 }
815 
816 /**
817  * Process per-frame actions
818  */
logic()819 void StatBlock::logic() {
820 	alive = !(hp <= 0 && !effects.triggered_death && !effects.revive);
821 
822 	// handle party buffs
823 	if (entitym && powers) {
824 		while (!party_buffs.empty()) {
825 			PowerID power_index = party_buffs.front();
826 			party_buffs.pop();
827 			Power *buff_power = &powers->powers[power_index];
828 
829 			for (size_t i=0; i < entitym->entities.size(); ++i) {
830 				Entity* party_member = entitym->entities[i];
831 				if(party_member->stats.hp > 0 &&
832 				   ((party_member->stats.hero_ally && hero) || (party_member->stats.enemy_ally && party_member->stats.summoner == this)) &&
833 				   (buff_power->buff_party_power_id == 0 || buff_power->buff_party_power_id == party_member->stats.summoned_power_index)
834 				) {
835 					powers->effect(&(party_member->stats), this, power_index, (hero ? Power::SOURCE_TYPE_HERO : Power::SOURCE_TYPE_ENEMY));
836 				}
837 			}
838 		}
839 	}
840 
841 	// handle effect timers
842 	effects.logic();
843 
844 	// apply bonuses from items/effects to base stats
845 	applyEffects();
846 
847 	if (hero && effects.refresh_stats) {
848 		refresh_stats = true;
849 		effects.refresh_stats = false;
850 	}
851 
852 	// preserve ratio on maxmp and maxhp changes
853 	float ratio;
854 	if (prev_maxhp != get(Stats::HP_MAX)) {
855 		ratio = static_cast<float>(prev_hp) / static_cast<float>(prev_maxhp);
856 		hp = static_cast<int>(ratio * static_cast<float>(get(Stats::HP_MAX)));
857 	}
858 	if (prev_maxmp != get(Stats::MP_MAX)) {
859 		ratio = static_cast<float>(prev_mp) / static_cast<float>(prev_maxmp);
860 		mp = static_cast<int>(ratio * static_cast<float>(get(Stats::MP_MAX)));
861 	}
862 
863 	// handle cooldowns
864 	cooldown.tick(); // global cooldown
865 
866 	for (size_t i=0; i<powers_ai.size(); ++i) { // NPC/enemy powerslot cooldown
867 		powers_ai[i].cooldown.tick();
868 	}
869 
870 	// sync hp/mp with their floating point counterparts
871 	if (static_cast<int>(hp_f) != hp)
872 		hp_f = static_cast<float>(hp);
873 	if (static_cast<int>(mp_f) != mp)
874 		mp_f = static_cast<float>(mp);
875 
876 	// HP regen
877 	if (hp < get(Stats::HP_MAX) && hp > 0) {
878 		float hp_regen_per_frame;
879 		if (!in_combat && !hero_ally && !hero && pc->stats.alive) {
880 			// enemies heal rapidly (full heal in 5 seconds) while not in combat
881 			hp_regen_per_frame = static_cast<float>(get(Stats::HP_MAX)) / 5.f / settings->max_frames_per_sec;
882 		}
883 		else {
884 			hp_regen_per_frame = static_cast<float>(get(Stats::HP_REGEN)) / 60.f / settings->max_frames_per_sec;
885 		}
886 		hp_f += hp_regen_per_frame;
887 		hp = std::min(static_cast<int>(hp_f), get(Stats::HP_MAX));
888 	}
889 
890 	// MP regen
891 	if (mp < get(Stats::MP_MAX) && hp > 0) {
892 		float mp_regen_per_frame = static_cast<float>(get(Stats::MP_REGEN)) / 60.f / settings->max_frames_per_sec;
893 		mp_f += mp_regen_per_frame;
894 		mp = std::min(static_cast<int>(mp_f), get(Stats::MP_MAX));
895 	}
896 
897 	// handle buff/debuff durations
898 	if (transform_duration > 0)
899 		transform_duration--;
900 
901 	// apply bleed
902 	if (effects.damage > 0 && hp > 0) {
903 		takeDamage(effects.damage, !StatBlock::TAKE_DMG_CRIT, effects.getDamageSourceType(Effect::DAMAGE));
904 		comb->addInt(effects.damage, pos, CombatText::MSG_TAKEDMG);
905 	}
906 	if (effects.damage_percent > 0 && hp > 0) {
907 		int damage = (get(Stats::HP_MAX)*effects.damage_percent)/100;
908 		takeDamage(damage, !StatBlock::TAKE_DMG_CRIT, effects.getDamageSourceType(Effect::DAMAGE_PERCENT));
909 		comb->addInt(damage, pos, CombatText::MSG_TAKEDMG);
910 	}
911 
912 	if(effects.death_sentence)
913 		takeDamage(get(Stats::HP_MAX), !StatBlock::TAKE_DMG_CRIT, Power::SOURCE_TYPE_NEUTRAL);
914 
915 	cooldown_hit.tick();
916 
917 	if (effects.stun) {
918 		// stun stops charge attacks
919 		state_timer.reset(Timer::END);
920 		charge_speed = 0;
921 	}
922 
923 	state_timer.tick();
924 
925 	// apply healing over time
926 	if (effects.hpot > 0) {
927 		comb->addString(msg->get("+%d HP",effects.hpot), pos, CombatText::MSG_BUFF);
928 		hp += effects.hpot;
929 		if (hp > get(Stats::HP_MAX)) hp = get(Stats::HP_MAX);
930 	}
931 	if (effects.hpot_percent > 0) {
932 		int hpot = (get(Stats::HP_MAX)*effects.hpot_percent)/100;
933 		comb->addString(msg->get("+%d HP",hpot), pos, CombatText::MSG_BUFF);
934 		hp += hpot;
935 		if (hp > get(Stats::HP_MAX)) hp = get(Stats::HP_MAX);
936 	}
937 	if (effects.mpot > 0) {
938 		comb->addString(msg->get("+%d MP",effects.mpot), pos, CombatText::MSG_BUFF);
939 		mp += effects.mpot;
940 		if (mp > get(Stats::MP_MAX)) mp = get(Stats::MP_MAX);
941 	}
942 	if (effects.mpot_percent > 0) {
943 		int mpot = (get(Stats::MP_MAX)*effects.mpot_percent)/100;
944 		comb->addString(msg->get("+%d MP",mpot), pos, CombatText::MSG_BUFF);
945 		mp += mpot;
946 		if (mp > get(Stats::MP_MAX)) mp = get(Stats::MP_MAX);
947 	}
948 
949 	// set movement type
950 	// some creatures may shift between movement types
951 	if (intangible) movement_type = MapCollision::MOVE_INTANGIBLE;
952 	else if (flying) movement_type = MapCollision::MOVE_FLYING;
953 	else movement_type = MapCollision::MOVE_NORMAL;
954 
955 	if (hp == 0)
956 		removeSummons();
957 
958 	if (effects.knockback_speed != 0) {
959 		float theta = Utils::calcTheta(knockback_srcpos.x, knockback_srcpos.y, knockback_destpos.x, knockback_destpos.y);
960 		knockback_speed.x = effects.knockback_speed * cosf(theta);
961 		knockback_speed.y = effects.knockback_speed * sinf(theta);
962 
963 		mapr->collider.unblock(pos.x, pos.y);
964 		mapr->collider.move(pos.x, pos.y, knockback_speed.x, knockback_speed.y, movement_type, mapr->collider.getCollideType(hero));
965 		mapr->collider.block(pos.x, pos.y, hero_ally);
966 	}
967 	else if (charge_speed != 0.0f) {
968 		float tmp_speed = charge_speed * SPEED_MULTIPLIER[direction];
969 		float dx = tmp_speed * DIRECTION_DELTA_X[direction];
970 		float dy = tmp_speed * DIRECTION_DELTA_Y[direction];
971 
972 		mapr->collider.unblock(pos.x, pos.y);
973 		mapr->collider.move(pos.x, pos.y, dx, dy, movement_type, mapr->collider.getCollideType(hero));
974 		mapr->collider.block(pos.x, pos.y, hero_ally);
975 	}
976 
977 	waypoint_timer.tick();
978 
979 	// check for revive
980 	if (hp <= 0 && effects.revive) {
981 		hp = get(Stats::HP_MAX);
982 		alive = true;
983 		corpse = false;
984 		cur_state = ENTITY_STANCE;
985 	}
986 
987 	// non-hero entities can have their disposition reversed
988 	if (!hero && effects.convert != converted) {
989 		converted = !converted;
990 		hero_ally = !hero_ally;
991 		if (convert_status != 0) {
992 			camp->setStatus(convert_status);
993 		}
994 	}
995 }
996 
canUsePower(PowerID powerid,bool allow_passive) const997 bool StatBlock::canUsePower(PowerID powerid, bool allow_passive) const {
998 	const Power& power = powers->powers[powerid];
999 
1000 	if (!alive) {
1001 		// can't use powers when dead
1002 		return false;
1003 	}
1004 	else if (!hero) {
1005 		// AI can always use their powers
1006 		return true;
1007 	}
1008 	else if (transformed) {
1009 		// needed to unlock shapeshifter powers
1010 		return mp >= power.requires_mp;
1011 	}
1012 	else {
1013 		return (
1014 			mp >= power.requires_mp
1015 			&& (!power.passive || allow_passive)
1016 			&& !power.meta_power
1017 			&& (!effects.stun || (allow_passive && power.passive))
1018 			&& (power.sacrifice || hp > power.requires_hp)
1019 			&& powers->checkRequiredMaxHPMP(power, this)
1020 			&& (!power.requires_corpse || (target_corpse && !target_corpse->corpse_timer.isEnd()) || (target_nearest_corpse && powers->checkNearestTargeting(power, this, true) && !target_nearest_corpse->corpse_timer.isEnd()))
1021 			&& (checkRequiredSpawns(power.requires_spawns))
1022 			&& (menu_powers && menu_powers->meetsUsageStats(powerid))
1023 			&& (power.type == Power::TYPE_SPAWN ? !summonLimitReached(powerid) : true)
1024 			&& !(power.spawn_type == "untransform" && !transformed)
1025 			&& std::includes(equip_flags.begin(), equip_flags.end(), power.requires_flags.begin(), power.requires_flags.end())
1026 			&& (!power.buff_party || (power.buff_party && entitym && entitym->checkPartyMembers()))
1027 			&& powers->checkRequiredItems(power, this)
1028 		);
1029 	}
1030 
1031 }
1032 
loadHeroStats()1033 void StatBlock::loadHeroStats() {
1034 	// set the default global cooldown
1035 	cooldown.setDuration(Parse::toDuration("66ms"));
1036 
1037 	// Redefine numbers from config file if present
1038 	FileParser infile;
1039 	// @CLASS StatBlock: Hero stats|Description of engine/stats.txt
1040 	if (infile.open("engine/stats.txt", FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
1041 		while (infile.next()) {
1042 			int value = Parse::toInt(infile.val);
1043 
1044 			bool valid = loadCoreStat(&infile);
1045 
1046 			if (infile.key == "max_points_per_stat") {
1047 				// @ATTR max_points_per_stat|int|Maximum points for each primary stat.
1048 				max_points_per_stat = value;
1049 			}
1050 			else if (infile.key == "sfx_step") {
1051 				// @ATTR sfx_step|string|An id for a set of step sound effects. See items/step_sounds.txt.
1052 				sfx_step = infile.val;
1053 			}
1054 			else if (infile.key == "stat_points_per_level") {
1055 				// @ATTR stat_points_per_level|int|The amount of stat points awarded each level.
1056 				stat_points_per_level = value;
1057 			}
1058 			else if (infile.key == "power_points_per_level") {
1059 				// @ATTR power_points_per_level|int|The amount of power points awarded each level.
1060 				power_points_per_level = value;
1061 			}
1062 			else if (!valid) {
1063 				infile.error("StatBlock: '%s' is not a valid key.", infile.key.c_str());
1064 			}
1065 		}
1066 		infile.close();
1067 	}
1068 
1069 	if (max_points_per_stat == 0) max_points_per_stat = max_spendable_stat_points / 4 + 1;
1070 	statsLoaded = true;
1071 
1072 	max_spendable_stat_points = eset->xp.getMaxLevel() * stat_points_per_level;
1073 }
1074 
loadHeroSFX()1075 void StatBlock::loadHeroSFX() {
1076 	// load the paths to base sound effects
1077 	FileParser infile;
1078 	if (infile.open("engine/avatar/"+gfx_base+".txt", FileParser::MOD_FILE, FileParser::ERROR_NONE)) {
1079 		while(infile.next()) {
1080 			loadSfxStat(&infile);
1081 		}
1082 		infile.close();
1083 	}
1084 }
1085 
1086 /**
1087  * Recursivly kill all summoned creatures
1088  */
removeSummons()1089 void StatBlock::removeSummons() {
1090 	for (std::vector<StatBlock*>::iterator it = summons.begin(); it != summons.end(); ++it) {
1091 		(*it)->takeDamage((*it)->get(Stats::HP_MAX), !StatBlock::TAKE_DMG_CRIT, Power::SOURCE_TYPE_NEUTRAL);
1092 		(*it)->removeSummons();
1093 		(*it)->summoner = NULL;
1094 	}
1095 
1096 	summons.clear();
1097 }
1098 
removeFromSummons()1099 void StatBlock::removeFromSummons() {
1100 
1101 	if(summoner != NULL && !summoner->summons.empty()) {
1102 		std::vector<StatBlock*>::iterator parent_ref = std::find(summoner->summons.begin(), summoner->summons.end(), this);
1103 
1104 		if(parent_ref != summoner->summons.end())
1105 			summoner->summons.erase(parent_ref);
1106 
1107 		summoner = NULL;
1108 	}
1109 
1110 	removeSummons();
1111 }
1112 
summonLimitReached(PowerID power_id) const1113 bool StatBlock::summonLimitReached(PowerID power_id) const {
1114 
1115 	//find the limit
1116 	Power *spawn_power = &powers->powers[power_id];
1117 
1118 	int max_summons = 0;
1119 
1120 	if(spawn_power->spawn_limit_mode == Power::SPAWN_LIMIT_MODE_FIXED)
1121 		max_summons = spawn_power->spawn_limit_qty;
1122 	else if(spawn_power->spawn_limit_mode == Power::SPAWN_LIMIT_MODE_STAT) {
1123 		int stat_val = 1;
1124 		for (size_t i = 0; i < eset->primary_stats.list.size(); ++i) {
1125 			if (spawn_power->spawn_limit_stat == i) {
1126 				stat_val = get_primary(i);
1127 				break;
1128 			}
1129 		}
1130 		max_summons = (stat_val / (spawn_power->spawn_limit_every == 0 ? 1 : spawn_power->spawn_limit_every)) * spawn_power->spawn_limit_qty;
1131 	}
1132 	else
1133 		return false;//unlimited or unknown mode
1134 
1135 	//if the power is available, there should be at least 1 allowed summon
1136 	if(max_summons < 1) max_summons = 1;
1137 
1138 
1139 	//find out how many there are currently
1140 	int qty_summons = 0;
1141 
1142 	for (unsigned int i=0; i < summons.size(); i++) {
1143 		if(!summons[i]->corpse && summons[i]->summoned_power_index == power_id
1144 				&& summons[i]->cur_state != ENTITY_SPAWN
1145 				&& summons[i]->cur_state != ENTITY_DEAD
1146 				&& summons[i]->cur_state != ENTITY_CRITDEAD) {
1147 			qty_summons++;
1148 		}
1149 	}
1150 
1151 	return qty_summons >= max_summons;
1152 }
1153 
setWanderArea(int r)1154 void StatBlock::setWanderArea(int r) {
1155 	wander_area.x = int(floorf(pos.x)) - r;
1156 	wander_area.y = int(floorf(pos.y)) - r;
1157 	wander_area.w = wander_area.h = (r*2) + 1;
1158 }
1159 
1160 /**
1161  * Returns the short version of the class string
1162  * For the sake of consistency with previous versions,
1163  * this means returning the generated subclass
1164  */
getShortClass()1165 std::string StatBlock::getShortClass() {
1166 	if (character_subclass == "")
1167 		return msg->get(character_class);
1168 	else
1169 		return msg->get(character_subclass);
1170 }
1171 
1172 /**
1173  * Returns the long version of the class string
1174  * It contains both the base class and the generated subclass
1175  */
getLongClass()1176 std::string StatBlock::getLongClass() {
1177 	if (character_subclass == "" || character_class == character_subclass)
1178 		return msg->get(character_class);
1179 	else
1180 		return msg->get(character_class) + " / " + msg->get(character_subclass);
1181 }
1182 
addXP(int amount)1183 void StatBlock::addXP(int amount) {
1184 	xp += amount;
1185 
1186 	unsigned long xp_max = eset->xp.getLevelXP(eset->xp.getMaxLevel());
1187 	xp = std::min(xp, xp_max);
1188 }
1189 
getAIPower(int ai_type)1190 StatBlock::AIPower* StatBlock::getAIPower(int ai_type) {
1191 	std::vector<size_t> possible_ids;
1192 	int chance = rand() % 100;
1193 
1194 	for (size_t i=0; i<powers_ai.size(); ++i) {
1195 		if (ai_type != powers_ai[i].type)
1196 			continue;
1197 
1198 		if (chance > powers_ai[i].chance)
1199 			continue;
1200 
1201 		if (!powers_ai[i].cooldown.isEnd())
1202 			continue;
1203 
1204 		if (powers->powers[powers_ai[i].id].type == Power::TYPE_SPAWN) {
1205 			if (summonLimitReached(powers_ai[i].id))
1206 				continue;
1207 		}
1208 
1209 		if (!checkRequiredSpawns(powers->powers[powers_ai[i].id].requires_spawns))
1210 			continue;
1211 
1212 		possible_ids.push_back(i);
1213 	}
1214 
1215 	if (!possible_ids.empty()) {
1216 		size_t index = static_cast<size_t>(rand()) % possible_ids.size();
1217 		return &powers_ai[possible_ids[index]];
1218 	}
1219 
1220 	return NULL;
1221 }
1222 
checkRequiredSpawns(int req_amount) const1223 bool StatBlock::checkRequiredSpawns(int req_amount) const {
1224 	if (req_amount <= 0)
1225 		return true;
1226 
1227 	int live_summon_count = 0;
1228 	for (size_t j=0; j<summons.size(); ++j) {
1229 		if (summons[j]->hp > 0) {
1230 			++live_summon_count;
1231 		}
1232 	}
1233 
1234 	if (live_summon_count < req_amount)
1235 		return false;
1236 
1237 	return true;
1238 }
1239 
getPowerCooldown(PowerID power_id)1240 int StatBlock::getPowerCooldown(PowerID power_id) {
1241 	if (hero) {
1242 		return pc->power_cooldown_timers[power_id].getDuration();
1243 	}
1244 	else {
1245 		for (size_t i = 0; i < powers_ai.size(); ++i) {
1246 			if (power_id == powers_ai[i].id)
1247 				return powers_ai[i].cooldown.getDuration();
1248 		}
1249 	}
1250 
1251 	return 0;
1252 }
1253 
setPowerCooldown(PowerID power_id,int power_cooldown)1254 void StatBlock::setPowerCooldown(PowerID power_id, int power_cooldown) {
1255 	if (hero) {
1256 		pc->power_cooldown_timers[power_id].setDuration(power_cooldown);
1257 	}
1258 	else {
1259 		for (size_t i = 0; i < powers_ai.size(); ++i) {
1260 			if (power_id == powers_ai[i].id) {
1261 				powers_ai[i].cooldown.setDuration(power_cooldown);
1262 				break;
1263 			}
1264 		}
1265 	}
1266 }
1267