tilesetName = 'doctor'; $this->frameDuration = 16 / 60; $this->animations = [ 'closed' => ['flags' => [], 'coords' => self::frames(self::CLOSED)], 'pressed' => ['flags' => [], 'coords' => self::frames(self::PRESSED)], 'broken' => ['flags' => [], 'coords' => self::frames(self::BROKEN)], ]; $this->setAnimation('closed'); } public function isOpened(): bool { return $this->opened; } public function isFinished(): bool { return $this->finished; } /** Top of the capsule, where Sonic can stand (the button). */ public function getTop(): float { return $this->y + ($this->isBroken() ? self::PROFILE[0] + 24 : 3); } /** Height of the rounded top under a centre X, null outside of it. */ public function getTopAt(float $centerX): ?float { $rel = (int)floor($centerX - $this->x); if ($rel < 0 || $rel > 31 || $this->isBroken()) return null; return $this->y + self::PROFILE[$rel]; } /** Sonic stands on the button (the middle of the top): drawn pushed down. */ public function isButtonAt(float $centerX): bool { $rel = (int)floor($centerX - $this->x); return $rel >= 0 && $rel <= 31 && self::PROFILE[$rel] === 3; } public function press(): void { if (!$this->isBroken()) $this->setAnimation('pressed'); } public function release(): void { $this->setAnimation('closed'); } private function isBroken(): bool { return $this->opened && $this->explosions >= 8; } public function open(): void { if ($this->opened) return; $this->opened = true; $this->frames = 0; } /** Returns events: ['explosion', x, y] / ['part', BossPart] / ['complete']. */ public function think(float $deltaTime): array { $events = []; $this->updateSprite($deltaTime); if (!$this->opened || $this->finished) return $events; $this->clock += $deltaTime; while ($this->clock >= self::FRAME) { $this->clock -= self::FRAME; $this->frames++; if ($this->explosions < 8) { if ($this->frames % 20 === 0) { $this->explosions++; $events[] = ['explosion', $this->x + mt_rand(0, 28), $this->y + mt_rand(0, 48)]; if ($this->explosions === 8) { $events[] = ['music', 'actcomplete']; $this->setAnimation('broken'); $this->frames = 0; } } continue; } if ($this->animals < 12 && $this->frames % 16 === 0) { $this->animals++; $bird = mt_rand(0, 1) === 1; $animal = (new BossPart($this->sdl, $this->x + 8 + 6, $this->y + 0x1A, $bird ? BossPart::BIRD : BossPart::RABBIT, 0.15)) ->withVelocity(mt_rand(-90, 90), -mt_rand(120, 240)); $animal->harmful = false; $animal->gravity = 600; $animal->hopper = !$bird; $animal->life = 4; if ($bird) { $animal->gravity = -120; } $events[] = ['part', $animal]; } if ($this->frames >= 0xA0 + 60) { $this->finished = true; $events[] = ['complete']; } } return $events; } }