PL
games/open-lemmings / src / Scene / LevelScene.php · commit 04e2e2a
<?phpnamespace App\Scene;use App\Service\Art;use App\Service\Audio;use App\Service\Fonts;use App\Service\Progress;use App\Sim\Levels;use JumPHP\Renderer\Renderer;use JumPHP\Scene\AbstractScene;/** The map of the levels: a grid of tiles, locked until the one before is won. */final class LevelScene extends AbstractScene{    private const COLS = 6;    private const TW = 72;    private const TH = 42;    private int $selected = 0;    private float $time = 0;    public function __construct(private Art $art, private Fonts $fonts, private Audio $audio, private Progress $progress)    {    }    public function enter(array $params = []): void    {        $this->art->load($this->engine);        $this->audio->load($this->sound());        $this->selected = max(0, min(Levels::count() - 1, (int)($params['level'] ?? $this->progress->unlocked($this->engine) - 1)));        $this->time = 0;        if ($this->sound()->getCurrentMusic() !== 'title') {            $this->sound()->playMusic('title');        }    }    /** @return array{0:float,1:float} */    private function tileXY(int $i): array    {        return [240 - self::COLS * (self::TW + 6) / 2 + 3 + ($i % self::COLS) * (self::TW + 6), 44 + intdiv($i, self::COLS) * (self::TH + 4)];    }    public function update(float $dt): void    {        $this->time += $dt;        $a = $this->actions();        $n = Levels::count();        $m = $this->engine->input->getMouse();        if ($m->isPressed(1)) {            if ($m->y > 236 && $m->x < 110) {                $this->scenes()->switchTo(TitleScene::class);                return;            }            for ($i = 0; $i < $n; $i++) {                [$x, $y] = $this->tileXY($i);                if ($m->x >= $x && $m->x < $x + self::TW && $m->y >= $y && $m->y < $y + self::TH) {                    $this->selected = $i;                    $this->start();                    return;                }            }        }        $move = ($a->isPressed('right') ? 1 : 0) - ($a->isPressed('left') ? 1 : 0) + self::COLS * (($a->isPressed('down') ? 1 : 0) - ($a->isPressed('up') ? 1 : 0));        if ($move !== 0 && $this->selected + $move >= 0 && $this->selected + $move < $n) {            $this->selected += $move;            $this->sound()->playSfx('menu_move');        }        if ($a->isPressed('confirm')) {            $this->start();        } elseif ($a->isPressed('back')) {            $this->scenes()->switchTo(TitleScene::class);        }    }    private function start(): void    {        if ($this->selected >= $this->progress->unlocked($this->engine)) {            $this->sound()->playSfx('deny');            return;        }        $this->sound()->playSfx('menu_confirm');        $this->scenes()->switchTo(PlayScene::class, ['level' => $this->selected]);    }    public function render(Renderer $r): void    {        $sdl = $this->sdl();        $e = $this->engine;        $this->art->draw($r, 'bg-night-sky', null, 0, 0);        $r->fillRect(0, 232, 480, 38, [8, 10, 32]);        foreach (['far' => 6, 'near' => 14] as $layer => $speed) {            $off = -fmod($this->time * $speed, 512);            $this->art->draw($r, "bg-night-$layer", null, round($off), 38);            $this->art->draw($r, "bg-night-$layer", null, round($off) + 512, 38);        }        $r->fillRect(0, 0, 480, 270, [8, 4, 20, 100]);        $this->fonts->text($e, $r, 'CHOOSE A LEVEL', 240, 12, 1.8, [255, 210, 60], 'center');        $open = $this->progress->unlocked($e);        foreach (Levels::all() as $i => $def) {            [$x, $y] = $this->tileXY($i);            $on = $i === $this->selected;            $locked = $i >= $open;            $y += $on ? sin($this->time * 5) * 1.2 : 0;            $r->fillRect($x, $y, self::TW, self::TH, $locked ? [26, 20, 48, 235] : ($on ? [120, 70, 40, 235] : [48, 36, 92, 235]));            $this->art->draw($r, 'bg-' . $def['theme'] . '-sky', [200, 60, self::TW - 4, 18], $x + 2, $y + 2);            $this->art->draw($r, 'bg-' . $def['theme'] . '-near', [100, 184, self::TW - 4, 18], $x + 2, $y + 2);            if ($locked) {                $r->fillRect($x + 2, $y + 2, self::TW - 4, 18, [10, 6, 24, 190]);                $this->art->icon($r, $e, 'lock', $x + self::TW / 2 - 6, $y + 5, '-dim');            } else {                $this->fonts->print($e, $r, (string)($i + 1), $x + self::TW / 2, $y + 2, 1.3, [255, 255, 255], 'center', [20, 12, 40]);            }            $r->drawRect($x, $y, self::TW, self::TH, $on ? [255, 220, 120] : [92, 74, 148]);            $this->fonts->text($e, $r, $def['name'], $x + self::TW / 2, $y + 20, 0.62, $locked ? [120, 110, 150] : [255, 255, 255], 'center');            $best = $this->progress->best($e, $i);            if ($best !== null) {                $stars = Progress::stars($best, (int)($def['need'] * 100 / $def['total']));                for ($s = 0; $s < 3; $s++) {                    $this->art->icon($r, $e, 'star', $x + self::TW / 2 - 18 + $s * 13, $y + 30, $s < $stars ? '-gold' : '-dim');                }            }        }        $this->art->draw($r, 'btn-wide', [0, 0, 100, 22], 6, 242);        $this->fonts->text($e, $r, 'BACK', 56, 246, 1.1, [255, 255, 255], 'center');    }}