ajhahn.de
← the-way-out
Python 114 lines
"""Inter-level loading screen.

Sits between the level menu and gameplay so the player gets one focused
beat — title, tagline, character avatar, control hints — before the
room becomes interactive. Replaces the older in-level intro card that
used to draw over a live world.

Press-or-timeout: auto-advances after ``LOADING_SCREEN_DURATION`` or on
Enter / Space / left-click / Esc. ``main.py`` gates this screen inside
``_start_level``; R-retry and pause-restart bypass it deliberately so
death-heavy levels stay snappy.
"""

import pygame

import theme
from settings import LOADING_SCREEN_DURATION
from units import CHARACTER_INFO


def _asset_folder_for(character_id):
    """Map the menu's character id (e.g. ``"c_wiz"``) to its sprite-
    sheet folder name. Falls back to the Wizard's folder so a missing
    id renders something instead of crashing."""
    for cid, cls, _name, _tagline in CHARACTER_INFO:
        if cid == character_id:
            return cls.asset_folder
    return "wizard"


class LoadingScreen:
    """One-shot scene shown before a level loads."""

    AVATAR_SCALE = 3

    def __init__(self, width, height, level_entry, character_id):
        self.width = width
        self.height = height
        self.level_entry = level_entry
        self.timer = LOADING_SCREEN_DURATION
        self.skipped = False

        self._dust = theme.PixelDust(width, height, seed=41)
        self._title_font = theme.font(theme.FONT_TITLE)
        self._tagline_font = theme.font(theme.FONT_HEADING)
        self._hint_font = theme.font(theme.FONT_CAPTION)

        folder = _asset_folder_for(character_id)
        self._avatar_frames = theme._load_idle_frames(
            folder, self.AVATAR_SCALE)
        self._ticks_at_start = pygame.time.get_ticks()

    @property
    def done(self):
        return self.skipped or self.timer <= 0

    def update(self, dt):
        if self.timer > 0:
            self.timer = max(0.0, self.timer - dt)

    def handle_input(self, event):
        """Return True when the player skipped the screen. Esc is left
        alone so main.py's global Esc handler can route it (cancel back
        to the level menu, rather than advancing into the level)."""
        if event.type == pygame.KEYDOWN and event.key in (
                pygame.K_RETURN, pygame.K_SPACE):
            self.skipped = True
            return True
        if (event.type == pygame.MOUSEBUTTONDOWN
                and event.button == 1):
            self.skipped = True
            return True
        return False

    def draw(self, screen):
        screen.fill(theme.BG)
        self._dust.draw(screen)

        cx = self.width // 2
        title_y = self.height // 3 - 30
        title = (self.level_entry.title or "LEVEL").upper()
        t_surf = self._title_font.render(title, True, theme.TITLE_C)
        screen.blit(t_surf, t_surf.get_rect(center=(cx, title_y)))

        ly = t_surf.get_rect(center=(cx, title_y)).bottom + 16
        pygame.draw.line(screen, theme.LINE_C,
                         (cx - 170, ly), (cx + 170, ly), 2)

        tagline = self.level_entry.tagline or ""
        if tagline:
            s_surf = self._tagline_font.render(tagline, True, theme.MUTED)
            screen.blit(s_surf, s_surf.get_rect(center=(cx, ly + 50)))

        # Avatar: pick one idle frame; cycle slowly so it reads as alive
        # without distracting from the title.
        if self._avatar_frames:
            ticks = pygame.time.get_ticks() - self._ticks_at_start
            idx = (ticks // 200) % len(self._avatar_frames)
            frame = self._avatar_frames[idx]
            screen.blit(frame, frame.get_rect(
                center=(cx, self.height // 2 + 90)))

        d = theme.HINT_DOT
        hint = (f"WASD/Arrows move  {d}  Space shoot  {d}  "
                f"Shift ability  {d}  E use")
        h_surf = self._hint_font.render(hint, True, theme.MUTED)
        screen.blit(h_surf, h_surf.get_rect(
            center=(cx, self.height - 70)))

        skip_hint = self._hint_font.render(
            "Enter / Space / click to skip", True, theme.MUTED)
        screen.blit(skip_hint, skip_hint.get_rect(
            center=(cx, self.height - 36)))