/** * @brief Notifies this entity that another sprite is overlapping it. * * This function is called by check_collision(MapEntity*, Sprite*) when another entity's * sprite overlaps a sprite of this detector. * * @param other_entity the entity overlapping this detector * @param other_sprite the sprite of other_entity that is overlapping this detector * @param this_sprite the sprite of this detector that is overlapping the other entity's sprite */ void Destructible::notify_collision(MapEntity& other_entity, Sprite& other_sprite, Sprite& this_sprite) { if (features[subtype].can_be_cut && !is_being_cut && !is_disabled() && !is_regenerating && other_entity.is_hero() && other_sprite.contains("sword")) { Hero& hero = static_cast<Hero&>(other_entity); if (hero.is_striking_with_sword(*this)) { play_destroy_animation(); hero.check_position(); // to update the ground under the hero create_pickable(); if (can_explode()) { explode(); } } } // TODO use dynamic dispatch if (other_entity.get_type() == EXPLOSION && can_explode() && !is_being_cut && !is_disabled() && !is_regenerating) { play_destroy_animation(); create_pickable(); explode(); } }
/** * \brief Destroys the item while it is being thrown. */ void CarriedItem::break_item() { if (is_throwing && throwing_direction != 3) { // destroy the item where it is actually drawn set_y(get_y() - item_height); } get_movement()->stop(); if (!can_explode()) { if (!destruction_sound_id.empty()) { Sound::play(destruction_sound_id); } if (get_sprite().has_animation("destroy")) { get_sprite().set_current_animation("destroy"); } else { remove_from_map(); } } else { get_entities().add_entity(std::make_shared<Explosion>( "", get_layer(), get_xy(), true )); Sound::play("explosion"); if (is_throwing) { remove_from_map(); // because if the item was still carried by the hero, then the hero class will destroy it } } is_throwing = false; is_breaking = true; }
/** * \brief This function is called when this carried item collides an enemy. * \param enemy the enemy */ void CarriedItem::notify_collision_with_enemy(Enemy &enemy) { if (is_throwing && !can_explode() && get_damage_on_enemies() > 0) { enemy.try_hurt(EnemyAttack::THROWN_ITEM, *this, nullptr); } }
/** * @brief This function is called when this carried item collides an enemy. * @param enemy the enemy */ void CarriedItem::notify_collision_with_enemy(Enemy &enemy) { if (is_throwing && !can_explode() && get_damage_on_enemies() > 0) { enemy.try_hurt(ATTACK_THROWN_ITEM, *this, NULL); } }
/** * \brief This function is called when a crystal detects a collision with this entity. * \param crystal the crystal * \param collision_mode the collision mode that detected the event */ void CarriedItem::notify_collision_with_crystal(Crystal& crystal, CollisionMode collision_mode) { if (collision_mode == COLLISION_OVERLAPPING && is_being_thrown() && !can_explode()) { crystal.activate(*this); break_item(); } }
/** * \brief This function is called when a switch detects a collision with this entity. * \param sw the switch * \param collision_mode the collision mode that detected the event */ void CarriedItem::notify_collision_with_switch(Switch& sw, CollisionMode collision_mode) { if (collision_mode == COLLISION_OVERLAPPING && is_being_thrown() && !can_explode()) { sw.try_activate(); break_item(); } }
/** * \brief Notifies this detector that the player is interacting with it by * pressing the action command. * * This function is called when the player presses the action command * while the hero is facing this detector, and the action command effect lets * him do this. */ void Destructible::notify_action_command_pressed() { KeysEffect::ActionKeyEffect effect = get_keys_effect().get_action_key_effect(); if ((effect == KeysEffect::ACTION_KEY_LIFT || effect == KeysEffect::ACTION_KEY_LOOK) && features[subtype].can_be_lifted && !is_being_cut && !is_disabled() && !is_regenerating) { int weight = features[subtype].weight; if (get_equipment().has_ability("lift", weight)) { uint32_t explosion_date = can_explode() ? System::now() + 6000 : 0; get_hero().start_lifting(new CarriedItem( get_hero(), *this, get_animation_set_id(), get_destruction_sound_id(), get_damage_on_enemies(), explosion_date) ); // play the sound Sound::play("lift"); // create the pickable item create_pickable(); // remove the item from the map if (!features[subtype].can_regenerate) { destruction_callback(); remove_from_map(); } else { // the item can actually regenerate play_destroy_animation(); } } else { if (features[subtype].can_be_cut && !features[subtype].can_explode && !get_equipment().has_ability("sword", 1)) { get_game().start_dialog("_cannot_lift_should_cut", LUA_REFNIL); } else if (!get_equipment().has_ability("lift", 1)) { get_game().start_dialog("_cannot_lift_too_heavy", LUA_REFNIL); } else { get_game().start_dialog("_cannot_lift_still_too_heavy", LUA_REFNIL); } } } }
/** * \brief This function is called by the map when the game is suspended or resumed. * \param suspended true to suspend the entity, false to resume it */ void CarriedItem::set_suspended(bool suspended) { Entity::set_suspended(suspended); // suspend the animation and the movement if (is_throwing) { // suspend the shadow shadow_sprite->set_suspended(suspended); } if (!suspended && get_when_suspended() != 0) { // recalculate the timers uint32_t diff = System::now() - get_when_suspended(); if (is_throwing) { next_down_date += diff; } if (can_explode()) { explosion_date += diff; } } }
/** * \brief Returns whether an enemy character is considered as an obstacle for this entity. * \param enemy an enemy * \return true if this enemy is considered as an obstacle for this entity. */ bool CarriedItem::is_enemy_obstacle(Enemy& /* enemy */) { // if this item explodes when reaching an obstacle, then we consider enemies as obstacles return can_explode(); }
/** * \brief This function is called repeatedly. */ void CarriedItem::update() { // update the sprite and the position Entity::update(); if (is_suspended()) { return; } // when the hero finishes lifting the item, start carrying it if (is_lifting && get_movement()->is_finished()) { is_lifting = false; // make the item follow the hero clear_movement(); set_movement(std::make_shared<RelativeMovement>( std::static_pointer_cast<Hero>(hero.shared_from_this()), 0, -18, true )); } // when the item has finished flying, destroy it else if (can_explode() && !is_breaking) { uint32_t now = System::now(); if (now >= explosion_date) { break_item(); } else if (will_explode_soon()) { std::string animation = get_sprite().get_current_animation(); if (animation == "stopped") { get_sprite().set_current_animation("stopped_explosion_soon"); } else if (animation == "walking") { get_sprite().set_current_animation("walking_explosion_soon"); } } } if (is_broken()) { remove_from_map(); } else if (is_throwing) { shadow_sprite->update(); if (break_one_layer_above) { break_item(); int layer = get_layer(); if (layer != get_map().get_highest_layer()) { get_entities().set_entity_layer(*this, layer + 1); } break_one_layer_above = false; } else if (get_movement()->is_stopped() || y_increment >= 7) { // Interrupt the movement. break_item_on_ground(); } else { uint32_t now = System::now(); while (now >= next_down_date) { next_down_date += 40; item_height -= y_increment; y_increment++; } } } }
/** * \brief Returns whether the item is broken. * \return true if the item is broken */ bool CarriedItem::is_broken() const { return is_breaking && (get_sprite().is_animation_finished() || can_explode()); }
/** * \brief Returns whether the item is about to explode. * \return true if the item is about to explode */ bool CarriedItem::will_explode_soon() const{ return can_explode() && System::now() >= explosion_date - 1500; }
/** * @brief This function is called repeatedly. */ void CarriedItem::update() { // update the sprite and the position MapEntity::update(); if (suspended) { return; } // when the hero finishes lifting the item, start carrying it if (is_lifting && get_movement()->is_finished()) { is_lifting = false; // make the item follow the hero clear_movement(); set_movement(new FollowMovement(&hero, 0, -18, true)); } // when the item has finished flying, destroy it else if (can_explode() && !is_breaking) { uint32_t now = System::now(); if (now >= explosion_date) { break_item(); } else if (will_explode_soon()) { std::string animation = get_sprite().get_current_animation(); if (animation == "stopped") { get_sprite().set_current_animation("stopped_explosion_soon"); } else if (animation == "walking") { get_sprite().set_current_animation("walking_explosion_soon"); } } } if (is_throwing) { shadow_sprite->update(); if (is_broken()) { remove_from_map(); } else if (break_on_intermediate_layer) { break_item(); get_entities().set_entity_layer(*this, LAYER_INTERMEDIATE); break_on_intermediate_layer = false; } else if (get_movement()->is_stopped() || y_increment >= 7) { break_item(); } else { uint32_t now = System::now(); while (now >= next_down_date) { next_down_date += 40; item_height -= y_increment; y_increment++; } } } }