Пример #1
0
    def wander(self):
        self.set_home_pos()

        self._generate_direction()

        self._pause_wander = False
        self._wander_timer = Timer(ms=self.MOVE_INTERVAL_MS,
                                   cb=self._on_wander_tick)
Пример #2
0
    def start_animation(self, name):
        self.frame_state = name
        self.anim_frame = 0

        if self.anim_timer:
            self.anim_timer.stop()

        self.anim_timer = Timer(ms=self.ANIM_MS,
                                cb=self._on_anim_tick)
Пример #3
0
class Wife(ChaseMixin, Human, WalkingSprite):
    MOVE_SPEED = 1
    CHASE_SPEED = 1
    WANDER_KEY_NAME = 'walking'
    DEFAULT_HEALTH = 1
    CLEAN_NAME = 'wife'
    INFECTED_NAME = 'infectedwife'
    NAME = INFECTED_NAME
    TRANSITION_MS = 250
    MAX_TRANSITIONS = 6

    def __init__(self):
        super(Wife, self).__init__()

        self.transitioned = Signal()
        self.showed_exclamation = False

    def on_collision(self, dx, dy, obj, self_rect, obj_rect):
        if obj == get_engine().player and not self.showed_exclamation:
            get_engine().ui.close_monologues()
            self.show_exclamation('heart_exclamation',
                                  self._transition_clean)
            self.showed_exclamation = True

    def _transition_clean(self):
        self.transition_count = 0
        self.transition_timer = Timer(ms=self.TRANSITION_MS,
                                      cb=lambda: self._on_transition())

    def _on_transition(self):
        self.transition_count += 1

        if self.name == self.INFECTED_NAME:
            self.name = self.CLEAN_NAME
        else:
            self.name = self.INFECTED_NAME

        if self.transition_count == self.MAX_TRANSITIONS:
            self.name = self.CLEAN_NAME
            self.transition_timer.stop()
            self.transitioned.emit()
Пример #4
0
    def __init__(self):
        super(Player, self).__init__('player')

        # Signals
        self.health_changed = Signal()
        self.lives_changed = Signal()

        # State
        self.human_kill_count = 0
        self.shoot_timer = Timer(ms=self.SHOOT_MS,
                                 cb=self.shoot,
                                 start_automatically=False)
        self.reset()
Пример #5
0
    def show_monologue(self, text, timeout_ms=None, on_done=None,
                       y_offset=MONOLOGUE_Y_OFFSET, actor=None, **kwargs):
        def _next_monologue():
            self.close(self.active_monologue)

            if len(text) > 1:
                self.show_monologue(text[1:], timeout_ms, on_done, **kwargs)
            elif on_done:
                on_done()

        if not isinstance(text, list):
            text = [text]

        self.close_monologues()

        if actor is None:
            actor = self.engine.player

        clip_rect = self.engine.camera.rect
        offset = (-clip_rect.x, -clip_rect.y)

        lines = text[0].splitlines()
        textbox = TextBox(self, lines, stay_open=True, **kwargs)
        self.widgets.append(textbox)
        textbox.rect = pygame.Rect(
            self.MONOLOGUE_X,
            actor.rect.move(offset).bottom + y_offset,
            self.size[0] - 2 * self.PADDING - self.MONOLOGUE_X,
            self.MONOLOGUE_HEIGHT * len(lines))
        self.active_monologue = textbox

        self.monologue_timer = Timer(ms=timeout_ms or self.MONOLOGUE_TIMEOUT_MS,
                                     cb=_next_monologue,
                                     one_shot=True)

        return textbox
Пример #6
0
 def blink(self, ms, on_done):
     self.blink_count = 0
     self.visible = 0
     self.blink_timer = Timer(ms=ms, cb=lambda: self._on_blinked(on_done))
Пример #7
0
 def start(self):
     self.anim_timer = Timer(ms=self.ANIM_MS,
                             cb=self._on_anim_tick,
                             start_automatically=False)
     self.started = True
Пример #8
0
class Sprite(BaseSprite):
    NAME = None
    MOVE_SPEED = 4
    RUN_SPEED = 8
    ANIM_MS = 150
    DEATH_BLINK_MS = 250
    MAX_BLINKS = 4
    LETHAL = False

    SPRITESHEET_ROWS = 1
    SPRITESHEET_COLS = 1
    SPRITESHEET_FRAMES = {
        Direction.SOUTH: {
            'default': [(0, 0)],
        },
        Direction.WEST: {
            'default': [(0, 0)],
        },
        Direction.EAST: {
            'default': [(0, 0)],
        },
        Direction.NORTH: {
            'default': [(0, 0)],
        },
    }

    NEED_TICKS = True

    def __init__(self, name=None):
        super(Sprite, self).__init__()

        # Signals
        self.moved = Signal()
        self.dead = Signal()

        # State
        self.quad_trees = set()
        self.layer = None
        self.name = name or self.NAME
        assert self.name

        self.started = False
        self.direction = Direction.SOUTH
        self.velocity = (0, 0)
        self.speed = self.MOVE_SPEED

        self.can_move = True
        self.use_quadtrees = True
        self.autoset_velocity = True

        self.frame_state = 'default'
        self.anim_frame = 0
        self.anim_timer = None

    def start(self):
        self.anim_timer = Timer(ms=self.ANIM_MS,
                                cb=self._on_anim_tick,
                                start_automatically=False)
        self.started = True

    def stop(self):
        self.stop_moving()
        self.started = False

    def damage(self, damage_value):
        if self.health > 0:
            self.health -= damage_value

            if self.health <= 0:
                self.die()
                return True

        return False

    def die(self, on_done=None):
        self.stop()
        self.blink(self.DEATH_BLINK_MS, lambda: self._stop_and_die(on_done))

    def _stop_and_die(self, on_done):
        self.dead.emit()
        self.remove()

        if on_done:
            on_done()

    def blink(self, ms, on_done):
        self.blink_count = 0
        self.visible = 0
        self.blink_timer = Timer(ms=ms, cb=lambda: self._on_blinked(on_done))

    def _on_blinked(self, on_done):
        self.blink_count += 1

        if self.visible:
            self.visible = 0
        else:
            self.visible = 1

        if self.blink_count == self.MAX_BLINKS:
            self.visible = 1
            self.blink_timer.stop()

            if on_done:
                on_done()

    def remove(self):
        self.stop()
        self.collidable = False
        self.visible = 0
        self.dirty = 1
        self.layer.remove(self)
        self.layer = None

    def update_image(self):
        self.image = self.generate_image()
        assert self.image

        self.rect.size = self.image.get_size()
        self.update_collision_rects()

    def generate_image(self):
        return load_spritesheet_frame(
            self.name,
            self._get_spritesheet_frames()[self.anim_frame],
            self.SPRITESHEET_ROWS,
            self.SPRITESHEET_COLS)

    def _get_spritesheet_frames(self):
        return self.SPRITESHEET_FRAMES[self.direction][self.frame_state]

    def move_by(self, dx, dy, check_collisions=True):
        super(Sprite, self).move_by(dx, dy, check_collisions=check_collisions)
        self.moved.emit(dx, dy)

    def set_direction(self, direction):
        if self.direction != direction:
            self.direction = direction
            self.update_velocity()
            self.update_image()

    def start_animation(self, name):
        self.frame_state = name
        self.anim_frame = 0

        if self.anim_timer:
            self.anim_timer.stop()

        self.anim_timer = Timer(ms=self.ANIM_MS,
                                cb=self._on_anim_tick)

    def stop_moving(self):
        self.velocity = (0, 0)
        self.frame_state = 'default'
        self.anim_frame = 0

        if self.anim_timer:
            self.anim_timer.stop()

        self.anim_timer = None

    def recompute_direction(self):
        if abs(self.velocity[0]) > abs(self.velocity[1]):
            if self.velocity[0] > 0:
                self.set_direction(Direction.EAST)
            elif self.velocity[0] < 0:
                self.set_direction(Direction.WEST)
        elif abs(self.velocity[1]) >= abs(self.velocity[0]):
            if self.velocity[1] > 0:
                self.set_direction(Direction.SOUTH)
            elif self.velocity[1] < 0:
                self.set_direction(Direction.NORTH)

    def update_collision_rects(self):
        pass

    def update_velocity(self):
        if not self.started or not self.autoset_velocity:
            return

        x, y = {
            Direction.WEST: (-1, None),
            Direction.EAST: (1, None),
            Direction.NORTH: (None, -1),
            Direction.SOUTH: (None, 1),
        }[self.direction]

        if x:
            x *= self.speed
        else:
            x = self.velocity[0]

        if y:
            y *= self.speed
        else:
            y = self.velocity[1]

        self.velocity = (x, y)

    def tick(self):
        if self.started and self.velocity != (0, 0):
            self.move_by(*self.velocity)

    def _on_anim_tick(self):
        frames = self._get_spritesheet_frames()

        self.anim_frame += 1

        if self.anim_frame == len(frames):
            self.anim_frame = 0

        self.dirty = 2
        self.update_image()
Пример #9
0
class Player(WalkingSprite):
    MAX_LIVES = 3
    MAX_HEALTH = 8

    SHOOT_MS = 500
    FALL_SPEED = 10
    HURT_BLINK_MS = 250

    SPRITESHEET_COLS = 4
    SPRITESHEET_FRAMES = {
        Direction.SOUTH: dict(
            WalkingSprite.SPRITESHEET_FRAMES[Direction.SOUTH], **{
                'shooting': [(3, 0)],
                'falling': [(1, 0)],
            }),
        Direction.WEST: dict(
            WalkingSprite.SPRITESHEET_FRAMES[Direction.WEST], **{
                'shooting': [(3, 1)],
                'falling': [(1, 1)],
            }),
        Direction.EAST: dict(
            WalkingSprite.SPRITESHEET_FRAMES[Direction.EAST], **{
                'shooting': [(3, 2)],
                'falling': [(1, 2)],
            }),
        Direction.NORTH: dict(
            WalkingSprite.SPRITESHEET_FRAMES[Direction.NORTH], **{
                'shooting': [(3, 3)],
                'falling': [(1, 3)],
            }),
    }

    def __init__(self):
        super(Player, self).__init__('player')

        # Signals
        self.health_changed = Signal()
        self.lives_changed = Signal()

        # State
        self.human_kill_count = 0
        self.shoot_timer = Timer(ms=self.SHOOT_MS,
                                 cb=self.shoot,
                                 start_automatically=False)
        self.reset()

    def update_collision_rects(self):
        self.collision_rects = [
            pygame.Rect(0, self.rect.height / 2,
                        self.rect.width, self.rect.height / 2),
        ]

    def reset(self):
        self.health = self.MAX_HEALTH
        self.lives = self.MAX_LIVES
        self.invulnerable = False
        self.running = False
        self.falling = False
        self.shooting = False
        self.can_run = True
        self.collidable = True
        self.allow_player_control = True

        self.health_changed.emit()
        self.lives_changed.emit()

        self.set_running(True)
        self.stop_moving()
        self._update_animation()

    def handle_event(self, event):
        if not self.allow_player_control:
            return

        if event.type == KEYDOWN:
            if event.key == K_RIGHT:
                self.move_direction(Direction.EAST)
            elif event.key == K_LEFT:
                self.move_direction(Direction.WEST)
            elif event.key == K_UP:
                self.move_direction(Direction.NORTH)
            elif event.key == K_DOWN:
                self.move_direction(Direction.SOUTH)
            elif event.key == K_c:
                self.set_shooting(True)
            elif event.key == K_F4:
                self.collidable = not self.collidable
        elif event.type == KEYUP:
            if event.key == K_RIGHT:
                self.stop_moving_direction(Direction.EAST)
            elif event.key == K_LEFT:
                self.stop_moving_direction(Direction.WEST)
            elif event.key == K_UP:
                self.stop_moving_direction(Direction.NORTH)
            elif event.key == K_DOWN:
                self.stop_moving_direction(Direction.SOUTH)
            elif event.key == K_c:
                self.set_shooting(False)

    def move_direction(self, direction):
        self.direction = direction

        self.update_velocity()
        self._update_animation()

        self.update_image()

    def stop_moving_direction(self, direction):
        if direction in (Direction.WEST, Direction.EAST):
            self.velocity = (0, self.velocity[1])
        elif direction in (Direction.NORTH, Direction.SOUTH):
            self.velocity = (self.velocity[0], 0)

        # The direction may not make any sense anymore, so recompute it.
        self.recompute_direction()
        self._update_animation()

    def set_shooting(self, shooting):
        if self.shooting == shooting:
            return

        self.shooting = shooting

        if shooting:
            self.shoot_timer.start()
            self.shoot()
        else:
            self.shoot_timer.stop()

        self._update_animation()

    def shoot(self):
        bullet = Bullet(self)
        self.layer.add(bullet)

        bullet.move_beside(self, self.direction)
        bullet.start()
        bullet.set_direction(self.direction)
        bullet.update_velocity()

    def should_adjust_position_with(self, obj, dx, dy):
        return not isinstance(obj, Bullet) or obj.owner_sprite != self

    def set_running(self, running):
        self.running = running

        if running:
            self.speed = self.RUN_SPEED
        else:
            self.speed = self.MOVE_SPEED

        if self.velocity != (0, 0):
            self.update_velocity()

        self._update_animation()

    def stop_running(self):
        self.running = False
        self._update_animation()

    def fall(self):
        self.collidable = False
        self.stop_running()
        self.set_direction(Direction.SOUTH)
        self.velocity = (0, self.FALL_SPEED)
        self.falling = True
        self._update_animation()

    def _update_animation(self):
        if self.velocity == (0, 0):
            if self.shooting and self.frame_state != 'shooting':
                self.frame_state = 'shooting'
            elif not self.shooting and self.frame_state != 'default':
                self.frame_state = 'default'
            else:
                return

            self.anim_timer.stop()
        else:
            new_state = None

            if self.running and self.frame_state != 'running':
                new_state = 'running'
            elif self.falling:
                new_state = 'falling'
            elif self.frame_state != 'walking':
                new_state = 'walking'
            else:
                return

            if new_state:
                self.start_animation(new_state)

        self.anim_frame = 0
        self.update_image()

    def on_collision(self, dx, dy, obj, self_rect, obj_rect):
        if obj.LETHAL and self.health > 0:
            if not self.invulnerable:
                self.health -= 1
                self.health_changed.emit()

                if self.health == 0:
                    self.on_dead()
                else:
                    self.invulnerable = True
                    self.blink(self.HURT_BLINK_MS, self._on_hurt_blink_done)

            return True

    def _on_hurt_blink_done(self):
        self.invulnerable = False

    def on_dead(self):
        self.lives -= 1
        self.lives_changed.emit()
        self.stop_moving()

        engine = get_engine()

        if self.lives == 0:
            self.die(engine.game_over)
        else:
            self.health = self.MAX_HEALTH
            self.health_changed.emit()
            engine.dead()
Пример #10
0
 def _transition_clean(self):
     self.transition_count = 0
     self.transition_timer = Timer(ms=self.TRANSITION_MS,
                                   cb=lambda: self._on_transition())
Пример #11
0
class WanderMixin(object):
    MOVE_INTERVAL_MS = 1000
    PAUSE_CHANCE = 0.1
    CHANGE_DIR_CHANCE = 0.3
    WANDER_KEY_NAME = 'wandering'
    WANDER_DISTANCE = 64 * 8

    def __init__(self, *args, **kwargs):
        super(WanderMixin, self).__init__(*args, **kwargs)
        self.auto_wander = True
        self._wander_timer = None

    def start(self):
        super(WanderMixin, self).start()

        if self.auto_wander:
            self.wander()

    def stop(self):
        super(WanderMixin, self).stop()
        self.stop_wandering()

    def wander(self):
        self.set_home_pos()

        self._generate_direction()

        self._pause_wander = False
        self._wander_timer = Timer(ms=self.MOVE_INTERVAL_MS,
                                   cb=self._on_wander_tick)

    def stop_wandering(self):
        if self._wander_timer:
            self._wander_timer.stop()
            self._wander_timer = None

    def set_home_pos(self):
        self.home_pos = self.rect.center

    def _generate_direction(self):
        self.velocity = (0, 0)

        # Do this twice, so we can maybe get different X, Y velocities.
        self.set_direction(random.randint(0, 3))
        self.update_velocity()

        self.set_direction(random.randint(0, 3))
        self.update_velocity()

    def _on_wander_tick(self):
        if self._pause_wander:
            self._pause_wander = False
            return

        dist_x = self.home_pos[0] - self.rect.centerx
        dist_y = self.home_pos[1] - self.rect.centery

        turning_back = False

        if abs(dist_x) >= self.WANDER_DISTANCE / 2:
            self.velocity = (-self.velocity[0], self.velocity[1])
            turning_back = True

        if abs(dist_y) >= self.WANDER_DISTANCE / 2:
            self.velocity = (self.velocity[0], -self.velocity[1])
            turning_back = True

        if turning_back:
            self.recompute_direction()
        else:
            if random.random() <= self.CHANGE_DIR_CHANCE:
                self._generate_direction()

            if random.random() <= self.PAUSE_CHANCE:
                self._pause_wander = True
                self.stop_moving()
            elif self.frame_state != self.WANDER_KEY_NAME:
                self.start_animation(self.WANDER_KEY_NAME)
Пример #12
0
class GameUI(object):
    PADDING = 40
    TEXTBOX_HEIGHT = 150
    STATUS_AREA_HEIGHT = 40

    MONOLOGUE_X = 250
    MONOLOGUE_Y_OFFSET = 50
    MONOLOGUE_HEIGHT = 50
    MONOLOGUE_TIMEOUT_MS = 3000

    def __init__(self, engine):
        pygame.font.init()

        self.ready = Signal()

        self.engine = engine
        self.size = engine.screen.get_size()
        self.surface = pygame.Surface(self.size).convert_alpha()
        self.widgets = []
        self.timers = []

        self.active_monologue = None
        self.monologue_timer = None
        self.paused_textbox = None
        self.confirm_quit_box = None

        self.default_font_file = get_font_filename()
        self.font = pygame.font.Font(self.default_font_file, 20)
        self.small_font = pygame.font.Font(self.default_font_file, 16)

        self.status_area = StatusArea(self)
        self.widgets.append(self.status_area)

    def show_textbox(self, text, **kwargs):
        textbox = TextBox(self, text, **kwargs)
        textbox.rect = pygame.Rect(
            self.PADDING, (self.size[1] - self.TEXTBOX_HEIGHT) / 2,
            self.size[0] - 2 * self.PADDING, self.TEXTBOX_HEIGHT)
        self.widgets.append(textbox)

        return textbox

    def show_monologue(self, text, timeout_ms=None, on_done=None,
                       y_offset=MONOLOGUE_Y_OFFSET, actor=None, **kwargs):
        def _next_monologue():
            self.close(self.active_monologue)

            if len(text) > 1:
                self.show_monologue(text[1:], timeout_ms, on_done, **kwargs)
            elif on_done:
                on_done()

        if not isinstance(text, list):
            text = [text]

        self.close_monologues()

        if actor is None:
            actor = self.engine.player

        clip_rect = self.engine.camera.rect
        offset = (-clip_rect.x, -clip_rect.y)

        lines = text[0].splitlines()
        textbox = TextBox(self, lines, stay_open=True, **kwargs)
        self.widgets.append(textbox)
        textbox.rect = pygame.Rect(
            self.MONOLOGUE_X,
            actor.rect.move(offset).bottom + y_offset,
            self.size[0] - 2 * self.PADDING - self.MONOLOGUE_X,
            self.MONOLOGUE_HEIGHT * len(lines))
        self.active_monologue = textbox

        self.monologue_timer = Timer(ms=timeout_ms or self.MONOLOGUE_TIMEOUT_MS,
                                     cb=_next_monologue,
                                     one_shot=True)

        return textbox

    def close_monologues(self):
        if self.active_monologue:
            self.monologue_timer.stop()
            self.close(self.active_monologue)

    def show_dialogue(self, actors, lines, timeout_ms=None, on_done=None,
                      **kwargs):
        def _next_dialogue():
            self.close(self.active_monologue)

            if len(lines) > 1:
                self.show_dialogue(actors, lines[1:], timeout_ms,
                                   on_done, **kwargs)
            elif on_done:
                on_done()

        person, text = lines[0]

        if person == 'player':
            self.show_monologue(text, timeout_ms, _next_dialogue)
        else:
            self.show_monologue(text, timeout_ms, _next_dialogue,
                                actor=actors[person],
                                y_offset=-3.5 * self.MONOLOGUE_Y_OFFSET,
                                bg_color=(232, 174, 174),
                                text_color=(0, 0, 0),
                                border_color=(0, 0, 0))

    def close(self, widget):
        try:
            self.widgets.remove(widget)
            widget.closed.emit()
        except ValueError:
            # It was already closed
            pass

    def handle_event(self, event):
        handled = False

        if event.type == KEYDOWN:
            if self.confirm_quit_box:
                handled = True

                if event.key in (K_ESCAPE, K_n):
                    self.confirm_quit_box.close()
                    self.confirm_quit_box = None
                    self.engine.paused = False
                elif event.key == K_y:
                    self.engine.quit()
            elif event.key in (K_ESCAPE, K_RIGHT, K_SPACE, K_RETURN):
                for widget in self.widgets:
                    if (isinstance(widget, TextBox) and
                        widget != self.paused_textbox and
                        not widget.stay_open):
                        widget.close()
                        handled = True

        return handled

    def pause(self):
        assert not self.paused_textbox
        self.paused_textbox = self.show_textbox('Paused')

    def unpause(self):
        if self.paused_textbox:
            self.paused_textbox.close()
            self.paused_textbox = None

    def confirm_quit(self):
        if self.confirm_quit_box:
            self.confirm_quit_box.close()
            return

        self.engine.paused = True
        self.confirm_quit_box = self.show_textbox([
            "Giving up already?",
            "'Y' to give up.",
            "'N' to keep playing."
        ])

    def show_opening_scene(self, on_done):
        lines = [
            (3000, "I'm Dr. Nick Rogers, Ph.D."),
            (6000,
             "I moved to this little town in the mountains with my "
             "wife, Laura, just 6 short months ago.\n"
             "I wanted a safe and quiet place to conduct my research."),
            (3000, "I planned to cure the common cold."),
            (6000,
             "The plan was to create an airborne virus, safe for "
             "humans, that would hunt down and destroy\n"
             "the viruses that cause the cold symptoms."),
            (6000,
             "My experiments on rats and chimps were promising.\n"
             "I needed to begin human trials, but that would take "
             "years."),
            (6000,
             "This morning... The details are still a bit fuzzy. "
             "I woke up in my lab up\n"
             "against the wall with a terrible headache."),
            (3000,
             "There was an explosion in the lab, and the virus was exposed."),
            (4000,
             "Stumbling out of the lab, I saw the townfolk bleeding, "
             "screaming, faces distorted."),
            (7000,
             "I had seen this before. In one of the batches of the "
             "virus, the chimps began to hallucinate. \n"
             "Some became aggressive and twisted their faces up in "
             "agony."),
            (6000,
             "I appeared to be fine, and I knew how to cure this. "
             "Unfortunately, the ingredients were destroyed."),
            (4000,
             "I told Laura to go to the mountains where it was safe, and "
             "ventured out to find the ingredients for the cure."),
        ]

        self._show_opening_line(lines, on_done)

    def _show_opening_line(self, lines, on_done, prev_textbox=None):
        if prev_textbox:
            self.close(prev_textbox)

        if not lines:
            on_done()
        else:
            timeout, text = lines[0]
            textbox = self.show_textbox(text.split('\n'))
            textbox.closed.connect(
                lambda: self._show_opening_line(lines[1:],
                                                on_done, textbox))

            Timer(ms=timeout, cb=textbox.close, one_shot=True)

    def draw(self, surface):
        for element in self.widgets:
            element.draw(surface)