/src/Avatar.cpp
C++ | 708 lines | 480 code | 125 blank | 103 comment | 217 complexity | e189af7a96010404e101275fcaa49e44 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 Avatar 20 * 21 * Contains logic and rendering routines for the player avatar. 22 */ 23 24#include "Avatar.h" 25#include "SharedResources.h" 26 27#include <sstream> 28 29using namespace std; 30 31 32Avatar::Avatar(PowerManager *_powers, MapIso *_map) : Entity(_map), powers(_powers) { 33 34 init(); 35 36 // default hero animation data 37 stats.cooldown = 4; 38 39 // load the hero's animations from hero definition file 40 loadAnimations("animations/hero.txt"); 41} 42 43void Avatar::init() { 44 45 // name, base, look are set by GameStateNew so don't reset it here 46 47 // other init 48 sprites = 0; 49 stats.cur_state = AVATAR_STANCE; 50 stats.pos.x = map->spawn.x; 51 stats.pos.y = map->spawn.y; 52 stats.direction = map->spawn_dir; 53 current_power = -1; 54 newLevelNotification = false; 55 56 lockSwing = false; 57 lockCast = false; 58 lockShoot = false; 59 60 stats.hero = true; 61 stats.level = 1; 62 stats.xp = 0; 63 stats.physical_character = 1; 64 stats.mental_character = 1; 65 stats.offense_character = 1; 66 stats.defense_character = 1; 67 stats.physical_additional = 0; 68 stats.mental_additional = 0; 69 stats.offense_additional = 0; 70 stats.defense_additional = 0; 71 stats.speed = 14; 72 stats.dspeed = 10; 73 stats.recalc(); 74 75 log_msg = ""; 76 77 stats.cooldown_ticks = 0; 78 79 haz = NULL; 80 81 img_main = ""; 82 img_armor = ""; 83 img_off = ""; 84 85 for (int i = 0; i < POWER_COUNT; i++) { 86 stats.hero_cooldown[i] = 0; 87 } 88 89 for (int i=0; i<4; i++) { 90 sound_steps[i] = NULL; 91 } 92} 93 94void Avatar::loadGraphics(const string& _img_main, string _img_armor, const string& _img_off) { 95 SDL_Surface *gfx_main = NULL; 96 SDL_Surface *gfx_off = NULL; 97 SDL_Surface *gfx_head = NULL; 98 SDL_Rect src; 99 SDL_Rect dest; 100 101 // Default appearance 102 if (_img_armor == "") _img_armor = "clothes"; 103 104 // Check if we really need to change the graphics 105 if (_img_main != img_main || _img_armor != img_armor || _img_off != img_off) { 106 img_main = _img_main; 107 img_armor = _img_armor; 108 img_off = _img_off; 109 110 // composite the hero graphic 111 if (sprites) SDL_FreeSurface(sprites); 112 sprites = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + img_armor + ".png").c_str()); 113 if (img_main != "") gfx_main = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + img_main + ".png").c_str()); 114 if (img_off != "") gfx_off = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + img_off + ".png").c_str()); 115 gfx_head = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + stats.head + ".png").c_str()); 116 117 SDL_SetColorKey( sprites, SDL_SRCCOLORKEY, SDL_MapRGB(sprites->format, 255, 0, 255) ); 118 if (gfx_main) SDL_SetColorKey( gfx_main, SDL_SRCCOLORKEY, SDL_MapRGB(gfx_main->format, 255, 0, 255) ); 119 if (gfx_off) SDL_SetColorKey( gfx_off, SDL_SRCCOLORKEY, SDL_MapRGB(gfx_off->format, 255, 0, 255) ); 120 if (gfx_head) SDL_SetColorKey( gfx_head, SDL_SRCCOLORKEY, SDL_MapRGB(gfx_head->format, 255, 0, 255) ); 121 122 // assuming the hero is right-handed, we know the layer z-order 123 // copy the furthest hand first 124 src.w = dest.w = 4096; 125 src.h = dest.h = 256; 126 src.x = dest.x = 0; 127 src.y = dest.y = 0; 128 if (gfx_main) SDL_BlitSurface(gfx_main, &src, sprites, &dest); // row 0,1 main hand 129 src.y = dest.y = 768; 130 if (gfx_main) SDL_BlitSurface(gfx_main, &src, sprites, &dest); // row 6,7 main hand 131 src.h = dest.h = 512; 132 src.y = dest.y = 256; 133 if (gfx_off) SDL_BlitSurface(gfx_off, &src, sprites, &dest); // row 2-5 off hand 134 135 // copy the head in the middle 136 src.h = dest.h = 1024; 137 src.y = dest.y = 0; 138 if (gfx_head) SDL_BlitSurface(gfx_head, &src, sprites, &dest); // head 139 140 // copy the closest hand last 141 src.w = dest.w = 4096; 142 src.h = dest.h = 256; 143 src.x = dest.x = 0; 144 src.y = dest.y = 0; 145 if (gfx_off) SDL_BlitSurface(gfx_off, &src, sprites, &dest); // row 0,1 off hand 146 src.y = dest.y = 768; 147 if (gfx_off) SDL_BlitSurface(gfx_off, &src, sprites, &dest); // row 6,7 off hand 148 src.h = dest.h = 512; 149 src.y = dest.y = 256; 150 if (gfx_main) SDL_BlitSurface(gfx_main, &src, sprites, &dest); // row 2-5 main hand 151 152 if (gfx_main) SDL_FreeSurface(gfx_main); 153 if (gfx_off) SDL_FreeSurface(gfx_off); 154 if (gfx_head) SDL_FreeSurface(gfx_head); 155 156 // optimize 157 SDL_Surface *cleanup = sprites; 158 sprites = SDL_DisplayFormatAlpha(sprites); 159 SDL_FreeSurface(cleanup); 160 } 161} 162 163void Avatar::loadSounds() { 164 sound_melee = Mix_LoadWAV(mods->locate("soundfx/melee_attack.ogg").c_str()); 165 sound_hit = Mix_LoadWAV(mods->locate("soundfx/" + stats.base + "_hit.ogg").c_str()); 166 sound_die = Mix_LoadWAV(mods->locate("soundfx/" + stats.base + "_die.ogg").c_str()); 167 sound_block = Mix_LoadWAV(mods->locate("soundfx/powers/block.ogg").c_str()); 168 level_up = Mix_LoadWAV(mods->locate("soundfx/level_up.ogg").c_str()); 169 170 if (!sound_melee || !sound_hit || !sound_die || !level_up) { 171 printf("Mix_LoadWAV: %s\n", Mix_GetError()); 172 } 173} 174 175/** 176 * Walking/running steps sound depends on worn armor 177 */ 178void Avatar::loadStepFX(const string& stepname) { 179 180 // TODO: put default step sound in engine config file 181 string filename = "cloth"; 182 if (stepname != "") { 183 filename = stepname; 184 } 185 186 // clear previous sounds 187 for (int i=0; i<4; i++) { 188 if (sound_steps[i] != NULL) { 189 Mix_FreeChunk(sound_steps[i]); 190 sound_steps[i] = NULL; 191 } 192 } 193 194 // load new sounds 195 sound_steps[0] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "1.ogg").c_str()); 196 sound_steps[1] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "2.ogg").c_str()); 197 sound_steps[2] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "3.ogg").c_str()); 198 sound_steps[3] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "4.ogg").c_str()); 199 200} 201 202 203bool Avatar::pressing_move() { 204 if(MOUSE_MOVE) { 205 return inp->pressing[MAIN1]; 206 } else { 207 return inp->pressing[UP] || inp->pressing[DOWN] || inp->pressing[LEFT] || inp->pressing[RIGHT]; 208 } 209} 210 211void Avatar::set_direction() { 212 // handle direction changes 213 if(MOUSE_MOVE) { 214 Point target = screen_to_map(inp->mouse.x, inp->mouse.y, stats.pos.x, stats.pos.y); 215 // if no line of movement to target, use pathfinder 216 if( !map->collider.line_of_movement(stats.pos.x, stats.pos.y, target.x, target.y) ) { 217 vector<Point> path; 218 // if a path is returned, target first waypoint 219 if ( map->collider.compute_path(stats.pos,target,path,1000) ) { 220 target = path.back(); 221 } 222 } 223 stats.direction = face(target.x, target.y); 224 } else { 225 if(inp->pressing[UP] && inp->pressing[LEFT]) stats.direction = 1; 226 else if(inp->pressing[UP] && inp->pressing[RIGHT]) stats.direction = 3; 227 else if(inp->pressing[DOWN] && inp->pressing[RIGHT]) stats.direction = 5; 228 else if(inp->pressing[DOWN] && inp->pressing[LEFT]) stats.direction = 7; 229 else if(inp->pressing[LEFT]) stats.direction = 0; 230 else if(inp->pressing[UP]) stats.direction = 2; 231 else if(inp->pressing[RIGHT]) stats.direction = 4; 232 else if(inp->pressing[DOWN]) stats.direction = 6; 233 } 234} 235 236/** 237 * logic() 238 * Handle a single frame. This includes: 239 * - move the avatar based on buttons pressed 240 * - calculate the next frame of animation 241 * - calculate camera position based on avatar position 242 * 243 * @param power_index The actionbar power activated. -1 means no power. 244 */ 245void Avatar::logic(int actionbar_power, bool restrictPowerUse) { 246 247 Point target; 248 int stepfx; 249 stats.logic(); 250 if (stats.forced_move_duration > 0) { 251 move(); 252 // calc new cam position from player position 253 // cam is focused at player position 254 map->cam.x = stats.pos.x; 255 map->cam.y = stats.pos.y; 256 map->hero_tile.x = stats.pos.x / 32; 257 map->hero_tile.y = stats.pos.y / 32; 258 return; 259 } 260 if (stats.stun_duration > 0) return; 261 bool allowed_to_move; 262 bool allowed_to_use_power; 263 264 // check level up 265 int max_spendable_stat_points = 16; 266 if (stats.xp >= stats.xp_table[stats.level] && stats.level < MAX_CHARACTER_LEVEL) { 267 stats.level++; 268 stringstream ss; 269 ss << msg->get("Congratulations, you have reached level %d!", stats.level); 270 if (stats.level < max_spendable_stat_points) { 271 ss << " " << msg->get("You may increase one attribute through the Character Menu."); 272 newLevelNotification = true; 273 } 274 log_msg = ss.str(); 275 stats.recalc(); 276 Mix_PlayChannel(-1, level_up, 0); 277 } 278 279 // check for bleeding spurt 280 if (stats.bleed_duration % 30 == 1) { 281 powers->activate(POWER_SPARK_BLOOD, &stats, stats.pos); 282 } 283 // check for bleeding to death 284 if (stats.hp == 0 && !(stats.cur_state == AVATAR_DEAD)) { 285 stats.cur_state = AVATAR_DEAD; 286 } 287 288 // assist mouse movement 289 if (!inp->pressing[MAIN1]) drag_walking = false; 290 291 // handle animation 292 activeAnimation->advanceFrame(); 293 294 switch(stats.cur_state) { 295 case AVATAR_STANCE: 296 297 setAnimation("stance"); 298 299 // allowed to move or use powers? 300 if (MOUSE_MOVE) { 301 allowed_to_move = restrictPowerUse && (!inp->lock[MAIN1] || drag_walking); 302 allowed_to_use_power = !allowed_to_move; 303 } 304 else { 305 allowed_to_move = true; 306 allowed_to_use_power = true; 307 } 308 309 // handle transitions to RUN 310 if (allowed_to_move) 311 set_direction(); 312 313 if (pressing_move() && allowed_to_move) { 314 if (MOUSE_MOVE && inp->pressing[MAIN1]) { 315 inp->lock[MAIN1] = true; 316 drag_walking = true; 317 } 318 319 if (move()) { // no collision 320 stats.cur_state = AVATAR_RUN; 321 } 322 323 } 324 // handle power usage 325 if (allowed_to_use_power && actionbar_power != -1 && stats.cooldown_ticks == 0) { 326 target = screen_to_map(inp->mouse.x, inp->mouse.y + powers->powers[actionbar_power].aim_assist, stats.pos.x, stats.pos.y); 327 328 // check requirements 329 if (powers->powers[actionbar_power].requires_mp > stats.mp) 330 break; 331 if (powers->powers[actionbar_power].requires_physical_weapon && !stats.wielding_physical) 332 break; 333 if (powers->powers[actionbar_power].requires_mental_weapon && !stats.wielding_mental) 334 break; 335 if (powers->powers[actionbar_power].requires_offense_weapon && !stats.wielding_offense) 336 break; 337 if (powers->powers[actionbar_power].requires_los && !map->collider.line_of_sight(stats.pos.x, stats.pos.y, target.x, target.y)) 338 break; 339 if (powers->powers[actionbar_power].requires_empty_target && !map->collider.is_empty(target.x, target.y)) 340 break; 341 if (stats.hero_cooldown[actionbar_power] > 0) 342 break; 343 344 stats.hero_cooldown[actionbar_power] = powers->powers[actionbar_power].cooldown; //set the cooldown timer 345 current_power = actionbar_power; 346 act_target.x = target.x; 347 act_target.y = target.y; 348 349 // is this a power that requires changing direction? 350 if (powers->powers[current_power].face) { 351 stats.direction = face(target.x, target.y); 352 } 353 354 // handle melee powers 355 if (powers->powers[current_power].new_state == POWSTATE_SWING) { 356 stats.cur_state = AVATAR_MELEE; 357 break; 358 } 359 // handle ranged powers 360 if (powers->powers[current_power].new_state == POWSTATE_SHOOT) { 361 stats.cur_state = AVATAR_SHOOT; 362 break; 363 } 364 // handle ment powers 365 if (powers->powers[current_power].new_state == POWSTATE_CAST) { 366 stats.cur_state = AVATAR_CAST; 367 break; 368 } 369 if (powers->powers[current_power].new_state == POWSTATE_BLOCK) { 370 stats.cur_state = AVATAR_BLOCK; 371 stats.blocking = true; 372 break; 373 } 374 } 375 376 break; 377 378 case AVATAR_RUN: 379 380 setAnimation("run"); 381 382 stepfx = rand() % 4; 383 384 if (activeAnimation->getCurFrame() == 1 || activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { 385 Mix_PlayChannel(-1, sound_steps[stepfx], 0); 386 } 387 388 // allowed to move or use powers? 389 if (MOUSE_MOVE) { 390 allowed_to_use_power = !(restrictPowerUse && !inp->lock[MAIN1]); 391 } 392 else { 393 allowed_to_use_power = true; 394 } 395 396 // handle direction changes 397 set_direction(); 398 399 // handle transition to STANCE 400 if (!pressing_move()) { 401 stats.cur_state = AVATAR_STANCE; 402 break; 403 } 404 else if (!move()) { // collide with wall 405 stats.cur_state = AVATAR_STANCE; 406 break; 407 } 408 409 // handle power usage 410 if (allowed_to_use_power && actionbar_power != -1 && stats.cooldown_ticks == 0) { 411 412 target = screen_to_map(inp->mouse.x, inp->mouse.y + powers->powers[actionbar_power].aim_assist, stats.pos.x, stats.pos.y); 413 414 // check requirements 415 if (powers->powers[actionbar_power].requires_mp > stats.mp) 416 break; 417 if (powers->powers[actionbar_power].requires_physical_weapon && !stats.wielding_physical) 418 break; 419 if (powers->powers[actionbar_power].requires_mental_weapon && !stats.wielding_mental) 420 break; 421 if (powers->powers[actionbar_power].requires_offense_weapon && !stats.wielding_offense) 422 break; 423 if (powers->powers[actionbar_power].requires_los && !map->collider.line_of_sight(stats.pos.x, stats.pos.y, target.x, target.y)) 424 break; 425 if (powers->powers[actionbar_power].requires_empty_target && !map->collider.is_empty(target.x, target.y)) 426 break; 427 if (stats.hero_cooldown[actionbar_power] > 0) 428 break; 429 430 stats.hero_cooldown[actionbar_power] = powers->powers[actionbar_power].cooldown; //set the cooldown timer 431 current_power = actionbar_power; 432 act_target.x = target.x; 433 act_target.y = target.y; 434 435 // is this a power that requires changing direction? 436 if (powers->powers[current_power].face) { 437 stats.direction = face(target.x, target.y); 438 } 439 440 // handle melee powers 441 if (powers->powers[current_power].new_state == POWSTATE_SWING) { 442 stats.cur_state = AVATAR_MELEE; 443 break; 444 } 445 // handle ranged powers 446 if (powers->powers[current_power].new_state == POWSTATE_SHOOT) { 447 stats.cur_state = AVATAR_SHOOT; 448 break; 449 } 450 // handle ment powers 451 if (powers->powers[current_power].new_state == POWSTATE_CAST) { 452 stats.cur_state = AVATAR_CAST; 453 break; 454 } 455 if (powers->powers[current_power].new_state == POWSTATE_BLOCK) { 456 stats.cur_state = AVATAR_BLOCK; 457 stats.blocking = true; 458 break; 459 } 460 } 461 462 break; 463 464 case AVATAR_MELEE: 465 466 setAnimation("melee"); 467 468 if (activeAnimation->getCurFrame() == 1) { 469 Mix_PlayChannel(-1, sound_melee, 0); 470 } 471 472 // do power 473 if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { 474 powers->activate(current_power, &stats, act_target); 475 } 476 477 if (activeAnimation->getTimesPlayed() >= 1) { 478 stats.cur_state = AVATAR_STANCE; 479 if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown; 480 } 481 break; 482 483 case AVATAR_CAST: 484 485 setAnimation("ment"); 486 487 // do power 488 if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { 489 powers->activate(current_power, &stats, act_target); 490 } 491 492 if (activeAnimation->getTimesPlayed() >= 1) { 493 stats.cur_state = AVATAR_STANCE; 494 if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown; 495 } 496 break; 497 498 499 case AVATAR_SHOOT: 500 501 setAnimation("ranged"); 502 503 // do power 504 if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { 505 powers->activate(current_power, &stats, act_target); 506 } 507 508 if (activeAnimation->getTimesPlayed() >= 1) { 509 stats.cur_state = AVATAR_STANCE; 510 if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown; 511 } 512 break; 513 514 case AVATAR_BLOCK: 515 516 setAnimation("block"); 517 518 if (powers->powers[actionbar_power].new_state != POWSTATE_BLOCK) { 519 stats.cur_state = AVATAR_STANCE; 520 stats.blocking = false; 521 } 522 break; 523 524 case AVATAR_HIT: 525 526 setAnimation("hit"); 527 528 if (activeAnimation->getTimesPlayed() >= 1) { 529 stats.cur_state = AVATAR_STANCE; 530 } 531 532 break; 533 534 case AVATAR_DEAD: 535 536 setAnimation("die"); 537 538 if (activeAnimation->getCurFrame() == 1 && activeAnimation->getTimesPlayed() < 1) { 539 Mix_PlayChannel(-1, sound_die, 0); 540 log_msg = msg->get("You are defeated. You lose half your gold. Press Enter to continue."); 541 } 542 543 if (activeAnimation->getTimesPlayed() >= 1) { 544 stats.corpse = true; 545 } 546 547 // allow respawn with Accept 548 if (inp->pressing[ACCEPT]) { 549 stats.hp = stats.maxhp; 550 stats.mp = stats.maxmp; 551 stats.alive = true; 552 stats.corpse = false; 553 stats.cur_state = AVATAR_STANCE; 554 555 // remove temporary effects 556 stats.clearEffects(); 557 558 // set teleportation variables. GameEngine acts on these. 559 map->teleportation = true; 560 map->teleport_mapname = map->respawn_map; 561 map->teleport_destination.x = map->respawn_point.x; 562 map->teleport_destination.y = map->respawn_point.y; 563 } 564 565 break; 566 567 default: 568 break; 569 } 570 571 // calc new cam position from player position 572 // cam is focused at player position 573 map->cam.x = stats.pos.x; 574 map->cam.y = stats.pos.y; 575 map->hero_tile.x = stats.pos.x / 32; 576 map->hero_tile.y = stats.pos.y / 32; 577 578 // check for map events 579 map->checkEvents(stats.pos); 580 581 // decrement all cooldowns 582 for (int i = 0; i < POWER_COUNT; i++){ 583 stats.hero_cooldown[i] -= 1000 / FRAMES_PER_SEC; 584 if (stats.hero_cooldown[i] < 0) stats.hero_cooldown[i] = 0; 585 } 586} 587 588/** 589 * Called by HazardManager 590 * Return false on a miss 591 */ 592bool Avatar::takeHit(Hazard h) { 593 594 if (stats.cur_state != AVATAR_DEAD) { 595 // check miss 596 int avoidance = stats.avoidance; 597 if (stats.blocking) avoidance *= 2; 598 if (rand() % 100 > (h.accuracy - avoidance + 25)) return false; 599 600 int dmg; 601 if (h.dmg_min == h.dmg_max) dmg = h.dmg_min; 602 else dmg = h.dmg_min + (rand() % (h.dmg_max - h.dmg_min + 1)); 603 604 // apply elemental resistance 605 // TODO: make this generic 606 if (h.trait_elemental == ELEMENT_FIRE) { 607 dmg = (dmg * stats.attunement_fire) / 100; 608 } 609 if (h.trait_elemental == ELEMENT_WATER) { 610 dmg = (dmg * stats.attunement_ice) / 100; 611 } 612 613 // apply absorption 614 int absorption; 615 if (!h.trait_armor_penetration) { // armor penetration ignores all absorption 616 if (stats.absorb_min == stats.absorb_max) absorption = stats.absorb_min; 617 else absorption = stats.absorb_min + (rand() % (stats.absorb_max - stats.absorb_min + 1)); 618 619 if (stats.blocking) absorption += absorption + stats.absorb_max; // blocking doubles your absorb amount 620 621 dmg = dmg - absorption; 622 if (dmg < 1 && !stats.blocking) dmg = 1; // when blocking, dmg can be reduced to 0 623 if (dmg <= 0) { 624 dmg = 0; 625 Mix_PlayChannel(-1, sound_block, 0); 626 activeAnimation->reset(); // shield stutter 627 } 628 } 629 630 631 int prev_hp = stats.hp; 632 stats.takeDamage(dmg); 633 634 // after effects 635 if (stats.hp > 0 && stats.immunity_duration == 0 && dmg > 0) { 636 if (h.stun_duration > stats.stun_duration) stats.stun_duration = h.stun_duration; 637 if (h.slow_duration > stats.slow_duration) stats.slow_duration = h.slow_duration; 638 if (h.bleed_duration > stats.bleed_duration) stats.bleed_duration = h.bleed_duration; 639 if (h.immobilize_duration > stats.immobilize_duration) stats.immobilize_duration = h.immobilize_duration; 640 if (h.forced_move_duration > stats.forced_move_duration) stats.forced_move_duration = h.forced_move_duration; 641 if (h.forced_move_speed != 0) { 642 float theta = powers->calcTheta(h.src_stats->pos.x, h.src_stats->pos.y, stats.pos.x, stats.pos.y); 643 stats.forced_speed.x = ceil((float)h.forced_move_speed * cos(theta)); 644 stats.forced_speed.y = ceil((float)h.forced_move_speed * sin(theta)); 645 } 646 if (h.hp_steal != 0) { 647 h.src_stats->hp += (int)ceil((float)dmg * (float)h.hp_steal / 100.0); 648 if (h.src_stats->hp > h.src_stats->maxhp) h.src_stats->hp = h.src_stats->maxhp; 649 } 650 // if (h.mp_steal != 0) { //enemies don't have MP 651 } 652 653 // post effect power 654 if (h.post_power >= 0 && dmg > 0) { 655 powers->activate(h.post_power, h.src_stats, stats.pos); 656 } 657 658 // Power-specific: Vengeance gains stacks when blocking 659 if (stats.blocking && stats.physdef >= 9) { 660 if (stats.vengeance_stacks < 3) 661 stats.vengeance_stacks++; 662 } 663 664 665 if (stats.hp <= 0) { 666 stats.cur_state = AVATAR_DEAD; 667 668 // raise the death penalty flag. Another module will read this and reset. 669 stats.death_penalty = true; 670 } 671 else if (prev_hp > stats.hp) { // only interrupt if damage was taken 672 Mix_PlayChannel(-1, sound_hit, 0); 673 stats.cur_state = AVATAR_HIT; 674 } 675 676 return true; 677 } 678 return false; 679} 680 681/** 682 * getRender() 683 * Map objects need to be drawn in Z order, so we allow a parent object (GameEngine) 684 * to collect all mobile sprites each frame. 685 */ 686Renderable Avatar::getRender() { 687 Renderable r = activeAnimation->getCurrentFrame(stats.direction); 688 r.sprite = sprites; 689 r.map_pos.x = stats.pos.x; 690 r.map_pos.y = stats.pos.y; 691 return r; 692} 693 694Avatar::~Avatar() { 695 696 SDL_FreeSurface(sprites); 697 Mix_FreeChunk(sound_melee); 698 Mix_FreeChunk(sound_hit); 699 Mix_FreeChunk(sound_die); 700 Mix_FreeChunk(sound_block); 701 Mix_FreeChunk(sound_steps[0]); 702 Mix_FreeChunk(sound_steps[1]); 703 Mix_FreeChunk(sound_steps[2]); 704 Mix_FreeChunk(sound_steps[3]); 705 Mix_FreeChunk(level_up); 706 707 delete haz; 708}