PL
games/open-lemmings / src / Service / Art.php · commit 04e2e2a
<?phpnamespace App\Service;use JumPHP\Kernel\Engine;/** The game's pictures (assets/, drawn by tools/art.py), loaded once. */final class Art{    public const THEMES = ['forest', 'cave', 'forge', 'night', 'desert', 'ice', 'jungle', 'sky', 'autumn', 'reef', 'clockwork'];    /** @var array<string, array<string, mixed>> */    public array $themes = [];    /** @var array<string, int> icon name => index in sprites/icons*.png (12 px cells) */    public array $icons = [];    private bool $loaded = false;    public function __construct(public readonly Effects $effects)    {    }    /** 1: retro pictures (assets/), 3: the HD ones (assets/hd/, 3 times finer, drawn at the same size) */    public int $scale = 1;    private ?Engine $engine = null;    /** @var array<string, int> pictures whose scale is not $scale */    private array $scales = [];    public function hdOn(Engine $engine): bool    {        return (bool)$engine->options->get('hd_art', !\JumPHP\Kernel\Platform::isMobile());    }    /** Switches between retro and HD pictures: everything is loaded again. */    public function toggleHd(Engine $engine): void    {        $engine->options->set('hd_art', !$this->hdOn($engine));        $this->loaded = false;        $this->load($engine);    }    public function load(Engine $engine): void    {        $this->effects->apply($engine);        if ($this->loaded) {            return;        }        $this->loaded = true;        $this->engine = $engine;        $sdl = $engine->sdl;        $hd = $this->hdOn($engine);        $this->scale = $hd ? 3 : 1;        $dir = $hd ? 'assets/hd' : 'assets';        // HD pictures are scaled smoothly (the window is rarely exactly 3 times the view); retro ones stay crisp        \SDL_SetHint(\SDL_HINT_RENDER_SCALE_QUALITY, $hd ? '1' : '0');        $this->themes = (array)json_decode((string)file_get_contents($engine->config->path('assets/themes.json')), true);        $this->icons = array_flip((array)json_decode((string)file_get_contents($engine->config->path('assets/icons.json')), true));        foreach (self::THEMES as $t) {            $sdl->loadTexture("bg-$t-sky", "$dir/bg/$t-sky." . ($hd ? 'jpg' : 'png'));            foreach (['far', 'near'] as $layer) {                $sdl->loadTexture("bg-$t-$layer", "$dir/bg/$t-$layer.png");            }        }        $sprites = ['lumi', 'hatch', 'exit', 'icons', 'icons-dim', 'icons-gold', 'logo', 'hud', 'btn', 'btn-on', 'btn-off', 'btn-small',            'btn-small-on', 'btn-wide', 'btn-wide-on', 'vignette', 'cursor', 'cursor-on', 'flash',            'glow-amber', 'glow-cyan', 'glow-white', 'glow-red', 'glow-violet', 'glow-green'];        // icons and the cursor stay pixel art in both sets: sharp at any size (never smoothed)        $crisp = ['cursor', 'cursor-on'];        $this->scales = [];        foreach ($sprites as $name) {            if (in_array($name, $crisp, true)) {                continue;            }            $sdl->loadTexture($name, "$dir/sprites/$name.png");        }        // the hint is read when a texture is made: back to crisp for everything made later (texts, icons)        \SDL_SetHint(\SDL_HINT_RENDER_SCALE_QUALITY, '0');        foreach ($crisp as $name) {            $sdl->loadTexture($name, "assets/sprites/$name.png");            $this->scales[$name] = 1;        }        $this->scales['vignette'] = $hd ? 2 : 1;        if ($hd) {            \SDL_SetHint(\SDL_HINT_RENDER_SCALE_QUALITY, '1');            $sdl->loadTexture('shadow', 'assets/hd/sprites/shadow.png');            \SDL_SetHint(\SDL_HINT_RENDER_SCALE_QUALITY, '0');        }    }    /** The folder of the tiles of the current set. */    public function tile(string $name): string    {        return ($this->scale > 1 ? 'assets/hd' : 'assets') . "/tiles/$name.png";    }    /**     * Draws a picture in retro pixels whatever the set: $src (in retro pixels) and the size are those of the retro     * picture, the HD one is just finer.     */    public function draw(\JumPHP\Renderer\Renderer $r, string $name, ?array $src, float $x, float $y, ?float $w = null, ?float $h = null, bool $flip = false): void    {        $t = $this->engine->sdl->getTexture($name);        $k = $this->scales[$name] ?? $this->scale;        if ($src === null) {            $r->copy($t['texture'], null, $x, $y, $w ?? $t['width'] / $k, $h ?? $t['height'] / $k, $flip);            return;        }        $r->copy($t['texture'], [$src[0] * $k, $src[1] * $k, $src[2] * $k, $src[3] * $k], $x, $y, $w ?? $src[2], $h ?? $src[3], $flip);    }    /** @return array{0: float, 1: float} the size of a picture in retro pixels */    public function size(string $name): array    {        $t = $this->engine->sdl->getTexture($name);        $k = $this->scales[$name] ?? $this->scale;        return [$t['width'] / $k, $t['height'] / $k];    }    /** Draws an icon by name, 12 x $scale pixels wide (the sheets have 16 px cells). */    public function icon(\JumPHP\Renderer\Renderer $r, Engine $engine, string $name, float $x, float $y, string $variant = '', float $scale = 1): void    {        $i = $this->icons[$name] ?? 0;        $this->draw($r, 'icons' . $variant, [$i * 16, 0, 16, 16], $x, $y, 12 * $scale, 12 * $scale);    }}