Exemple #1
0
class SelectedSpellBoxWidget(AbstractUIWidget):
    box_pos = config_ui["selected_spell_box_pos"]
    box_size = config_ui["selected_spell_box_size"]
    icon_move = config_ui["selected_spell_box_icon_move"]
    icon_pos = (box_pos[0] + icon_move[0], box_pos[1] + icon_move[1])
    icon_size = config_ui["selected_spell_box_icon_size"]
    box_image = imglib.load_image_from_file(
        "images/sl/ui/SelectedSpellBox.png", after_scale=box_size)

    def __init__(self, game, player):
        super().__init__(game, player)
        self.display_icon = self.last_selected = self.last_icon = None
        self.update()

    def update(self):
        select = self.player.selected_spell
        if self.player.selected_spell is not self.last_selected:
            self.last_selected = select
            if select is not None:
                self.display_icon = imglib.scale(select.icon,
                                                 self.icon_size,
                                                 docache=False)
            else:
                self.display_icon = None

    def draw(self, screen):
        screen.blit(self.box_image, self.box_pos)
        if self.display_icon is not None:
            screen.blit(self.display_icon, self.icon_pos)
Exemple #2
0
class Embers(AbstractSpell):
    icon = imglib.load_image_from_file("images/pt/fire-arrows-2.png",
                                       after_scale=base_icon_size)
    tree_pos = (0, 0)  # Position from center
    mana_cost = 10

    count_min, count_max = 1, 3
    angle_spread = 10
    mul_if_moving = 2

    @requires_mana(mana_cost)
    def cast(self):
        level = self.player.level
        mid_angle = self.player.best_heading_vector.to_angle()
        level = self.player.level
        pos = self.player.rect.center
        count = random.randint(self.count_min, self.count_max)
        any_move = any(self.player.moving.values())
        for i in range(count):
            vec = utils.Vector.from_angle(
                mid_angle +
                random.randint(-self.angle_spread, self.angle_spread))
            projectile = projectiles.Ember(level, pos, norm_vector=vec)
            if any_move:
                projectile.dist *= self.mul_if_moving
                projectile.set_ease_args()
            level.sprites.append(projectile)
Exemple #3
0
    class Rune(BaseSprite):
        friendly = True
        hostile = False
        cachable = False
        size = (20, 20)
        damage = 0.5
        aoe = 50
        damage_aoe = 0.25
        surface = imglib.load_image_from_file(
            "images/sl/spells/RunePoison.png", after_scale=size)

        def __init__(self, level, pos):
            super().__init__()
            self.level = level
            self.rect = pygame.Rect((0, 0), self.size)
            self.rect.center = pos

        def update(self):
            if self.simple_deal_damage():
                self.level.sprites.remove(self)
                for i in range(random.randint(4, 7)):
                    p = particles.Particle.from_sprite(self, 4,
                                                       utils.Vector.uniform(3),
                                                       50, Color.Green)
                    self.level.particles.append(p)
                for sprite in self.level.hostile_sprites:
                    if utils.dist(self.rect.center,
                                  sprite.rect.center) < self.aoe:
                        sprite.take_damage(self.damage_aoe)
Exemple #4
0
class DoorTile(BaseTile):
    needs_update = False
    passable = True
    transparent = False
    flags_template = FlagSet(TileFlags.Passage)
    drawn_surface = imglib.load_image_from_file("images/dd/env/DoorOnWall.png",
                                                after_scale=tile_size_t)
Exemple #5
0
class Arrow(OmniProjectile):
    rotating = True
    hostile = True
    friendly = False
    base_size = (15, 3)
    base_image = imglib.load_image_from_file("images/sl/projectiles/Arrow.png",
                                             after_scale=base_size)
    speed = 10
    damage = 0.5
Exemple #6
0
class GrayGoo(BaseEnemy):
    base_move_speed = 2
    base_damage = 0.5
    base_max_health_points = 1
    damage_on_player_touch = True
    size = (30, 30)
    surface = imglib.load_image_from_file("images/dd/enemies/GrayGoo.png",
                                          after_scale=size)

    def __init__(self, level, spawner_tile):
        super().__init__(level, spawner_tile)
        self.ticks_to_wait = 0
        self.moving = {k: False for k in base_directions}
        self.set_random_move_direction()

    def update(self):
        super().update()
        last_rect = self.rect
        if not self.ticks_to_wait:
            self.moving[self.direction] = True
            self.handle_moving()
            if self.rect == last_rect:
                self.set_random_move_direction()
        else:
            self.ticks_to_wait -= 1

    def set_random_move_direction(self):
        possible_directions = []
        col, row = self.closest_tile_index
        level_size = self.level.layout_size
        if col > 0 and self.level.layout[row][col - 1].passable:
            possible_directions.append("left")
        if col < level_size[0] - 1 and self.level.layout[row][col +
                                                              1].passable:
            possible_directions.append("right")
        if row > 0 and self.level.layout[row - 1][col].passable:
            possible_directions.append("up")
        if row < level_size[1] - 1 and self.level.layout[row +
                                                         1][col].passable:
            possible_directions.append("down")
        if not possible_directions:
            possible_directions = ["left", "right", "up", "down"]
        self.direction = random.choice(possible_directions)

    def create_cache(self):
        cache = super().create_cache()
        cache.update({"direction": self.direction})
        return cache

    @classmethod
    def from_cache(cls, level, spawner_tile, cache):
        obj = super().from_cache(level, spawner_tile, cache)
        obj.direction = cache["direction"]
        return obj
Exemple #7
0
 def __init__(self, level, pos, *, centerpos=True, norm_vector):
     super().__init__(level,
                      pos,
                      centerpos=centerpos,
                      norm_vector=norm_vector)
     anim_size = (self.base_size[0] * self.animation_frames,
                  self.base_size[1])
     animation_surf = imglib.load_image_from_file(
         "images/sl/projectiles/FireballAnim.png", after_scale=anim_size)
     self.animation = Animation.from_surface_w(animation_surf,
                                               self.base_size[0], 5)
Exemple #8
0
 class GenericLevel(BaseLevel):
     source = filename
     _leveldata = load_level_data_from_file(filename,
                                            is_special=expected_special)
     raw_layout, start_entries, bg_name = _leveldata
     start_entries_rev = {v: k for k, v in start_entries.items()}
     bg_tile = imglib.load_image_from_file(bg_name)
     passages = []
     for k, v in start_entries.items():
         if v is not None:
             passages.append(k)
Exemple #9
0
class EtherealSword(SimpleProjectile):
    hostile = False
    friendly = True
    base_size = (30, 12)
    base_image = imglib.load_image_from_file(
        "images/sl/projectiles/EtherealSword.png", after_scale=base_size)
    image_r = imglib.all_rotations(base_image)
    size_r = imglib.ValueRotationDependent(
        *[surface.get_size() for surface in image_r.as_list])
    speed = 10
    damage = 0.5
Exemple #10
0
class Fireball(AbstractSpell):
    icon = imglib.load_image_from_file("images/pt/fireball-red-2.png",
                                       after_scale=base_icon_size)
    tree_pos = (0, -100)
    mana_cost = 20

    @requires_mana(mana_cost)
    def cast(self):
        projectile = projectiles.Fireball(
            self.player.level,
            self.player.rect.center,
            norm_vector=self.player.best_heading_vector)
        self.player.level.sprites.append(projectile)
Exemple #11
0
class PoisonRune(AbstractSpell):
    icon = imglib.load_image_from_file("images/pt/shielding-acid-3.png",
                                       after_scale=base_icon_size)
    tree_pos = (-100, 0)
    mana_cost = 25

    count_min, count_max = 2, 4
    dist = 20

    class Rune(BaseSprite):
        friendly = True
        hostile = False
        cachable = False
        size = (20, 20)
        damage = 0.5
        aoe = 50
        damage_aoe = 0.25
        surface = imglib.load_image_from_file(
            "images/sl/spells/RunePoison.png", after_scale=size)

        def __init__(self, level, pos):
            super().__init__()
            self.level = level
            self.rect = pygame.Rect((0, 0), self.size)
            self.rect.center = pos

        def update(self):
            if self.simple_deal_damage():
                self.level.sprites.remove(self)
                for i in range(random.randint(4, 7)):
                    p = particles.Particle.from_sprite(self, 4,
                                                       utils.Vector.uniform(3),
                                                       50, Color.Green)
                    self.level.particles.append(p)
                for sprite in self.level.hostile_sprites:
                    if utils.dist(self.rect.center,
                                  sprite.rect.center) < self.aoe:
                        sprite.take_damage(self.damage_aoe)

    @requires_mana(mana_cost)
    def cast(self):
        level = self.player.level
        pos = self.player.rect.center
        vec = self.player.best_heading_vector
        rune = self.Rune(
            level, (pos[0] + vec.x * self.dist, pos[1] + vec.y * self.dist))
        level.sprites.append(rune)
Exemple #12
0
class HiddenRoomTile(BaseTile):
    needs_update = False
    passable = True
    transparent = False
    flags_template = FlagSet(TileFlags.PartOfHiddenRoom)
    drawn_surface = imglib.load_image_from_file("images/dd/env/Wall.png",
                                                after_scale=tile_size_t)
    uncovered_drawn_surface = pygame.Surface(tile_size_t)
    uncovered_drawn_surface.fill(Color.Black)
    uncovered_drawn_surface.set_colorkey(Color.Black)

    def __init__(self, level, col_idx, row_idx):
        super().__init__(level, col_idx, row_idx)
        self.uncovered = False

    def uncover(self):
        self.uncovered = True
        self.drawn_surface = self.uncovered_drawn_surface
        self.transparent = True
Exemple #13
0
class Ember(EasedProjectile):
    hostile = False
    friendly = True
    size = (4, 6)
    surface = imglib.load_image_from_file("images/sl/projectiles/Ember.png",
                                          after_scale=size)
    dist = 100
    dist_diff = 20
    length = 50
    damage = 0.25
    caused_effect = statuseffects.Burning
    effect_length = 60
    effect_chance = 1 / 2

    def deal_damage(self, sprite):
        super().deal_damage(sprite)
        if random.uniform(0, 1) <= self.effect_chance:
            tick = self.level.parent.game.ticks
            effect = self.caused_effect(sprite, tick, self.effect_length)
            sprite.status_effects.add(effect)
Exemple #14
0
class SkeletonArcher(BaseEnemy):
    base_move_speed = 1
    base_damage = 0.1
    base_max_health_points = 1.5
    damage_on_player_touch = True
    size = (30, 30)
    surface = imglib.load_image_from_file(
        "images/sl/enemies/SkeletonArcher.png", after_scale=size)
    shot_cooldown = 70

    def __init__(self, level, spawner_tile):
        super().__init__(level, spawner_tile)
        self.next_shot = self.shot_cooldown
        self.last_rect = None
        self.path_obstructed = False
        self.moving = {k: False for k in base_directions}
        self.last_path_target = None
        self.path_to_player = []
        self.current_target = None

    def update(self):
        super().update()
        player = self.level.parent.player
        for p in pathfinding.get_sprite_path_npoints(self, player):
            if not self.level.layout[p[1]][p[0]].passable:
                self.path_obstructed = True
                self.moving = {k: False for k in base_directions}
                break
        else:
            self.path_obstructed = False
            self.moving = False
            self.path_to_player = []
            self.current_target = None
        if self.next_shot <= 0 and not self.path_obstructed:
            p = projectiles.Arrow.towards(self.level, self.rect.center,
                                          player.rect.center)
            self.level.sprites.append(p)
            self.next_shot = self.shot_cooldown * (self.base_move_speed /
                                                   self.move_speed)
        self.next_shot -= 1
        cpoint = self.closest_tile_index
        p1, p2 = cpoint, player.closest_tile_index
        if self.path_obstructed and self.last_path_target != p2:
            self.last_path_target = p2
            self.path_to_player = pathfinding.a_star_in_level(
                p1, p2, self.level.layout)
        if self.path_to_player:
            while self.current_target is None or self.current_target == self.rect.center:
                p = self.path_to_player.pop()
                self.current_target = self.level.layout[p[1]][p[0]].rect.center
            t, c = self.current_target, self.rect.center
            d1, d2 = utils.sign(t[0] - c[0]), utils.sign(t[1] - c[1])
            if d1 == -1:
                self.moving["left"] = True
            elif d1 == 1:
                self.moving["right"] = True
            if d2 == -1:
                self.moving["up"] = True
            elif d2 == 1:
                self.moving["down"] = True
            self.handle_moving()
Exemple #15
0
class HeartsWidget(AbstractUIWidget):
    heart_size = config_ui["heart_size"]
    hearts_pos = config_ui["hearts_pos"]
    hearts_gap = config_ui["hearts_gap"]
    heart_img = imglib.load_image_from_file("images/sl/hearts/Full.png",
                                            after_scale=heart_size)
    halfheart_img = imglib.load_image_from_file("images/sl/hearts/Half.png",
                                                after_scale=heart_size)
    emptyheart_img = imglib.load_image_from_file("images/sl/hearts/Empty.png",
                                                 after_scale=heart_size)
    # Invulnerable hearts (empty invulnerable heart is the same as the normal)
    heartinv_img = imglib.load_image_from_file("images/sl/hearts/FullInv.png",
                                               after_scale=heart_size)
    halfheartinv_img = imglib.load_image_from_file(
        "images/sl/hearts/HalfInv.png", after_scale=heart_size)

    def __init__(self, game, player):
        super().__init__(game, player)
        self.heart_draws = []
        self.top_heart = None
        self.hearts_gap_default = self.hearts_gap
        self.update_hearts()

    def update(self):
        if self.player.last_health_points != self.player.health_points or \
          bool(self.player.last_invincibility_ticks) != bool(self.player.invincibility_ticks) or \
          math.ceil(self.player.max_health_points) != len(self.heart_draws):
            self.hearts_gap = self.hearts_gap_default
            self.update_hearts()

    def update_hearts(self):
        self.heart_draws.clear()
        self.top_heart = 0
        invincible = bool(self.player.invincibility_ticks)
        heart_img = self.heartinv_img if invincible else self.heart_img
        halfheart_img = self.halfheartinv_img if invincible else self.halfheart_img
        emptyheart_img = self.emptyheart_img
        health = self.player.health_points
        hearts = math.ceil(self.player.max_health_points)
        rect = pygame.Rect(self.hearts_pos, self.heart_size)
        inside_fix = 10
        gap = self.hearts_gap
        drawn = 0
        # Distribute the hearts as needed
        if health > 0:
            for h in range(math.floor(health)):
                self.heart_draws.append((heart_img, rect.copy()))
                drawn += 1
                rect.x += self.heart_size[0] + gap
                self.top_heart = drawn - 1
            if health % 1:
                surface = emptyheart_img.copy()
                width = inside_fix / 2 + round(
                    (rect.width - inside_fix) * (health % 1))
                if width == rect.width - inside_fix / 2:
                    width = rect.width
                area = pygame.Rect(0, 0, width, rect.height)
                surface.blit(heart_img, (0, 0), area)
                self.heart_draws.append((surface, rect.copy()))
                drawn += 1
                rect.x += self.heart_size[0] + gap
                self.top_heart = drawn - 1
            for h in range(hearts - drawn):
                self.heart_draws.append((emptyheart_img, rect.copy()))
                drawn += 1
                rect.x += self.heart_size[0] + gap
        else:
            for h in range(math.ceil(self.player.max_health_points)):
                self.heart_draws.append((emptyheart_img, rect.copy()))
                drawn += 1
                rect.x += self.heart_size[0] + gap
        if rect.x > 500 and self.hearts_gap > -self.heart_size[0]:
            self.hearts_gap -= 1
            self.update_hearts()

    def draw(self, screen):
        for i, pair in enumerate(self.heart_draws):
            if i == self.top_heart:
                continue
            img, rect = pair
            screen.blit(img, rect)
        top = self.heart_draws[self.top_heart]
        screen.blit(top[0], top[1])
Exemple #16
0
class IceBeam(ChanneledSpell):
    icon = imglib.load_image_from_file("images/pt/beam-blue-2.png",
                                       after_scale=base_icon_size)
    tree_pos = (100, 0)
    mana_channel_cost = 0.25

    cooldown = 1

    # If it works, it ain't broken.
    class BeamProjectile(projectiles.OmniProjectile):
        rotating = True
        hostile = False
        friendly = True
        base_size = (10, 5)
        base_image = imglib.load_image_from_file(
            "images/sl/projectiles/BeamBlue.png", after_scale=base_size)
        speed = base_size[0] - 1
        damage = 0.003
        particle_chance = 1 / 4
        particle_spread = 30
        particle_speed = 2
        charged_particle_chance = 1 / 100
        caused_effect = statuseffects.Chilled
        effect_length = 120

        def __init__(self,
                     level,
                     pos,
                     *,
                     centerpos=True,
                     norm_vector,
                     charged=True,
                     retain=lambda: True,
                     reason=None):
            super().__init__(level,
                             pos,
                             centerpos=centerpos,
                             norm_vector=norm_vector)
            self.charged = charged
            self.retain = retain
            self.reason = reason
            self.moving = True
            # Used to make the beam go deeper into the wall after collision
            self.destroy_on_next = False

        def update(self):
            if self.moving:
                self.xbuf += self.velx
                self.ybuf += self.vely
                self.rect.x, self.rect.y = self.xbuf, self.ybuf
            else:
                self.simple_deal_damage()
            if self.reason is None:
                if self.charged and random.uniform(
                        0, 1) <= self.charged_particle_chance:
                    p = particles.Particle.from_sprite(self, 3,
                                                       utils.Vector.uniform(1),
                                                       50, Color.lBlue)
                    self.level.particles.append(p)
            elif random.uniform(0, 1) <= self.particle_chance:
                norm = self.norm_vector
                spread = self.particle_spread
                source_sprite = self
                if self.reason == projectiles.DestroyReason.Collision:
                    vel = utils.Vector.random_spread(
                        norm, spread).opposite() * self.particle_speed
                elif self.reason == projectiles.DestroyReason.DamageDeal:
                    vel = utils.Vector.uniform(self.particle_speed)
                    source_sprite = self.last_attacked_sprite
                else:
                    vel = utils.Vector(0, 0)
                p = particles.Particle.from_sprite(source_sprite, 4, vel, 40,
                                                   Color.lBlue)
                self.level.particles.append(p)

        def draw(self, screen, pos_fix=(0, 0)):
            super().draw(screen, pos_fix)
            if not self.retain():
                self.destroy()

        def deal_damage(self, sprite):
            super().deal_damage(sprite)
            tick = self.level.parent.game.ticks
            eff = self.caused_effect(sprite, tick, self.effect_length)
            sprite.status_effects.add(eff)

    def __init__(self, player):
        super().__init__(player)
        self.next_beam = self.cooldown
        self.stationary_time = 0
        self.last_tick_cast = -1
        self.last_vec = None
        self.last_pos = None
        self.last_col = False
        self.beams = []

    @requires_channel_mana(mana_channel_cost)
    def cast(self):
        level = self.player.level
        vec = self.player.best_heading_vector
        pos = self.player.rect.center
        if not any(self.player.moving.values()
                   ) and self.player.game.ticks == self.last_tick_cast + 1:
            self.stationary_time += 1
        else:
            self.stationary_time = 0

        dest = False
        col = False
        any_beam = None
        ens = []
        if self.beams:
            any_beam = self.beams[0]
            ens = any_beam.get_local_enemy_sprites()
        if self.beams and not any_beam in self.player.level.sprites:
            dest = True
        if not dest and self.last_vec != vec or self.last_pos != pos:
            dest = True
        if not dest and self.beams and ens:
            for b in self.beams:
                for e in ens:
                    if b.rect.colliderect(e.rect):
                        dest = True
                        col = True
                        break
                else:
                    continue
                break
        if not dest and not col and self.last_col:
            dest = True
        if not dest and self.stationary_time >= 60 and not self.last_charged:
            dest = True
        if dest:
            self.destroy_beams()
        self.repopulate_beams()

        self.last_tick_cast = self.player.game.ticks
        self.last_vec = vec
        self.last_pos = pos
        self.last_col = col
        self.last_charged = self.stationary_time >= 60

    def destroy_beams(self):
        for b in self.beams:
            if not b.destroyed:
                b.destroy()
        self.beams.clear()

    def repopulate_beams(self):
        level = self.player.level
        vec = self.player.best_heading_vector
        pos = self.player.rect.center
        i = 0
        added = []
        hit = []
        stop_on_next = False
        first_collide = False
        while not stop_on_next:
            p = self.BeamProjectile(level,
                                    pos,
                                    norm_vector=vec,
                                    charged=self.stationary_time >= 60,
                                    retain=(lambda: self.cast_this_tick))
            for v in range(i):
                p.xbuf += p.velx
                p.ybuf += p.vely
            p.rect.x, p.rect.y = p.xbuf, p.ybuf
            if not p.inside_level:
                break
            if p.get_collision_nearby():
                p.reason = projectiles.DestroyReason.Collision
                stop_on_next = True
            for sprite in p.get_local_enemy_sprites():
                if sprite in hit:
                    continue
                if p.rect.colliderect(sprite.rect):
                    p.last_attacked_sprite = sprite
                    p.reason = projectiles.DestroyReason.DamageDeal
                    hit.append(sprite)
                    sprite.take_damage(p.damage)
                    if not p.charged or len(hit) >= 3:
                        stop_on_next = True
                        break
            for sprite in self.beams:
                if p.rect.colliderect(sprite.rect):
                    if i == 0:
                        first_collide = True
                    stop_on_next = True
                    break
            if first_collide:
                break
            added.append(p)
            i += 1
        for a in added:
            a.moving = False
            level.sprites.append(a)
            self.beams.append(a)
Exemple #17
0
class WallTile(BaseTile):
    needs_update = False
    passable = False
    transparent = False
    drawn_surface = imglib.load_image_from_file("images/dd/env/Wall.png",
                                                after_scale=tile_size_t)
Exemple #18
0
class FlyingBoomerang(OmniProjectile):
    hostile = False
    friendly = True
    base_size = (16, 16)
    base_image = imglib.load_image_from_file("images/sl/items/Boomerang.png",
                                             after_scale=base_size)
    damage = 0  # Override normal damage deal
    max_damage = 0.75
    damage_loss_mul = 0.6
    rotation_speed = 7
    base_length = 40
    back_speed = 10
    curve_spread = 30
    curve_point = 0.9
    curve_back_spread = 60
    curve_back_point = 0.2

    def __init__(self, level, pos, *, centerpos=True, source_sprite, target):
        super().__init__(level,
                         pos,
                         centerpos=centerpos,
                         norm_vector=utils.Vector(0, 0))
        self.source_sprite = source_sprite  # It always comes back!
        self.target = target
        self.dist = utils.dist(self.pos, self.target)
        self.surface = self.base_image
        self.length = self.base_length
        self.curve_points = None
        self.curve = self.new_curve()
        self.time_trans = lambda t: utils.translate_to_zero_to_one_bounds(
            t, (0, self.length))
        self.time = 0
        self.rotation = 0
        self.coming_back = False
        self.coming_back_curve = False
        self.hit = set()
        self.act_damage = self.max_damage
        self.last_source_sprite_pos = self.source_sprite.rect.topleft
        self.last_adist = self.dist

    def update(self):
        self.rotation += self.rotation_speed
        self.rotation %= 360
        self.surface = imglib.rotate(self.base_image, self.rotation)
        new_rect = self.surface.get_rect()
        new_rect.center = self.rect.center
        self.rect = new_rect
        for sprite in self.get_local_enemy_sprites():
            if sprite not in self.hit and self.rect.colliderect(sprite.rect):
                self.hit.add(sprite)
                sprite.take_damage(self.act_damage)
                self.act_damage *= self.damage_loss_mul
        if self.simple_tick() == TickStatus.Destroy and \
          (self.destroy_reason != DestroyReason.DamageDeal and self.act_damage > 0.2):
            if self.rect.colliderect(self.source_sprite.rect):
                super().destroy()
                return
            self.coming_back = True
        else:
            self.destroy_reason = None
        if (self.coming_back
                or self.coming_back_curve) and self.rect.colliderect(
                    self.source_sprite.rect):
            super().destroy()
            return
        if self.coming_back:
            self.curve = None
            self.curve_points = None
            # Move in a straight line ignoring everything
            vec_back = utils.Vector.from_points(self.source_sprite.rect.center,
                                                self.rect.center)
            move = self.back_speed * vec_back.normalize()
            self.xbuf += move.x
            self.ybuf += move.y
            self.rect.centerx, self.rect.centery = self.xbuf, self.ybuf
        else:
            # Move in a curve, collision breaks this state
            change = self.source_sprite.rect.topleft != self.last_source_sprite_pos
            if self.coming_back_curve and change:
                self.curve = self.new_back_curve()
            if self.time > self.length:
                if not self.coming_back_curve:
                    self.time = 0
                    self.curve = self.new_back_curve()
                    self.coming_back_curve = True
                else:
                    self.coming_back = True
            else:
                curve_pos = self.curve(self.time_trans(self.time))
                self.xbuf, self.ybuf = self.rect.center = (curve_pos.x,
                                                           curve_pos.y)
                self.time += 1
        self.last_source_sprite_pos = self.source_sprite.rect.topleft

    def destroy(self):
        pass

    def new_curve(self):
        p = utils.break_segment(self.rect.center, self.target,
                                self.curve_spread, self.curve_point)
        self.curve_points = p
        return utils.bezier(*p)

    def new_back_curve(self):
        # Make the boomerang go faster if the player is running away from it
        # THERE'S NO ESCAPE
        # IT WILL COME BACK
        adist = utils.dist(self.rect.center, self.source_sprite.rect.center)
        if adist < self.last_adist:
            d = self.dist / adist
            if d > 1: d = 1 / d
            d = d**(1 / 10)
            d = max(d, 0.2)
            self.length = self.base_length * d
            self.time = (self.time - 1) * (self.base_length / self.length)
            self.time = max(self.time, 1)
            self.last_adist = adist
        p = utils.break_segment(self.rect.center,
                                self.source_sprite.rect.center,
                                self.curve_back_spread, self.curve_back_point)
        self.curve_points = p
        return utils.bezier(*p)
Exemple #19
0
class Fireball(OmniProjectile):
    hostile = False
    friendly = True
    rotating = False
    base_size = (16, 16)
    base_image = imglib.load_image_from_file(
        "images/sl/projectiles/Fireball.png", after_scale=base_size)
    animation_frames = 5
    speed = 14
    particle_spread = 35
    particle_speed = 5
    damage = 0.5
    aoe = 40
    damage_aoe = 0.25
    caused_effect = statuseffects.Burning
    effect_length = 90
    aoe_effect_chance = 1 / 2

    def __init__(self, level, pos, *, centerpos=True, norm_vector):
        super().__init__(level,
                         pos,
                         centerpos=centerpos,
                         norm_vector=norm_vector)
        anim_size = (self.base_size[0] * self.animation_frames,
                     self.base_size[1])
        animation_surf = imglib.load_image_from_file(
            "images/sl/projectiles/FireballAnim.png", after_scale=anim_size)
        self.animation = Animation.from_surface_w(animation_surf,
                                                  self.base_size[0], 5)

    def update(self):
        super().update()
        self.animation.update()
        self.surface = self.animation.surface

    def destroy(self):
        super().destroy()
        norm, spread = self.norm_vector, self.particle_spread
        # Different particles depending on the destroy_reason
        # (collision - bouncing off the wall, damage deal - around the target sprite,
        # otherwise - assume the projectile is out-of-view anyways and don't spawn particles)
        for i in range(random.randint(5, 8)):
            if self.destroy_reason == DestroyReason.Collision:
                vel = utils.Vector.random_spread(
                    norm, spread).opposite() * self.particle_speed
                source_sprite = self
            elif self.destroy_reason == DestroyReason.DamageDeal:
                vel = utils.Vector.uniform(self.particle_speed)
                source_sprite = self.last_attacked_sprite
            else:
                break
            color = random.choice((Color.Yellow, Color.Orange, Color.Red))
            p = particles.Particle.from_sprite(source_sprite, 4, vel, 30,
                                               color)
            self.level.particles.append(p)
        # AOE Damage
        for sprite in self.get_local_enemy_sprites():
            if utils.dist(self.rect.center, sprite.rect.center) < self.aoe:
                sprite.take_damage(self.damage_aoe)
                if random.uniform(0, 1) <= self.aoe_effect_chance:
                    tick = self.level.parent.game.ticks
                    effect = self.caused_effect(sprite, tick,
                                                self.effect_length)
                    sprite.status_effects.add(effect)

    def deal_damage(self, sprite):
        super().deal_damage(sprite)
        tick = self.level.parent.game.ticks
        effect = self.caused_effect(sprite, tick, self.effect_length)
        sprite.status_effects.add(effect)
Exemple #20
0
class HiddenRoomDoorTile(HiddenRoomTile):
    drawn_surface = imglib.load_image_from_file("images/dd/env/DoorOnWall.png",
                                                after_scale=tile_size_t)
Exemple #21
0
class MinimapWidget(AbstractUIWidget):
    minimap_tile = config_ui["minimap_blocksize"]
    minimap_tile_t = (minimap_tile, minimap_tile)
    minimap_tiles = config_ui["minimap_tiles"]
    question_mark = imglib.load_image_from_file(
        "images/sl/minimap/QuestionMarkM.png", after_scale=minimap_tile_t)
    tile_empty = imglib.load_image_from_file("images/dd/env/Bricks.png",
                                             after_scale=minimap_tile_t)
    tile_wall = imglib.load_image_from_file("images/dd/env/WallSmall.png",
                                            after_scale=minimap_tile_t)
    border_color = (0, 29, 109)

    def __init__(self, game, player):
        super().__init__(game, player)
        full_minimap_size_px = (self.game.vars["mapsize"][0] *
                                self.minimap_tile,
                                self.game.vars["mapsize"][1] *
                                self.minimap_tile)
        self.background = imglib.repeated_image_texture(
            self.question_mark, full_minimap_size_px)
        self.full_surface = pygame.Surface(full_minimap_size_px)
        self.surface = pygame.Surface(config_ui["minimap_size"])
        self.rect = self.surface.get_rect()
        self.border = imglib.color_border(config_ui["minimap_size"],
                                          self.border_color,
                                          4,
                                          nowarn=True)
        self.tile_current = self.new_tile_current_surface()

    def update_full(self):
        # Redraw the entire minimap surface
        self.full_surface.fill(Color.Black)
        self.full_surface.blit(self.background, (0, 0))
        mazepos = self.game.vars["player_mazepos"]
        for row, irow in enumerate(self.game.vars["maze"]):
            for col, bit in enumerate(irow):
                x, y = col * self.minimap_tile, row * self.minimap_tile
                rect = pygame.Rect((x, y), self.minimap_tile_t)
                if (col, row) == mazepos:
                    tile = self.tile_current
                elif self.player.map_reveal[row][col]:
                    tile = self.tile_wall if bit else self.tile_empty
                else:
                    continue
                self.full_surface.blit(tile, rect)

    def update_part(self):
        # Redraw the needed part of the entire minimap surface to the display UI element
        mazepos = self.game.vars["player_mazepos"]
        mtopleftidx = tuple(
            (self.minimap_tiles[i] - 1) / 2 - mazepos[i] for i in range(2))
        mtopleft = mtopleftidx[0] * self.minimap_tile, mtopleftidx[
            1] * self.minimap_tile
        self.rect.topleft = mtopleft
        self.surface.fill(Color.Black)
        self.surface.blit(self.full_surface, self.rect)
        self.surface.blit(self.border, (0, 0))

    def update_on_new_level(self):
        self.update_full()
        self.update_part()

    def update(self):
        pass

    def draw(self, screen):
        screen.blit(self.surface, config_ui["minimap_pos"])

    def new_tile_current_surface(self):
        surface = self.tile_empty.copy()
        mini_player_size = (int(self.minimap_tile / 1.2),
                            int(self.minimap_tile / 1.2))
        scaled_player = imglib.scale(self.player.surface, mini_player_size)
        s_rect = scaled_player.get_rect()
        mini_player_pos = (int((self.minimap_tile - s_rect.width) / 2),
                           int((self.minimap_tile - s_rect.height) / 2))
        surface.blit(scaled_player, mini_player_pos)
        return surface
Exemple #22
0
class Chest(BaseContainer):
    drawn_surface = imglib.load_image_from_file("images/sl/env/Chest.png",
                                                after_scale=tile_size_t)
Exemple #23
0
class StarsWidget(AbstractUIWidget):
    mana_per_star = config_ui["mana_per_star"]
    star_main_color = config_ui["star_main_color"]
    star_size = config_ui["star_size"]
    stars_pos = config_ui["stars_pos"]
    stars_gap = config_ui["stars_gap"]
    star_img = imglib.load_image_from_file("images/sl/stars/Star.png",
                                           after_scale=star_size)
    main_r, main_g, main_b = star_main_color
    star_images = []
    lowest_alpha = 200
    _get_trans_color = lambda v, fr, m=255: v + (m - v) * fr
    for i in range(mana_per_star + 1):
        star_array = pygame.PixelArray(star_img.copy())
        _fr = 1 - (i / mana_per_star)
        if _fr > 0:
            repcolor = (_get_trans_color(main_r, _fr, lowest_alpha),
                        _get_trans_color(main_g, _fr, lowest_alpha),
                        _get_trans_color(main_b, _fr))
            star_array.replace(star_main_color, repcolor, 0.4)
        star_images.append(star_array.surface)
        del star_array

    def __init__(self, game, player):
        super().__init__(game, player)
        self.star_draws = []
        self.top_star = None
        self.stars_gap_default = self.stars_gap
        self.update_stars()

    def update(self):
        if self.player.last_mana_points != self.player.mana_points or \
           math.ceil(self.player.max_mana_points / self.mana_per_star) != len(self.star_draws):
            self.stars_gap = self.stars_gap_default
            self.update_stars()

    def update_stars(self):
        self.star_draws.clear()
        self.top_star = 0
        rect = pygame.Rect(self.stars_pos, self.star_size)
        count = math.ceil(self.player.max_mana_points / self.mana_per_star)
        drawn = 0
        for i in range(self.player.mana_points // self.mana_per_star):
            self.star_draws.append(
                (self.star_images[self.mana_per_star], rect.copy()))
            rect.x += self.star_size[0] + self.stars_gap
            drawn += 1
            self.top_star = drawn - 1
        if drawn != count:
            self.star_draws.append(
                (self.star_images[self.player.mana_points % 20], rect.copy()))
            rect.x += self.star_size[0] + self.stars_gap
            drawn += 1
            self.top_star = drawn - 1
            while drawn != count:
                self.star_draws.append((self.star_images[0], rect.copy()))
                rect.x += self.star_size[0] + self.stars_gap
                drawn += 1
        if rect.x > 500 and self.stars_gap > -self.star_size[0]:
            self.stars_gap -= 1
            self.update_stars()

    def draw(self, screen):
        for i, pair in enumerate(self.star_draws):
            if i == self.top_star:
                continue
            img, rect = pair
            screen.blit(img, rect)
        top = self.star_draws[self.top_star]
        screen.blit(top[0], top[1])
Exemple #24
0
class PlayerCharacter(BaseSprite):
    is_entity = True
    size = (32, 32)
    surface = imglib.load_image_from_file("images/dd/player/HeroBase.png",
                                          after_scale=size)
    attributes = [
        "move_speed", "max_health_points", "max_mana_points", "vision_radius"
    ]
    base_max_health_points = 3
    base_max_mana_points = 60
    base_move_speed = 4
    base_vision_radius = 16

    starting_items = [
        playeritems.Sword, playeritems.EnchantedSword,
        playeritems.FireballStaff, playeritems.Boomerang
    ]
    starting_spells = [spells.Embers]
    invincibility_ticks_on_damage = 120
    sprint_move_speed_gain = 4
    mana_regen_delay = 50
    mana_regen_args = (0, 0.7, 125)
    mana_regen_moving_malus_mul = 0.2

    def __init__(self, game):
        super().__init__()
        self.game = game
        # Set by the state
        self.parent_state = None
        self.level = None
        self.rect = pygame.Rect((0, 0), self.size)
        self.precache = {}

        self.inventory = playerinventory.PlayerInventory(self)
        for item_cls in self.starting_items:
            self.inventory.add_item(item_cls(self))
        self.selected_item_idx = 0

        self.unlocked_spells = self.starting_spells.copy()
        self.selected_spell = self.unlocked_spells[0](self)
        self.selected_spell.on_select()
        self.last_selected_spell = self.selected_spell

        self.reset_attributes()
        self.activate_tile = False
        self.moving = {
            "left": False,
            "right": False,
            "up": False,
            "down": False
        }
        self.move_sprint = False
        self.rotation = "right"
        self.going_through_door = False
        self.crouching = False
        self.use_item = False
        self.cast_spell = False

        self.fov_enabled = self.game.vars["enable_fov"]
        if self.fov_enabled:
            self.computed_fov_map = None
            self.level_vision = None

        self.health_points = self.max_health_points
        self.last_health_points = self.health_points
        self.invincibility_ticks = 0
        self.last_invincibility_ticks = self.invincibility_ticks

        self.mana_points = self.max_mana_points
        self.last_mana_points = self.mana_points
        self.mana_ticks_until_regen = -1
        self.mana_regen_tick = 0
        self.mana_regen_buffer = 0

        self.status_effects = statuseffects.StatusEffects(
            self, lambda: self.game.ticks)

        self.map_reveal = self.new_gamemap_map()

        self.widgets = []  # Populated by a widget
        self.minimap = playerui.MinimapWidget(self.game, self)
        self.hearts = playerui.HeartsWidget(self.game, self)
        self.stars = playerui.StarsWidget(self.game, self)
        self.item_box = playerui.SelectedItemBoxWidget(self.game, self)
        self.spell_box = playerui.SelectedSpellBoxWidget(self.game, self)

    def __repr__(self):
        return "<{} @ {}>".format(type(self).__name__, self.rect.topleft)

    def handle_events(self, events, pressed_keys, mouse_pos):
        self.activate_tile = False
        self.moving["left"] = pressed_keys[controls.Keys.Left]
        self.moving["right"] = pressed_keys[controls.Keys.Right]
        self.moving["up"] = pressed_keys[controls.Keys.Up]
        self.moving["down"] = pressed_keys[controls.Keys.Down]
        self.move_sprint = pressed_keys[controls.Keys.Sprint]
        self.crouching = pressed_keys[controls.Keys.Crouch]
        item = self.selected_item
        spell = self.selected_spell
        for event in events:
            if event.type == pygame.KEYDOWN:
                if event.key == controls.Keys.UseItem:
                    if item is not None and not item.special_use:
                        self.use_item = True
                elif event.key == controls.Keys.CastSpell:
                    if spell is not None and not spell.special_cast:
                        self.cast_spell = True
                elif event.key == controls.Keys.ActivateTile:
                    self.activate_tile = True
                elif event.key == controls.Keys.Left:
                    self.rotation = "left"
                elif event.key == controls.Keys.Right:
                    self.rotation = "right"
                elif event.key == controls.Keys.Up:
                    self.rotation = "up"
                elif event.key == controls.Keys.Down:
                    self.rotation = "down"
            if self.game.use_mouse and event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    if item is not None and not item.special_use:
                        self.use_item = True
                elif event.button == 3 and not spell.special_cast:
                    self.cast_spell = True
        if item is not None and item.special_use:
            self.use_item = item.can_use(events, pressed_keys, mouse_pos,
                                         controls)
        if spell is not None and spell.special_cast:
            self.cast_spell = spell.can_cast(events, pressed_keys, mouse_pos,
                                             controls)

    def update(self):
        top = self.game.top_state
        self.reset_attributes()
        self.status_effects.update()
        if self.use_item:
            self.selected_item.use()
            self.use_item = False
        if self.selected_item is not None:
            self.selected_item.update()
        if self.selected_spell is not self.last_selected_spell:
            self.last_selected_spell.on_deselect()
            self.selected_spell.on_select()
        if self.cast_spell:
            self.selected_spell.cast_this_tick = True
            self.selected_spell.cast()
            self.cast_spell = False
        else:
            self.selected_spell.cast_this_tick = False
        if self.move_sprint:
            self.move_speed += self.sprint_move_speed_gain
        if self.last_mana_points <= self.mana_points < self.max_mana_points:
            if self.mana_ticks_until_regen != 0:
                if self.mana_ticks_until_regen == -1:
                    self.mana_ticks_until_regen = self.mana_regen_delay
                if self.mana_ticks_until_regen >= 0:
                    self.mana_ticks_until_regen -= 1
        else:
            self.mana_ticks_until_regen = -1
            self.mana_regen_buffer = 0
            self.mana_regen_tick = 0
        if self.mana_ticks_until_regen == 0:
            self.mana_regen_tick += 1
            current_mana_regen = 0
            if self.mana_regen_tick <= self.mana_regen_args[2]:
                current_mana_regen += easing.ease_circular_in(
                    self.mana_regen_tick, *self.mana_regen_args)
            else:
                current_mana_regen += self.mana_regen_args[
                    0] + self.mana_regen_args[1]
            if any(self.moving.values()):
                current_mana_regen *= self.mana_regen_moving_malus_mul
            self.mana_regen_buffer += current_mana_regen
            self.mana_points += math.floor(self.mana_regen_buffer)
            self.mana_regen_buffer %= 1
        if self.mana_ticks_until_regen >= 0 and self.mana_points == self.max_mana_points:
            self.mana_ticks_until_regen = -1
            self.mana_regen_buffer = 0
            self.mana_regen_tick = 0
        self.check_attribute_bounds()
        for widget in self.widgets:
            widget.update()
        self.moving_last = self.moving.copy()
        if not self.crouching:
            self.handle_moving()
        self.near_passage = None
        pcol, prow = self.closest_tile_index
        ptile = self.level.layout[prow][pcol]
        next_to = self.get_tiles_next_to() + [(pcol, prow)]
        # Near passages to other levels
        self.going_through_door = False
        for col, row in next_to:
            tile = self.level.layout[row][col]
            if tile.flags.Passage:
                self.near_passage = tile
                break
        else:
            if ptile.flags.Passage:
                self.near_passage = ptile
        if self.near_passage is not None:
            if self.activate_tile or \
              (self.rect.left == 0 and self.moving_last["left"]) or \
              (self.rect.right == screen_size[0] and self.moving_last["right"]) or \
              (self.rect.top == 0 and self.moving_last["up"]) or \
              (self.rect.bottom == screen_size[1] and self.moving_last["down"]):
                self.going_through_door = True
        # Near containers (chests)
        self.near_container = None
        for col, row in next_to:
            tile = self.level.layout[row][col]
            if tile.flags.Container:
                self.near_container = tile
                break
        if self.near_container is not None and self.activate_tile:
            surf = top.get_as_parent_surface()
            s = states.PlayerInventoryState(self.game,
                                            parent_surface=surf,
                                            container=self.near_container)
            self.parent_state.queue_state = s
        # FOV
        inside_level = 0 <= pcol < self.level.width and 0 <= prow < self.level.height
        if self.fov_enabled and inside_level and not self.computed_fov_map[
                prow][pcol]:
            fovlib.calculate_fov(self.level.transparency_map,
                                 pcol,
                                 prow,
                                 self.vision_radius,
                                 dest=self.level_vision)
            self.computed_fov_map[prow][pcol] = True
        # Hidden rooms
        if ptile.flags.PartOfHiddenRoom and not ptile.uncovered:
            self.explore_room()
        # Invincibility ticks
        self.last_invincibility_ticks = self.invincibility_ticks
        if self.invincibility_ticks:
            self.invincibility_ticks -= 1
        # Set last
        self.last_health_points = self.health_points
        self.last_mana_points = self.mana_points
        self.last_selected_spell = self.selected_spell

    def draw(self, screen, pos_fix=(0, 0), *, dui=True):
        if self.fov_enabled:
            # Draw black squares in places the player didn't see yet
            for row in self.level.layout:
                for tile in row:
                    if not self.level_vision[tile.row_idx][tile.col_idx]:
                        screen.fill(Color.Black, tile.rect.move(pos_fix))
        super().draw(screen, pos_fix)
        if self.selected_item is not None:
            self.selected_item.draw(screen, pos_fix)

    def draw_ui(self, screen, pos_fix=(0, 0)):
        screen.fill(
            Color.Black,
            pygame.Rect(config_dungeon["topbar_position"],
                        config_dungeon["topbar_size"]))
        for widget in self.widgets:
            widget.draw(screen)

    # ===== Methods =====

    def create_cache(self):
        cache = deepcopy(self.precache)

        if "base_attributes" not in cache:
            cache["base_attributes"] = {}
        cache["base_attributes"]["move_speed"] = self.base_move_speed
        cache["base_attributes"][
            "max_health_points"] = self.base_max_health_points
        cache["base_attributes"]["max_mana_points"] = self.base_max_mana_points
        cache["base_attributes"]["vision_radius"] = self.base_vision_radius

        if "status" not in cache:
            cache["status"] = {}
        cache["status"]["pos"] = self.rect.topleft
        cache["status"]["health_points"] = self.health_points
        cache["status"]["mana_points"] = self.mana_points
        cache["status"]["selected_item_idx"] = self.selected_item_idx
        cache["status"]["selected_spell"] = type(self.selected_spell)

        cache["map_reveal"] = self.map_reveal

        if "status_effects" not in cache:
            cache["status_effects"] = []
        cache["status_effects"].extend(self.status_effects.create_cache())

        if "inventory" not in cache:
            cache["inventory"] = {}
        for i, item in enumerate(self.inventory.slots):
            if item is not None:
                icache = item.create_cache()
                # e.g. consumable items won't be added to caches,
                # since they already applied the status effect
                # and the rest of the effect is cosmetical
                if icache is not None:
                    cache["inventory"][i] = icache

        if "unlocked_spells" not in cache:
            cache["unlocked_spells"] = []
        for spelltype in self.unlocked_spells:
            cache["unlocked_spells"].append(spelltype)

        return cache

    def load_cache(self, cache):
        self.base_move_speed = cache["base_attributes"]["move_speed"]
        self.base_max_health_points = cache["base_attributes"][
            "max_health_points"]
        self.base_max_mana_points = cache["base_attributes"]["max_mana_points"]
        self.base_vision_radius = cache["base_attributes"]["vision_radius"]

        self.rect.topleft = cache["status"]["pos"]
        self.health_points = cache["status"]["health_points"]
        self.mana_points = cache["status"]["mana_points"]
        self.selected_item_idx = cache["status"]["selected_item_idx"]
        self.selected_spell = cache["status"]["selected_spell"](self)

        self.map_reveal = cache["map_reveal"]

        self.status_effects.load_cache(cache["status_effects"])

        self.inventory.clear_items()
        for i, item_cache in cache["inventory"].items():
            self.inventory.slots[int(i)] = item_cache["type"].from_cache(
                self, item_cache)

        self.unlocked_spells.clear()
        for spelltype in cache["unlocked_spells"]:
            self.unlocked_spells.append(spelltype)

        for widget in self.widgets:
            widget.update()

    def new_empty_level_map(self):
        return [[False for _ in range(self.level.width)]
                for _ in range(self.level.height)]

    def new_gamemap_map(self):
        return [[False for _ in range(self.game.vars["mapsize"][0])]
                for _ in range(self.game.vars["mapsize"][1])]

    def reset_attributes(self):
        self.move_speed = self.base_move_speed
        self.max_health_points = self.base_max_health_points
        self.max_mana_points = self.base_max_mana_points
        self.vision_radius = self.base_vision_radius

    def new_damage_particle(self, damage=1):
        maxvel = utils.Vector.uniform(2 * min(damage, 2.5))
        return particles.Particle.from_sprite(self, 5, maxvel, 200, Color.Red)

    def check_attribute_bounds(self):
        if self.health_points < 0:
            self.health_points = 0
        if self.health_points > self.max_health_points:
            self.health_points = self.max_health_points
        if self.mana_points < 0:
            self.mana_points = 0
        if self.mana_points > self.max_mana_points:
            self.mana_points = self.max_mana_points

    # Health

    def take_damage(self,
                    value,
                    *,
                    ignore_invincibility=False,
                    doparticles=True):
        if not self.invincibility_ticks or ignore_invincibility:
            if value > 0:
                self.invincibility_ticks = self.invincibility_ticks_on_damage
            self.health_points -= value
            self.health_points = _clamp(0, self.max_health_points,
                                        self.health_points)

            if value != 0:
                self.on_damage(value)

    def heal(self, value):
        return self.take_damage(-value, ignore_invincibility=True)

    # Updates

    def on_new_level(self):
        if self.fov_enabled:
            self.computed_fov_map = self.new_empty_level_map()
            self.level_vision = self.new_empty_level_map()
        self.reveal_nearby_map_tiles()
        self.minimap.update_on_new_level()

    def on_damage(self, value, *, doparticles=True):
        if doparticles and value > 0:
            mn = max(1, int(3 * value))
            mx = max(5, int(15 * value))
            for i in range(random.randint(mn, mx)):
                self.level.particles.append(self.new_damage_particle(value))

    # Minimap

    def reveal_nearby_map_tiles(self):
        col, row = self.game.vars["player_mazepos"]
        width, height = self.game.vars["mapsize"]
        self.map_reveal[row][col] = True
        for ncol, nrow in [(col + 1, row), (col - 1, row), (col, row + 1),
                           (col, row - 1), (col + 1, row + 1),
                           (col - 1, row + 1), (col + 1, row - 1),
                           (col - 1, row - 1)]:
            if 0 <= ncol < width and 0 <= nrow < height:
                self.map_reveal[nrow][ncol] = True

    # Level

    def explore_room(self):
        scol, srow = self.closest_tile_index
        width, height = self.level.width, self.level.height
        queue = deque()
        queue.appendleft((scol, srow))
        visited = {(scol, srow)}
        while queue:
            col, row = queue.pop()
            tile = self.level.layout[row][col]
            if tile.flags.PartOfHiddenRoom:
                tile.uncover()
                checked = [(col - 1, row), (col + 1, row), (col, row - 1),
                           (col, row + 1)]
                for ncol, nrow in checked:
                    tup = (ncol, nrow)
                    if 0 <= ncol < width - 1 and 0 <= nrow < height - 1 and tup not in visited:
                        visited.add(tup)
                        queue.appendleft(tup)
        self.level.force_render_update = True

    # Status
    @property
    def dying(self):
        return self.health_points < self.max_health_points

    # Other
    @property
    def selected_item(self):
        return self.inventory.slots[self.selected_item_idx]

    @property
    def rotation_vector(self):
        if self.rotation == "left":
            return utils.Vector(-1, 0)
        elif self.rotation == "right":
            return utils.Vector(1, 0)
        elif self.rotation == "up":
            return utils.Vector(0, -1)
        elif self.rotation == "down":
            return utils.Vector(0, 1)

    @property
    def to_mouse_vector(self):
        fix = [-n for n in config_dungeon["level_surface_position"]]
        return utils.norm_vector_to_mouse(self.rect.center, fix)

    @property
    def best_heading_vector(self):
        if self.game.use_mouse:
            return self.to_mouse_vector
        else:
            return self.rotation_vector

    # Cheats

    def reveal_all_map_tiles(self):
        for row in self.map_reveal:
            for i in range(len(row)):
                row[i] = True
        self.minimap.update_full()

    def unlock_all_spells(self):
        for name, spell in spells.register.items():
            self.unlocked_spells.append(spell)
Exemple #25
0
    class BeamProjectile(projectiles.OmniProjectile):
        rotating = True
        hostile = False
        friendly = True
        base_size = (10, 5)
        base_image = imglib.load_image_from_file(
            "images/sl/projectiles/BeamBlue.png", after_scale=base_size)
        speed = base_size[0] - 1
        damage = 0.003
        particle_chance = 1 / 4
        particle_spread = 30
        particle_speed = 2
        charged_particle_chance = 1 / 100
        caused_effect = statuseffects.Chilled
        effect_length = 120

        def __init__(self,
                     level,
                     pos,
                     *,
                     centerpos=True,
                     norm_vector,
                     charged=True,
                     retain=lambda: True,
                     reason=None):
            super().__init__(level,
                             pos,
                             centerpos=centerpos,
                             norm_vector=norm_vector)
            self.charged = charged
            self.retain = retain
            self.reason = reason
            self.moving = True
            # Used to make the beam go deeper into the wall after collision
            self.destroy_on_next = False

        def update(self):
            if self.moving:
                self.xbuf += self.velx
                self.ybuf += self.vely
                self.rect.x, self.rect.y = self.xbuf, self.ybuf
            else:
                self.simple_deal_damage()
            if self.reason is None:
                if self.charged and random.uniform(
                        0, 1) <= self.charged_particle_chance:
                    p = particles.Particle.from_sprite(self, 3,
                                                       utils.Vector.uniform(1),
                                                       50, Color.lBlue)
                    self.level.particles.append(p)
            elif random.uniform(0, 1) <= self.particle_chance:
                norm = self.norm_vector
                spread = self.particle_spread
                source_sprite = self
                if self.reason == projectiles.DestroyReason.Collision:
                    vel = utils.Vector.random_spread(
                        norm, spread).opposite() * self.particle_speed
                elif self.reason == projectiles.DestroyReason.DamageDeal:
                    vel = utils.Vector.uniform(self.particle_speed)
                    source_sprite = self.last_attacked_sprite
                else:
                    vel = utils.Vector(0, 0)
                p = particles.Particle.from_sprite(source_sprite, 4, vel, 40,
                                                   Color.lBlue)
                self.level.particles.append(p)

        def draw(self, screen, pos_fix=(0, 0)):
            super().draw(screen, pos_fix)
            if not self.retain():
                self.destroy()

        def deal_damage(self, sprite):
            super().deal_damage(sprite)
            tick = self.level.parent.game.ticks
            eff = self.caused_effect(sprite, tick, self.effect_length)
            sprite.status_effects.add(eff)