Пример #1
0
/**
 * \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);
    }
  }
}
Пример #2
0
/**
 * \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();
}
Пример #3
0
/**
 * \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);
}
Пример #4
0
/**
 * \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);
  }
}
Пример #5
0
/**
 * \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);
      }
    }
  }
}
Пример #6
0
/**
 * \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);
}
Пример #7
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
      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);
}
Пример #8
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
  );
}