/** * \brief Throws the item. * \param direction direction where the hero throws the item (0 to 3) */ void CarriedItem::throw_item(int direction) { this->throwing_direction = direction; this->is_lifting = false; this->is_throwing = true; // play the sound Sound::play("throw"); // stop the sprite animation Sprite& sprite = get_sprite(); sprite.set_current_animation("stopped"); // set the movement of the item sprite set_y(hero.get_y()); std::shared_ptr<StraightMovement> movement = std::make_shared<StraightMovement>(false, false); movement->set_speed(200); movement->set_angle(Geometry::degrees_to_radians(direction * 90)); clear_movement(); set_movement(movement); this->y_increment = -2; this->next_down_date = System::now() + 40; this->item_height = 18; }
/** * \brief This function is called by the engine when an entity overlaps the pickable item. * * If the entity is the player, we give him the item, and the map is notified * to destroy it. * \param entity_overlapping the entity overlapping the detector * \param collision_mode the collision mode that detected the collision */ void Pickable::notify_collision(MapEntity& entity_overlapping, CollisionMode collision_mode) { if (entity_overlapping.is_hero()) { try_give_item_to_player(); } else if (entity_followed == NULL) { if (entity_overlapping.get_type() == ENTITY_BOOMERANG) { Boomerang& boomerang = static_cast<Boomerang&>(entity_overlapping); if (!boomerang.is_going_back()) { boomerang.go_back(); } entity_followed = &boomerang; } else if (entity_overlapping.get_type() == ENTITY_HOOKSHOT) { Hookshot& hookshot = static_cast<Hookshot&>(entity_overlapping); if (!hookshot.is_going_back()) { hookshot.go_back(); } entity_followed = &hookshot; } if (entity_followed != NULL) { clear_movement(); set_movement(new FollowMovement(entity_followed, 0, 0, true)); falling_height = FALLING_NONE; set_blinking(false); } } }
/** * @brief Updates this entity. */ void Hookshot::update() { MapEntity::update(); if (suspended) { return; } uint32_t now = System::now(); if (now >= next_sound_date) { Sound::play("hookshot"); next_sound_date = now + 150; } if (entity_reached == NULL) { if (!going_back) { if (has_to_go_back) { going_back = true; Movement *movement = new TargetMovement(&get_hero(), 192, true); clear_movement(); set_movement(movement); } else if (get_distance(get_hero()) >= 120) { go_back(); } } else if (get_distance(get_hero()) == 0 || (get_movement() != NULL && get_movement()->is_finished())) { remove_from_map(); get_hero().start_state_from_ground(); } } }
/** * \brief This function is called by the engine when an entity overlaps the pickable item. * * If the entity is the player, we give him the item, and the map is notified * to destroy it. * \param entity_overlapping the entity overlapping the detector * \param collision_mode the collision mode that detected the collision */ void Pickable::notify_collision(MapEntity& entity_overlapping, CollisionMode /* collision_mode */) { if (entity_overlapping.is_hero()) { try_give_item_to_player(); } else if (entity_followed == nullptr) { MapEntityPtr shared_entity_overlapping = std::static_pointer_cast<MapEntity>(entity_overlapping.shared_from_this()); if (entity_overlapping.get_type() == ENTITY_BOOMERANG) { Boomerang& boomerang = static_cast<Boomerang&>(entity_overlapping); if (!boomerang.is_going_back()) { boomerang.go_back(); } entity_followed = shared_entity_overlapping; } else if (entity_overlapping.get_type() == ENTITY_HOOKSHOT) { Hookshot& hookshot = static_cast<Hookshot&>(entity_overlapping); if (!hookshot.is_going_back()) { hookshot.go_back(); } entity_followed = shared_entity_overlapping; } if (entity_followed != nullptr) { clear_movement(); set_movement(std::make_shared<FollowMovement>( entity_followed, 0, 0, true )); falling_height = FALLING_NONE; set_blinking(false); } } }
/** * \brief Hurts the enemy. * * Updates its state, its sprite and plays the sound. * * \param source the entity attacking the enemy (often the hero) */ void Enemy::hurt(MapEntity& source) { uint32_t now = System::now(); // update the enemy state being_hurt = true; set_movement_events_enabled(false); can_attack = false; can_attack_again_date = now + 300; // graphics and sounds set_animation("hurt"); play_hurt_sound(); // stop any movement clear_movement(); // push the enemy back if (pushed_back_when_hurt) { double angle = source.get_angle(*this); StraightMovement* movement = new StraightMovement(false, true); movement->set_max_distance(24); movement->set_speed(120); movement->set_angle(angle); set_movement(movement); } stop_hurt_date = now + 300; }
/** * \brief This function is called repeatedly. */ void Bomb::update() { Detector::update(); if (is_suspended()) { return; } // check the explosion date uint32_t now = System::now(); if (now >= explosion_date) { explode(); } else if (now >= explosion_date - 1500 && get_sprite().get_current_animation() != "stopped_explosion_soon") { get_sprite().set_current_animation("stopped_explosion_soon"); } // destroy the movement once finished if (get_movement() != nullptr && get_movement()->is_finished()) { clear_movement(); } // check collision with explosions, streams, etc. check_collision_with_detectors(); }
/** * \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 Attachs the hookshot to an entity and makes the hero move towards this entity. * @param entity_reached the entity to attach the hookshot to */ void Hookshot::attach_to(MapEntity& entity_reached) { Debug::check_assertion(this->entity_reached == NULL, "The hookshot is already attached to an entity"); this->entity_reached = &entity_reached; clear_movement(); int direction = get_sprite().get_current_direction(); std::string path = " "; path[0] = '0' + (direction * 2); get_hero().set_movement(new PathMovement(path, 192, true, false, false)); }
/** * @brief Resets the block at its initial position. */ void Block::reset() { if (get_movement() != NULL) { // the block was being pushed or pulled by the hero clear_movement(); when_can_move = System::now() + moving_delay; } set_xy(initial_position); last_position.set_xy(initial_position); this->maximum_moves = initial_maximum_moves; }
/** * \brief Resets the block at its initial position. */ void Block::reset() { if (get_movement() != nullptr) { // the block was being pushed or pulled by the hero clear_movement(); when_can_move = System::now() + moving_delay; } last_position = initial_position; this->maximum_moves = initial_maximum_moves; set_xy(initial_position); notify_position_changed(); }
/** * \brief This function is called when this entity stops being moved by the * hero. */ void Block::stop_movement_by_hero() { clear_movement(); when_can_move = System::now() + moving_delay; // see if the block has moved if (get_xy() != last_position) { // the block has moved last_position = get_xy(); // save the new position for next time if (maximum_moves == 1) { // if the block could be moved only once, maximum_moves = 0; // then it cannot move anymore } } }
/** * @brief This function is called when this entity stops being moved by the * hero. */ void Block::stop_movement_by_hero() { clear_movement(); when_can_move = System::now() + moving_delay; // see if the block has moved if (get_x() != last_position.get_x() || get_y() != last_position.get_y()) { // the block has moved last_position.set_xy(get_x(), get_y()); // save the new position for next time if (maximum_moves == 1) { // if the block could be moved only once, maximum_moves = 0; // then it cannot move anymore } } // notify the script get_map_script().event_block_moved(get_name()); }
/** * \brief This function is called when a conveyor belt detects a collision with this entity. * \param conveyor_belt a conveyor belt * \param dx direction of the x move in pixels (0, 1 or -1) * \param dy direction of the y move in pixels (0, 1 or -1) */ void Bomb::notify_collision_with_conveyor_belt(ConveyorBelt& conveyor_belt, int dx, int dy) { if (get_movement() == NULL) { // check that a significant part of the bomb is on the conveyor belt Rectangle center = get_center_point(); center.add_xy(-1, -1); center.set_size(2, 2); if (conveyor_belt.overlaps(center)) { set_xy(conveyor_belt.get_xy()); std::string path = " "; path[0] = path[1] = '0' + conveyor_belt.get_direction(); clear_movement(); set_movement(new PathMovement(path, 64, false, false, false)); } } }
/** * \copydoc MapEntity::notify_collision_with_stream */ void Bomb::notify_collision_with_stream(Stream& stream, int /* dx */, int /* dy */) { if (get_movement() == nullptr) { // TODO use a StreamAction, since it now works with any entity and not only the hero. // Check that a significant part of the bomb is on the stream. Rectangle center(get_center_point(), Size(2, 2)); center.add_xy(-1, -1); if (stream.overlaps(center)) { set_xy(stream.get_xy()); std::string path = " "; path[0] = path[1] = '0' + stream.get_direction(); clear_movement(); set_movement(std::make_shared<PathMovement>( path, 64, false, false, false )); } } }
/** * \brief Updates the boomerang. */ void Boomerang::update() { MapEntity::update(); if (is_suspended()) { return; } uint32_t now = System::now(); if (now >= next_sound_date) { Sound::play("boomerang"); next_sound_date = now + 150; } if (!going_back && has_to_go_back) { going_back = true; clear_movement(); set_movement(new TargetMovement(&hero, 0, 0, speed, true)); get_entities().set_entity_layer(*this, hero.get_layer()); // because the hero's layer may have changed } }
/** * \brief Updates this entity. */ void Hookshot::update() { MapEntity::update(); if (is_suspended()) { return; } uint32_t now = System::now(); if (now >= next_sound_date) { Sound::play("hookshot"); next_sound_date = now + 150; } if (entity_reached == nullptr) { if (!going_back) { if (has_to_go_back) { going_back = true; std::shared_ptr<Movement> movement = std::make_shared<TargetMovement>( std::static_pointer_cast<Hero>(get_hero().shared_from_this()), 0, 0, 192, true ); clear_movement(); set_movement(movement); } else if (get_distance(get_hero()) >= 120) { go_back(); } } else if (get_distance(get_hero()) == 0 || (get_movement() != nullptr && get_movement()->is_finished())) { remove_from_map(); get_hero().start_state_from_ground(); } } }
/** * \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 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 Updates this entity. */ void Arrow::update() { MapEntity::update(); if (is_suspended()) { return; } uint32_t now = System::now(); // stop the movement if necessary (i.e. stop() was called) if (stop_now) { clear_movement(); stop_now = false; if (entity_reached != NULL) { // the arrow just hit an entity (typically an enemy) and this entity may have a movement Rectangle dxy(get_x() - entity_reached->get_x(), get_y() - entity_reached->get_y()); set_movement(new FollowMovement(entity_reached, dxy.get_x(), dxy.get_y(), true)); } } if (entity_reached != NULL) { // see if the entity reached is still valid if (is_stopped()) { // the arrow is stopped because the entity that was reached just disappeared disappear_date = now; } else if (entity_reached->get_type() == ENTITY_DESTRUCTIBLE && !entity_reached->is_obstacle_for(*this)) { disappear_date = now; } else if (entity_reached->get_type() == ENTITY_ENEMY && ((Enemy*) entity_reached)->is_dying()) { // the enemy is dying disappear_date = now; } } // see if the arrow just hit a wall or an entity bool reached_obstacle = false; if (get_sprite().get_current_animation() != "reached_obstacle") { if (entity_reached != NULL) { // the arrow was just attached to an entity reached_obstacle = true; } else if (is_stopped()) { if (has_reached_map_border()) { // the map border was reached: destroy the arrow disappear_date = now; } else { // the arrow has just hit another obstacle reached_obstacle = true; } } } if (reached_obstacle) { // an obstacle or an entity was just reached disappear_date = now + 1500; get_sprite().set_current_animation("reached_obstacle"); Sound::play("arrow_hit"); if (entity_reached == NULL) { clear_movement(); } check_collision_with_detectors(false); } // destroy the arrow when disappear_date is reached if (now >= disappear_date) { remove_from_map(); } }
MapEntity::~MapEntity() { clear_movement(); clear_old_movements(); clear_sprites(); clear_old_sprites(); }
/** * @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++; } } } }