view skaapsteker/levelscene.py @ 313:7db1b7c5c961

Add health bar
author Neil Muller <drnlmuller@gmail.com>
date Fri, 08 Apr 2011 23:02:50 +0200
parents 78220c989e6a
children f29999d1bba6
line wrap: on
line source

"""Scene wrapping a level object."""

from pygame.locals import (KEYDOWN, KEYUP, K_DOWN, K_ESCAPE, K_LEFT, K_RIGHT,
                           K_SEMICOLON, K_UP, K_p, K_q, K_x, K_z, K_RETURN)

import pygame
import time

from . import options
from . import engine
from . import level
from . import physics
from . import constants
from .sprites import player
from .widgets.text import Text
from .widgets.bubble import DialogueWidget


class LevelScene(engine.Scene):

    def __init__(self, game_state, soundsystem, doorway_def=None):
        super(LevelScene, self).__init__(game_state, soundsystem)

        if doorway_def is not None:
            fox = self.game_state.world.fox
            fox.level, fox.doorway = doorway_def.split('.')
        self._level = level.Level(self.game_state.world.fox.level, soundsystem)
        self._player_dead = False
        self._dialogue = None

        self.setup_player()

        self._level_surface = self._level.get_surface()
        self._clip_rect = None
        self._world = physics.World()
        self._paused = False

        for sprite in self._level.sprites:
            self._world.add(sprite)
        npcs_and_items = game_state.create_sprites(self._level.name)
        self._npcs = dict((s.name, s) for s in npcs_and_items if hasattr(s, 'dsm'))
        for sprite in npcs_and_items:
            self._world.add(sprite)
        self._world.add(self._player)

        self._build_action_map()
        self._key_sequence = []

    def setup_player(self):
        doorway = self._level.doorways[self.game_state.world.fox.doorway]
        self._player = player.Player(self.game_state.world, self._soundsystem)
        self._player.set_facing(doorway.facing)
        self._player.set_image()
        self._player.set_pos(doorway._starting_tile_pos)

        # Double tap stuff
        self._last_keydown_time = None
        self._last_keyup_time = None

    def _build_action_map(self):
        action = lambda s: getattr(self._player, 'action_%s' % s)

        self._quit_keys = set([K_q, K_ESCAPE])
        self._restart_keys = set([K_x, K_z, K_RETURN])

        self._fast_key_map = {
            K_LEFT: action('left'),
            K_RIGHT: action('right'),
            K_UP: action('up'),
        }
        self._fast_keys_down = set()

        self._slow_key_map = {
            K_DOWN: action('down'),
            K_ESCAPE: self._quit,
            K_p: self._toggle_pause,
        }
        if options['dvorak']:
            self._slow_key_map[K_SEMICOLON] = action('fire1')
            self._slow_key_map[K_q] = action('fire2')
        else:
            self._slow_key_map[K_x] = action('fire1')
            self._slow_key_map[K_z] = action('fire2')
            self._slow_key_map[K_q] = self._quit

        self._key_tap_map = {
                (K_LEFT, K_LEFT) : action('double_left'),
                (K_RIGHT, K_RIGHT) : action('double_right'),
                (K_UP, K_UP) : action('double_up'),
                }

    def _quit(self, pause=True):
        import menuscene # avoid circular import
        if pause:
            engine.ChangeScene.post(menuscene.MenuScene(self.game_state, self._soundsystem, self))
        else:
            # FIXME: When starting the game, we shoudl ensure we have sane
            # states
            if self._player_dead:
                self._player.restore()
            engine.ChangeScene.post(menuscene.MenuScene(self.game_state, self._soundsystem, None))

    def _restart(self):
        if self._player_dead:
            self._player.restore()
        engine.ChangeScene.post(LevelScene(self.game_state, self._soundsystem))

    def _toggle_pause(self):
        if self._paused:
            self._world.thaw()
            self._paused = False
        else:
            self._world.freeze()
            self._paused = True

    def _open_dialogue(self, npc):
        if isinstance(npc, basestring):
            npc = self._npcs[npc]
        if npc.dsm.has_text():
            if self._dialogue is not None:
                self._dialogue.close()
            self._world.freeze()
            self._dialogue = DialogueWidget(npc)
        else:
            self._close_dialogue()

    def _close_dialogue(self):
        if self._dialogue is not None:
            self._world.thaw()
            self._dialogue.close()
            self._dialogue = None

    def leave(self):
        """Freeze the scene, for serialization"""
        self._world.freeze()
        self._level.leave()

    def enter(self):
        """Unfreeze"""
        self._world.thaw()
        self._level.enter()

    def draw(self, screen_surface, engine):
        if self._clip_rect is None:
            self._clip_rect = pygame.Rect((0, 0), screen_surface.get_size())

        if not self._paused and not self._dialogue:
            for key in self._fast_keys_down:
                self._fast_key_map[key]()
            self._world.update()

        self._update_clip_rect()

        self._level_surface.set_clip(self._clip_rect)
        self._level.draw(self._level_surface)
        self._world.draw(self._level_surface)
        if self._dialogue:
            self._dialogue.draw(self._level_surface)

        self._draw_fox_status()

        fps_text_pos = self._clip_rect.left + 10, self._clip_rect.top + 10
        fps_text = Text('FPS: %.1f' % engine.get_fps(), fps_text_pos, shadow='white')
        fps_text.draw(self._level_surface)

        if self._paused:
            paused_text_pos = self._clip_rect.centerx - 10, self._clip_rect.centery - 10
            paused_text = Text('Paused', paused_text_pos)
            paused_text.draw(self._level_surface)

        if self._player_dead:
            death_text_pos = self._clip_rect.centerx - 100, self._clip_rect.centery - 100
            death_text = Text("You've died.\nPress Escape to exit or Return to restart the level", death_text_pos)
            death_text.draw(self._level_surface)

        screen_surface.blit(self._level_surface, (0, 0), self._clip_rect)

    def _draw_fox_status(self):
        """Draw the fox inventory. The tails and the item are drawn on the
           left side of the screen, a health bar and collected tofu and
           scroll counts are shown on the right"""
        # Draw the healt bar
        health_bottom = self._clip_rect.right - 30, self._clip_rect.top + 200
        bar = pygame.Rect(0, 0, constants.FoxHud.HEALTH_WIDTH, constants.FoxHud.HEALTH_HEIGHT)
        bar.bottomleft = health_bottom
        pygame.draw.rect(self._level_surface, constants.FoxHud.HEALTH_BACKGROUND, bar)
        bar.height = int(constants.FoxHud.HEALTH_HEIGHT * float(self.game_state.world.fox.cur_health)/self.game_state.world.fox.max_health)
        bar.bottomleft = health_bottom
        pygame.draw.rect(self._level_surface, constants.FoxHud.HEALTH_FOREGROUND, bar)



    def _update_clip_rect(self):
        cr = self._clip_rect
        lr = self._level_surface.get_rect()
        cr.center = self._player.collide_rect.move(0, -level.TILE_SIZE[0]).midbottom
        cr.clamp_ip(lr)


    def _detect_key_sequence(self, ev):
        if ev.key not in self._fast_key_map:
            self._key_sequence = []
            return False

        if ev.type is KEYUP:
            if (not self._key_sequence
                or ev.key != self._key_sequence[-1]
                or (time.time() - self._last_keydown_time) > constants.DOUBLE_TAP_TIME):
                self._key_sequence = []
                return False
            self._last_keyup_time = time.time()
            return False

        if ev.type is KEYDOWN:
            if self._last_keyup_time is None or (time.time() - self._last_keyup_time) > constants.DOUBLE_TAP_TIME:
                self._key_sequence = []
            self._key_sequence.append(ev.key)
            self._last_keydown_time = time.time()
            for seq in self._key_tap_map:
                if seq == tuple(self._key_sequence[-len(seq):]):
                    self._key_sequence = self._key_sequence[-len(seq):]
                    return True
            return False


    def dispatch(self, ev):
        if ev.type is KEYDOWN:

            if self._player_dead:
                if ev.key in self._restart_keys:
                    self._restart()
                elif ev.key in self._quit_keys:
                    self._quit(False)
                return

            if self._dialogue:
                self._dialogue.dispatch(ev)
                return

            if self._detect_key_sequence(ev):
                print "Detected double-tap:", self._key_sequence
                action = self._key_tap_map.get(tuple(self._key_sequence))
                if action is not None:
                    action()
            if ev.key in self._fast_key_map:
                self._fast_keys_down.add(ev.key)
            action = self._slow_key_map.get(ev.key)
            if action is not None:
                action()

        elif ev.type is KEYUP:
            self._detect_key_sequence(ev)

            if ev.key in self._fast_key_map:
                self._fast_keys_down.discard(ev.key)

        elif engine.PlayerDied.matches(ev):
            self._player_dead = True
        elif engine.OpenDialog.matches(ev):
            self._open_dialogue(ev.npc)
        elif engine.CloseDialog.matches(ev):
            self._close_dialogue()
        elif engine.ItemRepopulationEvent.matches(ev):
            self._world.add(ev.item)