L'Armure Sacrée d'Antiriad

par davidnurdin · Remakes
pas encore noté
▶ Jouer Sources Éditer GitHub
games/antiriad-php / src / Antiriad / Actor.php · commit decc30a
<?phpnamespace App\Antiriad;/** * An object of a room: enemy, hazard, item, the empty suit, a shot, an explosion. It runs the original's * two little scripts, one tick at a time: *  - its movement program (#6BB0): steps [directions, ticks, dx, dy], directions bit 0 up, 1 down, 2 left, *    3 right, #0F = towards Tal (#6CDE); a step lasts ticks + 1; the end loops, holds the last step or stops; *  - its animation (#6B45): frames (sprite ids, bit 7 = mirrored), the end loops, holds, chains or kills. */final class Actor{    public const ENEMY = 'enemy';      // type byte 0 bit 6: shot at, hits = byte 1 bits 7-5    public const HAZARD = 'hazard';    // handler 5: hurts, cannot be shot    public const ITEM = 'item';        // handlers 0-4: recharge cell, the 4 modules    public const SUIT = 'suit';        // handler 6: the empty suit    public const TELEPORT = 'teleport';// handler 7    public const BEAM = 'beam';        // handler 8: energy barrier    public const CORE = 'core';        // handler 9: the base's heart    public const DECOR = 'decor';    public const SHOT = 'shot';    public const FX = 'fx';    public string $kind = self::DECOR;    public int $handler = -1;    public int $hits = 0;    /** what it bites off Tal's life when it touches him (type byte 1, bits 4-0: #5B9E); shots: hits taken */    public int $damage = 0;    /** score when shot down (type byte 2) */    public int $points = 0;    public bool $dead = false;    /** index in the world's item list (cells / modules) when it is one */    public int $itemIndex = -1;    public int $flash = 0;    /** shots: ticks left, owner damage */    public int $life = 0;    private ?array $program = null;    private int $step = 0;    private int $timer = 0;    private int $dirs = 0;    private int $dx = 0;    private int $dy = 0;    private array $anim;    private int $animId;    private int $frameIndex = 0;    private int $frameTimer = 0;    public int $frame = 0;    public function __construct(private World $world, public int $type, public int $x, public int $y, int $program = 0)    {        $t = $world->type($type);        $this->damage = $t[1] & 0x1F;        $this->points = $t[2];        if ($t[0] & 0x40) {            $this->kind = self::ENEMY;            $this->hits = max(1, $t[1] >> 5);        } elseif ($t[0] & 0x20) {            $this->handler = $t[0] & 0x1F;            $this->kind = match ($this->handler) {                0, 1, 2, 3, 4 => self::ITEM,                6 => self::SUIT,                7 => self::TELEPORT,                8 => self::BEAM,                9 => self::CORE,                default => self::HAZARD,            };        }        if ($program > 0) {            $this->program = $world->programs[$program] ?? null;        }        $this->setAnim($type);    }    public function setAnim(int $id): void    {        $this->animId = $id;        $this->anim = $this->world->anims[$id];        $this->frameIndex = 0;        $this->frameTimer = $this->anim['ticks'];        $this->frame = $this->anim['frames'][0] ?? 0;    }    /** Moves in a straight line (shots), instead of a program. */    public function setMotion(int $dirs, int $dx, int $dy): void    {        $this->program = null;        $this->dirs = $dirs;        $this->dx = $dx;        $this->dy = $dy;    }    public function width(): int    {        return intdiv($this->world->sprites[$this->frame & 0x7F]['w'], World::PIXELS_PER_BYTE);    }    public function height(): int    {        return $this->world->sprites[$this->frame & 0x7F]['h'];    }    /** [x, y, w, h] in view pixels. */    public function box(): array    {        $s = $this->world->sprites[$this->frame & 0x7F];        return [$this->x * World::PIXELS_PER_BYTE, $this->y - World::TOP, $s['w'], $s['h']];    }    public function tick(int $heroX, int $heroY): void    {        if ($this->flash > 0) {            $this->flash--;        }        $this->runProgram();        $this->move($heroX, $heroY);        $this->runAnim();        if ($this->kind === self::SHOT && --$this->life <= 0) {            $this->dead = true;        }    }    private function runProgram(): void    {        if ($this->program === null) {            return;        }        if ($this->timer > 0) {            $this->timer--;            return;        }        $steps = $this->program['steps'];        if ($this->step >= count($steps)) {            if ($this->program['end'] === 'loop' && $steps) {                $this->step = 0;            } else {                if ($this->program['end'] === 'kill') {                    $this->dirs = 0;                }                return;                         // 'hold': the last step goes on            }        }        [$this->dirs, $this->timer, $this->dx, $this->dy] = $steps[$this->step++];    }    private function move(int $heroX, int $heroY): void    {        $dirs = $this->dirs;        if ($dirs === 0x0F) {                   // #6CDE: towards Tal            $dirs = ($heroX > $this->x ? 8 : ($heroX < $this->x ? 4 : 0)) | ($heroY > $this->y ? 2 : ($heroY < $this->y ? 1 : 0));        }        $w = $this->width();        $h = $this->height();        if ($dirs & 1) {            $this->y -= $this->dy;        }        if ($dirs & 2) {            $this->y += $this->dy;        }        if ($dirs & 4) {            $this->x -= $this->dx;        }        if ($dirs & 8) {            $this->x += $this->dx;        }        if ($this->kind === self::SHOT) {            if ($this->x < 0 || $this->x + $w > 80 || $this->y >= World::BOTTOM) {                $this->dead = true;            }            return;        }        // #6C43...: the screen's bytes and the 0-255 lines bound them; what falls off the playfield is gone        $this->x = max(0, min(80 - $w, $this->x));        $this->y = max(0, min(255 - $h, $this->y));    }    /** On the playfield (objects above or under it are out of play). */    public function visible(): bool    {        return $this->y + $this->height() > World::TOP && $this->y < World::BOTTOM;    }    private function runAnim(): void    {        if (--$this->frameTimer > 0) {            return;        }        $this->frameTimer = $this->anim['ticks'];        $frames = $this->anim['frames'];        $this->frameIndex++;        if ($this->frameIndex >= count($frames)) {            switch ($this->anim['end']) {                case 'loop':                    $this->frameIndex = 0;                    break;                case 'next':                    $this->setAnim($this->anim['next']);                    return;                case 'kill':                    $this->dead = true;                    $this->frameIndex = count($frames) - 1;                    break;                default:                    $this->frameIndex = count($frames) - 1;            }        }        $this->frame = $frames[$this->frameIndex] ?? $this->frame;    }    public function animId(): int    {        return $this->animId;    }}