<?phpnamespace Tests;use App\Entity\Hitbox;use App\Entity\EnemyProfile;use App\Scene\PlayScene;use JumPHP\Testing\GameTestCase;final class CombatTest extends GameTestCase{ public function testMovementAndFire(): void { $this->game->start(PlayScene::class); $startX = $this->game->peek('x'); $this->game->hold('right', 30); self::assertGreaterThan($startX, $this->game->peek('x')); $this->game->hold('fire', 10); self::assertNotEmpty($this->game->peek('shots')); } public function testFourWingGunsFireAndBonusesChangeSpeedAndCadence(): void { $this->game->start(PlayScene::class); $this->game->hold('fire', 1); $friendly = array_values(array_filter($this->game->peek('shots'), static fn (array $shot): bool => $shot['friendly'])); self::assertCount(4, $friendly); self::assertSame([-22.0, -13.0, 13.0, 22.0], array_map(static fn (array $shot): float => $shot['x'] - 160, $friendly)); $this->game->start(PlayScene::class, ['testBonus' => 'speed']); $this->game->wait(0.02); self::assertGreaterThan(7, $this->game->peek('speedBoost')); self::assertSame([], $this->game->peek('bonuses')); $this->game->start(PlayScene::class, ['testBonus' => 'rapid']); $this->game->wait(0.02); self::assertGreaterThan(7, $this->game->peek('rapidBoost')); $this->game->hold('fire', 20); self::assertGreaterThan(4, count($this->game->peek('shots'))); } public function testBossCanBeDestroyedAndMissionRestarted(): void { $this->game->start(PlayScene::class, ['boss' => true, 'bossHp' => 4]); $this->game->hold('fire', 90); self::assertSame('victory', $this->game->peek('phase')); $this->game->press('confirm'); self::assertSame('wave', $this->game->peek('phase')); self::assertSame(3, $this->game->peek('lives')); } public function testHitboxesMeetOnlyWhenTheyOverlap(): void { self::assertTrue(Hitbox::overlap(0, 0, 10, 10, 9, 9, 2, 2)); self::assertFalse(Hitbox::overlap(0, 0, 10, 10, 10, 0, 2, 2)); } public function testPauseStopsTheMissionClock(): void { $this->game->start(PlayScene::class); $this->game->press('pause'); $time = $this->game->peek('time'); $this->game->wait(1); self::assertSame($time, $this->game->peek('time')); $this->game->press('pause'); $this->game->wait(0.2); self::assertGreaterThan($time, $this->game->peek('time')); } public function testBothDocumentedEnemyTypesSpawnInTheWave(): void { $this->game->start(PlayScene::class); $this->game->wait(1.35); $types = array_column($this->game->peek('enemies'), 'type'); self::assertContains('bf109', $types); self::assertContains('ju87', $types); } public function testEnemyProfilesHaveDistinctMotionHitboxesAndFrames(): void { $fighter = EnemyProfile::spawn('bf109', 160); $bomber = EnemyProfile::spawn('ju87', 160); EnemyProfile::step($fighter, 0.4); EnemyProfile::step($bomber, 0.4); self::assertGreaterThan($bomber['y'], $fighter['y']); self::assertSame(2, $fighter['hp']); self::assertSame(4, $bomber['hp']); self::assertGreaterThan(EnemyProfile::hitbox($fighter)[2], EnemyProfile::hitbox($bomber)[2]); self::assertSame([0, 512, 512, 512], EnemyProfile::frame(EnemyProfile::spawn('ju87', 160))); self::assertNotSame(EnemyProfile::frame($fighter), EnemyProfile::frame(EnemyProfile::spawn('bf109', 160))); } public function testFighterFiresAndBothEnemyHitboxesMatchTheirGameScale(): void { $fighter = EnemyProfile::spawn('bf109', 160); $bomber = EnemyProfile::spawn('ju87', 160); [$fx, $fy, $fw, $fh] = EnemyProfile::hitbox($fighter); [$sx, $sy, $sw, $sh] = EnemyProfile::hitbox($bomber); self::assertTrue(Hitbox::overlap($fx, $fy, $fw, $fh, 158, -42, 4, 4)); self::assertTrue(Hitbox::overlap($sx, $sy, $sw, $sh, 158, -42, 4, 4)); self::assertFalse(Hitbox::overlap($fx, $fy, $fw, $fh, 180, -42, 4, 4)); self::assertTrue(Hitbox::overlap($sx, $sy, $sw, $sh, 180, -42, 4, 4)); $this->game->start(PlayScene::class); $this->game->wait(1.8); self::assertContains(false, array_column($this->game->peek('shots'), 'friendly')); }}