PageRenderTime 85ms CodeModel.GetById 28ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 0ms

/src/PowerManager.cpp

http://github.com/clintbellanger/flare
C++ | 1026 lines | 715 code | 139 blank | 172 comment | 556 complexity | 0138d68ac308d12385701e79f89365e9 MD5 | raw file
Possible License(s): GPL-3.0
   1/*
   2Copyright 2011 Clint Bellanger
   3
   4This file is part of FLARE.
   5
   6FLARE is free software: you can redistribute it and/or modify it under the terms
   7of the GNU General Public License as published by the Free Software Foundation,
   8either version 3 of the License, or (at your option) any later version.
   9
  10FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
  11WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  12PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  13
  14You should have received a copy of the GNU General Public License along with
  15FLARE.  If not, see http://www.gnu.org/licenses/
  16*/
  17
  18/**
  19 * class PowerManager
  20 */
  21
  22#include "PowerManager.h"
  23#include "FileParser.h"
  24#include "SharedResources.h"
  25#include "UtilsFileSystem.h"
  26
  27#include <cmath>
  28
  29using namespace std;
  30
  31
  32/**
  33 * PowerManager constructor
  34 */
  35PowerManager::PowerManager() {
  36
  37	gfx_count = 0;
  38	sfx_count = 0;
  39	for (int i=0; i<POWER_MAX_GFX; i++) {
  40		gfx[i] = NULL;
  41	}
  42	for (int i=0; i<POWER_MAX_SFX; i++) {
  43		sfx[i] = NULL;
  44	}
  45
  46	// TODO: generalize Vengeance
  47	powers[POWER_VENGEANCE].type = POWTYPE_SINGLE;
  48
  49	used_item=-1;
  50	
  51	loadGraphics();
  52	loadAll();
  53}
  54
  55/**
  56 * Load all powers files in all mods
  57 */
  58void PowerManager::loadAll() {
  59
  60	string test_path;
  61
  62	// load each items.txt file. Individual item IDs can be overwritten with mods.
  63	for (unsigned int i = 0; i < mods->mod_list.size(); i++) {
  64
  65		test_path = PATH_DATA + "mods/" + mods->mod_list[i] + "/powers/powers.txt";
  66
  67		if (fileExists(test_path)) {
  68			this->loadPowers(test_path);
  69		}
  70	}
  71
  72}
  73
  74
  75/**
  76 * Powers are defined in [mod]/powers/powers.txt
  77 *
  78 * @param filename The full path and filename to this powers.txt file
  79 */
  80void PowerManager::loadPowers(const std::string& filename) {
  81
  82	FileParser infile;
  83	int input_id = 0;
  84	
  85	if (infile.open(filename.c_str())) {
  86		while (infile.next()) {
  87			// id needs to be the first component of each power.  That is how we write
  88			// data to the correct power.
  89			if (infile.key == "id") {
  90				input_id = atoi(infile.val.c_str());
  91			}
  92			else if (infile.key == "type") {
  93				if (infile.val == "single") powers[input_id].type = POWTYPE_SINGLE;
  94				else if (infile.val == "effect") powers[input_id].type = POWTYPE_EFFECT;
  95				else if (infile.val == "missile") powers[input_id].type = POWTYPE_MISSILE;
  96				else if (infile.val == "repeater") powers[input_id].type = POWTYPE_REPEATER;
  97				else if (infile.val == "spawn") powers[input_id].type = POWTYPE_SPAWN;
  98			}
  99			else if (infile.key == "name") {
 100				powers[input_id].name = msg->get(infile.val);
 101			}
 102			else if (infile.key == "description") {
 103				powers[input_id].description = msg->get(infile.val);
 104			}
 105			else if (infile.key == "icon") {
 106				powers[input_id].icon = atoi(infile.val.c_str());
 107			}
 108			else if (infile.key == "new_state") {
 109				if (infile.val == "swing") powers[input_id].new_state = POWSTATE_SWING;
 110				else if (infile.val == "shoot") powers[input_id].new_state = POWSTATE_SHOOT;
 111				else if (infile.val == "cast") powers[input_id].new_state = POWSTATE_CAST;
 112				else if (infile.val == "block") powers[input_id].new_state = POWSTATE_BLOCK;
 113			}
 114			else if (infile.key == "face") {
 115				if (infile.val == "true") powers[input_id].face = true;
 116			}
 117			else if (infile.key == "source_type") {
 118				if (infile.val == "hero") powers[input_id].source_type = SOURCE_TYPE_HERO;
 119				else if (infile.val == "neutral") powers[input_id].source_type = SOURCE_TYPE_NEUTRAL;
 120				else if (infile.val == "enemy") powers[input_id].source_type = SOURCE_TYPE_ENEMY;
 121			}
 122			else if (infile.key == "beacon") {
 123				if (infile.val == "true") powers[input_id].beacon = true;
 124			}
 125			// power requirements
 126			else if (infile.key == "requires_physical_weapon") {
 127				if (infile.val == "true") powers[input_id].requires_physical_weapon = true;
 128			}
 129			else if (infile.key == "requires_mental_weapon") {
 130				if (infile.val == "true") powers[input_id].requires_mental_weapon = true;
 131			}
 132			else if (infile.key == "requires_offense_weapon") {
 133				if (infile.val == "true") powers[input_id].requires_offense_weapon = true;
 134			}
 135			else if (infile.key == "requires_mp") {
 136				powers[input_id].requires_mp = atoi(infile.val.c_str());
 137			}
 138			else if (infile.key == "requires_los") {
 139				if (infile.val == "true") powers[input_id].requires_los = true;
 140			}
 141			else if (infile.key == "requires_empty_target") {
 142				if (infile.val == "true") powers[input_id].requires_empty_target = true;
 143			}
 144			else if (infile.key == "requires_item") {
 145				powers[input_id].requires_item = atoi(infile.val.c_str());
 146			}
 147			else if (infile.key == "requires_targeting") {
 148				if (infile.val == "true") powers[input_id].requires_targeting = true;
 149			}
 150			else if (infile.key == "cooldown") {
 151				powers[input_id].cooldown = atoi(infile.val.c_str());
 152			}
 153			
 154			// animation info
 155			else if (infile.key == "gfx") {
 156				powers[input_id].gfx_index = loadGFX(infile.val);
 157			}
 158			else if (infile.key == "sfx") {
 159				powers[input_id].sfx_index = loadSFX(infile.val);
 160			}
 161			else if (infile.key == "rendered") {
 162				if (infile.val == "true") powers[input_id].rendered = true;
 163			}
 164			else if (infile.key == "directional") {
 165				if (infile.val == "true") powers[input_id].directional = true;
 166			}
 167			else if (infile.key == "visual_random") {
 168				powers[input_id].visual_random = atoi(infile.val.c_str());
 169			}
 170			else if (infile.key == "visual_option") {
 171				powers[input_id].visual_option = atoi(infile.val.c_str());
 172			}
 173			else if (infile.key == "aim_assist") {
 174				powers[input_id].aim_assist = atoi(infile.val.c_str());
 175			}
 176			else if (infile.key == "speed") {
 177				powers[input_id].speed = atoi(infile.val.c_str());
 178			}
 179			else if (infile.key == "lifespan") {
 180				powers[input_id].lifespan = atoi(infile.val.c_str());
 181			}
 182			else if (infile.key == "frame_loop") {
 183				powers[input_id].frame_loop = atoi(infile.val.c_str());
 184			}
 185			else if (infile.key == "frame_duration") {
 186				powers[input_id].frame_duration = atoi(infile.val.c_str());
 187			}
 188			else if (infile.key == "frame_size") {
 189				powers[input_id].frame_size.x = atoi(infile.nextValue().c_str());
 190				powers[input_id].frame_size.y = atoi(infile.nextValue().c_str());
 191			}
 192			else if (infile.key == "frame_offset") {
 193				powers[input_id].frame_offset.x = atoi(infile.nextValue().c_str());
 194				powers[input_id].frame_offset.y = atoi(infile.nextValue().c_str());
 195			}
 196			else if (infile.key == "floor") {
 197				if (infile.val == "true") powers[input_id].floor = true;
 198			}
 199			else if (infile.key == "active_frame") {
 200				powers[input_id].active_frame = atoi(infile.val.c_str());
 201			}
 202			else if (infile.key == "complete_animation") {
 203				if (infile.val == "true") powers[input_id].complete_animation = true;
 204			}
 205			
 206			// hazard traits
 207			else if (infile.key == "use_hazard") {
 208				if (infile.val == "true") powers[input_id].use_hazard = true;
 209			}
 210			else if (infile.key == "no_attack") {
 211				if (infile.val == "true") powers[input_id].no_attack = true;
 212			}
 213			else if (infile.key == "radius") {
 214				powers[input_id].radius = atoi(infile.val.c_str());
 215			}
 216			else if (infile.key == "base_damage") {
 217				if (infile.val == "none")
 218					powers[input_id].base_damage = BASE_DAMAGE_NONE;
 219				else if (infile.val == "melee")
 220					powers[input_id].base_damage = BASE_DAMAGE_MELEE;
 221				else if (infile.val == "ranged")
 222					powers[input_id].base_damage = BASE_DAMAGE_RANGED;
 223				else if (infile.val == "ment")
 224					powers[input_id].base_damage = BASE_DAMAGE_MENT;
 225			}
 226			else if (infile.key == "damage_multiplier") {
 227				powers[input_id].damage_multiplier = atoi(infile.val.c_str());
 228			}
 229			else if (infile.key == "starting_pos") {
 230				if (infile.val == "source")
 231					powers[input_id].starting_pos = STARTING_POS_SOURCE;
 232				else if (infile.val == "target")
 233					powers[input_id].starting_pos = STARTING_POS_TARGET;
 234				else if (infile.val == "melee")
 235					powers[input_id].starting_pos = STARTING_POS_MELEE;
 236			}
 237			else if (infile.key == "multitarget") {
 238				if (infile.val == "true") powers[input_id].multitarget = true;
 239			}
 240			else if (infile.key == "trait_armor_penetration") {
 241				if (infile.val == "true") powers[input_id].trait_armor_penetration = true;
 242			}
 243			else if (infile.key == "trait_crits_impaired") {
 244				powers[input_id].trait_crits_impaired = atoi(infile.val.c_str());
 245			}
 246			else if (infile.key == "trait_elemental") {
 247				if (infile.val == "wood") powers[input_id].trait_elemental = ELEMENT_WOOD;
 248				else if (infile.val == "metal") powers[input_id].trait_elemental = ELEMENT_METAL;
 249				else if (infile.val == "wind") powers[input_id].trait_elemental = ELEMENT_WIND;
 250				else if (infile.val == "water") powers[input_id].trait_elemental = ELEMENT_WATER;
 251				else if (infile.val == "earth") powers[input_id].trait_elemental = ELEMENT_EARTH;
 252				else if (infile.val == "fire") powers[input_id].trait_elemental = ELEMENT_FIRE;
 253				else if (infile.val == "shadow") powers[input_id].trait_elemental = ELEMENT_SHADOW;
 254				else if (infile.val == "light") powers[input_id].trait_elemental = ELEMENT_LIGHT;
 255			}
 256			else if (infile.key == "forced_move") {
 257				powers[input_id].forced_move_speed = atoi(infile.nextValue().c_str());
 258				powers[input_id].forced_move_duration = atoi(infile.nextValue().c_str());
 259			}
 260			//steal effects
 261			else if (infile.key == "hp_steal") {
 262				powers[input_id].hp_steal = atoi(infile.val.c_str());
 263			}
 264			else if (infile.key == "mp_steal") {
 265				powers[input_id].mp_steal = atoi(infile.val.c_str());
 266			}
 267			//missile modifiers
 268			else if (infile.key == "missile_num") {
 269				powers[input_id].missile_num = atoi(infile.val.c_str());
 270			}
 271			else if (infile.key == "missile_angle") {
 272				powers[input_id].missile_angle = atoi(infile.val.c_str());
 273			}
 274			else if (infile.key == "angle_variance") {
 275				powers[input_id].angle_variance = atoi(infile.val.c_str());
 276			}
 277			else if (infile.key == "speed_variance") {
 278				powers[input_id].speed_variance = atoi(infile.val.c_str());
 279			}
 280			//repeater modifiers
 281			else if (infile.key == "delay") {
 282				powers[input_id].delay = atoi(infile.val.c_str());
 283			}
 284			else if (infile.key == "start_frame") {
 285				powers[input_id].start_frame = atoi(infile.val.c_str());
 286			}
 287			else if (infile.key == "repeater_num") {
 288				powers[input_id].repeater_num = atoi(infile.val.c_str());
 289			}
 290			// buff/debuff durations
 291			else if (infile.key == "bleed_duration") {
 292				powers[input_id].bleed_duration = atoi(infile.val.c_str());
 293			}
 294			else if (infile.key == "stun_duration") {
 295				powers[input_id].stun_duration = atoi(infile.val.c_str());
 296			}
 297			else if (infile.key == "slow_duration") {
 298				powers[input_id].slow_duration = atoi(infile.val.c_str());
 299			}
 300			else if (infile.key == "immobilize_duration") {
 301				powers[input_id].immobilize_duration = atoi(infile.val.c_str());
 302			}
 303			else if (infile.key == "immunity_duration") {
 304				powers[input_id].immunity_duration = atoi(infile.val.c_str());
 305			}
 306			else if (infile.key == "haste_duration") {
 307				powers[input_id].haste_duration = atoi(infile.val.c_str());
 308			}
 309			else if (infile.key == "hot_duration") {
 310				powers[input_id].hot_duration = atoi(infile.val.c_str());
 311			}
 312			else if (infile.key == "hot_value") {
 313				powers[input_id].hot_value = atoi(infile.val.c_str());
 314			}
 315			
 316			// buffs
 317			else if (infile.key == "buff_heal") {
 318				if (infile.val == "true") powers[input_id].buff_heal = true;
 319			}
 320			else if (infile.key == "buff_shield") {
 321				if (infile.val == "true") powers[input_id].buff_shield = true;
 322			}
 323			else if (infile.key == "buff_teleport") {
 324				if (infile.val == "true") powers[input_id].buff_teleport = true;
 325			}
 326			else if (infile.key == "buff_immunity") {
 327				if (infile.val == "true") powers[input_id].buff_immunity = true;
 328			}
 329			else if (infile.key == "buff_restore_hp") {
 330				powers[input_id].buff_restore_hp = atoi(infile.val.c_str());
 331			}
 332			else if (infile.key == "buff_restore_mp") {
 333				powers[input_id].buff_restore_mp = atoi(infile.val.c_str());
 334			}
 335			
 336			// pre and post power effects
 337			else if (infile.key == "post_power") {
 338				powers[input_id].post_power = atoi(infile.val.c_str());
 339			}
 340			else if (infile.key == "wall_power") {
 341				powers[input_id].wall_power = atoi(infile.val.c_str());
 342			}
 343			else if (infile.key == "allow_power_mod") {
 344				if (infile.val == "true") powers[input_id].allow_power_mod = true;
 345			}
 346			
 347			// spawn info
 348			else if (infile.key == "spawn_type") {
 349				powers[input_id].spawn_type = infile.val;
 350			}
 351		}
 352		infile.close();
 353	}
 354}
 355
 356/**
 357 * Load the specified graphic for this power
 358 *
 359 * @param filename The .png file containing sprites for this power, assumed to be in images/powers/
 360 * @return The gfx[] array index for this graphic, or -1 upon load failure
 361 */
 362int PowerManager::loadGFX(const string& filename) {
 363	
 364	// currently we restrict the total number of unique power sprite sets
 365	if (gfx_count == POWER_MAX_GFX) return -1;
 366	
 367	// first check to make sure the sprite isn't already loaded
 368	for (int i=0; i<gfx_count; i++) {
 369		if (gfx_filenames[i] == filename) {
 370			return i; // already have this one
 371		}
 372	}
 373
 374	// we don't already have this sprite loaded, so load it
 375	gfx[gfx_count] = IMG_Load(mods->locate("images/powers/" + filename).c_str());
 376	if(!gfx[gfx_count]) {
 377		fprintf(stderr, "Couldn't load power sprites: %s\n", IMG_GetError());
 378		return -1;
 379	}
 380	
 381	// optimize
 382	SDL_Surface *cleanup = gfx[gfx_count];
 383	gfx[gfx_count] = SDL_DisplayFormatAlpha(gfx[gfx_count]);
 384	SDL_FreeSurface(cleanup);	
 385
 386	// success; perform record-keeping
 387	gfx_filenames[gfx_count] = filename;
 388	gfx_count++;
 389	return gfx_count-1;
 390}
 391
 392/**
 393 * Load the specified sound effect for this power
 394 *
 395 * @param filename The .ogg file containing the sound for this power, assumed to be in soundfx/powers/
 396 * @return The sfx[] array index for this mix chunk, or -1 upon load failure
 397 */
 398int PowerManager::loadSFX(const string& filename) {
 399	
 400	// currently we restrict the total number of unique power sounds
 401	if (sfx_count == POWER_MAX_SFX) return -1;
 402	
 403	// first check to make sure the sound isn't already loaded
 404	for (int i=0; i<sfx_count; i++) {
 405		if (sfx_filenames[i] == filename) {
 406			return i; // already have this one
 407		}
 408	}
 409
 410	// we don't already have this sound loaded, so load it
 411	sfx[sfx_count] = Mix_LoadWAV(mods->locate("soundfx/powers/" + filename).c_str());
 412	if(!sfx[sfx_count]) {
 413		fprintf(stderr, "Couldn't load power soundfx: %s\n", filename.c_str());
 414		return -1;
 415	}
 416	
 417	// success; perform record-keeping
 418	sfx_filenames[sfx_count] = filename;
 419	sfx_count++;
 420	return sfx_count-1;
 421}
 422
 423
 424void PowerManager::loadGraphics() {
 425
 426	runes = IMG_Load(mods->locate("images/powers/runes.png").c_str());
 427	
 428	if(!runes) {
 429		fprintf(stderr, "Couldn't load image: %s\n", IMG_GetError());
 430		SDL_Quit();
 431	}
 432}
 433
 434/**
 435 * Set new collision object
 436 */
 437void PowerManager::handleNewMap(MapCollision *_collider) {
 438	collider = _collider;
 439}
 440
 441// convert cartesian to polar theta where (x1,x2) is the origin
 442float PowerManager::calcTheta(int x1, int y1, int x2, int y2) {
 443
 444	float pi = 3.1415926535898;
 445	
 446	// calculate base angle
 447	float dx = (float)x2 - (float)x1;
 448	float dy = (float)y2 - (float)y1;
 449	int exact_dx = x2 - x1;
 450	float theta;
 451	
 452	// convert cartesian to polar coordinates
 453	if (exact_dx == 0) {
 454		if (dy > 0.0) theta = pi/2.0;
 455		else theta = -pi/2.0;
 456	}
 457	else {
 458		theta = atan(dy/dx);
 459		if (dx < 0.0 && dy >= 0.0) theta += pi;
 460		if (dx < 0.0 && dy < 0.0) theta -= pi;
 461	}
 462	return theta;
 463}
 464
 465/**
 466 * Change direction to face the target map location
 467 */
 468int PowerManager::calcDirection(int origin_x, int origin_y, int target_x, int target_y) {
 469
 470	// TODO: use calcTheta instead and check for the areas between -PI and PI
 471	
 472	// inverting Y to convert map coordinates to standard cartesian coordinates
 473	int dx = target_x - origin_x;
 474	int dy = origin_y - target_y;
 475
 476	// avoid div by zero
 477	if (dx == 0) {
 478		if (dy > 0) return 3;
 479		else return 7;
 480	}
 481	
 482	float slope = ((float)dy)/((float)dx);
 483	if (0.5 <= slope && slope <= 2.0) {
 484		if (dy > 0) return 4;
 485		else return 0;
 486	}
 487	if (-0.5 <= slope && slope <= 0.5) {
 488		if (dx > 0) return 5;
 489		else return 1;
 490	}
 491	if (-2.0 <= slope && slope <= -0.5) {
 492		if (dx > 0) return 6;
 493		else return 2;
 494	}
 495	if (2.0 <= slope || -2.0 >= slope) {
 496		if (dy > 0) return 3;
 497		else return 7;
 498	}
 499	return 0;
 500}
 501
 502
 503/**
 504 * Apply basic power info to a new hazard.
 505 *
 506 * This can be called several times to combine powers.
 507 * Typically done when a base power can be modified by equipment
 508 * (e.g. ammo type affects the traits of powers that shoot)
 509 *
 510 * @param power_index The activated power ID
 511 * @param src_stats The StatBlock of the power activator
 512 * @param target Aim position in map coordinates
 513 * @param haz A newly-initialized hazard
 514 */
 515void PowerManager::initHazard(int power_index, StatBlock *src_stats, Point target, Hazard *haz) {
 516
 517	//the hazard holds the statblock of its source
 518	haz->src_stats = src_stats;
 519
 520	haz->power_index = power_index;
 521
 522	if (powers[power_index].source_type == -1){
 523		if (src_stats->hero) haz->source_type = SOURCE_TYPE_HERO;
 524		else haz->source_type = SOURCE_TYPE_ENEMY;
 525	}
 526	else {
 527		haz->source_type = powers[power_index].source_type;
 528	}
 529
 530	// Hazard attributes based on power source
 531	haz->crit_chance = src_stats->crit;
 532	haz->accuracy = src_stats->accuracy;
 533	
 534	// Hazard damage depends on equipped weapons and the power's optional damage_multiplier
 535	if (powers[power_index].base_damage == BASE_DAMAGE_MELEE) {
 536		haz->dmg_min = src_stats->dmg_melee_min;
 537		haz->dmg_max = src_stats->dmg_melee_max;
 538	}
 539	else if (powers[power_index].base_damage == BASE_DAMAGE_RANGED) {
 540		haz->dmg_min = src_stats->dmg_ranged_min;
 541		haz->dmg_max = src_stats->dmg_ranged_max;
 542	}
 543	else if (powers[power_index].base_damage == BASE_DAMAGE_MENT) {
 544		haz->dmg_min = src_stats->dmg_ment_min;
 545		haz->dmg_max = src_stats->dmg_ment_max;
 546	}
 547	//apply the multiplier
 548	haz->dmg_min = (int)ceil(haz->dmg_min * powers[power_index].damage_multiplier / 100.0);
 549	haz->dmg_max = (int)ceil(haz->dmg_max * powers[power_index].damage_multiplier / 100.0);
 550	
 551	// Only apply stats from powers that are not defaults
 552	// If we do this, we can init with multiple power layers
 553	// (e.g. base spell plus weapon type)
 554	
 555	if (powers[power_index].gfx_index != -1) {
 556		haz->sprites = gfx[powers[power_index].gfx_index];
 557	}
 558	if (powers[power_index].rendered) {
 559		haz->rendered = powers[power_index].rendered;
 560	}
 561	if (powers[power_index].lifespan != 0) {
 562		haz->lifespan = powers[power_index].lifespan;
 563	}
 564	if (powers[power_index].frame_loop != 1) {
 565		haz->frame_loop = powers[power_index].frame_loop;
 566	}
 567	if (powers[power_index].frame_duration != 1) {
 568		haz->frame_duration = powers[power_index].frame_duration;
 569	}
 570	if (powers[power_index].frame_size.x != 0) {
 571		haz->frame_size.x = powers[power_index].frame_size.x;
 572	}
 573	if (powers[power_index].frame_size.y != 0) {
 574		haz->frame_size.y = powers[power_index].frame_size.y;
 575	}
 576	if (powers[power_index].frame_offset.x != 0) {
 577		haz->frame_offset.x = powers[power_index].frame_offset.x;
 578	}
 579	if (powers[power_index].frame_offset.y != 0) {
 580		haz->frame_offset.y = powers[power_index].frame_offset.y;
 581	}
 582	if (powers[power_index].directional) {
 583		haz->direction = calcDirection(src_stats->pos.x, src_stats->pos.y, target.x, target.y);
 584	}
 585	else if (powers[power_index].visual_random != 0) {
 586		haz->visual_option = rand() % powers[power_index].visual_random;
 587	}
 588	else if (powers[power_index].visual_option != 0) {
 589		haz->visual_option = powers[power_index].visual_option;
 590	}
 591	haz->floor = powers[power_index].floor;
 592	if (powers[power_index].speed > 0) {
 593		haz->base_speed = powers[power_index].speed;
 594	}
 595	if (powers[power_index].complete_animation) {
 596		haz->complete_animation = true;
 597	}
 598	
 599	// combat traits
 600	if (powers[power_index].no_attack) {
 601		haz->active = false;
 602	}
 603	if (powers[power_index].multitarget) {
 604		haz->multitarget = true;
 605	}
 606	if (powers[power_index].active_frame != -1) {
 607		haz->active_frame = powers[power_index].active_frame;
 608	}
 609	if (powers[power_index].radius != 0) {
 610		haz->radius = powers[power_index].radius;
 611	}
 612	if (powers[power_index].trait_armor_penetration) {
 613		haz->trait_armor_penetration = true;
 614	}
 615	haz->trait_crits_impaired = powers[power_index].trait_crits_impaired;
 616	if (powers[power_index].trait_elemental) {
 617		haz->trait_elemental = powers[power_index].trait_elemental;
 618	}
 619	
 620	// status effect durations
 621	// durations stack when combining powers (e.g. base power and weapon/ammo type)
 622	haz->bleed_duration += powers[power_index].bleed_duration;
 623	haz->stun_duration += powers[power_index].stun_duration;
 624	haz->slow_duration += powers[power_index].slow_duration;
 625	haz->immobilize_duration += powers[power_index].immobilize_duration;
 626	// forced move
 627	haz->forced_move_speed += powers[power_index].forced_move_speed;
 628	haz->forced_move_duration += powers[power_index].forced_move_duration;
 629	// steal effects
 630	haz->hp_steal += powers[power_index].hp_steal;
 631	haz->mp_steal += powers[power_index].mp_steal;
 632	
 633	// hazard starting position
 634	if (powers[power_index].starting_pos == STARTING_POS_SOURCE) {
 635		haz->pos.x = (float)src_stats->pos.x;
 636		haz->pos.y = (float)src_stats->pos.y;
 637	}
 638	else if (powers[power_index].starting_pos == STARTING_POS_TARGET) {
 639		haz->pos.x = (float)target.x;
 640		haz->pos.y = (float)target.y;	
 641	}
 642	else if (powers[power_index].starting_pos == STARTING_POS_MELEE) {
 643		haz->pos = calcVector(src_stats->pos, src_stats->direction, src_stats->melee_range);
 644	}
 645	
 646	// pre/post power effects
 647	if (powers[power_index].post_power != -1) {
 648		haz->post_power = powers[power_index].post_power;
 649	}
 650	if (powers[power_index].wall_power != -1) {
 651		haz->wall_power = powers[power_index].wall_power;
 652	}
 653	
 654	// if equipment has special powers, apply it here (if it hasn't already been applied)
 655	if (!haz->equipment_modified && powers[power_index].allow_power_mod) {
 656		if (powers[power_index].base_damage == BASE_DAMAGE_MELEE && src_stats->melee_weapon_power != -1) {
 657			haz->equipment_modified = true;
 658			initHazard(src_stats->melee_weapon_power, src_stats, target, haz);
 659		}
 660		else if (powers[power_index].base_damage == BASE_DAMAGE_MENT && src_stats->mental_weapon_power != -1) {
 661			haz->equipment_modified = true;
 662			initHazard(src_stats->mental_weapon_power, src_stats, target, haz);
 663		}
 664		else if (powers[power_index].base_damage == BASE_DAMAGE_RANGED && src_stats->ranged_weapon_power != -1) {
 665			haz->equipment_modified = true;
 666			initHazard(src_stats->ranged_weapon_power, src_stats, target, haz);
 667		}		
 668	}
 669}
 670
 671/**
 672 * Any attack-based effects are handled by hazards.
 673 * Self-enhancements (buffs) are handled by this function.
 674 */
 675void PowerManager::buff(int power_index, StatBlock *src_stats, Point target) {
 676
 677	// heal for ment weapon damage * damage multiplier
 678	if (powers[power_index].buff_heal) {
 679		int heal_amt = 0;
 680		int heal_max = (int)ceil(src_stats->dmg_ment_max * powers[power_index].damage_multiplier / 100.0);
 681		int heal_min = (int)ceil(src_stats->dmg_ment_min * powers[power_index].damage_multiplier / 100.0);
 682		if (heal_max > heal_min)
 683			heal_amt = rand() % (heal_max - heal_min) + heal_min;
 684		else // avoid div by 0
 685			heal_amt = heal_min;
 686		src_stats->hp += heal_amt;
 687		if (src_stats->hp > src_stats->maxhp) src_stats->hp = src_stats->maxhp;
 688	}
 689	
 690	// hp restore
 691	if (powers[power_index].buff_restore_hp > 0) {
 692		src_stats->hp += powers[power_index].buff_restore_hp;
 693		if (src_stats->hp > src_stats->maxhp) src_stats->hp = src_stats->maxhp;
 694	}
 695
 696	// mp restore
 697	if (powers[power_index].buff_restore_mp > 0) {
 698		src_stats->mp += powers[power_index].buff_restore_mp;
 699		if (src_stats->mp > src_stats->maxmp) src_stats->mp = src_stats->maxmp;
 700	}
 701	
 702	// charge shield to max ment weapon damage * damage multiplier
 703	if (powers[power_index].buff_shield) {
 704		src_stats->shield_hp = (int)ceil(src_stats->dmg_ment_max * powers[power_index].damage_multiplier / 100.0);	
 705	}
 706	
 707	// teleport to the target location
 708	if (powers[power_index].buff_teleport) {
 709		src_stats->teleportation = true;
 710		src_stats->teleport_destination.x = target.x;
 711		src_stats->teleport_destination.y = target.y;
 712	}
 713	
 714	// buff_immunity removes all existing debuffs
 715	if (powers[power_index].buff_immunity) {
 716		src_stats->slow_duration = 0;
 717		src_stats->immobilize_duration = 0;
 718		src_stats->stun_duration = 0;
 719		src_stats->bleed_duration = 0;
 720	}
 721	
 722	// immunity_duration makes one immune to new debuffs
 723	if (src_stats->immunity_duration < powers[power_index].immunity_duration) {
 724		src_stats->immunity_duration = powers[power_index].immunity_duration;
 725	}
 726	
 727	// haste doubles run speed and removes power cooldowns
 728	if (src_stats->haste_duration < powers[power_index].haste_duration) {
 729		src_stats->haste_duration = powers[power_index].haste_duration;
 730	}
 731	
 732	// hot is healing over time
 733	if (src_stats->hot_duration < powers[power_index].hot_duration) {
 734		src_stats->hot_duration = powers[power_index].hot_duration;
 735		src_stats->hot_value = powers[power_index].hot_value;
 736	}
 737}
 738
 739/**
 740 * Play the sound effect for this power
 741 * Equipped items may have unique sounds
 742 */
 743void PowerManager::playSound(int power_index, StatBlock *src_stats) {
 744	bool play_base_sound = false;
 745	
 746	if (powers[power_index].allow_power_mod) {
 747		if (powers[power_index].base_damage == BASE_DAMAGE_MELEE && src_stats->melee_weapon_power != -1 
 748				&& powers[src_stats->melee_weapon_power].sfx_index != -1) {
 749			Mix_PlayChannel(-1,sfx[powers[src_stats->melee_weapon_power].sfx_index],0);
 750		}
 751		else if (powers[power_index].base_damage == BASE_DAMAGE_MENT && src_stats->mental_weapon_power != -1 
 752				&& powers[src_stats->mental_weapon_power].sfx_index != -1) {
 753			Mix_PlayChannel(-1,sfx[powers[src_stats->mental_weapon_power].sfx_index],0);
 754		}
 755		else if (powers[power_index].base_damage == BASE_DAMAGE_RANGED && src_stats->ranged_weapon_power != -1 
 756				&& powers[src_stats->ranged_weapon_power].sfx_index != -1) {
 757			Mix_PlayChannel(-1,sfx[powers[src_stats->ranged_weapon_power].sfx_index],0);
 758		}
 759		else play_base_sound = true;
 760	}
 761	else play_base_sound = true;
 762
 763	if (play_base_sound && powers[power_index].sfx_index != -1) {
 764		Mix_PlayChannel(-1,sfx[powers[power_index].sfx_index],0);
 765	}
 766		
 767}
 768
 769
 770/**
 771 * The activated power creates a static effect (not a moving hazard)
 772 *
 773 * @param power_index The activated power ID
 774 * @param src_stats The StatBlock of the power activator
 775 * @param target The mouse cursor position in map coordinates
 776 * return boolean true if successful
 777 */
 778bool PowerManager::effect(int power_index, StatBlock *src_stats, Point target) {
 779
 780	if (powers[power_index].use_hazard) {
 781		Hazard *haz = new Hazard();
 782		initHazard(power_index, src_stats, target, haz);
 783		
 784		// Hazard memory is now the responsibility of HazardManager
 785		hazards.push(haz);
 786	}
 787
 788	buff(power_index, src_stats, target);
 789	
 790	// If there's a sound effect, play it here
 791	playSound(power_index, src_stats);
 792
 793	// if all else succeeded, pay costs
 794	if (src_stats->hero && powers[power_index].requires_mp > 0) src_stats->mp -= powers[power_index].requires_mp;
 795	if (src_stats->hero && powers[power_index].requires_item != -1) used_item = powers[power_index].requires_item;
 796	
 797	return true;
 798}
 799
 800/**
 801 * The activated power creates a group of missile hazards (e.g. arrow, thrown knife, firebolt).
 802 * Each individual missile is a single animated hazard that travels from the caster position to the
 803 * mouse target position.
 804 *
 805 * @param power_index The activated power ID
 806 * @param src_stats The StatBlock of the power activator
 807 * @param target The mouse cursor position in map coordinates
 808 * return boolean true if successful
 809 */
 810bool PowerManager::missile(int power_index, StatBlock *src_stats, Point target) {
 811	float pi = 3.1415926535898;
 812
 813	Point src;
 814	if (powers[power_index].starting_pos == STARTING_POS_TARGET) {
 815		src.x = target.x;
 816		src.y = target.y;
 817	}
 818	else {
 819		src.x = src_stats->pos.x;
 820		src.y = src_stats->pos.y;
 821	}
 822
 823	Hazard *haz;
 824
 825	// calculate polar coordinates angle
 826	float theta = calcTheta(src.x, src.y, target.x, target.y);
 827	
 828	//generate hazards
 829	for (int i=0; i < powers[power_index].missile_num; i++) {
 830		haz = new Hazard();
 831
 832		//calculate individual missile angle
 833		float offset_angle = ((1.0 - powers[power_index].missile_num)/2 + i) * (powers[power_index].missile_angle * pi / 180.0);
 834		float variance = 0;
 835		if (powers[power_index].angle_variance != 0)
 836			variance = pow(-1.0f, (rand() % 2) - 1) * (rand() % powers[power_index].angle_variance) * pi / 180.0; //random between 0 and angle_variance away
 837		float alpha = theta + offset_angle + variance;
 838		while (alpha >= pi+pi) alpha -= pi+pi;
 839		while (alpha < 0.0) alpha += pi+pi;
 840
 841		initHazard(power_index, src_stats, target, haz);
 842
 843		//calculate the missile velocity
 844		int speed_var = 0;
 845		if (powers[power_index].speed_variance != 0)
 846			speed_var = (int)(pow(-1.0f, (rand() % 2) - 1) * (rand() % powers[power_index].speed_variance + 1) - 1);
 847		haz->speed.x = (haz->base_speed + speed_var) * cos(alpha);
 848		haz->speed.y = (haz->base_speed + speed_var) * sin(alpha);
 849		
 850		//calculate direction based on trajectory, not actual target (UNITS_PER_TILE reduces round off error)
 851		if (powers[power_index].directional)
 852			haz->direction = calcDirection(src.x, src.y, src.x + UNITS_PER_TILE * haz->speed.x, src.y + UNITS_PER_TILE * haz->speed.y);
 853		
 854		hazards.push(haz);
 855	}
 856
 857	// if all else succeeded, pay costs
 858	if (src_stats->hero && powers[power_index].requires_mp > 0) src_stats->mp -= powers[power_index].requires_mp;
 859	if (src_stats->hero && powers[power_index].requires_item != -1) used_item = powers[power_index].requires_item;
 860
 861	playSound(power_index, src_stats);
 862	return true;
 863}
 864
 865/**
 866 * Repeaters are multiple hazards that spawn in a straight line
 867 */
 868bool PowerManager::repeater(int power_index, StatBlock *src_stats, Point target) {
 869
 870	
 871	// pay costs up front
 872	if (src_stats->hero && powers[power_index].requires_mp > 0) src_stats->mp -= powers[power_index].requires_mp;
 873	if (src_stats->hero && powers[power_index].requires_item != -1) used_item = powers[power_index].requires_item;
 874	
 875	//initialize variables
 876	Hazard *haz[10];
 877	FPoint location_iterator;
 878	FPoint speed;
 879	int delay_iterator;
 880	int map_speed = 64;
 881
 882	// calculate polar coordinates angle
 883	float theta = calcTheta(src_stats->pos.x, src_stats->pos.y, target.x, target.y);
 884
 885	speed.x = (float)map_speed * cos(theta);
 886	speed.y = (float)map_speed * sin(theta);
 887
 888	location_iterator.x = (float)src_stats->pos.x;
 889	location_iterator.y = (float)src_stats->pos.y;
 890	delay_iterator = 0;
 891
 892	playSound(power_index, src_stats);
 893
 894	for (int i=0; i<powers[power_index].repeater_num; i++) {
 895
 896		location_iterator.x += speed.x;
 897		location_iterator.y += speed.y;
 898
 899		// only travels until it hits a wall
 900		if (collider->is_wall((int)location_iterator.x, (int)location_iterator.y)) {
 901			break; // no more hazards
 902		}
 903		
 904		haz[i] = new Hazard();
 905		initHazard(power_index, src_stats, target, haz[i]);
 906
 907		haz[i]->pos.x = location_iterator.x;
 908		haz[i]->pos.y = location_iterator.y;
 909		haz[i]->delay_frames = delay_iterator;
 910		delay_iterator += powers[power_index].delay;
 911		
 912		haz[i]->frame = powers[power_index].start_frame; // start at bottom frame
 913		
 914		hazards.push(haz[i]);
 915	}
 916
 917	return true;
 918	
 919}
 920
 921
 922/**
 923 * Basic single-frame area hazard
 924 */
 925bool PowerManager::single(int power_index, StatBlock *src_stats, Point target) {
 926	
 927	Hazard *haz = new Hazard();
 928
 929	initHazard(power_index, src_stats, target, haz);
 930
 931	// specific powers have different stats here
 932	if (power_index == POWER_VENGEANCE) {
 933		haz->pos = calcVector(src_stats->pos, src_stats->direction, src_stats->melee_range);
 934		haz->dmg_min = src_stats->dmg_melee_min;
 935		haz->dmg_max = src_stats->dmg_melee_max;
 936		haz->radius = 64;
 937		src_stats->mp--;
 938		
 939		// use vengeance stacks
 940		haz->accuracy += src_stats->vengeance_stacks * 25;
 941		haz->crit_chance += src_stats->vengeance_stacks * 25;
 942		src_stats->vengeance_stacks = 0;
 943	}
 944	
 945	hazards.push(haz);
 946
 947	// Hazard memory is now the responsibility of HazardManager
 948	return true;
 949}
 950
 951/**
 952 * Spawn a creature. Does not create a hazard
 953 */
 954bool PowerManager::spawn(int power_index, StatBlock *src_stats, Point target) {
 955		
 956	// apply any buffs
 957	buff(power_index, src_stats, target);
 958	
 959	// If there's a sound effect, play it here
 960	playSound(power_index, src_stats);
 961	
 962	EnemySpawn espawn;
 963	espawn.type = powers[power_index].spawn_type;
 964	
 965	// enemy spawning position
 966	if (powers[power_index].starting_pos == STARTING_POS_SOURCE) {
 967		espawn.pos.x = (float)src_stats->pos.x;
 968		espawn.pos.y = (float)src_stats->pos.y;
 969	}
 970	else if (powers[power_index].starting_pos == STARTING_POS_TARGET) {
 971		espawn.pos.x = (float)target.x;
 972		espawn.pos.y = (float)target.y;	
 973	}
 974	else if (powers[power_index].starting_pos == STARTING_POS_MELEE) {
 975		FPoint fpos = calcVector(src_stats->pos, src_stats->direction, src_stats->melee_range);
 976		espawn.pos.x = (int)fpos.x;
 977		espawn.pos.y = (int)fpos.y;		
 978	}
 979	
 980	espawn.direction = calcDirection(src_stats->pos.x, src_stats->pos.y, target.x, target.y);
 981	
 982	enemies.push(espawn);
 983	return true;
 984}
 985
 986
 987/**
 988 * Activate is basically a switch/redirect to the appropriate function
 989 */
 990bool PowerManager::activate(int power_index, StatBlock *src_stats, Point target) {
 991
 992	if (src_stats->hero) {
 993		if (powers[power_index].requires_mp > src_stats->mp)
 994			return false;
 995	}
 996	
 997	// logic for different types of powers are very different.  We allow these
 998	// separate functions to handle the details.
 999	if (powers[power_index].type == POWTYPE_SINGLE)
1000		return single(power_index, src_stats, target);
1001	else if (powers[power_index].type == POWTYPE_MISSILE)
1002		return missile(power_index, src_stats, target);
1003	else if (powers[power_index].type == POWTYPE_REPEATER)
1004		return repeater(power_index, src_stats, target);
1005	else if (powers[power_index].type == POWTYPE_EFFECT)
1006		return effect(power_index, src_stats, target);
1007	else if (powers[power_index].type == POWTYPE_SPAWN)
1008		return spawn(power_index, src_stats, target);
1009	
1010	return false;
1011}
1012
1013PowerManager::~PowerManager() {
1014
1015	for (int i=0; i<gfx_count; i++) {
1016		if (gfx[i] != NULL)
1017			SDL_FreeSurface(gfx[i]);
1018	}
1019	for (int i=0; i<sfx_count; i++) {
1020		if (sfx[i] != NULL)
1021			Mix_FreeChunk(sfx[i]);
1022	}
1023
1024	SDL_FreeSurface(runes);	
1025}
1026