1 /*
2 Copyright © 2011-2012 Clint Bellanger
3 Copyright © 2012 Igor Paliychuk
4 Copyright © 2012 Stefan Beller
5 Copyright © 2013 Henrik Andersson
6 Copyright © 2012-2016 Justin Jacobs
7
8 This file is part of FLARE.
9
10 FLARE is free software: you can redistribute it and/or modify it under the terms
11 of the GNU General Public License as published by the Free Software Foundation,
12 either version 3 of the License, or (at your option) any later version.
13
14 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE. See the GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License along with
19 FLARE. If not, see http://www.gnu.org/licenses/
20 */
21
22 /**
23 * class Avatar
24 *
25 * Contains logic and rendering routines for the player avatar.
26 */
27
28 #include "Animation.h"
29 #include "AnimationManager.h"
30 #include "AnimationSet.h"
31 #include "Avatar.h"
32 #include "CommonIncludes.h"
33 #include "CursorManager.h"
34 #include "EnemyGroupManager.h"
35 #include "Entity.h"
36 #include "EntityManager.h"
37 #include "EngineSettings.h"
38 #include "FileParser.h"
39 #include "InputState.h"
40 #include "MapRenderer.h"
41 #include "MenuActionBar.h"
42 #include "MenuExit.h"
43 #include "MenuGameOver.h"
44 #include "MenuManager.h"
45 #include "MessageEngine.h"
46 #include "ModManager.h"
47 #include "PowerManager.h"
48 #include "RenderDevice.h"
49 #include "SaveLoad.h"
50 #include "Settings.h"
51 #include "SharedGameResources.h"
52 #include "SharedResources.h"
53 #include "SoundManager.h"
54 #include "Utils.h"
55 #include "UtilsMath.h"
56 #include "UtilsParsing.h"
57
Avatar()58 Avatar::Avatar()
59 : Entity()
60 , attack_cursor(false)
61 , mm_key(settings->mouse_move_swap ? Input::MAIN2 : Input::MAIN1)
62 , hero_stats(NULL)
63 , charmed_stats(NULL)
64 , act_target()
65 , drag_walking(false)
66 , respawn(false)
67 , close_menus(false)
68 , allow_movement(true)
69 , cursor_enemy(NULL)
70 , lock_enemy(NULL)
71 , time_played(0)
72 , questlog_dismissed(false)
73 , using_main1(false)
74 , using_main2(false)
75 , prev_hp(0)
76 , playing_lowhp(false)
77 , teleport_camera_lock(false) {
78
79 init();
80
81 // load the hero's animations from hero definition file
82 anim->increaseCount("animations/hero.txt");
83 animationSet = anim->getAnimationSet("animations/hero.txt");
84 activeAnimation = animationSet->getAnimation("");
85
86 // set cooldown_hit to duration of hit animation if undefined
87 if (!stats.cooldown_hit_enabled) {
88 Animation *hit_anim = animationSet->getAnimation("hit");
89 if (hit_anim) {
90 stats.cooldown_hit.setDuration(hit_anim->getDuration());
91 delete hit_anim;
92 }
93 else {
94 stats.cooldown_hit.setDuration(0);
95 }
96 }
97
98 loadLayerDefinitions();
99
100 // load foot-step definitions
101 // @CLASS Avatar: Step sounds|Description of items/step_sounds.txt
102 FileParser infile;
103 if (infile.open("items/step_sounds.txt", FileParser::MOD_FILE, FileParser::ERROR_NONE)) {
104 while (infile.next()) {
105 if (infile.key == "id") {
106 // @ATTR id|string|An identifier name for a set of step sounds.
107 step_def.push_back(Step_sfx());
108 step_def.back().id = infile.val;
109 }
110
111 if (step_def.empty()) continue;
112
113 if (infile.key == "step") {
114 // @ATTR step|filename|Filename of a step sound effect.
115 step_def.back().steps.push_back(infile.val);
116 }
117 }
118 infile.close();
119 }
120
121 loadStepFX(stats.sfx_step);
122 }
123
init()124 void Avatar::init() {
125
126 // name, base, look are set by GameStateNew so don't reset it here
127
128 // other init
129 sprites = 0;
130 stats.cur_state = StatBlock::ENTITY_STANCE;
131 if (mapr->hero_pos_enabled) {
132 stats.pos.x = mapr->hero_pos.x;
133 stats.pos.y = mapr->hero_pos.y;
134 }
135 current_power = 0;
136 newLevelNotification = false;
137
138 stats.hero = true;
139 stats.humanoid = true;
140 stats.level = 1;
141 stats.xp = 0;
142 for (size_t i = 0; i < eset->primary_stats.list.size(); ++i) {
143 stats.primary[i] = stats.primary_starting[i] = 1;
144 stats.primary_additional[i] = 0;
145 }
146 stats.speed = 0.2f;
147 stats.recalc();
148
149 while (!log_msg.empty()) {
150 log_msg.pop();
151 }
152 respawn = false;
153
154 stats.cooldown.reset(Timer::END);
155
156 body = -1;
157
158 transform_triggered = false;
159 setPowers = false;
160 revertPowers = false;
161 last_transform = "";
162
163 power_cooldown_timers.clear();
164 power_cast_timers.clear();
165
166 // Find untransform power index to use for manual untransfrom ability
167 untransform_power = 0;
168 std::map<PowerID, Power>::iterator power_it;
169 for (power_it = powers->powers.begin(); power_it != powers->powers.end(); ++power_it) {
170 if (untransform_power == 0 && power_it->second.required_items.empty() && power_it->second.spawn_type == "untransform") {
171 untransform_power = power_it->first;
172 }
173
174 power_cooldown_timers[power_it->first] = Timer();
175 power_cast_timers[power_it->first] = Timer();
176 }
177 }
178
handleNewMap()179 void Avatar::handleNewMap() {
180 cursor_enemy = NULL;
181 lock_enemy = NULL;
182 playing_lowhp = false;
183 }
184
185 /**
186 * Load avatar sprite layer definitions into vector.
187 */
loadLayerDefinitions()188 void Avatar::loadLayerDefinitions() {
189 layer_def = std::vector<std::vector<unsigned> >(8, std::vector<unsigned>());
190 layer_reference_order = std::vector<std::string>();
191
192 FileParser infile;
193 // @CLASS Avatar: Hero layers|Description of engine/hero_layers.txt
194 if (infile.open("engine/hero_layers.txt", FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
195 while(infile.next()) {
196 if (infile.key == "layer") {
197 // @ATTR layer|direction, list(string) : Direction, Layer name(s)|Defines the hero avatar sprite layer
198 unsigned dir = Parse::toDirection(Parse::popFirstString(infile.val));
199 if (dir>7) {
200 infile.error("Avatar: Hero layer direction must be in range [0,7]");
201 Utils::logErrorDialog("Avatar: Hero layer direction must be in range [0,7]");
202 mods->resetModConfig();
203 Utils::Exit(1);
204 }
205 std::string layer = Parse::popFirstString(infile.val);
206 while (layer != "") {
207 // check if already in layer_reference:
208 unsigned ref_pos;
209 for (ref_pos = 0; ref_pos < layer_reference_order.size(); ++ref_pos)
210 if (layer == layer_reference_order[ref_pos])
211 break;
212 if (ref_pos == layer_reference_order.size())
213 layer_reference_order.push_back(layer);
214 layer_def[dir].push_back(ref_pos);
215
216 layer = Parse::popFirstString(infile.val);
217 }
218 }
219 else {
220 infile.error("Avatar: '%s' is not a valid key.", infile.key.c_str());
221 }
222 }
223 infile.close();
224 }
225
226 // There are the positions of the items relative to layer_reference_order
227 // so if layer_reference_order=main,body,head,off
228 // and we got a layer=3,off,body,head,main
229 // then the layer_def[3] looks like (3,1,2,0)
230 }
231
loadGraphics(std::vector<Layer_gfx> _img_gfx)232 void Avatar::loadGraphics(std::vector<Layer_gfx> _img_gfx) {
233
234 for (unsigned int i=0; i<animsets.size(); i++) {
235 if (animsets[i])
236 anim->decreaseCount(animsets[i]->getName());
237 delete anims[i];
238 }
239 animsets.clear();
240 anims.clear();
241
242 for (unsigned int i=0; i<_img_gfx.size(); i++) {
243 if (_img_gfx[i].gfx != "") {
244 std::string name = "animations/avatar/"+stats.gfx_base+"/"+_img_gfx[i].gfx+".txt";
245 anim->increaseCount(name);
246 animsets.push_back(anim->getAnimationSet(name));
247 animsets.back()->setParent(animationSet);
248 anims.push_back(animsets.back()->getAnimation(activeAnimation->getName()));
249 setAnimation("stance");
250 if(!anims.back()->syncTo(activeAnimation)) {
251 Utils::logError("Avatar: Error syncing animation in '%s' to 'animations/hero.txt'.", animsets.back()->getName().c_str());
252 }
253 }
254 else {
255 animsets.push_back(NULL);
256 anims.push_back(NULL);
257 }
258 }
259 anim->cleanUp();
260 }
261
262 /**
263 * Walking/running steps sound depends on worn armor
264 */
loadStepFX(const std::string & stepname)265 void Avatar::loadStepFX(const std::string& stepname) {
266 std::string filename = stats.sfx_step;
267 if (stepname != "") {
268 filename = stepname;
269 }
270
271 // clear previous sounds
272 for (unsigned i=0; i<sound_steps.size(); i++) {
273 snd->unload(sound_steps[i]);
274 }
275 sound_steps.clear();
276
277 if (filename == "") return;
278
279 // A literal "NULL" means we don't want to load any new sounds
280 // This is used when transforming, since creatures don't have step sound effects
281 if (stepname == "NULL") return;
282
283 // load new sounds
284 for (unsigned i=0; i<step_def.size(); i++) {
285 if (step_def[i].id == filename) {
286 sound_steps.resize(step_def[i].steps.size());
287 for (unsigned j=0; j<sound_steps.size(); j++) {
288 sound_steps[j] = snd->load(step_def[i].steps[j], "Avatar loading foot steps");
289 }
290 return;
291 }
292 }
293
294 // Could not find step sound fx
295 Utils::logError("Avatar: Could not find footstep sounds for '%s'.", filename.c_str());
296 }
297
298
pressing_move()299 bool Avatar::pressing_move() {
300 if (!allow_movement || teleport_camera_lock) {
301 return false;
302 }
303 else if (stats.effects.knockback_speed != 0) {
304 return false;
305 }
306 else if (settings->mouse_move) {
307 return inpt->pressing[mm_key] && !inpt->pressing[Input::SHIFT];
308 }
309 else {
310 return (inpt->pressing[Input::UP] && !inpt->lock[Input::UP]) ||
311 (inpt->pressing[Input::DOWN] && !inpt->lock[Input::DOWN]) ||
312 (inpt->pressing[Input::LEFT] && !inpt->lock[Input::LEFT]) ||
313 (inpt->pressing[Input::RIGHT] && !inpt->lock[Input::RIGHT]);
314 }
315 }
316
set_direction()317 void Avatar::set_direction() {
318 if (teleport_camera_lock || !set_dir_timer.isEnd())
319 return;
320
321 int old_dir = stats.direction;
322
323 // handle direction changes
324 if (settings->mouse_move) {
325 FPoint target = Utils::screenToMap(inpt->mouse.x, inpt->mouse.y, mapr->cam.pos.x, mapr->cam.pos.y);
326 stats.direction = Utils::calcDirection(stats.pos.x, stats.pos.y, target.x, target.y);
327 }
328 else {
329 if (inpt->pressing[Input::UP] && !inpt->lock[Input::UP] && inpt->pressing[Input::LEFT] && !inpt->lock[Input::LEFT]) stats.direction = 1;
330 else if (inpt->pressing[Input::UP] && !inpt->lock[Input::UP] && inpt->pressing[Input::RIGHT] && !inpt->lock[Input::RIGHT]) stats.direction = 3;
331 else if (inpt->pressing[Input::DOWN] && !inpt->lock[Input::DOWN] && inpt->pressing[Input::RIGHT] && !inpt->lock[Input::RIGHT]) stats.direction = 5;
332 else if (inpt->pressing[Input::DOWN] && !inpt->lock[Input::DOWN] && inpt->pressing[Input::LEFT] && !inpt->lock[Input::LEFT]) stats.direction = 7;
333 else if (inpt->pressing[Input::LEFT] && !inpt->lock[Input::LEFT]) stats.direction = 0;
334 else if (inpt->pressing[Input::UP] && !inpt->lock[Input::UP]) stats.direction = 2;
335 else if (inpt->pressing[Input::RIGHT] && !inpt->lock[Input::RIGHT]) stats.direction = 4;
336 else if (inpt->pressing[Input::DOWN] && !inpt->lock[Input::DOWN]) stats.direction = 6;
337 // Adjust for ORTHO tilesets
338 if (eset->tileset.orientation == eset->tileset.TILESET_ORTHOGONAL &&
339 ((inpt->pressing[Input::UP] && !inpt->lock[Input::UP]) || (inpt->pressing[Input::DOWN] && !inpt->lock[Input::UP]) ||
340 (inpt->pressing[Input::LEFT] && !inpt->lock[Input::LEFT]) || (inpt->pressing[Input::RIGHT] && !inpt->lock[Input::RIGHT])))
341 stats.direction = static_cast<unsigned char>((stats.direction == 7) ? 0 : stats.direction + 1);
342 }
343
344 // give direction changing a 100ms cooldown
345 // this allows the player to quickly change direction on their own without becoming overly "jittery"
346 // the cooldown can be ended by releasing the move button, but the cooldown is so fast that it doesn't matter much (maybe a speed run tactic?)
347 if (stats.direction != old_dir)
348 set_dir_timer.setDuration(settings->max_frames_per_sec / 10);
349 }
350
351 /**
352 * logic()
353 * Handle a single frame. This includes:
354 * - move the avatar based on buttons pressed
355 * - calculate the next frame of animation
356 * - calculate camera position based on avatar position
357 */
logic()358 void Avatar::logic() {
359 bool restrict_power_use = false;
360 if (settings->mouse_move) {
361 if(inpt->pressing[mm_key] && !inpt->pressing[Input::SHIFT] && !menu->act->isWithinSlots(inpt->mouse) && !menu->act->isWithinMenus(inpt->mouse)) {
362 restrict_power_use = true;
363 }
364 }
365
366 // clear current space to allow correct movement
367 mapr->collider.unblock(stats.pos.x, stats.pos.y);
368
369 // turn on all passive powers
370 if ((stats.hp > 0 || stats.effects.triggered_death) && !respawn && !transform_triggered)
371 powers->activatePassives(&stats);
372
373 if (transform_triggered)
374 transform_triggered = false;
375
376 // handle when the player stops blocking
377 if (stats.effects.triggered_block && !stats.blocking) {
378 stats.cur_state = StatBlock::ENTITY_STANCE;
379 stats.effects.triggered_block = false;
380 stats.effects.clearTriggerEffects(Power::TRIGGER_BLOCK);
381 stats.refresh_stats = true;
382 stats.block_power = 0;
383 }
384
385 stats.logic();
386
387 // alert on low health
388 if (isDroppedToLowHp()) {
389 // show message if set
390 if (isLowHpMessageEnabled()) {
391 logMsg(msg->get("Your health is low!"), MSG_NORMAL);
392 }
393 // play a sound if set in settings
394 if (isLowHpSoundEnabled() && !playing_lowhp) {
395 // if looping, then do not cleanup
396 snd->play(sound_lowhp, "lowhp", snd->NO_POS, stats.sfx_lowhp_loop, !stats.sfx_lowhp_loop);
397 playing_lowhp = true;
398 }
399 }
400 // if looping, stop sounds when HP recovered above threshold
401 if (isLowHpSoundEnabled() && !isLowHp() && playing_lowhp && stats.sfx_lowhp_loop) {
402 snd->pauseChannel("lowhp");
403 playing_lowhp = false;
404 }
405 else if (isLowHpSoundEnabled() && isLowHp() && !playing_lowhp && stats.sfx_lowhp_loop) {
406 snd->play(sound_lowhp, "lowhp", snd->NO_POS, stats.sfx_lowhp_loop, !stats.sfx_lowhp_loop);
407 playing_lowhp = true;
408 }
409 else if (!isLowHpSoundEnabled() && playing_lowhp) {
410 snd->pauseChannel("lowhp");
411 playing_lowhp = false;
412 }
413
414 // we can not use stats.prev_hp here
415 prev_hp = stats.hp;
416
417 // check level up
418 if (stats.level < eset->xp.getMaxLevel() && stats.xp >= eset->xp.getLevelXP(stats.level + 1)) {
419 stats.level_up = true;
420 stats.level = eset->xp.getLevelFromXP(stats.xp);
421 logMsg(msg->get("Congratulations, you have reached level %d!", stats.level), MSG_NORMAL);
422 if (pc->stats.stat_points_per_level > 0) {
423 logMsg(msg->get("You may increase one or more attributes through the Character Menu."), MSG_NORMAL);
424 newLevelNotification = true;
425 }
426 if (pc->stats.power_points_per_level > 0) {
427 logMsg(msg->get("You may unlock one or more abilities through the Powers Menu."), MSG_NORMAL);
428 }
429 stats.recalc();
430 snd->play(sound_levelup, snd->DEFAULT_CHANNEL, snd->NO_POS, !snd->LOOP);
431
432 // if the player managed to level up while dead (e.g. via a bleeding creature), restore to life
433 if (stats.cur_state == StatBlock::ENTITY_DEAD) {
434 stats.cur_state = StatBlock::ENTITY_STANCE;
435 }
436 }
437
438 // assist mouse movement
439 mm_key = settings->mouse_move_swap ? Input::MAIN2 : Input::MAIN1;
440 if (!inpt->pressing[mm_key]) {
441 drag_walking = false;
442 }
443
444 // block some interactions when attacking/moving
445 using_main1 = inpt->pressing[Input::MAIN1] && !inpt->lock[Input::MAIN1];
446 using_main2 = inpt->pressing[Input::MAIN2] && !inpt->lock[Input::MAIN2];
447
448 // handle animation
449 if (!stats.effects.stun) {
450 activeAnimation->advanceFrame();
451 for (unsigned i=0; i < anims.size(); i++) {
452 if (anims[i] != NULL)
453 anims[i]->advanceFrame();
454 }
455 }
456
457 // save a valid tile position in the event that we untransform on an invalid tile
458 if (stats.transformed && mapr->collider.isValidPosition(stats.pos.x, stats.pos.y, MapCollision::MOVE_NORMAL, MapCollision::COLLIDE_HERO)) {
459 transform_pos = stats.pos;
460 transform_map = mapr->getFilename();
461 }
462
463 PowerID mm_attack_id = (settings->mouse_move_swap ? menu->act->getSlotPower(MenuActionBar::SLOT_MAIN2) : menu->act->getSlotPower(MenuActionBar::SLOT_MAIN1));
464 bool mm_can_use_power = true;
465
466 if (settings->mouse_move) {
467 if (!inpt->pressing[mm_key]) {
468 lock_enemy = NULL;
469 }
470 if (lock_enemy && lock_enemy->stats.hp <= 0) {
471 lock_enemy = NULL;
472 }
473 if (mm_attack_id > 0) {
474 if (!stats.canUsePower(mm_attack_id, !StatBlock::CAN_USE_PASSIVE)) {
475 lock_enemy = NULL;
476 mm_can_use_power = false;
477 }
478 else if (!power_cooldown_timers[mm_attack_id].isEnd()) {
479 lock_enemy = NULL;
480 mm_can_use_power = false;
481 }
482 else if (lock_enemy && powers->powers[mm_attack_id].requires_los && !mapr->collider.lineOfSight(stats.pos.x, stats.pos.y, lock_enemy->stats.pos.x, lock_enemy->stats.pos.y)) {
483 lock_enemy = NULL;
484 mm_can_use_power = false;
485 }
486 }
487 }
488
489 if (teleport_camera_lock && Utils::calcDist(stats.pos, mapr->cam.pos) < 0.5f) {
490 teleport_camera_lock = false;
491 }
492
493 set_dir_timer.tick();
494 if (!pressing_move()) {
495 set_dir_timer.reset(Timer::END);
496 }
497
498 if (!stats.effects.stun) {
499 bool allowed_to_move;
500 bool allowed_to_use_power = true;
501
502 switch(stats.cur_state) {
503 case StatBlock::ENTITY_STANCE:
504
505 setAnimation("stance");
506
507 // allowed to move or use powers?
508 if (settings->mouse_move) {
509 allowed_to_move = restrict_power_use && (!inpt->lock[mm_key] || drag_walking) && !lock_enemy;
510 allowed_to_use_power = true;
511
512 if ((inpt->pressing[mm_key] && inpt->pressing[Input::SHIFT]) || lock_enemy) {
513 inpt->lock[mm_key] = false;
514 }
515 }
516 else {
517 allowed_to_move = true;
518 allowed_to_use_power = true;
519 }
520
521 // handle transitions to RUN
522 if (allowed_to_move)
523 set_direction();
524
525 if (pressing_move() && allowed_to_move) {
526 if (move()) { // no collision
527 if (settings->mouse_move && inpt->pressing[mm_key]) {
528 inpt->lock[mm_key] = true;
529 drag_walking = true;
530 }
531
532 stats.cur_state = StatBlock::ENTITY_MOVE;
533 }
534 }
535
536 if (settings->mouse_move && settings->mouse_move_attack && cursor_enemy && !cursor_enemy->stats.hero_ally && mm_can_use_power && powers->checkCombatRange(mm_attack_id, &stats, cursor_enemy->stats.pos)) {
537 stats.cur_state = StatBlock::ENTITY_STANCE;
538 lock_enemy = cursor_enemy;
539 }
540
541 break;
542
543 case StatBlock::ENTITY_MOVE:
544
545 setAnimation("run");
546
547 if (!sound_steps.empty()) {
548 int stepfx = rand() % static_cast<int>(sound_steps.size());
549
550 if (activeAnimation->isFirstFrame() || activeAnimation->isActiveFrame())
551 snd->play(sound_steps[stepfx], snd->DEFAULT_CHANNEL, snd->NO_POS, !snd->LOOP);
552 }
553
554 // handle direction changes
555 set_direction();
556
557 // handle transition to STANCE
558 if (!pressing_move()) {
559 stats.cur_state = StatBlock::ENTITY_STANCE;
560 break;
561 }
562 else if (!move()) { // collide with wall
563 stats.cur_state = StatBlock::ENTITY_STANCE;
564 break;
565 }
566 else if (settings->mouse_move && inpt->pressing[Input::SHIFT]) {
567 // when moving with the mouse, pressing Shift should stop movement and begin attacking
568 stats.cur_state = StatBlock::ENTITY_STANCE;
569 break;
570 }
571
572 if (activeAnimation->getName() != "run")
573 stats.cur_state = StatBlock::ENTITY_STANCE;
574
575 if (settings->mouse_move && settings->mouse_move_attack && cursor_enemy && !cursor_enemy->stats.hero_ally && mm_can_use_power && powers->checkCombatRange(mm_attack_id, &stats, cursor_enemy->stats.pos)) {
576 stats.cur_state = StatBlock::ENTITY_STANCE;
577 lock_enemy = cursor_enemy;
578 }
579
580 break;
581
582 case StatBlock::ENTITY_POWER:
583
584 setAnimation(attack_anim);
585
586 if (attack_cursor) {
587 curs->setCursor(CursorManager::CURSOR_ATTACK);
588 }
589
590 if (activeAnimation->isFirstFrame()) {
591 float attack_speed = (stats.effects.getAttackSpeed(attack_anim) * powers->powers[current_power].attack_speed) / 100.0f;
592 activeAnimation->setSpeed(attack_speed);
593 for (size_t i=0; i<anims.size(); ++i) {
594 if (anims[i])
595 anims[i]->setSpeed(attack_speed);
596 }
597 playAttackSound(attack_anim);
598 power_cast_timers[current_power].setDuration(activeAnimation->getDuration());
599 }
600
601 // do power
602 if (activeAnimation->isActiveFrame() && !stats.hold_state) {
603 // some powers check if the caster is blocking a tile
604 // so we block the player tile prematurely here
605 mapr->collider.block(stats.pos.x, stats.pos.y, !MapCollision::IS_ALLY);
606
607 powers->activate(current_power, &stats, act_target);
608 power_cooldown_timers[current_power].setDuration(powers->powers[current_power].cooldown);
609
610 if (!stats.state_timer.isEnd())
611 stats.hold_state = true;
612 }
613
614 // animation is done, switch back to normal stance
615 if ((activeAnimation->isLastFrame() && stats.state_timer.isEnd()) || activeAnimation->getName() != attack_anim) {
616 stats.cur_state = StatBlock::ENTITY_STANCE;
617 stats.cooldown.reset(Timer::BEGIN);
618 allowed_to_use_power = false;
619 stats.prevent_interrupt = false;
620 if (settings->mouse_move) {
621 drag_walking = true;
622 }
623 }
624
625 if (settings->mouse_move && lock_enemy && !powers->checkCombatRange(mm_attack_id, &stats, lock_enemy->stats.pos)) {
626 lock_enemy = NULL;
627 }
628
629 break;
630
631 case StatBlock::ENTITY_BLOCK:
632
633 setAnimation("block");
634
635 stats.blocking = false;
636
637 break;
638
639 case StatBlock::ENTITY_HIT:
640
641 setAnimation("hit");
642
643 if (activeAnimation->isFirstFrame()) {
644 stats.effects.triggered_hit = true;
645
646 if (stats.block_power != 0) {
647 power_cooldown_timers[stats.block_power].setDuration(powers->powers[stats.block_power].cooldown);
648 stats.block_power = 0;
649 }
650 }
651
652 if (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != "hit") {
653 stats.cur_state = StatBlock::ENTITY_STANCE;
654 if (settings->mouse_move) {
655 drag_walking = true;
656 }
657 }
658
659 break;
660
661 case StatBlock::ENTITY_DEAD:
662 allowed_to_use_power = false;
663
664 if (stats.effects.triggered_death) break;
665
666 if (stats.transformed) {
667 stats.transform_duration = 0;
668 untransform();
669 }
670
671 setAnimation("die");
672
673 if (!stats.corpse && activeAnimation->isFirstFrame() && activeAnimation->getTimesPlayed() < 1) {
674 stats.effects.clearEffects();
675 stats.powers_passive.clear();
676
677 // reset power cooldowns
678 std::map<size_t, Timer>::iterator pct_it;
679 for (pct_it = power_cooldown_timers.begin(); pct_it != power_cooldown_timers.end(); ++pct_it) {
680 pct_it->second.reset(Timer::END);
681 power_cast_timers[pct_it->first].reset(Timer::END);
682 }
683
684 // close menus in GameStatePlay
685 close_menus = true;
686
687 playSound(Entity::SOUND_DIE);
688
689 logMsg(msg->get("You are defeated."), MSG_NORMAL);
690
691 if (stats.permadeath) {
692 // ignore death penalty on permadeath and instead delete the player's saved game
693 stats.death_penalty = false;
694 Utils::removeSaveDir(save_load->getGameSlot());
695 menu->exit->disableSave();
696 menu->game_over->disableSave();
697 }
698 else {
699 // raise the death penalty flag. This is handled in MenuInventory
700 stats.death_penalty = true;
701 }
702
703 // if the player is attacking, we need to block further input
704 if (inpt->pressing[Input::MAIN1])
705 inpt->lock[Input::MAIN1] = true;
706 }
707
708 if (!stats.corpse && (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != "die")) {
709 stats.corpse = true;
710 menu->game_over->visible = true;
711 }
712
713 // allow respawn with Accept if not permadeath
714 if (menu->game_over->visible && menu->game_over->continue_clicked) {
715 menu->game_over->close();
716
717 mapr->teleportation = true;
718 mapr->teleport_mapname = mapr->respawn_map;
719
720 if (stats.permadeath) {
721 // set these positions so it doesn't flash before jumping to Title
722 mapr->teleport_destination.x = stats.pos.x;
723 mapr->teleport_destination.y = stats.pos.y;
724 }
725 else {
726 respawn = true;
727
728 // set teleportation variables. GameEngine acts on these.
729 mapr->teleport_destination.x = mapr->respawn_point.x;
730 mapr->teleport_destination.y = mapr->respawn_point.y;
731 }
732 }
733
734 break;
735
736 default:
737 break;
738 }
739
740 // handle power usage
741 if (allowed_to_use_power) {
742 bool blocking = false;
743
744 for (unsigned i=0; i<action_queue.size(); i++) {
745 ActionData &action = action_queue[i];
746 PowerID power_id = powers->checkReplaceByEffect(action.power, &stats);
747 const Power &power = powers->powers[power_id];
748
749 if (power.type == Power::TYPE_BLOCK)
750 blocking = true;
751
752 if (power_id != 0 && (stats.cooldown.isEnd() || action.instant_item)) {
753 FPoint target = action.target;
754
755 // check requirements
756 if ((stats.cur_state == StatBlock::ENTITY_POWER || stats.cur_state == StatBlock::ENTITY_HIT) && !action.instant_item)
757 continue;
758 if (!stats.canUsePower(power_id, !StatBlock::CAN_USE_PASSIVE))
759 continue;
760 if (power.requires_los && !mapr->collider.lineOfSight(stats.pos.x, stats.pos.y, target.x, target.y))
761 continue;
762 if (power.requires_empty_target && !mapr->collider.isEmpty(target.x, target.y))
763 continue;
764 if (!power_cooldown_timers[power_id].isEnd())
765 continue;
766 if (!powers->hasValidTarget(power_id, &stats, target))
767 continue;
768
769 // automatically target the selected enemy with melee attacks
770 if (inpt->usingMouse() && power.type == Power::TYPE_FIXED && power.starting_pos == Power::STARTING_POS_MELEE && cursor_enemy) {
771 target = cursor_enemy->stats.pos;
772 }
773
774 // is this a power that requires changing direction?
775 if (power.face) {
776 stats.direction = Utils::calcDirection(stats.pos.x, stats.pos.y, target.x, target.y);
777 }
778
779 if (power.new_state != Power::STATE_INSTANT) {
780 current_power = power_id;
781 act_target = target;
782 attack_anim = power.attack_anim;
783 }
784
785 if (power.state_duration > 0)
786 stats.state_timer.setDuration(power.state_duration);
787
788 if (power.charge_speed != 0.0f)
789 stats.charge_speed = power.charge_speed;
790
791 stats.prevent_interrupt = power.prevent_interrupt;
792
793 if (power.pre_power > 0 && Math::percentChance(power.pre_power_chance)) {
794 powers->activate(power.pre_power, &stats, target);
795 }
796
797 switch (power.new_state) {
798 case Power::STATE_ATTACK: // handle attack powers
799 stats.cur_state = StatBlock::ENTITY_POWER;
800 break;
801
802 case Power::STATE_INSTANT: // handle instant powers
803 powers->activate(power_id, &stats, target);
804 power_cooldown_timers[power_id].setDuration(power.cooldown);
805 break;
806
807 default:
808 if (power.type == Power::TYPE_BLOCK) {
809 stats.cur_state = StatBlock::ENTITY_BLOCK;
810 powers->activate(power_id, &stats, target);
811 stats.refresh_stats = true;
812 }
813 break;
814 }
815
816 // if the player is attacking, show the attack cursor
817 attack_cursor = (
818 stats.cur_state == StatBlock::ENTITY_POWER &&
819 !power.buff && !power.buff_teleport &&
820 power.type != Power::TYPE_TRANSFORM &&
821 power.type != Power::TYPE_BLOCK &&
822 !(power.starting_pos == Power::STARTING_POS_SOURCE && power.speed == 0)
823 );
824
825 }
826 }
827
828 stats.blocking = blocking;
829 }
830
831 }
832
833 // update camera
834 mapr->cam.setTarget(stats.pos);
835
836 // check for map events
837 mapr->checkEvents(stats.pos);
838
839 // decrement all cooldowns
840 std::map<size_t, Timer>::iterator pct_it;
841 for (pct_it = power_cooldown_timers.begin(); pct_it != power_cooldown_timers.end(); ++pct_it) {
842 pct_it->second.tick();
843 power_cast_timers[pct_it->first].tick();
844 }
845
846 // make the current square solid
847 mapr->collider.block(stats.pos.x, stats.pos.y, !MapCollision::IS_ALLY);
848
849 if (stats.state_timer.isEnd() && stats.hold_state)
850 stats.hold_state = false;
851
852 if (stats.cur_state != StatBlock::ENTITY_POWER && stats.charge_speed != 0.0f)
853 stats.charge_speed = 0.0f;
854 }
855
transform()856 void Avatar::transform() {
857 // dead players can't transform
858 if (stats.hp <= 0)
859 return;
860
861 // calling a transform power locks the actionbar, so we unlock it here
862 inpt->unlockActionBar();
863
864 delete charmed_stats;
865 charmed_stats = NULL;
866
867 Enemy_Level el = enemyg->getRandomEnemy(stats.transform_type, 0, 0);
868
869 if (el.type != "") {
870 charmed_stats = new StatBlock();
871 charmed_stats->load(el.type);
872 }
873 else {
874 Utils::logError("Avatar: Could not transform into creature type '%s'", stats.transform_type.c_str());
875 stats.transform_type = "";
876 return;
877 }
878
879 transform_triggered = true;
880 stats.transformed = true;
881 setPowers = true;
882
883 // temporary save hero stats
884 delete hero_stats;
885
886 hero_stats = new StatBlock();
887 *hero_stats = stats;
888
889 // do not allow two copies of the summons list
890 hero_stats->summons.clear();
891
892 // replace some hero stats
893 stats.speed = charmed_stats->speed;
894 stats.flying = charmed_stats->flying;
895 stats.intangible = charmed_stats->intangible;
896 stats.humanoid = charmed_stats->humanoid;
897 stats.animations = charmed_stats->animations;
898 stats.powers_list = charmed_stats->powers_list;
899 stats.powers_passive = charmed_stats->powers_passive;
900 stats.effects.clearEffects();
901
902 anim->decreaseCount("animations/hero.txt");
903 anim->increaseCount(charmed_stats->animations);
904 animationSet = anim->getAnimationSet(charmed_stats->animations);
905 delete activeAnimation;
906 activeAnimation = animationSet->getAnimation("");
907 stats.cur_state = StatBlock::ENTITY_STANCE;
908
909 // base stats
910 for (int i=0; i<Stats::COUNT; ++i) {
911 stats.starting[i] = std::max(stats.starting[i], charmed_stats->starting[i]);
912 }
913
914 // resistances
915 for (unsigned int i=0; i<stats.vulnerable.size(); i++) {
916 stats.vulnerable[i] = std::min(stats.vulnerable[i], charmed_stats->vulnerable[i]);
917 }
918
919 loadSoundsFromStatBlock(charmed_stats);
920 loadStepFX("NULL");
921
922 stats.applyEffects();
923
924 transform_pos = stats.pos;
925 transform_map = mapr->getFilename();
926 }
927
untransform()928 void Avatar::untransform() {
929 // calling a transform power locks the actionbar, so we unlock it here
930 inpt->unlockActionBar();
931
932 // For timed transformations, move the player to the last valid tile when untransforming
933 mapr->collider.unblock(stats.pos.x, stats.pos.y);
934 if (!mapr->collider.isValidPosition(stats.pos.x, stats.pos.y, MapCollision::MOVE_NORMAL, MapCollision::COLLIDE_HERO)) {
935 logMsg(msg->get("Transformation expired. You have been moved back to a safe place."), MSG_NORMAL);
936 if (transform_map != mapr->getFilename()) {
937 mapr->teleportation = true;
938 mapr->teleport_mapname = transform_map;
939 mapr->teleport_destination.x = floorf(transform_pos.x) + 0.5f;
940 mapr->teleport_destination.y = floorf(transform_pos.y) + 0.5f;
941 transform_map = "";
942 }
943 else {
944 stats.pos.x = floorf(transform_pos.x) + 0.5f;
945 stats.pos.y = floorf(transform_pos.y) + 0.5f;
946 }
947 }
948 mapr->collider.block(stats.pos.x, stats.pos.y, !MapCollision::IS_ALLY);
949
950 stats.transformed = false;
951 transform_triggered = true;
952 stats.transform_type = "";
953 revertPowers = true;
954 stats.effects.clearEffects();
955
956 // revert some hero stats to last saved
957 stats.speed = hero_stats->speed;
958 stats.flying = hero_stats->flying;
959 stats.intangible = hero_stats->intangible;
960 stats.humanoid = hero_stats->humanoid;
961 stats.animations = hero_stats->animations;
962 stats.effects = hero_stats->effects;
963 stats.powers_list = hero_stats->powers_list;
964 stats.powers_passive = hero_stats->powers_passive;
965
966 anim->increaseCount("animations/hero.txt");
967 anim->decreaseCount(charmed_stats->animations);
968 animationSet = anim->getAnimationSet("animations/hero.txt");
969 delete activeAnimation;
970 activeAnimation = animationSet->getAnimation("");
971 stats.cur_state = StatBlock::ENTITY_STANCE;
972
973 // This is a bit of a hack.
974 // In order to switch to the stance animation, we can't already be in a stance animation
975 setAnimation("run");
976
977 for (int i=0; i<Stats::COUNT; ++i) {
978 stats.starting[i] = hero_stats->starting[i];
979 }
980
981 for (unsigned int i=0; i<stats.vulnerable.size(); i++) {
982 stats.vulnerable[i] = hero_stats->vulnerable[i];
983 }
984
985 loadSounds();
986 loadStepFX(stats.sfx_step);
987
988 delete charmed_stats;
989 delete hero_stats;
990 charmed_stats = NULL;
991 hero_stats = NULL;
992
993 stats.applyEffects();
994 stats.untransform_on_hit = false;
995 }
996
checkTransform()997 void Avatar::checkTransform() {
998 // handle transformation
999 if (stats.transform_type != "" && stats.transform_type != "untransform" && stats.transformed == false)
1000 transform();
1001 if (stats.transform_type != "" && stats.transform_duration == 0)
1002 untransform();
1003 }
1004
setAnimation(std::string name)1005 void Avatar::setAnimation(std::string name) {
1006 if (name == activeAnimation->getName())
1007 return;
1008
1009 Entity::setAnimation(name);
1010 for (unsigned i=0; i < animsets.size(); i++) {
1011 delete anims[i];
1012 if (animsets[i])
1013 anims[i] = animsets[i]->getAnimation(name);
1014 else
1015 anims[i] = 0;
1016 }
1017 }
1018
resetActiveAnimation()1019 void Avatar::resetActiveAnimation() {
1020 activeAnimation->reset(); // shield stutter
1021 for (unsigned i=0; i < animsets.size(); i++)
1022 if (anims[i])
1023 anims[i]->reset();
1024 }
1025
addRenders(std::vector<Renderable> & r)1026 void Avatar::addRenders(std::vector<Renderable> &r) {
1027 if (!stats.transformed) {
1028 for (unsigned i = 0; i < layer_def[stats.direction].size(); ++i) {
1029 unsigned index = layer_def[stats.direction][i];
1030 if (anims[index]) {
1031 Renderable ren = anims[index]->getCurrentFrame(stats.direction);
1032 ren.map_pos = stats.pos;
1033 ren.prio = i+1;
1034 stats.effects.getCurrentColor(ren.color_mod);
1035 stats.effects.getCurrentAlpha(ren.alpha_mod);
1036 if (stats.hp > 0) {
1037 ren.type = Renderable::TYPE_HERO;
1038 }
1039 r.push_back(ren);
1040 }
1041 }
1042 }
1043 else {
1044 Renderable ren = activeAnimation->getCurrentFrame(stats.direction);
1045 ren.map_pos = stats.pos;
1046 stats.effects.getCurrentColor(ren.color_mod);
1047 stats.effects.getCurrentAlpha(ren.alpha_mod);
1048 if (stats.hp > 0) {
1049 ren.type = Renderable::TYPE_HERO;
1050 }
1051 r.push_back(ren);
1052 }
1053 // add effects
1054 for (unsigned i = 0; i < stats.effects.effect_list.size(); ++i) {
1055 if (stats.effects.effect_list[i].animation && !stats.effects.effect_list[i].animation->isCompleted()) {
1056 Renderable ren = stats.effects.effect_list[i].animation->getCurrentFrame(0);
1057 ren.map_pos = stats.pos;
1058 if (stats.effects.effect_list[i].render_above) ren.prio = layer_def[stats.direction].size()+1;
1059 else ren.prio = 0;
1060 r.push_back(ren);
1061 }
1062 }
1063 }
1064
logMsg(const std::string & str,int type)1065 void Avatar::logMsg(const std::string& str, int type) {
1066 log_msg.push(std::pair<std::string, int>(str, type));
1067 }
1068
1069 // isLowHp returns true if health is below set threshold
isLowHp()1070 bool Avatar::isLowHp() {
1071 if (stats.hp == 0)
1072 return false;
1073 float hp_one_perc = static_cast<float>(std::max(stats.get(Stats::HP_MAX), 1)) / 100.0f;
1074 return static_cast<float>(stats.hp)/hp_one_perc < static_cast<float>(settings->low_hp_threshold);
1075 }
1076
1077 // isDroppedToLowHp returns true only if player hp just dropped below threshold
isDroppedToLowHp()1078 bool Avatar::isDroppedToLowHp() {
1079 float hp_one_perc = static_cast<float>(std::max(stats.get(Stats::HP_MAX), 1)) / 100.0f;
1080 return static_cast<float>(stats.hp)/hp_one_perc < static_cast<float>(settings->low_hp_threshold) &&
1081 static_cast<float>(prev_hp)/hp_one_perc >= static_cast<float>(settings->low_hp_threshold);
1082 }
1083
isLowHpMessageEnabled()1084 bool Avatar::isLowHpMessageEnabled() {
1085 return settings->low_hp_warning_type == settings->LHP_WARN_TEXT ||
1086 settings->low_hp_warning_type == settings->LHP_WARN_TEXT_CURSOR ||
1087 settings->low_hp_warning_type == settings->LHP_WARN_TEXT_SOUND ||
1088 settings->low_hp_warning_type == settings->LHP_WARN_ALL;
1089 }
1090
isLowHpSoundEnabled()1091 bool Avatar::isLowHpSoundEnabled() {
1092 return settings->low_hp_warning_type == settings->LHP_WARN_SOUND ||
1093 settings->low_hp_warning_type == settings->LHP_WARN_TEXT_SOUND ||
1094 settings->low_hp_warning_type == settings->LHP_WARN_CURSOR_SOUND ||
1095 settings->low_hp_warning_type == settings->LHP_WARN_ALL;
1096 }
1097
isLowHpCursorEnabled()1098 bool Avatar::isLowHpCursorEnabled() {
1099 return settings->low_hp_warning_type == settings->LHP_WARN_CURSOR ||
1100 settings->low_hp_warning_type == settings->LHP_WARN_TEXT_CURSOR ||
1101 settings->low_hp_warning_type == settings->LHP_WARN_CURSOR_SOUND ||
1102 settings->low_hp_warning_type == settings->LHP_WARN_ALL;
1103 }
1104
~Avatar()1105 Avatar::~Avatar() {
1106 if (stats.transformed && charmed_stats && charmed_stats->animations != "") {
1107 anim->decreaseCount(charmed_stats->animations);
1108 }
1109 else {
1110 anim->decreaseCount("animations/hero.txt");
1111 }
1112
1113 for (unsigned int i=0; i<animsets.size(); i++) {
1114 if (animsets[i])
1115 anim->decreaseCount(animsets[i]->getName());
1116 delete anims[i];
1117 }
1118 anim->cleanUp();
1119
1120 delete charmed_stats;
1121 delete hero_stats;
1122
1123 unloadSounds();
1124
1125 for (unsigned i=0; i<sound_steps.size(); i++)
1126 snd->unload(sound_steps[i]);
1127 }
1128