/src/PowerManager.cpp
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