/src/PowerManager.cpp

http://github.com/clintbellanger/flare · C++ · 1026 lines · 715 code · 139 blank · 172 comment · 556 complexity · 0138d68ac308d12385701e79f89365e9 MD5 · raw file

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