/** * \brief Returns the subarea in which an entity tooking these stairs can be * displayed. * \param way The way you are taking these stairs. * \return The subarea of the map where the entity displaying should be * restricted to. */ Rectangle Stairs::get_clipping_rectangle(Way way) { if (subtype == INSIDE_FLOOR || subtype == STRAIGHT_UPSTAIRS) { return Rectangle(0, 0, 0, 0); // no restriction } Rectangle clipping_rectangle(0, 0, get_map().get_width(), get_map().get_height()); bool north = get_direction() == 1; // north or south // Hide the hero after the north of south edge. if (north) { clipping_rectangle.set_y(get_top_left_y() - 8); clipping_rectangle.set_height(48); } else { // south clipping_rectangle.set_y(get_top_left_y() - 32); clipping_rectangle.set_height(48); } // Spiral staircase: also hide a side. if ((subtype == SPIRAL_DOWNSTAIRS && north) || (subtype == SPIRAL_UPSTAIRS && !north)) { // north downstairs or south upstairs: hide the west side clipping_rectangle.set_x(get_top_left_x()); clipping_rectangle.set_width(32); } else if ((subtype == SPIRAL_UPSTAIRS && north) || (subtype == SPIRAL_DOWNSTAIRS && !north)) { // north downstairs or south upstairs: hide the east side clipping_rectangle.set_x(get_top_left_x() - 16); clipping_rectangle.set_width(32); } return clipping_rectangle; }
/** * @brief Displays the tile on the map. */ void DynamicTile::display_on_map() { // FIXME this code is duplicated from Tile Surface* map_surface = get_map().get_visible_surface(); const Rectangle& camera_position = get_map().get_camera_position(); Rectangle dst(0, 0); int limit_x = get_top_left_x() - camera_position.get_x() + get_width(); int limit_y = get_top_left_y() - camera_position.get_y() + get_height(); for (int y = get_top_left_y() - camera_position.get_y(); y < limit_y; y += tile_pattern->get_height()) { if (y <= 240 && y + tile_pattern->get_height() > 0) { dst.set_y(y); for (int x = get_top_left_x() - camera_position.get_x(); x < limit_x; x += tile_pattern->get_width()) { if (x <= 320 && x + tile_pattern->get_width() > 0) { dst.set_x(x); tile_pattern->display(map_surface, dst, get_map().get_tileset(), get_map().get_camera_position()); } } } } }
/** * \brief Returns whether a rectangle overlaps the active region of the jumper. * * For a horizontal or vertical jumper, the active region is the whole * bounding box, that is, an 8-pixel thick horizontal or vertical strip. * For a diagonal jumper, the active region is an 8-pixel thick diagonal strip. * * \param rectangle A rectangle. * \param extended_region Whether you want to consider the extended jumper * beyond its bounding box, or to keep it restricted to its bounding box. * \return \c true if the rectangle overlaps the active region of the jumper. */ bool Jumper::overlaps_jumping_region(const Rectangle& rectangle, bool /* extended_region */) const { if (!is_jump_diagonal()) { return overlaps(rectangle); } // Check the 4 corners of the rectangle. Point xy = rectangle.get_xy(); if (is_point_in_diagonal(xy)) { return true; } xy.x += rectangle.get_width() - 1; if (is_point_in_diagonal(xy)) { return true; } xy.y += rectangle.get_height() - 1; if (is_point_in_diagonal(xy)) { return true; } xy.x = rectangle.get_x(); if (is_point_in_diagonal(xy)) { return true; } // Check the two ends of the diagonal. if (get_direction() == 1 || get_direction() == 5) { if (rectangle.contains(get_top_left_x(), get_top_left_y())) { return true; } if (rectangle.contains( get_top_left_x() + get_width() - 1, get_top_left_y() + get_height() - 1)) { return true; } } else { if (rectangle.contains( get_top_left_x() + get_width() - 1, get_top_left_y())) { return true; } if (rectangle.contains( get_top_left_x(), get_top_left_y() + get_height() - 1)) { return true; } } return false; }
/** * @brief Displays the entity on the map. * * This is a redefinition of MapEntity::display_on_map() to also display the twinkling star * which has a special position. */ void CrystalSwitch::display_on_map() { // display the crystal switch MapEntity::display_on_map(); // display the star get_map().display_sprite(*star_sprite, get_top_left_x() + star_xy.get_x(), get_top_left_y() + star_xy.get_y()); }
/** * \brief Draws the tile on the specified surface. * \param dst_surface the destination surface * \param viewport coordinates of the top-left corner of dst_surface * relative to the map */ void Tile::draw(Surface& dst_surface, const Rectangle& viewport) { Rectangle dst_position(get_top_left_x() - viewport.get_x(), get_top_left_y() - viewport.get_y(), get_width(), get_height()); tile_pattern->fill_surface(dst_surface, dst_position, get_map().get_tileset(), viewport); }
/** * \brief This function is called when another entity overlaps this crystal block. * \param entity_overlapping the other entity * \param collision_mode the collision mode that detected the collision */ void CrystalBlock::notify_collision(MapEntity& entity_overlapping, CollisionMode collision_mode) { if (entity_overlapping.is_hero() && is_raised()) { // see if we have to make fim fall Hero& hero = static_cast<Hero&>(entity_overlapping); if (hero.can_control_movement()) { Rectangle collision_box = hero.get_bounding_box(); int x1 = get_top_left_x(); int x2 = x1 + get_width(); int y1 = get_top_left_y(); int y2 = y1 + get_height(); int jump_direction = 0; int jump_length = 0; bool jumped = false; const Rectangle &hero_center = hero.get_center_point(); if (hero_center.get_y() < y1) { // fall to the north collision_box.set_y(y1 - 16); jump_direction = 2; jump_length = hero.get_top_left_y() + 16 - y1; jumped = try_jump(hero, collision_box, jump_direction, jump_length); } else if (hero_center.get_y() >= y2) { // fall to the south collision_box.set_y(y2); jump_direction = 6; jump_length = y2 - hero.get_top_left_y(); jumped = try_jump(hero, collision_box, jump_direction, jump_length); } if (!jumped) { if (hero_center.get_x() >= x2) { // fall to the east collision_box.set_x(x2); jump_direction = 0; jump_length = x2 - hero.get_top_left_x(); try_jump(hero, collision_box, jump_direction, jump_length); } else if (hero_center.get_x() < x1) { // fall to the west collision_box.set_x(x1 - 16); jump_direction = 4; jump_length = hero.get_top_left_x() + 16 - x1; try_jump(hero, collision_box, jump_direction, jump_length); } } } } }
/** * \brief Draws the entity on the map. * * This is a redefinition of MapEntity::draw_on_map() to also draw the twinkling star * which has a special position. */ void Crystal::draw_on_map() { if (!is_drawn()) { return; } // draw the crystal MapEntity::draw_on_map(); // draw the star if (is_drawn()) { get_map().draw_sprite(*star_sprite, get_top_left_x() + star_xy.get_x(), get_top_left_y() + star_xy.get_y()); } }
/** * \brief Draws the tile on the map. */ void DynamicTile::draw_on_map() { if (!is_drawn()) { return; } const Rectangle& camera_position = get_map().get_camera_position(); Rectangle dst(0, 0); Rectangle dst_position(get_top_left_x() - camera_position.get_x(), get_top_left_y() - camera_position.get_y(), get_width(), get_height()); tile_pattern.fill_surface(get_map().get_visible_surface(), dst_position, get_map().get_tileset(), camera_position); }
/** * \brief Draws the tile on the specified surface. * \param dst_surface the destination surface * \param viewport coordinates of the top-left corner of dst_surface * relative to the map */ void Tile::draw(const SurfacePtr& dst_surface, const Point& viewport) { Rectangle dst_position( get_top_left_x() - viewport.x, get_top_left_y() - viewport.y, get_width(), get_height() ); tile_pattern.fill_surface( dst_surface, dst_position, get_map().get_tileset(), viewport ); }
/** * \brief Draws the entity on the map. * * This is a redefinition of Entity::draw_on_map to repeat the block pattern. */ void CrystalBlock::draw_on_map() { const SpritePtr& sprite = get_sprite(); if (sprite == nullptr) { return; } int x1 = get_top_left_x(); int y1 = get_top_left_y(); int x2 = x1 + get_width(); int y2 = y1 + get_height(); for (int y = y1; y < y2; y += 16) { for (int x = x1; x < x2; x += 16) { get_map().draw_visual(*sprite, x, y); } } }
/** * \brief Draws the entity on the map. * * This is a redefinition of MapEntity::draw_on_map to repeat the block pattern. */ void CrystalBlock::draw_on_map() { if (!is_drawn()) { return; } Sprite& sprite = get_sprite(); int x1 = get_top_left_x(); int y1 = get_top_left_y(); int x2 = x1 + get_width(); int y2 = y1 + get_height(); for (int y = y1; y < y2; y += 16) { for (int x = x1; x < x2; x += 16) { get_map().draw_sprite(sprite, x, y); } } }
/** * \brief Draws the tile on the map. */ void DynamicTile::draw_on_map() { const CameraPtr& camera = get_map().get_camera(); if (camera == nullptr) { return; } const Rectangle& camera_position = camera->get_bounding_box(); Rectangle dst_position(get_top_left_x() - camera_position.get_x(), get_top_left_y() - camera_position.get_y(), get_width(), get_height()); tile_pattern.fill_surface( get_map().get_camera_surface(), dst_position, get_map().get_tileset(), camera_position.get_xy() ); }
/** * \brief Returns whether lava is currently considered as obstacle by this entity. * \return true if lava is currently obstacle for this entity */ bool Enemy::is_lava_obstacle() const { if (obstacle_behavior == OBSTACLE_BEHAVIOR_FLYING) { return false; } if (is_being_hurt()) { return false; } const Layer layer = get_layer(); const int x = get_top_left_x(); const int y = get_top_left_y(); if (get_map().get_ground(layer, x, y) == GROUND_LAVA || get_map().get_ground(layer, x + get_width() - 1, y) == GROUND_LAVA || get_map().get_ground(layer, x, y + get_height() - 1) == GROUND_LAVA || get_map().get_ground(layer, x + get_width() - 1, y + get_height() - 1) == GROUND_LAVA) { return false; } return true; }
/** * \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 Returns whether the hero is correctly placed to * start jumping with this jumper. * \param hero The hero. * \param candidate_position The candidate bounding box. * \param extended_region Whether you want to consider the extended jumper * beyond its bounding box, or to keep it restricted to its bounding box. * \return \c true if the hero is correctly placed to start a jump. */ bool Jumper::is_in_jump_position( const Hero& hero, const Rectangle& candidate_position, bool extended_region) const { if (overlaps_jumping_region(candidate_position, extended_region)) { // Overlapping the active region: cannot start a jump from there. return false; } const int direction8 = get_direction(); const int expected_hero_direction4 = direction8 / 2; if (is_jump_diagonal()) { // Diagonal case: The sensor's shape is a diagonal bar. // The player should move toward one of both directions. if (!hero.is_moving_towards(expected_hero_direction4) && !hero.is_moving_towards((expected_hero_direction4 + 1) % 4)) { return false; } // Test if the appropriate corner of the hero crosses the diagonal. Point corner = { candidate_position.get_x() - 1, candidate_position.get_y() - 1 }; if (direction8 == 1 || direction8 == 7) { // Right-up or right-down. corner.x += candidate_position.get_width() + 1; } if (direction8 == 5 || direction8 == 7) { // Left-down or right-down. corner.y += candidate_position.get_height() + 1; } return extended_region ? is_point_in_extended_diagonal(corner) : is_point_in_diagonal(corner); } // Non-diagonal case: the sensor has one of the four main directions. // Its shape is exactly its rectangle. // The player should move toward the jumper's direction. if (!hero.is_moving_towards(expected_hero_direction4)) { return false; } Point facing_point; switch (expected_hero_direction4) { // right case 0: facing_point = { candidate_position.get_x() + 16, candidate_position.get_y() + 8 }; break; // up case 1: facing_point = { candidate_position.get_x() + 8, candidate_position.get_y() - 1 }; break; // left case 2: facing_point = { candidate_position.get_x() - 1, candidate_position.get_y() + 8 }; break; // down case 3: facing_point = { candidate_position.get_x() + 8, candidate_position.get_y() + 16 }; break; default: Debug::die("Invalid direction"); } if (is_jump_horizontal()) { if (extended_region) { // Are we inside the extended strip? return facing_point.x >= get_top_left_x() && facing_point.x < get_top_left_x() + get_width(); } else { // Are we inside the strip and the bounding box? return overlaps(facing_point.x, facing_point.y - 8) && overlaps(facing_point.x, facing_point.y + 7); } } else { // Same thing for a vertical jump. if (extended_region) { // Are we inside the extended strip? return facing_point.y >= get_top_left_y() && facing_point.y < get_top_left_y() + get_height(); } else { return overlaps(facing_point.x - 8, facing_point.y) && overlaps(facing_point.x + 7, facing_point.y); } } }