view skaapsteker/engine.py @ 632:0675f390653c

Initial port to Python 3 and Pygame 2.
author Simon Cross <hodgestar@gmail.com>
date Fri, 20 Jan 2023 20:01:06 +0100
parents fb9258d66137
children
line wrap: on
line source

"""Top-level engine for switching scenes."""

import os

import pygame.display
import pygame.time
import pygame.event
from pygame.locals import QUIT, USEREVENT

from . import options


class Engine(object):

    def __init__(self, soundsystem):
        # avoid circular imports
        from .gamestate import GameState
        self._framerate = 60
        self._current_scene = None
        self._fpss = [self._framerate] * 100
        self._cur_frame = 0
        self.game_state = GameState(os.path.join(options['save_location'], 'savegame.json'))
        if self.game_state.can_resume():
            self.game_state.load_game()
        self.soundsystem = soundsystem

    def change_scene(self, next_scene):
        self.game_state.save_game()
        self.soundsystem.stop_music()
        if self._current_scene is not None:
            self._current_scene.leave()
        self._current_scene = next_scene
        self._current_scene.enter()

    def run(self):
        """Run the game loop dispatching events as necessary."""
        assert self._current_scene is not None
        clock = pygame.time.Clock()
        surface = pygame.display.get_surface()
        while True:
            events = pygame.event.get()
            for ev in events:
                if ev.type == QUIT:
                    return
                if ChangeScene.matches(ev):
                    next_scene = ev.next_scene
                    if not isinstance(next_scene, Scene):
                        next_scene = next_scene[0](self.game_state, self.soundsystem, *(next_scene[1:]))
                    self.change_scene(next_scene)
                    break
                self._current_scene.dispatch(ev)
            self._current_scene.draw(surface, self)
            pygame.display.flip()
            self._fpss[self._cur_frame] = 1000.0 / clock.tick(self._framerate)
            self._cur_frame = self._cur_frame + 1 if self._cur_frame < 99 else 0

    def get_fps(self):
        return sum(self._fpss) / 100


class Scene(object):

    def __init__(self, game_state, soundsystem):
        self.widgets = []
        self.game_state = game_state
        self._soundsystem = soundsystem

    def post(self, ev):
        """Post an event to pygame's event loop."""
        pygame.event.post(ev)

    def enter(self):
        """Enter the scene."""
        pass

    def leave(self):
        """Exit the scene."""
        pass

    def dispatch(self, ev):
        """Dispatch an event."""
        for widget in self.widgets:
            widget.dispatch(ev)

    def draw(self, surface, engine):
        """Update the scene surface."""
        for widget in self.widgets:
            widget.draw(surface)


class UserEvent(object):

    utype = "UNKNOWN"

    @classmethod
    def post(cls, **kws):
        ev = pygame.event.Event(USEREVENT, utype=cls.utype, **kws)
        pygame.event.post(ev)

    @classmethod
    def matches(cls, ev):
        return ev.type == USEREVENT and ev.utype == cls.utype


class ChangeScene(UserEvent):

    utype = "CHANGE_SCENE"

    @classmethod
    def post(cls, next_scene):
        super(ChangeScene, cls).post(next_scene=next_scene)


class PlayerDied(UserEvent):

    utype = "PLAYER_DIED"

    @classmethod
    def post(cls):
        super(PlayerDied, cls).post()

class OpenDialog(UserEvent):

    utype = "OPEN_DIALOG"

    @classmethod
    def post(cls, npc):
        # request to open dialog box for given NPC sprite
        # will do nothing if the NPC's current state has
        # no text
        super(OpenDialog, cls).post(npc=npc)

class OpenNotification(UserEvent):

    utype = "OPEN_NOTIFICATION"

    @classmethod
    def post(cls, text):
        # request to open dialog box for given NPC sprite
        # will do nothing if the NPC's current state has
        # no text
        super(OpenNotification, cls).post(text=text)

class CloseDialog(UserEvent):

    utype = "CLOSE_DIALOG"

    @classmethod
    def post(cls, npc):
        # close dialog box for given NPC sprite
        # will do nothing if the sprite has no dialog open
        super(CloseDialog, cls).post(npc=npc)

class NpcEvent(UserEvent): # TODO: Needed?

    utype = "NPC_EVENT"

    @classmethod
    def post(cls, npc, ev):
        """npc is an NPC sprite.
           ev is a DsmEvent for that sprite.
           """
        super(NpcEvent, cls).post(npc=npc, ev=ev)

class GlobalNpcEvent(UserEvent): # TODO: Needed?

    utype = "GLOBAL_NPC_EVENT"

    @classmethod
    def post(cls, ev):
        """Send a DsmEvent event to all NPCs.
           """
        super(GlobalNpcEvent, cls).post(ev=ev)


class AddSpriteEvent(UserEvent):

    utype = "ADD_SPRITE_EVENT"

    @classmethod
    def post(cls, item):
        """Put a Sprite into the world.
           """
        super(AddSpriteEvent, cls).post(item=item)