/** * \brief Attacks the hero if possible. * * This function is called when there is a collision between the enemy and the hero. * * \param hero the hero * \param this_sprite the sprite of this enemy that detected the collision with the hero, * or NULL if it was not a pixel-precise collision. */ void Enemy::attack_hero(Hero &hero, Sprite *this_sprite) { if (!is_immobilized() && can_attack && hero.can_be_hurt(this)) { bool hero_protected = false; if (minimum_shield_needed != 0 && get_equipment().has_ability("shield", minimum_shield_needed)) { // compute the direction corresponding to the angle between the enemy and the hero double angle = hero.get_angle(*this); int protected_direction4 = (int) ((angle + Geometry::PI_OVER_2 / 2.0) * 4 / Geometry::TWO_PI); protected_direction4 = (protected_direction4 + 4) % 4; // also get the direction of the enemy's sprite int sprite_opposite_direction4 = -1; if (this_sprite != NULL) { sprite_opposite_direction4 = (this_sprite->get_current_direction() + 2) % 4; } // the hero is protected if he is facing the opposite of one of these directions hero_protected = hero.is_facing_direction4(protected_direction4) || hero.is_facing_direction4(sprite_opposite_direction4); } if (hero_protected) { attack_stopped_by_hero_shield(); } else { hero.hurt(*this, damage_on_hero, magic_damage_on_hero); } } }
/** * \brief Returns whether this enemy is in a normal state. * * The enemy is considered to be in its normal state if * it is not disabled, dying, being hurt or immobilized. * When this method returns false, the subclasses of Enemy * should not change the enemy properties. * * \return true if this enemy is in a normal state */ bool Enemy::is_in_normal_state() const { return is_enabled() && !is_being_hurt() && get_life() > 0 && !is_immobilized() && !is_being_removed(); }
/** * \brief Notifies this enemy that it should restart his movement. * * This function is called when the enemy needs to restart its movement * because something happened (for example the enemy has just been created, * or it was just hurt). * By default, the "walking" animation is set on the enemy's sprites. */ void Enemy::restart() { if (is_immobilized()) { stop_immobilized(); } set_animation("walking"); get_lua_context().enemy_on_restarted(*this); }
/** * \brief Kills the enemy. * * This function is called when the enemy has no more health points. */ void Enemy::kill() { // if the enemy is immobilized, give some money if (rank == RANK_NORMAL && is_immobilized() && !treasure.is_saved()) { // TODO choose random money (can we do this from scripts?) } // stop any movement and disable attacks set_collision_modes(COLLISION_NONE); clear_movement(); invulnerable = true; can_attack = false; can_attack_again_date = 0; dying_animation_started = true; if (hurt_style == HURT_BOSS) { // A boss: create some explosions. exploding = true; nb_explosions = 0; next_explosion_date = System::now() + 2000; } else { // Replace the enemy sprites. clear_sprites(); switch (get_ground_below()) { case GROUND_HOLE: // TODO animation of falling into a hole. Sound::play("jump"); break; case GROUND_DEEP_WATER: // TODO water animation. Sound::play("splash"); break; case GROUND_LAVA: // TODO lava animation. Sound::play("splash"); break; default: create_sprite("enemies/enemy_killed"); Sound::play("enemy_killed"); break; } } // save the enemy state if required if (is_saved()) { get_savegame().set_boolean(savegame_variable, true); } }
/** * \brief Attacks the hero if possible. * * This function is called when there is a collision between the enemy and the hero. * * \param hero The hero to attack. * \param this_sprite The sprite of this enemy that detected the collision with the hero, * or NULL if it was not a pixel-precise collision. */ void Enemy::attack_hero(Hero& hero, Sprite* this_sprite) { if (!is_immobilized() && can_attack && hero.can_be_hurt(this)) { bool hero_protected = false; if (minimum_shield_needed != 0 && get_equipment().has_ability(ABILITY_SHIELD, minimum_shield_needed) && hero.can_use_shield()) { // Compute the direction corresponding to the angle between the enemy and the hero. double angle = hero.get_angle(*this, NULL, this_sprite); int protected_direction4 = (int) ((angle + Geometry::PI_OVER_2 / 2.0) * 4 / Geometry::TWO_PI); protected_direction4 = (protected_direction4 + 4) % 4; // Also get the direction of the enemy's sprite. int sprite_opposite_direction4 = -1; if (this_sprite != NULL) { sprite_opposite_direction4 = (this_sprite->get_current_direction() + 2) % 4; } // The hero is protected if he is facing the opposite of one of these directions. hero_protected = hero.is_facing_direction4(protected_direction4) || hero.is_facing_direction4(sprite_opposite_direction4); } if (hero_protected) { attack_stopped_by_hero_shield(); } else { // Let the enemy script handle this if it wants. const bool handled = get_lua_context().enemy_on_attacking_hero( *this, hero, this_sprite); if (!handled) { // Scripts did not customize the attack: // do the built-in hurt state of the hero. hero.hurt(*this, this_sprite, damage_on_hero); } } } }
/** * \brief Updates the enemy. */ void Enemy::update() { MapEntity::update(); if (is_suspended() || !is_enabled()) { return; } uint32_t now = System::now(); if (being_hurt) { // see if we should stop the animation "hurt" if (now >= stop_hurt_date) { being_hurt = false; set_movement_events_enabled(true); if (life <= 0) { kill(); } else if (is_immobilized()) { clear_movement(); set_animation("immobilized"); notify_immobilized(); } else { clear_movement(); restart(); } } } if (life > 0 && invulnerable && now >= vulnerable_again_date && !being_hurt) { invulnerable = false; } if (life > 0 && !can_attack && !is_immobilized() && can_attack_again_date != 0 && now >= can_attack_again_date) { can_attack = true; } if (is_immobilized() && !is_killed() && now >= end_shaking_date && get_sprite().get_current_animation() == "shaking") { restart(); } if (is_immobilized() && !is_killed() && !is_being_hurt() && now >= start_shaking_date && get_sprite().get_current_animation() != "shaking") { end_shaking_date = now + 2000; set_animation("shaking"); } if (exploding) { uint32_t now = System::now(); if (now >= next_explosion_date) { // create an explosion Rectangle xy; xy.set_x(get_top_left_x() + Random::get_number(get_width())); xy.set_y(get_top_left_y() + Random::get_number(get_height())); get_entities().add_entity(new Explosion("", LAYER_HIGH, xy, false)); Sound::play("explosion"); next_explosion_date = now + 200; nb_explosions++; if (nb_explosions >= 15) { exploding = false; } } } if (is_killed() && is_dying_animation_finished()) { // Create the pickable treasure if any. get_entities().add_entity(Pickable::create(get_game(), "", get_layer(), get_x(), get_y(), treasure, FALLING_HIGH, false)); // Remove the enemy. remove_from_map(); // Notify Lua that this enemy is dead. // We need to do this after remove_from_map() so that this enemy is // considered dead in functions like map:has_entities(prefix). notify_dead(); } get_lua_context().enemy_on_update(*this); }
/** * \brief Makes the enemy receive an attack. * * He might resist to the attack or get hurt. * * \param attack type of attack * \param source the entity attacking the enemy (often the hero) * \param this_sprite the sprite of this enemy that received the attack, or NULL * if the attack comes from a non pixel-precise collision test */ void Enemy::try_hurt(EnemyAttack attack, MapEntity& source, Sprite* this_sprite) { EnemyReaction::Reaction reaction = get_attack_consequence(attack, this_sprite); if (invulnerable || reaction.type == EnemyReaction::IGNORED) { // ignore the attack return; } invulnerable = true; vulnerable_again_date = System::now() + 500; switch (reaction.type) { case EnemyReaction::PROTECTED: // attack failure sound Sound::play("sword_tapping"); break; case EnemyReaction::IMMOBILIZED: // get immobilized hurt(source); immobilize(); notify_hurt(source, attack, 0); break; case EnemyReaction::CUSTOM: // custom attack (defined in the script) if (is_in_normal_state()) { custom_attack(attack, this_sprite); } else { // no attack was made: notify the source correctly reaction.type = EnemyReaction::IGNORED; invulnerable = false; } break; case EnemyReaction::HURT: if (is_immobilized() && get_sprite().get_current_animation() == "shaking") { stop_immobilized(); } // compute the number of health points lost by the enemy if (attack == ATTACK_SWORD) { // for a sword attack, the damage depends on the sword and the variant of sword attack used int damage_multiplicator = ((Hero&) source).get_sword_damage_factor(); reaction.life_lost *= damage_multiplicator; } else if (attack == ATTACK_THROWN_ITEM) { reaction.life_lost *= ((CarriedItem&) source).get_damage_on_enemies(); } life -= reaction.life_lost; hurt(source); notify_hurt(source, attack, reaction.life_lost); break; case EnemyReaction::IGNORED: case EnemyReaction::REACTION_NUMBER: Debug::die(StringConcat() << "Invalid enemy reaction" << reaction.type); break; } // notify the source source.notify_attacked_enemy(attack, *this, reaction, get_life() <= 0); }
/** * \brief Makes the enemy receive an attack. * * He might resist to the attack or get hurt. * * \param attack type of attack * \param source the entity attacking the enemy (often the hero) * \param this_sprite the sprite of this enemy that received the attack, or NULL * if the attack comes from a non pixel-precise collision test */ void Enemy::try_hurt(EnemyAttack attack, MapEntity& source, Sprite* this_sprite) { EnemyReaction::Reaction reaction = get_attack_consequence(attack, this_sprite); if (invulnerable || reaction.type == EnemyReaction::IGNORED) { // ignore the attack return; } invulnerable = true; vulnerable_again_date = System::now() + 500; switch (reaction.type) { case EnemyReaction::PROTECTED: // attack failure sound Sound::play("sword_tapping"); break; case EnemyReaction::IMMOBILIZED: // get immobilized being_hurt = true; hurt(source, this_sprite); immobilize(); break; case EnemyReaction::CUSTOM: // custom attack (defined in the script) if (is_in_normal_state()) { custom_attack(attack, this_sprite); } else { // no attack was made: notify the source correctly reaction.type = EnemyReaction::IGNORED; invulnerable = false; } break; case EnemyReaction::HURT: if (is_immobilized() && get_sprite().get_current_animation() == "shaking") { stop_immobilized(); } // Compute the number of health points lost by the enemy. being_hurt = true; if (attack == ATTACK_SWORD) { Hero& hero = static_cast<Hero&>(source); // Sword attacks only use pixel-precise collisions. Debug::check_assertion(this_sprite != NULL, "Missing enemy sprite for sword attack" ); // For a sword attack, the damage may be something customized. bool customized = get_lua_context().enemy_on_hurt_by_sword( *this, hero, *this_sprite); if (customized) { reaction.life_lost = 0; // Already done by the script. } else { // If this is not customized, the default it to multiply the reaction // by a factor that depends on the sword. reaction.life_lost *= hero.get_sword_damage_factor(); } } else if (attack == ATTACK_THROWN_ITEM) { reaction.life_lost *= static_cast<CarriedItem&>(source).get_damage_on_enemies(); } life -= reaction.life_lost; hurt(source, this_sprite); notify_hurt(source, attack); break; case EnemyReaction::IGNORED: case EnemyReaction::REACTION_NUMBER: { std::ostringstream oss; oss << "Invalid enemy reaction: " << reaction.type; Debug::die(oss.str()); break; } } // notify the source source.notify_attacked_enemy( attack, *this, this_sprite, reaction, get_life() <= 0 ); }